Why does setting DLMS execution_time (Class 22 / Class 20) with wildcards 0xFF or 0x00 trigger continuous 1Hz firing or 32-bit overflow bugs?
-
[!WARNING]
Technical Redaction & Safe Harbor Notice:
All communication captures, system titles, and cryptographic parameters in this thread have been sanitized and neutralized. Real utility meter identifiers, production encryption keys (GUEK/GAK/MK), SIM IMSIs, and proprietary network envelopes have been substituted with standard IEC 62056 synthetic test vectors. Do not post unredacted production captures or live cryptographic keys.We are commissioning AMI firmware for multi-rate tariff switching and disconnect scheduling using Activity Calendar (Class 20, OBIS
0.0.13.0.0.255, Attribute 4activate_passive_calendar_time) and Single Action Schedule (Class 22, OBIS0.0.15.0.0.255, Attribute 2execution_time).When utility operators attempt to disable or deactivate a pending schedule, they frequently transmit either:
- All-wildcards date/time:
FF FF FF FF FF FF FF FF FF 80 00 00(12 bytes), - Zeroed date/time:
00 00 00 00 FF 00 00 00 00 80 00 00.
However, on several batch meters running firmware
v2.4.0, writingFF FF...causes the meter to execute the linked script action (e.g. Tariff Switch / Breaker Check) every single second in an infinite 1Hz loop, spiking MCU load to 100% and jamming the optical port! Writing00 00...or an expired timestamp (e.g.2020-01-01) immediately fires the script upon receiving theSET-Request!Here is the sanitized
SET-Requesttransmitted over TCP 4059 wrapper for0.0.13.0.0.255Attribute 4:00 01 00 01 00 01 00 23 DB 08 53 48 41 00 00 00 00 01 30 00 00 00 12 A1 88 9F 33 22 10 44 88 99 C1 C3 01 C1 00 14 00 00 0D 00 00 FF 04 09 0C FF FF FF FF FF FF FF FF FF 80 00 00And the meter responds with
SET-Response-Normal(0xC5 0x01 C1 00: success), immediately followed by the infinite execution loop.Our field operators assumed
0xFFwas the universal DLMS way to disable scheduled execution. Why is this triggering 1Hz execution storms? - All-wildcards date/time:
-
Carlos, your operators' assumption that
0xFFmeans 'disabled' in a COSEMdate_timeoctet-string directly contradicts the standard.Per DLMS Blue Book Ed. 17 Section 4.1.6.1 (Date and Time Formats) and IEC 62056-62 Section 6.2.19 (Single Action Schedule), a 12-byte
date_timeoctet-string is defined as:Byte 1-2: Year (High/Low) - 0xFFFF = not specified (wildcard: any year) Byte 3: Month - 0xFF = not specified (wildcard: every month) Byte 4: Day of month - 0xFF = not specified (wildcard: every day) Byte 5: Day of week - 0xFF = not specified (wildcard: any weekday) Byte 6: Hour - 0xFF = not specified (wildcard: every hour) Byte 7: Minute - 0xFF = not specified (wildcard: every minute) Byte 8: Second - 0xFF = not specified (wildcard: every second) Byte 9: Hundredths - 0xFF = not specified Byte 10-11: Deviation - 0x8000 = not specified Byte 12: Clock Status - 0x00In DLMS pattern matching,
0xFFis a Wildcard (meaning MATCH ALL). When every byte is set to0xFF, the meter's cron/scheduler evaluates:
Condition: (current_year == wildcard) AND (current_second == wildcard) => TRUEBecause the condition evaluates to TRUE on every 1-second RTC tick, the scheduler fires the linked script action every second! Setting
0xFFacross all fields creates an unconstrained periodic timer, not a disabled state. -
Sarah's explanation of the
0xFFwildcard matching is 100% accurate. Now let's address why writing all zeros (0000-00-00) or past timestamps triggers immediate execution uponSET:In embedded RTOS firmware, the scheduler task evaluates task queues using comparative delta timestamps:
// Typical flawed firmware scheduler check: if ((int32_t)(current_rtc_epoch - task->scheduled_epoch) >= 0 && !task->executed_flag) { execute_script(task->script_logical_name, task->script_selector); task->executed_flag = true; }When a past date (e.g.
2020-01-01or zeroed date) is written,current_rtc_epoch - task->scheduled_epochis a positive number (delta_t > 0). If the firmware clearsexecuted_flagupon receiving theSET-Requestwithout checking whether the newly written timestamp is already in the past, the scheduler evaluates delta_t >= 0 as an expired alarm that needs immediate catch-up execution!Furthermore, per DLMS Blue Book Ed. 17 Section 4.1.6.1 Table 6,
Year = 0x0000is illegal in Gregorian calendar conversions and causes standard C library functions (such asmktime()) to return(time_t)-1, triggering invalid epoch exceptions. -
A common workaround that utilities attempt when they realize past dates fire immediately is to write an extreme far-future year, such as Year 9999 (
0x270F-12-31-FF 23:59:59 00 80 00 00), hoping to push execution beyond the operational life of the meter.However, Year 9999 introduces a catastrophic 32-bit integer overflow bug:
Most smart meter microcontrollers (such as ARM Cortex-M4 or Renesas RL78) implement standard POSIX
time_tas a signed 32-bit integer:
Max 32-bit Unix Epoch: 2^31 - 1 = 2,147,483,647 => 2038-01-19 03:14:07 UTCWhen Year 9999 (
0x270F) is converted to Unix epoch seconds (~253,402,300,799), the 32-bit integer overflows into a negative value (~ -1,845,128,833). In two's complement, this represents a date in Year 1911!Because the negative epoch is less than current_rtc_epoch, the firmware immediately falls back into the 'past timestamp' trap that Elena described, firing the script instantaneously upon
SET-Request! -
From the security and APDU framing perspective, there is also a critical validation detail in the
date_timeOctet-String format.Per DLMS Green Book 10th Ed Section 9.2.5.3 and IEC 62056-53 Section 8.4.2, when writing
date_timeobjects over Security Suite 1 (0xDBGeneral-Glo-Ciphering), the APDU contains an explicit 12-byteOctet-String(Tag0x09 0x0C
09 0C [YY YY] [MM] [DD] [WD] [hh] [mm] [ss] [hd] [TZ_H] [TZ_L] [Status]If the client sends invalid Daylight Saving Time status bits in Byte 12 (e.g. setting Bit 7 without configuring
daylight_savings_beginin Class 8, OBIS0.0.1.0.0.255), or invalid timezone deviation bytes (Bytes 10-11 !=0x8000and outside[-720, +720]), a compliant COSEM parser must reject theSET-Requestwith:
data-access-result: type-mismatch(0xC5 0x01 C1 03) orhardware-fault(0xC5 0x01 C1 01).Carlos, did the meters that threw
temporary-failurehave strict timezone validation enabled in their COSEM stack? -
Yes! Alex and David, that perfectly explains the firmware split we observed:
- Meter Batch A (running firmware
v2.4.0) had the signed 32-bittime_toverflow. When we sent Year 9999, it overflowed to Year 1911 and triggered the disconnect breaker instantly. - Meter Batch B had strict Blue Book A-XDR validation on Byte 10-12 and rejected
FF FF...because Byte 10-11 was set to0xFFFFinstead of standard0x8000(not specified). - When we sent
FF FF FF FF FF FF FF FF FF 80 00 00, Batch A went into the 1Hz execution loop and clicked the internal load-shedding relay 60 times a minute until the power supply brownout occurred!
Given these vendor traps, what is the exact, standard-compliant way to disable or safely park a schedule for both Class 20 (Activity Calendar) and Class 22 (Single Action Schedule)?
- Meter Batch A (running firmware
-
For Class 22 (
Single Action Schedule, OBIS0.0.15.0.0.255), the DLMS standard provides architectural mechanisms to disable execution, but there is a major cross-device firmware defect to watch out for:Per DLMS Blue Book Ed. 17 Section 4.4.4 and IEC 62056-62 Table 24, Interface Class 22 defines:
- Attribute 2:
execution_time(Array ofdate_timeoctet-strings) - Attribute 4:
execution_type(Enum)
Theoretical Disabling Methods vs Real-World Firmware Defects:
- Method A (Empty Array
01 00per standard): Theoretically, writing an empty array (0x01 0x00) to Attribute 2execution_timeclears all scheduled triggers. - The Real-World Device Defect: On many legacy firmware builds and strict schema validators (especially meters enforcing fixed-capacity static array descriptors
min_entries >= 1), writing0x01 0x00is rejected withdata-access-result: type-mismatch(0x03) ortemporary-failure(0x02), making it impossible to clear via empty array on those specific devices! - Method B (Execution Type Enum): Setting Attribute 4 to
0(disabled) or5(inactive) also encounters firmware variance where Attribute 4 is read-only.
Therefore, relying exclusively on
0x01 0x00fails on non-standard hardware. What is the single universal method that succeeds across 100% of devices? - Attribute 2:
-
Elena here. Sarah is completely right—in our meter interoperability lab, we found that ~15% of deployed meter models reject the empty array
0x01 0x00on Class 22, and for Class 20 (Activity Calendar, OBIS0.0.13.0.0.255), Attribute 4 (activate_passive_calendar_time) is a mandatory single 12-byteOctet-Stringthat cannot be empty anyway!The Universal Golden Standard: Writing a Confirmed Distant Future Timestamp (2099 / 2100)
Whether targeting Class 20 (Activity Calendar) or Class 22 (Single Action Schedule), the most robust, cross-vendor, fail-safe solution is setting a confirmed valid distant future date:
Recommended Timestamp:
2099-12-31-FF 23:59:59.00 (UTC not specified)09 0C 08 33 0C 1F FF 17 3B 3B 00 80 00 00(Alternatively,
2100-01-01-FF 00:00:00->09 0C 08 34 01 01 FF 00 00 00 00 80 00 00)Why a Safe Distant Epoch (Year 2099/2100) is Universally Superior:
- 100% Schema Acceptance: Conforms to standard 12-byte
date_timeOctet-String format, passing schema validators on all meters (even those rejecting empty arrays). - Safe from 32-bit Unix Overflow: Year 2099/2100 fits safely within 32-bit unsigned epoch ranges ($< 2^{32} - 1 = 4{,}294{,}967{,}295$), avoiding the Year 9999 negative wraparound.
- Zero False Triggers: Pushes the trigger 70+ years beyond the physical operational lifespan of the meter.
- No 1Hz Storms: All time components (Month, Day, Hour, Min, Sec) are explicit non-wildcard values, preventing 1-second periodic matching.
- 100% Schema Acceptance: Conforms to standard 12-byte
-
Here is the verified, working
SET-Requesttrace for setting0.0.13.0.0.255Attribute 4 (activate_passive_calendar_time) to2099-12-31-FF 23:59:59over TCP 4059 with Security Suite 1:00 01 00 01 00 01 00 23 DB 08 53 48 41 00 00 00 00 01 30 00 00 00 45 B2 11 90 EE 44 19 82 77 1B C0 C3 01 C1 00 14 00 00 0D 00 00 FF 04 09 0C 08 33 0C 1F FF 17 3B 3B 00 80 00 00And here is the meter's successful
SET-Response-Normal:00 01 00 01 00 01 00 15 DB 08 53 48 41 00 00 00 00 01 30 00 00 00 4A C4 88 12 90 AA 34 11 02 C5 01 C1 00Dissecting the plain response:
C5 01:SET-Response-NormalC1: Invoke-ID100:data-access-result: success
With Year 2099 encoded as
0x0833and deviation as0x8000, the meter accepts the passive calendar time without scheduling false alarms or 1Hz execution storms. -
To synthesize this for all AMI and meter firmware engineers, here is the production Python snippet using Gurux.DLMS demonstrating how to park both Class 20 and Class 22 schedules safely using the universal distant timestamp approach:
from gurux_dlms.objects import GXDLMSActivityCalendar, GXDLMSSingleActionSchedule from gurux_dlms.GXDateTime import GXDateTime from datetime import datetime # The Universal Safe Distant Epoch: 2099-12-31 23:59:59 (Year 2099 = 0x0833) SAFE_DISTANT_EPOCH = GXDateTime(datetime(2099, 12, 31, 23, 59, 59)) def park_single_action_schedule_universal(client, reader, obis="0.0.15.0.0.255"): sas = GXDLMSSingleActionSchedule(obis) # 1. Try standard empty array first try: sas.executionTime = [] reader.write(sas, 2) print(f"[+] Class 22 {obis} cleared via empty array.") except Exception as err: # 2. Fallback to Universal Distant Epoch if meter rejects empty array print(f"[!] Meter rejected empty array (err: {err}). Falling back to safe distant epoch...") sas.executionTime = [SAFE_DISTANT_EPOCH] reader.write(sas, 2) print(f"[+] Class 22 {obis} successfully parked at 2099-12-31 23:59:59.") def park_activity_calendar_passive_time(client, reader, obis="0.0.13.0.0.255"): calendar = GXDLMSActivityCalendar(obis) calendar.time = SAFE_DISTANT_EPOCH reader.write(calendar, 4) # Write Attribute 4 (activate_passive_calendar_time) print(f"[+] Class 20 {obis} passive activation parked at 2099-12-31 23:59:59.")Summary Golden Rules for DLMS Schedule Management:
- Never use
0xFFacross all time bytes to disable: In DLMS Blue Book Section 4.1.6.1,0xFFis a wildcard that triggers continuous 1Hz execution. - Never use past timestamps (
2000-01-01or all zeros): Flawed RTOS schedulers treat past timestamps as expired alarms and execute immediately uponSET. - Avoid Year 9999: Exceeds 32-bit signed Unix epoch ($2^{31}-1$) and overflows into negative timestamps (Year 1911).
- Empty array
01 00is valid on most devices, but rejected on ~15% of legacy models: Always support fallback to a valid distant future timestamp. - Universal Bulletproof Standard: Writing a confirmed valid distant future timestamp (e.g.
2099-12-31 23:59:59/0x0833 0x0C 0x1F FF 0x17 0x3B 0x3B 0x00 0x8000 0x00or2100-01-01 00:00:00) is 100% compliant and accepted across all vendor meters without side effects.
- Never use
Hello! It looks like you're interested in this conversation, but you don't have an account yet.
Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.
With your input, this post could be even better 💗
Register Login