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: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ from miru_device_sdk import Miru
client = Miru()

calibration = client.calibrations.create(
component_slug="arm",
component="arm",
content={
"data": '{\n "enable_autonomy": true,\n "enable_remote_control": true,\n "max_payload_kg": 10.0\n}\n',
"format": "json",
Expand Down
68 changes: 24 additions & 44 deletions src/miru_device_sdk/resources/calibrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def with_streaming_response(self) -> CalibrationsResourceWithStreamingResponse:
def create(
self,
*,
component_slug: str,
component: str,
content: calibration_create_params.Content,
aspect: str | Omit = omit,
role: str | Omit = omit,
Expand All @@ -56,39 +56,29 @@ def create(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> CalibrationCreateResponse:
"""Report a calibration to Miru.

The agent checks the request, saves the
calibration on the device and returns right away, without waiting for Miru. It
uploads saved calibrations to Miru in the order received and retries until Miru
is reachable, so a calibration survives network outages and agent restarts. The
agent checks the request against Miru's rules before saving it, so Miru rarely
rejects one later; one it does reject, such as a new component once the
workspace holds 100, is set aside on the device and does not hold up later ones.
Content is at most 1 MiB and a request is at most about 2 MiB (1 MiB of content
plus JSON escaping). Each request is a new calibration, so sending the same
calibration twice records it twice. The agent sends the report's `report_id`
with every upload, so a retried upload does not record the calibration twice.
"""
Report a calibration to Miru.

Args:
component_slug: Slug of the component the calibrated unit belongs to. A new slug creates a
component in Miru.
component: Slug of the component the calibrated unit belongs to, such as `arm`. Miru
resolves it to the component, creating the component the first time it is
reported.

content: The calibration's content, at most 1 MiB. The agent uploads it to Miru
unchanged.

aspect: Slug of the aspect of the unit that was calibrated, when it has more than one,
e.g. `intrinsics` vs `extrinsics`, or `pose` vs `home-offsets`. Validated like a
slug. Miru resolves it to one of the component's `aspects`, creating the aspect
the first time it is reported.
e.g. `intrinsics` vs `extrinsics`, or `pose` vs `joint-offsets`. Validated like
a slug. Miru resolves it to one of the component's `aspects`, creating the
aspect the first time it is reported.

role: Slug of the role the calibrated unit fills on the device, such as `aligner-1` or
role: Slug of the role the calibrated unit fills on the device, such as `left` or
`front-left`. Distinguishes several units of one component on a device.
Validated like a slug. Miru resolves it to one of the component's `roles`,
creating the role the first time it is reported.

serial_number: Serial number of the physical unit that was calibrated, not of the device, such
as `M500-1234`. Free text, at most 255 characters.
as `SN-1234`. Free text, at most 255 characters.

extra_headers: Send extra headers

Expand All @@ -102,7 +92,7 @@ def create(
"/calibrations",
body=maybe_transform(
{
"component_slug": component_slug,
"component": component,
"content": content,
"aspect": aspect,
"role": role,
Expand Down Expand Up @@ -140,7 +130,7 @@ def with_streaming_response(self) -> AsyncCalibrationsResourceWithStreamingRespo
async def create(
self,
*,
component_slug: str,
component: str,
content: calibration_create_params.Content,
aspect: str | Omit = omit,
role: str | Omit = omit,
Expand All @@ -152,39 +142,29 @@ async def create(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> CalibrationCreateResponse:
"""Report a calibration to Miru.

The agent checks the request, saves the
calibration on the device and returns right away, without waiting for Miru. It
uploads saved calibrations to Miru in the order received and retries until Miru
is reachable, so a calibration survives network outages and agent restarts. The
agent checks the request against Miru's rules before saving it, so Miru rarely
rejects one later; one it does reject, such as a new component once the
workspace holds 100, is set aside on the device and does not hold up later ones.
Content is at most 1 MiB and a request is at most about 2 MiB (1 MiB of content
plus JSON escaping). Each request is a new calibration, so sending the same
calibration twice records it twice. The agent sends the report's `report_id`
with every upload, so a retried upload does not record the calibration twice.
"""
Report a calibration to Miru.

Args:
component_slug: Slug of the component the calibrated unit belongs to. A new slug creates a
component in Miru.
component: Slug of the component the calibrated unit belongs to, such as `arm`. Miru
resolves it to the component, creating the component the first time it is
reported.

content: The calibration's content, at most 1 MiB. The agent uploads it to Miru
unchanged.

aspect: Slug of the aspect of the unit that was calibrated, when it has more than one,
e.g. `intrinsics` vs `extrinsics`, or `pose` vs `home-offsets`. Validated like a
slug. Miru resolves it to one of the component's `aspects`, creating the aspect
the first time it is reported.
e.g. `intrinsics` vs `extrinsics`, or `pose` vs `joint-offsets`. Validated like
a slug. Miru resolves it to one of the component's `aspects`, creating the
aspect the first time it is reported.

role: Slug of the role the calibrated unit fills on the device, such as `aligner-1` or
role: Slug of the role the calibrated unit fills on the device, such as `left` or
`front-left`. Distinguishes several units of one component on a device.
Validated like a slug. Miru resolves it to one of the component's `roles`,
creating the role the first time it is reported.

serial_number: Serial number of the physical unit that was calibrated, not of the device, such
as `M500-1234`. Free text, at most 255 characters.
as `SN-1234`. Free text, at most 255 characters.

extra_headers: Send extra headers

Expand All @@ -198,7 +178,7 @@ async def create(
"/calibrations",
body=await async_maybe_transform(
{
"component_slug": component_slug,
"component": component,
"content": content,
"aspect": aspect,
"role": role,
Expand Down
17 changes: 9 additions & 8 deletions src/miru_device_sdk/types/calibration_create_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@


class CalibrationCreateParams(TypedDict, total=False):
component_slug: Required[str]
"""Slug of the component the calibrated unit belongs to.
component: Required[str]
"""Slug of the component the calibrated unit belongs to, such as `arm`.

A new slug creates a component in Miru.
Miru resolves it to the component, creating the component the first time it is
reported.
"""

content: Required[Content]
Expand All @@ -23,14 +24,14 @@ class CalibrationCreateParams(TypedDict, total=False):
aspect: str
"""
Slug of the aspect of the unit that was calibrated, when it has more than one,
e.g. `intrinsics` vs `extrinsics`, or `pose` vs `home-offsets`. Validated like a
slug. Miru resolves it to one of the component's `aspects`, creating the aspect
the first time it is reported.
e.g. `intrinsics` vs `extrinsics`, or `pose` vs `joint-offsets`. Validated like
a slug. Miru resolves it to one of the component's `aspects`, creating the
aspect the first time it is reported.
"""

role: str
"""
Slug of the role the calibrated unit fills on the device, such as `aligner-1` or
Slug of the role the calibrated unit fills on the device, such as `left` or
`front-left`. Distinguishes several units of one component on a device.
Validated like a slug. Miru resolves it to one of the component's `roles`,
creating the role the first time it is reported.
Expand All @@ -39,7 +40,7 @@ class CalibrationCreateParams(TypedDict, total=False):
serial_number: str
"""
Serial number of the physical unit that was calibrated, not of the device, such
as `M500-1234`. Free text, at most 255 characters.
as `SN-1234`. Free text, at most 255 characters.
"""


Expand Down
24 changes: 12 additions & 12 deletions tests/api_resources/test_calibrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class TestCalibrations:
@parametrize
def test_method_create(self, client: Miru) -> None:
calibration = client.calibrations.create(
component_slug="arm",
component="arm",
content={
"data": '{\n "enable_autonomy": true,\n "enable_remote_control": true,\n "max_payload_kg": 10.0\n}\n',
"format": "json",
Expand All @@ -33,22 +33,22 @@ def test_method_create(self, client: Miru) -> None:
@parametrize
def test_method_create_with_all_params(self, client: Miru) -> None:
calibration = client.calibrations.create(
component_slug="arm",
component="arm",
content={
"data": '{\n "enable_autonomy": true,\n "enable_remote_control": true,\n "max_payload_kg": 10.0\n}\n',
"format": "json",
},
aspect="pose",
role="aligner-1",
serial_number="M500-1234",
role="front-left",
serial_number="SN-1234",
)
assert_matches_type(CalibrationCreateResponse, calibration, path=["response"])

@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_raw_response_create(self, client: Miru) -> None:
response = client.calibrations.with_raw_response.create(
component_slug="arm",
component="arm",
content={
"data": '{\n "enable_autonomy": true,\n "enable_remote_control": true,\n "max_payload_kg": 10.0\n}\n',
"format": "json",
Expand All @@ -64,7 +64,7 @@ def test_raw_response_create(self, client: Miru) -> None:
@parametrize
def test_streaming_response_create(self, client: Miru) -> None:
with client.calibrations.with_streaming_response.create(
component_slug="arm",
component="arm",
content={
"data": '{\n "enable_autonomy": true,\n "enable_remote_control": true,\n "max_payload_kg": 10.0\n}\n',
"format": "json",
Expand All @@ -88,7 +88,7 @@ class TestAsyncCalibrations:
@parametrize
async def test_method_create(self, async_client: AsyncMiru) -> None:
calibration = await async_client.calibrations.create(
component_slug="arm",
component="arm",
content={
"data": '{\n "enable_autonomy": true,\n "enable_remote_control": true,\n "max_payload_kg": 10.0\n}\n',
"format": "json",
Expand All @@ -100,22 +100,22 @@ async def test_method_create(self, async_client: AsyncMiru) -> None:
@parametrize
async def test_method_create_with_all_params(self, async_client: AsyncMiru) -> None:
calibration = await async_client.calibrations.create(
component_slug="arm",
component="arm",
content={
"data": '{\n "enable_autonomy": true,\n "enable_remote_control": true,\n "max_payload_kg": 10.0\n}\n',
"format": "json",
},
aspect="pose",
role="aligner-1",
serial_number="M500-1234",
role="front-left",
serial_number="SN-1234",
)
assert_matches_type(CalibrationCreateResponse, calibration, path=["response"])

@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_raw_response_create(self, async_client: AsyncMiru) -> None:
response = await async_client.calibrations.with_raw_response.create(
component_slug="arm",
component="arm",
content={
"data": '{\n "enable_autonomy": true,\n "enable_remote_control": true,\n "max_payload_kg": 10.0\n}\n',
"format": "json",
Expand All @@ -131,7 +131,7 @@ async def test_raw_response_create(self, async_client: AsyncMiru) -> None:
@parametrize
async def test_streaming_response_create(self, async_client: AsyncMiru) -> None:
async with async_client.calibrations.with_streaming_response.create(
component_slug="arm",
component="arm",
content={
"data": '{\n "enable_autonomy": true,\n "enable_remote_control": true,\n "max_payload_kg": 10.0\n}\n',
"format": "json",
Expand Down
Loading