Merge pull request #1790 from proddy/feat_modules

Feat modules
This commit is contained in:
Proddy
2024-06-08 12:27:23 +02:00
committed by GitHub
57 changed files with 3928 additions and 2676 deletions

View File

@@ -30,7 +30,7 @@
"@mui/material": "^5.15.19", "@mui/material": "^5.15.19",
"@table-library/react-table-library": "4.1.7", "@table-library/react-table-library": "4.1.7",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/node": "^20.14.1", "@types/node": "^20.14.2",
"@types/react": "^18.3.3", "@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@types/react-router-dom": "^5.3.3", "@types/react-router-dom": "^5.3.3",
@@ -50,7 +50,7 @@
"typescript": "^5.4.5" "typescript": "^5.4.5"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.24.6", "@babel/core": "^7.24.7",
"@eslint/js": "^9.4.0", "@eslint/js": "^9.4.0",
"@preact/compat": "^17.1.2", "@preact/compat": "^17.1.2",
"@preact/preset-vite": "^2.8.2", "@preact/preset-vite": "^2.8.2",
@@ -60,11 +60,11 @@
"eslint": "^9.4.0", "eslint": "^9.4.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"preact": "^10.22.0", "preact": "^10.22.0",
"prettier": "^3.3.0", "prettier": "^3.3.1",
"rollup-plugin-visualizer": "^5.12.0", "rollup-plugin-visualizer": "^5.12.0",
"terser": "^5.31.0", "terser": "^5.31.1",
"typescript-eslint": "^7.12.0", "typescript-eslint": "^7.12.0",
"vite": "^5.2.12", "vite": "^5.2.13",
"vite-plugin-imagemin": "^0.6.1", "vite-plugin-imagemin": "^0.6.1",
"vite-tsconfig-paths": "^4.3.2" "vite-tsconfig-paths": "^4.3.2"
}, },

View File

@@ -10,8 +10,10 @@ import MemoryIcon from '@mui/icons-material/Memory';
import PermScanWifiIcon from '@mui/icons-material/PermScanWifi'; import PermScanWifiIcon from '@mui/icons-material/PermScanWifi';
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew'; import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
import RefreshIcon from '@mui/icons-material/Refresh'; import RefreshIcon from '@mui/icons-material/Refresh';
import RouterIcon from '@mui/icons-material/Router';
import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna'; import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
import TimerIcon from '@mui/icons-material/Timer'; import TimerIcon from '@mui/icons-material/Timer';
import WifiIcon from '@mui/icons-material/Wifi';
import { import {
Avatar, Avatar,
Box, Box,
@@ -38,7 +40,7 @@ import ListMenuItem from 'components/layout/ListMenuItem';
import { AuthenticatedContext } from 'contexts/authentication'; import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import { busConnectionStatus } from 'project/types'; import { busConnectionStatus } from 'project/types';
import { NTPSyncStatus } from 'types'; import { NTPSyncStatus, NetworkConnectionStatus } from 'types';
import RestartMonitor from './RestartMonitor'; import RestartMonitor from './RestartMonitor';
@@ -151,6 +153,46 @@ const SystemStatus: FC = () => {
} }
}; };
const networkStatusHighlight = () => {
switch (data.network_status) {
case NetworkConnectionStatus.WIFI_STATUS_IDLE:
case NetworkConnectionStatus.WIFI_STATUS_DISCONNECTED:
case NetworkConnectionStatus.WIFI_STATUS_NO_SHIELD:
return theme.palette.info.main;
case NetworkConnectionStatus.WIFI_STATUS_CONNECTED:
case NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED:
return theme.palette.success.main;
case NetworkConnectionStatus.WIFI_STATUS_CONNECT_FAILED:
case NetworkConnectionStatus.WIFI_STATUS_CONNECTION_LOST:
return theme.palette.error.main;
default:
return theme.palette.warning.main;
}
};
const networkStatus = () => {
switch (data.network_status) {
case NetworkConnectionStatus.WIFI_STATUS_NO_SHIELD:
return LL.INACTIVE(1);
case NetworkConnectionStatus.WIFI_STATUS_IDLE:
return LL.IDLE();
case NetworkConnectionStatus.WIFI_STATUS_NO_SSID_AVAIL:
return 'No SSID Available';
case NetworkConnectionStatus.WIFI_STATUS_CONNECTED:
return LL.CONNECTED(0) + ' (WiFi, ' + data.wifi_rssi + ' dBm)';
case NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED:
return LL.CONNECTED(0) + ' (Ethernet)';
case NetworkConnectionStatus.WIFI_STATUS_CONNECT_FAILED:
return LL.CONNECTED(1) + ' ' + LL.FAILED(0);
case NetworkConnectionStatus.WIFI_STATUS_CONNECTION_LOST:
return LL.CONNECTED(1) + ' ' + LL.LOST();
case NetworkConnectionStatus.WIFI_STATUS_DISCONNECTED:
return LL.DISCONNECTED();
default:
return LL.UNKNOWN();
}
};
const activeHighlight = (value: boolean) => const activeHighlight = (value: boolean) =>
value ? theme.palette.success.main : theme.palette.info.main; value ? theme.palette.success.main : theme.palette.info.main;
@@ -296,7 +338,7 @@ const SystemStatus: FC = () => {
<Button <Button
startIcon={<PowerSettingsNewIcon />} startIcon={<PowerSettingsNewIcon />}
variant="outlined" variant="outlined"
color="primary" color="error"
onClick={() => setConfirmRestart(true)} onClick={() => setConfirmRestart(true)}
> >
{LL.RESTART()} {LL.RESTART()}
@@ -355,6 +397,20 @@ const SystemStatus: FC = () => {
/> />
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
<ListMenuItem
disabled={!me.admin}
icon={
data.network_status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED
? WifiIcon
: RouterIcon
}
bgcolor={networkStatusHighlight()}
label={LL.STATUS_OF(LL.NETWORK(1))}
text={networkStatus()}
to="/settings/network/status"
/>
<Divider variant="inset" component="li" />
<ListMenuItem <ListMenuItem
disabled={!me.admin} disabled={!me.admin}
icon={DeviceHubIcon} icon={DeviceHubIcon}

View File

@@ -325,7 +325,10 @@ const de: Translation = {
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
SECURITY_1: 'Add or remove users', // TODO translate SECURITY_1: 'Add or remove users', // TODO translate
UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate
MODULES: 'Module' MODULES: 'Module', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate
MODULES_DESCRIPTION: 'Click on the Module to Activate or de-activate EMS-ESP library modules', // TODO translate
MODULES_NONE: 'No external modules detected' // TODO translate
}; };
export default de; export default de;

View File

@@ -325,7 +325,10 @@ const en: Translation = {
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings',
SECURITY_1: 'Add or remove users', SECURITY_1: 'Add or remove users',
UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware',
MODULES: 'Modules' MODULES: 'Modules',
MODULES_UPDATED: 'Modules updated',
MODULES_DESCRIPTION: 'Click on the Module to Activate or de-activate EMS-ESP library modules',
MODULES_NONE: 'No external modules detected'
}; };
export default en; export default en;

View File

@@ -325,7 +325,10 @@ const fr: Translation = {
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
SECURITY_1: 'Add or remove users', // TODO translate SECURITY_1: 'Add or remove users', // TODO translate
UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate
MODULES: 'Modules' // TODO translate MODULES: 'Module', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate
MODULES_DESCRIPTION: 'Click on the Module to Activate or de-activate EMS-ESP library modules', // TODO translate
MODULES_NONE: 'No external modules detected' // TODO translate
}; };
export default fr; export default fr;

View File

@@ -325,7 +325,10 @@ const it: Translation = {
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
SECURITY_1: 'Add or remove users', // TODO translate SECURITY_1: 'Add or remove users', // TODO translate
UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate
MODULES: 'Modules' // TODO translate MODULES: 'Module', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate
MODULES_DESCRIPTION: 'Click on the Module to Activate or de-activate EMS-ESP library modules', // TODO translate
MODULES_NONE: 'No external modules detected' // TODO translate
}; };
export default it; export default it;

View File

@@ -325,7 +325,10 @@ const nl: Translation = {
APPLICATION_SETTINGS_1: 'Applicatie-instellingen wijzigen', APPLICATION_SETTINGS_1: 'Applicatie-instellingen wijzigen',
SECURITY_1: 'Gebruikers toevoegen of verwijderen', SECURITY_1: 'Gebruikers toevoegen of verwijderen',
UPLOAD_DOWNLOAD_1: 'Upload-/downloadinstellingen en firmware', UPLOAD_DOWNLOAD_1: 'Upload-/downloadinstellingen en firmware',
MODULES: 'Modules' MODULES: 'Module',
MODULES_UPDATED: 'Modules geüpdatet',
MODULES_DESCRIPTION: 'Klik op de module om EMS-ESP library modules te activeren of te deactiveren',
MODULES_NONE: 'Geen externe modules gedetecteerd'
}; };
export default nl; export default nl;

View File

@@ -325,7 +325,10 @@ const no: Translation = {
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
SECURITY_1: 'Add or remove users', // TODO translate SECURITY_1: 'Add or remove users', // TODO translate
UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate
MODULES: 'Modules' // TODO translate MODULES: 'Module', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate
MODULES_DESCRIPTION: 'Click on the Module to Activate or de-activate EMS-ESP library modules', // TODO translate
MODULES_NONE: 'No external modules detected' // TODO translate
}; };
export default no; export default no;

View File

@@ -325,7 +325,10 @@ const pl: BaseTranslation = {
APPLICATION_SETTINGS_1: 'Modyfikacja ustawień aplikacji EMS-ESP', APPLICATION_SETTINGS_1: 'Modyfikacja ustawień aplikacji EMS-ESP',
SECURITY_1: 'Dodawanie i usuwanie użytkowników', SECURITY_1: 'Dodawanie i usuwanie użytkowników',
UPLOAD_DOWNLOAD_1: 'Wysyłanie/pobieranie ustawień i firmware', UPLOAD_DOWNLOAD_1: 'Wysyłanie/pobieranie ustawień i firmware',
MODULES: 'Moduły' MODULES: 'Module', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate
MODULES_DESCRIPTION: 'Click on the Module to Activate or de-activate EMS-ESP library modules', // TODO translate
MODULES_NONE: 'No external modules detected' // TODO translate
}; };
export default pl; export default pl;

View File

@@ -325,7 +325,10 @@ const sk: Translation = {
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
SECURITY_1: 'Add or remove users', // TODO translate SECURITY_1: 'Add or remove users', // TODO translate
UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate
MODULES: 'Modules' // TODO translate MODULES: 'Module', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate
MODULES_DESCRIPTION: 'Click on the Module to Activate or de-activate EMS-ESP library modules', // TODO translate
MODULES_NONE: 'No external modules detected' // TODO translate
}; };
export default sk; export default sk;

View File

@@ -325,7 +325,10 @@ const sv: Translation = {
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
SECURITY_1: 'Add or remove users', // TODO translate SECURITY_1: 'Add or remove users', // TODO translate
UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate
MODULES: 'Modules' // TODO translate MODULES: 'Module', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate
MODULES_DESCRIPTION: 'Click on the Module to Activate or de-activate EMS-ESP library modules', // TODO translate
MODULES_NONE: 'No external modules detected' // TODO translate
}; };
export default sv; export default sv;

View File

@@ -325,7 +325,10 @@ const tr: Translation = {
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
SECURITY_1: 'Add or remove users', // TODO translate SECURITY_1: 'Add or remove users', // TODO translate
UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', // TODO translate
MODULES: 'Modules' // TODO translate MODULES: 'Module', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate
MODULES_DESCRIPTION: 'Click on the Module to Activate or de-activate EMS-ESP library modules', // TODO translate
MODULES_NONE: 'No external modules detected' // TODO translate
}; };
export default tr; export default tr;

View File

