Merge pull request #713 from MichaelDvP/dev

min/max, bus-ids, MD5
This commit is contained in:
Proddy
2022-10-31 19:28:54 +01:00
committed by GitHub
32 changed files with 1511 additions and 1383 deletions

View File

@@ -10,11 +10,13 @@
- Add devices: Greenstar 30Ri boiler, Junkers FW500 thermostat, Buderus BC30 controller - Add devices: Greenstar 30Ri boiler, Junkers FW500 thermostat, Buderus BC30 controller
- Add program memory info - Add program memory info
- Add mqtt queue and connection infos - Add mqtt queue and connection infos
- Add min/max setting to customizations
- Adapt min/max if ems-value is not in this range - Adapt min/max if ems-value is not in this range
- Add heat pump settings for inputs and limits [#600](https://github.com/emsesp/EMS-ESP32/issues/600) - Add heat pump settings for inputs and limits [#600](https://github.com/emsesp/EMS-ESP32/issues/600)
- Add hybrid heatpump [#500](https://github.com/emsesp/EMS-ESP32/issues/500) - Add hybrid heatpump [#500](https://github.com/emsesp/EMS-ESP32/issues/500)
- Add translated tags - Add translated tags
- Add min/max to customization table [#686](https://github.com/emsesp/EMS-ESP32/issues/686)
- Add MD5 check [#637](https://github.com/emsesp/EMS-ESP32/issues/637)
- Add more bus-ids [#673](https://github.com/emsesp/EMS-ESP32/issues/673)
## Fixed ## Fixed
@@ -26,6 +28,7 @@
- RF room temperature sensor are shown as thermostat - RF room temperature sensor are shown as thermostat
- render mqtt float json values with trailing zero - render mqtt float json values with trailing zero
- removed flash strings - removed flash strings
- reload page after restart button is pressed
## **BREAKING CHANGES:** ## **BREAKING CHANGES:**

View File

@@ -1,5 +1,5 @@
{ {
"adapter": "react", "adapter": "react",
"baseLocale": "pl", "baseLocale": "en",
"$schema": "https://unpkg.com/typesafe-i18n@5.14.0/schema/typesafe-i18n.json" "$schema": "https://unpkg.com/typesafe-i18n@5.15.0/schema/typesafe-i18n.json"
} }

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

View File

@@ -35,7 +35,8 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, prog
onDrop, onDrop,
accept: { accept: {
'application/octet-stream': ['.bin'], 'application/octet-stream': ['.bin'],
'application/json': ['.json'] 'application/json': ['.json'],
'text/plain': ['.md5']
}, },
disabled: uploading, disabled: uploading,
multiple: false multiple: false

View File

@@ -16,6 +16,7 @@ const useFileUpload = ({ upload }: MediaUploadOptions) => {
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
const [uploading, setUploading] = useState<boolean>(false); const [uploading, setUploading] = useState<boolean>(false);
const [md5, setMd5] = useState<string>('');
const [uploadProgress, setUploadProgress] = useState<AxiosProgressEvent>(); const [uploadProgress, setUploadProgress] = useState<AxiosProgressEvent>();
const [uploadCancelToken, setUploadCancelToken] = useState<CancelTokenSource>(); const [uploadCancelToken, setUploadCancelToken] = useState<CancelTokenSource>();
@@ -23,6 +24,7 @@ const useFileUpload = ({ upload }: MediaUploadOptions) => {
setUploading(false); setUploading(false);
setUploadProgress(undefined); setUploadProgress(undefined);
setUploadCancelToken(undefined); setUploadCancelToken(undefined);
setMd5('');
}; };
const cancelUpload = useCallback(() => { const cancelUpload = useCallback(() => {
@@ -41,12 +43,17 @@ const useFileUpload = ({ upload }: MediaUploadOptions) => {
const cancelToken = axios.CancelToken.source(); const cancelToken = axios.CancelToken.source();
setUploadCancelToken(cancelToken); setUploadCancelToken(cancelToken);
setUploading(true); setUploading(true);
await upload(images[0], { const response = await upload(images[0], {
onUploadProgress: setUploadProgress, onUploadProgress: setUploadProgress,
cancelToken: cancelToken.token cancelToken: cancelToken.token
}); });
resetUploadingStates(); resetUploadingStates();
enqueueSnackbar(LL.UPLOAD() + ' ' + LL.SUCCESSFUL(), { variant: 'success' }); if (response.status === 200) {
enqueueSnackbar(LL.UPLOAD() + ' ' + LL.SUCCESSFUL(), { variant: 'success' });
} else if (response.status === 201) {
setMd5((String)(response.data));
enqueueSnackbar(LL.UPLOAD() + ' MD5 ' + LL.SUCCESSFUL(), { variant: 'success' });
}
} catch (error) { } catch (error) {
if (axios.isCancel(error)) { if (axios.isCancel(error)) {
enqueueSnackbar(LL.UPLOAD() + ' ' + LL.ABORTED(), { variant: 'warning' }); enqueueSnackbar(LL.UPLOAD() + ' ' + LL.ABORTED(), { variant: 'warning' });
@@ -57,7 +64,7 @@ const useFileUpload = ({ upload }: MediaUploadOptions) => {
} }
}; };
return [uploadFile, cancelUpload, uploading, uploadProgress] as const; return [uploadFile, cancelUpload, uploading, uploadProgress, md5] as const;
}; };
export default useFileUpload; export default useFileUpload;

View File

@@ -44,7 +44,7 @@ const dnsServers = ({ dns_ip_1, dns_ip_2 }: NetworkStatus) => {
if (!dns_ip_1) { if (!dns_ip_1) {
return 'none'; return 'none';
} }
return dns_ip_1 + (dns_ip_2 === '0.0.0.0' ? '' : ',' + dns_ip_2); return dns_ip_1 + (!dns_ip_2 || dns_ip_2 === '0.0.0.0' ? '' : ',' + dns_ip_2);
}; };
const IPs = (status: NetworkStatus) => { const IPs = (status: NetworkStatus) => {

View File

@@ -4,6 +4,7 @@ import { AxiosPromise } from 'axios';
import { Typography, Button, Box } from '@mui/material'; import { Typography, Button, Box } from '@mui/material';
import { FileUploadConfig } from '../../api/endpoints'; import { FileUploadConfig } from '../../api/endpoints';
import { SingleUpload, useFileUpload } from '../../components'; import { SingleUpload, useFileUpload } from '../../components';
import DownloadIcon from '@mui/icons-material/GetApp'; import DownloadIcon from '@mui/icons-material/GetApp';
@@ -21,7 +22,8 @@ interface UploadFileProps {
} }
const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => { const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
const [uploadFile, cancelUpload, uploading, uploadProgress] = useFileUpload({ upload: uploadGeneralFile });
const [uploadFile, cancelUpload, uploading, uploadProgress, md5] = useFileUpload({ upload: uploadGeneralFile });
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
@@ -80,6 +82,11 @@ const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
</Box> </Box>
</> </>
)} )}
{md5 !== '' && (
<Box mb={2}>
<Typography variant="body2">{'MD5: ' + md5}</Typography>
</Box>
)}
<SingleUpload onDrop={uploadFile} onCancel={cancelUpload} uploading={uploading} progress={uploadProgress} /> <SingleUpload onDrop={uploadFile} onCancel={cancelUpload} uploading={uploading} progress={uploadProgress} />
{!uploading && ( {!uploading && (

View File

@@ -39,6 +39,7 @@ import { extractErrorMessage, useRest } from '../../utils';
import { AuthenticatedContext } from '../../contexts/authentication'; import { AuthenticatedContext } from '../../contexts/authentication';
import axios from 'axios'; import axios from 'axios';
import RestartMonitor from './RestartMonitor';
import { useI18nContext } from '../../i18n/i18n-react'; import { useI18nContext } from '../../i18n/i18n-react';
@@ -52,6 +53,7 @@ function formatNumber(num: number) {
const SystemStatusForm: FC = () => { const SystemStatusForm: FC = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
const [restarting, setRestarting] = useState<boolean>();
const { loadData, data, errorMessage } = useRest<SystemStatus>({ read: SystemApi.readSystemStatus }); const { loadData, data, errorMessage } = useRest<SystemStatus>({ read: SystemApi.readSystemStatus });
@@ -69,7 +71,8 @@ const SystemStatusForm: FC = () => {
setLatestVersion({ setLatestVersion({
version: response.data.name, version: response.data.name,
url: response.data.assets[1].browser_download_url, url: response.data.assets[1].browser_download_url,
changelog: response.data.html_url changelog: response.data.assets[0].browser_download_url
}); });
}); });
axios.get(VERSIONCHECK_DEV_ENDPOINT).then((response) => { axios.get(VERSIONCHECK_DEV_ENDPOINT).then((response) => {
@@ -86,6 +89,7 @@ const SystemStatusForm: FC = () => {
try { try {
await SystemApi.restart(); await SystemApi.restart();
enqueueSnackbar(LL.APPLICATION_RESTARTING(), { variant: 'info' }); enqueueSnackbar(LL.APPLICATION_RESTARTING(), { variant: 'info' });
setRestarting(true);
} catch (error) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
} finally { } finally {
@@ -103,6 +107,7 @@ const SystemStatusForm: FC = () => {
startIcon={<CancelIcon />} startIcon={<CancelIcon />}
variant="outlined" variant="outlined"
onClick={() => setConfirmRestart(false)} onClick={() => setConfirmRestart(false)}
disabled={processing}
color="secondary" color="secondary"
> >
{LL.CANCEL()} {LL.CANCEL()}
@@ -199,6 +204,7 @@ const SystemStatusForm: FC = () => {
startIcon={<CancelIcon />} startIcon={<CancelIcon />}
variant="outlined" variant="outlined"
onClick={() => setConfirmFactoryReset(false)} onClick={() => setConfirmFactoryReset(false)}
disabled={processing}
color="secondary" color="secondary"
> >
{LL.CANCEL()} {LL.CANCEL()}
@@ -302,9 +308,7 @@ const SystemStatusForm: FC = () => {
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText
primary={LL.FLASH()} primary={LL.FLASH()}
secondary={ secondary={formatNumber(data.flash_chip_size) + ' KB / ' + (data.flash_chip_speed / 1000000).toFixed(0) + ' MHz'}
formatNumber(data.flash_chip_size) + ' KB / ' + (data.flash_chip_speed / 1000000).toFixed(0) + ' MHz'
}
/> />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
@@ -373,7 +377,7 @@ const SystemStatusForm: FC = () => {
return ( return (
<SectionContent title={LL.STATUS_OF(LL.SYSTEM(1))} titleGutter> <SectionContent title={LL.STATUS_OF(LL.SYSTEM(1))} titleGutter>
{content()} {restarting ? <RestartMonitor /> : content()}
</SectionContent> </SectionContent>
); );
}; };

View File

@@ -16,7 +16,9 @@ const UploadFileForm: FC = () => {
const uploadFile = useRef(async (file: File, config?: FileUploadConfig) => { const uploadFile = useRef(async (file: File, config?: FileUploadConfig) => {
const response = await SystemApi.uploadFile(file, config); const response = await SystemApi.uploadFile(file, config);
setRestarting(true); if (response.status === 200) {
setRestarting(true);
}
return response; return response;
}); });

View File

@@ -105,7 +105,7 @@ const de: Translation = {
BOARD_PROFILE: 'Platinenprofil', BOARD_PROFILE: 'Platinenprofil',
BUTTON: 'Taste', BUTTON: 'Taste',
TEMPERATURE: 'Temperatur', TEMPERATURE: 'Temperatur',
PHY_TYPE: 'Eth PHY Type', PHY_TYPE: 'Eth PHY Typ',
DISABLED: 'deaktiviert', DISABLED: 'deaktiviert',
TX_MODE: 'Tx Mode', TX_MODE: 'Tx Mode',
GENERAL_OPTIONS: 'Allgemeine Optionen', GENERAL_OPTIONS: 'Allgemeine Optionen',
@@ -177,7 +177,7 @@ const de: Translation = {
SYSTEM_VERSION_RUNNING: 'Sie verwenden die Version', SYSTEM_VERSION_RUNNING: 'Sie verwenden die Version',
SYSTEM_APPLY_FIRMWARE: 'um die neue Firmware anzuwenden', SYSTEM_APPLY_FIRMWARE: 'um die neue Firmware anzuwenden',
CLOSE: 'Schließen', CLOSE: 'Schließen',
USE: 'Verwenden', USE: 'Verwenden Sie',
FACTORY_RESET: 'Werkseinstellung', FACTORY_RESET: 'Werkseinstellung',
SYSTEM_FACTORY_TEXT: 'EMS-ESP wurde auf Werkseinstellung gesetzt und startet als Zugangspunkt neu', SYSTEM_FACTORY_TEXT: 'EMS-ESP wurde auf Werkseinstellung gesetzt und startet als Zugangspunkt neu',
SYSTEM_FACTORY_TEXT_DIALOG: 'Sind Sie sicher alle Einstellungen auf Werkseinstellung zu setzen?', SYSTEM_FACTORY_TEXT_DIALOG: 'Sind Sie sicher alle Einstellungen auf Werkseinstellung zu setzen?',
@@ -191,7 +191,7 @@ const de: Translation = {
PLATFORM: 'Platform (Platform / SDK)', PLATFORM: 'Platform (Platform / SDK)',
UPTIME: 'System Betriebszeit', UPTIME: 'System Betriebszeit',
CPU_FREQ: 'CPU Frequenz', CPU_FREQ: 'CPU Frequenz',
HEAP: 'RAM freier Speicher (Gesamt / max. Block)', HEAP: 'freier RAM Speicher (Gesamt / max. Block)',
PSRAM: 'PSRAM (Größe / Frei)', PSRAM: 'PSRAM (Größe / Frei)',
FLASH: 'Flash Speicher (Größe / Geschwindigkeit)', FLASH: 'Flash Speicher (Größe / Geschwindigkeit)',
APPSIZE: 'Programm (Genutzt / Frei)', APPSIZE: 'Programm (Genutzt / Frei)',
@@ -202,7 +202,7 @@ const de: Translation = {
DOWNLOAD_CUSTOMIZATION_TEXT: 'Herunterladen der individuellen Entitätsanpassungen', DOWNLOAD_CUSTOMIZATION_TEXT: 'Herunterladen der individuellen Entitätsanpassungen',
DOWNLOAD_SETTINGS_TEXT: DOWNLOAD_SETTINGS_TEXT:
'Herunterladen der Anwendungseinstellungen. Vorsicht beim Teilen der Einstellungen, da sie Passwörter und andere sensitive Einstellungen enthalten', 'Herunterladen der Anwendungseinstellungen. Vorsicht beim Teilen der Einstellungen, da sie Passwörter und andere sensitive Einstellungen enthalten',
UPLOAD_TEXT: 'Hochladen von neuer Firmware (.bin), Geräte- oder Entitätseinstellungen (.json)', UPLOAD_TEXT: 'Hochladen von neuer Firmware (.bin), Geräte- oder Entitätseinstellungen (.json), zur optionalen Validitätsprüfung zuerst die (.md5) Datei hochladen',
UPLOADING: 'Hochladen', UPLOADING: 'Hochladen',
UPLOAD_DROP_TEXT: 'Klicken Sie hier, oder ziehen eine Datei hierher', UPLOAD_DROP_TEXT: 'Klicken Sie hier, oder ziehen eine Datei hierher',
ERROR: 'Unerwarteter Fehler, bitter versuchen Sie es erneut', ERROR: 'Unerwarteter Fehler, bitter versuchen Sie es erneut',
@@ -259,9 +259,9 @@ const de: Translation = {
AP_PROVIDE_TEXT_3: 'Niemals', AP_PROVIDE_TEXT_3: 'Niemals',
AP_PREFERRED_CHANNEL: 'Bevorzugter Kanal', AP_PREFERRED_CHANNEL: 'Bevorzugter Kanal',
AP_HIDE_SSID: 'Verstecke SSID', AP_HIDE_SSID: 'Verstecke SSID',
AP_CLIENTS: 'AP Clients', AP_CLIENTS: 'AP-Klienten',
AP_MAX_CLIENTS: 'Max Clients', AP_MAX_CLIENTS: 'Max Anzahl AP-Klienten',
AP_LOCAL_IP: 'Local IP', AP_LOCAL_IP: 'Lokale IP',
NETWORK_SCAN: 'Suche nach WiFi Netzwerken', NETWORK_SCAN: 'Suche nach WiFi Netzwerken',
IDLE: 'Leerlauf', IDLE: 'Leerlauf',
LOST: 'Verloren', LOST: 'Verloren',
@@ -276,11 +276,11 @@ const de: Translation = {
NETWORK_LOW_BAND: 'Verwende niedrige WiFi Bandbreite', NETWORK_LOW_BAND: 'Verwende niedrige WiFi Bandbreite',
NETWORK_USE_DNS: 'Aktiviere mDNS Service', NETWORK_USE_DNS: 'Aktiviere mDNS Service',
NETWORK_ENABLE_IPV6: 'Aktiviere IPv6 Unterstützung', NETWORK_ENABLE_IPV6: 'Aktiviere IPv6 Unterstützung',
NETWORK_FIXED_IP: 'Feste IP Addresse', NETWORK_FIXED_IP: 'Feste IP Adresse',
NETWORK_GATEWAY: 'Getaway', NETWORK_GATEWAY: 'Gateway',
NETWORK_SUBNET: 'Subnet Mask', NETWORK_SUBNET: 'Subnetz Maske',
NETWORK_DNS: 'DNS Servers', NETWORK_DNS: 'DNS Server',
ADDRESS_OF: '{0} Address', ADDRESS_OF: '{0} Adresse',
ADMIN: 'Administrator', ADMIN: 'Administrator',
GUEST: 'Gast', GUEST: 'Gast',
NEW: 'Neuer', NEW: 'Neuer',

View File

@@ -19,8 +19,8 @@ const en: BaseTranslation = {
UPLOAD_SUCCESSFUL: 'Upload successful', UPLOAD_SUCCESSFUL: 'Upload successful',
DOWNLOAD_SUCCESSFUL: 'Download successful', DOWNLOAD_SUCCESSFUL: 'Download successful',
INVALID_LOGIN: 'Invalid login details', INVALID_LOGIN: 'Invalid login details',
NETWORK: 'Network', NETWORK: '{{Network}}',
SECURITY: 'Security', SECURITY: '{{Security}}',
ONOFF_CAP: 'ON/OFF', ONOFF_CAP: 'ON/OFF',
ONOFF: 'on/off', ONOFF: 'on/off',
TYPE: 'Type', TYPE: 'Type',
@@ -31,7 +31,7 @@ const en: BaseTranslation = {
DEVICE_DETAILS: 'Device Details', DEVICE_DETAILS: 'Device Details',
BRAND: 'Brand', BRAND: 'Brand',
ENTITY_NAME: 'Entity Name', ENTITY_NAME: 'Entity Name',
VALUE: 'Value', VALUE: '{{Value}}',
SHOW_FAV: 'only show favorites', SHOW_FAV: 'only show favorites',
DEVICE_SENSOR_DATA: 'Device and Sensor Data', DEVICE_SENSOR_DATA: 'Device and Sensor Data',
DEVICES_SENSORS: 'Devices & Sensors', DEVICES_SENSORS: 'Devices & Sensors',
@@ -39,7 +39,7 @@ const en: BaseTranslation = {
RUN_COMMAND: 'Call Command', RUN_COMMAND: 'Call Command',
CHANGE_VALUE: 'Change Value', CHANGE_VALUE: 'Change Value',
CANCEL: 'Cancel', CANCEL: 'Cancel',
RESET: 'Reset', RESET: '{{Reset}}',
SEND: 'Send', SEND: 'Send',
SAVE: 'Save', SAVE: 'Save',
REMOVE: 'Remove', REMOVE: 'Remove',
@@ -66,7 +66,7 @@ const en: BaseTranslation = {
EMS_BUS_WARNING: EMS_BUS_WARNING:
'EMS bus disconnected. If this warning still persists after a few seconds please check settings and board profile', 'EMS bus disconnected. If this warning still persists after a few seconds please check settings and board profile',
EMS_BUS_SCANNING: 'Scanning for EMS devices...', EMS_BUS_SCANNING: 'Scanning for EMS devices...',
CONNECTED: 'Connected', CONNECTED: '{{Connected}}',
TX_ISSUES: 'Tx issues - try a different Tx Mode', TX_ISSUES: 'Tx issues - try a different Tx Mode',
DISCONNECTED: 'Disconnected', DISCONNECTED: 'Disconnected',
EMS_SCAN: 'Are you sure you want to initiate a full device scan of the EMS bus?', EMS_SCAN: 'Are you sure you want to initiate a full device scan of the EMS bus?',
@@ -171,7 +171,7 @@ const en: BaseTranslation = {
ABORTED: 'aborted', ABORTED: 'aborted',
FAILED: 'failed', FAILED: 'failed',
SUCCESSFUL: 'successful', SUCCESSFUL: 'successful',
SYSTEM: 'System', SYSTEM: '{{System}}',
LOG_OF: '{0} Log', LOG_OF: '{0} Log',
STATUS_OF: '{0} Status', STATUS_OF: '{0} Status',
UPLOAD_DOWNLOAD: 'Upload/Download', UPLOAD_DOWNLOAD: 'Upload/Download',
@@ -182,7 +182,7 @@ const en: BaseTranslation = {
FACTORY_RESET: 'Factory Reset', FACTORY_RESET: 'Factory Reset',
SYSTEM_FACTORY_TEXT: 'Device has been factory reset and will now restart', SYSTEM_FACTORY_TEXT: 'Device has been factory reset and will now restart',
SYSTEM_FACTORY_TEXT_DIALOG: 'Are you sure you want to reset the device to its factory defaults?', SYSTEM_FACTORY_TEXT_DIALOG: 'Are you sure you want to reset the device to its factory defaults?',
VERSION_CHECK: 'Version Check', VERSION_CHECK: '{{Version Check}}',
THE_LATEST: 'The latest', THE_LATEST: 'The latest',
OFFICIAL: 'official', OFFICIAL: 'official',
DEVELOPMENT: 'development', DEVELOPMENT: 'development',
@@ -203,20 +203,20 @@ const en: BaseTranslation = {
DOWNLOAD_CUSTOMIZATION_TEXT: 'Download the entity customizations', DOWNLOAD_CUSTOMIZATION_TEXT: 'Download the entity customizations',
DOWNLOAD_SETTINGS_TEXT: DOWNLOAD_SETTINGS_TEXT:
'Download the application settings. Be careful when sharing your settings as this file contains passwords and other sensitive system information', 'Download the application settings. Be careful when sharing your settings as this file contains passwords and other sensitive system information',
UPLOAD_TEXT: 'Upload a new firmware (.bin) file, settings or customizations (.json) file below', UPLOAD_TEXT: 'Upload a new firmware (.bin) file, settings or customizations (.json) file below, for optional validation upload (.md5) first',
UPLOADING: 'Uploading', UPLOADING: 'Uploading',
UPLOAD_DROP_TEXT: 'Drop file or click here', UPLOAD_DROP_TEXT: 'Drop file or click here',
ERROR: 'Unexpected Error, please try again', ERROR: 'Unexpected Error, please try again',
TIME_SET: 'Time set', TIME_SET: 'Time set',
MANAGE_USERS: 'Manage Users', MANAGE_USERS: 'Manage Users',
IS_ADMIN: 'is Admin', IS_ADMIN: '{{is Admin}}',
USER_WARNING: 'You must have at least one admin user configured', USER_WARNING: 'You must have at least one admin user configured',
ADD: 'Add', ADD: '{{Add}}',
ACCESS_TOKEN_FOR: 'Access Token for', ACCESS_TOKEN_FOR: 'Access Token for',
ACCESS_TOKEN_TEXT: ACCESS_TOKEN_TEXT:
'The token below is used with REST API calls that require authorization. It can be passed either as a Bearer token in the Authorization header or in the access_token URL query parameter.', 'The token below is used with REST API calls that require authorization. It can be passed either as a Bearer token in the Authorization header or in the access_token URL query parameter.',
GENERATING_TOKEN: 'Generating token', GENERATING_TOKEN: 'Generating token',
USER: 'User', USER: '{{User}}',
MODIFY: 'Modify', MODIFY: 'Modify',
SU_TEXT: SU_TEXT:
'The su (super user) password is used to sign authentication tokens and also enable admin privileges within the Console.', 'The su (super user) password is used to sign authentication tokens and also enable admin privileges within the Console.',
@@ -246,14 +246,14 @@ const en: BaseTranslation = {
INACTIVE: 'Inactive', INACTIVE: 'Inactive',
ACTIVE: 'Active', ACTIVE: 'Active',
UNKNOWN: 'Unknown', UNKNOWN: 'Unknown',
SET_TIME: 'Set Time', SET_TIME: '{{Set Time}}',
SET_TIME_TEXT: 'Enter local date and time below to set the time', SET_TIME_TEXT: 'Enter local date and time below to set the time',
LOCAL_TIME: 'Local Time', LOCAL_TIME: 'Local Time',
UTC_TIME: 'UTC Time', UTC_TIME: 'UTC Time',
ENABLE_NTP: 'Enable NTP', ENABLE_NTP: 'Enable NTP',
NTP_SERVER: 'NTP Server', NTP_SERVER: 'NTP Server',
TIME_ZONE: 'Time Zone', TIME_ZONE: 'Time Zone',
ACCESS_POINT: 'Access Point', ACCESS_POINT: '{{Access Point}}',
AP_PROVIDE: 'Enable Access Point', AP_PROVIDE: 'Enable Access Point',
AP_PROVIDE_TEXT_1: 'always', AP_PROVIDE_TEXT_1: 'always',
AP_PROVIDE_TEXT_2: 'when WiFi is disconnected', AP_PROVIDE_TEXT_2: 'when WiFi is disconnected',

File diff suppressed because it is too large Load Diff

View File

@@ -14,10 +14,10 @@ const localeTranslationLoaders = {
se: () => import('./se'), se: () => import('./se'),
} }
const updateDictionary = (locale: Locales, dictionary: Partial<Translations>) => const updateDictionary = (locale: Locales, dictionary: Partial<Translations>): Translations =>
loadedLocales[locale] = { ...loadedLocales[locale], ...dictionary } loadedLocales[locale] = { ...loadedLocales[locale], ...dictionary }
export const importLocaleAsync = async (locale: Locales) => export const importLocaleAsync = async (locale: Locales): Promise<Translations> =>
(await localeTranslationLoaders[locale]()).default as unknown as Translations (await localeTranslationLoaders[locale]()).default as unknown as Translations
export const loadLocaleAsync = async (locale: Locales): Promise<void> => { export const loadLocaleAsync = async (locale: Locales): Promise<void> => {

View File

@@ -3,10 +3,11 @@
import { i18n as initI18n, i18nObject as initI18nObject, i18nString as initI18nString } from 'typesafe-i18n' import { i18n as initI18n, i18nObject as initI18nObject, i18nString as initI18nString } from 'typesafe-i18n'
import type { LocaleDetector } from 'typesafe-i18n/detectors' import type { LocaleDetector } from 'typesafe-i18n/detectors'
import type { LocaleTranslationFunctions, TranslateByString } from 'typesafe-i18n'
import { detectLocale as detectLocaleFn } from 'typesafe-i18n/detectors' import { detectLocale as detectLocaleFn } from 'typesafe-i18n/detectors'
import type { Formatters, Locales, Translations, TranslationFunctions } from './i18n-types' import type { Formatters, Locales, Translations, TranslationFunctions } from './i18n-types'
export const baseLocale: Locales = 'pl' export const baseLocale: Locales = 'en'
export const locales: Locales[] = [ export const locales: Locales[] = [
'de', 'de',
@@ -17,21 +18,22 @@ export const locales: Locales[] = [
'se' 'se'
] ]
export const isLocale = (locale: string) => locales.includes(locale as Locales) export const isLocale = (locale: string): locale is Locales => locales.includes(locale as Locales)
export const loadedLocales = {} as Record<Locales, Translations> export const loadedLocales: Record<Locales, Translations> = {} as Record<Locales, Translations>
export const loadedFormatters = {} as Record<Locales, Formatters> export const loadedFormatters: Record<Locales, Formatters> = {} as Record<Locales, Formatters>
export const i18nString = (locale: Locales) => initI18nString<Locales, Formatters>(locale, loadedFormatters[locale]) export const i18nString = (locale: Locales): TranslateByString => initI18nString<Locales, Formatters>(locale, loadedFormatters[locale])
export const i18nObject = (locale: Locales) => export const i18nObject = (locale: Locales): TranslationFunctions =>
initI18nObject<Locales, Translations, TranslationFunctions, Formatters>( initI18nObject<Locales, Translations, TranslationFunctions, Formatters>(
locale, locale,
loadedLocales[locale], loadedLocales[locale],
loadedFormatters[locale] loadedFormatters[locale]
) )
export const i18n = () => initI18n<Locales, Translations, TranslationFunctions, Formatters>(loadedLocales, loadedFormatters) export const i18n = (): LocaleTranslationFunctions<Locales, Translations, TranslationFunctions> =>
initI18n<Locales, Translations, TranslationFunctions, Formatters>(loadedLocales, loadedFormatters)
export const detectLocale = (...detectors: LocaleDetector[]) => detectLocaleFn<Locales>(baseLocale, locales, ...detectors) export const detectLocale = (...detectors: LocaleDetector[]): Locales => detectLocaleFn<Locales>(baseLocale, locales, ...detectors)

View File

@@ -311,12 +311,17 @@ const SettingsApplication: FC = () => {
margin="normal" margin="normal"
select select
> >
<MenuItem value={0x0a}>Terminal (0x0A)</MenuItem>
<MenuItem value={0x0b}>Service Key (0x0B)</MenuItem> <MenuItem value={0x0b}>Service Key (0x0B)</MenuItem>
<MenuItem value={0x0d}>Modem (0x0D)</MenuItem> <MenuItem value={0x0d}>Modem (0x0D)</MenuItem>
<MenuItem value={0x0a}>Terminal (0x0A)</MenuItem>
<MenuItem value={0x0e}>Converter (0x0E)</MenuItem> <MenuItem value={0x0e}>Converter (0x0E)</MenuItem>
<MenuItem value={0x0f}>Time Module (0x0F)</MenuItem> <MenuItem value={0x0f}>Time Module (0x0F)</MenuItem>
<MenuItem value={0x12}>Alarm Module (0x12)</MenuItem> <MenuItem value={0x48}>Gateway 1 (0x48)</MenuItem>
<MenuItem value={0x49}>Gateway 2 (0x49)</MenuItem>
<MenuItem value={0x4A}>Gateway 3 (0x4A)</MenuItem>
<MenuItem value={0x4B}>Gateway 4 (0x4B)</MenuItem>
<MenuItem value={0x4C}>Gateway 5 (0x4C)</MenuItem>
<MenuItem value={0x4D}>Gateway 7 (0x4D)</MenuItem>
</ValidatedTextField> </ValidatedTextField>
</Grid> </Grid>
</Grid> </Grid>
@@ -344,9 +349,7 @@ const SettingsApplication: FC = () => {
<MenuItem value="nl">Nederlands (NL)</MenuItem> <MenuItem value="nl">Nederlands (NL)</MenuItem>
<MenuItem value="se">Svenska (SE)</MenuItem> <MenuItem value="se">Svenska (SE)</MenuItem>
<MenuItem value="pl">Polski (PL)</MenuItem> <MenuItem value="pl">Polski (PL)</MenuItem>
<MenuItem disabled value="no"> <MenuItem value="no">Norsk (NO)</MenuItem>
Norsk (NO)
</MenuItem>
</ValidatedTextField> </ValidatedTextField>
</Box> </Box>
{data.led_gpio !== 0 && ( {data.led_gpio !== 0 && (

View File

@@ -66,7 +66,7 @@ const SettingsCustomization: FC = () => {
const entities_theme = useTheme({ const entities_theme = useTheme({
Table: ` Table: `
--data-table-library_grid-template-columns: 120px repeat(1, minmax(0, 1fr)) 120px; --data-table-library_grid-template-columns: 120px repeat(1, minmax(80px, 1fr)) 40px 40px 120px;
`, `,
BaseRow: ` BaseRow: `
font-size: 14px; font-size: 14px;
@@ -75,6 +75,12 @@ const SettingsCustomization: FC = () => {
} }
`, `,
BaseCell: ` BaseCell: `
&:nth-of-type(3) {
text-align: right;
}
&:nth-of-type(4) {
text-align: right;
}
&:last-of-type { &:last-of-type {
text-align: right; text-align: right;
} }
@@ -122,6 +128,12 @@ const SettingsCustomization: FC = () => {
&:nth-of-type(2) { &:nth-of-type(2) {
padding: 8px; padding: 8px;
} }
&:nth-of-type(3) {
padding-right: 4px;
}
&:nth-of-type(4) {
padding-right: 4px;
}
&:last-of-type { &:last-of-type {
padding-right: 8px; padding-right: 8px;
} }
@@ -446,6 +458,8 @@ const SettingsCustomization: FC = () => {
{LL.NAME()} {LL.NAME()}
</Button> </Button>
</HeaderCell> </HeaderCell>
<HeaderCell stiff>min</HeaderCell>
<HeaderCell stiff>max</HeaderCell>
<HeaderCell resize>{LL.VALUE(0)}</HeaderCell> <HeaderCell resize>{LL.VALUE(0)}</HeaderCell>
</HeaderRow> </HeaderRow>
</Header> </Header>
@@ -497,6 +511,8 @@ const SettingsCustomization: FC = () => {
</ToggleButtonGroup> </ToggleButtonGroup>
</Cell> </Cell>
<Cell>{formatName(de)}</Cell> <Cell>{formatName(de)}</Cell>
<Cell>{!(de.m & DeviceEntityMask.DV_READONLY) && (formatValue(de.mi))}</Cell>
<Cell>{!(de.m & DeviceEntityMask.DV_READONLY) && (formatValue(de.ma))}</Cell>
<Cell>{formatValue(de.v)}</Cell> <Cell>{formatValue(de.v)}</Cell>
</Row> </Row>
))} ))}
@@ -626,7 +642,7 @@ const SettingsCustomization: FC = () => {
onChange={updateValue(setDeviceEntity)} onChange={updateValue(setDeviceEntity)}
/> />
</Grid> </Grid>
{typeof de.v === 'number' && de.w && ( {typeof de.v === 'number' && de.w && !(de.m & DeviceEntityMask.DV_READONLY) && (
<> <>
<Grid item> <Grid item>
<TextField <TextField

View File

@@ -1,6 +1,6 @@
export const extractErrorMessage = (error: any, defaultMessage: string) => { export const extractErrorMessage = (error: any, defaultMessage: string) => {
if (error.request) { if (error.request) {
return defaultMessage + ' (' + error.request.statusText + ')'; return defaultMessage + ' (' + error.request.status + ': ' + error.request.statusText + ')';
} else if (error instanceof Error) { } else if (error instanceof Error) {
return defaultMessage + ' (' + error.message + ')'; return defaultMessage + ' (' + error.message + ')';
} }

View File

@@ -80,11 +80,11 @@ AsyncMqttClient * MqttSettingsService::getMqttClient() {
} }
void MqttSettingsService::onMqttConnect(bool sessionPresent) { void MqttSettingsService::onMqttConnect(bool sessionPresent) {
// emsesp::EMSESP::logger().info(F("Connected to MQTT, %s"), (sessionPresent) ? F("with persistent session") : F("without persistent session")); // emsesp::EMSESP::logger().info("Connected to MQTT, %s", (sessionPresent) ? ("with persistent session") : ("without persistent session"));
} }
void MqttSettingsService::onMqttDisconnect(AsyncMqttClientDisconnectReason reason) { void MqttSettingsService::onMqttDisconnect(AsyncMqttClientDisconnectReason reason) {
// emsesp::EMSESP::logger().info(F("Disconnected from MQTT reason: %d"), (uint8_t)reason); // emsesp::EMSESP::logger().info("Disconnected from MQTT reason: %d", (uint8_t)reason);
_disconnectReason = reason; _disconnectReason = reason;
_disconnectedAt = uuid::get_uptime(); _disconnectedAt = uuid::get_uptime();
} }
@@ -104,14 +104,14 @@ void MqttSettingsService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
case ARDUINO_EVENT_ETH_GOT_IP6: case ARDUINO_EVENT_ETH_GOT_IP6:
case ARDUINO_EVENT_WIFI_STA_GOT_IP6: case ARDUINO_EVENT_WIFI_STA_GOT_IP6:
if (_state.enabled) { if (_state.enabled) {
// emsesp::EMSESP::logger().info(F("IPv4 Network connection found, starting MQTT client")); // emsesp::EMSESP::logger().info("IPv4 Network connection found, starting MQTT client");
onConfigUpdated(); onConfigUpdated();
} }
break; break;
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
case ARDUINO_EVENT_ETH_DISCONNECTED: case ARDUINO_EVENT_ETH_DISCONNECTED:
if (_state.enabled) { if (_state.enabled) {
// emsesp::EMSESP::logger().info(F("Network connection dropped, stopping MQTT client")); // emsesp::EMSESP::logger().info("Network connection dropped, stopping MQTT client");
_mqttClient.disconnect(); _mqttClient.disconnect();
// onConfigUpdated(); // onConfigUpdated();
} }
@@ -127,7 +127,7 @@ void MqttSettingsService::configureMqtt() {
_mqttClient.disconnect(); _mqttClient.disconnect();
// only connect if WiFi is connected and MQTT is enabled // only connect if WiFi is connected and MQTT is enabled
if (_state.enabled && emsesp::EMSESP::system_.network_connected()) { if (_state.enabled && emsesp::EMSESP::system_.network_connected()) {
// emsesp::EMSESP::logger().info(F("Configuring Mqtt client")); // emsesp::EMSESP::logger().info("Configuring Mqtt client");
_mqttClient.setServer(retainCstr(_state.host.c_str(), &_retainedHost), _state.port); _mqttClient.setServer(retainCstr(_state.host.c_str(), &_retainedHost), _state.port);
if (_state.username.length() > 0) { if (_state.username.length() > 0) {
_mqttClient.setCredentials(retainCstr(_state.username.c_str(), &_retainedUsername), _mqttClient.setCredentials(retainCstr(_state.username.c_str(), &_retainedUsername),
@@ -141,7 +141,7 @@ void MqttSettingsService::configureMqtt() {
_mqttClient.setMaxTopicLength(_state.maxTopicLength); _mqttClient.setMaxTopicLength(_state.maxTopicLength);
_mqttClient.connect(); _mqttClient.connect();
// } else { // } else {
// emsesp::EMSESP::logger().info(F("Error configuring Mqtt client")); // emsesp::EMSESP::logger().info("Error configuring Mqtt client");
} }
} }

View File

@@ -28,14 +28,14 @@ void NTPSettingsService::WiFiEvent(WiFiEvent_t event) {
switch (event) { switch (event) {
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
case ARDUINO_EVENT_ETH_DISCONNECTED: case ARDUINO_EVENT_ETH_DISCONNECTED:
emsesp::EMSESP::logger().info(F("WiFi connection dropped, stopping NTP")); emsesp::EMSESP::logger().info("WiFi connection dropped, stopping NTP");
connected_ = false; connected_ = false;
configureNTP(); configureNTP();
break; break;
case ARDUINO_EVENT_WIFI_STA_GOT_IP: case ARDUINO_EVENT_WIFI_STA_GOT_IP:
case ARDUINO_EVENT_ETH_GOT_IP: case ARDUINO_EVENT_ETH_GOT_IP:
// emsesp::EMSESP::logger().info(F("Got IP address, starting NTP synchronization")); // emsesp::EMSESP::logger().info("Got IP address, starting NTP synchronization");
connected_ = true; connected_ = true;
configureNTP(); configureNTP();
break; break;
@@ -49,7 +49,7 @@ void NTPSettingsService::WiFiEvent(WiFiEvent_t event) {
void NTPSettingsService::configureNTP() { void NTPSettingsService::configureNTP() {
emsesp::EMSESP::system_.ntp_connected(false); emsesp::EMSESP::system_.ntp_connected(false);
if (connected_ && _state.enabled) { if (connected_ && _state.enabled) {
emsesp::EMSESP::logger().info(F("Starting NTP")); emsesp::EMSESP::logger().info("Starting NTP");
sntp_set_sync_interval(3600000); // onehour sntp_set_sync_interval(3600000); // onehour
sntp_set_time_sync_notification_cb(ntp_received); sntp_set_time_sync_notification_cb(ntp_received);
configTzTime(_state.tzFormat.c_str(), _state.server.c_str()); configTzTime(_state.tzFormat.c_str(), _state.server.c_str());
@@ -81,6 +81,6 @@ void NTPSettingsService::configureTime(AsyncWebServerRequest * request, JsonVari
} }
void NTPSettingsService::ntp_received(struct timeval * tv) { void NTPSettingsService::ntp_received(struct timeval * tv) {
// emsesp::EMSESP::logger().info(F("NTP sync to %d sec"), tv->tv_sec); // emsesp::EMSESP::logger().info("NTP sync to %d sec", tv->tv_sec);
emsesp::EMSESP::system_.ntp_connected(true); emsesp::EMSESP::system_.ntp_connected(true);
} }

View File

@@ -47,15 +47,15 @@ void OTASettingsService::configureArduinoOTA() {
#if defined(EMSESP_USE_SERIAL) #if defined(EMSESP_USE_SERIAL)
Serial.printf("Error[%u]: ", error); Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) if (error == OTA_AUTH_ERROR)
Serial.println(F("Auth Failed")); Serial.println("Auth Failed");
else if (error == OTA_BEGIN_ERROR) else if (error == OTA_BEGIN_ERROR)
Serial.println(F("Begin Failed")); Serial.println("Begin Failed");
else if (error == OTA_CONNECT_ERROR) else if (error == OTA_CONNECT_ERROR)
Serial.println(F("Connect Failed")); Serial.println("Connect Failed");
else if (error == OTA_RECEIVE_ERROR) else if (error == OTA_RECEIVE_ERROR)
Serial.println(F("Receive Failed")); Serial.println("Receive Failed");
else if (error == OTA_END_ERROR) else if (error == OTA_END_ERROR)
Serial.println(F("End Failed")); Serial.println("End Failed");
#endif #endif
}); });

View File

@@ -3,6 +3,7 @@
using namespace std::placeholders; // for `_1` etc using namespace std::placeholders; // for `_1` etc
static bool is_firmware = false; static bool is_firmware = false;
static char md5[33] = "\0";
UploadFileService::UploadFileService(AsyncWebServer * server, SecurityManager * securityManager) UploadFileService::UploadFileService(AsyncWebServer * server, SecurityManager * securityManager)
: _securityManager(securityManager) { : _securityManager(securityManager) {
@@ -33,45 +34,53 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri
Serial.println(); Serial.println();
#endif #endif
is_firmware = false;
if ((extension == "bin") && (fsize > 1500000)) { if ((extension == "bin") && (fsize > 1500000)) {
is_firmware = true; is_firmware = true;
} else if (extension == "json") { } else if (extension == "json") {
is_firmware = false; md5[0] = '\0'; // clear md5
} else if (extension == "md5") {
if (len == 32) {
memcpy(md5, data, 32);
md5[32] = '\0';
}
return;
} else { } else {
is_firmware = false; md5[0] = '\0';
return; // not support file type return; // not support file type
} }
if (is_firmware) { if (is_firmware) {
// Check firmware header, 0xE9 magic offset 0 indicates esp bin, chip offset 12: esp32:0, S2:2, C3:5 // Check firmware header, 0xE9 magic offset 0 indicates esp bin, chip offset 12: esp32:0, S2:2, C3:5
#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 #if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4
bool isC3 = (fname.find("C3") != std::string::npos); if (len > 12 && (data[0] != 0xE9 || data[12] != 0)) {
bool isS2 = (fname.find("S2") != std::string::npos);
if (isC3 || isS2 || (len > 12 && (data[0] != 0xE9 || data[12] != 0))) {
handleError(request, 503); // service unavailable handleError(request, 503); // service unavailable
return; return;
} }
#elif CONFIG_IDF_TARGET_ESP32S2 #elif CONFIG_IDF_TARGET_ESP32S2
bool isS2 = (fname.find("S2") != std::string::npos); if (len > 12 && (data[0] != 0xE9 || data[12] != 2)) {
if (!isS2 || (len > 12 && (data[0] != 0xE9 || data[12] != 2))) {
handleError(request, 503); // service unavailable handleError(request, 503); // service unavailable
return; return;
} }
#elif CONFIG_IDF_TARGET_ESP32C3 #elif CONFIG_IDF_TARGET_ESP32C3
bool isC3 = (fname.find("C3") != std::string::npos); if (len > 12 && (data[0] != 0xE9 || data[12] != 5)) {
if (!isC3 || (len > 12 && (data[0] != 0xE9 || data[12] != 5))) {
handleError(request, 503); // service unavailable handleError(request, 503); // service unavailable
return; return;
} }
#endif #endif
// it's firmware - initialize the ArduinoOTA updater // it's firmware - initialize the ArduinoOTA updater
if (Update.begin(fsize)) { if (Update.begin(fsize)) {
if (strlen(md5) == 32) {
Update.setMD5(md5);
md5[0] = '\0';
}
request->onDisconnect(UploadFileService::handleEarlyDisconnect); // success, let's make sure we end the update if the client hangs up request->onDisconnect(UploadFileService::handleEarlyDisconnect); // success, let's make sure we end the update if the client hangs up
} else { } else {
#if defined(EMSESP_USE_SERIAL) #if defined(EMSESP_USE_SERIAL)
Update.printError(Serial); Update.printError(Serial);
#endif #endif
handleError(request, 507); // failed to begin, send an error response Insufficient Storage handleError(request, 507); // failed to begin, send an error response Insufficient Storage
return;
} }
} else { } else {
// its a normal file, open a new temp file to write the contents too // its a normal file, open a new temp file to write the contents too
@@ -83,7 +92,6 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri
if (len) { if (len) {
request->_tempFile.write(data, len); // stream the incoming chunk to the opened file request->_tempFile.write(data, len); // stream the incoming chunk to the opened file
} }
} else { } else {
// if we haven't delt with an error, continue with the firmware update // if we haven't delt with an error, continue with the firmware update
if (!request->_tempObject) { if (!request->_tempObject) {
@@ -123,6 +131,11 @@ void UploadFileService::uploadComplete(AsyncWebServerRequest * request) {
request->send(response); request->send(response);
return; return;
} }
if (strlen(md5) == 32) {
AsyncWebServerResponse * response = request->beginResponse(201, "text/plain", md5); // created
request->send(response);
return;
}
handleError(request, 403); // send the forbidden response handleError(request, 403); // send the forbidden response
} }

View File

@@ -1154,7 +1154,7 @@ rest_server.post(EMSESP_BOARDPROFILE_ENDPOINT, (req, res) => {
} else if (board_profile == 'C3MINI') { } else if (board_profile == 'C3MINI') {
// Lolin C3 mini // Lolin C3 mini
data.led_gpio = 7 data.led_gpio = 7
data.dallas_gpio = 2 data.dallas_gpio = 1
data.rx_gpio = 4 data.rx_gpio = 4
data.tx_gpio = 5 data.tx_gpio = 5
data.pbutton_gpio = 9 data.pbutton_gpio = 9
@@ -1162,6 +1162,17 @@ rest_server.post(EMSESP_BOARDPROFILE_ENDPOINT, (req, res) => {
data.eth_power = 0 data.eth_power = 0
data.eth_phy_addr = 0 data.eth_phy_addr = 0
data.eth_clock_mode = 0 data.eth_clock_mode = 0
} else if (board_profile == 'S2MINI') {
// Lolin C3 mini
data.led_gpio = 15
data.dallas_gpio = 7
data.rx_gpio = 11
data.tx_gpio = 12
data.pbutton_gpio = 0
data.phy_type = 0
data.eth_power = 0
data.eth_phy_addr = 0
data.eth_clock_mode = 0
} }
console.log('boardProfile POST. Sending back, profile: ' + board_profile + ', ' + 'data: ' + JSON.stringify(data)) console.log('boardProfile POST. Sending back, profile: ' + board_profile + ', ' + 'data: ' + JSON.stringify(data))

View File

@@ -2,6 +2,7 @@ import shutil
import re import re
import os import os
Import("env") Import("env")
import hashlib
OUTPUT_DIR = "build{}".format(os.path.sep) OUTPUT_DIR = "build{}".format(os.path.sep)
@@ -18,7 +19,6 @@ def bin_copy(source, target, env):
bag[var] = m.group(1) bag[var] = m.group(1)
app_version = bag.get('app_version') app_version = bag.get('app_version')
platform = "ESP32" platform = "ESP32"
chip_target = env.get('PIOENV').upper() chip_target = env.get('PIOENV').upper()
@@ -33,14 +33,13 @@ def bin_copy(source, target, env):
# alternatively take platform from the pio target # alternatively take platform from the pio target
# platform = str(target[0]).split(os.path.sep)[2] # platform = str(target[0]).split(os.path.sep)[2]
chip_target = env.get('PIOENV').upper()
print("app version: " + app_version) print("app version: " + app_version)
print("platform: " + platform) print("platform: " + platform)
print("chip_target: " + chip_target) print("chip_target: " + chip_target)
# convert . to _ so Windows doesn't complain # convert . to _ so Windows doesn't complain
variant = "EMS-ESP-" + chip_target + "-" + app_version.replace(".", "_") variant = "EMS-ESP-" + app_version.replace(".", "_") + "-" + chip_target.replace("CI","ESP32")
# check if output directories exist and create if necessary # check if output directories exist and create if necessary
if not os.path.isdir(OUTPUT_DIR): if not os.path.isdir(OUTPUT_DIR):
@@ -52,15 +51,29 @@ def bin_copy(source, target, env):
# create string with location and file names based on variant # create string with location and file names based on variant
bin_file = "{}firmware{}{}.bin".format(OUTPUT_DIR, os.path.sep, variant) bin_file = "{}firmware{}{}.bin".format(OUTPUT_DIR, os.path.sep, variant)
md5_file = "{}firmware{}{}.md5".format(OUTPUT_DIR, os.path.sep, variant)
# check if new target files exist and remove if necessary # check if new target files exist and remove if necessary
for f in [bin_file]: for f in [bin_file]:
if os.path.isfile(f): if os.path.isfile(f):
os.remove(f) os.remove(f)
# check if new target files exist and remove if necessary
for f in [md5_file]:
if os.path.isfile(f):
os.remove(f)
print("Renaming file to "+bin_file) print("Renaming file to "+bin_file)
# copy firmware.bin to firmware/<variant>.bin # copy firmware.bin to firmware/<variant>.bin
shutil.copy(str(target[0]), bin_file) shutil.copy(str(target[0]), bin_file)
with open(bin_file,"rb") as f:
result = hashlib.md5(f.read())
print("Calculating MD5: "+result.hexdigest())
file1 = open(md5_file, 'w')
file1.write(result.hexdigest())
file1.close()
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", [bin_copy]) env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", [bin_copy])
env.AddPostAction("$BUILD_DIR/${PROGNAME}.md5", [bin_copy])

View File

@@ -151,11 +151,11 @@ void EMSESPShell::add_console_commands() {
}, },
"local"); "local");
} else { } else {
shell.println("Must be 0B, 0D, 0A, 0F or 12"); shell.println("Must be 0B, 0D, 0A, 0E, 0F, or 48 - 4D");
} }
}, },
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> const std::vector<std::string> { [](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> const std::vector<std::string> {
return std::vector<std::string>{"0B", "0D", "0A", "0F", "12"}; return std::vector<std::string>{"0B", "0D", "0A", "0E", "0F", "48", "49", "4A", "4B", "4C", "4D"};
}); });
commands->add_command(ShellContext::MAIN, commands->add_command(ShellContext::MAIN,

View File

@@ -947,15 +947,14 @@ void EMSdevice::generate_values_web_customization(JsonArray & output) {
obj["m"] = dv.state >> 4; // send back the mask state. We're only interested in the high nibble obj["m"] = dv.state >> 4; // send back the mask state. We're only interested in the high nibble
obj["w"] = dv.has_cmd; // if writable obj["w"] = dv.has_cmd; // if writable
if (dv.has_cmd) { if (dv.has_cmd && (obj["v"].is<float>() || obj["v"].is<int>())) {
// set the custom min and max values if there are any // set the min and max values if there are any and if entity has a value
int16_t dv_set_min; int16_t dv_set_min;
uint16_t dv_set_max; uint16_t dv_set_max;
if (dv.get_custom_min(dv_set_min)) { if (dv.get_min_max(dv_set_min, dv_set_max)) {
obj["mi"] = fahrenheit ? (int)(dv_set_min * 1.8 + 32 * (fahrenheit - 1)) : dv_set_min; char s[10];
} obj["mi"] = Helpers::render_value(s, dv_set_min, 0, fahrenheit);
if (dv.get_custom_max(dv_set_max)) { obj["ma"] = Helpers::render_value(s, dv_set_max, 0, fahrenheit);
obj["ma"] = fahrenheit ? (int)(dv_set_max * 1.8 + 32 * (fahrenheit - 1)) : dv_set_max;
} }
} }
} }

View File

@@ -287,13 +287,13 @@ bool DeviceValue::get_min_max(int16_t & dv_set_min, uint16_t & dv_set_max) {
if (type == DeviceValueType::USHORT) { if (type == DeviceValueType::USHORT) {
dv_set_min = Helpers::transformNumFloat(0, numeric_operator, fahrenheit); dv_set_min = Helpers::transformNumFloat(0, numeric_operator, fahrenheit);
dv_set_max = Helpers::transformNumFloat(EMS_VALUE_USHORT_NOTSET, numeric_operator, fahrenheit); dv_set_max = Helpers::transformNumFloat(EMS_VALUE_USHORT_NOTSET - 1, numeric_operator, fahrenheit);
return true; return true;
} }
if (type == DeviceValueType::SHORT) { if (type == DeviceValueType::SHORT) {
dv_set_min = Helpers::transformNumFloat(-EMS_VALUE_SHORT_NOTSET, numeric_operator, fahrenheit); dv_set_min = Helpers::transformNumFloat(-EMS_VALUE_SHORT_NOTSET + 1, numeric_operator, fahrenheit);
dv_set_max = Helpers::transformNumFloat(EMS_VALUE_SHORT_NOTSET, numeric_operator, fahrenheit); dv_set_max = Helpers::transformNumFloat(EMS_VALUE_SHORT_NOTSET - 1, numeric_operator, fahrenheit);
return true; return true;
} }
@@ -301,7 +301,7 @@ bool DeviceValue::get_min_max(int16_t & dv_set_min, uint16_t & dv_set_max) {
if (uom == DeviceValueUOM::PERCENT) { if (uom == DeviceValueUOM::PERCENT) {
dv_set_max = 100; dv_set_max = 100;
} else { } else {
dv_set_max = Helpers::transformNumFloat(EMS_VALUE_UINT_NOTSET, numeric_operator, fahrenheit); dv_set_max = Helpers::transformNumFloat(EMS_VALUE_UINT_NOTSET - 1, numeric_operator, fahrenheit);
} }
return true; return true;
} }
@@ -311,19 +311,19 @@ bool DeviceValue::get_min_max(int16_t & dv_set_min, uint16_t & dv_set_max) {
dv_set_min = -100; dv_set_min = -100;
dv_set_max = 100; dv_set_max = 100;
} else { } else {
dv_set_min = Helpers::transformNumFloat(-EMS_VALUE_INT_NOTSET, numeric_operator, fahrenheit); dv_set_min = Helpers::transformNumFloat(-EMS_VALUE_INT_NOTSET + 1, numeric_operator, fahrenheit);
dv_set_max = Helpers::transformNumFloat(EMS_VALUE_INT_NOTSET, numeric_operator, fahrenheit); dv_set_max = Helpers::transformNumFloat(EMS_VALUE_INT_NOTSET - 1, numeric_operator, fahrenheit);
} }
return true; return true;
} }
if (type == DeviceValueType::ULONG) { if (type == DeviceValueType::ULONG) {
dv_set_max = Helpers::transformNumFloat(EMS_VALUE_ULONG_NOTSET, numeric_operator); dv_set_max = Helpers::transformNumFloat(EMS_VALUE_ULONG_NOTSET - 1, numeric_operator);
return true; return true;
} }
if (type == DeviceValueType::TIME) { if (type == DeviceValueType::TIME) {
dv_set_max = Helpers::transformNumFloat(EMS_VALUE_ULONG_NOTSET, numeric_operator); dv_set_max = Helpers::transformNumFloat(EMS_VALUE_ULONG_NOTSET - 1, numeric_operator);
return true; return true;
} }

View File

@@ -377,7 +377,11 @@ void System::start() {
// disable bluetooth module // disable bluetooth module
// periph_module_disable(PERIPH_BT_MODULE); // periph_module_disable(PERIPH_BT_MODULE);
if (low_clock_) { if (low_clock_) {
#if CONFIG_IDF_TARGET_ESP32C3
setCpuFrequencyMhz(80);
#else
setCpuFrequencyMhz(160); setCpuFrequencyMhz(160);
#endif
} }
fstotal_ = LittleFS.totalBytes() / 1024; // read only once, it takes 500 ms to read fstotal_ = LittleFS.totalBytes() / 1024; // read only once, it takes 500 ms to read
psram_ = ESP.getPsramSize() / 1024; psram_ = ESP.getPsramSize() / 1024;

View File

@@ -1,4 +1,4 @@
#define EMSESP_APP_VERSION "3.5.0b7" #define EMSESP_APP_VERSION "3.5.0b8"
#if CONFIG_IDF_TARGET_ESP32C3 #if CONFIG_IDF_TARGET_ESP32C3
#define EMSESP_PLATFORM "ESP32-C3"; #define EMSESP_PLATFORM "ESP32-C3";

View File

@@ -34,7 +34,7 @@ WebStatusService::WebStatusService(AsyncWebServer * server, SecurityManager * se
void WebStatusService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) { void WebStatusService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
switch (event) { switch (event) {
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
EMSESP::logger().warning("WiFi disconnected. Reason code=%d", info.wifi_sta_disconnected.reason); // IDF 4.0 EMSESP::logger().warning("WiFi disconnected. Reason code=%s", disconnectReason(info.wifi_sta_disconnected.reason)); // IDF 4.0
WiFi.disconnect(true); WiFi.disconnect(true);
break; break;
@@ -42,7 +42,6 @@ void WebStatusService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
EMSESP::logger().info("WiFi connected with IP=%s, hostname=%s", WiFi.localIP().toString().c_str(), WiFi.getHostname()); EMSESP::logger().info("WiFi connected with IP=%s, hostname=%s", WiFi.localIP().toString().c_str(), WiFi.getHostname());
#endif #endif
// EMSESP::system_.send_heartbeat(); // send from mqtt start
EMSESP::system_.syslog_init(); EMSESP::system_.syslog_init();
mDNS_start(); mDNS_start();
break; break;
@@ -226,4 +225,68 @@ void WebStatusService::mDNS_start() const {
#endif #endif
} }
const char * WebStatusService::disconnectReason(uint8_t code) {
switch (code) {
case WIFI_REASON_UNSPECIFIED: // = 1,
return "unspecifiied";
case WIFI_REASON_AUTH_EXPIRE: // = 2,
return "auth expire";
case WIFI_REASON_AUTH_LEAVE: // = 3,
return "auth leave";
case WIFI_REASON_ASSOC_EXPIRE: // = 4,
return "assoc expired";
case WIFI_REASON_ASSOC_TOOMANY: // = 5,
return "assoc too many";
case WIFI_REASON_NOT_AUTHED: // = 6,
return "not authed";
case WIFI_REASON_NOT_ASSOCED: // = 7,
return "not assoced";
case WIFI_REASON_ASSOC_LEAVE: // = 8,
return "assoc leave";
case WIFI_REASON_ASSOC_NOT_AUTHED: // = 9,
return "assoc not authed";
case WIFI_REASON_DISASSOC_PWRCAP_BAD: // = 10,
return "disassoc powerCAP bad";
case WIFI_REASON_DISASSOC_SUPCHAN_BAD: // = 11,
return "disassoc supchan bad";
case WIFI_REASON_IE_INVALID: // = 13,
return "IE invalid";
case WIFI_REASON_MIC_FAILURE: // = 14,
return "MIC failure";
case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: // = 15,
return "4way handshake timeout";
case WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT: // = 16,
return "group key-update timeout";
case WIFI_REASON_IE_IN_4WAY_DIFFERS: // = 17,
return "IE in 4way differs";
case WIFI_REASON_GROUP_CIPHER_INVALID: // = 18,
return "group cipher invalid";
case WIFI_REASON_PAIRWISE_CIPHER_INVALID: // = 19,
return "pairwise cipher invalid";
case WIFI_REASON_AKMP_INVALID: // = 20,
return "AKMP invalid";
case WIFI_REASON_UNSUPP_RSN_IE_VERSION: // = 21,
return "unsupported RSN_IE version";
case WIFI_REASON_INVALID_RSN_IE_CAP: // = 22,
return "invalid RSN_IE_CAP";
case WIFI_REASON_802_1X_AUTH_FAILED: // = 23,
return "802 X1 auth failed";
case WIFI_REASON_CIPHER_SUITE_REJECTED: // = 24,
return "cipher suite rejected";
case WIFI_REASON_BEACON_TIMEOUT: // = 200,
return "beacon timeout";
case WIFI_REASON_NO_AP_FOUND: // = 201,
return "no AP found";
case WIFI_REASON_AUTH_FAIL: // = 202,
return "auth fail";
case WIFI_REASON_ASSOC_FAIL: // = 203,
return "assoc fail";
case WIFI_REASON_HANDSHAKE_TIMEOUT: // = 204,
return "handshake timeout";
default:
return "unknown";
}
return "";
}
} // namespace emsesp } // namespace emsesp

View File

@@ -33,6 +33,8 @@ class WebStatusService {
void webStatusService(AsyncWebServerRequest * request); void webStatusService(AsyncWebServerRequest * request);
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info); void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info);
void mDNS_start() const; void mDNS_start() const;
const char * disconnectReason(uint8_t code);
}; };
} // namespace emsesp } // namespace emsesp