Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/packages/harp-data/src/harp/data/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from ._read import read
from ._reader import parse_to_dataframe, payload_to_dataframe

__all__ = [
"read",
"parse_to_dataframe",
"payload_to_dataframe",
]
92 changes: 92 additions & 0 deletions src/packages/harp-data/src/harp/data/_read.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from typing import Any

import pandas as pd
from harp.protocol import (
PayloadType,
RegisterBase,
RegisterFloat,
RegisterFloatArray,
RegisterS8,
RegisterS8Array,
RegisterS16,
RegisterS16Array,
RegisterS32,
RegisterS32Array,
RegisterS64,
RegisterS64Array,
RegisterU8,
RegisterU8Array,
RegisterU16,
RegisterU16Array,
RegisterU32,
RegisterU32Array,
RegisterU64,
RegisterU64Array,
decode_payload_type,
)
from harp.protocol._constants import _HEADER_LEN, _TIMESTAMP_LEN

from ._reader import Source, _read_bytes, parse_to_dataframe

_SCALAR_REGISTER: dict[PayloadType, Any] = {
PayloadType.U8: RegisterU8,
PayloadType.S8: RegisterS8,
PayloadType.U16: RegisterU16,
PayloadType.S16: RegisterS16,
PayloadType.U32: RegisterU32,
PayloadType.S32: RegisterS32,
PayloadType.U64: RegisterU64,
PayloadType.S64: RegisterS64,
PayloadType.Float: RegisterFloat,
}

_ARRAY_REGISTER: dict[PayloadType, Any] = {
PayloadType.U8: RegisterU8Array,
PayloadType.S8: RegisterS8Array,
PayloadType.U16: RegisterU16Array,
PayloadType.S16: RegisterS16Array,
PayloadType.U32: RegisterU32Array,
PayloadType.S32: RegisterS32Array,
PayloadType.U64: RegisterU64Array,
PayloadType.S64: RegisterS64Array,
PayloadType.Float: RegisterFloatArray,
}


def _infer_native_register(raw: bytes) -> type[RegisterBase[Any]]:
"""Build a native register class from the first frame's header.

Reads the payload-type byte, the length byte and the timestamp flag to derive
the element type and element count, then returns the matching scalar or array
register from :mod:`harp.protocol`.
"""
if len(raw) < _HEADER_LEN:
raise ValueError(f"buffer too short to contain a Harp frame header ({len(raw)} bytes)")
info = decode_payload_type(raw[4])
stride = int(raw[1]) + 2
payload_offset = _HEADER_LEN + (_TIMESTAMP_LEN if info.has_timestamp else 0)
payload_bytes = stride - payload_offset - 1 # trailing checksum byte
count = payload_bytes // info.element_size
address = int(raw[2])
if count == 1:
return _SCALAR_REGISTER[info.payload_type]
return _ARRAY_REGISTER[info.payload_type](address, length=count)


def read(
source: Source,
*,
timestamp: bool = True,
message_type: bool = False,
) -> pd.DataFrame:
"""Read a single register's binary data, inferring its native layout.

``source`` may be a file path, raw bytes, or an open binary file. The element
type, length and timestamp presence are read from the first frame; values
decode to the matching native numpy type (no enum or bit-mask decoding).
"""
raw = _read_bytes(source)
if len(raw) == 0:
return pd.DataFrame()
register = _infer_native_register(raw)
return parse_to_dataframe(register, raw, timestamp=timestamp, message_type=message_type)
10 changes: 9 additions & 1 deletion src/packages/harp-protocol/src/harp/protocol/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,12 @@
AnonymousPayload,
Column,
)
from ._payload_type import PayloadType
from ._payload_type import (
PayloadType,
PayloadTypeInfo,
decode_payload_type,
encode_payload_type,
)
from ._register import (
RegisterBase,
RegisterU8,
Expand Down Expand Up @@ -64,6 +69,9 @@
"MessageType",
# Payload type
"PayloadType",
"PayloadTypeInfo",
"decode_payload_type",
"encode_payload_type",
# Message
"HarpMessage",
"ParsedHarpMessage",
Expand Down