Summary
When the last multicast Class B session stops (by timer expiry), Class B beacon search and ping
slots are not disabled — they keep running indefinitely. For Class C, two related issues exist
in the original code: a Class A device does not revert to Class A after the session ends; and a
native Class C device would be incorrectly switched off Class C entirely rather than returning
to its unicast RX2 frequency and data rate.
Affected components
lbm_lib/smtc_modem_core/lorawan_packages/remote_multicast_setup/v2.0.0/lorawan_remote_multicast_setup_package_v2.0.0.c
lbm_lib/smtc_modem_core/lorawan_packages/remote_multicast_setup/v1.0.0/lorawan_remote_multicast_setup_package_v1.0.0.c
Root cause
The Remote Multicast Setup package correctly manages session start and stop using a
timer-based scheduler, but there is an asymmetry between session start and session stop.
Class B
Session start — lorawan_class_b_management_enable called at line 615 in the scheduling
section of service_on_update; lorawan_api_multicast_b_start_session called at line 458
inside the LAUNCH_CLASS_B_TASK handler in v2.0.0:
lorawan_class_b_management_enable( stack_id, true, delay_class_b_s, true ); // enables beacon search
lorawan_api_multicast_b_start_session( ... );
Session stop — STOP_CLASS_B_TASK handler in original code, lines 490–502 in v2.0.0:
lorawan_api_multicast_b_stop_session( mc_grp_id, stack_id );
// ... check if all sessions done ...
if( tmp == 0 )
{
// ← lorawan_class_b_management_enable( false ) MISSING in original code
increment_asynchronous_msgnumber( SMTC_MODEM_EVENT_NO_MORE_MULTICAST_SESSION_CLASS_B, ... );
}
lorawan_class_b_management_enable( stack_id, false, 0, true ) is only called in one place in
the entire packages directory: when switching from Class B to Class C multicast (line 399 in
v2.0.0). There is no call to disable Class B when all multicast B sessions end normally.
Class C
Session start — LAUNCH_CLASS_C_TASK handler in original code (line 400 in v2.0.0):
lorawan_class_b_management_enable( stack_id, false, 0, true );
lorawan_api_class_c_enabled( true, stack_id );
lorawan_api_multicast_c_start_session( ... );
Session stop — STOP_CLASS_C_TASK handler in original code: calls
lorawan_api_multicast_c_stop_session() but never calls lorawan_api_class_c_enabled( false ).
This means a Class A device remains in continuous Class C receive mode after the session ends.
A naive fix of adding lorawan_api_class_c_enabled( false ) unconditionally would break native
Class C devices in the opposite direction: it would completely shut down Class C — including the
unicast RX2 window — rather than restoring the device to unicast Class C on its original RX2
frequency and data rate. The multicast session may use a different frequency and DR than the
unicast RX2 channel, so the correct handling depends on whether Class C was already active
before the multicast session started.
The problem is present in both v1.0.0 and v2.0.0 of the package.
Impact
- Class B: after all multicast Class B sessions expire, the device continues to search for
beacons and open ping slot receive windows, wasting battery and RF resources indefinitely.
This persists until the device is reset or Class B is explicitly disabled by the application.
- Class C / Class A device: remains in continuous receive mode after the multicast session
ends, causing permanently elevated current consumption.
- Class C / native Class C device: if
lorawan_api_class_c_enabled( false ) were called
unconditionally as a naive fix, the device would lose its unicast Class C RX2 window entirely
and revert to Class A behaviour.
- In all cases the application receives the
NO_MORE_MULTICAST_SESSION_CLASS_B/C event but has
no documented obligation to manually repair the transport mode — the stack is expected to
manage its own lifecycle.
Steps to reproduce
Class B
- Start a multicast Class B session via Remote Multicast Setup package commands.
- Wait for the session to run to its natural timeout (
session_time + time_out).
- Observe:
SMTC_MODEM_EVENT_NO_MORE_MULTICAST_SESSION_CLASS_B fires, but beacon search and
ping slots remain active (visible in radio activity or power trace).
Class C — Class A device
- Configure a Class A device and start a multicast Class C session.
- Wait for the session timeout.
- Observe:
SMTC_MODEM_EVENT_NO_MORE_MULTICAST_SESSION_CLASS_C fires, but the device stays in
Class C (continuous RX window open) instead of reverting to Class A.
Class C — native Class C device
- Configure a native Class C device and start a multicast Class C session on a different
frequency / DR than the unicast RX2 channel.
- Wait for the session timeout.
- Observe: device continues listening on the multicast frequency instead of the RX2 frequency
and DR (unicast Class C window is never restored).
Fix
Class B
In the STOP_CLASS_B_TASK handler, when tmp == 0, add lorawan_class_b_management_enable
disable call before the event, symmetric with what was enabled at session start:
if( tmp == 0 )
{
lorawan_class_b_management_enable( stack_id, false, 0, true ); // ← added
increment_asynchronous_msgnumber( SMTC_MODEM_EVENT_NO_MORE_MULTICAST_SESSION_CLASS_B, 0, stack_id );
}
Class C
The fix requires distinguishing between a Class A device and a native Class C device at session
stop time.
Step 1 — add bool class_c_running_before_session to
lorawan_remote_multicast_setup_package_ctx_t. It is zeroed by the memset at init.
Step 2 — in LAUNCH_CLASS_C_TASK, capture whether Class C was already running before
calling lorawan_api_class_c_enabled( true ):
lorawan_class_b_management_enable( stack_id, false, 0, true );
if( lorawan_api_class_c_is_running( stack_id ) )
{
ctx->class_c_running_before_session = true; // ← added: native Class C device
}
lorawan_api_class_c_enabled( true, stack_id );
lorawan_api_multicast_c_start_session( ... );
Step 3 — in STOP_CLASS_C_TASK when tmp == 0, replace the missing disable call with
conditional teardown:
if( tmp == 0 )
{
if( ctx->class_c_running_before_session == false )
{
// Device was Class A: fully disable Class C
lorawan_api_class_c_enabled( false, stack_id );
}
else
{
// Device was natively Class C: unicast RX2 is already restored automatically.
// lorawan_api_multicast_c_stop_session() above re-enabled the unicast session
// and issued rp_task_abort(), which triggers lr1mac_class_c_launch() to reload
// lr1_mac->rx2_data_rate / rx2_frequency. No further action needed.
ctx->class_c_running_before_session = false;
}
increment_asynchronous_msgnumber( SMTC_MODEM_EVENT_NO_MORE_MULTICAST_SESSION_CLASS_C, 0, stack_id );
}
The restore of unicast Class C frequency and DR for native Class C devices is automatic. When
lorawan_api_multicast_c_stop_session() stops the last multicast session,
lr1mac_class_c_multicast_stop_session() re-enables rx_session_param[RX_SESSION_UNICAST] and
calls rp_task_abort(). The RP abort callback re-enqueues via lr1mac_class_c_launch(), which
unconditionally reloads unicast parameters from lr1_mac->rx2_data_rate and
lr1_mac->rx2_frequency — the original unicast RX2 values that multicast sessions never
modify. This works correctly as long as class_c_obj->enabled remains true, which it does
because we no longer call lorawan_api_class_c_enabled( false ) for native Class C devices.
Both changes are applied in lorawan_remote_multicast_setup_package_v2.0.0.c and
lorawan_remote_multicast_setup_package_v1.0.0.c.
Notes
- This issue exists independently of this issue (multicast sessions not stopped on FUOTA done). Even
on the normal timer-expiry path (without FUOTA), Class B/C is not correctly handled after the
last session stops. Both issues should be fixed together.
- The
force_stop_class_b and force_stop_class_c functions introduced as part of this issue
fix reuse the same tmp == 0 logic and include the same conditional teardown, so the FUOTA
completion path and the timer-expiry path behave consistently.
- The
class_c_running_before_session flag is only ever set to true and never cleared during
an active session, so multiple concurrent multicast groups on a native Class C device are
handled correctly.
Summary
When the last multicast Class B session stops (by timer expiry), Class B beacon search and ping
slots are not disabled — they keep running indefinitely. For Class C, two related issues exist
in the original code: a Class A device does not revert to Class A after the session ends; and a
native Class C device would be incorrectly switched off Class C entirely rather than returning
to its unicast RX2 frequency and data rate.
Affected components
lbm_lib/smtc_modem_core/lorawan_packages/remote_multicast_setup/v2.0.0/lorawan_remote_multicast_setup_package_v2.0.0.clbm_lib/smtc_modem_core/lorawan_packages/remote_multicast_setup/v1.0.0/lorawan_remote_multicast_setup_package_v1.0.0.cRoot cause
The Remote Multicast Setup package correctly manages session start and stop using a
timer-based scheduler, but there is an asymmetry between session start and session stop.
Class B
Session start —
lorawan_class_b_management_enablecalled at line 615 in the schedulingsection of
service_on_update;lorawan_api_multicast_b_start_sessioncalled at line 458inside the
LAUNCH_CLASS_B_TASKhandler in v2.0.0:Session stop —
STOP_CLASS_B_TASKhandler in original code, lines 490–502 in v2.0.0:lorawan_class_b_management_enable( stack_id, false, 0, true )is only called in one place inthe entire packages directory: when switching from Class B to Class C multicast (line 399 in
v2.0.0). There is no call to disable Class B when all multicast B sessions end normally.
Class C
Session start —
LAUNCH_CLASS_C_TASKhandler in original code (line 400 in v2.0.0):Session stop —
STOP_CLASS_C_TASKhandler in original code: callslorawan_api_multicast_c_stop_session()but never callslorawan_api_class_c_enabled( false ).This means a Class A device remains in continuous Class C receive mode after the session ends.
A naive fix of adding
lorawan_api_class_c_enabled( false )unconditionally would break nativeClass C devices in the opposite direction: it would completely shut down Class C — including the
unicast RX2 window — rather than restoring the device to unicast Class C on its original RX2
frequency and data rate. The multicast session may use a different frequency and DR than the
unicast RX2 channel, so the correct handling depends on whether Class C was already active
before the multicast session started.
The problem is present in both v1.0.0 and v2.0.0 of the package.
Impact
beacons and open ping slot receive windows, wasting battery and RF resources indefinitely.
This persists until the device is reset or Class B is explicitly disabled by the application.
ends, causing permanently elevated current consumption.
lorawan_api_class_c_enabled( false )were calledunconditionally as a naive fix, the device would lose its unicast Class C RX2 window entirely
and revert to Class A behaviour.
NO_MORE_MULTICAST_SESSION_CLASS_B/Cevent but hasno documented obligation to manually repair the transport mode — the stack is expected to
manage its own lifecycle.
Steps to reproduce
Class B
session_time + time_out).SMTC_MODEM_EVENT_NO_MORE_MULTICAST_SESSION_CLASS_Bfires, but beacon search andping slots remain active (visible in radio activity or power trace).
Class C — Class A device
SMTC_MODEM_EVENT_NO_MORE_MULTICAST_SESSION_CLASS_Cfires, but the device stays inClass C (continuous RX window open) instead of reverting to Class A.
Class C — native Class C device
frequency / DR than the unicast RX2 channel.
and DR (unicast Class C window is never restored).
Fix
Class B
In the
STOP_CLASS_B_TASKhandler, whentmp == 0, addlorawan_class_b_management_enabledisable call before the event, symmetric with what was enabled at session start:
Class C
The fix requires distinguishing between a Class A device and a native Class C device at session
stop time.
Step 1 — add
bool class_c_running_before_sessiontolorawan_remote_multicast_setup_package_ctx_t. It is zeroed by thememsetat init.Step 2 — in
LAUNCH_CLASS_C_TASK, capture whether Class C was already running beforecalling
lorawan_api_class_c_enabled( true ):Step 3 — in
STOP_CLASS_C_TASKwhentmp == 0, replace the missing disable call withconditional teardown:
The restore of unicast Class C frequency and DR for native Class C devices is automatic. When
lorawan_api_multicast_c_stop_session()stops the last multicast session,lr1mac_class_c_multicast_stop_session()re-enablesrx_session_param[RX_SESSION_UNICAST]andcalls
rp_task_abort(). The RP abort callback re-enqueues vialr1mac_class_c_launch(), whichunconditionally reloads unicast parameters from
lr1_mac->rx2_data_rateandlr1_mac->rx2_frequency— the original unicast RX2 values that multicast sessions nevermodify. This works correctly as long as
class_c_obj->enabledremainstrue, which it doesbecause we no longer call
lorawan_api_class_c_enabled( false )for native Class C devices.Both changes are applied in
lorawan_remote_multicast_setup_package_v2.0.0.candlorawan_remote_multicast_setup_package_v1.0.0.c.Notes
on the normal timer-expiry path (without FUOTA), Class B/C is not correctly handled after the
last session stops. Both issues should be fixed together.
force_stop_class_bandforce_stop_class_cfunctions introduced as part of this issuefix reuse the same
tmp == 0logic and include the same conditional teardown, so the FUOTAcompletion path and the timer-expiry path behave consistently.
class_c_running_before_sessionflag is only ever set totrueand never cleared duringan active session, so multiple concurrent multicast groups on a native Class C device are
handled correctly.