mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2026-09-14 22:24:07 +00:00
Compare commits
136
Commits
core3
..
6a1f4c9941
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a1f4c9941 | ||
|
|
13c6ed9e06 | ||
|
|
c711d1c6de | ||
|
|
71824d1fc7 | ||
|
|
aaea00f943 | ||
|
|
f01b0fc3a7 | ||
|
|
ad62c79a40 | ||
|
|
e3b36a4084 | ||
|
|
ce2b28651d | ||
|
|
518ac0b4b4 | ||
|
|
c5e2ba024c | ||
|
|
c765b5c9b6 | ||
|
|
84d67c0946 | ||
|
|
04f99688eb | ||
|
|
0991ed467e | ||
|
|
4d8d3f0d46 | ||
|
|
db5ccb018f | ||
|
|
4d4d716e5f | ||
|
|
94926b0ca7 | ||
|
|
98a6199a69 | ||
|
|
416fecfaed | ||
|
|
319dfe6944 | ||
|
|
3d0399b3a8 | ||
|
|
8eb1a47b5a | ||
|
|
fe972db8c5 | ||
|
|
be224265f9 | ||
|
|
5fcd9bfd2d | ||
|
|
69b6af4069 | ||
|
|
cdade5f999 | ||
|
|
97af64c53b | ||
|
|
bcb47912c0 | ||
|
|
badd8f2241 | ||
|
|
5aed596140 | ||
|
|
015da730bf | ||
|
|
0b47e5ec05 | ||
|
|
b0255d726b | ||
|
|
fce46f7613 | ||
|
|
c3cd4ac6b6 | ||
|
|
91955689a2 | ||
|
|
89e88135bc | ||
|
|
42f86cd12b | ||
|
|
6efc7113b3 | ||
|
|
48d425e0b6 | ||
|
|
b18b27d2f6 | ||
|
|
5d992e5a70 | ||
|
|
80e48430f5 | ||
|
|
cedafbd462 | ||
|
|
9d3eaa5ff7 | ||
|
|
bc6351cc22 | ||
|
|
9b0ee07ee1 | ||
|
|
4fe64fbff8 | ||
|
|
51b689d91b | ||
|
|
f5189b4d09 | ||
|
|
55b882cec2 | ||
|
|
c16f12990a | ||
|
|
ed4b247d28 | ||
|
|
afb8efec53 | ||
|
|
84b23f19e5 | ||
|
|
668d59a2cd | ||
|
|
88f258d7e9 | ||
|
|
c45441e3be | ||
|
|
2994535158 | ||
|
|
fdfb560cf9 | ||
|
|
d56c5897fa | ||
|
|
cec0fbc95d | ||
|
|
8f118e07a3 | ||
|
|
b05ae6dbbb | ||
|
|
469ca8bf05 | ||
|
|
edd1677526 | ||
|
|
c363e0e2c8 | ||
|
|
d397a14b41 | ||
|
|
822096b635 | ||
|
|
0defebfdb6 | ||
|
|
574c4e45d5 | ||
|
|
48c118fed7 | ||
|
|
ef4335156c | ||
|
|
4d4269cdf7 | ||
|
|
95a8b78d1d | ||
|
|
982b0f304e | ||
|
|
1d9a0b6257 | ||
|
|
52486ed5b4 | ||
|
|
73f2634acc | ||
|
|
2a604008ff | ||
|
|
cd0a51f843 | ||
|
|
34cd93b7be | ||
|
|
d47915d815 | ||
|
|
cb97de7706 | ||
|
|
dbc5a73583 | ||
|
|
4d25c69c84 | ||
|
|
b430866bbb | ||
|
|
553752a7ca | ||
|
|
9c992d8fda | ||
|
|
e4d127db67 | ||
|
|
5b41217a67 | ||
|
|
d913824b35 | ||
|
|
ae3443c637 | ||
|
|
50c34c13f0 | ||
|
|
d8fab910a8 | ||
|
|
4231192991 | ||
|
|
b47e7d1dd6 | ||
|
|
fb55327373 | ||
|
|
4aab9e095a | ||
|
|
12ae358434 | ||
|
|
e22b82e913 | ||
|
|
285361c09f | ||
|
|
16caff789d | ||
|
|
835a8762f6 | ||
|
|
90f0c9c01b | ||
|
|
bde9ab53a1 | ||
|
|
901721fb99 | ||
|
|
dac1e1a69c | ||
|
|
1b2371c67a | ||
|
|
0f396200a2 | ||
|
|
1956ff8386 | ||
|
|
3a81c70018 | ||
|
|
7212439f1b | ||
|
|
6f7f343abd | ||
|
|
42e6e31476 | ||
|
|
96cf019f0a | ||
|
|
59b0417297 | ||
|
|
2369277a9e | ||
|
|
e166db8b70 | ||
|
|
36884867f7 | ||
|
|
7fb8eb3592 | ||
|
|
e0f59a4c31 | ||
|
|
7eba8acbb4 | ||
|
|
d0e6a603bb | ||
|
|
36b4dd55a7 | ||
|
|
341a4d4e1d | ||
|
|
a61745b710 | ||
|
|
b1ebd81ca2 | ||
|
|
6487a3be70 | ||
|
|
2eb804de0b | ||
|
|
1ff53e6ac7 | ||
|
|
9cab3b480e | ||
|
|
bacd3faac2 |
@@ -12,10 +12,18 @@ This release is based on the latest Espressif/Arduino core version 3. It brings
|
||||
- Commands Service that can be called via MQTT or API or used in the Scheduler Service
|
||||
- option to disable factory reset [#3150](https://github.com/emsesp/EMS-ESP32/issues/3150)
|
||||
- TLS support with 4MB boards without PSRAM
|
||||
- added eth_10mbit to Network Settings, which forces 10BASE-T half-duplex & autonegotiation off, halving the PHY’s power draw. It’s off by default as it will cause issues on managed switches with a port forced to 100Mb [#3213](https://github.com/emsesp/EMS-ESP32/pull/3213)
|
||||
|
||||
## Fixed
|
||||
|
||||
- shunting yard show json
|
||||
- `system/sendmail` called from a Command or the Scheduler computed its value twice, which stripped the quotes from literal text and then failed on characters like `!`. Also shunting yard treated a `?` inside a quoted string as a ternary, so text like `"too hot?"` returned nothing
|
||||
- memory leak when using different timezones for ems-esp and thermostat[#3184](https://github.com/emsesp/EMS-ESP32/issues/3184)
|
||||
- LED stayed off on a healthy system when "Disable LED" was unchecked
|
||||
- Ethernet MAC address changed with the new SDK, breaking DHCP reservations (currently disabled)
|
||||
- "IPv4 nameserver" showed an IPv6 address when IPv6 was in use
|
||||
- HA Discovery warning on Uptime after EMS-ESP boot due to NTP not ready
|
||||
- hc/control setting for UI800 thermostats [#3181](https://github.com/emsesp/EMS-ESP32/discussions/3181)
|
||||
|
||||
## Changed
|
||||
|
||||
@@ -24,4 +32,10 @@ This release is based on the latest Espressif/Arduino core version 3. It brings
|
||||
- network fallback to AP only after start [#3090](https://github.com/emsesp/EMS-ESP32/issues/3090)
|
||||
- replaced Web async-validator with custom validator and toast with native snackbar to reduce bundle size
|
||||
- Gateway and Connect devices are shown in the Devices page, but disabled [3126](https://github.com/emsesp/EMS-ESP32/discussions/3126)
|
||||
- show control setting only for master thermostats (0x10) [#3173](https://github.com/emsesp/EMS-ESP32/issues/3173)
|
||||
- remove devices without entities not listed in 0x07 telegram
|
||||
- changes to the Dialogs in Dashboard page, removed the run icon in the Commands page. Renamed Execute to Run.
|
||||
- set `None` for undefined values in HA `val_tpl`
|
||||
- changes in WebUI - remove multiple calls systemInfo endpoint, Vewrsion pages checks for internet connection [#3195](https://github.com/emsesp/EMS-ESP32/pull/3195)
|
||||
- multiple optimization in network, power down WiFi when Ethernet and WiFi nosleep is instant when selected [#3213](https://github.com/emsesp/EMS-ESP32/pull/3213)
|
||||
|
||||
|
||||
+2
-1
@@ -16,7 +16,8 @@
|
||||
"enableMDNS": true,
|
||||
"enableCORS": false,
|
||||
"CORSOrigin": "*",
|
||||
"tx_power": 0
|
||||
"tx_power": 0,
|
||||
"eth_10mbit": false
|
||||
},
|
||||
"AP": {
|
||||
"provision_mode": 2,
|
||||
|
||||
+521
-481
File diff suppressed because it is too large
Load Diff
+494
-481
File diff suppressed because it is too large
Load Diff
+14
-14
@@ -23,40 +23,40 @@
|
||||
"standalone-devcontainer": "concurrently -c \"auto\" \"typesafe-i18n\" \"pnpm:mock-rest\" \"vite --host\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@alova/adapter-xhr": "2.3.1",
|
||||
"@alova/adapter-xhr": "2.3.3",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@mui/icons-material": "^9.2.0",
|
||||
"@mui/material": "^9.2.0",
|
||||
"@mui/icons-material": "^9.4.0",
|
||||
"@mui/material": "^9.4.0",
|
||||
"@table-library/react-table-library": "4.1.15",
|
||||
"alova": "^3.5.3",
|
||||
"alova": "^3.5.5",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"preact": "^10.29.8",
|
||||
"react": "^19.2.8",
|
||||
"react-dom": "^19.2.8",
|
||||
"react-icons": "^5.7.0",
|
||||
"react-router": "^8.3.0",
|
||||
"react-router": "^8.3.1",
|
||||
"typesafe-i18n": "^5.27.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@preact/preset-vite": "^2.10.6",
|
||||
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
||||
"@types/node": "^26.1.2",
|
||||
"@types/node": "^26.4.1",
|
||||
"@types/react": "^19.2.18",
|
||||
"@types/react-dom": "^19.2.4",
|
||||
"concurrently": "^10.0.4",
|
||||
"eslint": "^10.8.0",
|
||||
"@types/react-dom": "^19.2.5",
|
||||
"concurrently": "^10.0.5",
|
||||
"eslint": "^10.9.1",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"etag": "^1.8.1",
|
||||
"mime-types": "^3.0.2",
|
||||
"prettier": "^3.9.6",
|
||||
"rollup-plugin-visualizer": "^7.0.1",
|
||||
"terser": "^5.49.0",
|
||||
"rollup-plugin-visualizer": "^7.1.1",
|
||||
"terser": "^5.51.2",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.65.0",
|
||||
"vite": "^8.2.0",
|
||||
"typescript-eslint": "^8.69.0",
|
||||
"vite": "^8.2.2",
|
||||
"vite-plugin-imagemin": "^0.6.1"
|
||||
},
|
||||
"packageManager": "pnpm@11.18.0+sha512.33d83c77da82f49fba836925c6f1b841181ec3132b670639bd012f7075f5c7cf634c5f870147c19aae7478fac01df09d8892e880454896edd23ee9b33757563c"
|
||||
"packageManager": "pnpm@11.25.0+sha512.5cde925b4f075f725eb71fbae18a42ffe784524789f19b61c731cb8721ec28aaee160e01a8d5af4fedb2a42cdbf300efe23db356b0d4a17b4d63e11f8ab7c956"
|
||||
}
|
||||
|
||||
Generated
+350
-438
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 2.4 KiB |
@@ -1,4 +1,4 @@
|
||||
import { memo, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { memo, useContext, useEffect, useState } from 'react';
|
||||
|
||||
import ForwardIcon from '@mui/icons-material/Forward';
|
||||
import { Box, Button, Paper, Typography } from '@mui/material';
|
||||
@@ -43,17 +43,13 @@ const SignIn = memo(() => {
|
||||
}
|
||||
});
|
||||
|
||||
const updateLoginRequestValue = useMemo(
|
||||
() =>
|
||||
updateValue((updater) =>
|
||||
setSignInRequest(
|
||||
updater as unknown as (prevState: SignInRequest) => SignInRequest
|
||||
)
|
||||
),
|
||||
[]
|
||||
const updateLoginRequestValue = updateValue((updater) =>
|
||||
setSignInRequest(
|
||||
updater as unknown as (prevState: SignInRequest) => SignInRequest
|
||||
)
|
||||
);
|
||||
|
||||
const signIn = useCallback(async () => {
|
||||
const signIn = async () => {
|
||||
await callSignIn(signInRequest).catch((event: Error) => {
|
||||
if (event.message === 'Unauthorized') {
|
||||
toast.warning(LL.INVALID_LOGIN());
|
||||
@@ -62,7 +58,7 @@ const SignIn = memo(() => {
|
||||
}
|
||||
setProcessing(false);
|
||||
});
|
||||
}, [callSignIn, signInRequest, LL]);
|
||||
};
|
||||
|
||||
const validateAndSignIn = async () => {
|
||||
setProcessing(true);
|
||||
@@ -78,7 +74,7 @@ const SignIn = memo(() => {
|
||||
}
|
||||
};
|
||||
|
||||
const submitOnEnter = useMemo(() => onEnterCallback(signIn), [signIn]);
|
||||
const submitOnEnter = onEnterCallback(signIn);
|
||||
|
||||
// get rid of scrollbar
|
||||
useEffect(() => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { LogSettings, SystemStatus } from 'types';
|
||||
|
||||
import { alovaInstance } from './endpoints';
|
||||
|
||||
// systemStatus - also used to ping in System Monitor for pinging
|
||||
// systemStatus - also used in System Monitor to ping the ESP
|
||||
export const readSystemStatus = () =>
|
||||
alovaInstance.Get<SystemStatus>('/rest/systemStatus');
|
||||
|
||||
|
||||
@@ -3,11 +3,9 @@ import { useBlocker } from 'react-router';
|
||||
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import { Box, Button, IconButton, Typography } from '@mui/material';
|
||||
import { Box, Button, Typography } from '@mui/material';
|
||||
|
||||
import { callAction } from '@/api/app';
|
||||
import {
|
||||
Body,
|
||||
Cell,
|
||||
@@ -48,7 +46,7 @@ const DEFAULT_COMMAND_ITEM: Omit<CommandItem, 'id'> = {
|
||||
|
||||
const commandsTheme = {
|
||||
Table: `
|
||||
--data-table-library_grid-template-columns: repeat(1, minmax(100px, 1fr)) repeat(1, minmax(100px, 1fr)) 160px 32px;
|
||||
--data-table-library_grid-template-columns: repeat(1, minmax(100px, 1fr)) repeat(1, minmax(100px, 1fr)) 160px;
|
||||
`,
|
||||
BaseRow: `
|
||||
font-size: 14px;
|
||||
@@ -143,21 +141,6 @@ const Commands = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const { send: executeCommand } = useRequest(
|
||||
(id: string) => callAction({ action: 'executeCommand', param: id }),
|
||||
{ immediate: false }
|
||||
)
|
||||
.onSuccess(() => {
|
||||
toast.success(LL.EXECUTE_COMMAND_SENT());
|
||||
})
|
||||
.onError((error) => {
|
||||
toast.error(String(error.error?.message || 'An error occurred'));
|
||||
});
|
||||
|
||||
const execute = async (name: string) => {
|
||||
await executeCommand(name);
|
||||
};
|
||||
|
||||
const editItem = (ci: CommandItem) => {
|
||||
setCreating(false);
|
||||
setSelectedItem(ci);
|
||||
@@ -222,7 +205,6 @@ const Commands = () => {
|
||||
<HeaderCell stiff>{LL.COMMAND(0)}</HeaderCell>
|
||||
<HeaderCell stiff>{LL.VALUE(0)}</HeaderCell>
|
||||
<HeaderCell stiff>{LL.NAME(0)}</HeaderCell>
|
||||
<HeaderCell></HeaderCell>
|
||||
</HeaderRow>
|
||||
</Header>
|
||||
<Body>
|
||||
@@ -231,16 +213,6 @@ const Commands = () => {
|
||||
<Cell>{ci.cmd}</Cell>
|
||||
<Cell>{ci.value}</Cell>
|
||||
<Cell>{ci.name}</Cell>
|
||||
<Cell>
|
||||
{numChanges === 0 && (
|
||||
<IconButton
|
||||
onClick={() => execute(ci.name)}
|
||||
style={{ backgroundColor: 'transparent' }}
|
||||
>
|
||||
<PlayArrowIcon color="primary" sx={{ fontSize: 16 }} />
|
||||
</IconButton>
|
||||
)}
|
||||
</Cell>
|
||||
</Row>
|
||||
))}
|
||||
</Body>
|
||||
|
||||
@@ -95,7 +95,7 @@ const CommandsDialog = ({
|
||||
{ immediate: false }
|
||||
)
|
||||
.onSuccess(() => {
|
||||
toast.success(LL.EXECUTE_COMMAND_SENT());
|
||||
toast.success(LL.COMMAND_EXECUTED());
|
||||
})
|
||||
.onError((error) => {
|
||||
toast.error(String(error.error?.message || 'An error occurred'));
|
||||
@@ -121,8 +121,8 @@ const CommandsDialog = ({
|
||||
return (
|
||||
<Dialog sx={dialogStyle} open={open} onClose={handleClose}>
|
||||
<DialogTitle>
|
||||
{creating ? `${LL.ADD(1)} ${LL.NEW(0)}` : LL.EDIT()}
|
||||
{LL.COMMAND(1)}
|
||||
{creating ? `${LL.ADD(1)} ` : LL.UPDATE() + '/' + LL.RUN()}
|
||||
{LL.COMMAND(0)}
|
||||
</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<ValidatedTextField
|
||||
@@ -195,7 +195,7 @@ const CommandsDialog = ({
|
||||
onClick={execute}
|
||||
color="success"
|
||||
>
|
||||
{LL.EXECUTE()}
|
||||
{LL.RUN()}
|
||||
</Button>
|
||||
)}
|
||||
</DialogActions>
|
||||
|
||||
@@ -334,7 +334,7 @@ const Dashboard = memo(() => {
|
||||
(di.dv.v === '' || di.dv.v === undefined ? (
|
||||
<IconButton
|
||||
size="small"
|
||||
aria-label={LL.RUN_COMMAND()}
|
||||
aria-label={LL.RUN()}
|
||||
onClick={() => editDashboardValue(di)}
|
||||
style={{ backgroundColor: 'transparent' }}
|
||||
>
|
||||
@@ -402,7 +402,10 @@ const Dashboard = memo(() => {
|
||||
{selectedDashboardItem && selectedDashboardItem.dv && (
|
||||
<DevicesDialog
|
||||
open={deviceValueDialogOpen}
|
||||
onClose={() => setDeviceValueDialogOpen(false)}
|
||||
onClose={() => {
|
||||
setDeviceValueDialogOpen(false);
|
||||
setSelectedDashboardItem(undefined);
|
||||
}}
|
||||
onSave={deviceValueDialogSave}
|
||||
selectedItem={selectedDashboardItem.dv}
|
||||
writeable={true}
|
||||
|
||||
@@ -592,6 +592,7 @@ const Devices = memo(() => {
|
||||
|
||||
const deviceValueDialogClose = () => {
|
||||
setDeviceValueDialogOpen(false);
|
||||
setSelectedDeviceValue(undefined);
|
||||
if (selectedDevice !== undefined) {
|
||||
void sendDeviceData(selectedDevice);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ import { ValidationError, validate } from 'validators';
|
||||
import type Schema from 'validators/schema';
|
||||
import type { ValidateFieldsError } from 'validators/schema';
|
||||
|
||||
import { DeviceValueUOM, DeviceValueUOM_s } from './types';
|
||||
import { DeviceValueUOM, DeviceValueUOM_s, getDeviceValueKind } from './types';
|
||||
import type { DeviceValue } from './types';
|
||||
|
||||
interface DevicesDialogProps {
|
||||
@@ -70,7 +70,7 @@ const DevicesDialog = ({
|
||||
{ immediate: false }
|
||||
)
|
||||
.onSuccess(() => {
|
||||
toast.success(LL.EXECUTE_COMMAND_SENT());
|
||||
toast.success(LL.COMMAND_EXECUTED());
|
||||
})
|
||||
.onError((error) => {
|
||||
toast.error(String(error.error?.message || 'An error occurred'));
|
||||
@@ -119,18 +119,20 @@ const DevicesDialog = ({
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const isCommand =
|
||||
(selectedItem.v === '' || selectedItem.v === undefined) &&
|
||||
Boolean(selectedItem.c);
|
||||
const isSchedulerImmediate = selectedItem.v === undefined;
|
||||
const dialogTitle = isCommand
|
||||
? isSchedulerImmediate
|
||||
? LL.EXECUTE() + ' ' + LL.SCHEDULE(0)
|
||||
: LL.RUN_COMMAND()
|
||||
: writeable
|
||||
? LL.CHANGE_VALUE()
|
||||
: LL.VALUE(0);
|
||||
const buttonLabel = isCommand ? LL.EXECUTE() : LL.UPDATE();
|
||||
const kind = getDeviceValueKind(selectedItem);
|
||||
|
||||
const dialogTitle =
|
||||
kind === 'command' || kind === 'deviceCommand'
|
||||
? LL.RUN() + ' ' + LL.COMMAND(0)
|
||||
: kind === 'scheduler'
|
||||
? LL.UPDATE() + ' ' + LL.SCHEDULE(0)
|
||||
: writeable
|
||||
? LL.CHANGE_VALUE()
|
||||
: LL.VALUE(0);
|
||||
|
||||
const buttonLabel =
|
||||
kind === 'command' || kind === 'deviceCommand' ? LL.RUN() : LL.UPDATE();
|
||||
|
||||
const helperText = showHelperText(editItem);
|
||||
|
||||
const valueLabel = LL.VALUE(0);
|
||||
@@ -142,7 +144,7 @@ const DevicesDialog = ({
|
||||
<Typography sx={{ mb: 2 }} color="warning" variant="body2">
|
||||
{editItem.id.slice(2)}
|
||||
</Typography>
|
||||
{!isSchedulerImmediate && (
|
||||
{kind !== 'command' && (
|
||||
<Grid container>
|
||||
<Grid size={12}>
|
||||
{editItem.l ? (
|
||||
@@ -227,11 +229,15 @@ const DevicesDialog = ({
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={
|
||||
isCommand ? <PlayArrowIcon /> : <WarningIcon color="warning" />
|
||||
kind === 'command' ? (
|
||||
<PlayArrowIcon />
|
||||
) : kind === 'deviceEntity' || kind === 'deviceCommand' ? (
|
||||
<WarningIcon color="warning" />
|
||||
) : undefined
|
||||
}
|
||||
variant="outlined"
|
||||
onClick={doAction}
|
||||
color={isCommand ? 'success' : 'primary'}
|
||||
color={kind !== 'value' ? 'success' : 'primary'}
|
||||
>
|
||||
{buttonLabel}
|
||||
</Button>
|
||||
|
||||
@@ -210,6 +210,18 @@ export enum DeviceValueUOM {
|
||||
HERTZ
|
||||
}
|
||||
|
||||
export type DeviceValueKind =
|
||||
'value' | 'scheduler' | 'command' | 'deviceCommand' | 'deviceEntity';
|
||||
|
||||
export function getDeviceValueKind(item: DeviceValue): DeviceValueKind {
|
||||
if (!item.c) return 'value';
|
||||
if (item.v !== undefined && item.l !== undefined && item.u === undefined)
|
||||
return 'scheduler';
|
||||
if (item.v === undefined && item.l === undefined) return 'command';
|
||||
if (item.u === DeviceValueUOM.NONE) return 'deviceCommand';
|
||||
return 'deviceEntity';
|
||||
}
|
||||
|
||||
export const DeviceValueUOM_s = [
|
||||
'',
|
||||
'°C',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
useLayoutTitle
|
||||
} from 'components';
|
||||
import { toast } from 'components/toast';
|
||||
import { AuthenticatedContext } from 'contexts/authentication';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { numberValue, updateValueDirty, useRest } from 'utils';
|
||||
import { ValidationError, validate } from 'validators';
|
||||
@@ -68,6 +69,14 @@ const ApplicationSettings = () => {
|
||||
update: writeSettings
|
||||
});
|
||||
|
||||
// the menu drawer reads the system name from the authentication context
|
||||
const { setSystemName } = useContext(AuthenticatedContext);
|
||||
useEffect(() => {
|
||||
if (origData?.system_name !== undefined) {
|
||||
setSystemName(origData.system_name);
|
||||
}
|
||||
}, [origData?.system_name, setSystemName]);
|
||||
|
||||
const [restarting, setRestarting] = useState<boolean>();
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
@@ -168,7 +177,7 @@ const ApplicationSettings = () => {
|
||||
await sendAPI({
|
||||
device: 'system',
|
||||
cmd: 'sendmail',
|
||||
data: 'Email notification test successful!',
|
||||
data: '"Email notification test successful!"',
|
||||
id: 0
|
||||
})
|
||||
.then(() => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { memo, useContext, useMemo, useState } from 'react';
|
||||
import { memo, useContext, useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
@@ -412,7 +412,7 @@ const getPlatform = (data: VersionData): string => {
|
||||
|
||||
const Version = () => {
|
||||
const { LL, locale } = useI18nContext();
|
||||
const { me, versions } = useContext(AuthenticatedContext);
|
||||
const { me, versions, refreshVersions } = useContext(AuthenticatedContext);
|
||||
|
||||
const [restarting, setRestarting] = useState<boolean>(false);
|
||||
const [confirmFactoryReset, setConfirmFactoryReset] = useState<boolean>(false);
|
||||
@@ -430,25 +430,27 @@ const Version = () => {
|
||||
const [showVersionInfo, setShowVersionInfo] = useState<number>(0); // 1 = stable, 2 = dev, 3 = partition
|
||||
const [firmwareSize, setFirmwareSize] = useState<number>(0);
|
||||
|
||||
const latestVersion = useMemo<VersionInfo | undefined>(
|
||||
() =>
|
||||
versions?.stable
|
||||
? { version: versions.stable.version, date: versions.stable.date }
|
||||
: undefined,
|
||||
[versions?.stable]
|
||||
);
|
||||
const latestDevVersion = useMemo<VersionInfo | undefined>(
|
||||
() =>
|
||||
versions?.dev
|
||||
? { version: versions.dev.version, date: versions.dev.date }
|
||||
: undefined,
|
||||
[versions?.dev]
|
||||
);
|
||||
const latestVersion: VersionInfo | undefined = versions?.stable
|
||||
? { version: versions.stable.version, date: versions.stable.date }
|
||||
: undefined;
|
||||
const latestDevVersion: VersionInfo | undefined = versions?.dev
|
||||
? { version: versions.dev.version, date: versions.dev.date }
|
||||
: undefined;
|
||||
const usingDevVersion = versions?.current?.type === 'dev';
|
||||
const stableUpgradeAvailable = versions?.stable?.upgradeable ?? false;
|
||||
const devUpgradeAvailable = versions?.dev?.upgradeable ?? false;
|
||||
const internetLive = Boolean(versions?.stable || versions?.dev);
|
||||
|
||||
useEffect(() => {
|
||||
if (internetLive) {
|
||||
return;
|
||||
}
|
||||
const interval = setInterval(() => {
|
||||
void refreshVersions();
|
||||
}, 3000);
|
||||
return () => clearInterval(interval);
|
||||
}, [internetLive, refreshVersions]);
|
||||
|
||||
const { send: sendSetPartition } = useRequest(
|
||||
(partition: string) => callAction({ action: 'setPartition', param: partition }),
|
||||
{ immediate: false }
|
||||
@@ -805,8 +807,7 @@ const Version = () => {
|
||||
</>
|
||||
) : (
|
||||
<Typography sx={{ mt: 2 }} color="warning">
|
||||
<WarningIcon color="warning" sx={{ verticalAlign: 'middle', mr: 2 }} />
|
||||
{LL.INTERNET_CONNECTION_REQUIRED()}
|
||||
{LL.INTERNET_CONNECTION_CHECK()}…
|
||||
</Typography>
|
||||
)}
|
||||
{me.admin && (
|
||||
@@ -899,7 +900,7 @@ const Version = () => {
|
||||
startIcon={<PowerSettingsNewIcon />}
|
||||
variant="outlined"
|
||||
onClick={doRestart}
|
||||
color="error"
|
||||
color="success"
|
||||
>
|
||||
{LL.RESTART()}
|
||||
</Button>
|
||||
@@ -910,20 +911,13 @@ const Version = () => {
|
||||
sx={{
|
||||
mt: 2,
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
flexWrap: 'nowrap',
|
||||
whiteSpace: 'nowrap',
|
||||
gap: 1
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
startIcon={<PowerSettingsNewIcon />}
|
||||
variant="outlined"
|
||||
onClick={handleRestartClick}
|
||||
color="error"
|
||||
>
|
||||
{LL.RESTART()}
|
||||
</Button>
|
||||
{!data.disable_reset && (
|
||||
<Button
|
||||
startIcon={<SettingsBackupRestoreIcon />}
|
||||
@@ -934,6 +928,14 @@ const Version = () => {
|
||||
{LL.FACTORY_RESET()}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
startIcon={<PowerSettingsNewIcon />}
|
||||
variant="outlined"
|
||||
onClick={handleRestartClick}
|
||||
color="success"
|
||||
>
|
||||
{LL.RESTART()}
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
|
||||
import * as NetworkApi from 'api/network';
|
||||
import { API } from 'api/app';
|
||||
import { readSystemStatus } from 'api/system';
|
||||
|
||||
import { updateState, useRequest } from 'alova/client';
|
||||
import type { APIcall } from 'app/main/types';
|
||||
@@ -50,6 +51,8 @@ import { isNetworkOpen, networkSecurityMode } from './WiFiNetworkSelector';
|
||||
const NetworkSettings = () => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const { data: hardwareData } = useRequest(readSystemStatus);
|
||||
|
||||
const { selectedNetwork, deselectNetwork } = useContext(WiFiConnectionContext);
|
||||
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
@@ -92,7 +95,8 @@ const NetworkSettings = () => {
|
||||
nosleep: true,
|
||||
enableMDNS: true,
|
||||
enableCORS: false,
|
||||
CORSOrigin: '*'
|
||||
CORSOrigin: '*',
|
||||
eth_10mbit: current_data ? current_data.eth_10mbit : false
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -136,7 +140,7 @@ const NetworkSettings = () => {
|
||||
};
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
if (!data || !hardwareData) {
|
||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage || ''} />;
|
||||
}
|
||||
|
||||
@@ -246,6 +250,23 @@ const NetworkSettings = () => {
|
||||
}
|
||||
label={LL.NETWORK_LOW_BAND()}
|
||||
/>
|
||||
{hardwareData.esp_platform === 'ESP32' && (
|
||||
<>
|
||||
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
||||
Ethernet
|
||||
</Typography>
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
name="eth_10mbit"
|
||||
checked={data.eth_10mbit}
|
||||
onChange={updateFormValue}
|
||||
/>
|
||||
}
|
||||
label="Force 10Mbit half-duplex"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
||||
{LL.GENERAL_OPTIONS()}
|
||||
</Typography>
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { memo } from 'react';
|
||||
import { memo, useContext } from 'react';
|
||||
|
||||
import { Box, Divider, Drawer, Toolbar, Typography, styled } from '@mui/material';
|
||||
|
||||
import { readSettings } from 'api/app';
|
||||
|
||||
import { useRequest } from 'alova/client';
|
||||
import { AuthenticatedContext } from 'contexts/authentication';
|
||||
import { PROJECT_NAME } from 'env';
|
||||
|
||||
import { DRAWER_WIDTH } from './Layout';
|
||||
@@ -27,8 +25,7 @@ interface LayoutDrawerProps {
|
||||
}
|
||||
|
||||
const LayoutDrawerComponent = ({ mobileOpen, onClose }: LayoutDrawerProps) => {
|
||||
const { data: settings } = useRequest(readSettings);
|
||||
const system_name = settings?.system_name;
|
||||
const { systemName } = useContext(AuthenticatedContext);
|
||||
|
||||
const drawer = (
|
||||
<>
|
||||
@@ -43,9 +40,9 @@ const LayoutDrawerComponent = ({ mobileOpen, onClose }: LayoutDrawerProps) => {
|
||||
}}
|
||||
>
|
||||
<Typography>{PROJECT_NAME}</Typography>
|
||||
{system_name && (
|
||||
{systemName && (
|
||||
<Typography color="secondary" variant="body2">
|
||||
{system_name}
|
||||
{systemName}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
@@ -23,6 +23,7 @@ const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
|
||||
const [initialized, setInitialized] = useState<boolean>(false);
|
||||
const [me, setMe] = useState<Me>();
|
||||
const [versions, setVersions] = useState<VersionsResponse>();
|
||||
const [systemName, setSystemName] = useState<string>();
|
||||
|
||||
const { send: sendVerifyAuthorization } = useRequest(verifyAuthorization(), {
|
||||
immediate: false
|
||||
@@ -33,7 +34,9 @@ const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
|
||||
{ immediate: false }
|
||||
)
|
||||
.onSuccess((event) => {
|
||||
setVersions(event.data as VersionsResponse);
|
||||
const response = event.data as VersionsResponse;
|
||||
setVersions(response);
|
||||
setSystemName(response.system_name);
|
||||
})
|
||||
.onError(() => {
|
||||
setVersions(undefined);
|
||||
@@ -60,6 +63,7 @@ const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
|
||||
AuthenticationApi.clearAccessToken();
|
||||
setMe(undefined);
|
||||
setVersions(undefined);
|
||||
setSystemName(undefined);
|
||||
if (doRedirect) {
|
||||
void navigate('/', { replace: true });
|
||||
}
|
||||
@@ -88,21 +92,20 @@ const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
|
||||
void refresh();
|
||||
}, [refresh]);
|
||||
|
||||
const obj = useMemo(
|
||||
() => ({
|
||||
signIn,
|
||||
signOut,
|
||||
refresh,
|
||||
refreshVersions,
|
||||
...(me && { me }),
|
||||
...(versions && { versions })
|
||||
}),
|
||||
[signIn, signOut, me, refresh, refreshVersions, versions]
|
||||
);
|
||||
|
||||
if (initialized) {
|
||||
return (
|
||||
<AuthenticationContext.Provider value={obj}>
|
||||
<AuthenticationContext.Provider
|
||||
value={{
|
||||
signIn,
|
||||
signOut,
|
||||
refresh,
|
||||
refreshVersions,
|
||||
setSystemName,
|
||||
...(me && { me }),
|
||||
...(versions && { versions }),
|
||||
...(systemName !== undefined && { systemName })
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AuthenticationContext.Provider>
|
||||
);
|
||||
|
||||
@@ -9,6 +9,8 @@ export interface AuthenticationContextValue {
|
||||
me?: Me;
|
||||
versions?: VersionsResponse;
|
||||
refreshVersions: () => Promise<void>;
|
||||
systemName?: string;
|
||||
setSystemName: (systemName: string) => void;
|
||||
}
|
||||
|
||||
const AuthenticationContextDefaultValue = {} as AuthenticationContextValue;
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { Translation } from '../i18n-types';
|
||||
const cs: Translation = {
|
||||
LANGUAGE: 'Jazyk',
|
||||
RETRY: 'Zkusit znovu',
|
||||
LOADING: 'Načítání',
|
||||
IS_REQUIRED: '{0} je vyžadováno',
|
||||
SIGN_IN: 'Přihlásit se',
|
||||
SIGN_OUT: 'Odhlásit se',
|
||||
@@ -37,13 +36,11 @@ const cs: Translation = {
|
||||
VALUE: '{{value|Value}}',
|
||||
DEVICES: 'Zařízení',
|
||||
SENSORS: 'Senzory',
|
||||
RUN_COMMAND: 'Zavolat příkaz',
|
||||
CHANGE_VALUE: 'Změnit hodnotu',
|
||||
CANCEL: 'Zrušit',
|
||||
REMOVE_ALL: 'Odebrat vše',
|
||||
APPLY_CHANGES: 'Použít změny ({0})',
|
||||
UPDATE: 'Aktualizovat',
|
||||
EXECUTE: 'Provést',
|
||||
REMOVE: 'Odebrat',
|
||||
PROBLEM_UPDATING: 'Problém s aktualizací',
|
||||
PROBLEM_LOADING: 'Problém s načítáním',
|
||||
@@ -52,8 +49,6 @@ const cs: Translation = {
|
||||
SETTINGS: 'Nastavení',
|
||||
UPDATED_OF: '{0} Aktualizováno',
|
||||
UPDATE_OF: '{0} Aktualizace',
|
||||
REMOVED_OF: '{0} Odebráno',
|
||||
DELETION_OF: '{0} Smazání',
|
||||
OFFSET: 'Offset',
|
||||
FACTOR: 'Faktor',
|
||||
FREQ: 'Frekvence',
|
||||
@@ -66,17 +61,14 @@ const cs: Translation = {
|
||||
TEMP_SENSORS: 'Teplotní senzory',
|
||||
WRITE_CMD_SENT: 'Zapisovací příkaz odeslán',
|
||||
EMS_BUS_WARNING: 'EMS sběrnice je odpojena. Pokud toto varování přetrvává i po několika sekundách, zkontrolujte nastavení a profil desky',
|
||||
EMS_BUS_SCANNING: 'Prohledávání zařízení na EMS sběrnici...',
|
||||
CONNECTED: 'Připojeno',
|
||||
TX_ISSUES: 'Problémy s Tx - zkontrolujte režim Tx',
|
||||
DISCONNECTED: 'Odpojeno',
|
||||
EMS_SCAN: 'Opravdu chcete zahájit úplné skenování zařízení na EMS sběrnici?',
|
||||
DATA_TRAFFIC: 'Datový provoz',
|
||||
EMS_DEVICE: 'EMS zařízení',
|
||||
SUCCESS: 'ÚSPĚCH',
|
||||
FAIL: 'SELHÁNÍ',
|
||||
QUALITY: 'KVALITA',
|
||||
SCAN: 'Skenovat',
|
||||
STATUS_NAMES: [
|
||||
'Přijaté EMS telegramy (Rx)',
|
||||
'Čtení EMS (Tx)',
|
||||
@@ -127,7 +119,6 @@ const cs: Translation = {
|
||||
ENUM_FORMAT: 'Formát enum v API/MQTT',
|
||||
INDEX: 'Index',
|
||||
ENABLE_PARASITE: 'Povolit 1-Wire parazitní napájení',
|
||||
LOGGING: 'Protokolování',
|
||||
LOG_HEX: 'Protokolovat EMS telegramy v hexadecimálním formátu',
|
||||
ENABLE_SYSLOG: 'Povolit Syslog',
|
||||
LOG_LEVEL: 'Úroveň protokolování',
|
||||
@@ -172,7 +163,6 @@ const cs: Translation = {
|
||||
CLOSE: 'Zavřít',
|
||||
USE: 'Použít',
|
||||
FACTORY_RESET: 'Obnovení továrního nastavení',
|
||||
SYSTEM_FACTORY_TEXT: 'Zařízení bylo obnoveno do továrního nastavení a nyní se restartuje',
|
||||
SYSTEM_FACTORY_TEXT_DIALOG: 'Opravdu chcete resetovat zařízení EMS-ESP do továrního nastavení?',
|
||||
AVAILABLE_VERSION: 'Nejnovější dostupné verze',
|
||||
STABLE: 'Stabilní',
|
||||
@@ -257,9 +247,7 @@ const cs: Translation = {
|
||||
NETWORK_SCAN: 'Skenovat WiFi sítě',
|
||||
IDLE: 'Nečinný',
|
||||
LOST: 'Ztraceno',
|
||||
SCANNING: 'Skenování',
|
||||
SCAN_AGAIN: 'Skenovat znovu',
|
||||
NETWORK_SCANNER: 'Síťový skener',
|
||||
NETWORK_NO_WIFI: 'Nenalezeny žádné WiFi sítě',
|
||||
NETWORK_BLANK_BSSID: 'ponechte prázdné pokud použijete jen SSID',
|
||||
TX_POWER: 'Vysílací výkon',
|
||||
@@ -294,9 +282,6 @@ const cs: Translation = {
|
||||
ONCHANGE: 'Při změně',
|
||||
CONDITION: 'Podmínka',
|
||||
SCHEDULE_UPDATED: 'Harmonogram aktualizován',
|
||||
SCHEDULE_TIMER_1: 'při startu',
|
||||
SCHEDULE_TIMER_2: 'každou minutu',
|
||||
SCHEDULE_TIMER_3: 'každou hodinu',
|
||||
CUSTOM_ENTITIES: 'Vlastní entity',
|
||||
ENTITIES_HELP_1: 'Definujte vlastní EMS entity nebo dynamické uživatelské proměnné',
|
||||
ENTITIES_UPDATED: 'Entity aktualizovány',
|
||||
@@ -312,9 +297,7 @@ const cs: Translation = {
|
||||
ACTIVELOW: 'Aktivní nízká',
|
||||
UNCHANGED: 'Beze změny',
|
||||
ALWAYS: 'Vždy',
|
||||
ACTIVITY: 'Aktivita',
|
||||
CONFIGURE: '{0} Konfigurovat',
|
||||
SYSTEM_MEMORY: 'Paměť systému',
|
||||
APPLICATION_SETTINGS_1: 'Upravit nastavení aplikace EMS-ESP',
|
||||
SECURITY_1: 'Přidat nebo odebrat uživatele',
|
||||
DOWNLOAD_UPLOAD_1: 'Stáhnout a nahrát nastavení a firmware',
|
||||
@@ -326,7 +309,6 @@ const cs: Translation = {
|
||||
RENAME: 'Přejmenovat',
|
||||
ENABLE_MODBUS: 'Povolit Modbus',
|
||||
VIEW_LOG: 'Zobrazit záznam pro diagnostiku problémů',
|
||||
UPLOAD_DRAG: 'přetáhněte soubor sem nebo klikněte pro výběr',
|
||||
SERVICES: 'Služby',
|
||||
ALLVALUES: 'Všechny hodnoty',
|
||||
SPECIAL_FUNCTIONS: 'Speciální funkce',
|
||||
@@ -352,10 +334,8 @@ const cs: Translation = {
|
||||
THIS_VERSION: 'Tato verze',
|
||||
PLATFORM: 'Platforma',
|
||||
RELEASE_TYPE: 'Typ sestavení',
|
||||
INTERNET_CONNECTION_REQUIRED: 'Pro automatickou kontrolu a instalaci aktualizací je třeba internetové připojení',
|
||||
SWITCH_RELEASE_TYPE: 'Přepnout na {0} verzi',
|
||||
FIRMWARE_VERSION_INFO: 'Informace o verzi firmwaru',
|
||||
NO_DATA: 'žádné údaje',
|
||||
USER_PROFILE: 'Uživatelský profil',
|
||||
STORED_VERSIONS: 'Uložené verze',
|
||||
ONLINE_HELP: 'online nápověda',
|
||||
@@ -366,9 +346,10 @@ const cs: Translation = {
|
||||
TEST_EMAIL_SUCCESSFUL: 'Test email byl úspěšně odeslán',
|
||||
SYSTEM_NAME: 'Název systému',
|
||||
COMMANDS: 'Příkazy',
|
||||
COMMANDS_UPDATED: 'Příkazy byly aktualizovány',
|
||||
COMMANDS_HELP_1: 'Definujte vlastní příkazy pro magistrali EMS',
|
||||
EXECUTE_COMMAND_SENT: 'Příkaz odeslán',
|
||||
COMMAND_EXECUTED: 'Příkaz úspěšně spuštěn',
|
||||
RUN: 'Spustit příkaz',
|
||||
INTERNET_CONNECTION_CHECK: 'Kontrola online pro dostupné verze'
|
||||
};
|
||||
|
||||
export default cs;
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { Translation } from '../i18n-types';
|
||||
const de: Translation = {
|
||||
LANGUAGE: 'Sprache',
|
||||
RETRY: 'Neuer Versuch',
|
||||
LOADING: 'Laden',
|
||||
IS_REQUIRED: '{0} ist erforderlich',
|
||||
SIGN_IN: 'Einloggen',
|
||||
SIGN_OUT: 'Ausloggen',
|
||||
@@ -37,13 +36,11 @@ const de: Translation = {
|
||||
VALUE: '{{wert|Wert}}',
|
||||
DEVICES: 'Geräte',
|
||||
SENSORS: 'Sensoren',
|
||||
RUN_COMMAND: 'Befehl ausführen',
|
||||
CHANGE_VALUE: 'Wert ändern',
|
||||
CANCEL: 'Abbrechen',
|
||||
REMOVE_ALL: 'Alles entfernen',
|
||||
APPLY_CHANGES: 'Änderungen anwenden ({0})',
|
||||
UPDATE: 'Aktualisieren',
|
||||
EXECUTE: 'Ausführen',
|
||||
REMOVE: 'Entfernen',
|
||||
PROBLEM_UPDATING: 'Problem beim Aktualisieren',
|
||||
PROBLEM_LOADING: 'Problem beim Laden',
|
||||
@@ -52,8 +49,6 @@ const de: Translation = {
|
||||
SETTINGS: 'Einstellungen',
|
||||
UPDATED_OF: '{0} Aktualisiert',
|
||||
UPDATE_OF: '{0} Aktualisieren',
|
||||
REMOVED_OF: '{0} Entfernt',
|
||||
DELETION_OF: '{0} Löschung',
|
||||
OFFSET: 'Position',
|
||||
FACTOR: 'Faktor',
|
||||
FREQ: 'Frequenz',
|
||||
@@ -66,17 +61,14 @@ const de: Translation = {
|
||||
TEMP_SENSORS: 'Temperatursensoren',
|
||||
WRITE_CMD_SENT: 'Befehl schreiben wurde gesendet',
|
||||
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?',
|
||||
DATA_TRAFFIC: 'Datenverkehr',
|
||||
EMS_DEVICE: 'EMS Gerät',
|
||||
SUCCESS: 'ERFOLG',
|
||||
FAIL: 'FEHLER',
|
||||
QUALITY: 'QUALITÄT',
|
||||
SCAN: 'Suche',
|
||||
STATUS_NAMES: [
|
||||
'EMS-Telegramme empfangen (Rx)',
|
||||
'EMS-Telegramme gelesen (Tx)',
|
||||
@@ -127,7 +119,6 @@ const de: Translation = {
|
||||
ENUM_FORMAT: 'Enum Format API/MQTT',
|
||||
INDEX: 'Index',
|
||||
ENABLE_PARASITE: '1-Wire parasitäre Stomversorgung',
|
||||
LOGGING: 'Protokollierung',
|
||||
LOG_HEX: 'EMS-Telegramme hexadezimal protokollieren',
|
||||
ENABLE_SYSLOG: 'Syslog aktivieren',
|
||||
LOG_LEVEL: 'Protokollebene',
|
||||
@@ -172,7 +163,6 @@ const de: Translation = {
|
||||
CLOSE: 'Schließen',
|
||||
USE: 'Verwenden Sie',
|
||||
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?',
|
||||
AVAILABLE_VERSION: 'Neuesten verfügbaren Versionen',
|
||||
STABLE: 'Stabil',
|
||||
@@ -257,9 +247,7 @@ const de: Translation = {
|
||||
NETWORK_SCAN: 'Suche nach WiFi-Netzwerken',
|
||||
IDLE: 'Leerlauf',
|
||||
LOST: 'Verloren',
|
||||
SCANNING: 'Suche',
|
||||
SCAN_AGAIN: 'Erneute Suche',
|
||||
NETWORK_SCANNER: 'Netzwerksuche',
|
||||
NETWORK_NO_WIFI: 'Keine WiFi-Netzwerke gefunden',
|
||||
NETWORK_BLANK_BSSID: 'Freilassen, um nur SSID für die Verbindung zu nutzen.',
|
||||
TX_POWER: 'Tx Leistung',
|
||||
@@ -294,9 +282,6 @@ const de: Translation = {
|
||||
ONCHANGE: 'Bei Änderung',
|
||||
CONDITION: 'Zustand',
|
||||
SCHEDULE_UPDATED: 'Zeitplan aktualisiert',
|
||||
SCHEDULE_TIMER_1: 'beim Start',
|
||||
SCHEDULE_TIMER_2: 'jede Minute',
|
||||
SCHEDULE_TIMER_3: 'jede Stunde',
|
||||
CUSTOM_ENTITIES: 'Individuelle Entitäten',
|
||||
ENTITIES_HELP_1: 'Definition eigener EMS-Werte oder dynamischer Variablen',
|
||||
ENTITIES_UPDATED: 'Entitäten gespeichert',
|
||||
@@ -312,9 +297,7 @@ const de: Translation = {
|
||||
ACTIVELOW: 'Aktiv Negativ',
|
||||
UNCHANGED: 'Unverändert',
|
||||
ALWAYS: 'Immer',
|
||||
ACTIVITY: 'Aktivität',
|
||||
CONFIGURE: 'Konfiguriere {0}',
|
||||
SYSTEM_MEMORY: 'Systemspeicher',
|
||||
APPLICATION_SETTINGS_1: 'Ändern Sie die EMS-ESP-Anwendungseinstellungen.',
|
||||
SECURITY_1: 'Benutzer hinzufügen oder entfernen',
|
||||
DOWNLOAD_UPLOAD_1: 'Einstellungen und Firmware herunterladen/hochladen',
|
||||
@@ -326,7 +309,6 @@ const de: Translation = {
|
||||
RENAME: 'Umbenennen',
|
||||
ENABLE_MODBUS: 'Modbus aktivieren',
|
||||
VIEW_LOG: 'Sehen Sie sich das Protokoll an, um Probleme zu diagnostizieren.',
|
||||
UPLOAD_DRAG: 'Ziehen Sie eine Datei hierher oder klicken Sie, um eine auszuwählen.',
|
||||
SERVICES: 'Dienste',
|
||||
ALLVALUES: 'Alle Werte',
|
||||
SPECIAL_FUNCTIONS: 'Sonderfunktionen',
|
||||
@@ -352,10 +334,8 @@ const de: Translation = {
|
||||
THIS_VERSION: 'Diese Version',
|
||||
PLATFORM: 'Plattform',
|
||||
RELEASE_TYPE: 'Release Typ',
|
||||
INTERNET_CONNECTION_REQUIRED: 'Für die automatische Versionsprüfung und Aktualisierung ist eine Internetverbindung erforderlich',
|
||||
SWITCH_RELEASE_TYPE: 'Zum {0}-Release wechseln',
|
||||
FIRMWARE_VERSION_INFO: 'Firmware-Versionsinformation',
|
||||
NO_DATA: 'Keine Daten',
|
||||
USER_PROFILE: 'Benutzerprofil',
|
||||
STORED_VERSIONS: 'Gespeicherte Versionen',
|
||||
ONLINE_HELP: 'Online-Hilfe',
|
||||
@@ -366,9 +346,10 @@ const de: Translation = {
|
||||
TEST_EMAIL_SUCCESSFUL: 'Test email erfolgreich gesendet',
|
||||
SYSTEM_NAME: 'Systemname',
|
||||
COMMANDS: 'Befehle',
|
||||
COMMANDS_UPDATED: 'Befehle wurden aktualisiert',
|
||||
COMMANDS_HELP_1: 'Definieren Sie eigene Befehle für die EMS-Magistral',
|
||||
EXECUTE_COMMAND_SENT: 'Befehl gesendet',
|
||||
COMMAND_EXECUTED: 'Befehl erfolgreich ausgeführt',
|
||||
RUN: 'Befehl ausführen',
|
||||
INTERNET_CONNECTION_CHECK: 'Online-Prüfung auf verfügbare Versionen'
|
||||
};
|
||||
|
||||
export default de;
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { Translation } from '../i18n-types';
|
||||
const en: Translation = {
|
||||
LANGUAGE: 'Language',
|
||||
RETRY: 'Retry',
|
||||
LOADING: 'Loading',
|
||||
IS_REQUIRED: '{0} is required',
|
||||
SIGN_IN: 'Sign In',
|
||||
SIGN_OUT: 'Sign Out',
|
||||
@@ -37,13 +36,11 @@ const en: Translation = {
|
||||
VALUE: '{{value|Value}}',
|
||||
DEVICES: 'Devices',
|
||||
SENSORS: 'Sensors',
|
||||
RUN_COMMAND: 'Call Command',
|
||||
CHANGE_VALUE: 'Change Value',
|
||||
CANCEL: 'Cancel',
|
||||
REMOVE_ALL: 'Remove All',
|
||||
APPLY_CHANGES: 'Apply Changes ({0})',
|
||||
UPDATE: 'Update',
|
||||
EXECUTE: 'Execute',
|
||||
REMOVE: 'Remove',
|
||||
PROBLEM_UPDATING: 'Problem updating',
|
||||
PROBLEM_LOADING: 'Problem loading',
|
||||
@@ -52,8 +49,6 @@ const en: Translation = {
|
||||
SETTINGS: 'Settings',
|
||||
UPDATED_OF: '{0} Updated',
|
||||
UPDATE_OF: '{0} Update',
|
||||
REMOVED_OF: '{0} Removed',
|
||||
DELETION_OF: '{0} Deletion',
|
||||
OFFSET: 'Offset',
|
||||
FACTOR: 'Factor',
|
||||
FREQ: 'Frequency',
|
||||
@@ -66,17 +61,14 @@ const en: Translation = {
|
||||
TEMP_SENSORS: 'Temperature Sensors',
|
||||
WRITE_CMD_SENT: 'Write command sent',
|
||||
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 - check Tx Mode',
|
||||
DISCONNECTED: 'Disconnected',
|
||||
EMS_SCAN: 'Are you sure you want to initiate a full device scan of the EMS bus?',
|
||||
DATA_TRAFFIC: 'Data Traffic',
|
||||
EMS_DEVICE: 'EMS Device',
|
||||
SUCCESS: 'SUCCESS',
|
||||
FAIL: 'FAIL',
|
||||
QUALITY: 'QUALITY',
|
||||
SCAN: 'Scan',
|
||||
STATUS_NAMES: [
|
||||
'EMS Telegrams Received (Rx)',
|
||||
'EMS Reads (Tx)',
|
||||
@@ -127,7 +119,6 @@ const en: Translation = {
|
||||
ENUM_FORMAT: 'Enum Format API/MQTT',
|
||||
INDEX: 'Index',
|
||||
ENABLE_PARASITE: 'Enable 1-Wire Parasite-Power',
|
||||
LOGGING: 'Logging',
|
||||
LOG_HEX: 'Log EMS telegrams in hexadecimal',
|
||||
ENABLE_SYSLOG: 'Enable Syslog',
|
||||
LOG_LEVEL: 'Log Level',
|
||||
@@ -172,7 +163,6 @@ const en: Translation = {
|
||||
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 EMS-ESP to its factory defaults?',
|
||||
AVAILABLE_VERSION: 'Latest Available Versions',
|
||||
STABLE: 'Stable',
|
||||
@@ -257,9 +247,7 @@ const en: Translation = {
|
||||
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_BSSID: 'leave blank to use only SSID',
|
||||
TX_POWER: 'Tx Power',
|
||||
@@ -294,9 +282,6 @@ const en: Translation = {
|
||||
ONCHANGE: 'On Change',
|
||||
CONDITION: 'Condition',
|
||||
SCHEDULE_UPDATED: 'Schedule updated',
|
||||
SCHEDULE_TIMER_1: 'on startup',
|
||||
SCHEDULE_TIMER_2: 'every minute',
|
||||
SCHEDULE_TIMER_3: 'every hour',
|
||||
CUSTOM_ENTITIES: 'Custom Entities',
|
||||
ENTITIES_HELP_1: 'Define custom EMS entities or dynamic user variables',
|
||||
ENTITIES_UPDATED: 'Entities updated',
|
||||
@@ -312,9 +297,7 @@ const en: Translation = {
|
||||
ACTIVELOW: 'Active Low',
|
||||
UNCHANGED: 'Unchanged',
|
||||
ALWAYS: 'Always',
|
||||
ACTIVITY: 'Activity',
|
||||
CONFIGURE: 'Configure {0}',
|
||||
SYSTEM_MEMORY: 'System Memory',
|
||||
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings',
|
||||
SECURITY_1: 'Add or remove users',
|
||||
DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware',
|
||||
@@ -326,7 +309,6 @@ const en: Translation = {
|
||||
RENAME: 'Rename',
|
||||
ENABLE_MODBUS: 'Enable Modbus',
|
||||
VIEW_LOG: 'View log to diagnose issues',
|
||||
UPLOAD_DRAG: 'drag and drop a file here or click to select one',
|
||||
SERVICES: 'Services',
|
||||
ALLVALUES: 'All Values',
|
||||
SPECIAL_FUNCTIONS: 'Special Functions',
|
||||
@@ -352,10 +334,8 @@ const en: Translation = {
|
||||
THIS_VERSION: 'This Version',
|
||||
PLATFORM: 'Platform',
|
||||
RELEASE_TYPE: 'Release Type',
|
||||
INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading',
|
||||
SWITCH_RELEASE_TYPE: 'Switch to {0} release',
|
||||
FIRMWARE_VERSION_INFO: 'Firmware Version Information',
|
||||
NO_DATA: 'No data',
|
||||
USER_PROFILE: 'User Profile',
|
||||
STORED_VERSIONS: 'Stored Versions',
|
||||
ONLINE_HELP: 'online help',
|
||||
@@ -366,9 +346,10 @@ const en: Translation = {
|
||||
TEST_EMAIL_SUCCESSFUL: 'Test email sent successfully',
|
||||
SYSTEM_NAME: 'System Name',
|
||||
COMMANDS: 'Commands',
|
||||
COMMANDS_UPDATED: 'Commands updated',
|
||||
COMMANDS_HELP_1: 'Define reusable named commands below. These can be executed from the console, API/MQTT, or referenced by the Scheduler',
|
||||
EXECUTE_COMMAND_SENT: 'Command sent',
|
||||
COMMAND_EXECUTED: 'Command executed successfully',
|
||||
RUN: 'Run',
|
||||
INTERNET_CONNECTION_CHECK: 'Checking online for available versions'
|
||||
};
|
||||
|
||||
export default en;
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { Translation } from '../i18n-types';
|
||||
const fr: Translation = {
|
||||
LANGUAGE: 'Langue',
|
||||
RETRY: 'Réessayer',
|
||||
LOADING: 'Chargement',
|
||||
IS_REQUIRED: '{0} est requis',
|
||||
SIGN_IN: 'Se connecter',
|
||||
SIGN_OUT: 'Se déconnecter',
|
||||
@@ -37,13 +36,11 @@ const fr: Translation = {
|
||||
VALUE: 'Valeur',
|
||||
DEVICES: 'Appareils',
|
||||
SENSORS: 'Capteurs',
|
||||
RUN_COMMAND: 'Lancer une commande',
|
||||
CHANGE_VALUE: 'Changer la valeur',
|
||||
CANCEL: 'Annuler',
|
||||
REMOVE_ALL: 'Supprimer tout',
|
||||
APPLY_CHANGES: 'Appliquer les changements ({0})',
|
||||
UPDATE: 'Update',
|
||||
EXECUTE: 'Execute',
|
||||
REMOVE: 'Enlever',
|
||||
PROBLEM_UPDATING: 'Problème lors de la mise à jour',
|
||||
PROBLEM_LOADING: 'Problème lors du chargement',
|
||||
@@ -52,8 +49,6 @@ const fr: Translation = {
|
||||
SETTINGS: 'Paramètres',
|
||||
UPDATED_OF: '{0} mis à jour',
|
||||
UPDATE_OF: 'Mise à jour de {0}',
|
||||
REMOVED_OF: '{0} enlevé',
|
||||
DELETION_OF: '{0} supprimé',
|
||||
OFFSET: 'Offset',
|
||||
FACTOR: 'Facteur',
|
||||
FREQ: 'Fréquence',
|
||||
@@ -66,17 +61,14 @@ const fr: Translation = {
|
||||
TEMP_SENSORS: 'Capteurs de température',
|
||||
WRITE_CMD_SENT: 'Envoyer la commande sent',
|
||||
EMS_BUS_WARNING: 'Bus EMS déconnecté. Si ce message persiste après quelques secondes, vérifiez les paramètres et la configuration de la carte.',
|
||||
EMS_BUS_SCANNING: 'Scan des appareils EMS...',
|
||||
CONNECTED: 'Connecté',
|
||||
TX_ISSUES: 'Problèmes de transmission (Tx) - Essayez un autre mode Tx',
|
||||
DISCONNECTED: 'Déconnecté',
|
||||
EMS_SCAN: 'Etes-vous sûr de vouloir lancer un scan complet du bus EMS ?',
|
||||
DATA_TRAFFIC: 'Traffic des données',
|
||||
EMS_DEVICE: 'Appareils EMS',
|
||||
SUCCESS: 'SUCCÈS',
|
||||
FAIL: 'ÉCHEC',
|
||||
QUALITY: 'QUALITÉ',
|
||||
SCAN: 'Scan',
|
||||
STATUS_NAMES: [
|
||||
'Télégrammes EMS reçus (Rx)',
|
||||
'Lectures EMS (Tx)',
|
||||
@@ -127,7 +119,6 @@ const fr: Translation = {
|
||||
ENUM_FORMAT: 'Format enum API/MQTT',
|
||||
INDEX: 'Index',
|
||||
ENABLE_PARASITE: 'Activer la puissance 1-wire parasite',
|
||||
LOGGING: 'Journal',
|
||||
LOG_HEX: 'Enregistrer les télégrammes EMS en hexadécimal',
|
||||
ENABLE_SYSLOG: 'Activer les logs système',
|
||||
LOG_LEVEL: 'Niveau de log',
|
||||
@@ -172,7 +163,6 @@ const fr: Translation = {
|
||||
CLOSE: 'Fermer',
|
||||
USE: 'Utiliser',
|
||||
FACTORY_RESET: 'Réinitialisation',
|
||||
SYSTEM_FACTORY_TEXT: "L'appareil a été réinitialisé et va maintenant redémarrer",
|
||||
SYSTEM_FACTORY_TEXT_DIALOG: "Êtes-vous sûr de vouloir réinitialiser l'appareil à ses paramètres d'usine ?",
|
||||
AVAILABLE_VERSION: 'Versions disponibles',
|
||||
STABLE: 'Stable',
|
||||
@@ -257,9 +247,7 @@ const fr: Translation = {
|
||||
NETWORK_SCAN: 'Scanner les réseaux WiFi',
|
||||
IDLE: 'Inactif',
|
||||
LOST: 'Perdu',
|
||||
SCANNING: 'Scan en cours',
|
||||
SCAN_AGAIN: 'Rescanner',
|
||||
NETWORK_SCANNER: 'Scan réseau',
|
||||
NETWORK_NO_WIFI: 'Pas de réseau WiFi trouvé',
|
||||
NETWORK_BLANK_BSSID: 'laisser vide pour utiliser uniquement le SSID',
|
||||
TX_POWER: 'Puissance Tx',
|
||||
@@ -294,9 +282,6 @@ const fr: Translation = {
|
||||
ONCHANGE: 'Sur le changement',
|
||||
CONDITION: 'Condition',
|
||||
SCHEDULE_UPDATED: 'Programme mis à jour',
|
||||
SCHEDULE_TIMER_1: 'au démarrage',
|
||||
SCHEDULE_TIMER_2: 'toutes les minutes',
|
||||
SCHEDULE_TIMER_3: 'toutes les heures',
|
||||
CUSTOM_ENTITIES: 'Entités personnalisées',
|
||||
ENTITIES_HELP_1: 'Récupérer les entités personnalisées du bus EMS',
|
||||
ENTITIES_UPDATED: 'Entités mises à jour',
|
||||
@@ -312,9 +297,7 @@ const fr: Translation = {
|
||||
ACTIVELOW: 'Actif bas',
|
||||
UNCHANGED: 'Inchangé',
|
||||
ALWAYS: 'Toujours',
|
||||
ACTIVITY: 'Activité',
|
||||
CONFIGURE: 'Configurer {0}',
|
||||
SYSTEM_MEMORY: 'Mémoire système',
|
||||
APPLICATION_SETTINGS_1: 'Modifier les paramètres de l\'application EMS-ESP',
|
||||
SECURITY_1: 'Ajouter ou supprimer des utilisateurs',
|
||||
DOWNLOAD_UPLOAD_1: 'Télécharger et mettre à jour les paramètres et le firmware',
|
||||
@@ -326,7 +309,6 @@ const fr: Translation = {
|
||||
RENAME: 'Renommer',
|
||||
ENABLE_MODBUS: 'Activer Modbus',
|
||||
VIEW_LOG: 'Voir le journal pour diagnostiquer les problèmes',
|
||||
UPLOAD_DRAG: 'glisser-déposer un fichier ici ou cliquer pour en sélectionner un',
|
||||
SERVICES: 'Services',
|
||||
ALLVALUES: 'Toutes les valeurs',
|
||||
SPECIAL_FUNCTIONS: 'Fonctions spéciales',
|
||||
@@ -352,10 +334,8 @@ const fr: Translation = {
|
||||
THIS_VERSION: 'Cette version',
|
||||
PLATFORM: 'Plateforme',
|
||||
RELEASE_TYPE: 'Type de version',
|
||||
INTERNET_CONNECTION_REQUIRED: 'Connexion Internet requise pour la vérification automatique des versions et la mise à niveau',
|
||||
SWITCH_RELEASE_TYPE: 'Passer à la version {0}',
|
||||
FIRMWARE_VERSION_INFO: 'Informations sur la version du firmware',
|
||||
NO_DATA: 'Aucune donnée',
|
||||
USER_PROFILE: 'Profil utilisateur',
|
||||
STORED_VERSIONS: 'Versions stockées',
|
||||
ONLINE_HELP: 'aide en ligne',
|
||||
@@ -366,9 +346,10 @@ const fr: Translation = {
|
||||
TEST_EMAIL_SUCCESSFUL: 'Test email envoyé avec succès',
|
||||
SYSTEM_NAME: 'Nom du système',
|
||||
COMMANDS: 'Commandes',
|
||||
COMMANDS_UPDATED: 'Commandes mises à jour',
|
||||
COMMANDS_HELP_1: 'Définir des commandes personnalisées pour la magistral EMS',
|
||||
EXECUTE_COMMAND_SENT: 'Commande envoyée',
|
||||
COMMAND_EXECUTED: 'Commande exécutée avec succès',
|
||||
RUN: 'Exécuter une commande',
|
||||
INTERNET_CONNECTION_CHECK: 'Vérification en ligne pour les versions disponibles'
|
||||
};
|
||||
|
||||
export default fr;
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { Translation } from '../i18n-types';
|
||||
const it: Translation = {
|
||||
LANGUAGE: 'Lingua',
|
||||
RETRY: 'Riprovare',
|
||||
LOADING: 'Caricamento',
|
||||
IS_REQUIRED: '{0} é richiesto',
|
||||
SIGN_IN: 'Connettersi',
|
||||
SIGN_OUT: 'Disconnettersi',
|
||||
@@ -37,13 +36,11 @@ const it: Translation = {
|
||||
VALUE: '{{valore|Valore}}',
|
||||
DEVICES: 'Dispositivi',
|
||||
SENSORS: 'Sensori',
|
||||
RUN_COMMAND: 'Esegui',
|
||||
CHANGE_VALUE: 'Cambia Valore',
|
||||
CANCEL: 'Annulla',
|
||||
REMOVE_ALL: 'Rimuovi tutto',
|
||||
APPLY_CHANGES: 'Applica Cambiamenti ({0})',
|
||||
UPDATE: 'Update',
|
||||
EXECUTE: 'Execute',
|
||||
REMOVE: 'Elimina',
|
||||
PROBLEM_UPDATING: 'Problema aggiornamento',
|
||||
PROBLEM_LOADING: 'Problema caricamento',
|
||||
@@ -52,8 +49,6 @@ const it: Translation = {
|
||||
SETTINGS: 'Settings',
|
||||
UPDATED_OF: '{0} Aggiornati',
|
||||
UPDATE_OF: 'Aggiorna {0}',
|
||||
REMOVED_OF: '{0} Rimossi',
|
||||
DELETION_OF: '{0} Cancellati',
|
||||
OFFSET: 'Offset',
|
||||
FACTOR: 'Fattore',
|
||||
FREQ: 'Frequenza',
|
||||
@@ -66,17 +61,14 @@ const it: Translation = {
|
||||
TEMP_SENSORS: 'Sensori Temperatura',
|
||||
WRITE_CMD_SENT: 'Scrittura comando inviata',
|
||||
EMS_BUS_WARNING: 'EMS bus disconnesso. Se questo avvertimento persiste dopo alcuni secondi prego verificare impostazioni scheda',
|
||||
EMS_BUS_SCANNING: 'Scansione dispositivi EMS ...',
|
||||
CONNECTED: 'Connesso',
|
||||
TX_ISSUES: 'Problema di Tx - prova una modalità differente',
|
||||
DISCONNECTED: 'Disconnesso',
|
||||
EMS_SCAN: 'Sei sicuro di voler iniziare una scansione completa del bus EMS ?',
|
||||
DATA_TRAFFIC: 'Traffico dati',
|
||||
EMS_DEVICE: 'Dispositivo EMS ',
|
||||
SUCCESS: 'SUCCESSO',
|
||||
FAIL: 'FALLITO',
|
||||
QUALITY: 'QUALITÂ',
|
||||
SCAN: 'Scansione',
|
||||
STATUS_NAMES: [
|
||||
'Telegrammi EMS Ricevuti (Rx)',
|
||||
'EMS Letti (Tx)',
|
||||
@@ -127,7 +119,6 @@ const it: Translation = {
|
||||
ENUM_FORMAT: 'Enum Format API/MQTT',
|
||||
INDEX: 'Indice',
|
||||
ENABLE_PARASITE: 'Abilita potenza 1-wire parassita',
|
||||
LOGGING: 'Registrazione',
|
||||
LOG_HEX: 'Registra telegrammi EMS in esadecimale',
|
||||
ENABLE_SYSLOG: 'Abilita Syslog',
|
||||
LOG_LEVEL: 'Livello registrazione',
|
||||
@@ -172,7 +163,6 @@ const it: Translation = {
|
||||
CLOSE: 'Chiudere',
|
||||
USE: 'Usa',
|
||||
FACTORY_RESET: 'Impostazioni di fabbrica',
|
||||
SYSTEM_FACTORY_TEXT: 'Il dispositivo è stato ripristinato alle impostazioni di fabbrica e ora verrà riavviato',
|
||||
SYSTEM_FACTORY_TEXT_DIALOG: 'Sei sicuro di voler ripristinare il dispositivo alle impostazioni di fabbrica??',
|
||||
AVAILABLE_VERSION: 'Versioni disponibili',
|
||||
STABLE: 'Stabile',
|
||||
@@ -257,9 +247,7 @@ const it: Translation = {
|
||||
NETWORK_SCAN: 'Scansione reti WiFi',
|
||||
IDLE: 'Inattivo',
|
||||
LOST: 'Perso',
|
||||
SCANNING: 'Scansione',
|
||||
SCAN_AGAIN: 'Scansiona ancora',
|
||||
NETWORK_SCANNER: 'Scansione Rete',
|
||||
NETWORK_NO_WIFI: 'Nessuana rete WiFi trovata',
|
||||
NETWORK_BLANK_BSSID: 'lasciare vuoto per usare solo SSID',
|
||||
TX_POWER: 'Potenza Tx',
|
||||
@@ -294,9 +282,6 @@ const it: Translation = {
|
||||
ONCHANGE: 'Sul cambiamento',
|
||||
CONDITION: 'Condizione',
|
||||
SCHEDULE_UPDATED: 'Calendario aggiornato',
|
||||
SCHEDULE_TIMER_1: 'All avvio',
|
||||
SCHEDULE_TIMER_2: 'Ogni minuto',
|
||||
SCHEDULE_TIMER_3: 'Ogni ora',
|
||||
CUSTOM_ENTITIES: 'Entità personalizzate',
|
||||
ENTITIES_HELP_1: 'Recupera entità personalizzate dal BUS EMS',
|
||||
ENTITIES_UPDATED: 'Entità aggiornate',
|
||||
@@ -312,9 +297,7 @@ const it: Translation = {
|
||||
ACTIVELOW: 'Active Low',
|
||||
UNCHANGED: 'Unchanged',
|
||||
ALWAYS: 'Always',
|
||||
ACTIVITY: 'Activity',
|
||||
CONFIGURE: 'Configure {0}',
|
||||
SYSTEM_MEMORY: 'System Memory',
|
||||
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings',
|
||||
SECURITY_1: 'Add or remove users',
|
||||
DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware',
|
||||
@@ -326,7 +309,6 @@ const it: Translation = {
|
||||
RENAME: 'Rename',
|
||||
ENABLE_MODBUS: 'Abilita Modbus',
|
||||
VIEW_LOG: 'Visualizza log per diagnosticare problemi',
|
||||
UPLOAD_DRAG: 'trascina e rilascia un file qui o clicca per selezionare uno',
|
||||
SERVICES: 'Servizi',
|
||||
ALLVALUES: 'Tutti i valori',
|
||||
SPECIAL_FUNCTIONS: 'Funzioni speciali',
|
||||
@@ -352,10 +334,8 @@ const it: Translation = {
|
||||
THIS_VERSION: 'Questa versione',
|
||||
PLATFORM: 'Piattaforma',
|
||||
RELEASE_TYPE: 'Tipo di rilascio',
|
||||
INTERNET_CONNECTION_REQUIRED: 'Connessione internet richiesta per il controllo automatico delle versioni e l\'aggiornamento',
|
||||
SWITCH_RELEASE_TYPE: 'Cambia in {0} rilascio',
|
||||
FIRMWARE_VERSION_INFO: 'Informazioni sulla versione del firmware',
|
||||
NO_DATA: 'Nessun dato',
|
||||
USER_PROFILE: 'Profilo utente',
|
||||
STORED_VERSIONS: 'Versioni memorizzate',
|
||||
ONLINE_HELP: 'aiuto online',
|
||||
@@ -366,9 +346,10 @@ const it: Translation = {
|
||||
TEST_EMAIL_SUCCESSFUL: 'Test email inviata con successo',
|
||||
SYSTEM_NAME: 'Nome del sistema',
|
||||
COMMANDS: 'Comandi',
|
||||
COMMANDS_UPDATED: 'Comandi aggiornati',
|
||||
COMMANDS_HELP_1: 'Definisci comandi personalizzati per la magistrali EMS',
|
||||
EXECUTE_COMMAND_SENT: 'Comando inviato',
|
||||
COMMAND_EXECUTED: 'Comando eseguito con successo',
|
||||
RUN: 'Esegui comando',
|
||||
INTERNET_CONNECTION_CHECK: 'Controllo online per le versioni disponibili'
|
||||
};
|
||||
|
||||
export default it;
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { Translation } from '../i18n-types';
|
||||
const nl: Translation = {
|
||||
LANGUAGE: 'Taal',
|
||||
RETRY: 'Opnieuw proberen',
|
||||
LOADING: 'Laden',
|
||||
IS_REQUIRED: '{0} is verplicht',
|
||||
SIGN_IN: 'Inloggen',
|
||||
SIGN_OUT: 'Uitloggen',
|
||||
@@ -37,13 +36,11 @@ const nl: Translation = {
|
||||
VALUE: '{{waarde|Waarde}}',
|
||||
DEVICES: 'Apparaten',
|
||||
SENSORS: 'Sensoren',
|
||||
RUN_COMMAND: 'Call commando',
|
||||
CHANGE_VALUE: 'Wijzig waarde',
|
||||
CANCEL: 'Annuleren',
|
||||
REMOVE_ALL: 'Verwijderen alles',
|
||||
APPLY_CHANGES: 'Aanpassen ({0})',
|
||||
UPDATE: 'Update',
|
||||
EXECUTE: 'Uitvoeren',
|
||||
REMOVE: 'Verwijderen',
|
||||
PROBLEM_UPDATING: 'Probleem met updaten',
|
||||
PROBLEM_LOADING: 'Probleem met laden',
|
||||
@@ -52,8 +49,6 @@ const nl: Translation = {
|
||||
SETTINGS: 'Instellingen',
|
||||
UPDATED_OF: '{0} Bijgewerkt',
|
||||
UPDATE_OF: '{0} Bijwerken',
|
||||
REMOVED_OF: '{0} Verwijderd',
|
||||
DELETION_OF: '{0} Verwijder',
|
||||
OFFSET: 'Offset',
|
||||
FACTOR: 'Factor',
|
||||
FREQ: 'Frequentie',
|
||||
@@ -66,17 +61,14 @@ const nl: Translation = {
|
||||
TEMP_SENSORS: 'Temperatuur Sensoren',
|
||||
WRITE_CMD_SENT: 'Schrijf commando gestuurd',
|
||||
EMS_BUS_WARNING: 'EMS bus niet gevonden. Als deze waarschuwing blijft staan na een paar seconden loop dan de instellingen na en in het bijzonder het apparaat type profiel.',
|
||||
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?',
|
||||
DATA_TRAFFIC: 'Dataverkeer',
|
||||
EMS_DEVICE: 'EMS Apparaat',
|
||||
SUCCESS: 'SUCCESS',
|
||||
FAIL: 'MISLUKT',
|
||||
QUALITY: 'KWALITEIT',
|
||||
SCAN: 'Scan',
|
||||
STATUS_NAMES: [
|
||||
'EMS Telegrammen ontvangen (Rx)',
|
||||
'EMS Leesopdrachten (Tx)',
|
||||
@@ -127,7 +119,6 @@ const nl: Translation = {
|
||||
ENUM_FORMAT: 'Enum formaat API/MQTT',
|
||||
INDEX: 'Index',
|
||||
ENABLE_PARASITE: 'Activeer 1-wire parasitaire modus',
|
||||
LOGGING: 'Logging',
|
||||
LOG_HEX: 'Log EMS telegrammen in hexadecimaal',
|
||||
ENABLE_SYSLOG: 'Activeer Syslog',
|
||||
LOG_LEVEL: 'Log Level',
|
||||
@@ -172,7 +163,6 @@ const nl: Translation = {
|
||||
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?',
|
||||
AVAILABLE_VERSION: 'Nieuwste beschikbare versies',
|
||||
STABLE: 'Stable',
|
||||
@@ -257,9 +247,7 @@ const nl: Translation = {
|
||||
NETWORK_SCAN: 'Scan WiFi Netwerken',
|
||||
IDLE: 'Inactief',
|
||||
LOST: 'Verloren',
|
||||
SCANNING: 'Scannen',
|
||||
SCAN_AGAIN: 'Opnieuw scannen',
|
||||
NETWORK_SCANNER: 'Netwerk Scannen',
|
||||
NETWORK_NO_WIFI: 'Geen WiFi netwerken gevonden',
|
||||
NETWORK_BLANK_BSSID: 'laat leeg om alleen SSID te bebruiken',
|
||||
TX_POWER: 'Tx Vermogen',
|
||||
@@ -294,9 +282,6 @@ const nl: Translation = {
|
||||
ONCHANGE: 'Op verandering',
|
||||
CONDITION: 'Voorwaarde',
|
||||
SCHEDULE_UPDATED: 'Schema bijgewerkt',
|
||||
SCHEDULE_TIMER_1: 'bij het opstarten',
|
||||
SCHEDULE_TIMER_2: 'elke minuut',
|
||||
SCHEDULE_TIMER_3: 'elke huur',
|
||||
CUSTOM_ENTITIES: 'Eigen entiteiten',
|
||||
ENTITIES_HELP_1: 'Eigen entiteiten ophalen uit de EMS-bus',
|
||||
ENTITIES_UPDATED: 'Entiteiten bijgewerkt',
|
||||
@@ -312,9 +297,7 @@ const nl: Translation = {
|
||||
ACTIVELOW: 'Actiev Laag',
|
||||
UNCHANGED: 'Ongewijzigd',
|
||||
ALWAYS: 'Altijd',
|
||||
ACTIVITY: 'Activiteit',
|
||||
CONFIGURE: '{0} Configureren',
|
||||
SYSTEM_MEMORY: 'System Geheugen',
|
||||
APPLICATION_SETTINGS_1: 'Applicatie-instellingen wijzigen',
|
||||
SECURITY_1: 'Gebruikers toevoegen of verwijderen',
|
||||
DOWNLOAD_UPLOAD_1: 'Download en upload instellingen en firmware',
|
||||
@@ -326,7 +309,6 @@ const nl: Translation = {
|
||||
RENAME: 'Hernoemen',
|
||||
ENABLE_MODBUS: 'Activeer Modbus',
|
||||
VIEW_LOG: 'Log weergeven om problemen te diagnosticeren',
|
||||
UPLOAD_DRAG: 'sleep hier een bestand en zet het neer of klik om er een te selecteren',
|
||||
SERVICES: 'Services',
|
||||
ALLVALUES: 'All waarden',
|
||||
SPECIAL_FUNCTIONS: 'Speciale functies',
|
||||
@@ -352,10 +334,8 @@ const nl: Translation = {
|
||||
THIS_VERSION: 'Deze Versie',
|
||||
PLATFORM: 'Platform',
|
||||
RELEASE_TYPE: 'Release Typ',
|
||||
INTERNET_CONNECTION_REQUIRED: 'Internetverbinding vereist voor automatische versiecontrole en -upgrade',
|
||||
SWITCH_RELEASE_TYPE: 'Switch naar {0} release',
|
||||
FIRMWARE_VERSION_INFO: 'Informatie over firmwareversie',
|
||||
NO_DATA: 'Geen data',
|
||||
USER_PROFILE: 'Gebruikersprofiel',
|
||||
STORED_VERSIONS: 'Opgeslagen versies',
|
||||
ONLINE_HELP: 'online help',
|
||||
@@ -366,9 +346,10 @@ const nl: Translation = {
|
||||
TEST_EMAIL_SUCCESSFUL: 'Test email verzonden succesvol',
|
||||
SYSTEM_NAME: 'Systeemnaam',
|
||||
COMMANDS: 'Commando\'s',
|
||||
COMMANDS_UPDATED: 'Commando\'s bijgewerkt',
|
||||
COMMANDS_HELP_1: 'Definieer hergebruikbare benoemde commando\'s hieronder. Deze kunnen worden uitgevoerd vanuit de console, API/MQTT, of worden aangeroepen door de Scheduler',
|
||||
EXECUTE_COMMAND_SENT: 'Commando verzonden',
|
||||
COMMAND_EXECUTED: 'Commando uitgevoerd succesvol',
|
||||
RUN: 'Commando uitvoeren',
|
||||
INTERNET_CONNECTION_CHECK: 'Controleren op online beschikbare versies'
|
||||
};
|
||||
|
||||
export default nl;
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { Translation } from '../i18n-types';
|
||||
const no: Translation = {
|
||||
LANGUAGE: 'Språk',
|
||||
RETRY: 'Forsøk igjen',
|
||||
LOADING: 'Laster',
|
||||
IS_REQUIRED: '{0} er nødvendig',
|
||||
SIGN_IN: 'Logg inn',
|
||||
SIGN_OUT: 'Logg ut',
|
||||
@@ -37,13 +36,11 @@ const no: Translation = {
|
||||
VALUE: '{{verdi|Verdi}}',
|
||||
DEVICES: 'Enheter',
|
||||
SENSORS: 'Sensorer',
|
||||
RUN_COMMAND: 'Kjør kommando',
|
||||
CHANGE_VALUE: 'Endre Verdi',
|
||||
CANCEL: 'Avbryt',
|
||||
REMOVE_ALL: 'Fjern alles',
|
||||
APPLY_CHANGES: 'Utfør endringer({0})',
|
||||
UPDATE: 'Oppdater',
|
||||
EXECUTE: 'Utfør',
|
||||
REMOVE: 'Fjern',
|
||||
PROBLEM_UPDATING: 'Problem med oppdatering',
|
||||
PROBLEM_LOADING: 'Problem med opplasting',
|
||||
@@ -52,8 +49,6 @@ const no: Translation = {
|
||||
SETTINGS: 'Innstillinger',
|
||||
UPDATED_OF: '{0} Oppdatert',
|
||||
UPDATE_OF: '{0} Oppdater',
|
||||
REMOVED_OF: '{0} Slettet',
|
||||
DELETION_OF: '{0} Sletting',
|
||||
OFFSET: 'Kompensering',
|
||||
FACTOR: 'Faktor',
|
||||
FREQ: 'Frekvens',
|
||||
@@ -66,17 +61,14 @@ const no: Translation = {
|
||||
TEMP_SENSORS: 'Temperaturesensorer',
|
||||
WRITE_CMD_SENT: 'Skriv kommando sent',
|
||||
EMS_BUS_WARNING: 'EMS bussen koblet ned. Hvis denne advarselen fortsetter etter noen f¨sekunder sjekk instillinger og prosessorkort',
|
||||
EMS_BUS_SCANNING: 'Søker etter EMS enheter...',
|
||||
CONNECTED: 'Tilkoblet',
|
||||
TX_ISSUES: 'Tx problemer - prøv en annen Tx Modus',
|
||||
DISCONNECTED: 'Frakoblet',
|
||||
EMS_SCAN: 'Er du sikker på du vil starte full søking av EMS bussen?',
|
||||
DATA_TRAFFIC: 'Data trafikk',
|
||||
EMS_DEVICE: 'EMS Enhet',
|
||||
SUCCESS: 'VELLYKKET',
|
||||
FAIL: 'MISLYKKET',
|
||||
QUALITY: 'KVALITET',
|
||||
SCAN: 'Søk',
|
||||
STATUS_NAMES: [
|
||||
'EMS Telegrammer Mottatt (Rx)',
|
||||
'EMS Lest (Tx)',
|
||||
@@ -127,7 +119,6 @@ const no: Translation = {
|
||||
ENUM_FORMAT: 'Enum Format API/MQTT',
|
||||
INDEX: 'Indeks',
|
||||
ENABLE_PARASITE: 'Aktiver 1-wire parasitt strømforsyning',
|
||||
LOGGING: 'Logging',
|
||||
LOG_HEX: 'Logg EMS telegrammer i hexadesimal',
|
||||
ENABLE_SYSLOG: 'Aktiver Syslog',
|
||||
LOG_LEVEL: 'Log Level',
|
||||
@@ -172,7 +163,6 @@ const no: Translation = {
|
||||
CLOSE: 'Steng',
|
||||
USE: 'Bruk',
|
||||
FACTORY_RESET: 'Sett tilbake til fabrikkinstilling',
|
||||
SYSTEM_FACTORY_TEXT: 'Enhet har blitt satt tilbake til fabrikkinstilling og vil restarte',
|
||||
SYSTEM_FACTORY_TEXT_DIALOG: 'Er du sikker på at du vil resette enheten til fabrikkinstillinger?',
|
||||
AVAILABLE_VERSION: 'Tilgjengelige versjoner',
|
||||
STABLE: 'Stabil',
|
||||
@@ -257,9 +247,7 @@ const no: Translation = {
|
||||
NETWORK_SCAN: 'Søk etter trådløst nettverk',
|
||||
IDLE: 'Klar',
|
||||
LOST: 'Mistet',
|
||||
SCANNING: 'Søker',
|
||||
SCAN_AGAIN: 'Søk igjen',
|
||||
NETWORK_SCANNER: 'Nettverk Scanner',
|
||||
NETWORK_NO_WIFI: 'Ingen trådløse nett funnet',
|
||||
NETWORK_BLANK_BSSID: 'la feltet være blankt for å bruke kun SSID',
|
||||
TX_POWER: 'Tx Effekt',
|
||||
@@ -294,9 +282,6 @@ const no: Translation = {
|
||||
ONCHANGE: 'På endring',
|
||||
CONDITION: 'Betingelse',
|
||||
SCHEDULE_UPDATED: 'Planlegger er oppdatert',
|
||||
SCHEDULE_TIMER_1: 'ved oppstart',
|
||||
SCHEDULE_TIMER_2: 'hvert minutt',
|
||||
SCHEDULE_TIMER_3: 'hver time',
|
||||
CUSTOM_ENTITIES: 'Personlige entiteter',
|
||||
ENTITIES_HELP_1: 'Hent personlige entiteter fra EMS bussen',
|
||||
ENTITIES_UPDATED: 'Entiteter oppdatert',
|
||||
@@ -312,9 +297,7 @@ const no: Translation = {
|
||||
ACTIVELOW: 'Active Low',
|
||||
UNCHANGED: 'Unchanged',
|
||||
ALWAYS: 'Always',
|
||||
ACTIVITY: 'Aktivitet',
|
||||
CONFIGURE: 'Konfigurer {0}',
|
||||
SYSTEM_MEMORY: 'System Memory',
|
||||
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings',
|
||||
SECURITY_1: 'Add or remove users',
|
||||
DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware',
|
||||
@@ -326,7 +309,6 @@ const no: Translation = {
|
||||
RENAME: 'Gi nytt navn',
|
||||
ENABLE_MODBUS: 'Aktiver Modbus',
|
||||
VIEW_LOG: 'Se logg for å diagnostisere problemer',
|
||||
UPLOAD_DRAG: 'dra og slippe en fil her eller klikk for å velge en',
|
||||
SERVICES: 'Tjenester',
|
||||
ALLVALUES: 'Alle verdier',
|
||||
SPECIAL_FUNCTIONS: 'Spesielle funksjoner',
|
||||
@@ -352,10 +334,8 @@ const no: Translation = {
|
||||
THIS_VERSION: 'Denne versjonen',
|
||||
PLATFORM: 'Plattform',
|
||||
RELEASE_TYPE: 'Utgivelses type',
|
||||
INTERNET_CONNECTION_REQUIRED: 'Internettilkobling kreves for automatisk versjonskontroll og oppgradering',
|
||||
SWITCH_RELEASE_TYPE: 'Bytt til {0} utgivelse',
|
||||
FIRMWARE_VERSION_INFO: 'Informasjon om firmwareversjon',
|
||||
NO_DATA: 'Ingen data',
|
||||
USER_PROFILE: 'Brukerprofil',
|
||||
STORED_VERSIONS: 'Lagret versjoner',
|
||||
ONLINE_HELP: 'online hjelp',
|
||||
@@ -366,9 +346,10 @@ const no: Translation = {
|
||||
TEST_EMAIL_SUCCESSFUL: 'Test email sendt suksessfullt',
|
||||
SYSTEM_NAME: 'Systemnavn',
|
||||
COMMANDS: 'Kommandoer',
|
||||
COMMANDS_UPDATED: 'Kommandoer oppdatert',
|
||||
COMMANDS_HELP_1: 'Definer egne kommandoer for EMS-Magistral',
|
||||
EXECUTE_COMMAND_SENT: 'Kommando sendt',
|
||||
COMMAND_EXECUTED: 'Kommando kjørt lykkes',
|
||||
RUN: 'Kjør',
|
||||
INTERNET_CONNECTION_CHECK: 'Kontrollerer online for tilgjengelige versjoner'
|
||||
};
|
||||
|
||||
export default no;
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { BaseTranslation } from '../i18n-types';
|
||||
const pl: BaseTranslation = {
|
||||
LANGUAGE: 'Język',
|
||||
RETRY: 'Ponów',
|
||||
LOADING: 'Ładowanie',
|
||||
IS_REQUIRED: 'Pole {0} nie może być puste!',
|
||||
SIGN_IN: 'Zaloguj się',
|
||||
SIGN_OUT: 'Wyloguj się',
|
||||
@@ -37,13 +36,11 @@ const pl: BaseTranslation = {
|
||||
VALUE: '{{W|w|w}}artość',
|
||||
DEVICES: 'Urządzenia',
|
||||
SENSORS: 'Czujniki',
|
||||
RUN_COMMAND: 'Wykonaj komendę',
|
||||
CHANGE_VALUE: 'Zmiana wartości',
|
||||
CANCEL: 'Anuluj',
|
||||
REMOVE_ALL: 'Usuń wszystko',
|
||||
APPLY_CHANGES: 'Zapisz zmiany ({0})',
|
||||
UPDATE: 'Zmień',
|
||||
EXECUTE: 'Wykonaj',
|
||||
REMOVE: 'Usuń',
|
||||
PROBLEM_UPDATING: 'Problem z uaktualnieniem!',
|
||||
PROBLEM_LOADING: 'Problem z załadowaniem!',
|
||||
@@ -52,8 +49,6 @@ const pl: BaseTranslation = {
|
||||
SETTINGS: '{{U|u|}}stawienia',
|
||||
UPDATED_OF: 'Zaktualizowano {0}.',
|
||||
UPDATE_OF: 'Aktualizacja {0}',
|
||||
REMOVED_OF: 'Usunięto ustawienia {0}.',
|
||||
DELETION_OF: 'Kasowanie {0}',
|
||||
OFFSET: 'Korekta ±',
|
||||
FACTOR: 'Mnożnik',
|
||||
FREQ: 'Częstotliwość',
|
||||
@@ -66,17 +61,14 @@ const pl: BaseTranslation = {
|
||||
TEMP_SENSORS: 'Czujniki temperatury 1-Wire®',
|
||||
WRITE_CMD_SENT: 'Komenda zapisu została wysłana.',
|
||||
EMS_BUS_WARNING: 'Brak połączenia z magistralą EMS. Jeśli ten błąd występuje dłużej niż kilka sekund, sprawdź ustawienia oraz profil płytki interfejsu.',
|
||||
EMS_BUS_SCANNING: 'Trwa skanowanie urządzeń na magistrali EMS...',
|
||||
CONNECTED: '{{połączono|połączenie|}}',
|
||||
TX_ISSUES: 'problem z zapisem na magistralę EMS, spróbuj wybrać inny "Tryb transmisji (Tx)"',
|
||||
DISCONNECTED: 'brak połączenia',
|
||||
EMS_SCAN: 'Czy na pewno wykonać pełne skanowanie magistrali EMS?',
|
||||
DATA_TRAFFIC: 'Ruch Danych',
|
||||
EMS_DEVICE: 'Urządzenie EMS',
|
||||
SUCCESS: 'Udane',
|
||||
FAIL: 'Nieudane',
|
||||
QUALITY: 'Jakość',
|
||||
SCAN: 'Skanuj',
|
||||
STATUS_NAMES: [
|
||||
'EMS, telegramy odebrane (Rx)',
|
||||
'EMS, wysłane telegramy "odczyt" (Tx)',
|
||||
@@ -127,7 +119,6 @@ const pl: BaseTranslation = {
|
||||
ENUM_FORMAT: 'Wartości z listy w API/MQTT',
|
||||
INDEX: 'indeks',
|
||||
ENABLE_PARASITE: 'Aktywuj zasilanie 1-wire pasożytnicze',
|
||||
LOGGING: 'Logowanie',
|
||||
LOG_HEX: 'Loguj telegramy EMS w systemie szesnastkowym (hex)',
|
||||
ENABLE_SYSLOG: 'Aktywuj SysLog',
|
||||
LOG_LEVEL: 'Poziom logowania',
|
||||
@@ -172,7 +163,6 @@ const pl: BaseTranslation = {
|
||||
CLOSE: 'Zamknij',
|
||||
USE: 'Aby zaktualizować firmware skorzystaj z funkcji',
|
||||
FACTORY_RESET: 'Ustawienia fabryczne',
|
||||
SYSTEM_FACTORY_TEXT: 'Interfejs EMS-ESP został przywrócony do ustawień fabrycznych i zostanie teraz ponownie uruchomiony.',
|
||||
SYSTEM_FACTORY_TEXT_DIALOG: 'Na pewno chcesz przywrócić ustawienia fabryczne interfejsu EMS-ESP?',
|
||||
AVAILABLE_VERSION: 'Najnowsze dostępne wersje',
|
||||
STABLE: 'Stabilna',
|
||||
@@ -257,9 +247,7 @@ const pl: BaseTranslation = {
|
||||
NETWORK_SCAN: 'Skanowanie sieci WiFi',
|
||||
IDLE: 'bezczynna',
|
||||
LOST: 'zostało utracone.',
|
||||
SCANNING: 'Skanuję',
|
||||
SCAN_AGAIN: 'Skanuj ponownie',
|
||||
NETWORK_SCANNER: 'Skaner sieci WiFi',
|
||||
NETWORK_NO_WIFI: 'Brak sieci WiFi w zasięgu',
|
||||
NETWORK_BLANK_BSSID: 'pozostaw puste aby używać tylko SSID',
|
||||
TX_POWER: 'Moc nadawania',
|
||||
@@ -294,9 +282,6 @@ const pl: BaseTranslation = {
|
||||
ONCHANGE: 'O zmianie',
|
||||
CONDITION: 'Stan',
|
||||
SCHEDULE_UPDATED: 'Harmonogram został uaktualniony.',
|
||||
SCHEDULE_TIMER_1: 'przy starcie',
|
||||
SCHEDULE_TIMER_2: 'co minutę',
|
||||
SCHEDULE_TIMER_3: 'co godzinę',
|
||||
CUSTOM_ENTITIES: '{{N|n|}}iestandardowe{{|j|}} encj{{e|i|}}',
|
||||
ENTITIES_HELP_1: 'Zdefiniuj niestandardowe encje dla magistrali EMS',
|
||||
ENTITIES_UPDATED: 'Niestandardowe encje zostały uaktualnione.',
|
||||
@@ -312,9 +297,7 @@ const pl: BaseTranslation = {
|
||||
ACTIVELOW: 'Wyzwalany stanem niskim',
|
||||
UNCHANGED: 'Zachowaj stan',
|
||||
ALWAYS: 'Zawsze',
|
||||
ACTIVITY: 'Aktywność',
|
||||
CONFIGURE: 'Konfiguracja {0}',
|
||||
SYSTEM_MEMORY: 'Pamięć systemowa',
|
||||
APPLICATION_SETTINGS_1: 'Modyfikacja ustawień aplikacji EMS-ESP',
|
||||
SECURITY_1: 'Dodawanie i usuwanie użytkowników',
|
||||
DOWNLOAD_UPLOAD_1: 'Pobieranie/wysyłanie ustawień i firmware',
|
||||
@@ -326,7 +309,6 @@ const pl: BaseTranslation = {
|
||||
RENAME: 'Zmień nazwę',
|
||||
ENABLE_MODBUS: 'Aktywuj Modbus',
|
||||
VIEW_LOG: 'Zdiagnozuj problemy',
|
||||
UPLOAD_DRAG: 'przeciągnij i upuść plik lub kliknij tutaj',
|
||||
SERVICES: 'Usługi',
|
||||
ALLVALUES: 'Wszystkie wartości',
|
||||
SPECIAL_FUNCTIONS: 'Specjalne funkcje',
|
||||
@@ -352,10 +334,8 @@ const pl: BaseTranslation = {
|
||||
THIS_VERSION: 'Ta wersja',
|
||||
PLATFORM: 'Platforma',
|
||||
RELEASE_TYPE: 'Typ wydania',
|
||||
INTERNET_CONNECTION_REQUIRED: 'Połączenie internetowe jest wymagane do automatycznej kontroli wersji i aktualizacji',
|
||||
SWITCH_RELEASE_TYPE: 'Zmień na {0} wydanie',
|
||||
FIRMWARE_VERSION_INFO: 'Informacje o wersji firmware',
|
||||
NO_DATA: 'Brak danych',
|
||||
USER_PROFILE: 'Profil użytkownika',
|
||||
STORED_VERSIONS: 'Zapisane wersje',
|
||||
ONLINE_HELP: 'pomoc online',
|
||||
@@ -366,9 +346,10 @@ const pl: BaseTranslation = {
|
||||
TEST_EMAIL_SUCCESSFUL: 'Test email wysłany pomyślnie',
|
||||
SYSTEM_NAME: 'Nazwa systemu',
|
||||
COMMANDS: 'Komendy',
|
||||
COMMANDS_UPDATED: 'Komendy zostały zaktualizowane',
|
||||
COMMANDS_HELP_1: 'Zdefiniuj niestandardowe komendy dla magistrali EMS',
|
||||
EXECUTE_COMMAND_SENT: 'Komenda wysłana',
|
||||
COMMAND_EXECUTED: 'Komenda wykonana pomyślnie',
|
||||
RUN: 'Wykonaj',
|
||||
INTERNET_CONNECTION_CHECK: 'Sprawdzanie online dla dostępnych wersji'
|
||||
};
|
||||
|
||||
export default pl;
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { Translation } from '../i18n-types';
|
||||
const sk: Translation = {
|
||||
LANGUAGE: 'Jazyk',
|
||||
RETRY: 'Opakovať',
|
||||
LOADING: 'Načítanie',
|
||||
IS_REQUIRED: '{0} je požadovaná',
|
||||
SIGN_IN: 'Prihlásiť sa',
|
||||
SIGN_OUT: 'Odhlásiť sa',
|
||||
@@ -37,13 +36,11 @@ const sk: Translation = {
|
||||
VALUE: '{{hodnota|Hodnota}}',
|
||||
DEVICES: 'Zariadenia',
|
||||
SENSORS: 'Snímače',
|
||||
RUN_COMMAND: 'Volať príkaz',
|
||||
CHANGE_VALUE: 'Zmena hodnoty',
|
||||
CANCEL: 'Zrušiť',
|
||||
REMOVE_ALL: 'Odstrániť všetko',
|
||||
APPLY_CHANGES: 'Aplikovať zmeny ({0})',
|
||||
UPDATE: 'Aktualizovať',
|
||||
EXECUTE: 'Spustiť',
|
||||
REMOVE: 'Odstrániť',
|
||||
PROBLEM_UPDATING: 'Problém s aktualizáciou',
|
||||
PROBLEM_LOADING: 'Problém s načítaním',
|
||||
@@ -52,8 +49,6 @@ const sk: Translation = {
|
||||
SETTINGS: 'Nastavenia',
|
||||
UPDATED_OF: '{0} aktualizovaných',
|
||||
UPDATE_OF: '{0} aktualizované',
|
||||
REMOVED_OF: '{0} odstránených',
|
||||
DELETION_OF: '{0} zmazaných',
|
||||
OFFSET: 'Ofset',
|
||||
FACTOR: 'Faktor',
|
||||
FREQ: 'Frekvencia',
|
||||
@@ -66,17 +61,14 @@ const sk: Translation = {
|
||||
TEMP_SENSORS: 'Snímače teploty',
|
||||
WRITE_CMD_SENT: 'Príkaz zápisu bol odoslaný',
|
||||
EMS_BUS_WARNING: 'Zbernica EMS odpojená. Ak toto upozornenie pretrváva aj po niekoľkých sekundách, skontrolujte nastavenia a profil dosky',
|
||||
EMS_BUS_SCANNING: 'Zisťovanie EMS zariadení...',
|
||||
CONNECTED: 'Pripojené',
|
||||
TX_ISSUES: 'Problémy s Tx – skontrolujte Tx režim',
|
||||
DISCONNECTED: 'Odpojené',
|
||||
EMS_SCAN: 'Naozaj chcete spustiť úplnú kontrolu zariadenia zbernice EMS?',
|
||||
DATA_TRAFFIC: 'Dátová prevádzka',
|
||||
EMS_DEVICE: 'EMS zariadenie',
|
||||
SUCCESS: 'ÚSPEŠNÉ',
|
||||
FAIL: 'ZLYHANIE',
|
||||
QUALITY: 'KVALITA',
|
||||
SCAN: 'Scan',
|
||||
STATUS_NAMES: [
|
||||
'EMS Telegramy prijaté (Rx)',
|
||||
'EMS Čítania (Tx)',
|
||||
@@ -127,7 +119,6 @@ const sk: Translation = {
|
||||
ENUM_FORMAT: 'Enum formát API/MQTT',
|
||||
INDEX: 'Index',
|
||||
ENABLE_PARASITE: 'Povoliť 1-wire parazitné napájanie DS18B20',
|
||||
LOGGING: 'Logovanie',
|
||||
LOG_HEX: 'Záznam telegramov EMS v hexadecimálnej sústave',
|
||||
ENABLE_SYSLOG: 'Povoliť Syslog',
|
||||
LOG_LEVEL: 'Log úroveň',
|
||||
@@ -172,7 +163,6 @@ const sk: Translation = {
|
||||
CLOSE: 'Zatvoriť',
|
||||
USE: 'Použiť',
|
||||
FACTORY_RESET: 'Továrenské nastavenia',
|
||||
SYSTEM_FACTORY_TEXT: 'Zariadenie bolo obnovené z výroby a teraz sa reštartuje',
|
||||
SYSTEM_FACTORY_TEXT_DIALOG: 'Naozaj chcete resetovať EMS-ESP na predvolené výrobné nastavenia?',
|
||||
AVAILABLE_VERSION: 'Najnovšie dostupné verzie',
|
||||
STABLE: 'Stabilná',
|
||||
@@ -257,9 +247,7 @@ const sk: Translation = {
|
||||
NETWORK_SCAN: 'Scan WiFi siete',
|
||||
IDLE: 'Nečinné',
|
||||
LOST: 'Stratené',
|
||||
SCANNING: 'Skenovanie',
|
||||
SCAN_AGAIN: 'Skenovať znova',
|
||||
NETWORK_SCANNER: 'Sieťový skener',
|
||||
NETWORK_NO_WIFI: 'WiFi siete nenájdené',
|
||||
NETWORK_BLANK_BSSID: 'ponechajte prázdne, ak chcete používať iba SSID',
|
||||
TX_POWER: 'Tx výkon',
|
||||
@@ -294,9 +282,6 @@ const sk: Translation = {
|
||||
ONCHANGE: 'Pri zmene',
|
||||
CONDITION: 'Podmienka',
|
||||
SCHEDULE_UPDATED: 'Plánovanie aktualizované',
|
||||
SCHEDULE_TIMER_1: 'pri spustení',
|
||||
SCHEDULE_TIMER_2: 'každú minútu',
|
||||
SCHEDULE_TIMER_3: 'každú hodinu',
|
||||
CUSTOM_ENTITIES: 'Vlastné entity',
|
||||
ENTITIES_HELP_1: 'Získavanie vlastných entít zo zbernice EMS',
|
||||
ENTITIES_UPDATED: 'Aktualizované entity',
|
||||
@@ -312,9 +297,7 @@ const sk: Translation = {
|
||||
ACTIVELOW: 'Aktívny Nízky',
|
||||
UNCHANGED: 'Nezmenené',
|
||||
ALWAYS: 'Vždy',
|
||||
ACTIVITY: 'Aktivita',
|
||||
CONFIGURE: 'Konfigurácia {0}',
|
||||
SYSTEM_MEMORY: 'Systémová pamäť',
|
||||
APPLICATION_SETTINGS_1: 'Zmeniť nastavenia aplikácie EMS-ESP',
|
||||
SECURITY_1: 'Pridať, alebo odstrániť používateľov',
|
||||
DOWNLOAD_UPLOAD_1: 'Stiahnuť a nahrať nastavenia a firmware',
|
||||
@@ -326,7 +309,6 @@ const sk: Translation = {
|
||||
RENAME: 'Premenovať',
|
||||
ENABLE_MODBUS: 'Povoliť Modbus',
|
||||
VIEW_LOG: 'Zobrazte log na diagnostiku problémov',
|
||||
UPLOAD_DRAG: 'presuňte sem súbor alebo ho kliknutím vyberte',
|
||||
SERVICES: 'Služby',
|
||||
ALLVALUES: 'Všetky hodnoty',
|
||||
SPECIAL_FUNCTIONS: 'Špeciálne funkcie',
|
||||
@@ -352,10 +334,8 @@ const sk: Translation = {
|
||||
THIS_VERSION: 'Táto verzia',
|
||||
PLATFORM: 'Platforma',
|
||||
RELEASE_TYPE: 'Typ vydania',
|
||||
INTERNET_CONNECTION_REQUIRED: 'Internetové pripojenie je potrebné pre automatickú kontrolu a aktualizáciu',
|
||||
SWITCH_RELEASE_TYPE: 'Prepnúť na {0} verziu',
|
||||
FIRMWARE_VERSION_INFO: 'Informácie o verzii firmware',
|
||||
NO_DATA: 'Žiadne dáta',
|
||||
USER_PROFILE: 'Profil používateľa',
|
||||
STORED_VERSIONS: 'Uložené verzie',
|
||||
ONLINE_HELP: 'online pomoc',
|
||||
@@ -366,9 +346,10 @@ const sk: Translation = {
|
||||
TEST_EMAIL_SUCCESSFUL: 'Test email bol úspešne odoslaný',
|
||||
SYSTEM_NAME: 'Názov systému',
|
||||
COMMANDS: 'Príkazy',
|
||||
COMMANDS_UPDATED: 'Príkazy aktualizované',
|
||||
COMMANDS_HELP_1: 'Definujte vlastné príkazy pre magistrali EMS',
|
||||
EXECUTE_COMMAND_SENT: 'Príkaz odoslaný',
|
||||
COMMAND_EXECUTED: 'Príkaz vykonaný úspešne',
|
||||
RUN: 'Volať príkaz',
|
||||
INTERNET_CONNECTION_CHECK: 'Kontrola online pre dostupné verzie'
|
||||
};
|
||||
|
||||
export default sk;
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { Translation } from '../i18n-types';
|
||||
const sv: Translation = {
|
||||
LANGUAGE: 'Språk',
|
||||
RETRY: 'Försök igen',
|
||||
LOADING: 'Laddar',
|
||||
IS_REQUIRED: '{0} Krävs',
|
||||
SIGN_IN: 'Logga in',
|
||||
SIGN_OUT: 'Logga ut',
|
||||
@@ -37,13 +36,11 @@ const sv: Translation = {
|
||||
VALUE: '{{värde|Värde}}',
|
||||
DEVICES: 'Enheter',
|
||||
SENSORS: 'Sensorer',
|
||||
RUN_COMMAND: 'Kör kommando',
|
||||
CHANGE_VALUE: 'Ändra värde',
|
||||
CANCEL: 'Avbryt',
|
||||
REMOVE_ALL: 'Ta bort allt',
|
||||
APPLY_CHANGES: 'Utför ändringar ({0})',
|
||||
UPDATE: 'Uppdatera',
|
||||
EXECUTE: 'Utför',
|
||||
REMOVE: 'Ta bort',
|
||||
PROBLEM_UPDATING: 'Problem vid uppdatering',
|
||||
PROBLEM_LOADING: 'Problem vid hämtning',
|
||||
@@ -52,8 +49,6 @@ const sv: Translation = {
|
||||
SETTINGS: 'Inställningar',
|
||||
UPDATED_OF: '{0} Uppdaterad',
|
||||
UPDATE_OF: '{0} Uppdatera',
|
||||
REMOVED_OF: '{0} Raderad',
|
||||
DELETION_OF: '{0} Radering',
|
||||
OFFSET: 'Offset',
|
||||
FACTOR: 'Faktor',
|
||||
FREQ: 'Frekvens',
|
||||
@@ -66,17 +61,14 @@ const sv: Translation = {
|
||||
TEMP_SENSORS: 'Temperatursensorer',
|
||||
WRITE_CMD_SENT: 'Skrivkommandon skickade',
|
||||
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 på att du vill starta en full genomsökning av EMS-bussen?',
|
||||
DATA_TRAFFIC: 'Datatrafik',
|
||||
EMS_DEVICE: 'EMS enhet',
|
||||
SUCCESS: 'Lyckades',
|
||||
FAIL: 'Misslyckades',
|
||||
QUALITY: 'Kvalitet',
|
||||
SCAN: 'Sök',
|
||||
STATUS_NAMES: [
|
||||
'EMS-telegram (Rx)',
|
||||
'EMS-läsningar (Tx)',
|
||||
@@ -127,7 +119,6 @@ const sv: Translation = {
|
||||
ENUM_FORMAT: 'Enum-format API/MQTT',
|
||||
INDEX: 'Index',
|
||||
ENABLE_PARASITE: 'Aktivera 1-wire parasitström',
|
||||
LOGGING: 'Loggning',
|
||||
LOG_HEX: 'Logga EMS-telegram i hexadecimalformat',
|
||||
ENABLE_SYSLOG: 'Aktivera Syslog',
|
||||
LOG_LEVEL: 'Loggnivå',
|
||||
@@ -172,7 +163,6 @@ const sv: Translation = {
|
||||
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 på att du vill fabriksåterställa enheten?',
|
||||
AVAILABLE_VERSION: 'Senaste tillgängliga versioner',
|
||||
STABLE: 'Stabil',
|
||||
@@ -257,9 +247,7 @@ const sv: Translation = {
|
||||
NETWORK_SCAN: 'Sök efter WiFi-nätverk',
|
||||
IDLE: 'Vilande',
|
||||
LOST: 'Förlorad',
|
||||
SCANNING: 'Söker',
|
||||
SCAN_AGAIN: 'Sök igen',
|
||||
NETWORK_SCANNER: 'Hittade nätverk',
|
||||
NETWORK_NO_WIFI: 'Inga WiFi-nätverk hittades',
|
||||
NETWORK_BLANK_BSSID: 'lämna blankt för att bara använda SSID',
|
||||
TX_POWER: 'Tx effekt',
|
||||
@@ -294,9 +282,6 @@ const sv: Translation = {
|
||||
ONCHANGE: 'Vid förändring',
|
||||
CONDITION: 'Villkor',
|
||||
SCHEDULE_UPDATED: 'Schema uppdaterat',
|
||||
SCHEDULE_TIMER_1: 'vid uppstart',
|
||||
SCHEDULE_TIMER_2: 'varje minut',
|
||||
SCHEDULE_TIMER_3: 'varje timme',
|
||||
CUSTOM_ENTITIES: 'Anpassade entiteter',
|
||||
ENTITIES_HELP_1: 'Hämta anpassade entiteter från EMS bussen',
|
||||
ENTITIES_UPDATED: 'Entiteter uppdaterade',
|
||||
@@ -312,9 +297,7 @@ const sv: Translation = {
|
||||
ACTIVELOW: 'Aktivt låg',
|
||||
UNCHANGED: 'Oändrad',
|
||||
ALWAYS: 'Alltid',
|
||||
ACTIVITY: 'Aktivitet',
|
||||
CONFIGURE: 'Konfigurera {0}',
|
||||
SYSTEM_MEMORY: 'Systemminne',
|
||||
APPLICATION_SETTINGS_1: 'Ändra EMS-ESP Applikationsinställningar',
|
||||
SECURITY_1: 'Lägg till eller ta bort användare',
|
||||
DOWNLOAD_UPLOAD_1: 'Ladda ner eller ladda upp inställningar och firmware',
|
||||
@@ -326,7 +309,6 @@ const sv: Translation = {
|
||||
RENAME: 'Byt namn',
|
||||
ENABLE_MODBUS: 'Aktivera Modbus',
|
||||
VIEW_LOG: 'Titta i loggen för att felsöka problem',
|
||||
UPLOAD_DRAG: 'dra och släpp en fil här eller klicka för att välja en',
|
||||
SERVICES: 'Tjänster',
|
||||
ALLVALUES: 'Alla värden',
|
||||
SPECIAL_FUNCTIONS: 'Specialfunktioner',
|
||||
@@ -352,10 +334,8 @@ const sv: Translation = {
|
||||
THIS_VERSION: 'Denna version',
|
||||
PLATFORM: 'Plattform',
|
||||
RELEASE_TYPE: 'Utgivelsestyp',
|
||||
INTERNET_CONNECTION_REQUIRED: 'Internetanslutning krävs för automatisk version kontroll och uppdatering',
|
||||
SWITCH_RELEASE_TYPE: 'Byt till {0} utgåva',
|
||||
FIRMWARE_VERSION_INFO: 'Information om firmwareversion',
|
||||
NO_DATA: 'Ingen data',
|
||||
USER_PROFILE: 'Användarprofil',
|
||||
STORED_VERSIONS: 'Lagrad versioner',
|
||||
ONLINE_HELP: 'online hjälp',
|
||||
@@ -366,9 +346,10 @@ const sv: Translation = {
|
||||
TEST_EMAIL_SUCCESSFUL: 'Test email skickad lyckades',
|
||||
SYSTEM_NAME: 'Systemnamn',
|
||||
COMMANDS: 'Kommandon',
|
||||
COMMANDS_UPDATED: 'Kommandon uppdaterade',
|
||||
COMMANDS_HELP_1: 'Definiera egna kommandon för EMS-Magistral',
|
||||
EXECUTE_COMMAND_SENT: 'Kommando skickat',
|
||||
COMMAND_EXECUTED: 'Kommando kört lyckades',
|
||||
RUN: 'Kör kommando',
|
||||
INTERNET_CONNECTION_CHECK: 'Kontrollerar online för tillgängliga versioner'
|
||||
};
|
||||
|
||||
export default sv;
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { Translation } from '../i18n-types';
|
||||
const tr: Translation = {
|
||||
LANGUAGE: 'Dil',
|
||||
RETRY: 'Tekrar Dene',
|
||||
LOADING: 'Yükleniyor',
|
||||
IS_REQUIRED: '{0} gerekiyor',
|
||||
SIGN_IN: 'Giriş Yap',
|
||||
SIGN_OUT: 'Çıkış Yap',
|
||||
@@ -37,13 +36,11 @@ const tr: Translation = {
|
||||
VALUE: '{{değer|Değer}}',
|
||||
DEVICES: 'Cihazlar',
|
||||
SENSORS: 'Sensörler',
|
||||
RUN_COMMAND: 'Çalıştırma Komutu',
|
||||
CHANGE_VALUE: 'Değeri Değiştir',
|
||||
CANCEL: 'İptal',
|
||||
REMOVE_ALL: 'Tümünü Kaldır',
|
||||
APPLY_CHANGES: 'Değişiklikleri Uygula ({0})',
|
||||
UPDATE: 'Update',
|
||||
EXECUTE: 'Uygulamak',
|
||||
REMOVE: 'Kaldır',
|
||||
PROBLEM_UPDATING: 'Güncelleme Sorunu',
|
||||
PROBLEM_LOADING: 'Yükleme Sorunu',
|
||||
@@ -52,8 +49,6 @@ const tr: Translation = {
|
||||
SETTINGS: 'Ayarlar',
|
||||
UPDATED_OF: '{0} Güncellendi',
|
||||
UPDATE_OF: '{0} Güncelleme',
|
||||
REMOVED_OF: '{0} Kaldırıldı',
|
||||
DELETION_OF: '{0} Silindi',
|
||||
OFFSET: 'Fark',
|
||||
FACTOR: 'Etmen',
|
||||
FREQ: 'Frekans',
|
||||
@@ -66,17 +61,14 @@ const tr: Translation = {
|
||||
TEMP_SENSORS: 'Sıcaklık Sensörleri',
|
||||
WRITE_CMD_SENT: 'Yazma komutu gönderildi',
|
||||
EMS_BUS_WARNING: 'EMS hat bağlantısı kesildi. Eğer bu uyarı birkaç saniye sonra devam ediyorsa lütfen ayarları ve kart tipini kontrol edin',
|
||||
EMS_BUS_SCANNING: 'EMS cihazları aranıyor...',
|
||||
CONNECTED: 'Bağlandı',
|
||||
TX_ISSUES: 'Tx sorunu - başka bir Tx Modu deneyin',
|
||||
DISCONNECTED: 'Bağlantı kesildi',
|
||||
EMS_SCAN: 'EMS Hattında tam bir cihaz taraması başlatmak istediğinizden emin misiniz?',
|
||||
DATA_TRAFFIC: 'Veri trafiği',
|
||||
EMS_DEVICE: 'EMS Cihazı',
|
||||
SUCCESS: 'BAŞARILI',
|
||||
FAIL: 'HATA',
|
||||
QUALITY: 'KALİTE',
|
||||
SCAN: 'Tara',
|
||||
STATUS_NAMES: [
|
||||
'EMS Telegramlar Alındı (Rx)',
|
||||
'EMS Okuması (Tx)',
|
||||
@@ -127,7 +119,6 @@ const tr: Translation = {
|
||||
ENUM_FORMAT: 'Enum Biçimleme API/MQTT',
|
||||
INDEX: 'İndeks',
|
||||
ENABLE_PARASITE: '1-wire parazit gücü devreye al',
|
||||
LOGGING: 'Kayıt ediliyor',
|
||||
LOG_HEX: 'EMS telegramlarını hexadecimal olarak kayıt et',
|
||||
ENABLE_SYSLOG: 'Sistem Kaydını Devreye Al',
|
||||
LOG_LEVEL: 'Kayıt Seviyesi',
|
||||
@@ -172,7 +163,6 @@ const tr: Translation = {
|
||||
CLOSE: 'Kapat',
|
||||
USE: 'KUllan',
|
||||
FACTORY_RESET: 'Fabrika ayarına dönme',
|
||||
SYSTEM_FACTORY_TEXT: 'Cihaz fabrika ayarlarına döndü ve şimdi yendiden başlatılacak',
|
||||
SYSTEM_FACTORY_TEXT_DIALOG: 'Cihazı fabrika ayarlarına döndürmek istediğinize emin misiniz?',
|
||||
AVAILABLE_VERSION: 'En son kullanılabilir sürümler',
|
||||
STABLE: 'Kararlı',
|
||||
@@ -257,9 +247,7 @@ const tr: Translation = {
|
||||
NETWORK_SCAN: 'Kablosuz Ağları Tara',
|
||||
IDLE: 'Serbest',
|
||||
LOST: 'Kayıp',
|
||||
SCANNING: 'Tarıyor',
|
||||
SCAN_AGAIN: 'Tekrar tara',
|
||||
NETWORK_SCANNER: 'Ağ Tarayıcısı',
|
||||
NETWORK_NO_WIFI: 'Hiçbir Kablosuz Ağ bulunamadı',
|
||||
NETWORK_BLANK_BSSID: 'sadece SSID kullanmak için boş bırakın',
|
||||
TX_POWER: 'Aktarım gücü',
|
||||
@@ -294,9 +282,6 @@ const tr: Translation = {
|
||||
ONCHANGE: 'Değişimde',
|
||||
CONDITION: 'Durum',
|
||||
SCHEDULE_UPDATED: 'Zamanlama güncellendi',
|
||||
SCHEDULE_TIMER_1: 'Başlangıçta',
|
||||
SCHEDULE_TIMER_2: 'her dakikada',
|
||||
SCHEDULE_TIMER_3: 'her saatte',
|
||||
CUSTOM_ENTITIES: 'Özel Varlıklar',
|
||||
ENTITIES_HELP_1: 'Özel varlıkları EMS hattından alın',
|
||||
ENTITIES_UPDATED: 'Varlıklar güncellendi',
|
||||
@@ -312,9 +297,7 @@ const tr: Translation = {
|
||||
ACTIVELOW: 'Aktif Düşük',
|
||||
UNCHANGED: 'Değiştirilmedi',
|
||||
ALWAYS: 'Her zaman',
|
||||
ACTIVITY: 'Faaliyet',
|
||||
CONFIGURE: 'Yapılandır {0}',
|
||||
SYSTEM_MEMORY: 'Sistem Belleği',
|
||||
APPLICATION_SETTINGS_1: 'EMS-ESP Uygulama Ayarlarını Değiştir',
|
||||
SECURITY_1: 'Kullanıcıları ekle veya kaldır',
|
||||
DOWNLOAD_UPLOAD_1: 'Ayarları ve firmware dosyasını indir ve yükle',
|
||||
@@ -326,7 +309,6 @@ const tr: Translation = {
|
||||
RENAME: 'Yeniden Adlandır',
|
||||
ENABLE_MODBUS: 'Modbus\'ı etkinleştir',
|
||||
VIEW_LOG: 'Sorunu tanımlamak için günlüğü görüntüleyin',
|
||||
UPLOAD_DRAG: 'Bir dosya buraya sürükleyip bırakın veya seçmek için tıklayın',
|
||||
SERVICES: 'Hizmetler',
|
||||
ALLVALUES: 'Tüm Değerler',
|
||||
SPECIAL_FUNCTIONS: 'Özel Fonksiyonlar',
|
||||
@@ -352,10 +334,8 @@ const tr: Translation = {
|
||||
THIS_VERSION: 'Bu Sürüm',
|
||||
PLATFORM: 'Platforma',
|
||||
RELEASE_TYPE: 'Sürüm Tipi',
|
||||
INTERNET_CONNECTION_REQUIRED: 'Otomatik sürüm kontrolü ve güncelleme için internet bağlantısı gereklidir',
|
||||
SWITCH_RELEASE_TYPE: '{0} sürümüne geç',
|
||||
FIRMWARE_VERSION_INFO: 'Firmware Sürüm Bilgisi',
|
||||
NO_DATA: 'Hiçbir veri yok',
|
||||
USER_PROFILE: 'Kullanıcı Profili',
|
||||
STORED_VERSIONS: 'Kaydedilmiş Sürümler',
|
||||
ONLINE_HELP: 'online yardım',
|
||||
@@ -366,9 +346,10 @@ const tr: Translation = {
|
||||
TEST_EMAIL_SUCCESSFUL: 'Test email başarıyla gönderildi',
|
||||
SYSTEM_NAME: 'Sistem Adı',
|
||||
COMMANDS: 'Komutlar',
|
||||
COMMANDS_UPDATED: 'Komutlar güncellendi',
|
||||
COMMANDS_HELP_1: 'Özel komutları EMS hattına tanımlayın',
|
||||
EXECUTE_COMMAND_SENT: 'Komut gönderildi',
|
||||
COMMAND_EXECUTED: 'Komut başarıyla çalıştırıldı',
|
||||
RUN: 'Komutu çalıştır',
|
||||
INTERNET_CONNECTION_CHECK: 'Online bağlantısı kontrol ediliyor'
|
||||
};
|
||||
|
||||
export default tr;
|
||||
|
||||
@@ -54,6 +54,7 @@ export interface NetworkSettingsType {
|
||||
enableMDNS: boolean;
|
||||
enableCORS: boolean;
|
||||
CORSOrigin: string;
|
||||
eth_10mbit: boolean;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Types for the `getVersions` action response coming from the device.
|
||||
// The device proxies the request to emsesp.org/versions.json. If the device
|
||||
// is offline the `stable` and `dev` fields are omitted.
|
||||
// is offline the `stable` and `dev` fields are omitted, but `system_name` is
|
||||
// always sent as it comes from the device itself.
|
||||
|
||||
export interface VersionInfo {
|
||||
version: string;
|
||||
@@ -20,4 +21,5 @@ export interface VersionsResponse {
|
||||
current: CurrentVersionInfo;
|
||||
stable?: RemoteVersionInfo;
|
||||
dev?: RemoteVersionInfo;
|
||||
system_name?: string;
|
||||
}
|
||||
|
||||
+27
-17
@@ -2,6 +2,7 @@ import preact from '@preact/preset-vite';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { visualizer } from 'rollup-plugin-visualizer';
|
||||
import { pathToFileURL } from 'url';
|
||||
import { Plugin, PluginOption, defineConfig } from 'vite';
|
||||
import viteImagemin from 'vite-plugin-imagemin';
|
||||
import zlib from 'zlib';
|
||||
@@ -178,24 +179,20 @@ const createProductionTerserOptions = () => ({
|
||||
toplevel: true
|
||||
});
|
||||
|
||||
// Image optimization plugin
|
||||
// Image optimization plugin.
|
||||
// Only svgo runs here: the raster optimizers (optipng, pngquant, gifsicle,
|
||||
// jpegtran, mozjpeg) are native binaries whose install scripts are blocked by
|
||||
// `allowBuilds` in pnpm-workspace.yaml, so they would fail on every build.
|
||||
// Raster assets are committed pre-optimized instead.
|
||||
const imageOptimizationPlugin = {
|
||||
...viteImagemin({
|
||||
verbose: false,
|
||||
gifsicle: {
|
||||
optimizationLevel: 7,
|
||||
interlaced: false
|
||||
},
|
||||
optipng: {
|
||||
optimizationLevel: 7
|
||||
},
|
||||
mozjpeg: {
|
||||
quality: 20
|
||||
},
|
||||
pngquant: {
|
||||
quality: [0.8, 0.9],
|
||||
speed: 4
|
||||
},
|
||||
filter: /\.svg$/i,
|
||||
gifsicle: false,
|
||||
jpegTran: false,
|
||||
mozjpeg: false,
|
||||
optipng: false,
|
||||
pngquant: false,
|
||||
svgo: {
|
||||
plugins: [
|
||||
{ name: 'removeViewBox' },
|
||||
@@ -210,8 +207,15 @@ export default defineConfig(
|
||||
async ({ command, mode }: { command: string; mode: string }) => {
|
||||
if (command === 'serve') {
|
||||
console.log(`Preparing for standalone build with server, mode=${mode}`);
|
||||
// @ts-expect-error - mock server doesn't have type declarations
|
||||
const { default: mockServer } = await import('../mock-api/mockServer.js');
|
||||
// Imported through a computed URL so the config bundler leaves it as a real
|
||||
// runtime import: mock-api is a separate package with its own node_modules,
|
||||
// and inlining it here would drag its dependencies into every build.
|
||||
const mockServerUrl = pathToFileURL(
|
||||
path.resolve(import.meta.dirname, '../mock-api/mockServer.js')
|
||||
).href;
|
||||
const { default: mockServer } = (await import(
|
||||
/* @vite-ignore */ mockServerUrl
|
||||
)) as { default: () => PluginOption };
|
||||
return {
|
||||
plugins: [...createBasePlugins(true, true), mockServer()],
|
||||
resolve: {
|
||||
@@ -253,6 +257,9 @@ export default defineConfig(
|
||||
minify: 'terser' as const,
|
||||
terserOptions: createHostedTerserOptions(),
|
||||
rollupOptions: {
|
||||
checks: {
|
||||
pluginTimings: false
|
||||
},
|
||||
treeshake: {
|
||||
moduleSideEffects: false
|
||||
},
|
||||
@@ -288,6 +295,9 @@ export default defineConfig(
|
||||
minify: 'terser' as const,
|
||||
terserOptions: createProductionTerserOptions(),
|
||||
rollupOptions: {
|
||||
checks: {
|
||||
pluginTimings: false
|
||||
},
|
||||
treeshake: {
|
||||
moduleSideEffects: false,
|
||||
propertyReadSideEffects: false as const,
|
||||
|
||||
@@ -22,11 +22,16 @@
|
||||
#define NTP_SETTINGS_FILE "/config/ntpSettings.json"
|
||||
#define EMSESP_SETTINGS_FILE "/config/emsespSettings.json"
|
||||
|
||||
// AP provision modes, mirrors src/ESP32React/APSettingsService.h
|
||||
#define AP_MODE_DISCONNECTED 1
|
||||
#define AP_MODE_NEVER 2
|
||||
|
||||
class DummySettings {
|
||||
public:
|
||||
// SYSTEM
|
||||
bool bandwidth20 = false;
|
||||
bool nosleep = true;
|
||||
bool eth_10mbit = false;
|
||||
|
||||
// MQTT
|
||||
uint16_t publish_time = 10;
|
||||
@@ -81,7 +86,7 @@ class DummySettings {
|
||||
String password = "ems-esp";
|
||||
|
||||
// AP
|
||||
uint8_t provisionMode = 0;
|
||||
uint8_t provisionMode = AP_MODE_DISCONNECTED; // always
|
||||
|
||||
// NTP
|
||||
String server = "pool.ntp.org";
|
||||
|
||||
@@ -64,9 +64,11 @@ class ESP_SSLClient : public Stream {
|
||||
}
|
||||
void flush() override {
|
||||
}
|
||||
|
||||
void stop() {
|
||||
}
|
||||
void setTimeout(uint32_t) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
#endif // ESP_SSLClient_h
|
||||
|
||||
@@ -39,7 +39,11 @@ class WiFiClient {
|
||||
}
|
||||
void stop() {
|
||||
}
|
||||
|
||||
void setTimeout(uint32_t){
|
||||
|
||||
}
|
||||
void setConnectionTimeout(uint32_t) {
|
||||
}
|
||||
// ESP32 socket option passthrough (e.g. TCP_NODELAY)
|
||||
int setSocketOption(int, int, const void *, size_t) {
|
||||
return 0;
|
||||
|
||||
@@ -18,5 +18,5 @@
|
||||
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
||||
"prettier": "^3.9.6"
|
||||
},
|
||||
"packageManager": "pnpm@11.18.0+sha512.33d83c77da82f49fba836925c6f1b841181ec3132b670639bd012f7075f5c7cf634c5f870147c19aae7478fac01df09d8892e880454896edd23ee9b33757563c"
|
||||
"packageManager": "pnpm@11.25.0+sha512.5cde925b4f075f725eb71fbae18a42ffe784524789f19b61c731cb8721ec28aaee160e01a8d5af4fedb2a42cdbf300efe23db356b0d4a17b4d63e11f8ab7c956"
|
||||
}
|
||||
|
||||
Generated
+5
-5
@@ -71,8 +71,8 @@ packages:
|
||||
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5':
|
||||
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||
'@jridgewell/sourcemap-codec@1.6.0':
|
||||
resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.31':
|
||||
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
||||
@@ -227,17 +227,17 @@ snapshots:
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.13':
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
'@jridgewell/sourcemap-codec': 1.6.0
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2': {}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||
'@jridgewell/sourcemap-codec@1.6.0': {}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.31':
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
'@jridgewell/sourcemap-codec': 1.6.0
|
||||
|
||||
'@msgpack/msgpack@3.1.3': {}
|
||||
|
||||
|
||||
+37
-23
@@ -65,7 +65,16 @@ let settings = {
|
||||
modbus_max_clients: 10,
|
||||
modbus_timeout: 10000,
|
||||
developer_mode: true,
|
||||
disable_reset: false
|
||||
disable_reset: false,
|
||||
email_enabled: false,
|
||||
email_security: 2,
|
||||
email_server: 'smtp.gmail.com',
|
||||
email_port: 587,
|
||||
email_login: 'test@emsesp.org',
|
||||
email_pass: 'password',
|
||||
email_sender: 'test@emsesp.org',
|
||||
email_recp: 'test@emsesp.org',
|
||||
email_subject: 'ems-esp notification'
|
||||
};
|
||||
|
||||
// EMS-ESP System Settings
|
||||
@@ -86,9 +95,9 @@ let system_status = {
|
||||
// network_status: 10, // ethernet connected
|
||||
// network_status: 6, // wifi disconnected
|
||||
wifi_rssi: -41,
|
||||
esp_platform: 'ESP32S3',
|
||||
esp_platform: 'ESP32',
|
||||
build_flags: 'DEMO',
|
||||
cpu_type: 'ESP32-S3',
|
||||
cpu_type: 'ESP32',
|
||||
cpu_rev: 0,
|
||||
cpu_cores: 2,
|
||||
cpu_freq_mhz: 240,
|
||||
@@ -193,8 +202,8 @@ system_status.partitions[0].version = THIS_VERSION; // app0
|
||||
|
||||
// set the ESP platform - using ESP32 will disable OTA and automatic version downloading
|
||||
let emulate_esp: string;
|
||||
emulate_esp = 'ESP32S3';
|
||||
// emulate_esp = 'ESP32';
|
||||
// emulate_esp = 'ESP32S3';
|
||||
emulate_esp = 'ESP32';
|
||||
|
||||
switch (emulate_esp) {
|
||||
// ESP32 4MB
|
||||
@@ -320,10 +329,10 @@ function updateMask(entity: any, de: any, dd: any) {
|
||||
const old_custom_name = dd.nodes[dd_objIndex].cn;
|
||||
console.log(
|
||||
'comparing names, old (' +
|
||||
old_custom_name +
|
||||
') with new (' +
|
||||
new_custom_name +
|
||||
')'
|
||||
old_custom_name +
|
||||
') with new (' +
|
||||
new_custom_name +
|
||||
')'
|
||||
);
|
||||
if (old_custom_name !== new_custom_name) {
|
||||
changed = true;
|
||||
@@ -440,9 +449,9 @@ function upgradeImportantMessages(version: string) {
|
||||
|
||||
console.log(
|
||||
'upgradeImportantMessageType: version=' +
|
||||
version +
|
||||
' type=' +
|
||||
upgradeImportantMessageType_n
|
||||
version +
|
||||
' type=' +
|
||||
upgradeImportantMessageType_n
|
||||
);
|
||||
return { upgradeImportantMessageType: upgradeImportantMessageType_n };
|
||||
}
|
||||
@@ -450,6 +459,8 @@ function upgradeImportantMessages(version: string) {
|
||||
// called by Action endpoint getVersions
|
||||
// Set MOCK_OFFLINE = true to simulate a device with no internet (omits stable/dev).
|
||||
const MOCK_OFFLINE = false;
|
||||
// const MOCK_OFFLINE = true;
|
||||
|
||||
function get_versions() {
|
||||
const isDev = THIS_VERSION.includes('dev');
|
||||
const currentUpgradeable =
|
||||
@@ -457,6 +468,7 @@ function get_versions() {
|
||||
(isDev ? DEV_VERSION_IS_UPGRADEABLE : STABLE_VERSION_IS_UPGRADEABLE);
|
||||
|
||||
const data: {
|
||||
system_name: string;
|
||||
current: {
|
||||
version: string;
|
||||
type: 'stable' | 'dev';
|
||||
@@ -466,6 +478,7 @@ function get_versions() {
|
||||
stable?: { version: string; date: string; upgradeable: boolean };
|
||||
dev?: { version: string; date: string; upgradeable: boolean };
|
||||
} = {
|
||||
system_name: settings.system_name,
|
||||
current: {
|
||||
version: THIS_VERSION,
|
||||
type: isDev ? 'dev' : 'stable',
|
||||
@@ -489,17 +502,17 @@ function get_versions() {
|
||||
|
||||
console.log(
|
||||
'getVersions: current=' +
|
||||
THIS_VERSION +
|
||||
' stable=' +
|
||||
LATEST_STABLE_VERSION +
|
||||
' (upgradeable=' +
|
||||
(STABLE_VERSION_IS_UPGRADEABLE ? 'YES' : 'NO') +
|
||||
') dev=' +
|
||||
LATEST_DEV_VERSION +
|
||||
' (upgradeable=' +
|
||||
(DEV_VERSION_IS_UPGRADEABLE ? 'YES' : 'NO') +
|
||||
')' +
|
||||
(MOCK_OFFLINE ? ' [offline]' : '')
|
||||
THIS_VERSION +
|
||||
' stable=' +
|
||||
LATEST_STABLE_VERSION +
|
||||
' (upgradeable=' +
|
||||
(STABLE_VERSION_IS_UPGRADEABLE ? 'YES' : 'NO') +
|
||||
') dev=' +
|
||||
LATEST_DEV_VERSION +
|
||||
' (upgradeable=' +
|
||||
(DEV_VERSION_IS_UPGRADEABLE ? 'YES' : 'NO') +
|
||||
')' +
|
||||
(MOCK_OFFLINE ? ' [offline]' : '')
|
||||
);
|
||||
return data;
|
||||
}
|
||||
@@ -569,6 +582,7 @@ let network_settings = {
|
||||
enableMDNS: true,
|
||||
enableCORS: false,
|
||||
CORSOrigin: '*',
|
||||
eth_10mbit: false,
|
||||
local_ip: '',
|
||||
gateway_ip: '',
|
||||
subnet_mask: '',
|
||||
|
||||
+2
-1
@@ -196,9 +196,10 @@ lib_ldf_mode = off
|
||||
lib_deps =
|
||||
|
||||
; unit tests
|
||||
; The test code is in ./test/test_api.cpp and the test_api.h file is created by the native-test-create environment.
|
||||
; The test code is in ./test/test_api.cpp and the test_api.h file is generated by the native-test-create environment.
|
||||
; to run use `platformio run -e native-test -t exec`. All tests should PASS.
|
||||
; tip: use https://jsondiff.com/ to compare the expected and actual responses.
|
||||
; to update the test output run `python3 scripts/generate_test_api.py`
|
||||
[env:native-test]
|
||||
platform = native
|
||||
test_build_src = true
|
||||
|
||||
+5
-1
@@ -1343,4 +1343,8 @@ optimisations
|
||||
IILE
|
||||
Sumr
|
||||
eraseap
|
||||
roomtempoffset
|
||||
roomtempoffset
|
||||
dhcpc
|
||||
wifioff
|
||||
autonegotiation
|
||||
Mbit
|
||||
@@ -7,7 +7,7 @@ Workflow:
|
||||
3. Write that block to test/test_api/test_api.h.
|
||||
4. Run `pio run -e native-test -t exec`.
|
||||
|
||||
run with `python3 scripts/generate_test_api.py`
|
||||
run with `python3 ./scripts/generate_test_api.py`
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
|
||||
@@ -31,7 +31,8 @@ StateUpdateResult APSettings::update(JsonObject root, APSettings & settings) {
|
||||
APSettings newSettings{};
|
||||
newSettings.provisionMode = static_cast<uint8_t>(root["provision_mode"] | FACTORY_AP_PROVISION_MODE);
|
||||
|
||||
switch (settings.provisionMode) {
|
||||
// coerce removed/invalid modes (e.g. legacy AP_MODE_ALWAYS = 0) to the current default
|
||||
switch (newSettings.provisionMode) {
|
||||
case AP_MODE_DISCONNECTED:
|
||||
case AP_MODE_NEVER:
|
||||
break;
|
||||
@@ -53,11 +54,17 @@ StateUpdateResult APSettings::update(JsonObject root, APSettings & settings) {
|
||||
return StateUpdateResult::UNCHANGED;
|
||||
}
|
||||
|
||||
settings = newSettings;
|
||||
const uint8_t previousProvisionMode = settings.provisionMode;
|
||||
settings = newSettings;
|
||||
|
||||
// if the AP mode has changed, force a disconnect and reconnect
|
||||
if (settings.provisionMode != newSettings.provisionMode) {
|
||||
emsesp::EMSESP::network_.reconnect();
|
||||
// FS load at boot uses this updater before Network::begin(); skip reconnect on that first apply.
|
||||
// Subsequent provision-mode changes (UI/API) must reload Network's cached AP settings.
|
||||
static bool appliedOnce = false;
|
||||
const bool modeChanged = previousProvisionMode != settings.provisionMode;
|
||||
if (appliedOnce && modeChanged) {
|
||||
emsesp::EMSESP::network_.schedule_reconnect();
|
||||
}
|
||||
appliedOnce = true;
|
||||
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ enum APNetworkStatus { ACTIVE = 0, INACTIVE };
|
||||
|
||||
class APSettings {
|
||||
public:
|
||||
uint8_t provisionMode = FACTORY_AP_PROVISION_MODE; // 0 = on, 2 = off
|
||||
uint8_t provisionMode = FACTORY_AP_PROVISION_MODE; // 1 = on disconnect, 2 = off
|
||||
String ssid = FACTORY_AP_SSID;
|
||||
String password = FACTORY_AP_PASSWORD;
|
||||
uint8_t channel = FACTORY_AP_CHANNEL;
|
||||
|
||||
@@ -7,7 +7,7 @@ MqttSettingsService::MqttSettingsService(AsyncWebServer * server, FS * fs, Secur
|
||||
, _fsPersistence(MqttSettings::read, MqttSettings::update, this, fs, MQTT_SETTINGS_FILE)
|
||||
, _reconfigureMqtt(false)
|
||||
, _disconnectedAt(0)
|
||||
, _disconnectReason(espMqttClientTypes::DisconnectReason::TCP_DISCONNECTED)
|
||||
, _disconnectReason(espMqttClientTypes::DisconnectReason::USER_OK)
|
||||
, _mqttClient(nullptr) {
|
||||
addUpdateHandler([this] { onConfigUpdated(); }, false);
|
||||
}
|
||||
@@ -41,6 +41,7 @@ void MqttSettingsService::startClient() {
|
||||
delete _mqttClient;
|
||||
_mqttClient = nullptr;
|
||||
}
|
||||
|
||||
if (_state.enableTLS) {
|
||||
isSecure = true;
|
||||
if (emsesp::EMSESP::system_.PSram() == 0) {
|
||||
@@ -82,7 +83,10 @@ void MqttSettingsService::loop() {
|
||||
_mqttClient->disconnect(true);
|
||||
}
|
||||
if (_reconfigureMqtt || (_disconnectedAt && static_cast<uint32_t>(uuid::get_uptime() - _disconnectedAt) >= MQTT_RECONNECTION_DELAY)) {
|
||||
// reconfigure MQTT client
|
||||
// configure MQTT client
|
||||
if (_reconfigureMqtt) {
|
||||
emsesp::EMSESP::logger().info("Starting MQTT service");
|
||||
}
|
||||
_disconnectedAt = configureMqtt() ? 0 : uuid::get_uptime();
|
||||
_reconfigureMqtt = false;
|
||||
}
|
||||
@@ -129,11 +133,14 @@ void MqttSettingsService::onMqttConnect(bool sessionPresent) {
|
||||
}
|
||||
|
||||
void MqttSettingsService::onMqttDisconnect(espMqttClientTypes::DisconnectReason reason) {
|
||||
_disconnectReason = reason;
|
||||
if (reason != _disconnectReason) {
|
||||
emsesp::EMSESP::mqtt_.on_disconnect(reason);
|
||||
_disconnectReason = reason;
|
||||
}
|
||||
|
||||
if (!_disconnectedAt) {
|
||||
_disconnectedAt = uuid::get_uptime();
|
||||
}
|
||||
emsesp::EMSESP::mqtt_.on_disconnect(reason);
|
||||
}
|
||||
|
||||
void MqttSettingsService::onConfigUpdated() {
|
||||
@@ -147,7 +154,9 @@ void MqttSettingsService::onConfigUpdated() {
|
||||
bool MqttSettingsService::configureMqtt() {
|
||||
// disconnect if already connected
|
||||
if (_mqttClient->connected()) {
|
||||
// emsesp::EMSESP::logger().info("Disconnecting to configure");
|
||||
#if defined(EMSESP_DEBUG)
|
||||
emsesp::EMSESP::logger().debug("Disconnecting to configure");
|
||||
#endif
|
||||
_mqttClient->disconnect(true);
|
||||
}
|
||||
|
||||
@@ -184,7 +193,7 @@ bool MqttSettingsService::configureMqtt() {
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)->setKeepAlive(_state.keepAlive);
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)->setCleanSession(_state.cleanSession);
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)->setWill(will_topic, 1, true, "offline"); // QOS 1, retain
|
||||
return _mqttClient->connect();
|
||||
return (_mqttClient->connect());
|
||||
}
|
||||
static_cast<espMqttClient *>(_mqttClient)->setServer(_state.host.c_str(), _state.port);
|
||||
if (_state.username.length() > 0) {
|
||||
@@ -194,7 +203,7 @@ bool MqttSettingsService::configureMqtt() {
|
||||
static_cast<espMqttClient *>(_mqttClient)->setKeepAlive(_state.keepAlive);
|
||||
static_cast<espMqttClient *>(_mqttClient)->setCleanSession(_state.cleanSession);
|
||||
static_cast<espMqttClient *>(_mqttClient)->setWill(will_topic, 1, true, "offline"); // QOS 1, retain
|
||||
return _mqttClient->connect();
|
||||
return (_mqttClient->connect());
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -77,13 +77,15 @@ void NTPSettings::read(NTPSettings & settings, JsonObject root) {
|
||||
root["enabled"] = settings.enabled;
|
||||
root["server"] = settings.server;
|
||||
root["tz_label"] = settings.tzLabel;
|
||||
root["tz_format"] = settings.tzFormat;
|
||||
root["tz_format"] = settings.tzFormat.substring(0, settings.tzFormat.indexOf(" "));
|
||||
root["thermostat_sync"] = settings.thermostat_sync;
|
||||
root["tz_label_t"] = settings.tzLabelT;
|
||||
root["tz_format_t"] = settings.tzFormatT;
|
||||
root["tz_format_t"] = settings.tzFormatT.substring(0, settings.tzFormatT.indexOf(" "));
|
||||
}
|
||||
|
||||
StateUpdateResult NTPSettings::update(JsonObject root, NTPSettings & settings) {
|
||||
settings.tzFormat.reserve(40);
|
||||
settings.tzFormatT.reserve(40);
|
||||
settings.enabled = root["enabled"] | FACTORY_NTP_ENABLED;
|
||||
settings.server = root["server"] | FACTORY_NTP_SERVER;
|
||||
settings.tzLabel = root["tz_label"] | FACTORY_NTP_TIME_ZONE_LABEL;
|
||||
@@ -91,5 +93,12 @@ StateUpdateResult NTPSettings::update(JsonObject root, NTPSettings & settings) {
|
||||
settings.thermostat_sync = root["thermostat_sync"] | FACTORY_NTP_THERMOSTAT_SYNC;
|
||||
settings.tzLabelT = root["tz_label_t"] | FACTORY_NTP_TIME_ZONE_LABEL;
|
||||
settings.tzFormatT = root["tz_format_t"] | FACTORY_NTP_TIME_ZONE_FORMAT;
|
||||
// The format string must have same length to avoid memory leak, #3184
|
||||
while (settings.tzFormat.length() < 40) {
|
||||
settings.tzFormat += " ";
|
||||
}
|
||||
while (settings.tzFormatT.length() < 40) {
|
||||
settings.tzFormatT += " ";
|
||||
}
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ void NetworkSettings::read(NetworkSettings & settings, JsonObject root) {
|
||||
root["enableCORS"] = settings.enableCORS;
|
||||
root["CORSOrigin"] = settings.CORSOrigin;
|
||||
root["tx_power"] = settings.tx_power;
|
||||
root["eth_10mbit"] = settings.eth_10mbit;
|
||||
|
||||
// extended settings
|
||||
JsonUtils::writeIP(root, "local_ip", settings.localIP);
|
||||
@@ -51,6 +52,7 @@ StateUpdateResult NetworkSettings::update(JsonObject root, NetworkSettings & set
|
||||
settings.enableMDNS = root["enableMDNS"] | true;
|
||||
settings.enableCORS = root["enableCORS"];
|
||||
settings.CORSOrigin = root["CORSOrigin"] | "*";
|
||||
settings.eth_10mbit = root["eth_10mbit"];
|
||||
|
||||
// extended settings
|
||||
JsonUtils::readIP(root, "local_ip", settings.localIP);
|
||||
|
||||
@@ -39,6 +39,7 @@ class NetworkSettings {
|
||||
bool enableMDNS = true;
|
||||
bool enableCORS = false;
|
||||
String CORSOrigin = "*";
|
||||
bool eth_10mbit = false; // force 10BASE-T half-duplex, no auto-negotiation
|
||||
|
||||
// optional configuration for static IP address
|
||||
IPAddress localIP;
|
||||
|
||||
@@ -94,11 +94,14 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri
|
||||
}
|
||||
#endif
|
||||
// it's firmware - initialize the ArduinoOTA updater
|
||||
emsesp::EMSESP::logger().info("Uploading firmware file %s (size: %d KB). Please wait...", filename.c_str(), filesize / 1024);
|
||||
emsesp::EMSESP::logger().info("Firmware uploading (file %s, size: %dKB). Please wait...", filename.c_str(), filesize / 1024);
|
||||
|
||||
// turn off UART to prevent interference with the upload
|
||||
emsesp::EMSuart::stop();
|
||||
|
||||
// tell main loop we're uploading so services as paused (e.g. MQTT)
|
||||
emsesp::EMSESP::system_.systemStatus(emsesp::SYSTEM_STATUS::SYSTEM_STATUS_UPLOADING);
|
||||
|
||||
if (Update.begin(filesize - sizeof(esp_image_header_t))) {
|
||||
if (strlen(_md5.data()) == _md5.size() - 1) {
|
||||
Update.setMD5(_md5.data());
|
||||
@@ -111,7 +114,7 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri
|
||||
}
|
||||
} else if (_is_filesystem) {
|
||||
// LittleFS filesystem image - flash directly to the spiffs/littlefs partition
|
||||
emsesp::EMSESP::logger().info("Uploading filesystem image %s (size: %u KB). Please wait...", filename.c_str(), static_cast<unsigned>(filesize / 1024));
|
||||
emsesp::EMSESP::logger().info("Uploading filesystem file %s (size: %uKB). Please wait...", filename.c_str(), static_cast<unsigned>(filesize / 1024));
|
||||
emsesp::EMSuart::stop();
|
||||
LittleFS.end(); // unmount LittleFS before we overwrite the partition under it
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ void AnalogSensor::start(const bool factory_settings) {
|
||||
Command::add(
|
||||
EMSdevice::DeviceType::ANALOGSENSOR,
|
||||
F_(setvalue),
|
||||
[&](const char * value, const int8_t id, JsonObject output) { return command_setvalue(value, id); },
|
||||
[this](const char * value, const int8_t id, JsonObject) { return command_setvalue(value, id); },
|
||||
FL_(setiovalue_cmd),
|
||||
CommandFlag::ADMIN_ONLY);
|
||||
|
||||
@@ -111,6 +111,12 @@ void AnalogSensor::start(const bool factory_settings) {
|
||||
Mqtt::subscribe(EMSdevice::DeviceType::ANALOGSENSOR, topic, nullptr); // use empty function callback
|
||||
}
|
||||
|
||||
void AnalogSensor::ha_reset() {
|
||||
for (const auto & sensor : sensors_) {
|
||||
remove_ha_topic(sensor.type(), sensor.gpio());
|
||||
}
|
||||
}
|
||||
|
||||
// load settings from the customization file, sorts them and initializes the GPIOs
|
||||
void AnalogSensor::reload(bool get_nvs) {
|
||||
exclude_types_.clear();
|
||||
@@ -195,7 +201,7 @@ void AnalogSensor::reload(bool get_nvs) {
|
||||
Command::add(
|
||||
EMSdevice::DeviceType::ANALOGSENSOR,
|
||||
sensor.name,
|
||||
[&](const char * value, const int8_t id, JsonObject output) { return command_setvalue(value, sensor.gpio); },
|
||||
[this, gpio = sensor.gpio](const char * value, const int8_t, JsonObject) { return command_setvalue(value, gpio); },
|
||||
sensor.type == AnalogType::COUNTER || (sensor.type >= AnalogType::CNT_0 && sensor.type <= AnalogType::CNT_2) ? FL_(counter)
|
||||
: sensor.type == AnalogType::DIGITAL_OUT ? FL_(digital_out)
|
||||
: sensor.type == AnalogType::RGB ? FL_(RGB)
|
||||
@@ -729,7 +735,7 @@ void AnalogSensor::publish_values(const bool force) {
|
||||
}
|
||||
// don't bother with value template conditions if using Domoticz which doesn't fully support MQTT Discovery
|
||||
if (Mqtt::discovery_type() == Mqtt::discoveryType::HOMEASSISTANT) {
|
||||
config["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + "}}";
|
||||
config["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + " else 'None'}}";
|
||||
} else {
|
||||
config["val_tpl"] = (std::string) "{{" + val_obj + "}}";
|
||||
}
|
||||
|
||||
@@ -144,6 +144,7 @@ class AnalogSensor {
|
||||
void publish_values(const bool force);
|
||||
void reload(bool get_nvs = false);
|
||||
bool updated_values();
|
||||
void ha_reset();
|
||||
|
||||
// return back reference to the sensor list, used by other classes
|
||||
const std::vector<Sensor, AllocatorPSRAM<Sensor>> & sensors() const {
|
||||
|
||||
@@ -492,7 +492,7 @@ uint8_t Command::call(const uint8_t device_type, const char * command, const cha
|
||||
}
|
||||
|
||||
// add a command to the list, which does not return json
|
||||
void Command::add(const uint8_t device_type, const uint8_t device_id, const char * cmd, const cmd_function_p cb, const char * const * description, uint8_t flags) {
|
||||
void Command::add(const uint8_t device_type, const uint8_t device_id, const char * cmd, const cmd_function_p & cb, const char * const * description, uint8_t flags) {
|
||||
// if the command already exists for that device type don't add it
|
||||
if (find_command(device_type, device_id, cmd, flags) != nullptr) {
|
||||
return;
|
||||
@@ -508,13 +508,13 @@ void Command::add(const uint8_t device_type, const uint8_t device_id, const char
|
||||
|
||||
// add a command with no json output
|
||||
// system/temperature/analog devices uses device_id 0
|
||||
void Command::add(const uint8_t device_type, const char * cmd, const cmd_function_p cb, const char * const * description, uint8_t flags) {
|
||||
void Command::add(const uint8_t device_type, const char * cmd, const cmd_function_p & cb, const char * const * description, uint8_t flags) {
|
||||
add(device_type, 0, cmd, cb, description, flags);
|
||||
}
|
||||
|
||||
// add a command to the list, which does return a json object as output
|
||||
// these commands bypass the readonly check (they are actions, not entity setters)
|
||||
void Command::add_json(const uint8_t device_type, const char * cmd, const cmd_function_p cb, const char * const * description, uint8_t flags) {
|
||||
void Command::add_json(const uint8_t device_type, const char * cmd, const cmd_function_p & cb, const char * const * description, uint8_t flags) {
|
||||
// if the command already exists for that device type don't add it
|
||||
if (find_command(device_type, 0, cmd, flags) != nullptr) {
|
||||
return;
|
||||
@@ -828,7 +828,7 @@ uint8_t Command::json_message(uint8_t error_code, const char * message, const Js
|
||||
// e.g. //one/two////three/// becomes /one/two/three
|
||||
std::string SUrlParser::path() {
|
||||
std::string s = "/"; // set up the beginning slash
|
||||
for (const std::string & f : m_folders) {
|
||||
for (const auto & f : m_folders) {
|
||||
s += f;
|
||||
s += "/";
|
||||
}
|
||||
|
||||
+19
-19
@@ -67,13 +67,13 @@ class Command {
|
||||
cmd_function_p cmdfunction_;
|
||||
const char * const * description_;
|
||||
|
||||
CmdFunction(const uint8_t device_type,
|
||||
const uint8_t device_id,
|
||||
const uint8_t flags,
|
||||
const bool has_json_output,
|
||||
const char * cmd,
|
||||
const cmd_function_p cmdfunction,
|
||||
const char * const * description)
|
||||
CmdFunction(const uint8_t device_type,
|
||||
const uint8_t device_id,
|
||||
const uint8_t flags,
|
||||
const bool has_json_output,
|
||||
const char * cmd,
|
||||
const cmd_function_p & cmdfunction,
|
||||
const char * const * description)
|
||||
: device_type_(device_type)
|
||||
, device_id_(device_id)
|
||||
, flags_(flags)
|
||||
@@ -105,22 +105,22 @@ class Command {
|
||||
static uint8_t call(const uint8_t device_type, const char * cmd, const char * value, const int8_t id = -1);
|
||||
|
||||
// with normal call back function taking a value and id
|
||||
static void add(const uint8_t device_type,
|
||||
const uint8_t device_id,
|
||||
const char * cmd,
|
||||
const cmd_function_p cb,
|
||||
const char * const * description,
|
||||
uint8_t flags = CommandFlag::CMD_FLAG_DEFAULT);
|
||||
static void add(const uint8_t device_type,
|
||||
const uint8_t device_id,
|
||||
const char * cmd,
|
||||
const cmd_function_p & cb,
|
||||
const char * const * description,
|
||||
uint8_t flags = CommandFlag::CMD_FLAG_DEFAULT);
|
||||
|
||||
// same for system/temperature/analog devices
|
||||
static void
|
||||
add(const uint8_t device_type, const char * cmd, const cmd_function_p cb, const char * const * description, uint8_t flags = CommandFlag::CMD_FLAG_DEFAULT);
|
||||
add(const uint8_t device_type, const char * cmd, const cmd_function_p & cb, const char * const * description, uint8_t flags = CommandFlag::CMD_FLAG_DEFAULT);
|
||||
// command that writes a JSON object as its output; bypasses the readonly check
|
||||
static void add_json(const uint8_t device_type,
|
||||
const char * cmd,
|
||||
const cmd_function_p cb,
|
||||
const char * const * description,
|
||||
uint8_t flags = CommandFlag::CMD_FLAG_DEFAULT);
|
||||
static void add_json(const uint8_t device_type,
|
||||
const char * cmd,
|
||||
const cmd_function_p & cb,
|
||||
const char * const * description,
|
||||
uint8_t flags = CommandFlag::CMD_FLAG_DEFAULT);
|
||||
|
||||
static void reserve(size_t num) {
|
||||
cmdfunctions_.reserve(num);
|
||||
|
||||
+7
-10
@@ -104,7 +104,7 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
|
||||
shell.printfln("Unknown show command");
|
||||
}
|
||||
},
|
||||
[](Shell const & shell, const std::vector<std::string> & current_arguments, const std::string & next_argument) -> std::vector<std::string> {
|
||||
[](Shell const &, const std::vector<std::string> &, const std::string &) -> std::vector<std::string> {
|
||||
return std::vector<std::string>{"system", "users", "devices", "log", "ems", "values", "mqtt", "commands"};
|
||||
});
|
||||
|
||||
@@ -191,7 +191,7 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
|
||||
CommandFlags::ADMIN,
|
||||
{F_(restart)},
|
||||
{F_(partitionname_optional)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments) {
|
||||
[](Shell &, const std::vector<std::string> & arguments) {
|
||||
if (arguments.size()) {
|
||||
EMSESP::system_.system_restart(arguments.front().c_str());
|
||||
} else {
|
||||
@@ -199,10 +199,9 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
|
||||
}
|
||||
});
|
||||
|
||||
commands->add_command(ShellContext::MAIN,
|
||||
CommandFlags::ADMIN,
|
||||
string_vector{F_(wifi), F_(reconnect)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments) { EMSESP::network_.reconnect(); });
|
||||
commands->add_command(ShellContext::MAIN, CommandFlags::ADMIN, string_vector{F_(wifi), F_(reconnect)}, [](Shell &, const std::vector<std::string> &) {
|
||||
EMSESP::network_.reconnect();
|
||||
});
|
||||
|
||||
//
|
||||
// SET commands
|
||||
@@ -244,7 +243,7 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
|
||||
commands->add_command(ShellContext::MAIN,
|
||||
CommandFlags::ADMIN,
|
||||
string_vector{F_(set), F_(wifi), F_(password)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments) {
|
||||
[](Shell & shell, const std::vector<std::string> &) {
|
||||
shell.enter_password(F_(new_password_prompt1), [](Shell & shell, bool completed, const std::string & password1) {
|
||||
if (completed) {
|
||||
shell.enter_password(F_(new_password_prompt2), [password1](Shell & shell, bool completed, const std::string & password2) {
|
||||
@@ -367,9 +366,7 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
|
||||
// EMS device commands
|
||||
//
|
||||
|
||||
commands->add_command(ShellContext::MAIN, CommandFlags::ADMIN, {F_(scan)}, [](Shell & shell, const std::vector<std::string> & arguments) {
|
||||
EMSESP::scan_devices();
|
||||
});
|
||||
commands->add_command(ShellContext::MAIN, CommandFlags::ADMIN, {F_(scan)}, [](Shell &, const std::vector<std::string> &) { EMSESP::scan_devices(); });
|
||||
|
||||
/* removed scan deep
|
||||
commands->add_command(ShellContext::MAIN, CommandFlags::ADMIN, {F_(scan)}, {F_(deep_optional)}, [](Shell & shell, const std::vector<std::string> & arguments) {
|
||||
|
||||
+77
-73
@@ -52,7 +52,7 @@ bool EMSdevice::has_entities() const {
|
||||
}
|
||||
bool found = false;
|
||||
EMSESP::webCustomizationService.read([&](WebCustomization & settings) {
|
||||
for (EntityCustomization entityCustomization : settings.entityCustomizations) {
|
||||
for (const auto & entityCustomization : settings.entityCustomizations) {
|
||||
if (entityCustomization.device_id == device_id() && entityCustomization.entity_ids.size()) {
|
||||
found = true;
|
||||
break;
|
||||
@@ -564,17 +564,17 @@ void EMSdevice::register_telegram_type(const uint16_t telegram_type_id, const ch
|
||||
|
||||
// add to device value library, also know now as a "device entity"
|
||||
// this function will also apply any customizations to the entity
|
||||
void EMSdevice::add_device_value(int8_t tag, // to be used to group mqtt together, either as separate topics as a nested object
|
||||
void * value_p, // pointer to the value from the .h file
|
||||
uint8_t type, // one of DeviceValueType
|
||||
const char * const ** options, // options for enum, which are translated as a list of lists
|
||||
const char * const * options_single, // list of names
|
||||
int8_t numeric_operator, // to divide or multiply, see DeviceValueNumOps::
|
||||
const char * const * name, // list of names, including shortname and translations
|
||||
uint8_t uom, // unit of measure from DeviceValueUOM
|
||||
const cmd_function_p f, // command function pointer
|
||||
int16_t min, // min allowed value
|
||||
uint32_t max // max allowed value
|
||||
void EMSdevice::add_device_value(int8_t tag, // to be used to group mqtt together, either as separate topics as a nested object
|
||||
void * value_p, // pointer to the value from the .h file
|
||||
uint8_t type, // one of DeviceValueType
|
||||
const char * const ** options, // options for enum, which are translated as a list of lists
|
||||
const char * const * options_single, // list of names
|
||||
int8_t numeric_operator, // to divide or multiply, see DeviceValueNumOps::
|
||||
const char * const * name, // list of names, including shortname and translations
|
||||
uint8_t uom, // unit of measure from DeviceValueUOM
|
||||
const cmd_function_p & f, // command function pointer
|
||||
int16_t min, // min allowed value
|
||||
uint32_t max // max allowed value
|
||||
) {
|
||||
uint8_t state = DeviceValueState::DV_DEFAULT; // determine state
|
||||
auto custom_fullname = std::string(""); // custom fullname
|
||||
@@ -636,7 +636,7 @@ void EMSdevice::add_device_value(int8_t tag, // to b
|
||||
|
||||
// scan through customizations to see if it's on the exclusion list by matching the productID and deviceID
|
||||
EMSESP::webCustomizationService.read([&](WebCustomization & settings) {
|
||||
for (EntityCustomization entityCustomization : settings.entityCustomizations) {
|
||||
for (const auto & entityCustomization : settings.entityCustomizations) {
|
||||
if ((entityCustomization.product_id == product_id()) && (entityCustomization.device_id == device_id())) {
|
||||
char entity[70];
|
||||
if (tag < DeviceValueTAG::TAG_HC1) {
|
||||
@@ -645,7 +645,7 @@ void EMSdevice::add_device_value(int8_t tag, // to b
|
||||
snprintf(entity, sizeof(entity), "%s/%s", tag_to_mqtt(tag), short_name);
|
||||
}
|
||||
|
||||
for (const std::string & entity_id : entityCustomization.entity_ids) {
|
||||
for (const auto & entity_id : entityCustomization.entity_ids) {
|
||||
// if there is an appended custom name, strip it to get the true entity name
|
||||
// and extract the new custom name
|
||||
auto custom_name_pos = entity_id.find('|');
|
||||
@@ -693,96 +693,100 @@ void EMSdevice::erase_device_values() {
|
||||
}
|
||||
}
|
||||
devicevalues_.clear();
|
||||
telegram_functions_.clear();
|
||||
handlers_ignored_.clear();
|
||||
handlers_config_.clear();
|
||||
handlers_broadcasted_.clear();
|
||||
}
|
||||
|
||||
// single list of options
|
||||
void EMSdevice::register_device_value(int8_t tag,
|
||||
void * value_p,
|
||||
uint8_t type,
|
||||
const char * const * options_single,
|
||||
const char * const * name,
|
||||
uint8_t uom,
|
||||
const cmd_function_p f) {
|
||||
void EMSdevice::register_device_value(int8_t tag,
|
||||
void * value_p,
|
||||
uint8_t type,
|
||||
const char * const * options_single,
|
||||
const char * const * name,
|
||||
uint8_t uom,
|
||||
const cmd_function_p & f) {
|
||||
// create a multi-list from the options
|
||||
add_device_value(tag, value_p, type, nullptr, options_single, 0, name, uom, f, 0, 0);
|
||||
};
|
||||
|
||||
// single list of options, with no translations, with min and max
|
||||
void EMSdevice::register_device_value(int8_t tag,
|
||||
void * value_p,
|
||||
uint8_t type,
|
||||
const char * const * options_single,
|
||||
const char * const * name,
|
||||
uint8_t uom,
|
||||
const cmd_function_p f,
|
||||
int16_t min,
|
||||
uint32_t max) {
|
||||
void EMSdevice::register_device_value(int8_t tag,
|
||||
void * value_p,
|
||||
uint8_t type,
|
||||
const char * const * options_single,
|
||||
const char * const * name,
|
||||
uint8_t uom,
|
||||
const cmd_function_p & f,
|
||||
int16_t min,
|
||||
uint32_t max) {
|
||||
// create a multi-list from the options
|
||||
add_device_value(tag, value_p, type, nullptr, options_single, 0, name, uom, f, min, max);
|
||||
};
|
||||
|
||||
void EMSdevice::register_device_value(int8_t tag,
|
||||
void * value_p,
|
||||
uint8_t type,
|
||||
int8_t numeric_operator,
|
||||
const char * const * name,
|
||||
uint8_t uom,
|
||||
const cmd_function_p f) {
|
||||
void EMSdevice::register_device_value(int8_t tag,
|
||||
void * value_p,
|
||||
uint8_t type,
|
||||
int8_t numeric_operator,
|
||||
const char * const * name,
|
||||
uint8_t uom,
|
||||
const cmd_function_p & f) {
|
||||
add_device_value(tag, value_p, type, nullptr, nullptr, numeric_operator, name, uom, f, 0, 0);
|
||||
}
|
||||
|
||||
void EMSdevice::register_device_value(int8_t tag,
|
||||
void * value_p,
|
||||
uint8_t type,
|
||||
int8_t numeric_operator,
|
||||
const char * const * name,
|
||||
uint8_t uom,
|
||||
const cmd_function_p f,
|
||||
int16_t min,
|
||||
uint32_t max) {
|
||||
void EMSdevice::register_device_value(int8_t tag,
|
||||
void * value_p,
|
||||
uint8_t type,
|
||||
int8_t numeric_operator,
|
||||
const char * const * name,
|
||||
uint8_t uom,
|
||||
const cmd_function_p & f,
|
||||
int16_t min,
|
||||
uint32_t max) {
|
||||
add_device_value(tag, value_p, type, nullptr, nullptr, numeric_operator, name, uom, f, min, max);
|
||||
}
|
||||
|
||||
// no options, no function
|
||||
void EMSdevice::register_device_value(int8_t tag, void * value_p, uint8_t type, const char * const * name, uint8_t uom, const cmd_function_p f) {
|
||||
void EMSdevice::register_device_value(int8_t tag, void * value_p, uint8_t type, const char * const * name, uint8_t uom, const cmd_function_p & f) {
|
||||
add_device_value(tag, value_p, type, nullptr, nullptr, 0, name, uom, f, 0, 0);
|
||||
};
|
||||
|
||||
// no options, with min/max
|
||||
void EMSdevice::register_device_value(int8_t tag,
|
||||
void * value_p,
|
||||
uint8_t type,
|
||||
const char * const * name,
|
||||
uint8_t uom,
|
||||
const cmd_function_p f,
|
||||
int16_t min,
|
||||
uint32_t max) {
|
||||
void EMSdevice::register_device_value(int8_t tag,
|
||||
void * value_p,
|
||||
uint8_t type,
|
||||
const char * const * name,
|
||||
uint8_t uom,
|
||||
const cmd_function_p & f,
|
||||
int16_t min,
|
||||
uint32_t max) {
|
||||
add_device_value(tag, value_p, type, nullptr, nullptr, 0, name, uom, f, min, max);
|
||||
};
|
||||
|
||||
// function with min and max values
|
||||
// adds a new command to the command list
|
||||
// in this function we separate out the short and long names and take any translations
|
||||
void EMSdevice::register_device_value(int8_t tag,
|
||||
void * value_p,
|
||||
uint8_t type,
|
||||
const char * const ** options,
|
||||
const char * const * name,
|
||||
uint8_t uom,
|
||||
const cmd_function_p f,
|
||||
int16_t min,
|
||||
uint32_t max) {
|
||||
void EMSdevice::register_device_value(int8_t tag,
|
||||
void * value_p,
|
||||
uint8_t type,
|
||||
const char * const ** options,
|
||||
const char * const * name,
|
||||
uint8_t uom,
|
||||
const cmd_function_p & f,
|
||||
int16_t min,
|
||||
uint32_t max) {
|
||||
add_device_value(tag, value_p, type, options, nullptr, 0, name, uom, f, min, max);
|
||||
}
|
||||
|
||||
// function with no min and max values (set to 0)
|
||||
void EMSdevice::register_device_value(int8_t tag,
|
||||
void * value_p,
|
||||
uint8_t type,
|
||||
const char * const ** options,
|
||||
const char * const * name,
|
||||
uint8_t uom,
|
||||
const cmd_function_p f) {
|
||||
void EMSdevice::register_device_value(int8_t tag,
|
||||
void * value_p,
|
||||
uint8_t type,
|
||||
const char * const ** options,
|
||||
const char * const * name,
|
||||
uint8_t uom,
|
||||
const cmd_function_p & f) {
|
||||
add_device_value(tag, value_p, type, options, nullptr, 0, name, uom, f, 0, 0);
|
||||
}
|
||||
|
||||
@@ -1206,10 +1210,10 @@ void EMSdevice::generate_values_web_customization(JsonArray output) {
|
||||
// this is when the mask has it's high bit (0x80) set
|
||||
// https://github.com/emsesp/EMS-ESP32/issues/891
|
||||
EMSESP::webCustomizationService.read([&](WebCustomization & settings) {
|
||||
for (EntityCustomization entityCustomization : settings.entityCustomizations) {
|
||||
for (const auto & entityCustomization : settings.entityCustomizations) {
|
||||
if (entityCustomization.device_id == device_id()) {
|
||||
// entity_ids is a list of all entities with the mask prefixed in the string
|
||||
for (const std::string & entity_id : entityCustomization.entity_ids) {
|
||||
for (const auto & entity_id : entityCustomization.entity_ids) {
|
||||
uint8_t mask = Helpers::hextoint(entity_id.substr(0, 2).c_str());
|
||||
if (mask & 0x80) {
|
||||
JsonObject obj = output.add<JsonObject>();
|
||||
|
||||
+56
-55
@@ -290,78 +290,79 @@ class EMSdevice {
|
||||
void generate_values_web(JsonObject output, const bool is_dashboard = false);
|
||||
void generate_values_web_customization(JsonArray output);
|
||||
|
||||
void add_device_value(int8_t tag,
|
||||
void * value_p,
|
||||
uint8_t type,
|
||||
const char * const ** options,
|
||||
const char * const * options_single,
|
||||
int8_t numeric_operator,
|
||||
const char * const * name,
|
||||
uint8_t uom,
|
||||
const cmd_function_p f,
|
||||
int16_t min,
|
||||
uint32_t max);
|
||||
void add_device_value(int8_t tag,
|
||||
void * value_p,
|
||||
uint8_t type,
|
||||
const char * const ** options,
|
||||
const char * const * options_single,
|
||||
int8_t numeric_operator,
|
||||
const char * const * name,
|
||||
uint8_t uom,
|
||||
const cmd_function_p & f,
|
||||
int16_t min,
|
||||
uint32_t max);
|
||||
|
||||
void register_device_value(int8_t tag,
|
||||
void * value_p,
|
||||
uint8_t type,
|
||||
const char * const ** options,
|
||||
const char * const * name,
|
||||
uint8_t uom,
|
||||
const cmd_function_p f,
|
||||
int16_t min,
|
||||
uint32_t max);
|
||||
void register_device_value(int8_t tag,
|
||||
void * value_p,
|
||||
uint8_t type,
|
||||
const char * const ** options,
|
||||
const char * const * name,
|
||||
uint8_t uom,
|
||||
const cmd_function_p & f,
|
||||
int16_t min,
|
||||
uint32_t max);
|
||||
|
||||
void erase_device_values();
|
||||
|
||||
void
|
||||
register_device_value(int8_t tag, void * value_p, uint8_t type, const char * const ** options, const char * const * name, uint8_t uom, const cmd_function_p f);
|
||||
register_device_value(int8_t tag, void * value_p, uint8_t type, const char * const ** options, const char * const * name, uint8_t uom, const cmd_function_p & f);
|
||||
|
||||
void register_device_value(int8_t tag, void * value_p, uint8_t type, const char * const ** options, const char * const * name, uint8_t uom);
|
||||
|
||||
void register_device_value(int8_t tag,
|
||||
void * value_p,
|
||||
uint8_t type,
|
||||
int8_t numeric_operator,
|
||||
const char * const * name,
|
||||
uint8_t uom,
|
||||
const cmd_function_p f = nullptr);
|
||||
void register_device_value(int8_t tag,
|
||||
void * value_p,
|
||||
uint8_t type,
|
||||
int8_t numeric_operator,
|
||||
const char * const * name,
|
||||
uint8_t uom,
|
||||
const cmd_function_p & f = nullptr);
|
||||
|
||||
void register_device_value(int8_t tag,
|
||||
void * value_p,
|
||||
uint8_t type,
|
||||
int8_t numeric_operator,
|
||||
const char * const * name,
|
||||
uint8_t uom,
|
||||
const cmd_function_p f,
|
||||
int16_t min,
|
||||
uint32_t max);
|
||||
void register_device_value(int8_t tag,
|
||||
void * value_p,
|
||||
uint8_t type,
|
||||
int8_t numeric_operator,
|
||||
const char * const * name,
|
||||
uint8_t uom,
|
||||
const cmd_function_p & f,
|
||||
int16_t min,
|
||||
uint32_t max);
|
||||
|
||||
// single list of options
|
||||
void register_device_value(int8_t tag,
|
||||
void * value_p,
|
||||
uint8_t type,
|
||||
const char * const * options_single,
|
||||
const char * const * name,
|
||||
uint8_t uom,
|
||||
const cmd_function_p f = nullptr);
|
||||
void register_device_value(int8_t tag,
|
||||
void * value_p,
|
||||
uint8_t type,
|
||||
const char * const * options_single,
|
||||
const char * const * name,
|
||||
uint8_t uom,
|
||||
const cmd_function_p & f = nullptr);
|
||||
|
||||
// single list of options, with no translations, with min and max
|
||||
void register_device_value(int8_t tag,
|
||||
void * value_p,
|
||||
uint8_t type,
|
||||
const char * const * options_single,
|
||||
const char * const * name,
|
||||
uint8_t uom,
|
||||
const cmd_function_p f,
|
||||
int16_t min,
|
||||
uint32_t max);
|
||||
void register_device_value(int8_t tag,
|
||||
void * value_p,
|
||||
uint8_t type,
|
||||
const char * const * options_single,
|
||||
const char * const * name,
|
||||
uint8_t uom,
|
||||
const cmd_function_p & f,
|
||||
int16_t min,
|
||||
uint32_t max);
|
||||
|
||||
// no options, optional function f
|
||||
void register_device_value(int8_t tag, void * value_p, uint8_t type, const char * const * name, uint8_t uom, const cmd_function_p f = nullptr);
|
||||
void register_device_value(int8_t tag, void * value_p, uint8_t type, const char * const * name, uint8_t uom, const cmd_function_p & f = nullptr);
|
||||
|
||||
// no options, with min/max
|
||||
void register_device_value(int8_t tag, void * value_p, uint8_t type, const char * const * name, uint8_t uom, const cmd_function_p f, int16_t min, uint32_t max);
|
||||
void
|
||||
register_device_value(int8_t tag, void * value_p, uint8_t type, const char * const * name, uint8_t uom, const cmd_function_p & f, int16_t min, uint32_t max);
|
||||
|
||||
void write_command(const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validate_typeid) const;
|
||||
void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid) const;
|
||||
|
||||
@@ -192,6 +192,11 @@ class DeviceValue {
|
||||
int16_t min; // min range
|
||||
uint32_t max; // max range
|
||||
|
||||
// optional custom name from customization. Allocated on heap only when actually set,
|
||||
// so unnamed entities (the vast majority) don't pay for an inline std::string.
|
||||
// Prefer custom_fullname() / set_custom_fullname() / has_custom_fullname() over direct access.
|
||||
std::unique_ptr<std::string> custom_fullname_;
|
||||
|
||||
DeviceValue(uint8_t device_type, // EMSdevice::DeviceType
|
||||
int8_t tag, // DeviceValueTAG::*
|
||||
void * value_p, // pointer to variable of any type
|
||||
@@ -245,11 +250,6 @@ class DeviceValue {
|
||||
static const char * const * DeviceValueTAG_s[];
|
||||
static const char * const DeviceValueTAG_mqtt[];
|
||||
static uint8_t NUM_TAGS; // # tags
|
||||
|
||||
private:
|
||||
// optional custom name from customization. Allocated on heap only when actually set,
|
||||
// so unnamed entities (the vast majority) don't pay for an inline std::string.
|
||||
std::unique_ptr<std::string> custom_fullname_;
|
||||
};
|
||||
|
||||
}; // namespace emsesp
|
||||
|
||||
+46
-21
@@ -54,19 +54,19 @@ bool EMSESP::entity_compaction_pending_ = false;
|
||||
AsyncWebServer webServer(80);
|
||||
|
||||
#if defined(EMSESP_STANDALONE)
|
||||
FS dummyFS;
|
||||
auto & fsRef = dummyFS;
|
||||
FS dummyFS;
|
||||
FS * const fsRef = &dummyFS;
|
||||
#else
|
||||
auto & fsRef = LittleFS;
|
||||
FS * const fsRef = &LittleFS;
|
||||
#endif
|
||||
|
||||
ESP32React EMSESP::esp32React(&webServer, &fsRef);
|
||||
WebSettingsService EMSESP::webSettingsService = WebSettingsService(&webServer, &fsRef, EMSESP::esp32React.getSecurityManager());
|
||||
WebCustomizationService EMSESP::webCustomizationService = WebCustomizationService(&webServer, &fsRef, EMSESP::esp32React.getSecurityManager());
|
||||
WebSchedulerService EMSESP::webSchedulerService = WebSchedulerService(&webServer, &fsRef, EMSESP::esp32React.getSecurityManager());
|
||||
WebCommandService EMSESP::webCommandService = WebCommandService(&webServer, &fsRef, EMSESP::esp32React.getSecurityManager());
|
||||
WebCustomEntityService EMSESP::webCustomEntityService = WebCustomEntityService(&webServer, &fsRef, EMSESP::esp32React.getSecurityManager());
|
||||
WebModulesService EMSESP::webModulesService = WebModulesService(&webServer, &fsRef, EMSESP::esp32React.getSecurityManager());
|
||||
ESP32React EMSESP::esp32React(&webServer, fsRef);
|
||||
WebSettingsService EMSESP::webSettingsService = WebSettingsService(&webServer, fsRef, EMSESP::esp32React.getSecurityManager());
|
||||
WebCustomizationService EMSESP::webCustomizationService = WebCustomizationService(&webServer, fsRef, EMSESP::esp32React.getSecurityManager());
|
||||
WebSchedulerService EMSESP::webSchedulerService = WebSchedulerService(&webServer, fsRef, EMSESP::esp32React.getSecurityManager());
|
||||
WebCommandService EMSESP::webCommandService = WebCommandService(&webServer, fsRef, EMSESP::esp32React.getSecurityManager());
|
||||
WebCustomEntityService EMSESP::webCustomEntityService = WebCustomEntityService(&webServer, fsRef, EMSESP::esp32React.getSecurityManager());
|
||||
WebModulesService EMSESP::webModulesService = WebModulesService(&webServer, fsRef, EMSESP::esp32React.getSecurityManager());
|
||||
|
||||
WebActivityService EMSESP::webActivityService = WebActivityService(&webServer, EMSESP::esp32React.getSecurityManager());
|
||||
WebStatusService EMSESP::webStatusService = WebStatusService(&webServer, EMSESP::esp32React.getSecurityManager());
|
||||
@@ -173,10 +173,23 @@ uint8_t EMSESP::device_id_from_cmd(const uint8_t device_type, const char * cmd,
|
||||
return 0;
|
||||
}
|
||||
|
||||
// clears list of recognized devices
|
||||
// remove a single device with all it's entities, commands, and telegrams
|
||||
void EMSESP::erase_device(const uint8_t type_id) {
|
||||
for (auto it = emsdevices.begin(); it < emsdevices.end(); it++) {
|
||||
if ((*it)->device_id() == type_id) {
|
||||
(*it)->erase_device_values();
|
||||
emsdevices.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clears list of recognized devices, entities and telegrams
|
||||
void EMSESP::clear_all_devices() {
|
||||
// temporarily removed: clearing the list causes a crash, the associated commands and mqtt should also be removed.
|
||||
// emsdevices.clear(); // remove entries, but doesn't delete actual devices
|
||||
for (const auto & emsdevice : emsdevices) {
|
||||
emsdevice->erase_device_values();
|
||||
}
|
||||
emsdevices.clear();
|
||||
}
|
||||
|
||||
// called from EMSdevice/Command whenever an entity or telegram handler is registered.
|
||||
@@ -644,8 +657,8 @@ void EMSESP::reset_mqtt_ha() {
|
||||
}
|
||||
|
||||
// force the re-creating of the temperature and analog sensor topics (for HA)
|
||||
temperaturesensor_.reload();
|
||||
analogsensor_.reload();
|
||||
temperaturesensor_.ha_reset();
|
||||
analogsensor_.ha_reset();
|
||||
|
||||
// rebuild MQTT HA config topics for shower, custom entities and scheduler
|
||||
shower_.ha_reset();
|
||||
@@ -1027,10 +1040,13 @@ void EMSESP::process_UBADevices(const std::shared_ptr<const Telegram> & telegram
|
||||
if (next_byte & 0x01) {
|
||||
// if we haven't already detected this device, request it's version details, unless its us (EMS-ESP)
|
||||
// when the version info is received, it will automagically add the device
|
||||
if ((device_id != EMSbus::ems_bus_id()) && !(EMSESP::device_exists(device_id))) {
|
||||
if ((device_id != EMSbus::ems_bus_id()) && !(device_exists(device_id))) {
|
||||
LOG_DEBUG("New EMS device detected with ID 0x%02X. Requesting version information.", device_id);
|
||||
send_read_request(EMSdevice::EMS_TYPE_VERSION, device_id);
|
||||
}
|
||||
} else if (device_exists(device_id) && !device_hasEntities(device_id)) {
|
||||
LOG_DEBUG("Remove EMS device with ID 0x%02X", device_id);
|
||||
erase_device(device_id); //
|
||||
}
|
||||
next_byte = next_byte >> 1; // advance 1 bit
|
||||
}
|
||||
@@ -1237,6 +1253,15 @@ bool EMSESP::process_telegram(const std::shared_ptr<const Telegram> & telegram)
|
||||
return telegram_found;
|
||||
}
|
||||
|
||||
bool EMSESP::device_hasEntities(const uint8_t device_id) {
|
||||
for (const auto & emsdevice : emsdevices) {
|
||||
if (emsdevice->is_device_id(device_id) && (emsdevice->count_entities() > 0)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false; // not found
|
||||
}
|
||||
|
||||
// return true if we have this device already registered
|
||||
bool EMSESP::device_exists(const uint8_t device_id) {
|
||||
if (emsdevices.empty()) {
|
||||
@@ -1459,7 +1484,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const
|
||||
|
||||
// see if we have a custom device name in our Customizations list, and if so set it
|
||||
webCustomizationService.read([&](WebCustomization const & settings) {
|
||||
for (EntityCustomization e : settings.entityCustomizations) {
|
||||
for (const auto & e : settings.entityCustomizations) {
|
||||
if ((e.device_id == device_id) && (e.product_id == product_id)) {
|
||||
LOG_DEBUG("Have customizations for %s with deviceID 0x%02X productID %d", e.custom_name.c_str(), device_id, product_id);
|
||||
emsdevices.back()->custom_name(e.custom_name);
|
||||
@@ -1868,7 +1893,7 @@ void EMSESP::shell_prompt() {
|
||||
void EMSESP::loop() {
|
||||
uuid::loop(); // store system uptime
|
||||
|
||||
// handle network
|
||||
// handle network (Wifi, ETH and AP)
|
||||
network_.loop();
|
||||
|
||||
// handles LED and checks system health, and syslog service
|
||||
@@ -1876,14 +1901,13 @@ void EMSESP::loop() {
|
||||
return; // LED flashing is active meaning its about to reboot, skip the rest of the loop
|
||||
}
|
||||
|
||||
esp32React.loop(); // core services: Network, AP, MQTT and NTP
|
||||
webLogService.loop(); // log in Web UI
|
||||
|
||||
// run the loop, unless we're in the middle of an OTA upload
|
||||
// run the main loop, unless we're in the middle of an OTA upload
|
||||
// if (EMSESP::system_.systemStatus() == SYSTEM_STATUS::SYSTEM_STATUS_NORMAL || EMSESP::system_.systemStatus() == SYSTEM_STATUS::SYSTEM_STATUS_INVALID_GPIO) {
|
||||
if (EMSESP::system_.systemStatus() != SYSTEM_STATUS::SYSTEM_STATUS_PENDING_UPLOAD
|
||||
&& EMSESP::system_.systemStatus() != SYSTEM_STATUS::SYSTEM_STATUS_UPLOADING) {
|
||||
// loop through the services
|
||||
esp32React.loop(); // MQTT and NTP
|
||||
webStatusService.loop(); // periodic refresh of cached versions.json
|
||||
rxservice_.loop(); // process any incoming Rx telegrams
|
||||
shower_.loop(); // check for shower on/off
|
||||
@@ -1896,6 +1920,7 @@ void EMSESP::loop() {
|
||||
scheduled_fetch_values(); // force a query on the EMS devices to fetch latest data at a set interval (1 min)
|
||||
compact_entities_if_stable(); // reclaim over-reserved entity vector capacity once device discovery settles
|
||||
}
|
||||
|
||||
// check for GPIO Errors - this is called once when booting
|
||||
if (EMSESP::system_.systemStatus() == SYSTEM_STATUS::SYSTEM_STATUS_INVALID_GPIO) {
|
||||
static bool only_once = false;
|
||||
|
||||
+3
-1
@@ -101,7 +101,7 @@ class Module {}; // forward declaration
|
||||
|
||||
// for Command Function callbacks (Command::cmd_function_p). The unified callback takes a JsonObject
|
||||
// output which entity/setter commands ignore.
|
||||
#define MAKE_CF_CB(__f) [&](const char * value, const int8_t id, JsonObject output) { return __f(value, id); }
|
||||
#define MAKE_CF_CB(__f) [this](const char * value, const int8_t id, JsonObject output) { return __f(value, id); }
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
@@ -152,6 +152,7 @@ class EMSESP {
|
||||
static void send_write_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, const uint8_t value);
|
||||
static void send_write_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid);
|
||||
|
||||
static bool device_hasEntities(const uint8_t device_id);
|
||||
static bool device_exists(const uint8_t device_id);
|
||||
static void device_active(const uint8_t device_id, const bool active);
|
||||
static bool cmd_is_readonly(const uint8_t device_type, const uint8_t device_id, const char * cmd, const int8_t id);
|
||||
@@ -238,6 +239,7 @@ class EMSESP {
|
||||
|
||||
static bool add_device(const uint8_t device_id, const uint8_t product_id, const char * version, const uint8_t brand);
|
||||
static void scan_devices();
|
||||
static void erase_device(const uint8_t type_id);
|
||||
static void clear_all_devices();
|
||||
|
||||
// called whenever a device entity or telegram handler is registered, so we can
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/emsesp/EMS-ESP
|
||||
* Copyright 2020-2026 emsesp.org
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#include "emsesp.h"
|
||||
|
||||
#include "httpClient.h"
|
||||
|
||||
#include <WiFiClient.h>
|
||||
#include <ESP_SSLClient.h>
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
int HttpClient::request(std::string url, const std::string & method, const std::string & value, JsonObjectConst headers, std::string & result) {
|
||||
int httpResult = 0;
|
||||
const bool is_post = value.length() || Helpers::toLower(method) == "post";
|
||||
const auto lower_url = Helpers::toLower(url.c_str());
|
||||
|
||||
const bool is_https = lower_url.starts_with("https://");
|
||||
if (!is_https && !lower_url.starts_with("http://")) {
|
||||
return 0; // unsupported scheme
|
||||
}
|
||||
|
||||
WiFiClient * basic_client = new WiFiClient;
|
||||
ESP_SSLClient * ssl_client = new ESP_SSLClient;
|
||||
if (is_https) {
|
||||
ssl_client->setInsecure();
|
||||
// Notes: with root CA we should set here: ssl_client->setCACert(rootCACert);
|
||||
// 1 KB RX buffer is fine for small JSON-style endpoints used by the scheduler/shunting-yard but it is NOT enough for servers that send full-size TLS records (>1 KB)
|
||||
ssl_client->setBufferSizes(16384, 1024);
|
||||
ssl_client->setSessionTimeout(120); // Set the timeout in seconds (>=120 seconds)
|
||||
}
|
||||
// WiFiClient is NetworkClient, which declares no setTimeout() of its own - calling it binds to
|
||||
// Stream::setTimeout() and only affects readBytes(), leaving the socket on the core's 3s default
|
||||
basic_client->setConnectionTimeout(CONNECT_TIMEOUT_MS);
|
||||
ssl_client->setTimeout(5); // seconds, drives BearSSL only - unused on the plain HTTP path
|
||||
ssl_client->setClient(basic_client, is_https); // enableSSL = false for plain HTTP
|
||||
|
||||
url.replace(0, is_https ? 8 : 7, "");
|
||||
std::string host = url;
|
||||
auto index = url.find_first_of('/');
|
||||
if (index != std::string::npos) {
|
||||
host = url.substr(0, index);
|
||||
url.replace(0, index, "");
|
||||
} else {
|
||||
url = "/";
|
||||
}
|
||||
|
||||
const uint16_t port = is_https ? 443 : 80;
|
||||
if (ssl_client->connect(host.c_str(), port)) {
|
||||
bool content_set = false;
|
||||
bool agent_set = false;
|
||||
|
||||
// assemble the request in one buffer. Sent as a dozen small writes it is left to Nagle to
|
||||
// dribble them out an ACK at a time, which on a slow link can cost more than the read budget
|
||||
std::string req = (is_post ? "POST " : "GET ") + url + " HTTP/1.1\r\nHost: " + host + "\r\n";
|
||||
for (JsonPairConst p : headers) {
|
||||
const auto key = Helpers::toLower(p.key().c_str());
|
||||
content_set |= (key == "content-type");
|
||||
agent_set |= (key == "user-agent");
|
||||
req += std::string(p.key().c_str()) + ": " + p.value().as<std::string>() + "\r\n";
|
||||
}
|
||||
if (!agent_set) {
|
||||
req += "User-Agent: EMS-ESP\r\n"; // CDNs are more likely to stall on a request without one
|
||||
}
|
||||
if (is_post) {
|
||||
if (!content_set) {
|
||||
req += "Content-Type: ";
|
||||
req += value.starts_with('{') ? asyncsrv::T_application_json : asyncsrv::T_text_plain;
|
||||
req += "\r\n";
|
||||
}
|
||||
req += "Content-Length: " + std::to_string(value.length()) + "\r\n";
|
||||
}
|
||||
req += "Connection: close\r\n\r\n"; // the blank line terminates the headers - without it the server never responds
|
||||
|
||||
ssl_client->print(req.c_str());
|
||||
if (is_post && value.length()) {
|
||||
ssl_client->print(value.c_str()); // sent separately so the body isn't copied into req
|
||||
}
|
||||
|
||||
// available() drops to zero between TCP segments, so stopping at the first gap truncates
|
||||
// any response that doesn't arrive in a single packet. Keep reading until the peer closes,
|
||||
// the stream goes idle, or the overall budget runs out
|
||||
const uint32_t started = millis();
|
||||
uint32_t last_data = started;
|
||||
while (millis() - started < TOTAL_TIMEOUT_MS) {
|
||||
const int avail = ssl_client->available();
|
||||
if (avail > 0) {
|
||||
uint8_t buf[128];
|
||||
const size_t want = (avail < (int)sizeof(buf)) ? (size_t)avail : sizeof(buf);
|
||||
const int len = ssl_client->read(buf, want);
|
||||
if (len > 0) {
|
||||
result.append(reinterpret_cast<const char *>(buf), len);
|
||||
last_data = millis();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!ssl_client->connected()) {
|
||||
break; // closed, with nothing left buffered
|
||||
}
|
||||
// the server may take a while to start replying
|
||||
if (millis() - last_data > (result.empty() ? FIRST_BYTE_TIMEOUT_MS : IDLE_TIMEOUT_MS)) {
|
||||
break;
|
||||
}
|
||||
delay(1);
|
||||
}
|
||||
ssl_client->stop();
|
||||
|
||||
const auto received = result.length();
|
||||
|
||||
// parse the status line "HTTP/1.x <code> <reason>". stoi() would abort rather than throw on
|
||||
// a malformed response, since the firmware is built with -fno-exceptions
|
||||
if (result.starts_with("HTTP/")) {
|
||||
index = result.find_first_of(' ');
|
||||
if (index != std::string::npos) {
|
||||
httpResult = Helpers::atoint(result.c_str() + index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
index = result.find("\r\n\r\n");
|
||||
if (index != std::string::npos) {
|
||||
result.replace(0, index + 4, "");
|
||||
}
|
||||
|
||||
if (httpResult == 0) {
|
||||
// the TCP connect worked but nothing usable came back
|
||||
EMSESP::logger().warning("%s no valid response from %s (%u bytes)", is_https ? "HTTPS" : "HTTP", host.c_str(), (unsigned)received);
|
||||
}
|
||||
} else {
|
||||
EMSESP::logger().warning("%s connection to %s failed", is_https ? "HTTPS" : "HTTP", host.c_str());
|
||||
}
|
||||
delete ssl_client;
|
||||
delete basic_client;
|
||||
|
||||
return httpResult;
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/emsesp/EMS-ESP
|
||||
* Copyright 2020-2026 emsesp.org
|
||||
*
|
||||
* 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_HTTPCLIENT_H
|
||||
#define EMSESP_HTTPCLIENT_H
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
class HttpClient {
|
||||
public:
|
||||
static int request(std::string url, const std::string & method, const std::string & value, JsonObjectConst headers, std::string & result);
|
||||
|
||||
private:
|
||||
static constexpr uint32_t CONNECT_TIMEOUT_MS = 5000; // TCP connect, the core defaults to 3s
|
||||
static constexpr uint32_t FIRST_BYTE_TIMEOUT_MS = 8000; // how long the server may take to start replying
|
||||
static constexpr uint32_t IDLE_TIMEOUT_MS = 500; // gap in the stream that marks the end of a response
|
||||
static constexpr uint32_t TOTAL_TIMEOUT_MS = 10000; // ceiling for the whole read
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
+49
-30
@@ -59,50 +59,60 @@ bool LED::loop(uint8_t healthcheck, bool button_busy) {
|
||||
// the user-requested LED blink always has preference
|
||||
if (!is_user_led_blink_) {
|
||||
// check for button press.
|
||||
// if button is pressed, show LED (yellow on RGB LED, on/off on standard LED)
|
||||
// it will turn off on the next loop cycle
|
||||
// while the button is held show the LED (yellow on RGB LED, on/off on standard LED),
|
||||
// and on release go back to the default state and re-check the health on the next cycle
|
||||
if (last_button_busy_ != button_busy) {
|
||||
last_button_busy_ = button_busy;
|
||||
set_led(button_busy ? Color::OFF : Color::YELLOW); // Yellow
|
||||
if (button_busy) {
|
||||
set_led(Color::YELLOW); // Yellow
|
||||
} else {
|
||||
reset_led();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// check the system health.
|
||||
// Set the sequence accordingly, only if the healthcheck is not 0 and has changed
|
||||
if (healthcheck != previous_healthcheck_) {
|
||||
previous_healthcheck_ = healthcheck;
|
||||
color_steps_[0] = Color::OFF;
|
||||
color_steps_[1] = Color::OFF;
|
||||
color_steps_[2] = Color::OFF;
|
||||
color_steps_[0] = Color::OFF;
|
||||
color_steps_[1] = Color::OFF;
|
||||
color_steps_[2] = Color::OFF;
|
||||
|
||||
// if the healthcheck is 0, i.e. system is healthy, reset the LED
|
||||
if (healthcheck == 0) {
|
||||
reset_led();
|
||||
return false;
|
||||
} else {
|
||||
// 1 flash (blue) is the EMS bus is not connected
|
||||
// 2 flashes (red, red) if the network (wifi or ethernet) is not connected
|
||||
// 3 flashes (red, red, blue) is both the bus and the network are not connected
|
||||
bool no_network = (healthcheck & System::HEALTHCHECK_NO_NETWORK) == System::HEALTHCHECK_NO_NETWORK;
|
||||
bool no_bus = (healthcheck & System::HEALTHCHECK_NO_BUS) == System::HEALTHCHECK_NO_BUS;
|
||||
|
||||
// set step 1
|
||||
if (no_network) {
|
||||
color_steps_[0] = Color::RED; // red, no network
|
||||
} else if (no_bus) {
|
||||
color_steps_[0] = Color::BLUE; // blue, no bus
|
||||
}
|
||||
|
||||
// set step 2
|
||||
if (no_network) {
|
||||
color_steps_[1] = Color::RED; // red, no network
|
||||
}
|
||||
|
||||
// set step 3
|
||||
if (no_network && no_bus) {
|
||||
color_steps_[2] = Color::BLUE; // blue, no network and no bus
|
||||
}
|
||||
}
|
||||
|
||||
// 1 flash (blue) is the EMS bus is not connected
|
||||
// 2 flashes (red, red) if the network (wifi or ethernet) is not connected
|
||||
// 3 flashes (red, red, blue) is both the bus and the network are not connected
|
||||
bool no_network = (healthcheck & System::HEALTHCHECK_NO_NETWORK) == System::HEALTHCHECK_NO_NETWORK;
|
||||
bool no_bus = (healthcheck & System::HEALTHCHECK_NO_BUS) == System::HEALTHCHECK_NO_BUS;
|
||||
previous_healthcheck_ = healthcheck; // must be set after reset_led(), which invalidates it
|
||||
}
|
||||
|
||||
// set step 1
|
||||
if (no_network) {
|
||||
color_steps_[0] = Color::RED; // red, no network
|
||||
} else if (no_bus) {
|
||||
color_steps_[0] = Color::BLUE; // blue, no bus
|
||||
}
|
||||
|
||||
// set step 2
|
||||
if (no_network) {
|
||||
color_steps_[1] = Color::RED; // red, no network
|
||||
}
|
||||
|
||||
// set step 3
|
||||
if (no_network && no_bus) {
|
||||
color_steps_[2] = Color::BLUE; // blue, no network and no bus
|
||||
}
|
||||
// there's nothing to flash, so leave the LED in the state reset_led() gave it.
|
||||
// Running the sequence here would keep writing Color::OFF and a healthy system would never show green.
|
||||
if (healthcheck == 0 || button_busy) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,6 +129,15 @@ void LED::reset_led() {
|
||||
color_steps_[0] = Color::OFF;
|
||||
color_steps_[1] = Color::OFF;
|
||||
color_steps_[2] = Color::OFF;
|
||||
|
||||
// invalidate the last health state so the sequence is rebuilt on the next check,
|
||||
// needed because the color steps above have been cleared
|
||||
previous_healthcheck_ = System::HEALTHCHECK_RESET;
|
||||
|
||||
// rewind the sequence so it starts from the top if the health degrades again
|
||||
led_long_timer_ = 1; // 1 will kick it off immediately
|
||||
led_short_timer_ = 0;
|
||||
led_flash_step_ = 0;
|
||||
}
|
||||
|
||||
// LED flash every few ms and then perform a factory reset
|
||||
@@ -313,4 +332,4 @@ void LED::start_led_fast_flash(uint8_t duration) {
|
||||
led_fast_flash_timer_ = true; // it's active
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
} // namespace emsesp
|
||||
@@ -28,4 +28,7 @@ void setup() {
|
||||
|
||||
void loop() {
|
||||
application.loop();
|
||||
#ifndef EMSESP_STANDALONE
|
||||
delay(1); // block for a tick so the idle task gets to run and the core can clock-gate
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -179,28 +179,29 @@ const std::initializer_list<Modbus::EntityModbusInfo> Modbus::modbus_register_ma
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(fanWork), 257, 1), // fanwork
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(ignWork), 258, 1), // ignwork
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(oilPreHeat), 259, 1), // oilpreheat
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(burnMaxPower), 260, 1), // burnmaxpower
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(burnMinPeriod), 261, 1), // burnminperiod
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(absBurnPow), 262, 1), // absburnpow
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(heatblock), 263, 1), // heatblock
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(boilHystOn), 264, 1), // boilhyston
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(boilHystOff), 265, 1), // boilhystoff
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(boil2HystOn), 266, 1), // boil2hyston
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(boil2HystOff), 267, 1), // boil2hystoff
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(curveOn), 268, 1), // curveon
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(curveBase), 269, 1), // curvebase
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(curveEnd), 270, 1), // curveend
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(summertemp), 271, 1), // summertemp
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nofrostmode), 272, 1), // nofrostmode
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nofrosttemp), 273, 1), // nofrosttemp
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(gasMeterHeat), 274, 2), // gasmeterheat
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgHeat2), 276, 2), // nrgheat2
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nomPower), 278, 1), // nompower
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(netFlowTemp), 279, 1), // netflowtemp
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(heatValve), 280, 1), // heatvalve
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(keepWarmTemp), 281, 1), // keepwarmtemp
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(setReturnTemp), 282, 1), // setreturntemp
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(heatingOn), 283, 1), // heating
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(burnMinPower), 260, 1), // burnminpower
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(burnMaxPower), 261, 1), // burnmaxpower
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(burnMinPeriod), 262, 1), // burnminperiod
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(absBurnPow), 263, 1), // absburnpow
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(heatblock), 264, 1), // heatblock
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(boilHystOn), 265, 1), // boilhyston
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(boilHystOff), 266, 1), // boilhystoff
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(boil2HystOn), 267, 1), // boil2hyston
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(boil2HystOff), 268, 1), // boil2hystoff
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(curveOn), 269, 1), // curveon
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(curveBase), 270, 1), // curvebase
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(curveEnd), 271, 1), // curveend
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(summertemp), 272, 1), // summertemp
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nofrostmode), 273, 1), // nofrostmode
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nofrosttemp), 274, 1), // nofrosttemp
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(gasMeterHeat), 275, 2), // gasmeterheat
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgHeat2), 277, 2), // nrgheat2
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nomPower), 279, 1), // nompower
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(netFlowTemp), 280, 1), // netflowtemp
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(heatValve), 281, 1), // heatvalve
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(keepWarmTemp), 282, 1), // keepwarmtemp
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(setReturnTemp), 283, 1), // setreturntemp
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(heatingOn), 284, 1), // heating
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(nrgWw), 0, 2), // nrg
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(meterWw), 2, 2), // meter
|
||||
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(upTimeCompWw), 4, 2), // uptimecomp
|
||||
|
||||
+78
-54
@@ -51,7 +51,7 @@ bool Mqtt::publish_single2cmd_;
|
||||
std::vector<Mqtt::MQTTSubFunction, AllocatorPSRAM<Mqtt::MQTTSubFunction>> Mqtt::mqtt_subfunctions_;
|
||||
|
||||
uint32_t Mqtt::mqtt_publish_fails_ = 0;
|
||||
bool Mqtt::connecting_ = false;
|
||||
bool Mqtt::connected_ = false;
|
||||
bool Mqtt::initialized_ = false;
|
||||
uint16_t Mqtt::queuecount_ = 0;
|
||||
uint8_t Mqtt::connectcount_ = 0;
|
||||
@@ -313,6 +313,31 @@ void Mqtt::on_publish(uint16_t packetId) const {
|
||||
LOG_DEBUG("Packet %d sent successful", packetId);
|
||||
}
|
||||
|
||||
// gracefully disconnect from the broker by sending a proper MQTT DISCONNECT packet.
|
||||
// this is called just before a restart so the broker ends our session cleanly
|
||||
// (avoids a lingering session that shows up as "session taken over" on reconnect).
|
||||
//
|
||||
// the disconnect is deferred inside espMqttClient - the DISCONNECT packet is only
|
||||
// queued and actually sent when loop() runs the state machine. On PSRAM boards the
|
||||
// client has its own task that keeps pumping loop(), so it flushes on its own. On
|
||||
// non-PSRAM boards (UseInternalTask::NO) nothing drives loop() once we're mid-restart,
|
||||
// so we pump it here until the client is fully disconnected, bounded by a timeout.
|
||||
void Mqtt::disconnect() {
|
||||
if (!mqttClient_) {
|
||||
return;
|
||||
}
|
||||
|
||||
mqttClient_->disconnect(); // graceful - queues the DISCONNECT packet
|
||||
|
||||
if (EMSESP::system_.PSram() == 0) {
|
||||
uint32_t start = uuid::get_uptime();
|
||||
while (!mqttClient_->disconnected() && (uuid::get_uptime() - start) < MQTT_DISCONNECT_TIMEOUT) {
|
||||
mqttClient_->loop();
|
||||
delay(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// called when MQTT settings have changed via the MQTT Settings or Application Settings Web pages
|
||||
void Mqtt::reset_mqtt() {
|
||||
if (!enabled()) {
|
||||
@@ -326,6 +351,7 @@ void Mqtt::reset_mqtt() {
|
||||
if (mqttClient_->connected()) {
|
||||
mqttClient_->disconnect(true); // force a disconnect
|
||||
}
|
||||
load_settings(); // reload MQTT settings
|
||||
}
|
||||
|
||||
// load the settings from service
|
||||
@@ -446,30 +472,39 @@ bool Mqtt::get_publish_onchange(uint8_t device_type) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void Mqtt::on_disconnect(espMqttClientTypes::DisconnectReason reason) {
|
||||
// only show the error once, not every 2 seconds
|
||||
if (!connecting_) {
|
||||
return;
|
||||
}
|
||||
connecting_ = false;
|
||||
connectcount_++; // count # reconnects
|
||||
|
||||
if (reason == espMqttClientTypes::DisconnectReason::TCP_DISCONNECTED) {
|
||||
LOG_WARNING("MQTT disconnected: TCP");
|
||||
} else if (reason == espMqttClientTypes::DisconnectReason::MQTT_UNACCEPTABLE_PROTOCOL_VERSION) {
|
||||
LOG_WARNING("MQTT disconnected: Unacceptable protocol version");
|
||||
} else if (reason == espMqttClientTypes::DisconnectReason::MQTT_IDENTIFIER_REJECTED) {
|
||||
LOG_WARNING("MQTT disconnected: Identifier Rejected");
|
||||
} else if (reason == espMqttClientTypes::DisconnectReason::MQTT_SERVER_UNAVAILABLE) {
|
||||
LOG_WARNING("MQTT disconnected: Server unavailable");
|
||||
} else if (reason == espMqttClientTypes::DisconnectReason::MQTT_MALFORMED_CREDENTIALS) {
|
||||
LOG_WARNING("MQTT disconnected: Malformed credentials");
|
||||
} else if (reason == espMqttClientTypes::DisconnectReason::MQTT_NOT_AUTHORIZED) {
|
||||
LOG_WARNING("MQTT disconnected: Not authorized");
|
||||
} else if (reason == espMqttClientTypes::DisconnectReason::TLS_BAD_FINGERPRINT) {
|
||||
LOG_WARNING("MQTT disconnected: Server fingerprint invalid");
|
||||
// human-readable cause for a MQTT disconnect reason, nullptr if not recognized
|
||||
static const char * mqtt_disconnect_cause(espMqttClientTypes::DisconnectReason reason) {
|
||||
using espMqttClientTypes::DisconnectReason;
|
||||
|
||||
switch (reason) {
|
||||
case DisconnectReason::TCP_DISCONNECTED:
|
||||
return "TCP";
|
||||
case DisconnectReason::MQTT_UNACCEPTABLE_PROTOCOL_VERSION:
|
||||
return "Unaccepted protocol version";
|
||||
case DisconnectReason::MQTT_IDENTIFIER_REJECTED:
|
||||
return "Identifier Rejected";
|
||||
case DisconnectReason::MQTT_SERVER_UNAVAILABLE:
|
||||
return "Server unavailable";
|
||||
case DisconnectReason::MQTT_MALFORMED_CREDENTIALS:
|
||||
return "Malformed credentials";
|
||||
case DisconnectReason::MQTT_NOT_AUTHORIZED:
|
||||
return "Not authorized";
|
||||
case DisconnectReason::TLS_BAD_FINGERPRINT:
|
||||
return "Server fingerprint invalid";
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Mqtt::on_disconnect(espMqttClientTypes::DisconnectReason reason) {
|
||||
connected_ = false;
|
||||
|
||||
const char * cause = mqtt_disconnect_cause(reason);
|
||||
if (cause) {
|
||||
LOG_ERROR("MQTT connection failure: %s. Retrying...", cause);
|
||||
} else {
|
||||
LOG_WARNING("MQTT disconnected: code %d", reason);
|
||||
LOG_ERROR("MQTT connection failure: code %d. Retrying...", static_cast<int>(reason));
|
||||
}
|
||||
|
||||
mqttClient_->clearQueue(true);
|
||||
@@ -477,17 +512,16 @@ void Mqtt::on_disconnect(espMqttClientTypes::DisconnectReason reason) {
|
||||
|
||||
// MQTT on_connect - when an MQTT connect is established
|
||||
void Mqtt::on_connect() {
|
||||
if (connecting_) {
|
||||
if (connected_) {
|
||||
return; // prevent duplicated connections
|
||||
}
|
||||
|
||||
LOG_INFO("MQTT connected");
|
||||
|
||||
connecting_ = true;
|
||||
connected_ = true;
|
||||
connectcount_++;
|
||||
queuecount_ = mqttClient_->queueSize();
|
||||
|
||||
load_settings(); // reload MQTT settings - in case they have changes
|
||||
|
||||
if (ha_enabled_) {
|
||||
queue_unsubscribe_message(discovery_prefix_ + "/+/" + Mqtt::basename() + "/#");
|
||||
EMSESP::reset_mqtt_ha(); // re-create all HA devices if there are any
|
||||
@@ -587,9 +621,11 @@ void Mqtt::ha_status() {
|
||||
|
||||
// These come from the info MQTT topic - and handled in the publish_ha_sensor_config function
|
||||
publish_system_ha_sensor_config(DeviceValueType::STRING, "Version", "version", DeviceValueUOM::NONE);
|
||||
publish_system_ha_sensor_config(DeviceValueType::STRING, "Boot time", "bootTime", DeviceValueUOM::UPTIME);
|
||||
publish_system_ha_sensor_config(DeviceValueType::STRING,
|
||||
"Uptime",
|
||||
"bootTime",
|
||||
DeviceValueUOM::UPTIME); // called Uptime in HA and derives value from bootTime element in info topic
|
||||
}
|
||||
|
||||
// add sub or pub task to the queue.
|
||||
// the base is not included in the topic
|
||||
bool Mqtt::queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, const bool retain) {
|
||||
@@ -988,10 +1024,9 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
|
||||
|
||||
// set the entity_id. This is breaking change in HA 2025.10.0 - see https://github.com/home-assistant/core/pull/151775
|
||||
// extract the string from topic up to the / using std::string
|
||||
// it must be lowercase (although HA will slugify it and not reject it)
|
||||
std::string topic_str(topic);
|
||||
doc["def_ent_id"] = topic_str.substr(0, topic_str.find("/")) + "." + uniq_id;
|
||||
|
||||
char sample_val[30] = "0"; // sample, correct(!) entity value, used only to prevent warning/error in HA if real value is not published yet
|
||||
doc["def_ent_id"] = Helpers::toLower(topic_str.substr(0, topic_str.find("/"))) + "." + Helpers::toLower(uniq_id);
|
||||
|
||||
// we add the command topic parameter for commands
|
||||
if (has_cmd) {
|
||||
@@ -1015,13 +1050,11 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
|
||||
for (uint8_t i = 0; i < options_size; i++) {
|
||||
option_list.add(Helpers::itoa(i)); // as a string
|
||||
}
|
||||
snprintf(sample_val, sizeof(sample_val), "'0'");
|
||||
} else {
|
||||
// use strings
|
||||
for (uint8_t i = 0; i < options_size; i++) {
|
||||
option_list.add(Helpers::translated_word(options[i]));
|
||||
}
|
||||
snprintf(sample_val, sizeof(sample_val), "'%s'", Helpers::translated_word(options[0]));
|
||||
}
|
||||
} else if (type != DeviceValueType::STRING && type != DeviceValueType::BOOL) {
|
||||
// For numeric's add the range and mode
|
||||
@@ -1043,7 +1076,6 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
|
||||
if (dv_set_min != 0 || dv_set_max != 0) {
|
||||
doc["min"] = dv_set_min;
|
||||
doc["max"] = dv_set_max;
|
||||
snprintf(sample_val, sizeof(sample_val), "%i", dv_set_min);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1067,7 +1099,7 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
|
||||
snprintf(stat_t, sizeof(stat_t), "~/%s", tag_to_topic(device_type, tag).c_str());
|
||||
|
||||
// Override - there are exceptions for DeviceType::SYSTEM, which uses the heartbeat topic
|
||||
// and when fetching the version and bootTime we want to take this from the info topic instead
|
||||
// and when fetching the version and uptime we want to take this from the info topic instead
|
||||
if (device_type == EMSdevice::DeviceType::SYSTEM) {
|
||||
// handle the exceptions
|
||||
if (strncmp(entity, "version", 7) == 0) {
|
||||
@@ -1095,18 +1127,19 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
|
||||
// has no unit of measure or icon, and must be true/false (not on/off or 1/0)
|
||||
if (type == DeviceValueType::BOOL) {
|
||||
add_ha_bool(doc.as<JsonObject>());
|
||||
strlcpy(sample_val, "false", sizeof(sample_val)); // default is "false"
|
||||
}
|
||||
|
||||
// don't bother with value template conditions if using Domoticz which doesn't fully support MQTT Discovery
|
||||
if (discovery_type() == discoveryType::HOMEASSISTANT) {
|
||||
// handle special cases for value_template depending on the uom
|
||||
if (uom == DeviceValueUOM::TIMESTAMP) {
|
||||
// special case for timestamp, using "value_template": "{{ (value_json.bootTime | as_datetime).isoformat() }}",
|
||||
// uptime
|
||||
char val_tpl[100];
|
||||
snprintf(val_tpl, sizeof(val_tpl), "{{ (value_json.%s | as_datetime).isoformat() }}", entity);
|
||||
doc["val_tpl"] = val_tpl;
|
||||
} else {
|
||||
doc["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + " else " + sample_val + "}}";
|
||||
// default
|
||||
doc["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + " else 'None'}}";
|
||||
}
|
||||
add_ha_avty_section(doc.as<JsonObject>(), stat_t, val_cond); // adds availability section
|
||||
} else {
|
||||
@@ -1141,7 +1174,7 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
|
||||
// https://developers.home-assistant.io/docs/core/entity/sensor/
|
||||
// Home Assistant tracks the min, max and mean value during the statistics period
|
||||
// For statistics, the sensor must have state_class set to either MEASUREMENT, TOTAL or TOTAL_INCREASING
|
||||
// When set to MEASUREMENT the device_class must cannot be DATE, ENUM, ENERGY, GAS, MONETARY, TIMESTAMP, VOLUME or WATER
|
||||
// When set to MEASUREMENT the device_class must cannot be DATE, ENUM, ENERGY, GAS, MONETARY, TIMESTAMP, UPTIME, VOLUME or WATER
|
||||
//
|
||||
void Mqtt::add_ha_classes(JsonObject doc, const uint8_t device_type, const uint8_t type, const uint8_t uom, const char * entity, bool display_only) {
|
||||
if (device_type == EMSdevice::DeviceType::SYSTEM) {
|
||||
@@ -1280,7 +1313,7 @@ void Mqtt::add_ha_classes(JsonObject doc, const uint8_t device_type, const uint8
|
||||
doc[sc_ha] = sc_ha_measurement;
|
||||
doc[dc_ha] = "timestamp";
|
||||
break;
|
||||
case DeviceValueUOM::UPTIME:
|
||||
case DeviceValueUOM::UPTIME: // no state class for uptime
|
||||
doc[dc_ha] = "uptime";
|
||||
break;
|
||||
case DeviceValueUOM::MV:
|
||||
@@ -1403,7 +1436,7 @@ bool Mqtt::publish_ha_climate_config(const DeviceValue & dv, const bool has_room
|
||||
|
||||
snprintf(mode_str_tpl,
|
||||
sizeof(mode_str_tpl),
|
||||
"{%%if %s%%}off{%%elif %s=='%s'%%}heat{%%elif %s=='%s'%%}heat{%%elif %s=='%s'%%}heat{%%elif %s=='%s'%%}off{%%elif %s=='%s'%%}off{%%elif "
|
||||
"{%%if %s%%}None{%%elif %s=='%s'%%}heat{%%elif %s=='%s'%%}heat{%%elif %s=='%s'%%}heat{%%elif %s=='%s'%%}off{%%elif %s=='%s'%%}off{%%elif "
|
||||
"%s=='%s'%%}off{%%else%%}auto{%%endif%%}",
|
||||
hc_mode_cond,
|
||||
hc_mode_s,
|
||||
@@ -1440,15 +1473,15 @@ bool Mqtt::publish_ha_climate_config(const DeviceValue & dv, const bool has_room
|
||||
doc["mode_stat_tpl"] = mode_str_tpl;
|
||||
doc["temp_cmd_t"] = temp_cmd_s;
|
||||
doc["temp_stat_t"] = topic_t;
|
||||
doc["temp_stat_tpl"] = (std::string) "{{" + seltemp_s + " if " + seltemp_cond + " else 0}}";
|
||||
doc["temp_stat_tpl"] = (std::string) "{{" + seltemp_s + " if " + seltemp_cond + " else 'None'}}";
|
||||
|
||||
if (has_roomtemp) {
|
||||
doc["curr_temp_t"] = topic_t;
|
||||
doc["curr_temp_tpl"] = (std::string) "{{" + currtemp_s + " if " + currtemp_cond + " else 0}}";
|
||||
doc["curr_temp_tpl"] = (std::string) "{{" + currtemp_s + " if " + currtemp_cond + " else 'None'}}";
|
||||
}
|
||||
if (tag >= DeviceValueTAG::TAG_SRC1) {
|
||||
doc["curr_hum_t"] = topic_t;
|
||||
doc["curr_hum_tpl"] = (std::string) "{{" + currhum_s + " if " + currhum_cond + " else 0}}";
|
||||
doc["curr_hum_tpl"] = (std::string) "{{" + currhum_s + " if " + currhum_cond + " else 'None'}}";
|
||||
}
|
||||
|
||||
doc["min_temp"] = Helpers::render_value(min_s, min, 0, EMSESP::system_.fahrenheit() ? 2 : 0);
|
||||
@@ -1602,9 +1635,6 @@ void Mqtt::add_ha_avty_section(JsonObject doc, const char * state_t, const char
|
||||
avty_json["t"] = state;
|
||||
snprintf(tpl, sizeof(tpl), "{{'online' if %s else 'offline'}}", cond1);
|
||||
avty_json["val_tpl"] = tpl;
|
||||
if (!avty.add(avty_json)) {
|
||||
LOG_WARNING("Failed to add availability condition 1 (low memory)");
|
||||
}
|
||||
}
|
||||
|
||||
// condition 2
|
||||
@@ -1613,9 +1643,6 @@ void Mqtt::add_ha_avty_section(JsonObject doc, const char * state_t, const char
|
||||
avty_json["t"] = state;
|
||||
snprintf(tpl, sizeof(tpl), "{{'online' if %s else 'offline'}}", cond2);
|
||||
avty_json["val_tpl"] = tpl;
|
||||
if (!avty.add(avty_json)) {
|
||||
LOG_WARNING("Failed to add availability condition 2 (low memory)");
|
||||
}
|
||||
}
|
||||
|
||||
// negative condition
|
||||
@@ -1624,9 +1651,6 @@ void Mqtt::add_ha_avty_section(JsonObject doc, const char * state_t, const char
|
||||
avty_json["t"] = state;
|
||||
snprintf(tpl, sizeof(tpl), "{{'offline' if %s else 'online'}}", negcond);
|
||||
avty_json["val_tpl"] = tpl;
|
||||
if (!avty.add(avty_json)) {
|
||||
LOG_WARNING("Failed to add negative availability condition (low memory)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-8
@@ -61,8 +61,9 @@ class Mqtt {
|
||||
enum Operation : uint8_t { PUBLISH, SUBSCRIBE, UNSUBSCRIBE };
|
||||
enum NestedFormat : uint8_t { NESTED = 1, SINGLE };
|
||||
|
||||
static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 128; // fixed, not a user setting anymore
|
||||
static constexpr uint16_t MQTT_QUEUE_MAX_SIZE = 300;
|
||||
static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 128; // fixed, not a user setting anymore
|
||||
static constexpr uint16_t MQTT_QUEUE_MAX_SIZE = 300;
|
||||
static constexpr uint32_t MQTT_DISCONNECT_TIMEOUT = 1000; // max ms to flush a graceful disconnect on non-PSRAM boards
|
||||
|
||||
static void on_connect();
|
||||
static void on_disconnect(espMqttClientTypes::DisconnectReason reason);
|
||||
@@ -132,11 +133,7 @@ class Mqtt {
|
||||
return mqttClient_ ? mqttClient_->connected() : false;
|
||||
}
|
||||
|
||||
static void disconnect() {
|
||||
if (mqttClient_) {
|
||||
mqttClient_->disconnect();
|
||||
};
|
||||
}
|
||||
static void disconnect();
|
||||
|
||||
static bool enabled() {
|
||||
return mqtt_enabled_;
|
||||
@@ -300,7 +297,7 @@ class Mqtt {
|
||||
uint32_t last_publish_heartbeat_ = 0;
|
||||
// uint32_t last_publish_queue_ = 0;
|
||||
|
||||
static bool connecting_;
|
||||
static bool connected_;
|
||||
static bool initialized_;
|
||||
static uint32_t mqtt_publish_fails_;
|
||||
static uint16_t queuecount_;
|
||||
|
||||
+179
-47
@@ -49,9 +49,10 @@ void Network::begin() {
|
||||
nosleep_ = settings.nosleep;
|
||||
tx_power_ = settings.tx_power;
|
||||
bssid_ = settings.bssid;
|
||||
eth_10mbit_ = settings.eth_10mbit;
|
||||
});
|
||||
|
||||
// read Ethernet settings
|
||||
// read Ethernet hardware settings
|
||||
EMSESP::webSettingsService.read([&](WebSettings & settings) {
|
||||
phy_type_ = settings.phy_type;
|
||||
eth_power_ = settings.eth_power;
|
||||
@@ -78,33 +79,16 @@ void Network::begin() {
|
||||
|
||||
phase_ = initialPhase();
|
||||
|
||||
// Initialise WiFi once when the Network service starts
|
||||
WiFi.persistent(false);
|
||||
WiFi.setAutoReconnect(false);
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.disconnect(true, true); // wipe old settings in NVS. Will give a warning on boot but can be ignored.
|
||||
WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN);
|
||||
WiFi.setHostname(hostname_.c_str()); // updates shared default_hostname buffer
|
||||
WiFi.enableSTA(true); // creates the STA netif
|
||||
WiFi.STA.setHostname(hostname_.c_str()); // pushes to esp_netif_set_hostname
|
||||
WiFi.enableIPv6(true);
|
||||
if (staticIPConfig_) {
|
||||
WiFi.config(localIP_, gatewayIP_, subnetMask_, dnsIP1_, dnsIP2_); // configure for static IP
|
||||
}
|
||||
startWiFiRadio();
|
||||
|
||||
// www.esp32.com/viewtopic.php?t=12055
|
||||
if (bandwidth20_) {
|
||||
esp_wifi_set_bandwidth(static_cast<wifi_interface_t>(ESP_IF_WIFI_STA), WIFI_BW_HT20);
|
||||
} else {
|
||||
esp_wifi_set_bandwidth(static_cast<wifi_interface_t>(ESP_IF_WIFI_STA), WIFI_BW_HT40);
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
// Ethernet is switched off but the board still has a PHY wired up. Hold its power/oscillator
|
||||
// enable low so an unused LAN8720 isn't left clocked and burning current for the whole uptime.
|
||||
if (phy_type_ == PHY_type::PHY_TYPE_NONE && eth_power_ != -1) {
|
||||
pinMode(eth_power_, OUTPUT);
|
||||
digitalWrite(eth_power_, LOW);
|
||||
}
|
||||
if (nosleep_) {
|
||||
WiFi.setSleep(false); // turn off sleep - WIFI_PS_NONE
|
||||
}
|
||||
|
||||
// scan settings give connect issues since arduino 2.0.14 and arduino 3.x.x with some wifi systems
|
||||
// WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); // default is FAST_SCAN
|
||||
// WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL); // is default, no need to set
|
||||
#endif
|
||||
|
||||
// avoid duplicate registration, so register the lambdas only once across the lifetime of this Network instance
|
||||
if (!wifi_events_registered_) {
|
||||
@@ -152,7 +136,7 @@ void Network::begin() {
|
||||
}
|
||||
|
||||
// format the BSSID (MAC address) of the network interface
|
||||
bool Network::formatBSSID(const String & bssid, uint8_t (&mac)[6]) {
|
||||
bool Network::formatBSSID([[maybe_unused]] const String & bssid, [[maybe_unused]] uint8_t (&mac)[6]) {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
uint tmp[6];
|
||||
if (bssid.isEmpty() || sscanf(bssid.c_str(), "%X:%X:%X:%X:%X:%X", &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4], &tmp[5]) != 6) {
|
||||
@@ -214,10 +198,10 @@ void Network::reconnect() {
|
||||
LOG_DEBUG("Reconnecting all networks");
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
// disconnect WiFi
|
||||
// Drop STA association but leave the driver running. begin() re-applies hostname
|
||||
// and IPv6; tearing the radio down here races with WIFI_EVENT_STA_START (12289/259).
|
||||
if (wifi_connected()) {
|
||||
WiFi.disconnect(true, true);
|
||||
WiFi.mode(WIFI_STA); // reset mode
|
||||
WiFi.disconnect(false, true);
|
||||
}
|
||||
|
||||
// disconnect AP
|
||||
@@ -243,11 +227,12 @@ void Network::reconnect() {
|
||||
|
||||
// pick the first phase that has the hardware/config to even be attempted on this device.
|
||||
// boards without an Ethernet PHY skip straight to WIFI; without a configured SSID, straight to AP
|
||||
// (unless AP provision mode is Never — then stay on WIFI and keep waiting)
|
||||
NetPhase Network::initialPhase() const {
|
||||
if (phy_type_ != PHY_type::PHY_TYPE_NONE) {
|
||||
return NetPhase::ETHERNET;
|
||||
}
|
||||
if (!ssid_.isEmpty()) {
|
||||
if (!ssid_.isEmpty() || ap_provisionMode_ == AP_MODE_NEVER) {
|
||||
return NetPhase::WIFI;
|
||||
}
|
||||
return NetPhase::AP;
|
||||
@@ -256,6 +241,16 @@ NetPhase Network::initialPhase() const {
|
||||
// network loop, looking for new and disconnecting networks
|
||||
void Network::loop() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
// a reconnect was requested from another task, run it here where nothing is mid-request
|
||||
if (reconnect_pending_) {
|
||||
reconnect_pending_ = false;
|
||||
reconnect();
|
||||
}
|
||||
// already have a connection: verify it's still alive
|
||||
if (network_ip_ != 0 || last_disconnect_reason_ != 0) {
|
||||
checkConnection();
|
||||
}
|
||||
|
||||
// if we already have a Wifi or Ethernet connection then re-check every NETWORK_RECONNECTION_DELAY_LONG, otherwise NETWORK_RECONNECTION_DELAY_SHORT
|
||||
const unsigned long currentMillis = uuid::get_uptime_ms();
|
||||
const uint32_t reconnectDelay =
|
||||
@@ -268,11 +263,6 @@ void Network::loop() {
|
||||
startWIFI(); // WiFi
|
||||
startAP(); // Captive Portal (AP)
|
||||
|
||||
// already have a connection: verify it's still alive
|
||||
if (network_ip_ != 0 || last_disconnect_reason_ != 0) {
|
||||
checkConnection();
|
||||
}
|
||||
|
||||
findNetworks(); // detect any new network connections
|
||||
}
|
||||
|
||||
@@ -314,6 +304,10 @@ void Network::checkConnection() {
|
||||
network_iface_ = NetIface::NONE;
|
||||
if (lost_iface == NetIface::ETHERNET) {
|
||||
LOG_WARNING("Ethernet connection lost");
|
||||
// restore the radio now so the WiFi and AP fallbacks have something to work with
|
||||
if (wifi_radio_off_) {
|
||||
startWiFiRadio();
|
||||
}
|
||||
return;
|
||||
}
|
||||
begin();
|
||||
@@ -331,7 +325,7 @@ void Network::checkConnection() {
|
||||
// 802.11ac - wifi5
|
||||
// 802.11ax - wifi6
|
||||
// tx_power is the Tx power to set, 0 for auto
|
||||
void Network::setWiFiPower(uint8_t tx_power) {
|
||||
void Network::setWiFiPower([[maybe_unused]] uint8_t tx_power) {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
if (tx_power != 0) {
|
||||
if (!WiFi.setTxPower(static_cast<wifi_power_t>(tx_power))) {
|
||||
@@ -413,7 +407,7 @@ void Network::startmDNS() const {
|
||||
}
|
||||
|
||||
// WiFi disconnect reason code to string
|
||||
const char * Network::disconnectReason(uint8_t code) {
|
||||
const char * Network::disconnectReason([[maybe_unused]] uint8_t code) {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
switch (code) {
|
||||
case WIFI_REASON_UNSPECIFIED: // = 1,
|
||||
@@ -490,6 +484,59 @@ const char * Network::disconnectReason(uint8_t code) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Bring up the STA interface and apply all the radio-level settings to it
|
||||
// Safe to call repeatedly - enableSTA() is a no-op when the STA is already running, and recreates the netif after stopWiFiRadio() has torn it down
|
||||
void Network::startWiFiRadio() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
WiFi.persistent(false); // keep credentials in RAM so we don't auto-join leftover NVS config
|
||||
WiFi.setAutoReconnect(false);
|
||||
WiFi.mode(WIFI_STA);
|
||||
|
||||
// Keep the radio up. disconnect(wifioff=true) maps to STA.end() -> esp_wifi_stop()
|
||||
if (WiFi.STA.started()) {
|
||||
WiFi.disconnect(false, true); // drop leftover association, wipe RAM config
|
||||
}
|
||||
|
||||
// scan settings give connect issues since arduino 2.0.14 and arduino 3.x.x with some wifi systems
|
||||
WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); // default is FAST_SCAN
|
||||
WiFi.setHostname(hostname_.c_str()); // updates shared default_hostname buffer
|
||||
WiFi.enableSTA(true); // no-op if already STA; recreates netif after a full off
|
||||
WiFi.STA.setHostname(hostname_.c_str()); // pushes to esp_netif_set_hostname
|
||||
WiFi.enableIPv6(true);
|
||||
if (staticIPConfig_) {
|
||||
WiFi.config(localIP_, gatewayIP_, subnetMask_, dnsIP1_, dnsIP2_); // configure for static IP
|
||||
}
|
||||
|
||||
// www.esp32.com/viewtopic.php?t=12055
|
||||
if (bandwidth20_) {
|
||||
esp_wifi_set_bandwidth(static_cast<wifi_interface_t>(ESP_IF_WIFI_STA), WIFI_BW_HT20);
|
||||
} else {
|
||||
esp_wifi_set_bandwidth(static_cast<wifi_interface_t>(ESP_IF_WIFI_STA), WIFI_BW_HT40);
|
||||
}
|
||||
if (nosleep_) {
|
||||
WiFi.setSleep(false); // turn off sleep - WIFI_PS_NONE
|
||||
} else {
|
||||
WiFi.setSleep(true); // WIFI_PS_MIN_MODEM
|
||||
}
|
||||
|
||||
wifi_radio_off_ = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Power down the WiFi radio completely (esp_wifi_stop), used when Ethernet is carrying the traffic.
|
||||
// An idle STA still keeps the receiver powered. It also unloads the driver's DMA buffer pool of around 20KB
|
||||
void Network::stopWiFiRadio() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
if (wifi_radio_off_) {
|
||||
return;
|
||||
}
|
||||
WiFi.mode(WIFI_OFF);
|
||||
wifi_radio_off_ = true;
|
||||
wifi_connect_pending_ = false;
|
||||
LOG_DEBUG("Powering down WiFi radio, Ethernet is active");
|
||||
#endif
|
||||
}
|
||||
|
||||
// WiFi management
|
||||
void Network::startWIFI() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
@@ -498,6 +545,11 @@ void Network::startWIFI() {
|
||||
return;
|
||||
}
|
||||
|
||||
// the radio was powered down while Ethernet was up, so bring it back before trying to associate
|
||||
if (wifi_radio_off_) {
|
||||
startWiFiRadio();
|
||||
}
|
||||
|
||||
// exit if WiFi or Ethernet is already connected, or if we have no SSID or another Wifi.begin() is already in progress
|
||||
if (WiFi.isConnected() || ssid_.isEmpty() || ethernet_connected() || wifi_connect_pending_) {
|
||||
return;
|
||||
@@ -516,6 +568,15 @@ void Network::startWIFI() {
|
||||
#endif
|
||||
}
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32 && !defined(EMSESP_STANDALONE)
|
||||
static char eth_hostname[33] = {0};
|
||||
static void ethApplyHostname(void * /*arg*/, esp_event_base_t /*base*/, int32_t /*event_id*/, void * /*event_data*/) {
|
||||
if (eth_hostname[0] != '\0' && ETH.netif() != nullptr) {
|
||||
esp_netif_set_hostname(ETH.netif(), eth_hostname);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Ethernet management
|
||||
// Brings up the ETH driver / netif exactly once. After ETH.begin() returns true the driver
|
||||
// continues to run autonomously (link negotiation, DHCP, etc)
|
||||
@@ -527,8 +588,8 @@ void Network::startEthernet() {
|
||||
return;
|
||||
}
|
||||
|
||||
// already up and running, nothing to do
|
||||
if (ethernet_connect_pending_ || ethernet_connected()) {
|
||||
// Ensure the Ethernet driver is installed once and then left alone
|
||||
if (ethernet_started_ || ethernet_connect_pending_ || ethernet_connected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -539,6 +600,17 @@ void Network::startEthernet() {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
// The Tasmota SDK is built with CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES=2, so the Ethernet MAC is derived as
|
||||
// a locally-administered address from base+1 instead of the universal base+3 that Arduino Core 2 used.
|
||||
// Seed the MAC table with the old value before the EMAC driver reads it, so DHCP reservations survive an upgrade.
|
||||
uint8_t eth_mac[6];
|
||||
if (esp_read_mac(eth_mac, ESP_MAC_BASE) == ESP_OK) {
|
||||
eth_mac[5] += 3;
|
||||
esp_iface_mac_addr_set(eth_mac, ESP_MAC_ETH);
|
||||
}
|
||||
*/
|
||||
|
||||
// reset power and add a delay as ETH doesn't not always start up correctly after a warm boot
|
||||
if (eth_power_ != -1) {
|
||||
pinMode(eth_power_, OUTPUT);
|
||||
@@ -560,16 +632,52 @@ void Network::startEthernet() {
|
||||
eth_phy_type_t type = (phy_type_ == PHY_type::PHY_TYPE_LAN8720) ? ETH_PHY_LAN8720
|
||||
: (phy_type_ == PHY_type::PHY_TYPE_TLK110) ? ETH_PHY_TLK110
|
||||
: ETH_PHY_RTL8201; // Type of the Ethernet PHY (LAN8720 or TLK110)
|
||||
|
||||
// must be in place before ETH.begin() so it beats the esp_netif glue to the START event
|
||||
strlcpy(eth_hostname, hostname_.c_str(), sizeof(eth_hostname));
|
||||
if (!eth_hostname_handler_registered_ && esp_event_handler_register(ETH_EVENT, ETHERNET_EVENT_START, ethApplyHostname, nullptr) == ESP_OK) {
|
||||
eth_hostname_handler_registered_ = true;
|
||||
}
|
||||
|
||||
// Forcing 10BASE-T roughly halves the PHY's power draw
|
||||
if (eth_10mbit_) {
|
||||
ETH.setAutoNegotiation(false);
|
||||
ETH.setLinkSpeed(10);
|
||||
ETH.setFullDuplex(false);
|
||||
}
|
||||
|
||||
if (ETH.begin(type, eth_phy_addr_, 23, 18, eth_power_, (eth_clock_mode_t)eth_clock_mode_)) {
|
||||
LOG_DEBUG("Ethernet module found - starting");
|
||||
// LOG_DEBUG("Ethernet module found - initialising");
|
||||
ethernet_started_ = true;
|
||||
ethernet_connect_pending_ = true;
|
||||
ETH.setHostname(hostname_.c_str()); // Push hostname to the ETH netif immediately after it's created
|
||||
|
||||
// check whether DHCP already announced us under lwIP's default hostname, before we correct it
|
||||
esp_netif_t * eth_netif = ETH.netif();
|
||||
const char * old_hostname = nullptr;
|
||||
bool stale_dhcp = false;
|
||||
if (eth_netif != nullptr && !staticIPConfig_) {
|
||||
esp_netif_dhcp_status_t dhcp_status = ESP_NETIF_DHCP_INIT;
|
||||
stale_dhcp = esp_netif_get_hostname(eth_netif, &old_hostname) == ESP_OK && old_hostname != nullptr && hostname_ != old_hostname
|
||||
&& esp_netif_dhcpc_get_status(eth_netif, &dhcp_status) == ESP_OK && dhcp_status == ESP_NETIF_DHCP_STARTED;
|
||||
}
|
||||
|
||||
if (!ETH.setHostname(hostname_.c_str())) {
|
||||
LOG_WARNING("Failed to set Ethernet hostname to %s", hostname_.c_str());
|
||||
}
|
||||
|
||||
// the DHCP server has already been given the wrong name, so re-run the client to re-announce
|
||||
if (stale_dhcp) {
|
||||
LOG_DEBUG("Restarting Ethernet DHCP client to announce hostname %s", hostname_.c_str());
|
||||
esp_netif_dhcpc_stop(eth_netif);
|
||||
esp_netif_dhcpc_start(eth_netif);
|
||||
}
|
||||
|
||||
ETH.enableIPv6(true);
|
||||
if (staticIPConfig_) {
|
||||
ETH.config(localIP_, gatewayIP_, subnetMask_, dnsIP1_, dnsIP2_);
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR("Failed to start Ethernet module");
|
||||
LOG_ERROR("Failed to start Ethernet");
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -614,6 +722,11 @@ void Network::findNetworks() {
|
||||
}
|
||||
|
||||
const NetIface candidate = iface_from_desc(desc);
|
||||
// AP is disabled in settings — don't treat SoftAP as a usable network
|
||||
if (candidate == NetIface::AP && ap_provisionMode_ == AP_MODE_NEVER) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (iface_priority(candidate) <= iface_priority(best_iface)) {
|
||||
continue; // already have something at least as good
|
||||
}
|
||||
@@ -662,6 +775,11 @@ void Network::findNetworks() {
|
||||
stopAP();
|
||||
}
|
||||
|
||||
// Ethernet is carrying the traffic so the STA is dead weight - shut the radio down.
|
||||
if (network_iface_ == NetIface::ETHERNET) {
|
||||
stopWiFiRadio();
|
||||
}
|
||||
|
||||
// count the number of restarts (for Wifi and Eth)
|
||||
if (juststopped_) {
|
||||
juststopped_ = false;
|
||||
@@ -692,12 +810,17 @@ void Network::findNetworks() {
|
||||
: "AP");
|
||||
|
||||
// give up on this interface and try the next one. ETHERNET -> WIFI (or straight to AP if no SSID configured); WIFI -> AP.
|
||||
// AP_MODE_NEVER: never promote to the AP phase — keep waiting on the current interface.
|
||||
if (connect_retry_ >= MAX_NETWORK_RECONNECTION_ATTEMPTS && phase_ != NetPhase::AP) {
|
||||
if (phase_ == NetPhase::ETHERNET) {
|
||||
if (ssid_.isEmpty()) {
|
||||
if (!ethernet_ever_connected_) {
|
||||
LOG_WARNING("Ethernet failed to connect after %u attempts, falling back to AP", connect_retry_);
|
||||
phase_ = NetPhase::AP;
|
||||
if (ap_provisionMode_ == AP_MODE_NEVER) {
|
||||
LOG_WARNING("Ethernet failed to connect after %u attempts (AP provision mode is Never)", connect_retry_);
|
||||
} else {
|
||||
LOG_WARNING("Ethernet failed to connect after %u attempts, falling back to AP", connect_retry_);
|
||||
phase_ = NetPhase::AP;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOG_WARNING("Ethernet failed to connect after %u attempts, switching to WiFi", connect_retry_);
|
||||
@@ -706,8 +829,12 @@ void Network::findNetworks() {
|
||||
ethernet_connect_pending_ = false;
|
||||
} else if (phase_ == NetPhase::WIFI) {
|
||||
if (!wifi_ever_connected_) {
|
||||
LOG_WARNING("WiFi failed to connect after %u attempts, falling back to AP", connect_retry_);
|
||||
phase_ = NetPhase::AP;
|
||||
if (ap_provisionMode_ == AP_MODE_NEVER) {
|
||||
LOG_WARNING("WiFi failed to connect after %u attempts (AP provision mode is Never)", connect_retry_);
|
||||
} else {
|
||||
LOG_WARNING("WiFi failed to connect after %u attempts, falling back to AP", connect_retry_);
|
||||
phase_ = NetPhase::AP;
|
||||
}
|
||||
}
|
||||
wifi_connect_pending_ = false;
|
||||
}
|
||||
@@ -737,6 +864,11 @@ void Network::startAP() {
|
||||
return;
|
||||
}
|
||||
|
||||
// the AP needs the radio, which may have been powered down while Ethernet was up
|
||||
if (wifi_radio_off_) {
|
||||
startWiFiRadio();
|
||||
}
|
||||
|
||||
if (!WiFi.softAPConfig(ap_localIP_, ap_gatewayIP_, ap_subnetMask_)) {
|
||||
LOG_DEBUG("softAPConfig failed");
|
||||
return;
|
||||
|
||||
+16
-3
@@ -20,6 +20,7 @@
|
||||
#define EMSESP_NETWORK_H_
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
#include <esp_mac.h>
|
||||
#include <esp_wifi.h>
|
||||
#include <ETH.h>
|
||||
#include <WiFi.h>
|
||||
@@ -137,6 +138,11 @@ class Network {
|
||||
|
||||
void reconnect();
|
||||
|
||||
// ask for a reconnect() to run from the next Network::loop()
|
||||
void schedule_reconnect() {
|
||||
reconnect_pending_ = true;
|
||||
}
|
||||
|
||||
// map a netif description string (from esp_netif_get_desc) to a NetIface
|
||||
static NetIface iface_from_desc(const char * desc) {
|
||||
if (!desc) {
|
||||
@@ -180,6 +186,8 @@ class Network {
|
||||
void startAP();
|
||||
void startWIFI();
|
||||
void startEthernet();
|
||||
void startWiFiRadio();
|
||||
void stopWiFiRadio();
|
||||
void setWiFiPower(uint8_t tx_power);
|
||||
const char * disconnectReason(uint8_t code);
|
||||
void stopAP();
|
||||
@@ -201,12 +209,16 @@ class Network {
|
||||
|
||||
volatile bool wifi_connect_pending_ = false;
|
||||
volatile bool ethernet_connect_pending_ = false;
|
||||
volatile bool reconnect_pending_ = false; // set by schedule_reconnect(), actioned in loop()
|
||||
|
||||
NetPhase phase_ = NetPhase::ETHERNET;
|
||||
|
||||
bool wifi_events_registered_ = false; // ensure WiFi.onEvent() handlers are registered only once across begin()/reconnect() cycles
|
||||
bool wifi_ever_connected_ = false; // set true once we've successfully obtained an IP
|
||||
bool ethernet_ever_connected_ = false; // set true once we've successfully obtained an IP
|
||||
bool wifi_events_registered_ = false; // ensure WiFi.onEvent() handlers are registered only once across begin()/reconnect() cycles
|
||||
bool eth_hostname_handler_registered_ = false; // for the ETHERNET_EVENT_START hostname handler
|
||||
bool ethernet_started_ = false; // ETH.begin() succeeded; the driver runs for the lifetime of the boot
|
||||
bool wifi_ever_connected_ = false; // set true once we've successfully obtained an IP
|
||||
bool ethernet_ever_connected_ = false; // set true once we've successfully obtained an IP
|
||||
bool wifi_radio_off_ = false; // STA powered down because Ethernet is carrying the traffic
|
||||
|
||||
// Network and AP settings
|
||||
bool enableMDNS_;
|
||||
@@ -227,6 +239,7 @@ class Network {
|
||||
int8_t eth_power_;
|
||||
uint8_t eth_phy_addr_;
|
||||
uint8_t eth_clock_mode_;
|
||||
bool eth_10mbit_;
|
||||
|
||||
// AP settings
|
||||
uint8_t ap_provisionMode_;
|
||||
|
||||
+2
-2
@@ -36,7 +36,7 @@ void Shower::start() {
|
||||
Command::add_json(
|
||||
EMSdevice::DeviceType::BOILER,
|
||||
F_(coldshot),
|
||||
[&](const char * value, const int8_t id, JsonObject output) {
|
||||
[this](const char * value, const int8_t id, JsonObject output) {
|
||||
LOG_INFO("Forcing coldshot...");
|
||||
if (shower_state_) {
|
||||
output["message"] = "OK";
|
||||
@@ -229,7 +229,7 @@ void Shower::create_ha_discovery() {
|
||||
|
||||
// don't bother with value template conditions if using Domoticz which doesn't fully support MQTT Discovery
|
||||
if (Mqtt::discovery_type() == Mqtt::discoveryType::HOMEASSISTANT) {
|
||||
doc["val_tpl"] = "{{value_json.duration if value_json.duration is defined else 0}}";
|
||||
doc["val_tpl"] = "{{value_json.duration if value_json.duration is defined else 'None'}}";
|
||||
} else {
|
||||
doc["val_tpl"] = "{{value_json.duration}}";
|
||||
}
|
||||
|
||||
+28
-104
@@ -22,8 +22,7 @@
|
||||
|
||||
#include "shuntingYard.h"
|
||||
|
||||
#include <WiFiClient.h>
|
||||
#include <ESP_SSLClient.h>
|
||||
#include "httpClient.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
@@ -336,7 +335,6 @@ bool isnum(const std::string & s) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// replace commands like "<device>/<hc>/<cmd>" with its value"
|
||||
std::string commands(std::string & expr, bool quotes) {
|
||||
auto expr_new = Helpers::toLower(expr);
|
||||
@@ -481,14 +479,14 @@ std::string calculate(const std::string & expr) {
|
||||
const auto rhs = stack.back();
|
||||
stack.pop_back();
|
||||
if (token.str[0] == '!') {
|
||||
if (to_logic(rhs) < 0) {
|
||||
}
|
||||
if (to_logic(rhs) >= 0) {
|
||||
stack.push_back(to_logic(rhs) == 0 ? "1" : "0");
|
||||
const auto logic = to_logic(rhs);
|
||||
if (logic >= 0) {
|
||||
stack.push_back(logic == 0 ? "1" : "0");
|
||||
} else if (isnum(rhs)) {
|
||||
stack.push_back(std::stod(rhs) == 0 ? "1" : "0");
|
||||
} else {
|
||||
EMSESP::logger().warning("missing operator");
|
||||
// usually literal text that was left unquoted, so its '!' got read as a logical NOT
|
||||
EMSESP::logger().warning("'!' needs a boolean or numeric operand, got '%s'. Literal text must be quoted", rhs.c_str());
|
||||
return "";
|
||||
}
|
||||
break;
|
||||
@@ -691,96 +689,22 @@ std::string calculate(const std::string & expr) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// perform an HTTP/HTTPS request; returns the HTTP status code (0 on failure or unsupported scheme)
|
||||
// the response headers are always stripped, so `result` contains only the body
|
||||
int http_request(std::string url, const std::string & method, const std::string & value, JsonObjectConst headers, std::string & result) {
|
||||
int httpResult = 0;
|
||||
const bool is_post = value.length() || Helpers::toLower(method) == "post";
|
||||
const auto lower_url = Helpers::toLower(url.c_str());
|
||||
|
||||
const bool is_https = lower_url.starts_with("https://");
|
||||
if (!is_https && !lower_url.starts_with("http://")) {
|
||||
return 0; // unsupported scheme
|
||||
// find the next occurrence of c at or after `from` that is not inside a quoted string, so that
|
||||
// literal text like "too hot?" is not mistaken for a ternary. Always scans from the start of the
|
||||
// string because the quote state at `from` depends on everything before it.
|
||||
static size_t find_unquoted(const std::string & s, char c, size_t from = 0) {
|
||||
bool in_single = false;
|
||||
bool in_double = false;
|
||||
for (size_t i = 0; i < s.length(); i++) {
|
||||
if (!in_single && s[i] == '"') {
|
||||
in_double = !in_double;
|
||||
} else if (!in_double && s[i] == '\'') {
|
||||
in_single = !in_single;
|
||||
} else if (!in_single && !in_double && s[i] == c && i >= from) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
WiFiClient * basic_client = new WiFiClient;
|
||||
ESP_SSLClient * ssl_client = new ESP_SSLClient;
|
||||
if (is_https) {
|
||||
ssl_client->setInsecure(); // with root CA we should set here: ssl_client->setCACert(rootCACert);
|
||||
// NOTE: 1 KB RX buffer is fine for small JSON-style endpoints used by the scheduler/shunting-yard,
|
||||
// but it is NOT enough for servers that send full-size TLS records (>1 KB), e.g. GitHub release
|
||||
// assets / large CDN responses. Such servers do not negotiate max_fragment_length, so the body
|
||||
// can't be decoded and reads return 0. If this path is ever used to fetch large or CDN-hosted
|
||||
// payloads, bump the RX buffer to 16384 (see uploadFirmwareURL in core/system.cpp for reference).
|
||||
ssl_client->setBufferSizes(1024, 1024);
|
||||
ssl_client->setSessionTimeout(120); // Set the timeout in seconds (>=120 seconds)
|
||||
}
|
||||
ssl_client->setClient(basic_client, is_https); // enableSSL = false for plain HTTP
|
||||
|
||||
url.replace(0, is_https ? 8 : 7, "");
|
||||
std::string host = url;
|
||||
auto index = url.find_first_of('/');
|
||||
if (index != std::string::npos) {
|
||||
host = url.substr(0, index);
|
||||
url.replace(0, index, "");
|
||||
} else {
|
||||
url = "/";
|
||||
}
|
||||
|
||||
const uint16_t port = is_https ? 443 : 80;
|
||||
if (ssl_client->connect(host.c_str(), port)) {
|
||||
bool content_set = false;
|
||||
ssl_client->print(is_post ? "POST " : "GET ");
|
||||
ssl_client->print(url.c_str());
|
||||
ssl_client->println(" HTTP/1.1");
|
||||
ssl_client->print("Host: ");
|
||||
ssl_client->println(host.c_str());
|
||||
for (JsonPairConst p : headers) {
|
||||
content_set |= (Helpers::toLower(p.key().c_str()) == "content-type");
|
||||
ssl_client->print(p.key().c_str());
|
||||
ssl_client->print(": ");
|
||||
ssl_client->println(p.value().as<std::string>().c_str());
|
||||
}
|
||||
if (is_post) {
|
||||
if (!content_set) {
|
||||
ssl_client->print("Content-Type: ");
|
||||
ssl_client->println(value.starts_with('{') ? asyncsrv::T_application_json : asyncsrv::T_text_plain);
|
||||
}
|
||||
ssl_client->print("Content-Length: ");
|
||||
ssl_client->println(value.length());
|
||||
ssl_client->println("Connection: close");
|
||||
ssl_client->print("\r\n");
|
||||
ssl_client->print(value.c_str());
|
||||
} else {
|
||||
ssl_client->println("Connection: close");
|
||||
ssl_client->print("\r\n"); // terminate headers - without this the server never responds
|
||||
}
|
||||
|
||||
auto ms = millis();
|
||||
while (ssl_client->connected() && !ssl_client->available() && millis() - ms < 3000) {
|
||||
delay(1);
|
||||
}
|
||||
|
||||
while (ssl_client->available()) {
|
||||
result += (char)ssl_client->read();
|
||||
}
|
||||
ssl_client->stop();
|
||||
|
||||
index = result.find_first_of(' ');
|
||||
if (index != std::string::npos) {
|
||||
httpResult = stoi(result.substr(index + 1, 3));
|
||||
}
|
||||
index = result.find("\r\n\r\n");
|
||||
if (index != std::string::npos) {
|
||||
result.replace(0, index + 4, "");
|
||||
}
|
||||
} else {
|
||||
EMSESP::logger().warning("%s connection failed", is_https ? "HTTPS" : "HTTP");
|
||||
}
|
||||
delete ssl_client;
|
||||
delete basic_client;
|
||||
|
||||
return httpResult;
|
||||
return std::string::npos;
|
||||
}
|
||||
|
||||
// check for multiple instances of <cond> ? <expr1> : <expr2>
|
||||
@@ -827,7 +751,7 @@ std::string compute(const std::string & expr) {
|
||||
std::string method = doc[method_s] | "GET";
|
||||
std::string result;
|
||||
|
||||
int httpResult = http_request(url, method, value, doc[header_s].as<JsonObjectConst>(), result);
|
||||
int httpResult = HttpClient::request(url, method, value, doc[header_s].as<JsonObjectConst>(), result);
|
||||
if (httpResult == 200) {
|
||||
std::string key = doc[key_s] | "";
|
||||
JsonDocument keys_doc; // JsonDocument to hold "keys" after doc is parsed with HTTP body
|
||||
@@ -866,14 +790,14 @@ std::string compute(const std::string & expr) {
|
||||
}
|
||||
|
||||
// positions: q-questionmark, c-colon
|
||||
auto q = expr_new.find_first_of('?');
|
||||
auto q = find_unquoted(expr_new, '?');
|
||||
while (q != std::string::npos) {
|
||||
// find corresponding colon
|
||||
auto c1 = expr_new.find_first_of(':', q + 1);
|
||||
auto q1 = expr_new.find_first_of('?', q + 1);
|
||||
auto c1 = find_unquoted(expr_new, ':', q + 1);
|
||||
auto q1 = find_unquoted(expr_new, '?', q + 1);
|
||||
while (q1 < c1 && q1 != std::string::npos && c1 != std::string::npos) {
|
||||
q1 = expr_new.find_first_of('?', q1 + 1);
|
||||
c1 = expr_new.find_first_of(':', c1 + 1);
|
||||
q1 = find_unquoted(expr_new, '?', q1 + 1);
|
||||
c1 = find_unquoted(expr_new, ':', c1 + 1);
|
||||
}
|
||||
if (c1 == std::string::npos) {
|
||||
return ""; // error: missing colon
|
||||
@@ -904,7 +828,7 @@ std::string compute(const std::string & expr) {
|
||||
} else {
|
||||
return ""; // error
|
||||
}
|
||||
q = expr_new.find_first_of('?'); // search next instance
|
||||
q = find_unquoted(expr_new, '?'); // search next instance
|
||||
}
|
||||
|
||||
return calculate(expr_new);
|
||||
|
||||
@@ -84,8 +84,6 @@ std::string calculate(const std::string & expr);
|
||||
// check for multiple instances of <cond> ? <expr1> : <expr2>
|
||||
std::string compute(const std::string & expr);
|
||||
|
||||
int http_request(std::string url, const std::string & method, const std::string & value, JsonObjectConst headers, std::string & result);
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace emsesp
|
||||
#endif
|
||||
+113
-73
@@ -40,9 +40,6 @@
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
#define ENABLE_SMTP
|
||||
#define USE_ESP_SSLCLIENT
|
||||
#define READYCLIENT_SSL_CLIENT ESP_SSLClient
|
||||
#define READYCLIENT_TYPE_1 // TYPE 1 when using ESP_SSLClient
|
||||
#include <ESP_SSLClient.h>
|
||||
#include <ReadyMail.h>
|
||||
#endif
|
||||
@@ -93,6 +90,27 @@ uint32_t System::min_free_mem_;
|
||||
std::vector<uint8_t, AllocatorPSRAM<uint8_t>> System::valid_system_gpios_;
|
||||
std::vector<System::GpioUsage, AllocatorPSRAM<System::GpioUsage>> System::used_gpios_;
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
// TLSHandshakeCallback is a C function pointer, so STARTTLS needs a process-wide client slot.
|
||||
static ESP_SSLClient * sendmail_ssl_client = nullptr;
|
||||
|
||||
static void sendmail_tls_handshake(bool & success) {
|
||||
success = sendmail_ssl_client != nullptr && sendmail_ssl_client->connectSSL();
|
||||
}
|
||||
|
||||
// The DNS slots are shared between IPv4 and IPv6, so with IPv6 enabled the first slot can hold an
|
||||
// IPv6 server. Pick the first IPv4 one so it matches the label we print it under.
|
||||
static IPAddress dns_ipv4(const NetworkInterface & netif) {
|
||||
for (uint8_t i = 0; i < ESP_NETIF_DNS_MAX; i++) {
|
||||
IPAddress ip = netif.dnsIP(i);
|
||||
if (ip.type() == IPv4 && static_cast<uint32_t>(ip) != 0) {
|
||||
return ip;
|
||||
}
|
||||
}
|
||||
return IPAddress();
|
||||
}
|
||||
#endif
|
||||
|
||||
// find the index of the language
|
||||
// 0 = EN, 1 = DE, etc...
|
||||
uint8_t System::language_index() {
|
||||
@@ -110,7 +128,7 @@ bool System::command_send(const char * value, const int8_t id) {
|
||||
}
|
||||
|
||||
// send email via SMTP
|
||||
bool System::command_sendmail(const char * value, const int8_t id) {
|
||||
bool System::command_sendmail(const char * value, const int8_t) {
|
||||
bool enabled = false;
|
||||
uint8_t security;
|
||||
uint16_t port;
|
||||
@@ -132,60 +150,89 @@ bool System::command_sendmail(const char * value, const int8_t id) {
|
||||
LOG_DEBUG("Command sendmail port %d%s called with '%s'",
|
||||
port,
|
||||
security == EMAIL_SECURITY::SSL ? " (SSL)"
|
||||
: security == EMAIL_SECURITY::STARTTLS ? ""
|
||||
: " (STARTTLS)",
|
||||
: security == EMAIL_SECURITY::STARTTLS ? " (STARTTLS)"
|
||||
: " (plain)",
|
||||
value);
|
||||
|
||||
bool success = false;
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
WiFiClient * basic_client = new WiFiClient;
|
||||
ESP_SSLClient * ssl_client = new ESP_SSLClient;
|
||||
ReadyClient * r_client = new ReadyClient(*ssl_client);
|
||||
SMTPClient * smtp = new SMTPClient(*r_client);
|
||||
|
||||
ssl_client->setClient(basic_client);
|
||||
ssl_client->setInsecure();
|
||||
ssl_client->setBufferSizes(1024, 1024);
|
||||
r_client->addPort(port,
|
||||
security == EMAIL_SECURITY::NONE ? readymail_protocol_plain_text
|
||||
: security == EMAIL_SECURITY::SSL ? readymail_protocol_ssl
|
||||
: readymail_protocol_tls);
|
||||
|
||||
|
||||
// smtp->connect(server, port, sendmailCallback);
|
||||
smtp->connect(server, port);
|
||||
if (!smtp->isConnected()) {
|
||||
LOG_ERROR("send email connection error");
|
||||
delete smtp;
|
||||
delete r_client;
|
||||
delete ssl_client;
|
||||
delete basic_client;
|
||||
return false;
|
||||
}
|
||||
|
||||
// LOG_INFO("authenticate %s:%s", login.c_str(), pass.c_str());
|
||||
smtp->authenticate(login, pass, readymail_auth_password);
|
||||
if (!smtp->isAuthenticated()) {
|
||||
LOG_ERROR("send email authentication error");
|
||||
delete smtp;
|
||||
delete r_client;
|
||||
delete ssl_client;
|
||||
delete basic_client;
|
||||
return false;
|
||||
}
|
||||
// Resolve the message before opening the SMTP session: compute() can do entity lookups and a
|
||||
// blocking {url} fetch, which would otherwise leave the connection idle long enough to time out.
|
||||
// The value is either a plain body or a JSON envelope overriding the configured subject/to/from.
|
||||
JsonDocument doc(PSRAM_DOC);
|
||||
String body = value;
|
||||
if (body.length()) {
|
||||
auto error = deserializeJson(doc, (const char *)value);
|
||||
if (!error && doc.as<JsonObject>().size() >= 0) {
|
||||
if (!error && doc.is<JsonObject>()) {
|
||||
subject = doc["subject"] | subject;
|
||||
recp = doc["to"] | recp;
|
||||
sender = doc["from"] | sender;
|
||||
body = doc["body"] | body;
|
||||
}
|
||||
}
|
||||
// keep the original body if the calculator returns nothing, so unquoted literal text still gets sent
|
||||
std::string computed_body = compute(body.c_str());
|
||||
if (!computed_body.empty()) {
|
||||
body = computed_body.c_str();
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
// Do not use ReadyClient: after the 220 greeting it calls connectSSL() whenever
|
||||
// STARTTLS is off, so Security=Off still sends a TLS Client Hello on port 25.
|
||||
auto * basic_client = new WiFiClient;
|
||||
ESP_SSLClient * ssl_client = nullptr;
|
||||
SMTPClient * smtp = nullptr;
|
||||
|
||||
basic_client->setTimeout(5000);
|
||||
|
||||
const bool implicit_ssl = (security == EMAIL_SECURITY::SSL);
|
||||
const bool start_tls = (security == EMAIL_SECURITY::STARTTLS);
|
||||
const bool tls_enabled = (security != EMAIL_SECURITY::NONE);
|
||||
|
||||
if (security == EMAIL_SECURITY::NONE) {
|
||||
smtp = new SMTPClient(*basic_client);
|
||||
} else {
|
||||
ssl_client = new ESP_SSLClient;
|
||||
ssl_client->setInsecure();
|
||||
ssl_client->setBufferSizes(16384, 1024);
|
||||
ssl_client->setTimeout(5);
|
||||
// enableSSL is baked in at setClient() — true only for implicit SSL (port 465)
|
||||
ssl_client->setClient(basic_client, implicit_ssl);
|
||||
if (start_tls) {
|
||||
sendmail_ssl_client = ssl_client;
|
||||
smtp = new SMTPClient(*ssl_client, sendmail_tls_handshake, true);
|
||||
} else {
|
||||
smtp = new SMTPClient(*ssl_client);
|
||||
}
|
||||
}
|
||||
|
||||
auto cleanup = [&]() {
|
||||
delete smtp;
|
||||
sendmail_ssl_client = nullptr;
|
||||
delete ssl_client;
|
||||
delete basic_client;
|
||||
};
|
||||
|
||||
if (!smtp->connect(server, port, String(""), static_cast<SMTPResponseCallback>(nullptr), tls_enabled)) {
|
||||
LOG_ERROR("send email connection error: %s", smtp->status().text.c_str());
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
if (!smtp->isConnected()) {
|
||||
LOG_ERROR("send email connection error: %s", smtp->status().text.c_str());
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
// internal/open relays often have no AUTH; empty login means skip it
|
||||
if (!login.isEmpty()) {
|
||||
smtp->authenticate(login, pass, readymail_auth_password);
|
||||
if (!smtp->isAuthenticated()) {
|
||||
LOG_ERROR("send email authentication error: %s", smtp->status().text.c_str());
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
SMTPMessage & msg = smtp->getMessage();
|
||||
msg.headers.add(rfc822_subject, subject);
|
||||
msg.headers.add(rfc822_from, sender);
|
||||
@@ -195,12 +242,6 @@ bool System::command_sendmail(const char * value, const int8_t id) {
|
||||
// msg.headers.addCustom("Importance", PRIORITY);
|
||||
// msg.headers.addCustom("X-MSMail-Priority", PRIORITY);
|
||||
// msg.headers.addCustom("X-Priority", PRIORITY_NUM);
|
||||
// run the body through the Shunting Yard calculator (entity substitution, expressions, optional {url} fetch)
|
||||
// keep the original body if the calculator returns nothing
|
||||
std::string computed_body = compute(body.c_str());
|
||||
if (!computed_body.empty()) {
|
||||
body = computed_body.c_str();
|
||||
}
|
||||
msg.text.body(body);
|
||||
|
||||
// bodyText.replace("\r\n", "<br>\r\n");
|
||||
@@ -214,11 +255,11 @@ bool System::command_sendmail(const char * value, const int8_t id) {
|
||||
msg.timestamp = time(nullptr);
|
||||
|
||||
success = smtp->send(msg);
|
||||
if (!success) {
|
||||
LOG_ERROR("send email failed: %s", smtp->status().text.c_str());
|
||||
}
|
||||
|
||||
delete smtp;
|
||||
delete r_client;
|
||||
delete ssl_client;
|
||||
delete basic_client;
|
||||
cleanup();
|
||||
#endif
|
||||
return success;
|
||||
}
|
||||
@@ -487,7 +528,7 @@ void System::set_partition_install_date() {
|
||||
}
|
||||
|
||||
// sets the partition to use on the next restart
|
||||
bool System::set_partition(const char * partitionname) {
|
||||
bool System::set_partition([[maybe_unused]] const char * partitionname) {
|
||||
#ifdef EMSESP_STANDALONE
|
||||
return true;
|
||||
#else
|
||||
@@ -572,7 +613,7 @@ void System::system_restart(const char * partitionname) {
|
||||
}
|
||||
Serial.flush(); // wait for hardware TX buffer to drain
|
||||
|
||||
Mqtt::disconnect(); // gracefully disconnect MQTT, needed for QOS1
|
||||
Mqtt::disconnect(); // gracefully disconnect MQTT (flushes the DISCONNECT before reboot, needed for QOS1)
|
||||
EMSuart::stop(); // stop UART so there is no interference
|
||||
#ifndef EMSESP_STANDALONE
|
||||
delay(1000); // wait 1 second
|
||||
@@ -839,7 +880,7 @@ void System::send_info_mqtt() {
|
||||
|
||||
// if NTP is enabled send the boot_time in local time in ISO 8601 format (eg: 2022-11-15 20:46:38)
|
||||
// https://github.com/emsesp/EMS-ESP32/issues/751
|
||||
if (ntp_connected_) {
|
||||
if (time(nullptr) > 1500000000L) {
|
||||
char time_string[25];
|
||||
time_t now = time(nullptr) - uuid::get_uptime_sec();
|
||||
strftime(time_string, 25, "%FT%T%z", localtime(&now));
|
||||
@@ -858,8 +899,7 @@ void System::send_info_mqtt() {
|
||||
doc["MAC"] = WiFi.macAddress();
|
||||
doc["IPv4 address"] = uuid::printable_to_string(WiFi.localIP()) + "/" + uuid::printable_to_string(WiFi.subnetMask());
|
||||
doc["IPv4 gateway"] = uuid::printable_to_string(WiFi.gatewayIP());
|
||||
doc["IPv4 nameserver"] = uuid::printable_to_string(WiFi.dnsIP());
|
||||
|
||||
doc["IPv4 DNS Server"] = uuid::printable_to_string(dns_ipv4(WiFi.STA));
|
||||
if (WiFi.linkLocalIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000" && WiFi.linkLocalIPv6().toString() != "::") {
|
||||
doc["IPv6 address"] = uuid::printable_to_string(WiFi.linkLocalIPv6());
|
||||
}
|
||||
@@ -1168,7 +1208,7 @@ void System::show_system(uuid::console::Shell & shell) {
|
||||
shell.printfln(" Hostname: %s", WiFi.getHostname());
|
||||
shell.printfln(" IPv4 address: %s/%s", uuid::printable_to_string(WiFi.localIP()).c_str(), uuid::printable_to_string(WiFi.subnetMask()).c_str());
|
||||
shell.printfln(" IPv4 gateway: %s", uuid::printable_to_string(WiFi.gatewayIP()).c_str());
|
||||
shell.printfln(" IPv4 nameserver: %s", uuid::printable_to_string(WiFi.dnsIP()).c_str());
|
||||
shell.printfln(" IPv4 DNS Server: %s", uuid::printable_to_string(dns_ipv4(WiFi.STA)).c_str());
|
||||
if (WiFi.linkLocalIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000" && WiFi.linkLocalIPv6().toString() != "::") {
|
||||
shell.printfln(" IPv6 address: %s", uuid::printable_to_string(WiFi.linkLocalIPv6()).c_str());
|
||||
}
|
||||
@@ -1201,7 +1241,7 @@ void System::show_system(uuid::console::Shell & shell) {
|
||||
shell.printfln(" Hostname: %s", ETH.getHostname());
|
||||
shell.printfln(" IPv4 address: %s/%s", uuid::printable_to_string(ETH.localIP()).c_str(), uuid::printable_to_string(ETH.subnetMask()).c_str());
|
||||
shell.printfln(" IPv4 gateway: %s", uuid::printable_to_string(ETH.gatewayIP()).c_str());
|
||||
shell.printfln(" IPv4 nameserver: %s", uuid::printable_to_string(ETH.dnsIP()).c_str());
|
||||
shell.printfln(" IPv4 DNS Server: %s", uuid::printable_to_string(dns_ipv4(ETH)).c_str());
|
||||
if (ETH.linkLocalIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000" && ETH.linkLocalIPv6().toString() != "::") {
|
||||
shell.printfln(" IPv6 address: %s", uuid::printable_to_string(ETH.linkLocalIPv6()).c_str());
|
||||
}
|
||||
@@ -1532,7 +1572,7 @@ bool System::check_upgrade() {
|
||||
EMSESP::esp32React.getAPSettingsService()->update([&](APSettings & apSettings) {
|
||||
if (apSettings.provisionMode == 0) {
|
||||
apSettings.provisionMode = AP_MODE_DISCONNECTED; // AP_MODE_DISCONNECTED is the new default
|
||||
LOG_INFO("Upgrade: Setting AP provision mode to auto");
|
||||
LOG_INFO("Upgrade: Setting AP provision mode to on disconnect");
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
return StateUpdateResult::UNCHANGED;
|
||||
@@ -1694,7 +1734,7 @@ static const std::pair<const char *, const char *> SECTION_MAP[] = {
|
||||
#endif
|
||||
|
||||
// convert a single config file into a section of the output json object
|
||||
void System::exportSettings(const std::string & type, const char * filename, JsonObject output) {
|
||||
void System::exportSettings(const std::string & type, [[maybe_unused]] const char * filename, JsonObject output) {
|
||||
if (type != "settings") {
|
||||
output["type"] = type; // add the type to the output, not for settings as it's already added because its grouped
|
||||
}
|
||||
@@ -1849,7 +1889,7 @@ void System::exportSystemBackup(JsonObject output) {
|
||||
}
|
||||
|
||||
// write a settings file using input from a json object, called from upload/restore
|
||||
bool System::saveSettings(const char * filename, JsonObject input) {
|
||||
bool System::saveSettings([[maybe_unused]] const char * filename, [[maybe_unused]] JsonObject input) {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
const char * section = nullptr;
|
||||
for (const auto & [f, label] : SECTION_MAP) {
|
||||
@@ -2819,7 +2859,7 @@ bool System::load_board_profile(std::vector<int8_t> & data, const std::string &
|
||||
// rgb for RGB
|
||||
// For example: /api/system/led?data=red:blink1
|
||||
// For older non-RGB models, the colour would default to just being on.
|
||||
bool System::command_led(const char * value, const int8_t id) {
|
||||
bool System::command_led(const char * value, const int8_t) {
|
||||
if (!value) {
|
||||
return false; // no argument
|
||||
}
|
||||
@@ -2842,7 +2882,7 @@ bool System::command_led(const char * value, const int8_t id) {
|
||||
}
|
||||
|
||||
// txpause command - temporarily pause the TX, by setting Txmode to 0 (disabled)
|
||||
bool System::command_txpause(const char * value, const int8_t id) {
|
||||
bool System::command_txpause(const char * value, const int8_t) {
|
||||
bool arg;
|
||||
if (!Helpers::value2bool(value, arg)) {
|
||||
return false; // argument not recognized
|
||||
@@ -2907,7 +2947,7 @@ bool System::command_restart(const char * value, const int8_t id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
LOG_INFO("Restarting system immediately");
|
||||
LOG_INFO("Restarting system...");
|
||||
// restart will be handled by the main loop
|
||||
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_RESTART_REQUESTED);
|
||||
return true;
|
||||
@@ -2985,7 +3025,7 @@ bool System::ntp_connected() {
|
||||
}
|
||||
|
||||
// see if its a BBQKees Gateway by checking the eFuse values
|
||||
String System::getBBQKeesGatewayDetails(uint8_t detail) {
|
||||
String System::getBBQKeesGatewayDetails([[maybe_unused]] uint8_t detail) {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
union {
|
||||
struct {
|
||||
@@ -3095,7 +3135,7 @@ bool System::uploadFirmwareURL(const char * url) {
|
||||
ssl_client.setSessionTimeout(120);
|
||||
}
|
||||
basic_client.setTimeout(15000); // socket-level read timeout
|
||||
ssl_client.setTimeout(15000); // Stream::readBytes timeout used by Update
|
||||
ssl_client.setTimeout(15); // Stream::readBytes timeout used by Update
|
||||
ssl_client.setClient(&basic_client, is_https); // enableSSL = false for plain HTTP
|
||||
|
||||
const uint16_t port = is_https ? 443 : 80;
|
||||
@@ -3235,7 +3275,7 @@ bool System::uploadFirmwareURL(const char * url) {
|
||||
return false; // error
|
||||
}
|
||||
|
||||
LOG_INFO("Firmware uploading (size: %d KB) over %s. Please wait...", firmware_size / 1024, is_https ? "HTTPS" : "HTTP");
|
||||
LOG_INFO("Firmware uploading (size: %dKB) over %s. Please wait...", firmware_size / 1024, is_https ? "HTTPS" : "HTTP");
|
||||
|
||||
Shell::loop_all(); // flush log buffers so latest messages are shown in console
|
||||
|
||||
@@ -3401,7 +3441,7 @@ bool System::readCommand(const char * data) {
|
||||
}
|
||||
|
||||
// system read command
|
||||
bool System::command_read(const char * value, const int8_t id) {
|
||||
bool System::command_read(const char * value, const int8_t) {
|
||||
return readCommand(value);
|
||||
}
|
||||
|
||||
@@ -3696,7 +3736,7 @@ void System::restore_snapshot_gpios(std::vector<int8_t> & u_gpios, std::vector<i
|
||||
}
|
||||
|
||||
// show the contents of a directory in the LittleFS filesystem
|
||||
void System::listDir(const char * dirname, uint8_t levels) {
|
||||
void System::listDir([[maybe_unused]] const char * dirname, [[maybe_unused]] uint8_t levels) {
|
||||
#if defined(EMSESP_DEBUG)
|
||||
#ifndef EMSESP_STANDALONE
|
||||
|
||||
|
||||
@@ -45,17 +45,20 @@ void TemperatureSensor::start(const bool factory_settings) {
|
||||
Mqtt::subscribe(EMSdevice::DeviceType::TEMPERATURESENSOR, topic, nullptr); // use empty function callback
|
||||
}
|
||||
|
||||
void TemperatureSensor::ha_reset() {
|
||||
for (auto & sensor : sensors_) {
|
||||
remove_ha_topic(sensor.id());
|
||||
sensor.ha_registered = false; // force HA configs to be re-created
|
||||
}
|
||||
}
|
||||
|
||||
// load settings
|
||||
void TemperatureSensor::reload() {
|
||||
EMSESP::webSettingsService.read([&](WebSettings const & settings) {
|
||||
dallas_gpio_ = settings.dallas_gpio;
|
||||
parasite_ = settings.dallas_parasite;
|
||||
});
|
||||
|
||||
for (auto & sensor : sensors_) {
|
||||
remove_ha_topic(sensor.id());
|
||||
sensor.ha_registered = false; // force HA configs to be re-created
|
||||
}
|
||||
ha_reset();
|
||||
}
|
||||
|
||||
void TemperatureSensor::loop() {
|
||||
@@ -543,7 +546,7 @@ void TemperatureSensor::publish_values(const bool force) {
|
||||
// for the value template, there's a problem still with Domoticz probably due to the special characters.
|
||||
// See https://github.com/emsesp/EMS-ESP32/issues/1360
|
||||
if (Mqtt::discovery_type() == Mqtt::discoveryType::HOMEASSISTANT) {
|
||||
config["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + " else -55}}";
|
||||
config["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + " else 'None'}}";
|
||||
} else {
|
||||
config["val_tpl"] = (std::string) "{{" + val_obj + "}}"; // omit value conditional Jinja2 template code
|
||||
}
|
||||
|
||||
@@ -93,6 +93,7 @@ class TemperatureSensor {
|
||||
void publish_sensor(const Sensor & sensor);
|
||||
void publish_values(const bool force);
|
||||
void reload();
|
||||
void ha_reset();
|
||||
bool updated_values();
|
||||
bool get_value_info(JsonObject output, const char * cmd, const int8_t id = -1);
|
||||
|
||||
|
||||
+21
-21
@@ -220,13 +220,13 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
|
||||
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);
|
||||
// remove burnMinPower, #2918
|
||||
// register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
// &burnMinPower_,
|
||||
// DeviceValueType::UINT8,
|
||||
// FL_(burnMinPower),
|
||||
// DeviceValueUOM::PERCENT,
|
||||
// MAKE_CF_CB(set_min_power));
|
||||
// remove burnMinPower, #2918, add back for 0xE6, #3193
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&burnMinPower_,
|
||||
DeviceValueType::UINT8,
|
||||
FL_(burnMinPower),
|
||||
DeviceValueUOM::PERCENT,
|
||||
MAKE_CF_CB(set_min_power));
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&burnMaxPower_,
|
||||
DeviceValueType::UINT8,
|
||||
@@ -1622,7 +1622,7 @@ void Boiler::process_UBAParametersPlus(const std::shared_ptr<const Telegram> & t
|
||||
has_update(telegram, heatingActivated_, 0);
|
||||
has_update(telegram, heatingTemp_, 1);
|
||||
has_update(telegram, burnMaxPower_, 4);
|
||||
// has_update(telegram, burnMinPower_, 5); // removed #2918
|
||||
has_update(telegram, burnMinPower_, 5); // removed #2918
|
||||
has_update(telegram, boilHystOff_, 8);
|
||||
has_update(telegram, boilHystOn_, 9);
|
||||
has_update(telegram, burnMinPeriod_, 10);
|
||||
@@ -1822,7 +1822,7 @@ void Boiler::process_HpPool(const std::shared_ptr<const Telegram> & telegram) {
|
||||
// Boiler(0x08) -> All(0x00), ?(0x04A2), data: 02 01 01 00 01 00
|
||||
// Boiler(0x08) -W-> Me(0x0B), HpInput(0x04A2), data: 20 07 06 01 00 (from #802)
|
||||
// see https://github.com/emsesp/EMS-ESP32/issues/2844#issuecomment-3689049155
|
||||
void Boiler::process_HpInput(const std::shared_ptr<const Telegram> & telegram) {
|
||||
void Boiler::process_HpInput(const std::shared_ptr<const Telegram> &) {
|
||||
}
|
||||
|
||||
// Heatpump inputs settings- type 0x486 (https://github.com/emsesp/EMS-ESP32/issues/600)
|
||||
@@ -1923,7 +1923,7 @@ void Boiler::process_UBASetPoints2(const std::shared_ptr<const Telegram> & teleg
|
||||
}
|
||||
|
||||
// 0x35 - not yet implemented, not readable, only for settings
|
||||
void Boiler::process_UBAFlags(const std::shared_ptr<const Telegram> & telegram) {
|
||||
void Boiler::process_UBAFlags(const std::shared_ptr<const Telegram> &) {
|
||||
}
|
||||
|
||||
// 0x1C
|
||||
@@ -1942,7 +1942,7 @@ void Boiler::process_UBAMaintenanceStatus(const std::shared_ptr<const Telegram>
|
||||
}
|
||||
|
||||
// 0xBF
|
||||
void Boiler::process_ErrorMessage(const std::shared_ptr<const Telegram> & telegram) {
|
||||
void Boiler::process_ErrorMessage(const std::shared_ptr<const Telegram> &) {
|
||||
EMSESP::send_read_request(0xC2, device_id(), 0, 20); // read last errorcode
|
||||
EMSESP::send_read_request(0xC6, device_id(), 0, 21); // read last errorcode
|
||||
}
|
||||
@@ -2494,7 +2494,7 @@ bool Boiler::set_ww_disinfect_temp(const char * value, const int8_t id) {
|
||||
}
|
||||
|
||||
// Set the dhw priority
|
||||
bool Boiler::set_ww_prio(const char * value, const int8_t id) {
|
||||
bool Boiler::set_ww_prio(const char * value, const int8_t) {
|
||||
bool b;
|
||||
if (!Helpers::value2bool(value, b)) {
|
||||
return false;
|
||||
@@ -2814,7 +2814,7 @@ bool Boiler::set_pump_delay(const char * value, const int8_t id) {
|
||||
}
|
||||
|
||||
// set pump logic temperature
|
||||
bool Boiler::set_pumpOnTemp(const char * value, const int8_t id) {
|
||||
bool Boiler::set_pumpOnTemp(const char * value, const int8_t) {
|
||||
int v;
|
||||
if (!Helpers::value2temperature(value, v)) {
|
||||
return false;
|
||||
@@ -3037,7 +3037,7 @@ bool Boiler::set_reset(const char * value, const int8_t id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Boiler::set_manDefrost(const char * value, const int8_t id) {
|
||||
bool Boiler::set_manDefrost(const char * value, const int8_t) {
|
||||
bool b;
|
||||
if (!Helpers::value2bool(value, b)) {
|
||||
return false;
|
||||
@@ -3046,7 +3046,7 @@ bool Boiler::set_manDefrost(const char * value, const int8_t id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Boiler::set_chimneysweeper(const char * value, const int8_t id) {
|
||||
bool Boiler::set_chimneysweeper(const char * value, const int8_t) {
|
||||
bool b;
|
||||
if (!Helpers::value2bool(value, b)) {
|
||||
return false;
|
||||
@@ -3325,7 +3325,7 @@ bool Boiler::set_auxLimit(const char * value, const int8_t id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Boiler::set_auxHeaterSource(const char * value, const int8_t id) {
|
||||
bool Boiler::set_auxHeaterSource(const char * value, const int8_t) {
|
||||
uint8_t v;
|
||||
if (Helpers::value2enum(value, v, FL_(enum_auxHeaterSource))) {
|
||||
write_command(0x491, 0, v, 0x491);
|
||||
@@ -3409,7 +3409,7 @@ bool Boiler::set_hpMaxPower(const char * value, const int8_t id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Boiler::set_pvMaxComp(const char * value, const int8_t id) {
|
||||
bool Boiler::set_pvMaxComp(const char * value, const int8_t) {
|
||||
float v;
|
||||
if (Helpers::value2float(value, v)) {
|
||||
write_command(0x484, 54, (uint8_t)(v * 10), 0x484);
|
||||
@@ -3437,7 +3437,7 @@ bool Boiler::set_hpPowerLimit(const char * value, const int8_t id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Boiler::set_powerReduction(const char * value, const int8_t id) {
|
||||
bool Boiler::set_powerReduction(const char * value, const int8_t) {
|
||||
int v;
|
||||
if (Helpers::value2number(value, v)) {
|
||||
write_command(0x484, 64, v / 10, 0x484);
|
||||
@@ -3661,7 +3661,7 @@ bool Boiler::set_shutdown(const char * value, const int8_t id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Boiler::set_pumpKickHour(const char * value, const int8_t id) {
|
||||
bool Boiler::set_pumpKickHour(const char * value, const int8_t) {
|
||||
int v;
|
||||
if (Helpers::value2number(value, v, 0, 23)) {
|
||||
write_command(0xEB, 0, v, 0xEB);
|
||||
@@ -3670,7 +3670,7 @@ bool Boiler::set_pumpKickHour(const char * value, const int8_t id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Boiler::set_pumpKickDay(const char * value, const int8_t id) {
|
||||
bool Boiler::set_pumpKickDay(const char * value, const int8_t) {
|
||||
uint8_t v;
|
||||
if (Helpers::value2enum(value, v, FL_(enum_dayOfWeek))) {
|
||||
write_command(0xEB, 1, v + 1, 0xEB);
|
||||
@@ -3679,7 +3679,7 @@ bool Boiler::set_pumpKickDay(const char * value, const int8_t id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Boiler::set_pumpKickDelay(const char * value, const int8_t id) {
|
||||
bool Boiler::set_pumpKickDelay(const char * value, const int8_t) {
|
||||
int v;
|
||||
if (Helpers::value2number(value, v, 0, 32767)) {
|
||||
uint8_t data[2] = {(uint8_t)(v >> 8), (uint8_t)v};
|
||||
|
||||
@@ -278,7 +278,7 @@ bool Mixer::set_flowTempOffset(const char * value, const int8_t id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Mixer::set_pressure(const char * value, const int8_t id) {
|
||||
bool Mixer::set_pressure(const char * value, const int8_t) {
|
||||
int v;
|
||||
if (!Helpers::value2number(value, v)) {
|
||||
return false;
|
||||
@@ -288,7 +288,7 @@ bool Mixer::set_pressure(const char * value, const int8_t id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Mixer::set_wwprio(const char * value, const int8_t id) {
|
||||
bool Mixer::set_wwprio(const char * value, const int8_t) {
|
||||
bool b;
|
||||
if (!Helpers::value2bool(value, b)) {
|
||||
return false;
|
||||
|
||||
@@ -1122,7 +1122,7 @@ bool Solar::set_diffControl(const char * value, const int8_t id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Solar::set_solarHeatAssistOn(const char * value, const int8_t id) {
|
||||
bool Solar::set_solarHeatAssistOn(const char * value, const int8_t) {
|
||||
float t;
|
||||
if (!Helpers::value2float(value, t)) {
|
||||
return false;
|
||||
@@ -1131,7 +1131,7 @@ bool Solar::set_solarHeatAssistOn(const char * value, const int8_t id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Solar::set_solarHeatAssistOff(const char * value, const int8_t id) {
|
||||
bool Solar::set_solarHeatAssistOff(const char * value, const int8_t) {
|
||||
float t;
|
||||
if (!Helpers::value2float(value, t)) {
|
||||
return false;
|
||||
|
||||
+55
-31
@@ -1663,32 +1663,40 @@ void Thermostat::process_RC35Timer(const std::shared_ptr<const Telegram> & teleg
|
||||
has_update(telegram, hc->pause, 85); // time in hours
|
||||
has_update(telegram, hc->party, 86); // time in hours
|
||||
|
||||
if (telegram->message_length + telegram->offset >= 92 && telegram->offset <= 87) {
|
||||
char data[sizeof(hc->vacation) + 4]; // avoid compiler warning
|
||||
snprintf(data,
|
||||
sizeof(data),
|
||||
"%02d.%02d.%04d-%02d.%02d.%04d",
|
||||
telegram->message_data[87 - telegram->offset],
|
||||
telegram->message_data[88 - telegram->offset],
|
||||
telegram->message_data[89 - telegram->offset] + 2000,
|
||||
telegram->message_data[90 - telegram->offset],
|
||||
telegram->message_data[91 - telegram->offset],
|
||||
telegram->message_data[92 - telegram->offset] + 2000);
|
||||
has_update(hc->vacation, data, sizeof(hc->vacation));
|
||||
// vacation dates are in positions 87 to 92
|
||||
if (telegram->offset <= 87 && (telegram->offset + telegram->message_length) > 92) {
|
||||
const uint8_t pos = 87 - telegram->offset;
|
||||
if ((pos + 5) < EMS_MAX_TELEGRAM_MESSAGE_LENGTH) {
|
||||
char data[sizeof(hc->vacation) + 4]; // avoid compiler warning
|
||||
snprintf(data,
|
||||
sizeof(data),
|
||||
"%02d.%02d.%04d-%02d.%02d.%04d",
|
||||
telegram->message_data[pos],
|
||||
telegram->message_data[pos + 1],
|
||||
telegram->message_data[pos + 2] + 2000,
|
||||
telegram->message_data[pos + 3],
|
||||
telegram->message_data[pos + 4],
|
||||
telegram->message_data[pos + 5] + 2000);
|
||||
has_update(hc->vacation, data, sizeof(hc->vacation));
|
||||
}
|
||||
}
|
||||
|
||||
if (telegram->message_length + telegram->offset >= 98 && telegram->offset <= 93) {
|
||||
char data[sizeof(hc->holiday) + 4]; // avoid compiler warning
|
||||
snprintf(data,
|
||||
sizeof(data),
|
||||
"%02d.%02d.%04d-%02d.%02d.%04d",
|
||||
telegram->message_data[93 - telegram->offset],
|
||||
telegram->message_data[94 - telegram->offset],
|
||||
telegram->message_data[95 - telegram->offset] + 2000,
|
||||
telegram->message_data[96 - telegram->offset],
|
||||
telegram->message_data[97 - telegram->offset],
|
||||
telegram->message_data[98 - telegram->offset] + 2000);
|
||||
has_update(hc->holiday, data, sizeof(hc->holiday));
|
||||
// holiday dates are in positions 93 to 98
|
||||
if (telegram->offset <= 93 && (telegram->offset + telegram->message_length) > 98) {
|
||||
const uint8_t pos = 93 - telegram->offset;
|
||||
if ((pos + 5) < EMS_MAX_TELEGRAM_MESSAGE_LENGTH) {
|
||||
char data[sizeof(hc->holiday) + 4]; // avoid compiler warning
|
||||
snprintf(data,
|
||||
sizeof(data),
|
||||
"%02d.%02d.%04d-%02d.%02d.%04d",
|
||||
telegram->message_data[pos],
|
||||
telegram->message_data[pos + 1],
|
||||
telegram->message_data[pos + 2] + 2000,
|
||||
telegram->message_data[pos + 3],
|
||||
telegram->message_data[pos + 4],
|
||||
telegram->message_data[pos + 5] + 2000);
|
||||
has_update(hc->holiday, data, sizeof(hc->holiday));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1862,7 +1870,7 @@ void Thermostat::process_RCErrorMessage(const std::shared_ptr<const Telegram> &
|
||||
}
|
||||
|
||||
// 0xBF
|
||||
void Thermostat::process_ErrorMessageBF(const std::shared_ptr<const Telegram> & telegram) {
|
||||
void Thermostat::process_ErrorMessageBF(const std::shared_ptr<const Telegram> &) {
|
||||
EMSESP::send_read_request(0xC0, device_id(), 0, 20); // read last errorcode
|
||||
}
|
||||
|
||||
@@ -2338,7 +2346,7 @@ bool Thermostat::set_damping(const char * value, const int8_t id) {
|
||||
}
|
||||
|
||||
// 0x0241- Set solar
|
||||
bool Thermostat::set_solar(const char * value, const int8_t id) {
|
||||
bool Thermostat::set_solar(const char * value, const int8_t) {
|
||||
bool b;
|
||||
if (Helpers::value2bool(value, b)) {
|
||||
if (model() == EMSdevice::EMS_DEVICE_FLAG_RC100) {
|
||||
@@ -2393,7 +2401,7 @@ bool Thermostat::set_control(const char * value, const int8_t id) {
|
||||
}
|
||||
// BC400
|
||||
// 1-RC100, 2-RC100H, 3-RC200
|
||||
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_BC400 || model() == EMSdevice::EMS_DEVICE_FLAG_UI800) {
|
||||
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_BC400) {
|
||||
if (Helpers::value2enum(value, ctrl, FL_(enum_control2))) {
|
||||
write_command(hpmode_typeids[hc->hc()], 3, ctrl);
|
||||
hc->control = ctrl; // set in advance, dont wait for verify
|
||||
@@ -2413,6 +2421,20 @@ bool Thermostat::set_control(const char * value, const int8_t id) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_UI800) {
|
||||
if (Helpers::value2enum(value, ctrl, FL_(enum_control3))) {
|
||||
write_command(hpmode_typeids[hc->hc()], 3, ctrl);
|
||||
hc->control = ctrl; // set in advance, dont wait for verify
|
||||
if (hc->remotetemp != EMS_VALUE_INT16_NOTSET && ctrl > 0) {
|
||||
if (ctrl == 6) {
|
||||
Roomctrl::set_remotetemp(Roomctrl::RT800, hc->hc(), hc->remotetemp);
|
||||
} else {
|
||||
hc->remotetemp = EMS_VALUE_INT16_NOTSET;
|
||||
Roomctrl::set_remotetemp(0, hc->hc(), hc->remotetemp);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// RC100 or RC300
|
||||
// 1-RC200, 2-RC100, 3-RC100H
|
||||
} else if (isRC300() || model() == EMSdevice::EMS_DEVICE_FLAG_RC100) {
|
||||
@@ -3034,7 +3056,7 @@ bool Thermostat::set_party(const char * value, const int8_t id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Thermostat::set_absent(const char * value, const int8_t id) {
|
||||
bool Thermostat::set_absent(const char * value, const int8_t) {
|
||||
bool b;
|
||||
if (Helpers::value2bool(value, b)) {
|
||||
write_command(0x16E, 0, b ? 0xFF : 0, 0x16E);
|
||||
@@ -4693,7 +4715,7 @@ void Thermostat::register_device_values() {
|
||||
1,
|
||||
10);
|
||||
register_device_value(
|
||||
DeviceValueTAG::TAG_DEVICE_DATA, &coolstart, DeviceValueType::UINT8, FL_(coolstart), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_coolstart), 20, 35);
|
||||
DeviceValueTAG::TAG_DEVICE_DATA, &coolstart, DeviceValueType::UINT8, FL_(coolstart), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_coolstart), 24, 35);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&heatondelay,
|
||||
DeviceValueType::UINT8,
|
||||
@@ -5172,7 +5194,7 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
|
||||
register_device_value(tag, &hc->control, DeviceValueType::ENUM, FL_(enum_control2), FL_(control), DeviceValueUOM::NONE, MAKE_CF_CB(set_control));
|
||||
} else if (model == EMSdevice::EMS_DEVICE_FLAG_UI800) {
|
||||
register_device_value(tag, &hc->control, DeviceValueType::ENUM, FL_(enum_control3), FL_(control), DeviceValueUOM::NONE, MAKE_CF_CB(set_control));
|
||||
} else {
|
||||
} else if (device_id() == 0x10) { // only master thermostats have control by remote
|
||||
register_device_value(tag, &hc->control, DeviceValueType::ENUM, FL_(enum_control1), FL_(control), DeviceValueUOM::NONE, MAKE_CF_CB(set_control));
|
||||
}
|
||||
if (model != EMSdevice::EMS_DEVICE_FLAG_RC100 && model != EMSdevice::EMS_DEVICE_FLAG_CR120) {
|
||||
@@ -5476,7 +5498,9 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
|
||||
FL_(nofrosttemp),
|
||||
DeviceValueUOM::DEGREES,
|
||||
MAKE_CF_CB(set_nofrosttemp));
|
||||
register_device_value(tag, &hc->control, DeviceValueType::ENUM, FL_(enum_j_control), FL_(control), DeviceValueUOM::NONE, MAKE_CF_CB(set_control));
|
||||
if (device_id() == 0x10) { // only master thermostats have control by remote
|
||||
register_device_value(tag, &hc->control, DeviceValueType::ENUM, FL_(enum_j_control), FL_(control), DeviceValueUOM::NONE, MAKE_CF_CB(set_control));
|
||||
}
|
||||
register_device_value(tag, &hc->program, DeviceValueType::ENUM, FL_(enum_progMode4), FL_(program), DeviceValueUOM::NONE, MAKE_CF_CB(set_program));
|
||||
register_device_value(tag,
|
||||
&hc->remotetemp,
|
||||
|
||||
@@ -87,7 +87,7 @@ void Ventilation::process_ModeMessage(const std::shared_ptr<const Telegram> & te
|
||||
}
|
||||
|
||||
// message 0x0587, data: 00 00 64 00 64 0A 00 01 54 01 00 01 00 00 00 46 00 00 00 02 00 A3 00 A3
|
||||
void Ventilation::process_BypassMessage(const std::shared_ptr<const Telegram> & telegram) {
|
||||
void Ventilation::process_BypassMessage(const std::shared_ptr<const Telegram> &) {
|
||||
// has_update(telegram, bypass_closing, 0);
|
||||
// has_update(telegram, bypass_opening, 1);
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
#define EMSESP_APP_VERSION "3.9.0-dev.0"
|
||||
#define EMSESP_APP_VERSION "3.9.0-dev.7"
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "WebCommandService.h"
|
||||
|
||||
#include "shuntingYard.h"
|
||||
#include "httpClient.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
@@ -99,6 +100,13 @@ bool WebCommandService::isUrlCommand(const std::string & command) {
|
||||
return lower_url.starts_with("http://") || lower_url.starts_with("https://");
|
||||
}
|
||||
|
||||
// true if the command runs the shunting-yard on its own argument. Those values must be passed
|
||||
// through raw: a first pass strips the quotes from literal text, so the command's own pass then
|
||||
// re-parses that text as an expression and chokes on characters like '!' or drops the spaces.
|
||||
bool WebCommandService::computesOwnValue(const std::string & cmd) {
|
||||
return cmd == "system/message" || cmd == "system/sendmail";
|
||||
}
|
||||
|
||||
// true if a value expression contains an embedded {"url":...} JSON snippet, which compute()
|
||||
// will resolve with a blocking HTTP request. Mirrors the scan compute() does in shuntingYard.cpp
|
||||
bool WebCommandService::valueContainsUrl(const std::string & value) {
|
||||
@@ -195,7 +203,7 @@ StateUpdateResult WebCommands::update(JsonObject root, WebCommands & webCommands
|
||||
Command::add(
|
||||
EMSdevice::DeviceType::COMMAND,
|
||||
webCommands.commandItems.back().name,
|
||||
[name = std::string(webCommands.commandItems.back().name)](const char * value, const int8_t id, JsonObject output) {
|
||||
[name = std::string(webCommands.commandItems.back().name)](const char * value, const int8_t, JsonObject) {
|
||||
return EMSESP::webCommandService.dispatchCommand(name.c_str(), value); // value is optional
|
||||
},
|
||||
FL_(command_cmd),
|
||||
@@ -238,13 +246,13 @@ bool WebCommandService::executeCommand(const char * name, const std::string & co
|
||||
// run the value through the shunting-yard calculator so expressions like "custom/heatcnt + 1"
|
||||
// are resolved (entity references replaced by their values, then computed). Plain values pass
|
||||
// through unchanged. Applies to both URL and internal commands, like the old scheduler code
|
||||
// which computed the value before executing. system/message runs the shunting-yard on its own
|
||||
// argument, so pre-computing it here would run it twice - pass it through raw.
|
||||
// which computed the value before executing. Commands that compute their own argument are
|
||||
// skipped here so the value is only ever evaluated once.
|
||||
std::string computed_data = data;
|
||||
if (!data.empty() && cmd != "system/message") {
|
||||
if (!data.empty() && !computesOwnValue(cmd)) {
|
||||
computed_data = compute(data);
|
||||
if (computed_data.empty()) {
|
||||
EMSESP::logger().warning("Command '%s': cannot compute value '%s'", name, data.c_str());
|
||||
EMSESP::logger().warning("Command '%s': cannot compute value '%s'. Literal text must be quoted", name, data.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -267,7 +275,7 @@ bool WebCommandService::executeCommand(const char * name, const std::string & co
|
||||
auto lower_url = Helpers::toLower(url.c_str());
|
||||
if (lower_url.starts_with("http://") || lower_url.starts_with("https://")) {
|
||||
std::string result;
|
||||
int httpResult = http_request(url, method, value, doc["header"].as<JsonObjectConst>(), result);
|
||||
int httpResult = HttpClient::request(url, method, value, doc["header"].as<JsonObjectConst>(), result);
|
||||
if (httpResult != 200) {
|
||||
EMSESP::logger().warning("Command '%s': URL command failed with http code %d", name, httpResult);
|
||||
return false;
|
||||
@@ -448,7 +456,7 @@ void WebCommandService::load_test_data() {
|
||||
Command::add(
|
||||
EMSdevice::DeviceType::COMMAND,
|
||||
item.name,
|
||||
[name = std::string(item.name)](const char * value, const int8_t id, JsonObject output) {
|
||||
[name = std::string(item.name)](const char * value, const int8_t, JsonObject) {
|
||||
return EMSESP::webCommandService.dispatchCommand(name.c_str(), value);
|
||||
},
|
||||
FL_(command_cmd),
|
||||
|
||||
@@ -85,6 +85,7 @@ class WebCommandService : public StatefulService<WebCommands> {
|
||||
|
||||
static bool isUrlCommand(const std::string & command); // true if the command definition is a HTTP/URL command
|
||||
static bool valueContainsUrl(const std::string & value); // true if a value embeds a {"url":...} compute() will fetch
|
||||
static bool computesOwnValue(const std::string & cmd); // true if the command runs the shunting-yard itself
|
||||
|
||||
const CommandItem * find(const char * name);
|
||||
|
||||
|
||||
@@ -147,7 +147,7 @@ StateUpdateResult WebCustomEntity::update(JsonObject root, WebCustomEntity & web
|
||||
Command::add(
|
||||
EMSdevice::DeviceType::CUSTOM,
|
||||
webCustomEntity.customEntityItems.back().name,
|
||||
[name = std::string(webCustomEntity.customEntityItems.back().name)](const char * value, const int8_t id, JsonObject output) {
|
||||
[name = std::string(webCustomEntity.customEntityItems.back().name)](const char * value, const int8_t id, JsonObject) {
|
||||
return EMSESP::webCustomEntityService.command_setvalue(value, id, name.c_str());
|
||||
},
|
||||
FL_(entity_cmd),
|
||||
@@ -501,7 +501,7 @@ void WebCustomEntityService::publish(const bool force) {
|
||||
snprintf(val_cond, sizeof(val_cond), "%s is defined", val_obj);
|
||||
// don't bother with value template conditions if using Domoticz which doesn't fully support MQTT Discovery
|
||||
if (Mqtt::discovery_type() == Mqtt::discoveryType::HOMEASSISTANT) {
|
||||
config["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + "}}";
|
||||
config["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + " else 'None'}}";
|
||||
} else {
|
||||
config["val_tpl"] = (std::string) "{{" + val_obj + "}}";
|
||||
}
|
||||
@@ -798,7 +798,7 @@ void WebCustomEntityService::load_test_data() {
|
||||
Command::add(
|
||||
EMSdevice::DeviceType::CUSTOM,
|
||||
webCustomEntity.customEntityItems.back().name,
|
||||
[name = std::string(webCustomEntity.customEntityItems.back().name)](const char * value, const int8_t id, JsonObject output) {
|
||||
[name = std::string(webCustomEntity.customEntityItems.back().name)](const char * value, const int8_t id, JsonObject) {
|
||||
return EMSESP::webCustomEntityService.command_setvalue(value, id, name.c_str());
|
||||
},
|
||||
FL_(entity_cmd),
|
||||
@@ -834,7 +834,7 @@ void WebCustomEntityService::load_test_data() {
|
||||
Command::add(
|
||||
EMSdevice::DeviceType::CUSTOM,
|
||||
webCustomEntity.customEntityItems.back().name,
|
||||
[name = std::string(webCustomEntity.customEntityItems.back().name)](const char * value, const int8_t id, JsonObject output) {
|
||||
[name = std::string(webCustomEntity.customEntityItems.back().name)](const char * value, const int8_t id, JsonObject) {
|
||||
return EMSESP::webCustomEntityService.command_setvalue(value, id, name.c_str());
|
||||
},
|
||||
FL_(entity_cmd),
|
||||
@@ -857,7 +857,7 @@ void WebCustomEntityService::load_test_data() {
|
||||
Command::add(
|
||||
EMSdevice::DeviceType::CUSTOM,
|
||||
webCustomEntity.customEntityItems.back().name,
|
||||
[name = std::string(webCustomEntity.customEntityItems.back().name)](const char * value, const int8_t id, JsonObject output) {
|
||||
[name = std::string(webCustomEntity.customEntityItems.back().name)](const char * value, const int8_t id, JsonObject) {
|
||||
return EMSESP::webCustomEntityService.command_setvalue(value, id, name.c_str());
|
||||
},
|
||||
FL_(entity_cmd),
|
||||
|
||||
@@ -317,7 +317,7 @@ void WebCustomizationService::customization_entities(AsyncWebServerRequest * req
|
||||
|
||||
// add deleted entities from file
|
||||
read([&](WebCustomization & settings) {
|
||||
for (EntityCustomization entityCustomization : settings.entityCustomizations) {
|
||||
for (const auto & entityCustomization : settings.entityCustomizations) {
|
||||
if (entityCustomization.device_id == device_id) {
|
||||
for (const auto & entity_id : entityCustomization.entity_ids) {
|
||||
uint8_t mask = Helpers::hextoint(entity_id.substr(0, 2).c_str());
|
||||
|
||||
@@ -98,7 +98,7 @@ StateUpdateResult WebScheduler::update(JsonObject root, WebScheduler & webSchedu
|
||||
Command::add(
|
||||
EMSdevice::DeviceType::SCHEDULER,
|
||||
webScheduler.scheduleItems.back().name,
|
||||
[name = std::string(webScheduler.scheduleItems.back().name)](const char * value, const int8_t id, JsonObject output) {
|
||||
[name = std::string(webScheduler.scheduleItems.back().name)](const char * value, const int8_t id, JsonObject) {
|
||||
return EMSESP::webSchedulerService.command_setvalue(value, id, name.c_str());
|
||||
},
|
||||
FL_(schedule_cmd),
|
||||
@@ -275,7 +275,7 @@ void WebSchedulerService::publish(const bool force) {
|
||||
|
||||
char val_tpl[150];
|
||||
if (Mqtt::discovery_type() == Mqtt::discoveryType::HOMEASSISTANT) {
|
||||
snprintf(val_tpl, sizeof(val_tpl), "{{%s if %s}}", val_obj, val_cond);
|
||||
snprintf(val_tpl, sizeof(val_tpl), "{{%s if %s else 'None'}}", val_obj, val_cond);
|
||||
} else {
|
||||
snprintf(val_tpl, sizeof(val_tpl), "{{%s}}", val_obj); // omit value conditional Jinja2 template code
|
||||
}
|
||||
@@ -464,7 +464,7 @@ void WebSchedulerService::load_test_data() {
|
||||
Command::add(
|
||||
EMSdevice::DeviceType::SCHEDULER,
|
||||
item.name,
|
||||
[name = std::string(item.name)](const char * value, const int8_t id, JsonObject output) {
|
||||
[name = std::string(item.name)](const char * value, const int8_t id, JsonObject) {
|
||||
return EMSESP::webSchedulerService.command_setvalue(value, id, name.c_str());
|
||||
},
|
||||
FL_(schedule_cmd),
|
||||
|
||||
@@ -316,8 +316,9 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) {
|
||||
settings.weblog_level = root["weblog_level"] | EMSESP_DEFAULT_WEBLOG_LEVEL;
|
||||
settings.weblog_compact = root["weblog_compact"] | EMSESP_DEFAULT_WEBLOG_COMPACT;
|
||||
|
||||
settings.email_enabled = root["email_enabled"] | FACTORY_EMAIL_ENABLE;
|
||||
settings.email_security = root["email_security"] | FACTORY_EMAIL_SECURITY;
|
||||
settings.email_enabled = root["email_enabled"] | FACTORY_EMAIL_ENABLE;
|
||||
// Off is 0, so `| FACTORY_EMAIL_SECURITY` would replace it with STARTTLS (2)
|
||||
settings.email_security = root["email_security"].isNull() ? FACTORY_EMAIL_SECURITY : root["email_security"].as<uint8_t>();
|
||||
settings.email_server = root["email_server"] | FACTORY_EMAIL_SERVER;
|
||||
settings.email_port = root["email_port"] | FACTORY_EMAIL_PORT;
|
||||
settings.email_login = root["email_login"] | FACTORY_EMAIL_LOGIN;
|
||||
|
||||
@@ -20,9 +20,8 @@
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
#include <esp_ota_ops.h>
|
||||
#include <WiFiClient.h>
|
||||
#include <ESP_SSLClient.h>
|
||||
#endif
|
||||
#include "httpClient.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
@@ -320,12 +319,15 @@ uint8_t WebStatusService::upgradeImportantMessages(std::string & version) {
|
||||
}
|
||||
|
||||
// action = getVersions
|
||||
// returns the device's current version for dev and stable
|
||||
// returns the device's current version for dev and stable, plus the system name
|
||||
// The remote fetch runs from the main loop task via WebStatusService::loop() so that we never block the AsyncTCP callback
|
||||
void WebStatusService::getVersions(JsonObject root) {
|
||||
FirmwareVersion current_version(current_version_s);
|
||||
bool is_dev = current_version.prerelease().find("dev") != std::string::npos;
|
||||
|
||||
// the WebUI shows this in the menu drawer. It's included here because this action is called once after login
|
||||
root["system_name"] = EMSESP::system_.system_name();
|
||||
|
||||
JsonObject current = root["current"].to<JsonObject>();
|
||||
current["version"] = current_version_s;
|
||||
current["type"] = is_dev ? "dev" : "stable";
|
||||
@@ -421,94 +423,17 @@ bool WebStatusService::refresh_versions_cache() {
|
||||
#ifdef EMSESP_STANDALONE
|
||||
return false;
|
||||
#else
|
||||
// detect scheme from EMSESP_VERSIONS_URL (case-insensitive). One code path for HTTP and HTTPS,
|
||||
// using ESP_SSLClient as a plain TCP passthrough when SSL is disabled.
|
||||
String url = EMSESP_VERSIONS_URL;
|
||||
String lower = url;
|
||||
lower.toLowerCase();
|
||||
const bool is_https = lower.startsWith("https://");
|
||||
if (!is_https && !lower.startsWith("http://")) {
|
||||
#if defined(EMSESP_DEBUG)
|
||||
EMSESP::logger().debug("versions.json: unsupported scheme");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
const int scheme_len = is_https ? 8 : 7;
|
||||
|
||||
WiFiClient basic_client;
|
||||
ESP_SSLClient ssl_client;
|
||||
if (is_https) {
|
||||
ssl_client.setInsecure();
|
||||
ssl_client.setBufferSizes(16384, 1024); // versions.json fits easily but TLS records may be full-size
|
||||
ssl_client.setSessionTimeout(120);
|
||||
}
|
||||
basic_client.setTimeout(5000);
|
||||
ssl_client.setTimeout(5000);
|
||||
ssl_client.setClient(&basic_client, is_https);
|
||||
|
||||
// split into host and path
|
||||
String rest = url.substring(scheme_len);
|
||||
String host;
|
||||
String path;
|
||||
int s = rest.indexOf('/');
|
||||
if (s < 0) {
|
||||
host = rest;
|
||||
path = "/";
|
||||
} else {
|
||||
host = rest.substring(0, s);
|
||||
path = rest.substring(s);
|
||||
}
|
||||
|
||||
if (!ssl_client.connect(host.c_str(), is_https ? 443 : 80)) {
|
||||
#if defined(EMSESP_DEBUG)
|
||||
EMSESP::logger().debug("versions.json: connection failed");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
// minimal HTTP/1.0 GET so we don't have to handle chunked encoding
|
||||
ssl_client.print("GET ");
|
||||
ssl_client.print(path);
|
||||
ssl_client.println(" HTTP/1.0");
|
||||
ssl_client.print("Host: ");
|
||||
ssl_client.println(host);
|
||||
ssl_client.println("User-Agent: EMS-ESP");
|
||||
ssl_client.println("Connection: close");
|
||||
ssl_client.print("\r\n");
|
||||
|
||||
// wait for the first byte. The 5 seconds is GitHub's SLO
|
||||
uint32_t ms = millis();
|
||||
while (ssl_client.connected() && !ssl_client.available() && millis() - ms < 5000) {
|
||||
delay(1);
|
||||
}
|
||||
|
||||
// parse status line
|
||||
String status_line = ssl_client.readStringUntil('\n');
|
||||
int sp = status_line.indexOf(' ');
|
||||
int http_code = (sp >= 0) ? status_line.substring(sp + 1, sp + 4).toInt() : 0;
|
||||
std::string result;
|
||||
JsonDocument doc;
|
||||
auto http_code = HttpClient::request(VERSIONS_URL, "GET", "", doc.as<JsonObjectConst>(), result);
|
||||
if (http_code != 200) {
|
||||
ssl_client.stop();
|
||||
#if defined(EMSESP_DEBUG)
|
||||
EMSESP::logger().debug("versions.json: HTTP error code %d", http_code);
|
||||
#endif
|
||||
EMSESP::logger().warning("Unable to retrieve online version information (HTTP error code %d)", http_code);
|
||||
return false;
|
||||
}
|
||||
|
||||
// skip headers
|
||||
while (ssl_client.connected() || ssl_client.available()) {
|
||||
String line = ssl_client.readStringUntil('\n');
|
||||
line.trim();
|
||||
if (line.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
JsonDocument doc(PSRAM_DOC);
|
||||
DeserializationError err = deserializeJson(doc, ssl_client);
|
||||
ssl_client.stop();
|
||||
DeserializationError err = deserializeJson(doc, result);
|
||||
if (err) {
|
||||
#if defined(EMSESP_DEBUG)
|
||||
EMSESP::logger().debug("versions.json: parse error");
|
||||
EMSESP::logger().debug("refresh_versions_cache() parse error");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
@@ -531,7 +456,8 @@ bool WebStatusService::refresh_versions_cache() {
|
||||
|
||||
versions_cache_valid_ = true;
|
||||
#if defined(EMSESP_DEBUG)
|
||||
EMSESP::logger().debug("versions.json: fetched stable=%s, dev=%s, current=%s",
|
||||
EMSESP::logger().debug("Fetched from %s: stable=%s, dev=%s, current=%s",
|
||||
VERSIONS_URL,
|
||||
versions_stable_.version.c_str(),
|
||||
versions_dev_.version.c_str(),
|
||||
current_version_s.c_str());
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
#define EMSESP_SYSTEM_STATUS_SERVICE_PATH "/rest/systemStatus"
|
||||
#define EMSESP_ACTION_SERVICE_PATH "/rest/action"
|
||||
|
||||
#define EMSESP_VERSIONS_URL "http://emsesp.org/versions.json"
|
||||
|
||||
#include "../core/firmwareVersion.h"
|
||||
#include "../emsesp_version.h"
|
||||
|
||||
@@ -72,6 +70,8 @@ class WebStatusService {
|
||||
|
||||
bool refresh_versions_cache(); // does the actual HTTPS fetch + parse, returns true on success
|
||||
|
||||
static constexpr const char * VERSIONS_URL = "http://emsesp.org/versions.json";
|
||||
|
||||
static constexpr uint32_t VERSIONS_REFRESH_INTERVAL_MS = 60UL * 60UL * 1000UL; // 1 hour on success
|
||||
static constexpr uint32_t VERSIONS_RETRY_INTERVAL_MS = 5UL * 60UL * 1000UL; // 5 min after failure
|
||||
static constexpr uint32_t VERSIONS_INITIAL_FETCH_DELAY_MS = 5UL * 1000UL; // 5 s after a link comes up
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -120,6 +120,31 @@ void shuntingYard_test28() {
|
||||
run_shuntingYard_test("", "-x");
|
||||
}
|
||||
|
||||
// quoted text keeps its spaces and punctuation, unquoted text does not
|
||||
void shuntingYard_test29() {
|
||||
run_shuntingYard_test("email test!", "\"email test!\"");
|
||||
}
|
||||
|
||||
// a trailing '!' on unquoted text is read as a logical NOT and has no valid operand
|
||||
void shuntingYard_test30() {
|
||||
run_shuntingYard_test("", "email test!");
|
||||
}
|
||||
|
||||
// computing an already computed value is lossy - callers must run compute() only once
|
||||
void shuntingYard_test31() {
|
||||
run_shuntingYard_test("", emsesp::compute("\"email test!\""));
|
||||
}
|
||||
|
||||
// a '?' inside quotes is literal text, not a ternary
|
||||
void shuntingYard_test32() {
|
||||
run_shuntingYard_test("Is it hot?", "\"Is it hot?\"");
|
||||
}
|
||||
|
||||
// mixing quoted text with entity values keeps the punctuation of both parts
|
||||
void shuntingYard_test33() {
|
||||
run_shuntingYard_test("temp is 40 C!", "\"temp is \" + boiler/flowtempoffset + \" C!\"");
|
||||
}
|
||||
|
||||
void run_shuntingYard_tests() {
|
||||
RUN_TEST(shuntingYard_test1);
|
||||
RUN_TEST(shuntingYard_test2);
|
||||
@@ -149,4 +174,9 @@ void run_shuntingYard_tests() {
|
||||
RUN_TEST(shuntingYard_test26);
|
||||
RUN_TEST(shuntingYard_test27);
|
||||
RUN_TEST(shuntingYard_test28);
|
||||
RUN_TEST(shuntingYard_test29);
|
||||
RUN_TEST(shuntingYard_test30);
|
||||
RUN_TEST(shuntingYard_test31);
|
||||
RUN_TEST(shuntingYard_test32);
|
||||
RUN_TEST(shuntingYard_test33);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user