From d6b7349a75e8c1cddbf9b8a3794257748c872b36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Folles=C3=B8?= Date: Mon, 29 Jun 2026 14:10:51 +0200 Subject: [PATCH 1/4] Add in-dive annotation messages Define the protocol for adding annotations during a dive and syncing them across connected clients and into the dive logfile. Data (message_formats.proto): AnnotationType (catalogue entry), Annotation (a recorded annotation), AnnotationCatalog (a catalogue snapshot), and the AnnotationSource enum. Mirrors the annotation model in Blueye Cloud; types are referenced by slug. Down, app -> drone (requests): AnnotationCtrl (control.proto) to add one; SetAnnotationCatalogReq/Rep (req_rep.proto) to push the catalogue. Up, drone -> all clients (broadcast + logged): AnnotationTel and AnnotationCatalogTel (telemetry.proto). The drone echoes a client's AnnotationCtrl as an AnnotationTel to every connected client and writes it to the dive logfile, so clients converge and the log is self-describing; the drone can also originate an AnnotationTel itself. Late joiners fetch the latest via GetTelemetryReq by message name. Additive and backward-compatible; no new imports. VersionPrefix left untouched (protocol version tracks the BlueyeApp version and is bumped with it). Co-Authored-By: Claude Opus 4.8 (1M context) --- protobuf_definitions/control.proto | 10 +++++ protobuf_definitions/message_formats.proto | 46 ++++++++++++++++++++++ protobuf_definitions/req_rep.proto | 14 +++++++ protobuf_definitions/telemetry.proto | 19 +++++++++ 4 files changed, 89 insertions(+) diff --git a/protobuf_definitions/control.proto b/protobuf_definitions/control.proto index a9c02ef4..7d312654 100644 --- a/protobuf_definitions/control.proto +++ b/protobuf_definitions/control.proto @@ -261,3 +261,13 @@ message SetSotTargetCtrl { // Issue a command to clear the single-object tracking (SOT) target (stop tracking). message ClearSotTargetCtrl { } + +// Request to add an annotation to the current dive. +// +// Sent by the app when a diver picks a type from the in-dive command palette. +// The drone stamps the authoritative time, publishes the result as an +// AnnotationTel to every connected client, and writes it to the dive logfile, so +// all clients converge on the same set. Fire-and-forget, like LightsCtrl. +message AnnotationCtrl { + Annotation annotation = 1; // The annotation to record. The drone owns the timestamp, not this message. +} diff --git a/protobuf_definitions/message_formats.proto b/protobuf_definitions/message_formats.proto index 76ca9869..1b2398c2 100644 --- a/protobuf_definitions/message_formats.proto +++ b/protobuf_definitions/message_formats.proto @@ -1621,3 +1621,49 @@ message SotState { uint32 image_width = 3; // Width of the source frame in pixels. uint32 image_height = 4; // Height of the source frame in pixels. } + +// Origin of an annotation. Mirrors the AnnotationSource enum in Blueye Cloud. +enum AnnotationSource { + ANNOTATION_SOURCE_UNSPECIFIED = 0; // Origin not specified. + ANNOTATION_SOURCE_CLOUD = 1; // Created in Blueye Cloud after the dive. + ANNOTATION_SOURCE_APP = 2; // Created in the app during the dive. + ANNOTATION_SOURCE_DRONE = 3; // Emitted by the drone itself. +} + +// A user-defined annotation type from a Blueye Cloud organisation's catalogue. +// +// The organisation maintains a catalogue of these in Blueye Cloud. It is synced +// to the app, pushed to the drone, and embedded in the dive logfile. The cloud +// is the authority on the documented length limits. +message AnnotationType { + string slug = 1; // Stable per-org id, lower-case, unique, max 50 chars (e.g. "hazard"). + string name = 2; // Title shown in the palette, max 50 chars (e.g. "Hazard"). + string icon = 3; // Font Awesome solid icon name, kebab-case, no prefix (e.g. "triangle-exclamation"). + string color = 4; // RGB hex including the leading '#', always 7 chars (e.g. "#DC2626"). + int32 sort_order = 5; // Palette sort position, ascending, 0-based. + string id = 6; // Optional cloud UUID (RFC 4122). Informational; prefer slug. Empty if app-local. +} + +// A single annotation added during a dive, e.g. by a diver tapping a type in the +// app command palette, or emitted by the drone itself. +// +// The drone owns the timestamp: when this is logged the enclosing BinlogRecord +// carries the time, like any other sample, so there is no time field here. +message Annotation { + string type_slug = 1; // Slug of the chosen AnnotationType. Empty for a free-form annotation. + string title = 2; // Optional short title; cloud falls back to the type name if empty. Max 200 chars. + string body = 3; // Optional free-form note. Unbounded. + google.protobuf.Duration duration = 4; // Optional span; omit for a point-in-time marker. + AnnotationSource source = 5; // Where the annotation originated. +} + +// A snapshot of an organisation's annotation-type catalogue. +// +// The drone logs one near the start of the dive so every Annotation.type_slug in +// the logfile resolves to a name/icon/color even if the type is later renamed or +// deleted in Blueye Cloud. +message AnnotationCatalog { + string organisation_slug = 1; // Org the catalogue belongs to (e.g. "blueye-robotics"). + repeated AnnotationType types = 2; // Available annotation types, ordered by sort_order. + google.protobuf.Timestamp synced_at = 3; // When the catalogue was fetched from cloud (UTC). +} diff --git a/protobuf_definitions/req_rep.proto b/protobuf_definitions/req_rep.proto index 2ef5947f..c7c255f9 100644 --- a/protobuf_definitions/req_rep.proto +++ b/protobuf_definitions/req_rep.proto @@ -246,3 +246,17 @@ message GetLogStreamingStatusReq { message GetLogStreamingStatusRep { bool enabled = 1; // If log streaming is enabled or disabled. } + +// Set (replace) the annotation-type catalogue on the drone. +// +// Pushed by the app after it syncs the organisation's catalogue from Blueye +// Cloud. The drone keeps it for the session, publishes it as an +// AnnotationCatalogTel, and logs a snapshot so the dive logfile is +// self-describing. Mirrors SetMissionReq/SetMissionRep. +message SetAnnotationCatalogReq { + AnnotationCatalog catalog = 1; // The full catalogue. Replaces any previously set catalogue. +} + +// Response after setting the annotation catalogue. +message SetAnnotationCatalogRep { +} diff --git a/protobuf_definitions/telemetry.proto b/protobuf_definitions/telemetry.proto index 1ed074e5..a292f658 100644 --- a/protobuf_definitions/telemetry.proto +++ b/protobuf_definitions/telemetry.proto @@ -362,3 +362,22 @@ message CameraPanTiltZoomTel { message SotStateTel { SotState sot_state = 1; // Current SOT state and bounding box. } + +// An annotation that now exists on the dive. +// +// Either added by a client (the drone echoes its AnnotationCtrl here) or emitted +// by the drone itself; Annotation.source distinguishes the two. Published to +// every connected client and written to the dive logfile. A late-joining client +// can fetch the most recent one with GetTelemetryReq("AnnotationTel"). +message AnnotationTel { + Annotation annotation = 1; // The annotation that was added. +} + +// The annotation-type catalogue currently active on the drone. +// +// Published when a client sets it with SetAnnotationCatalogReq and written to the +// dive logfile. A late-joining client fetches it with +// GetTelemetryReq("AnnotationCatalogTel") so it can render annotation icons. +message AnnotationCatalogTel { + AnnotationCatalog catalog = 1; // The active catalogue. +} From 93da4fb14974feeea8438cfecba3c9a32c7654c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Folles=C3=B8?= Date: Mon, 29 Jun 2026 14:35:10 +0200 Subject: [PATCH 2/4] Clarify expected Annotation.source values in docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document, per review feedback, that the app sets source = APP and the drone sets DRONE for its own annotations, and that CLOUD is the post-dive cloud path not seen on the live drone telemetry path. Documentation only — source remains an informational provenance hint, intentionally not enforced (this is a first-party, non-adversarial protocol). Co-Authored-By: Claude Opus 4.8 (1M context) --- protobuf_definitions/message_formats.proto | 2 +- protobuf_definitions/telemetry.proto | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/protobuf_definitions/message_formats.proto b/protobuf_definitions/message_formats.proto index 1b2398c2..8a1526eb 100644 --- a/protobuf_definitions/message_formats.proto +++ b/protobuf_definitions/message_formats.proto @@ -1654,7 +1654,7 @@ message Annotation { string title = 2; // Optional short title; cloud falls back to the type name if empty. Max 200 chars. string body = 3; // Optional free-form note. Unbounded. google.protobuf.Duration duration = 4; // Optional span; omit for a point-in-time marker. - AnnotationSource source = 5; // Where the annotation originated. + AnnotationSource source = 5; // Origin of the annotation; the app sets APP, the drone sets DRONE for its own. } // A snapshot of an organisation's annotation-type catalogue. diff --git a/protobuf_definitions/telemetry.proto b/protobuf_definitions/telemetry.proto index a292f658..20e7ec87 100644 --- a/protobuf_definitions/telemetry.proto +++ b/protobuf_definitions/telemetry.proto @@ -365,10 +365,11 @@ message SotStateTel { // An annotation that now exists on the dive. // -// Either added by a client (the drone echoes its AnnotationCtrl here) or emitted -// by the drone itself; Annotation.source distinguishes the two. Published to -// every connected client and written to the dive logfile. A late-joining client -// can fetch the most recent one with GetTelemetryReq("AnnotationTel"). +// Either added by a client (the drone echoes its AnnotationCtrl here, source APP) +// or emitted by the drone itself (source DRONE); CLOUD is the post-dive Blueye +// Cloud path and is not seen here. Published to every connected client and +// written to the dive logfile. A late-joining client can fetch the most recent +// one with GetTelemetryReq("AnnotationTel"). message AnnotationTel { Annotation annotation = 1; // The annotation that was added. } From 867b82396c576eb962a137605d2172895ac60302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Folles=C3=B8?= Date: Tue, 30 Jun 2026 10:06:04 +0200 Subject: [PATCH 3/4] Simplify: self-contained annotations, drop catalogue messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make each Annotation self-cohesive — it now carries the type's display fields (type_name, type_icon, type_color) inline alongside type_slug — so the drone, connected clients, and the dive logfile can render it without a separate catalogue. The app keeps the full catalogue locally (synced from Blueye Cloud) to drive its palette; only complete annotations go over the wire. type_slug is kept so Blueye Cloud can correlate the annotation to its type on import. Removes AnnotationType and AnnotationCatalog (message_formats.proto), SetAnnotationCatalogReq/Rep (req_rep.proto), and AnnotationCatalogTel (telemetry.proto). No catalogue push or ordering dependency; late joiners get a fully self-renderable annotation from GetTelemetryReq("AnnotationTel"). Co-Authored-By: Claude Opus 4.8 (1M context) --- protobuf_definitions/message_formats.proto | 44 +++++++--------------- protobuf_definitions/req_rep.proto | 14 ------- protobuf_definitions/telemetry.proto | 9 ----- 3 files changed, 14 insertions(+), 53 deletions(-) diff --git a/protobuf_definitions/message_formats.proto b/protobuf_definitions/message_formats.proto index 8a1526eb..aa606a65 100644 --- a/protobuf_definitions/message_formats.proto +++ b/protobuf_definitions/message_formats.proto @@ -1630,40 +1630,24 @@ enum AnnotationSource { ANNOTATION_SOURCE_DRONE = 3; // Emitted by the drone itself. } -// A user-defined annotation type from a Blueye Cloud organisation's catalogue. -// -// The organisation maintains a catalogue of these in Blueye Cloud. It is synced -// to the app, pushed to the drone, and embedded in the dive logfile. The cloud -// is the authority on the documented length limits. -message AnnotationType { - string slug = 1; // Stable per-org id, lower-case, unique, max 50 chars (e.g. "hazard"). - string name = 2; // Title shown in the palette, max 50 chars (e.g. "Hazard"). - string icon = 3; // Font Awesome solid icon name, kebab-case, no prefix (e.g. "triangle-exclamation"). - string color = 4; // RGB hex including the leading '#', always 7 chars (e.g. "#DC2626"). - int32 sort_order = 5; // Palette sort position, ascending, 0-based. - string id = 6; // Optional cloud UUID (RFC 4122). Informational; prefer slug. Empty if app-local. -} - // A single annotation added during a dive, e.g. by a diver tapping a type in the // app command palette, or emitted by the drone itself. // +// Self-contained: it carries the type's display fields (name/icon/color) inline, +// so the drone, any connected client, and the dive logfile can render it without +// a separate catalogue. The app keeps the full catalogue locally (synced from +// Blueye Cloud) to drive its palette, but only complete annotations go over the +// wire. type_slug lets Blueye Cloud correlate the annotation to its type on import. +// // The drone owns the timestamp: when this is logged the enclosing BinlogRecord // carries the time, like any other sample, so there is no time field here. message Annotation { - string type_slug = 1; // Slug of the chosen AnnotationType. Empty for a free-form annotation. - string title = 2; // Optional short title; cloud falls back to the type name if empty. Max 200 chars. - string body = 3; // Optional free-form note. Unbounded. - google.protobuf.Duration duration = 4; // Optional span; omit for a point-in-time marker. - AnnotationSource source = 5; // Origin of the annotation; the app sets APP, the drone sets DRONE for its own. -} - -// A snapshot of an organisation's annotation-type catalogue. -// -// The drone logs one near the start of the dive so every Annotation.type_slug in -// the logfile resolves to a name/icon/color even if the type is later renamed or -// deleted in Blueye Cloud. -message AnnotationCatalog { - string organisation_slug = 1; // Org the catalogue belongs to (e.g. "blueye-robotics"). - repeated AnnotationType types = 2; // Available annotation types, ordered by sort_order. - google.protobuf.Timestamp synced_at = 3; // When the catalogue was fetched from cloud (UTC). + string type_slug = 1; // Stable type id for grouping / Cloud correlation. Empty for a free-form annotation. + string type_name = 2; // Type display name, e.g. "Hazard". Snapshot of the type at creation time. + string type_icon = 3; // Font Awesome solid icon name, kebab-case (e.g. "triangle-exclamation"). + string type_color = 4; // Type colour, RGB hex incl. leading '#', 7 chars (e.g. "#DC2626"). + string title = 5; // Optional annotation title; cloud falls back to type_name if empty. Max 200 chars. + string body = 6; // Optional free-form note. Unbounded. + google.protobuf.Duration duration = 7; // Optional span; omit for a point-in-time marker. + AnnotationSource source = 8; // Origin of the annotation; the app sets APP, the drone sets DRONE for its own. } diff --git a/protobuf_definitions/req_rep.proto b/protobuf_definitions/req_rep.proto index c7c255f9..2ef5947f 100644 --- a/protobuf_definitions/req_rep.proto +++ b/protobuf_definitions/req_rep.proto @@ -246,17 +246,3 @@ message GetLogStreamingStatusReq { message GetLogStreamingStatusRep { bool enabled = 1; // If log streaming is enabled or disabled. } - -// Set (replace) the annotation-type catalogue on the drone. -// -// Pushed by the app after it syncs the organisation's catalogue from Blueye -// Cloud. The drone keeps it for the session, publishes it as an -// AnnotationCatalogTel, and logs a snapshot so the dive logfile is -// self-describing. Mirrors SetMissionReq/SetMissionRep. -message SetAnnotationCatalogReq { - AnnotationCatalog catalog = 1; // The full catalogue. Replaces any previously set catalogue. -} - -// Response after setting the annotation catalogue. -message SetAnnotationCatalogRep { -} diff --git a/protobuf_definitions/telemetry.proto b/protobuf_definitions/telemetry.proto index 20e7ec87..117187d3 100644 --- a/protobuf_definitions/telemetry.proto +++ b/protobuf_definitions/telemetry.proto @@ -373,12 +373,3 @@ message SotStateTel { message AnnotationTel { Annotation annotation = 1; // The annotation that was added. } - -// The annotation-type catalogue currently active on the drone. -// -// Published when a client sets it with SetAnnotationCatalogReq and written to the -// dive logfile. A late-joining client fetches it with -// GetTelemetryReq("AnnotationCatalogTel") so it can render annotation icons. -message AnnotationCatalogTel { - AnnotationCatalog catalog = 1; // The active catalogue. -} From 7b6e692176d9c76aa6ad0fd033d7d950a4987763 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Folles=C3=B8?= Date: Tue, 30 Jun 2026 10:16:54 +0200 Subject: [PATCH 4/4] Fix comment spelling consistency (American English, capitalize Cloud) Per review: colour -> color (matches the color/type_color field names), catalogue -> catalog, and "cloud" -> "Cloud" to match the file's "Blueye Cloud" usage. Comments only. Co-Authored-By: Claude Opus 4.8 (1M context) --- protobuf_definitions/message_formats.proto | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/protobuf_definitions/message_formats.proto b/protobuf_definitions/message_formats.proto index aa606a65..130fdf11 100644 --- a/protobuf_definitions/message_formats.proto +++ b/protobuf_definitions/message_formats.proto @@ -1635,7 +1635,7 @@ enum AnnotationSource { // // Self-contained: it carries the type's display fields (name/icon/color) inline, // so the drone, any connected client, and the dive logfile can render it without -// a separate catalogue. The app keeps the full catalogue locally (synced from +// a separate catalog. The app keeps the full catalog locally (synced from // Blueye Cloud) to drive its palette, but only complete annotations go over the // wire. type_slug lets Blueye Cloud correlate the annotation to its type on import. // @@ -1645,8 +1645,8 @@ message Annotation { string type_slug = 1; // Stable type id for grouping / Cloud correlation. Empty for a free-form annotation. string type_name = 2; // Type display name, e.g. "Hazard". Snapshot of the type at creation time. string type_icon = 3; // Font Awesome solid icon name, kebab-case (e.g. "triangle-exclamation"). - string type_color = 4; // Type colour, RGB hex incl. leading '#', 7 chars (e.g. "#DC2626"). - string title = 5; // Optional annotation title; cloud falls back to type_name if empty. Max 200 chars. + string type_color = 4; // Type color, RGB hex incl. leading '#', 7 chars (e.g. "#DC2626"). + string title = 5; // Optional annotation title; Cloud falls back to type_name if empty. Max 200 chars. string body = 6; // Optional free-form note. Unbounded. google.protobuf.Duration duration = 7; // Optional span; omit for a point-in-time marker. AnnotationSource source = 8; // Origin of the annotation; the app sets APP, the drone sets DRONE for its own.