mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-07 16:29:51 +03:00
Merge remote-tracking branch 'origin/v3.5.0' into dev
This commit is contained in:
2
.github/workflows/pre_release.yml
vendored
2
.github/workflows/pre_release.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio
|
||||
platformio upgrade
|
||||
platformio update
|
||||
pio pkg update
|
||||
|
||||
- name: Build WebUI
|
||||
run: |
|
||||
|
||||
2
.github/workflows/tagged_release.yml
vendored
2
.github/workflows/tagged_release.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio
|
||||
platformio upgrade
|
||||
platformio update
|
||||
pio pkg update
|
||||
|
||||
- name: Build WebUI
|
||||
run: |
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# Changelog
|
||||
|
||||
# [3.5.0]
|
||||
|
||||
## Added
|
||||
|
||||
- Translations in Web UI and all device entity names to German. [#22](https://github.com/emsesp/EMS-ESP32/issues/22)
|
||||
|
||||
## Fixed
|
||||
|
||||
## Changed
|
||||
|
||||
- Discovery in HomeAssistant don't work with custom base topic. [#596](https://github.com/emsesp/EMS-ESP32/issues/596)
|
||||
|
||||
## **BREAKING CHANGES:**
|
||||
|
||||
- MQTT Discovery (Home Assistant) entity names are now prefixed with the hostname, e.g. `select.thermostat_hc1_mode` becomes `select.emsesp_thermostat_hc1_mode`. You will need to recreate any custom dashboards.
|
||||
|
||||
# [3.4.2]
|
||||
|
||||
## Added
|
||||
|
||||
6
esp32_partition_16M.csv
Normal file
6
esp32_partition_16M.csv
Normal file
@@ -0,0 +1,6 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x5000,
|
||||
otadata, data, ota, , 0x2000,
|
||||
app0, app, ota_0, , 0x7F0000,
|
||||
app1, app, ota_1, , 0x7F0000,
|
||||
spiffs, data, spiffs, , 64K,
|
||||
|
6
esp32_partition_4M.csv
Normal file
6
esp32_partition_4M.csv
Normal file
@@ -0,0 +1,6 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x5000,
|
||||
otadata, data, ota, , 0x2000,
|
||||
app0, app, ota_0, , 0x1F0000,
|
||||
app1, app, ota_1, , 0x1F0000,
|
||||
spiffs, data, spiffs, , 64K,
|
||||
|
@@ -1,6 +0,0 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x5000,
|
||||
otadata, data, ota, 0xE000, 0x2000,
|
||||
app0, app, ota_0, 0x10000, 0x1F0000,
|
||||
app1, app, ota_1, 0x200000, 0x1F0000,
|
||||
spiffs, data, spiffs, 0x3F0000, 0x10000,
|
||||
|
5
interface/.typesafe-i18n.json
Normal file
5
interface/.typesafe-i18n.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"adapter": "react",
|
||||
"baseLocale": "en",
|
||||
"$schema": "https://unpkg.com/typesafe-i18n@5.13.0/schema/typesafe-i18n.json"
|
||||
}
|
||||
1036
interface/package-lock.json
generated
1036
interface/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "EMS-ESP",
|
||||
"version": "3.4.0",
|
||||
"version": "3.5.0",
|
||||
"private": true,
|
||||
"proxy": "http://localhost:3080",
|
||||
"dependencies": {
|
||||
@@ -30,6 +30,7 @@
|
||||
"react-router-dom": "^6.4.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"sockette": "^2.0.6",
|
||||
"typesafe-i18n": "^5.13.0",
|
||||
"typescript": "^4.8.3"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -41,8 +42,9 @@
|
||||
"build-hosted": "env-cmd -f .env.hosted npm run build",
|
||||
"build-localhost": "PUBLIC_URL=/ react-app-rewired build",
|
||||
"mock-api": "nodemon --watch ../mock-api ../mock-api/server.js",
|
||||
"standalone": "npm-run-all -p start mock-api",
|
||||
"lint": "eslint . --ext .ts,.tsx"
|
||||
"standalone": "npm-run-all -p start typesafe-i18n mock-api",
|
||||
"lint": "eslint . --ext .ts,.tsx",
|
||||
"typesafe-i18n": "typesafe-i18n"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
@@ -78,7 +80,7 @@
|
||||
"max-len": [
|
||||
1,
|
||||
{
|
||||
"code": 200
|
||||
"code": 220
|
||||
}
|
||||
],
|
||||
"arrow-parens": 1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FC, createRef, createContext, useContext, RefObject } from 'react';
|
||||
import { FC, createRef, createContext, useContext, useEffect, useState, RefObject } from 'react';
|
||||
import { SnackbarProvider } from 'notistack';
|
||||
|
||||
import { IconButton } from '@mui/material';
|
||||
@@ -9,6 +9,13 @@ import { FeaturesLoader } from './contexts/features';
|
||||
import CustomTheme from './CustomTheme';
|
||||
import AppRouting from './AppRouting';
|
||||
|
||||
import { localStorageDetector } from 'typesafe-i18n/detectors';
|
||||
import TypesafeI18n from './i18n/i18n-react';
|
||||
import { detectLocale } from './i18n/i18n-util';
|
||||
import { loadLocaleAsync } from './i18n/i18n-util.async';
|
||||
|
||||
const detectedLocale = detectLocale(localStorageDetector);
|
||||
|
||||
const App: FC = () => {
|
||||
const notistackRef: RefObject<any> = createRef();
|
||||
|
||||
@@ -20,24 +27,34 @@ const App: FC = () => {
|
||||
|
||||
const colorMode = useContext(ColorModeContext);
|
||||
|
||||
const [wasLoaded, setWasLoaded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
loadLocaleAsync(detectedLocale).then(() => setWasLoaded(true));
|
||||
}, []);
|
||||
|
||||
if (!wasLoaded) return null;
|
||||
|
||||
return (
|
||||
<ColorModeContext.Provider value={colorMode}>
|
||||
<CustomTheme>
|
||||
<SnackbarProvider
|
||||
maxSnack={3}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
|
||||
ref={notistackRef}
|
||||
action={(key) => (
|
||||
<IconButton onClick={onClickDismiss(key)} size="small">
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
>
|
||||
<FeaturesLoader>
|
||||
<AppRouting />
|
||||
</FeaturesLoader>
|
||||
</SnackbarProvider>
|
||||
</CustomTheme>
|
||||
<TypesafeI18n locale={detectedLocale}>
|
||||
<CustomTheme>
|
||||
<SnackbarProvider
|
||||
maxSnack={3}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
|
||||
ref={notistackRef}
|
||||
action={(key) => (
|
||||
<IconButton onClick={onClickDismiss(key)} size="small">
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
>
|
||||
<FeaturesLoader>
|
||||
<AppRouting />
|
||||
</FeaturesLoader>
|
||||
</SnackbarProvider>
|
||||
</CustomTheme>
|
||||
</TypesafeI18n>
|
||||
</ColorModeContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,8 @@ import { FC, useContext, useEffect } from 'react';
|
||||
import { Navigate, Routes, Route, useLocation } from 'react-router-dom';
|
||||
import { useSnackbar, VariantType } from 'notistack';
|
||||
|
||||
import { useI18nContext } from './i18n/i18n-react';
|
||||
|
||||
import { Authentication, AuthenticationContext } from './contexts/authentication';
|
||||
import { FeaturesContext } from './contexts/features';
|
||||
import { RequireAuthenticated, RequireUnauthenticated } from './components';
|
||||
@@ -41,13 +43,14 @@ export const RemoveTrailingSlashes = () => {
|
||||
|
||||
const AppRouting: FC = () => {
|
||||
const { features } = useContext(FeaturesContext);
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
return (
|
||||
<Authentication>
|
||||
<RemoveTrailingSlashes />
|
||||
<Routes>
|
||||
<Route path="/unauthorized" element={<RootRedirect message="Please sign in to continue" signOut />} />
|
||||
<Route path="/fileUpdated" element={<RootRedirect message="Upload successful" variant="success" />} />
|
||||
<Route path="/unauthorized" element={<RootRedirect message={LL.PLEASE_SIGNIN()} signOut />} />
|
||||
<Route path="/fileUpdated" element={<RootRedirect message={LL.UPLOAD_SUCCESSFUL()} variant="success" />} />
|
||||
{features.security && (
|
||||
<Route
|
||||
path="/"
|
||||
|
||||
@@ -2,7 +2,7 @@ import { FC, useContext, useState } from 'react';
|
||||
import { ValidateFieldsError } from 'async-validator';
|
||||
import { useSnackbar } from 'notistack';
|
||||
|
||||
import { Box, Fab, Paper, Typography } from '@mui/material';
|
||||
import { Box, Fab, Paper, Typography, Button } from '@mui/material';
|
||||
import ForwardIcon from '@mui/icons-material/Forward';
|
||||
|
||||
import * as AuthenticationApi from './api/authentication';
|
||||
@@ -16,6 +16,15 @@ import { SignInRequest } from './types';
|
||||
import { ValidatedTextField } from './components';
|
||||
import { SIGN_IN_REQUEST_VALIDATOR, validate } from './validators';
|
||||
|
||||
import { I18nContext } from './i18n/i18n-react';
|
||||
import type { Locales } from './i18n/i18n-types';
|
||||
import { loadLocaleAsync } from './i18n/i18n-util.async';
|
||||
|
||||
import { ReactComponent as NLflag } from './i18n/NL.svg';
|
||||
import { ReactComponent as DEflag } from './i18n/DE.svg';
|
||||
import { ReactComponent as GBflag } from './i18n/GB.svg';
|
||||
import { ReactComponent as SEflag } from './i18n/SE.svg';
|
||||
|
||||
const SignIn: FC = () => {
|
||||
const authenticationContext = useContext(AuthenticationContext);
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
@@ -31,6 +40,9 @@ const SignIn: FC = () => {
|
||||
|
||||
const validateAndSignIn = async () => {
|
||||
setProcessing(true);
|
||||
SIGN_IN_REQUEST_VALIDATOR.messages({
|
||||
required: '%s ' + LL.IS_REQUIRED()
|
||||
});
|
||||
try {
|
||||
await validate(SIGN_IN_REQUEST_VALIDATOR, signInRequest);
|
||||
signIn();
|
||||
@@ -47,10 +59,10 @@ const SignIn: FC = () => {
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof AxiosError) {
|
||||
if (error.response?.status === 401) {
|
||||
enqueueSnackbar('Invalid login details', { variant: 'warning' });
|
||||
enqueueSnackbar(LL.INVALID_LOGIN(), { variant: 'warning' });
|
||||
}
|
||||
} else {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Unexpected error, please try again'), { variant: 'error' });
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.ERROR()), { variant: 'error' });
|
||||
}
|
||||
setProcessing(false);
|
||||
}
|
||||
@@ -58,6 +70,14 @@ const SignIn: FC = () => {
|
||||
|
||||
const submitOnEnter = onEnterCallback(signIn);
|
||||
|
||||
const { LL, setLocale, locale } = useContext(I18nContext);
|
||||
|
||||
const selectLocale = async (loc: Locales) => {
|
||||
localStorage.setItem('lang', loc);
|
||||
await loadLocaleAsync(loc);
|
||||
setLocale(loc);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
display="flex"
|
||||
@@ -81,11 +101,37 @@ const SignIn: FC = () => {
|
||||
})}
|
||||
>
|
||||
<Typography variant="h4">{PROJECT_NAME}</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
'& button, & a, & .MuiCard-root': {
|
||||
mt: 0.5,
|
||||
mx: 0.5
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button size="small" variant={locale === 'en' ? 'contained' : 'outlined'} onClick={() => selectLocale('en')}>
|
||||
<GBflag style={{ width: 24 }} />
|
||||
EN
|
||||
</Button>
|
||||
<Button size="small" variant={locale === 'de' ? 'contained' : 'outlined'} onClick={() => selectLocale('de')}>
|
||||
<DEflag style={{ width: 24 }} />
|
||||
DE
|
||||
</Button>
|
||||
<Button size="small" variant={locale === 'nl' ? 'contained' : 'outlined'} onClick={() => selectLocale('nl')}>
|
||||
<NLflag style={{ width: 24 }} />
|
||||
NL
|
||||
</Button>
|
||||
<Button size="small" variant={locale === 'se' ? 'contained' : 'outlined'} onClick={() => selectLocale('se')}>
|
||||
<SEflag style={{ width: 24 }} />
|
||||
SE
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
disabled={processing}
|
||||
name="username"
|
||||
label="Username"
|
||||
label={LL.USERNAME()}
|
||||
value={signInRequest.username}
|
||||
onChange={updateLoginRequestValue}
|
||||
margin="normal"
|
||||
@@ -97,7 +143,7 @@ const SignIn: FC = () => {
|
||||
disabled={processing}
|
||||
type="password"
|
||||
name="password"
|
||||
label="Password"
|
||||
label={LL.PASSWORD()}
|
||||
value={signInRequest.password}
|
||||
onChange={updateLoginRequestValue}
|
||||
onKeyDown={submitOnEnter}
|
||||
@@ -107,7 +153,7 @@ const SignIn: FC = () => {
|
||||
/>
|
||||
<Fab variant="extended" color="primary" sx={{ mt: 2 }} onClick={validateAndSignIn} disabled={processing}>
|
||||
<ForwardIcon sx={{ mr: 1 }} />
|
||||
Sign In
|
||||
{LL.SIGN_IN()}
|
||||
</Fab>
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
@@ -1,12 +1,35 @@
|
||||
import { FC, useState, useContext } from 'react';
|
||||
import { FC, useState, useContext, ChangeEventHandler } from 'react';
|
||||
|
||||
import { Box, Button, Divider, IconButton, Popover, Typography, Avatar, styled, TypographyProps } from '@mui/material';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Divider,
|
||||
IconButton,
|
||||
Popover,
|
||||
Typography,
|
||||
Avatar,
|
||||
styled,
|
||||
TypographyProps,
|
||||
MenuItem,
|
||||
TextField
|
||||
} from '@mui/material';
|
||||
|
||||
import PersonIcon from '@mui/icons-material/Person';
|
||||
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
|
||||
|
||||
import { AuthenticatedContext } from '../../contexts/authentication';
|
||||
|
||||
import { I18nContext } from '../../i18n/i18n-react';
|
||||
import type { Locales } from '../../i18n/i18n-types';
|
||||
import { loadLocaleAsync } from '../../i18n/i18n-util.async';
|
||||
|
||||
import { ReactComponent as NLflag } from '../../i18n/NL.svg';
|
||||
import { ReactComponent as DEflag } from '../../i18n/DE.svg';
|
||||
import { ReactComponent as GBflag } from '../../i18n/GB.svg';
|
||||
import { ReactComponent as SEflag } from '../../i18n/SE.svg';
|
||||
import { ReactComponent as PLflag } from '../../i18n/PL.svg';
|
||||
import { ReactComponent as NOflag } from '../../i18n/NO.svg';
|
||||
|
||||
const ItemTypography = styled(Typography)<TypographyProps>({
|
||||
maxWidth: '250px',
|
||||
whiteSpace: 'nowrap',
|
||||
@@ -23,6 +46,15 @@ const LayoutAuthMenu: FC = () => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const { locale, LL, setLocale } = useContext(I18nContext);
|
||||
|
||||
const onLocaleSelected: ChangeEventHandler<HTMLInputElement> = async ({ target }) => {
|
||||
const loc = target.value as Locales;
|
||||
localStorage.setItem('lang', loc);
|
||||
await loadLocaleAsync(loc);
|
||||
setLocale(loc);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
@@ -32,7 +64,48 @@ const LayoutAuthMenu: FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<IconButton id="open-auth-menu" sx={{ padding: 0 }} aria-describedby={id} color="inherit" onClick={handleClick}>
|
||||
<TextField
|
||||
name="locale"
|
||||
InputProps={{ style: { fontSize: 10 } }}
|
||||
variant="outlined"
|
||||
value={locale}
|
||||
onChange={onLocaleSelected}
|
||||
size="small"
|
||||
select
|
||||
>
|
||||
<MenuItem key="en" value="en">
|
||||
<GBflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
EN
|
||||
</MenuItem>
|
||||
<MenuItem key="de" value="de">
|
||||
<DEflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
DE
|
||||
</MenuItem>
|
||||
<MenuItem key="nl" value="nl">
|
||||
<NLflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
NL
|
||||
</MenuItem>
|
||||
<MenuItem key="se" value="se">
|
||||
<SEflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
SE
|
||||
</MenuItem>
|
||||
<MenuItem key="pl" value="pl" disabled>
|
||||
<PLflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
PL
|
||||
</MenuItem>
|
||||
<MenuItem key="no" value="no" disabled>
|
||||
<NOflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||
NO
|
||||
</MenuItem>
|
||||
</TextField>
|
||||
|
||||
<IconButton
|
||||
id="open-auth-menu"
|
||||
sx={{ ml: 1, padding: 0 }}
|
||||
aria-describedby={id}
|
||||
color="inherit"
|
||||
onClick={handleClick}
|
||||
>
|
||||
<AccountCircleIcon />
|
||||
</IconButton>
|
||||
<Popover
|
||||
@@ -56,13 +129,15 @@ const LayoutAuthMenu: FC = () => {
|
||||
</Avatar>
|
||||
<Box pl={2}>
|
||||
<ItemTypography variant="h6">{me.username}</ItemTypography>
|
||||
<ItemTypography variant="body1">{me.admin ? 'Admin User' : 'Guest User'}</ItemTypography>
|
||||
<ItemTypography variant="body1">
|
||||
{me.admin ? LL.ADMIN() + ' ' + LL.USER() : LL.GUEST() + ' ' + LL.USER()}
|
||||
</ItemTypography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Divider />
|
||||
<Box p={1.5}>
|
||||
<Button variant="outlined" fullWidth color="primary" onClick={() => signOut(true)}>
|
||||
Sign Out
|
||||
{LL.SIGN_OUT()}
|
||||
</Button>
|
||||
</Box>
|
||||
</Popover>
|
||||
|
||||
@@ -15,9 +15,12 @@ import ProjectMenu from '../../project/ProjectMenu';
|
||||
import LayoutMenuItem from './LayoutMenuItem';
|
||||
import { AuthenticatedContext } from '../../contexts/authentication';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const LayoutMenu: FC = () => {
|
||||
const { features } = useContext(FeaturesContext);
|
||||
const authenticatedContext = useContext(AuthenticatedContext);
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -28,12 +31,17 @@ const LayoutMenu: FC = () => {
|
||||
</List>
|
||||
)}
|
||||
<List disablePadding component="nav">
|
||||
<LayoutMenuItem icon={SettingsEthernetIcon} label="Network Connection" to="/network" />
|
||||
<LayoutMenuItem icon={SettingsInputAntennaIcon} label="Access Point" to="/ap" />
|
||||
{features.ntp && <LayoutMenuItem icon={AccessTimeIcon} label="Network Time" to="/ntp" />}
|
||||
<LayoutMenuItem icon={SettingsEthernetIcon} label={LL.NETWORK()} to="/network" />
|
||||
<LayoutMenuItem icon={SettingsInputAntennaIcon} label={LL.ACCESS_POINT()} to="/ap" />
|
||||
{features.ntp && <LayoutMenuItem icon={AccessTimeIcon} label="NTP" to="/ntp" />}
|
||||
{features.mqtt && <LayoutMenuItem icon={DeviceHubIcon} label="MQTT" to="/mqtt" />}
|
||||
<LayoutMenuItem icon={LockIcon} label="Security" to="/security" disabled={!authenticatedContext.me.admin} />
|
||||
<LayoutMenuItem icon={SettingsIcon} label="System" to="/system" />
|
||||
<LayoutMenuItem
|
||||
icon={LockIcon}
|
||||
label={LL.SECURITY()}
|
||||
to="/security"
|
||||
disabled={!authenticatedContext.me.admin}
|
||||
/>
|
||||
<LayoutMenuItem icon={SettingsIcon} label={LL.SYSTEM()} to="/system" />
|
||||
</List>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -5,6 +5,8 @@ import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
|
||||
import { MessageBox } from '..';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
interface FormLoaderProps {
|
||||
message?: string;
|
||||
errorMessage?: string;
|
||||
@@ -12,12 +14,14 @@ interface FormLoaderProps {
|
||||
}
|
||||
|
||||
const FormLoader: FC<FormLoaderProps> = ({ errorMessage, onRetry, message = 'Loading…' }) => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
if (errorMessage) {
|
||||
return (
|
||||
<MessageBox my={2} level="error" message={errorMessage}>
|
||||
{onRetry && (
|
||||
<Button startIcon={<RefreshIcon />} variant="contained" color="error" onClick={onRetry}>
|
||||
Retry
|
||||
{LL.RETRY()}
|
||||
</Button>
|
||||
)}
|
||||
</MessageBox>
|
||||
|
||||
@@ -2,23 +2,29 @@ import { FC } from 'react';
|
||||
|
||||
import { CircularProgress, Box, Typography, Theme } from '@mui/material';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
interface LoadingSpinnerProps {
|
||||
height?: number | string;
|
||||
}
|
||||
|
||||
const LoadingSpinner: FC<LoadingSpinnerProps> = ({ height = '100%' }) => (
|
||||
<Box display="flex" alignItems="center" justifyContent="center" flexDirection="column" padding={2} height={height}>
|
||||
<CircularProgress
|
||||
sx={(theme: Theme) => ({
|
||||
margin: theme.spacing(4),
|
||||
color: theme.palette.text.secondary
|
||||
})}
|
||||
size={100}
|
||||
/>
|
||||
<Typography variant="h4" color="textSecondary">
|
||||
Loading…
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
const LoadingSpinner: FC<LoadingSpinnerProps> = ({ height = '100%' }) => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
return (
|
||||
<Box display="flex" alignItems="center" justifyContent="center" flexDirection="column" padding={2} height={height}>
|
||||
<CircularProgress
|
||||
sx={(theme: Theme) => ({
|
||||
margin: theme.spacing(4),
|
||||
color: theme.palette.text.secondary
|
||||
})}
|
||||
size={100}
|
||||
/>
|
||||
<Typography variant="h4" color="textSecondary">
|
||||
{LL.LOADING()}…
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoadingSpinner;
|
||||
|
||||
@@ -6,6 +6,8 @@ import { Box, Button, LinearProgress, Theme, Typography, useTheme } from '@mui/m
|
||||
import CloudUploadIcon from '@mui/icons-material/CloudUpload';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const progressPercentage = (progress: ProgressEvent) => Math.round((progress.loaded * 100) / progress.total);
|
||||
|
||||
const getBorderColor = (theme: Theme, props: DropzoneState) => {
|
||||
@@ -41,14 +43,16 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, prog
|
||||
const { getRootProps, getInputProps } = dropzoneState;
|
||||
const theme = useTheme();
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const progressText = () => {
|
||||
if (uploading) {
|
||||
if (progress?.lengthComputable) {
|
||||
return `Uploading: ${progressPercentage(progress)}%`;
|
||||
return LL.UPLOADING() + `: ${progressPercentage(progress)}%`;
|
||||
}
|
||||
return 'Uploading\u2026';
|
||||
return LL.UPLOADING() + `\u2026`;
|
||||
}
|
||||
return 'Drop file or click here';
|
||||
return LL.UPLOAD_DROP_TEXT();
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -81,7 +85,7 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, prog
|
||||
/>
|
||||
</Box>
|
||||
<Button startIcon={<CancelIcon />} variant="outlined" color="secondary" onClick={onCancel}>
|
||||
Cancel
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
</Fragment>
|
||||
)}
|
||||
|
||||
@@ -5,11 +5,15 @@ import { useSnackbar } from 'notistack';
|
||||
import { extractErrorMessage } from '../../utils';
|
||||
import { FileUploadConfig } from '../../api/endpoints';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
interface MediaUploadOptions {
|
||||
upload: (file: File, config?: FileUploadConfig) => AxiosPromise<void>;
|
||||
}
|
||||
|
||||
const useFileUpload = ({ upload }: MediaUploadOptions) => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const [uploading, setUploading] = useState<boolean>(false);
|
||||
const [uploadProgress, setUploadProgress] = useState<ProgressEvent>();
|
||||
@@ -42,13 +46,13 @@ const useFileUpload = ({ upload }: MediaUploadOptions) => {
|
||||
cancelToken: cancelToken.token
|
||||
});
|
||||
resetUploadingStates();
|
||||
enqueueSnackbar('File uploaded', { variant: 'success' });
|
||||
enqueueSnackbar(LL.UPLOAD() + ' ' + LL.SUCCESSFUL(), { variant: 'success' });
|
||||
} catch (error: unknown) {
|
||||
if (axios.isCancel(error)) {
|
||||
enqueueSnackbar('Upload aborted', { variant: 'warning' });
|
||||
enqueueSnackbar(LL.UPLOAD() + ' ' + LL.ABORTED(), { variant: 'warning' });
|
||||
} else {
|
||||
resetUploadingStates();
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Upload failed'), { variant: 'error' });
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.UPLOAD() + ' ' + LL.FAILED()), { variant: 'error' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,6 +2,8 @@ import { FC, useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
import * as AuthenticationApi from '../../api/authentication';
|
||||
import { ACCESS_TOKEN } from '../../api/endpoints';
|
||||
import { RequiredChildrenProps } from '../../utils';
|
||||
@@ -12,6 +14,8 @@ import { AuthenticationContext } from './context';
|
||||
|
||||
const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
|
||||
const { features } = useContext(FeaturesContext);
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const navigate = useNavigate();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
@@ -23,7 +27,7 @@ const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
|
||||
AuthenticationApi.getStorage().setItem(ACCESS_TOKEN, accessToken);
|
||||
const decodedMe = AuthenticationApi.decodeMeJWT(accessToken);
|
||||
setMe(decodedMe);
|
||||
enqueueSnackbar(`Logged in as ${decodedMe.username}`, { variant: 'success' });
|
||||
enqueueSnackbar(LL.LOGGED_IN({ name: decodedMe.username }), { variant: 'success' });
|
||||
} catch (error: unknown) {
|
||||
setMe(undefined);
|
||||
throw new Error('Failed to parse JWT');
|
||||
|
||||
@@ -19,6 +19,8 @@ import { APProvisionMode, APSettings } from '../../types';
|
||||
import { numberValue, updateValue, useRest } from '../../utils';
|
||||
import * as APApi from '../../api/ap';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
export const isAPEnabled = ({ provision_mode }: APSettings) => {
|
||||
return provision_mode === APProvisionMode.AP_MODE_ALWAYS || provision_mode === APProvisionMode.AP_MODE_DISCONNECTED;
|
||||
};
|
||||
@@ -29,6 +31,8 @@ const APSettingsForm: FC = () => {
|
||||
update: APApi.updateAPSettings
|
||||
});
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||
|
||||
const updateFormValue = updateValue(setData);
|
||||
@@ -53,7 +57,7 @@ const APSettingsForm: FC = () => {
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="provision_mode"
|
||||
label="Provide Access Point…"
|
||||
label={LL.AP_PROVIDE() + '...'}
|
||||
value={data.provision_mode}
|
||||
fullWidth
|
||||
select
|
||||
@@ -61,16 +65,16 @@ const APSettingsForm: FC = () => {
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
>
|
||||
<MenuItem value={APProvisionMode.AP_MODE_ALWAYS}>Always</MenuItem>
|
||||
<MenuItem value={APProvisionMode.AP_MODE_DISCONNECTED}>When WiFi Disconnected</MenuItem>
|
||||
<MenuItem value={APProvisionMode.AP_NEVER}>Never</MenuItem>
|
||||
<MenuItem value={APProvisionMode.AP_MODE_ALWAYS}>{LL.AP_PROVIDE_TEXT_1()}</MenuItem>
|
||||
<MenuItem value={APProvisionMode.AP_MODE_DISCONNECTED}>{LL.AP_PROVIDE_TEXT_2()}</MenuItem>
|
||||
<MenuItem value={APProvisionMode.AP_NEVER}>{LL.AP_PROVIDE_TEXT_3()}</MenuItem>
|
||||
</ValidatedTextField>
|
||||
{isAPEnabled(data) && (
|
||||
<>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="ssid"
|
||||
label="Access Point SSID"
|
||||
label={LL.ACCESS_POINT() + ' SSID'}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.ssid}
|
||||
@@ -80,7 +84,7 @@ const APSettingsForm: FC = () => {
|
||||
<ValidatedPasswordField
|
||||
fieldErrors={fieldErrors}
|
||||
name="password"
|
||||
label="Access Point Password"
|
||||
label={LL.ACCESS_POINT() + ' ' + LL.PASSWORD()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.password}
|
||||
@@ -90,7 +94,7 @@ const APSettingsForm: FC = () => {
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="channel"
|
||||
label="Preferred Channel"
|
||||
label={LL.AP_PREFERRED_CHANNEL()}
|
||||
value={numberValue(data.channel)}
|
||||
fullWidth
|
||||
select
|
||||
@@ -107,7 +111,7 @@ const APSettingsForm: FC = () => {
|
||||
</ValidatedTextField>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="ssid_hidden" checked={data.ssid_hidden} onChange={updateFormValue} />}
|
||||
label="Hide SSID"
|
||||
label={LL.AP_HIDE_SSID()}
|
||||
/>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
@@ -168,7 +172,7 @@ const APSettingsForm: FC = () => {
|
||||
type="submit"
|
||||
onClick={validateAndSubmit}
|
||||
>
|
||||
Save
|
||||
{LL.SAVE()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</>
|
||||
@@ -176,7 +180,7 @@ const APSettingsForm: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="Access Point Settings" titleGutter>
|
||||
<SectionContent title={LL.ACCESS_POINT() + ' ' + LL.SETTINGS()} titleGutter>
|
||||
{content()}
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
@@ -11,6 +11,8 @@ import { APNetworkStatus, APStatus } from '../../types';
|
||||
import { ButtonRow, FormLoader, SectionContent } from '../../components';
|
||||
import { useRest } from '../../utils';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
export const apStatusHighlight = ({ status }: APStatus, theme: Theme) => {
|
||||
switch (status) {
|
||||
case APNetworkStatus.ACTIVE:
|
||||
@@ -24,24 +26,26 @@ export const apStatusHighlight = ({ status }: APStatus, theme: Theme) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const apStatus = ({ status }: APStatus) => {
|
||||
switch (status) {
|
||||
case APNetworkStatus.ACTIVE:
|
||||
return 'Active';
|
||||
case APNetworkStatus.INACTIVE:
|
||||
return 'Inactive';
|
||||
case APNetworkStatus.LINGERING:
|
||||
return 'Lingering until idle';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
};
|
||||
|
||||
const APStatusForm: FC = () => {
|
||||
const { loadData, data, errorMessage } = useRest<APStatus>({ read: APApi.readAPStatus });
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const apStatus = ({ status }: APStatus) => {
|
||||
switch (status) {
|
||||
case APNetworkStatus.ACTIVE:
|
||||
return LL.ACTIVE();
|
||||
case APNetworkStatus.INACTIVE:
|
||||
return LL.INACTIVE();
|
||||
case APNetworkStatus.LINGERING:
|
||||
return 'Lingering until idle';
|
||||
default:
|
||||
return LL.UNKNOWN();
|
||||
}
|
||||
};
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
||||
@@ -56,7 +60,7 @@ const APStatusForm: FC = () => {
|
||||
<SettingsInputAntennaIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Status" secondary={apStatus(data)} />
|
||||
<ListItemText primary={LL.STATUS()} secondary={apStatus(data)} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
@@ -87,7 +91,7 @@ const APStatusForm: FC = () => {
|
||||
</List>
|
||||
<ButtonRow>
|
||||
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
|
||||
Refresh
|
||||
{LL.REFRESH()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</>
|
||||
@@ -95,7 +99,7 @@ const APStatusForm: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="Access Point Status" titleGutter>
|
||||
<SectionContent title={LL.ACCESS_POINT() + ' ' + LL.STATUS()} titleGutter>
|
||||
{content()}
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
@@ -8,8 +8,12 @@ import APStatusForm from './APStatusForm';
|
||||
import APSettingsForm from './APSettingsForm';
|
||||
import { RequireAdmin, RouterTabs, useLayoutTitle, useRouterTab } from '../../components';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const AccessPoint: FC = () => {
|
||||
useLayoutTitle('Access Point');
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
useLayoutTitle(LL.ACCESS_POINT());
|
||||
|
||||
const authenticatedContext = useContext(AuthenticatedContext);
|
||||
|
||||
@@ -18,8 +22,12 @@ const AccessPoint: FC = () => {
|
||||
return (
|
||||
<>
|
||||
<RouterTabs value={routerTab}>
|
||||
<Tab value="status" label="Access Point Status" />
|
||||
<Tab value="settings" label="Access Point Settings" disabled={!authenticatedContext.me.admin} />
|
||||
<Tab value="status" label={LL.ACCESS_POINT() + ' ' + LL.STATUS()} />
|
||||
<Tab
|
||||
value="settings"
|
||||
label={LL.ACCESS_POINT() + ' ' + LL.SETTINGS()}
|
||||
disabled={!authenticatedContext.me.admin}
|
||||
/>
|
||||
</RouterTabs>
|
||||
<Routes>
|
||||
<Route path="status" element={<APStatusForm />} />
|
||||
|
||||
@@ -9,7 +9,11 @@ import { AuthenticatedContext } from '../../contexts/authentication';
|
||||
import MqttStatusForm from './MqttStatusForm';
|
||||
import MqttSettingsForm from './MqttSettingsForm';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const Mqtt: FC = () => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
useLayoutTitle('MQTT');
|
||||
|
||||
const authenticatedContext = useContext(AuthenticatedContext);
|
||||
@@ -18,8 +22,8 @@ const Mqtt: FC = () => {
|
||||
return (
|
||||
<>
|
||||
<RouterTabs value={routerTab}>
|
||||
<Tab value="status" label="MQTT Status" />
|
||||
<Tab value="settings" label="MQTT Settings" disabled={!authenticatedContext.me.admin} />
|
||||
<Tab value="status" label={'MQTT ' + LL.STATUS()} />
|
||||
<Tab value="settings" label={'MQTT ' + LL.SETTINGS()} disabled={!authenticatedContext.me.admin} />
|
||||
</RouterTabs>
|
||||
<Routes>
|
||||
<Route path="status" element={<MqttStatusForm />} />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { FC, useState } from 'react';
|
||||
import { ValidateFieldsError } from 'async-validator';
|
||||
|
||||
import { Button, Checkbox, MenuItem, Grid, Typography } from '@mui/material';
|
||||
import { Button, Checkbox, MenuItem, Grid, Typography, InputAdornment } from '@mui/material';
|
||||
import SaveIcon from '@mui/icons-material/Save';
|
||||
|
||||
import { MQTT_SETTINGS_VALIDATOR, validate } from '../../validators';
|
||||
@@ -17,12 +17,16 @@ import { MqttSettings } from '../../types';
|
||||
import { numberValue, updateValue, useRest } from '../../utils';
|
||||
import * as MqttApi from '../../api/mqtt';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const MqttSettingsForm: FC = () => {
|
||||
const { loadData, saving, data, setData, saveData, errorMessage } = useRest<MqttSettings>({
|
||||
read: MqttApi.readMqttSettings,
|
||||
update: MqttApi.updateMqttSettings
|
||||
});
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||
|
||||
const updateFormValue = updateValue(setData);
|
||||
@@ -46,7 +50,7 @@ const MqttSettingsForm: FC = () => {
|
||||
<>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="enabled" checked={data.enabled} onChange={updateFormValue} />}
|
||||
label="Enable MQTT"
|
||||
label={LL.ENABLE_MQTT()}
|
||||
/>
|
||||
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
|
||||
<Grid item xs={6}>
|
||||
@@ -91,7 +95,7 @@ const MqttSettingsForm: FC = () => {
|
||||
<Grid item xs={6}>
|
||||
<ValidatedTextField
|
||||
name="client_id"
|
||||
label="Client ID (optional)"
|
||||
label={'Client ID (' + LL.OPTIONAL() + ')'}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.client_id}
|
||||
@@ -104,7 +108,7 @@ const MqttSettingsForm: FC = () => {
|
||||
<Grid item xs={6}>
|
||||
<ValidatedTextField
|
||||
name="username"
|
||||
label="Username"
|
||||
label={LL.USERNAME()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.username}
|
||||
@@ -115,7 +119,7 @@ const MqttSettingsForm: FC = () => {
|
||||
<Grid item xs={6}>
|
||||
<ValidatedPasswordField
|
||||
name="password"
|
||||
label="Password"
|
||||
label={LL.PASSWORD()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.password}
|
||||
@@ -129,7 +133,10 @@ const MqttSettingsForm: FC = () => {
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="keep_alive"
|
||||
label="Keep Alive (seconds)"
|
||||
label="Keep Alive"
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
|
||||
}}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={numberValue(data.keep_alive)}
|
||||
@@ -149,7 +156,7 @@ const MqttSettingsForm: FC = () => {
|
||||
margin="normal"
|
||||
select
|
||||
>
|
||||
<MenuItem value={0}>0 (default)</MenuItem>
|
||||
<MenuItem value={0}>0</MenuItem>
|
||||
<MenuItem value={1}>1</MenuItem>
|
||||
<MenuItem value={2}>2</MenuItem>
|
||||
</ValidatedTextField>
|
||||
@@ -157,18 +164,18 @@ const MqttSettingsForm: FC = () => {
|
||||
</Grid>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="clean_session" checked={data.clean_session} onChange={updateFormValue} />}
|
||||
label="Set Clean Session"
|
||||
label={LL.MQTT_CLEAN_SESSION()}
|
||||
/>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="mqtt_retain" checked={data.mqtt_retain} onChange={updateFormValue} />}
|
||||
label="Always use Retain Flag"
|
||||
label={LL.MQTT_RETAIN_FLAG()}
|
||||
/>
|
||||
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
||||
Formatting
|
||||
{LL.FORMATTING()}
|
||||
</Typography>
|
||||
<ValidatedTextField
|
||||
name="nested_format"
|
||||
label="Topic/Payload Format"
|
||||
label={'Topic/Payload ' + LL.FORMAT()}
|
||||
value={data.nested_format}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
@@ -176,19 +183,19 @@ const MqttSettingsForm: FC = () => {
|
||||
margin="normal"
|
||||
select
|
||||
>
|
||||
<MenuItem value={1}>Nested in a single topic</MenuItem>
|
||||
<MenuItem value={2}>As individual topics</MenuItem>
|
||||
<MenuItem value={1}>{LL.MQTT_NEST_1()}</MenuItem>
|
||||
<MenuItem value={2}>{LL.MQTT_NEST_2()}</MenuItem>
|
||||
</ValidatedTextField>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="send_response" checked={data.send_response} onChange={updateFormValue} />}
|
||||
label="Publish command output to a 'response' topic"
|
||||
label={LL.MQTT_RESPONSE()}
|
||||
/>
|
||||
{!data.ha_enabled && (
|
||||
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
|
||||
<Grid item>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="publish_single" checked={data.publish_single} onChange={updateFormValue} />}
|
||||
label="Publish single value topics on change"
|
||||
label={LL.MQTT_PUBLISH_TEXT_1()}
|
||||
/>
|
||||
</Grid>
|
||||
{data.publish_single && (
|
||||
@@ -197,7 +204,7 @@ const MqttSettingsForm: FC = () => {
|
||||
control={
|
||||
<Checkbox name="publish_single2cmd" checked={data.publish_single2cmd} onChange={updateFormValue} />
|
||||
}
|
||||
label="Publish to command topics (ioBroker)"
|
||||
label={LL.MQTT_PUBLISH_TEXT_2()}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
@@ -208,14 +215,14 @@ const MqttSettingsForm: FC = () => {
|
||||
<Grid item>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="ha_enabled" checked={data.ha_enabled} onChange={updateFormValue} />}
|
||||
label="Enable MQTT Discovery (Home Assistant, Domoticz)"
|
||||
label={LL.MQTT_PUBLISH_TEXT_3()}
|
||||
/>
|
||||
</Grid>
|
||||
{data.ha_enabled && (
|
||||
<Grid item xs={6}>
|
||||
<ValidatedTextField
|
||||
name="discovery_prefix"
|
||||
label="Prefix for the Discovery topics"
|
||||
label={LL.MQTT_PUBLISH_TEXT_4()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.discovery_prefix}
|
||||
@@ -227,14 +234,17 @@ const MqttSettingsForm: FC = () => {
|
||||
</Grid>
|
||||
)}
|
||||
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
||||
Publish Intervals (in seconds, 0=automatic)
|
||||
{LL.MQTT_PUBLISH_INTERVALS()} (0=auto)
|
||||
</Typography>
|
||||
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="publish_time_boiler"
|
||||
label="Boilers and Heat Pumps"
|
||||
label={LL.MQTT_INT_BOILER()}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
|
||||
}}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={numberValue(data.publish_time_boiler)}
|
||||
@@ -247,7 +257,10 @@ const MqttSettingsForm: FC = () => {
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="publish_time_thermostat"
|
||||
label="Thermostats"
|
||||
label={LL.MQTT_INT_THERMOSTATS()}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
|
||||
}}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={numberValue(data.publish_time_thermostat)}
|
||||
@@ -260,7 +273,10 @@ const MqttSettingsForm: FC = () => {
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="publish_time_solar"
|
||||
label="Solar Modules"
|
||||
label={LL.MQTT_INT_SOLAR()}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
|
||||
}}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={numberValue(data.publish_time_solar)}
|
||||
@@ -273,7 +289,10 @@ const MqttSettingsForm: FC = () => {
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="publish_time_mixer"
|
||||
label="Mixer Modules"
|
||||
label={LL.MQTT_INT_MIXER()}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
|
||||
}}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={numberValue(data.publish_time_mixer)}
|
||||
@@ -286,7 +305,10 @@ const MqttSettingsForm: FC = () => {
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="publish_time_sensor"
|
||||
label="Temperature Sensors"
|
||||
label={LL.TEMP_SENSORS()}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
|
||||
}}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={numberValue(data.publish_time_sensor)}
|
||||
@@ -299,7 +321,10 @@ const MqttSettingsForm: FC = () => {
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="publish_time_other"
|
||||
label="Default"
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
|
||||
}}
|
||||
label={LL.DEFAULT()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={numberValue(data.publish_time_other)}
|
||||
@@ -318,7 +343,7 @@ const MqttSettingsForm: FC = () => {
|
||||
type="submit"
|
||||
onClick={validateAndSubmit}
|
||||
>
|
||||
Save
|
||||
{LL.SAVE()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</>
|
||||
@@ -326,7 +351,7 @@ const MqttSettingsForm: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="MQTT Settings" titleGutter>
|
||||
<SectionContent title={'MQTT ' + LL.SETTINGS()} titleGutter>
|
||||
{content()}
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
@@ -11,6 +11,8 @@ import { MqttStatus, MqttDisconnectReason } from '../../types';
|
||||
import * as MqttApi from '../../api/mqtt';
|
||||
import { useRest } from '../../utils';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
export const mqttStatusHighlight = ({ enabled, connected }: MqttStatus, theme: Theme) => {
|
||||
if (!enabled) {
|
||||
return theme.palette.info.main;
|
||||
@@ -29,44 +31,46 @@ export const mqttPublishHighlight = ({ mqtt_fails }: MqttStatus, theme: Theme) =
|
||||
return theme.palette.error.main;
|
||||
};
|
||||
|
||||
export const mqttStatus = ({ enabled, connected }: MqttStatus) => {
|
||||
if (!enabled) {
|
||||
return 'Not enabled';
|
||||
}
|
||||
if (connected) {
|
||||
return 'Connected';
|
||||
}
|
||||
return 'Disconnected';
|
||||
};
|
||||
|
||||
export const disconnectReason = ({ disconnect_reason }: MqttStatus) => {
|
||||
switch (disconnect_reason) {
|
||||
case MqttDisconnectReason.TCP_DISCONNECTED:
|
||||
return 'TCP disconnected';
|
||||
case MqttDisconnectReason.MQTT_UNACCEPTABLE_PROTOCOL_VERSION:
|
||||
return 'Unacceptable protocol version';
|
||||
case MqttDisconnectReason.MQTT_IDENTIFIER_REJECTED:
|
||||
return 'Client ID rejected';
|
||||
case MqttDisconnectReason.MQTT_SERVER_UNAVAILABLE:
|
||||
return 'Server unavailable';
|
||||
case MqttDisconnectReason.MQTT_MALFORMED_CREDENTIALS:
|
||||
return 'Malformed credentials';
|
||||
case MqttDisconnectReason.MQTT_NOT_AUTHORIZED:
|
||||
return 'Not authorized';
|
||||
case MqttDisconnectReason.ESP8266_NOT_ENOUGH_SPACE:
|
||||
return 'Device out of memory';
|
||||
case MqttDisconnectReason.TLS_BAD_FINGERPRINT:
|
||||
return 'Server fingerprint invalid';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
};
|
||||
|
||||
const MqttStatusForm: FC = () => {
|
||||
const { loadData, data, errorMessage } = useRest<MqttStatus>({ read: MqttApi.readMqttStatus });
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const mqttStatus = ({ enabled, connected }: MqttStatus) => {
|
||||
if (!enabled) {
|
||||
return LL.NOT_ENABLED();
|
||||
}
|
||||
if (connected) {
|
||||
return LL.CONNECTED();
|
||||
}
|
||||
return LL.DISCONNECTED();
|
||||
};
|
||||
|
||||
const disconnectReason = ({ disconnect_reason }: MqttStatus) => {
|
||||
switch (disconnect_reason) {
|
||||
case MqttDisconnectReason.TCP_DISCONNECTED:
|
||||
return 'TCP disconnected';
|
||||
case MqttDisconnectReason.MQTT_UNACCEPTABLE_PROTOCOL_VERSION:
|
||||
return 'Unacceptable protocol version';
|
||||
case MqttDisconnectReason.MQTT_IDENTIFIER_REJECTED:
|
||||
return 'Client ID rejected';
|
||||
case MqttDisconnectReason.MQTT_SERVER_UNAVAILABLE:
|
||||
return 'Server unavailable';
|
||||
case MqttDisconnectReason.MQTT_MALFORMED_CREDENTIALS:
|
||||
return 'Malformed credentials';
|
||||
case MqttDisconnectReason.MQTT_NOT_AUTHORIZED:
|
||||
return 'Not authorized';
|
||||
case MqttDisconnectReason.ESP8266_NOT_ENOUGH_SPACE:
|
||||
return 'Device out of memory';
|
||||
case MqttDisconnectReason.TLS_BAD_FINGERPRINT:
|
||||
return 'Server fingerprint invalid';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
};
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
||||
@@ -89,7 +93,7 @@ const MqttStatusForm: FC = () => {
|
||||
<SpeakerNotesOffIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="MQTT Publish Errors" secondary={data.mqtt_fails} />
|
||||
<ListItemText primary={'MQTT Publish ' + LL.ERRORS()} secondary={data.mqtt_fails} />
|
||||
</ListItem>
|
||||
</>
|
||||
);
|
||||
@@ -102,7 +106,7 @@ const MqttStatusForm: FC = () => {
|
||||
<ReportIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Disconnect Reason" secondary={disconnectReason(data)} />
|
||||
<ListItemText primary={LL.DISCONNECT_REASON()} secondary={disconnectReason(data)} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
</>
|
||||
@@ -118,14 +122,14 @@ const MqttStatusForm: FC = () => {
|
||||
<DeviceHubIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Status" secondary={mqttStatus(data)} />
|
||||
<ListItemText primary={LL.STATUS()} secondary={mqttStatus(data)} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
{data.enabled && renderConnectionStatus()}
|
||||
</List>
|
||||
<ButtonRow>
|
||||
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
|
||||
Refresh
|
||||
{LL.REFRESH()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</>
|
||||
@@ -133,7 +137,7 @@ const MqttStatusForm: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="MQTT Status" titleGutter>
|
||||
<SectionContent title={'MQTT ' + LL.STATUS()} titleGutter>
|
||||
{content()}
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
@@ -11,12 +11,16 @@ import NetworkStatusForm from './NetworkStatusForm';
|
||||
import WiFiNetworkScanner from './WiFiNetworkScanner';
|
||||
import NetworkSettingsForm from './NetworkSettingsForm';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const NetworkConnection: FC = () => {
|
||||
useLayoutTitle('Network Connection');
|
||||
const { LL } = useI18nContext();
|
||||
useLayoutTitle(LL.NETWORK());
|
||||
|
||||
const { routerTab } = useRouterTab();
|
||||
|
||||
const authenticatedContext = useContext(AuthenticatedContext);
|
||||
const navigate = useNavigate();
|
||||
const { routerTab } = useRouterTab();
|
||||
|
||||
const [selectedNetwork, setSelectedNetwork] = useState<WiFiNetwork>();
|
||||
|
||||
@@ -41,9 +45,9 @@ const NetworkConnection: FC = () => {
|
||||
}}
|
||||
>
|
||||
<RouterTabs value={routerTab}>
|
||||
<Tab value="status" label="Network Status" />
|
||||
<Tab value="scan" label="Scan WiFi Networks" disabled={!authenticatedContext.me.admin} />
|
||||
<Tab value="settings" label="Network Settings" disabled={!authenticatedContext.me.admin} />
|
||||
<Tab value="status" label={LL.NETWORK() + ' ' + LL.STATUS()} />
|
||||
<Tab value="scan" label={LL.NETWORK_SCAN()} disabled={!authenticatedContext.me.admin} />
|
||||
<Tab value="settings" label={LL.NETWORK() + ' ' + LL.SETTINGS()} disabled={!authenticatedContext.me.admin} />
|
||||
</RouterTabs>
|
||||
<Routes>
|
||||
<Route path="status" element={<NetworkStatusForm />} />
|
||||
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
ListItemAvatar,
|
||||
ListItemSecondaryAction,
|
||||
ListItemText,
|
||||
Typography
|
||||
Typography,
|
||||
InputAdornment
|
||||
} from '@mui/material';
|
||||
|
||||
import LockOpenIcon from '@mui/icons-material/LockOpen';
|
||||
@@ -36,7 +37,11 @@ import { ValidateFieldsError } from 'async-validator';
|
||||
import { validate } from '../../validators';
|
||||
import { createNetworkSettingsValidator } from '../../validators/network';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const WiFiSettingsForm: FC = () => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const { selectedNetwork, deselectNetwork } = useContext(WiFiConnectionContext);
|
||||
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
@@ -111,7 +116,7 @@ const WiFiSettingsForm: FC = () => {
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="ssid"
|
||||
label="SSID (leave blank to disable WiFi)"
|
||||
label={'SSID (' + LL.NETWORK_BLANK_SSID() + ')'}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.ssid}
|
||||
@@ -123,7 +128,7 @@ const WiFiSettingsForm: FC = () => {
|
||||
<ValidatedPasswordField
|
||||
fieldErrors={fieldErrors}
|
||||
name="password"
|
||||
label="Password"
|
||||
label={LL.PASSWORD()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.password}
|
||||
@@ -135,7 +140,10 @@ const WiFiSettingsForm: FC = () => {
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="tx_power"
|
||||
label="WiFi Tx Power (dBm)"
|
||||
label={'WiFi Tx ' + LL.POWER()}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">dBm</InputAdornment>
|
||||
}}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={numberValue(data.tx_power)}
|
||||
@@ -146,21 +154,21 @@ const WiFiSettingsForm: FC = () => {
|
||||
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="nosleep" checked={data.nosleep} onChange={updateFormValue} />}
|
||||
label="Disable WiFi Sleep Mode"
|
||||
label={LL.NETWORK_DISABLE_SLEEP()}
|
||||
/>
|
||||
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="bandwidth20" checked={data.bandwidth20} onChange={updateFormValue} />}
|
||||
label="Use Lower WiFi Bandwidth"
|
||||
label={LL.NETWORK_LOW_BAND()}
|
||||
/>
|
||||
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="enableMDNS" checked={data.enableMDNS} onChange={updateFormValue} />}
|
||||
label="Enable mDNS Service"
|
||||
label={LL.NETWORK_USE_DNS()}
|
||||
/>
|
||||
|
||||
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
||||
General
|
||||
{LL.GENERAL_OPTIONS()}
|
||||
</Typography>
|
||||
|
||||
<ValidatedTextField
|
||||
@@ -176,12 +184,12 @@ const WiFiSettingsForm: FC = () => {
|
||||
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="enableIPv6" checked={data.enableIPv6} onChange={updateFormValue} />}
|
||||
label="Enable IPv6 support"
|
||||
label={LL.NETWORK_ENABLE_IPV6()}
|
||||
/>
|
||||
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="static_ip_config" checked={data.static_ip_config} onChange={updateFormValue} />}
|
||||
label="Use Fixed IP address"
|
||||
label={LL.NETWORK_FIXED_IP()}
|
||||
/>
|
||||
{data.static_ip_config && (
|
||||
<>
|
||||
@@ -246,7 +254,7 @@ const WiFiSettingsForm: FC = () => {
|
||||
type="submit"
|
||||
onClick={validateAndSubmit}
|
||||
>
|
||||
Save
|
||||
{LL.SAVE()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</>
|
||||
@@ -254,7 +262,7 @@ const WiFiSettingsForm: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="Network Settings" titleGutter>
|
||||
<SectionContent title={LL.NETWORK() + ' ' + LL.SETTINGS()} titleGutter>
|
||||
{content()}
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
@@ -14,6 +14,8 @@ import { NetworkConnectionStatus, NetworkStatus } from '../../types';
|
||||
import * as NetworkApi from '../../api/network';
|
||||
import { useRest } from '../../utils';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const isConnected = ({ status }: NetworkStatus) =>
|
||||
status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED ||
|
||||
status === NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED;
|
||||
@@ -35,29 +37,6 @@ const networkStatusHighlight = ({ status }: NetworkStatus, theme: Theme) => {
|
||||
}
|
||||
};
|
||||
|
||||
const networkStatus = ({ status }: NetworkStatus) => {
|
||||
switch (status) {
|
||||
case NetworkConnectionStatus.WIFI_STATUS_NO_SHIELD:
|
||||
return 'Inactive';
|
||||
case NetworkConnectionStatus.WIFI_STATUS_IDLE:
|
||||
return 'Idle';
|
||||
case NetworkConnectionStatus.WIFI_STATUS_NO_SSID_AVAIL:
|
||||
return 'No SSID Available';
|
||||
case NetworkConnectionStatus.WIFI_STATUS_CONNECTED:
|
||||
return 'Connected (WiFi)';
|
||||
case NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED:
|
||||
return 'Connected (Ethernet)';
|
||||
case NetworkConnectionStatus.WIFI_STATUS_CONNECT_FAILED:
|
||||
return 'Connection Failed';
|
||||
case NetworkConnectionStatus.WIFI_STATUS_CONNECTION_LOST:
|
||||
return 'Connection Lost';
|
||||
case NetworkConnectionStatus.WIFI_STATUS_DISCONNECTED:
|
||||
return 'Disconnected';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
};
|
||||
|
||||
export const isWiFi = ({ status }: NetworkStatus) => status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED;
|
||||
export const isEthernet = ({ status }: NetworkStatus) => status === NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED;
|
||||
|
||||
@@ -81,8 +60,33 @@ const IPs = (status: NetworkStatus) => {
|
||||
const NetworkStatusForm: FC = () => {
|
||||
const { loadData, data, errorMessage } = useRest<NetworkStatus>({ read: NetworkApi.readNetworkStatus });
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const networkStatus = ({ status }: NetworkStatus) => {
|
||||
switch (status) {
|
||||
case NetworkConnectionStatus.WIFI_STATUS_NO_SHIELD:
|
||||
return LL.INACTIVE();
|
||||
case NetworkConnectionStatus.WIFI_STATUS_IDLE:
|
||||
return LL.IDLE();
|
||||
case NetworkConnectionStatus.WIFI_STATUS_NO_SSID_AVAIL:
|
||||
return 'No SSID Available';
|
||||
case NetworkConnectionStatus.WIFI_STATUS_CONNECTED:
|
||||
return LL.CONNECTED() + ' (WiFi)';
|
||||
case NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED:
|
||||
return LL.CONNECTED() + ' (Ethernet)';
|
||||
case NetworkConnectionStatus.WIFI_STATUS_CONNECT_FAILED:
|
||||
return LL.CONNECTED() + ' ' + LL.FAILED();
|
||||
case NetworkConnectionStatus.WIFI_STATUS_CONNECTION_LOST:
|
||||
return LL.CONNECTED() + ' ' + LL.LOST();
|
||||
case NetworkConnectionStatus.WIFI_STATUS_DISCONNECTED:
|
||||
return LL.DISCONNECTED();
|
||||
default:
|
||||
return LL.UNKNOWN();
|
||||
}
|
||||
};
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
||||
@@ -162,7 +166,7 @@ const NetworkStatusForm: FC = () => {
|
||||
</List>
|
||||
<ButtonRow>
|
||||
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
|
||||
Refresh
|
||||
{LL.REFRESH()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</>
|
||||
@@ -170,7 +174,7 @@ const NetworkStatusForm: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="Network Status" titleGutter>
|
||||
<SectionContent title={LL.NETWORK() + ' ' + LL.STATUS()} titleGutter>
|
||||
{content()}
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
@@ -12,6 +12,8 @@ import { ButtonRow, FormLoader, SectionContent } from '../../components';
|
||||
|
||||
import WiFiNetworkSelector from './WiFiNetworkSelector';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const NUM_POLLS = 10;
|
||||
const POLLING_FREQUENCY = 500;
|
||||
|
||||
@@ -22,6 +24,8 @@ const compareNetworks = (network1: WiFiNetwork, network2: WiFiNetwork) => {
|
||||
};
|
||||
|
||||
const WiFiNetworkScanner: FC = () => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const pollCount = useRef(0);
|
||||
@@ -46,7 +50,7 @@ const WiFiNetworkScanner: FC = () => {
|
||||
pollCount.current = completedPollCount;
|
||||
setTimeout(pollNetworkList, POLLING_FREQUENCY);
|
||||
} else {
|
||||
finishedWithError('Device did not return network list in timely manner');
|
||||
finishedWithError(LL.PROBLEM_LOADING());
|
||||
}
|
||||
} else {
|
||||
const newNetworkList = response.data;
|
||||
@@ -55,12 +59,12 @@ const WiFiNetworkScanner: FC = () => {
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof AxiosError) {
|
||||
finishedWithError('Problem listing WiFi networks ' + error.response?.data.message);
|
||||
finishedWithError(LL.PROBLEM_LOADING() + ' ' + error.response?.data.message);
|
||||
} else {
|
||||
finishedWithError('Problem listing WiFi networks');
|
||||
finishedWithError(LL.PROBLEM_LOADING());
|
||||
}
|
||||
}
|
||||
}, [finishedWithError]);
|
||||
}, [finishedWithError, LL]);
|
||||
|
||||
const startNetworkScan = useCallback(async () => {
|
||||
pollCount.current = 0;
|
||||
@@ -71,12 +75,12 @@ const WiFiNetworkScanner: FC = () => {
|
||||
setTimeout(pollNetworkList, POLLING_FREQUENCY);
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof AxiosError) {
|
||||
finishedWithError('Problem scanning for WiFi networks ' + error.response?.data.message);
|
||||
finishedWithError(LL.PROBLEM_LOADING() + ' ' + error.response?.data.message);
|
||||
} else {
|
||||
finishedWithError('Problem scanning for WiFi networks');
|
||||
finishedWithError(LL.PROBLEM_LOADING());
|
||||
}
|
||||
}
|
||||
}, [finishedWithError, pollNetworkList]);
|
||||
}, [finishedWithError, pollNetworkList, LL]);
|
||||
|
||||
useEffect(() => {
|
||||
startNetworkScan();
|
||||
@@ -84,13 +88,13 @@ const WiFiNetworkScanner: FC = () => {
|
||||
|
||||
const renderNetworkScanner = () => {
|
||||
if (!networkList) {
|
||||
return <FormLoader message="Scanning…" errorMessage={errorMessage} />;
|
||||
return <FormLoader message={LL.SCANNING() + '...'} errorMessage={errorMessage} />;
|
||||
}
|
||||
return <WiFiNetworkSelector networkList={networkList} />;
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="Network Scanner">
|
||||
<SectionContent title={LL.NETWORK_SCANNER()}>
|
||||
{renderNetworkScanner()}
|
||||
<ButtonRow>
|
||||
<Button
|
||||
@@ -100,7 +104,7 @@ const WiFiNetworkScanner: FC = () => {
|
||||
onClick={startNetworkScan}
|
||||
disabled={!errorMessage && !networkList}
|
||||
>
|
||||
Scan again…
|
||||
{LL.SCAN_AGAIN()}…
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</SectionContent>
|
||||
|
||||
@@ -12,6 +12,8 @@ import { WiFiEncryptionType, WiFiNetwork, WiFiNetworkList } from '../../types';
|
||||
|
||||
import { WiFiConnectionContext } from './WiFiConnectionContext';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
interface WiFiNetworkSelectorProps {
|
||||
networkList: WiFiNetworkList;
|
||||
}
|
||||
@@ -39,6 +41,8 @@ export const networkSecurityMode = ({ encryption_type }: WiFiNetwork) => {
|
||||
};
|
||||
|
||||
const WiFiNetworkSelector: FC<WiFiNetworkSelectorProps> = ({ networkList }) => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const wifiConnectionContext = useContext(WiFiConnectionContext);
|
||||
|
||||
const renderNetwork = (network: WiFiNetwork) => {
|
||||
@@ -61,7 +65,7 @@ const WiFiNetworkSelector: FC<WiFiNetworkSelectorProps> = ({ networkList }) => {
|
||||
};
|
||||
|
||||
if (networkList.networks.length === 0) {
|
||||
return <MessageBox mt={2} mb={1} message="No WiFi networks found" level="info" />;
|
||||
return <MessageBox mt={2} mb={1} message={LL.NETWORK_NO_WIFI()} level="info" />;
|
||||
}
|
||||
|
||||
return <List>{networkList.networks.map(renderNetwork)}</List>;
|
||||
|
||||
@@ -12,12 +12,16 @@ import * as NTPApi from '../../api/ntp';
|
||||
import { selectedTimeZone, timeZoneSelectItems, TIME_ZONES } from './TZ';
|
||||
import { NTP_SETTINGS_VALIDATOR } from '../../validators/ntp';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const NTPSettingsForm: FC = () => {
|
||||
const { loadData, saving, data, setData, saveData, errorMessage } = useRest<NTPSettings>({
|
||||
read: NTPApi.readNTPSettings,
|
||||
update: NTPApi.updateNTPSettings
|
||||
});
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const updateFormValue = updateValue(setData);
|
||||
|
||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||
@@ -49,7 +53,7 @@ const NTPSettingsForm: FC = () => {
|
||||
<>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="enabled" checked={data.enabled} onChange={updateFormValue} />}
|
||||
label="Enable NTP"
|
||||
label={LL.ENABLE_NTP()}
|
||||
/>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
@@ -64,7 +68,7 @@ const NTPSettingsForm: FC = () => {
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="tz_label"
|
||||
label="Time zone"
|
||||
label={LL.TIME_ZONE()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={selectedTimeZone(data.tz_label, data.tz_format)}
|
||||
@@ -72,7 +76,7 @@ const NTPSettingsForm: FC = () => {
|
||||
margin="normal"
|
||||
select
|
||||
>
|
||||
<MenuItem disabled>Time zone...</MenuItem>
|
||||
<MenuItem disabled>{LL.TIME_ZONE()}...</MenuItem>
|
||||
{timeZoneSelectItems()}
|
||||
</ValidatedTextField>
|
||||
<ButtonRow>
|
||||
@@ -84,7 +88,7 @@ const NTPSettingsForm: FC = () => {
|
||||
type="submit"
|
||||
onClick={validateAndSubmit}
|
||||
>
|
||||
Save
|
||||
{LL.SAVE()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</>
|
||||
@@ -92,7 +96,7 @@ const NTPSettingsForm: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="NTP Settings" titleGutter>
|
||||
<SectionContent title={'NTP ' + LL.SETTINGS()} titleGutter>
|
||||
{content()}
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
@@ -16,7 +16,8 @@ import {
|
||||
ListItemText,
|
||||
TextField,
|
||||
Theme,
|
||||
useTheme
|
||||
useTheme,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
||||
@@ -31,6 +32,8 @@ import { ButtonRow, FormLoader, SectionContent } from '../../components';
|
||||
import { extractErrorMessage, formatDateTime, formatLocalDateTime, useRest } from '../../utils';
|
||||
import { AuthenticatedContext } from '../../contexts/authentication';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
export const isNtpActive = ({ status }: NTPStatus) => status === NTPSyncStatus.NTP_ACTIVE;
|
||||
export const isNtpEnabled = ({ status }: NTPStatus) => status !== NTPSyncStatus.NTP_DISABLED;
|
||||
|
||||
@@ -47,19 +50,6 @@ export const ntpStatusHighlight = ({ status }: NTPStatus, theme: Theme) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const ntpStatus = ({ status }: NTPStatus) => {
|
||||
switch (status) {
|
||||
case NTPSyncStatus.NTP_DISABLED:
|
||||
return 'Disabled';
|
||||
case NTPSyncStatus.NTP_INACTIVE:
|
||||
return 'Inactive';
|
||||
case NTPSyncStatus.NTP_ACTIVE:
|
||||
return 'Active';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
};
|
||||
|
||||
const NTPStatusForm: FC = () => {
|
||||
const { loadData, data, errorMessage } = useRest<NTPStatus>({ read: NTPApi.readNTPStatus });
|
||||
const [localTime, setLocalTime] = useState<string>('');
|
||||
@@ -68,6 +58,8 @@ const NTPStatusForm: FC = () => {
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const { me } = useContext(AuthenticatedContext);
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const updateLocalTime = (event: React.ChangeEvent<HTMLInputElement>) => setLocalTime(event.target.value);
|
||||
|
||||
const openSetTime = () => {
|
||||
@@ -77,59 +69,71 @@ const NTPStatusForm: FC = () => {
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const ntpStatus = ({ status }: NTPStatus) => {
|
||||
switch (status) {
|
||||
case NTPSyncStatus.NTP_DISABLED:
|
||||
return LL.DISABLED();
|
||||
case NTPSyncStatus.NTP_INACTIVE:
|
||||
return LL.INACTIVE();
|
||||
case NTPSyncStatus.NTP_ACTIVE:
|
||||
return LL.ACTIVE();
|
||||
default:
|
||||
return LL.UNKNOWN();
|
||||
}
|
||||
};
|
||||
|
||||
const configureTime = async () => {
|
||||
setProcessing(true);
|
||||
try {
|
||||
await NTPApi.updateTime({
|
||||
local_time: formatLocalDateTime(new Date(localTime))
|
||||
});
|
||||
enqueueSnackbar('Time set', { variant: 'success' });
|
||||
enqueueSnackbar(LL.TIME_SET(), { variant: 'success' });
|
||||
setSettingTime(false);
|
||||
loadData();
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem updating time'), { variant: 'error' });
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
|
||||
} finally {
|
||||
setProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const renderSetTimeDialog = () => {
|
||||
return (
|
||||
<Dialog open={settingTime} onClose={() => setSettingTime(false)}>
|
||||
<DialogTitle>Set Time</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<Box mb={2}>Enter local date and time below to set the device's time.</Box>
|
||||
<TextField
|
||||
label="Local Time"
|
||||
type="datetime-local"
|
||||
value={localTime}
|
||||
onChange={updateLocalTime}
|
||||
disabled={processing}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
InputLabelProps={{
|
||||
shrink: true
|
||||
}}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button startIcon={<CancelIcon />} variant="outlined" onClick={() => setSettingTime(false)} color="secondary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<AccessTimeIcon />}
|
||||
variant="outlined"
|
||||
onClick={configureTime}
|
||||
disabled={processing}
|
||||
color="primary"
|
||||
autoFocus
|
||||
>
|
||||
Set Time
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
const renderSetTimeDialog = () => (
|
||||
<Dialog open={settingTime} onClose={() => setSettingTime(false)}>
|
||||
<DialogTitle>{LL.SET_TIME()}</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<Box color="warning.main" p={0} pl={0} pr={0} mt={0} mb={2}>
|
||||
<Typography variant="body2">{LL.SET_TIME_TEXT()}</Typography>
|
||||
</Box>
|
||||
<TextField
|
||||
label={LL.LOCAL_TIME()}
|
||||
type="datetime-local"
|
||||
value={localTime}
|
||||
onChange={updateLocalTime}
|
||||
disabled={processing}
|
||||
fullWidth
|
||||
InputLabelProps={{
|
||||
shrink: true
|
||||
}}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button startIcon={<CancelIcon />} variant="outlined" onClick={() => setSettingTime(false)} color="secondary">
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<AccessTimeIcon />}
|
||||
variant="outlined"
|
||||
onClick={configureTime}
|
||||
disabled={processing}
|
||||
color="primary"
|
||||
autoFocus
|
||||
>
|
||||
{LL.SET_TIME()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
@@ -145,7 +149,7 @@ const NTPStatusForm: FC = () => {
|
||||
<UpdateIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Status" secondary={ntpStatus(data)} />
|
||||
<ListItemText primary={LL.STATUS()} secondary={ntpStatus(data)} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
{isNtpEnabled(data) && (
|
||||
@@ -167,7 +171,7 @@ const NTPStatusForm: FC = () => {
|
||||
<AccessTimeIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Local Time" secondary={formatDateTime(data.local_time)} />
|
||||
<ListItemText primary={LL.LOCAL_TIME()} secondary={formatDateTime(data.local_time)} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
@@ -176,7 +180,7 @@ const NTPStatusForm: FC = () => {
|
||||
<SwapVerticalCircleIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="UTC Time" secondary={formatDateTime(data.utc_time)} />
|
||||
<ListItemText primary={LL.UTC_TIME()} secondary={formatDateTime(data.utc_time)} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
</List>
|
||||
@@ -184,7 +188,7 @@ const NTPStatusForm: FC = () => {
|
||||
<Box flexGrow={1}>
|
||||
<ButtonRow>
|
||||
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
|
||||
Refresh
|
||||
{LL.REFRESH()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</Box>
|
||||
@@ -192,7 +196,7 @@ const NTPStatusForm: FC = () => {
|
||||
<Box flexWrap="nowrap" whiteSpace="nowrap">
|
||||
<ButtonRow>
|
||||
<Button onClick={openSetTime} variant="outlined" color="primary" startIcon={<AccessTimeIcon />}>
|
||||
Set Time
|
||||
{LL.SET_TIME()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</Box>
|
||||
@@ -204,7 +208,7 @@ const NTPStatusForm: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="NTP Status" titleGutter>
|
||||
<SectionContent title={'NTP ' + LL.STATUS()} titleGutter>
|
||||
{content()}
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
@@ -9,8 +9,11 @@ import { AuthenticatedContext } from '../../contexts/authentication';
|
||||
import NTPStatusForm from './NTPStatusForm';
|
||||
import NTPSettingsForm from './NTPSettingsForm';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const NetworkTime: FC = () => {
|
||||
useLayoutTitle('Network Time');
|
||||
const { LL } = useI18nContext();
|
||||
useLayoutTitle("NTP");
|
||||
|
||||
const authenticatedContext = useContext(AuthenticatedContext);
|
||||
const { routerTab } = useRouterTab();
|
||||
@@ -18,8 +21,8 @@ const NetworkTime: FC = () => {
|
||||
return (
|
||||
<>
|
||||
<RouterTabs value={routerTab}>
|
||||
<Tab value="status" label="NTP Status" />
|
||||
<Tab value="settings" label="NTP Settings" disabled={!authenticatedContext.me.admin} />
|
||||
<Tab value="status" label={'NTP ' + LL.STATUS()} />
|
||||
<Tab value="settings" label={'NTP ' + LL.SETTINGS()} disabled={!authenticatedContext.me.admin} />
|
||||
</RouterTabs>
|
||||
<Routes>
|
||||
<Route path="status" element={<NTPStatusForm />} />
|
||||
|
||||
@@ -19,6 +19,8 @@ import { MessageBox } from '../../components';
|
||||
import * as SecurityApi from '../../api/security';
|
||||
import { Token } from '../../types';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
interface GenerateTokenProps {
|
||||
username?: string;
|
||||
onClose: () => void;
|
||||
@@ -28,15 +30,17 @@ const GenerateToken: FC<GenerateTokenProps> = ({ username, onClose }) => {
|
||||
const [token, setToken] = useState<Token>();
|
||||
const open = !!username;
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const getToken = useCallback(async () => {
|
||||
try {
|
||||
setToken((await SecurityApi.generateToken(username)).data);
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem generating token'), { variant: 'error' });
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
|
||||
}
|
||||
}, [username, enqueueSnackbar]);
|
||||
}, [username, enqueueSnackbar, LL]);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
@@ -46,16 +50,11 @@ const GenerateToken: FC<GenerateTokenProps> = ({ username, onClose }) => {
|
||||
|
||||
return (
|
||||
<Dialog onClose={onClose} aria-labelledby="generate-token-dialog-title" open={!!username} fullWidth maxWidth="sm">
|
||||
<DialogTitle id="generate-token-dialog-title">Access Token for {username}</DialogTitle>
|
||||
<DialogTitle id="generate-token-dialog-title">{LL.ACCESS_TOKEN_FOR() + ' ' + username}</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
{token ? (
|
||||
<>
|
||||
<MessageBox
|
||||
message="The token below is used with REST API calls that require authorization. It can be passed either as a Bearer token in the
|
||||
'Authorization' header or in the 'access_token' URL query parameter."
|
||||
level="info"
|
||||
my={2}
|
||||
/>
|
||||
<MessageBox message={LL.ACCESS_TOKEN_TEXT()} level="info" my={2} />
|
||||
<Box mt={2} mb={2}>
|
||||
<TextField label="Token" multiline value={token.token} fullWidth contentEditable={false} />
|
||||
</Box>
|
||||
@@ -63,13 +62,13 @@ const GenerateToken: FC<GenerateTokenProps> = ({ username, onClose }) => {
|
||||
) : (
|
||||
<Box m={4} textAlign="center">
|
||||
<LinearProgress />
|
||||
<Typography variant="h6">Generating token…</Typography>
|
||||
<Typography variant="h6">{LL.GENERATING_TOKEN()}…</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button startIcon={<CloseIcon />} variant="outlined" onClick={onClose} color="secondary">
|
||||
Close
|
||||
{LL.CLOSE()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
@@ -20,6 +20,8 @@ import { createUserValidator } from '../../validators';
|
||||
import { useRest } from '../../utils';
|
||||
import { AuthenticatedContext } from '../../contexts/authentication';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
import GenerateToken from './GenerateToken';
|
||||
import UserForm from './UserForm';
|
||||
|
||||
@@ -34,9 +36,11 @@ const ManageUsersForm: FC = () => {
|
||||
const [generatingToken, setGeneratingToken] = useState<string>();
|
||||
const authenticatedContext = useContext(AuthenticatedContext);
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const table_theme = useTheme({
|
||||
Table: `
|
||||
--data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) 90px 120px;
|
||||
--data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) 120px 120px;
|
||||
`,
|
||||
BaseRow: `
|
||||
font-size: 14px;
|
||||
@@ -136,8 +140,8 @@ const ManageUsersForm: FC = () => {
|
||||
<>
|
||||
<Header>
|
||||
<HeaderRow>
|
||||
<HeaderCell resize>USERNAME</HeaderCell>
|
||||
<HeaderCell stiff>IS ADMIN</HeaderCell>
|
||||
<HeaderCell resize>{LL.USERNAME()}</HeaderCell>
|
||||
<HeaderCell stiff>{LL.IS_ADMIN()}</HeaderCell>
|
||||
<HeaderCell stiff />
|
||||
</HeaderRow>
|
||||
</Header>
|
||||
@@ -169,9 +173,7 @@ const ManageUsersForm: FC = () => {
|
||||
)}
|
||||
</Table>
|
||||
|
||||
{noAdminConfigured() && (
|
||||
<MessageBox level="warning" message="You must have at least one admin user configured" my={2} />
|
||||
)}
|
||||
{noAdminConfigured() && <MessageBox level="warning" message={LL.USER_WARNING()} my={2} />}
|
||||
|
||||
<Box display="flex" flexWrap="wrap">
|
||||
<Box flexGrow={1} sx={{ '& button': { mt: 2 } }}>
|
||||
@@ -183,14 +185,14 @@ const ManageUsersForm: FC = () => {
|
||||
type="submit"
|
||||
onClick={onSubmit}
|
||||
>
|
||||
Save
|
||||
{LL.SAVE()}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Box flexWrap="nowrap" whiteSpace="nowrap">
|
||||
<ButtonRow>
|
||||
<Button startIcon={<PersonAddIcon />} variant="outlined" color="secondary" onClick={createUser}>
|
||||
Add
|
||||
{LL.ADD()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</Box>
|
||||
@@ -210,7 +212,7 @@ const ManageUsersForm: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="Manage Users" titleGutter>
|
||||
<SectionContent title={LL.MANAGE_USERS()} titleGutter>
|
||||
{content()}
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
@@ -8,16 +8,19 @@ import { RouterTabs, useRouterTab, useLayoutTitle } from '../../components';
|
||||
import SecuritySettingsForm from './SecuritySettingsForm';
|
||||
import ManageUsersForm from './ManageUsersForm';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const Security: FC = () => {
|
||||
useLayoutTitle('Security');
|
||||
const { LL } = useI18nContext();
|
||||
useLayoutTitle(LL.SECURITY());
|
||||
|
||||
const { routerTab } = useRouterTab();
|
||||
|
||||
return (
|
||||
<>
|
||||
<RouterTabs value={routerTab}>
|
||||
<Tab value="users" label="Manage Users" />
|
||||
<Tab value="settings" label="Security Settings" />
|
||||
<Tab value="users" label={LL.MANAGE_USERS()} />
|
||||
<Tab value="settings" label={LL.SECURITY() + ' ' + LL.SETTINGS()} />
|
||||
</RouterTabs>
|
||||
<Routes>
|
||||
<Route path="users" element={<ManageUsersForm />} />
|
||||
|
||||
@@ -11,7 +11,11 @@ import { SECURITY_SETTINGS_VALIDATOR, validate } from '../../validators';
|
||||
import { updateValue, useRest } from '../../utils';
|
||||
import { AuthenticatedContext } from '../../contexts/authentication';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const SecuritySettingsForm: FC = () => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||
const { loadData, saving, data, setData, saveData, errorMessage } = useRest<SecuritySettings>({
|
||||
read: SecurityApi.readSecuritySettings,
|
||||
@@ -42,7 +46,7 @@ const SecuritySettingsForm: FC = () => {
|
||||
<ValidatedPasswordField
|
||||
fieldErrors={fieldErrors}
|
||||
name="jwt_secret"
|
||||
label="su Password"
|
||||
label={"su " + LL.PASSWORD()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.jwt_secret}
|
||||
@@ -51,7 +55,7 @@ const SecuritySettingsForm: FC = () => {
|
||||
/>
|
||||
<MessageBox
|
||||
level="info"
|
||||
message="The su (super user) password is used to sign authentication tokens and also enable admin privileges within the Console."
|
||||
message={LL.SU_TEXT()}
|
||||
mt={1}
|
||||
/>
|
||||
<ButtonRow>
|
||||
@@ -63,7 +67,7 @@ const SecuritySettingsForm: FC = () => {
|
||||
type="submit"
|
||||
onClick={validateAndSubmit}
|
||||
>
|
||||
Save
|
||||
{LL.SAVE()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</>
|
||||
@@ -71,7 +75,7 @@ const SecuritySettingsForm: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="Security Settings" titleGutter>
|
||||
<SectionContent title={LL.SECURITY() + " " + LL.SETTINGS()} titleGutter>
|
||||
{content()}
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
@@ -11,6 +11,8 @@ import { User } from '../../types';
|
||||
import { updateValue } from '../../utils';
|
||||
import { validate } from '../../validators';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
interface UserFormProps {
|
||||
creating: boolean;
|
||||
validator: Schema;
|
||||
@@ -23,6 +25,8 @@ interface UserFormProps {
|
||||
}
|
||||
|
||||
const UserForm: FC<UserFormProps> = ({ creating, validator, user, setUser, onDoneEditing, onCancelEditing }) => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const updateFormValue = updateValue(setUser);
|
||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||
const open = !!user;
|
||||
@@ -49,12 +53,14 @@ const UserForm: FC<UserFormProps> = ({ creating, validator, user, setUser, onDon
|
||||
<Dialog onClose={onCancelEditing} open={!!user} fullWidth maxWidth="sm">
|
||||
{user && (
|
||||
<>
|
||||
<DialogTitle id="user-form-dialog-title">{creating ? 'Add' : 'Modify'} User</DialogTitle>
|
||||
<DialogTitle id="user-form-dialog-title">
|
||||
{creating ? LL.ADD() : LL.MODIFY()} {LL.USER()}
|
||||
</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="username"
|
||||
label="Username"
|
||||
label={LL.USERNAME()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={user.username}
|
||||
@@ -65,7 +71,7 @@ const UserForm: FC<UserFormProps> = ({ creating, validator, user, setUser, onDon
|
||||
<ValidatedPasswordField
|
||||
fieldErrors={fieldErrors}
|
||||
name="password"
|
||||
label="Password"
|
||||
label={LL.PASSWORD()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={user.password}
|
||||
@@ -74,12 +80,12 @@ const UserForm: FC<UserFormProps> = ({ creating, validator, user, setUser, onDon
|
||||
/>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="admin" checked={user.admin} onChange={updateFormValue} />}
|
||||
label="is Admin?"
|
||||
label={LL.IS_ADMIN()}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button startIcon={<CancelIcon />} variant="outlined" onClick={onCancelEditing} color="secondary">
|
||||
Cancel
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<PersonAddIcon />}
|
||||
@@ -88,7 +94,7 @@ const UserForm: FC<UserFormProps> = ({ creating, validator, user, setUser, onDon
|
||||
color="primary"
|
||||
autoFocus
|
||||
>
|
||||
Add
|
||||
{LL.ADD()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
|
||||
@@ -14,6 +14,8 @@ import { extractErrorMessage } from '../../utils';
|
||||
|
||||
import * as EMSESP from '../../project/api';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
interface UploadFileProps {
|
||||
uploadGeneralFile: (file: File, config?: FileUploadConfig) => AxiosPromise<void>;
|
||||
}
|
||||
@@ -23,6 +25,8 @@ const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const saveFile = (json: any, endpoint: string) => {
|
||||
const a = document.createElement('a');
|
||||
const filename = 'emsesp_' + endpoint + '.json';
|
||||
@@ -35,19 +39,19 @@ const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
enqueueSnackbar('File downloaded', { variant: 'info' });
|
||||
enqueueSnackbar(LL.DOWNLOAD_SUCCESSFUL(), { variant: 'info' });
|
||||
};
|
||||
|
||||
const downloadSettings = async () => {
|
||||
try {
|
||||
const response = await EMSESP.getSettings();
|
||||
if (response.status !== 200) {
|
||||
enqueueSnackbar('Unable to get settings', { variant: 'error' });
|
||||
enqueueSnackbar(LL.PROBLEM_LOADING(), { variant: 'error' });
|
||||
} else {
|
||||
saveFile(response.data, 'settings');
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem with downloading'), { variant: 'error' });
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -55,47 +59,45 @@ const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
|
||||
try {
|
||||
const response = await EMSESP.getCustomizations();
|
||||
if (response.status !== 200) {
|
||||
enqueueSnackbar('Unable to get customizations', { variant: 'error' });
|
||||
enqueueSnackbar(LL.PROBLEM_LOADING(), { variant: 'error' });
|
||||
} else {
|
||||
saveFile(response.data, 'customizations');
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem with downloading'), { variant: 'error' });
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
|
||||
Upload
|
||||
</Typography>
|
||||
{!uploading && (
|
||||
<Box mb={2} color="warning.main">
|
||||
<Typography variant="body2">
|
||||
Upload a new firmware (.bin) file, settings or customizations (.json) file below.
|
||||
<>
|
||||
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
|
||||
{LL.UPLOAD()}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box mb={2} color="warning.main">
|
||||
<Typography variant="body2">{LL.UPLOAD_TEXT()} </Typography>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
<SingleUpload onDrop={uploadFile} onCancel={cancelUpload} uploading={uploading} progress={uploadProgress} />
|
||||
|
||||
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
|
||||
Download
|
||||
</Typography>
|
||||
{!uploading && (
|
||||
<>
|
||||
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
|
||||
{LL.DOWNLOAD()}
|
||||
</Typography>
|
||||
<Box color="warning.main">
|
||||
<Typography mb={1} variant="body2">
|
||||
Download the application settings. Be careful when sharing your settings as this file contains passwords
|
||||
and other sensitive system information.
|
||||
{LL.DOWNLOAD_SETTINGS_TEXT()}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={() => downloadSettings()}>
|
||||
settings
|
||||
{LL.SETTINGS()}
|
||||
</Button>
|
||||
|
||||
<Box color="warning.main">
|
||||
<Typography mt={2} mb={1} variant="body2">
|
||||
Download the entity customizations.
|
||||
{LL.DOWNLOAD_CUSTOMIZATION_TEXT()}{' '}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Button
|
||||
@@ -104,7 +106,7 @@ const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
|
||||
color="primary"
|
||||
onClick={() => downloadCustomizations()}
|
||||
>
|
||||
customizations
|
||||
{LL.CUSTOMIZATION()}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
ValidatedPasswordField,
|
||||
ValidatedTextField
|
||||
} from '../../components';
|
||||
|
||||
import { OTASettings } from '../../types';
|
||||
import { numberValue, updateValue, useRest } from '../../utils';
|
||||
|
||||
@@ -19,12 +20,16 @@ import { ValidateFieldsError } from 'async-validator';
|
||||
import { validate } from '../../validators';
|
||||
import { OTA_SETTINGS_VALIDATOR } from '../../validators/system';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const OTASettingsForm: FC = () => {
|
||||
const { loadData, saving, data, setData, saveData, errorMessage } = useRest<OTASettings>({
|
||||
read: SystemApi.readOTASettings,
|
||||
update: SystemApi.updateOTASettings
|
||||
});
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const updateFormValue = updateValue(setData);
|
||||
|
||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||
@@ -48,7 +53,7 @@ const OTASettingsForm: FC = () => {
|
||||
<>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox name="enabled" checked={data.enabled} onChange={updateFormValue} />}
|
||||
label="Enable OTA Updates"
|
||||
label={LL.ENABLE_OTA()}
|
||||
/>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
@@ -64,7 +69,7 @@ const OTASettingsForm: FC = () => {
|
||||
<ValidatedPasswordField
|
||||
fieldErrors={fieldErrors}
|
||||
name="password"
|
||||
label="Password"
|
||||
label={LL.PASSWORD()}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.password}
|
||||
@@ -80,7 +85,7 @@ const OTASettingsForm: FC = () => {
|
||||
type="submit"
|
||||
onClick={validateAndSubmit}
|
||||
>
|
||||
Save
|
||||
{LL.SAVE()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</>
|
||||
@@ -88,7 +93,7 @@ const OTASettingsForm: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="OTA Settings" titleGutter>
|
||||
<SectionContent title={'OTA ' + LL.SETTINGS()} titleGutter>
|
||||
{content()}
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
@@ -4,6 +4,8 @@ import { FC, useRef, useState } from 'react';
|
||||
import * as SystemApi from '../../api/system';
|
||||
import { FormLoader } from '../../components';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const RESTART_TIMEOUT = 2 * 60 * 1000;
|
||||
const POLL_TIMEOUT = 2000;
|
||||
const POLL_INTERVAL = 5000;
|
||||
@@ -12,6 +14,8 @@ const RestartMonitor: FC = () => {
|
||||
const [failed, setFailed] = useState<boolean>(false);
|
||||
const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout>();
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const timeoutAt = useRef(new Date().getTime() + RESTART_TIMEOUT);
|
||||
const poll = useRef(async () => {
|
||||
try {
|
||||
@@ -32,12 +36,7 @@ const RestartMonitor: FC = () => {
|
||||
|
||||
useEffect(() => () => timeoutId && clearTimeout(timeoutId), [timeoutId]);
|
||||
|
||||
return (
|
||||
<FormLoader
|
||||
message="EMS-ESP is restarting, please wait…"
|
||||
errorMessage={failed ? 'Timed out waiting for device to restart.' : undefined}
|
||||
/>
|
||||
);
|
||||
return <FormLoader message={LL.APPLICATION_RESTARTING() + '...'} errorMessage={failed ? 'Timed out' : undefined} />;
|
||||
};
|
||||
|
||||
export default RestartMonitor;
|
||||
|
||||
@@ -12,8 +12,13 @@ import OTASettingsForm from './OTASettingsForm';
|
||||
|
||||
import SystemLog from './SystemLog';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const System: FC = () => {
|
||||
useLayoutTitle('System');
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
useLayoutTitle(LL.SYSTEM());
|
||||
|
||||
const { me } = useContext(AuthenticatedContext);
|
||||
const { features } = useContext(FeaturesContext);
|
||||
@@ -22,11 +27,11 @@ const System: FC = () => {
|
||||
return (
|
||||
<>
|
||||
<RouterTabs value={routerTab}>
|
||||
<Tab value="status" label="System Status" />
|
||||
<Tab value="log" label="System Log" />
|
||||
<Tab value="status" label={LL.SYSTEM() + ' ' + LL.STATUS()} />
|
||||
<Tab value="log" label={LL.SYSTEM() + ' ' + LL.LOG()} />
|
||||
|
||||
{features.ota && <Tab value="ota" label="OTA Settings" disabled={!me.admin} />}
|
||||
{features.upload_firmware && <Tab value="upload" label="Upload/Download" disabled={!me.admin} />}
|
||||
{features.ota && <Tab value="ota" label={"OTA " + LL.SETTINGS()} disabled={!me.admin} />}
|
||||
{features.upload_firmware && <Tab value="upload" label={LL.UPLOAD_DOWNLOAD()} disabled={!me.admin} />}
|
||||
</RouterTabs>
|
||||
<Routes>
|
||||
<Route path="status" element={<SystemStatusForm />} />
|
||||
|
||||
@@ -15,6 +15,9 @@ import DownloadIcon from '@mui/icons-material/GetApp';
|
||||
import { useSnackbar } from 'notistack';
|
||||
|
||||
import { EVENT_SOURCE_ROOT } from '../../api/endpoints';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
export const LOG_EVENTSOURCE_URL = EVENT_SOURCE_ROOT + 'log';
|
||||
|
||||
const useWindowSize = () => {
|
||||
@@ -63,6 +66,8 @@ const levelLabel = (level: LogLevel) => {
|
||||
const SystemLog: FC = () => {
|
||||
useWindowSize();
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const { loadData, data, setData } = useRest<LogSettings>({
|
||||
read: SystemApi.readLogSettings
|
||||
});
|
||||
@@ -104,10 +109,10 @@ const SystemLog: FC = () => {
|
||||
compact: data.compact
|
||||
});
|
||||
if (response.status !== 200) {
|
||||
enqueueSnackbar('Problem applying log settings', { variant: 'error' });
|
||||
enqueueSnackbar(LL.PROBLEM_UPDATING(), { variant: 'error' });
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem applying log settings'), { variant: 'error' });
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -159,9 +164,9 @@ const SystemLog: FC = () => {
|
||||
try {
|
||||
setLogEntries((await SystemApi.readLogEntries()).data);
|
||||
} catch (error: unknown) {
|
||||
setErrorMessage(extractErrorMessage(error, 'Failed to fetch log'));
|
||||
setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
||||
}
|
||||
}, []);
|
||||
}, [LL]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchLog();
|
||||
@@ -214,7 +219,7 @@ const SystemLog: FC = () => {
|
||||
</ValidatedTextField>
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<FormLabel>Buffer size</FormLabel>
|
||||
<FormLabel>{LL.BUFFER_SIZE()}</FormLabel>
|
||||
<Slider
|
||||
value={data.max_messages}
|
||||
valueLabelDisplay="auto"
|
||||
@@ -235,12 +240,12 @@ const SystemLog: FC = () => {
|
||||
<Grid item>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox checked={data.compact} onChange={updateFormValue} name="compact" />}
|
||||
label="Compact"
|
||||
label={LL.COMPACT()}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Button startIcon={<DownloadIcon />} variant="outlined" color="secondary" onClick={onDownload}>
|
||||
Export
|
||||
{LL.EXPORT()}
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
@@ -273,7 +278,7 @@ const SystemLog: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="System Log" titleGutter id="log-window">
|
||||
<SectionContent title={LL.SYSTEM() + ' ' + LL.LOG()} titleGutter id="log-window">
|
||||
{content()}
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
@@ -39,6 +39,8 @@ import { AuthenticatedContext } from '../../contexts/authentication';
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
export const VERSIONCHECK_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/latest';
|
||||
export const VERSIONCHECK_DEV_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/tags/latest';
|
||||
export const uploadURL = window.location.origin + '/system/upload';
|
||||
@@ -48,6 +50,8 @@ function formatNumber(num: number) {
|
||||
}
|
||||
|
||||
const SystemStatusForm: FC = () => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const { loadData, data, errorMessage } = useRest<SystemStatus>({ read: SystemApi.readSystemStatus });
|
||||
|
||||
const { me } = useContext(AuthenticatedContext);
|
||||
@@ -80,9 +84,9 @@ const SystemStatusForm: FC = () => {
|
||||
setProcessing(true);
|
||||
try {
|
||||
await SystemApi.restart();
|
||||
enqueueSnackbar('EMS-ESP is restarting...', { variant: 'info' });
|
||||
enqueueSnackbar(LL.APPLICATION_RESTARTING(), { variant: 'info' });
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem restarting device'), { variant: 'error' });
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
|
||||
} finally {
|
||||
setConfirmRestart(false);
|
||||
setProcessing(false);
|
||||
@@ -92,7 +96,7 @@ const SystemStatusForm: FC = () => {
|
||||
const renderRestartDialog = () => (
|
||||
<Dialog open={confirmRestart} onClose={() => setConfirmRestart(false)}>
|
||||
<DialogTitle>Restart</DialogTitle>
|
||||
<DialogContent dividers>Are you sure you want to restart EMS-ESP?</DialogContent>
|
||||
<DialogContent dividers>{LL.RESTART_TEXT()}</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
startIcon={<CancelIcon />}
|
||||
@@ -100,7 +104,7 @@ const SystemStatusForm: FC = () => {
|
||||
onClick={() => setConfirmRestart(false)}
|
||||
color="secondary"
|
||||
>
|
||||
Cancel
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<PowerSettingsNewIcon />}
|
||||
@@ -110,7 +114,7 @@ const SystemStatusForm: FC = () => {
|
||||
color="primary"
|
||||
autoFocus
|
||||
>
|
||||
Restart
|
||||
{LL.RESTART()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
@@ -119,16 +123,12 @@ const SystemStatusForm: FC = () => {
|
||||
const renderVersionDialog = () => {
|
||||
return (
|
||||
<Dialog open={showingVersion} onClose={() => setShowingVersion(false)}>
|
||||
<DialogTitle>Version Check</DialogTitle>
|
||||
<DialogTitle>{LL.VERSION_CHECK()}</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<MessageBox
|
||||
my={0}
|
||||
level="info"
|
||||
message={'You are currently running EMS-ESP version ' + data?.emsesp_version}
|
||||
/>
|
||||
<MessageBox my={0} level="info" message={LL.SYSTEM_VERSION_RUNNING() + ' ' + data?.emsesp_version} />
|
||||
{latestVersion && (
|
||||
<Box mt={2} mb={2}>
|
||||
The latest <u>official</u> version is <b>{latestVersion.version}</b> (
|
||||
{LL.THE_LATEST()} <u>official</u> version is <b>{latestVersion.version}</b> (
|
||||
<Link target="_blank" href={latestVersion.changelog} color="primary">
|
||||
{'release notes'}
|
||||
</Link>
|
||||
@@ -142,7 +142,7 @@ const SystemStatusForm: FC = () => {
|
||||
|
||||
{latestDevVersion && (
|
||||
<Box mt={2} mb={2}>
|
||||
The latest <u>development</u> version is <b>{latestDevVersion.version}</b>
|
||||
{LL.THE_LATEST()} <u>development</u> version is <b>{latestDevVersion.version}</b>
|
||||
(
|
||||
<Link target="_blank" href={latestDevVersion.changelog} color="primary">
|
||||
{'release notes'}
|
||||
@@ -157,17 +157,17 @@ const SystemStatusForm: FC = () => {
|
||||
|
||||
<Box color="warning.main" p={0} pl={0} pr={0} mt={4} mb={0}>
|
||||
<Typography variant="body2">
|
||||
Use
|
||||
{LL.USE()}
|
||||
<Link target="_blank" href={uploadURL} color="primary">
|
||||
{'UPLOAD'}
|
||||
</Link>
|
||||
to apply the new firmware
|
||||
{LL.SYSTEM_APPLY_FIRMWARE()}
|
||||
</Typography>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button variant="outlined" onClick={() => setShowingVersion(false)} color="secondary">
|
||||
Close
|
||||
{LL.CLOSE()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
@@ -178,9 +178,9 @@ const SystemStatusForm: FC = () => {
|
||||
setProcessing(true);
|
||||
try {
|
||||
await SystemApi.factoryReset();
|
||||
enqueueSnackbar('Device has been factory reset and will now restart', { variant: 'info' });
|
||||
enqueueSnackbar(LL.SYSTEM_FACTORY_TEXT(), { variant: 'info' });
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem factory resetting the device'), { variant: 'error' });
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
|
||||
} finally {
|
||||
setConfirmFactoryReset(false);
|
||||
setProcessing(false);
|
||||
@@ -189,8 +189,8 @@ const SystemStatusForm: FC = () => {
|
||||
|
||||
const renderFactoryResetDialog = () => (
|
||||
<Dialog open={confirmFactoryReset} onClose={() => setConfirmFactoryReset(false)}>
|
||||
<DialogTitle>Factory Reset</DialogTitle>
|
||||
<DialogContent dividers>Are you sure you want to reset the device to its factory defaults?</DialogContent>
|
||||
<DialogTitle>{LL.FACTORY_RESET()}</DialogTitle>
|
||||
<DialogContent dividers>{LL.SYSTEM_FACTORY_TEXT_DIALOG()}</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
startIcon={<CancelIcon />}
|
||||
@@ -198,7 +198,7 @@ const SystemStatusForm: FC = () => {
|
||||
onClick={() => setConfirmFactoryReset(false)}
|
||||
color="secondary"
|
||||
>
|
||||
Cancel
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<SettingsBackupRestoreIcon />}
|
||||
@@ -208,7 +208,7 @@ const SystemStatusForm: FC = () => {
|
||||
autoFocus
|
||||
color="error"
|
||||
>
|
||||
Factory Reset
|
||||
{LL.FACTORY_RESET()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
@@ -231,7 +231,7 @@ const SystemStatusForm: FC = () => {
|
||||
<ListItemText primary="EMS-ESP Version" secondary={'v' + data.emsesp_version} />
|
||||
{latestVersion && (
|
||||
<Button color="primary" onClick={() => setShowingVersion(true)}>
|
||||
Version Check
|
||||
{LL.VERSION_CHECK()}
|
||||
</Button>
|
||||
)}
|
||||
</ListItem>
|
||||
@@ -242,7 +242,7 @@ const SystemStatusForm: FC = () => {
|
||||
<DevicesIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Device (Platform / SDK)" secondary={data.esp_platform + ' / ' + data.sdk_version} />
|
||||
<ListItemText primary={LL.PLATFORM()} secondary={data.esp_platform + ' / ' + data.sdk_version} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
@@ -251,7 +251,7 @@ const SystemStatusForm: FC = () => {
|
||||
<TimerIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="System Uptime" secondary={data.uptime} />
|
||||
<ListItemText primary={LL.UPTIME()} secondary={data.uptime} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
@@ -260,7 +260,7 @@ const SystemStatusForm: FC = () => {
|
||||
<ShowChartIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="CPU Frequency" secondary={data.cpu_freq_mhz + ' MHz'} />
|
||||
<ListItemText primary={LL.CPU_FREQ()} secondary={data.cpu_freq_mhz + ' MHz'} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
@@ -270,7 +270,7 @@ const SystemStatusForm: FC = () => {
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary="Heap (Free / Max Alloc)"
|
||||
primary={LL.HEAP()}
|
||||
secondary={
|
||||
formatNumber(data.free_heap) +
|
||||
' / ' +
|
||||
@@ -290,7 +290,7 @@ const SystemStatusForm: FC = () => {
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary="PSRAM (Size / Free)"
|
||||
primary={LL.PSRAM()}
|
||||
secondary={formatNumber(data.psram_size) + ' / ' + formatNumber(data.free_psram) + ' bytes'}
|
||||
/>
|
||||
</ListItem>
|
||||
@@ -304,7 +304,7 @@ const SystemStatusForm: FC = () => {
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary="Flash Chip (Size / Speed)"
|
||||
primary={LL.FLASH()}
|
||||
secondary={
|
||||
formatNumber(data.flash_chip_size) + ' bytes / ' + (data.flash_chip_speed / 1000000).toFixed(0) + ' MHz'
|
||||
}
|
||||
@@ -318,7 +318,7 @@ const SystemStatusForm: FC = () => {
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary="File System (Used / Total)"
|
||||
primary={LL.FILESYSTEM()}
|
||||
secondary={
|
||||
formatNumber(data.fs_used) +
|
||||
' / ' +
|
||||
@@ -335,7 +335,7 @@ const SystemStatusForm: FC = () => {
|
||||
<Box flexGrow={1} sx={{ '& button': { mt: 2 } }}>
|
||||
<ButtonRow>
|
||||
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
|
||||
Refresh
|
||||
{LL.REFRESH()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</Box>
|
||||
@@ -348,7 +348,7 @@ const SystemStatusForm: FC = () => {
|
||||
color="primary"
|
||||
onClick={() => setConfirmRestart(true)}
|
||||
>
|
||||
Restart
|
||||
{LL.RESTART()}
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<SettingsBackupRestoreIcon />}
|
||||
@@ -356,7 +356,7 @@ const SystemStatusForm: FC = () => {
|
||||
onClick={() => setConfirmFactoryReset(true)}
|
||||
color="error"
|
||||
>
|
||||
Factory reset
|
||||
{LL.FACTORY_RESET()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</Box>
|
||||
@@ -370,7 +370,7 @@ const SystemStatusForm: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="System Status" titleGutter>
|
||||
<SectionContent title={LL.SYSTEM() + ' ' + LL.STATUS()} titleGutter>
|
||||
{content()}
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
@@ -7,9 +7,13 @@ import { FileUploadConfig } from '../../api/endpoints';
|
||||
import GeneralFileUpload from './GeneralFileUpload';
|
||||
import RestartMonitor from './RestartMonitor';
|
||||
|
||||
import { useI18nContext } from '../../i18n/i18n-react';
|
||||
|
||||
const UploadFileForm: FC = () => {
|
||||
const [restarting, setRestarting] = useState<boolean>();
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const uploadFile = useRef(async (file: File, config?: FileUploadConfig) => {
|
||||
const response = await SystemApi.uploadFile(file, config);
|
||||
setRestarting(true);
|
||||
@@ -17,7 +21,7 @@ const UploadFileForm: FC = () => {
|
||||
});
|
||||
|
||||
return (
|
||||
<SectionContent title="Upload/Download" titleGutter>
|
||||
<SectionContent title={LL.UPLOAD_DOWNLOAD()} titleGutter>
|
||||
{restarting ? <RestartMonitor /> : <GeneralFileUpload uploadGeneralFile={uploadFile.current} />}
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
1
interface/src/i18n/DE.svg
Normal file
1
interface/src/i18n/DE.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 85.333 512 341.333"><path fill="#D80027" d="M0 85.331h512v341.337H0z"/><path d="M0 85.331h512v113.775H0z"/><path fill="#FFDA44" d="M0 312.882h512v113.775H0z"/></svg>
|
||||
|
After Width: | Height: | Size: 216 B |
1
interface/src/i18n/GB.svg
Normal file
1
interface/src/i18n/GB.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 85.333 512 341.333"><path fill="#FFF" d="M0 85.333h512V426.67H0z"/><path fill="#D80027" d="M288 85.33h-64v138.666H0v64h224v138.666h64V287.996h224v-64H288z"/><g fill="#0052B4"><path d="M393.785 315.358 512 381.034v-65.676zM311.652 315.358 512 426.662v-31.474l-143.693-79.83zM458.634 426.662l-146.982-81.664v81.664z"/></g><path fill="#FFF" d="M311.652 315.358 512 426.662v-31.474l-143.693-79.83z"/><path fill="#D80027" d="M311.652 315.358 512 426.662v-31.474l-143.693-79.83z"/><g fill="#0052B4"><path d="M90.341 315.356 0 365.546v-50.19zM200.348 329.51v97.151H25.491z"/></g><path fill="#D80027" d="M143.693 315.358 0 395.188v31.474l200.348-111.304z"/><g fill="#0052B4"><path d="M118.215 196.634 0 130.958v65.676zM200.348 196.634 0 85.33v31.474l143.693 79.83zM53.366 85.33l146.982 81.664V85.33z"/></g><path fill="#FFF" d="M200.348 196.634 0 85.33v31.474l143.693 79.83z"/><path fill="#D80027" d="M200.348 196.634 0 85.33v31.474l143.693 79.83z"/><g fill="#0052B4"><path d="M421.659 196.636 512 146.446v50.19zM311.652 182.482V85.331h174.857z"/></g><path fill="#D80027" d="M368.307 196.634 512 116.804V85.33L311.652 196.634z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
1
interface/src/i18n/NL.svg
Normal file
1
interface/src/i18n/NL.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 85.5 513 342"><path fill="#FFF" d="M0 85.5h513v342H0z"/><path fill="#cd1f2a" d="M0 85.5h513v114H0z"/><path fill="#1d4185" d="M0 312h513v114H0z"/></svg>
|
||||
|
After Width: | Height: | Size: 202 B |
1
interface/src/i18n/NO.svg
Normal file
1
interface/src/i18n/NO.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 85.333 512 341.333"><path fill="#D80027" d="M0 85.334h512v341.337H0z"/><path fill="#FFF" d="M512 295.883H202.195v130.783H122.435V295.883H0V216.111h122.435V85.329H202.195v130.782H512V277.329z"/><path fill="#2E52B2" d="M512 234.666v42.663H183.652v149.337h-42.674V277.329H0v-42.663h140.978V85.329h42.674v149.337z"/></svg>
|
||||
|
After Width: | Height: | Size: 369 B |
1
interface/src/i18n/PL.svg
Normal file
1
interface/src/i18n/PL.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 85.333 512 341.333"><g fill="#FFF"><path d="M0 85.337h512v341.326H0z"/><path d="M0 85.337h512V256H0z"/></g><path fill="#D80027" d="M0 256h512v170.663H0z"/></svg>
|
||||
|
After Width: | Height: | Size: 212 B |
1
interface/src/i18n/SE.svg
Normal file
1
interface/src/i18n/SE.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 85.333 512 341.333"><path fill="#0052B4" d="M0 85.333h512V426.67H0z"/><path fill="#FFDA44" d="M192 85.33h-64v138.666H0v64h128v138.666h64V287.996h320v-64H192z"/></svg>
|
||||
|
After Width: | Height: | Size: 217 B |
265
interface/src/i18n/de/index.ts
Normal file
265
interface/src/i18n/de/index.ts
Normal file
@@ -0,0 +1,265 @@
|
||||
import type { Translation } from '../i18n-types';
|
||||
|
||||
const de: Translation = {
|
||||
LANGUAGE: 'Sprache',
|
||||
RETRY: 'Neuer Versuch',
|
||||
LOADING: 'Laden',
|
||||
IS_REQUIRED: 'ist erforderlich',
|
||||
SIGN_IN: 'Einloggen',
|
||||
SIGN_OUT: 'Ausloggen',
|
||||
USERNAME: 'Nutzername',
|
||||
PASSWORD: 'Passwort',
|
||||
DASHBOARD: 'Kontrollzentrum',
|
||||
SETTINGS: 'Einstellungen',
|
||||
SAVED: 'gespeichert',
|
||||
HELP: 'Hilfe',
|
||||
LOGGED_IN: 'Eingeloggt als {name}',
|
||||
PLEASE_SIGNIN: 'Bitte einloggen, um fortzufahren',
|
||||
UPLOAD_SUCCESSFUL: 'Hochladen erfolgreich',
|
||||
DOWNLOAD_SUCCESSFUL: 'Herunterladen erfolgreich',
|
||||
INVALID_LOGIN: 'Ungültige Login Daten',
|
||||
NETWORK: 'Netzwerk',
|
||||
SECURITY: 'Sicherheit',
|
||||
ONOFF_CAP: 'AN/AUS',
|
||||
ONOFF: 'an/aus',
|
||||
TYPE: 'Typ',
|
||||
DESCRIPTION: 'Bezeichnung',
|
||||
ENTITIES: 'Entitäten',
|
||||
REFRESH: 'Aktualisieren',
|
||||
EXPORT: 'Exportieren',
|
||||
DEVICE_DETAILS: 'Geräte Details',
|
||||
BRAND: 'Marke',
|
||||
ENTITY_NAME: 'Entitätsname',
|
||||
VALUE: 'Wert',
|
||||
SHOW_FAV: 'nur Favoriten anzeigen',
|
||||
DEVICE_SENSOR_DATA: 'Geräte- und Sensordaten',
|
||||
DEVICES_SENSORS: 'Geräte & Sensoren',
|
||||
ATTACHED_SENSORS: 'Angeschlossene EMS-ESP Sensoren',
|
||||
RUN_COMMAND: 'Befehl ausführen',
|
||||
CHANGE_VALUE: 'Wert ändern',
|
||||
CANCEL: 'Abbrechen',
|
||||
RESET: 'Zurücksetzen',
|
||||
SEND: 'Senden',
|
||||
SAVE: 'Speichern',
|
||||
REMOVE: 'Entfernen',
|
||||
PROBLEM_UPDATING: 'Problem beim Aktualisieren',
|
||||
PROBLEM_LOADING: 'Problem beim Laden',
|
||||
ACCESS_DENIED: 'Zugriff abgelehnt',
|
||||
ANALOG_SENSOR: 'Analogsensor',
|
||||
ANALOG_SENSORS: 'Analogsensoren',
|
||||
UPDATED: 'Aktualisiert',
|
||||
UPDATE: 'Aktualisieren',
|
||||
REMOVED: 'Entfernt',
|
||||
DELETION: 'Löschung',
|
||||
OFFSET: 'Addition',
|
||||
FACTOR: 'Faktor',
|
||||
FREQ: 'Frequenz',
|
||||
STARTVALUE: 'Startwert',
|
||||
WARN_GPIO: 'Warnung: Vorsicht bei der korrekten Wahl des GPIO!',
|
||||
EDIT: 'Editiere',
|
||||
TEMP_SENSOR: 'Temperatursensor',
|
||||
TEMP_SENSORS: 'Temperatursensoren',
|
||||
WRITE_COMMAND: 'Befehl schreiben {cmd}',
|
||||
EMS_BUS_WARNING:
|
||||
'EMS-Bus getrennt. Wenn diese Warnung nach einigen Sekunden immer noch besteht, überprüfen Sie bitte die Einstellungen und das Board-Profil',
|
||||
EMS_BUS_SCANNING: 'Suche nach EMS Geräten...',
|
||||
CONNECTED: 'Verbunden',
|
||||
TX_ISSUES: 'Tx-Probleme - versuchen Sie einen anderen Tx-Modus',
|
||||
DISCONNECTED: 'Getrennt',
|
||||
EMS_SCAN: 'Möchten Sie wirklich eine vollständige Gerätesuche des EMS-Busses starten?',
|
||||
EMS_BUS_STATUS: 'EMS-Busstatus',
|
||||
ACTIVE_DEVICES: 'Aktive Geräte und Sensoren',
|
||||
DEVICE: 'Gerät',
|
||||
SUCCESS: 'ERFOLG',
|
||||
FAIL: 'FEHLER',
|
||||
QUALITY: 'QUALITÄT',
|
||||
SCAN_DEVICES: 'Nach neuen Geräten suchen',
|
||||
EMS_BUS_STATUS_TITLE: 'EMS-Bus- und Aktivitätsstatus',
|
||||
SCAN: 'Suche',
|
||||
STATUS_NAMES: [
|
||||
'EMS-Telegramme empfangen (Rx)',
|
||||
'EMS-Telegramme gelesen (Tx)',
|
||||
'EMS-Telegramme geschrieben (Tx)',
|
||||
'Temperatursensoren gelesen',
|
||||
'Analogsensoren gelesen',
|
||||
'MQTT-Nachrichten gesendet',
|
||||
'API-Aufrufe',
|
||||
'Syslog-Mitteilungen'
|
||||
],
|
||||
NUM_DEVICES: '{num} Gerät{{e}}',
|
||||
NUM_TEMP_SENSORS: '{num} Temperatursensor{{en}}',
|
||||
NUM_ANALOG_SENSORS: '{num} Analogsensor{{en}}',
|
||||
NUM_DAYS: '{num} Tag{{e}}',
|
||||
NUM_SECONDS: '{num} Sekunde{{n}}',
|
||||
NUM_HOURS: '{num} Stunde{{n}}',
|
||||
NUM_MINUTES: '{num} Minute{{n}}',
|
||||
APPLICATION_SETTINGS: 'Anwendungseinstellungen',
|
||||
CUSTOMIZATION: 'Anpassungen',
|
||||
APPLICATION_RESTARTING: 'EMS-ESP startet neu',
|
||||
BOARD_PROFILE_TEXT:
|
||||
'Wählen Sie ein vorkonfiguriertes Platinenprofil aus der Liste unten aus oder wählen Sie "Custom", um Ihre eigenen Hardwareeinstellungen zu konfigurieren',
|
||||
BOARD_PROFILE: 'Platinenprofil',
|
||||
BUTTON: 'Taste',
|
||||
TEMPERATURE: 'Temperatur',
|
||||
DISABLED: 'deaktiviert',
|
||||
GENERAL_OPTIONS: 'Allgemeine Optionen',
|
||||
LANGUAGE_ENTITIES: 'Sprache (für Geräteentitäten)',
|
||||
HIDE_LED: 'LED ausblenden',
|
||||
ENABLE_TELNET: 'Aktiviere Telnet Konsole',
|
||||
ENABLE_ANALOG: 'Aktiviere Analogsensorsen',
|
||||
CONVERT_FAHRENHEIT: 'Konvertiere Temperaturwerte in Fahrenheit',
|
||||
BYPASS_TOKEN: 'Zugriffstoken-Autorisierung bei API-Aufrufen umgehen',
|
||||
READONLY: 'Nur-Lese-Modus aktivieren (blockiert alle ausgehenden EMS Tx Write-Befehle)',
|
||||
UNDERCLOCK_CPU: 'CPU-Geschwindigkeit untertakten',
|
||||
ENABLE_SHOWER_TIMER: 'Duschtimer aktivieren',
|
||||
ENABLE_SHOWER_ALERT: 'Duschalarm aktivieren',
|
||||
TRIGGER_TIME: 'Auslösezeit',
|
||||
COLD_SHOT_DURATION: 'Kaltschussdauer',
|
||||
FORMATTING_OPTIONS: 'Formatierungsoptionen',
|
||||
BOOLEAN_FORMAT_DASHBOARD: 'Boolsches Format für Web',
|
||||
BOOLEAN_FORMAT_API: 'Boolesches Format API/MQTT',
|
||||
ENUM_FORMAT: 'Enum Format API/MQTT',
|
||||
INDEX: 'Index',
|
||||
ENABLE_PARASITE: 'Parasitäre Stomversorgung',
|
||||
LOGGING: 'Protokollierung',
|
||||
LOG_HEX: 'EMS-Telegramme hexadezimal protokollieren',
|
||||
ENABLE_SYSLOG: 'Syslog aktivieren',
|
||||
MARK_INTERVAL: 'Intervallmarke',
|
||||
SECONDS: 'Sekunden',
|
||||
MINUTES: 'Minuten',
|
||||
HOURS: 'Stunden',
|
||||
RESTART: 'Neu starten',
|
||||
RESTART_TEXT: 'EMS-ESP muss neu gestartet werden, um geänderte Systemeinstellungen zu übernehmen',
|
||||
COMMAND: 'Befehl',
|
||||
CUSTOMIZATIONS_RESTART: 'Alle Anpassungen wurden entfernt. Neustart...',
|
||||
CUSTOMIZATIONS_FULL: 'Ausgewählte Entitäten haben das Limit überschritten. Bitte stapelweise speichern',
|
||||
CUSTOMIZATIONS_SAVED: 'Anpassungen gespeichert',
|
||||
CUSTOMIZATIONS_HELP_1: 'Wählen Sie ein Gerät aus und passen Sie die Entitäten mithilfe der Optionen an',
|
||||
CUSTOMIZATIONS_HELP_2: 'als Favorit markieren',
|
||||
CUSTOMIZATIONS_HELP_3: 'Schreibaktion deaktivieren',
|
||||
CUSTOMIZATIONS_HELP_4: 'von MQTT und API ausschließen',
|
||||
CUSTOMIZATIONS_HELP_5: 'Aus dem Kontrollzentrum ausblenden',
|
||||
SELECT_DEVICE: 'Wählen Sie ein Gerät aus',
|
||||
SET_ALL: 'setzen Sie alle',
|
||||
OPTIONS: 'Optionen',
|
||||
NAME: 'Name',
|
||||
CUSTOMIZATIONS_RESET:
|
||||
'Möchten Sie wirklich alle Anpassungen entfernen, einschließlich der benutzerdefinierten Einstellungen der Temperatur- und Analogsensoren?',
|
||||
DEVICE_ENTITIES: 'Geräteentitäten',
|
||||
USER_CUSTOMIZATION: 'Benutzeranpassung',
|
||||
SUPPORT_INFORMATION: 'Unterstützende Informationen',
|
||||
CLICK_HERE: 'Hier klicken',
|
||||
HELP_INFORMATION_1: 'EMS-ESP Konfigurationsanweisungen und mehr finden Sie im Online-Wiki',
|
||||
HELP_INFORMATION_2: 'Für einen Live-Community-Chat besuchen Sie unseren Discord-Server',
|
||||
HELP_INFORMATION_3: 'Um neue Funktionen anzufragen oder Fehler zu melden, eröffnen Sie ein Issue auf Github',
|
||||
HELP_INFORMATION_4: 'Bitte laden Sie die System-Details und hängen Sie sie an das Support-Issue an. ',
|
||||
HELP_INFORMATION_5:
|
||||
'EMS-ESP ist ein freies Open-Source Projekt. Bitte unterstützen Sie die zukünftige Entwicklung mit einem "Star" auf Github!',
|
||||
SUPPORT_INFO: 'Support Info',
|
||||
UPLOAD: 'Hochladen',
|
||||
DOWNLOAD: 'Herunterladen',
|
||||
ABORTED: 'abgebrochen',
|
||||
FAILED: 'gescheitert',
|
||||
SUCCESSFUL: 'erfolgreich',
|
||||
SYSTEM: 'System',
|
||||
LOG: 'Log',
|
||||
STATUS: 'Status',
|
||||
UPLOAD_DOWNLOAD: 'Hoch-/Herunterladen',
|
||||
SYSTEM_VERSION_RUNNING: 'Sie verwenden die Version',
|
||||
SYSTEM_APPLY_FIRMWARE: 'um die neue Firmware anzuwenden',
|
||||
CLOSE: 'Schließen',
|
||||
USE: 'Verwenden',
|
||||
FACTORY_RESET: 'Werkseinstellung',
|
||||
SYSTEM_FACTORY_TEXT: 'EMS-ESP wurde auf Werkseinstellung gesetzt und startet als Zugangspunkt neu',
|
||||
SYSTEM_FACTORY_TEXT_DIALOG: 'Sind Sie sicher alle Einstellungen auf Werkseinstellung zu setzen?',
|
||||
VERSION_CHECK: 'Versionsprüfung',
|
||||
THE_LATEST: 'Die neueste',
|
||||
PLATFORM: 'Platform (Platform / SDK)',
|
||||
UPTIME: 'System Betriebszeit',
|
||||
CPU_FREQ: 'CPU Frequenz',
|
||||
HEAP: 'RAM Speicher (Frei / Max Belegt)',
|
||||
PSRAM: 'PSRAM (Größe / Frei)',
|
||||
FLASH: 'Flash Speicher (Größe / Geschwindigkeit)',
|
||||
FILESYSTEM: 'Dateisystem (Genutzt / Gesamt)',
|
||||
BUFFER_SIZE: 'Puffergröße',
|
||||
COMPACT: 'Kompakte Darstellung',
|
||||
ENABLE_OTA: 'OTA Updates verwenden',
|
||||
DOWNLOAD_CUSTOMIZATION_TEXT: 'Herunterladen der individuellen Entitätsanpassungen',
|
||||
DOWNLOAD_SETTINGS_TEXT:
|
||||
'Herunterladen der Anwendungseinstellungen. Vorsicht beim Teilen der Einstellungen, da sie Passwörter und andere sensitive Einstellungen enthalten',
|
||||
UPLOAD_TEXT: 'Hochladen von neuer Firmware (.bin), Geräte- oder Entitätseinstellungen (.json)',
|
||||
UPLOADING: 'Hochladen',
|
||||
UPLOAD_DROP_TEXT: 'Klicken Sie hier, oder ziehen eine Datei hierher',
|
||||
ERROR: 'Unerwarteter Fehler, bitter versuchen Sie es erneut',
|
||||
TIME_SET: 'Zeit gesetzt',
|
||||
MANAGE_USERS: 'Nutzerverwaltung',
|
||||
IS_ADMIN: 'ist Admin',
|
||||
USER_WARNING: 'Sie müssen mindestens einen Admin-Nutzer konfigurieren',
|
||||
ADD: 'Hinzufügen',
|
||||
ACCESS_TOKEN_FOR: 'Zugangs-Token für',
|
||||
ACCESS_TOKEN_TEXT:
|
||||
'Dieses Token ist für REST API Aufrufe bestimmt, die eine Authentifizierung benötigen. Es kann entweder als Bearer Token im `Authorization-Header` oder in der Access_Token URL verwendet werden.',
|
||||
GENERATING_TOKEN: 'Erzeuge Token',
|
||||
USER: 'Nutzer',
|
||||
MODIFY: 'Ändern',
|
||||
SU_TEXT:
|
||||
'Das su (super user) Passwort wird zum Signieren der Authentifikations-Tokens verwendet und ermöglicht Admin-Berechtigung in der Konsole.',
|
||||
NOT_ENABLED: 'Nicht aktiviert',
|
||||
ERRORS: 'Fehler',
|
||||
DISCONNECT_REASON: 'Grund der Verbindungsunterbrechung',
|
||||
ENABLE_MQTT: 'MQTT aktivieren',
|
||||
OPTIONAL: 'Optional',
|
||||
FORMATTING: 'Formattierung',
|
||||
FORMAT: 'Format',
|
||||
MQTT_NEST_1: 'Als Nester in in einem Gesamttopic',
|
||||
MQTT_NEST_2: 'Als einzelne Topics',
|
||||
MQTT_RESPONSE: 'Veröffentliche die Kommandoantwort als `response` Topic',
|
||||
MQTT_PUBLISH_TEXT_1: 'Veröffentliche einzelne Werte bei Veränderung als eigene Topics',
|
||||
MQTT_PUBLISH_TEXT_2: 'Veröffentliche als Kommando-Topic (ioBroker)',
|
||||
MQTT_PUBLISH_TEXT_3: 'Aktiviere `MQTT Discovery` (Home Assistant, Domoticz)',
|
||||
MQTT_PUBLISH_TEXT_4: 'Prefix für die `Discovery`-Topics',
|
||||
MQTT_PUBLISH_INTERVALS: 'Veröffentlichungs-Intervalle',
|
||||
MQTT_INT_BOILER: 'Boiler und Wärmepumpen',
|
||||
MQTT_INT_THERMOSTATS: 'Thermostate',
|
||||
MQTT_INT_SOLAR: 'Solarmodule',
|
||||
MQTT_INT_MIXER: 'Mischermodule',
|
||||
DEFAULT: 'Standard',
|
||||
MQTT_CLEAN_SESSION: 'Setze `Clean Session`',
|
||||
MQTT_RETAIN_FLAG: 'Setze `Retain flag` immer',
|
||||
INACTIVE: 'Inaktiv',
|
||||
ACTIVE: 'Aktiv',
|
||||
UNKNOWN: 'Unbekannt',
|
||||
SET_TIME: 'Zeiteinstellung',
|
||||
SET_TIME_TEXT: 'Geben Sie das lokale Datum und die Zeit ein',
|
||||
LOCAL_TIME: 'Lokalzeit',
|
||||
UTC_TIME: 'UTC Zeit',
|
||||
ENABLE_NTP: 'Aktiviere NTP',
|
||||
TIME_ZONE: 'Zeitzone',
|
||||
ACCESS_POINT: 'Zugangspunkt',
|
||||
AP_PROVIDE: 'Aktiviere Zugangspunkt',
|
||||
AP_PROVIDE_TEXT_1: 'Immer',
|
||||
AP_PROVIDE_TEXT_2: 'Wenn WiFi nicht verbunden',
|
||||
AP_PROVIDE_TEXT_3: 'Niemals',
|
||||
AP_PREFERRED_CHANNEL: 'Bevorzugter Kanal',
|
||||
AP_HIDE_SSID: 'Verstecke SSID',
|
||||
NETWORK_SCAN: 'Suche nach WiFi Netzwerken',
|
||||
IDLE: 'Leerlauf',
|
||||
LOST: 'Verloren',
|
||||
SCANNING: 'Suche',
|
||||
SCAN_AGAIN: 'Erneute Suche',
|
||||
NETWORK_SCANNER: 'Netzwerk Suche',
|
||||
NETWORK_NO_WIFI: 'Keine WiFi Netzwerke gefunden',
|
||||
NETWORK_BLANK_SSID: 'Freilassen um WiFi zu deaktivieren',
|
||||
POWER: 'Leistung',
|
||||
NETWORK_DISABLE_SLEEP: 'Deaktiviere WiFi Schlafmodus',
|
||||
NETWORK_LOW_BAND: 'Verwende niedrige WiFi Bandbreite',
|
||||
NETWORK_USE_DNS: 'Aktiviere mDNS Service',
|
||||
NETWORK_ENABLE_IPV6: 'Aktiviere IPv6 Unterstützung',
|
||||
NETWORK_FIXED_IP: 'Feste IP Addresse',
|
||||
ADMIN: 'Administrator',
|
||||
GUEST: 'Gast',
|
||||
NEW: 'Neuer',
|
||||
RENAME: 'Ändere'
|
||||
};
|
||||
|
||||
export default de;
|
||||
265
interface/src/i18n/en/index.ts
Normal file
265
interface/src/i18n/en/index.ts
Normal file
@@ -0,0 +1,265 @@
|
||||
import type { BaseTranslation } from '../i18n-types';
|
||||
|
||||
const en: BaseTranslation = {
|
||||
LANGUAGE: 'Language',
|
||||
RETRY: 'Retry',
|
||||
LOADING: 'Loading',
|
||||
IS_REQUIRED: 'is required',
|
||||
SIGN_IN: 'Sign In',
|
||||
SIGN_OUT: 'Sign Out',
|
||||
USERNAME: 'Username',
|
||||
PASSWORD: 'Password',
|
||||
DASHBOARD: 'Dashboard',
|
||||
SETTINGS: 'Settings',
|
||||
SAVED: 'saved',
|
||||
HELP: 'Help',
|
||||
LOGGED_IN: 'Logged in as {name}',
|
||||
PLEASE_SIGNIN: 'Please sign in to continue',
|
||||
UPLOAD_SUCCESSFUL: 'Upload successful',
|
||||
DOWNLOAD_SUCCESSFUL: 'Download successful',
|
||||
INVALID_LOGIN: 'Invalid login details',
|
||||
NETWORK: 'Network',
|
||||
SECURITY: 'Security',
|
||||
ONOFF_CAP: 'ON/OFF',
|
||||
ONOFF: 'on/off',
|
||||
TYPE: 'Type',
|
||||
DESCRIPTION: 'Description',
|
||||
ENTITIES: 'Entities',
|
||||
REFRESH: 'Refresh',
|
||||
EXPORT: 'Export',
|
||||
DEVICE_DETAILS: 'Device Details',
|
||||
BRAND: 'Brand',
|
||||
ENTITY_NAME: 'Entity Name',
|
||||
VALUE: 'Value',
|
||||
SHOW_FAV: 'only show favorites',
|
||||
DEVICE_SENSOR_DATA: 'Device and Sensor Data',
|
||||
DEVICES_SENSORS: 'Devices & Sensors',
|
||||
ATTACHED_SENSORS: 'Attached EMS-ESP Sensors',
|
||||
RUN_COMMAND: 'Call Command',
|
||||
CHANGE_VALUE: 'Change Value',
|
||||
CANCEL: 'Cancel',
|
||||
RESET: 'Reset',
|
||||
SEND: 'Send',
|
||||
SAVE: 'Save',
|
||||
REMOVE: 'Remove',
|
||||
PROBLEM_UPDATING: 'Problem updating',
|
||||
PROBLEM_LOADING: 'Problem loading',
|
||||
ACCESS_DENIED: 'Access Denied',
|
||||
ANALOG_SENSOR: 'Analog Sensor',
|
||||
ANALOG_SENSORS: 'Analog Sensors',
|
||||
UPDATED: 'Updated',
|
||||
UPDATE: 'Update',
|
||||
REMOVED: 'Removed',
|
||||
DELETION: 'Deletion',
|
||||
OFFSET: 'Offset',
|
||||
FACTOR: 'Factor',
|
||||
FREQ: 'Frequency',
|
||||
STARTVALUE: 'Start value',
|
||||
WARN_GPIO: 'Warning: be careful when assigning a GPIO!',
|
||||
EDIT: 'Edit',
|
||||
TEMP_SENSOR: 'Temperature Sensor',
|
||||
TEMP_SENSORS: 'Temperature Sensors',
|
||||
WRITE_COMMAND: 'Write command {cmd}',
|
||||
EMS_BUS_WARNING:
|
||||
'EMS bus disconnected. If this warning still persists after a few seconds please check settings and board profile',
|
||||
EMS_BUS_SCANNING: 'Scanning for EMS devices...',
|
||||
CONNECTED: 'Connected',
|
||||
TX_ISSUES: 'Tx issues - try a different Tx Mode',
|
||||
DISCONNECTED: 'Disconnected',
|
||||
EMS_SCAN: 'Are you sure you want to initiate a full device scan of the EMS bus?',
|
||||
EMS_BUS_STATUS: 'EMS Bus Status',
|
||||
ACTIVE_DEVICES: 'Active Devices & Sensors',
|
||||
DEVICE: 'Device',
|
||||
SUCCESS: 'SUCCESS',
|
||||
FAIL: 'FAIL',
|
||||
QUALITY: 'QUALITY',
|
||||
SCAN_DEVICES: 'Scan for new devices',
|
||||
EMS_BUS_STATUS_TITLE: 'EMS Bus & Activity Status',
|
||||
SCAN: 'Scan',
|
||||
STATUS_NAMES: [
|
||||
'EMS Telegrams Received (Rx)',
|
||||
'EMS Reads (Tx)',
|
||||
'EMS Writes (Tx)',
|
||||
'Temperature Sensor Reads',
|
||||
'Analog Sensor Reads',
|
||||
'MQTT Publishes',
|
||||
'API Calls',
|
||||
'Syslog Messages'
|
||||
],
|
||||
NUM_DEVICES: '{num} Device{{s}}',
|
||||
NUM_TEMP_SENSORS: '{num} Temperature Sensor{{s}}',
|
||||
NUM_ANALOG_SENSORS: '{num} Analog Sensor{{s}}',
|
||||
NUM_DAYS: '{num} Day{{s}}',
|
||||
NUM_SECONDS: '{num} Second{{s}}',
|
||||
NUM_HOURS: '{num} Hour{{s}}',
|
||||
NUM_MINUTES: '{num} Minute{{s}}',
|
||||
APPLICATION_SETTINGS: 'Application Settings',
|
||||
CUSTOMIZATION: 'Customization',
|
||||
APPLICATION_RESTARTING: 'EMS-ESP is restarting',
|
||||
BOARD_PROFILE_TEXT:
|
||||
'Select a pre-configured interface board profile from the list below or choose Custom to configure your own hardware settings',
|
||||
BOARD_PROFILE: 'Board Profile',
|
||||
BUTTON: 'Button',
|
||||
TEMPERATURE: 'Temperature',
|
||||
DISABLED: 'disabled',
|
||||
GENERAL_OPTIONS: 'General Options',
|
||||
LANGUAGE_ENTITIES: 'Language (for device entities)',
|
||||
HIDE_LED: 'Hide LED',
|
||||
ENABLE_TELNET: 'Enable Telnet Console',
|
||||
ENABLE_ANALOG: 'Enable Analog Sensors',
|
||||
CONVERT_FAHRENHEIT: 'Convert temperature values to Fahrenheit',
|
||||
BYPASS_TOKEN: 'Bypass Access Token authorization on API calls',
|
||||
READONLY: 'Enable read-only mode (blocks all outgoing EMS Tx Write commands)',
|
||||
UNDERCLOCK_CPU: 'Underclock CPU speed',
|
||||
ENABLE_SHOWER_TIMER: 'Enable Shower Timer',
|
||||
ENABLE_SHOWER_ALERT: 'Enable Shower Alert',
|
||||
TRIGGER_TIME: 'Trigger Time',
|
||||
COLD_SHOT_DURATION: 'Cold Shot Duration',
|
||||
FORMATTING_OPTIONS: 'Formatting Options',
|
||||
BOOLEAN_FORMAT_DASHBOARD: 'Boolean Format Dashboard',
|
||||
BOOLEAN_FORMAT_API: 'Boolean Format API/MQTT',
|
||||
ENUM_FORMAT: 'Enum Format API/MQTT',
|
||||
INDEX: 'Index',
|
||||
ENABLE_PARASITE: 'Enable parasite power',
|
||||
LOGGING: 'Logging',
|
||||
LOG_HEX: 'Log EMS telegrams in hexadecimal',
|
||||
ENABLE_SYSLOG: 'Enable Syslog',
|
||||
MARK_INTERVAL: 'Mark Interval',
|
||||
SECONDS: 'seconds',
|
||||
MINUTES: 'minutes',
|
||||
RESTART: 'Restart',
|
||||
HOURS: 'hours',
|
||||
RESTART_TEXT: 'EMS-ESP needs to be restarted to apply changed system settings',
|
||||
COMMAND: 'Command',
|
||||
CUSTOMIZATIONS_RESTART: 'All customizations have been removed. Restarting...',
|
||||
CUSTOMIZATIONS_FULL: 'Selected entities exceeded limit. Please save in batches',
|
||||
CUSTOMIZATIONS_SAVED: 'Customizations saved',
|
||||
CUSTOMIZATIONS_HELP_1: 'Select a device and customize the entities options or click to rename',
|
||||
CUSTOMIZATIONS_HELP_2: 'mark as favorite',
|
||||
CUSTOMIZATIONS_HELP_3: 'disable write action',
|
||||
CUSTOMIZATIONS_HELP_4: 'exclude from MQTT and API',
|
||||
CUSTOMIZATIONS_HELP_5: 'hide from Dashboard',
|
||||
SELECT_DEVICE: 'Select a device',
|
||||
SET_ALL: 'set all',
|
||||
OPTIONS: 'Options',
|
||||
NAME: 'Name',
|
||||
CUSTOMIZATIONS_RESET:
|
||||
'Are you sure you want remove all customizations including the custom settings of the Temperature and Analog sensors?',
|
||||
DEVICE_ENTITIES: 'Device Entities',
|
||||
USER_CUSTOMIZATION: 'User Customization',
|
||||
SUPPORT_INFORMATION: 'Support Information',
|
||||
CLICK_HERE: 'Click Here',
|
||||
HELP_INFORMATION_1: 'Visit the online wiki to get instructions on how to configure EMS-ESP',
|
||||
HELP_INFORMATION_2: 'For live community chat join our Discord server',
|
||||
HELP_INFORMATION_3: 'To request a feature or report a bug',
|
||||
HELP_INFORMATION_4: 'remember to download and attach your system information for a faster response when reporting an issue',
|
||||
HELP_INFORMATION_5:
|
||||
"EMS-ESP is a free and open-source project. Please support its future development by giving it a star on Github!",
|
||||
SUPPORT_INFO: 'Support Info',
|
||||
UPLOAD: 'Upload',
|
||||
DOWNLOAD: 'Download',
|
||||
ABORTED: 'aborted',
|
||||
FAILED: 'failed',
|
||||
SUCCESSFUL: 'successful',
|
||||
SYSTEM: 'System',
|
||||
LOG: 'Log',
|
||||
STATUS: 'Status',
|
||||
UPLOAD_DOWNLOAD: 'Upload/Download',
|
||||
SYSTEM_VERSION_RUNNING: 'You are currently running version',
|
||||
SYSTEM_APPLY_FIRMWARE: 'to apply the new firmware',
|
||||
CLOSE: 'Close',
|
||||
USE: 'Use',
|
||||
FACTORY_RESET: 'Factory Reset',
|
||||
SYSTEM_FACTORY_TEXT: 'Device has been factory reset and will now restart',
|
||||
SYSTEM_FACTORY_TEXT_DIALOG: 'Are you sure you want to reset the device to its factory defaults?',
|
||||
VERSION_CHECK: 'Version Check',
|
||||
THE_LATEST: 'The latest',
|
||||
PLATFORM: 'Device (Platform / SDK)',
|
||||
UPTIME: 'System Uptime',
|
||||
CPU_FREQ: 'CPU Frequency',
|
||||
HEAP: 'Heap (Free / Max Alloc)',
|
||||
PSRAM: 'PSRAM (Size / Free)',
|
||||
FLASH: 'Flash Chip (Size / Speed)',
|
||||
FILESYSTEM: 'File System (Used / Total)',
|
||||
BUFFER_SIZE: 'Buffer Size',
|
||||
COMPACT: 'Compact',
|
||||
ENABLE_OTA: 'Enable OTA Updates',
|
||||
DOWNLOAD_CUSTOMIZATION_TEXT: 'Download the entity customizations',
|
||||
DOWNLOAD_SETTINGS_TEXT:
|
||||
'Download the application settings. Be careful when sharing your settings as this file contains passwords and other sensitive system information',
|
||||
UPLOAD_TEXT: 'Upload a new firmware (.bin) file, settings or customizations (.json) file below',
|
||||
UPLOADING: 'Uploading',
|
||||
UPLOAD_DROP_TEXT: 'Drop file or click here',
|
||||
ERROR: 'Unexpected Error, please try again',
|
||||
TIME_SET: 'Time set',
|
||||
MANAGE_USERS: 'Manage Users',
|
||||
IS_ADMIN: 'is Admin',
|
||||
USER_WARNING: 'You must have at least one admin user configured',
|
||||
ADD: 'Add',
|
||||
ACCESS_TOKEN_FOR: 'Access Token for',
|
||||
ACCESS_TOKEN_TEXT:
|
||||
'The token below is used with REST API calls that require authorization. It can be passed either as a Bearer token in the Authorization header or in the access_token URL query parameter.',
|
||||
GENERATING_TOKEN: 'Generating token',
|
||||
USER: 'User',
|
||||
MODIFY: 'Modify',
|
||||
SU_TEXT:
|
||||
'The su (super user) password is used to sign authentication tokens and also enable admin privileges within the Console.',
|
||||
NOT_ENABLED: 'Not enabled',
|
||||
ERRORS: 'Errors',
|
||||
DISCONNECT_REASON: 'Disconnect Reason',
|
||||
ENABLE_MQTT: 'Enable MQTT',
|
||||
OPTIONAL: 'Optional',
|
||||
FORMATTING: 'Formatting',
|
||||
FORMAT: 'Format',
|
||||
MQTT_NEST_1: 'Nested in a single topic',
|
||||
MQTT_NEST_2: 'As individual topics',
|
||||
MQTT_RESPONSE: 'Publish command output to a `response` topic',
|
||||
MQTT_PUBLISH_TEXT_1: 'Publish single value topics on change',
|
||||
MQTT_PUBLISH_TEXT_2: 'Publish to command topics (ioBroker)',
|
||||
MQTT_PUBLISH_TEXT_3: 'Enable MQTT Discovery (Home Assistant, Domoticz)',
|
||||
MQTT_PUBLISH_TEXT_4: 'Prefix for the Discovery topics',
|
||||
MQTT_PUBLISH_INTERVALS: 'Publish Intervals',
|
||||
MQTT_INT_BOILER: 'Boilers and Heat Pumps',
|
||||
MQTT_INT_THERMOSTATS: 'Thermostats',
|
||||
MQTT_INT_SOLAR: 'Solar Modules',
|
||||
MQTT_INT_MIXER: 'Mixer Modules',
|
||||
DEFAULT: 'Default',
|
||||
MQTT_CLEAN_SESSION: 'Set Clean Session',
|
||||
MQTT_RETAIN_FLAG: 'Always set Retain flag',
|
||||
INACTIVE: 'Inactive',
|
||||
ACTIVE: 'Active',
|
||||
UNKNOWN: 'Unknown',
|
||||
SET_TIME: 'Set Time',
|
||||
SET_TIME_TEXT: 'Enter local date and time below to set the time',
|
||||
LOCAL_TIME: 'Local Time',
|
||||
UTC_TIME: 'UTC Time',
|
||||
ENABLE_NTP: 'Enable NTP',
|
||||
TIME_ZONE: 'Time Zone',
|
||||
ACCESS_POINT: 'Access Point',
|
||||
AP_PROVIDE: 'Enable Access Point',
|
||||
AP_PROVIDE_TEXT_1: 'always',
|
||||
AP_PROVIDE_TEXT_2: 'when WiFi is disconnected',
|
||||
AP_PROVIDE_TEXT_3: 'never',
|
||||
AP_PREFERRED_CHANNEL: 'Preferred Channel',
|
||||
AP_HIDE_SSID: 'Hide SSID',
|
||||
NETWORK_SCAN: 'Scan WiFi Networks',
|
||||
IDLE: 'Idle',
|
||||
LOST: 'Lost',
|
||||
SCANNING: 'Scanning',
|
||||
SCAN_AGAIN: 'Scan again',
|
||||
NETWORK_SCANNER: 'Network Scanner',
|
||||
NETWORK_NO_WIFI: 'No WiFi networks found',
|
||||
NETWORK_BLANK_SSID: 'leave blank to disable WiFi',
|
||||
POWER: 'Power',
|
||||
NETWORK_DISABLE_SLEEP: 'Disable WiFi Sleep Mode',
|
||||
NETWORK_LOW_BAND: 'Use Lower WiFi Bandwidth',
|
||||
NETWORK_USE_DNS: 'Enable mDNS Service',
|
||||
NETWORK_ENABLE_IPV6: 'Enable IPv6 support',
|
||||
NETWORK_FIXED_IP: 'Use Fixed IP address',
|
||||
ADMIN: 'Admin',
|
||||
GUEST: 'Guest',
|
||||
NEW: 'New',
|
||||
RENAME: 'Rename'
|
||||
};
|
||||
|
||||
export default en;
|
||||
11
interface/src/i18n/formatters.ts
Normal file
11
interface/src/i18n/formatters.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { FormattersInitializer } from 'typesafe-i18n';
|
||||
import type { Locales, Formatters } from './i18n-types';
|
||||
import { date } from 'typesafe-i18n/formatters';
|
||||
|
||||
export const initFormatters: FormattersInitializer<Locales, Formatters> = (locale: Locales) => {
|
||||
const formatters: Formatters = {
|
||||
weekday: date(locale, { weekday: 'long' })
|
||||
};
|
||||
|
||||
return formatters;
|
||||
};
|
||||
16
interface/src/i18n/i18n-react.tsx
Normal file
16
interface/src/i18n/i18n-react.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten.
|
||||
/* eslint-disable */
|
||||
|
||||
import { useContext } from 'react'
|
||||
import { initI18nReact } from 'typesafe-i18n/react'
|
||||
import type { I18nContextType } from 'typesafe-i18n/react'
|
||||
import type { Formatters, Locales, TranslationFunctions, Translations } from './i18n-types'
|
||||
import { loadedFormatters, loadedLocales } from './i18n-util'
|
||||
|
||||
const { component: TypesafeI18n, context: I18nContext } = initI18nReact<Locales, Translations, TranslationFunctions, Formatters>(loadedLocales, loadedFormatters)
|
||||
|
||||
const useI18nContext = (): I18nContextType<Locales, Translations, TranslationFunctions> => useContext(I18nContext)
|
||||
|
||||
export { I18nContext, useI18nContext }
|
||||
|
||||
export default TypesafeI18n
|
||||
2039
interface/src/i18n/i18n-types.ts
Normal file
2039
interface/src/i18n/i18n-types.ts
Normal file
File diff suppressed because it is too large
Load Diff
31
interface/src/i18n/i18n-util.async.ts
Normal file
31
interface/src/i18n/i18n-util.async.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten.
|
||||
/* eslint-disable */
|
||||
|
||||
import { initFormatters } from './formatters'
|
||||
import type { Locales, Translations } from './i18n-types'
|
||||
import { loadedFormatters, loadedLocales, locales } from './i18n-util'
|
||||
|
||||
const localeTranslationLoaders = {
|
||||
de: () => import('./de'),
|
||||
en: () => import('./en'),
|
||||
nl: () => import('./nl'),
|
||||
no: () => import('./no'),
|
||||
pl: () => import('./pl'),
|
||||
se: () => import('./se'),
|
||||
}
|
||||
|
||||
const updateDictionary = (locale: Locales, dictionary: Partial<Translations>) =>
|
||||
loadedLocales[locale] = { ...loadedLocales[locale], ...dictionary }
|
||||
|
||||
export const importLocaleAsync = async (locale: Locales) =>
|
||||
(await localeTranslationLoaders[locale]()).default as unknown as Translations
|
||||
|
||||
export const loadLocaleAsync = async (locale: Locales): Promise<void> => {
|
||||
updateDictionary(locale, await importLocaleAsync(locale))
|
||||
loadFormatters(locale)
|
||||
}
|
||||
|
||||
export const loadAllLocalesAsync = (): Promise<void[]> => Promise.all(locales.map(loadLocaleAsync))
|
||||
|
||||
export const loadFormatters = (locale: Locales): void =>
|
||||
void (loadedFormatters[locale] = initFormatters(locale))
|
||||
34
interface/src/i18n/i18n-util.sync.ts
Normal file
34
interface/src/i18n/i18n-util.sync.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten.
|
||||
/* eslint-disable */
|
||||
|
||||
import { initFormatters } from './formatters'
|
||||
import type { Locales, Translations } from './i18n-types'
|
||||
import { loadedFormatters, loadedLocales, locales } from './i18n-util'
|
||||
|
||||
import de from './de'
|
||||
import en from './en'
|
||||
import nl from './nl'
|
||||
import no from './no'
|
||||
import pl from './pl'
|
||||
import se from './se'
|
||||
|
||||
const localeTranslations = {
|
||||
de,
|
||||
en,
|
||||
nl,
|
||||
no,
|
||||
pl,
|
||||
se,
|
||||
}
|
||||
|
||||
export const loadLocale = (locale: Locales): void => {
|
||||
if (loadedLocales[locale]) return
|
||||
|
||||
loadedLocales[locale] = localeTranslations[locale] as unknown as Translations
|
||||
loadFormatters(locale)
|
||||
}
|
||||
|
||||
export const loadAllLocales = (): void => locales.forEach(loadLocale)
|
||||
|
||||
export const loadFormatters = (locale: Locales): void =>
|
||||
void (loadedFormatters[locale] = initFormatters(locale))
|
||||
37
interface/src/i18n/i18n-util.ts
Normal file
37
interface/src/i18n/i18n-util.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten.
|
||||
/* eslint-disable */
|
||||
|
||||
import { i18n as initI18n, i18nObject as initI18nObject, i18nString as initI18nString } from 'typesafe-i18n'
|
||||
import type { LocaleDetector } from 'typesafe-i18n/detectors'
|
||||
import { detectLocale as detectLocaleFn } from 'typesafe-i18n/detectors'
|
||||
import type { Formatters, Locales, Translations, TranslationFunctions } from './i18n-types'
|
||||
|
||||
export const baseLocale: Locales = 'en'
|
||||
|
||||
export const locales: Locales[] = [
|
||||
'de',
|
||||
'en',
|
||||
'nl',
|
||||
'no',
|
||||
'pl',
|
||||
'se'
|
||||
]
|
||||
|
||||
export const isLocale = (locale: string) => locales.includes(locale as Locales)
|
||||
|
||||
export const loadedLocales = {} as Record<Locales, Translations>
|
||||
|
||||
export const loadedFormatters = {} as Record<Locales, Formatters>
|
||||
|
||||
export const i18nString = (locale: Locales) => initI18nString<Locales, Formatters>(locale, loadedFormatters[locale])
|
||||
|
||||
export const i18nObject = (locale: Locales) =>
|
||||
initI18nObject<Locales, Translations, TranslationFunctions, Formatters>(
|
||||
locale,
|
||||
loadedLocales[locale],
|
||||
loadedFormatters[locale]
|
||||
)
|
||||
|
||||
export const i18n = () => initI18n<Locales, Translations, TranslationFunctions, Formatters>(loadedLocales, loadedFormatters)
|
||||
|
||||
export const detectLocale = (...detectors: LocaleDetector[]) => detectLocaleFn<Locales>(baseLocale, locales, ...detectors)
|
||||
265
interface/src/i18n/nl/index.ts
Normal file
265
interface/src/i18n/nl/index.ts
Normal file
@@ -0,0 +1,265 @@
|
||||
import type { BaseTranslation } from '../i18n-types';
|
||||
|
||||
const nl: BaseTranslation = {
|
||||
LANGUAGE: 'Taal',
|
||||
RETRY: 'Opnieuw proberen',
|
||||
LOADING: 'Laden',
|
||||
IS_REQUIRED: 'is verplicht',
|
||||
SIGN_IN: 'Inloggen',
|
||||
SIGN_OUT: 'Uitloggen',
|
||||
USERNAME: 'Gebruikersnaam',
|
||||
PASSWORD: 'Wachtwoord',
|
||||
DASHBOARD: 'Dashboard',
|
||||
SETTINGS: 'Instellingen',
|
||||
SAVED: 'opgeslagen',
|
||||
HELP: 'Help',
|
||||
LOGGED_IN: 'Ingelogd als {name}',
|
||||
PLEASE_SIGNIN: 'Log in om verder te gaan',
|
||||
UPLOAD_SUCCESSFUL: 'Upload successvol',
|
||||
DOWNLOAD_SUCCESSFUL: 'Download successvol',
|
||||
INVALID_LOGIN: 'Logingegevens fout',
|
||||
NETWORK: 'Netwerk',
|
||||
SECURITY: 'Beveiliging',
|
||||
ONOFF_CAP: 'AAN/UIT',
|
||||
ONOFF: 'aan/uit',
|
||||
TYPE: 'Type',
|
||||
DESCRIPTION: 'Beschrijving',
|
||||
ENTITIES: 'Entiteiten',
|
||||
REFRESH: 'Ververs',
|
||||
EXPORT: 'Export',
|
||||
BRAND: 'Merk',
|
||||
ENTITY_NAME: 'Entiteit',
|
||||
VALUE: 'Waarde',
|
||||
SHOW_FAV: 'alleen favorieten weergeven',
|
||||
DEVICE_SENSOR_DATA: 'Apparaat en Sensor data',
|
||||
DEVICES_SENSORS: 'Apparaten & Sensoren',
|
||||
ATTACHED_SENSORS: 'Aangesloten EMS-ESP sensoren',
|
||||
RUN_COMMAND: 'Call commando',
|
||||
CHANGE_VALUE: 'Wijzig waarde',
|
||||
CANCEL: 'Annuleren',
|
||||
RESET: 'Reset',
|
||||
SEND: 'Verzenden',
|
||||
SAVE: 'Opslaan',
|
||||
REMOVE: 'Verwijderen',
|
||||
PROBLEM_UPDATING: 'Probleem met updaten',
|
||||
PROBLEM_LOADING: 'Probleem met laden',
|
||||
ACCESS_DENIED: 'Toegang geweigerd',
|
||||
ANALOG_SENSOR: 'Analoge sensor',
|
||||
ANALOG_SENSORS: 'Analoge Sensoren',
|
||||
UPDATED: 'Bijgewerkt',
|
||||
UPDATE: 'Bijwerken',
|
||||
REMOVED: 'Verwijderd',
|
||||
DELETION: 'Verwijder',
|
||||
OFFSET: 'Offset',
|
||||
FACTOR: 'Factor',
|
||||
FREQ: 'Frequentie',
|
||||
STARTVALUE: 'Startwaarde',
|
||||
WARN_GPIO: 'Waarschuwing: let op met het koppelen van de juiste GPIO pin!',
|
||||
EDIT: 'Wijzigen',
|
||||
TEMP_SENSOR: 'Temperatuur sensor',
|
||||
TEMP_SENSORS: 'Temperatuur Sensoren',
|
||||
WRITE_COMMAND: 'Schrijf commando {cmd}',
|
||||
EMS_BUS_WARNING:
|
||||
'EMS bus niet gevonden. Als deze waarschuwing blijft staan na een paar seconden dan loop de instellingen na en in het bijzonder het apparaat type profiel na.',
|
||||
EMS_BUS_SCANNING: 'Scannen naar EMS apparaten...',
|
||||
CONNECTED: 'Verbonden',
|
||||
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?',
|
||||
EMS_BUS_STATUS: 'EMS busstatus',
|
||||
ACTIVE_DEVICES: 'Actieve Apparaten & Sensoren',
|
||||
DEVICE: 'Apparaat',
|
||||
SUCCESS: 'SUCCESS',
|
||||
FAIL: 'MISLUKT',
|
||||
QUALITY: 'QUALITEIT',
|
||||
SCAN_DEVICES: 'Scannen naar nieuwe apparaten',
|
||||
EMS_BUS_STATUS_TITLE: 'EMS Bus & Activiteitenstatus',
|
||||
SCAN: 'Scan',
|
||||
STATUS_NAMES: [
|
||||
'EMS Telegrammen ontvangen (Rx)',
|
||||
'EMS Leesopdrachten (Tx)',
|
||||
'EMS Schrijfopdrachten (Tx)',
|
||||
'Temperatuursensoren uitgelezen',
|
||||
'Analoge sensoren uitgelezen',
|
||||
'MQTT publicaties',
|
||||
'API calls',
|
||||
'Syslog berichten'
|
||||
],
|
||||
NUM_DEVICES: '{num} Apparaat{{en}}',
|
||||
NUM_TEMP_SENSORS: '{num} Temperatuursensor{{en}}',
|
||||
NUM_ANALOG_SENSORS: '{num} Analoge sensor{{en}}',
|
||||
NUM_DAYS: '{num} Dag{{en}}',
|
||||
NUM_SECONDS: '{num} Second{{en}}',
|
||||
NUM_HOURS: '{num} Uur{{en}}',
|
||||
NUM_MINUTES: '{num} Minuut{{en}}',
|
||||
APPLICATION_SETTINGS: 'Applicatieinstellingen',
|
||||
CUSTOMIZATION: 'Custom aanpassingen',
|
||||
APPLICATION_RESTARTING: 'EMS-ESP herstarten',
|
||||
BOARD_PROFILE_TEXT:
|
||||
'Selecteer een vooraf ingesteld apparaat profiel uit de lijst of kies Eigen om zelf uw hardware te configureren',
|
||||
BOARD_PROFILE: 'Apparaatprofiel',
|
||||
BUTTON: 'Toets',
|
||||
TEMPERATURE: 'Temperatuur',
|
||||
DISABLED: 'Uitgeschakeld',
|
||||
GENERAL_OPTIONS: 'Algemene Opties',
|
||||
LANGUAGE_ENTITIES: 'Taal (voor apparaat entiteiten)',
|
||||
HIDE_LED: 'Verberg LED',
|
||||
ENABLE_TELNET: 'Activeer Telnet console',
|
||||
ENABLE_ANALOG: 'Activeer analoge sensoren',
|
||||
CONVERT_FAHRENHEIT: 'Converteer temperatuurwaarden naar Fahrenheit',
|
||||
BYPASS_TOKEN: 'Bypass Access Token authorization on API calls',
|
||||
READONLY: 'Activeer read-only modus (blokkeert alle outgaande EMS Tx schrijf commandos)',
|
||||
UNDERCLOCK_CPU: 'Underclock CPU snelheid',
|
||||
ENABLE_SHOWER_TIMER: 'Activeer Douche Timer (tijdmeting)',
|
||||
ENABLE_SHOWER_ALERT: 'Activeer Douchemelding',
|
||||
TRIGGER_TIME: 'Trigger tijd',
|
||||
COLD_SHOT_DURATION: 'Tijd Shot koud water',
|
||||
FORMATTING_OPTIONS: 'Formatteringsopties',
|
||||
BOOLEAN_FORMAT_DASHBOARD: 'Boolean formaat dashboard',
|
||||
BOOLEAN_FORMAT_API: 'Boolean formaat API/MQTT',
|
||||
ENUM_FORMAT: 'Enum formaat API/MQTT',
|
||||
INDEX: 'Index',
|
||||
ENABLE_PARASITE: 'Activeer Dallas parasitaire modus',
|
||||
LOGGING: 'Logging',
|
||||
LOG_HEX: 'Log EMS telegrammen in hexadecimaal',
|
||||
ENABLE_SYSLOG: 'Activeer Syslog',
|
||||
MARK_INTERVAL: 'Markeringsinterval',
|
||||
SECONDS: 'seconden',
|
||||
MINUTES: 'minuten',
|
||||
RESTART: 'Herstarten',
|
||||
HOURS: 'uren',
|
||||
RESTART_TEXT: 'EMS-ESP dient opnieuw gestart te worden om de wijzingen toe te passen',
|
||||
COMMAND: 'Commando',
|
||||
CUSTOMIZATIONS_RESTART: 'Alle custom profielen worden verwijderd. Herstarten...',
|
||||
CUSTOMIZATIONS_FULL: 'Te veel entiteiten geselecteerd. Sla op in delen aub',
|
||||
CUSTOMIZATIONS_SAVED: 'Custom aanpassingen opgeslagen',
|
||||
CUSTOMIZATIONS_HELP_1: 'Selecteer een apparaat en pas de entiteiten aan door middel van de opties',
|
||||
CUSTOMIZATIONS_HELP_2: 'Markeer as favoriet',
|
||||
CUSTOMIZATIONS_HELP_3: 'Zet schrijfacties uit',
|
||||
CUSTOMIZATIONS_HELP_4: 'Uitsluiten van MQTT en API',
|
||||
CUSTOMIZATIONS_HELP_5: 'verberg van het Dashboard',
|
||||
SELECT_DEVICE: 'Selecteer een apparaat',
|
||||
SET_ALL: 'Alles aanzetten',
|
||||
OPTIONS: 'Opties',
|
||||
NAME: 'Naam',
|
||||
CUSTOMIZATIONS_RESET:
|
||||
'Weet je zeker dat je alle custom aanpassingen wilt verwijderen inclusief de custom instellingen voor analoge temperatuursensoren?',
|
||||
DEVICE_ENTITIES: 'Apparaat Entiteiten',
|
||||
USER_CUSTOMIZATION: 'Custom Instellingen',
|
||||
SUPPORT_INFORMATION: 'Support Informatie',
|
||||
CLICK_HERE: 'Klik Hier',
|
||||
HELP_INFORMATION_1: 'Visit the online wiki to get instructions on how to configure EMS-ESP',
|
||||
HELP_INFORMATION_2: 'For live community chat join our Discord server',
|
||||
HELP_INFORMATION_3: 'To request a feature or report a bug',
|
||||
HELP_INFORMATION_4:
|
||||
'remember to download and attach your system information for a faster response when reporting an issue',
|
||||
HELP_INFORMATION_5:
|
||||
"EMS-ESP is a free and open-source project. Please support its future development by giving it a star on Github!",
|
||||
SUPPORT_INFO: 'Support Info',
|
||||
UPLOAD: 'Upload',
|
||||
DOWNLOAD: 'Download',
|
||||
ABORTED: 'afgebroken',
|
||||
FAILED: 'mislukt',
|
||||
SUCCESSFUL: 'successvol',
|
||||
SYSTEM: 'Systeem',
|
||||
LOG: 'Log',
|
||||
STATUS: 'Status',
|
||||
UPLOAD_DOWNLOAD: 'Upload/Download',
|
||||
SYSTEM_VERSION_RUNNING: 'op dit moment draai je versie',
|
||||
SYSTEM_APPLY_FIRMWARE: 'om de nieuwe firmware te activeren',
|
||||
CLOSE: 'Sluiten',
|
||||
USE: 'Gebruik',
|
||||
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?',
|
||||
VERSION_CHECK: 'Versie Check',
|
||||
THE_LATEST: 'De laatste',
|
||||
PLATFORM: 'Apparaat (Platform / SDK)',
|
||||
UPTIME: 'Systeem Uptime',
|
||||
CPU_FREQ: 'CPU Frequency',
|
||||
HEAP: 'Heap (Free / Max Alloc)',
|
||||
PSRAM: 'PSRAM (Size / Free)',
|
||||
FLASH: 'Flash Chip (Size / Speed)',
|
||||
FILESYSTEM: 'File System (Used / Total)',
|
||||
BUFFER_SIZE: 'Buffer Size',
|
||||
COMPACT: 'Compact',
|
||||
ENABLE_OTA: 'Acitveer OTA Updates',
|
||||
DOWNLOAD_CUSTOMIZATION_TEXT: 'Download alle custom instellingen',
|
||||
DOWNLOAD_SETTINGS_TEXT:
|
||||
'Download de applicatie settings. Wees voorzichting met het delen van dit bestand want het bevat o.a. de wachtwoorden in plain text',
|
||||
UPLOAD_TEXT: 'Upload een nieuwe firmware (.bin) file, instellingen of custom instellingen (.json) bestand hieronder',
|
||||
UPLOADING: 'Uploading',
|
||||
UPLOAD_DROP_TEXT: 'Sleep bestand hierheen of klik hier',
|
||||
ERROR: 'Onverwachte fout, probeer opnieuw',
|
||||
TIME_SET: 'Tijd ingesteld',
|
||||
MANAGE_USERS: 'Beheer Gebruikers',
|
||||
IS_ADMIN: 'is Admin',
|
||||
USER_WARNING: 'U dient tenminste 1 admin gebruiker te configureren',
|
||||
ADD: 'Toevoegen',
|
||||
ACCESS_TOKEN_FOR: 'Access Token voor',
|
||||
ACCESS_TOKEN_TEXT:
|
||||
'Het token hieronder wordt gebruikt voor de REST API calls die authorisatie nodig hebben. Het kan zowel als Bearer token in de Authorization header of in acccess_token URL query parameter gebruikt worden',
|
||||
GENERATING_TOKEN: 'Token aan het genereren',
|
||||
USER: 'Gebruiker',
|
||||
MODIFY: 'Aanpassen',
|
||||
SU_TEXT:
|
||||
'Het su (super user) wachtwoord wordt gebruikt om authorisatie tokens te signeren en ook om admin privileges te activeren in de console.',
|
||||
NOT_ENABLED: 'Niet geactiveerd',
|
||||
ERRORS: 'Foutmeldingen',
|
||||
DISCONNECT_REASON: 'Verbinding verbroken vanwege',
|
||||
ENABLE_MQTT: 'Activeer MQTT',
|
||||
OPTIONAL: 'Optioneel',
|
||||
FORMATTING: 'Formatteren',
|
||||
FORMAT: 'Formattering',
|
||||
MQTT_NEST_1: 'Genest in 1 topic',
|
||||
MQTT_NEST_2: 'Als individuele topics',
|
||||
MQTT_RESPONSE: 'Publiceer commando output naar een `response` topic',
|
||||
MQTT_PUBLISH_TEXT_1: 'Publiceer enkele waarde topics on change',
|
||||
MQTT_PUBLISH_TEXT_2: 'Publiceer naar commando topics (ioBroker)',
|
||||
MQTT_PUBLISH_TEXT_3: 'Activeer MQTT Discovery (Home Assistant, Domoticz)',
|
||||
MQTT_PUBLISH_TEXT_4: 'Prefix voor de Discovery topics',
|
||||
MQTT_PUBLISH_INTERVALS: 'Publicatie intervallen',
|
||||
MQTT_INT_BOILER: 'CV ketels en warmtepompen',
|
||||
MQTT_INT_THERMOSTATS: 'Thermostaten',
|
||||
MQTT_INT_SOLAR: 'Solar Modules',
|
||||
MQTT_INT_MIXER: 'Mixer Modules',
|
||||
DEFAULT: 'Default',
|
||||
MQTT_CLEAN_SESSION: 'Set Clean Session',
|
||||
MQTT_RETAIN_FLAG: 'Always set Retain flag',
|
||||
INACTIVE: 'Inactief',
|
||||
ACTIVE: 'Actief',
|
||||
UNKNOWN: 'Onbekend',
|
||||
SET_TIME: 'Tijd instellen',
|
||||
SET_TIME_TEXT: 'Geef de locale datum en tijd in',
|
||||
LOCAL_TIME: 'Locale Tijd',
|
||||
UTC_TIME: 'UTC Tijd',
|
||||
ENABLE_NTP: 'Activeer NTP',
|
||||
TIME_ZONE: 'Tijdzone',
|
||||
ACCESS_POINT: 'Access Point',
|
||||
AP_PROVIDE: 'Activeer Access Point',
|
||||
AP_PROVIDE_TEXT_1: 'altijd',
|
||||
AP_PROVIDE_TEXT_2: 'als WiFi niet is verbonden',
|
||||
AP_PROVIDE_TEXT_3: 'nooit',
|
||||
AP_PREFERRED_CHANNEL: 'Voorkeurskanaal',
|
||||
AP_HIDE_SSID: 'SSID verbergen',
|
||||
NETWORK_SCAN: 'Scan WiFi Networken',
|
||||
IDLE: 'Idle',
|
||||
LOST: 'Verloren',
|
||||
SCANNING: 'Scannen',
|
||||
SCAN_AGAIN: 'Opnieuw scannen',
|
||||
NETWORK_SCANNER: 'Netwerk Scanner',
|
||||
NETWORK_NO_WIFI: 'Geen WiFi networken gevonden',
|
||||
NETWORK_BLANK_SSID: 'laat leeg om WiFi uit te schakelen',
|
||||
POWER: 'Vermogen',
|
||||
NETWORK_DISABLE_SLEEP: 'WiFi Sleep Mode uitzetten',
|
||||
NETWORK_LOW_BAND: 'Lagere WiFi bandbreedte gebruiken',
|
||||
NETWORK_USE_DNS: 'Activeer mDNS Service',
|
||||
NETWORK_ENABLE_IPV6: 'Activeer IPv6 support',
|
||||
NETWORK_FIXED_IP: 'Gebruik vast IP addres',
|
||||
ADMIN: 'Admin',
|
||||
GUEST: 'Gast',
|
||||
NEW: 'Nieuwe',
|
||||
RENAME: 'Hernoem'
|
||||
};
|
||||
|
||||
export default nl;
|
||||
265
interface/src/i18n/no/index.ts
Normal file
265
interface/src/i18n/no/index.ts
Normal file
@@ -0,0 +1,265 @@
|
||||
import type { BaseTranslation } from '../i18n-types';
|
||||
|
||||
const en: BaseTranslation = {
|
||||
LANGUAGE: 'Language',
|
||||
RETRY: 'Retry',
|
||||
LOADING: 'Loading',
|
||||
IS_REQUIRED: 'is required',
|
||||
SIGN_IN: 'Sign In',
|
||||
SIGN_OUT: 'Sign Out',
|
||||
USERNAME: 'Username',
|
||||
PASSWORD: 'Password',
|
||||
DASHBOARD: 'Dashboard',
|
||||
SETTINGS: 'Settings',
|
||||
SAVED: 'saved',
|
||||
HELP: 'Help',
|
||||
LOGGED_IN: 'Logged in as {name}',
|
||||
PLEASE_SIGNIN: 'Please sign in to continue',
|
||||
UPLOAD_SUCCESSFUL: 'Upload successful',
|
||||
DOWNLOAD_SUCCESSFUL: 'Download successful',
|
||||
INVALID_LOGIN: 'Invalid login details',
|
||||
NETWORK: 'Network',
|
||||
SECURITY: 'Security',
|
||||
ONOFF_CAP: 'ON/OFF',
|
||||
ONOFF: 'on/off',
|
||||
TYPE: 'Type',
|
||||
DESCRIPTION: 'Description',
|
||||
ENTITIES: 'Entities',
|
||||
REFRESH: 'Refresh',
|
||||
EXPORT: 'Export',
|
||||
DEVICE_DETAILS: 'Device Details',
|
||||
BRAND: 'Brand',
|
||||
ENTITY_NAME: 'Entity Name',
|
||||
VALUE: 'Value',
|
||||
SHOW_FAV: 'only show favorites',
|
||||
DEVICE_SENSOR_DATA: 'Device and Sensor Data',
|
||||
DEVICES_SENSORS: 'Devices & Sensors',
|
||||
ATTACHED_SENSORS: 'Attached EMS-ESP Sensors',
|
||||
RUN_COMMAND: 'Call Command',
|
||||
CHANGE_VALUE: 'Change Value',
|
||||
CANCEL: 'Cancel',
|
||||
RESET: 'Reset',
|
||||
SEND: 'Send',
|
||||
SAVE: 'Save',
|
||||
REMOVE: 'Remove',
|
||||
PROBLEM_UPDATING: 'Problem updating',
|
||||
PROBLEM_LOADING: 'Problem loading',
|
||||
ACCESS_DENIED: 'Access Denied',
|
||||
ANALOG_SENSOR: 'Analog Sensor',
|
||||
ANALOG_SENSORS: 'Analog Sensors',
|
||||
UPDATED: 'Updated',
|
||||
UPDATE: 'Update',
|
||||
REMOVED: 'Removed',
|
||||
DELETION: 'Deletion',
|
||||
OFFSET: 'Offset',
|
||||
FACTOR: 'Factor',
|
||||
FREQ: 'Frequency',
|
||||
STARTVALUE: 'Start value',
|
||||
WARN_GPIO: 'Warning: be careful when assigning a GPIO!',
|
||||
EDIT: 'Edit',
|
||||
TEMP_SENSOR: 'Temperature Sensor',
|
||||
TEMP_SENSORS: 'Temperature Sensors',
|
||||
WRITE_COMMAND: 'Write command {cmd}',
|
||||
EMS_BUS_WARNING:
|
||||
'EMS bus disconnected. If this warning still persists after a few seconds please check settings and board profile',
|
||||
EMS_BUS_SCANNING: 'Scanning for EMS devices...',
|
||||
CONNECTED: 'Connected',
|
||||
TX_ISSUES: 'Tx issues - try a different Tx Mode',
|
||||
DISCONNECTED: 'Disconnected',
|
||||
EMS_SCAN: 'Are you sure you want to initiate a full device scan of the EMS bus?',
|
||||
EMS_BUS_STATUS: 'EMS Bus Status',
|
||||
ACTIVE_DEVICES: 'Active Devices & Sensors',
|
||||
DEVICE: 'Device',
|
||||
SUCCESS: 'SUCCESS',
|
||||
FAIL: 'FAIL',
|
||||
QUALITY: 'QUALITY',
|
||||
SCAN_DEVICES: 'Scan for new devices',
|
||||
EMS_BUS_STATUS_TITLE: 'EMS Bus & Activity Status',
|
||||
SCAN: 'Scan',
|
||||
STATUS_NAMES: [
|
||||
'EMS Telegrams Received (Rx)',
|
||||
'EMS Reads (Tx)',
|
||||
'EMS Writes (Tx)',
|
||||
'Temperature Sensor Reads',
|
||||
'Analog Sensor Reads',
|
||||
'MQTT Publishes',
|
||||
'API Calls',
|
||||
'Syslog Messages'
|
||||
],
|
||||
NUM_DEVICES: '{num} Device{{s}}',
|
||||
NUM_TEMP_SENSORS: '{num} Temperature Sensor{{s}}',
|
||||
NUM_ANALOG_SENSORS: '{num} Analog Sensor{{s}}',
|
||||
NUM_DAYS: '{num} Day{{s}}',
|
||||
NUM_SECONDS: '{num} Second{{s}}',
|
||||
NUM_HOURS: '{num} Hour{{s}}',
|
||||
NUM_MINUTES: '{num} Minute{{s}}',
|
||||
APPLICATION_SETTINGS: 'Application Settings',
|
||||
CUSTOMIZATION: 'Customization',
|
||||
APPLICATION_RESTARTING: 'EMS-ESP is restarting',
|
||||
BOARD_PROFILE_TEXT:
|
||||
'Select a pre-configured interface board profile from the list below or choose Custom to configure your own hardware settings',
|
||||
BOARD_PROFILE: 'Board Profile',
|
||||
BUTTON: 'Button',
|
||||
TEMPERATURE: 'Temperature',
|
||||
DISABLED: 'disabled',
|
||||
GENERAL_OPTIONS: 'General Options',
|
||||
LANGUAGE_ENTITIES: 'Language (for device entities)',
|
||||
HIDE_LED: 'Hide LED',
|
||||
ENABLE_TELNET: 'Enable Telnet Console',
|
||||
ENABLE_ANALOG: 'Enable Analog Sensors',
|
||||
CONVERT_FAHRENHEIT: 'Convert temperature values to Fahrenheit',
|
||||
BYPASS_TOKEN: 'Bypass Access Token authorization on API calls',
|
||||
READONLY: 'Enable read-only mode (blocks all outgoing EMS Tx Write commands)',
|
||||
UNDERCLOCK_CPU: 'Underclock CPU speed',
|
||||
ENABLE_SHOWER_TIMER: 'Enable Shower Timer',
|
||||
ENABLE_SHOWER_ALERT: 'Enable Shower Alert',
|
||||
TRIGGER_TIME: 'Trigger Time',
|
||||
COLD_SHOT_DURATION: 'Cold Shot Duration',
|
||||
FORMATTING_OPTIONS: 'Formatting Options',
|
||||
BOOLEAN_FORMAT_DASHBOARD: 'Boolean Format Dashboard',
|
||||
BOOLEAN_FORMAT_API: 'Boolean Format API/MQTT',
|
||||
ENUM_FORMAT: 'Enum Format API/MQTT',
|
||||
INDEX: 'Index',
|
||||
ENABLE_PARASITE: 'Enable parasite power',
|
||||
LOGGING: 'Logging',
|
||||
LOG_HEX: 'Log EMS telegrams in hexadecimal',
|
||||
ENABLE_SYSLOG: 'Enable Syslog',
|
||||
MARK_INTERVAL: 'Mark Interval',
|
||||
SECONDS: 'seconds',
|
||||
MINUTES: 'minutes',
|
||||
RESTART: 'Restart',
|
||||
HOURS: 'hours',
|
||||
RESTART_TEXT: 'EMS-ESP needs to be restarted to apply changed system settings',
|
||||
COMMAND: 'Command',
|
||||
CUSTOMIZATIONS_RESTART: 'All customizations have been removed. Restarting...',
|
||||
CUSTOMIZATIONS_FULL: 'Selected entities exceeded limit. Please save in batches',
|
||||
CUSTOMIZATIONS_SAVED: 'Customizations saved',
|
||||
CUSTOMIZATIONS_HELP_1: 'Select a device and customize the entities options or click to rename',
|
||||
CUSTOMIZATIONS_HELP_2: 'mark as favorite',
|
||||
CUSTOMIZATIONS_HELP_3: 'disable write action',
|
||||
CUSTOMIZATIONS_HELP_4: 'exclude from MQTT and API',
|
||||
CUSTOMIZATIONS_HELP_5: 'hide from Dashboard',
|
||||
SELECT_DEVICE: 'Select a device',
|
||||
SET_ALL: 'set all',
|
||||
OPTIONS: 'Options',
|
||||
NAME: 'Name',
|
||||
CUSTOMIZATIONS_RESET:
|
||||
'Are you sure you want remove all customizations including the custom settings of the Temperature and Analog sensors?',
|
||||
DEVICE_ENTITIES: 'Device Entities',
|
||||
USER_CUSTOMIZATION: 'User Customization',
|
||||
SUPPORT_INFORMATION: 'Support Information',
|
||||
CLICK_HERE: 'Click Here',
|
||||
HELP_INFORMATION_1: 'Visit the online wiki to get instructions on how to configure EMS-ESP',
|
||||
HELP_INFORMATION_2: 'For live community chat join our Discord server',
|
||||
HELP_INFORMATION_3: 'To request a feature or report a bug',
|
||||
HELP_INFORMATION_4: 'remember to download and attach your system information for a faster response when reporting an issue',
|
||||
HELP_INFORMATION_5:
|
||||
"EMS-ESP is a free and open-source project. Please support its future development by giving it a star on Github!",
|
||||
SUPPORT_INFO: 'Support Info',
|
||||
UPLOAD: 'Upload',
|
||||
DOWNLOAD: 'Download',
|
||||
ABORTED: 'aborted',
|
||||
FAILED: 'failed',
|
||||
SUCCESSFUL: 'successful',
|
||||
SYSTEM: 'System',
|
||||
LOG: 'Log',
|
||||
STATUS: 'Status',
|
||||
UPLOAD_DOWNLOAD: 'Upload/Download',
|
||||
SYSTEM_VERSION_RUNNING: 'You are currently running version',
|
||||
SYSTEM_APPLY_FIRMWARE: 'to apply the new firmware',
|
||||
CLOSE: 'Close',
|
||||
USE: 'Use',
|
||||
FACTORY_RESET: 'Factory Reset',
|
||||
SYSTEM_FACTORY_TEXT: 'Device has been factory reset and will now restart',
|
||||
SYSTEM_FACTORY_TEXT_DIALOG: 'Are you sure you want to reset the device to its factory defaults?',
|
||||
VERSION_CHECK: 'Version Check',
|
||||
THE_LATEST: 'The latest',
|
||||
PLATFORM: 'Device (Platform / SDK)',
|
||||
UPTIME: 'System Uptime',
|
||||
CPU_FREQ: 'CPU Frequency',
|
||||
HEAP: 'Heap (Free / Max Alloc)',
|
||||
PSRAM: 'PSRAM (Size / Free)',
|
||||
FLASH: 'Flash Chip (Size / Speed)',
|
||||
FILESYSTEM: 'File System (Used / Total)',
|
||||
BUFFER_SIZE: 'Buffer Size',
|
||||
COMPACT: 'Compact',
|
||||
ENABLE_OTA: 'Enable OTA Updates',
|
||||
DOWNLOAD_CUSTOMIZATION_TEXT: 'Download the entity customizations',
|
||||
DOWNLOAD_SETTINGS_TEXT:
|
||||
'Download the application settings. Be careful when sharing your settings as this file contains passwords and other sensitive system information',
|
||||
UPLOAD_TEXT: 'Upload a new firmware (.bin) file, settings or customizations (.json) file below',
|
||||
UPLOADING: 'Uploading',
|
||||
UPLOAD_DROP_TEXT: 'Drop file or click here',
|
||||
ERROR: 'Unexpected Error, please try again',
|
||||
TIME_SET: 'Time set',
|
||||
MANAGE_USERS: 'Manage Users',
|
||||
IS_ADMIN: 'is Admin',
|
||||
USER_WARNING: 'You must have at least one admin user configured',
|
||||
ADD: 'Add',
|
||||
ACCESS_TOKEN_FOR: 'Access Token for',
|
||||
ACCESS_TOKEN_TEXT:
|
||||
'The token below is used with REST API calls that require authorization. It can be passed either as a Bearer token in the Authorization header or in the access_token URL query parameter.',
|
||||
GENERATING_TOKEN: 'Generating token',
|
||||
USER: 'User',
|
||||
MODIFY: 'Modify',
|
||||
SU_TEXT:
|
||||
'The su (super user) password is used to sign authentication tokens and also enable admin privileges within the Console.',
|
||||
NOT_ENABLED: 'Not enabled',
|
||||
ERRORS: 'Errors',
|
||||
DISCONNECT_REASON: 'Disconnect Reason',
|
||||
ENABLE_MQTT: 'Enable MQTT',
|
||||
OPTIONAL: 'Optional',
|
||||
FORMATTING: 'Formatting',
|
||||
FORMAT: 'Format',
|
||||
MQTT_NEST_1: 'Nested in a single topic',
|
||||
MQTT_NEST_2: 'As individual topics',
|
||||
MQTT_RESPONSE: 'Publish command output to a `response` topic',
|
||||
MQTT_PUBLISH_TEXT_1: 'Publish single value topics on change',
|
||||
MQTT_PUBLISH_TEXT_2: 'Publish to command topics (ioBroker)',
|
||||
MQTT_PUBLISH_TEXT_3: 'Enable MQTT Discovery (Home Assistant, Domoticz)',
|
||||
MQTT_PUBLISH_TEXT_4: 'Prefix for the Discovery topics',
|
||||
MQTT_PUBLISH_INTERVALS: 'Publish Intervals',
|
||||
MQTT_INT_BOILER: 'Boilers and Heat Pumps',
|
||||
MQTT_INT_THERMOSTATS: 'Thermostats',
|
||||
MQTT_INT_SOLAR: 'Solar Modules',
|
||||
MQTT_INT_MIXER: 'Mixer Modules',
|
||||
DEFAULT: 'Default',
|
||||
MQTT_CLEAN_SESSION: 'Set Clean Session',
|
||||
MQTT_RETAIN_FLAG: 'Always set Retain flag',
|
||||
INACTIVE: 'Inactive',
|
||||
ACTIVE: 'Active',
|
||||
UNKNOWN: 'Unknown',
|
||||
SET_TIME: 'Set Time',
|
||||
SET_TIME_TEXT: 'Enter local date and time below to set the time',
|
||||
LOCAL_TIME: 'Local Time',
|
||||
UTC_TIME: 'UTC Time',
|
||||
ENABLE_NTP: 'Enable NTP',
|
||||
TIME_ZONE: 'Time Zone',
|
||||
ACCESS_POINT: 'Access Point',
|
||||
AP_PROVIDE: 'Enable Access Point',
|
||||
AP_PROVIDE_TEXT_1: 'always',
|
||||
AP_PROVIDE_TEXT_2: 'when WiFi is disconnected',
|
||||
AP_PROVIDE_TEXT_3: 'never',
|
||||
AP_PREFERRED_CHANNEL: 'Preferred Channel',
|
||||
AP_HIDE_SSID: 'Hide SSID',
|
||||
NETWORK_SCAN: 'Scan WiFi Networks',
|
||||
IDLE: 'Idle',
|
||||
LOST: 'Lost',
|
||||
SCANNING: 'Scanning',
|
||||
SCAN_AGAIN: 'Scan again',
|
||||
NETWORK_SCANNER: 'Network Scanner',
|
||||
NETWORK_NO_WIFI: 'No WiFi networks found',
|
||||
NETWORK_BLANK_SSID: 'leave blank to disable WiFi',
|
||||
POWER: 'Power',
|
||||
NETWORK_DISABLE_SLEEP: 'Disable WiFi Sleep Mode',
|
||||
NETWORK_LOW_BAND: 'Use Lower WiFi Bandwidth',
|
||||
NETWORK_USE_DNS: 'Enable mDNS Service',
|
||||
NETWORK_ENABLE_IPV6: 'Enable IPv6 support',
|
||||
NETWORK_FIXED_IP: 'Use Fixed IP address',
|
||||
ADMIN: 'Admin',
|
||||
GUEST: 'Guest',
|
||||
NEW: 'New',
|
||||
RENAME: 'Rename'
|
||||
};
|
||||
|
||||
export default en;
|
||||
265
interface/src/i18n/pl/index.ts
Normal file
265
interface/src/i18n/pl/index.ts
Normal file
@@ -0,0 +1,265 @@
|
||||
import type { BaseTranslation } from '../i18n-types';
|
||||
|
||||
const en: BaseTranslation = {
|
||||
LANGUAGE: 'Language',
|
||||
RETRY: 'Retry',
|
||||
LOADING: 'Loading',
|
||||
IS_REQUIRED: 'is required',
|
||||
SIGN_IN: 'Sign In',
|
||||
SIGN_OUT: 'Sign Out',
|
||||
USERNAME: 'Username',
|
||||
PASSWORD: 'Password',
|
||||
DASHBOARD: 'Dashboard',
|
||||
SETTINGS: 'Settings',
|
||||
SAVED: 'saved',
|
||||
HELP: 'Help',
|
||||
LOGGED_IN: 'Logged in as {name}',
|
||||
PLEASE_SIGNIN: 'Please sign in to continue',
|
||||
UPLOAD_SUCCESSFUL: 'Upload successful',
|
||||
DOWNLOAD_SUCCESSFUL: 'Download successful',
|
||||
INVALID_LOGIN: 'Invalid login details',
|
||||
NETWORK: 'Network',
|
||||
SECURITY: 'Security',
|
||||
ONOFF_CAP: 'ON/OFF',
|
||||
ONOFF: 'on/off',
|
||||
TYPE: 'Type',
|
||||
DESCRIPTION: 'Description',
|
||||
ENTITIES: 'Entities',
|
||||
REFRESH: 'Refresh',
|
||||
EXPORT: 'Export',
|
||||
DEVICE_DETAILS: 'Device Details',
|
||||
BRAND: 'Brand',
|
||||
ENTITY_NAME: 'Entity Name',
|
||||
VALUE: 'Value',
|
||||
SHOW_FAV: 'only show favorites',
|
||||
DEVICE_SENSOR_DATA: 'Device and Sensor Data',
|
||||
DEVICES_SENSORS: 'Devices & Sensors',
|
||||
ATTACHED_SENSORS: 'Attached EMS-ESP Sensors',
|
||||
RUN_COMMAND: 'Call Command',
|
||||
CHANGE_VALUE: 'Change Value',
|
||||
CANCEL: 'Cancel',
|
||||
RESET: 'Reset',
|
||||
SEND: 'Send',
|
||||
SAVE: 'Save',
|
||||
REMOVE: 'Remove',
|
||||
PROBLEM_UPDATING: 'Problem updating',
|
||||
PROBLEM_LOADING: 'Problem loading',
|
||||
ACCESS_DENIED: 'Access Denied',
|
||||
ANALOG_SENSOR: 'Analog Sensor',
|
||||
ANALOG_SENSORS: 'Analog Sensors',
|
||||
UPDATED: 'Updated',
|
||||
UPDATE: 'Update',
|
||||
REMOVED: 'Removed',
|
||||
DELETION: 'Deletion',
|
||||
OFFSET: 'Offset',
|
||||
FACTOR: 'Factor',
|
||||
FREQ: 'Frequency',
|
||||
STARTVALUE: 'Start value',
|
||||
WARN_GPIO: 'Warning: be careful when assigning a GPIO!',
|
||||
EDIT: 'Edit',
|
||||
TEMP_SENSOR: 'Temperature Sensor',
|
||||
TEMP_SENSORS: 'Temperature Sensors',
|
||||
WRITE_COMMAND: 'Write command {cmd}',
|
||||
EMS_BUS_WARNING:
|
||||
'EMS bus disconnected. If this warning still persists after a few seconds please check settings and board profile',
|
||||
EMS_BUS_SCANNING: 'Scanning for EMS devices...',
|
||||
CONNECTED: 'Connected',
|
||||
TX_ISSUES: 'Tx issues - try a different Tx Mode',
|
||||
DISCONNECTED: 'Disconnected',
|
||||
EMS_SCAN: 'Are you sure you want to initiate a full device scan of the EMS bus?',
|
||||
EMS_BUS_STATUS: 'EMS Bus Status',
|
||||
ACTIVE_DEVICES: 'Active Devices & Sensors',
|
||||
DEVICE: 'Device',
|
||||
SUCCESS: 'SUCCESS',
|
||||
FAIL: 'FAIL',
|
||||
QUALITY: 'QUALITY',
|
||||
SCAN_DEVICES: 'Scan for new devices',
|
||||
EMS_BUS_STATUS_TITLE: 'EMS Bus & Activity Status',
|
||||
SCAN: 'Scan',
|
||||
STATUS_NAMES: [
|
||||
'EMS Telegrams Received (Rx)',
|
||||
'EMS Reads (Tx)',
|
||||
'EMS Writes (Tx)',
|
||||
'Temperature Sensor Reads',
|
||||
'Analog Sensor Reads',
|
||||
'MQTT Publishes',
|
||||
'API Calls',
|
||||
'Syslog Messages'
|
||||
],
|
||||
NUM_DEVICES: '{num} Device{{s}}',
|
||||
NUM_TEMP_SENSORS: '{num} Temperature Sensor{{s}}',
|
||||
NUM_ANALOG_SENSORS: '{num} Analog Sensor{{s}}',
|
||||
NUM_DAYS: '{num} Day{{s}}',
|
||||
NUM_SECONDS: '{num} Second{{s}}',
|
||||
NUM_HOURS: '{num} Hour{{s}}',
|
||||
NUM_MINUTES: '{num} Minute{{s}}',
|
||||
APPLICATION_SETTINGS: 'Application Settings',
|
||||
CUSTOMIZATION: 'Customization',
|
||||
APPLICATION_RESTARTING: 'EMS-ESP is restarting',
|
||||
BOARD_PROFILE_TEXT:
|
||||
'Select a pre-configured interface board profile from the list below or choose Custom to configure your own hardware settings',
|
||||
BOARD_PROFILE: 'Board Profile',
|
||||
BUTTON: 'Button',
|
||||
TEMPERATURE: 'Temperature',
|
||||
DISABLED: 'disabled',
|
||||
GENERAL_OPTIONS: 'General Options',
|
||||
LANGUAGE_ENTITIES: 'Language (for device entities)',
|
||||
HIDE_LED: 'Hide LED',
|
||||
ENABLE_TELNET: 'Enable Telnet Console',
|
||||
ENABLE_ANALOG: 'Enable Analog Sensors',
|
||||
CONVERT_FAHRENHEIT: 'Convert temperature values to Fahrenheit',
|
||||
BYPASS_TOKEN: 'Bypass Access Token authorization on API calls',
|
||||
READONLY: 'Enable read-only mode (blocks all outgoing EMS Tx Write commands)',
|
||||
UNDERCLOCK_CPU: 'Underclock CPU speed',
|
||||
ENABLE_SHOWER_TIMER: 'Enable Shower Timer',
|
||||
ENABLE_SHOWER_ALERT: 'Enable Shower Alert',
|
||||
TRIGGER_TIME: 'Trigger Time',
|
||||
COLD_SHOT_DURATION: 'Cold Shot Duration',
|
||||
FORMATTING_OPTIONS: 'Formatting Options',
|
||||
BOOLEAN_FORMAT_DASHBOARD: 'Boolean Format Dashboard',
|
||||
BOOLEAN_FORMAT_API: 'Boolean Format API/MQTT',
|
||||
ENUM_FORMAT: 'Enum Format API/MQTT',
|
||||
INDEX: 'Index',
|
||||
ENABLE_PARASITE: 'Enable parasite power',
|
||||
LOGGING: 'Logging',
|
||||
LOG_HEX: 'Log EMS telegrams in hexadecimal',
|
||||
ENABLE_SYSLOG: 'Enable Syslog',
|
||||
MARK_INTERVAL: 'Mark Interval',
|
||||
SECONDS: 'seconds',
|
||||
MINUTES: 'minutes',
|
||||
RESTART: 'Restart',
|
||||
HOURS: 'hours',
|
||||
RESTART_TEXT: 'EMS-ESP needs to be restarted to apply changed system settings',
|
||||
COMMAND: 'Command',
|
||||
CUSTOMIZATIONS_RESTART: 'All customizations have been removed. Restarting...',
|
||||
CUSTOMIZATIONS_FULL: 'Selected entities exceeded limit. Please save in batches',
|
||||
CUSTOMIZATIONS_SAVED: 'Customizations saved',
|
||||
CUSTOMIZATIONS_HELP_1: 'Select a device and customize the entities options or click to rename',
|
||||
CUSTOMIZATIONS_HELP_2: 'mark as favorite',
|
||||
CUSTOMIZATIONS_HELP_3: 'disable write action',
|
||||
CUSTOMIZATIONS_HELP_4: 'exclude from MQTT and API',
|
||||
CUSTOMIZATIONS_HELP_5: 'hide from Dashboard',
|
||||
SELECT_DEVICE: 'Select a device',
|
||||
SET_ALL: 'set all',
|
||||
OPTIONS: 'Options',
|
||||
NAME: 'Name',
|
||||
CUSTOMIZATIONS_RESET:
|
||||
'Are you sure you want remove all customizations including the custom settings of the Temperature and Analog sensors?',
|
||||
DEVICE_ENTITIES: 'Device Entities',
|
||||
USER_CUSTOMIZATION: 'User Customization',
|
||||
SUPPORT_INFORMATION: 'Support Information',
|
||||
CLICK_HERE: 'Click Here',
|
||||
HELP_INFORMATION_1: 'Visit the online wiki to get instructions on how to configure EMS-ESP',
|
||||
HELP_INFORMATION_2: 'For live community chat join our Discord server',
|
||||
HELP_INFORMATION_3: 'To request a feature or report a bug',
|
||||
HELP_INFORMATION_4: 'remember to download and attach your system information for a faster response when reporting an issue',
|
||||
HELP_INFORMATION_5:
|
||||
"EMS-ESP is a free and open-source project. Please support its future development by giving it a star on Github!",
|
||||
SUPPORT_INFO: 'Support Info',
|
||||
UPLOAD: 'Upload',
|
||||
DOWNLOAD: 'Download',
|
||||
ABORTED: 'aborted',
|
||||
FAILED: 'failed',
|
||||
SUCCESSFUL: 'successful',
|
||||
SYSTEM: 'System',
|
||||
LOG: 'Log',
|
||||
STATUS: 'Status',
|
||||
UPLOAD_DOWNLOAD: 'Upload/Download',
|
||||
SYSTEM_VERSION_RUNNING: 'You are currently running version',
|
||||
SYSTEM_APPLY_FIRMWARE: 'to apply the new firmware',
|
||||
CLOSE: 'Close',
|
||||
USE: 'Use',
|
||||
FACTORY_RESET: 'Factory Reset',
|
||||
SYSTEM_FACTORY_TEXT: 'Device has been factory reset and will now restart',
|
||||
SYSTEM_FACTORY_TEXT_DIALOG: 'Are you sure you want to reset the device to its factory defaults?',
|
||||
VERSION_CHECK: 'Version Check',
|
||||
THE_LATEST: 'The latest',
|
||||
PLATFORM: 'Device (Platform / SDK)',
|
||||
UPTIME: 'System Uptime',
|
||||
CPU_FREQ: 'CPU Frequency',
|
||||
HEAP: 'Heap (Free / Max Alloc)',
|
||||
PSRAM: 'PSRAM (Size / Free)',
|
||||
FLASH: 'Flash Chip (Size / Speed)',
|
||||
FILESYSTEM: 'File System (Used / Total)',
|
||||
BUFFER_SIZE: 'Buffer Size',
|
||||
COMPACT: 'Compact',
|
||||
ENABLE_OTA: 'Enable OTA Updates',
|
||||
DOWNLOAD_CUSTOMIZATION_TEXT: 'Download the entity customizations',
|
||||
DOWNLOAD_SETTINGS_TEXT:
|
||||
'Download the application settings. Be careful when sharing your settings as this file contains passwords and other sensitive system information',
|
||||
UPLOAD_TEXT: 'Upload a new firmware (.bin) file, settings or customizations (.json) file below',
|
||||
UPLOADING: 'Uploading',
|
||||
UPLOAD_DROP_TEXT: 'Drop file or click here',
|
||||
ERROR: 'Unexpected Error, please try again',
|
||||
TIME_SET: 'Time set',
|
||||
MANAGE_USERS: 'Manage Users',
|
||||
IS_ADMIN: 'is Admin',
|
||||
USER_WARNING: 'You must have at least one admin user configured',
|
||||
ADD: 'Add',
|
||||
ACCESS_TOKEN_FOR: 'Access Token for',
|
||||
ACCESS_TOKEN_TEXT:
|
||||
'The token below is used with REST API calls that require authorization. It can be passed either as a Bearer token in the Authorization header or in the access_token URL query parameter.',
|
||||
GENERATING_TOKEN: 'Generating token',
|
||||
USER: 'User',
|
||||
MODIFY: 'Modify',
|
||||
SU_TEXT:
|
||||
'The su (super user) password is used to sign authentication tokens and also enable admin privileges within the Console.',
|
||||
NOT_ENABLED: 'Not enabled',
|
||||
ERRORS: 'Errors',
|
||||
DISCONNECT_REASON: 'Disconnect Reason',
|
||||
ENABLE_MQTT: 'Enable MQTT',
|
||||
OPTIONAL: 'Optional',
|
||||
FORMATTING: 'Formatting',
|
||||
FORMAT: 'Format',
|
||||
MQTT_NEST_1: 'Nested in a single topic',
|
||||
MQTT_NEST_2: 'As individual topics',
|
||||
MQTT_RESPONSE: 'Publish command output to a `response` topic',
|
||||
MQTT_PUBLISH_TEXT_1: 'Publish single value topics on change',
|
||||
MQTT_PUBLISH_TEXT_2: 'Publish to command topics (ioBroker)',
|
||||
MQTT_PUBLISH_TEXT_3: 'Enable MQTT Discovery (Home Assistant, Domoticz)',
|
||||
MQTT_PUBLISH_TEXT_4: 'Prefix for the Discovery topics',
|
||||
MQTT_PUBLISH_INTERVALS: 'Publish Intervals',
|
||||
MQTT_INT_BOILER: 'Boilers and Heat Pumps',
|
||||
MQTT_INT_THERMOSTATS: 'Thermostats',
|
||||
MQTT_INT_SOLAR: 'Solar Modules',
|
||||
MQTT_INT_MIXER: 'Mixer Modules',
|
||||
DEFAULT: 'Default',
|
||||
MQTT_CLEAN_SESSION: 'Set Clean Session',
|
||||
MQTT_RETAIN_FLAG: 'Always set Retain flag',
|
||||
INACTIVE: 'Inactive',
|
||||
ACTIVE: 'Active',
|
||||
UNKNOWN: 'Unknown',
|
||||
SET_TIME: 'Set Time',
|
||||
SET_TIME_TEXT: 'Enter local date and time below to set the time',
|
||||
LOCAL_TIME: 'Local Time',
|
||||
UTC_TIME: 'UTC Time',
|
||||
ENABLE_NTP: 'Enable NTP',
|
||||
TIME_ZONE: 'Time Zone',
|
||||
ACCESS_POINT: 'Access Point',
|
||||
AP_PROVIDE: 'Enable Access Point',
|
||||
AP_PROVIDE_TEXT_1: 'always',
|
||||
AP_PROVIDE_TEXT_2: 'when WiFi is disconnected',
|
||||
AP_PROVIDE_TEXT_3: 'never',
|
||||
AP_PREFERRED_CHANNEL: 'Preferred Channel',
|
||||
AP_HIDE_SSID: 'Hide SSID',
|
||||
NETWORK_SCAN: 'Scan WiFi Networks',
|
||||
IDLE: 'Idle',
|
||||
LOST: 'Lost',
|
||||
SCANNING: 'Scanning',
|
||||
SCAN_AGAIN: 'Scan again',
|
||||
NETWORK_SCANNER: 'Network Scanner',
|
||||
NETWORK_NO_WIFI: 'No WiFi networks found',
|
||||
NETWORK_BLANK_SSID: 'leave blank to disable WiFi',
|
||||
POWER: 'Power',
|
||||
NETWORK_DISABLE_SLEEP: 'Disable WiFi Sleep Mode',
|
||||
NETWORK_LOW_BAND: 'Use Lower WiFi Bandwidth',
|
||||
NETWORK_USE_DNS: 'Enable mDNS Service',
|
||||
NETWORK_ENABLE_IPV6: 'Enable IPv6 support',
|
||||
NETWORK_FIXED_IP: 'Use Fixed IP address',
|
||||
ADMIN: 'Admin',
|
||||
GUEST: 'Guest',
|
||||
NEW: 'New',
|
||||
RENAME: 'Rename'
|
||||
};
|
||||
|
||||
export default en;
|
||||
266
interface/src/i18n/se/index.ts
Normal file
266
interface/src/i18n/se/index.ts
Normal file
@@ -0,0 +1,266 @@
|
||||
import type { BaseTranslation } from '../i18n-types';
|
||||
|
||||
const se: BaseTranslation = {
|
||||
LANGUAGE: 'Språk',
|
||||
RETRY: 'Försök igen',
|
||||
LOADING: 'Laddar',
|
||||
IS_REQUIRED: 'Krävs',
|
||||
SIGN_IN: 'Logga In',
|
||||
SIGN_OUT: 'Logga Ut',
|
||||
USERNAME: 'Användarnamn',
|
||||
PASSWORD: 'Lösenord',
|
||||
DASHBOARD: 'Kontrollpanel',
|
||||
SETTINGS: 'Inställningar',
|
||||
SAVED: 'Sparat',
|
||||
HELP: 'Hjälp',
|
||||
LOGGED_IN: 'Inloggad som {name}',
|
||||
PLEASE_SIGNIN: 'Vänligen logga in för att fortsätta',
|
||||
UPLOAD_SUCCESSFUL: 'Uppladdning lyckades',
|
||||
DOWNLOAD_SUCCESSFUL: 'Nedladdning lyckades',
|
||||
INVALID_LOGIN: 'Ogiltig login',
|
||||
NETWORK: 'Nätverk',
|
||||
SECURITY: 'Säkerhet',
|
||||
ONOFF_CAP: 'PÅ/AV',
|
||||
ONOFF: 'på/av',
|
||||
TYPE: 'Typ',
|
||||
DESCRIPTION: 'Beskrivning',
|
||||
ENTITIES: 'Entiteter',
|
||||
REFRESH: 'Uppdatera',
|
||||
EXPORT: 'Export',
|
||||
DEVICE_DETAILS: 'Enhetsdetaljer',
|
||||
BRAND: 'Fabrikat',
|
||||
ENTITY_NAME: 'Entitetsnamn',
|
||||
VALUE: 'Värde',
|
||||
SHOW_FAV: 'Visa enbart favoriter',
|
||||
DEVICE_SENSOR_DATA: 'Enhets och Sensor-data',
|
||||
DEVICES_SENSORS: 'Enheter & Sensorer',
|
||||
ATTACHED_SENSORS: 'Anslutna EMS-ESP Sensorer',
|
||||
RUN_COMMAND: 'Kör Kommando',
|
||||
CHANGE_VALUE: 'Ändra Värde',
|
||||
CANCEL: 'Avbryt',
|
||||
RESET: 'Nollsäll',
|
||||
SEND: 'Skicka',
|
||||
SAVE: 'Spara',
|
||||
REMOVE: 'Ta bort',
|
||||
PROBLEM_UPDATING: 'Problem vid uppdatering',
|
||||
PROBLEM_LOADING: 'Problem vid hämtning',
|
||||
ACCESS_DENIED: 'Åtkomst Nekad',
|
||||
ANALOG_SENSOR: 'Analog Sensor',
|
||||
ANALOG_SENSORS: 'Analoga Sensorer',
|
||||
UPDATED: 'Uppdaterad',
|
||||
UPDATE: 'Uppdatera',
|
||||
REMOVED: 'Raderad',
|
||||
DELETION: 'Radering',
|
||||
OFFSET: 'Kompensering',
|
||||
FACTOR: 'Faktor',
|
||||
FREQ: 'Frekvens',
|
||||
STARTVALUE: 'Startvärde',
|
||||
WARN_GPIO: 'Varning: Var försiktig vid aktivering av GPIO!',
|
||||
EDIT: 'Ändra',
|
||||
TEMP_SENSOR: 'Temperatursensor',
|
||||
TEMP_SENSORS: 'Temperatursensorer',
|
||||
WRITE_COMMAND: 'Skrivkommando {cmd}',
|
||||
EMS_BUS_WARNING:
|
||||
'EMS-buss nedkopplad. Om denna varning kvarstår efter några sekunder, kontrollera inställningar och enhets-profil.',
|
||||
EMS_BUS_SCANNING: 'Söker efter EMS-enheter...',
|
||||
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?',
|
||||
EMS_BUS_STATUS: 'EMS-Bus Status',
|
||||
ACTIVE_DEVICES: 'Aktiva Enheter & Sensorer',
|
||||
DEVICE: 'Enhet',
|
||||
SUCCESS: 'Lyckades',
|
||||
FAIL: 'Misslyckades',
|
||||
QUALITY: 'Kvalitet',
|
||||
SCAN_DEVICES: 'Sök efter nya enheter',
|
||||
EMS_BUS_STATUS_TITLE: 'EMS-buss & aktivitetsstatus',
|
||||
SCAN: 'Sök',
|
||||
STATUS_NAMES: [
|
||||
'EMS-telegram Mottagna (Rx)',
|
||||
'EMS-läsningar (Tx)',
|
||||
'EMS-skrivningar (Tx)',
|
||||
'Temperatursensor-läsningar',
|
||||
'Analog Sensor-läsningar',
|
||||
'MQTT-publiceringar',
|
||||
'API-anrop',
|
||||
'Syslog-meddelanden'
|
||||
],
|
||||
NUM_DEVICES: '{num} Enhet{{er}}',
|
||||
NUM_TEMP_SENSORS: '{num} Temperatur-sensor{{er}}',
|
||||
NUM_ANALOG_SENSORS: '{num} Analoga Sensor{{er}}',
|
||||
NUM_DAYS: '{num} Dag{{ar}}',
|
||||
NUM_SECONDS: '{num} Sekund{{er}}',
|
||||
NUM_HOURS: '{num} Timmar',
|
||||
NUM_MINUTES: '{num} Minut{{er}}',
|
||||
APPLICATION_SETTINGS: 'Inställningar',
|
||||
CUSTOMIZATION: 'Anpassa',
|
||||
APPLICATION_RESTARTING: 'EMS-ESP startar om',
|
||||
BOARD_PROFILE_TEXT:
|
||||
'Välj en förkonfigurerad hårdvaruprofil från listan nedan eller välj Anpassad för att konfigurera dina egna hårdvaruinställningar',
|
||||
BOARD_PROFILE: 'Hårdvaruprofil',
|
||||
BUTTON: 'Knapp',
|
||||
TEMPERATURE: 'Temperatur',
|
||||
DISABLED: 'inaktiverad',
|
||||
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 temperatur till Fahrenheit',
|
||||
BYPASS_TOKEN: 'Inaktivera Token-autensiering för API-anrop',
|
||||
READONLY: 'Aktivera read-only (blockerar alla utgående skrivkommandon mot EMS-bussen)',
|
||||
UNDERCLOCK_CPU: 'Nedklocka Processorhastighet',
|
||||
ENABLE_SHOWER_TIMER: 'Aktivera Dusch-timer',
|
||||
ENABLE_SHOWER_ALERT: 'Aktivera Dusch-warning',
|
||||
TRIGGER_TIME: 'Aktiveringstid',
|
||||
COLD_SHOT_DURATION: 'Längd på kalldusch',
|
||||
FORMATTING_OPTIONS: 'Formatteringsalternativ',
|
||||
BOOLEAN_FORMAT_DASHBOARD: 'Bool-format Kontrollpanel',
|
||||
BOOLEAN_FORMAT_API: 'Bool-format API/MQTT',
|
||||
ENUM_FORMAT: 'Enum-format API/MQTT',
|
||||
INDEX: 'Index',
|
||||
ENABLE_PARASITE: 'Aktivera parasitström',
|
||||
LOGGING: 'Loggning',
|
||||
LOG_HEX: 'Logga EMS-telegram i hexadecimal',
|
||||
ENABLE_SYSLOG: 'Aktivera Syslog',
|
||||
MARK_INTERVAL: 'Markerings-interval',
|
||||
SECONDS: 'sekunder',
|
||||
MINUTES: 'minuter',
|
||||
RESTART: 'Starta om',
|
||||
HOURS: 'timmar',
|
||||
RESTART_TEXT: 'EMS-ESP kräver en omstart för att applicera förändrade systeminställningar',
|
||||
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_HELP_2: 'markera som favorit',
|
||||
CUSTOMIZATIONS_HELP_3: 'inaktivera skrivningar',
|
||||
CUSTOMIZATIONS_HELP_4: 'exkludera från MQTT & API',
|
||||
CUSTOMIZATIONS_HELP_5: 'göm från Kontrollpanel',
|
||||
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?',
|
||||
DEVICE_ENTITIES: 'Enhets-entiteter',
|
||||
USER_CUSTOMIZATION: 'Användaranpassningar',
|
||||
SUPPORT_INFORMATION: 'Supportinformation',
|
||||
CLICK_HERE: 'Klicka Här',
|
||||
HELP_INFORMATION_1: 'Besök Wikin för instruktioner för 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_5:
|
||||
'EMS-ESP är gratis och är öppen källkod. Bidra till utveklingen genom att ge oss en stjärna på GitHub!',
|
||||
SUPPORT_INFO: 'Supportinfo',
|
||||
UPLOAD: 'Uppladdning',
|
||||
DOWNLOAD: 'Nedladdning',
|
||||
ABORTED: 'Avbruten',
|
||||
FAILED: 'Misslyckades',
|
||||
SUCCESSFUL: 'Lyckades',
|
||||
SYSTEM: 'System',
|
||||
LOG: 'Logg',
|
||||
STATUS: 'Status',
|
||||
UPLOAD_DOWNLOAD: 'Upp/Nedladdning',
|
||||
SYSTEM_VERSION_RUNNING: 'Du använder version',
|
||||
SYSTEM_APPLY_FIRMWARE: 'för att aktivera ny firmware',
|
||||
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?',
|
||||
VERSION_CHECK: 'Versionskontroll',
|
||||
THE_LATEST: 'Den senaste',
|
||||
PLATFORM: 'Enhet (Plattform / SDK)',
|
||||
UPTIME: 'Systemets Uptid',
|
||||
CPU_FREQ: 'CPU-frekvens',
|
||||
HEAP: 'Heap (Ledigt / Max allokerat)',
|
||||
PSRAM: 'PSRAM (Storlek / Ledigt)',
|
||||
FLASH: 'Flash Chip (Storlek / Hastighet)',
|
||||
FILESYSTEM: 'Filsystem (Använt / Totalt)',
|
||||
BUFFER_SIZE: 'Bufferstorlek',
|
||||
COMPACT: 'Komprimera',
|
||||
ENABLE_OTA: 'Aktivera OTA-uppdateringar',
|
||||
DOWNLOAD_CUSTOMIZATION_TEXT: 'Ladda ner entitetsanpassningar',
|
||||
DOWNLOAD_SETTINGS_TEXT:
|
||||
'Ladda ner applikationsinställningar. Var försiktig om du delar dina iställlningar då de innehåller lösenord och annan känslig systeminformation',
|
||||
UPLOAD_TEXT: 'Ladda upp ett nytt firmware (.bin), inställningar eller anpassningar (.json) nedan',
|
||||
UPLOADING: 'Laddar upp',
|
||||
UPLOAD_DROP_TEXT: 'Släpp fil eller klicka här',
|
||||
ERROR: 'Okänt Fel, var god försök igen',
|
||||
TIME_SET: 'Ställ in tid',
|
||||
MANAGE_USERS: 'Användarhantering',
|
||||
IS_ADMIN: 'Admin',
|
||||
USER_WARNING: 'Du måste ha minst en admin 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',
|
||||
NOT_ENABLED: 'Ej aktiv',
|
||||
ERRORS: 'fel',
|
||||
DISCONNECT_REASON: 'Anledning till nedkoppling',
|
||||
ENABLE_MQTT: 'Aktivera MQTT',
|
||||
OPTIONAL: 'Valfritt',
|
||||
FORMATTING: 'Formatering',
|
||||
FORMAT: 'Format',
|
||||
MQTT_NEST_1: 'Nestlad 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 (Home Assistant, Domoticz)',
|
||||
MQTT_PUBLISH_TEXT_4: 'Prefix för Discovery topics',
|
||||
MQTT_PUBLISH_INTERVALS: 'Publiceringsintervall',
|
||||
MQTT_INT_BOILER: 'Värmepump/panna',
|
||||
MQTT_INT_THERMOSTATS: 'Termostater',
|
||||
MQTT_INT_SOLAR: 'Solpaneler',
|
||||
MQTT_INT_MIXER: 'Blandarventiler',
|
||||
DEFAULT: 'Standard',
|
||||
MQTT_CLEAN_SESSION: 'Sätt "Clean Session"-flaggan',
|
||||
MQTT_RETAIN_FLAG: 'Sätt "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',
|
||||
LOCAL_TIME: 'Lokal Tid',
|
||||
UTC_TIME: 'UTC-tid',
|
||||
ENABLE_NTP: 'Aktivera NTP',
|
||||
TIME_ZONE: 'Tidszon',
|
||||
ACCESS_POINT: '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: 'Föredragen kanal',
|
||||
AP_HIDE_SSID: 'Göm SSID',
|
||||
NETWORK_SCAN: 'Sök efter WiFi-nätverk',
|
||||
IDLE: 'Vilande',
|
||||
LOST: 'Förlorad',
|
||||
SCANNING: 'Söker',
|
||||
SCAN_AGAIN: 'Sök igen',
|
||||
NETWORK_SCANNER: 'Nätverks-scanner',
|
||||
NETWORK_NO_WIFI: 'Inga WiFi-nätverk hittades',
|
||||
NETWORK_BLANK_SSID: 'lämna blankt för att inaktivera WiFi',
|
||||
POWER: 'Effekt',
|
||||
NETWORK_DISABLE_SLEEP: 'Inaktivera sömnläge',
|
||||
NETWORK_LOW_BAND: 'Använd lägre bandbredd',
|
||||
NETWORK_USE_DNS: 'Aktivera mDNS-tjänsten',
|
||||
NETWORK_ENABLE_IPV6: 'Aktivera IPv6-support',
|
||||
NETWORK_FIXED_IP: 'Använd statiskt IP',
|
||||
ADMIN: 'Admin',
|
||||
GUEST: 'Gäst',
|
||||
NEW: 'Ny',
|
||||
RENAME: 'Byt namn'
|
||||
};
|
||||
|
||||
export default se;
|
||||
@@ -5,17 +5,21 @@ import { Tab } from '@mui/material';
|
||||
|
||||
import { RouterTabs, useRouterTab, useLayoutTitle } from '../components';
|
||||
|
||||
import { useI18nContext } from '../i18n/i18n-react';
|
||||
|
||||
import DashboardStatus from './DashboardStatus';
|
||||
import DashboardData from './DashboardData';
|
||||
|
||||
const Dashboard: FC = () => {
|
||||
useLayoutTitle('Dashboard');
|
||||
const { routerTab } = useRouterTab();
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
useLayoutTitle(LL.DASHBOARD());
|
||||
|
||||
return (
|
||||
<>
|
||||
<RouterTabs value={routerTab}>
|
||||
<Tab value="data" label="Devices & Sensors" />
|
||||
<Tab value="data" label={LL.DEVICES_SENSORS()} />
|
||||
<Tab value="status" label="Status" />
|
||||
</RouterTabs>
|
||||
<Routes>
|
||||
|
||||
@@ -49,8 +49,6 @@ import DeviceIcon from './DeviceIcon';
|
||||
|
||||
import { IconContext } from 'react-icons';
|
||||
|
||||
import { formatDurationMin, pluralize } from '../utils';
|
||||
|
||||
import { AuthenticatedContext } from '../contexts/authentication';
|
||||
|
||||
import { ButtonRow, ValidatedTextField, SectionContent, MessageBox } from '../components';
|
||||
@@ -74,12 +72,24 @@ import {
|
||||
DeviceEntityMask
|
||||
} from './types';
|
||||
|
||||
import { useI18nContext } from '../i18n/i18n-react';
|
||||
|
||||
import parseMilliseconds from 'parse-ms';
|
||||
|
||||
const DashboardData: FC = () => {
|
||||
const { me } = useContext(AuthenticatedContext);
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const [coreData, setCoreData] = useState<CoreData>({ connected: true, devices: [], active_sensors: 0, analog_enabled: false });
|
||||
const [coreData, setCoreData] = useState<CoreData>({
|
||||
connected: true,
|
||||
devices: [],
|
||||
active_sensors: 0,
|
||||
analog_enabled: false
|
||||
});
|
||||
|
||||
const [deviceData, setDeviceData] = useState<DeviceData>({ label: '', data: [] });
|
||||
const [sensorData, setSensorData] = useState<SensorData>({ sensors: [], analogs: [] });
|
||||
const [deviceValue, setDeviceValue] = useState<DeviceValue>();
|
||||
@@ -134,7 +144,7 @@ const DashboardData: FC = () => {
|
||||
common_theme,
|
||||
{
|
||||
Table: `
|
||||
--data-table-library_grid-template-columns: 40px 100px repeat(1, minmax(0, 1fr)) 80px 40px;
|
||||
--data-table-library_grid-template-columns: 40px 100px repeat(1, minmax(0, 1fr)) 100px 40px;
|
||||
`,
|
||||
BaseRow: `
|
||||
.td {
|
||||
@@ -319,10 +329,10 @@ const DashboardData: FC = () => {
|
||||
|
||||
const handleDownloadCsv = () => {
|
||||
const columns = [
|
||||
{ accessor: (dv: any) => dv.id.slice(2), name: 'Entity' },
|
||||
{ accessor: (dv: any) => dv.id.slice(2), name: LL.ENTITY_NAME() },
|
||||
{
|
||||
accessor: (dv: any) => (typeof dv.v === 'number' ? new Intl.NumberFormat().format(dv.v) : dv.v),
|
||||
name: 'Value'
|
||||
name: LL.VALUE()
|
||||
},
|
||||
{ accessor: (dv: any) => DeviceValueUOM_s[dv.u], name: 'UoM' }
|
||||
];
|
||||
@@ -355,9 +365,9 @@ const DashboardData: FC = () => {
|
||||
try {
|
||||
setCoreData((await EMSESP.readCoreData()).data);
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Failed to fetch core data'), { variant: 'error' });
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
|
||||
}
|
||||
}, [enqueueSnackbar]);
|
||||
}, [enqueueSnackbar, LL]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchCoreData();
|
||||
@@ -376,7 +386,7 @@ const DashboardData: FC = () => {
|
||||
try {
|
||||
setDeviceData((await EMSESP.readDeviceData({ id: unique_id })).data);
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem fetching device data'), { variant: 'error' });
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -384,21 +394,38 @@ const DashboardData: FC = () => {
|
||||
try {
|
||||
setSensorData((await EMSESP.readSensorData()).data);
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem fetching sensor data'), { variant: 'error' });
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
const isCmdOnly = (dv: DeviceValue) => dv.v === '' && dv.c;
|
||||
|
||||
const formatDurationMin = (duration_min: number) => {
|
||||
const { days, hours, minutes } = parseMilliseconds(duration_min * 60000);
|
||||
let formatted = '';
|
||||
if (days) {
|
||||
formatted += LL.NUM_DAYS({ num: days }) + ' ';
|
||||
}
|
||||
if (hours) {
|
||||
formatted += LL.NUM_HOURS({ num: hours }) + ' ';
|
||||
}
|
||||
if (minutes) {
|
||||
formatted += LL.NUM_MINUTES({ num: minutes });
|
||||
}
|
||||
return formatted;
|
||||
};
|
||||
|
||||
function formatValue(value: any, uom: number) {
|
||||
if (value === undefined) {
|
||||
return '';
|
||||
}
|
||||
switch (uom) {
|
||||
case DeviceValueUOM.HOURS:
|
||||
return value ? formatDurationMin(value * 60) : '0 hours';
|
||||
return value ? formatDurationMin(value * 60) : LL.NUM_HOURS({ num: 0 });
|
||||
case DeviceValueUOM.MINUTES:
|
||||
return value ? formatDurationMin(value) : '0 minutes';
|
||||
return value ? formatDurationMin(value) : LL.NUM_MINUTES({ num: 0 });
|
||||
case DeviceValueUOM.SECONDS:
|
||||
return LL.NUM_SECONDS({ num: value });
|
||||
case DeviceValueUOM.NONE:
|
||||
if (typeof value === 'number') {
|
||||
return new Intl.NumberFormat().format(value);
|
||||
@@ -414,13 +441,24 @@ const DashboardData: FC = () => {
|
||||
' ' +
|
||||
DeviceValueUOM_s[uom]
|
||||
);
|
||||
case DeviceValueUOM.SECONDS:
|
||||
return pluralize(value, DeviceValueUOM_s[uom]);
|
||||
default:
|
||||
return new Intl.NumberFormat().format(value) + ' ' + DeviceValueUOM_s[uom];
|
||||
}
|
||||
}
|
||||
|
||||
const setUom = (uom: number) => {
|
||||
switch (uom) {
|
||||
case DeviceValueUOM.HOURS:
|
||||
return LL.HOURS();
|
||||
case DeviceValueUOM.MINUTES:
|
||||
return LL.MINUTES();
|
||||
case DeviceValueUOM.SECONDS:
|
||||
return LL.SECONDS();
|
||||
default:
|
||||
return DeviceValueUOM_s[uom];
|
||||
}
|
||||
};
|
||||
|
||||
const sendDeviceValue = async () => {
|
||||
if (deviceValue) {
|
||||
try {
|
||||
@@ -429,15 +467,15 @@ const DashboardData: FC = () => {
|
||||
devicevalue: deviceValue
|
||||
});
|
||||
if (response.status === 204) {
|
||||
enqueueSnackbar('Write command failed', { variant: 'error' });
|
||||
enqueueSnackbar(LL.WRITE_COMMAND({ cmd: 'failed' }), { variant: 'error' });
|
||||
} else if (response.status === 403) {
|
||||
enqueueSnackbar('Write access denied', { variant: 'error' });
|
||||
enqueueSnackbar(LL.ACCESS_DENIED(), { variant: 'error' });
|
||||
} else {
|
||||
enqueueSnackbar('Write command sent', { variant: 'success' });
|
||||
enqueueSnackbar(LL.WRITE_COMMAND({ cmd: 'send' }), { variant: 'success' });
|
||||
}
|
||||
setDeviceValue(undefined);
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem writing value'), { variant: 'error' });
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
|
||||
} finally {
|
||||
refreshData();
|
||||
setDeviceValue(undefined);
|
||||
@@ -449,7 +487,7 @@ const DashboardData: FC = () => {
|
||||
if (deviceValue) {
|
||||
return (
|
||||
<Dialog open={deviceValue !== undefined} onClose={() => setDeviceValue(undefined)}>
|
||||
<DialogTitle>{isCmdOnly(deviceValue) ? 'Run Command' : 'Change Value'}</DialogTitle>
|
||||
<DialogTitle>{isCmdOnly(deviceValue) ? LL.RUN_COMMAND() : LL.CHANGE_VALUE()}</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
{deviceValue.l && (
|
||||
<ValidatedTextField
|
||||
@@ -478,7 +516,7 @@ const DashboardData: FC = () => {
|
||||
onChange={updateValue(setDeviceValue)}
|
||||
inputProps={deviceValue.u ? { min: deviceValue.m, max: deviceValue.x, step: deviceValue.s } : {}}
|
||||
InputProps={{
|
||||
startAdornment: <InputAdornment position="start">{DeviceValueUOM_s[deviceValue.u]}</InputAdornment>
|
||||
startAdornment: <InputAdornment position="start">{setUom(deviceValue.u)}</InputAdornment>
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@@ -491,7 +529,7 @@ const DashboardData: FC = () => {
|
||||
onClick={() => setDeviceValue(undefined)}
|
||||
color="secondary"
|
||||
>
|
||||
Cancel
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<SendIcon />}
|
||||
@@ -500,7 +538,7 @@ const DashboardData: FC = () => {
|
||||
onClick={() => sendDeviceValue()}
|
||||
color="warning"
|
||||
>
|
||||
Send
|
||||
{LL.SEND()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
@@ -521,15 +559,15 @@ const DashboardData: FC = () => {
|
||||
offset: sensor.o
|
||||
});
|
||||
if (response.status === 204) {
|
||||
enqueueSnackbar('Sensor change failed', { variant: 'error' });
|
||||
enqueueSnackbar(LL.TEMP_SENSOR() + ' ' + LL.UPLOAD_TEXT() + ' ' + LL.FAILED(), { variant: 'error' });
|
||||
} else if (response.status === 403) {
|
||||
enqueueSnackbar('Access denied', { variant: 'error' });
|
||||
enqueueSnackbar(LL.ACCESS_DENIED(), { variant: 'error' });
|
||||
} else {
|
||||
enqueueSnackbar('Sensor updated', { variant: 'success' });
|
||||
enqueueSnackbar(LL.TEMP_SENSOR() + ' ' + LL.UPDATED(), { variant: 'success' });
|
||||
}
|
||||
setSensor(undefined);
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem updating sensor'), { variant: 'error' });
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
|
||||
} finally {
|
||||
setSensor(undefined);
|
||||
fetchSensorData();
|
||||
@@ -541,16 +579,18 @@ const DashboardData: FC = () => {
|
||||
if (sensor) {
|
||||
return (
|
||||
<Dialog open={sensor !== undefined} onClose={() => setSensor(undefined)}>
|
||||
<DialogTitle>Edit Temperature Sensor</DialogTitle>
|
||||
<DialogTitle>
|
||||
{LL.EDIT()} {LL.TEMP_SENSOR()}
|
||||
</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<Box color="warning.main" p={0} pl={0} pr={0} mt={0} mb={2}>
|
||||
<Typography variant="body2">Sensor ID {sensor.id}</Typography>
|
||||
<Typography variant="body2">Sensor ID: {sensor.id}</Typography>
|
||||
</Box>
|
||||
<Grid container spacing={1}>
|
||||
<Grid item>
|
||||
<ValidatedTextField
|
||||
name="n"
|
||||
label="Name"
|
||||
label={LL.ENTITY_NAME()}
|
||||
value={sensor.n}
|
||||
autoFocus
|
||||
sx={{ width: '30ch' }}
|
||||
@@ -560,7 +600,7 @@ const DashboardData: FC = () => {
|
||||
<Grid item>
|
||||
<ValidatedTextField
|
||||
name="o"
|
||||
label="Offset"
|
||||
label={LL.OFFSET()}
|
||||
value={numberValue(sensor.o)}
|
||||
sx={{ width: '12ch' }}
|
||||
type="number"
|
||||
@@ -581,7 +621,7 @@ const DashboardData: FC = () => {
|
||||
onClick={() => setSensor(undefined)}
|
||||
color="secondary"
|
||||
>
|
||||
Cancel
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<SaveIcon />}
|
||||
@@ -590,7 +630,7 @@ const DashboardData: FC = () => {
|
||||
onClick={() => sendSensor()}
|
||||
color="warning"
|
||||
>
|
||||
Save
|
||||
{LL.SAVE()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
@@ -602,17 +642,17 @@ const DashboardData: FC = () => {
|
||||
if (coreData && coreData.devices.length > 0 && deviceDialog !== -1) {
|
||||
return (
|
||||
<Dialog open={deviceDialog !== -1} onClose={() => setDeviceDialog(-1)}>
|
||||
<DialogTitle>Device Details</DialogTitle>
|
||||
<DialogTitle>{LL.DEVICE_DETAILS()}</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<List dense={true}>
|
||||
<ListItem>
|
||||
<ListItemText primary="Type" secondary={coreData.devices[deviceDialog].t} />
|
||||
<ListItemText primary={LL.TYPE()} secondary={coreData.devices[deviceDialog].t} />
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText primary="Name" secondary={coreData.devices[deviceDialog].n} />
|
||||
<ListItemText primary={LL.NAME()} secondary={coreData.devices[deviceDialog].n} />
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText primary="Brand" secondary={coreData.devices[deviceDialog].b} />
|
||||
<ListItemText primary={LL.BRAND()} secondary={coreData.devices[deviceDialog].b} />
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
@@ -630,7 +670,7 @@ const DashboardData: FC = () => {
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button variant="outlined" onClick={() => setDeviceDialog(-1)} color="secondary">
|
||||
Close
|
||||
{LL.CLOSE()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
@@ -640,17 +680,20 @@ const DashboardData: FC = () => {
|
||||
|
||||
const renderCoreData = () => (
|
||||
<IconContext.Provider value={{ color: 'lightblue', size: '24', style: { verticalAlign: 'middle' } }}>
|
||||
{!coreData.connected && <MessageBox my={2} level="error" message="EMSbus disconnected, check settings and board profile" />}
|
||||
{coreData.connected && coreData.devices.length === 0 && <MessageBox my={2} level="warning" message="Scanning for EMS devices..." />}
|
||||
{!coreData.connected && <MessageBox my={2} level="error" message={LL.EMS_BUS_WARNING()} />}
|
||||
{coreData.connected && coreData.devices.length === 0 && (
|
||||
<MessageBox my={2} level="warning" message={LL.EMS_BUS_SCANNING()} />
|
||||
)}
|
||||
|
||||
<Table data={{ nodes: coreData.devices }} select={device_select} theme={device_theme} layout={{ custom: true }}>
|
||||
{(tableList: any) => (
|
||||
<>
|
||||
<Header>
|
||||
<HeaderRow>
|
||||
<HeaderCell stiff />
|
||||
<HeaderCell stiff>TYPE</HeaderCell>
|
||||
<HeaderCell resize>DESCRIPTION</HeaderCell>
|
||||
<HeaderCell stiff>ENTITIES</HeaderCell>
|
||||
<HeaderCell stiff>{LL.TYPE()}</HeaderCell>
|
||||
<HeaderCell resize>{LL.DESCRIPTION()}</HeaderCell>
|
||||
<HeaderCell stiff>{LL.ENTITIES()}</HeaderCell>
|
||||
<HeaderCell stiff />
|
||||
</HeaderRow>
|
||||
</Header>
|
||||
@@ -676,7 +719,7 @@ const DashboardData: FC = () => {
|
||||
<DeviceIcon type="Sensor" />
|
||||
</Cell>
|
||||
<Cell>Sensors</Cell>
|
||||
<Cell>Attached EMS-ESP Sensors</Cell>
|
||||
<Cell>{LL.ATTACHED_SENSORS()}</Cell>
|
||||
<Cell>{coreData.active_sensors}</Cell>
|
||||
<Cell>
|
||||
<IconButton size="small" onClick={() => addAnalogSensor()}>
|
||||
@@ -723,7 +766,7 @@ const DashboardData: FC = () => {
|
||||
control={<Checkbox size="small" name="onlyFav" checked={onlyFav} onChange={() => setOnlyFav(!onlyFav)} />}
|
||||
label={
|
||||
<span style={{ fontSize: '12px' }}>
|
||||
only show favorites
|
||||
{LL.SHOW_FAV()}
|
||||
<StarIcon color="primary" sx={{ fontSize: 12 }} />
|
||||
</span>
|
||||
}
|
||||
@@ -749,7 +792,7 @@ const DashboardData: FC = () => {
|
||||
endIcon={getSortIcon(dv_sort.state, 'NAME')}
|
||||
onClick={() => dv_sort.fns.onToggleSort({ sortKey: 'NAME' })}
|
||||
>
|
||||
ENTITY NAME
|
||||
{LL.ENTITY_NAME()}
|
||||
</Button>
|
||||
</HeaderCell>
|
||||
<HeaderCell resize>
|
||||
@@ -759,7 +802,7 @@ const DashboardData: FC = () => {
|
||||
endIcon={getSortIcon(dv_sort.state, 'VALUE')}
|
||||
onClick={() => dv_sort.fns.onToggleSort({ sortKey: 'VALUE' })}
|
||||
>
|
||||
VALUE
|
||||
{LL.VALUE()}
|
||||
</Button>
|
||||
</HeaderCell>
|
||||
<HeaderCell stiff />
|
||||
@@ -806,7 +849,7 @@ const DashboardData: FC = () => {
|
||||
const renderDallasData = () => (
|
||||
<>
|
||||
<Typography sx={{ pt: 2, pb: 1 }} variant="h6" color="secondary">
|
||||
Temperature Sensors
|
||||
{LL.TEMP_SENSORS()}
|
||||
</Typography>
|
||||
<Table
|
||||
data={{ nodes: sensorData.sensors }}
|
||||
@@ -825,7 +868,7 @@ const DashboardData: FC = () => {
|
||||
endIcon={getSortIcon(sensor_sort.state, 'NAME')}
|
||||
onClick={() => sensor_sort.fns.onToggleSort({ sortKey: 'NAME' })}
|
||||
>
|
||||
NAME
|
||||
{LL.ENTITY_NAME()}
|
||||
</Button>
|
||||
</HeaderCell>
|
||||
<HeaderCell stiff>
|
||||
@@ -835,7 +878,7 @@ const DashboardData: FC = () => {
|
||||
endIcon={getSortIcon(sensor_sort.state, 'TEMPERATURE')}
|
||||
onClick={() => sensor_sort.fns.onToggleSort({ sortKey: 'TEMPERATURE' })}
|
||||
>
|
||||
TEMPERATURE
|
||||
{LL.VALUE()}
|
||||
</Button>
|
||||
</HeaderCell>
|
||||
<HeaderCell stiff />
|
||||
@@ -865,7 +908,7 @@ const DashboardData: FC = () => {
|
||||
const renderAnalogData = () => (
|
||||
<>
|
||||
<Typography sx={{ pt: 2, pb: 1 }} variant="h6" color="secondary">
|
||||
Analog Sensors
|
||||
{LL.ANALOG_SENSORS()}
|
||||
</Typography>
|
||||
|
||||
<Table data={{ nodes: sensorData.analogs }} theme={analog_theme} sort={analog_sort} layout={{ custom: true }}>
|
||||
@@ -890,7 +933,7 @@ const DashboardData: FC = () => {
|
||||
endIcon={getSortIcon(analog_sort.state, 'NAME')}
|
||||
onClick={() => analog_sort.fns.onToggleSort({ sortKey: 'NAME' })}
|
||||
>
|
||||
NAME
|
||||
{LL.ENTITY_NAME()}
|
||||
</Button>
|
||||
</HeaderCell>
|
||||
<HeaderCell stiff>
|
||||
@@ -900,10 +943,10 @@ const DashboardData: FC = () => {
|
||||
endIcon={getSortIcon(analog_sort.state, 'TYPE')}
|
||||
onClick={() => analog_sort.fns.onToggleSort({ sortKey: 'TYPE' })}
|
||||
>
|
||||
TYPE
|
||||
{LL.TYPE()}
|
||||
</Button>
|
||||
</HeaderCell>
|
||||
<HeaderCell stiff>VALUE</HeaderCell>
|
||||
<HeaderCell stiff>{LL.VALUE()}</HeaderCell>
|
||||
<HeaderCell stiff />
|
||||
</HeaderRow>
|
||||
</Header>
|
||||
@@ -943,14 +986,14 @@ const DashboardData: FC = () => {
|
||||
});
|
||||
|
||||
if (response.status === 204) {
|
||||
enqueueSnackbar('Analog deletion failed', { variant: 'error' });
|
||||
enqueueSnackbar(LL.ANALOG_SENSOR() + ' ' + LL.DELETION() + ' ' + LL.FAILED(), { variant: 'error' });
|
||||
} else if (response.status === 403) {
|
||||
enqueueSnackbar('Access denied', { variant: 'error' });
|
||||
enqueueSnackbar(LL.ACCESS_DENIED(), { variant: 'error' });
|
||||
} else {
|
||||
enqueueSnackbar('Analog sensor removed', { variant: 'success' });
|
||||
enqueueSnackbar(LL.ANALOG_SENSOR() + ' ' + LL.REMOVED(), { variant: 'success' });
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem updating analog sensor'), { variant: 'error' });
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
|
||||
} finally {
|
||||
setAnalog(undefined);
|
||||
fetchSensorData();
|
||||
@@ -971,14 +1014,14 @@ const DashboardData: FC = () => {
|
||||
});
|
||||
|
||||
if (response.status === 204) {
|
||||
enqueueSnackbar('Analog sensor update failed', { variant: 'error' });
|
||||
enqueueSnackbar(LL.ANALOG_SENSOR() + ' ' + LL.UPDATE() + ' ' + LL.FAILED(), { variant: 'error' });
|
||||
} else if (response.status === 403) {
|
||||
enqueueSnackbar('Access denied', { variant: 'error' });
|
||||
enqueueSnackbar(LL.ACCESS_DENIED(), { variant: 'error' });
|
||||
} else {
|
||||
enqueueSnackbar('Analog sensor updated', { variant: 'success' });
|
||||
enqueueSnackbar(LL.ANALOG_SENSOR() + ' ' + LL.UPDATED(), { variant: 'success' });
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem updating analog'), { variant: 'error' });
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
|
||||
} finally {
|
||||
setAnalog(undefined);
|
||||
fetchSensorData();
|
||||
@@ -990,7 +1033,9 @@ const DashboardData: FC = () => {
|
||||
if (analog) {
|
||||
return (
|
||||
<Dialog open={analog !== undefined} onClose={() => setAnalog(undefined)}>
|
||||
<DialogTitle>Edit Analog Sensor</DialogTitle>
|
||||
<DialogTitle>
|
||||
{LL.EDIT()} {LL.ANALOG_SENSOR()}
|
||||
</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item>
|
||||
@@ -1007,7 +1052,7 @@ const DashboardData: FC = () => {
|
||||
<Grid item>
|
||||
<ValidatedTextField
|
||||
name="n"
|
||||
label="Name"
|
||||
label={LL.ENTITY_NAME()}
|
||||
value={analog.n}
|
||||
sx={{ width: '20ch' }}
|
||||
variant="outlined"
|
||||
@@ -1015,7 +1060,13 @@ const DashboardData: FC = () => {
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<ValidatedTextField name="t" label="Type" value={analog.t} select onChange={updateValue(setAnalog)}>
|
||||
<ValidatedTextField
|
||||
name="t"
|
||||
label={LL.TYPE()}
|
||||
value={analog.t}
|
||||
select
|
||||
onChange={updateValue(setAnalog)}
|
||||
>
|
||||
{AnalogTypeNames.map((val, i) => (
|
||||
<MenuItem key={i} value={i}>
|
||||
{val}
|
||||
@@ -1038,7 +1089,7 @@ const DashboardData: FC = () => {
|
||||
<Grid item>
|
||||
<ValidatedTextField
|
||||
name="o"
|
||||
label="Offset"
|
||||
label={LL.OFFSET()}
|
||||
value={numberValue(analog.o)}
|
||||
sx={{ width: '20ch' }}
|
||||
type="number"
|
||||
@@ -1055,7 +1106,7 @@ const DashboardData: FC = () => {
|
||||
<Grid item>
|
||||
<ValidatedTextField
|
||||
name="o"
|
||||
label="Start Value"
|
||||
label={LL.STARTVALUE()}
|
||||
value={numberValue(analog.o)}
|
||||
sx={{ width: '20ch' }}
|
||||
type="number"
|
||||
@@ -1068,7 +1119,7 @@ const DashboardData: FC = () => {
|
||||
<Grid item>
|
||||
<ValidatedTextField
|
||||
name="f"
|
||||
label="Factor"
|
||||
label={LL.FACTOR()}
|
||||
value={numberValue(analog.f)}
|
||||
sx={{ width: '20ch' }}
|
||||
type="number"
|
||||
@@ -1084,7 +1135,7 @@ const DashboardData: FC = () => {
|
||||
<Grid item>
|
||||
<ValidatedTextField
|
||||
name="o"
|
||||
label="DAC Value"
|
||||
label={LL.VALUE()}
|
||||
value={numberValue(analog.o)}
|
||||
sx={{ width: '20ch' }}
|
||||
type="number"
|
||||
@@ -1100,7 +1151,7 @@ const DashboardData: FC = () => {
|
||||
<Grid item>
|
||||
<ValidatedTextField
|
||||
name="o"
|
||||
label="Value"
|
||||
label={LL.VALUE()}
|
||||
value={numberValue(analog.o)}
|
||||
sx={{ width: '20ch' }}
|
||||
type="number"
|
||||
@@ -1116,7 +1167,7 @@ const DashboardData: FC = () => {
|
||||
<Grid item>
|
||||
<ValidatedTextField
|
||||
name="f"
|
||||
label="Frequency"
|
||||
label={LL.CPU_FREQ()}
|
||||
value={numberValue(analog.f)}
|
||||
sx={{ width: '20ch' }}
|
||||
type="number"
|
||||
@@ -1131,7 +1182,7 @@ const DashboardData: FC = () => {
|
||||
<Grid item>
|
||||
<ValidatedTextField
|
||||
name="o"
|
||||
label="Dutycycle"
|
||||
label="Duty Cycle"
|
||||
value={numberValue(analog.o)}
|
||||
sx={{ width: '20ch' }}
|
||||
type="number"
|
||||
@@ -1147,13 +1198,13 @@ const DashboardData: FC = () => {
|
||||
)}
|
||||
</Grid>
|
||||
<Box color="warning.main" mt={2}>
|
||||
<Typography variant="body2">Warning: be careful when assigning a GPIO!</Typography>
|
||||
<Typography variant="body2">{LL.WARN_GPIO()}</Typography>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Box flexGrow={1} sx={{ '& button': { mt: 0 } }}>
|
||||
<Button startIcon={<RemoveIcon />} variant="outlined" color="error" onClick={() => sendRemoveAnalog()}>
|
||||
Remove
|
||||
{LL.REMOVE()}
|
||||
</Button>
|
||||
</Box>
|
||||
<Button
|
||||
@@ -1162,7 +1213,7 @@ const DashboardData: FC = () => {
|
||||
onClick={() => setAnalog(undefined)}
|
||||
color="secondary"
|
||||
>
|
||||
Cancel
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<SaveIcon />}
|
||||
@@ -1171,7 +1222,7 @@ const DashboardData: FC = () => {
|
||||
onClick={() => sendAnalog()}
|
||||
color="warning"
|
||||
>
|
||||
Save
|
||||
{LL.SAVE()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
@@ -1180,7 +1231,7 @@ const DashboardData: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="Device and Sensor Data" titleGutter>
|
||||
<SectionContent title={LL.DEVICE_SENSOR_DATA()} titleGutter>
|
||||
{renderCoreData()}
|
||||
{renderDeviceData()}
|
||||
{renderDeviceDialog()}
|
||||
@@ -1191,11 +1242,11 @@ const DashboardData: FC = () => {
|
||||
{renderAnalogDialog()}
|
||||
<ButtonRow>
|
||||
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={refreshData}>
|
||||
Refresh
|
||||
{LL.REFRESH()}
|
||||
</Button>
|
||||
{device_select.state.id && device_select.state.id !== 'sensor' && (
|
||||
<Button startIcon={<DownloadIcon />} variant="outlined" onClick={handleDownloadCsv}>
|
||||
Export
|
||||
{LL.EXPORT()}
|
||||
</Button>
|
||||
)}
|
||||
</ButtonRow>
|
||||
|
||||
@@ -32,10 +32,14 @@ import { ButtonRow, FormLoader, SectionContent } from '../components';
|
||||
|
||||
import { Status, busConnectionStatus, Stat } from './types';
|
||||
|
||||
import { formatDurationSec, pluralize, extractErrorMessage, useRest } from '../utils';
|
||||
import { extractErrorMessage, useRest } from '../utils';
|
||||
|
||||
import * as EMSESP from './api';
|
||||
|
||||
import type { Translation } from '../i18n/i18n-types';
|
||||
import { useI18nContext } from '../i18n/i18n-react';
|
||||
import parseMilliseconds from 'parse-ms';
|
||||
|
||||
export const isConnected = ({ status }: Status) => status !== busConnectionStatus.BUS_STATUS_OFFLINE;
|
||||
|
||||
const busStatusHighlight = ({ status }: Status, theme: Theme) => {
|
||||
@@ -51,19 +55,6 @@ const busStatusHighlight = ({ status }: Status, theme: Theme) => {
|
||||
}
|
||||
};
|
||||
|
||||
const busStatus = ({ status }: Status) => {
|
||||
switch (status) {
|
||||
case busConnectionStatus.BUS_STATUS_CONNECTED:
|
||||
return 'Connected';
|
||||
case busConnectionStatus.BUS_STATUS_TX_ERRORS:
|
||||
return 'Tx issues - try a different Tx Mode';
|
||||
case busConnectionStatus.BUS_STATUS_OFFLINE:
|
||||
return 'Disconnected';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
};
|
||||
|
||||
const showQuality = (stat: Stat) => {
|
||||
if (stat.q === 0 || stat.s + stat.f === 0) {
|
||||
return;
|
||||
@@ -81,12 +72,32 @@ const showQuality = (stat: Stat) => {
|
||||
const DashboardStatus: FC = () => {
|
||||
const { loadData, data, errorMessage } = useRest<Status>({ read: EMSESP.readStatus });
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const theme = useTheme();
|
||||
const [confirmScan, setConfirmScan] = useState<boolean>(false);
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const { me } = useContext(AuthenticatedContext);
|
||||
|
||||
const showName = (id: any) => {
|
||||
let name: keyof Translation['STATUS_NAMES'] = id;
|
||||
return LL.STATUS_NAMES[name]();
|
||||
};
|
||||
|
||||
const busStatus = ({ status }: Status) => {
|
||||
switch (status) {
|
||||
case busConnectionStatus.BUS_STATUS_CONNECTED:
|
||||
return LL.CONNECTED();
|
||||
case busConnectionStatus.BUS_STATUS_TX_ERRORS:
|
||||
return LL.TX_ISSUES();
|
||||
case busConnectionStatus.BUS_STATUS_OFFLINE:
|
||||
return LL.DISCONNECTED();
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
};
|
||||
|
||||
const stats_theme = tableTheme({
|
||||
Table: `
|
||||
--data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) 90px 90px 80px;
|
||||
@@ -137,24 +148,40 @@ const DashboardStatus: FC = () => {
|
||||
const scan = async () => {
|
||||
try {
|
||||
await EMSESP.scanDevices();
|
||||
enqueueSnackbar('Scanning for devices...', { variant: 'info' });
|
||||
enqueueSnackbar(LL.SCANNING() + '...', { variant: 'info' });
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem initiating scan'), { variant: 'error' });
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
|
||||
} finally {
|
||||
setConfirmScan(false);
|
||||
}
|
||||
};
|
||||
|
||||
const formatDurationSec = (duration_sec: number) => {
|
||||
const { days, hours, minutes, seconds } = parseMilliseconds(duration_sec * 1000);
|
||||
let formatted = ' ';
|
||||
if (days) {
|
||||
formatted += LL.NUM_DAYS({ num: days }) + ' ';
|
||||
}
|
||||
if (hours) {
|
||||
formatted += LL.NUM_HOURS({ num: hours }) + ' ';
|
||||
}
|
||||
if (minutes) {
|
||||
formatted += LL.NUM_MINUTES({ num: minutes }) + ' ';
|
||||
}
|
||||
formatted += LL.NUM_SECONDS({ num: seconds });
|
||||
return formatted;
|
||||
};
|
||||
|
||||
const renderScanDialog = () => (
|
||||
<Dialog open={confirmScan} onClose={() => setConfirmScan(false)}>
|
||||
<DialogTitle>EMS Device Scan</DialogTitle>
|
||||
<DialogContent dividers>Are you sure you want to initiate a full device scan of the EMS bus?</DialogContent>
|
||||
<DialogTitle>{LL.SCAN_DEVICES()}</DialogTitle>
|
||||
<DialogContent dividers>{LL.EMS_SCAN()}</DialogContent>
|
||||
<DialogActions>
|
||||
<Button startIcon={<CancelIcon />} variant="outlined" onClick={() => setConfirmScan(false)} color="secondary">
|
||||
Cancel
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
<Button startIcon={<PermScanWifiIcon />} variant="outlined" onClick={scan} color="primary" autoFocus>
|
||||
Scan
|
||||
{LL.SCAN()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
@@ -174,7 +201,7 @@ const DashboardStatus: FC = () => {
|
||||
<DirectionsBusIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="EMS Bus Status" secondary={busStatus(data) + formatDurationSec(data.uptime)} />
|
||||
<ListItemText primary={LL.EMS_BUS_STATUS()} secondary={busStatus(data) + formatDurationSec(data.uptime)} />
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
@@ -183,13 +210,13 @@ const DashboardStatus: FC = () => {
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary="Active Devices & Sensors"
|
||||
primary={LL.ACTIVE_DEVICES()}
|
||||
secondary={
|
||||
pluralize(data.num_devices, 'EMS Device') +
|
||||
LL.NUM_DEVICES({ num: data.num_devices }) +
|
||||
', ' +
|
||||
pluralize(data.num_sensors, 'Temperature Sensor') +
|
||||
LL.NUM_TEMP_SENSORS({ num: data.num_sensors }) +
|
||||
', ' +
|
||||
pluralize(data.num_analogs, 'Analog Sensor')
|
||||
LL.NUM_ANALOG_SENSORS({ num: data.num_analogs })
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
@@ -200,15 +227,15 @@ const DashboardStatus: FC = () => {
|
||||
<Header>
|
||||
<HeaderRow>
|
||||
<HeaderCell resize></HeaderCell>
|
||||
<HeaderCell stiff>SUCCESS</HeaderCell>
|
||||
<HeaderCell stiff>FAIL</HeaderCell>
|
||||
<HeaderCell stiff>QUALITY</HeaderCell>
|
||||
<HeaderCell stiff>{LL.SUCCESS()}</HeaderCell>
|
||||
<HeaderCell stiff>{LL.FAIL()}</HeaderCell>
|
||||
<HeaderCell stiff>{LL.QUALITY()}</HeaderCell>
|
||||
</HeaderRow>
|
||||
</Header>
|
||||
<Body>
|
||||
{tableList.map((stat: Stat) => (
|
||||
<Row key={stat.id} item={stat}>
|
||||
<Cell>{stat.id}</Cell>
|
||||
<Cell>{showName(stat.id)}</Cell>
|
||||
<Cell stiff>{Intl.NumberFormat().format(stat.s)}</Cell>
|
||||
<Cell stiff>{Intl.NumberFormat().format(stat.f)}</Cell>
|
||||
<Cell stiff>{showQuality(stat)}</Cell>
|
||||
@@ -223,7 +250,7 @@ const DashboardStatus: FC = () => {
|
||||
<Box display="flex" flexWrap="wrap">
|
||||
<Box flexGrow={1} sx={{ '& button': { mt: 2 } }}>
|
||||
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
|
||||
Refresh
|
||||
{LL.REFRESH()}
|
||||
</Button>
|
||||
</Box>
|
||||
<Box flexWrap="nowrap" whiteSpace="nowrap">
|
||||
@@ -235,7 +262,7 @@ const DashboardStatus: FC = () => {
|
||||
disabled={!me.admin}
|
||||
onClick={() => setConfirmScan(true)}
|
||||
>
|
||||
Scan for new devices
|
||||
{LL.SCAN_DEVICES()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</Box>
|
||||
@@ -245,7 +272,7 @@ const DashboardStatus: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="EMS Bus & Activity Status" titleGutter>
|
||||
<SectionContent title={LL.EMS_BUS_STATUS_TITLE()} titleGutter>
|
||||
{content()}
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
@@ -5,16 +5,20 @@ import { Tab } from '@mui/material';
|
||||
|
||||
import { RouterTabs, useRouterTab, useLayoutTitle } from '../components';
|
||||
|
||||
import { useI18nContext } from '../i18n/i18n-react';
|
||||
|
||||
import HelpInformation from './HelpInformation';
|
||||
|
||||
const Help: FC = () => {
|
||||
useLayoutTitle('Help');
|
||||
const { LL } = useI18nContext();
|
||||
const { routerTab } = useRouterTab();
|
||||
|
||||
useLayoutTitle(LL.HELP());
|
||||
|
||||
return (
|
||||
<>
|
||||
<RouterTabs value={routerTab}>
|
||||
<Tab value="information" label="EMS-ESP Help" />
|
||||
<Tab value="information" label={"EMS-ESP " + LL.HELP()} />
|
||||
</RouterTabs>
|
||||
<Routes>
|
||||
<Route path="information" element={<HelpInformation />} />
|
||||
|
||||
@@ -11,12 +11,17 @@ import MenuBookIcon from '@mui/icons-material/MenuBookTwoTone';
|
||||
import GitHubIcon from '@mui/icons-material/GitHub';
|
||||
import StarIcon from '@mui/icons-material/Star';
|
||||
import DownloadIcon from '@mui/icons-material/GetApp';
|
||||
import EastIcon from '@mui/icons-material/East';
|
||||
|
||||
import { extractErrorMessage } from '../utils';
|
||||
|
||||
import { useI18nContext } from '../i18n/i18n-react';
|
||||
|
||||
import * as EMSESP from './api';
|
||||
|
||||
const HelpInformation: FC = () => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const saveFile = (json: any, endpoint: string) => {
|
||||
@@ -31,7 +36,7 @@ const HelpInformation: FC = () => {
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
enqueueSnackbar('System information downloaded', { variant: 'info' });
|
||||
enqueueSnackbar(LL.DOWNLOAD_SUCCESSFUL(), { variant: 'info' });
|
||||
};
|
||||
|
||||
const callAPI = async (endpoint: string) => {
|
||||
@@ -42,83 +47,85 @@ const HelpInformation: FC = () => {
|
||||
id: 0
|
||||
});
|
||||
if (response.status !== 200) {
|
||||
enqueueSnackbar('API call failed', { variant: 'error' });
|
||||
enqueueSnackbar(LL.PROBLEM_LOADING(), { variant: 'error' });
|
||||
} else {
|
||||
saveFile(response.data, endpoint);
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem with downloading'), { variant: 'error' });
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="Support Information" titleGutter>
|
||||
<SectionContent title={LL.SUPPORT_INFORMATION()} titleGutter>
|
||||
<List>
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<MenuBookIcon />
|
||||
<MenuBookIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
|
||||
</ListItemAvatar>
|
||||
<ListItemText>
|
||||
Visit the online
|
||||
{LL.HELP_INFORMATION_1()}
|
||||
<EastIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
|
||||
|
||||
<Link target="_blank" href="https://emsesp.github.io/docs" color="primary">
|
||||
{'Wiki'}
|
||||
{LL.CLICK_HERE()}
|
||||
</Link>
|
||||
to get instructions on how to
|
||||
<Link
|
||||
target="_blank"
|
||||
href="https://emsesp.github.io/docs/#/Configure-firmware?id=ems-esp-settings"
|
||||
color="primary"
|
||||
>
|
||||
{'configure'}
|
||||
</Link>
|
||||
EMS-ESP and access other information.
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<CommentIcon />
|
||||
<CommentIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
|
||||
</ListItemAvatar>
|
||||
<ListItemText>
|
||||
For live community chat join our
|
||||
{LL.HELP_INFORMATION_2()}
|
||||
<EastIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
|
||||
|
||||
<Link target="_blank" href="https://discord.gg/3J3GgnzpyT" color="primary">
|
||||
{'Discord'}
|
||||
{LL.CLICK_HERE()}
|
||||
</Link>
|
||||
server.
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<GitHubIcon />
|
||||
<GitHubIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
|
||||
</ListItemAvatar>
|
||||
|
||||
<ListItemText>
|
||||
Submit a
|
||||
{LL.HELP_INFORMATION_3()}
|
||||
<EastIcon style={{ fontSize: 24, color: 'lightblue', verticalAlign: 'middle' }} />
|
||||
<Link target="_blank" href="https://github.com/emsesp/EMS-ESP32/issues/new/choose" color="primary">
|
||||
support issue
|
||||
{LL.CLICK_HERE()}
|
||||
</Link>
|
||||
for requesting a new feature or reporting a bug.
|
||||
<br />
|
||||
Make sure you also
|
||||
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={() => callAPI('info')}>
|
||||
download
|
||||
<i>({LL.HELP_INFORMATION_4()}</i>
|
||||
<Button
|
||||
startIcon={<DownloadIcon />}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={() => callAPI('info')}
|
||||
>
|
||||
{LL.SUPPORT_INFO()}
|
||||
</Button>
|
||||
and attach your system details for a faster response.
|
||||
)
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
</List>
|
||||
|
||||
<Box border={1} p={1} mt={4}>
|
||||
<Box border={1} p={1} mt={4} color="orange">
|
||||
<Typography align="center" variant="h6" color="orange">
|
||||
EMS-ESP will always be a free and open-source project
|
||||
<br></br>Please consider supporting it with a
|
||||
<StarIcon style={{ fontSize: 16, color: 'yellow', verticalAlign: 'middle' }} /> on
|
||||
<Link href="https://github.com/emsesp/EMS-ESP32" color="primary">
|
||||
{'GitHub'}
|
||||
</Link>
|
||||
{LL.HELP_INFORMATION_5()}
|
||||
</Typography>
|
||||
<Typography align="center">
|
||||
<Link href="https://github.com/emsesp/EMS-ESP32" color="primary">
|
||||
{'Github'}
|
||||
</Link>
|
||||
<StarIcon style={{ fontSize: 20, color: 'yellow', verticalAlign: 'middle' }} />
|
||||
</Typography>
|
||||
<Typography color="white" align="center">
|
||||
@proddy @MichaelDvP
|
||||
</Typography>
|
||||
<Typography align="center">@proddy @MichaelDvP</Typography>
|
||||
</Box>
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
@@ -6,6 +6,8 @@ import { AuthenticatedContext } from '../contexts/authentication';
|
||||
|
||||
import { PROJECT_PATH } from '../api/env';
|
||||
|
||||
import { useI18nContext } from '../i18n/i18n-react';
|
||||
|
||||
import TuneIcon from '@mui/icons-material/Tune';
|
||||
import DashboardIcon from '@mui/icons-material/Dashboard';
|
||||
import LayoutMenuItem from '../components/layout/LayoutMenuItem';
|
||||
@@ -13,17 +15,18 @@ import InfoIcon from '@mui/icons-material/Info';
|
||||
|
||||
const ProjectMenu: FC = () => {
|
||||
const authenticatedContext = useContext(AuthenticatedContext);
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
return (
|
||||
<List>
|
||||
<LayoutMenuItem icon={DashboardIcon} label="Dashboard" to={`/${PROJECT_PATH}/dashboard`} />
|
||||
<LayoutMenuItem icon={DashboardIcon} label={LL.DASHBOARD()} to={`/${PROJECT_PATH}/dashboard`} />
|
||||
<LayoutMenuItem
|
||||
icon={TuneIcon}
|
||||
label="Settings"
|
||||
label={LL.SETTINGS()}
|
||||
to={`/${PROJECT_PATH}/settings`}
|
||||
disabled={!authenticatedContext.me.admin}
|
||||
/>
|
||||
<LayoutMenuItem icon={InfoIcon} label="Help" to={`/${PROJECT_PATH}/help`} />
|
||||
<LayoutMenuItem icon={InfoIcon} label={LL.HELP()} to={`/${PROJECT_PATH}/help`} />
|
||||
</List>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,18 +5,22 @@ import { Tab } from '@mui/material';
|
||||
|
||||
import { RouterTabs, useRouterTab, useLayoutTitle } from '../components';
|
||||
|
||||
import { useI18nContext } from '../i18n/i18n-react';
|
||||
|
||||
import SettingsApplication from './SettingsApplication';
|
||||
import SettingsCustomization from './SettingsCustomization';
|
||||
|
||||
const Settings: FC = () => {
|
||||
useLayoutTitle('Settings');
|
||||
const { LL } = useI18nContext();
|
||||
const { routerTab } = useRouterTab();
|
||||
|
||||
useLayoutTitle(LL.SETTINGS());
|
||||
|
||||
return (
|
||||
<>
|
||||
<RouterTabs value={routerTab}>
|
||||
<Tab value="application" label="Application Settings" />
|
||||
<Tab value="customization" label="Customization" />
|
||||
<Tab value="application" label={LL.APPLICATION_SETTINGS()} />
|
||||
<Tab value="customization" label={LL.CUSTOMIZATION()} />
|
||||
</RouterTabs>
|
||||
<Routes>
|
||||
<Route path="application" element={<SettingsApplication />} />
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ValidateFieldsError } from 'async-validator';
|
||||
|
||||
import { useSnackbar } from 'notistack';
|
||||
|
||||
import { Box, Button, Checkbox, MenuItem, Grid, Typography, Divider } from '@mui/material';
|
||||
import { Box, Button, Checkbox, MenuItem, Grid, Typography, Divider, InputAdornment } from '@mui/material';
|
||||
|
||||
import SaveIcon from '@mui/icons-material/Save';
|
||||
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
|
||||
@@ -24,6 +24,8 @@ import { numberValue, extractErrorMessage, updateValue, useRest } from '../utils
|
||||
import * as EMSESP from './api';
|
||||
import { Settings, BOARD_PROFILES } from './types';
|
||||
|
||||
import { useI18nContext } from '../i18n/i18n-react';
|
||||
|
||||
export function boardProfileSelectItems() {
|
||||
return Object.keys(BOARD_PROFILES).map((code) => (
|
||||
<MenuItem key={code} value={code}>
|
||||
@@ -38,6 +40,8 @@ const SettingsApplication: FC = () => {
|
||||
update: EMSESP.writeSettings
|
||||
});
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const updateFormValue = updateValue(setData);
|
||||
@@ -65,7 +69,7 @@ const SettingsApplication: FC = () => {
|
||||
});
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem fetching board profile'), { variant: 'error' });
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
|
||||
} finally {
|
||||
setProcessingBoard(false);
|
||||
}
|
||||
@@ -102,26 +106,23 @@ const SettingsApplication: FC = () => {
|
||||
validateAndSubmit();
|
||||
try {
|
||||
await EMSESP.restart();
|
||||
enqueueSnackbar('EMS-ESP is restarting...', { variant: 'info' });
|
||||
enqueueSnackbar(LL.APPLICATION_RESTARTING(), { variant: 'info' });
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem restarting device'), { variant: 'error' });
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
||||
Interface Board Profile
|
||||
Interface {LL.BOARD_PROFILE()}
|
||||
</Typography>
|
||||
<Box color="warning.main">
|
||||
<Typography variant="body2">
|
||||
Select a pre-configured interface board profile from the list below or choose "Custom" to configure your own
|
||||
hardware settings.
|
||||
</Typography>
|
||||
<Typography variant="body2">{LL.BOARD_PROFILE_TEXT()}</Typography>
|
||||
</Box>
|
||||
<ValidatedTextField
|
||||
name="board_profile"
|
||||
label="Board Profile"
|
||||
label={LL.BOARD_PROFILE()}
|
||||
value={data.board_profile}
|
||||
disabled={processingBoard}
|
||||
variant="outlined"
|
||||
@@ -170,7 +171,7 @@ const SettingsApplication: FC = () => {
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="pbutton_gpio"
|
||||
label="Button GPIO"
|
||||
label={LL.BUTTON() + ' GPIO'}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={numberValue(data.pbutton_gpio)}
|
||||
@@ -184,7 +185,7 @@ const SettingsApplication: FC = () => {
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="dallas_gpio"
|
||||
label="Temperature GPIO (0=disabled)"
|
||||
label={LL.TEMPERATURE() + ' GPIO (0=' + LL.DISABLED() + ')'}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={numberValue(data.dallas_gpio)}
|
||||
@@ -198,7 +199,7 @@ const SettingsApplication: FC = () => {
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="led_gpio"
|
||||
label="LED GPIO (0=disabled)"
|
||||
label={'LED GPIO (0=' + LL.DISABLED() + ')'}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={numberValue(data.led_gpio)}
|
||||
@@ -221,7 +222,7 @@ const SettingsApplication: FC = () => {
|
||||
margin="normal"
|
||||
select
|
||||
>
|
||||
<MenuItem value={0}>No Ethernet Module</MenuItem>
|
||||
<MenuItem value={0}>{LL.DISABLED()}</MenuItem>
|
||||
<MenuItem value={1}>LAN8720</MenuItem>
|
||||
<MenuItem value={2}>TLK110</MenuItem>
|
||||
</ValidatedTextField>
|
||||
@@ -231,7 +232,7 @@ const SettingsApplication: FC = () => {
|
||||
<Grid item>
|
||||
<ValidatedTextField
|
||||
name="eth_power"
|
||||
label="Eth Power GPIO (-1=disabled)"
|
||||
label={'Eth Power GPIO (-1=' + LL.DISABLED() + ')'}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={numberValue(data.eth_power)}
|
||||
@@ -277,7 +278,7 @@ const SettingsApplication: FC = () => {
|
||||
</>
|
||||
)}
|
||||
<Typography variant="h6" color="primary">
|
||||
EMS Bus Settings
|
||||
EMS Bus {LL.SETTINGS()}
|
||||
</Typography>
|
||||
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
|
||||
<Grid item xs={6}>
|
||||
@@ -319,63 +320,90 @@ const SettingsApplication: FC = () => {
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
||||
General Options
|
||||
{LL.GENERAL_OPTIONS()}
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
'& .MuiTextField-root': { width: '25ch' }
|
||||
}}
|
||||
>
|
||||
<ValidatedTextField
|
||||
name="locale"
|
||||
label={LL.LANGUAGE_ENTITIES()}
|
||||
disabled={saving}
|
||||
value={data.locale}
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
size="small"
|
||||
select
|
||||
>
|
||||
<MenuItem value="en">English (EN)</MenuItem>
|
||||
<MenuItem value="de">Deutsch (DE)</MenuItem>
|
||||
<MenuItem value="nl">Nederlands (NL)</MenuItem>
|
||||
<MenuItem value="se">Svenska (SE)</MenuItem>
|
||||
<MenuItem disabled value="pl">Polski (PL)</MenuItem>
|
||||
<MenuItem disabled value="no">Norsk (NO)</MenuItem>
|
||||
</ValidatedTextField>
|
||||
</Box>
|
||||
{data.led_gpio !== 0 && (
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox checked={data.hide_led} onChange={updateFormValue} name="hide_led" />}
|
||||
label="Hide LED"
|
||||
label={LL.HIDE_LED()}
|
||||
disabled={saving}
|
||||
/>
|
||||
)}
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox checked={data.telnet_enabled} onChange={updateFormValue} name="telnet_enabled" />}
|
||||
label="Enable Telnet Console"
|
||||
label={LL.ENABLE_TELNET()}
|
||||
disabled={saving}
|
||||
/>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox checked={data.analog_enabled} onChange={updateFormValue} name="analog_enabled" />}
|
||||
label="Enable Analog Sensors"
|
||||
label={LL.ENABLE_ANALOG()}
|
||||
disabled={saving}
|
||||
/>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox checked={data.fahrenheit} onChange={updateFormValue} name="fahrenheit" />}
|
||||
label="Convert temperature values to Fahrenheit"
|
||||
label={LL.CONVERT_FAHRENHEIT()}
|
||||
disabled={saving}
|
||||
/>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox checked={data.notoken_api} onChange={updateFormValue} name="notoken_api" />}
|
||||
label="Bypass Access Token authorization on API calls"
|
||||
label={LL.BYPASS_TOKEN()}
|
||||
disabled={saving}
|
||||
/>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox checked={data.readonly_mode} onChange={updateFormValue} name="readonly_mode" />}
|
||||
label="Enable Read only mode (blocks all outgoing EMS Tx write commands)"
|
||||
label={LL.READONLY()}
|
||||
disabled={saving}
|
||||
/>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox checked={data.low_clock} onChange={updateFormValue} name="low_clock" />}
|
||||
label="Underclock CPU speed"
|
||||
label={LL.UNDERCLOCK_CPU()}
|
||||
disabled={saving}
|
||||
/>
|
||||
<Grid container spacing={0} direction="row" justifyContent="flex-start" alignItems="flex-start">
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox checked={data.shower_timer} onChange={updateFormValue} name="shower_timer" />}
|
||||
label="Enable Shower Timer"
|
||||
label={LL.ENABLE_SHOWER_TIMER()}
|
||||
disabled={saving}
|
||||
/>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox checked={data.shower_alert} onChange={updateFormValue} name="shower_alert" />}
|
||||
label="Enable Shower Alert"
|
||||
label={LL.ENABLE_SHOWER_ALERT()}
|
||||
disabled={!data.shower_timer}
|
||||
/>
|
||||
{data.shower_alert && (
|
||||
<>
|
||||
<Grid item xs={2}>
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="shower_alert_trigger"
|
||||
label="Trigger Time (minutes)"
|
||||
label={LL.TRIGGER_TIME()}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">{LL.MINUTES()}</InputAdornment>
|
||||
}}
|
||||
variant="outlined"
|
||||
value={data.shower_alert_trigger}
|
||||
type="number"
|
||||
@@ -383,11 +411,14 @@ const SettingsApplication: FC = () => {
|
||||
disabled={!data.shower_timer}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={2}>
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="shower_alert_coldshot"
|
||||
label="Cold Shot Time (seconds)"
|
||||
label={LL.COLD_SHOT_DURATION()}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
|
||||
}}
|
||||
variant="outlined"
|
||||
value={data.shower_alert_coldshot}
|
||||
type="number"
|
||||
@@ -399,13 +430,13 @@ const SettingsApplication: FC = () => {
|
||||
)}
|
||||
</Grid>
|
||||
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
||||
Formatting Options
|
||||
{LL.FORMATTING_OPTIONS()}
|
||||
</Typography>
|
||||
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
name="bool_dashboard"
|
||||
label="Boolean Format Dashboard"
|
||||
label={LL.BOOLEAN_FORMAT_DASHBOARD()}
|
||||
value={data.bool_dashboard}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
@@ -413,8 +444,8 @@ const SettingsApplication: FC = () => {
|
||||
margin="normal"
|
||||
select
|
||||
>
|
||||
<MenuItem value={1}>on/off</MenuItem>
|
||||
<MenuItem value={2}>ON/OFF</MenuItem>
|
||||
<MenuItem value={1}>{LL.ONOFF()}</MenuItem>
|
||||
<MenuItem value={2}>{LL.ONOFF_CAP()}</MenuItem>
|
||||
<MenuItem value={3}>true/false</MenuItem>
|
||||
<MenuItem value={5}>1/0</MenuItem>
|
||||
</ValidatedTextField>
|
||||
@@ -422,7 +453,7 @@ const SettingsApplication: FC = () => {
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
name="bool_format"
|
||||
label="Boolean Format API/MQTT"
|
||||
label={LL.BOOLEAN_FORMAT_API()}
|
||||
value={data.bool_format}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
@@ -430,8 +461,8 @@ const SettingsApplication: FC = () => {
|
||||
margin="normal"
|
||||
select
|
||||
>
|
||||
<MenuItem value={1}>"on"/"off"</MenuItem>
|
||||
<MenuItem value={2}>"ON"/"OFF"</MenuItem>
|
||||
<MenuItem value={1}>{LL.ONOFF()}</MenuItem>
|
||||
<MenuItem value={2}>{LL.ONOFF_CAP()}</MenuItem>
|
||||
<MenuItem value={3}>"true"/"false"</MenuItem>
|
||||
<MenuItem value={4}>true/false</MenuItem>
|
||||
<MenuItem value={5}>"1"/"0"</MenuItem>
|
||||
@@ -441,7 +472,7 @@ const SettingsApplication: FC = () => {
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
name="enum_format"
|
||||
label="Enum Format API/MQTT"
|
||||
label={LL.ENUM_FORMAT()}
|
||||
value={data.enum_format}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
@@ -449,29 +480,29 @@ const SettingsApplication: FC = () => {
|
||||
margin="normal"
|
||||
select
|
||||
>
|
||||
<MenuItem value={1}>Value</MenuItem>
|
||||
<MenuItem value={2}>Index</MenuItem>
|
||||
<MenuItem value={1}>{LL.VALUE()}</MenuItem>
|
||||
<MenuItem value={2}>{LL.INDEX()}</MenuItem>
|
||||
</ValidatedTextField>
|
||||
</Grid>
|
||||
</Grid>
|
||||
{data.dallas_gpio !== 0 && (
|
||||
<>
|
||||
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
||||
Temperature Sensors
|
||||
{LL.TEMP_SENSORS()}
|
||||
</Typography>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox checked={data.dallas_parasite} onChange={updateFormValue} name="dallas_parasite" />}
|
||||
label="Enable parasite power"
|
||||
label={LL.ENABLE_PARASITE()}
|
||||
disabled={saving}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
||||
Logging
|
||||
{LL.LOGGING()}
|
||||
</Typography>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox checked={data.trace_raw} onChange={updateFormValue} name="trace_raw" />}
|
||||
label="Log EMS telegrams in hexadecimal"
|
||||
label={LL.LOG_HEX()}
|
||||
disabled={saving}
|
||||
/>
|
||||
<BlockFormControlLabel
|
||||
@@ -483,11 +514,11 @@ const SettingsApplication: FC = () => {
|
||||
disabled={saving}
|
||||
/>
|
||||
}
|
||||
label="Enable Syslog"
|
||||
label={LL.ENABLE_SYSLOG()}
|
||||
/>
|
||||
{data.syslog_enabled && (
|
||||
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
|
||||
<Grid item xs={5}>
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="syslog_host"
|
||||
@@ -500,7 +531,7 @@ const SettingsApplication: FC = () => {
|
||||
disabled={saving}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="syslog_port"
|
||||
@@ -514,7 +545,7 @@ const SettingsApplication: FC = () => {
|
||||
disabled={saving}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={5}>
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
name="syslog_level"
|
||||
label="Log Level"
|
||||
@@ -534,11 +565,14 @@ const SettingsApplication: FC = () => {
|
||||
<MenuItem value={9}>ALL</MenuItem>
|
||||
</ValidatedTextField>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Grid item xs={4}>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="syslog_mark_interval"
|
||||
label="Mark Interval (seconds, 0=off)"
|
||||
label={LL.MARK_INTERVAL()}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
|
||||
}}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.syslog_mark_interval}
|
||||
@@ -551,9 +585,9 @@ const SettingsApplication: FC = () => {
|
||||
</Grid>
|
||||
)}
|
||||
{restartNeeded && (
|
||||
<MessageBox my={2} level="warning" message="EMS-ESP needs to be restarted to apply changed system settings">
|
||||
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT()}>
|
||||
<Button startIcon={<PowerSettingsNewIcon />} variant="contained" color="error" onClick={restart}>
|
||||
Restart
|
||||
{LL.RESTART()}
|
||||
</Button>
|
||||
</MessageBox>
|
||||
)}
|
||||
@@ -567,7 +601,7 @@ const SettingsApplication: FC = () => {
|
||||
type="submit"
|
||||
onClick={validateAndSubmit}
|
||||
>
|
||||
Save
|
||||
{LL.SAVE()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
)}
|
||||
@@ -576,7 +610,7 @@ const SettingsApplication: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="Application Settings" titleGutter>
|
||||
<SectionContent title={LL.APPLICATION_SETTINGS()} titleGutter>
|
||||
{content()}
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
|
||||
import { Table } from '@table-library/react-table-library/table';
|
||||
import { useTheme } from '@table-library/react-table-library/theme';
|
||||
import { useSort, SortToggleType } from '@table-library/react-table-library/sort';
|
||||
import { Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
|
||||
|
||||
import { useSnackbar } from 'notistack';
|
||||
@@ -28,9 +27,6 @@ import SaveIcon from '@mui/icons-material/Save';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
|
||||
import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore';
|
||||
import KeyboardArrowUpOutlinedIcon from '@mui/icons-material/KeyboardArrowUpOutlined';
|
||||
import KeyboardArrowDownOutlinedIcon from '@mui/icons-material/KeyboardArrowDownOutlined';
|
||||
import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import FilterListIcon from '@mui/icons-material/FilterList';
|
||||
|
||||
@@ -40,16 +36,22 @@ import { ButtonRow, FormLoader, ValidatedTextField, SectionContent } from '../co
|
||||
|
||||
import * as EMSESP from './api';
|
||||
|
||||
import { extractErrorMessage } from '../utils';
|
||||
import { extractErrorMessage, updateValue } from '../utils';
|
||||
|
||||
import { DeviceShort, Devices, DeviceEntity, DeviceEntityMask } from './types';
|
||||
|
||||
import { useI18nContext } from '../i18n/i18n-react';
|
||||
|
||||
export const APIURL = window.location.origin + '/api/';
|
||||
|
||||
const SettingsCustomization: FC = () => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const [deviceEntities, setDeviceEntities] = useState<DeviceEntity[]>([{ id: '', v: 0, n: '', m: 0, w: false }]);
|
||||
const emptyDeviceEntity = { id: '', v: 0, n: '', cn: '', m: 0, w: false };
|
||||
|
||||
const [deviceEntities, setDeviceEntities] = useState<DeviceEntity[]>([emptyDeviceEntity]);
|
||||
const [devices, setDevices] = useState<Devices>();
|
||||
const [errorMessage, setErrorMessage] = useState<string>();
|
||||
const [selectedDevice, setSelectedDevice] = useState<number>(-1);
|
||||
@@ -57,6 +59,8 @@ const SettingsCustomization: FC = () => {
|
||||
const [selectedFilters, setSelectedFilters] = useState<number>(0);
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const [deviceEntity, setDeviceEntity] = useState<DeviceEntity>();
|
||||
|
||||
// eslint-disable-next-line
|
||||
const [masks, setMasks] = useState(() => ['']);
|
||||
|
||||
@@ -92,6 +96,7 @@ const SettingsCustomization: FC = () => {
|
||||
Row: `
|
||||
background-color: #1e1e1e;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
.td {
|
||||
border-top: 1px solid #565656;
|
||||
@@ -104,6 +109,11 @@ const SettingsCustomization: FC = () => {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
&:hover .td {
|
||||
border-top: 1px solid #177ac9;
|
||||
border-bottom: 1px solid #177ac9;
|
||||
}
|
||||
|
||||
&:nth-of-type(odd) .td {
|
||||
background-color: #303030;
|
||||
}
|
||||
@@ -118,50 +128,24 @@ const SettingsCustomization: FC = () => {
|
||||
`
|
||||
});
|
||||
|
||||
const getSortIcon = (state: any, sortKey: any) => {
|
||||
if (state.sortKey === sortKey && state.reverse) {
|
||||
return <KeyboardArrowDownOutlinedIcon />;
|
||||
}
|
||||
if (state.sortKey === sortKey && !state.reverse) {
|
||||
return <KeyboardArrowUpOutlinedIcon />;
|
||||
}
|
||||
return <UnfoldMoreOutlinedIcon />;
|
||||
};
|
||||
|
||||
const entity_sort = useSort(
|
||||
{ nodes: deviceEntities },
|
||||
{},
|
||||
{
|
||||
sortIcon: {
|
||||
iconDefault: <UnfoldMoreOutlinedIcon />,
|
||||
iconUp: <KeyboardArrowUpOutlinedIcon />,
|
||||
iconDown: <KeyboardArrowDownOutlinedIcon />
|
||||
},
|
||||
sortToggleType: SortToggleType.AlternateWithReset,
|
||||
sortFns: {
|
||||
NAME: (array) => array.sort((a, b) => a.id.localeCompare(b.id))
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const fetchDevices = useCallback(async () => {
|
||||
try {
|
||||
setDevices((await EMSESP.readDevices()).data);
|
||||
} catch (error: unknown) {
|
||||
setErrorMessage(extractErrorMessage(error, 'Failed to fetch device list'));
|
||||
setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
||||
}
|
||||
}, []);
|
||||
}, [LL]);
|
||||
|
||||
const setInitialMask = (data: DeviceEntity[]) => {
|
||||
setDeviceEntities(data.map((de) => ({ ...de, om: de.m })));
|
||||
setDeviceEntities(data.map((de) => ({ ...de, o_m: de.m, o_cn: de.cn })));
|
||||
};
|
||||
|
||||
const fetchDeviceEntities = async (unique_id: number) => {
|
||||
try {
|
||||
const data = (await EMSESP.readDeviceEntities({ id: unique_id })).data;
|
||||
setInitialMask(data);
|
||||
const new_deviceEntities = (await EMSESP.readDeviceEntities({ id: unique_id })).data;
|
||||
setInitialMask(new_deviceEntities);
|
||||
} catch (error: unknown) {
|
||||
setErrorMessage(extractErrorMessage(error, 'Problem fetching device entities'));
|
||||
setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -183,12 +167,16 @@ const SettingsCustomization: FC = () => {
|
||||
function formatName(de: DeviceEntity) {
|
||||
if (de.n === undefined || de.n === de.id) {
|
||||
return de.id;
|
||||
} else if (de.n === '') {
|
||||
return 'Command: ' + de.id;
|
||||
}
|
||||
|
||||
if (de.n === '') {
|
||||
return LL.COMMAND() + ': ' + de.id;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{de.n} (
|
||||
{de.cn !== undefined && de.cn !== '' ? de.cn : de.n}
|
||||
(
|
||||
<Link target="_blank" href={APIURL + devices?.devices[selectedDevice].t + '/' + de.id}>
|
||||
{de.id}
|
||||
</Link>
|
||||
@@ -250,9 +238,9 @@ const SettingsCustomization: FC = () => {
|
||||
const resetCustomization = async () => {
|
||||
try {
|
||||
await EMSESP.resetCustomizations();
|
||||
enqueueSnackbar('All customizations have been removed. Restarting...', { variant: 'info' });
|
||||
enqueueSnackbar(LL.CUSTOMIZATIONS_RESTART(), { variant: 'info' });
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem resetting customizations'), { variant: 'error' });
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
|
||||
} finally {
|
||||
setConfirmReset(false);
|
||||
}
|
||||
@@ -261,26 +249,28 @@ const SettingsCustomization: FC = () => {
|
||||
const saveCustomization = async () => {
|
||||
if (devices && deviceEntities && selectedDevice !== -1) {
|
||||
const masked_entities = deviceEntities
|
||||
.filter((de) => de.m !== de.om)
|
||||
.map((new_de) => new_de.m.toString(16).padStart(2, '0') + new_de.id);
|
||||
.filter((de) => de.m !== de.o_m || de.cn !== de.o_cn)
|
||||
.map((new_de) => new_de.m.toString(16).padStart(2, '0') + new_de.id + (new_de.cn ? '|' + new_de.cn : ''));
|
||||
|
||||
if (masked_entities.length > 60) {
|
||||
enqueueSnackbar('Selected entities exceeded limit of 60. Please Save in batches', { variant: 'warning' });
|
||||
// check size in bytes to match buffer in CPP, which is 4096
|
||||
const bytes = new TextEncoder().encode(JSON.stringify(masked_entities)).length;
|
||||
if (bytes > 4000) {
|
||||
enqueueSnackbar(LL.CUSTOMIZATIONS_FULL(), { variant: 'warning' });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await EMSESP.writeMaskedEntities({
|
||||
const response = await EMSESP.writeCustomEntities({
|
||||
id: devices?.devices[selectedDevice].i,
|
||||
entity_ids: masked_entities
|
||||
});
|
||||
if (response.status === 200) {
|
||||
enqueueSnackbar('Customization saved', { variant: 'success' });
|
||||
enqueueSnackbar(LL.CUSTOMIZATIONS_SAVED(), { variant: 'success' });
|
||||
} else {
|
||||
enqueueSnackbar('Customization save failed', { variant: 'error' });
|
||||
enqueueSnackbar(LL.PROBLEM_UPDATING(), { variant: 'error' });
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
enqueueSnackbar(extractErrorMessage(error, 'Problem sending entity list'), { variant: 'error' });
|
||||
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
|
||||
}
|
||||
setInitialMask(deviceEntities);
|
||||
}
|
||||
@@ -294,21 +284,17 @@ const SettingsCustomization: FC = () => {
|
||||
return (
|
||||
<>
|
||||
<Box mb={2} color="warning.main">
|
||||
<Typography variant="body2">Select a device and customize each of its entities using the options:</Typography>
|
||||
<Typography variant="body2">{LL.CUSTOMIZATIONS_HELP_1()}</Typography>
|
||||
<Typography variant="body2">
|
||||
<OptionIcon type="favorite" isSet={true} />
|
||||
=mark as favorite
|
||||
<OptionIcon type="readonly" isSet={true} />
|
||||
=disable write action
|
||||
<OptionIcon type="api_mqtt_exclude" isSet={true} />
|
||||
=exclude from MQTT and API
|
||||
<OptionIcon type="web_exclude" isSet={true} />
|
||||
=hide from Dashboard
|
||||
<OptionIcon type="favorite" isSet={true} />={LL.CUSTOMIZATIONS_HELP_2()}
|
||||
<OptionIcon type="readonly" isSet={true} />={LL.CUSTOMIZATIONS_HELP_3()}
|
||||
<OptionIcon type="api_mqtt_exclude" isSet={true} />={LL.CUSTOMIZATIONS_HELP_4()}
|
||||
<OptionIcon type="web_exclude" isSet={true} />={LL.CUSTOMIZATIONS_HELP_5()}
|
||||
</Typography>
|
||||
</Box>
|
||||
<ValidatedTextField
|
||||
name="device"
|
||||
label="EMS Device"
|
||||
label={'EMS ' + LL.DEVICE()}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={selectedDevice}
|
||||
@@ -317,7 +303,7 @@ const SettingsCustomization: FC = () => {
|
||||
select
|
||||
>
|
||||
<MenuItem disabled key={0} value={-1}>
|
||||
Select a device...
|
||||
{LL.SELECT_DEVICE()}...
|
||||
</MenuItem>
|
||||
{devices.devices.map((device: DeviceShort, index) => (
|
||||
<MenuItem key={index} value={index}>
|
||||
@@ -329,6 +315,29 @@ const SettingsCustomization: FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const editEntity = (de: DeviceEntity) => {
|
||||
if (de.cn === undefined) {
|
||||
de.cn = '';
|
||||
}
|
||||
setDeviceEntity(de);
|
||||
};
|
||||
|
||||
const updateEntity = () => {
|
||||
if (deviceEntity) {
|
||||
setDeviceEntities((prevState) => {
|
||||
const newState = prevState.map((obj) => {
|
||||
if (obj.id === deviceEntity.id) {
|
||||
return { ...obj, cn: deviceEntity.cn };
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
return newState;
|
||||
});
|
||||
}
|
||||
|
||||
setDeviceEntity(undefined);
|
||||
};
|
||||
|
||||
const renderDeviceData = () => {
|
||||
if (devices?.devices.length === 0 || deviceEntities[0].id === '') {
|
||||
return;
|
||||
@@ -401,7 +410,7 @@ const SettingsCustomization: FC = () => {
|
||||
color="inherit"
|
||||
onClick={() => maskDisabled(false)}
|
||||
>
|
||||
set all
|
||||
{LL.SET_ALL()}
|
||||
<OptionIcon type="api_mqtt_exclude" isSet={false} />
|
||||
<OptionIcon type="web_exclude" isSet={false} />
|
||||
</Button>
|
||||
@@ -416,35 +425,30 @@ const SettingsCustomization: FC = () => {
|
||||
color="inherit"
|
||||
onClick={() => maskDisabled(true)}
|
||||
>
|
||||
set all
|
||||
{LL.SET_ALL()}
|
||||
<OptionIcon type="api_mqtt_exclude" isSet={true} />
|
||||
<OptionIcon type="web_exclude" isSet={true} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Table data={{ nodes: shown_data }} theme={entities_theme} sort={entity_sort} layout={{ custom: true }}>
|
||||
<Table data={{ nodes: shown_data }} theme={entities_theme} layout={{ custom: true }}>
|
||||
{(tableList: any) => (
|
||||
<>
|
||||
<Header>
|
||||
<HeaderRow>
|
||||
<HeaderCell stiff>OPTIONS</HeaderCell>
|
||||
<HeaderCell stiff>{LL.OPTIONS()}</HeaderCell>
|
||||
<HeaderCell resize>
|
||||
<Button
|
||||
fullWidth
|
||||
style={{ fontSize: '14px', justifyContent: 'flex-start' }}
|
||||
endIcon={getSortIcon(entity_sort.state, 'NAME')}
|
||||
onClick={() => entity_sort.fns.onToggleSort({ sortKey: 'NAME' })}
|
||||
>
|
||||
NAME
|
||||
<Button fullWidth style={{ fontSize: '14px', justifyContent: 'flex-start' }}>
|
||||
{LL.NAME()}
|
||||
</Button>
|
||||
</HeaderCell>
|
||||
<HeaderCell resize>VALUE</HeaderCell>
|
||||
<HeaderCell resize>{LL.VALUE()}</HeaderCell>
|
||||
</HeaderRow>
|
||||
</Header>
|
||||
<Body>
|
||||
{tableList.map((de: DeviceEntity) => (
|
||||
<Row key={de.id} item={de}>
|
||||
<Row key={de.id} item={de} onClick={() => editEntity(de)}>
|
||||
<Cell stiff>
|
||||
<ToggleButtonGroup
|
||||
size="small"
|
||||
@@ -503,14 +507,11 @@ const SettingsCustomization: FC = () => {
|
||||
|
||||
const renderResetDialog = () => (
|
||||
<Dialog open={confirmReset} onClose={() => setConfirmReset(false)}>
|
||||
<DialogTitle>Reset</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
Are you sure you want remove all customizations including the custom settings of the Temperature and Analog
|
||||
sensors?
|
||||
</DialogContent>
|
||||
<DialogTitle>{LL.RESET()}</DialogTitle>
|
||||
<DialogContent dividers>{LL.CUSTOMIZATIONS_RESET()}</DialogContent>
|
||||
<DialogActions>
|
||||
<Button startIcon={<CancelIcon />} variant="outlined" onClick={() => setConfirmReset(false)} color="secondary">
|
||||
Cancel
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<SettingsBackupRestoreIcon />}
|
||||
@@ -519,7 +520,7 @@ const SettingsCustomization: FC = () => {
|
||||
autoFocus
|
||||
color="error"
|
||||
>
|
||||
Reset
|
||||
{LL.RESET()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
@@ -529,15 +530,15 @@ const SettingsCustomization: FC = () => {
|
||||
return (
|
||||
<>
|
||||
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
|
||||
Device Entities
|
||||
{LL.DEVICE_ENTITIES()}
|
||||
</Typography>
|
||||
{renderDeviceList()}
|
||||
{renderDeviceData()}
|
||||
{!deviceEntity && renderDeviceData()}
|
||||
<Box display="flex" flexWrap="wrap">
|
||||
<Box flexGrow={1}>
|
||||
<ButtonRow>
|
||||
<Button startIcon={<SaveIcon />} variant="outlined" color="primary" onClick={() => saveCustomization()}>
|
||||
Save
|
||||
{LL.SAVE()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</Box>
|
||||
@@ -548,7 +549,7 @@ const SettingsCustomization: FC = () => {
|
||||
color="error"
|
||||
onClick={() => setConfirmReset(true)}
|
||||
>
|
||||
Reset
|
||||
{LL.RESET()}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
</Box>
|
||||
@@ -557,8 +558,55 @@ const SettingsCustomization: FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const renderEditEntity = () => {
|
||||
if (deviceEntity) {
|
||||
return (
|
||||
<Dialog open={!!deviceEntity} onClose={() => setDeviceEntity(undefined)}>
|
||||
<DialogTitle>{LL.RENAME() + ' ' + LL.ENTITY_NAME()}</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<Box color="warning.main" p={0} pl={0} pr={0} mt={0} mb={2}>
|
||||
<Typography variant="body2">{deviceEntity.n}</Typography>
|
||||
</Box>
|
||||
<Grid container spacing={1}>
|
||||
<Grid item>
|
||||
<TextField
|
||||
name="cn"
|
||||
label={LL.NEW() + ' ' + LL.ENTITY_NAME()}
|
||||
value={deviceEntity.cn}
|
||||
autoFocus
|
||||
sx={{ width: '30ch' }}
|
||||
onChange={updateValue(setDeviceEntity)}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
startIcon={<CancelIcon />}
|
||||
variant="outlined"
|
||||
onClick={() => setDeviceEntity(undefined)}
|
||||
color="secondary"
|
||||
>
|
||||
{LL.CANCEL()}
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<SaveIcon />}
|
||||
variant="outlined"
|
||||
type="submit"
|
||||
onClick={() => updateEntity()}
|
||||
color="warning"
|
||||
>
|
||||
{LL.SAVE()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent title="User Customization" titleGutter>
|
||||
<SectionContent title={LL.USER_CUSTOMIZATION()} titleGutter>
|
||||
{renderEditEntity()}
|
||||
{content()}
|
||||
</SectionContent>
|
||||
);
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
DeviceData,
|
||||
DeviceEntity,
|
||||
UniqueID,
|
||||
MaskedEntities,
|
||||
CustomEntities,
|
||||
WriteValue,
|
||||
WriteSensor,
|
||||
WriteAnalog,
|
||||
@@ -63,8 +63,8 @@ export function readDeviceEntities(unique_id: UniqueID): AxiosPromise<DeviceEnti
|
||||
return AXIOS_BIN.post('/deviceEntities', unique_id);
|
||||
}
|
||||
|
||||
export function writeMaskedEntities(maskedEntities: MaskedEntities): AxiosPromise<void> {
|
||||
return AXIOS.post('/maskedEntities', maskedEntities);
|
||||
export function writeCustomEntities(customEntities: CustomEntities): AxiosPromise<void> {
|
||||
return AXIOS.post('/customEntities', customEntities);
|
||||
}
|
||||
|
||||
export function writeValue(writevalue: WriteValue): AxiosPromise<void> {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export interface Settings {
|
||||
locale: string;
|
||||
tx_mode: number;
|
||||
ems_bus_id: number;
|
||||
syslog_enabled: boolean;
|
||||
@@ -41,7 +42,7 @@ export enum busConnectionStatus {
|
||||
}
|
||||
|
||||
export interface Stat {
|
||||
id: string; // name
|
||||
id: string; // id - needs to be a string
|
||||
s: number; // success
|
||||
f: number; // fail
|
||||
q: number; // quality
|
||||
@@ -136,12 +137,14 @@ export interface DeviceEntity {
|
||||
id: string; // shortname
|
||||
v?: any; // value, in any format, optional
|
||||
n?: string; // fullname, optional
|
||||
cn?: string; // custom fullname, optional
|
||||
m: number; // mask
|
||||
om?: number; // original mask before edits
|
||||
o_m?: number; // original mask before edits
|
||||
o_cn?: string; // original cn before edits
|
||||
w: boolean; // writeable
|
||||
}
|
||||
|
||||
export interface MaskedEntities {
|
||||
export interface CustomEntities {
|
||||
id: number;
|
||||
entity_ids: string[];
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import parseMilliseconds from 'parse-ms';
|
||||
|
||||
const LOCALE_FORMAT = new Intl.DateTimeFormat([...window.navigator.languages], {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
@@ -21,21 +19,6 @@ export const formatLocalDateTime = (date: Date) => {
|
||||
export const pluralize = (count: number, noun: string) =>
|
||||
`${Intl.NumberFormat().format(count)} ${noun}${count !== 1 ? 's' : ''}`;
|
||||
|
||||
export const formatDurationMin = (duration_min: number) => {
|
||||
const { days, hours, minutes } = parseMilliseconds(duration_min * 60000);
|
||||
let formatted = '';
|
||||
if (days) {
|
||||
formatted += pluralize(days, 'day') + ' ';
|
||||
}
|
||||
if (hours) {
|
||||
formatted += pluralize(hours, 'hour') + ' ';
|
||||
}
|
||||
if (minutes) {
|
||||
formatted += pluralize(minutes, 'minute') + ' ';
|
||||
}
|
||||
return formatted;
|
||||
};
|
||||
|
||||
export const formatDurationSec = (duration_sec: number) => {
|
||||
if (duration_sec === 0) {
|
||||
return ' ';
|
||||
|
||||
@@ -4,12 +4,16 @@ import { AxiosPromise } from 'axios';
|
||||
|
||||
import { extractErrorMessage } from '.';
|
||||
|
||||
import { useI18nContext } from '../i18n/i18n-react';
|
||||
|
||||
export interface RestRequestOptions<D> {
|
||||
read: () => AxiosPromise<D>;
|
||||
update?: (value: D) => AxiosPromise<D>;
|
||||
}
|
||||
|
||||
export const useRest = <D>({ read, update }: RestRequestOptions<D>) => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const [saving, setSaving] = useState<boolean>(false);
|
||||
@@ -23,11 +27,11 @@ export const useRest = <D>({ read, update }: RestRequestOptions<D>) => {
|
||||
try {
|
||||
setData((await read()).data);
|
||||
} catch (error: unknown) {
|
||||
const message = extractErrorMessage(error, 'Problem loading data');
|
||||
const message = extractErrorMessage(error, LL.PROBLEM_LOADING());
|
||||
enqueueSnackbar(message, { variant: 'error' });
|
||||
setErrorMessage(message);
|
||||
}
|
||||
}, [read, enqueueSnackbar]);
|
||||
}, [read, enqueueSnackbar, LL]);
|
||||
|
||||
const save = useCallback(
|
||||
async (toSave: D) => {
|
||||
@@ -43,17 +47,17 @@ export const useRest = <D>({ read, update }: RestRequestOptions<D>) => {
|
||||
if (response.status === 202) {
|
||||
setRestartNeeded(true);
|
||||
} else {
|
||||
enqueueSnackbar('Settings saved', { variant: 'success' });
|
||||
enqueueSnackbar(LL.SETTINGS() + ' ' + LL.SAVED(), { variant: 'success' });
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
const message = extractErrorMessage(error, 'Problem saving data');
|
||||
const message = extractErrorMessage(error, LL.PROBLEM_UPDATING());
|
||||
enqueueSnackbar(message, { variant: 'error' });
|
||||
setErrorMessage(message);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
},
|
||||
[update, enqueueSnackbar]
|
||||
[update, enqueueSnackbar, LL]
|
||||
);
|
||||
|
||||
const saveData = () => data && save(data);
|
||||
|
||||
@@ -3,10 +3,8 @@ import Schema from 'async-validator';
|
||||
export const SIGN_IN_REQUEST_VALIDATOR = new Schema({
|
||||
username: {
|
||||
required: true,
|
||||
message: 'Please provide a username'
|
||||
},
|
||||
password: {
|
||||
required: true,
|
||||
message: 'Please provide a password'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -704,8 +704,13 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen){
|
||||
} else if(_pinfo.opcode == WS_PING){
|
||||
_queueControl(new AsyncWebSocketControl(WS_PONG, data, datalen));
|
||||
} else if(_pinfo.opcode == WS_PONG){
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wstringop-overflow"
|
||||
if(datalen != AWSC_PING_PAYLOAD_LEN || memcmp_P(data, AWSC_PING_PAYLOAD, AWSC_PING_PAYLOAD_LEN) != 0)
|
||||
_server->_handleEvent(this, WS_EVT_PONG, NULL, data, datalen);
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
} else if(_pinfo.opcode < 8){//continuation or text/binary frame
|
||||
_server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, data, datalen);
|
||||
}
|
||||
|
||||
@@ -143,6 +143,7 @@ sample code bearing this copyright.
|
||||
#include "OneWire.h"
|
||||
#include "util/OneWire_direct_gpio.h"
|
||||
|
||||
#pragma GCC diagnostic ignored "-Wunused-variable"
|
||||
|
||||
void OneWire::begin(uint8_t pin)
|
||||
{
|
||||
@@ -154,7 +155,6 @@ void OneWire::begin(uint8_t pin)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// Perform the onewire reset function. We will wait up to 250uS for
|
||||
// the bus to come high, if it doesn't then it is broken or shorted
|
||||
// and we return a 0;
|
||||
@@ -578,3 +578,6 @@ uint16_t OneWire::crc16(const uint8_t* input, uint16_t len, uint16_t crc)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
|
||||
@@ -68,11 +68,28 @@ class FSPersistence {
|
||||
JsonObject jsonObject = jsonDocument.to<JsonObject>();
|
||||
_statefulService->read(jsonObject, _stateReader);
|
||||
|
||||
// make directories if required, for new IDF4.2 & LittleFS
|
||||
String path(_filePath);
|
||||
int index = 0;
|
||||
while ((index = path.indexOf('/', index + 1)) != -1) {
|
||||
String segment = path.substring(0, index);
|
||||
if (!_fs->exists(segment)) {
|
||||
_fs->mkdir(segment);
|
||||
}
|
||||
}
|
||||
|
||||
// serialize it to filesystem
|
||||
File settingsFile = _fs->open(_filePath, "w");
|
||||
|
||||
// failed to open file, return false
|
||||
if (!settingsFile) {
|
||||
#if defined(EMSESP_DEBUG)
|
||||
#if defined(EMSESP_USE_SERIAL)
|
||||
Serial.println();
|
||||
Serial.printf("Cannot write to file system.");
|
||||
Serial.println();
|
||||
#endif
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ void MqttSettingsService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
case ARDUINO_EVENT_ETH_GOT_IP6:
|
||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP6:
|
||||
if (_state.enabled) {
|
||||
// emsesp::EMSESP::logger().info(F("Network connection found, starting MQTT client"));
|
||||
// emsesp::EMSESP::logger().info(F("IPv4 Network connection found, starting MQTT client"));
|
||||
onConfigUpdated();
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -50,6 +50,7 @@ void NTPSettingsService::configureNTP() {
|
||||
emsesp::EMSESP::system_.ntp_connected(false);
|
||||
if (connected_ && _state.enabled) {
|
||||
emsesp::EMSESP::logger().info(F("Starting NTP"));
|
||||
sntp_set_sync_interval(3600000); // onehour
|
||||
sntp_set_time_sync_notification_cb(ntp_received);
|
||||
configTzTime(_state.tzFormat.c_str(), _state.server.c_str());
|
||||
} else {
|
||||
|
||||
@@ -24,7 +24,7 @@ void SystemStatus::systemStatus(AsyncWebServerRequest * request) {
|
||||
root["flash_chip_size"] = ESP.getFlashChipSize();
|
||||
root["flash_chip_speed"] = ESP.getFlashChipSpeed();
|
||||
|
||||
root["fs_total"] = LittleFS.totalBytes();
|
||||
root["fs_total"] = emsesp::EMSESP::system_.FStotal();
|
||||
root["fs_used"] = LittleFS.usedBytes();
|
||||
root["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
class DummySettings {
|
||||
public:
|
||||
String locale = "en";
|
||||
uint8_t tx_mode = 1;
|
||||
uint8_t ems_bus_id = 0x0B;
|
||||
bool syslog_enabled = false;
|
||||
|
||||
@@ -24,6 +24,8 @@ class FSPersistence {
|
||||
}
|
||||
|
||||
void readFromFS() {
|
||||
Serial.print("Fake reading file ");
|
||||
Serial.println(_filePath);
|
||||
applyDefaults();
|
||||
}
|
||||
|
||||
|
||||
@@ -253,7 +253,7 @@ const UPLOAD_FILE_ENDPOINT = REST_ENDPOINT_ROOT + 'uploadFile'
|
||||
const SIGN_IN_ENDPOINT = REST_ENDPOINT_ROOT + 'signIn'
|
||||
const GENERATE_TOKEN_ENDPOINT = REST_ENDPOINT_ROOT + 'generateToken'
|
||||
const system_status = {
|
||||
emsesp_version: '3.4demo',
|
||||
emsesp_version: '3.5demo',
|
||||
esp_platform: 'ESP32',
|
||||
max_alloc_heap: 113792,
|
||||
psram_size: 0,
|
||||
@@ -302,11 +302,12 @@ const EMSESP_BOARDPROFILE_ENDPOINT = REST_ENDPOINT_ROOT + 'boardProfile'
|
||||
const EMSESP_WRITE_VALUE_ENDPOINT = REST_ENDPOINT_ROOT + 'writeValue'
|
||||
const EMSESP_WRITE_SENSOR_ENDPOINT = REST_ENDPOINT_ROOT + 'writeSensor'
|
||||
const EMSESP_WRITE_ANALOG_ENDPOINT = REST_ENDPOINT_ROOT + 'writeAnalog'
|
||||
const EMSESP_MASKED_ENTITIES_ENDPOINT = REST_ENDPOINT_ROOT + 'maskedEntities'
|
||||
const EMSESP_CUSTOM_ENTITIES_ENDPOINT = REST_ENDPOINT_ROOT + 'customEntities'
|
||||
const EMSESP_RESET_CUSTOMIZATIONS_ENDPOINT = REST_ENDPOINT_ROOT + 'resetCustomizations'
|
||||
|
||||
settings = {
|
||||
tx_mode: 1,
|
||||
locale: 'en',
|
||||
tx_mode: 4,
|
||||
ems_bus_id: 11,
|
||||
syslog_enabled: false,
|
||||
syslog_level: 3,
|
||||
@@ -344,29 +345,24 @@ const emsesp_devices = {
|
||||
devices: [
|
||||
{
|
||||
i: 1,
|
||||
d: 23,
|
||||
p: 77,
|
||||
s: 'Thermostat (RC20/Moduline 300)',
|
||||
t: 'thermostat1',
|
||||
t: 'thermostat',
|
||||
},
|
||||
{
|
||||
i: 2,
|
||||
d: 8,
|
||||
p: 123,
|
||||
s: 'Boiler (Nefit GBx72/Trendline/Cerapur/Greenstar Si/27i)',
|
||||
t: 'boiler',
|
||||
},
|
||||
{
|
||||
i: 4,
|
||||
d: 16,
|
||||
p: 165,
|
||||
s: 'Thermostat (RC100/Moduline 1000/1010)',
|
||||
t: 'thermostat2',
|
||||
t: 'thermostat',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const emsesp_coredata = {
|
||||
connected: true,
|
||||
// devices: [],
|
||||
devices: [
|
||||
{
|
||||
@@ -429,13 +425,13 @@ const status = {
|
||||
num_sensors: 1,
|
||||
num_analogs: 1,
|
||||
stats: [
|
||||
{ id: 'EMS Telegrams Received (Rx)', s: 56506, f: 11, q: 100 },
|
||||
{ id: 'EMS Reads (Tx)', s: 9026, f: 0, q: 100 },
|
||||
{ id: 'EMS Writes (Tx)', s: 33, f: 2, q: 95 },
|
||||
{ id: 'Temperature Sensor Reads', s: 56506, f: 11, q: 100 },
|
||||
{ id: 'Analog Sensor Reads', s: 0, f: 0, q: 100 },
|
||||
{ id: 'MQTT Publishes', s: 12, f: 10, q: 20 },
|
||||
{ id: 'API Calls', s: 0, f: 0, q: 0 },
|
||||
{ id: '0', s: 56506, f: 11, q: 100 },
|
||||
{ id: '1', s: 9026, f: 0, q: 100 },
|
||||
{ id: '2', s: 33, f: 2, q: 95 },
|
||||
{ id: '3', s: 56506, f: 11, q: 100 },
|
||||
{ id: '4', s: 0, f: 0, q: 100 },
|
||||
{ id: '5', s: 12, f: 10, q: 20 },
|
||||
{ id: '6', s: 0, f: 0, q: 0 },
|
||||
],
|
||||
}
|
||||
|
||||
@@ -446,7 +442,7 @@ const emsesp_devicedata_1 = {
|
||||
{
|
||||
v: '(0)',
|
||||
u: 0,
|
||||
id: '00error code',
|
||||
id: '08my custom error code',
|
||||
},
|
||||
{
|
||||
v: '14:54:39 06/06/2021',
|
||||
@@ -584,8 +580,9 @@ const emsesp_deviceentities_1 = [
|
||||
{
|
||||
v: '(0)',
|
||||
n: 'error code',
|
||||
cn: 'my custom error code',
|
||||
id: 'errorcode',
|
||||
m: 0,
|
||||
m: 8,
|
||||
w: false,
|
||||
},
|
||||
{
|
||||
@@ -602,19 +599,13 @@ const emsesp_deviceentities_1 = [
|
||||
// m: 0,
|
||||
// w: false,
|
||||
// },
|
||||
{
|
||||
v: 'roomTemp',
|
||||
id: 'hc1/HA climate config creation',
|
||||
m: 0,
|
||||
w: false,
|
||||
},
|
||||
{
|
||||
v: 18.2,
|
||||
n: 'hc1 selected room temperature',
|
||||
id: 'hc1/seltemp',
|
||||
m: 0,
|
||||
w: true,
|
||||
},
|
||||
// {
|
||||
// v: 18.2,
|
||||
// n: 'hc1 selected room temperature',
|
||||
// id: 'hc1/seltemp',
|
||||
// m: 0,
|
||||
// w: true,
|
||||
// },
|
||||
{
|
||||
v: 22.6,
|
||||
n: 'hc1 current room temperature',
|
||||
@@ -852,6 +843,7 @@ rest_server.post(UPLOAD_FILE_ENDPOINT, (req, res) => {
|
||||
res.sendStatus(200)
|
||||
})
|
||||
rest_server.post(SIGN_IN_ENDPOINT, (req, res) => {
|
||||
console.log('Signed in as ' + req.body.username)
|
||||
res.json(signin)
|
||||
})
|
||||
rest_server.get(GENERATE_TOKEN_ENDPOINT, (req, res) => {
|
||||
@@ -931,29 +923,64 @@ rest_server.post(EMSESP_DEVICEENTITIES_ENDPOINT, (req, res) => {
|
||||
})
|
||||
|
||||
function updateMask(entity, de, dd) {
|
||||
const shortname = entity.slice(2)
|
||||
const new_mask = parseInt(entity.slice(0, 2), 16)
|
||||
const current_mask = parseInt(entity.slice(0, 2), 16)
|
||||
const shortname_with_customname = entity.slice(2)
|
||||
const shortname = shortname_with_customname.split('|')[0]
|
||||
const new_custom_name = shortname_with_customname.split('|')[1]
|
||||
|
||||
// find in de
|
||||
de_objIndex = de.findIndex((obj) => obj.id === shortname)
|
||||
if (de_objIndex !== -1) {
|
||||
if (de[de_objIndex].cn) {
|
||||
fullname = de[de_objIndex].cn
|
||||
} else {
|
||||
fullname = de[de_objIndex].n
|
||||
}
|
||||
|
||||
// find in dd, either looking for fullname or custom name
|
||||
dd_objIndex = dd.data.findIndex((obj) => obj.id.slice(2) === fullname)
|
||||
if (dd_objIndex !== -1) {
|
||||
let changed = new Boolean(false)
|
||||
|
||||
objIndex = de.findIndex((obj) => obj.id == shortname)
|
||||
if (objIndex !== -1) {
|
||||
de[objIndex].m = new_mask
|
||||
const fullname = de[objIndex].n
|
||||
objIndex = dd.data.findIndex((obj) => obj.id.slice(2) == fullname)
|
||||
if (objIndex !== -1) {
|
||||
// see if the mask has changed
|
||||
const old_mask = parseInt(dd.data[objIndex].id.slice(0, 2), 16)
|
||||
if (old_mask !== new_mask) {
|
||||
const mask_hex = entity.slice(0, 2)
|
||||
console.log('Updating ' + dd.data[objIndex].id + ' -> ' + mask_hex + fullname)
|
||||
dd.data[objIndex].id = mask_hex + fullname
|
||||
const old_mask = parseInt(dd.data[dd_objIndex].id.slice(0, 2), 16)
|
||||
if (old_mask !== current_mask) {
|
||||
changed = true
|
||||
console.log('mask has changed to ' + current_mask.toString(16))
|
||||
}
|
||||
|
||||
// see if the custom name has changed
|
||||
const old_custom_name = dd.data[dd_objIndex].cn
|
||||
if (old_custom_name !== new_custom_name) {
|
||||
changed = true
|
||||
new_fullname = new_custom_name
|
||||
console.log('name has changed to ' + new_custom_name)
|
||||
} else {
|
||||
new_fullname = fullname
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
console.log(
|
||||
'Updating ' + dd.data[dd_objIndex].id + ' -> ' + current_mask.toString(16).padStart(2, '0') + new_fullname,
|
||||
)
|
||||
de[de_objIndex].m = current_mask
|
||||
de[de_objIndex].cn = new_fullname
|
||||
dd.data[dd_objIndex].id = current_mask.toString(16).padStart(2, '0') + new_fullname
|
||||
}
|
||||
|
||||
console.log('new dd:')
|
||||
console.log(dd.data[dd_objIndex])
|
||||
console.log('new de:')
|
||||
console.log(de[de_objIndex])
|
||||
} else {
|
||||
console.log('error, dd not found')
|
||||
}
|
||||
} else {
|
||||
console.log("can't locate record for name " + shortname)
|
||||
console.log("can't locate record for shortname " + shortname)
|
||||
}
|
||||
}
|
||||
|
||||
rest_server.post(EMSESP_MASKED_ENTITIES_ENDPOINT, (req, res) => {
|
||||
rest_server.post(EMSESP_CUSTOM_ENTITIES_ENDPOINT, (req, res) => {
|
||||
const id = req.body.id
|
||||
console.log('customization id = ' + id)
|
||||
console.log(req.body.entity_ids)
|
||||
@@ -1135,7 +1162,7 @@ rest_server.post(EMSESP_BOARDPROFILE_ENDPOINT, (req, res) => {
|
||||
// EMS-ESP API specific
|
||||
const emsesp_info = {
|
||||
System: {
|
||||
version: '3.4.2',
|
||||
version: '3.5.0',
|
||||
uptime: '001+06:40:34.018',
|
||||
'uptime (seconds)': 110434,
|
||||
freemem: 131,
|
||||
|
||||
@@ -8,16 +8,27 @@
|
||||
; my_build_flags = -DEMSESP_DEBUG -DEMSESP_USE_SERIAL
|
||||
; my_build_flags = -DEMSESP_DEBUG -DCORE_DEBUG_LEVEL=5 ; 5=verbose, 4=debug, 3=info
|
||||
|
||||
[env:esp32]
|
||||
[env:esp32_4M]
|
||||
; if using OTA enter your details below
|
||||
; upload_protocol = espota
|
||||
; upload_flags =
|
||||
; --port=8266
|
||||
; --auth=ems-esp-neo
|
||||
; upload_port = ems-esp.local
|
||||
; upload_protocol = espota
|
||||
; upload_flags =
|
||||
; --port=8266
|
||||
; --auth=ems-esp-neo
|
||||
; upload_port = ems-esp.local
|
||||
; for USB use
|
||||
upload_port = /dev/ttyUSB*
|
||||
; upload_port = COM3
|
||||
|
||||
extra_scripts =
|
||||
pre:scripts/build_interface.py ; comment out if you don't want to re-build the WebUI each time
|
||||
; pre:scripts/build_interface.py ; comment out if you don't want to re-build the WebUI each time
|
||||
scripts/rename_fw.py
|
||||
|
||||
[env:esp32_16M]
|
||||
upload_port = /dev/ttyUSB*
|
||||
; upload_port = COM3
|
||||
|
||||
extra_scripts =
|
||||
; pre:scripts/build_interface.py ; comment out if you don't want to re-build the WebUI each time
|
||||
scripts/rename_fw.py
|
||||
|
||||
; pio run -e debug
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
; override any settings with your own local ones in pio_local.ini
|
||||
|
||||
[platformio]
|
||||
default_envs = esp32
|
||||
default_envs = esp32_4M
|
||||
; default_envs = esp32_16M
|
||||
|
||||
extra_configs =
|
||||
factory_settings.ini
|
||||
@@ -37,9 +38,11 @@ unbuild_flags =
|
||||
[env]
|
||||
framework = arduino
|
||||
monitor_speed = 115200
|
||||
monitor_raw = yes
|
||||
upload_speed = 921600
|
||||
build_type = release
|
||||
lib_ldf_mode = chain+
|
||||
debug_build_flags = -Os # optimize for size
|
||||
|
||||
check_tool = cppcheck, clangtidy
|
||||
check_severity = high, medium
|
||||
@@ -53,16 +56,28 @@ check_flags =
|
||||
extra_scripts = scripts/rename_fw.py
|
||||
board = esp32dev
|
||||
platform = espressif32
|
||||
board_build.partitions = esp32_partition_app1984k_spiffs64k.csv
|
||||
board_build.partitions = esp32_partition_4M.csv
|
||||
build_flags = ${common.build_flags}
|
||||
build_unflags = ${common.unbuild_flags}
|
||||
|
||||
[env:esp32]
|
||||
[env:esp32_4M]
|
||||
extra_scripts =
|
||||
pre:scripts/build_interface.py
|
||||
scripts/rename_fw.py
|
||||
board = esp32dev
|
||||
platform = espressif32
|
||||
board_build.partitions = esp32_partition_app1984k_spiffs64k.csv
|
||||
board_upload.flash_size = 4MB
|
||||
board_build.partitions = esp32_partition_4M.csv
|
||||
build_flags = ${common.build_flags}
|
||||
build_unflags = ${common.unbuild_flags}
|
||||
|
||||
[env:esp32_16M]
|
||||
extra_scripts =
|
||||
pre:scripts/build_interface.py
|
||||
scripts/rename_fw.py
|
||||
board = esp32dev
|
||||
platform = espressif32
|
||||
board_upload.flash_size = 16MB
|
||||
board_build.partitions = esp32_partition_16M.csv
|
||||
build_flags = ${common.build_flags}
|
||||
build_unflags = ${common.unbuild_flags}
|
||||
|
||||
@@ -5,7 +5,6 @@ Import("env")
|
||||
|
||||
OUTPUT_DIR = "build{}".format(os.path.sep)
|
||||
|
||||
|
||||
def bin_copy(source, target, env):
|
||||
|
||||
# get the build info
|
||||
@@ -21,20 +20,23 @@ def bin_copy(source, target, env):
|
||||
app_version = bag.get('app_version')
|
||||
platform = "ESP32"
|
||||
|
||||
flash_size = env["PIOENV"].split('_')[1]
|
||||
|
||||
# print(env.Dump())
|
||||
# my_flags = env.ParseFlags(env['BUILD_FLAGS'])
|
||||
# defines = {k: v for (k, v) in my_flags.get("CPPDEFINES")}
|
||||
# print(my_flags)
|
||||
# print((my_flags.get("CPPDEFINES"))
|
||||
|
||||
# alternatively take platfrom from the pio target
|
||||
# alternatively take platform from the pio target
|
||||
# platform = str(target[0]).split(os.path.sep)[2]
|
||||
|
||||
print("app version: "+app_version)
|
||||
print("platform: "+platform)
|
||||
print("flash size: "+flash_size)
|
||||
|
||||
# convert . to _ so Windows doesn't complain
|
||||
variant = "EMS-ESP-" + app_version.replace(".", "_") + "-" + platform
|
||||
variant = "EMS-ESP-" + app_version.replace(".", "_") + "-" + platform + "_" + flash_size
|
||||
|
||||
# check if output directories exist and create if necessary
|
||||
if not os.path.isdir(OUTPUT_DIR):
|
||||
@@ -52,10 +54,9 @@ def bin_copy(source, target, env):
|
||||
if os.path.isfile(f):
|
||||
os.remove(f)
|
||||
|
||||
print("renaming file to "+bin_file)
|
||||
print("Renaming file to "+bin_file)
|
||||
|
||||
# copy firmware.bin to firmware/<variant>.bin
|
||||
shutil.copy(str(target[0]), bin_file)
|
||||
|
||||
|
||||
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", [bin_copy])
|
||||
|
||||
0
scripts/run_sonar.sh
Executable file → Normal file
0
scripts/run_sonar.sh
Executable file → Normal file
@@ -1,7 +1,7 @@
|
||||
sonar.organization=emsesp
|
||||
sonar.projectKey=emsesp_EMS-ESP32
|
||||
sonar.projectName=EMS-ESP32
|
||||
sonar.projectVersion=3.4
|
||||
sonar.projectVersion=3.5.0
|
||||
sonar.sources=./src
|
||||
sonar.cfamily.build-wrapper-output=build_wrapper_output_directory
|
||||
sonar.sourceEncoding=UTF-8
|
||||
|
||||
@@ -42,7 +42,7 @@ void AnalogSensor::start() {
|
||||
EMSdevice::DeviceType::ANALOGSENSOR,
|
||||
F_(setvalue),
|
||||
[&](const char * value, const int8_t id) { return command_setvalue(value, id); },
|
||||
F("set io value"),
|
||||
F("set io value"), // TODO this needs translating
|
||||
CommandFlag::ADMIN_ONLY);
|
||||
Command::add(
|
||||
EMSdevice::DeviceType::ANALOGSENSOR,
|
||||
@@ -60,7 +60,10 @@ void AnalogSensor::reload() {
|
||||
#if defined(EMSESP_STANDALONE)
|
||||
analog_enabled_ = true; // for local offline testing
|
||||
#endif
|
||||
|
||||
if (!analog_enabled_) {
|
||||
sensors_.clear();
|
||||
return;
|
||||
}
|
||||
// load the list of analog sensors from the customization service
|
||||
// and store them locally and then activate them
|
||||
EMSESP::webCustomizationService.read([&](WebCustomization & settings) {
|
||||
@@ -459,7 +462,7 @@ bool AnalogSensor::get_value_info(JsonObject & output, const char * cmd, const i
|
||||
output["gpio"] = sensor.gpio();
|
||||
output["name"] = sensor.name();
|
||||
output["type"] = F_(number);
|
||||
output["analog"] = FL_(enum_sensortype)[sensor.type()];
|
||||
output["analog"] = FL_(list_sensortype)[sensor.type()];
|
||||
output["uom"] = EMSdevice::uom_to_string(sensor.uom());
|
||||
output["offset"] = sensor.offset();
|
||||
output["factor"] = sensor.factor();
|
||||
@@ -497,7 +500,7 @@ bool AnalogSensor::command_info(const char * value, const int8_t id, JsonObject
|
||||
JsonObject dataSensor = output.createNestedObject(sensor.name());
|
||||
dataSensor["gpio"] = sensor.gpio();
|
||||
dataSensor["type"] = F_(number);
|
||||
dataSensor["analog"] = FL_(enum_sensortype)[sensor.type()];
|
||||
dataSensor["analog"] = FL_(list_sensortype)[sensor.type()];
|
||||
if (sensor.type() == AnalogType::ADC) {
|
||||
dataSensor["uom"] = EMSdevice::uom_to_string(sensor.uom());
|
||||
dataSensor["offset"] = sensor.offset();
|
||||
|
||||
@@ -306,7 +306,7 @@ void Command::add(const uint8_t device_type, const __FlashStringHelper * cmd, co
|
||||
}
|
||||
|
||||
// if the description is empty, it's hidden which means it will not show up in Web API or Console as an available command
|
||||
if (description == nullptr) {
|
||||
if (!description) {
|
||||
flags |= CommandFlag::HIDDEN;
|
||||
}
|
||||
|
||||
@@ -424,7 +424,7 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo
|
||||
shell.print(EMSdevice::tag_to_string(DeviceValueTAG::TAG_DEVICE_DATA_WW));
|
||||
shell.print(' ');
|
||||
}
|
||||
shell.print(read_flash_string(cf.description_));
|
||||
shell.print(read_flash_string(cf.description_).c_str());
|
||||
if (!cf.has_flags(CommandFlag::ADMIN_ONLY)) {
|
||||
shell.print(' ');
|
||||
shell.print(COLOR_BRIGHT_RED);
|
||||
|
||||
64
src/common.h
Normal file
64
src/common.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/emsesp/EMS-ESP
|
||||
* Copyright 2020 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EMSESP_COMMON_H
|
||||
#define EMSESP_COMMON_H
|
||||
|
||||
// logging
|
||||
#include <uuid/log.h>
|
||||
|
||||
using uuid::log::Level;
|
||||
#define LOG_DEBUG(...) logger_.debug(__VA_ARGS__)
|
||||
#define LOG_INFO(...) logger_.info(__VA_ARGS__)
|
||||
#define LOG_TRACE(...) logger_.trace(__VA_ARGS__)
|
||||
#define LOG_NOTICE(...) logger_.notice(__VA_ARGS__)
|
||||
#define LOG_WARNING(...) logger_.warning(__VA_ARGS__)
|
||||
#define LOG_ERROR(...) logger_.err(__VA_ARGS__)
|
||||
|
||||
// flash strings
|
||||
using uuid::flash_string_vector;
|
||||
using uuid::read_flash_string;
|
||||
|
||||
#ifdef FPSTR
|
||||
#undef FPSTR
|
||||
#endif
|
||||
|
||||
#define FJSON(x) x
|
||||
// #define FJSON(x) F(x)
|
||||
|
||||
// clang-format off
|
||||
|
||||
#define MAKE_STR(string_name, string_literal) static constexpr const char * __str__##string_name = string_literal;
|
||||
|
||||
#define FPSTR(pstr_pointer) (reinterpret_cast<const __FlashStringHelper *>(pstr_pointer))
|
||||
#define F_(string_name) FPSTR(__pstr__##string_name)
|
||||
|
||||
#define MAKE_PSTR(string_name, string_literal) static const char __pstr__##string_name[] __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = string_literal;
|
||||
#define MAKE_PSTR_WORD(string_name) MAKE_PSTR(string_name, #string_name)
|
||||
|
||||
#define FL_(list_name) (__pstr__L_##list_name)
|
||||
#define MAKE_PSTR_LIST(list_name, ...) static const __FlashStringHelper * const __pstr__L_##list_name[] PROGMEM = {__VA_ARGS__, nullptr};
|
||||
#define MAKE_PSTR_ENUM(enum_name, ...) static const __FlashStringHelper * const * __pstr__L_##enum_name[] PROGMEM = {__VA_ARGS__, nullptr};
|
||||
|
||||
// clang-format on
|
||||
|
||||
// load translations
|
||||
#include "locale_translations.h"
|
||||
#include "locale_common.h"
|
||||
|
||||
#endif
|
||||
@@ -225,6 +225,7 @@ void EMSESPShell::add_console_commands() {
|
||||
flash_string_vector{F_(set)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
|
||||
EMSESP::webSettingsService.read([&](WebSettings & settings) {
|
||||
shell.printfln(F("Language: %s"), settings.locale.c_str());
|
||||
shell.printfln(F_(tx_mode_fmt), settings.tx_mode);
|
||||
shell.printfln(F_(bus_id_fmt), settings.ems_bus_id);
|
||||
shell.printfln(F_(board_profile_fmt), settings.board_profile.c_str());
|
||||
@@ -280,18 +281,19 @@ void EMSESPShell::add_console_commands() {
|
||||
// get raw/pretty
|
||||
if (arguments[0] == read_flash_string(F_(raw))) {
|
||||
EMSESP::watch(EMSESP::WATCH_RAW); // raw
|
||||
} else if (arguments[0] == read_flash_string(F_(on))) {
|
||||
} else if (arguments[0] == Helpers::translated_word(FL_(on), true) || arguments[0] == read_flash_string(FL_(on)[0])) {
|
||||
EMSESP::watch(EMSESP::WATCH_ON); // on
|
||||
} else if (arguments[0] == read_flash_string(F_(off))) {
|
||||
} else if (arguments[0] == Helpers::translated_word(FL_(off), true) || arguments[0] == read_flash_string(FL_(off)[0])) {
|
||||
EMSESP::watch(EMSESP::WATCH_OFF); // off
|
||||
} else if (arguments[0] == read_flash_string(F_(unknown))) {
|
||||
} else if (arguments[0] == Helpers::translated_word(FL_(unknown), true) || arguments[0] == read_flash_string(FL_(unknown)[0])) {
|
||||
EMSESP::watch(EMSESP::WATCH_UNKNOWN); // unknown
|
||||
watch_id = WATCH_ID_NONE;
|
||||
} else {
|
||||
watch_id = Helpers::hextoint(arguments[0].c_str());
|
||||
if (watch_id && ((EMSESP::watch() == EMSESP::WATCH_OFF) || (EMSESP::watch() == EMSESP::WATCH_UNKNOWN))) {
|
||||
if (watch_id > 0 && ((EMSESP::watch() == EMSESP::WATCH_OFF) || (EMSESP::watch() == EMSESP::WATCH_UNKNOWN))) {
|
||||
EMSESP::watch(EMSESP::WATCH_ON); // on
|
||||
} else if (!watch_id) {
|
||||
} else if (watch_id == 0) {
|
||||
EMSESP::watch(EMSESP::WATCH_OFF); // off
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -302,6 +304,9 @@ void EMSESPShell::add_console_commands() {
|
||||
}
|
||||
|
||||
EMSESP::watch_id(watch_id);
|
||||
} else {
|
||||
shell.printfln(F("Invalid: use watch raw|on|off|unknown|id [id]"));
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t watch = EMSESP::watch();
|
||||
|
||||
@@ -26,36 +26,8 @@
|
||||
#include "system.h"
|
||||
#include "mqtt.h"
|
||||
|
||||
using uuid::flash_string_vector;
|
||||
using uuid::read_flash_string;
|
||||
using uuid::console::Commands;
|
||||
using uuid::console::Shell;
|
||||
using uuid::log::Level;
|
||||
|
||||
#define LOG_DEBUG(...) logger_.debug(__VA_ARGS__)
|
||||
#define LOG_INFO(...) logger_.info(__VA_ARGS__)
|
||||
#define LOG_TRACE(...) logger_.trace(__VA_ARGS__)
|
||||
#define LOG_NOTICE(...) logger_.notice(__VA_ARGS__)
|
||||
#define LOG_WARNING(...) logger_.warning(__VA_ARGS__)
|
||||
#define LOG_ERROR(...) logger_.err(__VA_ARGS__)
|
||||
|
||||
// clang-format off
|
||||
// strings stored 32 bit aligned on ESP8266/ESP32
|
||||
#define MAKE_STR(string_name, string_literal) static constexpr const char * __str__##string_name = string_literal;
|
||||
#define MAKE_PSTR(string_name, string_literal) static const char __pstr__##string_name[] __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = string_literal;
|
||||
#define MAKE_PSTR_WORD(string_name) MAKE_PSTR(string_name, #string_name)
|
||||
#define F_(string_name) FPSTR(__pstr__##string_name)
|
||||
#define FSTR_(string_name) __str__##string_name
|
||||
#define MAKE_PSTR_LIST(list_name, ...) static const __FlashStringHelper * const __pstr__##list_name[] PROGMEM = {__VA_ARGS__, nullptr};
|
||||
#define FL_(list_name) (__pstr__##list_name)
|
||||
// clang-format on
|
||||
|
||||
// localizations
|
||||
#include "locale_EN.h"
|
||||
|
||||
#ifdef LOCAL
|
||||
#undef LOCAL
|
||||
#endif
|
||||
|
||||
static constexpr uint32_t INVALID_PASSWORD_DELAY_MS = 2000;
|
||||
|
||||
@@ -64,19 +36,14 @@ namespace emsesp {
|
||||
using LogLevel = ::uuid::log::Level;
|
||||
using LogFacility = ::uuid::log::Facility;
|
||||
|
||||
enum CommandFlags : uint8_t {
|
||||
|
||||
USER = 0,
|
||||
ADMIN = (1 << 0),
|
||||
LOCAL = (1 << 1)
|
||||
|
||||
};
|
||||
#ifdef LOCAL
|
||||
#undef LOCAL
|
||||
#endif
|
||||
enum CommandFlags : uint8_t { USER = 0, ADMIN = (1 << 0), LOCAL = (1 << 1) };
|
||||
|
||||
enum ShellContext : uint8_t {
|
||||
|
||||
MAIN = 0,
|
||||
SYSTEM,
|
||||
|
||||
};
|
||||
|
||||
class EMSESPShell : virtual public uuid::console::Shell {
|
||||
|
||||
@@ -36,6 +36,7 @@ void DallasSensor::start() {
|
||||
reload();
|
||||
|
||||
if (!dallas_gpio_) {
|
||||
sensors_.clear();
|
||||
return; // disabled if dallas gpio is 0
|
||||
}
|
||||
|
||||
@@ -152,23 +153,23 @@ void DallasSensor::loop() {
|
||||
if (sensor.internal_id() == get_id(addr)) {
|
||||
t += sensor.offset();
|
||||
if (t != sensor.temperature_c) {
|
||||
sensor.temperature_c = t;
|
||||
publish_sensor(sensor);
|
||||
changed_ |= true;
|
||||
}
|
||||
sensor.temperature_c = t;
|
||||
sensor.read = true;
|
||||
found = true;
|
||||
sensor.read = true;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// add new sensor. this will create the id string, empty name and offset
|
||||
if (!found && (sensors_.size() < (MAX_SENSORS - 1))) {
|
||||
sensors_.emplace_back(addr);
|
||||
sensors_.back().temperature_c = t + sensors_.back().offset();
|
||||
sensors_.back().read = true;
|
||||
changed_ = true;
|
||||
sensors_.back().read = true;
|
||||
changed_ = true;
|
||||
// look in the customization service for an optional alias or offset for that particular sensor
|
||||
sensors_.back().apply_customization();
|
||||
sensors_.back().temperature_c = t + sensors_.back().offset();
|
||||
publish_sensor(sensors_.back()); // call publish single
|
||||
// sort the sensors based on name
|
||||
// std::sort(sensors_.begin(), sensors_.end(), [](const Sensor & a, const Sensor & b) { return a.name() < b.name(); });
|
||||
@@ -363,10 +364,10 @@ bool DallasSensor::command_info(const char * value, const int8_t id, JsonObject
|
||||
JsonObject dataSensor = output.createNestedObject(sensor.name());
|
||||
dataSensor["id"] = sensor.id();
|
||||
if (Helpers::hasValue(sensor.temperature_c)) {
|
||||
dataSensor["temp"] = Helpers::round2((float)(sensor.temperature_c), 10, EMSESP::system_.fahrenheit() ? 2 : 0);
|
||||
dataSensor["temp"] = Helpers::transformNumFloat((float)(sensor.temperature_c), 10, EMSESP::system_.fahrenheit() ? 2 : 0);
|
||||
}
|
||||
} else if (Helpers::hasValue(sensor.temperature_c)) {
|
||||
output[sensor.name()] = Helpers::round2((float)(sensor.temperature_c), 10, EMSESP::system_.fahrenheit() ? 2 : 0);
|
||||
output[sensor.name()] = Helpers::transformNumFloat((float)(sensor.temperature_c), 10, EMSESP::system_.fahrenheit() ? 2 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -392,11 +393,11 @@ bool DallasSensor::get_value_info(JsonObject & output, const char * cmd, const i
|
||||
output["id"] = sensor.id();
|
||||
output["name"] = sensor.name();
|
||||
if (Helpers::hasValue(sensor.temperature_c)) {
|
||||
output["value"] = Helpers::round2((float)(sensor.temperature_c), 10, EMSESP::system_.fahrenheit() ? 2 : 0);
|
||||
output["value"] = Helpers::transformNumFloat((float)(sensor.temperature_c), 10, EMSESP::system_.fahrenheit() ? 2 : 0);
|
||||
}
|
||||
output["type"] = F_(number);
|
||||
output["min"] = Helpers::round2(-55, 0, EMSESP::system_.fahrenheit() ? 2 : 0);
|
||||
output["max"] = Helpers::round2(125, 0, EMSESP::system_.fahrenheit() ? 2 : 0);
|
||||
output["min"] = Helpers::transformNumFloat(-55, 0, EMSESP::system_.fahrenheit() ? 2 : 0);
|
||||
output["max"] = Helpers::transformNumFloat(125, 0, EMSESP::system_.fahrenheit() ? 2 : 0);
|
||||
output["uom"] = EMSdevice::uom_to_string(DeviceValueUOM::DEGREES);
|
||||
output["writeable"] = false;
|
||||
// if we're filtering on an attribute, go find it
|
||||
@@ -472,10 +473,10 @@ void DallasSensor::publish_values(const bool force) {
|
||||
JsonObject dataSensor = doc.createNestedObject(sensor.id());
|
||||
dataSensor["name"] = sensor.name();
|
||||
if (has_value) {
|
||||
dataSensor["temp"] = Helpers::round2((float)(sensor.temperature_c), 10, EMSESP::system_.fahrenheit() ? 2 : 0);
|
||||
dataSensor["temp"] = Helpers::transformNumFloat((float)(sensor.temperature_c), 10, EMSESP::system_.fahrenheit() ? 2 : 0);
|
||||
}
|
||||
} else if (has_value) {
|
||||
doc[sensor.name()] = Helpers::round2((float)(sensor.temperature_c), 10, EMSESP::system_.fahrenheit() ? 2 : 0);
|
||||
doc[sensor.name()] = Helpers::transformNumFloat((float)(sensor.temperature_c), 10, EMSESP::system_.fahrenheit() ? 2 : 0);
|
||||
}
|
||||
|
||||
// create the HA MQTT config
|
||||
|
||||
@@ -15,11 +15,20 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EMSESP_DEFAULT_SETTINGS_H
|
||||
#define EMSESP_DEFAULT_SETTINGS_H
|
||||
|
||||
// GENERAL SETTINGS
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
#define EMSESP_DEFAULT_LOCALE EMSESP_LOCALE_EN // English
|
||||
#else
|
||||
// this is for debugging different languages in standalone version
|
||||
#define EMSESP_DEFAULT_LOCALE EMSESP_LOCALE_DE // German
|
||||
// #define EMSESP_DEFAULT_LOCALE EMSESP_LOCALE_EN // English
|
||||
#endif
|
||||
|
||||
#ifndef EMSESP_DEFAULT_TX_MODE
|
||||
#define EMSESP_DEFAULT_TX_MODE 1 // EMS1.0
|
||||
#endif
|
||||
@@ -199,19 +208,19 @@
|
||||
// matches Web UI settings
|
||||
enum {
|
||||
|
||||
BOOL_FORMAT_ONOFF_STR = 1,
|
||||
BOOL_FORMAT_ONOFF_STR_CAP,
|
||||
BOOL_FORMAT_TRUEFALSE_STR,
|
||||
BOOL_FORMAT_TRUEFALSE,
|
||||
BOOL_FORMAT_10_STR,
|
||||
BOOL_FORMAT_10
|
||||
BOOL_FORMAT_ONOFF_STR = 1, // 1
|
||||
BOOL_FORMAT_ONOFF_STR_CAP, // 2
|
||||
BOOL_FORMAT_TRUEFALSE_STR, // 3
|
||||
BOOL_FORMAT_TRUEFALSE, // 4
|
||||
BOOL_FORMAT_10_STR, // 5
|
||||
BOOL_FORMAT_10 // 6
|
||||
|
||||
};
|
||||
|
||||
enum {
|
||||
|
||||
ENUM_FORMAT_VALUE = 1,
|
||||
ENUM_FORMAT_INDEX // 2
|
||||
ENUM_FORMAT_VALUE = 1, // 1
|
||||
ENUM_FORMAT_INDEX // 2
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ uuid::log::Logger Boiler::logger_{F_(boiler), uuid::log::Facility::CONSOLE};
|
||||
Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const char * version, const std::string & name, uint8_t flags, uint8_t brand)
|
||||
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
|
||||
// alternative heatsource special messages
|
||||
|
||||
if (device_id == EMSdevice::EMS_DEVICE_ID_AM200) {
|
||||
register_telegram_type(0x54D, F("AmTemperatures"), false, MAKE_PF_CB(process_amTempMessage));
|
||||
register_telegram_type(0x54E, F("AmStatus"), false, MAKE_PF_CB(process_amStatusMessage));
|
||||
@@ -34,25 +35,43 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
|
||||
register_telegram_type(0x550, F("AmExtra"), false, MAKE_PF_CB(process_amExtraMessage));
|
||||
register_telegram_type(0x54C, F("AmSettings"), true, MAKE_PF_CB(process_amSettingMessage)); // not broadcasted
|
||||
|
||||
register_device_value(DeviceValueTAG::TAG_AHS, &curFlowTemp_, DeviceValueType::SHORT, FL_(div10), FL_(sysFlowTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_AHS, &retTemp_, DeviceValueType::SHORT, FL_(div10), FL_(sysRetTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_AHS, &aFlowTemp_, DeviceValueType::SHORT, FL_(div10), FL_(aFlowTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_AHS, &aRetTemp_, DeviceValueType::SHORT, FL_(div10), FL_(aRetTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_AHS, &cylTopTemp_, DeviceValueType::SHORT, FL_(div10), FL_(aCylTopTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_AHS, &cylCenterTemp_, DeviceValueType::SHORT, FL_(div10), FL_(aCylCenterTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_AHS, &cylBottomTemp_, DeviceValueType::SHORT, FL_(div10), FL_(aCylBottomTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_AHS,
|
||||
&curFlowTemp_,
|
||||
DeviceValueType::SHORT,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV10,
|
||||
FL_(sysFlowTemp),
|
||||
DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_AHS, &retTemp_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(sysRetTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_AHS, &aFlowTemp_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(aFlowTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_AHS, &aRetTemp_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(aRetTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_AHS,
|
||||
&cylTopTemp_,
|
||||
DeviceValueType::SHORT,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV10,
|
||||
FL_(aCylTopTemp),
|
||||
DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_AHS,
|
||||
&cylCenterTemp_,
|
||||
DeviceValueType::SHORT,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV10,
|
||||
FL_(aCylCenterTemp),
|
||||
DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_AHS,
|
||||
&cylBottomTemp_,
|
||||
DeviceValueType::SHORT,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV10,
|
||||
FL_(aCylBottomTemp),
|
||||
DeviceValueUOM::DEGREES);
|
||||
// register_device_value(DeviceValueTAG::TAG_AHS, &valveByPass_, DeviceValueType::BOOL, nullptr, FL_(valveByPass), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_AHS, &valveBuffer_, DeviceValueType::UINT, nullptr, FL_(valveBuffer), DeviceValueUOM::PERCENT);
|
||||
register_device_value(DeviceValueTAG::TAG_AHS, &valveReturn_, DeviceValueType::UINT, nullptr, FL_(valveReturn), DeviceValueUOM::PERCENT);
|
||||
register_device_value(DeviceValueTAG::TAG_AHS, &aPumpMod_, DeviceValueType::UINT, nullptr, FL_(aPumpMod), DeviceValueUOM::PERCENT);
|
||||
register_device_value(DeviceValueTAG::TAG_AHS, &valveBuffer_, DeviceValueType::UINT, FL_(valveBuffer), DeviceValueUOM::PERCENT);
|
||||
register_device_value(DeviceValueTAG::TAG_AHS, &valveReturn_, DeviceValueType::UINT, FL_(valveReturn), DeviceValueUOM::PERCENT);
|
||||
register_device_value(DeviceValueTAG::TAG_AHS, &aPumpMod_, DeviceValueType::UINT, FL_(aPumpMod), DeviceValueUOM::PERCENT);
|
||||
// register_device_value(DeviceValueTAG::TAG_AHS, &heatSource_, DeviceValueType::BOOL, nullptr, FL_(heatSource), DeviceValueUOM::NONE);
|
||||
// Settings:
|
||||
register_device_value(
|
||||
DeviceValueTAG::TAG_AHS, &vr2Config_, DeviceValueType::ENUM, FL_(enum_vr2Config), FL_(vr2Config), DeviceValueUOM::NONE, MAKE_CF_CB(set_vr2Config));
|
||||
register_device_value(
|
||||
DeviceValueTAG::TAG_AHS, &ahsActivated_, DeviceValueType::BOOL, nullptr, FL_(ahsActivated), DeviceValueUOM::NONE, MAKE_CF_CB(set_ahsActivated));
|
||||
register_device_value(
|
||||
DeviceValueTAG::TAG_AHS, &aPumpConfig_, DeviceValueType::BOOL, nullptr, FL_(aPumpConfig), DeviceValueUOM::NONE, MAKE_CF_CB(set_aPumpConfig));
|
||||
register_device_value(DeviceValueTAG::TAG_AHS, &ahsActivated_, DeviceValueType::BOOL, FL_(ahsActivated), DeviceValueUOM::NONE, MAKE_CF_CB(set_ahsActivated));
|
||||
register_device_value(DeviceValueTAG::TAG_AHS, &aPumpConfig_, DeviceValueType::BOOL, FL_(aPumpConfig), DeviceValueUOM::NONE, MAKE_CF_CB(set_aPumpConfig));
|
||||
register_device_value(DeviceValueTAG::TAG_AHS,
|
||||
&aPumpSignal_,
|
||||
DeviceValueType::ENUM,
|
||||
@@ -60,35 +79,19 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
|
||||
FL_(aPumpSignal),
|
||||
DeviceValueUOM::NONE,
|
||||
MAKE_CF_CB(set_aPumpSignal));
|
||||
register_device_value(DeviceValueTAG::TAG_AHS, &aPumpMin_, DeviceValueType::UINT, FL_(aPumpMin), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_aPumpMin), 12, 50);
|
||||
register_device_value(DeviceValueTAG::TAG_AHS, &tempRise_, DeviceValueType::BOOL, FL_(tempRise), DeviceValueUOM::NONE, MAKE_CF_CB(set_tempRise));
|
||||
register_device_value(
|
||||
DeviceValueTAG::TAG_AHS, &aPumpMin_, DeviceValueType::UINT, nullptr, FL_(aPumpMin), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_aPumpMin), 12, 50);
|
||||
register_device_value(DeviceValueTAG::TAG_AHS, &tempRise_, DeviceValueType::BOOL, nullptr, FL_(tempRise), DeviceValueUOM::NONE, MAKE_CF_CB(set_tempRise));
|
||||
register_device_value(DeviceValueTAG::TAG_AHS,
|
||||
&setReturnTemp_,
|
||||
DeviceValueType::UINT,
|
||||
nullptr,
|
||||
FL_(setReturnTemp),
|
||||
DeviceValueUOM::DEGREES,
|
||||
MAKE_CF_CB(set_setReturnTemp),
|
||||
40,
|
||||
75);
|
||||
DeviceValueTAG::TAG_AHS, &setReturnTemp_, DeviceValueType::UINT, FL_(setReturnTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_setReturnTemp), 40, 75);
|
||||
register_device_value(
|
||||
DeviceValueTAG::TAG_AHS, &mixRuntime_, DeviceValueType::USHORT, nullptr, FL_(mixRuntime), DeviceValueUOM::SECONDS, MAKE_CF_CB(set_mixRuntime), 0, 600);
|
||||
register_device_value(DeviceValueTAG::TAG_AHS,
|
||||
&setFlowTemp_,
|
||||
DeviceValueType::UINT,
|
||||
nullptr,
|
||||
FL_(setFlowTemp),
|
||||
DeviceValueUOM::DEGREES,
|
||||
MAKE_CF_CB(set_setFlowTemp),
|
||||
40,
|
||||
75);
|
||||
DeviceValueTAG::TAG_AHS, &mixRuntime_, DeviceValueType::USHORT, FL_(mixRuntime), DeviceValueUOM::SECONDS, MAKE_CF_CB(set_mixRuntime), 0, 600);
|
||||
register_device_value(
|
||||
DeviceValueTAG::TAG_AHS, &setFlowTemp_, DeviceValueType::UINT, FL_(setFlowTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_setFlowTemp), 40, 75);
|
||||
register_device_value(
|
||||
DeviceValueTAG::TAG_AHS, &bufBypass_, DeviceValueType::ENUM, FL_(enum_bufBypass), FL_(bufBypass), DeviceValueUOM::NONE, MAKE_CF_CB(set_bufBypass));
|
||||
register_device_value(DeviceValueTAG::TAG_AHS,
|
||||
&bufMixRuntime_,
|
||||
DeviceValueType::USHORT,
|
||||
nullptr,
|
||||
FL_(bufMixRuntime),
|
||||
DeviceValueUOM::SECONDS,
|
||||
MAKE_CF_CB(set_bufMixRuntime),
|
||||
@@ -100,33 +103,31 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
|
||||
DeviceValueTAG::TAG_AHS, &blockMode_, DeviceValueType::ENUM, FL_(enum_blockMode), FL_(blockMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_blockMode));
|
||||
register_device_value(
|
||||
DeviceValueTAG::TAG_AHS, &blockTerm_, DeviceValueType::ENUM, FL_(enum_blockTerm), FL_(blockTerm), DeviceValueUOM::NONE, MAKE_CF_CB(set_blockTerm));
|
||||
register_device_value(DeviceValueTAG::TAG_AHS, &blockHyst_, DeviceValueType::INT, FL_(blockHyst), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_blockHyst), 0, 50);
|
||||
register_device_value(
|
||||
DeviceValueTAG::TAG_AHS, &blockHyst_, DeviceValueType::INT, nullptr, FL_(blockHyst), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_blockHyst), 0, 50);
|
||||
register_device_value(DeviceValueTAG::TAG_AHS,
|
||||
&releaseWait_,
|
||||
DeviceValueType::UINT,
|
||||
nullptr,
|
||||
FL_(releaseWait),
|
||||
DeviceValueUOM::MINUTES,
|
||||
MAKE_CF_CB(set_releaseWait),
|
||||
0,
|
||||
240);
|
||||
DeviceValueTAG::TAG_AHS, &releaseWait_, DeviceValueType::UINT, FL_(releaseWait), DeviceValueUOM::MINUTES, MAKE_CF_CB(set_releaseWait), 0, 240);
|
||||
return;
|
||||
}
|
||||
// cascaded heatingsources, only some values per individual heatsource (hs)
|
||||
|
||||
// cascaded heating sources, only some values per individual heatsource (hs)
|
||||
if (device_id >= EMSdevice::EMS_DEVICE_ID_BOILER_1) {
|
||||
uint8_t hs = device_id - EMSdevice::EMS_DEVICE_ID_BOILER_1; // heating source id, count from 0
|
||||
// Runtime of each heatingsource in 0x06DC, ff
|
||||
register_telegram_type(0x6DC + hs, F("CascadeMessage"), false, MAKE_PF_CB(process_CascadeMessage));
|
||||
register_device_value(DeviceValueTAG::TAG_HS1 + hs, &burnWorkMin_, DeviceValueType::TIME, nullptr, FL_(burnWorkMin), DeviceValueUOM::MINUTES);
|
||||
register_device_value(DeviceValueTAG::TAG_HS1 + hs, &burnWorkMin_, DeviceValueType::TIME, FL_(burnWorkMin), DeviceValueUOM::MINUTES);
|
||||
// selBurnpower in D2 and E4
|
||||
// register_telegram_type(0xD2, F("CascadePowerMessage"), false, MAKE_PF_CB(process_CascadePowerMessage));
|
||||
// individual Flowtemps and powervalues for each heatingsource in E4
|
||||
register_telegram_type(0xE4, F("UBAMonitorFastPlus"), false, MAKE_PF_CB(process_UBAMonitorFastPlus));
|
||||
register_device_value(DeviceValueTAG::TAG_HS1 + hs, &selFlowTemp_, DeviceValueType::UINT, nullptr, FL_(selFlowTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_HS1 + hs, &selBurnPow_, DeviceValueType::UINT, nullptr, FL_(selBurnPow), DeviceValueUOM::PERCENT);
|
||||
register_device_value(DeviceValueTAG::TAG_HS1 + hs, &curFlowTemp_, DeviceValueType::USHORT, FL_(div10), FL_(curFlowTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_HS1 + hs, &curBurnPow_, DeviceValueType::UINT, nullptr, FL_(curBurnPow), DeviceValueUOM::PERCENT);
|
||||
register_device_value(DeviceValueTAG::TAG_HS1 + hs, &selFlowTemp_, DeviceValueType::UINT, FL_(selFlowTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_HS1 + hs, &selBurnPow_, DeviceValueType::UINT, FL_(selBurnPow), DeviceValueUOM::PERCENT);
|
||||
register_device_value(DeviceValueTAG::TAG_HS1 + hs,
|
||||
&curFlowTemp_,
|
||||
DeviceValueType::USHORT,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV10,
|
||||
FL_(curFlowTemp),
|
||||
DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_HS1 + hs, &curBurnPow_, DeviceValueType::UINT, FL_(curBurnPow), DeviceValueUOM::PERCENT);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -142,15 +143,18 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
|
||||
register_telegram_type(0x14, F("UBATotalUptime"), true, MAKE_PF_CB(process_UBATotalUptime));
|
||||
register_telegram_type(0x15, F("UBAMaintenanceData"), false, MAKE_PF_CB(process_UBAMaintenanceData));
|
||||
register_telegram_type(0x1C, F("UBAMaintenanceStatus"), false, MAKE_PF_CB(process_UBAMaintenanceStatus));
|
||||
|
||||
// EMS1.0 and maybe EMS+?
|
||||
register_telegram_type(0x18, F("UBAMonitorFast"), false, MAKE_PF_CB(process_UBAMonitorFast));
|
||||
register_telegram_type(0x19, F("UBAMonitorSlow"), false, MAKE_PF_CB(process_UBAMonitorSlow));
|
||||
register_telegram_type(0x1A, F("UBASetPoints"), false, MAKE_PF_CB(process_UBASetPoints));
|
||||
register_telegram_type(0x35, F("UBAFlags"), false, MAKE_PF_CB(process_UBAFlags));
|
||||
|
||||
// only EMS 1.0
|
||||
register_telegram_type(0x16, F("UBAParameters"), true, MAKE_PF_CB(process_UBAParameters));
|
||||
register_telegram_type(0x33, F("UBAParameterWW"), true, MAKE_PF_CB(process_UBAParameterWW));
|
||||
register_telegram_type(0x34, F("UBAMonitorWW"), false, MAKE_PF_CB(process_UBAMonitorWW));
|
||||
|
||||
// not ems1.0, but HT3
|
||||
if (model() != EMSdevice::EMS_DEVICE_FLAG_EMS) {
|
||||
register_telegram_type(0x26, F("UBASettingsWW"), true, MAKE_PF_CB(process_UBASettingsWW));
|
||||
@@ -185,93 +189,100 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
|
||||
register_telegram_type(0xBB, F("HybridHp"), true, MAKE_PF_CB(process_HybridHp));
|
||||
}
|
||||
*/
|
||||
|
||||
// reset is a command uses a dummy variable which is always zero, shown as blank, but provides command enum options
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &reset_, DeviceValueType::CMD, FL_(enum_reset), FL_(reset), DeviceValueUOM::NONE, MAKE_CF_CB(set_reset));
|
||||
has_update(reset_, 0);
|
||||
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &heatingActive_, DeviceValueType::BOOL, nullptr, FL_(heatingActive), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &tapwaterActive_, DeviceValueType::BOOL, nullptr, FL_(tapwaterActive), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &heatingActive_, DeviceValueType::BOOL, FL_(heatingActive), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &tapwaterActive_, DeviceValueType::BOOL, FL_(tapwaterActive), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &selFlowTemp_, DeviceValueType::UINT, FL_(selFlowTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_flow_temp));
|
||||
register_device_value(
|
||||
DeviceValueTAG::TAG_DEVICE_DATA, &selFlowTemp_, DeviceValueType::UINT, nullptr, FL_(selFlowTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_flow_temp));
|
||||
DeviceValueTAG::TAG_DEVICE_DATA, &selBurnPow_, DeviceValueType::UINT, FL_(selBurnPow), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_burn_power), 0, 254);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &heatingPumpMod_, DeviceValueType::UINT, FL_(heatingPumpMod), DeviceValueUOM::PERCENT);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &heatingPump2Mod_, DeviceValueType::UINT, FL_(heatingPump2Mod), DeviceValueUOM::PERCENT);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&selBurnPow_,
|
||||
DeviceValueType::UINT,
|
||||
nullptr,
|
||||
FL_(selBurnPow),
|
||||
DeviceValueUOM::PERCENT,
|
||||
MAKE_CF_CB(set_burn_power),
|
||||
0,
|
||||
254);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &heatingPumpMod_, DeviceValueType::UINT, nullptr, FL_(heatingPumpMod), DeviceValueUOM::PERCENT);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &heatingPump2Mod_, DeviceValueType::UINT, nullptr, FL_(heatingPump2Mod), DeviceValueUOM::PERCENT);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &outdoorTemp_, DeviceValueType::SHORT, FL_(div10), FL_(outdoorTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &curFlowTemp_, DeviceValueType::USHORT, FL_(div10), FL_(curFlowTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &retTemp_, DeviceValueType::USHORT, FL_(div10), FL_(retTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &switchTemp_, DeviceValueType::USHORT, FL_(div10), FL_(switchTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &sysPress_, DeviceValueType::UINT, FL_(div10), FL_(sysPress), DeviceValueUOM::BAR);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &boilTemp_, DeviceValueType::USHORT, FL_(div10), FL_(boilTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &exhaustTemp_, DeviceValueType::USHORT, FL_(div10), FL_(exhaustTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &burnGas_, DeviceValueType::BOOL, nullptr, FL_(burnGas), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &burnGas2_, DeviceValueType::BOOL, nullptr, FL_(burnGas2), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &flameCurr_, DeviceValueType::USHORT, FL_(div10), FL_(flameCurr), DeviceValueUOM::UA);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &heatingPump_, DeviceValueType::BOOL, nullptr, FL_(heatingPump), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &fanWork_, DeviceValueType::BOOL, nullptr, FL_(fanWork), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &ignWork_, DeviceValueType::BOOL, nullptr, FL_(ignWork), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &oilPreHeat_, DeviceValueType::BOOL, nullptr, FL_(oilPreHeat), DeviceValueUOM::NONE);
|
||||
&outdoorTemp_,
|
||||
DeviceValueType::SHORT,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV10,
|
||||
FL_(outdoorTemp),
|
||||
DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&curFlowTemp_,
|
||||
DeviceValueType::USHORT,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV10,
|
||||
FL_(curFlowTemp),
|
||||
DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &retTemp_, DeviceValueType::USHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(retTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&switchTemp_,
|
||||
DeviceValueType::USHORT,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV10,
|
||||
FL_(switchTemp),
|
||||
DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &sysPress_, DeviceValueType::UINT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(sysPress), DeviceValueUOM::BAR);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&boilTemp_,
|
||||
DeviceValueType::USHORT,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV10,
|
||||
FL_(boilTemp),
|
||||
DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&exhaustTemp_,
|
||||
DeviceValueType::USHORT,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV10,
|
||||
FL_(exhaustTemp),
|
||||
DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &burnGas_, DeviceValueType::BOOL, FL_(burnGas), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &burnGas2_, DeviceValueType::BOOL, FL_(burnGas2), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &flameCurr_, DeviceValueType::USHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(flameCurr), DeviceValueUOM::UA);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &heatingPump_, DeviceValueType::BOOL, FL_(heatingPump), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &fanWork_, DeviceValueType::BOOL, FL_(fanWork), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &ignWork_, DeviceValueType::BOOL, FL_(ignWork), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &oilPreHeat_, DeviceValueType::BOOL, FL_(oilPreHeat), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&heatingActivated_,
|
||||
DeviceValueType::BOOL,
|
||||
nullptr,
|
||||
FL_(heatingActivated),
|
||||
DeviceValueUOM::NONE,
|
||||
MAKE_CF_CB(set_heating_activated));
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&heatingTemp_,
|
||||
DeviceValueType::UINT,
|
||||
nullptr,
|
||||
FL_(heatingTemp),
|
||||
DeviceValueUOM::DEGREES,
|
||||
MAKE_CF_CB(set_heating_temp));
|
||||
register_device_value(
|
||||
DeviceValueTAG::TAG_DEVICE_DATA, &pumpModMax_, DeviceValueType::UINT, nullptr, FL_(pumpModMax), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_max_pump));
|
||||
register_device_value(
|
||||
DeviceValueTAG::TAG_DEVICE_DATA, &pumpModMin_, DeviceValueType::UINT, nullptr, FL_(pumpModMin), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_min_pump));
|
||||
register_device_value(
|
||||
DeviceValueTAG::TAG_DEVICE_DATA, &pumpDelay_, DeviceValueType::UINT, nullptr, FL_(pumpDelay), DeviceValueUOM::MINUTES, MAKE_CF_CB(set_pump_delay));
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &pumpModMax_, DeviceValueType::UINT, FL_(pumpModMax), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_max_pump));
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &pumpModMin_, DeviceValueType::UINT, FL_(pumpModMin), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_min_pump));
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &pumpDelay_, DeviceValueType::UINT, FL_(pumpDelay), DeviceValueUOM::MINUTES, MAKE_CF_CB(set_pump_delay));
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&burnMinPeriod_,
|
||||
DeviceValueType::UINT,
|
||||
nullptr,
|
||||
FL_(burnMinPeriod),
|
||||
DeviceValueUOM::MINUTES,
|
||||
MAKE_CF_CB(set_burn_period));
|
||||
register_device_value(
|
||||
DeviceValueTAG::TAG_DEVICE_DATA, &burnMinPower_, DeviceValueType::UINT, nullptr, FL_(burnMinPower), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_min_power));
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&burnMaxPower_,
|
||||
&burnMinPower_,
|
||||
DeviceValueType::UINT,
|
||||
nullptr,
|
||||
FL_(burnMaxPower),
|
||||
FL_(burnMinPower),
|
||||
DeviceValueUOM::PERCENT,
|
||||
MAKE_CF_CB(set_max_power),
|
||||
0,
|
||||
254);
|
||||
MAKE_CF_CB(set_min_power));
|
||||
register_device_value(
|
||||
DeviceValueTAG::TAG_DEVICE_DATA, &boilHystOn_, DeviceValueType::INT, nullptr, FL_(boilHystOn), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_hyst_on));
|
||||
register_device_value(
|
||||
DeviceValueTAG::TAG_DEVICE_DATA, &boilHystOff_, DeviceValueType::INT, nullptr, FL_(boilHystOff), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_hyst_off));
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &setFlowTemp_, DeviceValueType::UINT, nullptr, FL_(setFlowTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &setBurnPow_, DeviceValueType::UINT, nullptr, FL_(setBurnPow), DeviceValueUOM::PERCENT);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &curBurnPow_, DeviceValueType::UINT, nullptr, FL_(curBurnPow), DeviceValueUOM::PERCENT);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &burnStarts_, DeviceValueType::ULONG, nullptr, FL_(burnStarts), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &burnWorkMin_, DeviceValueType::TIME, nullptr, FL_(burnWorkMin), DeviceValueUOM::MINUTES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &burn2WorkMin_, DeviceValueType::TIME, nullptr, FL_(burn2WorkMin), DeviceValueUOM::MINUTES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &heatWorkMin_, DeviceValueType::TIME, nullptr, FL_(heatWorkMin), DeviceValueUOM::MINUTES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &UBAuptime_, DeviceValueType::TIME, nullptr, FL_(UBAuptime), DeviceValueUOM::MINUTES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &lastCode_, DeviceValueType::STRING, nullptr, FL_(lastCode), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &serviceCode_, DeviceValueType::STRING, nullptr, FL_(serviceCode), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &serviceCodeNumber_, DeviceValueType::USHORT, nullptr, FL_(serviceCodeNumber), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &maintenanceMessage_, DeviceValueType::STRING, nullptr, FL_(maintenanceMessage), DeviceValueUOM::NONE);
|
||||
DeviceValueTAG::TAG_DEVICE_DATA, &burnMaxPower_, DeviceValueType::UINT, FL_(burnMaxPower), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_max_power), 0, 254);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &boilHystOn_, DeviceValueType::INT, FL_(boilHystOn), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_hyst_on));
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &boilHystOff_, DeviceValueType::INT, FL_(boilHystOff), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_hyst_off));
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &setFlowTemp_, DeviceValueType::UINT, FL_(setFlowTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &setBurnPow_, DeviceValueType::UINT, FL_(setBurnPow), DeviceValueUOM::PERCENT);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &curBurnPow_, DeviceValueType::UINT, FL_(curBurnPow), DeviceValueUOM::PERCENT);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &burnStarts_, DeviceValueType::ULONG, FL_(burnStarts), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &burnWorkMin_, DeviceValueType::TIME, FL_(burnWorkMin), DeviceValueUOM::MINUTES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &burn2WorkMin_, DeviceValueType::TIME, FL_(burn2WorkMin), DeviceValueUOM::MINUTES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &heatWorkMin_, DeviceValueType::TIME, FL_(heatWorkMin), DeviceValueUOM::MINUTES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &UBAuptime_, DeviceValueType::TIME, FL_(UBAuptime), DeviceValueUOM::MINUTES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &lastCode_, DeviceValueType::STRING, FL_(lastCode), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &serviceCode_, DeviceValueType::STRING, FL_(serviceCode), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &serviceCodeNumber_, DeviceValueType::USHORT, FL_(serviceCodeNumber), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &maintenanceMessage_, DeviceValueType::STRING, FL_(maintenanceMessage), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&maintenanceType_,
|
||||
DeviceValueType::ENUM,
|
||||
@@ -282,7 +293,6 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&maintenanceTime_,
|
||||
DeviceValueType::USHORT,
|
||||
nullptr,
|
||||
FL_(maintenanceTime),
|
||||
DeviceValueUOM::HOURS,
|
||||
MAKE_CF_CB(set_maintenancetime));
|
||||
@@ -296,14 +306,12 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&emergencyOps_,
|
||||
DeviceValueType::BOOL,
|
||||
nullptr,
|
||||
FL_(emergencyOps),
|
||||
DeviceValueUOM::NONE,
|
||||
MAKE_CF_CB(set_emergency_ops));
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&emergencyTemp_,
|
||||
DeviceValueType::UINT,
|
||||
nullptr,
|
||||
FL_(emergencyTemp),
|
||||
DeviceValueUOM::DEGREES,
|
||||
MAKE_CF_CB(set_emergency_temp),
|
||||
@@ -336,7 +344,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&energyCostRatio_,
|
||||
DeviceValueType::UINT,
|
||||
FL_(div10),
|
||||
DeviceValueNumOp::DV_NUMOP_DIV10,
|
||||
FL_(energyCostRatio),
|
||||
DeviceValueUOM::NONE,
|
||||
MAKE_CF_CB(set_energyCostRatio),
|
||||
@@ -345,7 +353,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&fossileFactor_,
|
||||
DeviceValueType::UINT,
|
||||
FL_(div10),
|
||||
DeviceValueNumOp::DV_NUMOP_DIV10,
|
||||
FL_(fossileFactor),
|
||||
DeviceValueUOM::NONE,
|
||||
MAKE_CF_CB(set_fossileFactor),
|
||||
@@ -354,7 +362,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&electricFactor_,
|
||||
DeviceValueType::UINT,
|
||||
FL_(div10),
|
||||
DeviceValueNumOp::DV_NUMOP_DIV10,
|
||||
FL_(electricFactor),
|
||||
DeviceValueUOM::NONE,
|
||||
MAKE_CF_CB(set_electricFactor),
|
||||
@@ -382,81 +390,110 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
|
||||
*/
|
||||
// heatpump info
|
||||
if (model() == EMS_DEVICE_FLAG_HEATPUMP) {
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &upTimeControl_, DeviceValueType::TIME, FL_(div60), FL_(upTimeControl), DeviceValueUOM::MINUTES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &upTimeCompHeating_, DeviceValueType::TIME, FL_(div60), FL_(upTimeCompHeating), DeviceValueUOM::MINUTES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &upTimeCompCooling_, DeviceValueType::TIME, FL_(div60), FL_(upTimeCompCooling), DeviceValueUOM::MINUTES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &upTimeCompWw_, DeviceValueType::TIME, FL_(div60), FL_(upTimeCompWw), DeviceValueUOM::MINUTES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &upTimeCompPool_, DeviceValueType::TIME, FL_(div60), FL_(upTimeCompPool), DeviceValueUOM::MINUTES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &totalCompStarts_, DeviceValueType::ULONG, nullptr, FL_(totalCompStarts), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &heatingStarts_, DeviceValueType::ULONG, nullptr, FL_(heatingStarts), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &coolingStarts_, DeviceValueType::ULONG, nullptr, FL_(coolingStarts), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwStarts2_, DeviceValueType::ULONG, nullptr, FL_(wwStarts2), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &poolStarts_, DeviceValueType::ULONG, nullptr, FL_(poolStarts), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &nrgConsTotal_, DeviceValueType::ULONG, nullptr, FL_(nrgConsTotal), DeviceValueUOM::KWH);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &nrgConsCompTotal_, DeviceValueType::ULONG, nullptr, FL_(nrgConsCompTotal), DeviceValueUOM::KWH);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &nrgConsCompHeating_, DeviceValueType::ULONG, nullptr, FL_(nrgConsCompHeating), DeviceValueUOM::KWH);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &nrgConsCompWw_, DeviceValueType::ULONG, nullptr, FL_(nrgConsCompWw), DeviceValueUOM::KWH);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &nrgConsCompCooling_, DeviceValueType::ULONG, nullptr, FL_(nrgConsCompCooling), DeviceValueUOM::KWH);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &nrgConsCompPool_, DeviceValueType::ULONG, nullptr, FL_(nrgConsCompPool), DeviceValueUOM::KWH);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&auxElecHeatNrgConsTotal_,
|
||||
DeviceValueType::ULONG,
|
||||
nullptr,
|
||||
FL_(auxElecHeatNrgConsTotal),
|
||||
DeviceValueUOM::KWH);
|
||||
&upTimeControl_,
|
||||
DeviceValueType::TIME,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV60,
|
||||
FL_(upTimeControl),
|
||||
DeviceValueUOM::MINUTES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&upTimeCompHeating_,
|
||||
DeviceValueType::TIME,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV60,
|
||||
FL_(upTimeCompHeating),
|
||||
DeviceValueUOM::MINUTES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&upTimeCompCooling_,
|
||||
DeviceValueType::TIME,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV60,
|
||||
FL_(upTimeCompCooling),
|
||||
DeviceValueUOM::MINUTES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&upTimeCompWw_,
|
||||
DeviceValueType::TIME,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV60,
|
||||
FL_(upTimeCompWw),
|
||||
DeviceValueUOM::MINUTES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&upTimeCompPool_,
|
||||
DeviceValueType::TIME,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV60,
|
||||
FL_(upTimeCompPool),
|
||||
DeviceValueUOM::MINUTES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &totalCompStarts_, DeviceValueType::ULONG, FL_(totalCompStarts), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &heatingStarts_, DeviceValueType::ULONG, FL_(heatingStarts), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &coolingStarts_, DeviceValueType::ULONG, FL_(coolingStarts), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwStarts2_, DeviceValueType::ULONG, FL_(wwStarts2), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &poolStarts_, DeviceValueType::ULONG, FL_(poolStarts), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &nrgConsTotal_, DeviceValueType::ULONG, FL_(nrgConsTotal), DeviceValueUOM::KWH);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &nrgConsCompTotal_, DeviceValueType::ULONG, FL_(nrgConsCompTotal), DeviceValueUOM::KWH);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &nrgConsCompHeating_, DeviceValueType::ULONG, FL_(nrgConsCompHeating), DeviceValueUOM::KWH);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &nrgConsCompWw_, DeviceValueType::ULONG, FL_(nrgConsCompWw), DeviceValueUOM::KWH);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &nrgConsCompCooling_, DeviceValueType::ULONG, FL_(nrgConsCompCooling), DeviceValueUOM::KWH);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &nrgConsCompPool_, DeviceValueType::ULONG, FL_(nrgConsCompPool), DeviceValueUOM::KWH);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &auxElecHeatNrgConsTotal_, DeviceValueType::ULONG, FL_(auxElecHeatNrgConsTotal), DeviceValueUOM::KWH);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&auxElecHeatNrgConsHeating_,
|
||||
DeviceValueType::ULONG,
|
||||
nullptr,
|
||||
FL_(auxElecHeatNrgConsHeating),
|
||||
DeviceValueUOM::KWH);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&auxElecHeatNrgConsWW_,
|
||||
DeviceValueType::ULONG,
|
||||
nullptr,
|
||||
FL_(auxElecHeatNrgConsWW),
|
||||
DeviceValueUOM::KWH);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&auxElecHeatNrgConsPool_,
|
||||
DeviceValueType::ULONG,
|
||||
nullptr,
|
||||
FL_(auxElecHeatNrgConsPool),
|
||||
DeviceValueUOM::KWH);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &nrgSuppTotal_, DeviceValueType::ULONG, nullptr, FL_(nrgSuppTotal), DeviceValueUOM::KWH);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &nrgSuppHeating_, DeviceValueType::ULONG, nullptr, FL_(nrgSuppHeating), DeviceValueUOM::KWH);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &nrgSuppWw_, DeviceValueType::ULONG, nullptr, FL_(nrgSuppWw), DeviceValueUOM::KWH);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &nrgSuppCooling_, DeviceValueType::ULONG, nullptr, FL_(nrgSuppCooling), DeviceValueUOM::KWH);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &nrgSuppPool_, DeviceValueType::ULONG, nullptr, FL_(nrgSuppPool), DeviceValueUOM::KWH);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpPower_, DeviceValueType::UINT, FL_(div10), FL_(hpPower), DeviceValueUOM::KW);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpCompOn_, DeviceValueType::BOOL, nullptr, FL_(hpCompOn), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &auxElecHeatNrgConsWW_, DeviceValueType::ULONG, FL_(auxElecHeatNrgConsWW), DeviceValueUOM::KWH);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &auxElecHeatNrgConsPool_, DeviceValueType::ULONG, FL_(auxElecHeatNrgConsPool), DeviceValueUOM::KWH);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &nrgSuppTotal_, DeviceValueType::ULONG, FL_(nrgSuppTotal), DeviceValueUOM::KWH);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &nrgSuppHeating_, DeviceValueType::ULONG, FL_(nrgSuppHeating), DeviceValueUOM::KWH);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &nrgSuppWw_, DeviceValueType::ULONG, FL_(nrgSuppWw), DeviceValueUOM::KWH);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &nrgSuppCooling_, DeviceValueType::ULONG, FL_(nrgSuppCooling), DeviceValueUOM::KWH);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &nrgSuppPool_, DeviceValueType::ULONG, FL_(nrgSuppPool), DeviceValueUOM::KWH);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpPower_, DeviceValueType::UINT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(hpPower), DeviceValueUOM::KW);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpCompOn_, DeviceValueType::BOOL, FL_(hpCompOn), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpActivity_, DeviceValueType::ENUM, FL_(enum_hpactivity), FL_(hpActivity), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpHeatingOn_, DeviceValueType::BOOL, nullptr, FL_(hpHeatingOn), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpCoolingOn_, DeviceValueType::BOOL, nullptr, FL_(hpCoolingOn), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpWwOn_, DeviceValueType::BOOL, nullptr, FL_(hpWwOn), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpPoolOn_, DeviceValueType::BOOL, nullptr, FL_(hpPoolOn), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpBrinePumpSpd_, DeviceValueType::UINT, nullptr, FL_(hpBrinePumpSpd), DeviceValueUOM::PERCENT);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpSwitchValve_, DeviceValueType::BOOL, nullptr, FL_(hpSwitchValve), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpCompSpd_, DeviceValueType::UINT, nullptr, FL_(hpCompSpd), DeviceValueUOM::PERCENT);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpCircSpd_, DeviceValueType::UINT, nullptr, FL_(hpCircSpd), DeviceValueUOM::PERCENT);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpBrineIn_, DeviceValueType::SHORT, FL_(div10), FL_(hpBrineIn), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpBrineOut_, DeviceValueType::SHORT, FL_(div10), FL_(hpBrineOut), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpSuctionGas_, DeviceValueType::SHORT, FL_(div10), FL_(hpSuctionGas), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpHotGas_, DeviceValueType::SHORT, FL_(div10), FL_(hpHotGas), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpTc0_, DeviceValueType::SHORT, FL_(div10), FL_(hpTc0), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpTc1_, DeviceValueType::SHORT, FL_(div10), FL_(hpTc1), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpTc3_, DeviceValueType::SHORT, FL_(div10), FL_(hpTc3), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpTr3_, DeviceValueType::SHORT, FL_(div10), FL_(hpTr3), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpTr4_, DeviceValueType::SHORT, FL_(div10), FL_(hpTr4), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpTr5_, DeviceValueType::SHORT, FL_(div10), FL_(hpTr5), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpTr6_, DeviceValueType::SHORT, FL_(div10), FL_(hpTr6), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpTr7_, DeviceValueType::SHORT, FL_(div10), FL_(hpTr7), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpTl2_, DeviceValueType::SHORT, FL_(div10), FL_(hpTl2), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpPl1_, DeviceValueType::SHORT, FL_(div10), FL_(hpPl1), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpPh1_, DeviceValueType::SHORT, FL_(div10), FL_(hpPh1), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpHeatingOn_, DeviceValueType::BOOL, FL_(hpHeatingOn), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpCoolingOn_, DeviceValueType::BOOL, FL_(hpCoolingOn), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpWwOn_, DeviceValueType::BOOL, FL_(hpWwOn), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpPoolOn_, DeviceValueType::BOOL, FL_(hpPoolOn), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpBrinePumpSpd_, DeviceValueType::UINT, FL_(hpBrinePumpSpd), DeviceValueUOM::PERCENT);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpSwitchValve_, DeviceValueType::BOOL, FL_(hpSwitchValve), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpCompSpd_, DeviceValueType::UINT, FL_(hpCompSpd), DeviceValueUOM::PERCENT);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpCircSpd_, DeviceValueType::UINT, FL_(hpCircSpd), DeviceValueUOM::PERCENT);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&hpBrineIn_,
|
||||
DeviceValueType::SHORT,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV10,
|
||||
FL_(hpBrineIn),
|
||||
DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&hpBrineOut_,
|
||||
DeviceValueType::SHORT,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV10,
|
||||
FL_(hpBrineOut),
|
||||
DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&hpSuctionGas_,
|
||||
DeviceValueType::SHORT,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV10,
|
||||
FL_(hpSuctionGas),
|
||||
DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&hpHotGas_,
|
||||
DeviceValueType::SHORT,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV10,
|
||||
FL_(hpHotGas),
|
||||
DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpTc0_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(hpTc0), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpTc1_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(hpTc1), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpTc3_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(hpTc3), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpTr3_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(hpTr3), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpTr4_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(hpTr4), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpTr5_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(hpTr5), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpTr6_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(hpTr6), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpTr7_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(hpTr7), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpTl2_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(hpTl2), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpPl1_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(hpPl1), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpPh1_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(hpPh1), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&poolSetTemp_,
|
||||
DeviceValueType::UINT,
|
||||
FL_(div2),
|
||||
DeviceValueNumOp::DV_NUMOP_DIV2,
|
||||
FL_(poolSetTemp),
|
||||
DeviceValueUOM::DEGREES,
|
||||
MAKE_CF_CB(set_pool_temp));
|
||||
@@ -466,25 +503,21 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW,
|
||||
&wwTapActivated_,
|
||||
DeviceValueType::BOOL,
|
||||
nullptr,
|
||||
FL_(wwtapactivated),
|
||||
DeviceValueUOM::NONE,
|
||||
MAKE_CF_CB(set_tapwarmwater_activated));
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwSetTemp_, DeviceValueType::UINT, nullptr, FL_(wwSetTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(
|
||||
DeviceValueTAG::TAG_BOILER_DATA_WW, &wwSelTemp_, DeviceValueType::UINT, nullptr, FL_(wwSelTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_ww_temp));
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwSetTemp_, DeviceValueType::UINT, FL_(wwSetTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwSelTemp_, DeviceValueType::UINT, FL_(wwSelTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_ww_temp));
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW,
|
||||
&wwSelTempLow_,
|
||||
DeviceValueType::UINT,
|
||||
nullptr,
|
||||
FL_(wwSelTempLow),
|
||||
DeviceValueUOM::DEGREES,
|
||||
MAKE_CF_CB(set_ww_temp_low));
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwSelTempOff_, DeviceValueType::UINT, nullptr, FL_(wwSelTempOff), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwSelTempOff_, DeviceValueType::UINT, FL_(wwSelTempOff), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW,
|
||||
&wwSelTempSingle_,
|
||||
DeviceValueType::UINT,
|
||||
nullptr,
|
||||
FL_(wwSelTempSingle),
|
||||
DeviceValueUOM::DEGREES,
|
||||
MAKE_CF_CB(set_ww_temp_single));
|
||||
@@ -506,7 +539,6 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW,
|
||||
&wwFlowTempOffset_,
|
||||
DeviceValueType::UINT,
|
||||
nullptr,
|
||||
FL_(wwFlowTempOffset),
|
||||
DeviceValueUOM::DEGREES_R,
|
||||
MAKE_CF_CB(set_ww_flowTempOffset),
|
||||
@@ -515,44 +547,30 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW,
|
||||
&wwChargeOptimization_,
|
||||
DeviceValueType::BOOL,
|
||||
nullptr,
|
||||
FL_(wwChargeOptimization),
|
||||
DeviceValueUOM::NONE,
|
||||
MAKE_CF_CB(set_ww_chargeOptimization));
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW,
|
||||
&wwMaxPower_,
|
||||
DeviceValueType::UINT,
|
||||
nullptr,
|
||||
FL_(wwMaxPower),
|
||||
DeviceValueUOM::PERCENT,
|
||||
MAKE_CF_CB(set_ww_maxpower),
|
||||
0,
|
||||
254);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW,
|
||||
&wwMaxTemp_,
|
||||
DeviceValueType::UINT,
|
||||
nullptr,
|
||||
FL_(wwMaxTemp),
|
||||
DeviceValueUOM::DEGREES,
|
||||
MAKE_CF_CB(set_ww_maxtemp),
|
||||
0,
|
||||
70);
|
||||
register_device_value(
|
||||
DeviceValueTAG::TAG_BOILER_DATA_WW, &wwMaxPower_, DeviceValueType::UINT, FL_(wwMaxPower), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_ww_maxpower), 0, 254);
|
||||
register_device_value(
|
||||
DeviceValueTAG::TAG_BOILER_DATA_WW, &wwMaxTemp_, DeviceValueType::UINT, FL_(wwMaxTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_ww_maxtemp), 0, 70);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW,
|
||||
&wwCircPump_,
|
||||
DeviceValueType::BOOL,
|
||||
nullptr,
|
||||
FL_(wwCircPump),
|
||||
DeviceValueUOM::NONE,
|
||||
MAKE_CF_CB(set_ww_circulation_pump));
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwChargeType_, DeviceValueType::ENUM, FL_(enum_charge), FL_(wwChargeType), DeviceValueUOM::NONE);
|
||||
register_device_value(
|
||||
DeviceValueTAG::TAG_BOILER_DATA_WW, &wwHystOn_, DeviceValueType::INT, nullptr, FL_(wwHystOn), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_ww_hyst_on));
|
||||
register_device_value(
|
||||
DeviceValueTAG::TAG_BOILER_DATA_WW, &wwHystOff_, DeviceValueType::INT, nullptr, FL_(wwHystOff), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_ww_hyst_off));
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwHystOn_, DeviceValueType::INT, FL_(wwHystOn), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_ww_hyst_on));
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW,
|
||||
&wwHystOff_,
|
||||
DeviceValueType::INT,
|
||||
FL_(wwHystOff),
|
||||
DeviceValueUOM::DEGREES_R,
|
||||
MAKE_CF_CB(set_ww_hyst_off));
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW,
|
||||
&wwDisinfectionTemp_,
|
||||
DeviceValueType::UINT,
|
||||
nullptr,
|
||||
FL_(wwDisinfectionTemp),
|
||||
DeviceValueUOM::DEGREES,
|
||||
MAKE_CF_CB(set_ww_disinfect_temp));
|
||||
@@ -563,45 +581,76 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
|
||||
FL_(wwCircMode),
|
||||
DeviceValueUOM::NONE,
|
||||
MAKE_CF_CB(set_ww_circulation_mode));
|
||||
register_device_value(
|
||||
DeviceValueTAG::TAG_BOILER_DATA_WW, &wwCirc_, DeviceValueType::BOOL, nullptr, FL_(wwCirc), DeviceValueUOM::NONE, MAKE_CF_CB(set_ww_circulation));
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwCurTemp_, DeviceValueType::USHORT, FL_(div10), FL_(wwCurTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwCurTemp2_, DeviceValueType::USHORT, FL_(div10), FL_(wwCurTemp2), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwCurFlow_, DeviceValueType::UINT, FL_(div10), FL_(wwCurFlow), DeviceValueUOM::LMIN);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwStorageTemp1_, DeviceValueType::USHORT, FL_(div10), FL_(wwStorageTemp1), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwStorageTemp2_, DeviceValueType::USHORT, FL_(div10), FL_(wwStorageTemp2), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwCirc_, DeviceValueType::BOOL, FL_(wwCirc), DeviceValueUOM::NONE, MAKE_CF_CB(set_ww_circulation));
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW,
|
||||
&wwCurTemp_,
|
||||
DeviceValueType::USHORT,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV10,
|
||||
FL_(wwCurTemp),
|
||||
DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW,
|
||||
&wwCurTemp2_,
|
||||
DeviceValueType::USHORT,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV10,
|
||||
FL_(wwCurTemp2),
|
||||
DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW,
|
||||
&wwCurFlow_,
|
||||
DeviceValueType::UINT,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV10,
|
||||
FL_(wwCurFlow),
|
||||
DeviceValueUOM::LMIN);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW,
|
||||
&wwStorageTemp1_,
|
||||
DeviceValueType::USHORT,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV10,
|
||||
FL_(wwStorageTemp1),
|
||||
DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW,
|
||||
&wwStorageTemp2_,
|
||||
DeviceValueType::USHORT,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV10,
|
||||
FL_(wwStorageTemp2),
|
||||
DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW,
|
||||
&wwActivated_,
|
||||
DeviceValueType::BOOL,
|
||||
nullptr,
|
||||
FL_(wwActivated),
|
||||
DeviceValueUOM::NONE,
|
||||
MAKE_CF_CB(set_ww_activated));
|
||||
register_device_value(
|
||||
DeviceValueTAG::TAG_BOILER_DATA_WW, &wwOneTime_, DeviceValueType::BOOL, nullptr, FL_(wwOneTime), DeviceValueUOM::NONE, MAKE_CF_CB(set_ww_onetime));
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwOneTime_, DeviceValueType::BOOL, FL_(wwOneTime), DeviceValueUOM::NONE, MAKE_CF_CB(set_ww_onetime));
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW,
|
||||
&wwDisinfect_,
|
||||
DeviceValueType::BOOL,
|
||||
nullptr,
|
||||
FL_(wwDisinfecting),
|
||||
DeviceValueUOM::NONE,
|
||||
MAKE_CF_CB(set_ww_disinfect));
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwCharging_, DeviceValueType::BOOL, nullptr, FL_(wwCharging), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwRecharging_, DeviceValueType::BOOL, nullptr, FL_(wwRecharging), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwTempOK_, DeviceValueType::BOOL, nullptr, FL_(wwTempOK), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwActive_, DeviceValueType::BOOL, nullptr, FL_(wwActive), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &ww3wayValve_, DeviceValueType::BOOL, nullptr, FL_(ww3wayValve), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwSetPumpPower_, DeviceValueType::UINT, nullptr, FL_(wwSetPumpPower), DeviceValueUOM::PERCENT);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwMixerTemp_, DeviceValueType::USHORT, FL_(div10), FL_(wwMixerTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwCylMiddleTemp_, DeviceValueType::USHORT, FL_(div10), FL_(wwCylMiddleTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwStarts_, DeviceValueType::ULONG, nullptr, FL_(wwStarts), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwWorkM_, DeviceValueType::TIME, nullptr, FL_(wwWorkM), DeviceValueUOM::MINUTES);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwCharging_, DeviceValueType::BOOL, FL_(wwCharging), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwRecharging_, DeviceValueType::BOOL, FL_(wwRecharging), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwTempOK_, DeviceValueType::BOOL, FL_(wwTempOK), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwActive_, DeviceValueType::BOOL, FL_(wwActive), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &ww3wayValve_, DeviceValueType::BOOL, FL_(ww3wayValve), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwSetPumpPower_, DeviceValueType::UINT, FL_(wwSetPumpPower), DeviceValueUOM::PERCENT);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW,
|
||||
&wwMixerTemp_,
|
||||
DeviceValueType::USHORT,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV10,
|
||||
FL_(wwMixerTemp),
|
||||
DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW,
|
||||
&wwCylMiddleTemp_,
|
||||
DeviceValueType::USHORT,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV10,
|
||||
FL_(wwCylMiddleTemp),
|
||||
DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwStarts_, DeviceValueType::ULONG, FL_(wwStarts), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwWorkM_, DeviceValueType::TIME, FL_(wwWorkM), DeviceValueUOM::MINUTES);
|
||||
|
||||
// fetch some initial data
|
||||
EMSESP::send_read_request(0x10, device_id); // read last errorcode on start (only published on errors)
|
||||
EMSESP::send_read_request(0x11, device_id); // read last errorcode on start (only published on errors)
|
||||
EMSESP::send_read_request(0x15, device_id); // read maintenace data on start (only published on change)
|
||||
EMSESP::send_read_request(0x1C, device_id); // read maintenace status on start (only published on change)
|
||||
EMSESP::send_read_request(0x15, device_id); // read maintenance data on start (only published on change)
|
||||
EMSESP::send_read_request(0x1C, device_id); // read maintenance status on start (only published on change)
|
||||
EMSESP::send_read_request(0xC2, device_id); // read last errorcode on start (only published on errors)
|
||||
}
|
||||
|
||||
@@ -1286,9 +1335,6 @@ void Boiler::process_amCommandMessage(std::shared_ptr<const Telegram> telegram)
|
||||
// pos 6: boiler blocking 0-off, 1-on
|
||||
}
|
||||
|
||||
// 0x0550 AM200 broadcasted message, all 27 bytes unkown
|
||||
// Rx: 60 00 FF 00 04 50 00 FF 00 FF FF 00 0D 00 01 00 00 00 00 01 03 01 00 03 00 2D 19 C8 02 94 00 4A
|
||||
// Rx: 60 00 FF 19 04 50 00 FF FF 39
|
||||
void Boiler::process_amExtraMessage(std::shared_ptr<const Telegram> telegram) {
|
||||
}
|
||||
|
||||
@@ -1458,6 +1504,11 @@ bool Boiler::set_releaseWait(const char * value, const int8_t id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// 0x0550 AM200 broadcasted message, all 27 bytes unkown
|
||||
// Rx: 60 00 FF 00 04 50 00 FF 00 FF FF 00 0D 00 01 00 00 00 00 01 03 01 00 03 00 2D 19 C8 02 94 00 4A
|
||||
// Rx: 60 00 FF 19 04 50 00 FF FF 39
|
||||
|
||||
/*
|
||||
* Hybrid heatpump with telegram 0xBB is readable and writeable in boiler and thermostat
|
||||
* thermostat always overwrites settings in boiler
|
||||
@@ -2079,7 +2130,7 @@ bool Boiler::set_maintenance(const char * value, const int8_t id) {
|
||||
|
||||
std::string s;
|
||||
if (Helpers::value2string(value, s)) {
|
||||
if (s == Helpers::toLower(read_flash_string(F_(reset)))) {
|
||||
if (s == Helpers::translated_word(FL_(reset))) {
|
||||
// LOG_INFO(F("Reset boiler maintenance message"));
|
||||
write_command(0x05, 0x08, 0xFF, 0x1C);
|
||||
return true;
|
||||
|
||||
@@ -227,15 +227,11 @@ class Boiler : public EMSdevice {
|
||||
int8_t blockHyst_; // pos 14?: Hyst. for bolier block (K)
|
||||
uint8_t releaseWait_; // pos 15: Boiler release wait time (min)
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Hybrid heatpump with telegram 0xBB is readable and writeable in boiler and thermostat
|
||||
* thermostat always overwrites settings in boiler
|
||||
* enable settings here if no thermostat is used in system
|
||||
*
|
||||
// HybridHP
|
||||
// Hybrid heatpump with telegram 0xBB is readable and writeable in boiler and thermostat
|
||||
// thermostat always overwrites settings in boiler
|
||||
//enable settings here if no thermostat is used in system
|
||||
// HybridHP
|
||||
uint8_t hybridStrategy_; // cost = 2, temperature = 3, mix = 4
|
||||
int8_t switchOverTemp_; // degrees
|
||||
uint8_t energyCostRatio_; // is *10
|
||||
@@ -243,7 +239,7 @@ class Boiler : public EMSdevice {
|
||||
uint8_t electricFactor_; // is * 10
|
||||
uint8_t delayBoiler_; // minutes
|
||||
uint8_t tempDiffBoiler_; // relative temperature degrees
|
||||
*/
|
||||
*/
|
||||
|
||||
void process_UBAParameterWW(std::shared_ptr<const Telegram> telegram);
|
||||
void process_UBAMonitorFast(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user