# Reading data

> Learn how to read data from channels in Synnax, including real-time streaming and historical reads.

This guide covers reading channel data from Synnax. For real-time data acquisition and monitoring, streaming is the recommended approach. Historical reads are useful for analyzing data that has already been recorded.

If you’d like a conceptual overview of how reads work in Synnax, check out the [reads concepts guide](https://docs.synnaxlabs.com/reference/concepts/reads).

## Streaming data

Streaming data is useful for real-time processing, visualization, and monitoring. If you’d like a conceptual overview of how streaming works in Synnax, check out the [streams](https://docs.synnaxlabs.com/reference/concepts/streams) page.

### Opening a streamer

To start streaming data, call the `open_streamer` method on the client and provide a list of channels to stream.

**Python**

```python
# Python's context manager is recommended
with client.open_streamer(["ch1", "ch2"]) as streamer:
    for frame in streamer:
        # Process the frame

# Alternative method
# (requires explicit closing)
streamer = client.open_streamer(["ch1", "ch2"])
```

**TypeScript**

```typescript
const streamer = await client.openStreamer(["ch1", "ch2"]);
try {
  for await (const frame of streamer) {
    // Process the frame
  }
} finally {
  streamer.close();
}
```

### Downsampling option

To stream data at a lower rate, use the `downsample_factor` parameter. The streamer will skip the specified number of samples in each series before returning the next frame. Note that this does not average values—it skips samples.

**Python**

```python
# Read every 2nd sample
streamer = client.open_streamer(
    channels=["temperature", "pressure"],
    downsample_factor=2,
)
```

**TypeScript**

```typescript
// Read every 2nd sample
const streamer = await client.openStreamer({
  channels: ["temperature", "pressure"],
  downsampleFactor: 2,
});
```

### Using an async streamer

**Python**

For `asyncio` support, use `open_async_streamer`. It has the same interface as the synchronous streamer:

```python
async with await client.open_async_streamer(["ch1", "ch2"]) as streamer:
    async for frame in streamer:
        # Process the frame
```

**TypeScript**



TypeScript streaming is async by default.

### Reading frames

To read the next incoming frame, call the `read` method on the streamer.

**Python**

```python
frame = streamer.read()
```

**TypeScript**

```typescript
const frame = await streamer.read();
```

This call will block until a new frame is available. A frame may not contain data for every channel, so be ready to [handle partial frames](https://docs.synnaxlabs.com/reference/client/read-data#handling-partial-frames).

For more on working with frames, see [Series and Frames](https://docs.synnaxlabs.com/reference/client/series-and-frames).

### Using a for loop

The streamer implements an iterator, allowing you to use a for loop to continuously process incoming frames.

**Python**

```python
# Process frames synchronously

for frame in streamer:
    print(frame.at(-1))
```

**TypeScript**

```typescript
// Process frames asynchronously
for await (const frame of streamer) {
  console.log(frame.at(-1));
}
```

### Handling partial frames

When reading frames from a streamer, it’s important to note that a frame may not contain data for every channel specified when opening the streamer. For example, when reading from two sensors, `temperature` and `pressure`, that are being sampled by different devices at different rates, a frame may contain data only for the first channel, followed by a frame containing only data for the second channel.

**Python**

```python
streamer = client.open_streamer(["temperature", "pressure"])

frame = streamer.read()
print(frame[-1])
# Output: {"temperature": 25.0}

frame = streamer.read()
print(frame[-1])
# Output: {"pressure": 1013.25}

frame = streamer.read()
print(frame[-1])
# Output: {"temperature": 25.1, "pressure": 1013.25}

# Check if a frame contains data for a specific channel
if "temperature" in frame:
    print(frame["temperature"])
```

**TypeScript**

```typescript
const streamer = await client.openStreamer(["temperature", "pressure"]);

const frame = await streamer.read();
console.log(frame.at(-1));
// Output: { temperature: 25.0 }

const frame2 = await streamer.read();
console.log(frame2.at(-1));
// Output: { pressure: 1013.25 }

const frame3 = await streamer.read();
console.log(frame3.at(-1));
// Output: { temperature: 25.1, pressure: 1013.25 }

// Check if a frame contains data for a specific channel
if (frame.has("temperature")) console.log(frame.get("temperature"));
```

### Specifying a timeout

**Python**

Add a `timeout` parameter to the `read` method. If the timeout is reached before a new frame is available, the method returns `None`.

```python
# Wait for 5 seconds
frame = streamer.read(timeout=5)

# A `TimeSpan` object can also be used
frame = streamer.read(timeout=5 * sy.TimeSpan.SECOND)

if frame is None:
    print("Timed out waiting for a frame")
```

**TypeScript**

TypeScript doesn’t have built-in timeout support. Instead, you can use `Promise.race()` to achieve the equivalent functionality.

```typescript
const timeout = (ms: number) =>
  new Promise<null>((resolve) => setTimeout(() => resolve(null), ms));

// Wait for 5 seconds
const frame = await Promise.race([streamer.read(), timeout(5000)]);

if (frame === null) {
  console.log("Timed out waiting for a frame");
}
```

### Updating the channel list

To update the list of channels being streamed, call the `update*channels` method on the streamer. This will \_replace\* the current list of channels, not add to it.

**Python**

```python
streamer.update_channels(["temperature", "pressure", "humidity"])
```

**TypeScript**

```typescript
await streamer.update(["temperature", "pressure", "humidity"]);
```

### Closing the streamer

After you’re done streaming, it’s essential that you call the `close` method on the streamer to release the network connection and other related resources.

**Python**

```python
streamer.close()
```

**TypeScript**

```typescript
streamer.close();
```

Using structured cleanup patterns ensures the streamer is always closed, even if an exception is thrown.

**Python**

```python
# Using the streamer as a context manager ensures
# the streamer is always closed correctly.

with client.open_streamer(["temperature1", "temperature2"]) as streamer:
    for frame in streamer:
        # Process the frame
```

**TypeScript**

```typescript
const streamer = await client.openStreamer(["temperature1", "temperature2"]);
try {
  for await (const frame of streamer) {
    // Process the frame
  }
} finally {
  streamer.close();
}
```

## Historical reads

Historical reads are useful for analyzing data that has already been recorded. Use these patterns when working with past data rather than live streams.

### Reading from a channel

The simplest way to read historical data from Synnax is to use the `read` method on a `Channel`.

**Python**

```python
channel = client.channels.retrieve("my_temp_sensor")
start = sy.TimeStamp("2025-02-12 12:30:00")
end = sy.TimeStamp("2025-02-12 14:30:00")

data = channel.read(start, end)
```

The returned data is a [`Series`](https://docs.synnaxlabs.com/reference/client/series-and-frames#series) object, which contains the time-range occupied by the data. Notably, the `Series` can be treated exactly like a `numpy.ndarray`.

```python
data = data - 273.15
tr = data.time_range
```

**TypeScript**

```typescript
const channel = await client.channels.retrieve("my_temp_sensor");
const start = new Date("2025-02-12T12:30:00Z");
const end = new Date("2025-02-12T14:30:00Z");

const series = await channel.read(start, end);
```

The returned data is a [`Series`](https://docs.synnaxlabs.com/reference/client/series-and-frames#series), which maintains a very similar interface to a JavaScript typed array (e.g. `Float32Array`, `Int32Array`, etc.). Convert the returned data to a JavaScript array easily:

```typescript
const data = Array.from(series);
```

### Reading from multiple channels

The returned data is an instance of the [Frame](https://docs.synnaxlabs.com/reference/client/series-and-frames#frames) class.

**Python**

Access the `Series` object using the `[]` operator. The `Frame` can also be converted to a `pandas.DataFrame` by calling the `to_df` method.

```python
import synnax as sy

start = sy.TimeStamp("2025-02-12 12:30:00")
end = sy.TimeStamp("2025-02-12 14:30:00")

frame = client.read(start, end, ["my_sensor", "sensor_index"])

data = frame["my_sensor"]
df = frame.to_df()
```

**TypeScript**

To access the data for a specific channel, use the `get` method.

```typescript
import { TimeStamp, TimeSpan } from "@synnaxlabs/client";

const start = TimeStamp.now();
const end = start.add(TimeSpan.seconds(10));

const frame = await client.read({ start, end }, ["my_sensor", "sensor_index"]);

const temperature = frame.get("temperature");
const humidity = frame.get("humidity");
```

### Reading the latest data

The `readLatest` method allows you to read the latest N samples from one or more channels:

**Python**

```python
# Read the latest sample from a channel
latest_value = client.read_latest("my_tc", n=1)

# Read the latest 10 samples from a channel
latest_samples = client.read_latest("my_tc", n=10)

# Read the latest 5 samples from multiple channels
frame = client.read_latest(["my_tc", "my_sg", "my_pt"], n=5)
```

**TypeScript**

```typescript
// Read the latest sample from a channel
const latestValue = await client.readLatest("my_tc", 1);

// Read the latest 10 samples from a channel
const latestSamples = await client.readLatest("my_tc", 10);

// Read the latest 5 samples from multiple channels
const frame = await client.readLatest(["my_tc", "my_sg", "my_pt"], 5);
```

The returned data follows the same conventions as regular reads - a `MultiSeries` for single channels or a `Frame` for multiple channels. If fewer than `n` samples are available, the method returns only the samples that exist.
