The Live MQTT control is meant for live control. For sending schedules ahead of time, see Scheduled MQTT Control instead.
This guide will help you configure MQTT on your Sunlight Group EMS to remotely control and monitor battery and solar panel installations.
I have a Sunlight Group EMS that I'd like to setup for MQTT Remote Control.
Before continuing, make sure your network and devices are ready by following the MQTT Setup guide.
Request the required Eniris MQTT username and password through the distributor before starting the configuration. Support tickets and SmartgridX/Insights are not provisioning channels for new MQTT credentials.
The fallback mechanism timeout tells the Sunlight Group EMS how long it should wait for new commands. When the Sunlight Group EMS stops receiving commands, it automatically picks up the default strategy after this timeout.
After, select all devices that you'd wish to include in MQTT Remote Control.
The MQTT Remote Control interface has now been activated on the Sunlight Group EMS.
We are now ready to send some basics commands using a simple example. The Status column tells you if any command is active.
A good first starting point would be to test your newly setup integration with a simple example.
This test code does a simple job of continuously sending the following commands:
The Sunlight Group EMS continuously responds with a 'feedback' message containing the observed grid and asset power values. This feature is also included in this example.
Please download the file below in your preferred Python IDE. Fill in your serial number and the MQTT credentials requested through the distributor, then execute the script:
When the above is successful, you can continue with sending other type of commands. All commands are described in our MQTT Remote Control Documentation.
This section details the MQTT messaging format and payload requirements for remotely controlling power policies on devices within the Sunlight Group EMS's network.
The MQTT topic used for sending commands is structured as follows:
standard1/rp_one_s/remoteControlMetrics/'controller SN'
Where 'controller SN' should be replaced with the actual serial number of the Sunlight Group EMS you intend to control.
Commands are sent as JSON payloads. The payload structure is designed to specify various power management policies and setpoints for different components of the smart grid system. The following examples use fictional identifiers and illustrative limits. Replace the site node ID, timestamp and values with those appropriate for your installation:
{
"extraTags": {
"nodeId": "DEMO_CONTROLLER_SiteA"
},
"time": 1791547200,
"fields": {
"storage_policy": "setpoint",
"storage_power_setpoint_w": 5000,
"site_policy": "limit",
"site_import": 25000
}
}
Multiple device types (e.g. batteries + solar) can be controlled at the same time.
<Component>_policy (String): Policy type for the component. If omitted, a previous group policy can remain active until its original timeout. Use an explicit fallback policy to relinquish control.<Component>_power_setpoint_w (Float): Desired power setpoint in watts for the component. This is optional and only relevant if a corresponding policy is specified.Assets of the same type (e.g. two batteries) will be combined as one component. For example, when two 5 kWh batteries are installed, it will be treated as one 10 kWh battery.
Each component in the fields object can include a policy and a power setpoint. The following components can be controlled:
solar_policy and solar_power_setpoint_w:
storage_policy and storage_power_setpoint_w:
heat_pump_policy:
switched_load_policy:
variable_power_load_policy and variable_power_load_power_setpoint_w:
Instead of setting control strategies and setpoint, power limits can also be set for storage and solar devices.
For example:
{
"extraTags": {
"nodeId": "DEMO_CONTROLLER_SiteA"
},
"time": 1791547200,
"fields": {
"storage_policy": "limit",
"storage_maxChargePower_W": 10000,
"storage_maxDischargePower_W": 10000,
"solar_policy": "limit",
"solar_maxProductionPower_W": 15000
}
}
The field prefix is storage, solar or the applicable device node ID. Include the corresponding limit policy when sending limits-only commands, as above. Command limit values are non-negative magnitudes: for example, storage_maxDischargePower_W: 10000 means a discharge limit of 10 kW. This differs from feedback, where max_discharge_power_W is negative or zero.
The site can be controlled seperately. The following site commands can be sent to the controller:
default or fallback
Remove any active site commandsexport
Set the site export limitimport
Set the site import limitsetpoint
A site setpoint can vary up to 5% in both directionssetpoint_A
Not yet implementedThe following variables are peak-shaving parameters, and will only be applied when the battery strategy is set to peak-shaving.
startChargeBelow_WstopChargeAbove_WstartDischargeAbove_WstopDischargeBelow_WA site setpoint is NOT compatible with an import/export limit.
For example:
{
"extraTags": {
"nodeId": "DEMO_CONTROLLER_SiteA"
},
"time": 1791547200,
"fields": {
"storage_policy": "self-consumption",
"site_policy": "limit",
"site_export": 10000,
"site_import": 25000
}
}
Specific devices can also be controlled, instead of groups of devices based on their types. The message is identically structured:
nodeId_policy and nodeId_power_setpoint_wWhen two commands are sent to the same asset (e.g. one device-specific command to a solar inverter, and a command to all solar devices), the device-specific control method will take preference over the device type control.
Omitting a group policy is not an immediate cancellation: a previously received group command can remain active until its original configured timeout. Use an explicit fallback command for the group or device when you want to relinquish remote control. Device-specific commands are not preserved by the same group-command cache; see the empty-command warning below.
If no command is being send at all, after 60 seconds (or configured timeout period), the default policies for assets will be reactivated.
An active command can be cancelled by sending a fallback command message.
A fallback command will cancel the existing command immediately and the Sunlight Group EMS will take over control of the installation. The executed policy depends on what is set in the Sunlight Group EMS Settings.
This can also be used in cases where a secondary control signal, such as a schedule, is used as a fallback.
Message examples:
{
"extraTags": {
"nodeId": "DEMO_CONTROLLER_SiteA"
},
"time": 1791547200,
"fields": {
"storage_policy": "fallback"
}
}
An empty command returns aggregate site feedback, but it is not a read-only status request. It replaces the latest received message and can remove earlier device-specific commands. Still-valid group policies may be reused until their original fallback timeout expires. Do not use this as polling while expecting device-specific control to remain unchanged.
The empty command is structured as follows:
{
"extraTags": {
"nodeId": "DEMO_CONTROLLER_SiteA"
},
"time": 1791547200,
"fields": {}
}
Below is an example of a payload to set various policies and setpoints:
{
"extraTags": {
"nodeId": "DEMO_CONTROLLER_SiteA"
},
"time": 1714652046,
"fields": {
"solar_policy": "setpoint",
"solar_power_setpoint_w": 5000,
"storage_policy": "setpoint",
"storage_power_setpoint_w": -5000
}
}
In this example, solar production is limited to 5000 watts and the negative storage setpoint requests 5000 watts of discharge. A positive storage setpoint would request charging. Omitting a group policy does not necessarily cancel its earlier command; use an explicit fallback policy when you want to return that group to local control.
Subscribe to the feedback topic to read the site's state and the result of a processed command:
standard1/outbound/remoteControlMetrics/feedback/<Controller SN>
Replace <Controller SN> with the controller serial number. The serial number in the topic and the site's siteNodeId are different identifiers: always use the actual site node ID, which does not necessarily end in _site_0.
The standard live-control connection returns a state snapshot when it accepts a command and triggers control processing. This is not a confirmation that the physical device has already reached the new setpoint.
An empty command with "fields": {} also returns aggregate state, but it is not side-effect-free polling: it replaces the latest message and can remove previously targeted device-specific commands. Earlier group policies can remain active only within their original fallback timeout. Do not use empty commands to poll while expecting device-specific control to remain unchanged.
Some provider-specific MQTT connections also publish unsolicited feedback approximately every 30 seconds. This periodic publishing is connection-specific, not a guaranteed interval for every MQTT integration. Periodic snapshots and replies to empty commands do not include individual-device state.
Use the JSON Schema to validate feedback payloads, or the AsyncAPI document to describe the controller's feedback channel. Both describe feedback, not the complete command interface or provider-specific extensions.
| Field | Type | Meaning |
|---|---|---|
time | Integer | Feedback timestamp, in Unix seconds. |
requestTime | Integer | Timestamp copied from the command's integer time value. It is 0 when that value is absent or not an integer. Periodic integrations can use the snapshot timestamp here, so do not assume an unsolicited snapshot has a matching command. |
siteNodeId | String | Actual node ID of the site sending feedback. Do not construct it from the serial number. |
fields | Object | Empty object in feedback messages. |
data.responseCode | Integer | 0 for a successfully constructed feedback response; 1 for a processing error. The spelling and capitalization are significant. |
data.state | Object | State grouped by asset type. Present on success, absent on error. |
data.errors | String | Error/status text returned with successful feedback; "" when there is none. This is not an object or array. |
data.error | String | Error text returned with responseCode: 1, instead of state and errors. |
responseCode: 0 does not prove that every device reached its requested setpoint. Read measured power, executed setpoints, constraint labels and device error codes as well.
This illustrative snapshot uses fictional identifiers and a deliberately small selection of measurements. Other available fields are described below.
{
"time": 1791547201,
"requestTime": 1791547200,
"siteNodeId": "DEMO_CONTROLLER_SiteA",
"fields": {},
"data": {
"responseCode": 0,
"errors": "",
"state": {
"load": { "load_power_W": 12000 },
"grid": { "active_power_W": 7000, "import_limit_W": 25000 },
"storage": {
"active_power_W": -5000,
"mean_soc_perc": 60,
"max_charge_power_W": 10000,
"max_discharge_power_W": -10000,
"dynamic_max_charge_power_W": 8000,
"dynamic_max_discharge_power_W": -9000,
"executed_power_W": -5000,
"executed_policy": ["setpoint"],
"errorCodes": {},
"nr_devices": 1
}
}
}
}
On a processing error, do not try to read data.state:
{
"time": 1791547201,
"requestTime": 1791547200,
"siteNodeId": "DEMO_CONTROLLER_SiteA",
"fields": {},
"data": {
"responseCode": 1,
"error": "Command could not be processed"
}
}
The section names are grid, load, storage, solar, heat_pump, switched_load and variable_power_load. Each asset section combines devices of that type. For example, two solar devices with a capacity of 3 kW each give an aggregate capacity of 6 kW.
solar and storage can be empty objects ({}) when there are no matching devices. The heat-pump, switched-load and variable-power-load sections retain zero counts, empty arrays/maps and applicable nullable power values in that situation.
Treat a missing field or null as unavailable, not as zero. Many measurements are omitted when unknown; some fields explicitly contain null. A total can contain only the devices for which a value is available. Do not infer complete measurement coverage from nr_devices alone.
The following common fields apply to storage, solar, heat_pump, switched_load and variable_power_load:
| Field | Aggregate type | Meaning |
|---|---|---|
executed_policy | Array of strings and/or null entries | Distinct policies reported by the devices. The array can contain null for an unknown policy; its order does not identify devices. |
constraint_ph0_label | Array of strings, optional | One label per device that reports a constraint. Labels can repeat. The key is omitted if no device reports one. Use individual-device feedback to attribute a label. |
errorCodes | Object | Maps node IDs to arrays of device error/status entries: { "DEMO_BATTERY_1": [...] }. It is {} when no such entries are reported. |
nr_devices | Integer | Number of devices represented by this section, not the number with valid measurements. |
individual_devices | Object, conditional | Per-device feedback, keyed by node ID; see Individual-device feedback. |
Every measurement or limit in state.grid is optional and is omitted when unknown. Values are numbers; power is in watts, energy in watt-hours and current in amperes.
| Field | Meaning |
|---|---|
active_power_W | Grid active power. Positive means import; negative means export. |
import_limit_W | Present grid import limit. |
export_limit_W | Present grid export limit. |
nominal_current_A | Nominal current configured for the site's electrical protection. |
today_imported_energy_Wh | Energy imported today, when available. |
today_exported_energy_Wh | Energy exported today, when available. |
total_imported_energy_Wh | Cumulative imported energy. |
total_exported_energy_Wh | Cumulative exported energy. |
today_produced_energy_Wh | Production energy today, for a meter/site that provides production readings. |
total_produced_energy_Wh | Cumulative production energy, when provided. |
state.load.load_power_W is a number or null. It reports other site consumption, excluding separately accounted EV charging and heat-pump consumption. It is not total site consumption or another name for grid power.
| Field | Type / availability | Meaning |
|---|---|---|
energy_capacity_Wh | Number, optional | Combined available battery capacity metadata. |
energy_stored_Wh | Number, optional | Current stored energy. |
mean_soc_perc | Number, optional | Capacity-weighted state of charge in percent. A 10 kWh battery at 20% and a 20 kWh battery at 50% give 40%. |
mean_min_soc_perc | Number, optional | Capacity-weighted minimum state-of-charge limit in percent. |
mean_max_soc_perc | Number, optional | Capacity-weighted maximum state-of-charge limit in percent. |
active_power_W | Number, optional | Measured battery power: positive for charging, negative for discharging. |
max_charge_power_W | Number | Combined static charge limit, positive or zero. |
max_discharge_power_W | Number | Combined static discharge limit, negative or zero. For example, -53000 represents up to 53 kW of discharge. |
dynamic_max_charge_power_W | Number | Estimated present charge capability, positive or zero, accounting for available device limits and state-of-charge headroom. |
dynamic_max_discharge_power_W | Number | Estimated present discharge capability, negative or zero, accounting for available device limits and state-of-charge headroom. |
executed_power_W | Number, optional | Combined power setpoint sent to the battery devices, rather than measured power. |
today_charged_Wh | Number, optional | Energy charged into the batteries today. |
today_discharged_Wh | Number, optional | Energy discharged from the batteries today. |
total_charged_Wh | Number, optional | Cumulative energy charged. |
total_discharged_Wh | Number, optional | Cumulative energy discharged. |
Use dynamic_max_charge_power_W and dynamic_max_discharge_power_W for the current feedback format. They replace the former realised_charge_power_W and realised_discharge_power_W fields. Dynamic capability is an estimate, not a guarantee that every requested setpoint can be executed; site and device constraints still apply.
These numeric fields are omitted when unavailable:
| Field | Meaning |
|---|---|
active_power_W | Measured solar-device power. |
capacity_W | Combined nominal solar capacity. |
executed_power_W | Combined power setpoint sent to the solar devices. |
today_energy_Wh | Energy produced today. |
total_energy_Wh | Cumulative energy produced. |
| Field | Type | Meaning |
|---|---|---|
active_power_W | Number or null | Combined measured heat-pump power; null when unavailable. |
executed_power_W | Number or null | Combined expected/requested heat-pump power; null when unavailable. |
operation_modes | Array of modes or null entries | Distinct reported operating modes, such as blocking, boosting or self-control. Can contain null when a mode is unknown. |
| Field | Type / availability | Meaning |
|---|---|---|
active_power_W | Number, optional | Combined measured switched-load power. |
devices_on | Integer | Number of devices reporting an on state. |
devices_off | Integer | Number counted as off; an unknown on/off state is counted as off. |
executed_power_W | Number, optional | Combined expected/requested switched-load power. |
The section is named variable_power_load, not variable_load.
| Field | Type / availability | Meaning |
|---|---|---|
active_power_W | Number, optional | Combined EV/variable-load power, not grid power. |
ev_charging | Integer | Devices reporting that an EV requires charge. This does not mean that current is actually flowing. |
ev_not_charging | Integer | Devices not reporting a need to charge, including chargers without a connected vehicle. |
executed_power_W | Number or null | Combined power setpoint; null when unavailable. |
currentL1_A, currentL2_A, currentL3_A | Number, optional | Combined measured current on each phase. Each phase field can be omitted independently. |
executed_current_A | Number, optional | Combined requested current. |
Charging energy counters, current limits, pause support and charging sessions belong to individual devices, not this aggregate section.
To include a device's state, the command must contain its <nodeId>_policy field and target a recognized device. A device-specific setpoint on its own does not request individual-device feedback. Neither an empty command nor a periodic snapshot includes it.
The location is:
data.state.<section>.individual_devices.<nodeId>
For example, a command containing both "DEMO_BATTERY_1_policy": "setpoint" and "DEMO_BATTERY_1_power_setpoint_w": -5000 can produce this fragment at data.state.storage:
{
"executed_policy": ["setpoint"],
"errorCodes": {},
"nr_devices": 1,
"individual_devices": {
"DEMO_BATTERY_1": {
"active_power_W": -5000,
"executed_power_W": -5000,
"executed_policy": "setpoint",
"constraint_ph0_label": null,
"nr_devices": 1
}
}
}
Per-device policy, mode and constraint fields are single values, not aggregate lists. Unknown measurements can be null. The device's errorCodes is an array of error/status entries and is included only when that device reports entries; it is not another node-ID map.
| Device type | Fields available in its individual state |
|---|---|
| Solar | active_power_W, today_energy_Wh, total_energy_Wh, capacity_W, executed_power_W, executed_policy, constraint_ph0_label, nr_devices. |
| Storage | energy_capacity_Wh, mean_soc_perc, energy_stored_Wh, max_charge_power_W, max_discharge_power_W, dynamic_max_charge_power_W, dynamic_max_discharge_power_W, mean_min_soc_perc, mean_max_soc_perc, active_power_W, executed_power_W, executed_policy, constraint_ph0_label, today_charged_Wh, today_discharged_Wh, total_charged_Wh, total_discharged_Wh, nr_devices. |
| Heat pump | active_power_W, operation_modes, executed_power_W, executed_policy, constraint_ph0_label, nr_devices. Despite its plural name, operation_modes is a single mode here. |
| Switched load | active_power_W, devices_on, devices_off, executed_power_W, executed_policy, constraint_ph0_label, nr_devices. The on/off counts are 0 or 1. |
| EV / variable-power load | active_power_W, ev_requiring_charge, executed_power_W, today_charged_Wh, today_discharged_Wh, total_charged_Wh, total_discharged_Wh, currentL1_A, currentL2_A, currentL3_A, executed_current_A, executed_policy, min_charge_current_A, max_charge_current_A, allow_zero_current, current_charging_session, previous_charging_session, constraint_ph0_label, nr_devices. |
| Site, where exposed by the connection | The available fields from the grid reference above, under state.site.individual_devices. Power-meter feedback uses the same field definitions where an integration exposes it; the ordinary live-control connection does not provide general direct addressing of power meters. |
In the current per-device battery response, mean_soc_perc contains a fraction despite its name: 0.6 means 60%. The aggregate storage.mean_soc_perc contains a percentage: 60 means 60%. The per-device minimum and maximum SoC fields remain percentages. Do not apply the same conversion to both levels.
| Field | Type | Meaning |
|---|---|---|
ev_requiring_charge | Boolean | Whether this device reports an EV that requires charge. |
today_charged_Wh, total_charged_Wh | Number or null | Energy charged into the vehicle today / cumulatively. |
today_discharged_Wh, total_discharged_Wh | Number or null | Energy discharged from the vehicle today / cumulatively, when supported. |
min_charge_current_A, max_charge_current_A | Number or null | Configured minimum and maximum charging current. |
allow_zero_current | Boolean or null | Whether pausing with zero charging current is allowed; null if unknown. |
current_charging_session | Object | Current session, or {} when unavailable. The key is singular. |
previous_charging_session | Object | Previous session, or {} when unavailable. |
A populated charging-session object contains:
| Field | Type | Meaning |
|---|---|---|
pluginTime | String | Plug-in time as an ISO 8601 date-time. |
endTime | String or null | Session end time, if known. |
firstChargingStartTime | String or null | First charging start time, if known. |
isFull | Boolean | Whether the session reports the vehicle as full. |
chargedEnergy_Ws | Number | Energy charged during this session in watt-seconds. Divide by 3600 for watt-hours. |
usedPhases | Array of strings or null | Phases used during charging, if known. |
historicMaxChargeCurrent_A | Number | Highest charging current recorded during the session. |
vpp_id belongs to a separately configured VPP connection. Do not add it to ordinary live MQTT configuration or commands; the standard feedback state does not require it. Provider-specific additions must not be treated as part of the general live-control interface.
The setpoints sent by an external signal can be internally limited by the EMS. The table below gives an overview of possible constraints for the constraint_ph0_label feedback field.
| Label | Node Types | Description |
|---|---|---|
breaker_current | Site | Site level breaker current |
max_charge_current (EV) | EV | Maximum charge current limit. |
min_charge_current | EV | Minimum charge current limit. |
nom_current | EV | Device nominal current limit. |
max_export_power | Site | Site export limit |
max_import_power | Site | Site import limit |
max_charge_power | EV, Storage | Maximum charge power limit. |
max_discharge_power | Storage | Maximum discharge power limit. |
nom_charge_power | EV, Storage | Nominal charge power limit. |
nom_discharge_power | Storage | Nominal discharge power limit. |
nom_production_power | PV | Nominal production power limit. |
device_reported_max_charge_power | Storage | Device maximum charge power limit. |
device_reported_max_charge_current | Storage | Device maximum charge current limit. |
device_reported_max_discharge_power | Storage | Device maximum discharge power limit. |
device_reported_max_discharge_current | Storage | Device maximum charge current limit. |
ev_charging_suspended | EV | EV Charging is paused |
setpoint_power | All | The setpoint is the limiting factor - no internal constraints. |
high_soc_limit_charge_power | Storage | Charging is limited by the high battery SOC. |
low_soc_limit_discharge_power | Storage | Discharging is limited by the low battery SOC. |
soc_power_curve_max_charge_power | Storage | Charging is limited by the SOC power curve. |
soc_power_curve_max_discharge_power | Storage | Discharging is limited by the SOC power curve. |
too_low_soc_force_charge_power | Storage | Minimum charging power due to a too low SOC. |
peakshaving_charge_threshold | Storage | Charge due to peak-shaving-parameters. |
peakshaving_discharge_threshold | Storage | Discharge due to peak-shaving-parameters. |
self_consumption_charge_power | EV, Storage | Charge due to the self-consumption strategy. |
self_consumption_discharge_power | EV, Storage | Discharge due to the self-consumption strategy. |
self_consumption_load_power | Switched Load | Consume due to the self-consumption strategy. |
external_signal_device_power_consumption_limit | EV, Switched Load | External signal limits the device consumption. |
external_signal_device_power_production_limit | PV | External signal limits the device production. |
external_signal_battery_charge_power_limit | Storage | External signal limits the battery charging power. |
external_signal_battery_discharge_power_limit | Storage | External signal limits the battery discharging power. |
setpoint_current | EV | Current Setpoint limit. |
observed_max_current_demand | EV | Observed max current used by EV. |
near_fully_charged | EV | Current limited by a nearly full EV. |
fully_charged | EV | The EV is fully charged |
dynamic_derated_min_power | Storage | Setpoint is limited due to the device not being able to reach the expected setpoint power. |
dynamic_derated_max_power | Storage | Setpoint is limited due to the device not being able to reach the expected setpoint power. |
Error codes are grouped into ranges that indicate the general category of the problem:
| Range | Category |
|---|---|
-200 | Connection errors |
-300 | Response errors |
-500 | Driver errors |
-600 | Control errors |
The individual error codes are listed below.
Connection issues between the EMS and the device.
| Code | Label | Additional Fields |
|---|---|---|
-204 | Connection Refused | |
-209 | No Route To Host | |
-210 | No IP For MAC Address |
The device is connected, but its responses are invalid.
| Code | Label | Additional Fields |
|---|---|---|
-300 | Device Unavailable Error | |
-310 | No Response Error | |
-402 | Device Status Error | Message |
| Code | Label | Additional Fields |
|---|---|---|
-600 | Setpoint Ignored Error | |
-602 | Setpoint Rejected Error |
| Code | Label | Additional Fields |
|---|---|---|
-1000 | No Measurements | |
-1001 | No Power Measurements | |
-1003 | No Power Setpoint | |
-1005 | Setpoints Not Followed | nomPowerDeviation_frac, setpointDeviation_frac |
-1006 | Limits Not Followed |
When using MQTT, it is important to consider the differences in specifications between versions 3.1, 3.1.1, and 5.0, particularly regarding the broker’s behavior when clients publish to unauthorized topics.
According to the MQTT 3.1.1 specification (see OASIS MQTT 3.1.1 Specification, section MQTT-3.3.5-2), a broker must terminate the connection as soon as a client sends a PUBLISH to a topic for which it does not have permission. This behavior can lead to unexpected disconnections for clients attempting to publish to misconfigured or unauthorized topics.
In MQTT 3.1, this requirement is not present. When a client publishes to an unauthorized topic under this version, the broker typically ignores the message (silent drop) without terminating the connection. This makes MQTT 3.1 in some cases more suitable when robustness against configuration errors or temporarily missing permissions is more important than strict security enforcement.
Although MQTT 5.0 introduces the ability to work with reason codes (such as PUBACK with a refusal reason), this requires support on both the client and server sides. Migrating to MQTT 5.0 therefore involves additional implementation effort.
Consequences of Ignoring Compatibility: If a client connects using MQTT 3.1.1 and attempts to publish messages to unauthorized topics, the broker will abruptly terminate the session. This may lead to instability, loss of connectivity, or increased load due to repeated reconnect attempts.
Recommended Approach: For systems where clients may (temporarily) attempt to publish to unauthorized topics, or where error handling is not strictly implemented, we recommend using MQTT 3.1. This ensures more stable connections and avoids unintended disconnects during runtime.