> This is the Markdown version of the page. For the complete Lattice Developers documentation index, fetch https://developer.anduril.com/llms.txt. Append .md to any page URL for its clean Markdown. # Watch entities > Monitoring entities in Lattice using the Lattice SDK This shows how to use the SDK to fetch entities from Lattice and stream real-time information about entity components. Complete the steps to learn how to use the following APIs: * [`GetEntity`](/reference/rest/entities/get-entity) -- Retrieves a single entity from Lattice. * [`StreamEntities`](/reference/rest/entities/stream-entities) -- Establishes a persistent connection to stream entity events. ## Before you begin * To configure your app to watch entities, [set up the Lattice SDK](/guides/getting-started/set-up). * Learn about required components and various [entity shapes](/guides/entities/overview) in Lattice. #### gRPC authentication These gRPC examples authenticate with a static environment token attached as request metadata. If you are using OAuth 2.0 client credentials instead, [set up the token refresh module](/guides/getting-started/authenticate#client-credentials) before running the examples on this page. ## Get an entity Get details of a specific entity using [`entity_ID`](/reference/rest/entities/publish-entity#request.body.entityId) and the [`GetEntity`](/reference/rest/entities/get-entity) API: #### Get the entity ID Open the Lattice UI and find the entity in the Entity Explorer. Copy its ID from the **Entity ID** column: ![Shows the Entity Explorer in the Lattice Developer Console.](/_fern-img/72c242c7f0277f5a308bd2efb784cfcd9675f4dab9d1790bd9b195c6df74c7d0.webp) #### Get the entity object Use the `GetEntity` API action to retrieve a single entity object from Lattice. Replace `$ENTITY_ID` in the following example with the entity ID you copied in the previous step: ```go title={"Go (REST)"} startLine={35} maxLines={20} package main import ( "context" "fmt" "net/http" "os" "time" Lattice "github.com/anduril/lattice-sdk-go/v5" "github.com/anduril/lattice-sdk-go/v5/client" "github.com/anduril/lattice-sdk-go/v5/option" ) func main() { latticeEndpoint := os.Getenv("LATTICE_ENDPOINT") environmentToken := os.Getenv("ENVIRONMENT_TOKEN") // Remove sandboxesToken from the following statements if you are not developing on Sandboxes. sandboxesToken := os.Getenv("SANDBOXES_TOKEN") if latticeEndpoint == "" || environmentToken == "" || sandboxesToken == "" { fmt.Println("Missing required environment variables") os.Exit(1) } headers := http.Header{} headers.Add("Anduril-Sandbox-Authorization", fmt.Sprintf("Bearer %s", sandboxesToken)) LatticeClient := client.NewClient( option.WithToken(environmentToken), option.WithBaseURL(fmt.Sprintf("https://%s", latticeEndpoint)), option.WithHTTPHeader(headers), ) for { ctx := context.Background() entity, err := LatticeClient.Entities.GetEntity(ctx, &Lattice.GetEntityRequest{EntityID: ""}) if err != nil { fmt.Printf("Error fetching entity: %v\n", err) } else { fmt.Printf("Asset name | %s\n", *entity.GetAliases().GetName()) fmt.Printf("Asset location | %f, %f\n", *entity.GetLocation().GetPosition().GetLatitudeDegrees(), *entity.GetLocation().GetPosition().GetLongitudeDegrees()) } time.Sleep(5 * time.Second) } } ``` ```java title={"Java (REST)"} startLine={28} maxLines={20} package org.example; import com.anduril.Lattice; import com.anduril.types.Entity; import java.time.LocalDateTime; public class GetEntityEnvToken { private static final String ENTITY_ID = ""; public static void main(String[] args) { String endpoint = System.getenv("LATTICE_ENDPOINT"); String environmentToken = System.getenv("ENVIRONMENT_TOKEN"); // Remove sandboxesToken from the following statements if you are not developing on Sandboxes. String sandboxesToken = System.getenv("SANDBOXES_TOKEN"); if (endpoint == null || environmentToken == null || sandboxesToken == null) { System.err.println("Missing required environment variables"); System.exit(1); } if (!endpoint.startsWith("https://")) endpoint = "https://" + endpoint; try { Lattice client = Lattice.builder() .url(endpoint) .token(environmentToken) .addHeader("Anduril-Sandbox-Authorization", "Bearer " + sandboxesToken) .build(); while (true) { try { Entity entity = client.entities().getEntity(ENTITY_ID); System.out.println("Timestamp: " + LocalDateTime.now()); System.out.println("Asset name: " + entity.getAliases()); if (entity.getLocation() != null) { System.out.println("Location: " + entity.getLocation()); } System.out.println("---------------"); Thread.sleep(5000); } catch (Exception e) { System.err.println("Error: " + e.getMessage()); Thread.sleep(10000); } } } catch (Exception e) { System.err.println("Failed to initialize: " + e.getMessage()); } } } ``` ```py title={"Python (REST)"} startLine={21} maxLines={20} from anduril import Lattice import asyncio import os import sys lattice_endpoint = os.getenv('LATTICE_ENDPOINT') environment_token = os.getenv('ENVIRONMENT_TOKEN') # Remove sandboxes_token from the following statements if you are not developing on Sandboxes. sandboxes_token = os.getenv('SANDBOXES_TOKEN') if not environment_token or not lattice_endpoint or not sandboxes_token: print("Missing required environment variables.") sys.exit(1) client = Lattice( base_url=f"https://{lattice_endpoint}", token=lambda: str(environment_token), headers={ "anduril-sandbox-authorization": f"Bearer {sandboxes_token}" } ) async def app(entity_id): try: while(True): entity = client.entities.get_entity(entity_id=entity_id) if entity.aliases: print(f"Asset name | {entity.aliases.name}") if entity.location and entity.location.position: print(f"Asset location | {entity.location.position.latitude_degrees}, {entity.location.position.longitude_degrees}") await asyncio.sleep(5) except (asyncio.CancelledError, KeyboardInterrupt): print(">>>Exiting...") except Exception as error: print(f"Exception: {error}") if __name__ == "__main__": asyncio.run(app("")) ``` ```ts title={"Typescript (REST)"} startLine={17} maxLines={20} import { LatticeClient } from "@anduril-industries/lattice-sdk"; const latticeEndpoint = process.env.LATTICE_ENDPOINT; const environmentToken = process.env.ENVIRONMENT_TOKEN; // Remove sandboxesToken from the following statements if you are not developing on Sandboxes. const sandboxesToken = process.env.SANDBOXES_TOKEN; if (!latticeEndpoint || !environmentToken || !sandboxesToken) { console.log('Missing required environment variables.'); process.exit(1); } const client = new LatticeClient({ baseUrl: `https://${latticeEndpoint}`, token: environmentToken, headers: { "Anduril-Sandbox-Authorization": `Bearer ${sandboxesToken}` } }); async function App(entityId: string) { try { const entity = await client.entities.getEntity({ entityId }); if (entity && Object.keys(entity).length > 0) { console.log(`Asset name | ${entity.aliases?.name}`); console.log(`Asset location | ${entity.location?.position?.latitudeDegrees}, ${entity.location?.position?.longitudeDegrees}`); } else { console.log('Entity object is empty.'); } } catch (error) { console.log(`Encountered the following error while fetching entity: ${error}`); } } (async function runIndefinitely() { while (true) { await App(""); await new Promise(resolve => setTimeout(resolve, 1000)); } })(); ``` ```go title={"Go (gRPC)"} startLine={62} maxLines={20} // This Go example is compatible with artifacts generated using // the following grpc/go plugin: https://buf.build/anduril/lattice-sdk/sdks/main:grpc/go package main import ( "context" "log" "os" "time" "schema-registry.developer.anduril.com/gen/go/anduril/lattice-sdk/grpc/go/anduril/entitymanager/v1/entitymanagerv1grpc" entitymanagerv1 "schema-registry.developer.anduril.com/gen/go/anduril/lattice-sdk/protocolbuffers/go/anduril/entitymanager/v1" "google.golang.org/grpc" "google.golang.org/grpc/credentials" ) type BearerTokenAuth struct { Token string SandboxesToken string } func (b *BearerTokenAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { return map[string]string{ "authorization": "Bearer " + b.Token, "anduril-sandbox-authorization": "Bearer " + b.SandboxesToken, }, nil } func (b *BearerTokenAuth) RequireTransportSecurity() bool { return true } func main() { ctx := context.Background() environmentToken := os.Getenv("ENVIRONMENT_TOKEN") latticeEndpoint := os.Getenv("LATTICE_ENDPOINT") // Remove sandboxesToken from the following statements if you are not developing on Sandboxes. sandboxesToken := os.Getenv("SANDBOXES_TOKEN") if latticeEndpoint == "" || environmentToken == "" || sandboxesToken == "" { log.Fatalf("Missing required environment variables") } auth := &BearerTokenAuth{ Token: environmentToken, SandboxesToken: sandboxesToken, } opts := []grpc.DialOption{ grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")), grpc.WithPerRPCCredentials(auth), } conn, err := grpc.NewClient(latticeEndpoint, opts...) if err != nil { log.Fatalf("Did not connect: %v", err) } defer conn.Close() client := entitymanagerv1grpc.NewEntityManagerAPIClient(conn) // Periodically get the latest entity state for { entity, err := client.GetEntity(ctx, &entitymanagerv1.GetEntityRequest{ EntityId: "Demo-Sim-Asset1", }) if err != nil { log.Fatalf("Error getting entity: %v", err) } // Process each entity. log.Printf("Fetching entity at location: %f %f", entity.GetEntity().GetLocation().GetPosition().LatitudeDegrees, entity.GetEntity().GetLocation().GetPosition().LongitudeDegrees) time.Sleep(5 * time.Second) } } ``` ```py title={"Python (gRPC)"} startLine={57} maxLines={20} # This Python example is compatible with artifacts generated using the following # Buf plugins: https://schema-registry.developer.anduril.com/anduril/lattice-sdk/sdks/main:bufbuild/py # and https://schema-registry.developer.anduril.com/anduril/lattice-sdk/sdks/main:connectrpc/py import os import sys import time from connectrpc.interceptor import MetadataInterceptorSync from connectrpc.protocol import ProtocolType from connectrpc.request import RequestContext from pyqwest import SyncClient, SyncHTTPTransport from anduril.entitymanager.v1.entity_manager_api_pub_pb import GetEntityRequest from anduril.entitymanager.v1.entity_manager_api_pub_connect import EntityManagerAPIClientSync class BearerTokenInterceptor(MetadataInterceptorSync): """Connect interceptor that adds bearer token headers to requests.""" def __init__(self, token: str, sandboxes_token: str): self.token = token self.sandboxes_token = sandboxes_token def on_start_sync(self, ctx: RequestContext) -> None: ctx.request_headers["authorization"] = f"Bearer {self.token}" ctx.request_headers["anduril-sandbox-authorization"] = ( f"Bearer {self.sandboxes_token}" ) return None def on_end_sync(self, token, ctx: RequestContext, error) -> None: return def main(): # Load environment variables environment_token = os.getenv("ENVIRONMENT_TOKEN") lattice_endpoint = os.getenv("LATTICE_ENDPOINT") # Remove sandboxes_token from the following statements if you are not developing on Sandboxes. sandboxes_token = os.getenv("SANDBOXES_TOKEN") if not environment_token or not lattice_endpoint or not sandboxes_token: print("Missing required environment variables", file=sys.stderr) sys.exit(1) # Create the EntityManager API client over Connect gRPC with a bearer token # interceptor. pyqwest trusts no certificates by default, so enable the # system certificate pool for TLS. http_client = SyncClient(transport=SyncHTTPTransport(tls_include_system_certs=True)) client = EntityManagerAPIClientSync( f"https://{lattice_endpoint}", http_client=http_client, interceptors=[BearerTokenInterceptor(environment_token, sandboxes_token)], protocol=ProtocolType.GRPC, ) # Periodically get the latest entity state while True: try: request = GetEntityRequest(entity_id="") # Call the GetEntity RPC response = client.get_entity(request) # Log entity location if available if ( response.entity and response.entity.location and response.entity.location.position ): position = response.entity.location.position print( f"Fetching entity at location: " f"{position.latitude_degrees} {position.longitude_degrees}" ) except Exception as error: print(f"Error getting entity: {error}", file=sys.stderr) time.sleep(5) if __name__ == "__main__": main() ``` ```rs title={"Rust (gRPC)"} startLine={61} maxLines={20} // This Rust example is compatible with artifacts generated using // the following grpc/rust plugin: https://buf.build/anduril/lattice-sdk/sdks/main:community/neoeinstein-tonic use anduril_lattice_sdk_community_neoeinstein_tonic::anduril::entitymanager::v1::tonic::entity_manager_api_client::EntityManagerApiClient; use anduril_lattice_sdk_community_neoeinstein_prost::anduril::entitymanager::v1::GetEntityRequest; use tonic::metadata::MetadataValue; use tonic::transport::{Channel, ClientTlsConfig}; use tonic::Request; use std::env; use std::time::Duration; /// Main application entry point #[tokio::main] async fn main() -> Result<(), Box> { // Load environment variables let environment_token = env::var("ENVIRONMENT_TOKEN") .expect("ENVIRONMENT_TOKEN environment variable not set"); let lattice_endpoint = env::var("LATTICE_ENDPOINT") .expect("LATTICE_ENDPOINT environment variable not set"); // Remove sandboxes_token from the following statements if you are not developing on Sandboxes. let sandboxes_token = env::var("SANDBOXES_TOKEN") .expect("SANDBOXES_TOKEN environment variable not set"); // Validate required environment variables if environment_token.is_empty() || lattice_endpoint.is_empty() || sandboxes_token.is_empty() { eprintln!("Missing required environment variables:"); eprintln!(" ENVIRONMENT_TOKEN"); eprintln!(" LATTICE_ENDPOINT"); eprintln!(" SANDBOXES_TOKEN"); std::process::exit(1); } // Parse tokens into metadata values let auth_header: MetadataValue<_> = format!("Bearer {}", environment_token).parse()?; let sandbox_header: MetadataValue<_> = format!("Bearer {}", sandboxes_token).parse()?; // Create gRPC channel with TLS let tls_config = ClientTlsConfig::new().with_native_roots(); let channel = Channel::from_shared(format!("https://{}", lattice_endpoint))? .tls_config(tls_config)? .connect() .await?; // Create EntityManager API client with authentication interceptor let mut client = EntityManagerApiClient::with_interceptor( channel, move |mut req: Request<()>| { req.metadata_mut() .insert("authorization", auth_header.clone()); req.metadata_mut() .insert("anduril-sandbox-authorization", sandbox_header.clone()); Ok(req) }, ); println!("Starting EntityManager API client..."); println!("Connected to: {}", lattice_endpoint); println!(); // Periodically get the latest entity state loop { let request = Request::new(GetEntityRequest { entity_id: "".to_string(), }); match client.get_entity(request).await { Ok(response) => { // Log entity location if available if let Some(entity) = response.get_ref().entity.as_ref() { if let Some(location) = entity.location.as_ref() { if let Some(position) = location.position.as_ref() { println!( "Fetching entity at location: {} {}", position.latitude_degrees, position.longitude_degrees ); } } } } Err(e) => { eprintln!("Error getting entity: {}", e); } } // Wait 5 seconds before next request tokio::time::sleep(Duration::from_secs(5)).await; } } ``` #### Verify the response If successful, you'll see the entity's`aliases.name` and its real-time location logged in the console: ```bash maxLines=3 Asset name | Demo-Sim-Asset1 Asset location | 37.7749, -122.4194 ``` ## Stream entities The [`StreamEntities`](/reference/rest/entities/stream-entities) API establishes a persistent connection to stream entity events as they occur. The stream sends two types of events: [`entity`](/reference/rest/entities/stream-entities#response.body.entity) and [`heartbeat`](/reference/rest/entities/stream-entities#response.body.heartbeat). Use the following optional parameters to control the frequency and type of data fetched from your environment: [`heartbeatIntervalMS`](/reference/rest/entities/stream-entities#request.body.heartbeatIntervalMS), [`preExistingOnly`](/reference/rest/entities/stream-entities#request.body.preExistingOnly), and [`componentsToInclude`](/reference/rest/entities/stream-entities#request.body.componentsToInclude). To stream entities from Lattice, do the following: #### Stream all components To get stream of all entity components from your environment, including new entities as they are updated, use the default options. By default, the `preExistingOnly` option is set to `false`, instructing Lattice to establish a persistent, real-time connection with the client: ```go title={"Go (REST)"} startLine={39} maxLines={20} package main import ( "context" "errors" "fmt" "io" "log" "net/http" "os" Lattice "github.com/anduril/lattice-sdk-go/v5" "github.com/anduril/lattice-sdk-go/v5/client" "github.com/anduril/lattice-sdk-go/v5/option" ) func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() latticeEndpoint := os.Getenv("LATTICE_ENDPOINT") environmentToken := os.Getenv("ENVIRONMENT_TOKEN") sandboxesToken := os.Getenv("SANDBOXES_TOKEN") if latticeEndpoint == "" || environmentToken == "" || sandboxesToken == "" { log.Fatal("Required environment variables not set.") } headers := http.Header{} headers.Add("Anduril-Sandbox-Authorization", fmt.Sprintf("Bearer %s", sandboxesToken)) latticeClient := client.NewClient( option.WithToken(environmentToken), option.WithBaseURL(fmt.Sprintf("https://%s", latticeEndpoint)), option.WithHTTPHeader(headers), ) // Create the entity stream. stream, err := latticeClient.Entities.StreamEntities(ctx, &Lattice.EntityStreamRequest{ PreExistingOnly: Lattice.Bool(false), }) if err != nil { log.Fatalf("Failed to create entity stream: %v", err) } defer stream.Close() for { select { case <-ctx.Done(): log.Printf("Context canceled: %v", ctx.Err()) return default: // Continue processing } message, err := stream.Recv() if errors.Is(err, io.EOF) { log.Println("Stream completed successfully.") return } if err != nil { log.Printf("Error receiving message: %v", err) continue } // Process the message based on its type switch message.Event { case "heartbeat": timestamp := *message.Heartbeat.Timestamp log.Printf("Heartbeat: %s", timestamp) case "entity": log.Printf("Entity: %s", *message.Entity.Entity.EntityID) default: log.Printf("Unknown event type: %s", message.Event) } } } ``` ```java title={"Java (REST)"} startLine={37} maxLines={20} package org.example; import com.anduril.AsyncLattice; import com.anduril.resources.entities.requests.EntityStreamRequest; import com.anduril.resources.entities.types.StreamEntitiesResponse; import com.anduril.types.EntityStreamEvent; /** * Example application that streams all entities using the Lattice SDK. */ public class StreamAllEntities { public static void main(String[] args) { // Get environment variables String endpoint = System.getenv("LATTICE_ENDPOINT"); String environmentToken = System.getenv("ENVIRONMENT_TOKEN"); String sandboxesToken = System.getenv("SANDBOXES_TOKEN"); // Check required variables if (endpoint == null || environmentToken == null || sandboxesToken == null) { System.err.println("Missing required environment variables"); System.exit(1); } if (!endpoint.startsWith("https://")) endpoint = "https://" + endpoint; try { AsyncLattice client = AsyncLattice.builder() .url(endpoint) .token(environmentToken) .addHeader("Anduril-Sandbox-Authorization", "Bearer " + sandboxesToken) .build(); System.out.println("Starting entity stream..."); try { EntityStreamRequest request = EntityStreamRequest .builder() .preExistingOnly(false) .build(); client.entities().streamEntities(request) .thenAccept(responses -> { for (StreamEntitiesResponse response : responses) { // If the event is of type heartbeat, log the timestamp. if (response.getHeartbeat() != null && response.getHeartbeat().isPresent()) { System.out.println("Heartbeat received: " + response.getHeartbeat().get().getTimestamp().get()); } // If the event is of type entity, log the entity ID. else if (response.getEntity() != null && response.getEntity().isPresent()) { EntityStreamEvent event = response.getEntity().get(); System.out.println("Entity: " + event.getEntity().get().getEntityId().get()); } } }) .exceptionally(ex -> { System.err.println("Exception while streaming entities: " + ex.getMessage()); return null; }) .join(); } catch (Exception e) { System.err.println("Error in streaming loop: " + e.getMessage()); } } catch (Exception e) { System.err.println("Failed to initialize: " + e.getMessage()); } } } ``` ```py title={"Python (REST)"} startLine={24} maxLines={20} from anduril import AsyncLattice import asyncio import os import sys lattice_endpoint = os.getenv('LATTICE_ENDPOINT') environment_token = os.getenv('ENVIRONMENT_TOKEN') # Remove sandboxes_token from the following statements if you are not developing on Sandboxes. sandboxes_token = os.getenv('SANDBOXES_TOKEN') if not environment_token or not lattice_endpoint or not sandboxes_token: print("Missing environment variables.") sys.exit(1) client = AsyncLattice( base_url=f"https://{lattice_endpoint}", token=lambda: str(environment_token), # Remove the following header if you are not developing on Sandboxes. headers={ "anduril-sandbox-authorization": f"Bearer {sandboxes_token}" } ) async def app(): try: event_stream = client.entities.stream_entities(pre_existing_only=False) async for event in event_stream: # If the event is of type heartbeat, log the timestamp. if event.event == "heartbeat": print(f'Heartbeat: {event.timestamp}') # If the event is of type entity, log the entity ID. elif event.entity: print(f'Entity: {event.entity.entity_id}') except asyncio.CancelledError: print("Streaming cancelled...") except Exception as error: print(f"Exception: {error}") if __name__ == "__main__": asyncio.run(app()) ``` ```ts title={"Typescript (REST)"} startLine={25} maxLines={20} import { LatticeClient } from "@anduril-industries/lattice-sdk"; const latticeEndpoint = process.env.LATTICE_ENDPOINT; const environmentToken = process.env.ENVIRONMENT_TOKEN; // Remove sandboxesToken from the following statement if you are not developing on Sandboxes. const sandboxesToken = process.env.SANDBOXES_TOKEN; if (!latticeEndpoint || !environmentToken || !sandboxesToken) { console.log('Missing required environment variables.'); process.exit(1); } const client = new LatticeClient( { baseUrl: `https://${latticeEndpoint}`, token: environmentToken, // Remove the following statement if you are not developing on Sandboxes. headers: { "Anduril-Sandbox-Authorization": `Bearer ${sandboxesToken}` } } ); async function App() { try { // Start streaming entities const eventStream = await client.entities.streamEntities({ preExistingOnly: false }); // Process the stream for await (const event of eventStream) { // Check if it's a heartbeat event. if (event.event === "heartbeat") { console.log(`Heartbeat received: ${event.timestamp}`); continue; } // Process entity event. const entity = event.entity; if (entity) { console.log(`Entity: ${entity.entityId}`); } } } catch (error) { console.log(`Exception while streaming entities: ${error}`); } } App(); ``` ```go title={"Go (gRPC)"} startLine={45} maxLines={20} // This Go example is compatible with artifacts generated using // the following grpc/go plugin: https://buf.build/anduril/lattice-sdk/sdks/main:grpc/go package main import ( "context" "io" "log" "os" "schema-registry.developer.anduril.com/gen/go/anduril/lattice-sdk/grpc/go/anduril/entitymanager/v1/entitymanagerv1grpc" entitymanagerv1 "schema-registry.developer.anduril.com/gen/go/anduril/lattice-sdk/protocolbuffers/go/anduril/entitymanager/v1" "google.golang.org/grpc" "google.golang.org/grpc/credentials" ) func main() { ctx := context.Background() environmentToken := os.Getenv("ENVIRONMENT_TOKEN") latticeEndpoint := os.Getenv("LATTICE_ENDPOINT") sandboxesToken := os.Getenv("SANDBOXES_TOKEN") if latticeEndpoint == "" || environmentToken == "" || sandboxesToken == "" { log.Fatalf("Missing required environment variables") } auth := &BearerTokenAuth{ Token: environmentToken, SandboxesToken: sandboxesToken, } opts := []grpc.DialOption{ grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")), grpc.WithPerRPCCredentials(auth), } conn, err := grpc.NewClient(latticeEndpoint, opts...) if err != nil { log.Fatalf("Did not connect: %v", err) } defer conn.Close() client := entitymanagerv1grpc.NewEntityManagerAPIClient(conn) stream, err := client.StreamEntityComponents(ctx, &entitymanagerv1.StreamEntityComponentsRequest{ IncludeAllComponents: true, }) if err != nil { log.Fatalf("Error creating stream: %v", err) } log.Println("Starting to receive stream data...") for { response, err := stream.Recv() if err == io.EOF { log.Println("End of stream reached") break } if err != nil { log.Fatalf("Error receiving stream data: %v", err) } // Process each entity. log.Printf("Streaming entity ID: %s", response.GetEntityEvent().GetEntity().EntityId) } } ``` ```py title={"Python (gRPC)"} startLine={32} maxLines={20} # This Python example is compatible with artifacts generated using the following # Buf plugins: https://schema-registry.developer.anduril.com/anduril/lattice-sdk/sdks/main:bufbuild/py # and https://schema-registry.developer.anduril.com/anduril/lattice-sdk/sdks/main:connectrpc/py import os import sys from bearer_auth import create_client from anduril.entitymanager.v1.entity_manager_api_pub_pb import ( StreamEntityComponentsRequest, ) from anduril.entitymanager.v1.entity_manager_api_pub_connect import EntityManagerAPIClientSync def main(): environment_token = os.getenv("ENVIRONMENT_TOKEN") lattice_endpoint = os.getenv("LATTICE_ENDPOINT") sandboxes_token = os.getenv("SANDBOXES_TOKEN") if not environment_token or not lattice_endpoint or not sandboxes_token: print("Missing required environment variables", file=sys.stderr) sys.exit(1) # Create the API client over Connect gRPC with authentication client = create_client(EntityManagerAPIClientSync, lattice_endpoint, environment_token, sandboxes_token) request = StreamEntityComponentsRequest(include_all_components=True) try: stream = client.stream_entity_components(request) except Exception as error: print(f"Error creating stream: {error}", file=sys.stderr) sys.exit(1) print("Starting to receive stream data...") try: for response in stream: # Heartbeat messages carry no entity_event, so skip them. if not response.entity_event or not response.entity_event.entity: continue entity = response.entity_event.entity print(f"Streaming entity ID: {entity.entity_id}") print("End of stream reached") except Exception as error: print(f"Error receiving stream data: {error}", file=sys.stderr) sys.exit(1) if __name__ == "__main__": main() ``` ```rs title={"Rust (gRPC)"} startLine={44} maxLines={20} // This Rust example is compatible with artifacts generated using // the following grpc/rust plugin: https://buf.build/anduril/lattice-sdk/sdks/main:community/neoeinstein-tonic mod bearer_auth; use anduril_lattice_sdk_community_neoeinstein_prost::anduril::entitymanager::v1::StreamEntityComponentsRequest; use anduril_lattice_sdk_community_neoeinstein_tonic::anduril::entitymanager::v1::tonic::entity_manager_api_client::EntityManagerApiClient; use bearer_auth::EnvironmentTokenAuth; use tonic::metadata::MetadataValue; use tonic::transport::{Channel, ClientTlsConfig}; use tonic::Request; use std::env; #[tokio::main] async fn main() -> Result<(), Box> { // Load environment variables let environment_token = env::var("ENVIRONMENT_TOKEN") .expect("ENVIRONMENT_TOKEN environment variable not set"); let lattice_endpoint = env::var("LATTICE_ENDPOINT") .expect("LATTICE_ENDPOINT environment variable not set"); let sandboxes_token = env::var("SANDBOXES_TOKEN") .expect("SANDBOXES_TOKEN environment variable not set"); // Set up authentication handler let auth = EnvironmentTokenAuth::new( environment_token, sandboxes_token, ); // Create gRPC channel with TLS let tls_config = ClientTlsConfig::new().with_native_roots(); let channel = Channel::from_shared(format!("https://{}", lattice_endpoint))? .tls_config(tls_config)? .connect() .await?; // Fetch a fresh access token and build metadata values let access_token = auth.get_token().await?; let sandboxes_token = auth.sandboxes_token(); let auth_header: MetadataValue<_> = format!("Bearer {}", access_token).parse()?; let sandbox_header: MetadataValue<_> = format!("Bearer {}", sandboxes_token).parse()?; // Create EntityManager API client with authentication interceptor let mut client = EntityManagerApiClient::with_interceptor( channel, move |mut req: Request<()>| { req.metadata_mut() .insert("authorization", auth_header.clone()); req.metadata_mut() .insert("anduril-sandbox-authorization", sandbox_header.clone()); Ok(req) }, ); // Subscribe to all components on every entity let request = Request::new(StreamEntityComponentsRequest { include_all_components: true, ..Default::default() }); let mut stream = client.stream_entity_components(request).await?.into_inner(); println!("Starting to receive stream data..."); while let Some(response) = stream.message().await? { if let Some(entity) = response.entity_event.and_then(|e| e.entity) { println!("Streaming entity ID: {}", entity.entity_id); } } println!("End of stream reached"); Ok(()) } ``` If successful, you get a stream of all entities as they are updated in Lattice: ```bash maxLines=3 Entity: Demo-Sim-Asset2 Entity: adsbEntity Entity: esim.adsb.aus-4005 ``` #### Stream specific components Use `componentsToInclude` and provide a list of components in `snake_case`. For example, `aliases`, and `location_uncertainty`: ```go title={"Go (REST)"} startLine={39} maxLines={20} package main import ( "context" "errors" "fmt" "io" "log" "net/http" "os" Lattice "github.com/anduril/lattice-sdk-go/v5" "github.com/anduril/lattice-sdk-go/v5/client" "github.com/anduril/lattice-sdk-go/v5/option" ) func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() latticeEndpoint := os.Getenv("LATTICE_ENDPOINT") environmentToken := os.Getenv("ENVIRONMENT_TOKEN") sandboxesToken := os.Getenv("SANDBOXES_TOKEN") if latticeEndpoint == "" || environmentToken == "" || sandboxesToken == "" { log.Fatal("Required environment variables not set.") } headers := http.Header{} headers.Add("Anduril-Sandbox-Authorization", fmt.Sprintf("Bearer %s", sandboxesToken)) latticeClient := client.NewClient( option.WithToken(environmentToken), option.WithBaseURL(fmt.Sprintf("https://%s", latticeEndpoint)), option.WithHTTPHeader(headers), ) // Create the entity stream. stream, err := latticeClient.Entities.StreamEntities(ctx, &Lattice.EntityStreamRequest{ PreExistingOnly: Lattice.Bool(false), // Define a list of components to control which entities are fetched. // If set, Lattice streams only entities with the components you provide. ComponentsToInclude: []string{"aliases", "location_uncertainty"}, }) if err != nil { log.Fatalf("Failed to create entity stream: %v", err) } defer stream.Close() for { select { case <-ctx.Done(): log.Printf("Context canceled: %v", ctx.Err()) return default: // Continue processing } message, err := stream.Recv() // Handle stream completion if errors.Is(err, io.EOF) { log.Println("Stream completed successfully.") return } if err != nil { log.Printf("Error receiving message: %v", err) continue } // Process the event based whether it is a heartbeat or entity event. switch message.Event { case "heartbeat": timestamp := *message.Heartbeat.Timestamp log.Printf("Heartbeat: %s", timestamp) case "entity": log.Printf("Entity: %s", *message.Entity.Entity.Aliases.Name) default: log.Printf("Unknown event type: %s", message.Event) } } } ``` ```java title={"Java (REST)"} startLine={40} maxLines={20} package org.example; import java.util.Arrays; import java.util.Optional; import com.anduril.AsyncLattice; import com.anduril.resources.entities.requests.EntityStreamRequest; import com.anduril.resources.entities.types.StreamEntitiesResponse; import com.anduril.types.EntityStreamEvent; /** * Example application that streams all entities using the Lattice SDK. */ public class StreamSpecificComponents { public static void main(String[] args) { // Get environment variables String endpoint = System.getenv("LATTICE_ENDPOINT"); String environmentToken = System.getenv("ENVIRONMENT_TOKEN"); String sandboxesToken = System.getenv("SANDBOXES_TOKEN"); // Check required variables if (endpoint == null || environmentToken == null || sandboxesToken == null) { System.err.println("Missing required environment variables"); System.exit(1); } if (!endpoint.startsWith("https://")) endpoint = "https://" + endpoint; try { AsyncLattice client = AsyncLattice.builder() .url(endpoint) .token(environmentToken) .addHeader("Anduril-Sandbox-Authorization", "Bearer " + sandboxesToken) .build(); System.out.println("Starting entity stream..."); try { EntityStreamRequest request = EntityStreamRequest.builder() .preExistingOnly(false) .componentsToInclude(Optional.of(Arrays.asList("aliases", "location_uncertainty"))) .build(); client.entities().streamEntities(request) .thenAccept(responses -> { for (StreamEntitiesResponse response : responses) { // If the event is of type heartbeat, log the timestamp. if (response.getHeartbeat() != null && response.getHeartbeat().isPresent()) { System.out.println("Heartbeat received: " + response.getHeartbeat().get().getTimestamp().get()); } // If the event is of type entity, log the entity ID. else if (response.getEntity() != null && response.getEntity().isPresent()) { EntityStreamEvent event = response.getEntity().get(); System.out.println("Entity: " + event.getEntity().get().getAliases().get().getName().get()); } } }) .exceptionally(ex -> { System.err.println("Exception while streaming entities: " + ex.getMessage()); return null; }) .join(); } catch (Exception e) { System.err.println("Error in streaming loop: " + e.getMessage()); } } catch (Exception e) { System.err.println("Failed to initialize: " + e.getMessage()); } } } ``` ```py title={"Python (REST)"} startLine={24} maxLines={20} from anduril import AsyncLattice import asyncio import os import sys lattice_endpoint = os.getenv('LATTICE_ENDPOINT') environment_token = os.getenv('ENVIRONMENT_TOKEN') # Remove sandboxes_token from the following statements if you are not developing on Sandboxes. sandboxes_token = os.getenv('SANDBOXES_TOKEN') if not environment_token or not lattice_endpoint or not sandboxes_token: print("Missing environment variables.") sys.exit(1) client = AsyncLattice( base_url=f"https://{lattice_endpoint}", token=lambda: str(environment_token), # Remove the following header if you are not developing on Sandboxes. headers={ "anduril-sandbox-authorization": f"Bearer {sandboxes_token}" } ) async def app(): try: event_stream = client.entities.stream_entities( pre_existing_only=False, components_to_include=["aliases", "location_uncertainty"] ) async for event in event_stream: # If the event is of type heartbeat, log the timestamp. if event.event == "heartbeat": print(f'Heartbeat: {event.timestamp}') # If the event is of type entity, log the entity name. elif event.entity and event.entity.aliases: print(f'Entity: {event.entity.aliases.name}') except asyncio.CancelledError: print("Streaming cancelled...") except Exception as error: print(f"Exception: {error}") if __name__ == "__main__": asyncio.run(app()) ``` ```ts title={"Typescript (REST)"} startLine={25} maxLines={20} import { LatticeClient } from "@anduril-industries/lattice-sdk"; const latticeEndpoint = process.env.LATTICE_ENDPOINT; const environmentToken = process.env.ENVIRONMENT_TOKEN; // Remove sandboxesToken from the following statement if you are not developing on Sandboxes. const sandboxesToken = process.env.SANDBOXES_TOKEN; if (!latticeEndpoint || !environmentToken || !sandboxesToken) { console.log('Missing required environment variables.'); process.exit(1); } const client = new LatticeClient( { baseUrl: `https://${latticeEndpoint}`, token: environmentToken, // Remove the following statement if you are not developing on Sandboxes. headers: { "Anduril-Sandbox-Authorization": `Bearer ${sandboxesToken}` } } ); async function App() { try { // Start streaming entities const eventStream = await client.entities.streamEntities( { preExistingOnly: false, componentsToInclude: ["aliases", "location_uncertainty"] } ); // Process the stream for await (const event of eventStream) { // Check if it's a heartbeat event. if (event.event === "heartbeat") { console.log(`Heartbeat received: ${event.timestamp}`); continue; } // Process entity event. const entity = event.entity; if (entity) { console.log(`Entity: ${entity.aliases?.name}`); } } } catch (error) { console.log(`Exception while streaming entities: ${error}`); } } App(); ``` ```go title={"Go (gRPC)"} startLine={45} maxLines={20} // This Go example is compatible with artifacts generated using // the following grpc/go plugin: https://buf.build/anduril/lattice-sdk/sdks/main:grpc/go package main import ( "context" "io" "log" "os" "schema-registry.developer.anduril.com/gen/go/anduril/lattice-sdk/grpc/go/anduril/entitymanager/v1/entitymanagerv1grpc" entitymanagerv1 "schema-registry.developer.anduril.com/gen/go/anduril/lattice-sdk/protocolbuffers/go/anduril/entitymanager/v1" "google.golang.org/grpc" "google.golang.org/grpc/credentials" ) func main() { ctx := context.Background() environmentToken := os.Getenv("ENVIRONMENT_TOKEN") latticeEndpoint := os.Getenv("LATTICE_ENDPOINT") sandboxesToken := os.Getenv("SANDBOXES_TOKEN") if latticeEndpoint == "" || environmentToken == "" || sandboxesToken == "" { log.Fatalf("Missing required environment variables") } auth := &BearerTokenAuth{ Token: environmentToken, SandboxesToken: sandboxesToken, } opts := []grpc.DialOption{ grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")), grpc.WithPerRPCCredentials(auth), } conn, err := grpc.NewClient(latticeEndpoint, opts...) if err != nil { log.Fatalf("Did not connect: %v", err) } defer conn.Close() client := entitymanagerv1grpc.NewEntityManagerAPIClient(conn) stream, err := client.StreamEntityComponents(ctx, &entitymanagerv1.StreamEntityComponentsRequest{ ComponentsToInclude: []string{"aliases", "location"}, }) if err != nil { log.Fatalf("Error creating stream: %v", err) } log.Println("Starting to receive stream data...") for { response, err := stream.Recv() if err == io.EOF { // End of stream log.Println("End of stream reached.") break } if err != nil { log.Fatalf("Error receiving stream data: %v", err) } entity := response.GetEntityEvent().GetEntity() if position := entity.GetLocation().GetPosition(); position != nil { log.Printf("Entity %s at location: %f, %f", entity.EntityId, position.LatitudeDegrees, position.LongitudeDegrees, ) } } } ``` ```py title={"Python (gRPC)"} startLine={32} maxLines={20} # This Python example is compatible with artifacts generated using the following # Buf plugins: https://schema-registry.developer.anduril.com/anduril/lattice-sdk/sdks/main:bufbuild/py # and https://schema-registry.developer.anduril.com/anduril/lattice-sdk/sdks/main:connectrpc/py import os import sys from bearer_auth import create_client from anduril.entitymanager.v1.entity_manager_api_pub_pb import ( StreamEntityComponentsRequest, ) from anduril.entitymanager.v1.entity_manager_api_pub_connect import EntityManagerAPIClientSync def main(): environment_token = os.getenv("ENVIRONMENT_TOKEN") lattice_endpoint = os.getenv("LATTICE_ENDPOINT") sandboxes_token = os.getenv("SANDBOXES_TOKEN") if not environment_token or not lattice_endpoint or not sandboxes_token: print("Missing required environment variables", file=sys.stderr) sys.exit(1) # Create the API client over Connect gRPC with authentication client = create_client(EntityManagerAPIClientSync, lattice_endpoint, environment_token, sandboxes_token) request = StreamEntityComponentsRequest(components_to_include=["aliases", "location"]) try: stream = client.stream_entity_components(request) except Exception as error: print(f"Error creating stream: {error}", file=sys.stderr) sys.exit(1) print("Starting to receive stream data...") try: for response in stream: # Heartbeat messages carry no entity_event, so skip them. if not response.entity_event or not response.entity_event.entity: continue entity = response.entity_event.entity if not entity.location or not entity.location.position: continue position = entity.location.position if position.latitude_degrees or position.longitude_degrees: print( f"Entity {entity.entity_id} at location: " f"{position.latitude_degrees}, {position.longitude_degrees}" ) print("End of stream reached.") except Exception as error: print(f"Error receiving stream data: {error}", file=sys.stderr) sys.exit(1) if __name__ == "__main__": main() ``` ```rs title={"Rust (gRPC)"} startLine={44} maxLines={20} // This Rust example is compatible with artifacts generated using // the following grpc/rust plugin: https://buf.build/anduril/lattice-sdk/sdks/main:community/neoeinstein-tonic mod bearer_auth; use anduril_lattice_sdk_community_neoeinstein_prost::anduril::entitymanager::v1::StreamEntityComponentsRequest; use anduril_lattice_sdk_community_neoeinstein_tonic::anduril::entitymanager::v1::tonic::entity_manager_api_client::EntityManagerApiClient; use bearer_auth::EnvironmentTokenAuth; use tonic::metadata::MetadataValue; use tonic::transport::{Channel, ClientTlsConfig}; use tonic::Request; use std::env; #[tokio::main] async fn main() -> Result<(), Box> { // Load environment variables let environment_token = env::var("ENVIRONMENT_TOKEN") .expect("ENVIRONMENT_TOKEN environment variable not set"); let lattice_endpoint = env::var("LATTICE_ENDPOINT") .expect("LATTICE_ENDPOINT environment variable not set"); let sandboxes_token = env::var("SANDBOXES_TOKEN") .expect("SANDBOXES_TOKEN environment variable not set"); // Set up authentication handler let auth = EnvironmentTokenAuth::new( environment_token, sandboxes_token, ); // Create gRPC channel with TLS let tls_config = ClientTlsConfig::new().with_native_roots(); let channel = Channel::from_shared(format!("https://{}", lattice_endpoint))? .tls_config(tls_config)? .connect() .await?; // Fetch a fresh access token and build metadata values let access_token = auth.get_token().await?; let sandboxes_token = auth.sandboxes_token(); let auth_header: MetadataValue<_> = format!("Bearer {}", access_token).parse()?; let sandbox_header: MetadataValue<_> = format!("Bearer {}", sandboxes_token).parse()?; // Create EntityManager API client with authentication interceptor let mut client = EntityManagerApiClient::with_interceptor( channel, move |mut req: Request<()>| { req.metadata_mut() .insert("authorization", auth_header.clone()); req.metadata_mut() .insert("anduril-sandbox-authorization", sandbox_header.clone()); Ok(req) }, ); // Subscribe to only the aliases and location components on every entity let request = Request::new(StreamEntityComponentsRequest { components_to_include: vec!["aliases".to_string(), "location".to_string()], ..Default::default() }); let mut stream = client.stream_entity_components(request).await?.into_inner(); println!("Starting to receive stream data..."); while let Some(response) = stream.message().await? { if let Some(entity) = response.entity_event.and_then(|e| e.entity) { if let Some(position) = entity.location.and_then(|l| l.position) { println!( "Entity {} at location: {}, {}", entity.entity_id, position.latitude_degrees, position.longitude_degrees ); } } } println!("End of stream reached."); Ok(()) } ``` If you're directly invoking the [`streamEntities`](/reference/rest/entities/stream-entities) REST API using `curl` or another similar tool, you must list the components using `camelCase`: `locationUncertainty`. If successful, you receive a real-time stream of entities with `aliases` populated: ```bash maxLines=3 Entity: ADS-B: N113PF Entity: DIVE Entity: FISHING VESSEL (37958) ``` ## What's next? * Learn more about the Entities API in [REST](/reference/rest/entities/publish-entity) and [gRPC](/reference/grpc/anduril-entitymanager-v-1/entity-manager-api/anduril-entitymanager-v-1-publish-entity). * Learn how to [Publish entities to Lattice](/guides/entities/publish). * Check out the Lattice [sample apps](/samples/overview). > Monitoring entities in Lattice using the Lattice SDK