mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 07:49:52 +03:00
Merge pull request #2750 from proddy/dev
adjustments to invalid sensors
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -75,3 +75,5 @@ sdkconfig_tasmota_esp32
|
||||
pnpm-lock.yaml
|
||||
package.json
|
||||
.cache/
|
||||
interface/.tsbuildinfo
|
||||
test/test_api/package-lock.json
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"formidable": "^3.5.4",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"magic-string": "^0.30.21",
|
||||
"mime-types": "^3.0.1",
|
||||
"mime-types": "^3.0.2",
|
||||
"preact": "^10.27.2",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
@@ -53,7 +53,7 @@
|
||||
"@preact/preset-vite": "^2.10.2",
|
||||
"@trivago/prettier-plugin-sort-imports": "^6.0.0",
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/react": "^19.2.5",
|
||||
"@types/react": "^19.2.6",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"axe-core": "^4.11.0",
|
||||
"concurrently": "^9.2.1",
|
||||
@@ -62,10 +62,10 @@
|
||||
"prettier": "^3.6.2",
|
||||
"rollup-plugin-visualizer": "^6.0.5",
|
||||
"terser": "^5.44.1",
|
||||
"typescript-eslint": "^8.46.4",
|
||||
"vite": "^7.2.2",
|
||||
"typescript-eslint": "^8.47.0",
|
||||
"vite": "^7.2.4",
|
||||
"vite-plugin-imagemin": "^0.6.1",
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
},
|
||||
"packageManager": "pnpm@10.22.0+sha512.bf049efe995b28f527fd2b41ae0474ce29186f7edcb3bf545087bd61fbbebb2bf75362d1307fda09c2d288e1e499787ac12d4fcb617a974718a6051f2eee741c"
|
||||
"packageManager": "pnpm@10.23.0+sha512.21c4e5698002ade97e4efe8b8b4a89a8de3c85a37919f957e7a0f30f38fbc5bbdd05980ffe29179b2fb6e6e691242e098d945d1601772cad0fef5fb6411e2a4b"
|
||||
}
|
||||
|
||||
498
interface/pnpm-lock.yaml
generated
498
interface/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,6 @@ import type { Locales } from 'i18n/i18n-types';
|
||||
import { loadLocaleAsync } from 'i18n/i18n-util.async';
|
||||
import { detectLocale, navigatorDetector } from 'typesafe-i18n/detectors';
|
||||
|
||||
// Memoize available locales to prevent recreation on every render
|
||||
const AVAILABLE_LOCALES = [
|
||||
'de',
|
||||
'en',
|
||||
|
||||
@@ -11,17 +11,15 @@ import { createTheme } from '@mui/material/styles';
|
||||
|
||||
import type { RequiredChildrenProps } from 'utils';
|
||||
|
||||
// Memoize dialog style to prevent recreation
|
||||
export const dialogStyle = {
|
||||
'& .MuiDialog-paper': {
|
||||
borderRadius: '8px',
|
||||
borderColor: '#565656',
|
||||
borderStyle: 'solid',
|
||||
borderWidth: '1px'
|
||||
borderWidth: '2px'
|
||||
}
|
||||
} as const;
|
||||
|
||||
// Memoize theme creation to prevent recreation
|
||||
const theme = responsiveFontSizes(
|
||||
createTheme({
|
||||
typography: {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { memo, useCallback, useContext, useMemo, useState } from 'react';
|
||||
import { memo, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import ForwardIcon from '@mui/icons-material/Forward';
|
||||
@@ -81,6 +81,15 @@ const SignIn = memo(() => {
|
||||
// Memoize callback to prevent recreation on every render
|
||||
const submitOnEnter = useMemo(() => onEnterCallback(signIn), [signIn]);
|
||||
|
||||
// get rid of scrollbar
|
||||
useEffect(() => {
|
||||
const originalOverflow = document.body.style.overflow;
|
||||
document.body.style.overflow = 'hidden';
|
||||
return () => {
|
||||
document.body.style.overflow = originalOverflow;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box
|
||||
display="flex"
|
||||
@@ -102,23 +111,27 @@ const SignIn = memo(() => {
|
||||
width: '100%'
|
||||
})}
|
||||
>
|
||||
<Typography variant="h4">{PROJECT_NAME}</Typography>
|
||||
|
||||
<Typography mb={1} variant="h4">
|
||||
{PROJECT_NAME}
|
||||
</Typography>
|
||||
<LanguageSelector />
|
||||
|
||||
<Box display="flex" flexDirection="column" alignItems="center">
|
||||
<Box
|
||||
mt={1}
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
gap={1}
|
||||
alignItems="center"
|
||||
>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors || {}}
|
||||
disabled={processing}
|
||||
sx={{
|
||||
width: 240
|
||||
width: '32ch'
|
||||
}}
|
||||
name="username"
|
||||
label={LL.USERNAME(0)}
|
||||
value={signInRequest.username}
|
||||
onChange={updateLoginRequestValue}
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
slotProps={{
|
||||
input: {
|
||||
autoCapitalize: 'none',
|
||||
@@ -130,14 +143,13 @@ const SignIn = memo(() => {
|
||||
fieldErrors={fieldErrors || {}}
|
||||
disabled={processing}
|
||||
sx={{
|
||||
width: 240
|
||||
width: '32ch'
|
||||
}}
|
||||
name="password"
|
||||
label={LL.PASSWORD()}
|
||||
value={signInRequest.password}
|
||||
onChange={updateLoginRequestValue}
|
||||
onKeyDown={submitOnEnter}
|
||||
variant="outlined"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -285,13 +285,17 @@ const Customizations = () => {
|
||||
return value as string;
|
||||
}
|
||||
|
||||
const isCommand = useCallback((de: DeviceEntity) => {
|
||||
return de.n && de.n[0] === '!';
|
||||
}, []);
|
||||
|
||||
const formatName = useCallback(
|
||||
(de: DeviceEntity, withShortname: boolean) => {
|
||||
let name: string;
|
||||
if (de.n && de.n[0] === '!') {
|
||||
if (isCommand(de)) {
|
||||
name = de.t
|
||||
? `${LL.COMMAND(1)}: ${de.t} ${de.n.slice(1)}`
|
||||
: `${LL.COMMAND(1)}: ${de.n.slice(1)}`;
|
||||
? `${LL.COMMAND(1)}: ${de.t} ${de.n?.slice(1)}`
|
||||
: `${LL.COMMAND(1)}: ${de.n?.slice(1)}`;
|
||||
} else if (de.cn && de.cn !== '') {
|
||||
name = de.t ? `${de.t} ${de.cn}` : de.cn;
|
||||
} else {
|
||||
@@ -543,7 +547,7 @@ const Customizations = () => {
|
||||
return (
|
||||
<>
|
||||
<Box color="warning.main">
|
||||
<Typography variant="body2" mt={1}>
|
||||
<Typography variant="body2" mt={1} mb={1}>
|
||||
<OptionIcon type="favorite" isSet={true} />={LL.CUSTOMIZATIONS_HELP_2()}
|
||||
|
||||
<OptionIcon type="readonly" isSet={true} />={LL.CUSTOMIZATIONS_HELP_3()}
|
||||
@@ -666,14 +670,27 @@ const Customizations = () => {
|
||||
<EntityMaskToggle onUpdate={updateDeviceEntity} de={de} />
|
||||
</Cell>
|
||||
<Cell>
|
||||
{formatName(de, false)} (
|
||||
<Link
|
||||
target="_blank"
|
||||
href={APIURL + selectedDeviceTypeNameURL + '/' + de.id}
|
||||
<span
|
||||
style={{
|
||||
color:
|
||||
de.v === undefined && !isCommand(de) ? 'grey' : 'inherit'
|
||||
}}
|
||||
>
|
||||
{de.id}
|
||||
</Link>
|
||||
)
|
||||
{formatName(de, false)} (
|
||||
<Link
|
||||
style={{
|
||||
color:
|
||||
de.v === undefined && !isCommand(de)
|
||||
? 'grey'
|
||||
: 'primary'
|
||||
}}
|
||||
target="_blank"
|
||||
href={APIURL + selectedDeviceTypeNameURL + '/' + de.id}
|
||||
>
|
||||
{de.id}
|
||||
</Link>
|
||||
)
|
||||
</span>
|
||||
</Cell>
|
||||
<Cell>
|
||||
{!(de.m & DeviceEntityMask.DV_READONLY) && formatValue(de.mi)}
|
||||
@@ -726,8 +743,9 @@ const Customizations = () => {
|
||||
{devices && renderDeviceList()}
|
||||
{selectedDevice !== -1 && !rename && renderDeviceData()}
|
||||
{restartNeeded ? (
|
||||
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT(0)}>
|
||||
<MessageBox level="warning" message={LL.RESTART_TEXT(0)}>
|
||||
<Button
|
||||
sx={{ ml: 2 }}
|
||||
startIcon={<PowerSettingsNewIcon />}
|
||||
variant="contained"
|
||||
color="error"
|
||||
|
||||
@@ -263,7 +263,7 @@ const Dashboard = memo(() => {
|
||||
return (
|
||||
<>
|
||||
{!data.connected && (
|
||||
<MessageBox mb={2} level="error" message={LL.EMS_BUS_WARNING()} />
|
||||
<MessageBox level="error" message={LL.EMS_BUS_WARNING()} />
|
||||
)}
|
||||
|
||||
{data.connected && data.nodes.length > 0 && !hasFavEntities && (
|
||||
|
||||
@@ -533,19 +533,17 @@ const Devices = memo(() => {
|
||||
|
||||
const renderCoreData = () => (
|
||||
<>
|
||||
<Box justifyContent="center" flexDirection="column">
|
||||
<IconContext.Provider
|
||||
value={{
|
||||
color: 'lightblue',
|
||||
size: '18',
|
||||
style: { verticalAlign: 'middle' }
|
||||
}}
|
||||
>
|
||||
{!coreData.connected && (
|
||||
<MessageBox my={2} level="error" message={LL.EMS_BUS_WARNING()} />
|
||||
)}
|
||||
|
||||
{coreData.connected && (
|
||||
{!coreData.connected ? (
|
||||
<MessageBox level="error" message={LL.EMS_BUS_WARNING()} />
|
||||
) : (
|
||||
<Box justifyContent="center" flexDirection="column">
|
||||
<IconContext.Provider
|
||||
value={{
|
||||
color: 'lightblue',
|
||||
size: '18',
|
||||
style: { verticalAlign: 'middle' }
|
||||
}}
|
||||
>
|
||||
<Table
|
||||
data={{ nodes: [...coreData.devices] }}
|
||||
select={device_select}
|
||||
@@ -581,9 +579,9 @@ const Devices = memo(() => {
|
||||
</>
|
||||
)}
|
||||
</Table>
|
||||
)}
|
||||
</IconContext.Provider>
|
||||
</Box>
|
||||
</IconContext.Provider>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useContext, useMemo, useState } from 'react';
|
||||
import { useCallback, useContext, useMemo, useRef, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import AddCircleOutlineOutlinedIcon from '@mui/icons-material/AddCircleOutlineOutlined';
|
||||
@@ -54,7 +54,6 @@ const MS_PER_SECOND = 1000;
|
||||
const MS_PER_MINUTE = 60 * MS_PER_SECOND;
|
||||
const MS_PER_HOUR = 60 * MS_PER_MINUTE;
|
||||
const MS_PER_DAY = 24 * MS_PER_HOUR;
|
||||
const DEFAULT_GPIO = 99; // not set
|
||||
const MIN_TEMP_ID = -100;
|
||||
const MAX_TEMP_ID = 100;
|
||||
const GPIO_25 = 25;
|
||||
@@ -128,15 +127,21 @@ const Sensors = () => {
|
||||
const [temperatureDialogOpen, setTemperatureDialogOpen] = useState<boolean>(false);
|
||||
const [analogDialogOpen, setAnalogDialogOpen] = useState<boolean>(false);
|
||||
const [creating, setCreating] = useState<boolean>(false);
|
||||
const firstAvailableGPIO = useRef<number>(undefined);
|
||||
|
||||
const { data: sensorData, send: fetchSensorData } = useRequest(readSensorData, {
|
||||
initialData: {
|
||||
ts: [],
|
||||
as: [],
|
||||
analog_enabled: false,
|
||||
valid_gpio_list: [],
|
||||
available_gpios: [] as number[],
|
||||
platform: 'ESP32'
|
||||
}
|
||||
}).onSuccess((event) => {
|
||||
// store the first available GPIO in a ref
|
||||
if (event.data.available_gpios.length > 0) {
|
||||
firstAvailableGPIO.current = event.data.available_gpios[0];
|
||||
}
|
||||
});
|
||||
|
||||
const { send: sendTemperatureSensor } = useRequest(
|
||||
@@ -186,10 +191,14 @@ const Sensors = () => {
|
||||
sortToggleType: SortToggleType.AlternateWithReset,
|
||||
sortFns: {
|
||||
GPIO: (array) =>
|
||||
[...array].sort((a, b) => (a as AnalogSensor).g - (b as AnalogSensor).g),
|
||||
[...array].sort(
|
||||
(a, b) => ((a as AnalogSensor)?.g ?? 0) - ((b as AnalogSensor)?.g ?? 0)
|
||||
),
|
||||
NAME: (array) =>
|
||||
[...array].sort((a, b) =>
|
||||
(a as AnalogSensor).n.localeCompare((b as AnalogSensor).n)
|
||||
((a as AnalogSensor)?.n ?? '').localeCompare(
|
||||
(b as AnalogSensor)?.n ?? ''
|
||||
)
|
||||
),
|
||||
TYPE: (array) =>
|
||||
[...array].sort((a, b) => (a as AnalogSensor).t - (b as AnalogSensor).t),
|
||||
@@ -338,19 +347,23 @@ const Sensors = () => {
|
||||
}, [fetchSensorData]);
|
||||
|
||||
const addAnalogSensor = useCallback(() => {
|
||||
if (firstAvailableGPIO.current === undefined) {
|
||||
toast.error('No available GPIO found');
|
||||
return;
|
||||
}
|
||||
setCreating(true);
|
||||
setSelectedAnalogSensor({
|
||||
id: Math.floor(Math.random() * (MAX_TEMP_ID - MIN_TEMP_ID) + MIN_TEMP_ID),
|
||||
n: '',
|
||||
g: DEFAULT_GPIO,
|
||||
u: 0,
|
||||
g: firstAvailableGPIO.current,
|
||||
u: DeviceValueUOM.NONE,
|
||||
v: 0,
|
||||
o: 0,
|
||||
t: 0,
|
||||
f: 1,
|
||||
t: AnalogType.DIGITAL_IN, // default to digital in 1
|
||||
d: false,
|
||||
o_n: '',
|
||||
s: false
|
||||
s: false,
|
||||
o_n: ''
|
||||
});
|
||||
setAnalogDialogOpen(true);
|
||||
}, []);
|
||||
@@ -447,23 +460,17 @@ const Sensors = () => {
|
||||
item={as}
|
||||
onClick={() => updateAnalogSensor(as)}
|
||||
>
|
||||
<Cell stiff>{as.g !== 99 ? as.g : ''}</Cell>
|
||||
<Cell stiff>{as.g}</Cell>
|
||||
<Cell>{as.n}</Cell>
|
||||
<Cell stiff>{AnalogTypeNames[as.t]} </Cell>
|
||||
<Cell stiff>{AnalogTypeNames[as.t - 1]} </Cell>
|
||||
{(as.t === AnalogType.DIGITAL_OUT &&
|
||||
as.g !== GPIO_25 &&
|
||||
as.g !== GPIO_26) ||
|
||||
as.t === AnalogType.DIGITAL_IN ||
|
||||
as.t === AnalogType.PULSE ? (
|
||||
<Cell stiff>
|
||||
{as.g !== 99 ? (as.v ? LL.ON() : LL.OFF()) : ''}
|
||||
</Cell>
|
||||
<Cell stiff>{as.v ? LL.ON() : LL.OFF()}</Cell>
|
||||
) : (
|
||||
<Cell stiff>
|
||||
{as.t !== AnalogType.NOTUSED && as.g !== 99
|
||||
? formatValue(as.v, as.u)
|
||||
: ''}
|
||||
</Cell>
|
||||
<Cell stiff>{formatValue(as.v, as.u)}</Cell>
|
||||
)}
|
||||
</Row>
|
||||
))}
|
||||
@@ -578,7 +585,7 @@ const Sensors = () => {
|
||||
onSave={onAnalogDialogSave}
|
||||
creating={creating}
|
||||
selectedItem={selectedAnalogSensor}
|
||||
analogGPIOList={sensorData.valid_gpio_list}
|
||||
analogGPIOList={sensorData.available_gpios}
|
||||
validator={analogSensorItemValidation(sensorData.as, selectedAnalogSensor)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -98,7 +98,7 @@ const SensorsAnalogDialog = ({
|
||||
const analogTypeMenuItems = useMemo(
|
||||
() =>
|
||||
AnalogTypeNames.map((val, i) => (
|
||||
<MenuItem key={val} value={i}>
|
||||
<MenuItem key={val} value={i + 1}>
|
||||
{val}
|
||||
</MenuItem>
|
||||
)),
|
||||
@@ -115,6 +115,23 @@ const SensorsAnalogDialog = ({
|
||||
[]
|
||||
);
|
||||
|
||||
const analogGPIOMenuItems = () =>
|
||||
// add selectedItem.g to the list
|
||||
[
|
||||
...(analogGPIOList?.includes(selectedItem.g) || selectedItem.g === undefined
|
||||
? analogGPIOList
|
||||
: [selectedItem.g, ...analogGPIOList])
|
||||
]
|
||||
.filter((gpio, idx, arr) => arr.indexOf(gpio) === idx)
|
||||
.sort((a, b) => a - b)
|
||||
.map((gpio: number) => {
|
||||
return (
|
||||
<MenuItem key={gpio} value={gpio}>
|
||||
{gpio}
|
||||
</MenuItem>
|
||||
);
|
||||
});
|
||||
|
||||
// Reset form when dialog opens or selectedItem changes
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
@@ -152,20 +169,6 @@ const SensorsAnalogDialog = ({
|
||||
[creating, LL]
|
||||
);
|
||||
|
||||
// Ensure the current GPIO is in the list when no creating
|
||||
// note GPIO 99 means not set
|
||||
const availableGPIOs = useMemo(() => {
|
||||
const filteredList = analogGPIOList.filter((gpio) => gpio !== 99);
|
||||
if (
|
||||
editItem.g !== undefined &&
|
||||
editItem.g !== 99 &&
|
||||
!filteredList.includes(editItem.g)
|
||||
) {
|
||||
return [...filteredList, editItem.g].sort((a, b) => a - b);
|
||||
}
|
||||
return filteredList;
|
||||
}, [analogGPIOList, editItem.g]);
|
||||
|
||||
return (
|
||||
<Dialog sx={dialogStyle} open={open} onClose={handleClose}>
|
||||
<DialogTitle>{dialogTitle}</DialogTitle>
|
||||
@@ -175,16 +178,12 @@ const SensorsAnalogDialog = ({
|
||||
name="g"
|
||||
label="GPIO"
|
||||
value={editItem.g}
|
||||
sx={{ width: '8ch' }}
|
||||
disabled={editItem.s}
|
||||
sx={{ width: '9ch' }}
|
||||
disabled={editItem.s || !creating}
|
||||
select
|
||||
onChange={updateFormValue}
|
||||
>
|
||||
{availableGPIOs?.map((gpio: number) => (
|
||||
<MenuItem key={gpio} value={gpio}>
|
||||
{gpio}
|
||||
</MenuItem>
|
||||
))}
|
||||
{analogGPIOMenuItems()}
|
||||
</ValidatedTextField>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
|
||||
@@ -89,15 +89,15 @@ export interface TemperatureSensor {
|
||||
export interface AnalogSensor {
|
||||
id: number;
|
||||
g: number; // GPIO
|
||||
n: string;
|
||||
v: number;
|
||||
u: number;
|
||||
o: number;
|
||||
f: number;
|
||||
t: number;
|
||||
n: string; // name
|
||||
v: number; // value
|
||||
u: number; // uom
|
||||
o: number; // offset
|
||||
f: number; // factor
|
||||
t: number; // type
|
||||
d: boolean; // deleted flag
|
||||
s: boolean; // system sensor flag
|
||||
o_n?: string;
|
||||
o_n?: string; // original name
|
||||
}
|
||||
|
||||
export interface WriteTemperatureSensor {
|
||||
@@ -111,7 +111,7 @@ export interface SensorData {
|
||||
ts: TemperatureSensor[];
|
||||
as: AnalogSensor[];
|
||||
analog_enabled: boolean;
|
||||
valid_gpio_list: number[];
|
||||
available_gpios: number[];
|
||||
platform: string;
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ export enum DeviceValueUOM {
|
||||
export const DeviceValueUOM_s = [
|
||||
'',
|
||||
'°C',
|
||||
'°C',
|
||||
'°C Rel',
|
||||
'%',
|
||||
'l/min',
|
||||
'kWh',
|
||||
@@ -231,7 +231,6 @@ export const DeviceValueUOM_s = [
|
||||
|
||||
export enum AnalogType {
|
||||
REMOVED = -1,
|
||||
NOTUSED = 0,
|
||||
DIGITAL_IN = 1,
|
||||
COUNTER = 2,
|
||||
ADC = 3,
|
||||
@@ -250,22 +249,21 @@ export enum AnalogType {
|
||||
}
|
||||
|
||||
export const AnalogTypeNames = [
|
||||
'(disabled)',
|
||||
'Digital In',
|
||||
'Counter',
|
||||
'ADC In',
|
||||
'Timer',
|
||||
'Rate',
|
||||
'Digital Out',
|
||||
'PWM 0',
|
||||
'PWM 1',
|
||||
'PWM 2',
|
||||
'NTC Temp.',
|
||||
'RGB Led',
|
||||
'Pulse',
|
||||
'Freq 0',
|
||||
'Freq 1',
|
||||
'Freq 2'
|
||||
'Digital In', // 1
|
||||
'Counter', // 2
|
||||
'ADC In', // 3
|
||||
'Timer', // 4
|
||||
'Rate', // 5
|
||||
'Digital Out', // 6
|
||||
'PWM 0', // 7
|
||||
'PWM 1', // 8
|
||||
'PWM 2', // 9
|
||||
'NTC Temp.', // 10
|
||||
'RGB Led', // 11
|
||||
'Pulse', // 12
|
||||
'Freq 0', // 13
|
||||
'Freq 1', // 14
|
||||
'Freq 2' // 15
|
||||
] as const;
|
||||
|
||||
export const BOARD_PROFILES = {
|
||||
|
||||
@@ -310,14 +310,6 @@ export const analogSensorItemValidation = (
|
||||
{ required: true, message: 'Name is required' },
|
||||
NAME_PATTERN,
|
||||
uniqueAnalogNameValidator(sensors, sensor.o_n)
|
||||
],
|
||||
g: [
|
||||
{
|
||||
required: true,
|
||||
type: 'number',
|
||||
min: 1,
|
||||
message: 'GPIO is required'
|
||||
}
|
||||
]
|
||||
});
|
||||
};
|
||||
|
||||
@@ -857,8 +857,9 @@ const ApplicationSettings = () => {
|
||||
</Grid>
|
||||
|
||||
{restartNeeded && (
|
||||
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT(0)}>
|
||||
<MessageBox level="warning" message={LL.RESTART_TEXT(0)}>
|
||||
<Button
|
||||
sx={{ ml: 2 }}
|
||||
startIcon={<PowerSettingsNewIcon />}
|
||||
variant="contained"
|
||||
color="error"
|
||||
|
||||
@@ -355,8 +355,9 @@ const NetworkSettings = () => {
|
||||
</>
|
||||
)}
|
||||
{restartNeeded && (
|
||||
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT(0)}>
|
||||
<MessageBox level="warning" message={LL.RESTART_TEXT(0)}>
|
||||
<Button
|
||||
sx={{ ml: 2 }}
|
||||
startIcon={<PowerSettingsNewIcon />}
|
||||
variant="contained"
|
||||
color="error"
|
||||
|
||||
@@ -94,7 +94,7 @@ const WiFiNetworkSelector = ({ networkList }: { networkList: WiFiNetworkList })
|
||||
);
|
||||
|
||||
if (networkList.networks.length === 0) {
|
||||
return <MessageBox mt={2} mb={1} message={LL.NETWORK_NO_WIFI()} level="info" />;
|
||||
return <MessageBox message={LL.NETWORK_NO_WIFI()} level="info" />;
|
||||
}
|
||||
|
||||
return <List>{networkList.networks.map(renderNetwork)}</List>;
|
||||
|
||||
@@ -113,7 +113,7 @@ const SystemMonitor = () => {
|
||||
minWidth: '300px',
|
||||
maxWidth: '500px',
|
||||
backgroundColor: '#393939',
|
||||
border: 3,
|
||||
border: 2,
|
||||
borderColor: '#565656',
|
||||
borderRadius: '8px',
|
||||
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.3)',
|
||||
@@ -136,10 +136,10 @@ const SystemMonitor = () => {
|
||||
</Typography>
|
||||
|
||||
{errorMessage ? (
|
||||
<MessageBox my={2} level="error" message={errorMessage}>
|
||||
<MessageBox level="error" message={errorMessage}>
|
||||
<Button
|
||||
size="small"
|
||||
sx={{ ml: 2 }}
|
||||
size="small"
|
||||
startIcon={<CancelIcon />}
|
||||
variant="contained"
|
||||
color="error"
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { memo, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState
|
||||
} from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
@@ -356,9 +364,11 @@ const Version = () => {
|
||||
setShowVersionInfo(0);
|
||||
}, []);
|
||||
|
||||
// Effect for checking upgrades
|
||||
// check upgrades - only once when both versions are available
|
||||
const upgradeCheckedRef = useRef(false);
|
||||
useEffect(() => {
|
||||
if (latestVersion && latestDevVersion) {
|
||||
if (latestVersion && latestDevVersion && !upgradeCheckedRef.current) {
|
||||
upgradeCheckedRef.current = true;
|
||||
const versions = `${latestDevVersion.name},${latestVersion.name}`;
|
||||
sendCheckUpgrade(versions)
|
||||
.catch((error: Error) => {
|
||||
|
||||
@@ -19,10 +19,8 @@ import { I18nContext } from 'i18n/i18n-react';
|
||||
import type { Locales } from 'i18n/i18n-types';
|
||||
import { loadLocaleAsync } from 'i18n/i18n-util.async';
|
||||
|
||||
// Extract style to constant to prevent recreation
|
||||
const flagStyle: CSSProperties = { width: 16, verticalAlign: 'middle' };
|
||||
|
||||
// Define language options outside component to prevent recreation
|
||||
interface LanguageOption {
|
||||
key: Locales;
|
||||
flag: string;
|
||||
|
||||
@@ -15,6 +15,7 @@ export type ValidatedTextFieldProps = ValidatedFieldProps & TextFieldProps;
|
||||
|
||||
const ValidatedTextField: FC<ValidatedTextFieldProps> = ({
|
||||
fieldErrors,
|
||||
sx,
|
||||
...rest
|
||||
}) => {
|
||||
const errors = fieldErrors?.[rest.name];
|
||||
@@ -25,11 +26,23 @@ const ValidatedTextField: FC<ValidatedTextFieldProps> = ({
|
||||
error={!!errors}
|
||||
{...rest}
|
||||
aria-label="Error"
|
||||
slotProps={{
|
||||
inputLabel: {
|
||||
style: rest.disabled ? { color: 'grey' } : undefined
|
||||
}
|
||||
sx={{
|
||||
'& .MuiInputBase-input.Mui-disabled': {
|
||||
WebkitTextFillColor: 'grey'
|
||||
},
|
||||
...(sx || {})
|
||||
}}
|
||||
{...(rest.disabled && {
|
||||
slotProps: {
|
||||
select: {
|
||||
IconComponent: () => null
|
||||
},
|
||||
inputLabel: {
|
||||
style: { color: 'grey' }
|
||||
}
|
||||
}
|
||||
})}
|
||||
color={rest.disabled ? 'secondary' : 'primary'}
|
||||
/>
|
||||
{errors?.map((e) => (
|
||||
<FormHelperText key={e.message} sx={{ color: 'rgb(250, 95, 84)' }}>
|
||||
|
||||
@@ -22,7 +22,6 @@ interface ListMenuItemProps {
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
// Extract styles to prevent recreation
|
||||
const iconStyles: CSSProperties = {
|
||||
justifyContent: 'right',
|
||||
color: 'lightblue',
|
||||
|
||||
@@ -16,7 +16,7 @@ const FormLoaderComponent = ({ errorMessage, onRetry }: FormLoaderProps) => {
|
||||
|
||||
if (errorMessage) {
|
||||
return (
|
||||
<MessageBox my={2} level="error" message={errorMessage}>
|
||||
<MessageBox level="error" message={errorMessage}>
|
||||
{onRetry && (
|
||||
<Button
|
||||
sx={{ ml: 2 }}
|
||||
|
||||
@@ -3,7 +3,6 @@ import { memo } from 'react';
|
||||
import { Box, CircularProgress } from '@mui/material';
|
||||
import type { SxProps, Theme } from '@mui/material';
|
||||
|
||||
// Extract styles to prevent recreation on every render
|
||||
const containerStyles: SxProps<Theme> = {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
|
||||
@@ -7,7 +7,6 @@ interface LoadingSpinnerProps {
|
||||
height?: number | string;
|
||||
}
|
||||
|
||||
// Extract styles to prevent recreation on every render
|
||||
const circularProgressStyles: SxProps<Theme> = (theme: Theme) => ({
|
||||
margin: theme.spacing(4),
|
||||
color: theme.palette.text.secondary
|
||||
|
||||
@@ -50,7 +50,7 @@ inline Transition mkx(const char c, Parser_state p, State_transition_hook pth) {
|
||||
}
|
||||
|
||||
inline void Parse_error(const std::string & s) {
|
||||
// emsesp::EMSESP::logger().err("parse error: %s", s.c_str());
|
||||
// EMSESP::logger().err("parse error: %s", s.c_str());
|
||||
}
|
||||
|
||||
/// Advance parser state machine by a single step.
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
"itty-router": "^5.0.22",
|
||||
"prettier": "^3.6.2"
|
||||
},
|
||||
"packageManager": "pnpm@10.22.0+sha512.bf049efe995b28f527fd2b41ae0474ce29186f7edcb3bf545087bd61fbbebb2bf75362d1307fda09c2d288e1e499787ac12d4fcb617a974718a6051f2eee741c"
|
||||
"packageManager": "pnpm@10.23.0+sha512.21c4e5698002ade97e4efe8b8b4a89a8de3c85a37919f957e7a0f30f38fbc5bbdd05980ffe29179b2fb6e6e691242e098d945d1601772cad0fef5fb6411e2a4b"
|
||||
}
|
||||
|
||||
@@ -984,10 +984,10 @@ const emsesp_sensordata = {
|
||||
],
|
||||
// as: [],
|
||||
as: [
|
||||
{ id: 1, g: 35, n: 'motor', v: 0, u: 0, o: 17, f: 0, t: 0, d: false, s: false },
|
||||
{ id: 1, g: 35, n: 'motor', v: 0, u: 0, o: 17, f: 0, t: 7, d: false, s: false },
|
||||
{
|
||||
id: 2,
|
||||
g: 37,
|
||||
g: 34,
|
||||
n: 'External_switch',
|
||||
v: 13,
|
||||
u: 0,
|
||||
@@ -1047,7 +1047,7 @@ const emsesp_sensordata = {
|
||||
}
|
||||
],
|
||||
analog_enabled: true,
|
||||
valid_gpio_list: [0, 2, 5, 12, 13, 15, 18, 19, 23, 25, 26, 27, 33, 37, 38]
|
||||
available_gpios: [] as number[]
|
||||
};
|
||||
|
||||
const activity = {
|
||||
@@ -4540,6 +4540,28 @@ router
|
||||
.get(EMSESP_SENSOR_DATA_ENDPOINT, () => {
|
||||
// random change the zolder temperature 0-100
|
||||
emsesp_sensordata.ts[2].t = Math.floor(Math.random() * 100);
|
||||
|
||||
// Build list of available GPIOs (S3 board pins) excluding used ones
|
||||
// and sort it
|
||||
const allGPIOs = [
|
||||
2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 21, 33, 34, 35, 36, 37, 38,
|
||||
45, 46
|
||||
];
|
||||
const usedGPIOs = new Set([
|
||||
settings.led_gpio,
|
||||
settings.dallas_gpio,
|
||||
settings.pbutton_gpio,
|
||||
settings.rx_gpio,
|
||||
settings.tx_gpio,
|
||||
...emsesp_sensordata.as.map((item) => item.g)
|
||||
]);
|
||||
|
||||
emsesp_sensordata.available_gpios = allGPIOs
|
||||
.filter((gpio) => !usedGPIOs.has(gpio))
|
||||
.sort((a, b) => a - b);
|
||||
|
||||
// console.log('available_gpios', emsesp_sensordata.available_gpios);
|
||||
|
||||
return emsesp_sensordata;
|
||||
})
|
||||
.get(EMSESP_DEVICEDATA_ENDPOINT1, (request) =>
|
||||
|
||||
@@ -218,6 +218,7 @@ lib_deps =
|
||||
; to run use `platformio run -e native-test -t exec`. All tests should PASS.
|
||||
; to update the test results, compile with -DEMSESP_UNITY_CREATE by uncommenting the line below
|
||||
; then re-run and capture the output between "START - CUT HERE" and "END - CUT HERE" into the test_api.h file
|
||||
; tip: use https://jsondiff.com/ to compare the expected and actual responses.
|
||||
[env:native-test]
|
||||
platform = native
|
||||
test_build_src = true
|
||||
|
||||
@@ -831,7 +831,6 @@ notoken
|
||||
NOTOKEN
|
||||
NOTRANSLATION
|
||||
NOTSET
|
||||
NOTUSED
|
||||
NOTYPE
|
||||
nrgconscomp
|
||||
nrgconscompcooling
|
||||
|
||||
@@ -37,9 +37,11 @@ void NetworkSettingsService::begin() {
|
||||
|
||||
WiFi.mode(WIFI_MODE_MAX);
|
||||
WiFi.mode(WIFI_MODE_NULL);
|
||||
|
||||
// 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
|
||||
|
||||
_fsPersistence.readFromFS();
|
||||
}
|
||||
|
||||
|
||||
@@ -155,14 +155,10 @@ void AnalogSensor::reload(bool get_nvs) {
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
// check if the GPIO is valid before registering. If not, force set the sensor to disabled, but don't remove it
|
||||
// should only trigger if uploading a customization file with invalid gpios.
|
||||
if (!EMSESP::system_.is_valid_gpio(sensor.gpio)) {
|
||||
LOG_WARNING("Bad GPIO %d for Sensor %s. Disabling.", sensor.gpio, sensor.name.c_str());
|
||||
sensors_.emplace_back(99, sensor.name, sensor.offset, sensor.factor, sensor.uom, sensor.type, sensor.is_system);
|
||||
} else {
|
||||
sensors_.emplace_back(sensor.gpio, sensor.name, sensor.offset, sensor.factor, sensor.uom, sensor.type, sensor.is_system);
|
||||
}
|
||||
// it's new, we assume it's valid
|
||||
AnalogType type = static_cast<AnalogType>(sensor.type);
|
||||
sensors_.emplace_back(sensor.gpio, sensor.name, sensor.offset, sensor.factor, sensor.uom, type, sensor.is_system);
|
||||
|
||||
sensors_.back().ha_registered = false; // this will trigger recreate of the HA config
|
||||
if (sensor.type == AnalogType::COUNTER || sensor.type >= AnalogType::DIGITAL_OUT) {
|
||||
sensors_.back().set_value(sensor.offset);
|
||||
@@ -170,6 +166,8 @@ void AnalogSensor::reload(bool get_nvs) {
|
||||
sensors_.back().set_value(0); // reset value only for new sensors
|
||||
}
|
||||
}
|
||||
|
||||
// add the command to set the value of the sensor
|
||||
if (sensor.type == AnalogType::COUNTER || (sensor.type >= AnalogType::DIGITAL_OUT && sensor.type <= AnalogType::PWM_2)
|
||||
|| sensor.type == AnalogType::RGB || sensor.type == AnalogType::PULSE) {
|
||||
Command::add(
|
||||
@@ -471,8 +469,9 @@ void AnalogSensor::loop() {
|
||||
measure(); // take the measurements
|
||||
}
|
||||
|
||||
// update analog information name and offset
|
||||
// update analog information name, offset, factor, uom, type, deleted, is_system
|
||||
// a type value of -1 is used to delete the sensor
|
||||
// the gpio is the key
|
||||
bool AnalogSensor::update(uint8_t gpio, std::string & name, double offset, double factor, uint8_t uom, int8_t type, bool deleted, bool is_system) {
|
||||
// first see if we can find the sensor in our customization list
|
||||
bool found_sensor = false;
|
||||
@@ -492,8 +491,9 @@ bool AnalogSensor::update(uint8_t gpio, std::string & name, double offset, doubl
|
||||
found_sensor = true; // found the record
|
||||
// see if it's marked for deletion
|
||||
if (deleted) {
|
||||
EMSESP::nvs_.remove(AnalogCustomization.name.c_str());
|
||||
LOG_DEBUG("Removing analog sensor GPIO %02d", gpio);
|
||||
EMSESP::system_.remove_gpio(gpio); // remove from used list only
|
||||
EMSESP::nvs_.remove(AnalogCustomization.name.c_str());
|
||||
settings.analogCustomizations.remove(AnalogCustomization);
|
||||
} else {
|
||||
// update existing record
|
||||
@@ -520,8 +520,7 @@ bool AnalogSensor::update(uint8_t gpio, std::string & name, double offset, doubl
|
||||
}
|
||||
|
||||
// we didn't find it, it's new, so create and store it in the customization list
|
||||
// gpio is already checked in web interface, should never trigger.
|
||||
if (!found_sensor && EMSESP::system_.is_valid_gpio(gpio)) {
|
||||
if (!found_sensor) {
|
||||
found_sensor = true;
|
||||
EMSESP::webCustomizationService.update([&](WebCustomization & settings) {
|
||||
auto newSensor = AnalogCustomization();
|
||||
@@ -533,18 +532,23 @@ bool AnalogSensor::update(uint8_t gpio, std::string & name, double offset, doubl
|
||||
newSensor.type = type;
|
||||
newSensor.is_system = is_system;
|
||||
settings.analogCustomizations.push_back(newSensor);
|
||||
LOG_DEBUG("Adding new customization for analog sensor GPIO %02d", gpio);
|
||||
return StateUpdateResult::CHANGED; // persist the change
|
||||
// check the gpio again and add to used list
|
||||
if (EMSESP::system_.add_gpio(gpio, "Analog Sensor")) {
|
||||
LOG_DEBUG("Adding customization for analog sensor GPIO %02d", gpio);
|
||||
return StateUpdateResult::CHANGED; // persist the change
|
||||
} else {
|
||||
found_sensor = false;
|
||||
return StateUpdateResult::ERROR; // if we can't add the GPIO, return an error
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// reloads the sensors in the customizations file into the sensors list
|
||||
if (found_sensor) {
|
||||
reload();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return found_sensor;
|
||||
}
|
||||
|
||||
// check to see if values have been updated
|
||||
@@ -624,158 +628,156 @@ void AnalogSensor::publish_values(const bool force) {
|
||||
JsonDocument doc;
|
||||
|
||||
for (auto & sensor : sensors_) {
|
||||
if (sensor.type() != AnalogType::NOTUSED) {
|
||||
if (Mqtt::is_nested()) {
|
||||
char s[10];
|
||||
JsonObject dataSensor = doc[Helpers::smallitoa(s, sensor.gpio())].to<JsonObject>();
|
||||
dataSensor["name"] = sensor.name();
|
||||
if (Mqtt::is_nested()) {
|
||||
char s[10];
|
||||
JsonObject dataSensor = doc[Helpers::smallitoa(s, sensor.gpio())].to<JsonObject>();
|
||||
dataSensor["name"] = sensor.name();
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
if (sensor.type() == AnalogType::PULSE || (sensor.type() == AnalogType::DIGITAL_OUT && sensor.gpio() != 25 && sensor.gpio() != 26)) {
|
||||
if (sensor.type() == AnalogType::PULSE || (sensor.type() == AnalogType::DIGITAL_OUT && sensor.gpio() != 25 && sensor.gpio() != 26)) {
|
||||
#else
|
||||
if (sensor.type() == AnalogType::PULSE || sensor.type() == AnalogType::DIGITAL_OUT) {
|
||||
if (sensor.type() == AnalogType::PULSE || sensor.type() == AnalogType::DIGITAL_OUT) {
|
||||
#endif
|
||||
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
|
||||
dataSensor["value"] = sensor.value() != 0;
|
||||
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
|
||||
dataSensor["value"] = sensor.value() != 0 ? 1 : 0;
|
||||
} else {
|
||||
char result[12];
|
||||
dataSensor["value"] = Helpers::render_boolean(result, sensor.value() != 0);
|
||||
}
|
||||
} else {
|
||||
dataSensor["value"] = serialized(Helpers::render_value(s, sensor.value(), 2)); // double
|
||||
}
|
||||
} else if (sensor.type() == AnalogType::DIGITAL_IN || sensor.type() == AnalogType::DIGITAL_OUT || sensor.type() == AnalogType::PULSE) {
|
||||
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
|
||||
doc[sensor.name()] = sensor.value() != 0;
|
||||
dataSensor["value"] = sensor.value() != 0;
|
||||
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
|
||||
doc[sensor.name()] = sensor.value() != 0 ? 1 : 0;
|
||||
dataSensor["value"] = sensor.value() != 0 ? 1 : 0;
|
||||
} else {
|
||||
char result[12];
|
||||
doc[sensor.name()] = Helpers::render_boolean(result, sensor.value() != 0);
|
||||
dataSensor["value"] = Helpers::render_boolean(result, sensor.value() != 0);
|
||||
}
|
||||
} else {
|
||||
char s[10];
|
||||
doc[sensor.name()] = serialized(Helpers::render_value(s, sensor.value(), 2));
|
||||
dataSensor["value"] = serialized(Helpers::render_value(s, sensor.value(), 2)); // double
|
||||
}
|
||||
} else if (sensor.type() == AnalogType::DIGITAL_IN || sensor.type() == AnalogType::DIGITAL_OUT || sensor.type() == AnalogType::PULSE) {
|
||||
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
|
||||
doc[sensor.name()] = sensor.value() != 0;
|
||||
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
|
||||
doc[sensor.name()] = sensor.value() != 0 ? 1 : 0;
|
||||
} else {
|
||||
char result[12];
|
||||
doc[sensor.name()] = Helpers::render_boolean(result, sensor.value() != 0);
|
||||
}
|
||||
} else {
|
||||
char s[10];
|
||||
doc[sensor.name()] = serialized(Helpers::render_value(s, sensor.value(), 2));
|
||||
}
|
||||
|
||||
// create HA config if hasn't already been done
|
||||
if (Mqtt::ha_enabled() && (!sensor.ha_registered || force)) {
|
||||
LOG_DEBUG("Recreating HA config for analog sensor GPIO %02d", sensor.gpio());
|
||||
|
||||
JsonDocument config;
|
||||
|
||||
char stat_t[50];
|
||||
snprintf(stat_t, sizeof(stat_t), "%s/%s_data", Mqtt::base().c_str(), F_(analogsensor)); // use base path
|
||||
config["stat_t"] = stat_t;
|
||||
|
||||
char val_obj[50];
|
||||
char val_cond[95];
|
||||
if (Mqtt::is_nested()) {
|
||||
snprintf(val_obj, sizeof(val_obj), "value_json['%02d']['value']", sensor.gpio());
|
||||
snprintf(val_cond, sizeof(val_cond), "value_json['%02d'] is defined and %s is defined", sensor.gpio(), val_obj);
|
||||
} else {
|
||||
snprintf(val_obj, sizeof(val_obj), "value_json['%s']", sensor.name().c_str());
|
||||
snprintf(val_cond, sizeof(val_cond), "%s is defined", val_obj);
|
||||
}
|
||||
char sample_val[12] = "0";
|
||||
if (sensor.type() == AnalogType::DIGITAL_IN || sensor.type() == AnalogType::DIGITAL_OUT || sensor.type() == AnalogType::PULSE) {
|
||||
Helpers::render_boolean(sample_val, false);
|
||||
}
|
||||
// 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 + "}}";
|
||||
} else {
|
||||
config["val_tpl"] = (std::string) "{{" + val_obj + "}}";
|
||||
}
|
||||
|
||||
// create HA config if hasn't already been done
|
||||
if (Mqtt::ha_enabled() && (!sensor.ha_registered || force)) {
|
||||
LOG_DEBUG("Recreating HA config for analog sensor GPIO %02d", sensor.gpio());
|
||||
char uniq_s[70];
|
||||
if (Mqtt::entity_format() == Mqtt::entityFormat::MULTI_SHORT) {
|
||||
snprintf(uniq_s, sizeof(uniq_s), "%s_%s_%02d", Mqtt::basename().c_str(), F_(analogsensor), sensor.gpio());
|
||||
} else {
|
||||
snprintf(uniq_s, sizeof(uniq_s), "%s_%02d", F_(analogsensor), sensor.gpio());
|
||||
}
|
||||
|
||||
JsonDocument config;
|
||||
config["uniq_id"] = uniq_s;
|
||||
|
||||
char stat_t[50];
|
||||
snprintf(stat_t, sizeof(stat_t), "%s/%s_data", Mqtt::base().c_str(), F_(analogsensor)); // use base path
|
||||
config["stat_t"] = stat_t;
|
||||
char name[50];
|
||||
snprintf(name, sizeof(name), "%s", sensor.name().c_str());
|
||||
config["name"] = name;
|
||||
|
||||
char val_obj[50];
|
||||
char val_cond[95];
|
||||
if (Mqtt::is_nested()) {
|
||||
snprintf(val_obj, sizeof(val_obj), "value_json['%02d']['value']", sensor.gpio());
|
||||
snprintf(val_cond, sizeof(val_cond), "value_json['%02d'] is defined and %s is defined", sensor.gpio(), val_obj);
|
||||
} else {
|
||||
snprintf(val_obj, sizeof(val_obj), "value_json['%s']", sensor.name().c_str());
|
||||
snprintf(val_cond, sizeof(val_cond), "%s is defined", val_obj);
|
||||
}
|
||||
char sample_val[12] = "0";
|
||||
if (sensor.type() == AnalogType::DIGITAL_IN || sensor.type() == AnalogType::DIGITAL_OUT || sensor.type() == AnalogType::PULSE) {
|
||||
Helpers::render_boolean(sample_val, false);
|
||||
}
|
||||
// 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 + "}}";
|
||||
} else {
|
||||
config["val_tpl"] = (std::string) "{{" + val_obj + "}}";
|
||||
}
|
||||
if (sensor.uom() != DeviceValueUOM::NONE && sensor.type() != AnalogType::DIGITAL_OUT) {
|
||||
config["unit_of_meas"] = EMSdevice::uom_to_string(sensor.uom());
|
||||
}
|
||||
|
||||
char uniq_s[70];
|
||||
if (Mqtt::entity_format() == Mqtt::entityFormat::MULTI_SHORT) {
|
||||
snprintf(uniq_s, sizeof(uniq_s), "%s_%s_%02d", Mqtt::basename().c_str(), F_(analogsensor), sensor.gpio());
|
||||
} else {
|
||||
snprintf(uniq_s, sizeof(uniq_s), "%s_%02d", F_(analogsensor), sensor.gpio());
|
||||
}
|
||||
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||
|
||||
config["uniq_id"] = uniq_s;
|
||||
|
||||
char name[50];
|
||||
snprintf(name, sizeof(name), "%s", sensor.name().c_str());
|
||||
config["name"] = name;
|
||||
|
||||
if (sensor.uom() != DeviceValueUOM::NONE && sensor.type() != AnalogType::DIGITAL_OUT) {
|
||||
config["unit_of_meas"] = EMSdevice::uom_to_string(sensor.uom());
|
||||
}
|
||||
|
||||
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||
|
||||
// Set commands for some analog types
|
||||
char command_topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||
// Set commands for some analog types
|
||||
char command_topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
if (sensor.type() == AnalogType::PULSE || (sensor.type() == AnalogType::DIGITAL_OUT && sensor.gpio() != 25 && sensor.gpio() != 26)) {
|
||||
if (sensor.type() == AnalogType::PULSE || (sensor.type() == AnalogType::DIGITAL_OUT && sensor.gpio() != 25 && sensor.gpio() != 26)) {
|
||||
#else
|
||||
if (sensor.type() == AnalogType::PULSE || sensor.type() == AnalogType::DIGITAL_OUT) {
|
||||
if (sensor.type() == AnalogType::PULSE || sensor.type() == AnalogType::DIGITAL_OUT) {
|
||||
#endif
|
||||
snprintf(topic, sizeof(topic), "switch/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), sensor.gpio());
|
||||
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), F_(analogsensor), sensor.name().c_str());
|
||||
config["cmd_t"] = command_topic;
|
||||
Mqtt::add_ha_bool(config.as<JsonObject>());
|
||||
} else if (sensor.type() == AnalogType::DIGITAL_OUT) { // DAC
|
||||
snprintf(topic, sizeof(topic), "number/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), sensor.gpio());
|
||||
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), F_(analogsensor), sensor.name().c_str());
|
||||
config["cmd_t"] = command_topic;
|
||||
config["min"] = 0;
|
||||
config["max"] = 255;
|
||||
config["mode"] = "box"; // auto, slider or box
|
||||
config["step"] = 1;
|
||||
} else if (sensor.type() >= AnalogType::PWM_0 && sensor.type() <= AnalogType::PWM_2) {
|
||||
snprintf(topic, sizeof(topic), "number/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), sensor.gpio());
|
||||
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), F_(analogsensor), sensor.name().c_str());
|
||||
config["cmd_t"] = command_topic;
|
||||
config["min"] = 0;
|
||||
config["max"] = 100;
|
||||
config["mode"] = "box"; // auto, slider or box
|
||||
config["step"] = 0.1;
|
||||
} else if (sensor.type() == AnalogType::RGB) {
|
||||
snprintf(topic, sizeof(topic), "number/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), sensor.gpio());
|
||||
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), F_(analogsensor), sensor.name().c_str());
|
||||
config["cmd_t"] = command_topic;
|
||||
config["min"] = 0;
|
||||
config["max"] = 999999;
|
||||
config["mode"] = "box"; // auto, slider or box
|
||||
config["step"] = 1;
|
||||
} else if (sensor.type() == AnalogType::COUNTER) {
|
||||
snprintf(topic, sizeof(topic), "sensor/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), sensor.gpio());
|
||||
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), F_(analogsensor), sensor.name().c_str());
|
||||
config["cmd_t"] = command_topic;
|
||||
config["stat_cla"] = "total_increasing";
|
||||
// config["mode"] = "box"; // auto, slider or box
|
||||
// config["step"] = sensor.factor();
|
||||
} else if (sensor.type() == AnalogType::DIGITAL_IN) {
|
||||
snprintf(topic, sizeof(topic), "binary_sensor/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), sensor.gpio());
|
||||
Mqtt::add_ha_bool(config.as<JsonObject>());
|
||||
} else {
|
||||
snprintf(topic, sizeof(topic), "sensor/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), sensor.gpio());
|
||||
config["stat_cla"] = "measurement";
|
||||
}
|
||||
|
||||
// see if we need to create the [devs] discovery section, as this needs only to be done once for all sensors
|
||||
bool is_ha_device_created = false;
|
||||
for (auto const & sensor : sensors_) {
|
||||
if (sensor.ha_registered) {
|
||||
is_ha_device_created = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// add default_entity_id
|
||||
std::string topic_str(topic);
|
||||
doc["def_ent_id"] = topic_str.substr(0, topic_str.find("/")) + "." + uniq_s;
|
||||
|
||||
Mqtt::add_ha_dev_section(config.as<JsonObject>(), "Analog Sensors", nullptr, nullptr, nullptr, false);
|
||||
Mqtt::add_ha_avail_section(config.as<JsonObject>(), stat_t, !is_ha_device_created, val_cond);
|
||||
|
||||
sensor.ha_registered = Mqtt::queue_ha(topic, config.as<JsonObject>());
|
||||
snprintf(topic, sizeof(topic), "switch/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), sensor.gpio());
|
||||
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), F_(analogsensor), sensor.name().c_str());
|
||||
config["cmd_t"] = command_topic;
|
||||
Mqtt::add_ha_bool(config.as<JsonObject>());
|
||||
} else if (sensor.type() == AnalogType::DIGITAL_OUT) { // DAC
|
||||
snprintf(topic, sizeof(topic), "number/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), sensor.gpio());
|
||||
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), F_(analogsensor), sensor.name().c_str());
|
||||
config["cmd_t"] = command_topic;
|
||||
config["min"] = 0;
|
||||
config["max"] = 255;
|
||||
config["mode"] = "box"; // auto, slider or box
|
||||
config["step"] = 1;
|
||||
} else if (sensor.type() >= AnalogType::PWM_0 && sensor.type() <= AnalogType::PWM_2) {
|
||||
snprintf(topic, sizeof(topic), "number/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), sensor.gpio());
|
||||
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), F_(analogsensor), sensor.name().c_str());
|
||||
config["cmd_t"] = command_topic;
|
||||
config["min"] = 0;
|
||||
config["max"] = 100;
|
||||
config["mode"] = "box"; // auto, slider or box
|
||||
config["step"] = 0.1;
|
||||
} else if (sensor.type() == AnalogType::RGB) {
|
||||
snprintf(topic, sizeof(topic), "number/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), sensor.gpio());
|
||||
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), F_(analogsensor), sensor.name().c_str());
|
||||
config["cmd_t"] = command_topic;
|
||||
config["min"] = 0;
|
||||
config["max"] = 999999;
|
||||
config["mode"] = "box"; // auto, slider or box
|
||||
config["step"] = 1;
|
||||
} else if (sensor.type() == AnalogType::COUNTER) {
|
||||
snprintf(topic, sizeof(topic), "sensor/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), sensor.gpio());
|
||||
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), F_(analogsensor), sensor.name().c_str());
|
||||
config["cmd_t"] = command_topic;
|
||||
config["stat_cla"] = "total_increasing";
|
||||
// config["mode"] = "box"; // auto, slider or box
|
||||
// config["step"] = sensor.factor();
|
||||
} else if (sensor.type() == AnalogType::DIGITAL_IN) {
|
||||
snprintf(topic, sizeof(topic), "binary_sensor/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), sensor.gpio());
|
||||
Mqtt::add_ha_bool(config.as<JsonObject>());
|
||||
} else {
|
||||
snprintf(topic, sizeof(topic), "sensor/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), sensor.gpio());
|
||||
config["stat_cla"] = "measurement";
|
||||
}
|
||||
|
||||
// see if we need to create the [devs] discovery section, as this needs only to be done once for all sensors
|
||||
bool is_ha_device_created = false;
|
||||
for (auto const & sensor : sensors_) {
|
||||
if (sensor.ha_registered) {
|
||||
is_ha_device_created = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// add default_entity_id
|
||||
std::string topic_str(topic);
|
||||
doc["def_ent_id"] = topic_str.substr(0, topic_str.find("/")) + "." + uniq_s;
|
||||
|
||||
Mqtt::add_ha_dev_section(config.as<JsonObject>(), "Analog Sensors", nullptr, nullptr, nullptr, false);
|
||||
Mqtt::add_ha_avail_section(config.as<JsonObject>(), stat_t, !is_ha_device_created, val_cond);
|
||||
|
||||
sensor.ha_registered = Mqtt::queue_ha(topic, config.as<JsonObject>());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -118,7 +118,6 @@ class AnalogSensor {
|
||||
~AnalogSensor() = default;
|
||||
|
||||
enum AnalogType : int8_t {
|
||||
NOTUSED = 0, // 0 = disabled
|
||||
DIGITAL_IN = 1,
|
||||
COUNTER = 2,
|
||||
ADC = 3,
|
||||
@@ -164,12 +163,10 @@ class AnalogSensor {
|
||||
return (!sensors_.empty());
|
||||
}
|
||||
|
||||
// count number of items in sensors_ where type is not set to disabled and not a system sensor
|
||||
size_t count_entities(bool exclude_disabled_system = false) const {
|
||||
if (exclude_disabled_system) {
|
||||
// count number of items in sensors_ where type is not set to disabled and not a system sensor
|
||||
return std::count_if(sensors_.begin(), sensors_.end(), [](const Sensor & sensor) {
|
||||
return sensor.type() != AnalogSensor::AnalogType::NOTUSED && !sensor.is_system();
|
||||
});
|
||||
return std::count_if(sensors_.begin(), sensors_.end(), [](const Sensor & sensor) { return !sensor.is_system(); });
|
||||
}
|
||||
return sensors_.size();
|
||||
}
|
||||
|
||||
@@ -320,8 +320,8 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
|
||||
settings.eth_clock_mode = data[8];
|
||||
return StateUpdateResult::CHANGED;
|
||||
});
|
||||
shell.printfln("Loaded board profile %s", board_profile.c_str());
|
||||
EMSESP::system_.network_init(true);
|
||||
shell.printfln("Loaded board profile %s. Restarting...", board_profile.c_str());
|
||||
EMSESP::system_.system_restart();
|
||||
});
|
||||
|
||||
commands->add_command(
|
||||
@@ -338,7 +338,7 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
|
||||
return StateUpdateResult::CHANGED;
|
||||
});
|
||||
} else {
|
||||
shell.println("Must be 0B, 0D, 0A, 0E, 0F, or 48 - 4D");
|
||||
shell.println("Must be 0B, 0D, 0A, 0E, 0F or 48-4D");
|
||||
}
|
||||
},
|
||||
[](Shell & shell, const std::vector<std::string> & current_arguments, const std::string & next_argument) -> std::vector<std::string> {
|
||||
@@ -357,7 +357,7 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
|
||||
shell.printfln(F_(tx_mode_fmt), settings.tx_mode);
|
||||
return StateUpdateResult::CHANGED;
|
||||
});
|
||||
EMSESP::system_.uart_init(false);
|
||||
EMSESP::system_.uart_init();
|
||||
});
|
||||
|
||||
//
|
||||
|
||||
@@ -1665,6 +1665,9 @@ void EMSESP::start() {
|
||||
bool factory_settings = false;
|
||||
#endif
|
||||
|
||||
// set valid GPIOs list based on ESP32 chip/platform type
|
||||
system_.set_valid_system_gpios();
|
||||
|
||||
// start web log service. now we can start capturing logs to the web log
|
||||
webLogService.begin();
|
||||
|
||||
@@ -1714,8 +1717,6 @@ void EMSESP::start() {
|
||||
};
|
||||
LOG_INFO("Library loaded: %d EMS devices, %d device entities, %s", device_library_.size(), EMSESP_TRANSLATION_COUNT, system_.languages_string().c_str());
|
||||
|
||||
system_.reload_settings(); // ... and store some of the settings locally
|
||||
|
||||
webCustomizationService.begin(); // load the customizations
|
||||
webSchedulerService.begin(); // load the scheduler events
|
||||
webCustomEntityService.begin(); // load the custom telegram reads
|
||||
@@ -1776,12 +1777,12 @@ void EMSESP::shell_prompt() {
|
||||
|
||||
// main loop calling all services
|
||||
void EMSESP::loop() {
|
||||
esp32React.loop(); // web services
|
||||
system_.loop(); // does LED and checks system health, and syslog service
|
||||
esp32React.loop(); // web services
|
||||
system_.loop(); // does LED and checks system health, and syslog service
|
||||
webLogService.loop(); // log in Web UI
|
||||
|
||||
// run the loop, unless we're in the middle of an OTA upload
|
||||
if (EMSESP::system_.systemStatus() == SYSTEM_STATUS::SYSTEM_STATUS_NORMAL) {
|
||||
webLogService.loop(); // log in Web UI
|
||||
rxservice_.loop(); // process any incoming Rx telegrams
|
||||
shower_.loop(); // check for shower on/off
|
||||
temperaturesensor_.loop(); // read sensor temperatures
|
||||
@@ -1806,6 +1807,14 @@ void EMSESP::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
if (EMSESP::system_.systemStatus() == SYSTEM_STATUS::SYSTEM_STATUS_INVALID_GPIO) {
|
||||
static bool only_once = false;
|
||||
if (!only_once) {
|
||||
LOG_ERROR("Invalid GPIOs used. Please check your settings and log");
|
||||
only_once = true;
|
||||
}
|
||||
}
|
||||
|
||||
uuid::loop();
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
|
||||
@@ -697,7 +697,7 @@ bool Helpers::value2bool(const char * value, bool & value_b) {
|
||||
}
|
||||
|
||||
#ifdef EMSESP_STANDALONE
|
||||
emsesp::EMSESP::logger().debug("Error. value2bool: %s is not a boolean", value);
|
||||
EMSESP::logger().debug("Error. value2bool: %s is not a boolean", value);
|
||||
#endif
|
||||
|
||||
return false; // not a bool
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
#include "shuntingYard.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
// find tokens - optimized to reduce string allocations
|
||||
std::deque<Token> exprToTokens(const std::string & expr) {
|
||||
std::deque<Token> tokens;
|
||||
@@ -340,9 +342,9 @@ bool isnum(const std::string & s) {
|
||||
|
||||
// replace commands like "<device>/<hc>/<cmd>" with its value"
|
||||
std::string commands(std::string & expr, bool quotes) {
|
||||
auto expr_new = emsesp::Helpers::toLower(expr);
|
||||
for (uint8_t device = 0; device < emsesp::EMSdevice::DeviceType::UNKNOWN; device++) {
|
||||
std::string d = (std::string)emsesp::EMSdevice::device_type_2_device_name(device) + "/";
|
||||
auto expr_new = Helpers::toLower(expr);
|
||||
for (uint8_t device = 0; device < EMSdevice::DeviceType::UNKNOWN; device++) {
|
||||
std::string d = (std::string)EMSdevice::device_type_2_device_name(device) + "/";
|
||||
auto f = expr_new.find(d);
|
||||
while (f != std::string::npos) {
|
||||
// entity names are alphanumeric or _
|
||||
@@ -367,9 +369,9 @@ std::string commands(std::string & expr, bool quotes) {
|
||||
JsonObject input = doc_in.to<JsonObject>();
|
||||
std::string cmd_s = "api/" + std::string(cmd);
|
||||
|
||||
auto return_code = emsesp::Command::process(cmd_s.c_str(), true, input, output);
|
||||
auto return_code = Command::process(cmd_s.c_str(), true, input, output);
|
||||
// check for no value (entity is valid but has no value set)
|
||||
if (return_code != emsesp::CommandRet::OK && return_code != emsesp::CommandRet::NO_VALUE) {
|
||||
if (return_code != CommandRet::OK && return_code != CommandRet::NO_VALUE) {
|
||||
return expr = "";
|
||||
}
|
||||
|
||||
@@ -380,7 +382,7 @@ std::string commands(std::string & expr, bool quotes) {
|
||||
}
|
||||
expr.replace(f, l, data);
|
||||
e = f + data.length();
|
||||
expr_new = emsesp::Helpers::toLower(expr);
|
||||
expr_new = Helpers::toLower(expr);
|
||||
f = expr_new.find(d, e);
|
||||
}
|
||||
}
|
||||
@@ -400,7 +402,7 @@ int to_logic(const std::string & s) {
|
||||
if (s.empty()) {
|
||||
return -1;
|
||||
}
|
||||
auto l = emsesp::Helpers::toLower(s);
|
||||
auto l = Helpers::toLower(s);
|
||||
if (s[0] == '1' || l == "on" || l == "true") {
|
||||
return 1;
|
||||
}
|
||||
@@ -438,7 +440,7 @@ std::string calculate(const std::string & expr) {
|
||||
const auto tokens = exprToTokens(expr_new);
|
||||
// for debugging only
|
||||
// for (const auto & t : tokens) {
|
||||
// emsesp::EMSESP::logger().debug("shunt token: %s(%d)", t.str.c_str(), t.type);
|
||||
// EMSESP::logger().debug("shunt token: %s(%d)", t.str.c_str(), t.type);
|
||||
// Serial.printf("shunt token: %s(%d)\n", t.str.c_str(), t.type);
|
||||
// Serial.println();
|
||||
// }
|
||||
@@ -475,7 +477,7 @@ std::string calculate(const std::string & expr) {
|
||||
} else if (isnum(rhs)) {
|
||||
stack.push_back(std::stod(rhs) == 0 ? "1" : "0");
|
||||
} else {
|
||||
emsesp::EMSESP::logger().warning("missing operator");
|
||||
EMSESP::logger().warning("missing operator");
|
||||
return "";
|
||||
}
|
||||
break;
|
||||
@@ -573,7 +575,7 @@ std::string calculate(const std::string & expr) {
|
||||
break;
|
||||
}
|
||||
// compare strings lower case
|
||||
stack.push_back((emsesp::Helpers::toLower(lhs) == emsesp::Helpers::toLower(rhs)) ? "1" : "0");
|
||||
stack.push_back((Helpers::toLower(lhs) == Helpers::toLower(rhs)) ? "1" : "0");
|
||||
break;
|
||||
case '!':
|
||||
if (isnum(rhs) && isnum(lhs)) {
|
||||
@@ -581,7 +583,7 @@ std::string calculate(const std::string & expr) {
|
||||
break;
|
||||
}
|
||||
// compare strings lower case
|
||||
stack.push_back((emsesp::Helpers::toLower(lhs) != emsesp::Helpers::toLower(rhs)) ? "1" : "0");
|
||||
stack.push_back((Helpers::toLower(lhs) != Helpers::toLower(rhs)) ? "1" : "0");
|
||||
break;
|
||||
}
|
||||
} break;
|
||||
@@ -690,18 +692,18 @@ std::string compute(const std::string & expr) {
|
||||
std::string url, header_s, value_s, method_s, key_s, keys_s;
|
||||
// search keys lower case
|
||||
for (JsonPair p : doc.as<JsonObject>()) {
|
||||
if (emsesp::Helpers::toLower(p.key().c_str()) == "url") {
|
||||
if (Helpers::toLower(p.key().c_str()) == "url") {
|
||||
url = p.value().as<std::string>();
|
||||
} else if (emsesp::Helpers::toLower(p.key().c_str()) == "header") {
|
||||
} else if (Helpers::toLower(p.key().c_str()) == "header") {
|
||||
header_s = p.key().c_str();
|
||||
} else if (emsesp::Helpers::toLower(p.key().c_str()) == "value") {
|
||||
} else if (Helpers::toLower(p.key().c_str()) == "value") {
|
||||
value_s = p.key().c_str();
|
||||
} else if (emsesp::Helpers::toLower(p.key().c_str()) == "method") {
|
||||
} else if (Helpers::toLower(p.key().c_str()) == "method") {
|
||||
method_s = p.key().c_str();
|
||||
} else if (emsesp::Helpers::toLower(p.key().c_str()) == "key") {
|
||||
} else if (Helpers::toLower(p.key().c_str()) == "key") {
|
||||
keys_s = "";
|
||||
key_s = p.key().c_str();
|
||||
} else if (emsesp::Helpers::toLower(p.key().c_str()) == "keys") {
|
||||
} else if (Helpers::toLower(p.key().c_str()) == "keys") {
|
||||
key_s = "";
|
||||
keys_s = p.key().c_str();
|
||||
}
|
||||
@@ -715,7 +717,7 @@ std::string compute(const std::string & expr) {
|
||||
std::string method = doc[method_s] | "get";
|
||||
|
||||
// if there is data, force a POST
|
||||
if (value.length() || emsesp::Helpers::toLower(method) == "post") {
|
||||
if (value.length() || Helpers::toLower(method) == "post") {
|
||||
if (value.find_first_of('{') != std::string::npos) {
|
||||
http.addHeader("Content-Type", "application/json"); // auto-set to JSON
|
||||
}
|
||||
@@ -805,3 +807,5 @@ std::string compute(const std::string & expr) {
|
||||
|
||||
return calculate(expr_new);
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
@@ -29,6 +29,8 @@
|
||||
#include <deque>
|
||||
#include <math.h>
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
class Token {
|
||||
public:
|
||||
enum class Type : uint8_t {
|
||||
@@ -84,3 +86,5 @@ std::string calculate(const std::string & expr);
|
||||
std::string compute(const std::string & expr);
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace emsesp
|
||||
@@ -19,7 +19,6 @@
|
||||
#include "system.h"
|
||||
#include "emsesp.h" // for send_raw_telegram() command
|
||||
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
#include "esp_ota_ops.h"
|
||||
#endif
|
||||
@@ -87,10 +86,12 @@ static constexpr uint8_t NUM_LANGUAGES = sizeof(languages) / sizeof(const char *
|
||||
uuid::log::Logger System::logger_{F_(system), uuid::log::Facility::KERN};
|
||||
|
||||
// init statics
|
||||
PButton System::myPButton_;
|
||||
bool System::test_set_all_active_ = false;
|
||||
uint32_t System::max_alloc_mem_;
|
||||
uint32_t System::heap_mem_;
|
||||
PButton System::myPButton_;
|
||||
bool System::test_set_all_active_ = false;
|
||||
uint32_t System::max_alloc_mem_;
|
||||
uint32_t System::heap_mem_;
|
||||
std::vector<uint8_t> System::valid_system_gpios_;
|
||||
std::vector<uint8_t> System::used_gpios_;
|
||||
|
||||
// find the index of the language
|
||||
// 0 = EN, 1 = DE, etc...
|
||||
@@ -404,62 +405,48 @@ void System::syslog_init() {
|
||||
#endif
|
||||
}
|
||||
|
||||
// read some specific system settings to store locally for faster access
|
||||
void System::reload_settings() {
|
||||
// set gpios to zero for valid check
|
||||
led_gpio_ = 0;
|
||||
rx_gpio_ = 0;
|
||||
tx_gpio_ = 0;
|
||||
pbutton_gpio_ = 0;
|
||||
dallas_gpio_ = 0;
|
||||
EMSESP::webSettingsService.read([&](WebSettings & settings) {
|
||||
version_ = settings.version;
|
||||
// first check gpios, priority to rx and tx
|
||||
rx_gpio_ = is_valid_gpio(settings.rx_gpio) ? settings.rx_gpio : 0;
|
||||
tx_gpio_ = is_valid_gpio(settings.tx_gpio) ? settings.tx_gpio : 0;
|
||||
pbutton_gpio_ = is_valid_gpio(settings.pbutton_gpio) ? settings.pbutton_gpio : 0;
|
||||
dallas_gpio_ = is_valid_gpio(settings.dallas_gpio) ? settings.dallas_gpio : 0;
|
||||
led_gpio_ = is_valid_gpio(settings.led_gpio) ? settings.led_gpio : 0;
|
||||
// read specific major system settings to store locally for faster access
|
||||
void System::store_settings(WebSettings & settings) {
|
||||
version_ = settings.version;
|
||||
|
||||
analog_enabled_ = settings.analog_enabled;
|
||||
low_clock_ = settings.low_clock;
|
||||
hide_led_ = settings.hide_led;
|
||||
led_type_ = settings.led_type;
|
||||
board_profile_ = settings.board_profile;
|
||||
telnet_enabled_ = settings.telnet_enabled;
|
||||
rx_gpio_ = settings.rx_gpio;
|
||||
tx_gpio_ = settings.tx_gpio;
|
||||
pbutton_gpio_ = settings.pbutton_gpio;
|
||||
dallas_gpio_ = settings.dallas_gpio;
|
||||
led_gpio_ = settings.led_gpio;
|
||||
|
||||
modbus_enabled_ = settings.modbus_enabled;
|
||||
modbus_port_ = settings.modbus_port;
|
||||
modbus_max_clients_ = settings.modbus_max_clients;
|
||||
modbus_timeout_ = settings.modbus_timeout;
|
||||
analog_enabled_ = settings.analog_enabled;
|
||||
low_clock_ = settings.low_clock;
|
||||
hide_led_ = settings.hide_led;
|
||||
led_type_ = settings.led_type;
|
||||
board_profile_ = settings.board_profile;
|
||||
telnet_enabled_ = settings.telnet_enabled;
|
||||
|
||||
tx_mode_ = settings.tx_mode;
|
||||
syslog_enabled_ = settings.syslog_enabled;
|
||||
syslog_level_ = settings.syslog_level;
|
||||
syslog_mark_interval_ = settings.syslog_mark_interval;
|
||||
syslog_host_ = settings.syslog_host;
|
||||
syslog_port_ = settings.syslog_port;
|
||||
modbus_enabled_ = settings.modbus_enabled;
|
||||
modbus_port_ = settings.modbus_port;
|
||||
modbus_max_clients_ = settings.modbus_max_clients;
|
||||
modbus_timeout_ = settings.modbus_timeout;
|
||||
|
||||
fahrenheit_ = settings.fahrenheit;
|
||||
bool_format_ = settings.bool_format;
|
||||
bool_dashboard_ = settings.bool_dashboard;
|
||||
enum_format_ = settings.enum_format;
|
||||
readonly_mode_ = settings.readonly_mode;
|
||||
tx_mode_ = settings.tx_mode;
|
||||
syslog_enabled_ = settings.syslog_enabled;
|
||||
syslog_level_ = settings.syslog_level;
|
||||
syslog_mark_interval_ = settings.syslog_mark_interval;
|
||||
syslog_host_ = settings.syslog_host;
|
||||
syslog_port_ = settings.syslog_port;
|
||||
|
||||
phy_type_ = settings.phy_type;
|
||||
eth_power_ = settings.eth_power;
|
||||
eth_phy_addr_ = settings.eth_phy_addr;
|
||||
eth_clock_mode_ = settings.eth_clock_mode;
|
||||
fahrenheit_ = settings.fahrenheit;
|
||||
bool_format_ = settings.bool_format;
|
||||
bool_dashboard_ = settings.bool_dashboard;
|
||||
enum_format_ = settings.enum_format;
|
||||
readonly_mode_ = settings.readonly_mode;
|
||||
|
||||
locale_ = settings.locale;
|
||||
developer_mode_ = settings.developer_mode;
|
||||
});
|
||||
}
|
||||
phy_type_ = settings.phy_type;
|
||||
eth_power_ = settings.eth_power;
|
||||
eth_phy_addr_ = settings.eth_phy_addr;
|
||||
eth_clock_mode_ = settings.eth_clock_mode;
|
||||
|
||||
// check for valid ESP32 pins
|
||||
bool System::is_valid_gpio(uint8_t pin) {
|
||||
auto valid_gpios = valid_gpio_list();
|
||||
return std::find(valid_gpios.begin(), valid_gpios.end(), pin) != valid_gpios.end();
|
||||
locale_ = settings.locale;
|
||||
developer_mode_ = settings.developer_mode;
|
||||
}
|
||||
|
||||
// Starts up the UART Serial bridge
|
||||
@@ -501,12 +488,12 @@ void System::start() {
|
||||
hostname(networkSettings.hostname.c_str()); // sets the hostname
|
||||
});
|
||||
|
||||
commands_init(); // console & api commands
|
||||
led_init(false); // init LED
|
||||
button_init(false); // the special button
|
||||
network_init(false); // network
|
||||
uart_init(false); // start UART
|
||||
syslog_init(); // start syslog
|
||||
commands_init(); // console & api commands
|
||||
led_init(); // init LED
|
||||
button_init(); // button
|
||||
network_init(); // network
|
||||
uart_init(); // start UART
|
||||
syslog_init(); // start syslog
|
||||
}
|
||||
|
||||
// button single click
|
||||
@@ -552,11 +539,7 @@ void System::button_OnVLongPress(PButton & b) {
|
||||
}
|
||||
|
||||
// push button
|
||||
void System::button_init(bool refresh) {
|
||||
if (refresh) {
|
||||
reload_settings();
|
||||
}
|
||||
|
||||
void System::button_init() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
if (!myPButton_.init(pbutton_gpio_, HIGH)) {
|
||||
LOG_WARNING("Multi-functional button not detected");
|
||||
@@ -572,18 +555,13 @@ void System::button_init(bool refresh) {
|
||||
}
|
||||
|
||||
// set the LED to on or off when in normal operating mode
|
||||
void System::led_init(bool refresh) {
|
||||
if (refresh) {
|
||||
// disabled old led port before setting new one
|
||||
if (led_gpio_) {
|
||||
void System::led_init() {
|
||||
// disabled old led port before setting new one
|
||||
#if ESP_ARDUINO_VERSION_MAJOR < 3
|
||||
led_type_ ? neopixelWrite(led_gpio_, 0, 0, 0) : digitalWrite(led_gpio_, !LED_ON);
|
||||
led_type_ ? neopixelWrite(led_gpio_, 0, 0, 0) : digitalWrite(led_gpio_, !LED_ON);
|
||||
#else
|
||||
led_type_ ? rgbLedWrite(led_gpio_, 0, 0, 0) : digitalWrite(led_gpio_, !LED_ON);
|
||||
led_type_ ? rgbLedWrite(led_gpio_, 0, 0, 0) : digitalWrite(led_gpio_, !LED_ON);
|
||||
#endif
|
||||
}
|
||||
reload_settings();
|
||||
}
|
||||
|
||||
if ((led_gpio_)) { // 0 means disabled
|
||||
if (led_type_) {
|
||||
@@ -602,18 +580,11 @@ void System::led_init(bool refresh) {
|
||||
}
|
||||
}
|
||||
|
||||
void System::uart_init(bool refresh) {
|
||||
if (refresh) {
|
||||
reload_settings();
|
||||
}
|
||||
void System::uart_init() {
|
||||
EMSuart::stop();
|
||||
|
||||
// don't start UART if we have invalid GPIOs
|
||||
if (rx_gpio_ && tx_gpio_) {
|
||||
EMSuart::start(tx_mode_, rx_gpio_, tx_gpio_); // start UART
|
||||
} else {
|
||||
LOG_WARNING("Invalid UART Rx/Tx GPIOs. Check config.");
|
||||
}
|
||||
// start UART, GPIOs have already been checked
|
||||
EMSuart::start(tx_mode_, rx_gpio_, tx_gpio_);
|
||||
|
||||
EMSESP::txservice_.start(); // reset counters and send devices request
|
||||
}
|
||||
@@ -775,11 +746,7 @@ void System::send_heartbeat() {
|
||||
}
|
||||
|
||||
// initializes network
|
||||
void System::network_init(bool refresh) {
|
||||
if (refresh) {
|
||||
reload_settings();
|
||||
}
|
||||
|
||||
void System::network_init() {
|
||||
last_system_check_ = 0; // force the LED to go from fast flash to pulse
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
@@ -1103,7 +1070,7 @@ void System::show_system(uuid::console::Shell & shell) {
|
||||
shell.printfln(" BSSID: %s", WiFi.BSSIDstr().c_str());
|
||||
shell.printfln(" RSSI: %d dBm (%d %%)", WiFi.RSSI(), wifi_quality(WiFi.RSSI()));
|
||||
char result[10];
|
||||
shell.printfln(" TxPower: %s dBm", emsesp::Helpers::render_value(result, (double)(WiFi.getTxPower() / 4), 1));
|
||||
shell.printfln(" TxPower: %s dBm", Helpers::render_value(result, (double)(WiFi.getTxPower() / 4), 1));
|
||||
shell.printfln(" MAC address: %s", WiFi.macAddress().c_str());
|
||||
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());
|
||||
@@ -1263,7 +1230,7 @@ bool System::check_upgrade(bool factory_settings) {
|
||||
version::Semver200_version settings_version(settingsVersion);
|
||||
|
||||
if (!missing_version) {
|
||||
LOG_DEBUG("Checking for version upgrades (settings file has v%d.%d.%d-%s)",
|
||||
LOG_DEBUG("Checking for version upgrades (settings file is v%d.%d.%d-%s)",
|
||||
settings_version.major(),
|
||||
settings_version.minor(),
|
||||
settings_version.patch(),
|
||||
@@ -1933,15 +1900,12 @@ bool System::command_test(const char * value, const int8_t id) {
|
||||
// takes a board profile and populates a data array with GPIO configurations
|
||||
// returns false if profile is unknown
|
||||
//
|
||||
// data = led, dallas, rx, tx, button, phy_type, eth_power, eth_phy_addr, eth_clock_mode, led_type
|
||||
// 0=led, 1=dallas, 2=rx, 3=tx, 4=button, 5=phy_type, 6=eth_power, 7=eth_phy_addr, 8=eth_clock_mode, 9=led_type
|
||||
//
|
||||
// clock modes:
|
||||
// 0 = RMII clock input to GPIO0
|
||||
// 1 = RMII clock output from GPIO0
|
||||
// 2 = RMII clock output from GPIO16
|
||||
// 3 = RMII clock output from GPIO17, for 50hz inverted clock
|
||||
bool System::load_board_profile(std::vector<int8_t> & data, const std::string & board_profile) {
|
||||
if (board_profile == "S32") {
|
||||
if (board_profile == EMSESP_DEFAULT_BOARD_PROFILE) {
|
||||
return false; // unknown, return false
|
||||
} else if (board_profile == "S32") {
|
||||
data = {2, 18, 23, 5, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0, 0}; // BBQKees Gateway S32
|
||||
} else if (board_profile == "E32") {
|
||||
data = {2, 4, 5, 17, 33, PHY_type::PHY_TYPE_LAN8720, 16, 1, 0, 0}; // BBQKees Gateway E32
|
||||
@@ -1971,24 +1935,10 @@ bool System::load_board_profile(std::vector<int8_t> & data, const std::string &
|
||||
data = {17, 18, 8, 5, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0, 0}; // Liligo S3
|
||||
} else if (board_profile == "S32S3") {
|
||||
data = {2, 18, 5, 17, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0, 0}; // BBQKees Gateway S3
|
||||
} else if (board_profile == "CUSTOM") {
|
||||
// send back current values
|
||||
data = {(int8_t)EMSESP::system_.led_gpio_,
|
||||
(int8_t)EMSESP::system_.dallas_gpio_,
|
||||
(int8_t)EMSESP::system_.rx_gpio_,
|
||||
(int8_t)EMSESP::system_.tx_gpio_,
|
||||
(int8_t)EMSESP::system_.pbutton_gpio_,
|
||||
(int8_t)EMSESP::system_.phy_type_,
|
||||
EMSESP::system_.eth_power_,
|
||||
(int8_t)EMSESP::system_.eth_phy_addr_,
|
||||
(int8_t)EMSESP::system_.eth_clock_mode_,
|
||||
(int8_t)EMSESP::system_.led_type_};
|
||||
} else {
|
||||
LOG_DEBUG("Couldn't identify board profile %s", board_profile.c_str());
|
||||
return false; // unknown, return false
|
||||
}
|
||||
|
||||
// LOG_DEBUG("Found data for board profile %s", board_profile.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2218,7 +2168,6 @@ bool System::uploadFirmwareURL(const char * url) {
|
||||
|
||||
// we're about to start the upload, set the status so the Web System Monitor spots it
|
||||
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_UPLOADING);
|
||||
// TODO do we need to stop the UART first with EMSuart::stop() ?
|
||||
|
||||
// set a callback so we can monitor progress in the WebUI
|
||||
Update.onProgress([](size_t progress, size_t total) { EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_UPLOADING + (progress * 100 / total)); });
|
||||
@@ -2313,7 +2262,7 @@ bool System::command_read(const char * value, const int8_t id) {
|
||||
// set the system status code - SYSTEM_STATUS in system.h
|
||||
void System::systemStatus(uint8_t status_code) {
|
||||
systemStatus_ = status_code;
|
||||
// LOG_DEBUG("Setting System status code %d", status_code);
|
||||
LOG_DEBUG("Setting System status code %d", status_code);
|
||||
}
|
||||
|
||||
uint8_t System::systemStatus() {
|
||||
@@ -2322,11 +2271,11 @@ uint8_t System::systemStatus() {
|
||||
|
||||
// takes a string range like "6-11, 1, 23, 24-48" which has optional ranges and single values and converts to a vector of ints
|
||||
std::vector<uint8_t> System::string_range_to_vector(const std::string & range) {
|
||||
std::vector<uint8_t> valid_gpios;
|
||||
std::vector<uint8_t> gpios;
|
||||
std::string::size_type pos = 0;
|
||||
std::string::size_type prev = 0;
|
||||
|
||||
auto process_part = [&valid_gpios](std::string part) {
|
||||
auto process_part = [&gpios](std::string part) {
|
||||
// trim whitespace
|
||||
part.erase(0, part.find_first_not_of(" \t"));
|
||||
part.erase(part.find_last_not_of(" \t") + 1);
|
||||
@@ -2338,10 +2287,10 @@ std::vector<uint8_t> System::string_range_to_vector(const std::string & range) {
|
||||
int start = std::stoi(part.substr(0, dash_pos));
|
||||
int end = std::stoi(part.substr(dash_pos + 1));
|
||||
for (int i = start; i <= end; i++) {
|
||||
valid_gpios.push_back(static_cast<uint8_t>(i));
|
||||
gpios.push_back(static_cast<uint8_t>(i));
|
||||
}
|
||||
} else {
|
||||
valid_gpios.push_back(static_cast<uint8_t>(std::stoi(part)));
|
||||
gpios.push_back(static_cast<uint8_t>(std::stoi(part)));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2353,66 +2302,92 @@ std::vector<uint8_t> System::string_range_to_vector(const std::string & range) {
|
||||
// handle the last part
|
||||
process_part(range.substr(prev));
|
||||
|
||||
return valid_gpios;
|
||||
return gpios;
|
||||
}
|
||||
|
||||
// return a list of valid GPIOs for the ESP32 board
|
||||
// initialize a list of valid GPIOs based on the ESP32 board
|
||||
// notes:
|
||||
// - we allow 0, which is used sometimes to indicate a disabled pin
|
||||
// - also allow input only pins are accepted (34-39) on some boards
|
||||
// - we always allow 0, which is used to indicate Dallas or LED is disabled
|
||||
// - GPIO 1 and 3 are disabled for Serial (UART0's TX0, RX0)
|
||||
// - we also allow input only pins are accepted (34-39) on some boards
|
||||
// - and allow pins 33-38 for octal SPI for 32M vchip version on some boards
|
||||
std::vector<uint8_t> System::valid_gpio_list() {
|
||||
void System::set_valid_system_gpios() {
|
||||
valid_system_gpios_.clear(); // reset system list
|
||||
used_gpios_.clear(); // reset used list
|
||||
|
||||
// get free gpios based on board/platform type
|
||||
#if CONFIG_IDF_TARGET_ESP32C3
|
||||
// https://www.wemos.cc/en/latest/c3/c3_mini.html
|
||||
std::vector<uint8_t> valid_gpios = string_range_to_vector("0-10");
|
||||
valid_system_gpios_ = string_range_to_vector("0-10");
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
std::vector<uint8_t> valid_gpios = string_range_to_vector("0-14, 19, 20, 21, 33-38, 45, 46");
|
||||
valid_system_gpios_ = string_range_to_vector("0, 2, 4-14, 19, 20, 21, 33-38, 45, 46");
|
||||
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||
std::vector<uint8_t> valid_gpios = string_range_to_vector("2, 4-14, 17, 18, 21, 33-38, 45, 46");
|
||||
valid_system_gpios_ = string_range_to_vector("0, 2, 4-14, 17, 18, 21, 33-38, 45, 46");
|
||||
#elif CONFIG_IDF_TARGET_ESP32 || defined(EMSESP_STANDALONE)
|
||||
std::vector<uint8_t> valid_gpios = string_range_to_vector("0, 2, 4, 5, 12-15, 18, 19, 23, 25-27, 32-39");
|
||||
valid_system_gpios_ = string_range_to_vector("0, 2, 4, 5, 12-19, 23, 25-27, 32-39");
|
||||
#else
|
||||
std::vector<uint8_t> valid_gpios = {};
|
||||
#endif
|
||||
|
||||
// if psram is enabled remove pins 16 and 17 from the list, if set
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
// if psram is enabled remove pins 16 and 17 from the list
|
||||
if (ESP.getPsramSize() > 0) {
|
||||
valid_gpios.erase(std::remove(valid_gpios.begin(), valid_gpios.end(), 16), valid_gpios.end());
|
||||
valid_gpios.erase(std::remove(valid_gpios.begin(), valid_gpios.end(), 17), valid_gpios.end());
|
||||
valid_system_gpios_.erase(std::remove(valid_system_gpios_.begin(), valid_system_gpios_.end(), 16), valid_system_gpios_.end());
|
||||
valid_system_gpios_.erase(std::remove(valid_system_gpios_.begin(), valid_system_gpios_.end(), 17), valid_system_gpios_.end());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// if ethernet is enabled, remove pins 21 and 22 (I2C)
|
||||
if ((EMSESP::system_.ethernet_connected() || EMSESP::system_.phy_type_ != PHY_type::PHY_TYPE_NONE)) {
|
||||
valid_gpios.erase(std::remove(valid_gpios.begin(), valid_gpios.end(), 21), valid_gpios.end());
|
||||
valid_gpios.erase(std::remove(valid_gpios.begin(), valid_gpios.end(), 22), valid_gpios.end());
|
||||
// check if a pin is valid ESP32 pin and if not already used, add to the used gpio list
|
||||
// return false if not allowed or already used
|
||||
bool System::add_gpio(uint8_t pin, const char * source_name) {
|
||||
// check if this is a valid user GPIO
|
||||
if (std::find(valid_system_gpios_.begin(), valid_system_gpios_.end(), pin) != valid_system_gpios_.end()) {
|
||||
// It's valid now check if it's already in the used list
|
||||
if (std::find(used_gpios_.begin(), used_gpios_.end(), pin) != used_gpios_.end()) {
|
||||
LOG_WARNING("GPIO %d for %s is already in use", pin, source_name);
|
||||
return false; // Pin is already used
|
||||
}
|
||||
} else {
|
||||
// not valid
|
||||
LOG_WARNING("GPIO %d for %s is not valid", pin, source_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
// filter out GPIOs already used in application settings, gpio 0 means disabled, except for pbutton
|
||||
for (const auto & gpio : valid_gpios) {
|
||||
if (gpio == EMSESP::system_.pbutton_gpio_
|
||||
|| (gpio
|
||||
&& (gpio == EMSESP::system_.led_gpio_ || gpio == EMSESP::system_.dallas_gpio_ || gpio == EMSESP::system_.rx_gpio_
|
||||
|| gpio == EMSESP::system_.tx_gpio_))) {
|
||||
valid_gpios.erase(std::remove(valid_gpios.begin(), valid_gpios.end(), gpio), valid_gpios.end());
|
||||
// remove the old pin, if exists from used list
|
||||
remove_gpio(pin);
|
||||
|
||||
LOG_DEBUG("Adding GPIO %d for %s to used gpio list", pin, source_name);
|
||||
used_gpios_.push_back(pin); // add to used list
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// remove a gpio from both valid and used lists
|
||||
void System::remove_gpio(uint8_t pin, bool also_system) {
|
||||
auto it = std::find(used_gpios_.begin(), used_gpios_.end(), pin);
|
||||
if (it != used_gpios_.end()) {
|
||||
LOG_DEBUG("GPIO %d removed from used gpio list", pin);
|
||||
used_gpios_.erase(it);
|
||||
}
|
||||
|
||||
if (also_system) {
|
||||
it = std::find(valid_system_gpios_.begin(), valid_system_gpios_.end(), pin);
|
||||
if (it != valid_system_gpios_.end()) {
|
||||
LOG_DEBUG("GPIO %d removed from valid gpio list", pin);
|
||||
valid_system_gpios_.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// filter out GPIOs already used in analog sensors, if enabled
|
||||
if (EMSESP::system_.analog_enabled_) {
|
||||
for (const auto & sensor : EMSESP::analogsensor_.sensors()) {
|
||||
if (std::find(valid_gpios.begin(), valid_gpios.end(), sensor.gpio()) != valid_gpios.end()) {
|
||||
valid_gpios.erase(std::find(valid_gpios.begin(), valid_gpios.end(), sensor.gpio()));
|
||||
}
|
||||
// return a list of GPIO's available for use
|
||||
std::vector<uint8_t> System::available_gpios() {
|
||||
std::vector<uint8_t> gpios;
|
||||
for (const auto & gpio : valid_system_gpios_) {
|
||||
if (std::find(used_gpios_.begin(), used_gpios_.end(), gpio) == used_gpios_.end()) {
|
||||
gpios.push_back(gpio); // didn't find it in used_gpios_, so it's available
|
||||
}
|
||||
}
|
||||
|
||||
// sort the list of valid GPIOs
|
||||
std::sort(valid_gpios.begin(), valid_gpios.end());
|
||||
|
||||
return valid_gpios;
|
||||
return gpios;
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -67,7 +67,8 @@ enum SYSTEM_STATUS : uint8_t {
|
||||
SYSTEM_STATUS_UPLOADING = 100,
|
||||
SYSTEM_STATUS_ERROR_UPLOAD = 3,
|
||||
SYSTEM_STATUS_PENDING_RESTART = 4,
|
||||
SYSTEM_STATUS_RESTART_REQUESTED = 5
|
||||
SYSTEM_STATUS_RESTART_REQUESTED = 5,
|
||||
SYSTEM_STATUS_INVALID_GPIO = 6
|
||||
};
|
||||
|
||||
enum FUSE_VALUE : uint8_t { ALL = 0, MFG = 1, MODEL = 2, BOARD = 3, REV = 4, BATCH = 5, FUSE = 6 };
|
||||
@@ -103,11 +104,12 @@ class System {
|
||||
void system_restart(const char * partition = nullptr);
|
||||
|
||||
void show_mem(const char * note);
|
||||
void reload_settings();
|
||||
void store_settings(class WebSettings & settings);
|
||||
void syslog_init();
|
||||
bool check_upgrade(bool factory_settings);
|
||||
bool check_restore();
|
||||
void heartbeat_json(JsonObject output);
|
||||
|
||||
void send_heartbeat();
|
||||
void send_info_mqtt();
|
||||
|
||||
@@ -129,11 +131,11 @@ class System {
|
||||
|
||||
static bool uploadFirmwareURL(const char * url = nullptr);
|
||||
|
||||
void led_init(bool refresh);
|
||||
void network_init(bool refresh);
|
||||
void button_init(bool refresh);
|
||||
void led_init();
|
||||
void network_init();
|
||||
void button_init();
|
||||
void commands_init();
|
||||
void uart_init(bool refresh);
|
||||
void uart_init();
|
||||
|
||||
void systemStatus(uint8_t status_code);
|
||||
uint8_t systemStatus();
|
||||
@@ -141,8 +143,9 @@ class System {
|
||||
static void extractSettings(const char * filename, const char * section, JsonObject output);
|
||||
static bool saveSettings(const char * filename, const char * section, JsonObject input);
|
||||
|
||||
bool is_valid_gpio(uint8_t pin);
|
||||
static bool load_board_profile(std::vector<int8_t> & data, const std::string & board_profile);
|
||||
static bool add_gpio(uint8_t pin, const char * source_name);
|
||||
static std::vector<uint8_t> available_gpios();
|
||||
static bool load_board_profile(std::vector<int8_t> & data, const std::string & board_profile);
|
||||
|
||||
static bool readCommand(const char * data);
|
||||
|
||||
@@ -342,7 +345,7 @@ class System {
|
||||
test_set_all_active_ = n;
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> valid_gpio_list();
|
||||
static void set_valid_system_gpios();
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
|
||||
float temperature() {
|
||||
@@ -350,6 +353,8 @@ class System {
|
||||
}
|
||||
#endif
|
||||
|
||||
static void remove_gpio(uint8_t pin, bool also_system = false); // remove a gpio from both valid (optional) and used lists
|
||||
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
@@ -391,6 +396,9 @@ class System {
|
||||
|
||||
static std::vector<uint8_t> string_range_to_vector(const std::string & range);
|
||||
|
||||
static std::vector<uint8_t> valid_system_gpios_; // list of valid GPIOs for the ESP32 board that can be used
|
||||
static std::vector<uint8_t> used_gpios_; // list of GPIOs used by the application
|
||||
|
||||
int8_t wifi_quality(int8_t dBm);
|
||||
|
||||
uint8_t healthcheck_ = HEALTHCHECK_NO_NETWORK | HEALTHCHECK_NO_BUS; // start with all flags set, no wifi and no ems bus connection
|
||||
@@ -406,7 +414,6 @@ class System {
|
||||
bool eth_present_ = false;
|
||||
|
||||
// EMS-ESP settings
|
||||
// copies from WebSettings class in WebSettingsService.h and loaded with reload_settings()
|
||||
std::string hostname_;
|
||||
String locale_;
|
||||
bool hide_led_;
|
||||
|
||||
@@ -274,7 +274,7 @@ void TxService::send_poll() const {
|
||||
}
|
||||
}
|
||||
|
||||
// get src id from next telegram to check poll in emsesp::incoming_telegram
|
||||
// get src id from next telegram to check poll in incoming_telegram() in emsesp.cpp
|
||||
uint8_t TxService::get_send_id() {
|
||||
static uint32_t count = 0;
|
||||
if (!tx_telegrams_.empty() && tx_telegrams_.front().telegram_->src != ems_bus_id()) {
|
||||
|
||||
@@ -57,7 +57,7 @@ void TemperatureSensor::reload() {
|
||||
// load the service settings
|
||||
EMSESP::system_.dallas_gpio(0); // reset in system to check valid sensor
|
||||
EMSESP::webSettingsService.read([&](WebSettings const & settings) {
|
||||
dallas_gpio_ = EMSESP::system_.is_valid_gpio(settings.dallas_gpio) ? settings.dallas_gpio : 0;
|
||||
dallas_gpio_ = settings.dallas_gpio;
|
||||
parasite_ = settings.dallas_parasite;
|
||||
});
|
||||
EMSESP::system_.dallas_gpio(dallas_gpio_); // set to system for checks
|
||||
|
||||
@@ -115,9 +115,10 @@ class TemperatureSensor {
|
||||
}
|
||||
|
||||
size_t count_entities(bool exclude_disabled_system = false) const {
|
||||
return std::count_if(sensors_.begin(), sensors_.end(), [exclude_disabled_system](const Sensor & sensor) {
|
||||
return exclude_disabled_system ? !sensor.is_system() : sensor.is_system();
|
||||
});
|
||||
if (exclude_disabled_system) {
|
||||
return std::count_if(sensors_.begin(), sensors_.end(), [](const Sensor & sensor) { return !sensor.is_system(); });
|
||||
}
|
||||
return sensors_.size();
|
||||
}
|
||||
|
||||
bool update(const std::string & id, const std::string & name, int16_t offset, bool is_system);
|
||||
|
||||
@@ -1 +1 @@
|
||||
#define EMSESP_APP_VERSION "3.7.3-dev.30"
|
||||
#define EMSESP_APP_VERSION "3.7.3-dev.31"
|
||||
|
||||
@@ -1732,7 +1732,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
|
||||
request.url("/api");
|
||||
EMSESP::webAPIService.webAPIService(&request, json);
|
||||
|
||||
emsesp::EMSESP::logger().warning("* these next ones should fail *");
|
||||
EMSESP::logger().warning("* these next ones should fail *");
|
||||
|
||||
// write value from web - testing hc9/seltemp - should fail!
|
||||
char data8[] = "{\"id\":2,\"devicevalue\":{\"v\":\"55\",\"u\":1,\"n\":\"hc2 selected room temperature\",\"c\":\"hc9/seltemp\"}";
|
||||
|
||||
@@ -105,7 +105,7 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) {
|
||||
}
|
||||
|
||||
// capture current heap memory before allocating the large return buffer
|
||||
emsesp::EMSESP::system_.refreshHeapMem();
|
||||
EMSESP::system_.refreshHeapMem();
|
||||
|
||||
// output json buffer
|
||||
auto response = new AsyncJsonResponse();
|
||||
@@ -152,19 +152,17 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) {
|
||||
// 401 (unauthorized)
|
||||
// 400 (invalid)
|
||||
int ret_codes[7] = {400, 200, 404, 400, 401, 400, 404};
|
||||
response->setCode(ret_codes[return_code]);
|
||||
response->setLength();
|
||||
response->setContentType("application/json; charset=utf-8");
|
||||
request->send(response);
|
||||
api_count_++;
|
||||
|
||||
// serialize JSON to string to ensure correct content-length and avoid HTTP parsing errors (issue #2752)
|
||||
std::string output_str;
|
||||
serializeJson(output, output_str);
|
||||
request->send(ret_codes[return_code], "application/json; charset=utf-8", output_str.c_str());
|
||||
|
||||
#if defined(EMSESP_UNITY)
|
||||
// store the result so we can test with Unity later
|
||||
storeResponse(output);
|
||||
#endif
|
||||
#if defined(EMSESP_STANDALONE) && !defined(EMSESP_UNITY)
|
||||
std::string output_str;
|
||||
serializeJson(output, output_str);
|
||||
Serial.printf("%sweb output: %s[%s] %s(%d)%s %s%s",
|
||||
COLOR_WHITE,
|
||||
COLOR_BRIGHT_CYAN,
|
||||
@@ -177,6 +175,9 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) {
|
||||
Serial.println();
|
||||
EMSESP::logger().debug("web output: %s %s", request->url().c_str(), output_str.c_str());
|
||||
#endif
|
||||
|
||||
api_count_++;
|
||||
delete response;
|
||||
}
|
||||
|
||||
#if defined(EMSESP_UNITY)
|
||||
|
||||
@@ -43,7 +43,7 @@ void WebCustomEntityService::begin() {
|
||||
}
|
||||
|
||||
// this creates the entity file, saving it to the FS
|
||||
// and also calls when the Entity web page is refreshed
|
||||
// and also calls when the Custom Entity web page is refreshed
|
||||
void WebCustomEntity::read(WebCustomEntity & webEntity, JsonObject root) {
|
||||
JsonArray entity = root["entities"].to<JsonArray>();
|
||||
uint8_t counter = 0;
|
||||
|
||||
@@ -117,6 +117,13 @@ StateUpdateResult WebCustomization::update(JsonObject root, WebCustomization & c
|
||||
auto analogJsons = root["as"].as<JsonArray>();
|
||||
for (const JsonObject analogJson : analogJsons) {
|
||||
// create each of the sensor, overwriting any previous settings
|
||||
// if the gpio is invalid skip the sensor
|
||||
if (!EMSESP::system_.add_gpio(analogJson["gpio"].as<uint8_t>(), "Analog Sensor")) {
|
||||
EMSESP::logger().warning("Analog sensor: Invalid GPIO %d for %s. Skipping.",
|
||||
analogJson["gpio"].as<uint8_t>(),
|
||||
analogJson["name"].as<std::string>().c_str());
|
||||
continue;
|
||||
}
|
||||
auto analog = AnalogCustomization();
|
||||
analog.gpio = analogJson["gpio"];
|
||||
analog.name = analogJson["name"].as<std::string>();
|
||||
@@ -169,8 +176,7 @@ void WebCustomizationService::reset_customization(AsyncWebServerRequest * reques
|
||||
|
||||
AsyncWebServerResponse * response = request->beginResponse(200);
|
||||
request->send(response);
|
||||
emsesp::EMSESP::system_.systemStatus(
|
||||
emsesp::SYSTEM_STATUS::SYSTEM_STATUS_PENDING_RESTART); // will be handled by the main loop. We use pending for the Web's SystemMonitor
|
||||
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_PENDING_RESTART); // will be handled by the main loop. We use pending for the Web's SystemMonitor
|
||||
return;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -145,21 +145,17 @@ void WebDataService::sensor_data(AsyncWebServerRequest * request) {
|
||||
obj["f"] = sensor.factor();
|
||||
obj["t"] = sensor.type();
|
||||
obj["s"] = sensor.is_system();
|
||||
|
||||
if (sensor.type() != AnalogSensor::AnalogType::NOTUSED && sensor.gpio() != 99) {
|
||||
obj["v"] = Helpers::transformNumFloat(sensor.value()); // is optional and is a float
|
||||
} else {
|
||||
obj["v"] = 0; // must have a value for web sorting to work
|
||||
}
|
||||
obj["v"] = Helpers::transformNumFloat(sensor.value()); // is optional and is a float
|
||||
}
|
||||
}
|
||||
|
||||
root["analog_enabled"] = EMSESP::analog_enabled();
|
||||
root["platform"] = EMSESP_PLATFORM;
|
||||
|
||||
JsonArray valid_gpio_list = root["valid_gpio_list"].to<JsonArray>();
|
||||
for (const auto & gpio : EMSESP::system_.valid_gpio_list()) {
|
||||
valid_gpio_list.add(gpio);
|
||||
// send back a list of valid and unused GPIOs still available for use
|
||||
JsonArray available_gpios = root["available_gpios"].to<JsonArray>();
|
||||
for (const auto & gpio : EMSESP::system_.available_gpios()) {
|
||||
available_gpios.add(gpio);
|
||||
}
|
||||
|
||||
response->setLength();
|
||||
@@ -186,7 +182,7 @@ void WebDataService::device_data(AsyncWebServerRequest * request) {
|
||||
for (const auto & emsdevice : EMSESP::emsdevices) {
|
||||
if (emsdevice->unique_id() == id) {
|
||||
// wait max 2.5 sec for updated data (post_send_delay is 2 sec)
|
||||
for (uint16_t i = 0; i < (emsesp::TxService::POST_SEND_DELAY + 500) && EMSESP::wait_validate(); i++) {
|
||||
for (uint16_t i = 0; i < (TxService::POST_SEND_DELAY + 500) && EMSESP::wait_validate(); i++) {
|
||||
delay(1);
|
||||
}
|
||||
EMSESP::wait_validate(0); // reset in case of timeout
|
||||
@@ -436,7 +432,7 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) {
|
||||
uint8_t count = 0;
|
||||
for (const auto & sensor : EMSESP::analogsensor_.sensors()) {
|
||||
// ignore system and disabled sensors
|
||||
if (sensor.is_system() || sensor.type() == AnalogSensor::AnalogType::NOTUSED || sensor.gpio() == 99) {
|
||||
if (sensor.is_system()) {
|
||||
continue;
|
||||
}
|
||||
JsonObject node = nodes.add<JsonObject>();
|
||||
|
||||
@@ -371,13 +371,13 @@ bool WebSchedulerService::command(const char * name, const std::string & command
|
||||
if (httpResult != 200) {
|
||||
char error[100];
|
||||
snprintf(error, sizeof(error), "Schedule %s: URL command failed with http code %d", name, httpResult);
|
||||
emsesp::EMSESP::logger().warning(error);
|
||||
EMSESP::logger().warning(error);
|
||||
return false;
|
||||
}
|
||||
#if defined(EMSESP_DEBUG)
|
||||
char msg[100];
|
||||
snprintf(msg, sizeof(msg), "Schedule %s: URL command successful with http code %d", name, httpResult);
|
||||
emsesp::EMSESP::logger().debug(msg);
|
||||
EMSESP::logger().debug(msg);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
@@ -416,7 +416,7 @@ bool WebSchedulerService::command(const char * name, const std::string & command
|
||||
snprintf(error, sizeof(error), "Schedule %s: command %s failed with error %s", name, cmd.c_str(), Command::return_code_string(return_code));
|
||||
}
|
||||
|
||||
emsesp::EMSESP::logger().warning(error);
|
||||
EMSESP::logger().warning(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -439,14 +439,14 @@ void WebSchedulerService::condition() {
|
||||
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_CONDITION) {
|
||||
auto match = compute(scheduleItem.time);
|
||||
#ifdef EMESESP_DEBUG
|
||||
// emsesp::EMSESP::logger().debug("condition match: %s", match.c_str());
|
||||
// EMSESP::logger().debug("condition match: %s", match.c_str());
|
||||
#endif
|
||||
if (match.length() == 1 && match[0] == '1' && scheduleItem.retry_cnt == 0xFF) {
|
||||
scheduleItem.retry_cnt = command(scheduleItem.name.c_str(), scheduleItem.cmd, compute(scheduleItem.value)) ? 1 : 0xFF;
|
||||
} else if (match.length() == 1 && match[0] == '0' && scheduleItem.retry_cnt == 1) {
|
||||
scheduleItem.retry_cnt = 0xFF;
|
||||
} else if (match.length() != 1) { // the match is not boolean
|
||||
emsesp::EMSESP::logger().debug("condition result: %s", match.c_str());
|
||||
EMSESP::logger().debug("condition result: %s", match.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,25 +85,322 @@ void WebSettings::read(WebSettings & settings, JsonObject root) {
|
||||
root["developer_mode"] = settings.developer_mode;
|
||||
}
|
||||
|
||||
// call on initialization and also when settings are updated via web or console
|
||||
// call on initialization and also when settings are updated/saved via web or console
|
||||
// note, settings is empty when the service starts
|
||||
StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) {
|
||||
// load the version from the settings config. This can be blank and later used in System::check_upgrade()
|
||||
settings.version = root["version"] | EMSESP_DEFAULT_VERSION;
|
||||
// make a copy of the settings to compare to later
|
||||
const WebSettings original_settings(settings);
|
||||
|
||||
reset_flags();
|
||||
|
||||
settings.version = root["version"] | EMSESP_DEFAULT_VERSION; // save the version, we use it later in System::check_upgrade()
|
||||
settings.board_profile = root["board_profile"] | EMSESP_DEFAULT_BOARD_PROFILE;
|
||||
|
||||
// get current values that are related to the board profile
|
||||
settings.led_gpio = root["led_gpio"];
|
||||
settings.dallas_gpio = root["dallas_gpio"];
|
||||
settings.rx_gpio = root["rx_gpio"];
|
||||
settings.tx_gpio = root["tx_gpio"];
|
||||
settings.pbutton_gpio = root["pbutton_gpio"];
|
||||
settings.phy_type = root["phy_type"];
|
||||
settings.eth_power = root["eth_power"];
|
||||
settings.eth_phy_addr = root["eth_phy_addr"];
|
||||
settings.eth_clock_mode = root["eth_clock_mode"];
|
||||
settings.led_type = root["led_type"]; // 1 = RGB-LED
|
||||
|
||||
#if defined(EMSESP_DEBUG)
|
||||
EMSESP::logger().debug("NVS boot value=[%s], board profile=[%s], EMSESP_DEFAULT_BOARD_PROFILE=[%s]",
|
||||
EMSESP::logger().debug("NVS boot value=[%s], current board_profile=[%s], new board_profile=[%s]",
|
||||
EMSESP::nvs_.getString("boot").c_str(),
|
||||
root["board_profile"] | "",
|
||||
EMSESP_DEFAULT_BOARD_PROFILE);
|
||||
original_settings.board_profile.c_str(),
|
||||
settings.board_profile.c_str());
|
||||
#endif
|
||||
|
||||
// get the current board profile saved in the settings file
|
||||
String org_board_profile = settings.board_profile;
|
||||
settings.board_profile = root["board_profile"] | EMSESP_DEFAULT_BOARD_PROFILE; // this is set at compile time in platformio.ini, other it's "default"
|
||||
String old_board_profile = settings.board_profile;
|
||||
// see if the user has changed the board profile
|
||||
// this will set: led_gpio, dallas_gpio, rx_gpio, tx_gpio, pbutton_gpio, phy_type, eth_power, eth_phy_addr, eth_clock_mode, led_type
|
||||
// this will always run when EMS-ESP starts since original_settings{} is empty
|
||||
if (original_settings.board_profile != settings.board_profile) {
|
||||
set_board_profile(settings);
|
||||
add_flags(ChangeFlags::RESTART);
|
||||
}
|
||||
|
||||
// if any of the GPIOs have changed and re-validate them
|
||||
bool have_valid_gpios = true;
|
||||
|
||||
if (check_flag(original_settings.led_gpio, settings.led_gpio, ChangeFlags::LED)) {
|
||||
if (settings.led_gpio != 0) { // 0 means disabled
|
||||
have_valid_gpios = have_valid_gpios && EMSESP::system_.add_gpio(settings.led_gpio, "LED");
|
||||
}
|
||||
}
|
||||
|
||||
if (check_flag(original_settings.dallas_gpio, settings.dallas_gpio, ChangeFlags::TEMPERATURE_SENSOR)) {
|
||||
if (settings.dallas_gpio != 0) { // 0 means disabled
|
||||
have_valid_gpios = have_valid_gpios && EMSESP::system_.add_gpio(settings.dallas_gpio, "Dallas");
|
||||
}
|
||||
}
|
||||
|
||||
if (check_flag(original_settings.rx_gpio, settings.rx_gpio, ChangeFlags::RESTART)) {
|
||||
have_valid_gpios = have_valid_gpios && EMSESP::system_.add_gpio(settings.rx_gpio, "UART Rx");
|
||||
}
|
||||
|
||||
if (check_flag(original_settings.tx_gpio, settings.tx_gpio, ChangeFlags::RESTART)) {
|
||||
have_valid_gpios = have_valid_gpios && EMSESP::system_.add_gpio(settings.tx_gpio, "UART Tx");
|
||||
}
|
||||
|
||||
if (check_flag(original_settings.pbutton_gpio, settings.pbutton_gpio, ChangeFlags::BUTTON)) {
|
||||
have_valid_gpios = have_valid_gpios && EMSESP::system_.add_gpio(settings.pbutton_gpio, "Button");
|
||||
}
|
||||
|
||||
if (check_flag(original_settings.phy_type, settings.phy_type, ChangeFlags::RESTART)) {
|
||||
// ETH has changed, so we need to check the ethernet pins. Only if ETH is being used.
|
||||
if (settings.phy_type != PHY_type::PHY_TYPE_NONE) {
|
||||
if (settings.eth_power != -1) {
|
||||
// Ethernet Power -1 means disabled
|
||||
have_valid_gpios = have_valid_gpios && EMSESP::system_.add_gpio(settings.eth_power, "Ethernet Power");
|
||||
}
|
||||
// all valid so far, now remove the ethernet pins from valid list, regardless of whether the GPIOs are valid or not
|
||||
EMSESP::system_.remove_gpio(23, true); // MDC
|
||||
EMSESP::system_.remove_gpio(18, true); // MDIO
|
||||
EMSESP::system_.remove_gpio(0, true); // ETH.clock input
|
||||
EMSESP::system_.remove_gpio(16, true); // ETH.clock output
|
||||
EMSESP::system_.remove_gpio(17, true); // ETH.clock output
|
||||
EMSESP::system_.remove_gpio(21, true); // I2C SDA
|
||||
EMSESP::system_.remove_gpio(22, true); // I2C SCL
|
||||
}
|
||||
}
|
||||
|
||||
// check if the LED type, eth_phy_addr or eth_clock_mode have changed
|
||||
check_flag(original_settings.led_type, settings.led_type, ChangeFlags::LED);
|
||||
check_flag(original_settings.eth_phy_addr, settings.eth_phy_addr, ChangeFlags::RESTART);
|
||||
check_flag(original_settings.eth_clock_mode, settings.eth_clock_mode, ChangeFlags::RESTART);
|
||||
|
||||
// tx_mode
|
||||
settings.tx_mode = root["tx_mode"] | EMSESP_DEFAULT_TX_MODE;
|
||||
check_flag(original_settings.tx_mode, settings.tx_mode, ChangeFlags::UART);
|
||||
|
||||
// syslog
|
||||
settings.syslog_enabled = root["syslog_enabled"] | EMSESP_DEFAULT_SYSLOG_ENABLED;
|
||||
check_flag(original_settings.syslog_enabled, settings.syslog_enabled, ChangeFlags::SYSLOG);
|
||||
settings.syslog_level = root["syslog_level"] | EMSESP_DEFAULT_SYSLOG_LEVEL;
|
||||
check_flag(original_settings.syslog_level, settings.syslog_level, ChangeFlags::SYSLOG);
|
||||
settings.syslog_mark_interval = root["syslog_mark_interval"] | EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL;
|
||||
check_flag(original_settings.syslog_mark_interval, settings.syslog_mark_interval, ChangeFlags::SYSLOG);
|
||||
settings.syslog_port = root["syslog_port"] | EMSESP_DEFAULT_SYSLOG_PORT;
|
||||
check_flag(original_settings.syslog_port, settings.syslog_port, ChangeFlags::SYSLOG);
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
settings.syslog_host = root["syslog_host"] | EMSESP_DEFAULT_SYSLOG_HOST;
|
||||
if (original_settings.syslog_host != settings.syslog_host) {
|
||||
add_flags(ChangeFlags::SYSLOG);
|
||||
}
|
||||
#endif
|
||||
|
||||
// temperature sensor
|
||||
settings.dallas_parasite = root["dallas_parasite"] | EMSESP_DEFAULT_DALLAS_PARASITE;
|
||||
check_flag(original_settings.dallas_parasite, settings.dallas_parasite, ChangeFlags::TEMPERATURE_SENSOR);
|
||||
|
||||
// shower
|
||||
settings.shower_timer = root["shower_timer"] | EMSESP_DEFAULT_SHOWER_TIMER;
|
||||
check_flag(original_settings.shower_timer, settings.shower_timer, ChangeFlags::SHOWER);
|
||||
settings.shower_alert = root["shower_alert"] | EMSESP_DEFAULT_SHOWER_ALERT;
|
||||
check_flag(original_settings.shower_alert, settings.shower_alert, ChangeFlags::SHOWER);
|
||||
settings.shower_alert_trigger = root["shower_alert_trigger"] | EMSESP_DEFAULT_SHOWER_ALERT_TRIGGER;
|
||||
check_flag(original_settings.shower_alert_trigger, settings.shower_alert_trigger, ChangeFlags::SHOWER);
|
||||
settings.shower_min_duration = root["shower_min_duration"] | EMSESP_DEFAULT_SHOWER_MIN_DURATION;
|
||||
check_flag(original_settings.shower_min_duration, settings.shower_min_duration, ChangeFlags::SHOWER);
|
||||
settings.shower_alert_coldshot = root["shower_alert_coldshot"] | EMSESP_DEFAULT_SHOWER_ALERT_COLDSHOT;
|
||||
check_flag(original_settings.shower_alert_coldshot, settings.shower_alert_coldshot, ChangeFlags::SHOWER);
|
||||
|
||||
// LED
|
||||
settings.hide_led = root["hide_led"] | EMSESP_DEFAULT_HIDE_LED;
|
||||
check_flag(original_settings.hide_led, settings.hide_led, ChangeFlags::LED);
|
||||
|
||||
// adc
|
||||
settings.analog_enabled = root["analog_enabled"] | EMSESP_DEFAULT_ANALOG_ENABLED;
|
||||
check_flag(original_settings.analog_enabled, settings.analog_enabled, ChangeFlags::ANALOG_SENSOR);
|
||||
|
||||
// telnet, ems bus id and low clock
|
||||
settings.telnet_enabled = root["telnet_enabled"] | EMSESP_DEFAULT_TELNET_ENABLED;
|
||||
check_flag(original_settings.telnet_enabled, settings.telnet_enabled, ChangeFlags::RESTART);
|
||||
settings.ems_bus_id = root["ems_bus_id"] | EMSESP_DEFAULT_EMS_BUS_ID;
|
||||
check_flag(original_settings.ems_bus_id, settings.ems_bus_id, ChangeFlags::RESTART);
|
||||
settings.low_clock = root["low_clock"];
|
||||
check_flag(original_settings.low_clock, settings.low_clock, ChangeFlags::RESTART);
|
||||
|
||||
// Modbus settings
|
||||
settings.modbus_enabled = root["modbus_enabled"] | EMSESP_DEFAULT_MODBUS_ENABLED;
|
||||
check_flag(original_settings.modbus_enabled, settings.modbus_enabled, ChangeFlags::RESTART);
|
||||
settings.modbus_port = root["modbus_port"] | EMSESP_DEFAULT_MODBUS_PORT;
|
||||
check_flag(original_settings.modbus_port, settings.modbus_port, ChangeFlags::RESTART);
|
||||
settings.modbus_max_clients = root["modbus_max_clients"] | EMSESP_DEFAULT_MODBUS_MAX_CLIENTS;
|
||||
check_flag(original_settings.modbus_max_clients, settings.modbus_max_clients, ChangeFlags::RESTART);
|
||||
settings.modbus_timeout = root["modbus_timeout"] | EMSESP_DEFAULT_MODBUS_TIMEOUT;
|
||||
check_flag(original_settings.modbus_timeout, settings.modbus_timeout, ChangeFlags::RESTART);
|
||||
|
||||
//
|
||||
// these may need mqtt restart to rebuild HA discovery topics
|
||||
//
|
||||
settings.bool_format = root["bool_format"] | EMSESP_DEFAULT_BOOL_FORMAT;
|
||||
EMSESP::system_.bool_format(settings.bool_format);
|
||||
if (Mqtt::ha_enabled()) {
|
||||
check_flag(original_settings.bool_format, settings.bool_format, ChangeFlags::MQTT);
|
||||
}
|
||||
|
||||
settings.enum_format = root["enum_format"] | EMSESP_DEFAULT_ENUM_FORMAT;
|
||||
EMSESP::system_.enum_format(settings.enum_format);
|
||||
if (Mqtt::ha_enabled()) {
|
||||
check_flag(original_settings.enum_format, settings.enum_format, ChangeFlags::MQTT);
|
||||
}
|
||||
|
||||
settings.locale = root["locale"] | EMSESP_DEFAULT_LOCALE;
|
||||
EMSESP::system_.locale(settings.locale);
|
||||
if (Mqtt::ha_enabled() && original_settings.locale != settings.locale) {
|
||||
add_flags(ChangeFlags::MQTT);
|
||||
}
|
||||
|
||||
//
|
||||
// without checks or necessary restarts...
|
||||
//
|
||||
settings.trace_raw = root["trace_raw"] | EMSESP_DEFAULT_TRACELOG_RAW;
|
||||
EMSESP::trace_raw(settings.trace_raw);
|
||||
|
||||
settings.notoken_api = root["notoken_api"] | EMSESP_DEFAULT_NOTOKEN_API;
|
||||
settings.solar_maxflow = root["solar_maxflow"] | EMSESP_DEFAULT_SOLAR_MAXFLOW;
|
||||
settings.boiler_heatingoff = root["boiler_heatingoff"] | EMSESP_DEFAULT_BOILER_HEATINGOFF;
|
||||
settings.remote_timeout = root["remote_timeout"] | EMSESP_DEFAULT_REMOTE_TIMEOUT;
|
||||
settings.remote_timeout_enabled = root["remote_timeout_en"] | EMSESP_DEFAULT_REMOTE_TIMEOUT_EN;
|
||||
Roomctrl::set_timeout(settings.remote_timeout_enabled ? settings.remote_timeout : 0);
|
||||
|
||||
settings.fahrenheit = root["fahrenheit"];
|
||||
EMSESP::system_.fahrenheit(settings.fahrenheit);
|
||||
|
||||
settings.readonly_mode = root["readonly_mode"];
|
||||
EMSESP::system_.readonly_mode(settings.readonly_mode);
|
||||
|
||||
settings.developer_mode = root["developer_mode"];
|
||||
EMSESP::system_.developer_mode(settings.developer_mode);
|
||||
|
||||
settings.bool_dashboard = root["bool_dashboard"] | EMSESP_DEFAULT_BOOL_FORMAT;
|
||||
EMSESP::system_.bool_dashboard(settings.bool_dashboard);
|
||||
|
||||
settings.weblog_level = root["weblog_level"] | EMSESP_DEFAULT_WEBLOG_LEVEL;
|
||||
settings.weblog_compact = root["weblog_compact"] | EMSESP_DEFAULT_WEBLOG_COMPACT;
|
||||
|
||||
// if no psram limit weblog buffer to 25 messages
|
||||
if (EMSESP::system_.PSram() > 0) {
|
||||
settings.weblog_buffer = root["weblog_buffer"] | EMSESP_DEFAULT_WEBLOG_BUFFER;
|
||||
} else {
|
||||
settings.weblog_buffer = root["weblog_buffer"] | 25; // limit to 25 messages if no psram
|
||||
}
|
||||
|
||||
// save the setting internally, for reference later
|
||||
EMSESP::system_.store_settings(settings);
|
||||
|
||||
// save the settings if changed from the webUI
|
||||
// if we encountered an invalid GPIO, don't save settings and report the error
|
||||
if (!have_valid_gpios) {
|
||||
#if defined(EMSESP_DEBUG)
|
||||
EMSESP::logger().debug("Warning: one or more GPIOs are invalid");
|
||||
#endif
|
||||
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_INVALID_GPIO);
|
||||
return StateUpdateResult::CHANGED; // save the settings anyway, without restart
|
||||
}
|
||||
|
||||
if (has_flags(WebSettings::ChangeFlags::RESTART)) {
|
||||
return StateUpdateResult::CHANGED_RESTART;
|
||||
}
|
||||
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
|
||||
// this is called after any of the settings have been persisted to the filesystem
|
||||
// either via the Web UI or via the Console
|
||||
void WebSettingsService::onUpdate() {
|
||||
// skip if we're restarting anyway
|
||||
|
||||
if (WebSettings::has_flags(WebSettings::ChangeFlags::RESTART)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (WebSettings::has_flags(WebSettings::ChangeFlags::SHOWER)) {
|
||||
EMSESP::shower_.start();
|
||||
}
|
||||
|
||||
if (WebSettings::has_flags(WebSettings::ChangeFlags::TEMPERATURE_SENSOR)) {
|
||||
EMSESP::temperaturesensor_.start();
|
||||
}
|
||||
|
||||
if (WebSettings::has_flags(WebSettings::ChangeFlags::UART)) {
|
||||
EMSESP::system_.uart_init();
|
||||
}
|
||||
|
||||
if (WebSettings::has_flags(WebSettings::ChangeFlags::SYSLOG)) {
|
||||
EMSESP::system_.syslog_init(); // re-start (or stop)
|
||||
}
|
||||
|
||||
if (WebSettings::has_flags(WebSettings::ChangeFlags::ANALOG_SENSOR)) {
|
||||
EMSESP::analogsensor_.start();
|
||||
}
|
||||
|
||||
if (WebSettings::has_flags(WebSettings::ChangeFlags::BUTTON)) {
|
||||
EMSESP::system_.button_init();
|
||||
}
|
||||
|
||||
if (WebSettings::has_flags(WebSettings::ChangeFlags::LED)) {
|
||||
EMSESP::system_.led_init();
|
||||
}
|
||||
|
||||
if (WebSettings::has_flags(WebSettings::ChangeFlags::MQTT)) {
|
||||
Mqtt::reset_mqtt(); // reload MQTT, init HA etc
|
||||
}
|
||||
|
||||
WebSettings::reset_flags();
|
||||
}
|
||||
|
||||
void WebSettingsService::begin() {
|
||||
_fsPersistence.readFromFS();
|
||||
}
|
||||
|
||||
void WebSettingsService::save() {
|
||||
_fsPersistence.writeToFS();
|
||||
}
|
||||
|
||||
// send the board profile as JSON
|
||||
void WebSettingsService::board_profile(AsyncWebServerRequest * request) {
|
||||
if (request->hasParam("boardProfile")) {
|
||||
std::string board_profile = request->getParam("boardProfile")->value().c_str();
|
||||
|
||||
auto * response = new AsyncJsonResponse(false);
|
||||
JsonObject root = response->getRoot();
|
||||
|
||||
// 0=led, 1=dallas, 2=rx, 3=tx, 4=button, 5=phy_type, 6=eth_power, 7=eth_phy_addr, 8=eth_clock_mode, 9=led_type
|
||||
std::vector<int8_t> data;
|
||||
(void)System::load_board_profile(data, board_profile);
|
||||
root["board_profile"] = board_profile;
|
||||
root["led_gpio"] = data[0];
|
||||
root["dallas_gpio"] = data[1];
|
||||
root["rx_gpio"] = data[2];
|
||||
root["tx_gpio"] = data[3];
|
||||
root["pbutton_gpio"] = data[4];
|
||||
root["phy_type"] = data[5];
|
||||
root["eth_power"] = data[6];
|
||||
root["eth_phy_addr"] = data[7];
|
||||
root["eth_clock_mode"] = data[8];
|
||||
root["led_type"] = data[9];
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
AsyncWebServerResponse * response = request->beginResponse(200);
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
// loads the board profile to set the gpios
|
||||
// if the board profile is not found, or default, it will try to autodetect the board profile
|
||||
void WebSettings::set_board_profile(WebSettings & settings) {
|
||||
// The optional NVS boot value has priority and overrides any board_profile setting.
|
||||
// We only do this for BBQKees boards
|
||||
// This is only done for BBQKees boards
|
||||
// Note 1: we never set the NVS boot value in the code - this is done on initial pre-loading
|
||||
// Note 2: The board profile is dynamically changed for the session, but the value in the settings file on the FS remains untouched
|
||||
if (EMSESP::system_.getBBQKeesGatewayDetails(FUSE_VALUE::MFG).startsWith("BBQKees")) {
|
||||
@@ -116,45 +413,29 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) {
|
||||
}
|
||||
}
|
||||
|
||||
// load the board profile from the settings, if it's not "default"
|
||||
std::vector<int8_t> data; // // led, dallas, rx, tx, button, phy_type, eth_power, eth_phy_addr, eth_clock_mode
|
||||
// if it's CUSTOM no need to load the board profile from the settings
|
||||
// as it's already set
|
||||
if (settings.board_profile == "CUSTOM") {
|
||||
EMSESP::logger().info("Using CUSTOM board profile");
|
||||
return;
|
||||
}
|
||||
|
||||
// load the board profile into the data vector
|
||||
// 0=led, 1=dallas, 2=rx, 3=tx, 4=button, 5=phy_type, 6=eth_power, 7=eth_phy_addr, 8=eth_clock_mode, 9=led_type
|
||||
std::vector<int8_t> data(10, 255); // initialize with 255 for all values
|
||||
if (settings.board_profile != "default") {
|
||||
if (System::load_board_profile(data, settings.board_profile.c_str())) {
|
||||
if (settings.board_profile == "CUSTOM") { // read pins, fallback to S32 values
|
||||
data = {(int8_t)(root["led_gpio"] | 2),
|
||||
(int8_t)(root["dallas_gpio"] | 18),
|
||||
(int8_t)(root["rx_gpio"] | 23),
|
||||
(int8_t)(root["tx_gpio"] | 5),
|
||||
(int8_t)(root["pbutton_gpio"] | 0),
|
||||
(int8_t)(root["phy_type"] | PHY_type::PHY_TYPE_NONE),
|
||||
(int8_t)(root["eth_power"] | 0),
|
||||
(int8_t)(root["eth_phy_addr"] | 0),
|
||||
(int8_t)(root["eth_clock_mode"] | 0),
|
||||
#if defined(ARDUINO_LOLIN_C3_MINI) && !defined(BOARD_C3_MINI_V1)
|
||||
(int8_t)(root["led_type"] | 1)};
|
||||
#else
|
||||
(int8_t)(root["led_type"] | 0)}; // 0 = LED, 1 = RGB-LED
|
||||
if (!System::load_board_profile(data, settings.board_profile.c_str())) {
|
||||
#if defined(EMSESP_DEBUG)
|
||||
EMSESP::logger().debug("Unable to identify board profile %s", settings.board_profile.c_str());
|
||||
#endif
|
||||
}
|
||||
// check valid pins in this board profile
|
||||
if (!EMSESP::system_.is_valid_gpio(data[0]) || !EMSESP::system_.is_valid_gpio(data[1]) || !EMSESP::system_.is_valid_gpio(data[2])
|
||||
|| !EMSESP::system_.is_valid_gpio(data[3]) || !EMSESP::system_.is_valid_gpio(data[4]) || !EMSESP::system_.is_valid_gpio(data[6])) {
|
||||
settings.board_profile = "default"; // reset to factory default
|
||||
}
|
||||
} else {
|
||||
settings.board_profile = "default"; // can't find profile, use "default"
|
||||
settings.board_profile = "default"; // can't find profile, fallback to "default"
|
||||
}
|
||||
}
|
||||
|
||||
// still don't have a valid board profile. Let's see if we can determine one
|
||||
// we still don't have a valid board profile. Let's see if we can determine one from the build config or hardware
|
||||
if (settings.board_profile == "default") {
|
||||
#if defined(EMSESP_DEBUG)
|
||||
EMSESP::logger().debug("Trying to detect board and set board profile...");
|
||||
#endif
|
||||
|
||||
#if defined(EMSESP_STANDALONE)
|
||||
settings.board_profile = "S32";
|
||||
#elif CONFIG_IDF_TARGET_ESP32
|
||||
EMSESP::logger().info("Autodetecting board profile");
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
// check for no PSRAM, could be a E32 or S32?
|
||||
if (!ESP.getPsramSize()) {
|
||||
#if ESP_ARDUINO_VERSION_MAJOR < 3
|
||||
@@ -183,297 +464,61 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) {
|
||||
settings.board_profile = "S32"; // ESP32 standard WiFi with PSRAM
|
||||
}
|
||||
}
|
||||
|
||||
// override if we know the target from the build config like C3, S2, S3 etc..
|
||||
// override if we know the target from the build config like C3, S2, S3 etc..
|
||||
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||
settings.board_profile = "C3MINI";
|
||||
settings.board_profile = "C3MINI";
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
settings.board_profile = "S2MINI";
|
||||
settings.board_profile = "S2MINI";
|
||||
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||
settings.board_profile = "S32S3"; // BBQKees Gateway S3
|
||||
settings.board_profile = "S32S3"; // BBQKees Gateway S3
|
||||
#endif
|
||||
// apply the new board profile setting
|
||||
System::load_board_profile(data, settings.board_profile.c_str());
|
||||
}
|
||||
|
||||
if (org_board_profile != settings.board_profile) {
|
||||
if (org_board_profile.isEmpty()) {
|
||||
EMSESP::logger().info("Setting board profile to %s", settings.board_profile.c_str());
|
||||
} else {
|
||||
EMSESP::logger().info("Setting board profile to %s (was %s)", settings.board_profile.c_str(), org_board_profile.c_str());
|
||||
}
|
||||
}
|
||||
EMSESP::logger().info("Loaded board profile %s", settings.board_profile.c_str());
|
||||
|
||||
int prev;
|
||||
reset_flags();
|
||||
// apply the new board profile settings
|
||||
// 0=led, 1=dallas, 2=rx, 3=tx, 4=button, 5=phy_type, 6=eth_power, 7=eth_phy_addr, 8=eth_clock_mode, 9=led_type
|
||||
settings.led_gpio = data[0]; // LED GPIO
|
||||
settings.dallas_gpio = data[1]; // Dallas GPIO
|
||||
settings.rx_gpio = data[2]; // UART Rx GPIO
|
||||
settings.tx_gpio = data[3]; // UART Tx GPIO
|
||||
settings.pbutton_gpio = data[4]; // Button GPIO
|
||||
settings.phy_type = data[5]; // PHY Type
|
||||
settings.eth_power = data[6]; // Ethernet Power GPIO
|
||||
settings.eth_phy_addr = data[7]; // Ethernet PHY Address
|
||||
settings.eth_clock_mode = data[8]; // Ethernet Clock Mode
|
||||
settings.led_type = data[9]; // LED Type
|
||||
}
|
||||
|
||||
// pins
|
||||
prev = settings.led_gpio;
|
||||
settings.led_gpio = data[0];
|
||||
check_flag(prev, settings.led_gpio, ChangeFlags::LED);
|
||||
prev = settings.dallas_gpio;
|
||||
settings.dallas_gpio = data[1];
|
||||
check_flag(prev, settings.dallas_gpio, ChangeFlags::SENSOR);
|
||||
prev = settings.rx_gpio;
|
||||
settings.rx_gpio = data[2];
|
||||
check_flag(prev, settings.rx_gpio, ChangeFlags::RESTART);
|
||||
prev = settings.tx_gpio;
|
||||
settings.tx_gpio = data[3];
|
||||
check_flag(prev, settings.tx_gpio, ChangeFlags::RESTART);
|
||||
prev = settings.pbutton_gpio;
|
||||
settings.pbutton_gpio = data[4];
|
||||
check_flag(prev, settings.pbutton_gpio, ChangeFlags::BUTTON);
|
||||
prev = settings.phy_type;
|
||||
settings.phy_type = data[5];
|
||||
check_flag(prev, settings.phy_type, ChangeFlags::RESTART);
|
||||
prev = settings.eth_power;
|
||||
settings.eth_power = data[6];
|
||||
check_flag(prev, settings.eth_power, ChangeFlags::RESTART);
|
||||
prev = settings.eth_phy_addr;
|
||||
settings.eth_phy_addr = data[7];
|
||||
check_flag(prev, settings.eth_phy_addr, ChangeFlags::RESTART);
|
||||
prev = settings.eth_clock_mode;
|
||||
settings.eth_clock_mode = data[8];
|
||||
check_flag(prev, settings.eth_clock_mode, ChangeFlags::RESTART);
|
||||
prev = settings.led_type;
|
||||
settings.led_type = data[9];
|
||||
check_flag(prev, settings.led_type, ChangeFlags::LED);
|
||||
|
||||
// tx_mode, rx and tx pins
|
||||
prev = settings.tx_mode;
|
||||
settings.tx_mode = root["tx_mode"] | EMSESP_DEFAULT_TX_MODE;
|
||||
check_flag(prev, settings.tx_mode, ChangeFlags::UART);
|
||||
|
||||
// syslog
|
||||
prev = settings.syslog_enabled;
|
||||
settings.syslog_enabled = root["syslog_enabled"] | EMSESP_DEFAULT_SYSLOG_ENABLED;
|
||||
check_flag(prev, settings.syslog_enabled, ChangeFlags::SYSLOG);
|
||||
prev = settings.syslog_level;
|
||||
settings.syslog_level = root["syslog_level"] | EMSESP_DEFAULT_SYSLOG_LEVEL;
|
||||
check_flag(prev, settings.syslog_level, ChangeFlags::SYSLOG);
|
||||
prev = settings.syslog_mark_interval;
|
||||
settings.syslog_mark_interval = root["syslog_mark_interval"] | EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL;
|
||||
check_flag(prev, settings.syslog_mark_interval, ChangeFlags::SYSLOG);
|
||||
prev = settings.syslog_port;
|
||||
settings.syslog_port = root["syslog_port"] | EMSESP_DEFAULT_SYSLOG_PORT;
|
||||
check_flag(prev, settings.syslog_port, ChangeFlags::SYSLOG);
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
String old_syslog_host = settings.syslog_host;
|
||||
settings.syslog_host = root["syslog_host"] | EMSESP_DEFAULT_SYSLOG_HOST;
|
||||
if (old_syslog_host != settings.syslog_host) {
|
||||
add_flags(ChangeFlags::SYSLOG);
|
||||
}
|
||||
// returns true if the value was changed
|
||||
bool WebSettings::check_flag(int prev_v, int new_v, uint8_t flag) {
|
||||
if (prev_v != new_v) {
|
||||
add_flags(flag);
|
||||
#if defined(EMSESP_DEBUG)
|
||||
// EMSESP::logger().debug("check_flag: flag %d, prev_v=%d, new_v=%d", flag, prev_v, new_v);
|
||||
#endif
|
||||
|
||||
// temperature sensor
|
||||
prev = settings.dallas_parasite;
|
||||
settings.dallas_parasite = root["dallas_parasite"] | EMSESP_DEFAULT_DALLAS_PARASITE;
|
||||
check_flag(prev, settings.dallas_parasite, ChangeFlags::SENSOR);
|
||||
|
||||
// shower
|
||||
prev = settings.shower_timer;
|
||||
settings.shower_timer = root["shower_timer"] | EMSESP_DEFAULT_SHOWER_TIMER;
|
||||
check_flag(prev, settings.shower_timer, ChangeFlags::SHOWER);
|
||||
prev = settings.shower_alert;
|
||||
settings.shower_alert = root["shower_alert"] | EMSESP_DEFAULT_SHOWER_ALERT;
|
||||
check_flag(prev, settings.shower_alert, ChangeFlags::SHOWER);
|
||||
prev = settings.shower_alert_trigger;
|
||||
settings.shower_alert_trigger = root["shower_alert_trigger"] | EMSESP_DEFAULT_SHOWER_ALERT_TRIGGER;
|
||||
check_flag(prev, settings.shower_alert_trigger, ChangeFlags::SHOWER);
|
||||
prev = settings.shower_min_duration;
|
||||
settings.shower_min_duration = root["shower_min_duration"] | EMSESP_DEFAULT_SHOWER_MIN_DURATION;
|
||||
check_flag(prev, settings.shower_min_duration, ChangeFlags::SHOWER);
|
||||
prev = settings.shower_alert_coldshot;
|
||||
settings.shower_alert_coldshot = root["shower_alert_coldshot"] | EMSESP_DEFAULT_SHOWER_ALERT_COLDSHOT;
|
||||
check_flag(prev, settings.shower_alert_coldshot, ChangeFlags::SHOWER);
|
||||
|
||||
// led
|
||||
prev = settings.hide_led;
|
||||
settings.hide_led = root["hide_led"] | EMSESP_DEFAULT_HIDE_LED;
|
||||
check_flag(prev, settings.hide_led, ChangeFlags::LED);
|
||||
|
||||
// adc
|
||||
prev = settings.analog_enabled;
|
||||
settings.analog_enabled = root["analog_enabled"] | EMSESP_DEFAULT_ANALOG_ENABLED;
|
||||
check_flag(prev, settings.analog_enabled, ChangeFlags::ADC);
|
||||
|
||||
//
|
||||
// these need system restarts first before settings are activated...
|
||||
//
|
||||
prev = settings.telnet_enabled;
|
||||
settings.telnet_enabled = root["telnet_enabled"] | EMSESP_DEFAULT_TELNET_ENABLED;
|
||||
check_flag(prev, settings.telnet_enabled, ChangeFlags::RESTART);
|
||||
|
||||
prev = settings.ems_bus_id;
|
||||
settings.ems_bus_id = root["ems_bus_id"] | EMSESP_DEFAULT_EMS_BUS_ID;
|
||||
check_flag(prev, settings.ems_bus_id, ChangeFlags::RESTART);
|
||||
|
||||
prev = settings.low_clock;
|
||||
settings.low_clock = root["low_clock"];
|
||||
check_flag(prev, settings.low_clock, ChangeFlags::RESTART);
|
||||
|
||||
// Modbus settings
|
||||
prev = settings.modbus_enabled;
|
||||
settings.modbus_enabled = root["modbus_enabled"] | EMSESP_DEFAULT_MODBUS_ENABLED;
|
||||
check_flag(prev, settings.modbus_enabled, ChangeFlags::RESTART);
|
||||
|
||||
prev = settings.modbus_port;
|
||||
settings.modbus_port = root["modbus_port"] | EMSESP_DEFAULT_MODBUS_PORT;
|
||||
check_flag(prev, settings.modbus_port, ChangeFlags::RESTART);
|
||||
|
||||
prev = settings.modbus_max_clients;
|
||||
settings.modbus_max_clients = root["modbus_max_clients"] | EMSESP_DEFAULT_MODBUS_MAX_CLIENTS;
|
||||
check_flag(prev, settings.modbus_max_clients, ChangeFlags::RESTART);
|
||||
|
||||
prev = settings.modbus_timeout;
|
||||
settings.modbus_timeout = root["modbus_timeout"] | EMSESP_DEFAULT_MODBUS_TIMEOUT;
|
||||
check_flag(prev, settings.modbus_timeout, ChangeFlags::RESTART);
|
||||
|
||||
//
|
||||
// these may need mqtt restart to rebuild HA discovery topics
|
||||
//
|
||||
prev = settings.bool_format;
|
||||
settings.bool_format = root["bool_format"] | EMSESP_DEFAULT_BOOL_FORMAT;
|
||||
EMSESP::system_.bool_format(settings.bool_format);
|
||||
if (Mqtt::ha_enabled()) {
|
||||
check_flag(prev, settings.bool_format, ChangeFlags::MQTT);
|
||||
return true;
|
||||
}
|
||||
|
||||
prev = settings.enum_format;
|
||||
settings.enum_format = root["enum_format"] | EMSESP_DEFAULT_ENUM_FORMAT;
|
||||
EMSESP::system_.enum_format(settings.enum_format);
|
||||
if (Mqtt::ha_enabled()) {
|
||||
check_flag(prev, settings.enum_format, ChangeFlags::MQTT);
|
||||
}
|
||||
|
||||
String old_locale = settings.locale;
|
||||
settings.locale = root["locale"] | EMSESP_DEFAULT_LOCALE;
|
||||
EMSESP::system_.locale(settings.locale);
|
||||
if (Mqtt::ha_enabled() && old_locale != settings.locale) {
|
||||
add_flags(ChangeFlags::MQTT);
|
||||
}
|
||||
|
||||
//
|
||||
// without checks or necessary restarts...
|
||||
//
|
||||
settings.trace_raw = root["trace_raw"] | EMSESP_DEFAULT_TRACELOG_RAW;
|
||||
EMSESP::trace_raw(settings.trace_raw);
|
||||
|
||||
settings.notoken_api = root["notoken_api"] | EMSESP_DEFAULT_NOTOKEN_API;
|
||||
settings.solar_maxflow = root["solar_maxflow"] | EMSESP_DEFAULT_SOLAR_MAXFLOW;
|
||||
settings.boiler_heatingoff = root["boiler_heatingoff"] | EMSESP_DEFAULT_BOILER_HEATINGOFF;
|
||||
settings.remote_timeout = root["remote_timeout"] | EMSESP_DEFAULT_REMOTE_TIMEOUT;
|
||||
settings.remote_timeout_enabled = root["remote_timeout_en"] | EMSESP_DEFAULT_REMOTE_TIMEOUT_EN;
|
||||
emsesp::Roomctrl::set_timeout(settings.remote_timeout_enabled ? settings.remote_timeout : 0);
|
||||
|
||||
settings.fahrenheit = root["fahrenheit"];
|
||||
EMSESP::system_.fahrenheit(settings.fahrenheit);
|
||||
|
||||
settings.readonly_mode = root["readonly_mode"];
|
||||
EMSESP::system_.readonly_mode(settings.readonly_mode);
|
||||
|
||||
settings.developer_mode = root["developer_mode"];
|
||||
EMSESP::system_.developer_mode(settings.developer_mode);
|
||||
|
||||
settings.bool_dashboard = root["bool_dashboard"] | EMSESP_DEFAULT_BOOL_FORMAT;
|
||||
EMSESP::system_.bool_dashboard(settings.bool_dashboard);
|
||||
|
||||
settings.weblog_level = root["weblog_level"] | EMSESP_DEFAULT_WEBLOG_LEVEL;
|
||||
settings.weblog_compact = root["weblog_compact"] | EMSESP_DEFAULT_WEBLOG_COMPACT;
|
||||
|
||||
// if no psram limit weblog buffer to 25 messages
|
||||
if (EMSESP::system_.PSram() > 0) {
|
||||
settings.weblog_buffer = root["weblog_buffer"] | EMSESP_DEFAULT_WEBLOG_BUFFER;
|
||||
} else {
|
||||
settings.weblog_buffer = root["weblog_buffer"] | 25;
|
||||
}
|
||||
|
||||
// save the settings
|
||||
if (get_flags() == WebSettings::ChangeFlags::RESTART) {
|
||||
return StateUpdateResult::CHANGED_RESTART; // tell WebUI that a restart is needed
|
||||
}
|
||||
|
||||
return StateUpdateResult::CHANGED;
|
||||
return false;
|
||||
}
|
||||
|
||||
// this is called after any of the settings have been persisted to the filesystem
|
||||
// either via the Web UI or via the Console
|
||||
void WebSettingsService::onUpdate() {
|
||||
if (WebSettings::has_flags(WebSettings::ChangeFlags::SHOWER)) {
|
||||
EMSESP::shower_.start();
|
||||
}
|
||||
|
||||
if (WebSettings::has_flags(WebSettings::ChangeFlags::SENSOR)) {
|
||||
EMSESP::temperaturesensor_.start();
|
||||
}
|
||||
|
||||
if (WebSettings::has_flags(WebSettings::ChangeFlags::UART)) {
|
||||
EMSESP::system_.uart_init(true);
|
||||
}
|
||||
|
||||
if (WebSettings::has_flags(WebSettings::ChangeFlags::SYSLOG)) {
|
||||
EMSESP::system_.syslog_init(); // re-start (or stop)
|
||||
}
|
||||
|
||||
if (WebSettings::has_flags(WebSettings::ChangeFlags::ADC)) {
|
||||
EMSESP::analogsensor_.start();
|
||||
}
|
||||
|
||||
if (WebSettings::has_flags(WebSettings::ChangeFlags::BUTTON)) {
|
||||
EMSESP::system_.button_init(true);
|
||||
}
|
||||
|
||||
if (WebSettings::has_flags(WebSettings::ChangeFlags::LED)) {
|
||||
EMSESP::system_.led_init(true);
|
||||
}
|
||||
|
||||
if (WebSettings::has_flags(WebSettings::ChangeFlags::MQTT)) {
|
||||
emsesp::Mqtt::reset_mqtt(); // reload MQTT, init HA etc
|
||||
}
|
||||
|
||||
WebSettings::reset_flags();
|
||||
void WebSettings::add_flags(uint8_t flags) {
|
||||
flags_ |= flags;
|
||||
}
|
||||
|
||||
void WebSettingsService::begin() {
|
||||
_fsPersistence.readFromFS();
|
||||
|
||||
WebSettings::reset_flags();
|
||||
bool WebSettings::has_flags(uint8_t flags) {
|
||||
return (flags_ & flags) == flags;
|
||||
}
|
||||
|
||||
void WebSettingsService::save() {
|
||||
_fsPersistence.writeToFS();
|
||||
void WebSettings::reset_flags() {
|
||||
flags_ = ChangeFlags::NONE;
|
||||
}
|
||||
|
||||
// build the json profile to send back
|
||||
void WebSettingsService::board_profile(AsyncWebServerRequest * request) {
|
||||
if (request->hasParam("boardProfile")) {
|
||||
std::string board_profile = request->getParam("boardProfile")->value().c_str();
|
||||
|
||||
auto * response = new AsyncJsonResponse(false);
|
||||
JsonObject root = response->getRoot();
|
||||
|
||||
std::vector<int8_t> data; // led, dallas, rx, tx, button, phy_type, eth_power, eth_phy_addr, eth_clock_mode
|
||||
(void)System::load_board_profile(data, board_profile);
|
||||
root["board_profile"] = board_profile;
|
||||
root["led_gpio"] = data[0];
|
||||
root["dallas_gpio"] = data[1];
|
||||
root["rx_gpio"] = data[2];
|
||||
root["tx_gpio"] = data[3];
|
||||
root["pbutton_gpio"] = data[4];
|
||||
root["phy_type"] = data[5];
|
||||
root["eth_power"] = data[6];
|
||||
root["eth_phy_addr"] = data[7];
|
||||
root["eth_clock_mode"] = data[8];
|
||||
root["led_type"] = data[9];
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
AsyncWebServerResponse * response = request->beginResponse(200);
|
||||
request->send(response);
|
||||
uint8_t WebSettings::get_flags() {
|
||||
return flags_;
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -89,43 +89,27 @@ class WebSettings {
|
||||
static StateUpdateResult update(JsonObject root, WebSettings & settings);
|
||||
|
||||
enum ChangeFlags : uint8_t {
|
||||
|
||||
NONE = 0,
|
||||
UART = (1 << 0), // 1
|
||||
SYSLOG = (1 << 1), // 2
|
||||
ADC = (1 << 2), // 4 - analog
|
||||
SENSOR = (1 << 3), // 8
|
||||
SHOWER = (1 << 4), // 16
|
||||
LED = (1 << 5), // 32
|
||||
BUTTON = (1 << 6), // 64
|
||||
MQTT = (1 << 7), // 128
|
||||
RESTART = 0xFF
|
||||
|
||||
NONE = 0,
|
||||
UART = (1 << 0), // 1 - uart
|
||||
SYSLOG = (1 << 1), // 2 - syslog
|
||||
ANALOG_SENSOR = (1 << 2), // 4 - analog
|
||||
TEMPERATURE_SENSOR = (1 << 3), // 8 - dallas sensor
|
||||
SHOWER = (1 << 4), // 16 - shower timer and alert
|
||||
LED = (1 << 5), // 32 - led
|
||||
BUTTON = (1 << 6), // 64 - button
|
||||
MQTT = (1 << 7), // 128 - mqtt
|
||||
RESTART = 0xFF // 255 - restart request (all changes)
|
||||
};
|
||||
|
||||
static void check_flag(int prev_v, int new_v, uint8_t flag) {
|
||||
if (prev_v != new_v) {
|
||||
add_flags(flag);
|
||||
}
|
||||
}
|
||||
|
||||
static void add_flags(uint8_t flags) {
|
||||
flags_ |= flags;
|
||||
}
|
||||
|
||||
static bool has_flags(uint8_t flags) {
|
||||
return (flags_ & flags) == flags;
|
||||
}
|
||||
|
||||
static void reset_flags() {
|
||||
flags_ = ChangeFlags::NONE;
|
||||
}
|
||||
|
||||
static uint8_t get_flags() {
|
||||
return flags_;
|
||||
}
|
||||
static bool check_flag(int prev_v, int new_v, uint8_t flag);
|
||||
static void add_flags(uint8_t flags);
|
||||
static bool has_flags(uint8_t flags);
|
||||
static void reset_flags();
|
||||
static uint8_t get_flags();
|
||||
|
||||
private:
|
||||
static void set_board_profile(WebSettings & settings);
|
||||
|
||||
static uint8_t flags_;
|
||||
};
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ void WebStatusService::systemStatus(AsyncWebServerRequest * request) {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
uint8_t ntp_status = 0; // 0=disabled, 1=enabled, 2=connected
|
||||
if (esp_sntp_enabled()) {
|
||||
ntp_status = (emsesp::EMSESP::system_.ntp_connected()) ? 2 : 1;
|
||||
ntp_status = (EMSESP::system_.ntp_connected()) ? 2 : 1;
|
||||
}
|
||||
root["ntp_status"] = ntp_status;
|
||||
if (ntp_status == 2) {
|
||||
@@ -83,7 +83,7 @@ void WebStatusService::systemStatus(AsyncWebServerRequest * request) {
|
||||
|
||||
root["ap_status"] = EMSESP::esp32React.apStatus();
|
||||
|
||||
if (emsesp::EMSESP::system_.ethernet_connected()) {
|
||||
if (EMSESP::system_.ethernet_connected()) {
|
||||
root["network_status"] = 10; // custom code #10 - ETHERNET_STATUS_CONNECTED
|
||||
root["wifi_rssi"] = 0;
|
||||
} else {
|
||||
@@ -196,7 +196,7 @@ void WebStatusService::action(AsyncWebServerRequest * request, JsonVariant json)
|
||||
} else if (action == "systemStatus" && is_admin) {
|
||||
ok = setSystemStatus(param.c_str());
|
||||
} else if (action == "resetMQTT" && is_admin) {
|
||||
emsesp::EMSESP::mqtt_.reset_mqtt();
|
||||
EMSESP::mqtt_.reset_mqtt();
|
||||
ok = true;
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ void WebStatusService::action(AsyncWebServerRequest * request, JsonVariant json)
|
||||
|
||||
// check for error
|
||||
if (!ok) {
|
||||
emsesp::EMSESP::logger().err("Action '%s' failed", action.c_str());
|
||||
EMSESP::logger().err("Action '%s' failed", action.c_str());
|
||||
request->send(400); // bad request
|
||||
return;
|
||||
}
|
||||
@@ -233,7 +233,7 @@ bool WebStatusService::checkUpgrade(JsonObject root, std::string & versions) {
|
||||
#if defined(EMSESP_DEBUG)
|
||||
// look for dev in the name to determine if we're using a dev release
|
||||
bool using_dev_version = !current_version.prerelease().find("dev");
|
||||
emsesp::EMSESP::logger()
|
||||
EMSESP::logger()
|
||||
.debug("Checking version upgrade. This version=%d.%d.%d-%s (%s),latest dev=%d.%d.%d-%s (%s upgradeable),latest stable=%d.%d.%d-%s (%s upgradeable)",
|
||||
current_version.major(),
|
||||
current_version.minor(),
|
||||
@@ -334,7 +334,7 @@ bool WebStatusService::getCustomSupport(JsonObject root) {
|
||||
if (!file) {
|
||||
// there is no custom file, return empty object
|
||||
#if defined(EMSESP_DEBUG)
|
||||
emsesp::EMSESP::logger().debug("No custom support file found");
|
||||
EMSESP::logger().debug("No custom support file found");
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
@@ -342,7 +342,7 @@ bool WebStatusService::getCustomSupport(JsonObject root) {
|
||||
// read the contents of the file into a json doc. We can't do this direct to object since 7.2.1
|
||||
DeserializationError error = deserializeJson(doc, file);
|
||||
if (error) {
|
||||
emsesp::EMSESP::logger().err("Failed to read custom support file");
|
||||
EMSESP::logger().err("Failed to read custom support file");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -350,7 +350,7 @@ bool WebStatusService::getCustomSupport(JsonObject root) {
|
||||
#endif
|
||||
|
||||
#if defined(EMSESP_DEBUG)
|
||||
emsesp::EMSESP::logger().debug("Showing custom support page");
|
||||
EMSESP::logger().debug("Showing custom support page");
|
||||
#endif
|
||||
|
||||
root.set(doc.as<JsonObject>()); // add to web response root object
|
||||
@@ -362,14 +362,14 @@ bool WebStatusService::getCustomSupport(JsonObject root) {
|
||||
// uploads a firmware file from a URL
|
||||
bool WebStatusService::uploadURL(const char * url) {
|
||||
// this will keep a copy of the URL, but won't initiate the download yet
|
||||
emsesp::EMSESP::system_.uploadFirmwareURL(url);
|
||||
EMSESP::system_.uploadFirmwareURL(url);
|
||||
return true;
|
||||
}
|
||||
|
||||
// action = systemStatus
|
||||
// sets the system status
|
||||
bool WebStatusService::setSystemStatus(const char * status) {
|
||||
emsesp::EMSESP::system_.systemStatus(Helpers::atoint(status));
|
||||
EMSESP::system_.systemStatus(Helpers::atoint(status));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
# @host = http://ems-esp.local
|
||||
@host = http://192.168.1.65
|
||||
@host_dev = http://10.10.10.175
|
||||
@host_dev = http://192.168.1.223
|
||||
@host_standalone = http://localhost:3080
|
||||
@host_standalone2 = http://localhost:3082
|
||||
|
||||
|
||||
40
test/test_api/api_test.js
Normal file
40
test/test_api/api_test.js
Normal file
@@ -0,0 +1,40 @@
|
||||
// npm install axios
|
||||
// node api_test.js
|
||||
const axios = require('axios');
|
||||
|
||||
async function testAPI(ip = "ems-esp.local", apiPath = "system") {
|
||||
const baseUrl = `http://${ip}/api`;
|
||||
const url = `${baseUrl}/${apiPath}`;
|
||||
|
||||
try {
|
||||
const response = await axios.get(url, {
|
||||
timeout: 5000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Status:', response.status);
|
||||
console.log('Data:', JSON.stringify(response.data, null, 2));
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error:', error.message);
|
||||
if (error.response) {
|
||||
console.error('Response status:', error.response.status);
|
||||
console.error('Response data:', error.response.data);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Run the test
|
||||
testAPI("192.168.1.223", "system")
|
||||
.then(() => {
|
||||
console.log('Test completed successfully');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Test failed:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -16,6 +16,8 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// tip: use https://jsondiff.com/ to compare the expected and actual responses.
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <unity.h>
|
||||
#include <emsesp.h>
|
||||
@@ -36,8 +38,8 @@ WebAPIService * webAPIService;
|
||||
EMSESP application;
|
||||
FS dummyFS;
|
||||
|
||||
std::shared_ptr<emsesp::EMSESPConsole> shell;
|
||||
char output_buffer[4096];
|
||||
std::shared_ptr<EMSESPConsole> shell;
|
||||
char output_buffer[4096];
|
||||
|
||||
class TestStream : public Stream {
|
||||
public:
|
||||
|
||||
@@ -1,27 +1,75 @@
|
||||
// ---------- START - CUT HERE ----------
|
||||
|
||||
void test_1() {
|
||||
auto expected_response = "[{\"reset\":\"\",\"chimneysweeper\":\"\",\"heatingoff\":\"off\",\"heatingactive\":\"off\",\"tapwateractive\":\"on\",\"selflowtemp\":0,\"curflowtemp\":60.2,\"rettemp\":48.1,\"syspress\":1.4,\"burngas\":\"on\",\"burngas2\":\"off\",\"flamecurr\":37.4,\"fanwork\":\"on\",\"ignwork\":\"off\",\"oilpreheat\":\"off\",\"heatingpump\":\"on\",\"selburnpow\":115,\"curburnpow\":61,\"ubauptime\":3940268,\"servicecode\":\"=H\",\"servicecodenumber\":201,\"nompower\":0,\"nrgtotal\":0.0,\"nrgheat\":0.0,\"dhw\":{\"seltemp\":52,\"comfort\":\"hot\",\"flowtempoffset\":40,\"chargeoptimization\":\"off\",\"circpump\":\"off\",\"chargetype\":\"3-way valve\",\"hyston\":-5,\"disinfectiontemp\":70,\"circmode\":\"off\",\"circ\":\"off\",\"storagetemp1\":53.8,\"activated\":\"on\",\"3wayvalve\":\"on\",\"chargepump\":\"off\",\"nrg\":0.0}}]";
|
||||
auto expected_response =
|
||||
"[{\"reset\":\"\",\"chimneysweeper\":\"\",\"heatingoff\":\"off\",\"heatingactive\":\"off\",\"tapwateractive\":\"on\",\"selflowtemp\":0,\"curflowtemp\":"
|
||||
"60.2,\"rettemp\":48.1,\"syspress\":1.4,\"burngas\":\"on\",\"burngas2\":\"off\",\"flamecurr\":37.4,\"fanwork\":\"on\",\"ignwork\":\"off\","
|
||||
"\"oilpreheat\":\"off\",\"heatingpump\":\"on\",\"selburnpow\":115,\"curburnpow\":61,\"ubauptime\":3940268,\"servicecode\":\"=H\",\"servicecodenumber\":"
|
||||
"201,\"nompower\":0,\"nrgtotal\":0.0,\"nrgheat\":0.0,\"dhw\":{\"seltemp\":52,\"comfort\":\"hot\",\"flowtempoffset\":40,\"chargeoptimization\":\"off\","
|
||||
"\"circpump\":\"off\",\"chargetype\":\"3-way "
|
||||
"valve\",\"hyston\":-5,\"disinfectiontemp\":70,\"circmode\":\"off\",\"circ\":\"off\",\"storagetemp1\":53.8,\"activated\":\"on\",\"3wayvalve\":\"on\","
|
||||
"\"chargepump\":\"off\",\"nrg\":0.0}}]";
|
||||
TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/boiler"));
|
||||
}
|
||||
|
||||
void test_2() {
|
||||
auto expected_response = "[{\"info\":\"list all values (verbose)\",\"values\":\"list all values\",\"commands\":\"list all commands\",\"entities\":\"list all entities\",\"boil2hystoff\":\"hysteresis stage 2 off temperature\",\"boil2hyston\":\"hysteresis stage 2 on temperature\",\"boilhystoff\":\"hysteresis off temperature\",\"boilhyston\":\"hysteresis on temperature\",\"burnmaxpower\":\"burner max power\",\"burnminperiod\":\"burner min period\",\"burnminpower\":\"burner min power\",\"chimneysweeper\":\"chimney sweeper\",\"coldshot\":\"send a cold shot of water\",\"curvebase\":\"heatingcurve base\",\"curveend\":\"heatingcurve end\",\"curveon\":\"heatingcurve on\",\"dhw[n].activated\":\"activated\",\"dhw[n].chargeoptimization\":\"charge optimization\",\"dhw[n].circ\":\"circulation active\",\"dhw[n].circmode\":\"circulation pump mode\",\"dhw[n].circpump\":\"circulation pump available\",\"dhw[n].comfort\":\"comfort\",\"dhw[n].comfort1\":\"comfort mode\",\"dhw[n].dhwprio\":\"dhw priority\",\"dhw[n].disinfecting\":\"disinfecting\",\"dhw[n].disinfectiontemp\":\"disinfection temperature\",\"dhw[n].flowtempoffset\":\"flow temperature offset\",\"dhw[n].hystoff\":\"hysteresis off temperature\",\"dhw[n].hyston\":\"hysteresis on temperature\",\"dhw[n].maxpower\":\"max power\",\"dhw[n].maxtemp\":\"maximum temperature\",\"dhw[n].nrg\":\"energy\",\"dhw[n].onetime\":\"one time charging\",\"dhw[n].seltemp\":\"selected temperature\",\"dhw[n].seltemplow\":\"selected lower temperature\",\"dhw[n].seltempsingle\":\"single charge temperature\",\"dhw[n].tapactivated\":\"turn on/off\",\"dhw[n].tempecoplus\":\"selected eco+ temperature\",\"emergencyops\":\"emergency operation\",\"emergencytemp\":\"emergency temperature\",\"heatingactivated\":\"heating activated\",\"heatingoff\":\"force heating off\",\"heatingtemp\":\"heating temperature\",\"maintenance\":\"maintenance scheduled\",\"maintenancedate\":\"next maintenance date\",\"maintenancetime\":\"time to next maintenance\",\"nofrostmode\":\"nofrost mode\",\"nofrosttemp\":\"nofrost temperature\",\"nompower\":\"nominal Power\",\"nrgheat\":\"energy heating\",\"pumpcharacter\":\"boiler pump characteristic\",\"pumpdelay\":\"pump delay\",\"pumpmode\":\"boiler pump mode\",\"pumpmodmax\":\"boiler pump max power\",\"pumpmodmin\":\"boiler pump min power\",\"pumpontemp\":\"pump logic temperature\",\"reset\":\"reset\",\"selburnpow\":\"burner selected max power\",\"selflowtemp\":\"selected flow temperature\",\"summertemp\":\"summer temperature\"}]";
|
||||
auto expected_response =
|
||||
"[{\"info\":\"list all values (verbose)\",\"values\":\"list all values\",\"commands\":\"list all commands\",\"entities\":\"list all "
|
||||
"entities\",\"boil2hystoff\":\"hysteresis stage 2 off temperature\",\"boil2hyston\":\"hysteresis stage 2 on temperature\",\"boilhystoff\":\"hysteresis "
|
||||
"off temperature\",\"boilhyston\":\"hysteresis on temperature\",\"burnmaxpower\":\"burner max power\",\"burnminperiod\":\"burner min "
|
||||
"period\",\"burnminpower\":\"burner min power\",\"chimneysweeper\":\"chimney sweeper\",\"coldshot\":\"send a cold shot of "
|
||||
"water\",\"curvebase\":\"heatingcurve base\",\"curveend\":\"heatingcurve end\",\"curveon\":\"heatingcurve "
|
||||
"on\",\"dhw[n].activated\":\"activated\",\"dhw[n].chargeoptimization\":\"charge optimization\",\"dhw[n].circ\":\"circulation "
|
||||
"active\",\"dhw[n].circmode\":\"circulation pump mode\",\"dhw[n].circpump\":\"circulation pump "
|
||||
"available\",\"dhw[n].comfort\":\"comfort\",\"dhw[n].comfort1\":\"comfort mode\",\"dhw[n].dhwprio\":\"dhw "
|
||||
"priority\",\"dhw[n].disinfecting\":\"disinfecting\",\"dhw[n].disinfectiontemp\":\"disinfection temperature\",\"dhw[n].flowtempoffset\":\"flow "
|
||||
"temperature offset\",\"dhw[n].hystoff\":\"hysteresis off temperature\",\"dhw[n].hyston\":\"hysteresis on temperature\",\"dhw[n].maxpower\":\"max "
|
||||
"power\",\"dhw[n].maxtemp\":\"maximum temperature\",\"dhw[n].nrg\":\"energy\",\"dhw[n].onetime\":\"one time charging\",\"dhw[n].seltemp\":\"selected "
|
||||
"temperature\",\"dhw[n].seltemplow\":\"selected lower temperature\",\"dhw[n].seltempsingle\":\"single charge "
|
||||
"temperature\",\"dhw[n].tapactivated\":\"turn on/off\",\"dhw[n].tempecoplus\":\"selected eco+ temperature\",\"emergencyops\":\"emergency "
|
||||
"operation\",\"emergencytemp\":\"emergency temperature\",\"heatingactivated\":\"heating activated\",\"heatingoff\":\"force heating "
|
||||
"off\",\"heatingtemp\":\"heating temperature\",\"maintenance\":\"maintenance scheduled\",\"maintenancedate\":\"next maintenance "
|
||||
"date\",\"maintenancetime\":\"time to next maintenance\",\"nofrostmode\":\"nofrost mode\",\"nofrosttemp\":\"nofrost "
|
||||
"temperature\",\"nompower\":\"nominal Power\",\"nrgheat\":\"energy heating\",\"pumpcharacter\":\"boiler pump characteristic\",\"pumpdelay\":\"pump "
|
||||
"delay\",\"pumpmode\":\"boiler pump mode\",\"pumpmodmax\":\"boiler pump max power\",\"pumpmodmin\":\"boiler pump min power\",\"pumpontemp\":\"pump "
|
||||
"logic temperature\",\"reset\":\"reset\",\"selburnpow\":\"burner selected max power\",\"selflowtemp\":\"selected flow "
|
||||
"temperature\",\"summertemp\":\"summer temperature\"}]";
|
||||
TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/boiler/commands"));
|
||||
}
|
||||
|
||||
void test_3() {
|
||||
auto expected_response = "[{\"reset\":\"\",\"chimneysweeper\":\"\",\"heatingoff\":\"off\",\"heatingactive\":\"off\",\"tapwateractive\":\"on\",\"selflowtemp\":0,\"curflowtemp\":60.2,\"rettemp\":48.1,\"syspress\":1.4,\"burngas\":\"on\",\"burngas2\":\"off\",\"flamecurr\":37.4,\"fanwork\":\"on\",\"ignwork\":\"off\",\"oilpreheat\":\"off\",\"heatingpump\":\"on\",\"selburnpow\":115,\"curburnpow\":61,\"ubauptime\":3940268,\"servicecode\":\"=H\",\"servicecodenumber\":201,\"nompower\":0,\"nrgtotal\":0.0,\"nrgheat\":0.0,\"dhw\":{\"seltemp\":52,\"comfort\":\"hot\",\"flowtempoffset\":40,\"chargeoptimization\":\"off\",\"circpump\":\"off\",\"chargetype\":\"3-way valve\",\"hyston\":-5,\"disinfectiontemp\":70,\"circmode\":\"off\",\"circ\":\"off\",\"storagetemp1\":53.8,\"activated\":\"on\",\"3wayvalve\":\"on\",\"chargepump\":\"off\",\"nrg\":0.0}}]";
|
||||
auto expected_response =
|
||||
"[{\"reset\":\"\",\"chimneysweeper\":\"\",\"heatingoff\":\"off\",\"heatingactive\":\"off\",\"tapwateractive\":\"on\",\"selflowtemp\":0,\"curflowtemp\":"
|
||||
"60.2,\"rettemp\":48.1,\"syspress\":1.4,\"burngas\":\"on\",\"burngas2\":\"off\",\"flamecurr\":37.4,\"fanwork\":\"on\",\"ignwork\":\"off\","
|
||||
"\"oilpreheat\":\"off\",\"heatingpump\":\"on\",\"selburnpow\":115,\"curburnpow\":61,\"ubauptime\":3940268,\"servicecode\":\"=H\",\"servicecodenumber\":"
|
||||
"201,\"nompower\":0,\"nrgtotal\":0.0,\"nrgheat\":0.0,\"dhw\":{\"seltemp\":52,\"comfort\":\"hot\",\"flowtempoffset\":40,\"chargeoptimization\":\"off\","
|
||||
"\"circpump\":\"off\",\"chargetype\":\"3-way "
|
||||
"valve\",\"hyston\":-5,\"disinfectiontemp\":70,\"circmode\":\"off\",\"circ\":\"off\",\"storagetemp1\":53.8,\"activated\":\"on\",\"3wayvalve\":\"on\","
|
||||
"\"chargepump\":\"off\",\"nrg\":0.0}}]";
|
||||
TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/boiler/values"));
|
||||
}
|
||||
|
||||
void test_4() {
|
||||
auto expected_response = "[{\"reset (reset)\":\"\",\"chimney sweeper (chimneysweeper)\":\"\",\"force heating off (heatingoff)\":\"off\",\"is my heating on? (heatingactive)\":\"off\",\"tapwater active (tapwateractive)\":\"on\",\"selected flow temperature (selflowtemp)\":0,\"current flow temperature (curflowtemp)\":60.2,\"return temperature (rettemp)\":48.1,\"system pressure (syspress)\":1.4,\"gas (burngas)\":\"on\",\"gas stage 2 (burngas2)\":\"off\",\"flame current (flamecurr)\":37.4,\"fan (fanwork)\":\"on\",\"ignition (ignwork)\":\"off\",\"oil preheating (oilpreheat)\":\"off\",\"heating pump (heatingpump)\":\"on\",\"burner selected max power (selburnpow)\":115,\"burner current power (curburnpow)\":61,\"total UBA operating time (ubauptime)\":\"2736 days 7 hours 8 minutes\",\"service code (servicecode)\":\"=H\",\"service code number (servicecodenumber)\":201,\"dhw selected temperature (seltemp)\":52,\"dhw comfort (comfort)\":\"hot\",\"dhw flow temperature offset (flowtempoffset)\":40,\"dhw charge optimization (chargeoptimization)\":\"off\",\"dhw circulation pump available (circpump)\":\"off\",\"dhw charging type (chargetype)\":\"3-way valve\",\"dhw hysteresis on temperature (hyston)\":-5,\"dhw disinfection temperature (disinfectiontemp)\":70,\"dhw circulation pump mode (circmode)\":\"off\",\"dhw circulation active (circ)\":\"off\",\"dhw storage intern temperature (storagetemp1)\":53.8,\"dhw activated (activated)\":\"on\",\"dhw 3-way valve active (3wayvalve)\":\"on\",\"dhw charge pump (chargepump)\":\"off\",\"nominal Power (nompower)\":0,\"total energy (nrgtotal)\":0.0,\"energy heating (nrgheat)\":0.0,\"dhw energy (nrg)\":0.0}]";
|
||||
auto expected_response =
|
||||
"[{\"reset (reset)\":\"\",\"chimney sweeper (chimneysweeper)\":\"\",\"force heating off (heatingoff)\":\"off\",\"is my heating on? "
|
||||
"(heatingactive)\":\"off\",\"tapwater active (tapwateractive)\":\"on\",\"selected flow temperature (selflowtemp)\":0,\"current flow temperature "
|
||||
"(curflowtemp)\":60.2,\"return temperature (rettemp)\":48.1,\"system pressure (syspress)\":1.4,\"gas (burngas)\":\"on\",\"gas stage 2 "
|
||||
"(burngas2)\":\"off\",\"flame current (flamecurr)\":37.4,\"fan (fanwork)\":\"on\",\"ignition (ignwork)\":\"off\",\"oil preheating "
|
||||
"(oilpreheat)\":\"off\",\"heating pump (heatingpump)\":\"on\",\"burner selected max power (selburnpow)\":115,\"burner current power "
|
||||
"(curburnpow)\":61,\"total UBA operating time (ubauptime)\":\"2736 days 7 hours 8 minutes\",\"service code (servicecode)\":\"=H\",\"service code "
|
||||
"number (servicecodenumber)\":201,\"dhw selected temperature (seltemp)\":52,\"dhw comfort (comfort)\":\"hot\",\"dhw flow temperature offset "
|
||||
"(flowtempoffset)\":40,\"dhw charge optimization (chargeoptimization)\":\"off\",\"dhw circulation pump available (circpump)\":\"off\",\"dhw charging "
|
||||
"type (chargetype)\":\"3-way valve\",\"dhw hysteresis on temperature (hyston)\":-5,\"dhw disinfection temperature (disinfectiontemp)\":70,\"dhw "
|
||||
"circulation pump mode (circmode)\":\"off\",\"dhw circulation active (circ)\":\"off\",\"dhw storage intern temperature (storagetemp1)\":53.8,\"dhw "
|
||||
"activated (activated)\":\"on\",\"dhw 3-way valve active (3wayvalve)\":\"on\",\"dhw charge pump (chargepump)\":\"off\",\"nominal Power "
|
||||
"(nompower)\":0,\"total energy (nrgtotal)\":0.0,\"energy heating (nrgheat)\":0.0,\"dhw energy (nrg)\":0.0}]";
|
||||
TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/boiler/info"));
|
||||
}
|
||||
|
||||
void test_5() {
|
||||
auto expected_response = "[{\"name\":\"comfort\",\"fullname\":\"dhw comfort\",\"circuit\":\"dhw\",\"index\":0,\"enum\":[\"hot\",\"eco\",\"intelligent\"],\"value\":\"hot\",\"type\":\"enum\",\"readable\":true,\"writeable\":true,\"visible\":true}]";
|
||||
auto expected_response = "[{\"name\":\"comfort\",\"fullname\":\"dhw "
|
||||
"comfort\",\"circuit\":\"dhw\",\"index\":0,\"enum\":[\"hot\",\"eco\",\"intelligent\"],\"value\":\"hot\",\"type\":\"enum\","
|
||||
"\"readable\":true,\"writeable\":true,\"visible\":true}]";
|
||||
TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/boiler/comfort"));
|
||||
}
|
||||
|
||||
@@ -36,12 +84,15 @@ void test_7() {
|
||||
}
|
||||
|
||||
void test_8() {
|
||||
auto expected_response = "[{\"name\":\"outdoortemp\",\"fullname\":\"outside temperature\",\"circuit\":\"\",\"type\":\"number\",\"uom\":\"°C\",\"state_class\":\"measurement\",\"device_class\":\"temperature\",\"readable\":true,\"writeable\":false,\"visible\":true}]";
|
||||
auto expected_response = "[{\"name\":\"outdoortemp\",\"fullname\":\"outside "
|
||||
"temperature\",\"circuit\":\"\",\"type\":\"number\",\"uom\":\"°C\",\"state_class\":\"measurement\",\"device_class\":"
|
||||
"\"temperature\",\"readable\":true,\"writeable\":false,\"visible\":true}]";
|
||||
TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/boiler/outdoortemp"));
|
||||
}
|
||||
|
||||
void test_9() {
|
||||
auto expected_response = "[{\"name\":\"chargetype\",\"fullname\":\"dhw charging type\",\"circuit\":\"dhw\",\"index\":1,\"enum\":[\"chargepump\",\"3-way valve\"],\"value\":\"3-way valve\",\"type\":\"enum\",\"readable\":true,\"writeable\":false,\"visible\":true}]";
|
||||
auto expected_response = "[{\"name\":\"chargetype\",\"fullname\":\"dhw charging type\",\"circuit\":\"dhw\",\"index\":1,\"enum\":[\"chargepump\",\"3-way "
|
||||
"valve\"],\"value\":\"3-way valve\",\"type\":\"enum\",\"readable\":true,\"writeable\":false,\"visible\":true}]";
|
||||
TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/boiler/dhw/chargetype"));
|
||||
}
|
||||
|
||||
@@ -56,7 +107,9 @@ void test_11() {
|
||||
}
|
||||
|
||||
void test_12() {
|
||||
auto expected_response = "[{\"hc1\":{\"seltemp\":20.5,\"currtemp\":22.8,\"haclimate\":\"roomTemp\",\"modetype\":\"heat\",\"remotetemp\":null},\"hc2\":{\"seltemp\":20.6,\"currtemp\":22.9,\"haclimate\":\"roomTemp\",\"modetype\":\"eco\",\"remotetemp\":null},\"hc3\":{\"seltemp\":20.7,\"currtemp\":23.0,\"haclimate\":\"roomTemp\",\"modetype\":\"nofrost\",\"remotetemp\":null},\"dhw\":{}}]";
|
||||
auto expected_response = "[{\"hc1\":{\"seltemp\":20.5,\"currtemp\":22.8,\"haclimate\":\"roomTemp\",\"modetype\":\"heat\",\"remotetemp\":null},\"hc2\":{"
|
||||
"\"seltemp\":20.6,\"currtemp\":22.9,\"haclimate\":\"roomTemp\",\"modetype\":\"eco\",\"remotetemp\":null},\"hc3\":{\"seltemp\":20."
|
||||
"7,\"currtemp\":23.0,\"haclimate\":\"roomTemp\",\"modetype\":\"nofrost\",\"remotetemp\":null},\"dhw\":{}}]";
|
||||
TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/thermostat"));
|
||||
}
|
||||
|
||||
@@ -66,12 +119,16 @@ void test_13() {
|
||||
}
|
||||
|
||||
void test_14() {
|
||||
auto expected_response = "[{\"name\":\"seltemp\",\"fullname\":\"hc1 selected room temperature\",\"circuit\":\"hc1\",\"value\":20.5,\"type\":\"number\",\"min\":0,\"max\":30,\"uom\":\"°C\",\"state_class\":\"measurement\",\"device_class\":\"temperature\",\"readable\":true,\"writeable\":true,\"visible\":true}]";
|
||||
auto expected_response = "[{\"name\":\"seltemp\",\"fullname\":\"hc1 selected room "
|
||||
"temperature\",\"circuit\":\"hc1\",\"value\":20.5,\"type\":\"number\",\"min\":0,\"max\":30,\"uom\":\"°C\",\"state_class\":"
|
||||
"\"measurement\",\"device_class\":\"temperature\",\"readable\":true,\"writeable\":true,\"visible\":true}]";
|
||||
TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/thermostat/hc1/seltemp"));
|
||||
}
|
||||
|
||||
void test_15() {
|
||||
auto expected_response = "[{\"name\":\"seltemp\",\"fullname\":\"hc2 selected room temperature\",\"circuit\":\"hc2\",\"value\":20.6,\"type\":\"number\",\"min\":0,\"max\":30,\"uom\":\"°C\",\"state_class\":\"measurement\",\"device_class\":\"temperature\",\"readable\":true,\"writeable\":true,\"visible\":true}]";
|
||||
auto expected_response = "[{\"name\":\"seltemp\",\"fullname\":\"hc2 selected room "
|
||||
"temperature\",\"circuit\":\"hc2\",\"value\":20.6,\"type\":\"number\",\"min\":0,\"max\":30,\"uom\":\"°C\",\"state_class\":"
|
||||
"\"measurement\",\"device_class\":\"temperature\",\"readable\":true,\"writeable\":true,\"visible\":true}]";
|
||||
TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/thermostat/hc2/seltemp"));
|
||||
}
|
||||
|
||||
@@ -86,7 +143,8 @@ void test_17() {
|
||||
}
|
||||
|
||||
void test_18() {
|
||||
auto expected_response = "[{\"name\":\"test_seltemp\",\"fullname\":\"test_seltemp\",\"storage\":\"ram\",\"type\":\"number\",\"readable\":true,\"writeable\":true,\"visible\":true,\"ent_cat\":\"diagnostic\",\"value\":\"14\"}]";
|
||||
auto expected_response = "[{\"name\":\"test_seltemp\",\"fullname\":\"test_seltemp\",\"storage\":\"ram\",\"type\":\"number\",\"readable\":true,"
|
||||
"\"writeable\":true,\"visible\":true,\"ent_cat\":\"diagnostic\",\"value\":\"14\"}]";
|
||||
TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/custom/test_seltemp"));
|
||||
}
|
||||
|
||||
@@ -96,22 +154,71 @@ void test_19() {
|
||||
}
|
||||
|
||||
void test_20() {
|
||||
auto expected_response = "[{\"name\":\"test_custom\",\"fullname\":\"test_custom\",\"storage\":\"ems\",\"type\":\"number\",\"readable\":true,\"writeable\":true,\"visible\":true,\"device_id\":\"0x08\",\"type_id\":\"0x18\",\"offset\":0,\"factor\":1,\"ent_cat\":\"diagnostic\",\"uom\":\"°C\",\"state_class\":\"measurement\",\"device_class\":\"temperature\",\"value\":0.00}]";
|
||||
auto expected_response = "[{\"name\":\"test_custom\",\"fullname\":\"test_custom\",\"storage\":\"ems\",\"type\":\"number\",\"readable\":true,\"writeable\":"
|
||||
"true,\"visible\":true,\"device_id\":\"0x08\",\"type_id\":\"0x18\",\"offset\":0,\"factor\":1,\"ent_cat\":\"diagnostic\",\"uom\":"
|
||||
"\"°C\",\"state_class\":\"measurement\",\"device_class\":\"temperature\",\"value\":0.00}]";
|
||||
TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/custom/test_custom"));
|
||||
}
|
||||
|
||||
void test_21() {
|
||||
auto expected_response = "[{\"system\":{\"version\":\"dev\",\"uptime\":\"000+00:00:00.000\",\"uptimeSec\":0,\"resetReason\":\"Unknown / Unknown\"},\"network\":{\"network\":\"WiFi\",\"hostname\":\"ems-esp\",\"RSSI\":-23,\"TxPowerSetting\":0,\"staticIP\":false,\"lowBandwidth\":false,\"disableSleep\":true,\"enableMDNS\":true,\"enableCORS\":false},\"ntp\":{\"enabled\":true,\"server\":\"pool.ntp.org\",\"tzLabel\":\"Europe/London\",\"NTPStatus\":\"disconnected\"},\"ap\":{\"provisionMode\":\"always\",\"ssid\":\"ems-esp\"},\"mqtt\":{\"MQTTStatus\":\"disconnected\",\"MQTTPublishes\":0,\"MQTTQueued\":0,\"MQTTPublishFails\":0,\"MQTTReconnects\":0,\"enabled\":true,\"clientID\":\"ems-esp\",\"keepAlive\":60,\"cleanSession\":false,\"entityFormat\":1,\"base\":\"ems-esp\",\"discoveryPrefix\":\"homeassistant\",\"discoveryType\":0,\"nestedFormat\":1,\"haEnabled\":true,\"mqttQos\":0,\"mqttRetain\":false,\"publishTimeHeartbeat\":60,\"publishTimeBoiler\":10,\"publishTimeThermostat\":10,\"publishTimeSolar\":10,\"publishTimeMixer\":10,\"publishTimeWater\":0,\"publishTimeOther\":10,\"publishTimeSensor\":10,\"publishSingle\":false,\"publish2command\":false,\"sendResponse\":false},\"syslog\":{\"enabled\":false},\"sensor\":{\"temperatureSensors\":1,\"temperatureSensorReads\":0,\"temperatureSensorFails\":0,\"analogSensors\":5,\"analogSensorReads\":0,\"analogSensorFails\":0},\"api\":{\"APICalls\":0,\"APIFails\":0},\"bus\":{\"busStatus\":\"connected\",\"busProtocol\":\"Buderus\",\"busTelegramsReceived\":8,\"busReads\":0,\"busWrites\":0,\"busIncompleteTelegrams\":0,\"busReadsFailed\":0,\"busWritesFailed\":0,\"busRxLineQuality\":100,\"busTxLineQuality\":100},\"settings\":{\"boardProfile\":\"S32\",\"locale\":\"en\",\"txMode\":8,\"emsBusID\":11,\"showerTimer\":false,\"showerMinDuration\":180,\"showerAlert\":false,\"hideLed\":false,\"noTokenApi\":false,\"readonlyMode\":false,\"fahrenheit\":false,\"dallasParasite\":false,\"boolFormat\":1,\"boolDashboard\":1,\"enumFormat\":1,\"analogEnabled\":true,\"telnetEnabled\":true,\"maxWebLogBuffer\":25,\"modbusEnabled\":false,\"forceHeatingOff\":false,\"developerMode\":false},\"devices\":[{\"type\":\"boiler\",\"name\":\"My Custom Boiler\",\"deviceID\":\"0x08\",\"productID\":123,\"brand\":\"\",\"version\":\"01.00\",\"entities\":39,\"handlersReceived\":\"0x18\",\"handlersFetched\":\"0x14 0x33\",\"handlersPending\":\"0xBF 0x10 0x11 0xC2 0xC6 0x15 0x1C 0x19 0x1A 0x35 0x34 0x2A 0xD1 0xE3 0xE4 0xE5 0xE9 0x02E0 0x2E 0x3B\"},{\"type\":\"thermostat\",\"name\":\"FW120\",\"deviceID\":\"0x10\",\"productID\":192,\"brand\":\"\",\"version\":\"01.00\",\"entities\":15,\"handlersReceived\":\"0x016F\",\"handlersFetched\":\"0x0170 0x0171\",\"handlersPending\":\"0xA3 0x06 0xA2 0x12 0x13 0x0172 0x0165 0x0168\"},{\"type\":\"temperaturesensor\",\"name\":\"temperaturesensor\",\"entities\":1},{\"type\":\"analogsensor\",\"name\":\"analogsensor\",\"entities\":5},{\"type\":\"scheduler\",\"name\":\"scheduler\",\"entities\":2},{\"type\":\"custom\",\"name\":\"custom\",\"entities\":4}]}]";
|
||||
auto expected_response =
|
||||
"[{\"system\":{\"version\":\"dev\",\"uptime\":\"000+00:00:00.000\",\"uptimeSec\":0,\"resetReason\":\"Unknown / "
|
||||
"Unknown\"},\"network\":{\"network\":\"WiFi\",\"hostname\":\"ems-esp\",\"RSSI\":-23,\"TxPowerSetting\":0,\"staticIP\":false,\"lowBandwidth\":false,"
|
||||
"\"disableSleep\":true,\"enableMDNS\":true,\"enableCORS\":false},\"ntp\":{\"enabled\":true,\"server\":\"pool.ntp.org\",\"tzLabel\":\"Europe/"
|
||||
"London\",\"NTPStatus\":\"disconnected\"},\"ap\":{\"provisionMode\":\"always\",\"ssid\":\"ems-esp\"},\"mqtt\":{\"MQTTStatus\":\"disconnected\","
|
||||
"\"MQTTPublishes\":0,\"MQTTQueued\":0,\"MQTTPublishFails\":0,\"MQTTReconnects\":0,\"enabled\":true,\"clientID\":\"ems-esp\",\"keepAlive\":60,"
|
||||
"\"cleanSession\":false,\"entityFormat\":1,\"base\":\"ems-esp\",\"discoveryPrefix\":\"homeassistant\",\"discoveryType\":0,\"nestedFormat\":1,"
|
||||
"\"haEnabled\":true,\"mqttQos\":0,\"mqttRetain\":false,\"publishTimeHeartbeat\":60,\"publishTimeBoiler\":10,\"publishTimeThermostat\":10,"
|
||||
"\"publishTimeSolar\":10,\"publishTimeMixer\":10,\"publishTimeWater\":0,\"publishTimeOther\":10,\"publishTimeSensor\":10,\"publishSingle\":false,"
|
||||
"\"publish2command\":false,\"sendResponse\":false},\"syslog\":{\"enabled\":false},\"sensor\":{\"temperatureSensors\":3,\"temperatureSensorReads\":0,"
|
||||
"\"temperatureSensorFails\":0,\"analogSensors\":5,\"analogSensorReads\":0,\"analogSensorFails\":0},\"api\":{\"APICalls\":0,\"APIFails\":0},\"bus\":{"
|
||||
"\"busStatus\":\"connected\",\"busProtocol\":\"Buderus\",\"busTelegramsReceived\":8,\"busReads\":0,\"busWrites\":0,\"busIncompleteTelegrams\":0,"
|
||||
"\"busReadsFailed\":0,\"busWritesFailed\":0,\"busRxLineQuality\":100,\"busTxLineQuality\":100},\"settings\":{\"boardProfile\":\"default\",\"locale\":"
|
||||
"\"en\",\"txMode\":8,\"emsBusID\":11,\"showerTimer\":false,\"showerMinDuration\":180,\"showerAlert\":false,\"hideLed\":false,\"noTokenApi\":false,"
|
||||
"\"readonlyMode\":false,\"fahrenheit\":false,\"dallasParasite\":false,\"boolFormat\":1,\"boolDashboard\":1,\"enumFormat\":1,\"analogEnabled\":true,"
|
||||
"\"telnetEnabled\":true,\"maxWebLogBuffer\":25,\"modbusEnabled\":false,\"forceHeatingOff\":false,\"developerMode\":false},\"devices\":[{\"type\":"
|
||||
"\"boiler\",\"name\":\"My Custom "
|
||||
"Boiler\",\"deviceID\":\"0x08\",\"productID\":123,\"brand\":\"\",\"version\":\"01.00\",\"entities\":39,\"handlersReceived\":\"0x18\","
|
||||
"\"handlersFetched\":\"0x14 0x33\",\"handlersPending\":\"0xBF 0x10 0x11 0xC2 0xC6 0x15 0x1C 0x19 0x1A 0x35 0x34 0x2A 0xD1 0xE3 0xE4 0xE5 0xE9 0x02E0 "
|
||||
"0x2E "
|
||||
"0x3B\"},{\"type\":\"thermostat\",\"name\":\"FW120\",\"deviceID\":\"0x10\",\"productID\":192,\"brand\":\"\",\"version\":\"01.00\",\"entities\":15,"
|
||||
"\"handlersReceived\":\"0x016F\",\"handlersFetched\":\"0x0170 0x0171\",\"handlersPending\":\"0xA3 0x06 0xA2 0x12 0x13 0x0172 0x0165 "
|
||||
"0x0168\"},{\"type\":\"temperaturesensor\",\"name\":\"temperaturesensor\",\"entities\":3},{\"type\":\"analogsensor\",\"name\":\"analogsensor\","
|
||||
"\"entities\":5},{\"type\":\"scheduler\",\"name\":\"scheduler\",\"entities\":2},{\"type\":\"custom\",\"name\":\"custom\",\"entities\":4}]}]";
|
||||
TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/system"));
|
||||
}
|
||||
|
||||
void test_22() {
|
||||
auto expected_response = "[{\"system\":{\"version\":\"dev\",\"uptime\":\"000+00:00:00.000\",\"uptimeSec\":0,\"resetReason\":\"Unknown / Unknown\"},\"network\":{\"network\":\"WiFi\",\"hostname\":\"ems-esp\",\"RSSI\":-23,\"TxPowerSetting\":0,\"staticIP\":false,\"lowBandwidth\":false,\"disableSleep\":true,\"enableMDNS\":true,\"enableCORS\":false},\"ntp\":{\"enabled\":true,\"server\":\"pool.ntp.org\",\"tzLabel\":\"Europe/London\",\"NTPStatus\":\"disconnected\"},\"ap\":{\"provisionMode\":\"always\",\"ssid\":\"ems-esp\"},\"mqtt\":{\"MQTTStatus\":\"disconnected\",\"MQTTPublishes\":0,\"MQTTQueued\":0,\"MQTTPublishFails\":0,\"MQTTReconnects\":0,\"enabled\":true,\"clientID\":\"ems-esp\",\"keepAlive\":60,\"cleanSession\":false,\"entityFormat\":1,\"base\":\"ems-esp\",\"discoveryPrefix\":\"homeassistant\",\"discoveryType\":0,\"nestedFormat\":1,\"haEnabled\":true,\"mqttQos\":0,\"mqttRetain\":false,\"publishTimeHeartbeat\":60,\"publishTimeBoiler\":10,\"publishTimeThermostat\":10,\"publishTimeSolar\":10,\"publishTimeMixer\":10,\"publishTimeWater\":0,\"publishTimeOther\":10,\"publishTimeSensor\":10,\"publishSingle\":false,\"publish2command\":false,\"sendResponse\":false},\"syslog\":{\"enabled\":false},\"sensor\":{\"temperatureSensors\":1,\"temperatureSensorReads\":0,\"temperatureSensorFails\":0,\"analogSensors\":5,\"analogSensorReads\":0,\"analogSensorFails\":0},\"api\":{\"APICalls\":0,\"APIFails\":0},\"bus\":{\"busStatus\":\"connected\",\"busProtocol\":\"Buderus\",\"busTelegramsReceived\":8,\"busReads\":0,\"busWrites\":0,\"busIncompleteTelegrams\":0,\"busReadsFailed\":0,\"busWritesFailed\":0,\"busRxLineQuality\":100,\"busTxLineQuality\":100},\"settings\":{\"boardProfile\":\"S32\",\"locale\":\"en\",\"txMode\":8,\"emsBusID\":11,\"showerTimer\":false,\"showerMinDuration\":180,\"showerAlert\":false,\"hideLed\":false,\"noTokenApi\":false,\"readonlyMode\":false,\"fahrenheit\":false,\"dallasParasite\":false,\"boolFormat\":1,\"boolDashboard\":1,\"enumFormat\":1,\"analogEnabled\":true,\"telnetEnabled\":true,\"maxWebLogBuffer\":25,\"modbusEnabled\":false,\"forceHeatingOff\":false,\"developerMode\":false},\"devices\":[{\"type\":\"boiler\",\"name\":\"My Custom Boiler\",\"deviceID\":\"0x08\",\"productID\":123,\"brand\":\"\",\"version\":\"01.00\",\"entities\":39,\"handlersReceived\":\"0x18\",\"handlersFetched\":\"0x14 0x33\",\"handlersPending\":\"0xBF 0x10 0x11 0xC2 0xC6 0x15 0x1C 0x19 0x1A 0x35 0x34 0x2A 0xD1 0xE3 0xE4 0xE5 0xE9 0x02E0 0x2E 0x3B\"},{\"type\":\"thermostat\",\"name\":\"FW120\",\"deviceID\":\"0x10\",\"productID\":192,\"brand\":\"\",\"version\":\"01.00\",\"entities\":15,\"handlersReceived\":\"0x016F\",\"handlersFetched\":\"0x0170 0x0171\",\"handlersPending\":\"0xA3 0x06 0xA2 0x12 0x13 0x0172 0x0165 0x0168\"},{\"type\":\"temperaturesensor\",\"name\":\"temperaturesensor\",\"entities\":1},{\"type\":\"analogsensor\",\"name\":\"analogsensor\",\"entities\":5},{\"type\":\"scheduler\",\"name\":\"scheduler\",\"entities\":2},{\"type\":\"custom\",\"name\":\"custom\",\"entities\":4}]}]";
|
||||
auto expected_response =
|
||||
"[{\"system\":{\"version\":\"dev\",\"uptime\":\"000+00:00:00.000\",\"uptimeSec\":0,\"resetReason\":\"Unknown / "
|
||||
"Unknown\"},\"network\":{\"network\":\"WiFi\",\"hostname\":\"ems-esp\",\"RSSI\":-23,\"TxPowerSetting\":0,\"staticIP\":false,\"lowBandwidth\":false,"
|
||||
"\"disableSleep\":true,\"enableMDNS\":true,\"enableCORS\":false},\"ntp\":{\"enabled\":true,\"server\":\"pool.ntp.org\",\"tzLabel\":\"Europe/"
|
||||
"London\",\"NTPStatus\":\"disconnected\"},\"ap\":{\"provisionMode\":\"always\",\"ssid\":\"ems-esp\"},\"mqtt\":{\"MQTTStatus\":\"disconnected\","
|
||||
"\"MQTTPublishes\":0,\"MQTTQueued\":0,\"MQTTPublishFails\":0,\"MQTTReconnects\":0,\"enabled\":true,\"clientID\":\"ems-esp\",\"keepAlive\":60,"
|
||||
"\"cleanSession\":false,\"entityFormat\":1,\"base\":\"ems-esp\",\"discoveryPrefix\":\"homeassistant\",\"discoveryType\":0,\"nestedFormat\":1,"
|
||||
"\"haEnabled\":true,\"mqttQos\":0,\"mqttRetain\":false,\"publishTimeHeartbeat\":60,\"publishTimeBoiler\":10,\"publishTimeThermostat\":10,"
|
||||
"\"publishTimeSolar\":10,\"publishTimeMixer\":10,\"publishTimeWater\":0,\"publishTimeOther\":10,\"publishTimeSensor\":10,\"publishSingle\":false,"
|
||||
"\"publish2command\":false,\"sendResponse\":false},\"syslog\":{\"enabled\":false},\"sensor\":{\"temperatureSensors\":3,\"temperatureSensorReads\":0,"
|
||||
"\"temperatureSensorFails\":0,\"analogSensors\":5,\"analogSensorReads\":0,\"analogSensorFails\":0},\"api\":{\"APICalls\":0,\"APIFails\":0},\"bus\":{"
|
||||
"\"busStatus\":\"connected\",\"busProtocol\":\"Buderus\",\"busTelegramsReceived\":8,\"busReads\":0,\"busWrites\":0,\"busIncompleteTelegrams\":0,"
|
||||
"\"busReadsFailed\":0,\"busWritesFailed\":0,\"busRxLineQuality\":100,\"busTxLineQuality\":100},\"settings\":{\"boardProfile\":\"default\",\"locale\":"
|
||||
"\"en\",\"txMode\":8,\"emsBusID\":11,\"showerTimer\":false,\"showerMinDuration\":180,\"showerAlert\":false,\"hideLed\":false,\"noTokenApi\":false,"
|
||||
"\"readonlyMode\":false,\"fahrenheit\":false,\"dallasParasite\":false,\"boolFormat\":1,\"boolDashboard\":1,\"enumFormat\":1,\"analogEnabled\":true,"
|
||||
"\"telnetEnabled\":true,\"maxWebLogBuffer\":25,\"modbusEnabled\":false,\"forceHeatingOff\":false,\"developerMode\":false},\"devices\":[{\"type\":"
|
||||
"\"boiler\",\"name\":\"My Custom "
|
||||
"Boiler\",\"deviceID\":\"0x08\",\"productID\":123,\"brand\":\"\",\"version\":\"01.00\",\"entities\":39,\"handlersReceived\":\"0x18\","
|
||||
"\"handlersFetched\":\"0x14 0x33\",\"handlersPending\":\"0xBF 0x10 0x11 0xC2 0xC6 0x15 0x1C 0x19 0x1A 0x35 0x34 0x2A 0xD1 0xE3 0xE4 0xE5 0xE9 0x02E0 "
|
||||
"0x2E "
|
||||
"0x3B\"},{\"type\":\"thermostat\",\"name\":\"FW120\",\"deviceID\":\"0x10\",\"productID\":192,\"brand\":\"\",\"version\":\"01.00\",\"entities\":15,"
|
||||
"\"handlersReceived\":\"0x016F\",\"handlersFetched\":\"0x0170 0x0171\",\"handlersPending\":\"0xA3 0x06 0xA2 0x12 0x13 0x0172 0x0165 "
|
||||
"0x0168\"},{\"type\":\"temperaturesensor\",\"name\":\"temperaturesensor\",\"entities\":3},{\"type\":\"analogsensor\",\"name\":\"analogsensor\","
|
||||
"\"entities\":5},{\"type\":\"scheduler\",\"name\":\"scheduler\",\"entities\":2},{\"type\":\"custom\",\"name\":\"custom\",\"entities\":4}]}]";
|
||||
TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/system/info"));
|
||||
}
|
||||
|
||||
void test_23() {
|
||||
auto expected_response = "[{\"name\":\"locale\",\"circuit\":\"settings\",\"readable\":true,\"writeable\":false,\"visible\":true,\"value\":\"en\",\"type\":\"string\"}]";
|
||||
auto expected_response =
|
||||
"[{\"name\":\"locale\",\"circuit\":\"settings\",\"readable\":true,\"writeable\":false,\"visible\":true,\"value\":\"en\",\"type\":\"string\"}]";
|
||||
TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/system/settings/locale"));
|
||||
}
|
||||
|
||||
@@ -121,7 +228,8 @@ void test_24() {
|
||||
}
|
||||
|
||||
void test_25() {
|
||||
auto expected_response = "[{\"network\":\"WiFi\",\"hostname\":\"ems-esp\",\"RSSI\":\"-23\",\"TxPowerSetting\":\"0\",\"staticIP\":\"false\",\"lowBandwidth\":\"false\",\"disableSleep\":\"true\",\"enableMDNS\":\"true\",\"enableCORS\":\"false\"}]";
|
||||
auto expected_response = "[{\"network\":\"WiFi\",\"hostname\":\"ems-esp\",\"RSSI\":\"-23\",\"TxPowerSetting\":\"0\",\"staticIP\":\"false\","
|
||||
"\"lowBandwidth\":\"false\",\"disableSleep\":\"true\",\"enableMDNS\":\"true\",\"enableCORS\":\"false\"}]";
|
||||
TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/system/network/values"));
|
||||
}
|
||||
|
||||
@@ -136,7 +244,8 @@ void test_27() {
|
||||
}
|
||||
|
||||
void test_28() {
|
||||
auto expected_response = "[{\"name\":\"test_scheduler\",\"fullname\":\"test_scheduler\",\"type\":\"boolean\",\"value\":\"on\",\"time\":\"12:00\",\"command\":\"system/fetch\",\"cmd_data\":\"10\",\"readable\":true,\"writeable\":true,\"visible\":true}]";
|
||||
auto expected_response = "[{\"name\":\"test_scheduler\",\"fullname\":\"test_scheduler\",\"type\":\"boolean\",\"value\":\"on\",\"time\":\"12:00\","
|
||||
"\"command\":\"system/fetch\",\"cmd_data\":\"10\",\"readable\":true,\"writeable\":true,\"visible\":true}]";
|
||||
TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/scheduler/test_scheduler"));
|
||||
}
|
||||
|
||||
@@ -151,12 +260,14 @@ void test_30() {
|
||||
}
|
||||
|
||||
void test_31() {
|
||||
auto expected_response = "[{\"id\":\"0B_0C0D_0E0F_1011\",\"name\":\"test_tempsensor2\",\"fullname\":\"test_tempsensor2\",\"value\":45.6,\"type\":\"number\",\"uom\":\"°C\",\"readable\":true,\"writeable\":false,\"visible\":true,\"is_system\":false}]";
|
||||
auto expected_response = "[{\"id\":\"0B_0C0D_0E0F_1011\",\"name\":\"test_tempsensor2\",\"fullname\":\"test_tempsensor2\",\"value\":45.6,\"type\":"
|
||||
"\"number\",\"uom\":\"°C\",\"readable\":true,\"writeable\":false,\"visible\":true,\"is_system\":false}]";
|
||||
TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/temperaturesensor/test_tempsensor2"));
|
||||
}
|
||||
|
||||
void test_32() {
|
||||
auto expected_response = "[{\"id\":\"0B_0C0D_0E0F_1011\",\"name\":\"test_tempsensor2\",\"fullname\":\"test_tempsensor2\",\"value\":45.6,\"type\":\"number\",\"uom\":\"°C\",\"readable\":true,\"writeable\":false,\"visible\":true,\"is_system\":false}]";
|
||||
auto expected_response = "[{\"id\":\"0B_0C0D_0E0F_1011\",\"name\":\"test_tempsensor2\",\"fullname\":\"test_tempsensor2\",\"value\":45.6,\"type\":"
|
||||
"\"number\",\"uom\":\"°C\",\"readable\":true,\"writeable\":false,\"visible\":true,\"is_system\":false}]";
|
||||
TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/temperaturesensor/0B_0C0D_0E0F_1011"));
|
||||
}
|
||||
|
||||
@@ -176,7 +287,9 @@ void test_35() {
|
||||
}
|
||||
|
||||
void test_36() {
|
||||
auto expected_response = "[{\"name\":\"test_analogsensor1\",\"fullname\":\"test_analogsensor1\",\"gpio\":36,\"type\":\"number\",\"analog\":\"adc\",\"value\":0,\"readable\":true,\"writeable\":false,\"visible\":true,\"is_system\":false,\"offset\":0,\"factor\":0.2,\"uom\":\"mV\"}]";
|
||||
auto expected_response =
|
||||
"[{\"name\":\"test_analogsensor1\",\"fullname\":\"test_analogsensor1\",\"gpio\":36,\"type\":\"number\",\"analog\":\"adc\",\"value\":0,\"readable\":"
|
||||
"true,\"writeable\":false,\"visible\":true,\"is_system\":false,\"offset\":0,\"factor\":0.2,\"uom\":\"mV\"}]";
|
||||
TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/analogsensor/test_analogsensor1"));
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "core/shuntingYard.h"
|
||||
|
||||
void run_shuntingYard_test(const std::string & expected, const std::string & actual) {
|
||||
TEST_ASSERT_EQUAL_STRING(expected.c_str(), compute(actual).c_str());
|
||||
TEST_ASSERT_EQUAL_STRING(expected.c_str(), emsesp::compute(actual).c_str());
|
||||
}
|
||||
|
||||
void shuntingYard_test1() {
|
||||
|
||||
Reference in New Issue
Block a user