Broadcast Communication

By default, communication occurs between all satellites that are specified by the communication method. This tutorial shows how to use two classes to configure a broadcast action that must be taken for communication to occur:

  • Broadcast, which gives satellites an action that enables communication from them at the end of the current step.

  • BroadcastCommunication, which can be combined with another communication method to limit communication from broadcasters to those satellites satisfying the requirements of the other communication method.

Configuring the Environment

For this example, a multisatellite target imaging environment will be used. The goal is to maximize the value of unique images taken. This configuration is similar to the Multi-Agent Environments example.

[1]:
from typing import ClassVar

from bsk_rl import act, comm, data, obs, sats, scene
from bsk_rl.sim import dyn, fsw


class ImagingSatellite(sats.ImagingSatellite):
    observation_spec: ClassVar[list[obs.Observation]] = [
        obs.OpportunityProperties(
            dict(prop="priority"),
            dict(prop="opportunity_open", norm=5700.0),
            n_ahead_observe=4,
        )
    ]
    action_spec: ClassVar[list[act.Action]] = [act.Broadcast(duration=15.0), act.Image(n_ahead_image=4)]
    dyn_type = dyn.FullFeaturedDynModel
    fsw_type = fsw.SteeringImagerFSWModel


ACTION_BROADCAST = 0
ACTION_IMAGE_0 = 1
ACTION_IMAGE_1 = 2
ACTION_IMAGE_2 = 3
ACTION_IMAGE_3 = 4

Satellite properties are set to give the satellite near-unlimited power and storage resources. To randomize some parameters in a correlated manner across satellites, a sat_arg_randomizer is set and passed to the environment. In this case, the satellites are distributed in a trivial single-plane Walker-delta constellation.

[2]:
from bsk_rl.utils.orbital import walker_delta_args

N_AGENTS = 2
sat_args = dict(
    imageAttErrorRequirement=0.01,
    imageRateErrorRequirement=0.01,
    batteryStorageCapacity=1e9,
    storedCharge_Init=1e9,
    dataStorageCapacity=1e12,
    u_max=0.4,
    K1=0.25,
    K3=3.0,
    omega_max=0.087,
    servo_Ki=5.0,
    servo_P=150 / 5,
)
sat_arg_randomizer = walker_delta_args(
    altitude=800.0, inc=60.0, n_planes=1, clustersize=N_AGENTS, clusterspacing=5.0
)

A communication type is defined that uses multidegree line-of-sight communication with broadcasting.

[3]:
class BroadcastLOS(comm.BroadcastCommunication, comm.LOSMultiCommunication):
    pass

Finally, the environment can be instantiated.

[4]:
from bsk_rl import ConstellationTasking

env = ConstellationTasking(
    satellites=[ImagingSatellite(f"EO-{i + 1}", sat_args) for i in range(N_AGENTS)],
    scenario=scene.UniformTargets(1000),
    rewarder=data.UniqueImageReward(),
    communicator=BroadcastLOS(),
    sat_arg_randomizer=sat_arg_randomizer,
    log_level="INFO",
)
_ = env.reset()
2026-09-02 14:51:32,102 gym                            INFO       Resetting environment with seed=117868976
2026-09-02 14:51:32,104 scene.targets                  INFO       Generating 1000 targets
2026-09-02 14:51:32,163 sats.satellite.EO-1            INFO       <0.00> EO-1: Finding opportunity windows from 0.00 to 600.00 seconds
2026-09-02 14:51:32,195 sats.satellite.EO-2            INFO       <0.00> EO-2: Finding opportunity windows from 0.00 to 600.00 seconds
2026-09-02 14:51:32,224 gym                            INFO       <0.00> Environment reset

In the first step, both agents are tasked with an imaging action, and one successfully images a target.

