add install date to firmware versions

This commit is contained in:
proddy
2025-12-24 11:18:22 +01:00
parent 1a3f7fbbee
commit 0557def0b6
6 changed files with 192 additions and 72 deletions

View File

@@ -86,6 +86,9 @@ const VersionInfoDialog = memo(
showVersionInfo, showVersionInfo,
latestVersion, latestVersion,
latestDevVersion, latestDevVersion,
previousVersion,
partition,
size,
locale, locale,
LL, LL,
onClose onClose
@@ -93,6 +96,9 @@ const VersionInfoDialog = memo(
showVersionInfo: number; showVersionInfo: number;
latestVersion?: VersionInfo; latestVersion?: VersionInfo;
latestDevVersion?: VersionInfo; latestDevVersion?: VersionInfo;
previousVersion?: VersionInfo | undefined;
partition: string;
size: number;
locale: string; locale: string;
LL: TranslationFunctions; LL: TranslationFunctions;
onClose: () => void; onClose: () => void;
@@ -100,8 +106,19 @@ const VersionInfoDialog = memo(
if (showVersionInfo === 0) return null; if (showVersionInfo === 0) return null;
const isStable = showVersionInfo === 1; const isStable = showVersionInfo === 1;
const version = isStable ? latestVersion : latestDevVersion; const isDev = showVersionInfo === 2;
const relNotesUrl = isStable ? STABLE_RELNOTES_URL : DEV_RELNOTES_URL; const isPrevious = showVersionInfo === 3;
const version = isStable
? latestVersion
: isDev
? latestDevVersion
: previousVersion;
const relNotesUrl = isStable
? STABLE_RELNOTES_URL
: isDev
? DEV_RELNOTES_URL
: '';
return ( return (
<Dialog sx={dialogStyle} open={showVersionInfo !== 0} onClose={onClose}> <Dialog sx={dialogStyle} open={showVersionInfo !== 0} onClose={onClose}>
@@ -119,13 +136,17 @@ const VersionInfoDialog = memo(
pr: 1, pr: 1,
py: 0.5, py: 0.5,
fontSize: 13, fontSize: 13,
width: 90 width: 140
}} }}
> >
{LL.TYPE(0)} {isPrevious ? LL.TYPE(0) : LL.RELEASE_TYPE()}
</TableCell> </TableCell>
<TableCell sx={{ borderBottom: 'none', py: 0.5, fontSize: 13 }}> <TableCell sx={{ borderBottom: 'none', py: 0.5, fontSize: 13 }}>
{isStable ? LL.STABLE() : LL.DEVELOPMENT()} {isStable
? LL.STABLE()
: isDev
? LL.DEVELOPMENT()
: 'Partition ' + LL.VERSION()}
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow sx={{ height: 24, borderBottom: 'none' }}> <TableRow sx={{ height: 24, borderBottom: 'none' }}>
@@ -143,9 +164,53 @@ const VersionInfoDialog = memo(
{LL.VERSION()} {LL.VERSION()}
</TableCell> </TableCell>
<TableCell sx={{ borderBottom: 'none', py: 0.5, fontSize: 13 }}> <TableCell sx={{ borderBottom: 'none', py: 0.5, fontSize: 13 }}>
{version?.name} {isPrevious
? typeof version === 'string'
? version
: version?.name
: version?.name}
</TableCell> </TableCell>
</TableRow> </TableRow>
{isPrevious && (
<TableRow sx={{ height: 24, borderBottom: 'none' }}>
<TableCell
component="th"
scope="row"
sx={{
color: 'lightblue',
borderBottom: 'none',
pr: 1,
py: 0.5,
fontSize: 13
}}
>
Partition
</TableCell>
<TableCell sx={{ borderBottom: 'none', py: 0.5, fontSize: 13 }}>
{partition}
</TableCell>
</TableRow>
)}
{isPrevious && (
<TableRow sx={{ height: 24, borderBottom: 'none' }}>
<TableCell
component="th"
scope="row"
sx={{
color: 'lightblue',
borderBottom: 'none',
pr: 1,
py: 0.5,
fontSize: 13
}}
>
Size
</TableCell>
<TableCell sx={{ borderBottom: 'none', py: 0.5, fontSize: 13 }}>
{size} KB
</TableCell>
</TableRow>
)}
{version?.published_at && ( {version?.published_at && (
<TableRow sx={{ height: 24, borderBottom: 'none' }}> <TableRow sx={{ height: 24, borderBottom: 'none' }}>
<TableCell <TableCell
@@ -159,7 +224,7 @@ const VersionInfoDialog = memo(
fontSize: 13 fontSize: 13
}} }}
> >
Build Date {isPrevious ? 'Install Date' : 'Build Date'}
</TableCell> </TableCell>
<TableCell sx={{ borderBottom: 'none', py: 0.5, fontSize: 13 }}> <TableCell sx={{ borderBottom: 'none', py: 0.5, fontSize: 13 }}>
{prettyDateTime(locale, new Date(version.published_at))} {prettyDateTime(locale, new Date(version.published_at))}
@@ -170,15 +235,17 @@ const VersionInfoDialog = memo(
</Table> </Table>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button {!isPrevious && (
variant="outlined" <Button
component="a" variant="outlined"
href={relNotesUrl} component="a"
target="_blank" href={relNotesUrl}
color="primary" target="_blank"
> color="primary"
Changelog >
</Button> Changelog
</Button>
)}
<Button variant="outlined" onClick={onClose} color="secondary"> <Button variant="outlined" onClick={onClose} color="secondary">
{LL.CLOSE()} {LL.CLOSE()}
</Button> </Button>
@@ -328,7 +395,9 @@ const Version = () => {
const [restarting, setRestarting] = useState<boolean>(false); const [restarting, setRestarting] = useState<boolean>(false);
const [openInstallDialog, setOpenInstallDialog] = useState<boolean>(false); const [openInstallDialog, setOpenInstallDialog] = useState<boolean>(false);
const [previousVersion, setPreviousVersion] = useState<string>(''); const [previousVersion, setPreviousVersion] = useState<VersionInfo | undefined>(
undefined
);
const [previousPartition, setPreviousPartition] = useState<string>(''); const [previousPartition, setPreviousPartition] = useState<string>('');
const [openInstallPreviousDialog, setOpenInstallPreviousDialog] = const [openInstallPreviousDialog, setOpenInstallPreviousDialog] =
useState<boolean>(false); useState<boolean>(false);
@@ -340,7 +409,8 @@ const Version = () => {
useState<boolean>(false); useState<boolean>(false);
const [internetLive, setInternetLive] = useState<boolean>(false); const [internetLive, setInternetLive] = useState<boolean>(false);
const [downloadOnly, setDownloadOnly] = useState<boolean>(false); const [downloadOnly, setDownloadOnly] = useState<boolean>(false);
const [showVersionInfo, setShowVersionInfo] = useState<number>(0); const [showVersionInfo, setShowVersionInfo] = useState<number>(0); // 1 = stable, 2 = dev, 3 = previous
const [firmwareSize, setFirmwareSize] = useState<number>(0);
const { send: sendCheckUpgrade } = useRequest( const { send: sendCheckUpgrade } = useRequest(
(versions: string) => callAction({ action: 'checkUpgrade', param: versions }), (versions: string) => callAction({ action: 'checkUpgrade', param: versions }),
@@ -389,6 +459,16 @@ const Version = () => {
[data?.emsesp_version] [data?.emsesp_version]
); );
const setPreviousVersionInfo = useCallback(
(version: string, partition: string, size: number, install_date: string) => {
setShowVersionInfo(3);
setPreviousVersion({ name: version, published_at: install_date });
setPreviousPartition(partition);
setFirmwareSize(size);
},
[]
);
const doRestart = useCallback(async () => { const doRestart = useCallback(async () => {
await sendAPI({ device: 'system', cmd: 'restart', id: 0 }).catch( await sendAPI({ device: 'system', cmd: 'restart', id: 0 }).catch(
(error: Error) => { (error: Error) => {
@@ -418,11 +498,14 @@ const Version = () => {
[sendSetPartition] [sendSetPartition]
); );
const showPreviousDialog = useCallback((version: string, partition: string) => { const showPreviousDialog = useCallback(
setOpenInstallPreviousDialog(true); (version: string, partition: string, install_date: string) => {
setPreviousVersion(version); setOpenInstallPreviousDialog(true);
setPreviousPartition(partition); setPreviousVersion({ name: version, published_at: install_date });
}, []); setPreviousPartition(partition);
},
[]
);
const showFirmwareDialog = useCallback((useDevVersion: boolean) => { const showFirmwareDialog = useCallback((useDevVersion: boolean) => {
setFetchDevVersion(useDevVersion); setFetchDevVersion(useDevVersion);
@@ -439,6 +522,8 @@ const Version = () => {
const handleVersionInfoClose = useCallback(() => { const handleVersionInfoClose = useCallback(() => {
setShowVersionInfo(0); setShowVersionInfo(0);
setPreviousVersion(undefined);
setPreviousPartition('');
}, []); }, []);
// check upgrades - only once when both versions are available // check upgrades - only once when both versions are available
@@ -485,7 +570,7 @@ const Version = () => {
{LL.LATEST_VERSION(usingDevVersion ? LL.DEVELOPMENT() : LL.STABLE())} {LL.LATEST_VERSION(usingDevVersion ? LL.DEVELOPMENT() : LL.STABLE())}
</span> </span>
<Button <Button
sx={{ ml: 2 }} sx={{ ml: 1 }}
variant="outlined" variant="outlined"
size="small" size="small"
onClick={() => showFirmwareDialog(showingDev)} onClick={() => showFirmwareDialog(showingDev)}
@@ -500,7 +585,7 @@ const Version = () => {
return ( return (
<Button <Button
sx={{ ml: 2 }} sx={{ ml: 1 }}
variant="outlined" variant="outlined"
color={choice === LL.UPDATE_AVAILABLE() ? 'success' : 'warning'} color={choice === LL.UPDATE_AVAILABLE() ? 'success' : 'warning'}
size="small" size="small"
@@ -528,14 +613,14 @@ const Version = () => {
return ( return (
<> <>
<Box p={2} border="1px solid grey" borderRadius={2}> <Box p={2} border="1px solid grey" borderRadius={2}>
<Typography mb={2} variant="h6" color="primary"> <Typography mb={1} variant="h6" color="primary">
{LL.THIS_VERSION()} {LL.THIS_VERSION()}
</Typography> </Typography>
<Grid <Grid
container container
direction="row" direction="row"
rowSpacing={1} // rowSpacing={0}
sx={{ sx={{
justifyContent: 'flex-start', justifyContent: 'flex-start',
alignItems: 'baseline' alignItems: 'baseline'
@@ -552,6 +637,12 @@ const Version = () => {
&nbsp; &#40;{data.build_flags}&#41; &nbsp; &#40;{data.build_flags}&#41;
</Typography> </Typography>
)} )}
<IconButton
onClick={() => setShowVersionInfo(isDev ? 2 : 1)}
aria-label={LL.FIRMWARE_VERSION_INFO()}
>
<InfoOutlinedIcon color="primary" sx={{ fontSize: 18 }} />
</IconButton>
</Typography> </Typography>
</Grid> </Grid>
@@ -584,20 +675,11 @@ const Version = () => {
</Typography> </Typography>
</Typography> </Typography>
</Grid> </Grid>
<Grid size={{ xs: 4, md: 2 }}>
<Typography color="secondary">{LL.RELEASE_TYPE()}</Typography>
</Grid>
{isDev ? (
<Typography>{LL.DEVELOPMENT()}</Typography>
) : (
<Typography>{LL.STABLE()}</Typography>
)}
</Grid> </Grid>
{internetLive ? ( {internetLive ? (
<> <>
<Typography mt={2} mb={2} variant="h6" color="primary"> <Typography mt={4} mb={1} variant="h6" color="primary">
{LL.AVAILABLE_VERSION()} {LL.AVAILABLE_VERSION()}
</Typography> </Typography>
@@ -620,17 +702,32 @@ const Version = () => {
<Grid size={{ xs: 8, md: 10 }}> <Grid size={{ xs: 8, md: 10 }}>
{data.partitions.map((partition) => ( {data.partitions.map((partition) => (
<Typography key={partition.partition} mb={1}> <Typography key={partition.partition} mb={1}>
v{partition.version} ({partition.partition}:{' '} {partition.version}
{partition.size} <IconButton
{' KB'}) onClick={() =>
setPreviousVersionInfo(
partition.version,
partition.partition ?? '',
partition.size ?? '',
partition.install_date ?? ''
)
}
aria-label={LL.FIRMWARE_VERSION_INFO()}
>
<InfoOutlinedIcon
color="primary"
sx={{ fontSize: 18 }}
/>
</IconButton>
<Button <Button
sx={{ ml: 2 }} sx={{ ml: 0 }}
variant="outlined" variant="outlined"
size="small" size="small"
onClick={() => onClick={() =>
showPreviousDialog( showPreviousDialog(
partition.version, partition.version,
partition.partition partition.partition,
partition.install_date ?? ''
) )
} }
> >
@@ -686,7 +783,10 @@ const Version = () => {
showVersionInfo={showVersionInfo} showVersionInfo={showVersionInfo}
latestVersion={latestVersion} latestVersion={latestVersion}
latestDevVersion={latestDevVersion} latestDevVersion={latestDevVersion}
previousVersion={previousVersion}
locale={locale} locale={locale}
partition={previousPartition}
size={firmwareSize}
LL={LL} LL={LL}
onClose={handleVersionInfoClose} onClose={handleVersionInfoClose}
/> />
@@ -703,7 +803,7 @@ const Version = () => {
/> />
<InstallPreviousDialog <InstallPreviousDialog
openInstallPreviousDialog={openInstallPreviousDialog} openInstallPreviousDialog={openInstallPreviousDialog}
version={previousVersion} version={previousVersion?.name || ''}
partition={previousPartition} partition={previousPartition}
LL={LL} LL={LL}
onClose={closeInstallPreviousDialog} onClose={closeInstallPreviousDialog}

View File

@@ -56,6 +56,7 @@ export interface SystemStatus {
partition: string; partition: string;
version: string; version: string;
size: number; size: number;
install_date?: string;
}[]; }[];
status: number; // System Status Codes which matches SYSTEM_STATUS in System.h status: number; // System Status Codes which matches SYSTEM_STATUS in System.h
developer_mode: boolean; developer_mode: boolean;

View File

@@ -111,16 +111,18 @@ let system_status = {
{ {
partition: 'app1', partition: 'app1',
version: '3.7.3-dev.41', version: '3.7.3-dev.41',
install_date: '2025-03-01T13:29:13.999Z',
size: 4672 size: 4672
}, },
{ {
partition: 'factory', partition: 'factory',
version: '3.7.3-dev.39', version: '3.7.3-dev.39',
install_date: '2025-03-01T13:29:13.999Z',
size: 4672 size: 4672
} }
], ],
// partitions: [], // partitions: [],
developer_mode: false, developer_mode: true,
model: '', model: '',
// model: 'BBQKees Electronics EMS Gateway E32 V2 (E32 V2.0 P3/2024011)', // model: 'BBQKees Electronics EMS Gateway E32 V2 (E32 V2.0 P3/2024011)',
// status: 0, // status: 0,
@@ -291,10 +293,10 @@ function updateMask(entity: any, de: any, dd: any) {
const old_custom_name = dd.nodes[dd_objIndex].cn; const old_custom_name = dd.nodes[dd_objIndex].cn;
console.log( console.log(
'comparing names, old (' + 'comparing names, old (' +
old_custom_name + old_custom_name +
') with new (' + ') with new (' +
new_custom_name + new_custom_name +
')' ')'
); );
if (old_custom_name !== new_custom_name) { if (old_custom_name !== new_custom_name) {
changed = true; changed = true;
@@ -390,15 +392,15 @@ function check_upgrade(version: string) {
console.log( console.log(
'Upgrade this version (' + 'Upgrade this version (' +
THIS_VERSION + THIS_VERSION +
') to dev (' + ') to dev (' +
dev_version + dev_version +
') is ' + ') is ' +
(DEV_VERSION_IS_UPGRADEABLE ? 'YES' : 'NO') + (DEV_VERSION_IS_UPGRADEABLE ? 'YES' : 'NO') +
' and to stable (' + ' and to stable (' +
stable_version + stable_version +
') is ' + ') is ' +
(STABLE_VERSION_IS_UPGRADEABLE ? 'YES' : 'NO') (STABLE_VERSION_IS_UPGRADEABLE ? 'YES' : 'NO')
); );
data = { data = {
emsesp_version: THIS_VERSION, emsesp_version: THIS_VERSION,

View File

@@ -301,10 +301,11 @@ void System::get_partition_info() {
#ifdef EMSESP_STANDALONE #ifdef EMSESP_STANDALONE
// dummy data for standalone mode // dummy data for standalone mode
partition_info_["app0"] = {EMSESP_APP_VERSION, 0}; // version, size, install_date
partition_info_["app1"] = {"", 0}; partition_info_["app0"] = {EMSESP_APP_VERSION, 0, ""};
partition_info_["factory"] = {"", 0}; partition_info_["app1"] = {"", 0, ""};
partition_info_["boot"] = {"", 0}; partition_info_["factory"] = {"", 0, ""};
partition_info_["boot"] = {"", 0, ""};
#else #else
auto current_partition = (const char *)esp_ota_get_running_partition()->label; auto current_partition = (const char *)esp_ota_get_running_partition()->label;
@@ -337,14 +338,27 @@ void System::get_partition_info() {
// get the version from the NVS store, and add to map // get the version from the NVS store, and add to map
if (is_valid) { if (is_valid) {
PartitionInfo info; PartitionInfo p_info;
info.size = part->size / 1024; // in KB p_info.size = part->size / 1024; // set size in KB
// get version from NVS, if not found, use empty string
if (EMSESP::nvs_.isKey(part->label)) { if (EMSESP::nvs_.isKey(part->label)) {
info.version = EMSESP::nvs_.getString(part->label).c_str(); p_info.version = EMSESP::nvs_.getString(part->label).c_str();
} else { } else {
info.version = ""; // no version, empty string p_info.version = "";
} }
partition_info_[part->label] = info;
// get install date from NTP if active and add
if (ntp_connected_) {
char time_string[25];
time_t now = time(nullptr) - uuid::get_uptime_sec();
strftime(time_string, sizeof(time_string), "%FT%T%z", localtime(&now));
p_info.install_date = time_string;
} else {
p_info.install_date = "";
}
partition_info_[part->label] = p_info;
} }
it = esp_partition_next(it); it = esp_partition_next(it);

View File

@@ -77,6 +77,7 @@ enum FUSE_VALUE : uint8_t { ALL = 0, MFG = 1, MODEL = 2, BOARD = 3, REV = 4, BAT
struct PartitionInfo { struct PartitionInfo {
std::string version; std::string version;
size_t size; size_t size;
std::string install_date; // optional, only available if NTP is connected
}; };
class System { class System {
@@ -370,9 +371,10 @@ class System {
static void remove_gpio(uint8_t pin, bool also_system = false); // remove a gpio from both valid (optional) and used lists static void remove_gpio(uint8_t pin, bool also_system = false); // remove a gpio from both valid (optional) and used lists
// Partition info map: partition name -> {version, size} // Partition info map: partition name -> {version, size, install_date}
std::map<std::string, PartitionInfo, std::less<>, AllocatorPSRAM<std::pair<const std::string, PartitionInfo>>> partition_info_; std::map<std::string, PartitionInfo, std::less<>, AllocatorPSRAM<std::pair<const std::string, PartitionInfo>>> partition_info_;
static bool set_partition(const char * partitionname);
static bool set_partition(const char * partitionname);
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;

View File

@@ -154,10 +154,11 @@ void WebStatusService::systemStatus(AsyncWebServerRequest * request) {
if (partition.first == (const char *)esp_ota_get_running_partition()->label || partition.second.version.empty() || partition.second.size == 0) { if (partition.first == (const char *)esp_ota_get_running_partition()->label || partition.second.version.empty() || partition.second.size == 0) {
continue; continue;
} }
JsonObject part = partitions.add<JsonObject>(); JsonObject part = partitions.add<JsonObject>();
part["partition"] = partition.first; part["partition"] = partition.first;
part["version"] = partition.second.version; part["version"] = partition.second.version;
part["size"] = partition.second.size; part["size"] = partition.second.size;
part["install_date"] = partition.second.install_date;
} }
root["developer_mode"] = EMSESP::system_.developer_mode(); root["developer_mode"] = EMSESP::system_.developer_mode();