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 program memory info
- Add mqtt queue and connection infos
- Add min/max setting to customizations
- 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 hybrid heatpump [#500](https://github.com/emsesp/EMS-ESP32/issues/500)
- 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
@@ -26,6 +28,7 @@
- RF room temperature sensor are shown as thermostat
- render mqtt float json values with trailing zero
- removed flash strings
- reload page after restart button is pressed
## **BREAKING CHANGES:**

View File

@@ -1,5 +1,5 @@
{
"adapter": "react",
"baseLocale": "pl",
"$schema": "https://unpkg.com/typesafe-i18n@5.14.0/schema/typesafe-i18n.json"
"baseLocale": "en",
"$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,
accept: {
'application/octet-stream': ['.bin'],
'application/json': ['.json']
'application/json': ['.json'],
'text/plain': ['.md5']
},
disabled: uploading,
multiple: false

View File

@@ -16,6 +16,7 @@ const useFileUpload = ({ upload }: MediaUploadOptions) => {
const { enqueueSnackbar } = useSnackbar();
const [uploading, setUploading] = useState<boolean>(false);
const [md5, setMd5] = useState<string>('');
const [uploadProgress, setUploadProgress] = useState<AxiosProgressEvent>();
const [uploadCancelToken, setUploadCancelToken] = useState<CancelTokenSource>();
@@ -23,6 +24,7 @@ const useFileUpload = ({ upload }: MediaUploadOptions) => {
setUploading(false);
setUploadProgress(undefined);
setUploadCancelToken(undefined);
setMd5('');
};
const cancelUpload = useCallback(() => {
@@ -41,12 +43,17 @@ const useFileUpload = ({ upload }: MediaUploadOptions) => {
const cancelToken = axios.CancelToken.source();
setUploadCancelToken(cancelToken);
setUploading(true);
await upload(images[0], {
const response = await upload(images[0], {
onUploadProgress: setUploadProgress,
cancelToken: cancelToken.token
});
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) {
if (axios.isCancel(error)) {
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;

View File

@@ -44,7 +44,7 @@ const dnsServers = ({ dns_ip_1, dns_ip_2 }: NetworkStatus) => {
if (!dns_ip_1) {
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) => {

View File

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

View File

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

View File

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

View File

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

View File

@@ -19,8 +19,8 @@ const en: BaseTranslation = {
UPLOAD_SUCCESSFUL: 'Upload successful',
DOWNLOAD_SUCCESSFUL: 'Download successful',
INVALID_LOGIN: 'Invalid login details',
NETWORK: 'Network',
SECURITY: 'Security',
NETWORK: '{{Network}}',
SECURITY: '{{Security}}',
ONOFF_CAP: 'ON/OFF',
ONOFF: 'on/off',
TYPE: 'Type',
@@ -31,7 +31,7 @@ const en: BaseTranslation = {
DEVICE_DETAILS: 'Device Details',
BRAND: 'Brand',
ENTITY_NAME: 'Entity Name',
VALUE: 'Value',
VALUE: '{{Value}}',
SHOW_FAV: 'only show favorites',
DEVICE_SENSOR_DATA: 'Device and Sensor Data',
DEVICES_SENSORS: 'Devices & Sensors',
@@ -39,7 +39,7 @@ const en: BaseTranslation = {
RUN_COMMAND: 'Call Command',
CHANGE_VALUE: 'Change Value',
CANCEL: 'Cancel',
RESET: 'Reset',
RESET: '{{Reset}}',
SEND: 'Send',
SAVE: 'Save',
REMOVE: 'Remove',
@@ -66,7 +66,7 @@ const en: BaseTranslation = {
EMS_BUS_WARNING:
'EMS bus disconnected. If this warning still persists after a few seconds please check settings and board profile',
EMS_BUS_SCANNING: 'Scanning for EMS devices...',
CONNECTED: 'Connected',
CONNECTED: '{{Connected}}',
TX_ISSUES: 'Tx issues - try a different Tx Mode',
DISCONNECTED: 'Disconnected',
EMS_SCAN: 'Are you sure you want to initiate a full device scan of the EMS bus?',
@@ -171,7 +171,7 @@ const en: BaseTranslation = {
ABORTED: 'aborted',
FAILED: 'failed',
SUCCESSFUL: 'successful',
SYSTEM: 'System',
SYSTEM: '{{System}}',
LOG_OF: '{0} Log',
STATUS_OF: '{0} Status',
UPLOAD_DOWNLOAD: 'Upload/Download',
@@ -182,7 +182,7 @@ const en: BaseTranslation = {
FACTORY_RESET: 'Factory Reset',
SYSTEM_FACTORY_TEXT: 'Device has been factory reset and will now restart',
SYSTEM_FACTORY_TEXT_DIALOG: 'Are you sure you want to reset the device to its factory defaults?',
VERSION_CHECK: 'Version Check',
VERSION_CHECK: '{{Version Check}}',
THE_LATEST: 'The latest',
OFFICIAL: 'official',
DEVELOPMENT: 'development',
@@ -203,20 +203,20 @@ const en: BaseTranslation = {
DOWNLOAD_CUSTOMIZATION_TEXT: 'Download the entity customizations',
DOWNLOAD_SETTINGS_TEXT:
'Download the application settings. Be careful when sharing your settings as this file contains passwords and other sensitive system information',
UPLOAD_TEXT: 'Upload a new firmware (.bin) file, settings or customizations (.json) file below',
UPLOAD_TEXT: 'Upload a new firmware (.bin) file, settings or customizations (.json) file below, for optional validation upload (.md5) first',
UPLOADING: 'Uploading',
UPLOAD_DROP_TEXT: 'Drop file or click here',
ERROR: 'Unexpected Error, please try again',
TIME_SET: 'Time set',
MANAGE_USERS: 'Manage Users',
IS_ADMIN: 'is Admin',
IS_ADMIN: '{{is Admin}}',
USER_WARNING: 'You must have at least one admin user configured',
ADD: 'Add',
ADD: '{{Add}}',
ACCESS_TOKEN_FOR: 'Access Token for',
ACCESS_TOKEN_TEXT:
'The token below is used with REST API calls that require authorization. It can be passed either as a Bearer token in the Authorization header or in the access_token URL query parameter.',
GENERATING_TOKEN: 'Generating token',
USER: 'User',
USER: '{{User}}',
MODIFY: 'Modify',
SU_TEXT:
'The su (super user) password is used to sign authentication tokens and also enable admin privileges within the Console.',
@@ -246,14 +246,14 @@ const en: BaseTranslation = {
INACTIVE: 'Inactive',
ACTIVE: 'Active',
UNKNOWN: 'Unknown',
SET_TIME: 'Set Time',
SET_TIME: '{{Set Time}}',
SET_TIME_TEXT: 'Enter local date and time below to set the time',
LOCAL_TIME: 'Local Time',
UTC_TIME: 'UTC Time',
ENABLE_NTP: 'Enable NTP',
NTP_SERVER: 'NTP Server',
TIME_ZONE: 'Time Zone',
ACCESS_POINT: 'Access Point',
ACCESS_POINT: '{{Access Point}}',
AP_PROVIDE: 'Enable Access Point',
AP_PROVIDE_TEXT_1: 'always',
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'),
}
const updateDictionary = (locale: Locales, dictionary: Partial<Translations>) =>
const updateDictionary = (locale: Locales, dictionary: Partial<Translations>): Translations =>
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
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 type { LocaleDetector } from 'typesafe-i18n/detectors'
import type { LocaleTranslationFunctions, TranslateByString } from 'typesafe-i18n'
import { detectLocale as detectLocaleFn } from 'typesafe-i18n/detectors'
import type { Formatters, Locales, Translations, TranslationFunctions } from './i18n-types'
export const baseLocale: Locales = 'pl'
export const baseLocale: Locales = 'en'
export const locales: Locales[] = [
'de',
@@ -17,21 +18,22 @@ export const locales: Locales[] = [
'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>(
locale,
loadedLocales[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"
select
>
<MenuItem value={0x0a}>Terminal (0x0A)</MenuItem>
<MenuItem value={0x0b}>Service Key (0x0B)</MenuItem>
<MenuItem value={0x0d}>Modem (0x0D)</MenuItem>
<MenuItem value={0x0a}>Terminal (0x0A)</MenuItem>
<MenuItem value={0x0e}>Converter (0x0E)</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>
</Grid>
</Grid>
@@ -344,9 +349,7 @@ const SettingsApplication: FC = () => {
<MenuItem value="nl">Nederlands (NL)</MenuItem>
<MenuItem value="se">Svenska (SE)</MenuItem>
<MenuItem value="pl">Polski (PL)</MenuItem>
<MenuItem disabled value="no">
Norsk (NO)
</MenuItem>
<MenuItem value="no">Norsk (NO)</MenuItem>
</ValidatedTextField>
</Box>
{data.led_gpio !== 0 && (

View File

@@ -66,7 +66,7 @@ const SettingsCustomization: FC = () => {
const entities_theme = useTheme({
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: `
font-size: 14px;
@@ -75,6 +75,12 @@ const SettingsCustomization: FC = () => {
}
`,
BaseCell: `
&:nth-of-type(3) {
text-align: right;
}
&:nth-of-type(4) {
text-align: right;
}
&:last-of-type {
text-align: right;
}
@@ -122,6 +128,12 @@ const SettingsCustomization: FC = () => {
&:nth-of-type(2) {
padding: 8px;
}
&:nth-of-type(3) {
padding-right: 4px;
}
&:nth-of-type(4) {
padding-right: 4px;
}
&:last-of-type {
padding-right: 8px;
}
@@ -446,6 +458,8 @@ const SettingsCustomization: FC = () => {
{LL.NAME()}
</Button>
</HeaderCell>
<HeaderCell stiff>min</HeaderCell>
<HeaderCell stiff>max</HeaderCell>
<HeaderCell resize>{LL.VALUE(0)}</HeaderCell>
</HeaderRow>
</Header>
@@ -497,6 +511,8 @@ const SettingsCustomization: FC = () => {
</ToggleButtonGroup>
</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>
</Row>
))}
@@ -626,7 +642,7 @@ const SettingsCustomization: FC = () => {
onChange={updateValue(setDeviceEntity)}
/>
</Grid>
{typeof de.v === 'number' && de.w && (
{typeof de.v === 'number' && de.w && !(de.m & DeviceEntityMask.DV_READONLY) && (
<>
<Grid item>
<TextField

View File

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

View File

@@ -80,11 +80,11 @@ AsyncMqttClient * MqttSettingsService::getMqttClient() {
}
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) {
// 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;
_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_WIFI_STA_GOT_IP6:
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();
}
break;
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
case ARDUINO_EVENT_ETH_DISCONNECTED:
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();
// onConfigUpdated();
}
@@ -127,7 +127,7 @@ void MqttSettingsService::configureMqtt() {
_mqttClient.disconnect();
// only connect if WiFi is connected and MQTT is enabled
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);
if (_state.username.length() > 0) {
_mqttClient.setCredentials(retainCstr(_state.username.c_str(), &_retainedUsername),
@@ -141,7 +141,7 @@ void MqttSettingsService::configureMqtt() {
_mqttClient.setMaxTopicLength(_state.maxTopicLength);
_mqttClient.connect();
// } 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) {
case ARDUINO_EVENT_WIFI_STA_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;
configureNTP();
break;
case ARDUINO_EVENT_WIFI_STA_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;
configureNTP();
break;
@@ -49,7 +49,7 @@ void NTPSettingsService::WiFiEvent(WiFiEvent_t event) {
void NTPSettingsService::configureNTP() {
emsesp::EMSESP::system_.ntp_connected(false);
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_time_sync_notification_cb(ntp_received);
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) {
// 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);
}

View File

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

View File

@@ -3,6 +3,7 @@
using namespace std::placeholders; // for `_1` etc
static bool is_firmware = false;
static char md5[33] = "\0";
UploadFileService::UploadFileService(AsyncWebServer * server, SecurityManager * securityManager)
: _securityManager(securityManager) {
@@ -33,45 +34,53 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri
Serial.println();
#endif
is_firmware = false;
if ((extension == "bin") && (fsize > 1500000)) {
is_firmware = true;
} 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 {
is_firmware = false;
md5[0] = '\0';
return; // not support file type
}
if (is_firmware) {
// 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
bool isC3 = (fname.find("C3") != std::string::npos);
bool isS2 = (fname.find("S2") != std::string::npos);
if (isC3 || isS2 || (len > 12 && (data[0] != 0xE9 || data[12] != 0))) {
if (len > 12 && (data[0] != 0xE9 || data[12] != 0)) {
handleError(request, 503); // service unavailable
return;
}
#elif CONFIG_IDF_TARGET_ESP32S2
bool isS2 = (fname.find("S2") != std::string::npos);
if (!isS2 || (len > 12 && (data[0] != 0xE9 || data[12] != 2))) {
if (len > 12 && (data[0] != 0xE9 || data[12] != 2)) {
handleError(request, 503); // service unavailable
return;
}
#elif CONFIG_IDF_TARGET_ESP32C3
bool isC3 = (fname.find("C3") != std::string::npos);
if (!isC3 || (len > 12 && (data[0] != 0xE9 || data[12] != 5))) {
if (len > 12 && (data[0] != 0xE9 || data[12] != 5)) {
handleError(request, 503); // service unavailable
return;
}
#endif
// it's firmware - initialize the ArduinoOTA updater
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
} else {
#if defined(EMSESP_USE_SERIAL)
Update.printError(Serial);
#endif
handleError(request, 507); // failed to begin, send an error response Insufficient Storage
return;
}
} else {
// 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) {
request->_tempFile.write(data, len); // stream the incoming chunk to the opened file
}
} else {
// if we haven't delt with an error, continue with the firmware update
if (!request->_tempObject) {
@@ -123,6 +131,11 @@ void UploadFileService::uploadComplete(AsyncWebServerRequest * request) {
request->send(response);
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
}

View File

@@ -1154,7 +1154,7 @@ rest_server.post(EMSESP_BOARDPROFILE_ENDPOINT, (req, res) => {
} else if (board_profile == 'C3MINI') {
// Lolin C3 mini
data.led_gpio = 7
data.dallas_gpio = 2
data.dallas_gpio = 1
data.rx_gpio = 4
data.tx_gpio = 5
data.pbutton_gpio = 9
@@ -1162,6 +1162,17 @@ rest_server.post(EMSESP_BOARDPROFILE_ENDPOINT, (req, res) => {
data.eth_power = 0
data.eth_phy_addr = 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))

View File

@@ -2,6 +2,7 @@ import shutil
import re
import os
Import("env")
import hashlib
OUTPUT_DIR = "build{}".format(os.path.sep)
@@ -18,7 +19,6 @@ def bin_copy(source, target, env):
bag[var] = m.group(1)
app_version = bag.get('app_version')
platform = "ESP32"
chip_target = env.get('PIOENV').upper()
@@ -33,14 +33,13 @@ def bin_copy(source, target, env):
# alternatively take platform from the pio target
# platform = str(target[0]).split(os.path.sep)[2]
chip_target = env.get('PIOENV').upper()
print("app version: " + app_version)
print("platform: " + platform)
print("chip_target: " + chip_target)
# 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
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
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
for f in [bin_file]:
if os.path.isfile(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)
# copy firmware.bin to firmware/<variant>.bin
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}.md5", [bin_copy])

View File

@@ -151,11 +151,11 @@ void EMSESPShell::add_console_commands() {
},
"local");
} 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> {
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,

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["w"] = dv.has_cmd; // if writable
if (dv.has_cmd) {
// set the custom min and max values if there are any
if (dv.has_cmd && (obj["v"].is<float>() || obj["v"].is<int>())) {
// set the min and max values if there are any and if entity has a value
int16_t dv_set_min;
uint16_t dv_set_max;
if (dv.get_custom_min(dv_set_min)) {
obj["mi"] = fahrenheit ? (int)(dv_set_min * 1.8 + 32 * (fahrenheit - 1)) : dv_set_min;
}
if (dv.get_custom_max(dv_set_max)) {
obj["ma"] = fahrenheit ? (int)(dv_set_max * 1.8 + 32 * (fahrenheit - 1)) : dv_set_max;
if (dv.get_min_max(dv_set_min, dv_set_max)) {
char s[10];
obj["mi"] = Helpers::render_value(s, dv_set_min, 0, fahrenheit);
obj["ma"] = Helpers::render_value(s, dv_set_max, 0, fahrenheit);
}
}
}

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) {
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;
}
if (type == DeviceValueType::SHORT) {
dv_set_min = Helpers::transformNumFloat(-EMS_VALUE_SHORT_NOTSET, numeric_operator, fahrenheit);
dv_set_max = 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 - 1, numeric_operator, fahrenheit);
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) {
dv_set_max = 100;
} 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;
}
@@ -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_max = 100;
} else {
dv_set_min = Helpers::transformNumFloat(-EMS_VALUE_INT_NOTSET, numeric_operator, fahrenheit);
dv_set_max = 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 - 1, numeric_operator, fahrenheit);
}
return true;
}
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;
}
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;
}

View File

@@ -377,7 +377,11 @@ void System::start() {
// disable bluetooth module
// periph_module_disable(PERIPH_BT_MODULE);
if (low_clock_) {
#if CONFIG_IDF_TARGET_ESP32C3
setCpuFrequencyMhz(80);
#else
setCpuFrequencyMhz(160);
#endif
}
fstotal_ = LittleFS.totalBytes() / 1024; // read only once, it takes 500 ms to read
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
#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) {
switch (event) {
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);
break;
@@ -42,7 +42,6 @@ void WebStatusService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
#ifndef EMSESP_STANDALONE
EMSESP::logger().info("WiFi connected with IP=%s, hostname=%s", WiFi.localIP().toString().c_str(), WiFi.getHostname());
#endif
// EMSESP::system_.send_heartbeat(); // send from mqtt start
EMSESP::system_.syslog_init();
mDNS_start();
break;
@@ -226,4 +225,68 @@ void WebStatusService::mDNS_start() const {
#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

View File

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