Come possiamo aiutarti?

Print

Come gestire automaticamente l’ora legale nell’HMI utilizzando una macro?

In questo post vi mostreremo come gestire automaticamente l’ora legale nell’HMI utilizzando una macro. Il cambio dell’ora è stato applicato per la prima volta nel 1916. Dal 2002, il cambio dell’ora viene effettuato in modo uniforme in tutti gli Stati membri dell’Unione Europea:

  • il passaggio all’ora legale avviene nella notte dell’ultimo sabato di marzo
  • il passaggio all’ ora invernale avviene nella notte dell ‘ultimo sabato di ottobre

Come promemoria, i Paesi dell’UE sono attualmente suddivisi in tre fusi orari:

  • Europa occidentale (GMT): Irlanda e Portogallo
  • Europa centrale (GMT 1): Austria, Belgio, Croazia, Repubblica Ceca, Danimarca, Francia, Germania, Ungheria, Italia, Lussemburgo, Malta, Paesi Bassi, Polonia, Slovacchia, Slovenia, Spagna e Svezia
  • Europa orientale (GMT 2): Bulgaria, Cipro, Estonia, Finlandia, Grecia, Lettonia, Lituania e Romania.

Con HMI-Tool

Nell’Editor macro, aggiungere una nuova macro e copiarla.

#include "MacroInit.h"
// Returns the day-of-month of the last Sunday of a given month/year
// Uses the Tomohiko Sakamoto-inspired method (no division by 7 needed)
int LastSundayOfMonth(int month, int year)
{
// Day of week of the last day of the month (0=Sunday ... 6=Saturday)
// Zeller-like formula for day-of-week of day 1, then offset to last day
int y = year;
int m = month;
int daysInMonth;
switch(m)
{
case 1:  daysInMonth = 31; break;
case 2:
if ((y % 400 == 0) || (y % 4 == 0 && y % 100 != 0))
daysInMonth = 29;
else
daysInMonth = 28;
break;
case 3:  daysInMonth = 31; break;
case 4:  daysInMonth = 30; break;
case 5:  daysInMonth = 31; break;
case 6:  daysInMonth = 30; break;
case 7:  daysInMonth = 31; break;
case 8:  daysInMonth = 31; break;
case 9:  daysInMonth = 30; break;
case 10: daysInMonth = 31; break;
case 11: daysInMonth = 30; break;
case 12: daysInMonth = 31; break;
default: daysInMonth = 30; break;
}
// Compute day-of-week of the last day of the month
// Using Zeller's formula (0=Sunday, 1=Monday ... 6=Saturday)
if (m < 3) { m += 12; y -= 1; }
int k = y % 100;
int j = y / 100;
int dow = (daysInMonth + (13*(m+1))/5 + k + k/4 + j/4 - 2*j) % 7;
// Zeller returns: 0=Saturday, 1=Sunday, 2=Monday ... 6=Friday
// Normalize to: 0=Sunday, 1=Monday ... 6=Saturday
dow = (dow + 6) % 7;
// Subtract offset to reach last Sunday (dow==0)
return daysInMonth - dow;
}
void Macro_main(IN *p)
{
MarcoInit
// Automatic Daylight Saving Time (DST) management
// <!> This script must be called every minute
// DST start : last Sunday of March   - 02:00 -> 03:00 (clock forward +1h)
// DST end   : last Sunday of October - 03:00 -> 02:00 (clock back   -1h)
// LocalBit[50000]: 0 = summer time, 1 = winter time (backed up on power failure)
// Catchup window: up to 6 days after the DST Sunday (HMI was powered off)
int minute    = BCD2BIN(LocalWord[60001]); // Current minute
int hour      = BCD2BIN(LocalWord[60002]); // Current hour
int day       = BCD2BIN(LocalWord[60003]); // Day of month
int month     = BCD2BIN(LocalWord[60004]); // Month
int year      = BCD2BIN(LocalWord[60005]); // Year (needed for LastSundayOfMonth)
int dayOfWeek = BCD2BIN(LocalWord[60006]); // Monday=1 ... Sunday=7
// --- DST START : last Sunday of March at 02:00 -> 03:00 ---
// Window: from Sunday at 02:00 until the following Saturday at 23:59 (6 days catchup)
int lastSunMarch   = LastSundayOfMonth(3, year);
int daysSinceDSTStart = day - lastSunMarch; // Positive if we are past the last Sunday
int dstStartWindow = (daysSinceDSTStart == 0 && hour >= 2) || // Sunday from 02:00
(daysSinceDSTStart >= 1 && daysSinceDSTStart <= 6); // Mon to Sat catchup
if ((LocalBit[50000] == 1) &&
(month == 3)           &&
dstStartWindow)
{
LocalWord[60002] = BIN2BCD(hour + 1); // Shift clock forward by 1 hour
LocalBit[50000]  = 0;                 // Flag: now summer time
}
// --- DST END : last Sunday of October at 03:00 -> 02:00 ---
// Window: from Sunday at 03:00 until the following Saturday at 23:59 (6 days catchup)
int lastSunOctober = LastSundayOfMonth(10, year);
int daysSinceDSTEnd = day - lastSunOctober;
int dstEndWindow = (daysSinceDSTEnd == 0 && hour >= 3) || // Sunday from 03:00
(daysSinceDSTEnd >= 1 && daysSinceDSTEnd <= 6); // Mon to Sat catchup
if ((LocalBit[50000] == 0) &&
(month == 10)          &&
dstEndWindow)
{
LocalWord[60002] = BIN2BCD(hour - 1); // Shift clock back by 1 hour
LocalBit[50000]  = 1;                 // Flag: now winter time
}
}

Aggiungete la macro all’elenco “Macro globali” e impostate la sua frequenza di esecuzione a 60000 ms (1 minuto) per aggiornare l’orologio ogni minuto.

Come funziona

Il programma viene eseguito ogni minuto e legge l’ora corrente (minuti, ora, giorno, mese, anno, giorno della settimana) dai registri.
Tutti i valori dell’ora sono memorizzati in formato BCD, quindi vengono prima convertiti in decimale.
Una funzione ausiliaria calcola il numero di giorni del mese corrente, compresi gli anni bisestili.
Per individuare l’ultima domenica del mese, il codice controlla: dayOfWeek == 7 (domenica) e day 7 > daysInMonth.
Se è l’ultima domenica di marzo esattamente alle 03:00, l’orologio viene anticipato alle 04:00 (ora legale).
Viene impostato un bit flag (LocalBit[65535]) per indicare l’ora legale.
Se è l’ultima domenica di ottobre esattamente alle 03:00, l’orologio viene spostato indietro alle 02:00 (ora invernale).
Il bit flag viene commutato per indicare l’ora invernale, evitando correzioni ripetute.
In questo modo si evitano loop infiniti in cui la stessa ora verrebbe elaborata più volte.
Il sistema mantiene quindi automaticamente la sincronizzazione dell’ora locale con le regole ufficiali della DST.

È possibile sostituire LB65535 con LB51999 per il backup del valore(tipi di registro HMI: LB, LW, RWI e registro ritentivo/salvataggio)

Vedere anche Come impostare e sincronizzare l’orologio del PLC ACE con l’HMI utilizzando una macro