Metadata-Version: 2.3
Name: slm_open_api_package
Version: 0.2.11
Summary: Add your description here
Author: Sofus Vingaard
Author-email: Sofus Vingaard <sofus.krebsvingaard@hbkworld.com>
Requires-Dist: requests
Requires-Dist: numpy
Requires-Dist: pandas
Requires-Dist: websockets
Requires-Dist: kaitaistruct
Requires-Dist: packaging
Requires-Dist: pyqtgraph
Requires-Dist: pyqt5
Requires-Dist: pyqt5-qt5<5.15.11
Requires-Dist: miniaudio
Requires-Dist: soundfile
Requires-Dist: matplotlib>=3.11.1
Requires-Dist: cryptography>=50.0.1
Requires-Python: >=3.12
Description-Content-Type: text/markdown

# SLM Open API Package

Python helpers for streaming measurements and audio from an HBK sound level meter (SLM) through WebXi.

This PyPI package is made for the [Open Interface for Sound Level Meter](https://github.com/hbkworld/Open-Interface-for-Sound-Level-Meter) repository. Refer to that repository for more detailed usage information and additional examples.



## Install

```powershell
pip install slm_open_api_package
```

Or add it to an `uv` project:

```powershell
uv add slm_open_api_package
```

The package requires Python 3.12 or newer and a network connection to a compatible SLM.

## Connect To A Device

Give every stream one `host`, either an IP address or host name:

```python
host = "192.168.1.183"
```

The bundled examples use `set_host()` to read a host from a local `slm_ip` file. If the file is missing, it prompts once and creates it:

```python
from slm_api.helpers import webxi_helper_functions as webxi_helper

host = webxi_helper.set_host(__file__)
```

## Basic Leq Stream

`WebXiStreamHandler` is the main API. `mode="leq"` is the default and streams one sequence selected by `sequenceID`.

```python
from slm_api.enums.sequence_id_enum import SequenceIdEnums
from slm_api.helpers.stream_handlers import WebXiStreamHandler

streamer = WebXiStreamHandler(
	host="192.168.1.183",
	sequenceID=SequenceIdEnums.LAeq.value,
	mode="leq",
	saving="csv",
	saving_path="saved_data/laeq",
)

try:
	streamer.startStream()
except KeyboardInterrupt:
	streamer.stopStream()
```

JSON and CSV publish complete snapshots in a background worker, at most once per second while new data arrives, rather than rewriting the file for every sample. They retain records in memory and preserve their existing layouts; full-history snapshot costs still grow with recording length. Construct `JsonDataHandler(path, snapshot_interval=2.0)` or `CsvDataHandler(path, snapshot_interval=2.0)` and attach it with `setDataHandler()` to use a different interval. `close()` waits for the worker and saves all remaining records; background write failures are reported on subsequent updates or during the final flush. WAV still appends each audio chunk without buffering the recording in memory. `stopStream()` stops the measurement, closes saving handlers, and removes the device stream. Measurement saving formats are `"csv"` and `"json"`; audio uses `"wav"`. File extensions are added automatically.

## Multiple Sequences

Based on [example_multi.py](src/slm_api/example_multi.py), use `mode="multi"` for several enabled broadband sequences in a single stream:

```python
from slm_api.helpers.data_handler import DataHandler
from slm_api.helpers.stream_handlers import WebXiStreamHandler


class PrintHandler(DataHandler):
	def handle(self, *, host, name, value, **data):
		print(f"{host} {name}: {value}")


streamer = WebXiStreamHandler(
	host="192.168.1.183",
	sequenceNames=["LAeq", "LCeq", "LAF"],
	mode="multi",
	saving="json",
	saving_path="saved_data/broadband",
)
streamer.setDataHandler(PrintHandler())

try:
	streamer.startStream()
except KeyboardInterrupt:
	streamer.stopStream()
```

Enable the requested sequences and frequency weightings on the SLM before starting the stream. Each `DataHandler.handle()` call receives the source `host`, `name`, and `value`. The `saving=` handler continues saving when a custom handler is attached.

## Fast Logging

Based on [fast_log_example.py](src/slm_api/fast_log_example.py), use `mode="fast_logging"` for fast broadband samples:

```python
from slm_api.enums.fast_log_intervals_enum import FastLogInterval
from slm_api.enums.sequence_id_enum import SequenceIdEnums
from slm_api.helpers.stream_handlers import WebXiStreamHandler

streamer = WebXiStreamHandler(
	host="192.168.1.183",
	sequenceID=SequenceIdEnums.FastLAF.value,
	mode="fast_logging",
	fast_logging_interval=FastLogInterval.interval_1000ms.value,
	saving="csv",
	saving_path="saved_data/fast_laf",
)

try:
	streamer.startStream()
except KeyboardInterrupt:
	streamer.stopStream()
```

## Audio

Use the MP3 or FLAC examples for decoded audio samples:

```python
from slm_api.enums.sequence_id_enum import SequenceIdEnums
from slm_api.helpers.stream_handlers import WebXiStreamHandler

streamer = WebXiStreamHandler(
	host="192.168.1.183",
	sequenceID=SequenceIdEnums.MP3Signal.value,
	mode="mp3",
	saving="wav",
	saving_path="saved_data/audio",
)

try:
	streamer.startStream()
except KeyboardInterrupt:
	streamer.stopStream()
```

The matching `flac` form uses `SequenceIdEnums.FLACSignal.value` and `mode="flac"`. Audio setup is enabled by the handler. The SLM can take a few seconds to initialize audio recording when its quality setting changes.

WAV saving appends decoded mono samples as recording happens, updating the file header after each chunk so the file is readable before shutdown. Saving happens after decoding and calibration to pascals. Samples are divided by the fixed full-scale pressure `calibrationFactor * (2**23 - 1)` and saved as `PCM_24`, not peak-normalized per chunk. The filename includes `_fullscale-<value>Pa.wav`. To recover pressure values, multiply floating-point samples read from the WAV by that full-scale value (within 24-bit quantization accuracy). Samples outside the calibrated range produce a clipping warning; this scaling cannot repair upstream clipping. MP3 uses the decoded sample rate; the current FLAC decoder uses 65536 Hz. JSON and CSV saving are rejected for audio modes, and WAV saving is rejected for measurement modes. Run `uv sync` to install the `soundfile` dependency used for progressive WAV writing.

To attach the handler directly, use `WavDataHandler("saved_data/audio", sample_rate=65536, full_scale_pa=calibrationFactor * (2**23 - 1))` from `slm_api.helpers.data_handler`, using the device's actual calibration factor. You can instead supply `sample_rate` and `full_scale_pa` to `handle()`; the stream supplies both automatically. Call `close()` when finished. Use a separate WAV handler and output path for each device. Existing float32 pressure WAVs are unchanged; this format applies to new recordings.

## Synchronized Multi-Device Stream

Based on [multi_device_flac.py](src/slm_api/multi_device_flac.py), pass `multi_device=True` and start/stop the group through the synchronized helpers:

```python
from slm_api.enums.sequence_id_enum import SequenceIdEnums
from slm_api.helpers.stream_handlers import WebXiStreamHandler, start_all_synced, stop_all

hosts = ["192.168.1.183", "192.168.1.216"]
streams = [
	WebXiStreamHandler(
		host=host,
		sequenceID=SequenceIdEnums.MP3Signal.value,
		mode="mp3",
		streamName=f"Audio_{host}",
		multi_device=True,
		saving="wav",
		saving_path=f"saved_data/{host}",
	)
	for host in hosts
]

start_all_synced(streams)

try:
    while True:
        time.sleep(1)
except KeyboardInterrupt:
    stop_all(streams)
```

`stop_all()` synchronizes the device measurement stops, flushes saving handlers, and removes each device stream.

## Modes

| Mode | Use |
| --- | --- |
| `"leq"` | One broadband sequence; this is the default. |
| `"multi"` | Several named broadband sequences. |
| `"cpb"` | Constant-percentage-band data. |
| `"fast_logging"` | Fast broadband samples. |
| `"flac"` / `"mp3"` | Decoded audio samples. |
| `"screen_record"` | Latest SLM screen image in `streamer.image_bytes`. |

Legacy flags such as `mp3=True` remain supported, but `mode=` is preferred. Only select one mode per stream.
```

## Troubleshooting

- **No values arrive:** Confirm the requested sequences are enabled on the SLM.
- **Audio returns no samples after first enable:** Allow the SLM a few seconds to initialize audio recording before retrying.
- **Saved file is missing:** Files are written when the handler first receives data (nonempty samples for WAV). Check the saving path and that the stream is delivering data. Still call `stopStream()` or `stop_all()` to clean up recording resources.
