Backup
This commit is contained in:
parent
c30a3ef4e7
commit
7e451b4e3b
@ -227,9 +227,7 @@ bool HistoryStore::updateLatest(uint16_t groupAddr, TextSource source, float val
|
||||
}
|
||||
|
||||
int64_t HistoryStore::now() const {
|
||||
int64_t monoSec = esp_timer_get_time() / 1000000LL;
|
||||
if (!timeSynced_) return monoSec;
|
||||
return baseEpoch_ + (monoSec - baseMono_);
|
||||
return (int64_t)time(nullptr);
|
||||
}
|
||||
|
||||
int32_t HistoryStore::periodSeconds(ChartPeriod period) const {
|
||||
@ -294,9 +292,6 @@ bool HistoryStore::fillChartSeries(uint16_t groupAddr, TextSource source, ChartP
|
||||
bool hasData = false;
|
||||
int32_t lastValidValue = NO_POINT;
|
||||
|
||||
// Optimization: Try to find a starting value from before the window if index 0 is empty?
|
||||
// For now, simple forward-fill (step) is sufficient to connect sparse points.
|
||||
|
||||
for (size_t i = 0; i < outCount; ++i) {
|
||||
if (counts[i] > 0) {
|
||||
float avg = sums[i] / counts[i];
|
||||
@ -305,15 +300,9 @@ bool HistoryStore::fillChartSeries(uint16_t groupAddr, TextSource source, ChartP
|
||||
lastValidValue = val;
|
||||
hasData = true;
|
||||
} else {
|
||||
// No data in this bucket.
|
||||
// Use the last valid value to draw a connected line (Step Hold).
|
||||
// If we haven't seen any data yet (lastValidValue == NO_POINT),
|
||||
// we leave it as NO_POINT (gap at start).
|
||||
outValues[i] = lastValidValue;
|
||||
|
||||
// If we just filled with a valid value, count it as having data
|
||||
if (lastValidValue != NO_POINT) {
|
||||
hasData = true; // Ensures y-scaling considers this flat line
|
||||
hasData = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -324,8 +313,14 @@ bool HistoryStore::fillChartSeries(uint16_t groupAddr, TextSource source, ChartP
|
||||
bool HistoryStore::tick() {
|
||||
xSemaphoreTake(mutex_, portMAX_DELAY);
|
||||
int64_t nowSec = now();
|
||||
bool added = false;
|
||||
|
||||
// Only collect data if time is roughly synced (after 2020)
|
||||
if (nowSec < 1577836800LL) {
|
||||
xSemaphoreGive(mutex_);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool added = false;
|
||||
for (size_t i = 0; i < series_.size(); ++i) {
|
||||
HistorySeries& series = series_[i];
|
||||
if (!series.active || !series.hasLatest) continue;
|
||||
@ -345,20 +340,18 @@ bool HistoryStore::tick() {
|
||||
|
||||
if (added) {
|
||||
dirty_ = true;
|
||||
if (timeSynced_) dataEpoch_ = true;
|
||||
}
|
||||
xSemaphoreGive(mutex_);
|
||||
|
||||
// REMOVED synchronous save from here to avoid blocking UI task
|
||||
return added;
|
||||
}
|
||||
|
||||
void HistoryStore::performAutoSave() {
|
||||
xSemaphoreTake(mutex_, portMAX_DELAY);
|
||||
bool shouldSave = false;
|
||||
int64_t nowSec = now();
|
||||
int64_t monoUs = esp_timer_get_time();
|
||||
|
||||
if (dirty_ && timeSynced_ && SdCard::instance().isMounted()) {
|
||||
if (dirty_ && nowSec > 1577836800LL && SdCard::instance().isMounted()) {
|
||||
if (monoUs - lastSaveMonoUs_ >= HISTORY_SAVE_INTERVAL_US) {
|
||||
shouldSave = true;
|
||||
lastSaveMonoUs_ = monoUs;
|
||||
@ -371,120 +364,8 @@ void HistoryStore::performAutoSave() {
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryStore::updateTimeOfDay(const struct tm& value) {
|
||||
xSemaphoreTake(mutex_, portMAX_DELAY);
|
||||
hour_ = value.tm_hour;
|
||||
minute_ = value.tm_min;
|
||||
second_ = value.tm_sec;
|
||||
hasTime_ = true;
|
||||
// applyTimeSync calls internal logic, assume called inside lock or extracted
|
||||
// But applyTimeSync calls clearAll which touches series.
|
||||
// So we must be careful about locking.
|
||||
// Let's unlock before applyTimeSync and re-lock inside?
|
||||
// No, let's just make applyTimeSync assume lock or lock itself?
|
||||
// applyTimeSync modifies member vars.
|
||||
// Refactoring for simplicity: lock around the whole block
|
||||
// BUT applyTimeSync calls settimeofday which is system call (thread safe?)
|
||||
|
||||
// We will inline the logic briefly or handle it carefully.
|
||||
// For now, assume lock held is OK for short duration.
|
||||
// We need to implement applyTimeSync correctly with locking.
|
||||
xSemaphoreGive(mutex_);
|
||||
applyTimeSync();
|
||||
}
|
||||
|
||||
void HistoryStore::updateDate(const struct tm& value) {
|
||||
xSemaphoreTake(mutex_, portMAX_DELAY);
|
||||
year_ = value.tm_year;
|
||||
month_ = value.tm_mon;
|
||||
day_ = value.tm_mday;
|
||||
hasDate_ = true;
|
||||
xSemaphoreGive(mutex_);
|
||||
applyTimeSync();
|
||||
}
|
||||
|
||||
void HistoryStore::updateDateTime(const struct tm& value) {
|
||||
xSemaphoreTake(mutex_, portMAX_DELAY);
|
||||
year_ = value.tm_year;
|
||||
month_ = value.tm_mon;
|
||||
day_ = value.tm_mday;
|
||||
hour_ = value.tm_hour;
|
||||
minute_ = value.tm_min;
|
||||
second_ = value.tm_sec;
|
||||
hasDate_ = true;
|
||||
hasTime_ = true;
|
||||
xSemaphoreGive(mutex_);
|
||||
applyTimeSync();
|
||||
}
|
||||
|
||||
int64_t HistoryStore::buildEpoch() const {
|
||||
if (year_ < 1990 || month_ < 1 || month_ > 12 || day_ < 1 || day_ > 31) return -1;
|
||||
|
||||
struct tm combined = {};
|
||||
combined.tm_year = year_ - 1900;
|
||||
combined.tm_mon = month_ - 1;
|
||||
combined.tm_mday = day_;
|
||||
combined.tm_hour = hour_;
|
||||
combined.tm_min = minute_;
|
||||
combined.tm_sec = second_;
|
||||
combined.tm_isdst = -1;
|
||||
|
||||
time_t epoch = mktime(&combined);
|
||||
if (epoch < 0) return -1;
|
||||
return static_cast<int64_t>(epoch);
|
||||
}
|
||||
|
||||
bool HistoryStore::applyTimeSync() {
|
||||
xSemaphoreTake(mutex_, portMAX_DELAY);
|
||||
if (!hasDate_ || !hasTime_) {
|
||||
xSemaphoreGive(mutex_);
|
||||
return false;
|
||||
}
|
||||
|
||||
int64_t epoch = buildEpoch();
|
||||
if (epoch <= 0) {
|
||||
ESP_LOGW(TAG, "Invalid KNX time/date for sync");
|
||||
xSemaphoreGive(mutex_);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool wasSynced = timeSynced_;
|
||||
timeSynced_ = true;
|
||||
baseEpoch_ = epoch;
|
||||
baseMono_ = esp_timer_get_time() / 1000000LL;
|
||||
|
||||
// Release lock for system call (optional but safer)
|
||||
xSemaphoreGive(mutex_);
|
||||
struct timeval tv = {};
|
||||
tv.tv_sec = static_cast<time_t>(epoch);
|
||||
settimeofday(&tv, nullptr);
|
||||
xSemaphoreTake(mutex_, portMAX_DELAY);
|
||||
|
||||
if (!wasSynced) {
|
||||
bool hasData = false;
|
||||
for (size_t i = 0; i < series_.size(); ++i) {
|
||||
const HistorySeries& series = series_[i];
|
||||
if (!series.active) continue;
|
||||
if (series.fine.count > 0 || series.coarse.count > 0 || series.hasLatest) {
|
||||
hasData = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!dataEpoch_ && hasData) {
|
||||
clearAll();
|
||||
}
|
||||
dataEpoch_ = true;
|
||||
}
|
||||
|
||||
dirty_ = true;
|
||||
xSemaphoreGive(mutex_);
|
||||
|
||||
ESP_LOGI(TAG, "Time synced: %ld", static_cast<long>(epoch));
|
||||
return !wasSynced;
|
||||
}
|
||||
|
||||
void HistoryStore::clearAll() {
|
||||
// Assumes lock is held by caller
|
||||
xSemaphoreTake(mutex_, portMAX_DELAY);
|
||||
for (size_t i = 0; i < series_.size(); ++i) {
|
||||
HistorySeries& series = series_[i];
|
||||
if (!series.active) continue;
|
||||
@ -496,17 +377,14 @@ void HistoryStore::clearAll() {
|
||||
series.lastFineSampleTs = 0;
|
||||
series.lastCoarseSampleTs = 0;
|
||||
}
|
||||
xSemaphoreGive(mutex_);
|
||||
}
|
||||
|
||||
void HistoryStore::saveToSdCard() {
|
||||
if (!SdCard::instance().isMounted()) return;
|
||||
|
||||
xSemaphoreTake(mutex_, portMAX_DELAY);
|
||||
if (!timeSynced_) {
|
||||
xSemaphoreGive(mutex_);
|
||||
return;
|
||||
}
|
||||
xSemaphoreGive(mutex_); // Release to open file
|
||||
// Double check time sync before save
|
||||
if (now() < 1577836800LL) return;
|
||||
|
||||
FILE* f = fopen(HISTORY_FILE, "wb");
|
||||
if (!f) {
|
||||
@ -537,8 +415,6 @@ void HistoryStore::saveToSdCard() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Allocate temp buffer for one series to minimize lock time
|
||||
// Size: Header + Fine + Coarse
|
||||
size_t seriesDataSize = sizeof(HistoryFileSeriesHeader) +
|
||||
sizeof(HistoryPoint) * (HISTORY_FINE_CAP + HISTORY_COARSE_CAP);
|
||||
uint8_t* tempBuf = (uint8_t*)malloc(seriesDataSize);
|
||||
@ -556,7 +432,6 @@ void HistoryStore::saveToSdCard() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Copy to temp buffer
|
||||
HistoryFileSeriesHeader* sh = (HistoryFileSeriesHeader*)tempBuf;
|
||||
sh->groupAddr = series.key.addr;
|
||||
sh->source = static_cast<uint8_t>(series.key.source);
|
||||
@ -575,7 +450,6 @@ void HistoryStore::saveToSdCard() {
|
||||
|
||||
xSemaphoreGive(mutex_);
|
||||
|
||||
// Write to file (unlocked)
|
||||
if (fwrite(tempBuf, 1, seriesDataSize, f) != seriesDataSize) {
|
||||
ESP_LOGW(TAG, "Failed to write series data");
|
||||
break;
|
||||
@ -586,7 +460,6 @@ void HistoryStore::saveToSdCard() {
|
||||
|
||||
xSemaphoreTake(mutex_, portMAX_DELAY);
|
||||
dirty_ = false;
|
||||
dataEpoch_ = true;
|
||||
xSemaphoreGive(mutex_);
|
||||
|
||||
fclose(f);
|
||||
@ -648,7 +521,6 @@ void HistoryStore::loadFromSdCard() {
|
||||
}
|
||||
}
|
||||
dirty_ = false;
|
||||
dataEpoch_ = true;
|
||||
xSemaphoreGive(mutex_);
|
||||
|
||||
fclose(f);
|
||||
|
||||
@ -22,11 +22,7 @@ public:
|
||||
int32_t* outValues, size_t outCount) const;
|
||||
|
||||
int64_t now() const;
|
||||
bool isTimeSynced() const { return timeSynced_; }
|
||||
|
||||
void updateTimeOfDay(const struct tm& value);
|
||||
void updateDate(const struct tm& value);
|
||||
void updateDateTime(const struct tm& value);
|
||||
bool isTimeSynced() const { return now() > 1577836800LL; }
|
||||
|
||||
void loadFromSdCard();
|
||||
void saveToSdCard();
|
||||
@ -101,8 +97,6 @@ private:
|
||||
static bool keysEqual(const SeriesKey& a, const SeriesKey& b);
|
||||
|
||||
int32_t periodSeconds(ChartPeriod period) const;
|
||||
int64_t buildEpoch() const;
|
||||
bool applyTimeSync();
|
||||
void clearAll();
|
||||
|
||||
HistorySeries* findSeries(uint16_t groupAddr, TextSource source);
|
||||
@ -113,21 +107,8 @@ private:
|
||||
std::array<HistorySeries, HISTORY_MAX_SERIES> series_ = {};
|
||||
size_t seriesCount_ = 0;
|
||||
|
||||
bool timeSynced_ = false;
|
||||
bool hasDate_ = false;
|
||||
bool hasTime_ = false;
|
||||
int year_ = 0;
|
||||
int month_ = 0;
|
||||
int day_ = 0;
|
||||
int hour_ = 0;
|
||||
int minute_ = 0;
|
||||
int second_ = 0;
|
||||
int64_t baseEpoch_ = 0;
|
||||
int64_t baseMono_ = 0;
|
||||
|
||||
bool dirty_ = false;
|
||||
int64_t lastSaveMonoUs_ = 0;
|
||||
bool dataEpoch_ = false;
|
||||
|
||||
mutable SemaphoreHandle_t mutex_ = nullptr;
|
||||
};
|
||||
|
||||
@ -64,6 +64,9 @@ enum class TextSource : uint8_t {
|
||||
KNX_DPT_TIME = 8, // KNX Time of day (DPT 10.001)
|
||||
KNX_DPT_DATE = 9, // KNX Date (DPT 11.001)
|
||||
KNX_DPT_DATETIME = 10, // KNX DateTime (DPT 19.001)
|
||||
SYSTEM_TIME = 11, // System Time (RTC)
|
||||
SYSTEM_DATE = 12, // System Date (RTC)
|
||||
SYSTEM_DATETIME = 13, // System DateTime (RTC)
|
||||
};
|
||||
|
||||
enum class TextAlign : uint8_t {
|
||||
|
||||
@ -481,7 +481,7 @@ void WidgetManager::showModalScreenLocked(const ScreenConfig& screen) {
|
||||
for (uint8_t i = 0; i < screen.widgetCount; i++) {
|
||||
const WidgetConfig& cfg = screen.widgets[i];
|
||||
auto widget = WidgetFactory::create(cfg);
|
||||
if (widget && cfg.id < MAX_WIDGETS) {
|
||||
if (widget && cfg.id < 256) {
|
||||
widget->create(modal);
|
||||
widget->applyStyle();
|
||||
widgets_[cfg.id] = std::move(widget);
|
||||
@ -699,6 +699,8 @@ void WidgetManager::loop() {
|
||||
refreshChartWidgets();
|
||||
}
|
||||
|
||||
updateSystemTimeWidgets();
|
||||
|
||||
if (didUiNav) return;
|
||||
|
||||
if (!config_.standbyEnabled || config_.standbyMinutes == 0) return;
|
||||
@ -753,7 +755,7 @@ void WidgetManager::createAllWidgets(const ScreenConfig& screen, lv_obj_t* paren
|
||||
if (cfg.parentId != -1) continue;
|
||||
|
||||
auto widget = WidgetFactory::create(cfg);
|
||||
if (widget && cfg.id < MAX_WIDGETS) {
|
||||
if (widget && cfg.id < 256) {
|
||||
widget->create(parent);
|
||||
widget->applyStyle();
|
||||
widgets_[cfg.id] = std::move(widget);
|
||||
@ -773,18 +775,18 @@ void WidgetManager::createAllWidgets(const ScreenConfig& screen, lv_obj_t* paren
|
||||
const WidgetConfig& cfg = screen.widgets[i];
|
||||
|
||||
// Skip if already created
|
||||
if (widgets_[cfg.id]) continue;
|
||||
if (cfg.id < 256 && widgets_[cfg.id]) continue;
|
||||
|
||||
// Skip if it's a root widget (should be created in Pass 1, but if failed/skipped, ignore)
|
||||
if (cfg.parentId == -1) continue;
|
||||
|
||||
// Check if parent exists
|
||||
if (cfg.parentId >= 0 && cfg.parentId < MAX_WIDGETS && widgets_[cfg.parentId]) {
|
||||
if (cfg.parentId >= 0 && cfg.parentId < 256 && widgets_[cfg.parentId]) {
|
||||
// Parent exists! Get its LVGL object
|
||||
lv_obj_t* parentObj = widgets_[cfg.parentId]->getObj();
|
||||
if (parentObj) {
|
||||
auto widget = WidgetFactory::create(cfg);
|
||||
if (widget && cfg.id < MAX_WIDGETS) {
|
||||
if (widget && cfg.id < 256) {
|
||||
widget->create(parentObj);
|
||||
widget->applyStyle();
|
||||
widgets_[cfg.id] = std::move(widget);
|
||||
@ -955,6 +957,30 @@ void WidgetManager::refreshChartWidgets() {
|
||||
refreshChartWidgetsLocked();
|
||||
}
|
||||
|
||||
void WidgetManager::updateSystemTimeWidgets() {
|
||||
int64_t nowUs = esp_timer_get_time();
|
||||
if (nowUs - lastSystemTimeUpdateUs_ < 500000) { // Update every 500ms
|
||||
return;
|
||||
}
|
||||
lastSystemTimeUpdateUs_ = nowUs;
|
||||
|
||||
time_t now;
|
||||
time(&now);
|
||||
struct tm timeinfo;
|
||||
localtime_r(&now, &timeinfo);
|
||||
|
||||
// Call from loop() which is ALREADY LOCKED by LVGL timer
|
||||
for (auto& widget : widgets_) {
|
||||
if (!widget) continue;
|
||||
TextSource src = widget->getTextSource();
|
||||
if (src == TextSource::SYSTEM_TIME ||
|
||||
src == TextSource::SYSTEM_DATE ||
|
||||
src == TextSource::SYSTEM_DATETIME) {
|
||||
widget->onKnxTime(timeinfo, src);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WidgetManager::applyKnxValue(uint16_t groupAddr, float value, TextSource source) {
|
||||
for (auto& widget : widgets_) {
|
||||
if (widget && widget->getKnxAddress() == groupAddr &&
|
||||
@ -989,26 +1015,40 @@ void WidgetManager::applyKnxText(uint16_t groupAddr, const char* text) {
|
||||
}
|
||||
|
||||
void WidgetManager::applyKnxTime(uint16_t groupAddr, const struct tm& value, KnxTimeType type) {
|
||||
bool updated = false;
|
||||
switch (type) {
|
||||
case KnxTimeType::TIME:
|
||||
if (config_.knxTimeAddress != 0 && groupAddr == config_.knxTimeAddress) {
|
||||
HistoryStore::instance().updateTimeOfDay(value);
|
||||
updated = true;
|
||||
// Simplified system time synchronization
|
||||
bool isGlobalTime = false;
|
||||
if (type == KnxTimeType::TIME && config_.knxTimeAddress != 0 && groupAddr == config_.knxTimeAddress) isGlobalTime = true;
|
||||
if (type == KnxTimeType::DATE && config_.knxDateAddress != 0 && groupAddr == config_.knxDateAddress) isGlobalTime = true;
|
||||
if (type == KnxTimeType::DATETIME && config_.knxDateTimeAddress != 0 && groupAddr == config_.knxDateTimeAddress) isGlobalTime = true;
|
||||
|
||||
if (isGlobalTime) {
|
||||
time_t now;
|
||||
time(&now);
|
||||
struct tm t_new;
|
||||
localtime_r(&now, &t_new);
|
||||
|
||||
if (type == KnxTimeType::TIME) {
|
||||
t_new.tm_hour = value.tm_hour;
|
||||
t_new.tm_min = value.tm_min;
|
||||
t_new.tm_sec = value.tm_sec;
|
||||
} else if (type == KnxTimeType::DATE) {
|
||||
t_new.tm_year = value.tm_year;
|
||||
if (t_new.tm_year < 1900) t_new.tm_year += 1900;
|
||||
t_new.tm_mon = value.tm_mon;
|
||||
t_new.tm_mday = value.tm_mday;
|
||||
} else if (type == KnxTimeType::DATETIME) {
|
||||
t_new = value;
|
||||
if (t_new.tm_year < 1900) t_new.tm_year += 1900;
|
||||
}
|
||||
break;
|
||||
case KnxTimeType::DATE:
|
||||
if (config_.knxDateAddress != 0 && groupAddr == config_.knxDateAddress) {
|
||||
HistoryStore::instance().updateDate(value);
|
||||
updated = true;
|
||||
|
||||
// Final normalization for mktime (expects year - 1900)
|
||||
if (t_new.tm_year >= 1900) t_new.tm_year -= 1900;
|
||||
|
||||
time_t t = mktime(&t_new);
|
||||
if (t != -1) {
|
||||
struct timeval tv = { .tv_sec = t, .tv_usec = 0 };
|
||||
settimeofday(&tv, NULL);
|
||||
}
|
||||
break;
|
||||
case KnxTimeType::DATETIME:
|
||||
if (config_.knxDateTimeAddress != 0 && groupAddr == config_.knxDateTimeAddress) {
|
||||
HistoryStore::instance().updateDateTime(value);
|
||||
updated = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
TextSource source = TextSource::STATIC;
|
||||
@ -1025,10 +1065,8 @@ void WidgetManager::applyKnxTime(uint16_t groupAddr, const struct tm& value, Knx
|
||||
}
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
chartRefreshPending_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void WidgetManager::cacheKnxValue(uint16_t groupAddr, TextSource source, float value) {
|
||||
if (groupAddr == 0) return;
|
||||
|
||||
@ -45,6 +45,9 @@ public:
|
||||
// User activity (resets standby timer)
|
||||
void onUserActivity();
|
||||
|
||||
// Update widgets displaying system time
|
||||
void updateSystemTimeWidgets();
|
||||
|
||||
// Thread-safe KNX updates (queued to UI thread)
|
||||
void onKnxValue(uint16_t groupAddr, float value, TextSource source);
|
||||
void onKnxValue(uint16_t groupAddr, float value);
|
||||
@ -170,9 +173,10 @@ private:
|
||||
uint8_t navTargetScreen_ = 0xFF;
|
||||
int64_t navRequestUs_ = 0;
|
||||
int64_t lastActivityUs_ = 0;
|
||||
int64_t lastSystemTimeUpdateUs_ = 0;
|
||||
|
||||
// Runtime widget instances (indexed by widget ID)
|
||||
std::array<std::unique_ptr<Widget>, MAX_WIDGETS> widgets_;
|
||||
std::array<std::unique_ptr<Widget>, 256> widgets_;
|
||||
lv_obj_t* screen_ = nullptr;
|
||||
lv_obj_t* modalContainer_ = nullptr;
|
||||
lv_obj_t* modalDimmer_ = nullptr;
|
||||
|
||||
@ -174,6 +174,17 @@ lv_obj_t* LabelWidget::create(lv_obj_t* parent) {
|
||||
|
||||
if (obj_ != nullptr) {
|
||||
lv_obj_clear_flag(obj_, LV_OBJ_FLAG_CLICKABLE);
|
||||
|
||||
// Initial update for system time widgets to avoid empty display
|
||||
if (config_.textSource == TextSource::SYSTEM_TIME ||
|
||||
config_.textSource == TextSource::SYSTEM_DATE ||
|
||||
config_.textSource == TextSource::SYSTEM_DATETIME) {
|
||||
time_t now;
|
||||
time(&now);
|
||||
struct tm timeinfo;
|
||||
localtime_r(&now, &timeinfo);
|
||||
onKnxTime(timeinfo, config_.textSource);
|
||||
}
|
||||
}
|
||||
return obj_;
|
||||
}
|
||||
@ -260,16 +271,25 @@ void LabelWidget::onKnxTime(const struct tm& value, TextSource source) {
|
||||
if (config_.textSource != source) return;
|
||||
if (source != TextSource::KNX_DPT_TIME &&
|
||||
source != TextSource::KNX_DPT_DATE &&
|
||||
source != TextSource::KNX_DPT_DATETIME) {
|
||||
source != TextSource::KNX_DPT_DATETIME &&
|
||||
source != TextSource::SYSTEM_TIME &&
|
||||
source != TextSource::SYSTEM_DATE &&
|
||||
source != TextSource::SYSTEM_DATETIME) {
|
||||
return;
|
||||
}
|
||||
|
||||
int year = value.tm_year;
|
||||
// tm_year is usually year-1900.
|
||||
// If value comes from KNX raw decode, it might be full year depending on logic.
|
||||
// Standard tm_year: 123 for 2023.
|
||||
// Our existing logic handled year < 1900 by adding 1900.
|
||||
// Let's ensure consistent behavior for SYSTEM time which uses standard tm struct.
|
||||
if (year > 0 && year < 1900) {
|
||||
year += 1900;
|
||||
}
|
||||
int month = value.tm_mon;
|
||||
if (month < 1 || month > 12) {
|
||||
// tm_mon is 0-11
|
||||
if (month >= 0 && month <= 11) {
|
||||
month += 1;
|
||||
}
|
||||
@ -277,9 +297,9 @@ void LabelWidget::onKnxTime(const struct tm& value, TextSource source) {
|
||||
|
||||
const char* fmt = config_.text;
|
||||
if (!fmt || fmt[0] == '\0' || strchr(fmt, '%') == nullptr) {
|
||||
if (source == TextSource::KNX_DPT_TIME) {
|
||||
if (source == TextSource::KNX_DPT_TIME || source == TextSource::SYSTEM_TIME) {
|
||||
fmt = "%02d:%02d:%02d";
|
||||
} else if (source == TextSource::KNX_DPT_DATE) {
|
||||
} else if (source == TextSource::KNX_DPT_DATE || source == TextSource::SYSTEM_DATE) {
|
||||
fmt = "%02d.%02d.%04d";
|
||||
} else {
|
||||
fmt = "%02d.%02d.%04d %02d:%02d:%02d";
|
||||
@ -287,9 +307,9 @@ void LabelWidget::onKnxTime(const struct tm& value, TextSource source) {
|
||||
}
|
||||
|
||||
char buf[32];
|
||||
if (source == TextSource::KNX_DPT_TIME) {
|
||||
if (source == TextSource::KNX_DPT_TIME || source == TextSource::SYSTEM_TIME) {
|
||||
snprintf(buf, sizeof(buf), fmt, value.tm_hour, value.tm_min, value.tm_sec);
|
||||
} else if (source == TextSource::KNX_DPT_DATE) {
|
||||
} else if (source == TextSource::KNX_DPT_DATE || source == TextSource::SYSTEM_DATE) {
|
||||
snprintf(buf, sizeof(buf), fmt, value.tm_mday, month, year);
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), fmt, value.tm_mday, month, year,
|
||||
|
||||
9
sdcard_content/webseite/assets/index-DYaUUEn9.js
Normal file
9
sdcard_content/webseite/assets/index-DYaUUEn9.js
Normal file
File diff suppressed because one or more lines are too long
9
sdcard_content/webseite/assets/index-DeUfQjDD.js
Normal file
9
sdcard_content/webseite/assets/index-DeUfQjDD.js
Normal file
File diff suppressed because one or more lines are too long
1
sdcard_content/webseite/assets/index-kFitTaMN.css
Normal file
1
sdcard_content/webseite/assets/index-kFitTaMN.css
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
1
sdcard_content/webseite/vite.svg
Normal file
1
sdcard_content/webseite/vite.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@ -29,7 +29,7 @@
|
||||
</div>
|
||||
<template v-else>
|
||||
<div :class="rowClass"><label :class="labelClass">Format</label><input :class="inputClass" type="text" v-model="w.text"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">KNX Objekt</label>
|
||||
<div v-if="w.textSrc < 11" :class="rowClass"><label :class="labelClass">KNX Objekt</label>
|
||||
<select :class="inputClass" v-model.number="w.knxAddr">
|
||||
<option :value="0">-- Waehlen --</option>
|
||||
<option v-for="addr in store.knxAddresses" :key="`${addr.addr}-${addr.index}`" :value="addr.addr">
|
||||
|
||||
@ -72,11 +72,15 @@ export const textSources = {
|
||||
7: 'KNX Dezimalfaktor (DPT 5.005)',
|
||||
8: 'KNX Uhrzeit (DPT 10.001)',
|
||||
9: 'KNX Datum (DPT 11.001)',
|
||||
10: 'KNX Datum & Uhrzeit (DPT 19.001)'
|
||||
10: 'KNX Datum & Uhrzeit (DPT 19.001)',
|
||||
11: 'System Uhrzeit',
|
||||
12: 'System Datum',
|
||||
13: 'System Datum & Uhrzeit'
|
||||
};
|
||||
|
||||
export const textSourceGroups = [
|
||||
{ label: 'Statisch', values: [0] },
|
||||
{ label: 'System', values: [11, 12, 13] },
|
||||
{ label: 'DPT 1.x', values: [2] },
|
||||
{ label: 'DPT 5.x', values: [3, 7] },
|
||||
{ label: 'DPT 9.x', values: [1] },
|
||||
@ -89,7 +93,7 @@ export const textSourceGroups = [
|
||||
];
|
||||
|
||||
export const sourceOptions = {
|
||||
label: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
||||
label: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13],
|
||||
button: [0],
|
||||
led: [0, 2],
|
||||
icon: [0, 2],
|
||||
@ -126,7 +130,10 @@ export const defaultFormats = {
|
||||
7: '%d',
|
||||
8: '%02d:%02d:%02d',
|
||||
9: '%02d.%02d.%04d',
|
||||
10: '%02d.%02d.%04d %02d:%02d:%02d'
|
||||
10: '%02d.%02d.%04d %02d:%02d:%02d',
|
||||
11: '%02d:%02d:%02d',
|
||||
12: '%02d.%02d.%04d',
|
||||
13: '%02d.%02d.%04d %02d:%02d:%02d'
|
||||
};
|
||||
|
||||
export const WIDGET_DEFAULTS = {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user