[5]:
_ = env.step({"EO-1": ACTION_IMAGE_3, "EO-2": ACTION_IMAGE_2})
2026-09-02 14:51:32,229 gym                            INFO       <0.00> === STARTING STEP ===
2026-09-02 14:51:32,230 sats.satellite.EO-1            INFO       <0.00> EO-1: target index 3 tasked
2026-09-02 14:51:32,231 sats.satellite.EO-1            INFO       <0.00> EO-1: Target(tgt-298) tasked for imaging
2026-09-02 14:51:32,232 sats.satellite.EO-1            INFO       <0.00> EO-1: Target(tgt-298) window enabled: 280.5 to 373.2
2026-09-02 14:51:32,232 sats.satellite.EO-1            INFO       <0.00> EO-1: setting timed terminal event at 373.2
2026-09-02 14:51:32,233 sats.satellite.EO-2            INFO       <0.00> EO-2: target index 2 tasked
2026-09-02 14:51:32,234 sats.satellite.EO-2            INFO       <0.00> EO-2: Target(tgt-298) tasked for imaging
2026-09-02 14:51:32,235 sats.satellite.EO-2            INFO       <0.00> EO-2: Target(tgt-298) window enabled: 184.1 to 296.3
2026-09-02 14:51:32,235 sats.satellite.EO-2            INFO       <0.00> EO-2: setting timed terminal event at 296.3
2026-09-02 14:51:32,266 sats.satellite.EO-2            INFO       <187.00> EO-2: imaged Target(tgt-298)
2026-09-02 14:51:32,267 data.base                      INFO       <187.00> Total reward: {'EO-2': 0.4647482490566316}
2026-09-02 14:51:32,269 sats.satellite.EO-2            INFO       <187.00> EO-2: Satellite EO-2 requires retasking
2026-09-02 14:51:32,271 gym                            INFO       <187.00> Step reward: {'EO-2': 0.4647482490566316}

When both select the broadcast action, data is shared in both directions. This is subject to line-of-sight availability as well as selecting the correct action.

[6]:
_ = env.step({"EO-1": ACTION_BROADCAST, "EO-2": ACTION_BROADCAST})
2026-09-02 14:51:32,276 gym                            INFO       <187.00> === STARTING STEP ===
2026-09-02 14:51:32,277 sats.satellite.EO-1            INFO       <187.00> EO-1: setting timed terminal event at 202.0
2026-09-02 14:51:32,278 sats.satellite.EO-2            INFO       <187.00> EO-2: setting timed terminal event at 202.0
2026-09-02 14:51:32,281 sats.satellite.EO-1            INFO       <202.00> EO-1: timed termination at 202.0 for broadcast
2026-09-02 14:51:32,281 sats.satellite.EO-2            INFO       <202.00> EO-2: timed termination at 202.0 for broadcast
2026-09-02 14:51:32,282 data.base                      INFO       <202.00> Total reward: {}
2026-09-02 14:51:32,283 comm.communication             INFO       <202.00> Communicating data in 2 directions.
2026-09-02 14:51:32,283 comm.communication             INFO       <202.00> Optimizing data communication between all pairs of satellites
2026-09-02 14:51:32,284 sats.satellite.EO-1            INFO       <202.00> EO-1: Satellite EO-1 requires retasking
2026-09-02 14:51:32,284 sats.satellite.EO-2            INFO       <202.00> EO-2: Satellite EO-2 requires retasking
2026-09-02 14:51:32,287 gym                            INFO       <202.00> Step reward: {}

One agent can broadcast while the others are completing a different task.

[7]:
_ = env.step({"EO-1": ACTION_IMAGE_2, "EO-2": ACTION_BROADCAST})  # Agent 1 broadcasts
2026-09-02 14:51:32,292 gym                            INFO       <202.00> === STARTING STEP ===
2026-09-02 14:51:32,293 sats.satellite.EO-1            INFO       <202.00> EO-1: target index 2 tasked
2026-09-02 14:51:32,293 sats.satellite.EO-1            INFO       <202.00> EO-1: Target(tgt-381) tasked for imaging
2026-09-02 14:51:32,294 sats.satellite.EO-1            INFO       <202.00> EO-1: Target(tgt-381) window enabled: 305.0 to 402.8
2026-09-02 14:51:32,294 sats.satellite.EO-1            INFO       <202.00> EO-1: setting timed terminal event at 402.8
2026-09-02 14:51:32,295 sats.satellite.EO-2            INFO       <202.00> EO-2: setting timed terminal event at 217.0
2026-09-02 14:51:32,299 sats.satellite.EO-2            INFO       <217.00> EO-2: timed termination at 217.0 for broadcast
2026-09-02 14:51:32,299 data.base                      INFO       <217.00> Total reward: {}
2026-09-02 14:51:32,300 comm.communication             INFO       <217.00> Communicating data in 1 direction.
2026-09-02 14:51:32,301 sats.satellite.EO-2            INFO       <217.00> EO-2: Satellite EO-2 requires retasking
2026-09-02 14:51:32,303 gym                            INFO       <217.00> Step reward: {}