@@ -1,13 +1,16 @@
import { useState } from 'react'; import { useCallback, useState } from 'react';
import type { FC } from 'react'; import type { FC } from 'react';
import { useBlocker } from 'react-router-dom'; import { useBlocker } from 'react-router-dom';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
import CircleIcon from '@mui/icons-material/Circle'; import CircleIcon from '@mui/icons-material/Circle';
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
import WarningIcon from '@mui/icons-material/Warning'; import WarningIcon from '@mui/icons-material/Warning';
import { Box, Button, Typography } from '@mui/material'; import { Box, Button, Typography } from '@mui/material';
import * as SystemApi from 'api/system';
import { import {
Body, Body,
Cell, Cell,
@@ -23,19 +26,28 @@ import {
BlockNavigation, BlockNavigation,
ButtonRow, ButtonRow,
FormLoader, FormLoader,
MessageBox,
SectionContent, SectionContent,
useLayoutTitle useLayoutTitle
} from 'components'; } from 'components';
import RestartMonitor from 'framework/system/RestartMonitor';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from './api'; import * as EMSESP from './api';
import ModulesDialog from './ModulesDialog';
import type { ModuleItem, Modules } from './types'; import type { ModuleItem, Modules } from './types';
const Modules: FC = () => { const Modules: FC = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const [numChanges, setNumChanges] = useState<number>(0); const [numChanges, setNumChanges] = useState<number>(0);
const [licenseChanges, setLicenseChanges] = useState<number>(0);
const blocker = useBlocker(numChanges !== 0); const blocker = useBlocker(numChanges !== 0);
const [selectedModuleItem, setSelectedModuleItem] = useState<ModuleItem>();
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
const [restarting, setRestarting] = useState<boolean>(false);
const [restartNeeded, setRestartNeeded] = useState<boolean>(false);
const { const {
data: modules, data: modules,
send: fetchModules, send: fetchModules,
@@ -44,6 +56,17 @@ const Modules: FC = () => {
initialData: [] initialData: []
}); });
const { send: restartCommand } = useRequest(SystemApi.restart(), {
immediate: false
});
const restart = async () => {
await restartCommand().catch((error: Error) => {
toast.error(error.message);
});
setRestarting(true);
};
const { send: writeModules } = useRequest( const { send: writeModules } = useRequest(
(data: Modules) => EMSESP.writeModules(data), (data: Modules) => EMSESP.writeModules(data),
{ {
@@ -53,7 +76,7 @@ const Modules: FC = () => {
const modules_theme = useTheme({ const modules_theme = useTheme({
Table: ` Table: `
--data-table-library_grid-template-columns: 48px 180px 120px 100px repeat(1, minmax(160px, 1fr)); --data-table-library_grid-template-columns: 48px 180px 120px 100px repeat(1, minmax(160px, 1fr)) 180px;
`, `,
BaseRow: ` BaseRow: `
font-size: 14px; font-size: 14px;
@@ -93,6 +116,20 @@ const Modules: FC = () => {
` `
}); });
const onDialogClose = () => {
setDialogOpen(false);
};
const onDialogSave = (updatedItem: ModuleItem) => {
setDialogOpen(false);
updateModuleItem(updatedItem);
};
const editModuleItem = useCallback((mi: ModuleItem) => {
setSelectedModuleItem(mi);
setDialogOpen(true);
}, []);
const onCancel = async () => { const onCancel = async () => {
await fetchModules().then(() => { await fetchModules().then(() => {
setNumChanges(0); setNumChanges(0);
@@ -100,16 +137,20 @@ const Modules: FC = () => {
}; };
function hasModulesChanged(mi: ModuleItem) { function hasModulesChanged(mi: ModuleItem) {
return mi.enabled !== mi.o_enabled; return mi.enabled !== mi.o_enabled || mi.license !== mi.o_license;
} }
const selectModule = (updatedItem: ModuleItem) => { function hasModuleLicenseChanged(mi: ModuleItem) {
updatedItem.enabled = !updatedItem.enabled; return mi.license !== mi.o_license;
}
const updateModuleItem = (updatedItem: ModuleItem) => {
updateState('modules', (data: ModuleItem[]) => { updateState('modules', (data: ModuleItem[]) => {
const new_data = data.map((mi) => const new_data = data.map((mi) =>
mi.id === updatedItem.id ? { ...mi, ...updatedItem } : mi mi.id === updatedItem.id ? { ...mi, ...updatedItem } : mi
); );
setNumChanges(new_data.filter((mi) => hasModulesChanged(mi)).length); setNumChanges(new_data.filter((mi) => hasModulesChanged(mi)).length);
setLicenseChanges(new_data.filter((mi) => hasModuleLicenseChanged(mi)).length);
return new_data; return new_data;
}); });
}; };
@@ -117,12 +158,13 @@ const Modules: FC = () => {
const saveModules = async () => { const saveModules = async () => {
await writeModules({ await writeModules({
modules: modules.map((condensed_mi) => ({ modules: modules.map((condensed_mi) => ({
name: condensed_mi.name, key: condensed_mi.key,
enabled: condensed_mi.enabled enabled: condensed_mi.enabled,
license: condensed_mi.license
})) }))
}) })
.then(() => { .then(() => {
toast.success('Modules saved'); toast.success(LL.MODULES_UPDATED());
}) })
.catch((error: Error) => { .catch((error: Error) => {
toast.error(error.message); toast.error(error.message);
@@ -130,104 +172,134 @@ const Modules: FC = () => {
.finally(() => { .finally(() => {
setNumChanges(0); setNumChanges(0);
}); });
setRestartNeeded(licenseChanges > 0);
}; };
const renderModules = () => { const renderContent = () => {
if (!modules) { if (!modules) {
return <FormLoader onRetry={fetchModules} errorMessage={error?.message} />; return <FormLoader onRetry={fetchModules} errorMessage={error?.message} />;
} }
useLayoutTitle('Modules'); useLayoutTitle(LL.MODULES());
if (modules.length === 0) { if (modules.length === 0) {
return ( return (
<Typography variant="body2" color="error"> <Typography variant="body2" color="error">
No modules detected {LL.MODULES_NONE()}
</Typography> </Typography>
); );
} }
const colorStatus = (status: number) => {
if (status === 1) {
return <div style={{ color: 'red' }}>Pending Activation</div>;
}
return <div style={{ color: '#00FF7F' }}>Activated</div>;
};
return ( return (
<Table <>
data={{ nodes: modules }} <Box mb={2} color="warning.main">
theme={modules_theme} <Typography variant="body2">{LL.MODULES_DESCRIPTION()}</Typography>
layout={{ custom: true }} </Box>
> <Table
{(tableList: ModuleItem[]) => ( data={{ nodes: modules }}
<> theme={modules_theme}
<Header> layout={{ custom: true }}
<HeaderRow> >
<HeaderCell /> {(tableList: ModuleItem[]) => (
<HeaderCell stiff>{LL.NAME(0)}</HeaderCell> <>
<HeaderCell stiff>Author</HeaderCell> <Header>
<HeaderCell stiff>{LL.VERSION()}</HeaderCell> <HeaderRow>
<HeaderCell stiff>{LL.STATUS_OF('')}</HeaderCell> <HeaderCell />
</HeaderRow> <HeaderCell>{LL.NAME(0)}</HeaderCell>
</Header> <HeaderCell>Author</HeaderCell>
<Body> <HeaderCell>{LL.VERSION()}</HeaderCell>
{tableList.map((mi: ModuleItem) => ( <HeaderCell>Message</HeaderCell>
<Row key={mi.id} item={mi} onClick={() => selectModule(mi)}> <HeaderCell>{LL.STATUS_OF('')}</HeaderCell>
<Cell stiff> </HeaderRow>
{mi.enabled ? ( </Header>
<CircleIcon <Body>
color="success" {tableList.map((mi: ModuleItem) => (
sx={{ fontSize: 16, verticalAlign: 'middle' }} <Row key={mi.id} item={mi} onClick={() => editModuleItem(mi)}>
/> <Cell stiff>
) : ( {mi.enabled ? (
<CircleIcon <CircleIcon
color="error" color="success"
sx={{ fontSize: 16, verticalAlign: 'middle' }} sx={{ fontSize: 16, verticalAlign: 'middle' }}
/> />
)} ) : (
</Cell> <CircleIcon
<Cell>{mi.name}</Cell> color="error"
<Cell>{mi.author}</Cell> sx={{ fontSize: 16, verticalAlign: 'middle' }}
<Cell>{mi.version}</Cell> />
<Cell>{mi.status}</Cell> )}
</Row> </Cell>
))} <Cell>{mi.name}</Cell>
</Body> <Cell>{mi.author}</Cell>
</> <Cell>{mi.version}</Cell>
<Cell>{mi.message}</Cell>
<Cell>{colorStatus(mi.status)}</Cell>
</Row>
))}
</Body>
</>
)}
</Table>
{restartNeeded ? (
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT(0)}>
<Button
startIcon={<PowerSettingsNewIcon />}
variant="contained"
color="error"
onClick={restart}
>
{LL.RESTART()}
</Button>
</MessageBox>
) : (
<Box mt={1} display="flex" flexWrap="wrap">
<Box flexGrow={1}>
{numChanges !== 0 && (
<ButtonRow>
<Button
startIcon={<CancelIcon />}
variant="outlined"
onClick={onCancel}
color="secondary"
>
{LL.CANCEL()}
</Button>
<Button
startIcon={<WarningIcon color="warning" />}
variant="contained"
color="info"
onClick={saveModules}
>
{LL.APPLY_CHANGES(numChanges)}
</Button>
</ButtonRow>
)}
</Box>
</Box>
)} )}
</Table> </>
); );
}; };
return ( return (
<SectionContent> <SectionContent>
{blocker ? <BlockNavigation blocker={blocker} /> : null} {blocker ? <BlockNavigation blocker={blocker} /> : null}
<Box mb={2} color="warning.main"> {restarting ? <RestartMonitor /> : renderContent()}
<Typography variant="body2"> {selectedModuleItem && (
Activate or de-activate EMS-ESP library modules by selecting (** <ModulesDialog
experimental **) open={dialogOpen}
</Typography> onClose={onDialogClose}
</Box> onSave={onDialogSave}
{renderModules()} selectedItem={selectedModuleItem}
/>
<Box mt={1} display="flex" flexWrap="wrap"> )}
<Box flexGrow={1}>
{numChanges !== 0 && (
<ButtonRow>
<Button
startIcon={<CancelIcon />}
variant="outlined"
onClick={onCancel}
color="secondary"
>
{LL.CANCEL()}
</Button>
<Button
startIcon={<WarningIcon color="warning" />}
variant="contained"
color="info"
onClick={saveModules}
>
{LL.APPLY_CHANGES(numChanges)}
</Button>
</ButtonRow>
)}
</Box>
</Box>
</SectionContent> </SectionContent>
); );
}; };

View File

@@ -0,0 +1,108 @@
import { useEffect, useState } from 'react';
import CancelIcon from '@mui/icons-material/Cancel';
import DoneIcon from '@mui/icons-material/Done';
import {
Box,
Button,
Checkbox,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Grid,
TextField
} from '@mui/material';
import { dialogStyle } from 'CustomTheme';
import { BlockFormControlLabel } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { updateValue } from 'utils';
import type { ModuleItem } from './types';
interface ModulesDialogProps {
open: boolean;
onClose: () => void;
onSave: (mi: ModuleItem) => void;
selectedItem: ModuleItem;
}
const ModulesDialog = ({
open,
onClose,
onSave,
selectedItem
}: ModulesDialogProps) => {
const { LL } = useI18nContext();
const [editItem, setEditItem] = useState<ModuleItem>(selectedItem);
const updateFormValue = updateValue(setEditItem);
useEffect(() => {
if (open) {
setEditItem(selectedItem);
}
}, [open, selectedItem]);
const close = () => {
onClose();
};
const save = () => {
onSave(editItem);
};
return (
<Dialog sx={dialogStyle} open={open} onClose={onClose}>
<DialogTitle>
{LL.EDIT() + ' ' + LL.MODULES() + ' : ' + editItem.key}
</DialogTitle>
<DialogContent dividers>
<Grid container>
<BlockFormControlLabel
control={
<Checkbox
checked={editItem.enabled}
onChange={updateFormValue}
name="enabled"
/>
}
label={LL.ACTIVE()}
/>
</Grid>
<Box mt={1} mb={1}>
<TextField
name="license"
label="License Key"
multiline
rows={6}
fullWidth
value={editItem.license}
onChange={updateFormValue}
/>
</Box>
</DialogContent>
<DialogActions>
<Button
startIcon={<CancelIcon />}
variant="outlined"
onClick={close}
color="secondary"
>
{LL.CANCEL()}
</Button>
<Button
startIcon={<DoneIcon />}
variant="outlined"
onClick={save}
color="primary"
>
{LL.UPDATE()}
</Button>
</DialogActions>
</Dialog>
);
};
export default ModulesDialog;

View File

@@ -113,7 +113,8 @@ export const readModules = () =>
transformData(data) { transformData(data) {
return (data as Modules).modules.map((mi: ModuleItem) => ({ return (data as Modules).modules.map((mi: ModuleItem) => ({
...mi, ...mi,
o_enabled: mi.enabled o_enabled: mi.enabled,
o_license: mi.license
})); }));
} }
}); });

View File

@@ -310,12 +310,16 @@ export interface Schedule {
export interface ModuleItem { export interface ModuleItem {
id: number; // unique index id: number; // unique index
key: string;
name: string; name: string;
author: string; author: string;
version: string; version: string;
status: string; status: number;
message: string;
enabled: boolean; enabled: boolean;
license: string;
o_enabled?: boolean; o_enabled?: boolean;
o_license?: string;
} }
export interface Modules { export interface Modules {

View File

@@ -1,5 +1,7 @@
import type { busConnectionStatus } from 'project/types'; import type { busConnectionStatus } from 'project/types';
import type { NetworkConnectionStatus } from './network';
export interface ESPSystemStatus { export interface ESPSystemStatus {
emsesp_version: string; emsesp_version: string;
esp_platform: string; esp_platform: string;
@@ -37,6 +39,8 @@ export interface SystemStatus {
ntp_status: number; ntp_status: number;
mqtt_status: boolean; mqtt_status: boolean;
ap_status: boolean; ap_status: boolean;
network_status: NetworkConnectionStatus;
wifi_rssi: number;
} }
export enum LogLevel { export enum LogLevel {

View File

@@ -39,13 +39,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/code-frame@npm:^7.24.6": "@babel/code-frame@npm:^7.24.7":
version: 7.24.6 version: 7.24.7
resolution: "@babel/code-frame@npm:7.24.6" resolution: "@babel/code-frame@npm:7.24.7"
dependencies: dependencies:
"@babel/highlight": "npm:^7.24.6" "@babel/highlight": "npm:^7.24.7"
picocolors: "npm:^1.0.0" picocolors: "npm:^1.0.0"
checksum: 10c0/c93c6d1763530f415218c31d07359364397f19b70026abdff766164c21ed352a931cf07f3102c5fb9e04792de319e332d68bcb1f7debef601a02197f90f9ba24 checksum: 10c0/ab0af539473a9f5aeaac7047e377cb4f4edd255a81d84a76058595f8540784cc3fbe8acf73f1e073981104562490aabfb23008cd66dc677a456a4ed5390fdde6
languageName: node languageName: node
linkType: hard linkType: hard
@@ -56,10 +56,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/compat-data@npm:^7.24.6": "@babel/compat-data@npm:^7.24.7":
version: 7.24.6 version: 7.24.7
resolution: "@babel/compat-data@npm:7.24.6" resolution: "@babel/compat-data@npm:7.24.7"
checksum: 10c0/f50abbd4008eb2a5d12139c578809cebbeaeb8e660fb12d546eb2e7c2108ae1836ab8339184a5f5ce0e95bf81bb91e18edce86b387c59db937b01693ec0bc774 checksum: 10c0/dcd93a5632b04536498fbe2be5af1057f635fd7f7090483d8e797878559037e5130b26862ceb359acbae93ed27e076d395ddb4663db6b28a665756ffd02d324f
languageName: node languageName: node
linkType: hard linkType: hard
@@ -86,26 +86,26 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/core@npm:^7.24.6": "@babel/core@npm:^7.24.7":
version: 7.24.6 version: 7.24.7
resolution: "@babel/core@npm:7.24.6" resolution: "@babel/core@npm:7.24.7"
dependencies: dependencies:
"@ampproject/remapping": "npm:^2.2.0" "@ampproject/remapping": "npm:^2.2.0"
"@babel/code-frame": "npm:^7.24.6" "@babel/code-frame": "npm:^7.24.7"
"@babel/generator": "npm:^7.24.6" "@babel/generator": "npm:^7.24.7"
"@babel/helper-compilation-targets": "npm:^7.24.6" "@babel/helper-compilation-targets": "npm:^7.24.7"
"@babel/helper-module-transforms": "npm:^7.24.6" "@babel/helper-module-transforms": "npm:^7.24.7"
"@babel/helpers": "npm:^7.24.6" "@babel/helpers": "npm:^7.24.7"
"@babel/parser": "npm:^7.24.6" "@babel/parser": "npm:^7.24.7"
"@babel/template": "npm:^7.24.6" "@babel/template": "npm:^7.24.7"
"@babel/traverse": "npm:^7.24.6" "@babel/traverse": "npm:^7.24.7"
"@babel/types": "npm:^7.24.6" "@babel/types": "npm:^7.24.7"
convert-source-map: "npm:^2.0.0" convert-source-map: "npm:^2.0.0"
debug: "npm:^4.1.0" debug: "npm:^4.1.0"
gensync: "npm:^1.0.0-beta.2" gensync: "npm:^1.0.0-beta.2"
json5: "npm:^2.2.3" json5: "npm:^2.2.3"
semver: "npm:^6.3.1" semver: "npm:^6.3.1"
checksum: 10c0/e0762a8daef7f417494d555929418cfacd6848c7fc3310ec00e6dd8cecac20b7f590e760bfc9365d2af07874a3f5599832f9c9ff7f1a9d126a168f77ba67945a checksum: 10c0/4004ba454d3c20a46ea66264e06c15b82e9f6bdc35f88819907d24620da70dbf896abac1cb4cc4b6bb8642969e45f4d808497c9054a1388a386cf8c12e9b9e0d
languageName: node languageName: node
linkType: hard linkType: hard
@@ -132,15 +132,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/generator@npm:^7.24.6": "@babel/generator@npm:^7.24.7":
version: 7.24.6 version: 7.24.7
resolution: "@babel/generator@npm:7.24.6" resolution: "@babel/generator@npm:7.24.7"
dependencies: dependencies:
"@babel/types": "npm:^7.24.6" "@babel/types": "npm:^7.24.7"
"@jridgewell/gen-mapping": "npm:^0.3.5" "@jridgewell/gen-mapping": "npm:^0.3.5"
"@jridgewell/trace-mapping": "npm:^0.3.25" "@jridgewell/trace-mapping": "npm:^0.3.25"
jsesc: "npm:^2.5.1" jsesc: "npm:^2.5.1"
checksum: 10c0/8d71a17b386536582354afba53cc784396458a88cc9f05f0c6de0ec99475f6f539943b3566b2e733820c4928236952473831765e483c25d68cc007a6e604d782 checksum: 10c0/06b1f3350baf527a3309e50ffd7065f7aee04dd06e1e7db794ddfde7fe9d81f28df64edd587173f8f9295496a7ddb74b9a185d4bf4de7bb619e6d4ec45c8fd35
languageName: node languageName: node
linkType: hard linkType: hard
@@ -166,16 +166,16 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/helper-compilation-targets@npm:^7.24.6": "@babel/helper-compilation-targets@npm:^7.24.7":
version: 7.24.6 version: 7.24.7
resolution: "@babel/helper-compilation-targets@npm:7.24.6" resolution: "@babel/helper-compilation-targets@npm:7.24.7"
dependencies: dependencies:
"@babel/compat-data": "npm:^7.24.6" "@babel/compat-data": "npm:^7.24.7"
"@babel/helper-validator-option": "npm:^7.24.6" "@babel/helper-validator-option": "npm:^7.24.7"
browserslist: "npm:^4.22.2" browserslist: "npm:^4.22.2"
lru-cache: "npm:^5.1.1" lru-cache: "npm:^5.1.1"
semver: "npm:^6.3.1" semver: "npm:^6.3.1"
checksum: 10c0/4d41150086959f5f4d72d27bae29204192e943537ecb71df1711d1f5d8791358a44f3a5882ed3c8238ba0c874b0b55213af43767e14771765f13b8d15b262432 checksum: 10c0/1d580a9bcacefe65e6bf02ba1dafd7ab278269fef45b5e281d8354d95c53031e019890464e7f9351898c01502dd2e633184eb0bcda49ed2ecd538675ce310f51
languageName: node languageName: node
linkType: hard linkType: hard
@@ -186,10 +186,12 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/helper-environment-visitor@npm:^7.24.6": "@babel/helper-environment-visitor@npm:^7.24.7":
version: 7.24.6 version: 7.24.7
resolution: "@babel/helper-environment-visitor@npm:7.24.6" resolution: "@babel/helper-environment-visitor@npm:7.24.7"
checksum: 10c0/fdcd18ac505ed71f40c05cc992b648a4495b0aa5310a774492a0f74d8dcf3579691102f516561a651d3de6c3a44fe64bfb3049d11c14c5857634ef1823ea409a dependencies:
"@babel/types": "npm:^7.24.7"
checksum: 10c0/36ece78882b5960e2d26abf13cf15ff5689bf7c325b10a2895a74a499e712de0d305f8d78bb382dd3c05cfba7e47ec98fe28aab5674243e0625cd38438dd0b2d
languageName: node languageName: node
linkType: hard linkType: hard
@@ -203,13 +205,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/helper-function-name@npm:^7.24.6": "@babel/helper-function-name@npm:^7.24.7":
version: 7.24.6 version: 7.24.7
resolution: "@babel/helper-function-name@npm:7.24.6" resolution: "@babel/helper-function-name@npm:7.24.7"
dependencies: dependencies:
"@babel/template": "npm:^7.24.6" "@babel/template": "npm:^7.24.7"
"@babel/types": "npm:^7.24.6" "@babel/types": "npm:^7.24.7"
checksum: 10c0/5ba2f8db789b3f5a2b2239300a217aa212e303cd7bfad9c8b90563807f49215e8c679e8f8f177b6aaca2038038e29bc702b83839e1f7b4896d79c44a75cac97a checksum: 10c0/e5e41e6cf86bd0f8bf272cbb6e7c5ee0f3e9660414174435a46653efba4f2479ce03ce04abff2aa2ef9359cf057c79c06cb7b134a565ad9c0e8a50dcdc3b43c4
languageName: node languageName: node
linkType: hard linkType: hard
@@ -222,12 +224,12 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/helper-hoist-variables@npm:^7.24.6": "@babel/helper-hoist-variables@npm:^7.24.7":
version: 7.24.6 version: 7.24.7
resolution: "@babel/helper-hoist-variables@npm:7.24.6" resolution: "@babel/helper-hoist-variables@npm:7.24.7"
dependencies: dependencies:
"@babel/types": "npm:^7.24.6" "@babel/types": "npm:^7.24.7"
checksum: 10c0/e10ec6b864aaa419ec4934f5fcb5d0cfcc9d0657584a1b6c3c42ada949d44ca6bffcdab433a90ada4396c747e551cca31ba0e565ea005ab3f50964e3817bf6cf checksum: 10c0/19ee37563bbd1219f9d98991ad0e9abef77803ee5945fd85aa7aa62a67c69efca9a801696a1b58dda27f211e878b3327789e6fd2a6f6c725ccefe36774b5ce95
languageName: node languageName: node
linkType: hard linkType: hard
@@ -240,12 +242,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/helper-module-imports@npm:^7.24.6": "@babel/helper-module-imports@npm:^7.24.7":
version: 7.24.6 version: 7.24.7
resolution: "@babel/helper-module-imports@npm:7.24.6" resolution: "@babel/helper-module-imports@npm:7.24.7"
dependencies: dependencies:
"@babel/types": "npm:^7.24.6" "@babel/traverse": "npm:^7.24.7"
checksum: 10c0/e0db3fbfcd963d138f0792ff626f940a576fcf212d02b8fe6478dccf3421bd1c2a76f8e69c7450c049985e7b63b30be309a24eeeb6ad7c2137a31b676a095a84 "@babel/types": "npm:^7.24.7"
checksum: 10c0/97c57db6c3eeaea31564286e328a9fb52b0313c5cfcc7eee4bc226aebcf0418ea5b6fe78673c0e4a774512ec6c86e309d0f326e99d2b37bfc16a25a032498af0
languageName: node languageName: node
linkType: hard linkType: hard
@@ -264,18 +267,18 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/helper-module-transforms@npm:^7.24.6": "@babel/helper-module-transforms@npm:^7.24.7":
version: 7.24.6 version: 7.24.7
resolution: "@babel/helper-module-transforms@npm:7.24.6" resolution: "@babel/helper-module-transforms@npm:7.24.7"
dependencies: dependencies:
"@babel/helper-environment-visitor": "npm:^7.24.6" "@babel/helper-environment-visitor": "npm:^7.24.7"
"@babel/helper-module-imports": "npm:^7.24.6" "@babel/helper-module-imports": "npm:^7.24.7"
"@babel/helper-simple-access": "npm:^7.24.6" "@babel/helper-simple-access": "npm:^7.24.7"
"@babel/helper-split-export-declaration": "npm:^7.24.6" "@babel/helper-split-export-declaration": "npm:^7.24.7"
"@babel/helper-validator-identifier": "npm:^7.24.6" "@babel/helper-validator-identifier": "npm:^7.24.7"
peerDependencies: peerDependencies:
"@babel/core": ^7.0.0 "@babel/core": ^7.0.0
checksum: 10c0/9e2e3d0ddb397b36b9e8c7d94e175a36be8cb888ef370cefef2cdfd53ae1f87d567b268bd90ed9a6c706485a8de3da19cac577657613e9cd17210b91cbdfb00b checksum: 10c0/4f311755fcc3b4cbdb689386309cdb349cf0575a938f0b9ab5d678e1a81bbb265aa34ad93174838245f2ac7ff6d5ddbd0104638a75e4e961958ed514355687b6
languageName: node languageName: node
linkType: hard linkType: hard
@@ -295,12 +298,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/helper-simple-access@npm:^7.24.6": "@babel/helper-simple-access@npm:^7.24.7":
version: 7.24.6 version: 7.24.7
resolution: "@babel/helper-simple-access@npm:7.24.6" resolution: "@babel/helper-simple-access@npm:7.24.7"
dependencies: dependencies:
"@babel/types": "npm:^7.24.6" "@babel/traverse": "npm:^7.24.7"
checksum: 10c0/b17e404dd6c9787fc7d558aea5222471a77e29596705f0d10b4c2a58b9d71ff7eae915094204848cc1af99b771553caa69337a768b9abdd82b54a0050ba83eb9 "@babel/types": "npm:^7.24.7"
checksum: 10c0/7230e419d59a85f93153415100a5faff23c133d7442c19e0cd070da1784d13cd29096ee6c5a5761065c44e8164f9f80e3a518c41a0256df39e38f7ad6744fed7
languageName: node languageName: node
linkType: hard linkType: hard
@@ -313,12 +317,12 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/helper-split-export-declaration@npm:^7.24.6": "@babel/helper-split-export-declaration@npm:^7.24.7":
version: 7.24.6 version: 7.24.7
resolution: "@babel/helper-split-export-declaration@npm:7.24.6" resolution: "@babel/helper-split-export-declaration@npm:7.24.7"
dependencies: dependencies:
"@babel/types": "npm:^7.24.6" "@babel/types": "npm:^7.24.7"
checksum: 10c0/53a5dd8691fdffc89cc7fcf5aed0ad1d8bc39796a5782a3d170dcbf249eb5c15cc8a290e8d09615711d18798ad04a7d0694ab5195d35fa651abbc1b9c885d6a8 checksum: 10c0/0254577d7086bf09b01bbde98f731d4fcf4b7c3fa9634fdb87929801307c1f6202a1352e3faa5492450fa8da4420542d44de604daf540704ff349594a78184f6
languageName: node languageName: node
linkType: hard linkType: hard
@@ -329,10 +333,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/helper-string-parser@npm:^7.24.6": "@babel/helper-string-parser@npm:^7.24.7":
version: 7.24.6 version: 7.24.7
resolution: "@babel/helper-string-parser@npm:7.24.6" resolution: "@babel/helper-string-parser@npm:7.24.7"
checksum: 10c0/95115bf676e92c4e99166395649108d97447e6cabef1fabaec8cdbc53a43f27b5df2268ff6534439d405bc1bd06685b163eb3b470455bd49f69159dada414145 checksum: 10c0/47840c7004e735f3dc93939c77b099bb41a64bf3dda0cae62f60e6f74a5ff80b63e9b7cf77b5ec25a324516381fc994e1f62f922533236a8e3a6af57decb5e1e
languageName: node languageName: node
linkType: hard linkType: hard
@@ -343,10 +347,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/helper-validator-identifier@npm:^7.24.6": "@babel/helper-validator-identifier@npm:^7.24.7":
version: 7.24.6 version: 7.24.7
resolution: "@babel/helper-validator-identifier@npm:7.24.6" resolution: "@babel/helper-validator-identifier@npm:7.24.7"
checksum: 10c0/d29d2e3fca66c31867a009014169b93f7bc21c8fc1dd7d0b9d85d7a4000670526ff2222d966febb75a6e12f9859a31d1e75b558984e28ecb69651314dd0a6fd1 checksum: 10c0/87ad608694c9477814093ed5b5c080c2e06d44cb1924ae8320474a74415241223cc2a725eea2640dd783ff1e3390e5f95eede978bc540e870053152e58f1d651
languageName: node languageName: node
linkType: hard linkType: hard
@@ -357,10 +361,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/helper-validator-option@npm:^7.24.6": "@babel/helper-validator-option@npm:^7.24.7":
version: 7.24.6 version: 7.24.7
resolution: "@babel/helper-validator-option@npm:7.24.6" resolution: "@babel/helper-validator-option@npm:7.24.7"
checksum: 10c0/787268dff5cf77f3b704454b96ab7b58aa4f43b2808247e51859a103a1c28a9c252100f830433f4b37a73f4a61ba745bbeef4cdccbab48c1e9adf037f4ca3491 checksum: 10c0/21aea2b7bc5cc8ddfb828741d5c8116a84cbc35b4a3184ec53124f08e09746f1f67a6f9217850188995ca86059a7942e36d8965a6730784901def777b7e8a436
languageName: node languageName: node
linkType: hard linkType: hard
@@ -375,13 +379,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/helpers@npm:^7.24.6": "@babel/helpers@npm:^7.24.7":
version: 7.24.6 version: 7.24.7
resolution: "@babel/helpers@npm:7.24.6" resolution: "@babel/helpers@npm:7.24.7"
dependencies: dependencies:
"@babel/template": "npm:^7.24.6" "@babel/template": "npm:^7.24.7"
"@babel/types": "npm:^7.24.6" "@babel/types": "npm:^7.24.7"
checksum: 10c0/e5b5c0919fd6fa56ae11c15a72962d8de0ac19db524849554af28cf08ac32f9ae5aee49a43146eb150f54418cefb8e890fa2b2f33d029434dc7777dbcdfd5bac checksum: 10c0/aa8e230f6668773e17e141dbcab63e935c514b4b0bf1fed04d2eaefda17df68e16b61a56573f7f1d4d1e605ce6cc162b5f7e9fdf159fde1fd9b77c920ae47d27
languageName: node languageName: node
linkType: hard linkType: hard
@@ -397,15 +401,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/highlight@npm:^7.24.6": "@babel/highlight@npm:^7.24.7":
version: 7.24.6 version: 7.24.7
resolution: "@babel/highlight@npm:7.24.6" resolution: "@babel/highlight@npm:7.24.7"
dependencies: dependencies:
"@babel/helper-validator-identifier": "npm:^7.24.6" "@babel/helper-validator-identifier": "npm:^7.24.7"
chalk: "npm:^2.4.2" chalk: "npm:^2.4.2"
js-tokens: "npm:^4.0.0" js-tokens: "npm:^4.0.0"
picocolors: "npm:^1.0.0" picocolors: "npm:^1.0.0"
checksum: 10c0/5bbc31695e5d44e97feb267f7aaf4c52908560d184ffeb2e2e57aae058d40125592931883889413e19def3326895ddb41ff45e090fa90b459d8c294b4ffc238c checksum: 10c0/674334c571d2bb9d1c89bdd87566383f59231e16bcdcf5bb7835babdf03c9ae585ca0887a7b25bdf78f303984af028df52831c7989fecebb5101cc132da9393a
languageName: node languageName: node
linkType: hard linkType: hard
@@ -418,12 +422,12 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/parser@npm:^7.24.6": "@babel/parser@npm:^7.24.7":
version: 7.24.6 version: 7.24.7
resolution: "@babel/parser@npm:7.24.6" resolution: "@babel/parser@npm:7.24.7"
bin: bin:
parser: ./bin/babel-parser.js parser: ./bin/babel-parser.js
checksum: 10c0/cbef70923078a20fe163b03f4a6482be65ed99d409a57f3091a23ce3a575ee75716c30e7ea9f40b692ac5660f34055f4cbeb66a354fad15a6cf1fca35c3496c5 checksum: 10c0/8b244756872185a1c6f14b979b3535e682ff08cb5a2a5fd97cc36c017c7ef431ba76439e95e419d43000c5b07720495b00cf29a7f0d9a483643d08802b58819b
languageName: node languageName: node
linkType: hard linkType: hard
@@ -484,14 +488,14 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/template@npm:^7.24.6": "@babel/template@npm:^7.24.7":
version: 7.24.6 version: 7.24.7
resolution: "@babel/template@npm:7.24.6" resolution: "@babel/template@npm:7.24.7"
dependencies: dependencies:
"@babel/code-frame": "npm:^7.24.6" "@babel/code-frame": "npm:^7.24.7"
"@babel/parser": "npm:^7.24.6" "@babel/parser": "npm:^7.24.7"
"@babel/types": "npm:^7.24.6" "@babel/types": "npm:^7.24.7"
checksum: 10c0/a4d5805770de908b445f7cdcebfcb6eaa07b1ec9c7b78fd3f375a911b1522c249bddae6b96bc4aac24247cc603e3e6cffcf2fe50b4c929dfeb22de289b517525 checksum: 10c0/95b0b3ee80fcef685b7f4426f5713a855ea2cd5ac4da829b213f8fb5afe48a2a14683c2ea04d446dbc7f711c33c5cd4a965ef34dcbe5bc387c9e966b67877ae3
languageName: node languageName: node
linkType: hard linkType: hard
@@ -531,21 +535,21 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/traverse@npm:^7.24.6": "@babel/traverse@npm:^7.24.7":
version: 7.24.6 version: 7.24.7
resolution: "@babel/traverse@npm:7.24.6" resolution: "@babel/traverse@npm:7.24.7"
dependencies: dependencies:
"@babel/code-frame": "npm:^7.24.6" "@babel/code-frame": "npm:^7.24.7"
"@babel/generator": "npm:^7.24.6" "@babel/generator": "npm:^7.24.7"
"@babel/helper-environment-visitor": "npm:^7.24.6" "@babel/helper-environment-visitor": "npm:^7.24.7"
"@babel/helper-function-name": "npm:^7.24.6" "@babel/helper-function-name": "npm:^7.24.7"
"@babel/helper-hoist-variables": "npm:^7.24.6" "@babel/helper-hoist-variables": "npm:^7.24.7"
"@babel/helper-split-export-declaration": "npm:^7.24.6" "@babel/helper-split-export-declaration": "npm:^7.24.7"
"@babel/parser": "npm:^7.24.6" "@babel/parser": "npm:^7.24.7"
"@babel/types": "npm:^7.24.6" "@babel/types": "npm:^7.24.7"
debug: "npm:^4.3.1" debug: "npm:^4.3.1"
globals: "npm:^11.1.0" globals: "npm:^11.1.0"
checksum: 10c0/39027d5fc7a241c6b71bb5872c2bdcec53743cd7ef3c151bbe6fd7cf874d15f4bc09e5d7e19e2f534b0eb2c115f5368553885fa4253aa1bc9441c6e5bf9efdaf checksum: 10c0/a5135e589c3f1972b8877805f50a084a04865ccb1d68e5e1f3b94a8841b3485da4142e33413d8fd76bc0e6444531d3adf1f59f359c11ffac452b743d835068ab
languageName: node languageName: node
linkType: hard linkType: hard
@@ -570,14 +574,14 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/types@npm:^7.24.6": "@babel/types@npm:^7.24.7":
version: 7.24.6 version: 7.24.7
resolution: "@babel/types@npm:7.24.6" resolution: "@babel/types@npm:7.24.7"
dependencies: dependencies:
"@babel/helper-string-parser": "npm:^7.24.6" "@babel/helper-string-parser": "npm:^7.24.7"
"@babel/helper-validator-identifier": "npm:^7.24.6" "@babel/helper-validator-identifier": "npm:^7.24.7"
to-fast-properties: "npm:^2.0.0" to-fast-properties: "npm:^2.0.0"
checksum: 10c0/1d94d92d97ef49030ad7f9e14cfccfeb70b1706dabcaa69037e659ec9d2c3178fb005d2088cce40d88dfc1306153d9157fe038a79ea2be92e5e6b99a59ef80cc checksum: 10c0/d9ecbfc3eb2b05fb1e6eeea546836ac30d990f395ef3fe3f75ced777a222c3cfc4489492f72e0ce3d9a5a28860a1ce5f81e66b88cf5088909068b3ff4fab72c1
languageName: node languageName: node
linkType: hard linkType: hard
@@ -1738,12 +1742,12 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/node@npm:^20.14.1": "@types/node@npm:^20.14.2":
version: 20.14.1 version: 20.14.2
resolution: "@types/node@npm:20.14.1" resolution: "@types/node@npm:20.14.2"
dependencies: dependencies:
undici-types: "npm:~5.26.4" undici-types: "npm:~5.26.4"
checksum: 10c0/12b7879047f50cc217bbea3add7c45e542070f6e9fb2092be97542152b7022512bcb2bf848d04f77e295c4c8699acd484e79a4a4dbe9bcfa4e89dd543d530611 checksum: 10c0/2d86e5f2227aaa42212e82ea0affe72799111b888ff900916376450b02b09b963ca888b20d9c332d8d2b833ed4781987867a38eaa2e4863fa8439071468b0a6f
languageName: node languageName: node
linkType: hard linkType: hard
@@ -1962,7 +1966,7 @@ __metadata:
dependencies: dependencies:
"@alova/adapter-xhr": "npm:^1.0.6" "@alova/adapter-xhr": "npm:^1.0.6"
"@alova/scene-react": "npm:^1.5.0" "@alova/scene-react": "npm:^1.5.0"
"@babel/core": "npm:^7.24.6" "@babel/core": "npm:^7.24.7"
"@emotion/react": "npm:^11.11.4" "@emotion/react": "npm:^11.11.4"
"@emotion/styled": "npm:^11.11.5" "@emotion/styled": "npm:^11.11.5"
"@eslint/js": "npm:^9.4.0" "@eslint/js": "npm:^9.4.0"
@@ -1974,7 +1978,7 @@ __metadata:
"@trivago/prettier-plugin-sort-imports": "npm:^4.3.0" "@trivago/prettier-plugin-sort-imports": "npm:^4.3.0"
"@types/babel__core": "npm:^7" "@types/babel__core": "npm:^7"
"@types/lodash-es": "npm:^4.17.12" "@types/lodash-es": "npm:^4.17.12"
"@types/node": "npm:^20.14.1" "@types/node": "npm:^20.14.2"
"@types/react": "npm:^18.3.3" "@types/react": "npm:^18.3.3"
"@types/react-dom": "npm:^18.3.0" "@types/react-dom": "npm:^18.3.0"
"@types/react-router-dom": "npm:^5.3.3" "@types/react-router-dom": "npm:^5.3.3"
@@ -1988,7 +1992,7 @@ __metadata:
lodash-es: "npm:^4.17.21" lodash-es: "npm:^4.17.21"
mime-types: "npm:^2.1.35" mime-types: "npm:^2.1.35"
preact: "npm:^10.22.0" preact: "npm:^10.22.0"
prettier: "npm:^3.3.0" prettier: "npm:^3.3.1"
react: "npm:latest" react: "npm:latest"
react-dom: "npm:latest" react-dom: "npm:latest"
react-dropzone: "npm:^14.2.3" react-dropzone: "npm:^14.2.3"
@@ -1996,11 +2000,11 @@ __metadata:
react-router-dom: "npm:^6.23.1" react-router-dom: "npm:^6.23.1"
react-toastify: "npm:^10.0.5" react-toastify: "npm:^10.0.5"
rollup-plugin-visualizer: "npm:^5.12.0" rollup-plugin-visualizer: "npm:^5.12.0"
terser: "npm:^5.31.0" terser: "npm:^5.31.1"
typesafe-i18n: "npm:^5.26.2" typesafe-i18n: "npm:^5.26.2"
typescript: "npm:^5.4.5" typescript: "npm:^5.4.5"
typescript-eslint: "npm:^7.12.0" typescript-eslint: "npm:^7.12.0"
vite: "npm:^5.2.12" vite: "npm:^5.2.13"
vite-plugin-imagemin: "npm:^0.6.1" vite-plugin-imagemin: "npm:^0.6.1"
vite-tsconfig-paths: "npm:^4.3.2" vite-tsconfig-paths: "npm:^4.3.2"
languageName: unknown languageName: unknown
@@ -5932,12 +5936,12 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"prettier@npm:^3.3.0": "prettier@npm:^3.3.1":
version: 3.3.0 version: 3.3.1
resolution: "prettier@npm:3.3.0" resolution: "prettier@npm:3.3.1"
bin: bin:
prettier: bin/prettier.cjs prettier: bin/prettier.cjs
checksum: 10c0/d033c356320aa2e468bf29c931b094ac730d2f4defd5eb2989d8589313dec901d2fc866e3788f3d161e420b142ea4ec3dda535dbe0169ef4d0026397a68ba9cf checksum: 10c0/c25a709c9f0be670dc6bcb190b622347e1dbeb6c3e7df8b0711724cb64d8647c60b839937a4df4df18e9cfb556c2b08ca9d24d9645eb5488a7fc032a2c4d5cb3
languageName: node languageName: node
linkType: hard linkType: hard
@@ -6968,9 +6972,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"terser@npm:^5.31.0": "terser@npm:^5.31.1":
version: 5.31.0 version: 5.31.1
resolution: "terser@npm:5.31.0" resolution: "terser@npm:5.31.1"
dependencies: dependencies:
"@jridgewell/source-map": "npm:^0.3.3" "@jridgewell/source-map": "npm:^0.3.3"
acorn: "npm:^8.8.2" acorn: "npm:^8.8.2"
@@ -6978,7 +6982,7 @@ __metadata:
source-map-support: "npm:~0.5.20" source-map-support: "npm:~0.5.20"
bin: bin:
terser: bin/terser terser: bin/terser
checksum: 10c0/cb127a579b03fb9dcee0d293ff24814deedcd430f447933b618e8593b7454f615b5c8493c68e86a4b0188769d5ea2af5251b5d507edb208114f7e8aebdc7c850 checksum: 10c0/4d49a58f64c11f3742e779a0a03aff69972ca5739decb361d909d22c8f3f7d8e2ec982a928d987d56737ad50229e8ab3f62d8ba993e4b5f360a53ed487d3c06c
languageName: node languageName: node
linkType: hard linkType: hard
@@ -7318,9 +7322,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"vite@npm:^5.2.12": "vite@npm:^5.2.13":
version: 5.2.12 version: 5.2.13
resolution: "vite@npm:5.2.12" resolution: "vite@npm:5.2.13"
dependencies: dependencies:
esbuild: "npm:^0.20.1" esbuild: "npm:^0.20.1"
fsevents: "npm:~2.3.3" fsevents: "npm:~2.3.3"
@@ -7354,7 +7358,7 @@ __metadata:
optional: true optional: true
bin: bin:
vite: bin/vite.js vite: bin/vite.js
checksum: 10c0/f03fdfc320adea3397df3e327029fd875f8220779f679ab183a3a994e8788b4ce531fee28f830361fb274f3cf08ed9adb9429496ecefdc3faf535b38da7ea8b1 checksum: 10c0/f7a99da71884e69cc581dcfb43d73c8d56d73b9668d6980131603c544d6323c6003a20f376531dc0cfcf36bf5009bc465f89e6c5f8bd9d22868987aba4e4af1b
languageName: node languageName: node
linkType: hard linkType: hard

View File

@@ -48,7 +48,7 @@ extern "C" {
//If core is not defined, then we are running in Arduino or PIO //If core is not defined, then we are running in Arduino or PIO
#ifndef CONFIG_ASYNC_TCP_RUNNING_CORE #ifndef CONFIG_ASYNC_TCP_RUNNING_CORE
#define CONFIG_ASYNC_TCP_RUNNING_CORE -1 //any available core #define CONFIG_ASYNC_TCP_RUNNING_CORE -1 //any available core
// Note default was 1 and previously set to 0 for EMS-ESP32 // Note default was 1 and previously set to 0 for EMS-ESP
#define CONFIG_ASYNC_TCP_USE_WDT 1 //if enabled, adds between 33us and 200us per event #define CONFIG_ASYNC_TCP_USE_WDT 1 //if enabled, adds between 33us and 200us per event
#endif #endif
@@ -56,13 +56,13 @@ extern "C" {
#define CONFIG_ASYNC_TCP_TASK_PRIORITY 5 #define CONFIG_ASYNC_TCP_TASK_PRIORITY 5
#endif #endif
// EMS-ESP32: stack usage measured: ESP32: ~2.3K, ESP32S3: ~3.5k // EMS-ESP: stack usage measured: ESP32: ~2.3K, ESP32S3: ~3.5k
#ifndef CONFIG_ASYNC_TCP_STACK_SIZE #ifndef CONFIG_ASYNC_TCP_STACK_SIZE
#define CONFIG_ASYNC_TCP_STACK_SIZE 5120 #define CONFIG_ASYNC_TCP_STACK_SIZE 5120
#endif #endif
// EMS-ESP32: maybe enlarge queue to 64 or 128 see https://github.com/emsesp/EMS-ESP32/issues/177 // EMS-ESP: maybe enlarge queue to 64 or 128 see https://github.com/emsesp/EMS-ESP32/issues/177
#ifndef CONFIG_ASYNC_TCP_QUEUE #ifndef CONFIG_ASYNC_TCP_QUEUE
#define CONFIG_ASYNC_TCP_QUEUE 32 #define CONFIG_ASYNC_TCP_QUEUE 32
#endif #endif

File diff suppressed because it is too large Load Diff

View File

@@ -89,8 +89,8 @@ typedef enum { WS_MSG_SENDING, WS_MSG_SENT, WS_MSG_ERROR } AwsMessageStatus;
typedef enum { WS_EVT_CONNECT, WS_EVT_DISCONNECT, WS_EVT_PONG, WS_EVT_ERROR, WS_EVT_DATA } AwsEventType; typedef enum { WS_EVT_CONNECT, WS_EVT_DISCONNECT, WS_EVT_PONG, WS_EVT_ERROR, WS_EVT_DATA } AwsEventType;
class AsyncWebSocketMessageBuffer { class AsyncWebSocketMessageBuffer {
friend AsyncWebSocket; friend AsyncWebSocket;
friend AsyncWebSocketClient; friend AsyncWebSocketClient;
private: private:
std::shared_ptr<std::vector<uint8_t>> _buffer; std::shared_ptr<std::vector<uint8_t>> _buffer;
@@ -98,114 +98,165 @@ class AsyncWebSocketMessageBuffer {
public: public:
AsyncWebSocketMessageBuffer(); AsyncWebSocketMessageBuffer();
AsyncWebSocketMessageBuffer(size_t size); AsyncWebSocketMessageBuffer(size_t size);
AsyncWebSocketMessageBuffer(uint8_t* data, size_t size); AsyncWebSocketMessageBuffer(uint8_t * data, size_t size);
~AsyncWebSocketMessageBuffer(); ~AsyncWebSocketMessageBuffer();
bool reserve(size_t size); bool reserve(size_t size);
uint8_t* get() { return _buffer->data(); } uint8_t * get() {
size_t length() const { return _buffer->size(); } return _buffer->data();
}
size_t length() const {
return _buffer->size();
}
}; };
class AsyncWebSocketMessage class AsyncWebSocketMessage {
{ private:
private:
std::shared_ptr<std::vector<uint8_t>> _WSbuffer; std::shared_ptr<std::vector<uint8_t>> _WSbuffer;
uint8_t _opcode{WS_TEXT}; uint8_t _opcode{WS_TEXT};
bool _mask{false}; bool _mask{false};
AwsMessageStatus _status{WS_MSG_ERROR}; AwsMessageStatus _status{WS_MSG_ERROR};
size_t _sent{}; size_t _sent{};
size_t _ack{}; size_t _ack{};
size_t _acked{}; size_t _acked{};
public: public:
AsyncWebSocketMessage(std::shared_ptr<std::vector<uint8_t>> buffer, uint8_t opcode=WS_TEXT, bool mask=false); AsyncWebSocketMessage(std::shared_ptr<std::vector<uint8_t>> buffer, uint8_t opcode = WS_TEXT, bool mask = false);
bool finished() const { return _status != WS_MSG_SENDING; } bool finished() const {
bool betweenFrames() const { return _acked == _ack; } return _status != WS_MSG_SENDING;
}
bool betweenFrames() const {
return _acked == _ack;
}
void ack(size_t len, uint32_t time); void ack(size_t len, uint32_t time);
size_t send(AsyncClient *client); size_t send(AsyncClient * client);
}; };
class AsyncWebSocketClient { class AsyncWebSocketClient {
private: private:
AsyncClient *_client; AsyncClient * _client;
AsyncWebSocket *_server; AsyncWebSocket * _server;
uint32_t _clientId; uint32_t _clientId;
AwsClientStatus _status; AwsClientStatus _status;
AsyncWebLock _lock; AsyncWebLock _lock;
std::deque<AsyncWebSocketControl> _controlQueue; std::deque<AsyncWebSocketControl> _controlQueue;
std::deque<AsyncWebSocketMessage> _messageQueue; std::deque<AsyncWebSocketMessage> _messageQueue;
bool closeWhenFull = true;
uint8_t _pstate; uint8_t _pstate;
AwsFrameInfo _pinfo; AwsFrameInfo _pinfo;
uint32_t _lastMessageTime; uint32_t _lastMessageTime;
uint32_t _keepAlivePeriod; uint32_t _keepAlivePeriod;
void _queueControl(uint8_t opcode, const uint8_t *data=NULL, size_t len=0, bool mask=false); void _queueControl(uint8_t opcode, const uint8_t * data = NULL, size_t len = 0, bool mask = false);
void _queueMessage(std::shared_ptr<std::vector<uint8_t>> buffer, uint8_t opcode=WS_TEXT, bool mask=false); void _queueMessage(std::shared_ptr<std::vector<uint8_t>> buffer, uint8_t opcode = WS_TEXT, bool mask = false);
void _runQueue(); void _runQueue();
void _clearQueue(); void _clearQueue();
public: public:
void *_tempObject; void * _tempObject;
AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server); AsyncWebSocketClient(AsyncWebServerRequest * request, AsyncWebSocket * server);
~AsyncWebSocketClient(); ~AsyncWebSocketClient();
//client id increments for the given server //client id increments for the given server
uint32_t id() const { return _clientId; } uint32_t id() const {
AwsClientStatus status() const { return _status; } return _clientId;
AsyncClient* client() { return _client; } }
const AsyncClient* client() const { return _client; } AwsClientStatus status() const {
AsyncWebSocket *server(){ return _server; } return _status;
const AsyncWebSocket *server() const { return _server; } }
AwsFrameInfo const &pinfo() const { return _pinfo; } AsyncClient * client() {
return _client;
}
const AsyncClient * client() const {
return _client;
}
AsyncWebSocket * server() {
return _server;
}
const AsyncWebSocket * server() const {
return _server;
}
AwsFrameInfo const & pinfo() const {
return _pinfo;
}
// - If "true" (default), the connection will be closed if the message queue is full.
// This is the default behavior in yubox-node-org, which is not silently discarding messages but instead closes the connection.
// The big issue with this behavior is that is can cause the UI to automatically re-create a new WS connection, which can be filled again,
// and so on, causing a resource exhaustion.
//
// - If "false", the incoming message will be discarded if the queue is full.
// This is the default behavior in the original ESPAsyncWebServer library from me-no-dev.
// This behavior allows the best performance at the expense of unreliable message delivery in case the queue is full (some messages may be lost).
//
// - In any case, when the queue is full, a message is logged.
// - IT is recommended to use the methods queueIsFull(), availableForWriteAll(), availableForWrite(clientId) to check if the queue is full before sending a message.
//
// Usage:
// - can be set in the onEvent listener when connecting (event type is: WS_EVT_CONNECT)
//
// Use cases:,
// - if using websocket to send logging messages, maybe some loss is acceptable.
// - But if using websocket to send UI update messages, maybe the connection should be closed and the UI redrawn.
void setCloseClientOnQueueFull(bool close) {
closeWhenFull = close;
}
bool willCloseClientOnQueueFull() const {
return closeWhenFull;
}
IPAddress remoteIP() const; IPAddress remoteIP() const;
uint16_t remotePort() const; uint16_t remotePort() const;
bool shouldBeDeleted() const { return !_client; } bool shouldBeDeleted() const {
return !_client;
}
//control frames //control frames
void close(uint16_t code=0, const char * message=NULL); void close(uint16_t code = 0, const char * message = NULL);
void ping(const uint8_t *data=NULL, size_t len=0); void ping(const uint8_t * data = NULL, size_t len = 0);
//set auto-ping period in seconds. disabled if zero (default) //set auto-ping period in seconds. disabled if zero (default)
void keepAlivePeriod(uint16_t seconds){ void keepAlivePeriod(uint16_t seconds) {
_keepAlivePeriod = seconds * 1000; _keepAlivePeriod = seconds * 1000;
} }
uint16_t keepAlivePeriod(){ uint16_t keepAlivePeriod() {
return (uint16_t)(_keepAlivePeriod / 1000); return (uint16_t)(_keepAlivePeriod / 1000);
} }
//data packets //data packets
void message(std::shared_ptr<std::vector<uint8_t>> buffer, uint8_t opcode=WS_TEXT, bool mask=false) { _queueMessage(buffer, opcode, mask); } void message(std::shared_ptr<std::vector<uint8_t>> buffer, uint8_t opcode = WS_TEXT, bool mask = false) {
bool queueIsFull() const; _queueMessage(buffer, opcode, mask);
}
bool queueIsFull() const;
size_t queueLen() const; size_t queueLen() const;
size_t printf(const char *format, ...) __attribute__ ((format (printf, 2, 3))); size_t printf(const char * format, ...) __attribute__((format(printf, 2, 3)));
#ifndef ESP32 #ifndef ESP32
size_t printf_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3))); size_t printf_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3)));
#endif #endif
void text(std::shared_ptr<std::vector<uint8_t>> buffer); void text(std::shared_ptr<std::vector<uint8_t>> buffer);
void text(const uint8_t *message, size_t len); void text(const uint8_t * message, size_t len);
void text(const char *message, size_t len); void text(const char * message, size_t len);
void text(const char *message); void text(const char * message);
void text(const String &message); void text(const String & message);
void text(const __FlashStringHelper *message); void text(const __FlashStringHelper * message);
void text(AsyncWebSocketMessageBuffer *buffer); void text(AsyncWebSocketMessageBuffer * buffer);
void binary(std::shared_ptr<std::vector<uint8_t>> buffer); void binary(std::shared_ptr<std::vector<uint8_t>> buffer);
void binary(const uint8_t *message, size_t len); void binary(const uint8_t * message, size_t len);
void binary(const char * message, size_t len); void binary(const char * message, size_t len);
void binary(const char * message); void binary(const char * message);
void binary(const String &message); void binary(const String & message);
void binary(const __FlashStringHelper *message, size_t len); void binary(const __FlashStringHelper * message, size_t len);
void binary(AsyncWebSocketMessageBuffer *buffer); void binary(AsyncWebSocketMessageBuffer * buffer);
bool canSend() const; bool canSend() const;
@@ -215,114 +266,133 @@ class AsyncWebSocketClient {
void _onPoll(); void _onPoll();
void _onTimeout(uint32_t time); void _onTimeout(uint32_t time);
void _onDisconnect(); void _onDisconnect();
void _onData(void *pbuf, size_t plen); void _onData(void * pbuf, size_t plen);
}; };
typedef std::function<bool(AsyncWebServerRequest *request)> AwsHandshakeHandler; typedef std::function<bool(AsyncWebServerRequest * request)> AwsHandshakeHandler;
typedef std::function<void(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len)> AwsEventHandler; typedef std::function<void(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t * data, size_t len)> AwsEventHandler;
//WebServer Handler implementation that plays the role of a socket server //WebServer Handler implementation that plays the role of a socket server
class AsyncWebSocket: public AsyncWebHandler { class AsyncWebSocket : public AsyncWebHandler {
private: private:
String _url; String _url;
std::list<AsyncWebSocketClient> _clients; std::list<AsyncWebSocketClient> _clients;
uint32_t _cNextId; uint32_t _cNextId;
AwsEventHandler _eventHandler; AwsEventHandler _eventHandler;
AwsHandshakeHandler _handshakeHandler; AwsHandshakeHandler _handshakeHandler;
bool _enabled; bool _enabled;
AsyncWebLock _lock; AsyncWebLock _lock;
public: public:
AsyncWebSocket(const String& url); AsyncWebSocket(const String & url);
~AsyncWebSocket(); ~AsyncWebSocket();
const char * url() const { return _url.c_str(); } const char * url() const {
void enable(bool e){ _enabled = e; } return _url.c_str();
bool enabled() const { return _enabled; } }
void enable(bool e) {
_enabled = e;
}
bool enabled() const {
return _enabled;
}
bool availableForWriteAll(); bool availableForWriteAll();
bool availableForWrite(uint32_t id); bool availableForWrite(uint32_t id);
size_t count() const; size_t count() const;
AsyncWebSocketClient * client(uint32_t id); AsyncWebSocketClient * client(uint32_t id);
bool hasClient(uint32_t id){ return client(id) != NULL; } bool hasClient(uint32_t id) {
return client(id) != NULL;
}
void close(uint32_t id, uint16_t code=0, const char * message=NULL); void close(uint32_t id, uint16_t code = 0, const char * message = NULL);
void closeAll(uint16_t code=0, const char * message=NULL); void closeAll(uint16_t code = 0, const char * message = NULL);
void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS); void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS);
void ping(uint32_t id, const uint8_t *data=NULL, size_t len=0); void ping(uint32_t id, const uint8_t * data = NULL, size_t len = 0);
void pingAll(const uint8_t *data=NULL, size_t len=0); // done void pingAll(const uint8_t * data = NULL, size_t len = 0); // done
void text(uint32_t id, const uint8_t * message, size_t len); void text(uint32_t id, const uint8_t * message, size_t len);
void text(uint32_t id, const char *message, size_t len); void text(uint32_t id, const char * message, size_t len);
void text(uint32_t id, const char *message); void text(uint32_t id, const char * message);
void text(uint32_t id, const String &message); void text(uint32_t id, const String & message);
void text(uint32_t id, const __FlashStringHelper *message); void text(uint32_t id, const __FlashStringHelper * message);
void text(uint32_t id, AsyncWebSocketMessageBuffer * buffer);
void text(uint32_t id, std::shared_ptr<std::vector<uint8_t>> buffer);
void textAll(std::shared_ptr<std::vector<uint8_t>> buffer); void textAll(const uint8_t * message, size_t len);
void textAll(const uint8_t *message, size_t len);
void textAll(const char * message, size_t len); void textAll(const char * message, size_t len);
void textAll(const char * message); void textAll(const char * message);
void textAll(const String &message); void textAll(const String & message);
void textAll(const __FlashStringHelper *message); // need to convert void textAll(const __FlashStringHelper * message);
void textAll(AsyncWebSocketMessageBuffer *buffer); void textAll(AsyncWebSocketMessageBuffer * buffer);
void textAll(std::shared_ptr<std::vector<uint8_t>> buffer);
void binary(uint32_t id, const uint8_t *message, size_t len); void binary(uint32_t id, const uint8_t * message, size_t len);
void binary(uint32_t id, const char *message, size_t len); void binary(uint32_t id, const char * message, size_t len);
void binary(uint32_t id, const char *message); void binary(uint32_t id, const char * message);
void binary(uint32_t id, const String &message); void binary(uint32_t id, const String & message);
void binary(uint32_t id, const __FlashStringHelper *message, size_t len); void binary(uint32_t id, const __FlashStringHelper * message, size_t len);
void binary(uint32_t id, AsyncWebSocketMessageBuffer * buffer);
void binary(uint32_t id, std::shared_ptr<std::vector<uint8_t>> buffer);
void binaryAll(const uint8_t * message, size_t len);
void binaryAll(const char * message, size_t len);
void binaryAll(const char * message);
void binaryAll(const String & message);
void binaryAll(const __FlashStringHelper * message, size_t len);
void binaryAll(AsyncWebSocketMessageBuffer * buffer);
void binaryAll(std::shared_ptr<std::vector<uint8_t>> buffer); void binaryAll(std::shared_ptr<std::vector<uint8_t>> buffer);
void binaryAll(const uint8_t *message, size_t len);
void binaryAll(const char *message, size_t len);
void binaryAll(const char *message);
void binaryAll(const String &message);
void binaryAll(const __FlashStringHelper *message, size_t len);
void binaryAll(AsyncWebSocketMessageBuffer *buffer);
size_t printf(uint32_t id, const char *format, ...) __attribute__ ((format (printf, 3, 4))); size_t printf(uint32_t id, const char * format, ...) __attribute__((format(printf, 3, 4)));
size_t printfAll(const char *format, ...) __attribute__ ((format (printf, 2, 3))); size_t printfAll(const char * format, ...) __attribute__((format(printf, 2, 3)));
#ifndef ESP32 #ifndef ESP32
size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__ ((format (printf, 3, 4))); size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__((format(printf, 3, 4)));
#endif #endif
size_t printfAll_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3))); size_t printfAll_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3)));
//event listener //event listener
void onEvent(AwsEventHandler handler){ void onEvent(AwsEventHandler handler) {
_eventHandler = handler; _eventHandler = handler;
} }
// Handshake Handler // Handshake Handler
void handleHandshake(AwsHandshakeHandler handler){ void handleHandshake(AwsHandshakeHandler handler) {
_handshakeHandler = handler; _handshakeHandler = handler;
} }
//system callbacks (do not call) //system callbacks (do not call)
uint32_t _getNextId(){ return _cNextId++; } uint32_t _getNextId() {
AsyncWebSocketClient *_newClient(AsyncWebServerRequest *request); return _cNextId++;
void _handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len); }
virtual bool canHandle(AsyncWebServerRequest *request) override final; AsyncWebSocketClient * _newClient(AsyncWebServerRequest * request);
virtual void handleRequest(AsyncWebServerRequest *request) override final; void _handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t * data, size_t len);
virtual bool canHandle(AsyncWebServerRequest * request) override final;
virtual void handleRequest(AsyncWebServerRequest * request) override final;
// messagebuffer functions/objects. // messagebuffer functions/objects.
AsyncWebSocketMessageBuffer * makeBuffer(size_t size = 0); AsyncWebSocketMessageBuffer * makeBuffer(size_t size = 0);
AsyncWebSocketMessageBuffer * makeBuffer(uint8_t * data, size_t size); AsyncWebSocketMessageBuffer * makeBuffer(uint8_t * data, size_t size);
const std::list<AsyncWebSocketClient> &getClients() const { return _clients; } const std::list<AsyncWebSocketClient> & getClients() const {
return _clients;
}
}; };
//WebServer response to authenticate the socket and detach the tcp client from the web server request //WebServer response to authenticate the socket and detach the tcp client from the web server request
class AsyncWebSocketResponse: public AsyncWebServerResponse { class AsyncWebSocketResponse : public AsyncWebServerResponse {
private: private:
String _content; String _content;
AsyncWebSocket *_server; AsyncWebSocket * _server;
public: public:
AsyncWebSocketResponse(const String& key, AsyncWebSocket *server); AsyncWebSocketResponse(const String & key, AsyncWebSocket * server);
void _respond(AsyncWebServerRequest *request); void _respond(AsyncWebServerRequest * request);
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); size_t _ack(AsyncWebServerRequest * request, size_t len, uint32_t time);
bool _sourceValid() const { return true; } bool _sourceValid() const {
return true;
}
}; };
#endif /* ASYNCWEBSOCKET_H_ */ #endif /* ASYNCWEBSOCKET_H_ */

View File

@@ -28,7 +28,7 @@
#include <vector> #include <vector>
#include "FS.h" #include "FS.h"
#include <ArduinoJson.h> // added by proddy #include <ArduinoJson.h> // added by proddy for EMS-ESP
#include "StringArray.h" #include "StringArray.h"
@@ -330,7 +330,7 @@ class AsyncWebServerRequest {
AsyncWebServerResponse * beginResponse_P(int code, const String & contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback = nullptr); AsyncWebServerResponse * beginResponse_P(int code, const String & contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback = nullptr);
AsyncWebServerResponse * beginResponse_P(int code, const String & contentType, PGM_P content, AwsTemplateProcessor callback = nullptr); AsyncWebServerResponse * beginResponse_P(int code, const String & contentType, PGM_P content, AwsTemplateProcessor callback = nullptr);
// added by proddy // added by proddy for EMS-ESP
AsyncWebServerResponse * beginResponse(const String & contentType, const uint8_t * content, size_t len); AsyncWebServerResponse * beginResponse(const String & contentType, const uint8_t * content, size_t len);
size_t headers() const; // get header count size_t headers() const; // get header count
@@ -532,7 +532,7 @@ typedef std::function<void(AsyncWebServerRequest * request)> ArRequestHandlerFun
typedef std::function<void(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final)> ArUploadHandlerFunction; typedef std::function<void(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final)> ArUploadHandlerFunction;
typedef std::function<void(AsyncWebServerRequest * request, uint8_t * data, size_t len, size_t index, size_t total)> ArBodyHandlerFunction; typedef std::function<void(AsyncWebServerRequest * request, uint8_t * data, size_t len, size_t index, size_t total)> ArBodyHandlerFunction;
typedef std::function<void(AsyncWebServerRequest * request, JsonVariant json)> ArJsonRequestHandlerFunction; // added by proddy typedef std::function<void(AsyncWebServerRequest * request, JsonVariant json)> ArJsonRequestHandlerFunction; // added by proddy for EMS-ESP
class AsyncWebServer { class AsyncWebServer {
protected: protected:
@@ -566,7 +566,7 @@ class AsyncWebServer {
AsyncCallbackWebHandler & AsyncCallbackWebHandler &
on(const char * uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody); on(const char * uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody);
void on(const char * uri, ArJsonRequestHandlerFunction onRequest); // added by proddy void on(const char * uri, ArJsonRequestHandlerFunction onRequest); // added by proddy for EMS-ESP
AsyncStaticWebHandler & serveStatic(const char * uri, fs::FS & fs, const char * path, const char * cache_control = NULL); AsyncStaticWebHandler & serveStatic(const char * uri, fs::FS & fs, const char * path, const char * cache_control = NULL);

View File

@@ -21,7 +21,7 @@
#include "WebAuthentication.h" #include "WebAuthentication.h"
#include <libb64/cencode.h> #include <libb64/cencode.h>
#ifdef ESP32 #ifdef ESP32
#include "mbedtls/md5.h" #include <MD5Builder.h>
#else #else
#include "md5.h" #include "md5.h"
#endif #endif
@@ -29,220 +29,221 @@
// Basic Auth hash = base64("username:password") // Basic Auth hash = base64("username:password")
bool checkBasicAuthentication(const char * hash, const char * username, const char * password){ bool checkBasicAuthentication(const char * hash, const char * username, const char * password) {
if(username == NULL || password == NULL || hash == NULL) if (username == NULL || password == NULL || hash == NULL)
return false; return false;
size_t toencodeLen = strlen(username)+strlen(password)+1; size_t toencodeLen = strlen(username) + strlen(password) + 1;
size_t encodedLen = base64_encode_expected_len(toencodeLen); size_t encodedLen = base64_encode_expected_len(toencodeLen);
if(strlen(hash) != encodedLen) if (strlen(hash) != encodedLen)
// Fix from https://github.com/me-no-dev/ESPAsyncWebServer/issues/667 // Fix from https://github.com/me-no-dev/ESPAsyncWebServer/issues/667
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
if(strlen(hash) != encodedLen) if (strlen(hash) != encodedLen)
#else #else
if (strlen(hash) != encodedLen - 1) if (strlen(hash) != encodedLen - 1)
#endif #endif
return false; return false;
char *toencode = new char[toencodeLen+1]; char * toencode = new char[toencodeLen + 1];
if(toencode == NULL){ if (toencode == NULL) {
return false; return false;
} }
char *encoded = new char[base64_encode_expected_len(toencodeLen)+1]; char * encoded = new char[base64_encode_expected_len(toencodeLen) + 1];
if(encoded == NULL){ if (encoded == NULL) {
delete[] toencode; delete[] toencode;
return false; return false;
} }
sprintf_P(toencode, PSTR("%s:%s"), username, password); sprintf_P(toencode, PSTR("%s:%s"), username, password);
if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && memcmp(hash, encoded, encodedLen) == 0){ if (base64_encode_chars(toencode, toencodeLen, encoded) > 0 && memcmp(hash, encoded, encodedLen) == 0) {
delete[] toencode;
delete[] encoded;
return true;
}
delete[] toencode; delete[] toencode;
delete[] encoded; delete[] encoded;
return true; return false;
}
delete[] toencode;
delete[] encoded;
return false;
} }
static bool getMD5(uint8_t * data, uint16_t len, char * output){//33 bytes or more static bool getMD5(uint8_t * data, uint16_t len, char * output) { //33 bytes or more
#ifdef ESP32 #ifdef ESP32
mbedtls_md5_context _ctx; MD5Builder md5;
md5.begin();
md5.add(data, len);
md5.calculate();
md5.getChars(output);
#else #else
md5_context_t _ctx; md5_context_t _ctx;
#endif
uint8_t i;
uint8_t * _buf = (uint8_t*)malloc(16);
if(_buf == NULL)
return false;
memset(_buf, 0x00, 16);
#ifdef ESP32
#if ESP_IDF_VERSION_MAJOR < 5
mbedtls_md5_init(&_ctx);
mbedtls_md5_starts_ret(&_ctx);
mbedtls_md5_update_ret(&_ctx, data, len);
mbedtls_md5_finish_ret(&_ctx, _buf);
#else
mbedtls_md5_init(&_ctx);
mbedtls_md5_starts(&_ctx);
mbedtls_md5_update(&_ctx, data, len);
mbedtls_md5_finish(&_ctx, _buf);
#endif
#else
MD5Init(&_ctx);
MD5Update(&_ctx, data, len);
MD5Final(_buf, &_ctx);
#endif
for(i = 0; i < 16; i++) {
sprintf_P(output + (i * 2), PSTR("%02x"), _buf[i]);
}
free(_buf);
return true;
}
static String genRandomMD5(){ uint8_t * _buf = (uint8_t *)malloc(16);
#ifdef ESP8266 if (_buf == NULL)
uint32_t r = RANDOM_REG32; return false;
#else memset(_buf, 0x00, 16);
uint32_t r = rand();
#endif
char * out = (char*)malloc(33);
if(out == NULL || !getMD5((uint8_t*)(&r), 4, out))
return emptyString;
String res = String(out);
free(out);
return res;
}
static String stringMD5(const String& in){ MD5Init(&_ctx);
char * out = (char*)malloc(33); MD5Update(&_ctx, data, len);
if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out)) MD5Final(_buf, &_ctx);
return emptyString;
String res = String(out);
free(out);
return res;
}
String generateDigestHash(const char * username, const char * password, const char * realm){ for (uint8_t i = 0; i < 16; i++) {
if(username == NULL || password == NULL || realm == NULL){ sprintf_P(output + (i * 2), PSTR("%02x"), _buf[i]);
return emptyString;
}
char * out = (char*)malloc(33);
String res = String(username);
res += ':';
res.concat(realm);
res += ':';
String in = res;
in.concat(password);
if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out))
return emptyString;
res.concat(out);
free(out);
return res;
}
String requestDigestAuthentication(const char * realm){
String header = F("realm=\"");
if(realm == NULL)
header.concat(F("asyncesp"));
else
header.concat(realm);
header.concat(F("\", qop=\"auth\", nonce=\""));
header.concat(genRandomMD5());
header.concat(F("\", opaque=\""));
header.concat(genRandomMD5());
header += '"';
return header;
}
bool checkDigestAuthentication(const char * header, const __FlashStringHelper *method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri){
if(username == NULL || password == NULL || header == NULL || method == NULL){
//os_printf("AUTH FAIL: missing requred fields\n");
return false;
}
String myHeader = String(header);
int nextBreak = myHeader.indexOf(',');
if(nextBreak < 0){
//os_printf("AUTH FAIL: no variables\n");
return false;
}
String myUsername = String();
String myRealm = String();
String myNonce = String();
String myUri = String();
String myResponse = String();
String myQop = String();
String myNc = String();
String myCnonce = String();
myHeader += F(", ");
do {
String avLine = myHeader.substring(0, nextBreak);
avLine.trim();
myHeader = myHeader.substring(nextBreak+1);
nextBreak = myHeader.indexOf(',');
int eqSign = avLine.indexOf('=');
if(eqSign < 0){
//os_printf("AUTH FAIL: no = sign\n");
return false;
}
String varName = avLine.substring(0, eqSign);
avLine = avLine.substring(eqSign + 1);
if(avLine.startsWith(String('"'))){
avLine = avLine.substring(1, avLine.length() - 1);
} }
if(varName.equals(F("username"))){ free(_buf);
if(!avLine.equals(username)){ #endif
//os_printf("AUTH FAIL: username\n");
return false;
}
myUsername = avLine;
} else if(varName.equals(F("realm"))){
if(realm != NULL && !avLine.equals(realm)){
//os_printf("AUTH FAIL: realm\n");
return false;
}
myRealm = avLine;
} else if(varName.equals(F("nonce"))){
if(nonce != NULL && !avLine.equals(nonce)){
//os_printf("AUTH FAIL: nonce\n");
return false;
}
myNonce = avLine;
} else if(varName.equals(F("opaque"))){
if(opaque != NULL && !avLine.equals(opaque)){
//os_printf("AUTH FAIL: opaque\n");
return false;
}
} else if(varName.equals(F("uri"))){
if(uri != NULL && !avLine.equals(uri)){
//os_printf("AUTH FAIL: uri\n");
return false;
}
myUri = avLine;
} else if(varName.equals(F("response"))){
myResponse = avLine;
} else if(varName.equals(F("qop"))){
myQop = avLine;
} else if(varName.equals(F("nc"))){
myNc = avLine;
} else if(varName.equals(F("cnonce"))){
myCnonce = avLine;
}
} while(nextBreak > 0);
String ha1 = (passwordIsHash) ? String(password) : stringMD5(myUsername + ':' + myRealm + ':' + String(password));
String ha2 = String(method) + ':' + myUri;
String response = ha1 + ':' + myNonce + ':' + myNc + ':' + myCnonce + ':' + myQop + ':' + stringMD5(ha2);
if(myResponse.equals(stringMD5(response))){
//os_printf("AUTH SUCCESS\n");
return true; return true;
}
//os_printf("AUTH FAIL: password\n");
return false;
} }
static String genRandomMD5() {
#ifdef ESP8266
uint32_t r = RANDOM_REG32;
#else
uint32_t r = rand();
#endif
char * out = (char *)malloc(33);
if (out == NULL || !getMD5((uint8_t *)(&r), 4, out))
return emptyString;
String res = String(out);
free(out);
return res;
}
static String stringMD5(const String & in) {
char * out = (char *)malloc(33);
if (out == NULL || !getMD5((uint8_t *)(in.c_str()), in.length(), out))
return emptyString;
String res = String(out);
free(out);
return res;
}
String generateDigestHash(const char * username, const char * password, const char * realm) {
if (username == NULL || password == NULL || realm == NULL) {
return emptyString;
}
char * out = (char *)malloc(33);
String res = String(username);
res += ':';
res.concat(realm);
res += ':';
String in = res;
in.concat(password);
if (out == NULL || !getMD5((uint8_t *)(in.c_str()), in.length(), out))
return emptyString;
res.concat(out);
free(out);
return res;
}
String requestDigestAuthentication(const char * realm) {
String header = F("realm=\"");
if (realm == NULL)
header.concat(F("asyncesp"));
else
header.concat(realm);
header.concat(F("\", qop=\"auth\", nonce=\""));
header.concat(genRandomMD5());
header.concat(F("\", opaque=\""));
header.concat(genRandomMD5());
header += '"';
return header;
}
bool checkDigestAuthentication(const char * header,
const __FlashStringHelper * method,
const char * username,
const char * password,
const char * realm,
bool passwordIsHash,
const char * nonce,
const char * opaque,
const char * uri) {
if (username == NULL || password == NULL || header == NULL || method == NULL) {
//os_printf("AUTH FAIL: missing requred fields\n");
return false;
}
String myHeader = String(header);
int nextBreak = myHeader.indexOf(',');
if (nextBreak < 0) {
//os_printf("AUTH FAIL: no variables\n");
return false;
}
String myUsername = String();
String myRealm = String();
String myNonce = String();
String myUri = String();
String myResponse = String();
String myQop = String();
String myNc = String();
String myCnonce = String();
myHeader += F(", ");
do {
String avLine = myHeader.substring(0, nextBreak);
avLine.trim();
myHeader = myHeader.substring(nextBreak + 1);
nextBreak = myHeader.indexOf(',');
int eqSign = avLine.indexOf('=');
if (eqSign < 0) {
//os_printf("AUTH FAIL: no = sign\n");
return false;
}
String varName = avLine.substring(0, eqSign);
avLine = avLine.substring(eqSign + 1);
if (avLine.startsWith(String('"'))) {
avLine = avLine.substring(1, avLine.length() - 1);
}
if (varName.equals(F("username"))) {
if (!avLine.equals(username)) {
//os_printf("AUTH FAIL: username\n");
return false;
}
myUsername = avLine;
} else if (varName.equals(F("realm"))) {
if (realm != NULL && !avLine.equals(realm)) {
//os_printf("AUTH FAIL: realm\n");
return false;
}
myRealm = avLine;
} else if (varName.equals(F("nonce"))) {
if (nonce != NULL && !avLine.equals(nonce)) {
//os_printf("AUTH FAIL: nonce\n");
return false;
}
myNonce = avLine;
} else if (varName.equals(F("opaque"))) {
if (opaque != NULL && !avLine.equals(opaque)) {
//os_printf("AUTH FAIL: opaque\n");
return false;
}
} else if (varName.equals(F("uri"))) {
if (uri != NULL && !avLine.equals(uri)) {
//os_printf("AUTH FAIL: uri\n");
return false;
}
myUri = avLine;
} else if (varName.equals(F("response"))) {
myResponse = avLine;
} else if (varName.equals(F("qop"))) {
myQop = avLine;
} else if (varName.equals(F("nc"))) {
myNc = avLine;
} else if (varName.equals(F("cnonce"))) {
myCnonce = avLine;
}
} while (nextBreak > 0);
String ha1 = (passwordIsHash) ? String(password) : stringMD5(myUsername + ':' + myRealm + ':' + String(password));
String ha2 = String(method) + ':' + myUri;
String response = ha1 + ':' + myNonce + ':' + myNc + ':' + myCnonce + ':' + myQop + ':' + stringMD5(ha2);
if (myResponse.equals(stringMD5(response))) {
//os_printf("AUTH SUCCESS\n");
return true;
}
//os_printf("AUTH FAIL: password\n");
return false;
}

View File

@@ -175,7 +175,7 @@ AsyncCallbackWebHandler & AsyncWebServer::on(const char * uri, ArRequestHandlerF
return *handler; return *handler;
} }
// added by proddy // added by proddy for EMS-ESP
void AsyncWebServer::on(const char * uri, ArJsonRequestHandlerFunction onRequest) { void AsyncWebServer::on(const char * uri, ArJsonRequestHandlerFunction onRequest) {
auto * handler = new AsyncCallbackJsonWebHandler(uri, onRequest); auto * handler = new AsyncCallbackJsonWebHandler(uri, onRequest);
addHandler(handler); addHandler(handler);

View File

@@ -0,0 +1,284 @@
/*
* FIPS-180-1 compliant SHA-1 implementation
*
* Copyright (C) 2006-2015, ARM Limited, All Rights Reserved
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is part of mbed TLS (https://tls.mbed.org)
* Modified for esp32 by Lucas Saavedra Vaz on 11 Jan 2024
*/
#include <Arduino.h>
#if ESP_IDF_VERSION_MAJOR < 5
#include "SHA1Builder.h"
// 32-bit integer manipulation macros (big endian)
#ifndef GET_UINT32_BE
#define GET_UINT32_BE(n, b, i) \
{ (n) = ((uint32_t)(b)[(i)] << 24) | ((uint32_t)(b)[(i) + 1] << 16) | ((uint32_t)(b)[(i) + 2] << 8) | ((uint32_t)(b)[(i) + 3]); }
#endif
#ifndef PUT_UINT32_BE
#define PUT_UINT32_BE(n, b, i) \
{ \
(b)[(i)] = (uint8_t)((n) >> 24); \
(b)[(i) + 1] = (uint8_t)((n) >> 16); \
(b)[(i) + 2] = (uint8_t)((n) >> 8); \
(b)[(i) + 3] = (uint8_t)((n)); \
}
#endif
// Constants
static const uint8_t sha1_padding[64] = {0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
// Private methods
void SHA1Builder::process(const uint8_t * data) {
uint32_t temp, W[16], A, B, C, D, E;
GET_UINT32_BE(W[0], data, 0);
GET_UINT32_BE(W[1], data, 4);
GET_UINT32_BE(W[2], data, 8);
GET_UINT32_BE(W[3], data, 12);
GET_UINT32_BE(W[4], data, 16);
GET_UINT32_BE(W[5], data, 20);
GET_UINT32_BE(W[6], data, 24);
GET_UINT32_BE(W[7], data, 28);
GET_UINT32_BE(W[8], data, 32);
GET_UINT32_BE(W[9], data, 36);
GET_UINT32_BE(W[10], data, 40);
GET_UINT32_BE(W[11], data, 44);
GET_UINT32_BE(W[12], data, 48);
GET_UINT32_BE(W[13], data, 52);
GET_UINT32_BE(W[14], data, 56);
GET_UINT32_BE(W[15], data, 60);
#define sha1_S(x, n) ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n)))
#define sha1_R(t) (temp = W[(t - 3) & 0x0F] ^ W[(t - 8) & 0x0F] ^ W[(t - 14) & 0x0F] ^ W[t & 0x0F], (W[t & 0x0F] = sha1_S(temp, 1)))
#define sha1_P(a, b, c, d, e, x) \
{ \
e += sha1_S(a, 5) + sha1_F(b, c, d) + sha1_K + x; \
b = sha1_S(b, 30); \
}
A = state[0];
B = state[1];
C = state[2];
D = state[3];
E = state[4];
#define sha1_F(x, y, z) (z ^ (x & (y ^ z)))
#define sha1_K 0x5A827999
sha1_P(A, B, C, D, E, W[0]);
sha1_P(E, A, B, C, D, W[1]);
sha1_P(D, E, A, B, C, W[2]);
sha1_P(C, D, E, A, B, W[3]);
sha1_P(B, C, D, E, A, W[4]);
sha1_P(A, B, C, D, E, W[5]);
sha1_P(E, A, B, C, D, W[6]);
sha1_P(D, E, A, B, C, W[7]);
sha1_P(C, D, E, A, B, W[8]);
sha1_P(B, C, D, E, A, W[9]);
sha1_P(A, B, C, D, E, W[10]);
sha1_P(E, A, B, C, D, W[11]);
sha1_P(D, E, A, B, C, W[12]);
sha1_P(C, D, E, A, B, W[13]);
sha1_P(B, C, D, E, A, W[14]);
sha1_P(A, B, C, D, E, W[15]);
sha1_P(E, A, B, C, D, sha1_R(16));
sha1_P(D, E, A, B, C, sha1_R(17));
sha1_P(C, D, E, A, B, sha1_R(18));
sha1_P(B, C, D, E, A, sha1_R(19));
#undef sha1_K
#undef sha1_F
#define sha1_F(x, y, z) (x ^ y ^ z)
#define sha1_K 0x6ED9EBA1
sha1_P(A, B, C, D, E, sha1_R(20));
sha1_P(E, A, B, C, D, sha1_R(21));
sha1_P(D, E, A, B, C, sha1_R(22));
sha1_P(C, D, E, A, B, sha1_R(23));
sha1_P(B, C, D, E, A, sha1_R(24));
sha1_P(A, B, C, D, E, sha1_R(25));
sha1_P(E, A, B, C, D, sha1_R(26));
sha1_P(D, E, A, B, C, sha1_R(27));
sha1_P(C, D, E, A, B, sha1_R(28));
sha1_P(B, C, D, E, A, sha1_R(29));
sha1_P(A, B, C, D, E, sha1_R(30));
sha1_P(E, A, B, C, D, sha1_R(31));
sha1_P(D, E, A, B, C, sha1_R(32));
sha1_P(C, D, E, A, B, sha1_R(33));
sha1_P(B, C, D, E, A, sha1_R(34));
sha1_P(A, B, C, D, E, sha1_R(35));
sha1_P(E, A, B, C, D, sha1_R(36));
sha1_P(D, E, A, B, C, sha1_R(37));
sha1_P(C, D, E, A, B, sha1_R(38));
sha1_P(B, C, D, E, A, sha1_R(39));
#undef sha1_K
#undef sha1_F
#define sha1_F(x, y, z) ((x & y) | (z & (x | y)))
#define sha1_K 0x8F1BBCDC
sha1_P(A, B, C, D, E, sha1_R(40));
sha1_P(E, A, B, C, D, sha1_R(41));
sha1_P(D, E, A, B, C, sha1_R(42));
sha1_P(C, D, E, A, B, sha1_R(43));
sha1_P(B, C, D, E, A, sha1_R(44));
sha1_P(A, B, C, D, E, sha1_R(45));
sha1_P(E, A, B, C, D, sha1_R(46));
sha1_P(D, E, A, B, C, sha1_R(47));
sha1_P(C, D, E, A, B, sha1_R(48));
sha1_P(B, C, D, E, A, sha1_R(49));
sha1_P(A, B, C, D, E, sha1_R(50));
sha1_P(E, A, B, C, D, sha1_R(51));
sha1_P(D, E, A, B, C, sha1_R(52));
sha1_P(C, D, E, A, B, sha1_R(53));
sha1_P(B, C, D, E, A, sha1_R(54));
sha1_P(A, B, C, D, E, sha1_R(55));
sha1_P(E, A, B, C, D, sha1_R(56));
sha1_P(D, E, A, B, C, sha1_R(57));
sha1_P(C, D, E, A, B, sha1_R(58));
sha1_P(B, C, D, E, A, sha1_R(59));
#undef sha1_K
#undef sha1_F
#define sha1_F(x, y, z) (x ^ y ^ z)
#define sha1_K 0xCA62C1D6
sha1_P(A, B, C, D, E, sha1_R(60));
sha1_P(E, A, B, C, D, sha1_R(61));
sha1_P(D, E, A, B, C, sha1_R(62));
sha1_P(C, D, E, A, B, sha1_R(63));
sha1_P(B, C, D, E, A, sha1_R(64));
sha1_P(A, B, C, D, E, sha1_R(65));
sha1_P(E, A, B, C, D, sha1_R(66));
sha1_P(D, E, A, B, C, sha1_R(67));
sha1_P(C, D, E, A, B, sha1_R(68));
sha1_P(B, C, D, E, A, sha1_R(69));
sha1_P(A, B, C, D, E, sha1_R(70));
sha1_P(E, A, B, C, D, sha1_R(71));
sha1_P(D, E, A, B, C, sha1_R(72));
sha1_P(C, D, E, A, B, sha1_R(73));
sha1_P(B, C, D, E, A, sha1_R(74));
sha1_P(A, B, C, D, E, sha1_R(75));
sha1_P(E, A, B, C, D, sha1_R(76));
sha1_P(D, E, A, B, C, sha1_R(77));
sha1_P(C, D, E, A, B, sha1_R(78));
sha1_P(B, C, D, E, A, sha1_R(79));
#undef sha1_K
#undef sha1_F
state[0] += A;
state[1] += B;
state[2] += C;
state[3] += D;
state[4] += E;
}
// Public methods
void SHA1Builder::begin(void) {
total[0] = 0;
total[1] = 0;
state[0] = 0x67452301;
state[1] = 0xEFCDAB89;
state[2] = 0x98BADCFE;
state[3] = 0x10325476;
state[4] = 0xC3D2E1F0;
memset(buffer, 0x00, sizeof(buffer));
memset(hash, 0x00, sizeof(hash));
}
void SHA1Builder::add(const uint8_t * data, size_t len) {
size_t fill;
uint32_t left;
if (len == 0) {
return;
}
left = total[0] & 0x3F;
fill = 64 - left;
total[0] += (uint32_t)len;
total[0] &= 0xFFFFFFFF;
if (total[0] < (uint32_t)len) {
total[1]++;
}
if (left && len >= fill) {
memcpy((void *)(buffer + left), data, fill);
process(buffer);
data += fill;
len -= fill;
left = 0;
}
while (len >= 64) {
process(data);
data += 64;
len -= 64;
}
if (len > 0) {
memcpy((void *)(buffer + left), data, len);
}
}
void SHA1Builder::calculate(void) {
uint32_t last, padn;
uint32_t high, low;
uint8_t msglen[8];
high = (total[0] >> 29) | (total[1] << 3);
low = (total[0] << 3);
PUT_UINT32_BE(high, msglen, 0);
PUT_UINT32_BE(low, msglen, 4);
last = total[0] & 0x3F;
padn = (last < 56) ? (56 - last) : (120 - last);
add((uint8_t *)sha1_padding, padn);
add(msglen, 8);
PUT_UINT32_BE(state[0], hash, 0);
PUT_UINT32_BE(state[1], hash, 4);
PUT_UINT32_BE(state[2], hash, 8);
PUT_UINT32_BE(state[3], hash, 12);
PUT_UINT32_BE(state[4], hash, 16);
}
void SHA1Builder::getBytes(uint8_t * output) {
memcpy(output, hash, SHA1_HASH_SIZE);
}
#endif // ESP_IDF_VERSION_MAJOR < 5

View File

@@ -0,0 +1,39 @@
// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SHA1Builder_h
#define SHA1Builder_h
#include <Stream.h>
#include <WString.h>
#define SHA1_HASH_SIZE 20
class SHA1Builder {
private:
uint32_t total[2]; /* number of bytes processed */
uint32_t state[5]; /* intermediate digest state */
unsigned char buffer[64]; /* data block being processed */
uint8_t hash[SHA1_HASH_SIZE]; /* SHA-1 result */
void process(const uint8_t * data);
public:
void begin();
void add(const uint8_t * data, size_t len);
void calculate();
void getBytes(uint8_t * output);
};
#endif // SHA1Builder_h

View File

@@ -3,50 +3,16 @@
MQTT client library for the Espressif devices ESP8266 and ESP32 on the Arduino framework. MQTT client library for the Espressif devices ESP8266 and ESP32 on the Arduino framework.
Aims to be a non-blocking, fully compliant MQTT 3.1.1 client. Aims to be a non-blocking, fully compliant MQTT 3.1.1 client.
![platformio](https://github.com/bertmelis/espMqttClient/actions/workflows/build_platformio.yml/badge.svg) Copy of <https://github.com/bertmelis/espMqttClient>
![cpplint](https://github.com/bertmelis/espMqttClient/actions/workflows/cpplint.yml/badge.svg)
![cppcheck](https://github.com/bertmelis/espMqttClient/actions/workflows/cppcheck.yml/badge.svg)
[![PlatformIO Registry](https://badges.registry.platformio.org/packages/bertmelis/library/espMqttClient.svg)](https://registry.platformio.org/libraries/bertmelis/espMqttClient)
# Features Based on Version 1.7.0 - <https://github.com/bertmelis/espMqttClient/tree/v1.7.0>
- MQTT 3.1.1 compliant library with additional changes to support EMS-ESP such as compiling with Tasmota and not using `SecureWifiClient` in these two files:
- Sending and receiving at all QoS levels
- TCP and TCP/TLS using standard WiFiClient and WiFiClientSecure connections
- Virtually unlimited incoming and outgoing payload sizes
- Readable and understandable code
- Fully async clients available via [AsyncTCP](https://github.com/me-no-dev/AsyncTCP) or [ESPAsnycTCP](https://github.com/me-no-dev/ESPAsyncTCP) (no TLS supported)
- Supported platforms:
- Espressif ESP8266 and ESP32 using the Arduino framework
- Basic Linux compatibility*. This includes WSL on Windows
> Linux compatibility is mainly for automatic testing. It relies on a quick and dirty Arduino-style `Client` with a POSIX TCP client underneath and Arduino-style `IPAddress` class. These are lacking many features needed for proper Linux support. ```
src/espMqttClient.cpp
# Documentation src/Transport/ClientSecureSync.h
```
See [documentation](https://www.emelis.net/espMqttClient/) and the [examples](examples/).
## Limitations
### MQTT 3.1.1 Compliancy
Outgoing messages and session data are not stored in non-volatile memory. Any events like loss of power or sudden resets result in loss of data. Despite this limitation, one could still consider this library as fully complaint based on the non normative remark in point 4.1.1 of the specification.
### Non-blocking
This library aims to be fully non-blocking. It is however limited by the underlying `WiFiClient` library which is part of the Arduino framework and has a blocking `connect` method. This is not an issue on ESP32 because the call is offloaded to a separate task. On ESP8266 however, connecting will block until succesful or until the connection timeouts.
If you need a fully asynchronous MQTT client, you can use `espMqttClientAsync` which uses AsyncTCP/ESPAsyncTCP under the hood. These underlying libraries do not support TLS (anymore). I will not provide support TLS for the async client.
# Bugs and feature requests
Please use Github's facilities to get in touch.
# About this library
This client wouldn't exist without [Async-mqtt-client](https://github.com/marvinroger/async-mqtt-client). It has been my go-to MQTT client for many years. It was fast, reliable and had features that were non-existing in alternative libraries. However, the underlying async TCP libraries are lacking updates, especially updates related to secure connections. Adapting this library to use up-to-date TCP clients would not be trivial. I eventually decided to write my own MQTT library, from scratch.
The result is an almost non-blocking library with no external dependencies. The library is almost a drop-in replacement for the async-mqtt-client except a few parameter type changes (eg. `uint8_t*` instead of `char*` for payloads).
# License # License

View File

@@ -8,12 +8,8 @@ the LICENSE file.
#pragma once #pragma once
#ifndef TASMOTA_SDK
#define EMC_CLIENT_SECURE
#endif
#ifndef EMC_TX_TIMEOUT #ifndef EMC_TX_TIMEOUT
#define EMC_TX_TIMEOUT 2000 #define EMC_TX_TIMEOUT 10000
#endif #endif
#ifndef EMC_RX_BUFFER_SIZE #ifndef EMC_RX_BUFFER_SIZE
@@ -64,3 +60,16 @@ the LICENSE file.
#ifndef EMC_USE_WATCHDOG #ifndef EMC_USE_WATCHDOG
#define EMC_USE_WATCHDOG 0 #define EMC_USE_WATCHDOG 0
#endif #endif
#ifndef EMC_USE_MEMPOOL
#define EMC_USE_MEMPOOL 0
#endif
#if EMC_USE_MEMPOOL
#ifndef EMC_NUM_POOL_ELEMENTS
#define EMC_NUM_POOL_ELEMENTS 32
#endif
#ifndef EMC_SIZE_POOL_ELEMENTS
#define EMC_SIZE_POOL_ELEMENTS 128
#endif
#endif

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Bert Melis
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,105 @@
# Memory Pool
EARLY VERSION. USE AT OWN RISK.
### Description
This is a simple memory pool that doesn't solve the fragmentation problem but contains it. Inside the pool you will still suffer memory fragmentation. The upside is that you're not restricted on memory size. As long as it fits in the pool, you can request any size!
For applications where the (maximum) size to allocate is known, a simple fixed block size memory pool is available. There is no memory fragmentation happening in this case. The downside is wastage of memory if you need less then the specified blocksize.
#### Features
- pool memory is statically allocated
- pool size adjusts on architecture
- no size calculation required: input number of blocks and size of block
- header-only library
- Variable size pool: no restriction on allocated size
- Variable size pool: malloc and free are O(n); The number of allocated blocks affects lookup.
- Fixed size pool: malloc and free are O(1).
[![Test with Platformio](https://github.com/bertmelis/MemoryPool/actions/workflows/test-platformio.yml/badge.svg)](https://github.com/bertmelis/MemoryPool/actions/workflows/test-platformio.yml)
[![cpplint](https://github.com/bertmelis/MemoryPool/actions/workflows/cpplint.yml/badge.svg)](https://github.com/bertmelis/MemoryPool/actions/workflows/cpplint.yml)
<!---[![cppcheck](https://github.com/bertmelis/MemoryPool/actions/workflows/cppcheck.yml/badge.svg)](https://github.com/bertmelis/MemoryPool/actions/workflows/cppcheck.yml)--->
### Usage
#### Variable size pool
```cpp
#include <MemoryPool.h>
Struct MyStruct {
unsigned int id;
std::size_t size;
unsigned char data[256];
};
// pool will be able to hold 10 blocks the size of MyStruct
MemoryPool::Variable<10, sizeof(MyStruct)> pool;
// you can allocate the specified blocksize
// allocation is done in number of 'unsigned char'
MyStruct* s = reinterpret_cast<MyStruct*>(pool.malloc(sizeof(MyStruct)));
// you can allocate less than the specified blocksize
int* i = reinterpret_cast<int*>(pool.malloc(sizeof(int)));
// you can allocate more than the specified blocksize
unsigned char* m = reinterpret_cast<unsigned char*>(pool.malloc(400));
pool.free(s);
pool.free(i);
pool.free(m);
```
#### Fixed size pool
```cpp
#include <MemoryPool.h>
Struct MyStruct {
unsigned int id;
std::size_t size;
unsigned char data[256];
};
// pool will be able to hold 10 blocks the size of MyStruct
MemoryPool::Fixed<10, sizeof(MyStruct)> pool;
// there is no size argument in the malloc function!
MyStruct* s = reinterpret_cast<MyStruct*>(pool.malloc());
// you can allocate less than the specified blocksize
int* i = reinterpret_cast<int*>(pool.malloc());
pool.free(s);
pool.free(i);
```
#### How it works
##### Variable size pool
Free blocks are organized as a linked list with their header (contains pointer to next and size). An allocated block also has this header with it's pointer set to `nullptr`. Therefore, each allocation wastes memory the size of the header (`sizeof(void*) + sizeof(std::size_t)`). On creation, the pool calculations the needed space to store the number of blocks wich each their header.
However, memory allocation isn't restricted the the specified blocksize. So in reality, you can allocate more if you allocate larger chunks because less memory blocks means less headers. After all, memory needs to be contiguous.
If you inspect the pool you'll see that a free pool only has one big block.
Allocation is linear: the pool is iterated until a suitable spot is found.
Freeing is also linear as the pool is traversed to insert the chunk in the linked list of free blocks
When freeing, free blocks which are adjacent are combined into one.
##### Fixed size pool
The fixed size pool is implemented as an array. Free blocks are saved as a linked list in this array.
### Bugs and feature requests
Please use Github's facilities to get in touch.
### License
This library is released under the MIT Licence. A copy is included in the repo.

View File

@@ -0,0 +1,16 @@
# Datatypes (KEYWORD1)
Fixed KEYWORD1
Variable KEYWORD1
# Methods and Functions (KEYWORD2)
malloc KEYWORD2
free KEYWORD2
freeMemory KEYWORD2
maxBlockSize KEYWORD2
print KEYWORD2
# Structures (KEYWORD3)
# structure KEYWORD3
# Constants (LITERAL1)
MemoryPool LITERAL1

View File

@@ -0,0 +1,21 @@
{
"name": "MemoryPool",
"keywords": "memory",
"description": "A simple memory pool for fixed and variable sizes",
"authors":
{
"name": "Bert Melis",
"url": "https://github.com/bertmelis"
},
"license": "MIT",
"homepage": "https://github.com/bertmelis/MemoryPool",
"repository":
{
"type": "git",
"url": "https://github.com/bertmelis/MemoryPool.git"
},
"version": "0.1.0",
"frameworks": "*",
"platforms": "*",
"headers": ["MemoryPool.h"]
}

View File

@@ -0,0 +1,10 @@
name=MemoryPool
version=0.1.0
author=Bert Melis
maintainer=Bert Melis
sentence=A simple memory pool for fixed and variable sizes
paragraph=
category=Other
url=https://github.com/bertmelis/MemoryPool
architectures=*
includes=MemoryPool.h

View File

@@ -0,0 +1,119 @@
/*
Copyright (c) 2024 Bert Melis. All rights reserved.
This work is licensed under the terms of the MIT license.
For a copy, see <https://opensource.org/licenses/MIT> or
the LICENSE file.
*/
#pragma once
#include <cstddef> // std::size_t
#include <cassert> // assert
#if _GLIBCXX_HAS_GTHREADS
#include <mutex> // NOLINT [build/c++11] std::mutex, std::lock_guard
#else
#warning "The memory pool is not thread safe"
#endif
#ifdef MEMPOL_DEBUG
#include <iostream>
#endif
namespace MemoryPool {
template <std::size_t nrBlocks, std::size_t blocksize>
class Fixed {
public:
Fixed() // cppcheck-suppress uninitMemberVar
: _buffer{0}
, _head(_buffer) {
unsigned char* b = _head;
std::size_t adjustedBlocksize = sizeof(std::size_t) > blocksize ? sizeof(std::size_t) : blocksize;
for (std::size_t i = 0; i < nrBlocks - 1; ++i) {
*reinterpret_cast<unsigned char**>(b) = b + adjustedBlocksize;
b += adjustedBlocksize;
}
*reinterpret_cast<unsigned char**>(b) = nullptr;
}
// no copy nor move
Fixed (const Fixed&) = delete;
Fixed& operator= (const Fixed&) = delete;
void* malloc() {
#if _GLIBCXX_HAS_GTHREADS
const std::lock_guard<std::mutex> lockGuard(_mutex);
#endif
if (_head) {
void* retVal = _head;
_head = *reinterpret_cast<unsigned char**>(_head);
return retVal;
}
return nullptr;
}
void free(void* ptr) {
if (!ptr) return;
#if _GLIBCXX_HAS_GTHREADS
const std::lock_guard<std::mutex> lockGuard(_mutex);
#endif
*reinterpret_cast<unsigned char**>(ptr) = _head;
_head = reinterpret_cast<unsigned char*>(ptr);
}
std::size_t freeMemory() {
#if _GLIBCXX_HAS_GTHREADS
const std::lock_guard<std::mutex> lockGuard(_mutex);
#endif
unsigned char* i = _head;
std::size_t retVal = 0;
while (i) {
retVal += blocksize;
i = reinterpret_cast<unsigned char**>(i)[0];
}
return retVal;
}
#ifdef MEMPOL_DEBUG
void print() {
std::size_t adjustedBlocksize = sizeof(std::size_t) > blocksize ? sizeof(std::size_t) : blocksize;
std::cout << "+--------------------" << std::endl;
std::cout << "|start:" << reinterpret_cast<void*>(_buffer) << std::endl;
std::cout << "|blocks:" << nrBlocks << std::endl;
std::cout << "|blocksize:" << adjustedBlocksize << std::endl;
std::cout << "|head: " << reinterpret_cast<void*>(_head) << std::endl;
unsigned char* currentBlock = _buffer;
for (std::size_t i = 0; i < nrBlocks; ++i) {
std::cout << "|" << i + 1 << ": " << reinterpret_cast<void*>(currentBlock) << std::endl;
if (_isFree(currentBlock)) {
std::cout << "| free" << std::endl;
std::cout << "| next: " << reinterpret_cast<void*>(*reinterpret_cast<unsigned char**>(currentBlock)) << std::endl;
} else {
std::cout << "| allocated" << std::endl;
}
currentBlock += adjustedBlocksize;
}
std::cout << "+--------------------" << std::endl;
}
bool _isFree(const unsigned char* ptr) {
unsigned char* b = _head;
while (b) {
if (b == ptr) return true;
b = *reinterpret_cast<unsigned char**>(b);
}
return false;
}
#endif
private:
unsigned char _buffer[nrBlocks * (sizeof(std::size_t) > blocksize ? sizeof(std::size_t) : blocksize)];
unsigned char* _head;
#if _GLIBCXX_HAS_GTHREADS
std::mutex _mutex;
#endif
};
} // end namespace MemoryPool

View File

@@ -0,0 +1,12 @@
/*
Copyright (c) 2024 Bert Melis. All rights reserved.
This work is licensed under the terms of the MIT license.
For a copy, see <https://opensource.org/licenses/MIT> or
the LICENSE file.
*/
#pragma once
#include "Variable.h"
#include "Fixed.h"

View File

@@ -0,0 +1,242 @@
/*
Copyright (c) 2024 Bert Melis. All rights reserved.
This work is licensed under the terms of the MIT license.
For a copy, see <https://opensource.org/licenses/MIT> or
the LICENSE file.
*/
#pragma once
#include <cstddef> // std::size_t
#include <cassert> // assert
#if _GLIBCXX_HAS_GTHREADS
#include <mutex> // NOLINT [build/c++11] std::mutex, std::lock_guard
#else
#warning "The memory pool is not thread safe"
#endif
#ifdef MEMPOL_DEBUG
#include <iostream>
#endif
namespace MemoryPool {
template <std::size_t nrBlocks, std::size_t blocksize>
class Variable {
public:
Variable()
: _buffer{0}
, _head(nullptr)
#ifdef MEMPOL_DEBUG
, _bufferSize(0)
#endif
{
std::size_t _normBlocksize = blocksize / sizeof(BlockHeader) + ((blocksize % sizeof(BlockHeader)) ? 1 : 0);
size_t nrBlocksToAlloc = nrBlocks * (_normBlocksize + 1);
BlockHeader* h = reinterpret_cast<BlockHeader*>(_buffer);
h->next = nullptr;
h->size = nrBlocksToAlloc;
_head = h;
#ifdef MEMPOL_DEBUG
_bufferSize = nrBlocksToAlloc;
#endif
}
// no copy nor move
Variable (const Variable&) = delete;
Variable& operator= (const Variable&) = delete;
void* malloc(size_t size) {
#if _GLIBCXX_HAS_GTHREADS
const std::lock_guard<std::mutex> lockGuard(_mutex);
#endif
if (size == 0) return nullptr;
size = (size / sizeof(BlockHeader) + (size % sizeof(BlockHeader) != 0)) + 1; // count by BlockHeader size, add 1 for header
#ifdef MEMPOL_DEBUG
std::cout << "malloc (raw) " << size << std::endl;
std::cout << "malloc (adj) " << size << " - ";
#endif
BlockHeader* currentBlock = _head;
BlockHeader* previousBlock = nullptr;
void* retVal = nullptr;
// iterate through linked free blocks
while (currentBlock) {
// consume whole block is size equals required size
if (currentBlock->size == size) {
if (previousBlock) previousBlock->next = currentBlock->next;
break;
// split block if size is larger and add second part to list of free blocks
} else if (currentBlock->size > size) {
BlockHeader* newBlock = currentBlock + size;
if (previousBlock) previousBlock->next = newBlock;
newBlock->next = currentBlock->next;
newBlock->size = currentBlock->size - size;
currentBlock->next = newBlock;
break;
}
previousBlock = currentBlock;
currentBlock = currentBlock->next;
}
if (currentBlock) {
if (currentBlock == _head) {
_head = currentBlock->next;
}
currentBlock->size = size;
currentBlock->next = nullptr; // used when freeing memory
retVal = currentBlock + 1;
#ifdef MEMPOL_DEBUG
std::cout << "ok" << std::endl;
#endif
} else {
#ifdef MEMPOL_DEBUG
std::cout << "nok" << std::endl;
#endif
(void)0;
}
return retVal;
}
void free(void* ptr) {
if (!ptr) return;
// check if ptr points to region in _buffer
#ifdef MEMPOL_DEBUG
std::cout << "free " << static_cast<void*>(reinterpret_cast<BlockHeader*>(ptr) - 1) << std::endl;
#endif
#if _GLIBCXX_HAS_GTHREADS
const std::lock_guard<std::mutex> lockGuard(_mutex);
#endif
BlockHeader* toFree = reinterpret_cast<BlockHeader*>(ptr) - 1;
BlockHeader* previous = reinterpret_cast<BlockHeader*>(_buffer);
BlockHeader* next = _head;
// toFree is the only free block
if (!next) {
_head = toFree;
return;
}
while (previous) {
if (!next || toFree < next) {
// 1. add block to linked list of free blocks
if (toFree < _head) {
toFree->next = _head;
_head = toFree;
} else {
previous->next = toFree;
toFree->next = next;
}
// 2. merge with previous if adjacent
if (toFree > _head && toFree == previous + previous->size) {
previous->size += toFree->size;
previous->next = toFree->next;
toFree = previous; // used in next check
}
// 3. merge with next if adjacent
if (toFree + toFree->size == next) {
toFree->size += next->size;
toFree->next = next->next;
}
// 4. done
return;
}
previous = next;
next = next->next;
}
}
std::size_t freeMemory() {
#if _GLIBCXX_HAS_GTHREADS
const std::lock_guard<std::mutex> lockGuard(_mutex);
#endif
size_t retVal = 0;
BlockHeader* currentBlock = reinterpret_cast<BlockHeader*>(_head);
while (currentBlock) {
retVal += currentBlock->size - 1;
currentBlock = currentBlock->next;
}
return retVal * sizeof(BlockHeader);
}
std::size_t maxBlockSize() {
#if _GLIBCXX_HAS_GTHREADS
const std::lock_guard<std::mutex> lockGuard(_mutex);
#endif
size_t retVal = 0;
BlockHeader* currentBlock = reinterpret_cast<BlockHeader*>(_head);
while (currentBlock) {
retVal = (currentBlock->size - 1 > retVal) ? currentBlock->size - 1 : retVal;
currentBlock = currentBlock->next;
}
return retVal * sizeof(BlockHeader);
}
#ifdef MEMPOL_DEBUG
void print() {
std::cout << "+--------------------" << std::endl;
std::cout << "|start:" << static_cast<void*>(_buffer) << std::endl;
std::cout << "|size:" << _bufferSize << std::endl;
std::cout << "|headersize:" << sizeof(BlockHeader) << std::endl;
std::cout << "|head: " << static_cast<void*>(_head) << std::endl;
BlockHeader* nextFreeBlock = _head;
BlockHeader* currentBlock = reinterpret_cast<BlockHeader*>(_buffer);
size_t blockNumber = 1;
while (currentBlock < reinterpret_cast<BlockHeader*>(_buffer) + _bufferSize) {
std::cout << "|" << blockNumber << ": " << static_cast<void*>(currentBlock) << std::endl;
std::cout << "| " << static_cast<void*>(currentBlock->next) << std::endl;
std::cout << "| " << currentBlock->size << std::endl;
if (currentBlock == nextFreeBlock) {
std::cout << "| free" << std::endl;
nextFreeBlock = nextFreeBlock->next;
} else {
std::cout << "| allocated" << std::endl;
}
++blockNumber;
currentBlock += currentBlock->size;
}
std::cout << "+--------------------" << std::endl;
}
#endif
private:
struct BlockHeader {
BlockHeader* next;
std::size_t size;
};
/*
pool size is aligned to sizeof(BlockHeader).
requested blocksize is therefore multiple of blockheader (rounded up)
total size = nr requested blocks * multiplier * blockheadersize
see constructor for calculation
*/
unsigned char _buffer[(nrBlocks * ((blocksize / sizeof(BlockHeader) + ((blocksize % sizeof(BlockHeader)) ? 1 : 0)) + 1)) * sizeof(BlockHeader)];
BlockHeader* _head;
#if _GLIBCXX_HAS_GTHREADS
std::mutex _mutex;
#endif
#ifdef MEMPOL_DEBUG
std::size_t _bufferSize;
#endif
};
} // end namespace MemoryPool

File diff suppressed because it is too large Load Diff

View File

@@ -24,179 +24,178 @@ the LICENSE file.
#include "Transport/Transport.h" #include "Transport/Transport.h"
class MqttClient { class MqttClient {
public: public:
virtual ~MqttClient(); virtual ~MqttClient();
bool connected() const; bool connected() const;
bool disconnected() const; bool disconnected() const;
bool connect(); bool connect();
bool disconnect(bool force = false); bool disconnect(bool force = false);
template <typename... Args> template <typename... Args>
uint16_t subscribe(const char * topic, uint8_t qos, Args &&... args) { uint16_t subscribe(const char* topic, uint8_t qos, Args&&... args) {
uint16_t packetId = _getNextPacketId(); uint16_t packetId = 0;
if (_state != State::connected) { if (_state != State::connected) {
packetId = 0; return packetId;
} else { } else {
EMC_SEMAPHORE_TAKE(); EMC_SEMAPHORE_TAKE();
if (!_addPacket(packetId, topic, qos, std::forward<Args>(args)...)) { packetId = _getNextPacketId();
emc_log_e("Could not create SUBSCRIBE packet"); if (!_addPacket(packetId, topic, qos, std::forward<Args>(args) ...)) {
packetId = 0; emc_log_e("Could not create SUBSCRIBE packet");
} packetId = 0;
EMC_SEMAPHORE_GIVE(); }
} EMC_SEMAPHORE_GIVE();
return packetId;
} }
template <typename... Args> return packetId;
uint16_t unsubscribe(const char * topic, Args &&... args) { }
uint16_t packetId = _getNextPacketId(); template <typename... Args>
if (_state != State::connected) { uint16_t unsubscribe(const char* topic, Args&&... args) {
packetId = 0; uint16_t packetId = 0;
} else { if (_state != State::connected) {
EMC_SEMAPHORE_TAKE(); return packetId;
if (!_addPacket(packetId, topic, std::forward<Args>(args)...)) { } else {
emc_log_e("Could not create UNSUBSCRIBE packet"); EMC_SEMAPHORE_TAKE();
packetId = 0; packetId = _getNextPacketId();
} if (!_addPacket(packetId, topic, std::forward<Args>(args) ...)) {
EMC_SEMAPHORE_GIVE(); emc_log_e("Could not create UNSUBSCRIBE packet");
} packetId = 0;
return packetId; }
EMC_SEMAPHORE_GIVE();
} }
uint16_t publish(const char * topic, uint8_t qos, bool retain, const uint8_t * payload, size_t length); return packetId;
uint16_t publish(const char * topic, uint8_t qos, bool retain, const char * payload); }
uint16_t publish(const char * topic, uint8_t qos, bool retain, espMqttClientTypes::PayloadCallback callback, size_t length); uint16_t publish(const char* topic, uint8_t qos, bool retain, const uint8_t* payload, size_t length);
void clearQueue(bool deleteSessionData = false); // Not MQTT compliant and may cause unpredictable results when `deleteSessionData` = true! uint16_t publish(const char* topic, uint8_t qos, bool retain, const char* payload);
const char * getClientId() const; uint16_t publish(const char* topic, uint8_t qos, bool retain, espMqttClientTypes::PayloadCallback callback, size_t length);
size_t queueSize(); // No const because of mutex void clearQueue(bool deleteSessionData = false); // Not MQTT compliant and may cause unpredictable results when `deleteSessionData` = true!
void loop(); const char* getClientId() const;
size_t queueSize(); // No const because of mutex
void loop();
protected: protected:
explicit MqttClient(espMqttClientTypes::UseInternalTask useInternalTask, uint8_t priority = 1, uint8_t core = 1); explicit MqttClient(espMqttClientTypes::UseInternalTask useInternalTask, uint8_t priority = 1, uint8_t core = 1);
espMqttClientTypes::UseInternalTask _useInternalTask; espMqttClientTypes::UseInternalTask _useInternalTask;
espMqttClientInternals::Transport * _transport; espMqttClientInternals::Transport* _transport;
espMqttClientTypes::OnConnectCallback _onConnectCallback; espMqttClientTypes::OnConnectCallback _onConnectCallback;
espMqttClientTypes::OnDisconnectCallback _onDisconnectCallback; espMqttClientTypes::OnDisconnectCallback _onDisconnectCallback;
espMqttClientTypes::OnSubscribeCallback _onSubscribeCallback; espMqttClientTypes::OnSubscribeCallback _onSubscribeCallback;
espMqttClientTypes::OnUnsubscribeCallback _onUnsubscribeCallback; espMqttClientTypes::OnUnsubscribeCallback _onUnsubscribeCallback;
espMqttClientTypes::OnMessageCallback _onMessageCallback; espMqttClientTypes::OnMessageCallback _onMessageCallback;
espMqttClientTypes::OnPublishCallback _onPublishCallback; espMqttClientTypes::OnPublishCallback _onPublishCallback;
espMqttClientTypes::OnErrorCallback _onErrorCallback; espMqttClientTypes::OnErrorCallback _onErrorCallback;
typedef void (*mqttClientHook)(void *); typedef void(*mqttClientHook)(void*);
const char * _clientId; const char* _clientId;
IPAddress _ip; IPAddress _ip;
const char * _host; const char* _host;
uint16_t _port; uint16_t _port;
bool _useIp; bool _useIp;
uint32_t _keepAlive; uint32_t _keepAlive;
bool _cleanSession; bool _cleanSession;
const char * _username; const char* _username;
const char * _password; const char* _password;
const char * _willTopic; const char* _willTopic;
const uint8_t * _willPayload; const uint8_t* _willPayload;
uint16_t _willPayloadLength; uint16_t _willPayloadLength;
uint8_t _willQos; uint8_t _willQos;
bool _willRetain; bool _willRetain;
uint32_t _timeout; uint32_t _timeout;
// state is protected to allow state changes by the transport system, defined in child classes // state is protected to allow state changes by the transport system, defined in child classes
// eg. to allow AsyncTCP // eg. to allow AsyncTCP
enum class State { enum class State {
disconnected = 0, disconnected = 0,
connectingTcp1 = 1, connectingTcp1 = 1,
connectingTcp2 = 2, connectingTcp2 = 2,
connectingMqtt = 3, connectingMqtt = 3,
connected = 4, connected = 4,
disconnectingMqtt1 = 5, disconnectingMqtt1 = 5,
disconnectingMqtt2 = 6, disconnectingMqtt2 = 6,
disconnectingTcp1 = 7, disconnectingTcp1 = 7,
disconnectingTcp2 = 8 disconnectingTcp2 = 8
}; };
std::atomic<State> _state; std::atomic<State> _state;
inline void _setState(State newState);
private: private:
char _generatedClientId[EMC_CLIENTID_LENGTH]; char _generatedClientId[EMC_CLIENTID_LENGTH];
uint16_t _packetId; uint16_t _packetId;
#if defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP32)
SemaphoreHandle_t _xSemaphore; SemaphoreHandle_t _xSemaphore;
TaskHandle_t _taskHandle; TaskHandle_t _taskHandle;
static void _loop(MqttClient * c); static void _loop(MqttClient* c);
#elif defined(ARDUINO_ARCH_ESP8266) && EMC_ESP8266_MULTITHREADING #elif defined(ARDUINO_ARCH_ESP8266) && EMC_ESP8266_MULTITHREADING
std::atomic<bool> _xSemaphore = false; std::atomic<bool> _xSemaphore = false;
#elif defined(__linux__) #elif defined(__linux__)
mutable std::mutex mtx; // modified by proddy for EMS-ESP compiling standalone std::mutex mtx;
#endif #endif
uint8_t _rxBuffer[EMC_RX_BUFFER_SIZE]; uint8_t _rxBuffer[EMC_RX_BUFFER_SIZE];
struct OutgoingPacket { struct OutgoingPacket {
uint32_t timeSent; uint32_t timeSent;
espMqttClientInternals::Packet packet; espMqttClientInternals::Packet packet;
template <typename... Args>
OutgoingPacket(uint32_t t, espMqttClientTypes::Error & error, Args &&... args)
: // NOLINT(runtime/references)
timeSent(t)
, packet(error, std::forward<Args>(args)...) {
}
};
espMqttClientInternals::Outbox<OutgoingPacket> _outbox;
size_t _bytesSent;
espMqttClientInternals::Parser _parser;
uint32_t _lastClientActivity;
uint32_t _lastServerActivity;
bool _pingSent;
espMqttClientTypes::DisconnectReason _disconnectReason;
uint16_t _getNextPacketId();
template <typename... Args> template <typename... Args>
bool _addPacket(Args &&... args) { OutgoingPacket(uint32_t t, espMqttClientTypes::Error& error, Args&&... args) : // NOLINT(runtime/references)
espMqttClientTypes::Error error(espMqttClientTypes::Error::SUCCESS); timeSent(t),
espMqttClientInternals::Outbox<OutgoingPacket>::Iterator it = _outbox.emplace(0, error, std::forward<Args>(args)...); packet(error, std::forward<Args>(args) ...) {}
if (it && error == espMqttClientTypes::Error::SUCCESS) { };
return true; espMqttClientInternals::Outbox<OutgoingPacket> _outbox;
} else { size_t _bytesSent;
if (it) espMqttClientInternals::Parser _parser;
_outbox.remove(it); uint32_t _lastClientActivity;
return false; uint32_t _lastServerActivity;
} bool _pingSent;
espMqttClientTypes::DisconnectReason _disconnectReason;
uint16_t _getNextPacketId();
template <typename... Args>
bool _addPacket(Args&&... args) {
espMqttClientTypes::Error error(espMqttClientTypes::Error::SUCCESS);
espMqttClientInternals::Outbox<OutgoingPacket>::Iterator it = _outbox.emplace(0, error, std::forward<Args>(args) ...);
if (it && error == espMqttClientTypes::Error::SUCCESS) {
return true;
} else {
if (it) _outbox.remove(it);
return false;
} }
}
template <typename... Args> template <typename... Args>
bool _addPacketFront(Args &&... args) { bool _addPacketFront(Args&&... args) {
espMqttClientTypes::Error error(espMqttClientTypes::Error::SUCCESS); espMqttClientTypes::Error error(espMqttClientTypes::Error::SUCCESS);
espMqttClientInternals::Outbox<OutgoingPacket>::Iterator it = _outbox.emplaceFront(0, error, std::forward<Args>(args)...); espMqttClientInternals::Outbox<OutgoingPacket>::Iterator it = _outbox.emplaceFront(0, error, std::forward<Args>(args) ...);
if (it && error == espMqttClientTypes::Error::SUCCESS) { if (it && error == espMqttClientTypes::Error::SUCCESS) {
return true; return true;
} else { } else {
if (it) if (it) _outbox.remove(it);
_outbox.remove(it); return false;
return false;
}
} }
}
void _checkOutbox(); void _checkOutbox();
int _sendPacket(); int _sendPacket();
bool _advanceOutbox(); bool _advanceOutbox();
void _checkIncoming(); void _checkIncoming();
void _checkPing(); void _checkPing();
void _checkTimeout(); void _checkTimeout();
void _onConnack(); void _onConnack();
void _onPublish(); void _onPublish();
void _onPuback(); void _onPuback();
void _onPubrec(); void _onPubrec();
void _onPubrel(); void _onPubrel();
void _onPubcomp(); void _onPubcomp();
void _onSuback(); void _onSuback();
void _onUnsuback(); void _onUnsuback();
void _clearQueue(int clearData); // 0: keep session, void _clearQueue(int clearData); // 0: keep session,
// 1: keep only PUBLISH qos > 0 // 1: keep only PUBLISH qos > 0
// 2: delete all // 2: delete all
void _onError(uint16_t packetId, espMqttClientTypes::Error error); void _onError(uint16_t packetId, espMqttClientTypes::Error error);
#if defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP32)
#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO
size_t _highWaterMark; size_t _highWaterMark;
#endif #endif
#endif #endif
}; };

View File

@@ -1,217 +1,255 @@
/* /*
Copyright (c) 2022 Bert Melis. All rights reserved. Copyright (c) 2022 Bert Melis. All rights reserved.
This work is licensed under the terms of the MIT license. This work is licensed under the terms of the MIT license.
For a copy, see <https://opensource.org/licenses/MIT> or For a copy, see <https://opensource.org/licenses/MIT> or
the LICENSE file. the LICENSE file.
*/ */
#pragma once #pragma once
#include <new> // new (std::nothrow) #if EMC_USE_MEMPOOL
#include <utility> // std::forward #include "MemoryPool/src/MemoryPool.h"
#include "Config.h"
namespace espMqttClientInternals { #else
#include <new> // new (std::nothrow)
/** #endif
* @brief Singly linked queue with builtin non-invalidating forward iterator #include <utility> // std::forward
*
* Queue items can only be emplaced, at front and back of the queue. namespace espMqttClientInternals {
* Remove items using an iterator or the builtin iterator.
*/ /**
* @brief Singly linked queue with builtin non-invalidating forward iterator
template <typename T> *
class Outbox { * Queue items can only be emplaced, at front and back of the queue.
public: * Remove items using an iterator or the builtin iterator.
Outbox() */
: _first(nullptr)
, _last(nullptr) template <typename T>
, _current(nullptr) class Outbox {
, _prev(nullptr) {} public:
~Outbox() { Outbox()
while (_first) { : _first(nullptr)
Node* n = _first->next; , _last(nullptr)
delete _first; , _current(nullptr)
_first = n; , _prev(nullptr)
} #if EMC_USE_MEMPOOL
} , _memPool()
#endif
struct Node { {}
public: ~Outbox() {
template <typename... Args> while (_first) {
explicit Node(Args&&... args) Node* n = _first->next;
: data(std::forward<Args>(args) ...) #if EMC_USE_MEMPOOL
, next(nullptr) { _first->~Node();
// empty _memPool.free(_first);
} #else
delete _first;
T data; #endif
Node* next; _first = n;
}; }
}
class Iterator {
friend class Outbox; struct Node {
public: public:
void operator++() { template <typename... Args>
if (_node) { explicit Node(Args&&... args)
_prev = _node; : data(std::forward<Args>(args) ...)
_node = _node->next; , next(nullptr) {
} // empty
} }
explicit operator bool() const { T data;
if (_node) return true; Node* next;
return false; };
}
class Iterator {
T* get() const { friend class Outbox;
if (_node) return &(_node->data); public:
return nullptr; void operator++() {
} if (_node) {
_prev = _node;
private: _node = _node->next;
Node* _node = nullptr; }
Node* _prev = nullptr; }
};
explicit operator bool() const {
// add node to back, advance current to new if applicable if (_node) return true;
template <class... Args> return false;
Iterator emplace(Args&&... args) { }
Iterator it;
Node* node = new (std::nothrow) Node(std::forward<Args>(args) ...); T* get() const {
if (node != nullptr) { if (_node) return &(_node->data);
if (!_first) { return nullptr;
// queue is empty }
_first = _current = node;
} else { private:
// queue has at least one item Node* _node = nullptr;
_last->next = node; Node* _prev = nullptr;
it._prev = _last; };
}
_last = node; // add node to back, advance current to new if applicable
it._node = node; template <class... Args>
// point current to newly created if applicable Iterator emplace(Args&&... args) {
if (!_current) { Iterator it;
_current = _last; #if EMC_USE_MEMPOOL
} void* buf = _memPool.malloc();
} Node* node = nullptr;
return it; if (buf) {
} node = new(buf) Node(std::forward<Args>(args) ...);
}
// add item to front, current points to newly created front. #else
template <class... Args> Node* node = new(std::nothrow) Node(std::forward<Args>(args) ...);
Iterator emplaceFront(Args&&... args) { #endif
Iterator it; if (node != nullptr) {
Node* node = new (std::nothrow) Node(std::forward<Args>(args) ...); if (!_first) {
if (node != nullptr) { // queue is empty
if (!_first) { _first = _current = node;
// queue is empty } else {
_last = node; // queue has at least one item
} else { _last->next = node;
// queue has at least one item it._prev = _last;
node->next = _first; }
} _last = node;
_current = _first = node; it._node = node;
_prev = nullptr; // point current to newly created if applicable
it._node = node; if (!_current) {
} _current = _last;
return it; }
} }
return it;
// remove node at iterator, iterator points to next }
void remove(Iterator& it) { // NOLINT(runtime/references)
if (!it) return; // add item to front, current points to newly created front.
Node* node = it._node; template <class... Args>
Node* prev = it._prev; Iterator emplaceFront(Args&&... args) {
++it; Iterator it;
_remove(prev, node); #if EMC_USE_MEMPOOL
} void* buf = _memPool.malloc();
Node* node = nullptr;
// remove current node, current points to next if (buf) {
void removeCurrent() { node = new(buf) Node(std::forward<Args>(args) ...);
_remove(_prev, _current); }
} #else
Node* node = new(std::nothrow) Node(std::forward<Args>(args) ...);
// Get current item or return nullptr #endif
T* getCurrent() const { if (node != nullptr) {
if (_current) return &(_current->data); if (!_first) {
return nullptr; // queue is empty
} _last = node;
} else {
void resetCurrent() { // queue has at least one item
_current = _first; node->next = _first;
} }
_current = _first = node;
Iterator front() const { _prev = nullptr;
Iterator it; it._node = node;
it._node = _first; }
return it; return it;
} }
// Advance current item // remove node at iterator, iterator points to next
void next() { void remove(Iterator& it) { // NOLINT(runtime/references)
if (_current) { if (!it) return;
_prev = _current; Node* node = it._node;
_current = _current->next; Node* prev = it._prev;
} ++it;
} _remove(prev, node);
}
// Outbox is empty
bool empty() { // remove current node, current points to next
if (!_first) return true; void removeCurrent() {
return false; _remove(_prev, _current);
} }
size_t size() const { // Get current item or return nullptr
Node* n = _first; T* getCurrent() const {
size_t count = 0; if (_current) return &(_current->data);
while (n) { return nullptr;
n = n->next; }
++count;
} void resetCurrent() {
return count; _current = _first;
} }
private: Iterator front() const {
Node* _first; Iterator it;
Node* _last; it._node = _first;
Node* _current; return it;
Node* _prev; // element just before _current }
void _remove(Node* prev, Node* node) { // Advance current item
if (!node) return; void next() {
if (_current) {
// set current to next, node->next may be nullptr _prev = _current;
if (_current == node) { _current = _current->next;
_current = node->next; }
} }
if (_prev == node) { // Outbox is empty
_prev = prev; bool empty() {
} if (!_first) return true;
return false;
// only one element in outbox }
if (_first == _last) {
_first = _last = nullptr; size_t size() const {
Node* n = _first;
// delete first el in longer outbox size_t count = 0;
} else if (_first == node) { while (n) {
_first = node->next; n = n->next;
++count;
// delete last in longer outbox }
} else if (_last == node) { return count;
_last = prev; }
_last->next = nullptr;
private:
// delete somewhere in the middle Node* _first;
} else { Node* _last;
prev->next = node->next; Node* _current;
} Node* _prev; // element just before _current
#if EMC_USE_MEMPOOL
// finally, delete the node MemoryPool::Fixed<EMC_NUM_POOL_ELEMENTS, sizeof(Node)> _memPool;
delete node; #endif
}
}; void _remove(Node* prev, Node* node) {
if (!node) return;
} // end namespace espMqttClientInternals
// set current to next, node->next may be nullptr
if (_current == node) {
_current = node->next;
}
if (_prev == node) {
_prev = prev;
}
// only one element in outbox
if (_first == _last) {
_first = _last = nullptr;
// delete first el in longer outbox
} else if (_first == node) {
_first = node->next;
// delete last in longer outbox
} else if (_last == node) {
_last = prev;
_last->next = nullptr;
// delete somewhere in the middle
} else {
prev->next = node->next;
}
// finally, delete the node
#if EMC_USE_MEMPOOL
node->~Node();
_memPool.free(node);
#else
delete node;
#endif
}
};
} // end namespace espMqttClientInternals

View File

@@ -10,427 +10,445 @@ the LICENSE file.
namespace espMqttClientInternals { namespace espMqttClientInternals {
#if EMC_USE_MEMPOOL
MemoryPool::Variable<EMC_NUM_POOL_ELEMENTS, EMC_SIZE_POOL_ELEMENTS> Packet::_memPool;
#endif
Packet::~Packet() { Packet::~Packet() {
free(_data); #if EMC_USE_MEMPOOL
_memPool.free(_data);
#else
free(_data);
#endif
} }
size_t Packet::available(size_t index) { size_t Packet::available(size_t index) {
if (index >= _size) if (index >= _size) return 0;
return 0; if (!_getPayload) return _size - index;
if (!_getPayload) return _chunkedAvailable(index);
return _size - index;
return _chunkedAvailable(index);
} }
const uint8_t * Packet::data(size_t index) const { const uint8_t* Packet::data(size_t index) const {
if (!_getPayload) { if (!_getPayload) {
if (!_data) if (!_data) return nullptr;
return nullptr; if (index >= _size) return nullptr;
if (index >= _size) return &_data[index];
return nullptr; }
return &_data[index]; return _chunkedData(index);
}
return _chunkedData(index);
} }
size_t Packet::size() const { size_t Packet::size() const {
return _size; return _size;
} }
void Packet::setDup() { void Packet::setDup() {
if (!_data) if (!_data) return;
return; if (packetType() != PacketType.PUBLISH) return;
if (packetType() != PacketType.PUBLISH) if (_packetId == 0) return;
return; _data[0] |= 0x08;
if (_packetId == 0)
return;
_data[0] |= 0x08;
} }
uint16_t Packet::packetId() const { uint16_t Packet::packetId() const {
return _packetId; return _packetId;
} }
MQTTPacketType Packet::packetType() const { MQTTPacketType Packet::packetType() const {
if (_data) if (_data) return static_cast<MQTTPacketType>(_data[0] & 0xF0);
return static_cast<MQTTPacketType>(_data[0] & 0xF0); return static_cast<MQTTPacketType>(0);
return static_cast<MQTTPacketType>(0);
} }
bool Packet::removable() const { bool Packet::removable() const {
if (_packetId == 0) if (_packetId == 0) return true;
return true; if ((packetType() == PacketType.PUBACK) || (packetType() == PacketType.PUBCOMP)) return true;
if ((packetType() == PacketType.PUBACK) || (packetType() == PacketType.PUBCOMP)) return false;
return true;
return false;
} }
Packet::Packet(espMqttClientTypes::Error & error, Packet::Packet(espMqttClientTypes::Error& error,
bool cleanSession, bool cleanSession,
const char * username, const char* username,
const char * password, const char* password,
const char * willTopic, const char* willTopic,
bool willRetain, bool willRetain,
uint8_t willQos, uint8_t willQos,
const uint8_t * willPayload, const uint8_t* willPayload,
uint16_t willPayloadLength, uint16_t willPayloadLength,
uint16_t keepAlive, uint16_t keepAlive,
const char * clientId) const char* clientId)
: _packetId(0) : _packetId(0)
, _data(nullptr) , _data(nullptr)
, _size(0) , _size(0)
, _payloadIndex(0) , _payloadIndex(0)
, _payloadStartIndex(0) , _payloadStartIndex(0)
, _payloadEndIndex(0) , _payloadEndIndex(0)
, _getPayload(nullptr) { , _getPayload(nullptr) {
if (willPayload && willPayloadLength == 0) { if (willPayload && willPayloadLength == 0) {
size_t length = strlen(reinterpret_cast<const char *>(willPayload)); size_t length = strlen(reinterpret_cast<const char*>(willPayload));
if (length > UINT16_MAX) { if (length > UINT16_MAX) {
emc_log_w("Payload length truncated (l:%zu)", length); emc_log_w("Payload length truncated (l:%zu)", length);
willPayloadLength = UINT16_MAX; willPayloadLength = UINT16_MAX;
} else {
willPayloadLength = length;
}
}
if (!clientId || strlen(clientId) == 0) {
emc_log_w("clientId not set error");
error = espMqttClientTypes::Error::MALFORMED_PARAMETER;
return;
}
// Calculate size
size_t remainingLength = 6 + // protocol
1 + // protocol level
1 + // connect flags
2 + // keepalive
2 + strlen(clientId) + (willTopic ? 2 + strlen(willTopic) + 2 + willPayloadLength : 0) + (username ? 2 + strlen(username) : 0)
+ (password ? 2 + strlen(password) : 0);
// allocate memory
if (!_allocate(remainingLength, false)) {
error = espMqttClientTypes::Error::OUT_OF_MEMORY;
return;
}
// serialize
size_t pos = 0;
// FIXED HEADER
_data[pos++] = PacketType.CONNECT | HeaderFlag.CONNECT_RESERVED;
pos += encodeRemainingLength(remainingLength, &_data[pos]);
pos += encodeString(PROTOCOL, &_data[pos]);
_data[pos++] = PROTOCOL_LEVEL;
uint8_t connectFlags = 0;
if (cleanSession)
connectFlags |= espMqttClientInternals::ConnectFlag.CLEAN_SESSION;
if (username != nullptr)
connectFlags |= espMqttClientInternals::ConnectFlag.USERNAME;
if (password != nullptr)
connectFlags |= espMqttClientInternals::ConnectFlag.PASSWORD;
if (willTopic != nullptr) {
connectFlags |= espMqttClientInternals::ConnectFlag.WILL;
if (willRetain)
connectFlags |= espMqttClientInternals::ConnectFlag.WILL_RETAIN;
switch (willQos) {
case 0:
connectFlags |= espMqttClientInternals::ConnectFlag.WILL_QOS0;
break;
case 1:
connectFlags |= espMqttClientInternals::ConnectFlag.WILL_QOS1;
break;
case 2:
connectFlags |= espMqttClientInternals::ConnectFlag.WILL_QOS2;
break;
}
}
_data[pos++] = connectFlags;
_data[pos++] = keepAlive >> 8;
_data[pos++] = keepAlive & 0xFF;
// PAYLOAD
// client ID
pos += encodeString(clientId, &_data[pos]);
// will
if (willTopic != nullptr && willPayload != nullptr) {
pos += encodeString(willTopic, &_data[pos]);
_data[pos++] = willPayloadLength >> 8;
_data[pos++] = willPayloadLength & 0xFF;
memcpy(&_data[pos], willPayload, willPayloadLength);
pos += willPayloadLength;
}
// credentials
if (username != nullptr)
pos += encodeString(username, &_data[pos]);
if (password != nullptr)
encodeString(password, &_data[pos]);
error = espMqttClientTypes::Error::SUCCESS;
}
Packet::Packet(espMqttClientTypes::Error & error, uint16_t packetId, const char * topic, const uint8_t * payload, size_t payloadLength, uint8_t qos, bool retain)
: _packetId(packetId)
, _data(nullptr)
, _size(0)
, _payloadIndex(0)
, _payloadStartIndex(0)
, _payloadEndIndex(0)
, _getPayload(nullptr) {
size_t remainingLength = 2 + strlen(topic) + // topic length + topic
2 + // packet ID
payloadLength;
if (qos == 0) {
remainingLength -= 2;
_packetId = 0;
}
if (!_allocate(remainingLength)) {
error = espMqttClientTypes::Error::OUT_OF_MEMORY;
return;
}
size_t pos = _fillPublishHeader(packetId, topic, remainingLength, qos, retain);
// PAYLOAD
memcpy(&_data[pos], payload, payloadLength);
error = espMqttClientTypes::Error::SUCCESS;
}
Packet::Packet(espMqttClientTypes::Error & error,
uint16_t packetId,
const char * topic,
espMqttClientTypes::PayloadCallback payloadCallback,
size_t payloadLength,
uint8_t qos,
bool retain)
: _packetId(packetId)
, _data(nullptr)
, _size(0)
, _payloadIndex(0)
, _payloadStartIndex(0)
, _payloadEndIndex(0)
, _getPayload(payloadCallback) {
size_t remainingLength = 2 + strlen(topic) + // topic length + topic
2 + // packet ID
payloadLength;
if (qos == 0) {
remainingLength -= 2;
_packetId = 0;
}
if (!_allocate(remainingLength - payloadLength + std::min(payloadLength, static_cast<size_t>(EMC_RX_BUFFER_SIZE)))) {
error = espMqttClientTypes::Error::OUT_OF_MEMORY;
return;
}
size_t pos = _fillPublishHeader(packetId, topic, remainingLength, qos, retain);
// payload will be added by 'Packet::available'
_size = pos + payloadLength;
_payloadIndex = pos;
_payloadStartIndex = _payloadIndex;
_payloadEndIndex = _payloadIndex;
error = espMqttClientTypes::Error::SUCCESS;
}
Packet::Packet(espMqttClientTypes::Error & error, uint16_t packetId, const char * topic, uint8_t qos)
: _packetId(packetId)
, _data(nullptr)
, _size(0)
, _payloadIndex(0)
, _payloadStartIndex(0)
, _payloadEndIndex(0)
, _getPayload(nullptr) {
SubscribeItem list[1] = {{topic, qos}};
_createSubscribe(error, list, 1);
}
Packet::Packet(espMqttClientTypes::Error & error, MQTTPacketType type, uint16_t packetId)
: _packetId(packetId)
, _data(nullptr)
, _size(0)
, _payloadIndex(0)
, _payloadStartIndex(0)
, _payloadEndIndex(0)
, _getPayload(nullptr) {
if (!_allocate(2)) {
error = espMqttClientTypes::Error::OUT_OF_MEMORY;
return;
}
size_t pos = 0;
_data[pos] = type;
if (type == PacketType.PUBREL) {
_data[pos++] |= HeaderFlag.PUBREL_RESERVED;
} else { } else {
pos++; willPayloadLength = length;
} }
pos += encodeRemainingLength(2, &_data[pos]); }
_data[pos++] = packetId >> 8; if (!clientId || strlen(clientId) == 0) {
_data[pos] = packetId & 0xFF; emc_log_w("clientId not set error");
error = espMqttClientTypes::Error::MALFORMED_PARAMETER;
return;
}
error = espMqttClientTypes::Error::SUCCESS; // Calculate size
size_t remainingLength =
6 + // protocol
1 + // protocol level
1 + // connect flags
2 + // keepalive
2 + strlen(clientId) +
(willTopic ? 2 + strlen(willTopic) + 2 + willPayloadLength : 0) +
(username ? 2 + strlen(username) : 0) +
(password ? 2 + strlen(password) : 0);
// allocate memory
if (!_allocate(remainingLength, false)) {
error = espMqttClientTypes::Error::OUT_OF_MEMORY;
return;
}
// serialize
size_t pos = 0;
// FIXED HEADER
_data[pos++] = PacketType.CONNECT | HeaderFlag.CONNECT_RESERVED;
pos += encodeRemainingLength(remainingLength, &_data[pos]);
pos += encodeString(PROTOCOL, &_data[pos]);
_data[pos++] = PROTOCOL_LEVEL;
uint8_t connectFlags = 0;
if (cleanSession) connectFlags |= espMqttClientInternals::ConnectFlag.CLEAN_SESSION;
if (username != nullptr) connectFlags |= espMqttClientInternals::ConnectFlag.USERNAME;
if (password != nullptr) connectFlags |= espMqttClientInternals::ConnectFlag.PASSWORD;
if (willTopic != nullptr) {
connectFlags |= espMqttClientInternals::ConnectFlag.WILL;
if (willRetain) connectFlags |= espMqttClientInternals::ConnectFlag.WILL_RETAIN;
switch (willQos) {
case 0:
connectFlags |= espMqttClientInternals::ConnectFlag.WILL_QOS0;
break;
case 1:
connectFlags |= espMqttClientInternals::ConnectFlag.WILL_QOS1;
break;
case 2:
connectFlags |= espMqttClientInternals::ConnectFlag.WILL_QOS2;
break;
}
}
_data[pos++] = connectFlags;
_data[pos++] = keepAlive >> 8;
_data[pos++] = keepAlive & 0xFF;
// PAYLOAD
// client ID
pos += encodeString(clientId, &_data[pos]);
// will
if (willTopic != nullptr && willPayload != nullptr) {
pos += encodeString(willTopic, &_data[pos]);
_data[pos++] = willPayloadLength >> 8;
_data[pos++] = willPayloadLength & 0xFF;
memcpy(&_data[pos], willPayload, willPayloadLength);
pos += willPayloadLength;
}
// credentials
if (username != nullptr) pos += encodeString(username, &_data[pos]);
if (password != nullptr) encodeString(password, &_data[pos]);
error = espMqttClientTypes::Error::SUCCESS;
} }
Packet::Packet(espMqttClientTypes::Error & error, uint16_t packetId, const char * topic) Packet::Packet(espMqttClientTypes::Error& error,
: _packetId(packetId) uint16_t packetId,
, _data(nullptr) const char* topic,
, _size(0) const uint8_t* payload,
, _payloadIndex(0) size_t payloadLength,
, _payloadStartIndex(0) uint8_t qos,
, _payloadEndIndex(0) bool retain)
, _getPayload(nullptr) { : _packetId(packetId)
const char * list[1] = {topic}; , _data(nullptr)
_createUnsubscribe(error, list, 1); , _size(0)
, _payloadIndex(0)
, _payloadStartIndex(0)
, _payloadEndIndex(0)
, _getPayload(nullptr) {
size_t remainingLength =
2 + strlen(topic) + // topic length + topic
2 + // packet ID
payloadLength;
if (qos == 0) {
remainingLength -= 2;
_packetId = 0;
}
if (!_allocate(remainingLength, true)) {
error = espMqttClientTypes::Error::OUT_OF_MEMORY;
return;
}
size_t pos = _fillPublishHeader(packetId, topic, remainingLength, qos, retain);
// PAYLOAD
memcpy(&_data[pos], payload, payloadLength);
error = espMqttClientTypes::Error::SUCCESS;
} }
Packet::Packet(espMqttClientTypes::Error & error, MQTTPacketType type) Packet::Packet(espMqttClientTypes::Error& error,
: _packetId(0) uint16_t packetId,
, _data(nullptr) const char* topic,
, _size(0) espMqttClientTypes::PayloadCallback payloadCallback,
, _payloadIndex(0) size_t payloadLength,
, _payloadStartIndex(0) uint8_t qos,
, _payloadEndIndex(0) bool retain)
, _getPayload(nullptr) { : _packetId(packetId)
if (!_allocate(0)) { , _data(nullptr)
error = espMqttClientTypes::Error::OUT_OF_MEMORY; , _size(0)
return; , _payloadIndex(0)
} , _payloadStartIndex(0)
_data[0] |= type; , _payloadEndIndex(0)
, _getPayload(payloadCallback) {
size_t remainingLength =
2 + strlen(topic) + // topic length + topic
2 + // packet ID
payloadLength;
error = espMqttClientTypes::Error::SUCCESS; if (qos == 0) {
remainingLength -= 2;
_packetId = 0;
}
if (!_allocate(remainingLength - payloadLength + std::min(payloadLength, static_cast<size_t>(EMC_RX_BUFFER_SIZE)), true)) {
error = espMqttClientTypes::Error::OUT_OF_MEMORY;
return;
}
size_t pos = _fillPublishHeader(packetId, topic, remainingLength, qos, retain);
// payload will be added by 'Packet::available'
_size = pos + payloadLength;
_payloadIndex = pos;
_payloadStartIndex = _payloadIndex;
_payloadEndIndex = _payloadIndex;
error = espMqttClientTypes::Error::SUCCESS;
}
Packet::Packet(espMqttClientTypes::Error& error, uint16_t packetId, const char* topic, uint8_t qos)
: _packetId(packetId)
, _data(nullptr)
, _size(0)
, _payloadIndex(0)
, _payloadStartIndex(0)
, _payloadEndIndex(0)
, _getPayload(nullptr) {
SubscribeItem list[1] = {topic, qos};
_createSubscribe(error, list, 1);
}
Packet::Packet(espMqttClientTypes::Error& error, MQTTPacketType type, uint16_t packetId)
: _packetId(packetId)
, _data(nullptr)
, _size(0)
, _payloadIndex(0)
, _payloadStartIndex(0)
, _payloadEndIndex(0)
, _getPayload(nullptr) {
if (!_allocate(2, true)) {
error = espMqttClientTypes::Error::OUT_OF_MEMORY;
return;
}
size_t pos = 0;
_data[pos] = type;
if (type == PacketType.PUBREL) {
_data[pos++] |= HeaderFlag.PUBREL_RESERVED;
} else {
pos++;
}
pos += encodeRemainingLength(2, &_data[pos]);
_data[pos++] = packetId >> 8;
_data[pos] = packetId & 0xFF;
error = espMqttClientTypes::Error::SUCCESS;
}
Packet::Packet(espMqttClientTypes::Error& error, uint16_t packetId, const char* topic)
: _packetId(packetId)
, _data(nullptr)
, _size(0)
, _payloadIndex(0)
, _payloadStartIndex(0)
, _payloadEndIndex(0)
, _getPayload(nullptr) {
const char* list[1] = {topic};
_createUnsubscribe(error, list, 1);
}
Packet::Packet(espMqttClientTypes::Error& error, MQTTPacketType type)
: _packetId(0)
, _data(nullptr)
, _size(0)
, _payloadIndex(0)
, _payloadStartIndex(0)
, _payloadEndIndex(0)
, _getPayload(nullptr) {
if (!_allocate(0, true)) {
error = espMqttClientTypes::Error::OUT_OF_MEMORY;
return;
}
_data[0] |= type;
error = espMqttClientTypes::Error::SUCCESS;
} }
bool Packet::_allocate(size_t remainingLength, bool check) { bool Packet::_allocate(size_t remainingLength, bool check) {
if (check && EMC_GET_FREE_MEMORY() < EMC_MIN_FREE_MEMORY) { #if EMC_USE_MEMPOOL
emc_log_w("Packet buffer not allocated: low memory"); (void) check;
return false; #else
} if (check && EMC_GET_FREE_MEMORY() < EMC_MIN_FREE_MEMORY) {
_size = 1 + remainingLengthLength(remainingLength) + remainingLength; emc_log_w("Packet buffer not allocated: low memory");
_data = reinterpret_cast<uint8_t *>(malloc(_size)); return false;
if (!_data) { }
_size = 0; #endif
emc_log_w("Alloc failed (l:%zu)", _size); _size = 1 + remainingLengthLength(remainingLength) + remainingLength;
return false; #if EMC_USE_MEMPOOL
} _data = reinterpret_cast<uint8_t*>(_memPool.malloc(_size));
emc_log_i("Alloc (l:%zu)", _size); #else
memset(_data, 0, _size); _data = reinterpret_cast<uint8_t*>(malloc(_size));
return true; #endif
if (!_data) {
_size = 0;
emc_log_w("Alloc failed (l:%zu)", _size);
return false;
}
emc_log_i("Alloc (l:%zu)", _size);
memset(_data, 0, _size);
return true;
} }
size_t Packet::_fillPublishHeader(uint16_t packetId, const char * topic, size_t remainingLength, uint8_t qos, bool retain) { size_t Packet::_fillPublishHeader(uint16_t packetId,
size_t index = 0; const char* topic,
size_t remainingLength,
uint8_t qos,
bool retain) {
size_t index = 0;
// FIXED HEADER // FIXED HEADER
_data[index] = PacketType.PUBLISH; _data[index] = PacketType.PUBLISH;
if (retain) if (retain) _data[index] |= HeaderFlag.PUBLISH_RETAIN;
_data[index] |= HeaderFlag.PUBLISH_RETAIN; if (qos == 0) {
if (qos == 0) { _data[index++] |= HeaderFlag.PUBLISH_QOS0;
_data[index++] |= HeaderFlag.PUBLISH_QOS0; } else if (qos == 1) {
} else if (qos == 1) { _data[index++] |= HeaderFlag.PUBLISH_QOS1;
_data[index++] |= HeaderFlag.PUBLISH_QOS1; } else if (qos == 2) {
} else if (qos == 2) { _data[index++] |= HeaderFlag.PUBLISH_QOS2;
_data[index++] |= HeaderFlag.PUBLISH_QOS2; }
} index += encodeRemainingLength(remainingLength, &_data[index]);
index += encodeRemainingLength(remainingLength, &_data[index]);
// VARIABLE HEADER // VARIABLE HEADER
index += encodeString(topic, &_data[index]); index += encodeString(topic, &_data[index]);
if (qos > 0) { if (qos > 0) {
_data[index++] = packetId >> 8; _data[index++] = packetId >> 8;
_data[index++] = packetId & 0xFF; _data[index++] = packetId & 0xFF;
} }
return index; return index;
} }
void Packet::_createSubscribe(espMqttClientTypes::Error & error, SubscribeItem * list, size_t numberTopics) { void Packet::_createSubscribe(espMqttClientTypes::Error& error,
// Calculate size SubscribeItem* list,
size_t payload = 0; size_t numberTopics) {
for (size_t i = 0; i < numberTopics; ++i) { // Calculate size
payload += 2 + strlen(list[i].topic) + 1; // length bytes, string, qos size_t payload = 0;
} for (size_t i = 0; i < numberTopics; ++i) {
size_t remainingLength = 2 + payload; // packetId + payload payload += 2 + strlen(list[i].topic) + 1; // length bytes, string, qos
}
size_t remainingLength = 2 + payload; // packetId + payload
// allocate memory // allocate memory
if (!_allocate(remainingLength)) { if (!_allocate(remainingLength, true)) {
error = espMqttClientTypes::Error::OUT_OF_MEMORY; error = espMqttClientTypes::Error::OUT_OF_MEMORY;
return; return;
} }
// serialize // serialize
size_t pos = 0; size_t pos = 0;
_data[pos++] = PacketType.SUBSCRIBE | HeaderFlag.SUBSCRIBE_RESERVED; _data[pos++] = PacketType.SUBSCRIBE | HeaderFlag.SUBSCRIBE_RESERVED;
pos += encodeRemainingLength(remainingLength, &_data[pos]); pos += encodeRemainingLength(remainingLength, &_data[pos]);
_data[pos++] = _packetId >> 8; _data[pos++] = _packetId >> 8;
_data[pos++] = _packetId & 0xFF; _data[pos++] = _packetId & 0xFF;
for (size_t i = 0; i < numberTopics; ++i) { for (size_t i = 0; i < numberTopics; ++i) {
pos += encodeString(list[i].topic, &_data[pos]); pos += encodeString(list[i].topic, &_data[pos]);
_data[pos++] = list[i].qos; _data[pos++] = list[i].qos;
} }
error = espMqttClientTypes::Error::SUCCESS; error = espMqttClientTypes::Error::SUCCESS;
} }
void Packet::_createUnsubscribe(espMqttClientTypes::Error & error, const char ** list, size_t numberTopics) { void Packet::_createUnsubscribe(espMqttClientTypes::Error& error,
// Calculate size const char** list,
size_t payload = 0; size_t numberTopics) {
for (size_t i = 0; i < numberTopics; ++i) { // Calculate size
payload += 2 + strlen(list[i]); // length bytes, string size_t payload = 0;
} for (size_t i = 0; i < numberTopics; ++i) {
size_t remainingLength = 2 + payload; // packetId + payload payload += 2 + strlen(list[i]); // length bytes, string
}
size_t remainingLength = 2 + payload; // packetId + payload
// allocate memory // allocate memory
if (!_allocate(remainingLength)) { if (!_allocate(remainingLength, true)) {
error = espMqttClientTypes::Error::OUT_OF_MEMORY; error = espMqttClientTypes::Error::OUT_OF_MEMORY;
return; return;
} }
// serialize // serialize
size_t pos = 0; size_t pos = 0;
_data[pos++] = PacketType.UNSUBSCRIBE | HeaderFlag.UNSUBSCRIBE_RESERVED; _data[pos++] = PacketType.UNSUBSCRIBE | HeaderFlag.UNSUBSCRIBE_RESERVED;
pos += encodeRemainingLength(remainingLength, &_data[pos]); pos += encodeRemainingLength(remainingLength, &_data[pos]);
_data[pos++] = _packetId >> 8; _data[pos++] = _packetId >> 8;
_data[pos++] = _packetId & 0xFF; _data[pos++] = _packetId & 0xFF;
for (size_t i = 0; i < numberTopics; ++i) { for (size_t i = 0; i < numberTopics; ++i) {
pos += encodeString(list[i], &_data[pos]); pos += encodeString(list[i], &_data[pos]);
} }
error = espMqttClientTypes::Error::SUCCESS; error = espMqttClientTypes::Error::SUCCESS;
} }
size_t Packet::_chunkedAvailable(size_t index) { size_t Packet::_chunkedAvailable(size_t index) {
// index vs size check done in 'available(index)' // index vs size check done in 'available(index)'
// index points to header or first payload byte // index points to header or first payload byte
if (index < _payloadIndex) { if (index < _payloadIndex) {
if (_size > _payloadIndex && _payloadEndIndex != 0) { if (_size > _payloadIndex && _payloadEndIndex != 0) {
size_t copied = _getPayload(&_data[_payloadIndex], std::min(static_cast<size_t>(EMC_TX_BUFFER_SIZE), _size - _payloadStartIndex), index); size_t copied = _getPayload(&_data[_payloadIndex], std::min(static_cast<size_t>(EMC_TX_BUFFER_SIZE), _size - _payloadStartIndex), index);
_payloadStartIndex = _payloadIndex; _payloadStartIndex = _payloadIndex;
_payloadEndIndex = _payloadStartIndex + copied - 1; _payloadEndIndex = _payloadStartIndex + copied - 1;
}
// index points to payload unavailable
} else if (index > _payloadEndIndex || _payloadStartIndex > index) {
_payloadStartIndex = index;
size_t copied = _getPayload(&_data[_payloadIndex], std::min(static_cast<size_t>(EMC_TX_BUFFER_SIZE), _size - _payloadStartIndex), index);
_payloadEndIndex = _payloadStartIndex + copied - 1;
} }
// now index points to header or payload available // index points to payload unavailable
return _payloadEndIndex - index + 1; } else if (index > _payloadEndIndex || _payloadStartIndex > index) {
_payloadStartIndex = index;
size_t copied = _getPayload(&_data[_payloadIndex], std::min(static_cast<size_t>(EMC_TX_BUFFER_SIZE), _size - _payloadStartIndex), index);
_payloadEndIndex = _payloadStartIndex + copied - 1;
}
// now index points to header or payload available
return _payloadEndIndex - index + 1;
} }
const uint8_t * Packet::_chunkedData(size_t index) const { const uint8_t* Packet::_chunkedData(size_t index) const {
// CAUTION!! available(index) has to be called first to check available data and possibly fill payloadbuffer // CAUTION!! available(index) has to be called first to check available data and possibly fill payloadbuffer
if (index < _payloadIndex) { if (index < _payloadIndex) {
return &_data[index]; return &_data[index];
} }
return &_data[index - _payloadStartIndex + _payloadIndex]; return &_data[index - _payloadStartIndex + _payloadIndex];
} }
} // end namespace espMqttClientInternals } // end namespace espMqttClientInternals

View File

@@ -17,7 +17,11 @@ the LICENSE file.
#include "../Helpers.h" #include "../Helpers.h"
#include "../Logging.h" #include "../Logging.h"
#include "RemainingLength.h" #include "RemainingLength.h"
#include "String.h" #include "StringUtil.h"
#if EMC_USE_MEMPOOL
#include "MemoryPool/src/MemoryPool.h"
#endif
namespace espMqttClientInternals { namespace espMqttClientInternals {
@@ -133,7 +137,7 @@ class Packet {
private: private:
// pass remainingLength = total size - header - remainingLengthLength! // pass remainingLength = total size - header - remainingLengthLength!
bool _allocate(size_t remainingLength, bool check = true); bool _allocate(size_t remainingLength, bool check);
// fills header and returns index of next available byte in buffer // fills header and returns index of next available byte in buffer
size_t _fillPublishHeader(uint16_t packetId, size_t _fillPublishHeader(uint16_t packetId,
@@ -150,6 +154,10 @@ class Packet {
size_t _chunkedAvailable(size_t index); size_t _chunkedAvailable(size_t index);
const uint8_t* _chunkedData(size_t index) const; const uint8_t* _chunkedData(size_t index) const;
#if EMC_USE_MEMPOOL
static MemoryPool::Variable<EMC_NUM_POOL_ELEMENTS, EMC_SIZE_POOL_ELEMENTS> _memPool;
#endif
}; };
} // end namespace espMqttClientInternals } // end namespace espMqttClientInternals

View File

@@ -6,7 +6,7 @@ For a copy, see <https://opensource.org/licenses/MIT> or
the LICENSE file. the LICENSE file.
*/ */
#include "String.h" #include "StringUtil.h"
namespace espMqttClientInternals { namespace espMqttClientInternals {

View File

@@ -38,9 +38,10 @@ bool ClientPosix::connect(IPAddress ip, uint16_t port) {
memset(&_host, 0, sizeof(_host)); memset(&_host, 0, sizeof(_host));
_host.sin_family = AF_INET; _host.sin_family = AF_INET;
_host.sin_addr.s_addr = htonl(uint32_t(ip)); _host.sin_addr.s_addr = htonl(uint32_t(ip));
_host.sin_port = htons(port); // modified by proddy for EMS-ESP compiling standalone _host.sin_port = ::htons(port);
int ret = ::connect(_sockfd, reinterpret_cast<sockaddr*>(&_host), sizeof(_host)); int ret = ::connect(_sockfd, reinterpret_cast<sockaddr*>(&_host), sizeof(_host));
if (ret < 0) { if (ret < 0) {
emc_log_e("Error connecting: %d - (%d) %s", ret, errno, strerror(errno)); emc_log_e("Error connecting: %d - (%d) %s", ret, errno, strerror(errno));
return false; return false;

View File

@@ -8,7 +8,7 @@ the LICENSE file.
#if defined(__linux__) #if defined(__linux__)
#include "IPAddress.h" #include "ClientPosixIPAddress.h"
IPAddress::IPAddress() IPAddress::IPAddress()
: _address(0) { : _address(0) {

View File

@@ -17,7 +17,6 @@ the LICENSE file.
#else #else
#include <WiFiClient.h> #include <WiFiClient.h>
#endif #endif
#include "Transport.h" #include "Transport.h"
namespace espMqttClientInternals { namespace espMqttClientInternals {
@@ -32,6 +31,7 @@ class ClientSecureSync : public Transport {
void stop() override; void stop() override;
bool connected() override; bool connected() override;
bool disconnected() override; bool disconnected() override;
// added for EMS-ESP
#if defined(EMC_CLIENT_SECURE) #if defined(EMC_CLIENT_SECURE)
WiFiClientSecure client; WiFiClientSecure client;
#else #else

View File

@@ -10,7 +10,7 @@ the LICENSE file.
#include <stddef.h> // size_t #include <stddef.h> // size_t
#include "IPAddress.h" #include "ClientPosixIPAddress.h"
namespace espMqttClientInternals { namespace espMqttClientInternals {

View File

@@ -10,114 +10,114 @@ the LICENSE file.
#if defined(ARDUINO_ARCH_ESP8266) #if defined(ARDUINO_ARCH_ESP8266)
espMqttClient::espMqttClient() espMqttClient::espMqttClient()
: MqttClientSetup(espMqttClientTypes::UseInternalTask::NO) : MqttClientSetup(espMqttClientTypes::UseInternalTask::NO)
, _client() { , _client() {
_transport = &_client; _transport = &_client;
} }
espMqttClientSecure::espMqttClientSecure() espMqttClientSecure::espMqttClientSecure()
: MqttClientSetup(espMqttClientTypes::UseInternalTask::NO) : MqttClientSetup(espMqttClientTypes::UseInternalTask::NO)
, _client() { , _client() {
_transport = &_client; _transport = &_client;
} }
espMqttClientSecure& espMqttClientSecure::setInsecure() { espMqttClientSecure & espMqttClientSecure::setInsecure() {
_client.client.setInsecure(); _client.client.setInsecure();
return *this; return *this;
} }
espMqttClientSecure& espMqttClientSecure::setFingerprint(const uint8_t fingerprint[20]) { espMqttClientSecure & espMqttClientSecure::setFingerprint(const uint8_t fingerprint[20]) {
_client.client.setFingerprint(fingerprint); _client.client.setFingerprint(fingerprint);
return *this; return *this;
} }
espMqttClientSecure& espMqttClientSecure::setTrustAnchors(const X509List *ta) { espMqttClientSecure & espMqttClientSecure::setTrustAnchors(const X509List * ta) {
_client.client.setTrustAnchors(ta); _client.client.setTrustAnchors(ta);
return *this; return *this;
} }
espMqttClientSecure& espMqttClientSecure::setClientRSACert(const X509List *cert, const PrivateKey *sk) { espMqttClientSecure & espMqttClientSecure::setClientRSACert(const X509List * cert, const PrivateKey * sk) {
_client.client.setClientRSACert(cert, sk); _client.client.setClientRSACert(cert, sk);
return *this; return *this;
} }
espMqttClientSecure& espMqttClientSecure::setClientECCert(const X509List *cert, const PrivateKey *sk, unsigned allowed_usages, unsigned cert_issuer_key_type) { espMqttClientSecure & espMqttClientSecure::setClientECCert(const X509List * cert, const PrivateKey * sk, unsigned allowed_usages, unsigned cert_issuer_key_type) {
_client.client.setClientECCert(cert, sk, allowed_usages, cert_issuer_key_type); _client.client.setClientECCert(cert, sk, allowed_usages, cert_issuer_key_type);
return *this; return *this;
} }
espMqttClientSecure& espMqttClientSecure::setCertStore(CertStoreBase *certStore) { espMqttClientSecure & espMqttClientSecure::setCertStore(CertStoreBase * certStore) {
_client.client.setCertStore(certStore); _client.client.setCertStore(certStore);
return *this; return *this;
} }
#endif #endif
#if defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP32)
espMqttClient::espMqttClient(espMqttClientTypes::UseInternalTask useInternalTask) espMqttClient::espMqttClient(espMqttClientTypes::UseInternalTask useInternalTask)
: MqttClientSetup(useInternalTask) : MqttClientSetup(useInternalTask)
, _client() { , _client() {
_transport = &_client; _transport = &_client;
} }
espMqttClient::espMqttClient(uint8_t priority, uint8_t core) espMqttClient::espMqttClient(uint8_t priority, uint8_t core)
: MqttClientSetup(espMqttClientTypes::UseInternalTask::YES, priority, core) : MqttClientSetup(espMqttClientTypes::UseInternalTask::YES, priority, core)
, _client() { , _client() {
_transport = &_client; _transport = &_client;
} }
espMqttClientSecure::espMqttClientSecure(espMqttClientTypes::UseInternalTask useInternalTask) espMqttClientSecure::espMqttClientSecure(espMqttClientTypes::UseInternalTask useInternalTask)
: MqttClientSetup(useInternalTask) : MqttClientSetup(useInternalTask)
, _client() { , _client() {
_transport = &_client; _transport = &_client;
} }
espMqttClientSecure::espMqttClientSecure(uint8_t priority, uint8_t core) espMqttClientSecure::espMqttClientSecure(uint8_t priority, uint8_t core)
: MqttClientSetup(espMqttClientTypes::UseInternalTask::YES, priority, core) : MqttClientSetup(espMqttClientTypes::UseInternalTask::YES, priority, core)
, _client() { , _client() {
_transport = &_client; _transport = &_client;
} }
espMqttClientSecure& espMqttClientSecure::setInsecure() { espMqttClientSecure & espMqttClientSecure::setInsecure() {
#if defined(EMC_CLIENT_SECURE) #if defined(EMC_CLIENT_SECURE)
_client.client.setInsecure(); _client.client.setInsecure();
#endif #endif
return *this; return *this;
} }
espMqttClientSecure& espMqttClientSecure::setCACert(const char* rootCA) { espMqttClientSecure & espMqttClientSecure::setCACert(const char * rootCA) {
#if defined(EMC_CLIENT_SECURE) #if defined(EMC_CLIENT_SECURE)
_client.client.setCACert(rootCA); _client.client.setCACert(rootCA);
#endif #endif
return *this; return *this;
} }
espMqttClientSecure& espMqttClientSecure::setCertificate(const char* clientCa) { espMqttClientSecure & espMqttClientSecure::setCertificate(const char * clientCa) {
#if defined(EMC_CLIENT_SECURE) #if defined(EMC_CLIENT_SECURE)
_client.client.setCertificate(clientCa); _client.client.setCertificate(clientCa);
#endif #endif
return *this; return *this;
} }
espMqttClientSecure& espMqttClientSecure::setPrivateKey(const char* privateKey) { espMqttClientSecure & espMqttClientSecure::setPrivateKey(const char * privateKey) {
#if defined(EMC_CLIENT_SECURE) #if defined(EMC_CLIENT_SECURE)
_client.client.setPrivateKey(privateKey); _client.client.setPrivateKey(privateKey);
#endif #endif
return *this; return *this;
} }
espMqttClientSecure& espMqttClientSecure::setPreSharedKey(const char* pskIdent, const char* psKey) { espMqttClientSecure & espMqttClientSecure::setPreSharedKey(const char * pskIdent, const char * psKey) {
#if defined(EMC_CLIENT_SECURE) #if defined(EMC_CLIENT_SECURE)
_client.client.setPreSharedKey(pskIdent, psKey); _client.client.setPreSharedKey(pskIdent, psKey);
#endif #endif
return *this; return *this;
} }
#endif #endif
#if defined(__linux__) #if defined(__linux__)
espMqttClient::espMqttClient() espMqttClient::espMqttClient()
: MqttClientSetup(espMqttClientTypes::UseInternalTask::NO) : MqttClientSetup(espMqttClientTypes::UseInternalTask::NO)
, _client() { , _client() {
_transport = &_client; _transport = &_client;
} }
#endif #endif

View File

@@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
// compare_flash_string added by Proddy // compare_flash_string added by Proddy for EMS-ESP
#ifndef UUID_COMMON_H_ #ifndef UUID_COMMON_H_
#define UUID_COMMON_H_ #define UUID_COMMON_H_
@@ -102,8 +102,8 @@ void loop();
*/ */
uint64_t get_uptime_ms(); uint64_t get_uptime_ms();
uint32_t get_uptime(); uint32_t get_uptime();
uint32_t get_uptime_sec(); uint32_t get_uptime_sec();
void set_uptime(); void set_uptime();

View File

@@ -64,7 +64,7 @@ namespace uuid {
* - <a href="https://mcu-uuid-log.readthedocs.io/">Documentation</a> * - <a href="https://mcu-uuid-log.readthedocs.io/">Documentation</a>
*/ */
// ANSI Colors - added by Proddy // ANSI Colors - added by Proddy for EMS-ESP
// See https://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html // See https://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html
#define COLOR_RESET "\x1B[0m" #define COLOR_RESET "\x1B[0m"
#define COLOR_BLACK "\x1B[0;30m" #define COLOR_BLACK "\x1B[0;30m"

View File

@@ -92,18 +92,18 @@ class AsyncWebServerRequest {
_method = method_s; _method = method_s;
} }
void addInterestingHeader(const String & name){}; void addInterestingHeader(const String & name) {};
size_t args() const { size_t args() const {
return 0; return 0;
} }
void send(AsyncWebServerResponse * response){}; void send(AsyncWebServerResponse * response) {};
void send(AsyncJsonResponse * response){}; void send(AsyncJsonResponse * response) {};
void send(PrettyAsyncJsonResponse * response){}; void send(PrettyAsyncJsonResponse * response) {};
void send(MsgpackAsyncJsonResponse * response){}; void send(MsgpackAsyncJsonResponse * response) {};
void send(int code, const String & contentType = String(), const String & content = String()){}; void send(int code, const String & contentType = String(), const String & content = String()) {};
void send(int code, const String & contentType, const __FlashStringHelper *){}; void send(int code, const String & contentType, const __FlashStringHelper *) {};
const String & url() const { const String & url() const {
return _url; return _url;
@@ -213,7 +213,7 @@ class AsyncWebServerResponse {
typedef std::function<void(AsyncWebServerRequest * request)> ArRequestHandlerFunction; typedef std::function<void(AsyncWebServerRequest * request)> ArRequestHandlerFunction;
typedef std::function<void(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final)> ArUploadHandlerFunction; typedef std::function<void(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final)> ArUploadHandlerFunction;
typedef std::function<void(AsyncWebServerRequest * request, uint8_t * data, size_t len, size_t index, size_t total)> ArBodyHandlerFunction; typedef std::function<void(AsyncWebServerRequest * request, uint8_t * data, size_t len, size_t index, size_t total)> ArBodyHandlerFunction;
typedef std::function<void(AsyncWebServerRequest * request, JsonVariant json)> ArJsonRequestHandlerFunction; // added by proddy typedef std::function<void(AsyncWebServerRequest * request, JsonVariant json)> ArJsonRequestHandlerFunction; // added by proddy for EMS-ESP
class AsyncWebServer { class AsyncWebServer {
protected: protected:
@@ -225,15 +225,15 @@ class AsyncWebServer {
~AsyncWebServer(){}; ~AsyncWebServer(){};
void begin(){}; void begin() {};
void end(); void end();
AsyncWebHandler & addHandler(AsyncWebHandler * handler) { AsyncWebHandler & addHandler(AsyncWebHandler * handler) {
return *handler; return *handler;
} }
void on(const char * uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest){}; void on(const char * uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest) {};
void on(const char * uri, ArJsonRequestHandlerFunction onRequest){}; // added by proddy void on(const char * uri, ArJsonRequestHandlerFunction onRequest) {}; // added by proddy for EMS-ESP
}; };
@@ -246,7 +246,7 @@ class AsyncEventSource : public AsyncWebHandler {
return 1; return 1;
} }
void send(const char * message, const char * event = NULL, uint32_t id = 0, uint32_t reconnect = 0){}; void send(const char * message, const char * event = NULL, uint32_t id = 0, uint32_t reconnect = 0) {};
}; };

View File

@@ -4,7 +4,7 @@
#include "Arduino.h" #include "Arduino.h"
#include <functional> #include <functional>
#include <IPAddress.h> #include <ClientPosixIPAddress.h>
#define WiFiMode_t wifi_mode_t #define WiFiMode_t wifi_mode_t
#define WIFI_OFF WIFI_MODE_NULL #define WIFI_OFF WIFI_MODE_NULL
@@ -162,7 +162,7 @@ class WiFiClass {
return 0; return 0;
}; };
void disconnect(bool v){}; void disconnect(bool v) {};
char * getHostname() { char * getHostname() {
return nullptr; return nullptr;
@@ -183,7 +183,7 @@ class ETHClass {
return false; return false;
}; };
void setHostname(const char * s){}; void setHostname(const char * s) {};
char * getHostname() { char * getHostname() {
return nullptr; return nullptr;

View File

@@ -381,7 +381,11 @@ const system_status = {
free_heap: 143, free_heap: 143,
ntp_status: 2, ntp_status: 2,
mqtt_status: true, mqtt_status: true,
ap_status: false ap_status: false,
network_status: 3, // wifi connected
// network_status: 10, // ethernet connected
// network_status: 6, // wifi disconnected
wifi_rssi: -41
}; };
let security_settings = { let security_settings = {
@@ -2134,19 +2138,26 @@ let emsesp_modules = {
"modules": [ "modules": [
{ {
id: 1, id: 1,
"name": "ModuleTest1", "key": "ModuleTest1",
"name": "Module Test 1",
"author": "proddy", "author": "proddy",
"version": "1.0.0", "version": "1.0.0",
"enabled": true, "enabled": true,
"status": "active" "status": 1,
"message": "Ready",
"license": "1234567890"
}, },
{ {
id: 2, id: 2,
"name": "ModuleTest2", "key": "ModuleTest2",
"name": "Module Test 2",
"author": "proddy", "author": "proddy",
"version": "1.0.0", "version": "1.0.0",
"enabled": true, "enabled": true,
"status": "active" "status": 2,
"message": "Ready",
"license": "ABCDEFGHIJKL"
} }
] ]
} }
@@ -2386,7 +2397,9 @@ router
return status(200); return status(200);
}) })
.get(VERIFY_AUTHORIZATION_ENDPOINT, () => verify_authentication) .get(VERIFY_AUTHORIZATION_ENDPOINT, () => verify_authentication)
.post(RESTART_ENDPOINT, () => status(200)) .post(RESTART_ENDPOINT, () => {
console.log('restarting...');
return status(200);})
.post(FACTORY_RESET_ENDPOINT, () => status(200)) .post(FACTORY_RESET_ENDPOINT, () => status(200))
.post(SIGN_IN_ENDPOINT, () => signin) .post(SIGN_IN_ENDPOINT, () => signin)
.get(GENERATE_TOKEN_ENDPOINT, () => generate_token); .get(GENERATE_TOKEN_ENDPOINT, () => generate_token);
@@ -2514,10 +2527,11 @@ router
const content = await request.json(); const content = await request.json();
let modules = content.modules; let modules = content.modules;
for (let i = 0; i < modules.length; i++) { for (let i = 0; i < modules.length; i++) {
const name = modules[i].name; const key = modules[i].key;
const objIndex = emsesp_modules.modules.findIndex((obj: any) => obj.name === name); const objIndex = emsesp_modules.modules.findIndex((obj: any) => obj.key === key);
if (objIndex !== -1) { if (objIndex !== -1) {
emsesp_modules.modules[objIndex].enabled = modules[i].enabled; emsesp_modules.modules[objIndex].enabled = modules[i].enabled;
emsesp_modules.modules[objIndex].license = modules[i].license;
} }
} }
console.log('modules updated', emsesp_modules); console.log('modules updated', emsesp_modules);

View File

@@ -32,55 +32,62 @@ WebModulesService::WebModulesService(AsyncWebServer * server, FS * fs, SecurityM
// load the settings when the service starts // load the settings when the service starts
void WebModulesService::begin() { void WebModulesService::begin() {
EMSESP::logger().info("Starting Modules service"); EMSESP::logger().info("Starting Modules service");
moduleLibrary.start(emsesp_);
_fsPersistence.readFromFS(); // read the file from FS #ifdef EMSESP_TEST
moduleLibrary.start(emsesp_, true); // hot load the test modules as well
#else
moduleLibrary.start(emsesp_);
#endif
_fsPersistence.readFromFS(); // read the file from FS - this will call the update function WebModules::update()
} }
void WebModulesService::loop() { void WebModulesService::loop() {
moduleLibrary.loop(); // loop the external library modules moduleLibrary.loop(); // loop the external library modules
} }
// this creates the modules file, saving it to the FS // this creates the modules settings file, saving it to the file system
// and also calls when the Modules web page is refreshed // it adds data to an empty 'root' json object
// and also calls when the Modules web page is refreshed/loaded
void WebModules::read(WebModules & webModules, JsonObject root) { void WebModules::read(WebModules & webModules, JsonObject root) {
#ifdef EMSESP_DEBUG emsesp_->logger().err("debug: in WebModules::read()"); // TODO remove
EMSESP::logger().debug("module read called");
#endif
JsonDocument doc_modules; JsonDocument doc_modules;
JsonObject root_modules = doc_modules.to<JsonObject>(); JsonObject root_modules = doc_modules.to<JsonObject>();
moduleLibrary.list(root_modules); // get list the external library modules, put in root json object moduleLibrary.list(root_modules); // get list the external library modules, put in a json object
JsonArray modules = root["modules"].to<JsonArray>(); JsonArray modules = root["modules"].to<JsonArray>();
uint8_t counter = 0; uint8_t counter = 0;
for (const JsonObject module : root_modules["modules"].as<JsonArray>()) { for (const JsonObject module : root_modules["modules"].as<JsonArray>()) {
JsonObject mi = modules.add<JsonObject>(); JsonObject mi = modules.add<JsonObject>();
mi["id"] = counter++; // id is only used to render the table and must be unique mi["id"] = counter++; // id is only used to render the table and must be unique
mi["key"] = module["key"].as<std::string>();
mi["name"] = module["name"].as<std::string>(); mi["name"] = module["name"].as<std::string>();
mi["author"] = module["author"].as<std::string>(); mi["author"] = module["author"].as<std::string>();
mi["version"] = module["version"].as<std::string>(); mi["version"] = module["version"].as<std::string>();
mi["status"] = module["status"].as<std::string>(); mi["status"] = module["status"].as<uint8_t>();
mi["enabled"] = module["enabled"].as<bool>(); mi["enabled"] = module["enabled"].as<bool>();
mi["message"] = module["message"].as<std::string>();
mi["license"] = module["license"].as<std::string>();
} }
} }
// read any Module settings from the FS settings // read any Module settings from the settings file
// and then apply the enable/disable // and then apply the enable/disable that is set by the user
// it's also called on a save // This function is called when ems-esp boots and also on a save from the Modules web page
StateUpdateResult WebModules::update(JsonObject root, WebModules & webModules) { StateUpdateResult WebModules::update(JsonObject root, WebModules & webModules) {
#ifdef EMSESP_DEBUG
EMSESP::logger().debug("module update called");
#endif
if (root["modules"].is<JsonArray>()) { if (root["modules"].is<JsonArray>()) {
for (const JsonObject module : root["modules"].as<JsonArray>()) { for (const JsonObject module : root["modules"].as<JsonArray>()) {
// set enabled/disabled auto key = module["key"].as<const char *>();
moduleLibrary.enable(module["name"], module["enabled"].as<bool>()); auto license = module["license"].as<const char *>();
auto enable = module["enabled"].as<bool>();
if (!moduleLibrary.enable(key, license, enable)) {
return StateUpdateResult::ERROR;
}
} }
} }
return StateUpdateResult::CHANGED_RESTART; return StateUpdateResult::CHANGED;
} }
} // namespace emsesp } // namespace emsesp

View File

@@ -251,7 +251,6 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) {
settings.analog_enabled = root["analog_enabled"] | EMSESP_DEFAULT_ANALOG_ENABLED; settings.analog_enabled = root["analog_enabled"] | EMSESP_DEFAULT_ANALOG_ENABLED;
check_flag(prev, settings.analog_enabled, ChangeFlags::ADC); check_flag(prev, settings.analog_enabled, ChangeFlags::ADC);
// //
// these need system restarts first before settings are activated... // these need system restarts first before settings are activated...
// //
@@ -294,7 +293,6 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) {
// //
// without checks or necessary restarts... // without checks or necessary restarts...
// //
settings.trace_raw = root["trace_raw"] | EMSESP_DEFAULT_TRACELOG_RAW; settings.trace_raw = root["trace_raw"] | EMSESP_DEFAULT_TRACELOG_RAW;
EMSESP::trace_raw(settings.trace_raw); EMSESP::trace_raw(settings.trace_raw);

View File

@@ -70,6 +70,14 @@ void WebStatusService::systemStatus(AsyncWebServerRequest * request) {
root["ap_status"] = EMSESP::esp8266React.apStatus(); root["ap_status"] = EMSESP::esp8266React.apStatus();
if (emsesp::EMSESP::system_.ethernet_connected()) {
root["network_status"] = 10; // custom code #10 - ETHERNET_STATUS_CONNECTED
root["wifi_rssi"] = 0;
} else {
root["network_status"] = static_cast<uint8_t>(WiFi.status());
root["wifi_rssi"] = WiFi.RSSI();
}
response->setLength(); response->setLength();
request->send(response); request->send(response);
} }