From 51933cb3b297d4b1cc3780a16a78e55064e51063 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 5 Sep 2025 15:55:58 +0100 Subject: [PATCH 01/38] sitemap config --- .vitepress/config.mts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.vitepress/config.mts b/.vitepress/config.mts index dac7a66..f8eca80 100644 --- a/.vitepress/config.mts +++ b/.vitepress/config.mts @@ -94,6 +94,9 @@ export default withMermaid(defineConfig({ description: "home for everything Lightbug", lang: 'en-GB', cleanUrls: true, + sitemap: { + hostname: process.env.DEPLOYMENT_NAME === 'Production' ? 'https://docs.lightbug.io' : 'https://docs-next.lightbug.io' + }, rewrites: { '/onprem/' : '/silos/', }, From 0fc92476bdb77ddfa723f2066e43a1c167845611 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 5 Sep 2025 17:06:20 +0100 Subject: [PATCH 02/38] Rework the device API, protocol and message page layouts --- .vitepress/config.mts | 67 ++- .vitepress/theme/Layout.vue | 2 + cspell.json | 18 +- devices/api/glossary.md | 16 +- devices/api/headers.md | 93 ---- devices/api/index.md | 37 +- devices/api/messages/5-ack.md | 2 +- devices/api/messages/index.md | 8 +- devices/api/protocol/examples.md | 44 ++ devices/api/protocol/headers.md | 126 +++++ devices/api/protocol/index.md | 81 +++ devices/api/protocol/prefix.md | 15 + devices/api/protocol/stop.md | 15 + devices/api/protocol/structure.md | 40 ++ devices/api/sdks/toit/examples/eink.md | 96 ---- devices/api/sdks/toit/examples/index.md | 4 +- devices/api/sdks/toit/getting-started.md | 110 +++- devices/api/structure.md | 148 ------ public/files/protocol-v3.yaml | 642 +++++++++++++++++------ 19 files changed, 988 insertions(+), 576 deletions(-) delete mode 100644 devices/api/headers.md create mode 100644 devices/api/protocol/examples.md create mode 100644 devices/api/protocol/headers.md create mode 100644 devices/api/protocol/index.md create mode 100644 devices/api/protocol/prefix.md create mode 100644 devices/api/protocol/stop.md create mode 100644 devices/api/protocol/structure.md delete mode 100644 devices/api/sdks/toit/examples/eink.md delete mode 100644 devices/api/structure.md diff --git a/.vitepress/config.mts b/.vitepress/config.mts index f8eca80..59bfc9f 100644 --- a/.vitepress/config.mts +++ b/.vitepress/config.mts @@ -291,34 +291,49 @@ export default withMermaid(defineConfig({ link: '/devices/api/glossary', }, { - text: 'Structure', + text: 'Toit', + link: '/devices/api/sdks/toit/', + items: [ + { text: 'Getting Started', link: '/devices/api/sdks/toit/getting-started' }, + { + text: 'Examples', + link: '/devices/api/sdks/toit/examples/', + }, + ], + }, + { + text: 'Messages', collapsed: true, - link: '/devices/api/structure', + link: '/devices/api/messages', + items: protocolMenuItems, + }, + { + text: 'Protocol', + collapsed: true, + link: '/devices/api/protocol/', items: [ { text: 'Prefix', - link: '/devices/api/structure#prefix', + link: '/devices/api/protocol/prefix', + }, + { + text: 'Stop', + link: '/devices/api/protocol/stop', }, { - text: 'Message', - link: '/devices/api/structure#message', + text: 'Structure', + link: '/devices/api/protocol/structure', + }, + { + text: 'Headers', + link: '/devices/api/protocol/headers', }, { text: 'Examples', - link: '/devices/api/structure#examples', + link: '/devices/api/protocol/examples', }, ] }, - { - text: 'Headers', - link: '/devices/api/headers', - }, - { - text: 'Messages', - collapsed: true, - link: '/devices/api/messages', - items: protocolMenuItems, - }, { text: 'Tools', collapsed: true, @@ -337,26 +352,6 @@ export default withMermaid(defineConfig({ }, ] }, - { - text: 'SDKs', - collapsed: true, - items: [ - { - text: 'Toit', - link: '/devices/api/sdks/toit/', - items: [ - { text: 'Getting Started', link: '/devices/api/sdks/toit/getting-started' }, - { - text: 'Examples', - link: '/devices/api/sdks/toit/examples/', - items: [ - { text: 'EInk Hello World', link: '/devices/api/sdks/toit/examples/eink' }, - ] - }, - ], - }, - ] - }, ] }, ], diff --git a/.vitepress/theme/Layout.vue b/.vitepress/theme/Layout.vue index 262f49e..b60ef6b 100644 --- a/.vitepress/theme/Layout.vue +++ b/.vitepress/theme/Layout.vue @@ -17,6 +17,8 @@ const redirects = Object.entries({ '/devices/api/generate': '/devices/api/tools/generate', '/apps/admin/creating-account': '/apps/admin/authentication#creating-an-account', '/apps/admin/permissions': '/apps/admin/authentication#permissions', + '/devices/api/structure': '/devices/api/protocol', + '/devices/api/headers': '/devices/api/protocol/headers', }) watch( diff --git a/cspell.json b/cspell.json index dd6eb4e..835ca79 100644 --- a/cspell.json +++ b/cspell.json @@ -5,7 +5,8 @@ "package-lock.json", "package.json", "public/device-specs/**/*.yaml", - "public/swagger/v1.json" + "public/swagger/v1.json", + "public/files/protocol-v3.yaml" ], "dictionaryDefinitions": [], "dictionaries": [], @@ -52,7 +53,20 @@ "multipath", "iccid", "imei", - "NTRIP" + "NTRIP", + "microcontroller", + "UART", + "HPSYS", + "SPIWP", + "acking", + "RSSI", + "actioned", + "centi", + "DGNSS", + "NMEA", + "WCDMA", + "hectopascals", + "codegen" ], "ignoreWords": [], "import": [] diff --git a/devices/api/glossary.md b/devices/api/glossary.md index a036b60..9c271fb 100644 --- a/devices/api/glossary.md +++ b/devices/api/glossary.md @@ -6,10 +6,12 @@ outline: deep | Term | Description | | ------------------ | ----------------------------------------------------------------------- | -| Message | A single message sent between hosts | -| Prefix | Optional bytes before a message to make it easier to see in a byte stream. | -| Header | The first part of the message that contains metadata about the message. | -| Payload | The primary data that is being sent in the message. | -| Header field type | A field type (uint8) that is used within header data | -| Payload field type | A field type (uint8) that is used within payload data | -| bBytes | A byte array that represents a length and then the data itself, such as `3 9 9 9`, where `3` is the length | +| [Toit](/devices/api/sdks/toit/) | A programming language designed for IoT devices, which we provide a [high level SDK](/devices/api/sdks/toit/) for.
[[Toit official website](https://toitlang.org/)] | +| ESP32 | A popular low-cost microcontroller with built-in WiFi and Bluetooth, used in some Lightbug devices.
[[ESP32 on Wikipedia](https://en.wikipedia.org/wiki/ESP32)] | +| Message | A packet of communication sent between clients, using the [Lightbug protocol](/devices/api/protocol/). | +| [Prefix](/devices/api/protocol/prefix) | Optional bytes before a message to make it easier to see in a byte stream.
`0x4c, 0x42` which is `LB` in ascii. | +| Header | The first part of the message that contains metadata about a message.
Length, type, method, and other generic metadata or instruction. | +| Payload | The primary data that is being sent in a message. | +| [Header field](/devices/api/protocol/headers) type | A field type `uint8` that is used within header data.
These are generally reused across all message types. | +| Payload field type | A field type `uint8` that is used within payload data.
These are generally specific to a message type.
See [messages](/devices/api/messages/). | +| bBytes | A byte array that represents a length and that amount of data in bytes.
A bByte entry might be `3 9 9 9`, where `3` is the length, and `9 9 9` is the data. | diff --git a/devices/api/headers.md b/devices/api/headers.md deleted file mode 100644 index 3b81c11..0000000 --- a/devices/api/headers.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -outline: [1,3] ---- - - - -# Headers - -These header field types are reserved across all message types. - - - -## 1: Message ID - -Used to identify the message. - -Can be one of uint8, uint16, uint32, or uint64, as Little Endian. - -Should be used with ACK messages to identify the message that is being ACKed. - -ACKs should return the same message ID and type as the message being ACKed. - -## 2: Client ID - -Used to identify the client that sent the message where appropriate. - -## 3: Response Message ID - -This message is responding to a previous message with the specified message ID. - -Should be the same message ID and type as the message being responded to. - -## 4: Message Status - -The status of the response. - -Values below 0 indicate an OK status, but with special meanings. - -Values above 0 indicate an error or status that is not OK. - -| Value | Status Description | -|--------|----------------------------------------------------| -| 0 | OK | -| -1 | OK Processing (there will be one or more further responses) | -| -126 | OK No Ack (internal only, message does not require an ACK) | -| -127 | OK Already Responded (message has already been responded to) | -| 1 | NOT OK (Generic error) | -| 3 | Method not supported | -| 4 | Invalid payload param | -| 5 | Invalid state | -| 6 | No data | -| 7 | Not supported | -| 8 | Failed, will retry | -| 9 | Failed permanently | -| 10 | Unknown message | - -## 5: Method - -- 1: Set, Request a change, using the data provided. -- 2: Get, Request the current value or values. -- 3: Subscribe, Request to be notified with updates for the message. -- 4: Do, Perform an action, using the data provided. -- 5: Unsubscribe, Request to no longer be notified with updates for the message. - -## 6: Subscription interval - -Interval in ms. To be used with the SUBSCRIBE method - - - -## 10: Forwarded For - -ID of the client sending the original message that is being forwarded. - -## 11: Forwarded RSSI - -RSSI of forwarded message, where applicable. - -## 12: Forwarded SNR - -SNR of forwarded message, where applicable. - - - -## 14: Forward To - -ID of the client to forward the message to. Forwarding to type alters the meaning of this field diff --git a/devices/api/index.md b/devices/api/index.md index 204dffc..3d29e27 100644 --- a/devices/api/index.md +++ b/devices/api/index.md @@ -8,38 +8,27 @@ import ProtocolBytes from '../../components/ProtocolBytes.vue'; # Device API -The device API makes use of the Lightbug communication protocol, also known as the V3 protocol, which is a byte oriented protocol used for device communication. +The device API is a messaging system for communicating with Lightbug devices. It allows you to: + - send commands to the device + - ask for data (GET) + - subscribe to data streams (SUBSCRIBE) + - receive responses, instructions and other data from the device -The protocol builds on top of existing Lightbug protocols, and is designed to be: - - Easy to use and understand - - Efficient to read and build - - Lightweight to store and transmit - - Usable in a variety of settings, without complex or bloated dependencies +It makes use of the Lightbug communication protocol, also known as the V3 protocol, which is a byte oriented protocol used for device communication. -## API Access +On the wire, a single message might look like `4c 42 03 0b 00 01 00 00 00 00 00 4b be`, however, the API abstracts this away so you can work with high level messaging concepts. -API access depends on the device and may not be supported on all models. +And you can [get started with Toit quickly](/devices/api/sdks/toit/), using an SDK built on top of the messages as another layer of abstraction. Typically, it can be accessed via UART, I2C, or a UDP network connection. -For access details, please [contact our support team](https://lightbug.io/contact/). -Or you can [get started with Toit quickly](/devices/api/sdks/toit/) using the Lightbug provided SDK. +## Availability -## Example Message +Device API access depends on the device and may not be supported on all models. -An example [prefixed](structure#prefix) and minimal message might look as follows: +The [RH2 RTK device](/devices/rtk/) will be the first general release product to support the API, with a future low cost development board planned. -| Format | Message | -| ------ | --- | -| Bytes | 76 66 3 11 0 1 0 0 0 0 0 75 190 | -| Hex | `4c 42 03 0b 00 01 00 00 00 00 00 4b be` | +For details, please [contact our support team](https://lightbug.io/contact/). - - -::: info -All integers are in [little-endian](https://en.wikipedia.org/wiki/Endianness) format eg. (uint16 `1` is represented as `0x01 0x00`). -::: +If you have a supported device, you can [get started with Toit quickly](/devices/api/sdks/toit/) using the Lightbug SDK, or communicate with the device from other languages using the messages documented here. diff --git a/devices/api/messages/5-ack.md b/devices/api/messages/5-ack.md index a34ff3f..3c9fc8b 100644 --- a/devices/api/messages/5-ack.md +++ b/devices/api/messages/5-ack.md @@ -30,7 +30,7 @@ flowchart LR B -->|ACK| A ``` -The [Response Message ID](./../headers#_3-response-message-id) field in the header can be used in place of an ACK if an immediate response is being sent. +The [Response Message ID](../protocol/headers#_3-response-message-id) field in the header can be used in place of an ACK if an immediate response is being sent. In such cases the response will not have an ACK message type, instead it will have the message type of the response (often the same as the request). diff --git a/devices/api/messages/index.md b/devices/api/messages/index.md index adec2f4..6075f05 100644 --- a/devices/api/messages/index.md +++ b/devices/api/messages/index.md @@ -5,11 +5,11 @@ import ProtocolBytes from '../../../components/ProtocolBytes.vue' # Messages -Messages often make use of a few common [header fields](/devices/api/headers), and you can expect to see these in most messages types: +Messages often make use of a few common [header fields](/devices/api/protocol/headers), and you can expect to see these in most messages types: ## Requests -Requests may or may not require a [MH 5: Method](./../headers#_5-method) header field, such as `GET`, `DO`, `SET` or `SUBSCRIBE`. +Requests may or may not require a [MH 5: Method](../protocol/headers#_5-method) header field, such as `GET`, `DO`, `SET` or `SUBSCRIBE`. This is decided by the individual message type, and if required, should be included in the header. @@ -19,11 +19,11 @@ This is decided by the individual message type, and if required, should be inclu You should receive a response to every valid and expected message when the recipient is able to process the request. -When a message ID is provided in the request, any direct response should include the initiating message ID in the header [MH 3: Response Message ID](./../headers#_3-response-message-id). +When a message ID is provided in the request, any direct response should include the initiating message ID in the header [MH 3: Response Message ID](../protocol/headers#_3-response-message-id). Responses can come in the form of a basic [MT 5: ACK](./5-ack) or as the same message type as the request. -Responses should contain a [MH 4: Response Status](./../headers#_4-response-status) in the header, where 0 and below are various OK responses, and anything higher than 0 indicates a warning or error. +Responses should contain a [MH 4: Response Status](../protocol/headers#_4-response-status) in the header, where 0 and below are various OK responses, and anything higher than 0 indicates a warning or error. ## Types diff --git a/devices/api/protocol/examples.md b/devices/api/protocol/examples.md new file mode 100644 index 0000000..2940d80 --- /dev/null +++ b/devices/api/protocol/examples.md @@ -0,0 +1,44 @@ +--- +outline: [2,3] +--- + + + +# Examples + +### Type 3, header empty, data empty + + + +### .. as above, with LB prefix bytes: + + + +### Type 6, header (1:1), data empty + + + +### Type 6, header (1:9), data empty + + + +### Type 10009, header empty, data (10:hello): + + diff --git a/devices/api/protocol/headers.md b/devices/api/protocol/headers.md new file mode 100644 index 0000000..f2b4b5a --- /dev/null +++ b/devices/api/protocol/headers.md @@ -0,0 +1,126 @@ +--- +outline: [1,3] +--- + + + +# Headers + +These header field types are reserved across all message types and usages of the protocol. + + + +## 1: Message ID + +A `uint32` identifier for the message. + +Useful for debugging, tracing, deduplication, acking, re-sends, responses and subscriptions. + +## 2: Client ID + +A `uint32` or `uint64` identifier for the client sending the message. + +Can vary depending on the context of the messaging. + +## 3: Response to + +`uint32` message ID that this message is a response to. + +Can be used both for direct responses, or subscription updates. + +## 4: Message Status + +`uint8` + +The status of the message. + +If omitted, the message is assumed to be OK, or determined by the context of the message. + +0 indicates an OK status, below 0 is OK but with a special meaning. + +Values above 0 indicate an error or other status that is not OK. +| Value | Name | Description | +|-------|------|-------------| +| 0 | OK | Everything appears to be OK | +| 1 | Generic Error | | +| 2 | Missing Payload Parameter | | +| 3 | Method Not Supported | e.g., SETting GPS position | +| 4 | Invalid Payload Parameter | Unsupported values requested (e.g., requesting the GPS data at 100hz) | +| 5 | Invalid State | e.g., requesting GPS data stream before turning GPS on | +| 6 | No Data | e.g., requesting GPS or time data when not yet available | +| 7 | Not Supported | If the receiver does not support the requested action or method | +| 8 | Failed Will Retry | Currently only used for tx now | +| 9 | Failed Permanently | | +| 10 | Abandoned | Status code when an action has timed out or been deliberately cancelled by another process | +| 11 | Expired | Status code when a request has expired and has been cancelled, typically subscriptions | + +## 5: Method + +`uint8` + +The method of the message. + +| Value | Name | Description | +|-------|------|-------------| +| 1 | SET | Set one or more values that are provided in the message payload. | +| 2 | GET | Get one or more values that have their types provided in the message payload. If no payload types are provided, all available values are returned. | +| 3 | SUBSCRIBE | Subscribe to a value or values. If no payload types are provided, all available values will be subscribed to. If a value changes, the service will send a message with the new value. | +| 5 | UNSUBSCRIBE | Unsubscribe from some previously subscribed-to data. | + +## 6: Interval + +`uint32` + +Interval in ms. To be used with the SUBSCRIBE method + +## 7: Duration + +`uint32` + +Time in ms. For subscriptions this defines a duration to keep the subscription active for. 0 = no time limit. + +An ACK with EXPIRED status code will be sent if the subscription duration elapses without being renewed (with a new subscription request). + +For actions that can be timed, this defines how long the action should go on for. + +## 8: Timeout + +`uint32` + +Timeout in ms. When requesting data or services that can take time to acquire (such as a GPS fix), timeout defines how long to try for before abandoning the action. + +An ACK with ABANDONED status code will be sent in this case. + +For actions that support it, timeout defines how long to wait before performing the action. + +## 9: Forwarded For Type + +Reserved for future use. + +## 10: Forwarded For + +ID of the client sending the original message that is being forwarded. + +## 11: Forwarded RSSI + +RSSI of forwarded message, where applicable. + +## 12: Forwarded SNR + +SNR of forwarded message, where applicable. + +## 13: Forward To Type + +## 14: Forward To + +ID of the client to forward the message to. Forwarding to type alters the meaning of this field + +## 15: Storage Level + +Reserved for future use. + +## 16: Message Level + +Reserved for future use. diff --git a/devices/api/protocol/index.md b/devices/api/protocol/index.md new file mode 100644 index 0000000..b83bf25 --- /dev/null +++ b/devices/api/protocol/index.md @@ -0,0 +1,81 @@ +--- +outline: [1,3] +--- + + + +# Protocol + +The protocol is designed to be: + - Easy to use and understand + - Efficient to read and build + - Lightweight to store and transmit + - Usable in a variety of settings, without complex or bloated dependencies + + +## Byte Breakdown + +In a byte stream, you may receive a series of messages. + +The makeup of a message is as follows (including options prefix) is as follows: + +``` +[optional prefix] + + ::= + + + ::= + ::= + + ::= +``` + +::: info +All integers are in [little-endian](https://en.wikipedia.org/wiki/Endianness) format eg. (uint16 `1` is represented as `0x01 0x00`). +::: + +As an example empty message including prefix (with no additional header or payload data): + + + +## Example Message + +An example [prefixed](structure#prefix) and minimal message might look as follows: + +| Format | Message | +| ------ | --- | +| Bytes | 76 66 3 11 0 1 0 0 0 0 0 75 190 | +| Hex | `4c 42 03 0b 00 01 00 00 00 00 00 4b be` | + + + +::: info +All integers are in [little-endian](https://en.wikipedia.org/wiki/Endianness) format eg. (uint16 `1` is represented as `0x01 0x00`). +::: + +The general structure of a message is as follows: + +| Byte position | Description | Type | Example | +| ------------- | ---------------------------------- | ------------------------- | -- | +| 1 | Protocol Version (always 3) | uint8 | 3 | +| 2 -> 3 | Message Length | uint16 | 11 0 | +| 4 -> 5 | Message Type | uint16 | 1 0 | +| 6 -> a | Header Data (field count, fields, data) | uint16, []uint8, []bBytes | 0 0 | +| a -> b | Payload Data (field count, fields, data) | uint16, []uint8, []bBytes | 0 0 | +| b -> n | Checksum | uint16 | 75 190 | + +So the full above example would be: + + diff --git a/devices/api/protocol/prefix.md b/devices/api/protocol/prefix.md new file mode 100644 index 0000000..d0628d5 --- /dev/null +++ b/devices/api/protocol/prefix.md @@ -0,0 +1,15 @@ +--- +outline: [2,3] +--- + +# Prefix + +To aid reading from a possibly noisy byte stream, such as UART, and to increase efficiency, the messages can be prefixed with a set of start bytes. + +`0x4c, 0x42`, or `76, 66`, which is the ASCII representation of `LB`. + +In combination with the protocol version, this allows for a simple check that indicates you are probably looking at the start of a message. + +`0x4c 0x42 0x03` or `76 66 3` for example. + +Communication methods such as I2C or UDP will not include this prefix. diff --git a/devices/api/protocol/stop.md b/devices/api/protocol/stop.md new file mode 100644 index 0000000..d570ddf --- /dev/null +++ b/devices/api/protocol/stop.md @@ -0,0 +1,15 @@ +--- +outline: [2,3] +--- + +# Stop + +The 2 bytes after the prefix and protocol version indicate the length of the message which gives you a stop point. + +In total the first 5 bytes would look something like this with a prefix: + +`0x4c 0x42 0x03 0x0b 0x00` or `76 66 3 11 0`. + +Or with no prefix: + +`0x03 0x0b 0x00` or `3 11 0`. diff --git a/devices/api/protocol/structure.md b/devices/api/protocol/structure.md new file mode 100644 index 0000000..712e720 --- /dev/null +++ b/devices/api/protocol/structure.md @@ -0,0 +1,40 @@ +--- +outline: [2,3] +--- + + + +# Structure + +- Protocol Version: The version of the protocol (always 3). +- Message Length: The length of the message, including the version, header, data, and checksum. +- Message Type: The type of message. This is used to determine how to interpret the message. +- Data: Both data field (header and payload) are made up of the same format: + - Field Count: The number of fields in the data. + - Fields: List of field types in the data + - Data: The data itself, making used of bBytes to represent length and values. +- Checksum: A 16-bit CRC checksum of the message (XMODEM). + +### Data + +Within each Data element (the header data, or payload data), the structure is as follows: + +| Byte position | Description | Type | Example | +| ------------- | ---------------- | ----- | ------- | +| 1 -> 2 | Number of fields (n) | uint16 | 2 0 | +| 3 -> 3+n | Field types | []uint8 | 1 2 | +| 3+n+1 -> end | Data | []bBytes | [1 8] [3 9 9 9] | + +The Data should be in the order of the field types. + +The example data includes `2` data fields, the first of type `1`, with value byte array `[8]`, the second of type `2`, with value byte array `[9 9 9]`. + +#### bBytes + +bBytes are a byte array that represents a length and then the data itself. + +For example, `[1 8]` would represent a byte array of length 1, with the value 8. + +Or `[3 9 9 9]` would represent a byte array of length 3, with the values 9, 9, 9. diff --git a/devices/api/sdks/toit/examples/eink.md b/devices/api/sdks/toit/examples/eink.md deleted file mode 100644 index ace7743..0000000 --- a/devices/api/sdks/toit/examples/eink.md +++ /dev/null @@ -1,96 +0,0 @@ - - -# Eink Hello World - -The latest version of the eink example can be [found on GitHub](https://github.com/lightbug-io/toit-lightbug/blob/main/examples/eink.toit). - - -## Output - -When the program is running, you should see some output through the `monitor` command that looks something like this: - -``` -[lb-comms] INFO: Comms starting -[lb-comms] INFO: Comms started -💬 Sending text page to device -💬 Text page sent -Waiting on message latch -✅ Text page ACKed -Latch response: Message type: 5 length: 40 response-to: 3405447839 -``` - -## Device - -The device EInk screen will update, displaying the text that was included in the message. - - - -## Code - -First, the required dependencies are [imported](https://docs.toit.io/language/imports). - -This primarily includes things from the `lightbug` package, but also the [log library](https://libs.toit.io/log/library-summary) that is part of the toit SDK. - -```toit -import lightbug.devices as devices -import lightbug.services as services -import lightbug.messages as messages -import log -``` - -A `main` function is defined, which is the entry point of the toit program. - -Here we start setting up the application, such as the log level to use, the device to use, and the communication service. - -```toit -main: - // Setup the Toit logger with the INFO log level - log.set-default (log.default.with-level log.INFO-LEVEL) - - // This example is setup to work with the RH2 device - device := devices.RtkHandheld2 - - // Setup the comms service, which allows communication with the Lightbug device - comms := services.Comms --device=device -``` - -We then send a single message to the device, which is a [text page](./../../../messages/10009-text-page.md). - -The `send` function is used to send the message, and it can take a number of optional parameters that allow hooking into the message lifecycle. - -```toit - // Send a basic text page to the device to display - // https://docs.lightbug.io/devices/api/tools/parse?bytes=03%2C57%2C00%2C19%2C27%2C02%2C00%2C05%2C01%2C01%2C01%2C04%2C7f%2C0e%2C60%2C9b%2C05%2C00%2C03%2C04%2C06%2C65%2C66%2C02%2Cd1%2C07%2C0b%2C48%2C65%2C6c%2C6c%2C6f%2C20%2C77%2C6f%2C72%2C6c%2C64%2C01%2C02%2C1f%2C57%2C65%2C6c%2C63%2C6f%2C6d%2C65%2C20%2C74%2C6f%2C20%2C79%2C6f%2C75%2C72%2C20%2C4c%2C69%2C67%2C68%2C74%2C62%2C75%2C67%2C20%2C64%2C65%2C76%2C69%2C63%2C65%2C0c%2C72%2C75%2C6e%2C6e%2C69%2C6e%2C67%2C20%2C54%2C6f%2C69%2C74%2Cb8%2C71 - latch := comms.send (messages.TextPage.to-msg - --page-id=2001 - --page-title="Hello world" - --line2="Welcome to your Lightbug device" - --line3="running Toit" - --redraw-type=2 // FullRedraw - ) - --now=true - --withLatch=true - --preSend=(:: print "💬 Sending text page to device") - --postSend=(:: print "💬 Text page sent") - --onAck=(:: print "✅ Text page ACKed") - --onNack=(:: print "❌ Text page NACKed") - --onError=(:: print "❌ Text page error") -``` - -The code then waits for the message to be responded to, printing the response to the console. - -See [Latch](https://docs.toit.io/language/tasks/#latch) from the Toit [monitor library](https://libs.toit.io/monitor/library-summary). - -```toit - print "Waiting on message latch" - response := latch.get - // The value of the latch will be false on timeout or unknown error, or the msg object for the response - // Such as https://docs.lightbug.io/devices/api/tools/parse?bytes=03%2C28%2C00%2C05%2C00%2C04%2C00%2C03%2C04%2C01%2C02%2C04%2C7f%2C0e%2C60%2C9b%2C01%2C00%2C04%2Cd9%2C16%2C00%2C00%2C08%2Cb3%2Cd5%2C9b%2C00%2C00%2C00%2C00%2C00%2C01%2C00%2C01%2C02%2C19%2C27%2Ca9%2C0c - print "Latch response: $response" -``` diff --git a/devices/api/sdks/toit/examples/index.md b/devices/api/sdks/toit/examples/index.md index 85e83ee..b5e19ca 100644 --- a/devices/api/sdks/toit/examples/index.md +++ b/devices/api/sdks/toit/examples/index.md @@ -1,5 +1,7 @@ # Examples -The lightbug pkg comes with a variety of examples that can be used to get started with the Lightbug device API. +The lightbug pkg comes with various examples touching on various levels of the device communication and capabilities. These are found in the [examples directory](https://github.com/lightbug-io/toit-lightbug/tree/main/examples) of the pkg, which itself is a standalone Toit package. + +More examples and guides will be included here over time, and if you need specific help, please reach out on Discord. diff --git a/devices/api/sdks/toit/getting-started.md b/devices/api/sdks/toit/getting-started.md index f503c4b..4730432 100644 --- a/devices/api/sdks/toit/getting-started.md +++ b/devices/api/sdks/toit/getting-started.md @@ -1,11 +1,17 @@ # Getting started +::: warning ⚠️ main branch documentation +This documentation is currently for the `main` branch of the Toit SDK, which is under active development. + +There are pinned early releases available, but they may be out of date in comparison to this documentation. +::: + ### 1. Get an ESP32 enabled device -ESP32s are being introduced on some Lightbug devices. +ESP32s are being introduced on some Lightbug devices, such as the [RTK handheld (RH2)](/devices/rtk/). -You'll need one of these devices to run Toit on a Lightbug device. +You'll need one of these devices to run Toit code on. ### 2. Install Jaguar @@ -14,16 +20,16 @@ You'll need one of these devices to run Toit on a Lightbug device. Install Jaguar, [following step 2](https://docs.toit.io/getstarted/device/#2-install-jaguar) in the Toit getting started guide. ::: info -We currently require `v1.50.3`+ of Jaguar, which bundles at least `v2.0.0-alpha.179` of Toit. +We currently recommend `v1.55.0`+ of Jaguar, which bundles at least `v2.0.0-alpha.188` of Toit. ::: Once installed, you should be able to run the version command: ```sh jag version -Version: v1.50.3 -SDK version: v2.0.0-alpha.179 -Build date: 2025-04-31T10:10:10Z +Version: v1.55.0 +SDK version: v2.0.0-alpha.188 +Build date: 2025-08-20T08:35:24Z ``` ### 3. Flash the ESP @@ -44,6 +50,38 @@ After flashing, your device boots up and starts the Toit virtual machine. The Ja You can see what the ESP is doing by monitoring the serial output of the device with the `jag monitor` command. +
+Click to see example `jag monitor` output + +``` +Starting serial monitor of port 'COM22' ... +[wifi] DEBUG: connected +ESP-ROM:esp32c6-20220919 +Build:Sep 19 2022 +rst:0x15 (USB_UART_HPSYS),boot:0x6f (SPI_FAST_FLASH_BOOT) +Saved PC:0x4081306a +SPIWP:0xee +mode:DIO, clock dESP-ROM:esp32c6-20220919 +Build:Sep 19 2022 +rst:0x15 (USB_UART_HPSYS),boot:0x6f (SPI_FAST_FLASH_BOOT) +Saved PC:0x400294a2 +SPIWP:0xee +mode:DIO, clock div:2 +load:0x40875720,len:0x9c +load:0x4086c110,len:0xb94 +load:0x4086e610,len:0x2534 +entry 0x4086c110 +[toit] INFO: starting +[toit] DEBUG: clearing RTC memory: powered on by hardware source +[toit] INFO: running on ESP32C6 - revision 0.1 +[wifi] DEBUG: connecting +[wifi] DEBUG: connected +[wifi] INFO: network address dynamically assigned through dhcp {ip: 192.168.68.50} +[wifi] INFO: dns server address dynamically assigned through dhcp {ip: [8.8.8.8, 1.1.1.1]} +[jaguar.http] INFO: running Jaguar device 'long-expert' (id: '736b8804-dcdf-4d96-890a-8785c1bfa31d') on 'http://192.168.68.50:9000' +``` +
+ ::: tip Leave the terminal with `jag monitor` running open, as it will show you the output of the device, and will also show you the output of the code you run on the device. ::: @@ -65,10 +103,10 @@ VERSION=$(jag pkg search lightbug-io/toit-lightbug | awk '{print $3}') jag pkg install github.com/lightbug-io/toit-lightbug@$VERSION ``` -You can copy the example [eink based Hello World application](https://github.com/lightbug-io/toit-lightbug/blob/main/examples/eink.toit) for the RTK Handheld 2 device into your project with the following command: +You can copy the example [eink based Hello World application](https://github.com/lightbug-io/toit-lightbug/blob/main/examples/modules/eink/element-box-text.toit) into your project with the following command: ```sh -cp ./.packages/github.com/lightbug-io/toit-lightbug/$VERSION/examples/eink.toit ./main.toit +cp ./.packages/github.com/lightbug-io/toit-lightbug/$VERSION/examples/modules/eink/element-box-text.toit ./main.toit ``` ### 6. Run the code @@ -84,14 +122,52 @@ If your device can not be found automatically, you can specify the device IP add Once the code is running, you should see some output through the `monitor` command you ran earlier. ``` -[jaguar] INFO: program cf533602-d549-c9bd-34a3-3ae1093bda51 started -[lb-comms] INFO: Comms starting -[lb-comms] INFO: Comms started -💬 Sending text page to device -💬 Text page sent -Waiting on message latch -✅ Text page ACKed -Latch response: Message type: 5 length: 40 response-to: 3405447839 +[jaguar] INFO: program 970dfee5-ec68-d8ad-ade5-06b62aaad39b started +💬 Sending text to device ``` -And you should see the device screen update +And you should see the device screen update, saying `Lightbug...` in the top right. + +### 7. Inspect the code + +The code is very minimal. + +Initially importing dependencies... + +``` +import lightbug.devices as devices +import lightbug.messages.messages_gen as +``` + +Then defining the `main` function, which is the entry point for the Toit application. + +``` +main: +``` + +Here we get a handle to the Lightbug device. + +``` + device := devices.I2C +``` + +And send it a message, asking it to draw a clear box with some text in it, using a random page ID, in position `0,0` (top left), and disabling the status bar. + +``` + print "💬 Sending text to device" + device.comms.send (messages.DrawElement.msg + --data=(messages.DrawElement.data + --page-id=(random 10 255) + --status-bar-enable=false + --type=messages.DrawElement.TYPE_BOX + --x=0 + --y=0 + --text="Lightbug...")) +``` + +We then enter an infinite loop, sleeping for 10 seconds at a time, to keep the application running. + +``` + while true: + sleep --ms=10000 +``` diff --git a/devices/api/structure.md b/devices/api/structure.md deleted file mode 100644 index 8c75c6a..0000000 --- a/devices/api/structure.md +++ /dev/null @@ -1,148 +0,0 @@ ---- -outline: [2,3] ---- - - - -# Structure - -::: info -All integers are in [little-endian](https://en.wikipedia.org/wiki/Endianness) format eg. (uint16 `1` is represented as `0x01 0x00`). -::: - -In a byte stream, a message takes the follow structure: - -``` -
-> -``` - -The header data, and payload data, are made up of the same structure: - -``` - -``` - -As an example empty message including prefix (with no additional header or payload data): - - - -## Prefix - -To aid reading from a possibly noisy byte stream, such as UART, and to increase efficiency, the messages can be prefixed with a set of start bytes. - -`0x4c, 0x42`, or `76, 66`, which is the ASCII representation of `LB`. - -In combination with the protocol version, this allows for a simple check that indicates you are probably looking at the start of a message. - -`0x4c 0x42 0x03` or `76 66 3` for example. - -Communication methods such as I2C or UDP will not include this prefix. - -## Stop - -The 2 bytes after the prefix and protocol version indicate the length of the message which gives you a stop point. - -In total the first 5 bytes would look something like this with a prefix: - -`0x4c 0x42 0x03 0x0b 0x00` or `76 66 3 11 0`. - -Or with no prefix: - -`0x03 0x0b 0x00` or `3 11 0`. - -## Message - -The general structure of a message is as follows: - -| Byte position | Description | Type | Example | -| ------------- | ---------------------------------- | ------------------------- | -- | -| 1 | Protocol Version (always 3) | uint8 | 3 | -| 2 -> 3 | Message Length | uint16 | 11 0 | -| 4 -> 5 | Message Type | uint16 | 1 0 | -| 6 -> a | Header Data (field count, fields, data) | uint16, []uint8, []bBytes | 0 0 | -| a -> b | Payload Data (field count, fields, data) | uint16, []uint8, []bBytes | 0 0 | -| b -> n | Checksum | uint16 | 75 190 | - -So the full above example would be: - - - -## Components - -- Protocol Version: The version of the protocol. Always 3. -- Message Length: The length of the message, including the version, header, data, and checksum. -- Message Type: The type of message. This is used to determine how to interpret the message. (See [Message Types](#message-types) below.) -- Data: Both data field (header and payload) are made up of the same format: - - Field Count: The number of fields in the data. - - Fields: List of field types in the data - - Data: The data itself, making used of bBytes to represent length and values. -- Checksum: A 16-bit CRC checksum of the message (XMODEM). - -### Data - -Within each Data element (the header data, or payload data), the structure is as follows: - -| Byte position | Description | Type | Example | -| ------------- | ---------------- | ----- | ------- | -| 1 -> 2 | Number of fields (n) | uint16 | 2 0 | -| 3 -> 3+n | Field types | []uint8 | 1 2 | -| 3+n+1 -> end | Data | []bBytes | [1 8] [3 9 9 9] | - -The Data should be in the order of the field types. - -The example data includes `2` data fields, the first of type `1`, with value byte array `[8]`, the second of type `2`, with value byte array `[9 9 9]`. - -#### bBytes - -bBytes are a byte array that represents a length and then the data itself. - -For example, `[1 8]` would represent a byte array of length 1, with the value 8. - -Or `[3 9 9 9]` would represent a byte array of length 3, with the values 9, 9, 9. - -## Examples - -#### Type 3, header empty, data empty - - - -#### .. as above, with LB prefix bytes: - - - -#### Type 6, header (1:1), data empty - - - -#### Type 6, header (1:9), data empty - - - -#### Type 10009, header empty, data (10:hello): - - diff --git a/public/files/protocol-v3.yaml b/public/files/protocol-v3.yaml index 8de2b39..4684cf1 100644 --- a/public/files/protocol-v3.yaml +++ b/public/files/protocol-v3.yaml @@ -21,20 +21,20 @@ groups: header: 1: name: Message ID - description: ID that can be used by receiver to ACK, and client to track for various responses, or re-sends. Max uint32 4,294,967,295. - type: uint + description: ID that can be used by receiver to ACK, and client to track for various responses, or re-sends. Max 4,294,967,295. + type: uint32 2: name: Client ID - description: ID of the client sending the message + description: ID of the client sending the message. Must be uint32 or uint64 for Lightbug cloud services. type: uint 3: - name: Response to Message ID - description: ID of the message that is being responded to - type: uint + name: Response to + description: ID of the message that is being responded to (Message ID of the request or subscription) + type: uint32 4: - name: Message Status + name: Status description: Status of the message. If omitted, assume OK? - type: uint + type: uint8 values: 0: name: OK @@ -57,13 +57,19 @@ header: description: e.g., requesting GPS or time data when not yet available 7: name: Not Supported - description: If the receiver does not support the requested action but otherwise the message is valid + description: If the receiver does not support the requested action or method 8: name: Failed Will Retry 9: name: Failed Permanently + 10: + name: Abandoned + description: Status code when an actioned has timed out or been deliberately cancelled by another process + 11: + name: Expired + description: Status code when a request has expired and has been cancelled, typically subscriptions 5: - name: Message Method + name: Method description: Request a service to be perform an action type: uint values: @@ -83,22 +89,30 @@ header: name: UNSUBSCRIBE description: Unsubscribe from some previously subscribed to data 6: - name: Subscription interval + name: Interval description: Interval in ms. To be used with the SUBSCRIBE method type: uint32 + 7: + name: Duration + description: Time in ms. For subscriptions this defines a duration to keep the subscription active for. 0 = no time limit. An ACK with EXPIRED status code will be sent if the subscription duration elapses without being renewed (with a new subscription request). For actions that can be timed, this defines how long the action should go on for. + type: uint32 + 8: + name: Timeout + description: Timeout in ms. When requesting data or services that can take time to acquire (such as a GPS fix), timeout defines how long to try for before abandoning the action. An ACK with ABANDONED status code will be sent in this case. For actions that support it, timeout defines how long to wait before performing the action. + type: uint32 9: name: Forward For Type description: 'FOR FUTURE USE: Type of client that the message is being forwarded for.' values: 1: - name: Device - description: Message is being forwarded for a device. LORA forwarding default. + name: LoRa + description: Message is being forwarded for another Lightbug device over LoRa 2: - name: Module - description: Message is being forwarded for a module. Default when receiving a message over a link. + name: I2C + description: Message is being forwarded for an I2C connection, typically from the ESP32 co-processor. 3: - name: Link - description: Message is being forwarded for a link. Default when receiving a message from a module + name: Cloud + description: Message is being forwarded for a cloud link. Typically used when cloud messages are being forwarded to the ESP32 co-processor. 10: name: Forwarded For description: ID of the client sending the original message that is being forwarded. Forward for type alters the meaning of this field @@ -110,35 +124,66 @@ header: description: SNR of forwarded message 13: name: Forwarding To Type - description: 'FOR FUTURE USE: Type of client to forward the message to.' + description: 'INTERNAL FUTURE USE ONLY: Type of client to forward the message to.' type: uint values: 1: - name: Device - description: Message should be forwarded to a device. Lightbug device IDs. + name: LoRa + description: Message is being forwarded for another Lightbug device over LoRa 2: - name: Module - description: Message should be forwarded to a module. IDs 1=STM, 2=ESP. Default for messages received from a link. + name: I2C + description: Message is being forwarded for an I2C connection, typically from the ESP32 co-processor. 3: - name: Link - description: Message should be forwarded to a link. The only valid link ID is 1. Default for messages received from a module. + name: Cloud + description: Message is being forwarded for a cloud link. Typically used when cloud messages are being forwarded to the ESP32 co-processor. 14: name: Forward To - description: ID of the client to forward the message to. Forwarding to type alters the meaning of this field - type: uint + description: INTERNAL USE - ID of the client to forward the message to. Forwarding to type alters the meaning of this field + type: uint8 + 15: + name: Storage Level + description: Defines the behaviour for messages that have not been ACKed. The default is None, i.e. failed messages will not be retried. Devices have limited storage, if using retries and when storage is full, the oldest messages will be overwritten. Future iterations may support "In Order" which will require messages to be sent and ACKed in timestamp order. + values: + 0: + name: None + description: Do not attempt to resend messages if they are not ACKed + 10: + name: RAM + description: Keep the messages in RAM until they are ACKed or overwritten. Messages are discarded on reboot + 20: + name: NVM + description: Keep the messages until they are ACKed or overwritten + 16: + name: Message Level + description: Loosely analogous to log levels (trace, debug, info, warning, etc.) in that you must define the lowest level of interest. Message Levels can be used with timeouts on subscriptions - if a message of the requested level is not available within the specified time, the subscription will be abandoned. Currently you may only define one subscription per message type - requesting multiple levels will result in existing subscriptions being modified to the new level. Future iterations will support "Changes Only" and "Critical" and may support multiple concurrent subscriptions with varying levels + values: + 1: + name: Info-Watch + description: Receive all messages if and when available, without explicitly requesting that the underlying hardware be turned on. + 2: + name: Info-Auto + description: Receive all messages and explicitly request underlying hardware be activated to provide the requested data where neeeded + 10: + name: Valid + description: Activate the relevant hardware but only receive valid data. May be used with subscription timeout feature to request usable data without leaving the hardware on indefinitely messages: 5: name: ACK group: general data: 1: - name: ACKed Type + name: ACK Type description: Type of previous message being ACKed type: uint16 11: name: Open description: Explicit indicator for the start of a connection group: general + data: + 10: + name: Device Type + description: Type of device, relates to the SN prefix + type: uint8 12: name: Close description: Explicit indicator for a connection about to close. Can be used to differentiate between deliberate and accidental disconnections. @@ -153,7 +198,7 @@ messages: data: 7: name: Key - type: uint + type: uint16 9: name: Payload description: Payload for the config @@ -161,31 +206,34 @@ messages: values: 19: name: RtkMinUsableSatDb - description: Minimum usable satellite db (byte) + description: Minimum usable satellite db 20: name: RtkMinElevation - description: RtkMinElevation (byte) + description: RtkMinElevation 15: name: Position group: location data: 1: name: Timestamp - description: Timestamp the position was taken + description: Timestamp of when the position was taken, may be invalid if the GPS has not locked in yet (Type will be set to 'invalid' in this scenario) raw-description: Unix timestamp in ms (number of milliseconds since 1 Jan 1970) type: uint64 + readonly: true unit: ms since epoch parser: timestamp 2: name: Latitude raw-description: Fixed point representation of Latitude. type: int32 + readonly: true conversion: "1e7" unit: degree 3: name: Longitude raw-description: Fixed point representation of Longitude. type: int32 + readonly: true conversion: "1e7" unit: degree 4: @@ -193,20 +241,23 @@ messages: description: Altitude raw-description: Altitude in mm. type: int32 + readonly: true conversion: "1e3" unit: meter 5: name: Accuracy description: Accuracy - raw-description: Accuracy in cm. type: uint16 + readonly: true conversion: "1e2" unit: meter + raw-unit: cm 6: name: Course description: Course over ground raw-description: Course over ground centi-degrees (cd). type: uint16 + readonly: true conversion: "1e2" unit: degree 7: @@ -214,21 +265,25 @@ messages: description: Speed raw-description: Speed in meters per second (m/s). type: uint16 + readonly: true conversion: "1e2" unit: km/h 8: name: Satellites type: uint8 + readonly: true unit: count 9: name: CN0 description: Average CN0. Carrier to noise density. Higher is better. type: uint8 + readonly: true unit: dB-Hz 10: name: Type description: Position type type: uint8 + readonly: true values: 0: name: invalid @@ -236,18 +291,18 @@ messages: name: fixed 2: name: reserved + description: Can indicate a 2D fix, low accuracy, should be treated as invalid 3: - name: standalone + name: standalone 3d fix 4: name: rtk-float 5: name: rtk-fix - 6: - name: surveying 11: name: Source description: Position source type: uint8 + readonly: true values: 0: name: gps @@ -257,8 +312,9 @@ messages: description: Position has come from an RTK module. This does not mean the position is RTK corrected. 12: name: Correction Age - description: Age of the any correction data (primarily relating to MINEW based RTK devices) + description: Age of the correction data (RTK or DGNSS) from NMEA GGA sentence type: uint8 + readonly: true conversion: "10" unit: seconds methods: @@ -270,15 +326,15 @@ messages: group: location data: 1: - name: Average SNR + name: SNR Average description: Average signal-to-noise ratio across all satellites type: uint8 2: - name: Minimum SNR + name: SNR Minimum description: Minimum signal-to-noise ratio among all satellites type: uint8 3: - name: Maximum SNR + name: SNR Maximum description: Maximum signal-to-noise ratio among all satellites type: uint8 4: @@ -287,7 +343,7 @@ messages: type: uint8 5: name: Good Satellites - description: Number of satellites with good signal + description: Heuristic for signal quality (defined as satellites with SNR>=38dBm. This number should be higher than 8 for a good RTK fix, typically) type: uint8 10: name: GPS L1 @@ -296,37 +352,37 @@ messages: parser: gsv 11: name: GPS Lx - description: Summary data for GPS Lx satellites + description: Summary data for GPS L2 or L5 satellite signals (depending on hardware) type: bytes parser: gsv 12: name: GLONASS L1 - description: Summary data for GLONASS L1 satellites + description: Summary data for GLONASS L1 satellite signals type: bytes parser: gsv 13: name: GLONASS Lx - description: Summary data for GLONASS Lx satellites + description: Summary data for GLONASS L2 or L5 satellite signals (depending on hardware) type: bytes parser: gsv 14: name: Beidou L1 - description: Summary data for Beidou L1 satellites + description: Summary data for Beidou L1 satellite signals type: bytes parser: gsv 15: name: Beidou Lx - description: Summary data for Beidou Lx satellites + description: Summary data for Beidou L2 or L5 satellite signals (depending on hardware) type: bytes parser: gsv 16: name: Galileo L1 - description: Summary data for Galileo L1 satellites + description: Summary data for Galileo L1 satellite signals type: bytes parser: gsv 17: name: Galileo Lx - description: Summary data for Galileo Lx satellites + description: Summary data for Galileo L2 or L5 satellite signals (depending on hardware) type: bytes parser: gsv methods: @@ -339,12 +395,11 @@ messages: group: gsm data: 1: - name: Search GPS - description: 0 = no, 1 = yes - type: uint8 + name: GPS Search + type: bool 2: - name: Data - description: can be up to 200 bytes + name: Payload + description: Data to send, can be up to 200 bytes type: bytes 3: name: Retries @@ -361,26 +416,26 @@ messages: group: gsm data: 1: - name: Flight mode - description: 0 = no, 1 = yes - type: uint8 + name: Enable Flight mode + type: bool 2: name: Duration - description: in mins type: uint32 + unit: minutes 3: - name: GSM Active - description: READONLY report of if GSM is active. 0 = no, 1 = yes - type: uint8 + name: Is GSM Active + type: bool + readonly: true 4: name: Request Control - description: 0 = no, 1 = yes - type: uint8 + description: Note this will always be true when GETting state in flight mode (as control has been taken). + type: bool methods: GET: null SET: null 32: name: GSM Request Ownership + description: Only available for implant modules that expose modem control over usb to a host directly group: gsm data: 2: @@ -389,9 +444,39 @@ messages: type: uint32 4: name: Request Control - description: 0 = no, 1 = yes - type: uint8 + type: bool + methods: + GET: null + SET: null + 33: + name: Change SIM settings + description: For devices that have dual-sim functionality, control which SIM is active and set APN parameters for SIM2 + group: gsm + data: + 1: + name: Active SIM + description: Activate the specified SIM + type: uint32 + values: + 0: + name: SIM1 + 1: + name: SIM2 + 2: + name: SIM2 APN + type: ascii + 3: + name: SIM2 APN Username + type: ascii + 4: + name: SIM2 APN Password + type: ascii + 8: + name: SIM2 ICCID + type: bool + readonly: true methods: + GET: null SET: null 34: name: Device Status @@ -402,10 +487,12 @@ messages: name: Battery description: Battery level type: uint8 + unit: '%' 2: name: Signal Strength description: Signal strength type: uint8 + unit: '%' 3: name: Mode description: Device mode @@ -436,6 +523,12 @@ messages: 7: name: Firmware Version type: uint32 + 10: + name: Device Type + description: Type of device, relates to the SN prefix + type: uint8 + methods: + GET: null 35: name: Device IDs description: Device ID information @@ -443,16 +536,18 @@ messages: data: 1: name: ID - description: ID + description: Unique ID for the device which is used in the cloud API. uint32 or uint64 only type: uint 2: name: IMEI - description: IMEI + description: IMEI - 15 characters type: ascii 3: name: ICCID - description: ICCID + description: ICCID - 19 to 22 characters type: ascii + methods: + GET: null 36: name: Device Time description: Device time information, if known @@ -472,11 +567,11 @@ messages: type: uint8 4: name: Date - description: Date + description: Date in month type: uint8 5: name: Weekday - description: Weekday (1 monday etc) + description: Weekday (0 = sunday, 1 = monday etc) type: uint8 6: name: Hour @@ -490,19 +585,21 @@ messages: name: Second description: Second type: uint8 + methods: + GET: null 38: name: Button Press description: Press of a device button group: ungrouped data: 1: - name: ID - description: ID of the button, 0 indexed + name: Button ID + description: ID of the button, 0 indexed. Check device spec for button numbering type: uint8 2: name: Duration description: Duration of the button press in ms - type: uint + type: uint32 methods: SUBSCRIBE: null UNSUBSCRIBE: null @@ -512,25 +609,22 @@ messages: group: location data: 1: - name: GPS Enable - type: uint8 - values: - 0: - name: Disabled - 1: - name: Enabled + name: GPS is on + description: Status of the GPS, is it on? + type: bool + readonly: true 2: - name: RTK Enable Correction - description: Request correction data to be applied to the GPS + name: Corrections Enabled + description: Request and apply correction data to the GPS, such as RTK. type: uint8 values: 0: name: Disabled 1: - name: Enabled + name: Full RTCM stream 3: name: Start Mode - description: If only start mode is set, and the module is already enabled, it will reboot the module into that state. + description: Start mode of the GPS module. type: uint8 values: 1: @@ -541,6 +635,21 @@ messages: name: Warm 4: name: Hot + methods: + GET: + description: Return the current state of the GPS module + request: null + response: + 1: null + 2: null + SET: + description: Enable correction data and/or restart the GPS into a given boot mode + request: + 2: + description: Automatically controls the GPS module. + 3: + description: If the GPS is currently enabled, this will cause the GPS to be rebooted into the requested state. If the GPS is currently disabled, this will cause the GPS to be enabled and the requested state to be set. + response: null 40: name: Haptics Control description: Control the haptics @@ -563,6 +672,9 @@ messages: name: Temperature description: Temperature in Celsius type: float32 + unit: C + methods: + GET: null 42: name: Buzzer Control description: Control the buzzer. Either pass duration and frequency, Or all fields without frequency. @@ -595,7 +707,7 @@ messages: name: Alarm 3: name: Intensity - description: Intensity of buzzer. [0-2]. Work as speed control for buzzer types. + description: Intensity of buzzer. [0-2]. Work as frequency control for buzzer types (moving towards and away from resonance). type: uint8 4: name: Run Count @@ -614,10 +726,14 @@ messages: name: Voltage description: Current battery voltage type: float32 + unit: V 2: name: Percent description: Current battery percent type: uint8 + unit: '%' + methods: + GET: null 44: name: Pressure description: Pressure information @@ -625,16 +741,19 @@ messages: data: 1: name: Pressure - description: Pressure in Milibar + description: Pressure in millibar / hectopascals type: float32 + unit: hPa + methods: + GET: null 45: name: Alarm - description: Trigger an alarm, using buzzer, haptics, strobe and optional prompt + description: Trigger an alarm, using buzzer, haptics, strobe and optional prompt. Use header fields for timing control - Duration = time the alert is played for - Timeout = time the prompt is shown for group: ungrouped data: 1: name: Legacy alarm action - description: 4 bytes of encoded data relating to legacy alarm formats. Can not be used with other options. + description: 4 bytes of encoded data relating to legacy alarm formats. Can not be used with other options. Note using this field will override Duration header field setting type: uint32 2: name: Duration @@ -666,13 +785,10 @@ messages: description: Timeout for the prompt in seconds type: uint8 11: - name: Prompt button 1 text + name: Prompt button 1 type: ascii 12: - name: Prompt button 2 text - type: ascii - 13: - name: Prompt button 3 text + name: Prompt button 2 type: ascii 46: name: Buzzer Sequence @@ -689,7 +805,7 @@ messages: type: uint16[] 47: name: CPU2 Sleep - description: See how much power is being used by the device, between subscription and un-subscription time. + description: Request CPU2 sleep group: ungrouped data: 1: @@ -699,22 +815,24 @@ messages: 2: name: Wake on Event description: Should CPU1 wake up CPU2 on new events / messages - type: uint8 + type: bool methods: DO: null 48: name: Power Profile - description: See how much power is being used by the device, between subscription and un-subscription time. + description: See how much power is being used by the device. Total power is reset to zero when a new subscription is requested. group: ungrouped data: 3: name: Total power - description: Total power used in mAH + description: Total power used in mAH since the subscription was started (or the device was turned on, if only using GET) type: float32 + unit: mAh 4: - name: Current now - description: Current power usage in mA + name: Current + description: Instantaneous Current power usage type: float32 + unit: mA methods: SUBSCRIBE: interval: '>= 300' @@ -730,55 +848,105 @@ messages: group: gsm data: 1: - name: Address + name: IP Address description: IP Address of the link type: ascii 2: name: Port - description: Port of the link + description: UDP Port for the link type: uint16 3: name: Enable description: Enable or disable the link - type: uint8 - values: - 1: - name: Enable - 2: - name: Disable + type: bool 53: - name: Ublox Protection Level - description: Get information about the ublox protection level - group: ungrouped + name: Protection Level + description: Get information about the protection level - i.e. 95% confidence accuracy ellipse. Only supported on RTK enabled products. + group: location data: 1: - name: PL Valid - description: Indicates if the protection level is valid + name: Valid + description: Indicates if the protection level data is valid type: uint8 2: - name: PL X - description: Protection level in the X direction in mm + name: latitude + description: Protection level in the Lat direction (North South) type: uint32 + unit: mm 3: - name: PL Y - description: Protection level in the Y direction in mm + name: longitude + description: Protection level in the Lon direction (East West) type: uint32 + unit: mm 4: - name: PL Z - description: Protection level in the Z direction in mm + name: Altitude + description: Protection level in the Z direction type: uint32 - 5: - name: Horizontal Orientation - description: Horizontal orientation of the protection level + unit: mm + methods: + GET: null + 54: + name: Charger Settings + description: For products with configurable charge settings (notably NOT Vehicle RTK), get and define charging parameters + group: location + data: + 1: + name: Input Current Limit + description: Maximum power draw allowed from Vin. Typically higher than Charge Current Limit (additional current is used to power device operation whilst charging) type: uint16 - 6: - name: TMIR Coefficient - description: TMIR coefficient for protection level calculation + unit: mA + 2: + name: Charge Current Limit + description: Maximum charge rate for the battery. Recommended value is 0.5C (where C is the battery capacity) + type: uint16 + unit: mA + 3: + name: Charge Termination Volgate + description: Target charge voltage for the battery. Typically 4.25V for lithium ion batteries. + type: uint16 + unit: mV + methods: + GET: null + SET: null + 55: + name: WiFi Scan + group: scan + data: + 1: + name: SSID + description: SSID of the access point + type: ascii + 2: + name: MAC + description: MAC Address of the access point, as 6 bytes + type: bytes + 3: + name: RSSI + description: Signal strength of the access point + type: int8 + 4: + name: Channel + description: WiFi channel of the access point type: uint8 - 7: - name: TMIR Exponent - description: TMIR exponent for protection level calculation + methods: + SUBSCRIBE: null + 56: + name: BLE Scan + group: scan + data: + 1: + name: Advertising Data + type: bytes + 2: + name: MAC + description: MAC Address of the access point, as 6 bytes + type: bytes + 3: + name: RSSI + description: Signal strength type: int8 + methods: + SUBSCRIBE: null 1004: name: LORA description: Interaction with LORA @@ -817,16 +985,83 @@ messages: type: uint32 11: name: Sleep - description: Any value will tell the LORA to stop all activity now - type: uint8 + description: True will tell the LORA to stop all activity now + type: bool 12: name: State - description: Readonly type: uint + readonly: true + methods: + DO: null + GET: null + NONE: null + SET: null + SUBSCRIBE: null + UNSUBSCRIBE: null + 2000: + name: RTCM Data + description: Data relating to RTCM (Radio Technical Commission for Maritime Services) correction data for high-precision GNSS positioning. + group: "" + data: + 1: + name: Data + description: RTCM v3 message data as complete frames (including D3 header, length, payload, and CRC) or raw payload bytes. Can be chunked and split across multiple fields in a message with incrementing field numbers. + type: bytes + parser: rtcm + codegen: + exclude: + - toit 10008: - name: Preset Page - description: Display a predefined Lightbug page. + name: Base Page + description: Draw a base page group: screen + data: + 3: + name: Page ID + description: | + The page to draw or update. + Page ids 0-10 are reserved for system use. + If no page id is provided, page id 11 will be assumed. + type: uint8 + 5: + name: Status bar Enable + description: Show the status bar + type: bool + 6: + name: Redraw Type + type: uint8 + values: + 0: + name: Auto + description: | + Automatically choose the redraw type, based on page id. + No page id provided will assume the same page id as last set. + Same page id as last set will do a partial redraw, and leave the buffer intact. + Changed page id will clear the buffer and do a full redraw. + 1: + name: PartialRedraw + description: | + Only redraw the parts of the screen changed in this message. + Leaves the buffer intact. + Will occasionally do a full redraw if the firmware thinks it is needed. + 2: + name: FullRedraw + description: | + Clear the screen buffer, and redraw the entire screen + 3: + name: BufferOnly + description: | + Do not redraw the screen, only update the buffer. + Will clear the buffer if the page id has changed. + Similar to Auto, but will not redraw the screen. + Similar to ClearDontDraw, but only clears the buffer if the page id has changed. + 4: + name: FullRedrawWithoutClear + description: | + Redraw the entire screen, without clearing the buffer + 5: + name: ClearDontDraw + description: Clear the screen buffer (always), but don't redraw. Similar to BufferOnly, but always clears the buffer. 10009: name: Text Page description: Display or change a text page @@ -834,16 +1069,19 @@ messages: data: 3: name: Page ID - description: ID of page to display or update - type: uint + description: | + The page to draw or update. + Page ids 0-10 are reserved for system use. + If no page id is provided, page id 11 will be assumed. + type: uint8 4: name: Page Title description: Title of the page type: ascii 5: - name: Status bar + name: Status bar Enable description: Show the status bar - type: uint8 + type: bool 6: name: Redraw Type type: uint8 @@ -883,7 +1121,7 @@ messages: type: ascii 10010: name: Menu Page - description: Display or change a menu page + description: Draw or change a menu group: screen data: 2: @@ -891,13 +1129,17 @@ messages: type: uint8 3: name: Page ID - type: uint + description: | + The page to draw or update. + Page ids 0-10 are reserved for system use. + If no page id is provided, page id 11 will be assumed. + type: uint8 4: name: Page Title type: ascii - 5: - name: Initial item selection - description: An optional item to show as initially selected + 31: + name: Selected item + description: Optionally select a specific item, else the first will be used type: uint8 100: name: Item 1 @@ -960,13 +1202,21 @@ messages: name: Item 20 type: ascii 10011: - name: Draw Bitmap - description: Draw bitmap in UX + name: Draw Element + description: Draw an element group: screen data: 3: name: Page ID - type: uint + description: | + The page to draw or update. + Page ids 0-10 are reserved for system use. + If no page id is provided, page id 11 will be assumed. + type: uint8 + 5: + name: Status bar Enable + description: Show the status bar + type: bool 6: name: Redraw Type type: uint8 @@ -991,20 +1241,118 @@ messages: description: Clear the screen buffer, but don't redraw 7: name: X - description: X coordinate for the start of the bitmap + description: X coordinate for the start of the element. If padded, this is the start of the padded area. type: uint 8: name: "Y" - description: Y coordinate for the start of the bitmap + description: Y coordinate for the start of the element. If padded, this is the start of the padded area. type: uint 9: name: Width - description: Width of the bitmap + description: Width of the element. If padded this does not include the padding. type: uint 10: name: Height - description: Height of the bitmap + description: Height of the element. If padded this does not include the padding. + type: uint + 11: + name: Type + description: Type of element to draw + type: uint8 + values: + 0: + name: Box + description: | + Draw a Box. + Requires x, y (top left corner), width and height. + Can include style, and padding + Can include text and a font size and alignment. + Can have corners rounded with the radius parameter. + Can have a border with the line width parameter. + 1: + name: Circle + description: | + Draw a circle. + Requires x, y (top left corner) and width. + Can have a border with the line width parameter. + 2: + name: Line + description: | + Draw a line. + Requires x, y (start point), width and height (end point). + Can have line width and line type (dashed or solid). + 3: + name: Bitmap + description: | + Draw a bitmap, from provided data. + Requires x, y (top left corner), width, height and bitmap data. + 12: + name: Style + description: Style of the element to draw. Default is BlackOnClear. + type: uint8 + values: + 0: + name: BlackOnClear + 1: + name: WhiteOnBlack + 2: + name: BlackOutline + 3: + name: WhiteOutline + 13: + name: FontSize + description: Size of the font to use. Default is Medium. + type: uint8 + values: + 0: + name: Small + 1: + name: Medium + 2: + name: Large + 14: + name: TextAlign + description: Alignment of the text. Default is Middle. + type: uint8 + values: + 0: + name: Left + 1: + name: Middle + 2: + name: Right + 15: + name: LineWidth + description: Default is 1. Max is 8. + type: uint8 + 16: + name: Padding + description: Padding inside the element (in terms of x and y). Default is 0. + type: uint8 + 17: + name: Radius + description: For use with circle, or corner rounding. Default is 0. + type: uint8 + 18: + name: LineType + description: Default is Solid. + type: uint8 + values: + 0: + name: Solid + 1: + name: Dashed + 19: + name: X2 + description: Second X coordinate, primarily for lines. Min 0, Max WIDTH -1. + type: uint + 20: + name: Y2 + description: Second Y coordinate, primarily for lines. Min 0, Max HEIGHT -1. type: uint 25: - name: Data + name: Bitmap type: bytes + 100: + name: Text + type: ascii From 94157a40d5b93007f8a17d3e7081fe6ac5cd1013 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 5 Sep 2025 18:05:05 +0100 Subject: [PATCH 03/38] Intro to device messaging changes --- devices/api/index.md | 17 +++++--- devices/api/messages/5-ack.md | 6 --- devices/api/messages/index.md | 54 ++++++++++++++++++------ devices/api/protocol/headers.md | 6 ++- devices/api/sdks/toit/getting-started.md | 4 +- 5 files changed, 61 insertions(+), 26 deletions(-) diff --git a/devices/api/index.md b/devices/api/index.md index 3d29e27..7a8e458 100644 --- a/devices/api/index.md +++ b/devices/api/index.md @@ -8,21 +8,28 @@ import ProtocolBytes from '../../components/ProtocolBytes.vue'; # Device API -The device API is a messaging system for communicating with Lightbug devices. It allows you to: +The device API is a messaging system for communicating with Lightbug devices. It allows you to send and receive [messages](/devices/api/messages/) to and from the device, enabling control and data exchange. + +It makes use of the [Lightbug communication protocol](/devices/api/protocol/), which is a byte oriented protocol used for device communication. + +This documentation focuses on the higher level element of this API first, including the [Toit SDK](/devices/api/sdks/toit/), before working its was down to the [message level](/devices/api/messages/), and finally the [protocol level](/devices/api/protocol/) which underpins it all. + +You can [get started with Toit quickly](/devices/api/sdks/toit/), using an SDK built on top of the messages as another layer of abstraction. + +## Capabilities + +The Device API allows you to: - send commands to the device - ask for data (GET) - subscribe to data streams (SUBSCRIBE) - receive responses, instructions and other data from the device -It makes use of the Lightbug communication protocol, also known as the V3 protocol, which is a byte oriented protocol used for device communication. - On the wire, a single message might look like `4c 42 03 0b 00 01 00 00 00 00 00 4b be`, however, the API abstracts this away so you can work with high level messaging concepts. -And you can [get started with Toit quickly](/devices/api/sdks/toit/), using an SDK built on top of the messages as another layer of abstraction. +## Accessability Typically, it can be accessed via UART, I2C, or a UDP network connection. - ## Availability Device API access depends on the device and may not be supported on all models. diff --git a/devices/api/messages/5-ack.md b/devices/api/messages/5-ack.md index 3c9fc8b..746a8d1 100644 --- a/devices/api/messages/5-ack.md +++ b/devices/api/messages/5-ack.md @@ -11,12 +11,6 @@ import PayloadTable from '../../../components/PayloadTable.vue' import HeaderTable from '../../../components/HeaderTable.vue' -::: danger ⚠️ Not yet public -The Device API currently in development and is not yet accessible on production devices. - -These pages can be seen as a view of what is to come later this year. -::: - # 5: ACK diff --git a/devices/api/messages/index.md b/devices/api/messages/index.md index 6075f05..380659f 100644 --- a/devices/api/messages/index.md +++ b/devices/api/messages/index.md @@ -5,30 +5,58 @@ import ProtocolBytes from '../../../components/ProtocolBytes.vue' # Messages -Messages often make use of a few common [header fields](/devices/api/protocol/headers), and you can expect to see these in most messages types: +Messages are the core building blocks of the protocol, allowing you to send and receive data and commands to and from devices. -## Requests +All messages follow a common structure, but each type of message has its own specific purpose, payload, requirements and usage. -Requests may or may not require a [MH 5: Method](../protocol/headers#_5-method) header field, such as `GET`, `DO`, `SET` or `SUBSCRIBE`. +## Types -This is decided by the individual message type, and if required, should be included in the header. +Messages all have a type, represented as a `uint16` value, which defines the structure and purpose of the message, including what headers and payload it may or should contain. +Some example message types for the device API include: +- [13: Heartbeat](./13-heartbeat) - A simple message to indicate the communication link is alive +- [34: Device Status](./34-device-status) - A message containing various status information about the device +- [10011: Draw Element](./10011-draw-element) - A message to draw a shape on the device's display +- ... +You can find complete documentation for the messages used as part of the device API in the sidebar of this page. -## Responses +Values of `60,000` to `61,000` are currently reserved for custom use. Feel free to implement your own messages within this range. -You should receive a response to every valid and expected message when the recipient is able to process the request. +## Header fields -When a message ID is provided in the request, any direct response should include the initiating message ID in the header [MH 3: Response Message ID](../protocol/headers#_3-response-message-id). +Messages make use of a few common [header fields](/devices/api/protocol/headers) that are defined at the protocol level that you'll likely want to familiarize yourself with as they are used in device messaging. -Responses can come in the form of a basic [MT 5: ACK](./5-ack) or as the same message type as the request. +Such as: +- [1: Message ID](../protocol/headers#_1-message-id) for uniquely identifying messages +- [3: Response to](../protocol/headers#_3-response-to) for linking responses to requests +- [4: Status](../protocol/headers#_4-status) for indicating the status of a message +- [5: Method](../protocol/headers#_5-method) for optionally specifying the action to be taken -Responses should contain a [MH 4: Response Status](../protocol/headers#_4-response-status) in the header, where 0 and below are various OK responses, and anything higher than 0 indicates a warning or error. +You can find the full list of header fields in the [Headers documentation](/devices/api/protocol/headers) for the protocol. -## Types +### Methods + +A method is an optional header field that can be included in messages, and is sometimes used throughout the device API. + +Currently defined methods are: +- `1: SET` - Set a value or state on the device +- `2: GET` - Get a value or state from the device +- `3: SUBSCRIBE` - Subscribe to updates for a value or state on the device +- `5: UNSUBSCRIBE` - Unsubscribe from updates for a value or state on the device -Per the specification, message types are represented by a single `uint16` value. +You can find more information about these methods in the [MH 5: Method](../protocol/headers#_5-method) section. -Values of `60,000` to `61,000` are currently reserved for custom use. +## Communication patterns + +You should receive a response to every valid and expected message when the recipient is able to process the request. + +When your message includes a [MH 1: Message ID](../protocol/headers#_1-message-id), the response should include the same message ID in the [MH 3: Response to](../protocol/headers#_3-response-to) header field. + +:::warning +If you do not include a message ID, you may not receive a response +::: + +Responses can come in the form of a basic [MT 5: ACK](./5-ack) or as the same message type as the request. -Feel free to implement your own messages within this range. +Responses can contain a [MH 4: Response Status](../protocol/headers#_4-message-status) in the header, where 0 and below are various OK responses, and anything higher than 0 indicates a warning or error. diff --git a/devices/api/protocol/headers.md b/devices/api/protocol/headers.md index f2b4b5a..54028c0 100644 --- a/devices/api/protocol/headers.md +++ b/devices/api/protocol/headers.md @@ -10,7 +10,7 @@ import GenerateConsts from '../../../components/GenerateConsts.vue' These header field types are reserved across all message types and usages of the protocol. - +For use in code, you can find a code generation section at the [bottom of this page](#code-generation). ## 1: Message ID @@ -124,3 +124,7 @@ Reserved for future use. ## 16: Message Level Reserved for future use. + +## Code generation + + diff --git a/devices/api/sdks/toit/getting-started.md b/devices/api/sdks/toit/getting-started.md index 4730432..5402097 100644 --- a/devices/api/sdks/toit/getting-started.md +++ b/devices/api/sdks/toit/getting-started.md @@ -126,7 +126,9 @@ Once the code is running, you should see some output through the `monitor` comma 💬 Sending text to device ``` -And you should see the device screen update, saying `Lightbug...` in the top right. +And you should see the device screen update, saying `Lightbug...` in the top left. + +![](https://i.imgur.com/8QP1022.png) ### 7. Inspect the code From af64f1a2bf49fb98192d9da6d155173f5e7c1553 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 12 Sep 2025 09:53:45 +0100 Subject: [PATCH 04/38] Add favicons --- .vitepress/config.mts | 17 +++++++++++++++++ public/android-chrome-192x192.png | Bin 0 -> 5914 bytes public/android-chrome-512x512.png | Bin 0 -> 20633 bytes public/apple-touch-icon.png | Bin 0 -> 5446 bytes public/favicon-16x16.png | Bin 0 -> 518 bytes public/favicon-32x32.png | Bin 0 -> 878 bytes public/favicon-530x530.png | Bin 0 -> 6845 bytes public/favicon.ico | Bin 0 -> 15406 bytes public/site.webmanifest | 1 + 9 files changed, 18 insertions(+) create mode 100644 public/android-chrome-192x192.png create mode 100644 public/android-chrome-512x512.png create mode 100644 public/apple-touch-icon.png create mode 100644 public/favicon-16x16.png create mode 100644 public/favicon-32x32.png create mode 100644 public/favicon-530x530.png create mode 100644 public/favicon.ico create mode 100644 public/site.webmanifest diff --git a/.vitepress/config.mts b/.vitepress/config.mts index 59bfc9f..a83d22d 100644 --- a/.vitepress/config.mts +++ b/.vitepress/config.mts @@ -141,6 +141,23 @@ export default withMermaid(defineConfig({ gtag('js', new Date()); gtag('config', '${process.env.PUBLIC_GOOGLE_ANALYTICS}');` ] + , + [ + 'link', + { rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon.png' } + ], + [ + 'link', + { rel: 'icon', type: 'image/png', sizes: '32x32', href: '/favicon-32x32.png' } + ], + [ + 'link', + { rel: 'icon', type: 'image/png', sizes: '16x16', href: '/favicon-16x16.png' } + ], + [ + 'link', + { rel: 'manifest', href: '/site.webmanifest' } + ], ], themeConfig: { // https://vitepress.dev/reference/default-theme-config diff --git a/public/android-chrome-192x192.png b/public/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..751f4b099302121552d09c156e33ce1e2e5520fe GIT binary patch literal 5914 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 J**%Ad{{p2MA#eZy literal 0 HcmV?d00001 diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..3a65618d2bfaecfeb8c3dad6329c6804a4d2be0e GIT binary patch literal 518 zcmV+h0{Q)kP)Px$z)3_wR5(w)(m!icVHC&l@9*Zu8mzQ-Fr|Nn>!P+1B((^diyZ{1BDm<_P$%hJ z!Nmj#*c(B71G?#;z5vm>=+FkMHtJFrA%j~H992y697(T@NlbClGd(=#KA(G@a~{za zR-75cHKrO3<|SqfOEl94pFQJl&GESJ=E_HHZpEQ?pq39S64~~>?zLgF$;FkWoj8PL`7(EC>BPg{S@9dQxKp}iBnBEp>&DSSU zeEtuBdLdi|j{Z@>{GGr(x(m49m3Fj67liNMW{t`1IY?ZuFO4HG$3e2^A>85jN@tE&u=k07*qo IM6N<$f=hDeLjV8( literal 0 HcmV?d00001 diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..073394c21552c1d0c44db40645c29dbd9d11bfa3 GIT binary patch literal 878 zcmV-!1CjiRP)Px&C`m*?R9HvtmtROzaTLeD-+S-W{%D{n*&q9d{iTq=r#6W_6a`{|QBYtIR`l;p zskTwjEGm1*wJ7VMh@d|%BO)V;sHV|Ffe%SSJw~MzMMTTp-BWjFuDR>(z=FWv~O zmIGW3!jUd8fv5;>f^0J+*6HYtbh7zb)XL8iAm*tE3T1n`gz4-Aj;a2~^%(=`Ue|d5 zHR%$hvs2`h+7RCH{{cij6(#`12`);ZfC!9kRcpV|+p}zJVG$yT>T3+J zG*tqF^4#GM-^o~kbWKR2Q9by$F6MIvMQC|j{$dl#EzKXU)V;3z=5Vr>UxJ!HY!0xL z$9fKUo#zDHJWODl07gLbx9=VW7*!x+1$3{g89=S=CY(ASEUyBzgaHYF6Nsx^_xxkkXkjU4#8}kYPEgMj^4czL5jSD|D-M zAxPIF7?*v3^sfZ2-4A&hg1YfYKiuGHAsOE@Bcs4C0HZAc>A3bgO&lQ902G%(-E2DsH$4-6=f3{dTZbX*1WzW$QT zE{C!zK9y|%c$`7dddUC@yTAefljnf*oP&i{9Z*~w{vklJd|5eAeRoFz{u2daURRR< zyE3{3_s&8dUbJ?}EkJx?+PMW$kMjgc%ZJ1tw;*?-TdltyAH#+T@$JhH2r4RAwgQ~D z)U;h8xqjF|lo0^vR%%azB}3n^DdaE!tRvYnf&g%vTaYu#07ud`EGP+a#kRz{y!yEZ zUJQU)?*gx@77%k9DyfTV<54Pz0j$v)9$m6(3`+phLH4A(f|Me07*qoM6N<$ Eg4X$n-2eap literal 0 HcmV?d00001 diff --git a/public/favicon-530x530.png b/public/favicon-530x530.png new file mode 100644 index 0000000000000000000000000000000000000000..b715b6aa087b79e3ebeadc87a0777883df830d54 GIT binary patch literal 6845 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 Date: Fri, 12 Sep 2025 11:02:01 +0100 Subject: [PATCH 05/38] A first pass on RTK improvements --- .vitepress/config.mts | 23 +++++-- components/DeviceSpecTable.vue | 14 +++- cspell.json | 7 +- devices/api/messages/index.md | 2 +- devices/enviro/index.md | 4 ++ devices/pro/index.md | 4 ++ devices/rtk/handheld.md | 10 +++ devices/rtk/index.md | 11 +-- devices/rtk/vehicle.md | 9 +++ devices/vehicle/index.md | 2 + devices/zero/index.md | 4 ++ faq/connectivity.md | 2 +- faq/troubleshooting.md | 2 +- terminology/positioning.md | 102 ---------------------------- terminology/positioning/cellular.md | 32 +++++++++ terminology/positioning/gnss.md | 42 ++++++++++++ terminology/positioning/index.md | 11 +++ terminology/positioning/rtk.md | 56 +++++++++++++++ terminology/positioning/wifi.md | 32 +++++++++ 19 files changed, 252 insertions(+), 117 deletions(-) create mode 100644 devices/rtk/handheld.md create mode 100644 devices/rtk/vehicle.md delete mode 100644 terminology/positioning.md create mode 100644 terminology/positioning/cellular.md create mode 100644 terminology/positioning/gnss.md create mode 100644 terminology/positioning/index.md create mode 100644 terminology/positioning/rtk.md create mode 100644 terminology/positioning/wifi.md diff --git a/.vitepress/config.mts b/.vitepress/config.mts index a83d22d..a99aab0 100644 --- a/.vitepress/config.mts +++ b/.vitepress/config.mts @@ -210,8 +210,17 @@ export default withMermaid(defineConfig({ { text: 'General', items: [ + { + text: 'Positioning', + link: '/terminology/positioning/', + items: [ + { text: 'GNSS', link: '/terminology/positioning/gnss' }, + { text: 'RTK', link: '/terminology/positioning/rtk' }, + { text: 'WiFi', link: '/terminology/positioning/wifi' }, + { text: 'Cellular', link: '/terminology/positioning/cellular' }, + ] + }, { text: 'IoT', link: '/terminology/iot' }, - { text: 'Positioning', link: '/terminology/positioning' }, { text: 'Observability', link: '/terminology/observability' }, ] }, @@ -261,10 +270,14 @@ export default withMermaid(defineConfig({ text: 'Enviro', link: '/devices/enviro/', }, - { - text: 'RTK', - link: '/devices/rtk/', - }, + ] + }, + { + text: 'RTK', + link: '/devices/rtk/', + items: [ + { text: 'Handheld', link: '/devices/rtk/handheld' }, + { text: 'Vehicle', link: '/devices/rtk/vehicle' }, ] }, { diff --git a/components/DeviceSpecTable.vue b/components/DeviceSpecTable.vue index 07e8819..0e282f4 100644 --- a/components/DeviceSpecTable.vue +++ b/components/DeviceSpecTable.vue @@ -54,6 +54,13 @@ const props = defineProps({ type: String, required: true } + , + // Optional override for the device title. If provided, this will be used + // instead of the title extracted from the YAML. + deviceTitle: { + type: String, + required: false + } }) const specs = ref(null) @@ -97,7 +104,12 @@ watchEffect(() => { genericSections.value = [] if (specs.value && specs.value.product) { const p = specs.value.product - deviceTitle.value = p.sku ? `${p.name} (${p.sku})` : p.name + // Prefer the explicit prop if provided, otherwise derive from YAML + if (props.deviceTitle) { + deviceTitle.value = props.deviceTitle + } else { + deviceTitle.value = p.sku ? `${p.name} (${p.sku})` : p.name + } // Main table (excluding sectionKeys) displaySpecs.value = { 'Name': p.name, diff --git a/cspell.json b/cspell.json index 835ca79..7dba023 100644 --- a/cspell.json +++ b/cspell.json @@ -65,8 +65,13 @@ "DGNSS", "NMEA", "WCDMA", + "GPRS", "hectopascals", - "codegen" + "codegen", + "VTRTK", + "pseudorange", + "Centimetre", + "integ" ], "ignoreWords": [], "import": [] diff --git a/devices/api/messages/index.md b/devices/api/messages/index.md index 380659f..9205f6d 100644 --- a/devices/api/messages/index.md +++ b/devices/api/messages/index.md @@ -16,7 +16,7 @@ Messages all have a type, represented as a `uint16` value, which defines the str Some example message types for the device API include: - [13: Heartbeat](./13-heartbeat) - A simple message to indicate the communication link is alive - [34: Device Status](./34-device-status) - A message containing various status information about the device -- [10011: Draw Element](./10011-draw-element) - A message to draw a shape on the device's display +- [10011: Draw Element](./10011-draw-bitmap) - A message to draw a shape on the device's display - ... You can find complete documentation for the messages used as part of the device API in the sidebar of this page. diff --git a/devices/enviro/index.md b/devices/enviro/index.md index 5c22b51..4c9cc29 100644 --- a/devices/enviro/index.md +++ b/devices/enviro/index.md @@ -1,3 +1,7 @@ +--- +aside: false +--- + + + diff --git a/devices/rtk/index.md b/devices/rtk/index.md index 59c9c15..0b69f30 100644 --- a/devices/rtk/index.md +++ b/devices/rtk/index.md @@ -1,6 +1,7 @@ - +--- +title: RTK +layout: full +fullWidth: true +--- - +# RTK diff --git a/devices/rtk/vehicle.md b/devices/rtk/vehicle.md new file mode 100644 index 0000000..c5fe454 --- /dev/null +++ b/devices/rtk/vehicle.md @@ -0,0 +1,9 @@ +--- +aside: false +--- + +# Powered Vehicle RTK (VTRTK) + +A powered RTK tracker for vehicles, with advanced GNSS and RTK capabilities. + +Currently available for demo, with the form factor of a [VT3](/devices/vehicle/) device, and capabilities of an [RH2](/devices/rtk/handheld) device. diff --git a/devices/vehicle/index.md b/devices/vehicle/index.md index a178001..a9f8bfd 100644 --- a/devices/vehicle/index.md +++ b/devices/vehicle/index.md @@ -1,4 +1,6 @@ --- + +aside: false next: text: 'Installation' link: '/devices/vehicle/installation' diff --git a/devices/zero/index.md b/devices/zero/index.md index 11946db..0934cb8 100644 --- a/devices/zero/index.md +++ b/devices/zero/index.md @@ -1,3 +1,7 @@ +--- +aside: false +--- + + + diff --git a/components/DownloadPdfButton.vue b/components/DownloadPdfButton.vue index 800d30b..5ec96de 100644 --- a/components/DownloadPdfButton.vue +++ b/components/DownloadPdfButton.vue @@ -1,6 +1,6 @@ @@ -29,7 +29,11 @@ const borderColor = [221, 221, 221]; // #ddd (keep for subtle borders) const subsectionBg = [252, 232, 220]; // light orange tint for subsections const props = defineProps({ - getPdfData: Function + getPdfData: Function, + label: { + type: String, + default: 'Download PDF' + } }); function loadScript(src) { @@ -363,22 +367,29 @@ async function downloadPdf() { diff --git a/cspell.json b/cspell.json index 7dba023..1650e9c 100644 --- a/cspell.json +++ b/cspell.json @@ -71,6 +71,7 @@ "VTRTK", "pseudorange", "Centimetre", + "wght", "integ" ], "ignoreWords": [], diff --git a/public/device-specs/rtk/v2.yaml b/public/device-specs/rtk/v2.yaml index 43de367..00dd376 100644 --- a/public/device-specs/rtk/v2.yaml +++ b/public/device-specs/rtk/v2.yaml @@ -2,7 +2,9 @@ product: name: "RTK" description: "Our highest accuracy device with both indoor and outdoor positioning capabilities." sku: RH2 + prefix: 43 version: 2 + booklet: "https://lightbug.io/rh2_rtk_handheld_booklet.pdf" images: - "https://lightbug.io/raw-renders/2025-07-01/RH2_w-front angle.png?x=600&y=0&w=600&h=1000" - "https://lightbug.io/raw-renders/2025-07-01/RH2_w-top side.png?x=600&y=0&w=600&h=1000" diff --git a/public/device-specs/zero/v2.yaml b/public/device-specs/zero/v2.yaml index bc21d93..6d7706c 100644 --- a/public/device-specs/zero/v2.yaml +++ b/public/device-specs/zero/v2.yaml @@ -1,5 +1,5 @@ product: - name: "Lightbug Zero" + name: "Zero" description: "Our smallest core GPS tracking device." usage: "For tracking non powered assets, where you have a size or weight restriction" sku: ZE2 From e76e62b2f59d82fbb59623f726acad9ab96c316f Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 12 Sep 2025 11:50:25 +0100 Subject: [PATCH 07/38] link more spec terms --- components/DeviceSpecTable.vue | 30 ++++++++++++++++++++++++++++++ public/device-specs/rtk/v2.yaml | 1 + 2 files changed, 31 insertions(+) diff --git a/components/DeviceSpecTable.vue b/components/DeviceSpecTable.vue index c89981d..7d9390c 100644 --- a/components/DeviceSpecTable.vue +++ b/components/DeviceSpecTable.vue @@ -67,6 +67,13 @@ const props = defineProps({ type: String, required: false } + , + // Optional mapping from term -> url. Allows overriding where terms link to. + termLinkMap: { + type: Object, + required: false, + default: () => ({}) + } }) const specs = ref(null) @@ -89,6 +96,26 @@ function normalizePhrase(str) { return phrase } +const defaultTermLinkMap = { + rtk: '/terminology/positioning/rtk', + gnss: '/terminology/positioning/gnss', + "Wi-Fi AP scanning": '/terminology/positioning/wifi', +} + +function linkTerms(text) { + if (!text || typeof text !== 'string') return text + const map = { ...defaultTermLinkMap, ...props.termLinkMap } + // Sort keys by length desc to avoid partial matches (e.g., 'gps' inside other words) + const keys = Object.keys(map).sort((a, b) => b.length - a.length) + let out = text + for (const key of keys) { + const url = map[key] + const re = new RegExp('\\b' + key.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\$&') + '\\b', 'gi') + out = out.replace(re, match => `${match}`) + } + return out +} + function formatValueForDisplay(val) { if (Array.isArray(val)) { // Array: join items, format each recursively @@ -180,6 +207,9 @@ watchEffect(() => { .join('
') } displayValue = formatValueForDisplay(v) + if (typeof displayValue === 'string') { + displayValue = linkTerms(displayValue) + } if (displayValue !== undefined && displayValue !== null && displayValue !== '' && displayValue !== '[]' && displayValue !== '{}') { rows.push({ label: normalizePhrase(k), value: displayValue }) } diff --git a/public/device-specs/rtk/v2.yaml b/public/device-specs/rtk/v2.yaml index 00dd376..d878cd7 100644 --- a/public/device-specs/rtk/v2.yaml +++ b/public/device-specs/rtk/v2.yaml @@ -68,6 +68,7 @@ product: gnss: typical_accuracy_meters: "0.01-1" technologies: + - "RTK" - "GPS" - "QZSS" - "GLONASS" From 9dc861f05c1cfe4fee20382059e119d9fea5affc Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 12 Sep 2025 12:12:59 +0100 Subject: [PATCH 08/38] RH2 has ESP32-C6 --- public/device-specs/rtk/v2.yaml | 43 +++++++++++++++------------------ 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/public/device-specs/rtk/v2.yaml b/public/device-specs/rtk/v2.yaml index d878cd7..6fd8ac4 100644 --- a/public/device-specs/rtk/v2.yaml +++ b/public/device-specs/rtk/v2.yaml @@ -239,33 +239,28 @@ product: spi: "Yes (Multiplexed from I2C and UART1)" wifi: manufacturer: "Espressif Systems" - model: "ESP8285" + model: "ESP32-C6" specifications: - soc_description: "Highly integrated Wi-Fi SoC solution for IoT industry (can perform as a standalone application or as the slave to a host MCU)" - recommended_upgrade: "ESP8684" - wi_fi_protocols: "802.11 b/g/n (HT20)" - wi_fi_frequency_range_mhz: "2400 ~ 2483.5" + soc_description: "Ultra-low-power SoC with RISC-V single-core CPU, supports Wi-Fi 6 (802.11ax), Bluetooth LE, Zigbee, Thread" + wi_fi_protocols: "802.11 b/g/n/ac/ax (Wi-Fi 6), backward compatible" + wi_fi_frequency_range_mhz: "2412 ~ 2484" tx_power_dbm: - 802_11b: "+19" - 802_11g_6mbps: "+19" - 802_11g_54mbps: "+15" - 802_11n_mcs0: "+19" - 802_11n_mcs7: "+14" - rx_sensitivity_dbm: - 802_11b_1mbps: "-97" - 802_11g_54mbps: "-74" - 802_11n_mcs7: "-70" - cpu: "Tensilica L106 32-bit processor (up to 160 MHz)" - memory: "1 MB" - operating_voltage_v: "2.7 ~ 3.6" - crystal_frequency_range_mhz: "24 ~ 52" - power_consumption_deep_sleep_ua: 20 + 802_11b: "+21" + 802_11ax (MCS9, 20MHz): "+19.5" + cpu: + high_performance: "32-bit RISC-V up to 160 MHz" + low_power: "32-bit RISC-V up to 20 MHz" + memory: + rom: "320 KB" + hp_sram: "512 KB" + lp_sram: "16 KB" + external_flash: "TBD" + operating_voltage_v: "3.0 ~ 3.6" + crystal_frequency_range_mhz: "40" + power_consumption_deep_sleep_ua: 7 power_consumption_active_ma: - average: 80 - tx_802_11b_19dbm: 197 - rx_802_11b: 73 - modem_sleep_typical: "~15 (DTIM3, 300ms sleep/3ms wakeup)" - light_sleep_typical: "~0.9 (DTIM3, 300ms sleep/3ms wakeup)" + typical_receive: ~71-82 + typical_transmit_max: ~354 accelerometer: manufacturer: "STMicroelectronics" model: "LIS3DHTR" From f5e0ea90a9d7560aa34d16f2de782537b0c592a4 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 12 Sep 2025 12:34:17 +0100 Subject: [PATCH 09/38] fix RH2 STM & Link datasheet urls --- components/DeviceSpecTable.vue | 20 ++++++++++---- public/device-specs/rtk/v2.yaml | 49 +++++++++++++++++---------------- 2 files changed, 40 insertions(+), 29 deletions(-) diff --git a/components/DeviceSpecTable.vue b/components/DeviceSpecTable.vue index 7d9390c..0de455e 100644 --- a/components/DeviceSpecTable.vue +++ b/components/DeviceSpecTable.vue @@ -16,11 +16,11 @@

Specification

Overview

{{ specs.product.description }} - +
- +
{{ key }}{{ value }}
@@ -116,6 +116,13 @@ function linkTerms(text) { return out } +function linkUrls(text) { + if (!text || typeof text !== 'string') return text + // Simple URL regex (http/https). Keep it conservative. + const urlRe = /https?:\/\/[^\s"'<>]+/g + return text.replace(urlRe, url => `${url}`) +} + function formatValueForDisplay(val) { if (Array.isArray(val)) { // Array: join items, format each recursively @@ -145,12 +152,12 @@ watchEffect(() => { } // Main table (excluding sectionKeys) displaySpecs.value = { - 'Name': p.name, + 'Name': linkUrls(linkTerms(p.name)), 'Version': p.version, 'Serial prefix': p.prefix, - 'Connectivity': p.connectivity ? Object.keys(p.connectivity).join(', ') : '', - 'Positioning': p.positioning ? Object.keys(p.positioning).join(', ') : '', - 'Sensors': p.sensors ? Object.keys(p.sensors).join(', ') : '', + 'Connectivity': p.connectivity ? linkUrls(linkTerms(Object.keys(p.connectivity).join(', '))) : '', + 'Positioning': p.positioning ? linkUrls(linkTerms(Object.keys(p.positioning).join(', '))) : '', + 'Sensors': p.sensors ? linkUrls(linkTerms(Object.keys(p.sensors).join(', '))) : '', } // Generic section rendering for (const sectionKey of sectionKeys) { @@ -209,6 +216,7 @@ watchEffect(() => { displayValue = formatValueForDisplay(v) if (typeof displayValue === 'string') { displayValue = linkTerms(displayValue) + displayValue = linkUrls(displayValue) } if (displayValue !== undefined && displayValue !== null && displayValue !== '' && displayValue !== '[]' && displayValue !== '{}') { rows.push({ label: normalizePhrase(k), value: displayValue }) diff --git a/public/device-specs/rtk/v2.yaml b/public/device-specs/rtk/v2.yaml index 6fd8ac4..c198cc5 100644 --- a/public/device-specs/rtk/v2.yaml +++ b/public/device-specs/rtk/v2.yaml @@ -178,34 +178,36 @@ product: - "REACH" - "RCM" - "PTCRB" - bluetooth-le: + P1 & LORA: manufacturer: "STMicroelectronics" - model: "STM32WB55xx" + model: "STM32WL55xx" + datasheet: "https://www.st.com/resource/en/datasheet/stm32wl55cc.pdf" specifications: - chip_type: "Multiprotocol wireless 32-bit MCU Arm®-based Cortex®-M4 with FPU" + chip_type: "Multiprotocol wireless 32-bit MCU, Arm® Cortex-M4 with FPU + Cortex-M0+ for real-time radio tasks" radio_features: - frequency: "2.4 GHz" - bluetooth_specification: "5.4" - ieee_802_15_4_support: "Yes (supports PHY and MAC, supporting Thread 1.3 and Zigbee® 3.0)" + frequency_range_mhz: "150 - 960" + supported_modulations: "LoRa, (G)FSK, (G)MSK, BPSK" rx_sensitivity_dbm: - bluetooth_le_1mbps: -96 - ieee_802_15_4: -100 - programmable_output_power_dbm: "+6 (with 1 dB steps)" - integrated_balun: "Yes" - data_rates_mbps: "1, 2" - dedicated_radio_cpu: "Arm® 32-bit Cortex® M0+" - external_pa_support: "Yes" + 2fsK_1_2kbps: -123 + LoRa_SF12_BW_10_4kHz: -148 + programmable_output_power_dbm: + low_power_path: "+15" + high_power_path: "+22" + integrated_balun: "Yes (with IPD companion options for matching / filtering / balun)" memory: flash_memory_size: "Up to 1 Mbyte" - sram_size: "Up to 256 Kbytes (includes 64 K SRAM1 and 64 K SRAM2 for STM32WB55Cx models)" - operating_voltage_v: "1.71 to 3.6" - operating_temperature_c: "-40 to +85 and -40 to +105 (Ambient)" - power_consumption_typical_ma_at_3_3v: - tx_max_output_bluetooth_le: 7.8 - tx_max_output_802_15_4: 6.5 - rx_bluetooth_le: 4.5 - rx_802_15_4: 4.5 - run_mode_flash_64mhz_typical_ma: 8.8 + sram_size: "Up to 256 Kbytes" + operating_voltage_v: "1.8 to 3.6" + operating_temperature_c: "-40 to +105 (ambient), -40 to +85 (with radio)" + power_consumption_modes: + shutdown_at_3v: "31 nA" + standby_rtc_at_3v: "360 nA" + stop2_rtc_at_3v: "1.07 µA" + active_mcu_performance: "<72 µA/MHz (CoreMark benchmark)" + rx_active: "4.82 mA" + tx_active: + LoRa_125kHz_10dBm: "15 mA" + LoRa_125kHz_20dBm: "87 mA" gnss: manufacturer: "Quectel" model: "LC29HEA" @@ -237,9 +239,10 @@ product: i2c: "Yes (Up to 400 kbps)" uart: "Yes (Adjustable: 9600-3000000 bps)" spi: "Yes (Multiplexed from I2C and UART1)" - wifi: + P2 & WIFI/BT: manufacturer: "Espressif Systems" model: "ESP32-C6" + datasheet: "https://www.espressif.com/sites/default/files/documentation/esp32-c6_datasheet_en.pdf" specifications: soc_description: "Ultra-low-power SoC with RISC-V single-core CPU, supports Wi-Fi 6 (802.11ax), Bluetooth LE, Zigbee, Thread" wi_fi_protocols: "802.11 b/g/n/ac/ax (Wi-Fi 6), backward compatible" From 29fd5e606cc65143ff6caa932cd4e84df240cd7a Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 12 Sep 2025 12:45:55 +0100 Subject: [PATCH 10/38] rtk: Section on correction data --- terminology/positioning/rtk.md | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/terminology/positioning/rtk.md b/terminology/positioning/rtk.md index 2f061bb..3c61ea8 100644 --- a/terminology/positioning/rtk.md +++ b/terminology/positioning/rtk.md @@ -27,6 +27,16 @@ It continuously collects GNSS observations and computes correction data that rep With the Lightbug platform, you are able to bring your own base station, make use of an existing network of stream of data, or use a Lightbug device in base station mode to provide corrections to other Lightbug devices in rover mode. +## Correction data + +Correction data is the set of messages produced by a base station that describe how satellite measurements should be adjusted so a rover can compute a much more accurate position. Corrections are commonly encoded in RTCM formats and delivered over radio links, cellular/NTRIP streams, or cloud services. + +Options include: + +- [**Swift Navigation Skylark**](https://www.swiftnav.com/products/skylark) — we have partnered with Swift Navigation to provide out of the box access to their Skylark service with a six-month free trial of Skylark Precise Positioning Service, providing immediate access to centimeter-level accuracy ([press release](https://www.swiftnav.com/resource/press-release/swift-navigation-and-lightbug-partner-to-deliver-centimeter-level-positioning-for-worker-safety-and-asset-tracking)) +- **Bring your own (BYO) correction source** — you can use any service or base station that outputs standard RTCM messages and stream them to your devices (for example via NTRIP or a private RTCM server). +- **Lightbug device as a base** — an RTK enabled Lightbug device can be configured in base station mode to generate correction streams that other Lightbug devices in rover mode can consume. + ## How it works (high level) - A fixed base station sits at a precisely surveyed, known location and collects GNSS observations. @@ -35,13 +45,6 @@ With the Lightbug platform, you are able to bring your own base station, make us - Corrections are transmitted to the rover in real time (commonly using RTCM formats over radio, cellular/NTRIP, or other links). - The rover applies the corrections and attempts to resolve carrier-phase integer ambiguities, producing a corrected, high-precision fix. -## Other terminology - -- Base station: a stationary receiver with a surveyed location that provides correction data. -- Rover: the mobile receiver that applies corrections to improve its position. -- RTCM: a common protocol family (RTCM 2.x/3.x) used to encode and stream correction messages. -- NTRIP: Networked Transport of RTCM via Internet Protocol, commonly used to stream corrections over cellular data. - ## Practical accuracy and limits - Centimetre-level horizontal accuracy is achievable under good conditions with successful ambiguity resolution. @@ -54,3 +57,8 @@ With the Lightbug platform, you are able to bring your own base station, make us - Ionospheric and tropospheric delays: residual atmospheric errors increase with baseline; network RTK mitigates this. - Latency and packet loss: delayed or missing corrections cause the rover to revert to a less accurate solution or lose fix. - Incorrect base coordinates: if the base station location is wrong, corrections will be biased and produce incorrect rover positions. + +## Other terminology + +- RTCM: a common protocol family (RTCM 2.x/3.x) used to encode and stream correction messages. +- NTRIP: Networked Transport of RTCM via Internet Protocol, commonly used to stream corrections over cellular data. From b90463c0e3babfc322b69dc1c4de38c5ea4fb1dd Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 12 Sep 2025 13:12:26 +0100 Subject: [PATCH 11/38] Use a single image for VT3 install tools --- devices.md | 2 +- devices/vehicle/installation.md | 51 ++------------------------------- 2 files changed, 3 insertions(+), 50 deletions(-) diff --git a/devices.md b/devices.md index 22c3ceb..dca5c37 100644 --- a/devices.md +++ b/devices.md @@ -1,5 +1,5 @@ --- -outline: deep| +aside: true --- # Devices diff --git a/devices/vehicle/installation.md b/devices/vehicle/installation.md index 4c5c161..3a4295c 100644 --- a/devices/vehicle/installation.md +++ b/devices/vehicle/installation.md @@ -11,56 +11,9 @@ If you purchase a vehicle tracker today, you will receive the VT3 device. ## Tools -You will likely need the following tools to complete installation. +You will likely need the following tools to complete installation: - - - - - - - - - +![](https://i.imgur.com/rKFNxsu.png){width=300 style="float:right"} - Long Nose Pliers - Wire cutters or strippers From 5b3b5eecea5460336e59e0c3a21e5155dd92ef9f Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 12 Sep 2025 15:37:51 +0100 Subject: [PATCH 12/38] admin: Split all devices doc pages --- .vitepress/config.mts | 46 +++- apps/admin/devices.md | 248 --------------------- apps/admin/devices/bulk/activate.md | 6 + apps/admin/devices/bulk/export.md | 8 + apps/admin/devices/bulk/index.md | 9 + apps/admin/devices/bulk/settings.md | 10 + apps/admin/devices/bulk/tags.md | 22 ++ apps/admin/devices/bulk/users.md | 8 + apps/admin/devices/firmware.md | 9 + apps/admin/devices/forwarding.md | 13 ++ apps/admin/devices/ids.md | 9 + apps/admin/devices/index.md | 44 ++++ apps/admin/devices/information.md | 65 ++++++ apps/admin/devices/name.md | 11 + apps/admin/devices/rtk.md | 9 + apps/admin/devices/settings.md | 11 + apps/admin/devices/users.md | 46 ++++ package-lock.json | 332 +++++++++++++++++++++++++++- package.json | 3 +- 19 files changed, 653 insertions(+), 256 deletions(-) delete mode 100644 apps/admin/devices.md create mode 100644 apps/admin/devices/bulk/activate.md create mode 100644 apps/admin/devices/bulk/export.md create mode 100644 apps/admin/devices/bulk/index.md create mode 100644 apps/admin/devices/bulk/settings.md create mode 100644 apps/admin/devices/bulk/tags.md create mode 100644 apps/admin/devices/bulk/users.md create mode 100644 apps/admin/devices/firmware.md create mode 100644 apps/admin/devices/forwarding.md create mode 100644 apps/admin/devices/ids.md create mode 100644 apps/admin/devices/index.md create mode 100644 apps/admin/devices/information.md create mode 100644 apps/admin/devices/name.md create mode 100644 apps/admin/devices/rtk.md create mode 100644 apps/admin/devices/settings.md create mode 100644 apps/admin/devices/users.md diff --git a/.vitepress/config.mts b/.vitepress/config.mts index a99aab0..96f45a3 100644 --- a/.vitepress/config.mts +++ b/.vitepress/config.mts @@ -7,6 +7,7 @@ import { loadSpec } from '../swagger/load'; import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs'; import { pagefindPlugin } from 'vitepress-plugin-pagefind'; import { withMermaid } from "vitepress-plugin-mermaid"; +import { withSidebar, generateSidebar } from 'vitepress-sidebar'; // Load protocol messages from YAML file @@ -61,6 +62,44 @@ const sidebarItemsFromDir = (dir) => { const sidebarSpec1 = useSidebar({ spec: loadSpec(1) }); const sidebarSpec2 = useSidebar({ spec: loadSpec(2) }); +// Generate Admin Actions sidebar automatically using vitepress-sidebar +const generateAdminDevicesSidebar = () => { + // Helper function to fix links recursively + const fixLinks = (items, prefix = '/apps/admin/devices') => { + return items.map(item => { + const newItem = { ...item }; + + // Fix the link if it exists and doesn't already start with the prefix + if (newItem.link && !newItem.link.startsWith(prefix)) { + newItem.link = prefix + (newItem.link.startsWith('/') ? '' : '/') + newItem.link; + } + + // Recursively fix nested items + if (newItem.items) { + newItem.items = fixLinks(newItem.items, prefix); + } + + return newItem; + }); + }; + + // Use vitepress-sidebar to auto-generate the entire devices section + const devicesSidebar = generateSidebar({ + documentRootPath: '/', + scanStartPath: 'apps/admin/devices', + hyphenToSpace: true, + capitalizeFirst: true, + sortMenusByFrontmatterOrder: true, + frontmatterOrderDefaultValue: 100, + includeRootIndexFile: false, + includeFolderIndexFile: false, + useFolderLinkFromIndexFile: true, + }); + + const fixedSidebar = Array.isArray(devicesSidebar) ? fixLinks(devicesSidebar) : devicesSidebar; + return fixedSidebar as any; +}; + // Function to make a sidebar group be collapsed function collapse(group) { group.collapsed = true; @@ -658,12 +697,7 @@ export default withMermaid(defineConfig({ ] }, { text: 'Devices', link: '/apps/admin/devices', // No ending /, as the sub items are not sub pages - items: [ - { text: 'Actions', link: '/apps/admin/devices#actions' }, - { text: 'Metrics', link: '/apps/admin/devices#metric-summary' }, - { text: 'Timeline', link: '/apps/admin/devices#timeline' }, - ] - + items: generateAdminDevicesSidebar() }, { text: 'Configs', link: '/apps/admin/configs' }, { text: 'Users', link: '/apps/admin/users' }, diff --git a/apps/admin/devices.md b/apps/admin/devices.md deleted file mode 100644 index d10719b..0000000 --- a/apps/admin/devices.md +++ /dev/null @@ -1,248 +0,0 @@ ---- -outline: deep ---- -# Devices - -The default view is the device list, which shows all devices in the account. - -![Lightbug Admin device list](https://i.imgur.com/pHBzHGL.png) - - -## Fields - -The device list shows the following fields: -- Identifiers: Static [identifiers](/terminology/devices#identity) for the device. - - ID - - Serial Number - - IMEI - - ICCID -- Names - - Name: User defined name for the device. - - Type: Device type, such as `ZeroN` etc. - - Tags: User defined [tags](/terminology/devices#tags) for the device. -- Seen - - Last: Time the device was last seen by the Lightbug platform, this may not be a 1:1 match with when data was last received. - - First: Time the device was fist seen by the Lightbug platform, likely around the time it was manufactured. -- Users -- Plan details -- Firmware -- Status - - Battery - - Enabled -- [Actions](#actions) - -## Filters - -Filters can be used to narrow down the list of devices. - -Such as all `ZeroN` devices. - -![](https://i.imgur.com/JkdXTz6.png) - -## Actions - -The devices table has a number of actions that can be performed on devices, either in row actions, or via buttons on the right hand side of the rows, or via bulk actions having multiple devices selected. - -### In row actions - -Some actions are available on the device list itself, by interacting with various fields. - -#### Reveal ICCID & IMEI - -Click the `(more)` link in the identifiers column to reveal the ICCID and IMEI of the device. - -![](https://i.imgur.com/xoTN8ga.png) - -#### Set device name - -Hovering over a device name will reveal a pencil icon, which can be clicked to edit the device name. - -Hit `Enter` to save the new name, or `Escape` to cancel. - -![](https://i.imgur.com/22CIjYw.png) - -#### Schedule Firmware Update - -Clicking on the firmware version will open a dialog allowing you to schedule a firmware update for the device. - -![](https://i.imgur.com/sK66Th6.png) - -### Button Actions - -Some per device actions are available via buttons on the right hand side of the rows, these are: - -![](https://i.imgur.com/vOxhOLt.png) - -- Activate / Deactivate -- [Manage Users](#manage-users) -- Manage Settings -- [See Device information](#device-information) -- [Manage Forwarding](#manage-forwarding) - -#### Manage Users - -Clicking on the manage users icon will open a dialog allowing you to add or remove users from the device. - -Users can have different global permissions, which can be controlled from the [users page](/apps/admin/users#permissions). - -![](https://i.imgur.com/yIbkNpT.png) - -Clicking `Add User`, open an additional dialog allowing you to select a user from the list of users on your account, or create a new user. - -##### Add Existing user - -In order to select an existing user, click on their name from the list. - -![](https://i.imgur.com/5aFeQlp.png) - -On success, you'll see a confirmation message in the top right corner. - -:::warning -If the user already exists, and they are not a sub user of your account, you will not be able to select them. -If this is the case please contact support to get them added to your account. -::: - -##### Add New user - -To create a new user, click on the `Create New` button. - -This will open a dialog allowing you to enter the new users details. - -![](https://i.imgur.com/QEnoQwF.png) - -Once you have entered the details, click `Create` to create the user. - -On success, you'll see a confirmation message in the top right corner. - -:::warning -If the user already exists, you will not be able to create an account for them. -::: - -##### Remove a user - -To remove a user from the device, click on the `X` icon next to their name. - -![](https://i.imgur.com/HBqlqN1.png) - -#### Device information - -Verbose information about the device, including: - - Overview: General device state information, and summarized device information over time. - - Timeline: A detailed timeline of data received from the device, and received by SIM providers. - - Config Pages: Current config pages applied to the device. - -![](https://i.imgur.com/GnwHbXd.png) - -##### Plan - -The top of the overview summarizes the current plan and billing state of the device. - -![](https://i.imgur.com/tIjgUsM.png) - -##### Metric Summary - -An expandable overview of some device metrics, errors, sends and power usage are then displayed. - -You can mouse over the graphs to see more details about the data. - -Use the `+1 Day` and `+1 Week` buttons to expands the time range of the graph into the past. - -![](https://i.imgur.com/alZiO0u.png) - -This data is then summarized further with some key metrics. - -- Total Time -- Total Points -- Avenge Signal -- Average Points per transmit -- Average Time Between Points -- Average Time Between -- Average Power per Transmit -- Average Power per Point -- Battery percent and voltage at start of the time range. -- Battery percent and voltage at end of the time range. -- Max battery voltage during the time range. - -And a breakdown of the source of locations received during the time range. - -![](https://i.imgur.com/oIgFTmS.png) - -##### Timeline - -The timeline shows a detailed view of events from and related to the device. - -This will poll for new events as they happen, so you should be able to see your device connecting and sending data when active. - -![](https://i.imgur.com/WoevCJG.png) - -:::warning -If your device has not recently sent any data, you might find the timeline empty, or timing out. -::: - -##### Config Pages - -The config pages tab shows the current config pages applied to the device. - -You can remove config pages by clicking the `X` icon next to the config page name. - -![](https://i.imgur.com/nrXrJW2.png) - -#### Manage Forwarding - -Forwarding is a new feature that allows you to forward data from your devices to other services, currently webhooks and MQTT. - -![](https://i.imgur.com/zkPvShs.png) - -This will replace similar messaging based on Notifications, which is only accessible via API or the [Cloud app](/apps/cloud/account/notifications). - -If you would like to use this feature, please contact support to get it enabled on your account, as it is not yet at General Availability. - -### Bulk actions - -You can select multiple devices and perform actions on them by using the check boxes at the left of each row. - -![](https://i.imgur.com/Y2LyH7r.png) - -#### Export - -Exporting selected devices will display a modal allowing you to copy a IDs, Serial numbers, IMEIs or ICCIDs in a bulk format. - -![](https://i.imgur.com/GiLWsY4.png) - -#### Settings - -Settings allows you to change the settings of multiple devices at once, making use of [config pages](./configs.html) that you have on your account. - -![](https://i.imgur.com/MTQGSiH.png) - - - -#### Users - -Allows, bulk adding or removing of users from the selected devices. - -![](https://i.imgur.com/KIweB1w.png) - -#### Tags - -Allows, bulk adding or removing of tags from the selected devices. - -Devices with no tags will allow you to add new tags. - -![](https://i.imgur.com/KjnS0iT.png) - -Changes can be reviewed before they are applied. - -![](https://i.imgur.com/lE4YthQ.png) - -If selected devices have a mixture of tags, they will be shown differently in the existing tags list. - -![](https://i.imgur.com/ES7HrLi.png) - -Tags can be: - - Removed from all devices, using the bin icon. - - Applied to all devices by clicking tags only applied to "Some devices" (in yellow). - -#### Activate / Deactivate - -Bulk activations and deactivations of devices. diff --git a/apps/admin/devices/bulk/activate.md b/apps/admin/devices/bulk/activate.md new file mode 100644 index 0000000..1414fac --- /dev/null +++ b/apps/admin/devices/bulk/activate.md @@ -0,0 +1,6 @@ +--- +outline: shallow +--- +# Activate / Deactivate (bulk) + +Bulk activations and deactivations of devices. diff --git a/apps/admin/devices/bulk/export.md b/apps/admin/devices/bulk/export.md new file mode 100644 index 0000000..aeaf515 --- /dev/null +++ b/apps/admin/devices/bulk/export.md @@ -0,0 +1,8 @@ +--- +outline: shallow +--- +# Export + +Exporting selected devices will display a modal allowing you to copy IDs, Serial numbers, IMEIs or ICCIDs in a bulk format. + +![](https://i.imgur.com/GiLWsY4.png) diff --git a/apps/admin/devices/bulk/index.md b/apps/admin/devices/bulk/index.md new file mode 100644 index 0000000..6d59e1a --- /dev/null +++ b/apps/admin/devices/bulk/index.md @@ -0,0 +1,9 @@ +--- +outline: deep +order: 50 +--- +# Bulk actions + +Bulk actions operate on multiple selected devices from the devices list. + +Some bulk actions can conflict across devices, and lead to error messages, so make sure to select only devices that can share the same action. diff --git a/apps/admin/devices/bulk/settings.md b/apps/admin/devices/bulk/settings.md new file mode 100644 index 0000000..6e67e8a --- /dev/null +++ b/apps/admin/devices/bulk/settings.md @@ -0,0 +1,10 @@ +--- +outline: shallow +--- +# Settings (bulk) + +Settings allows you to change the settings of multiple devices at once, making use of config pages that you have on your account. + +![](https://i.imgur.com/MTQGSiH.png) + + diff --git a/apps/admin/devices/bulk/tags.md b/apps/admin/devices/bulk/tags.md new file mode 100644 index 0000000..310ca74 --- /dev/null +++ b/apps/admin/devices/bulk/tags.md @@ -0,0 +1,22 @@ +--- +outline: shallow +--- +# Tags (bulk) + +Allows bulk adding or removing of tags from the selected devices. + +Devices with no tags will allow you to add new tags. + +![](https://i.imgur.com/KjnS0iT.png) + +Changes can be reviewed before they are applied. + +![](https://i.imgur.com/lE4YthQ.png) + +If selected devices have a mixture of tags, they will be shown differently in the existing tags list. + +![](https://i.imgur.com/ES7HrLi.png) + +Tags can be: + - Removed from all devices, using the bin icon. + - Applied to all devices by clicking tags only applied to "Some devices" (in yellow). diff --git a/apps/admin/devices/bulk/users.md b/apps/admin/devices/bulk/users.md new file mode 100644 index 0000000..fd6ba76 --- /dev/null +++ b/apps/admin/devices/bulk/users.md @@ -0,0 +1,8 @@ +--- +outline: shallow +--- +# Users (bulk) + +Allows bulk adding or removing of users from the selected devices. + +![](https://i.imgur.com/KIweB1w.png) diff --git a/apps/admin/devices/firmware.md b/apps/admin/devices/firmware.md new file mode 100644 index 0000000..a77e5bd --- /dev/null +++ b/apps/admin/devices/firmware.md @@ -0,0 +1,9 @@ +--- +outline: shallow +order: 3 +--- +# Schedule Firmware Update + +Clicking on the firmware version will open a dialog allowing you to schedule a firmware update for the device. + +![](https://i.imgur.com/sK66Th6.png) diff --git a/apps/admin/devices/forwarding.md b/apps/admin/devices/forwarding.md new file mode 100644 index 0000000..75f5613 --- /dev/null +++ b/apps/admin/devices/forwarding.md @@ -0,0 +1,13 @@ +--- +outline: shallow +order: 8 +--- +# Manage Forwarding + +Forwarding allows you to forward data from your devices to other services, currently webhooks and MQTT. + +![](https://i.imgur.com/zkPvShs.png) + +This will replace similar messaging based on Notifications, which is only accessible via API or the [Cloud app](/apps/cloud/account/notifications). + +If you would like to use this feature, please contact support to get it enabled on your account, as it is not yet at General Availability. diff --git a/apps/admin/devices/ids.md b/apps/admin/devices/ids.md new file mode 100644 index 0000000..894dc10 --- /dev/null +++ b/apps/admin/devices/ids.md @@ -0,0 +1,9 @@ +--- +outline: shallow +order: 1 +--- +# Reveal ICCID & IMEI + +Click the `(more)` link in the identifiers column to reveal the ICCID and IMEI of the device. + +![](https://i.imgur.com/xoTN8ga.png) diff --git a/apps/admin/devices/index.md b/apps/admin/devices/index.md new file mode 100644 index 0000000..8860f18 --- /dev/null +++ b/apps/admin/devices/index.md @@ -0,0 +1,44 @@ +--- +outline: deep +--- + +# Devices + +The devices page lists all devices that you have access to. + +From here you can view device status, location, and other information. You can also perform actions on devices, such as viewing logs, sending commands, and managing settings. + +A variety of individual and bulk actions are available. + +![Lightbug Admin device list](https://i.imgur.com/pHBzHGL.png) + +## Fields + +The device list shows the following fields: +- Identifiers: Static [identifiers](/terminology/devices#identity) for the device. + - ID + - Serial Number + - IMEI + - ICCID +- Names + - Name: User defined name for the device. + - Type: Device type, such as `ZeroN` etc. + - Tags: User defined [tags](/terminology/devices#tags) for the device. +- Seen + - Last: Time the device was last seen by the Lightbug platform, this may not be a 1:1 match with when data was last received. + - First: Time the device was fist seen by the Lightbug platform, likely around the time it was manufactured. +- Users +- Plan details +- Firmware +- Status + - Battery + - Enabled +- [Actions](#actions) + +## Filters + +Filters can be used to narrow down the list of devices. + +Such as all `ZeroN` devices. + +![](https://i.imgur.com/JkdXTz6.png) diff --git a/apps/admin/devices/information.md b/apps/admin/devices/information.md new file mode 100644 index 0000000..72622fa --- /dev/null +++ b/apps/admin/devices/information.md @@ -0,0 +1,65 @@ +--- +outline: deep +order: 7 +--- +# Device information + +Verbose information about the device, including: + - Overview: General device state information, and summarized device information over time. + - Timeline: A detailed timeline of data received from the device, and received by SIM providers. + - Config Pages: Current config pages applied to the device. + +![](https://i.imgur.com/GnwHbXd.png) + +## Plan + +The top of the overview summarizes the current plan and billing state of the device. + +![](https://i.imgur.com/tIjgUsM.png) + +## Metric Summary + +An expandable overview of some device metrics, errors, sends and power usage are then displayed. +You can mouse over the graphs to see more details about the data. + +Use the `+1 Day` and `+1 Week` buttons to expands the time range of the graph into the past. + +![](https://i.imgur.com/alZiO0u.png) + +This data is then summarized further with some key metrics. + +- Total Time +- Total Points +- Avenge Signal +- Average Points per transmit +- Average Time Between Points +- Average Time Between +- Average Power per Transmit +- Average Power per Point +- Battery percent and voltage at start of the time range. +- Battery percent and voltage at end of the time range. +- Max battery voltage during the time range. + +And a breakdown of the source of locations received during the time range. + +![](https://i.imgur.com/oIgFTmS.png) + +## Timeline + +The timeline shows a detailed view of events from and related to the device. + +This will poll for new events as they happen, so you should be able to see your device connecting and sending data when active. + +![](https://i.imgur.com/WoevCJG.png) + +:::warning +If your device has not recently sent any data, you might find the timeline empty, or timing out. +::: + +## Config Pages + +The config pages tab shows the current config pages applied to the device. + +You can remove config pages by clicking the `X` icon next to the config page name. + +![](https://i.imgur.com/nrXrJW2.png) diff --git a/apps/admin/devices/name.md b/apps/admin/devices/name.md new file mode 100644 index 0000000..f62c5f8 --- /dev/null +++ b/apps/admin/devices/name.md @@ -0,0 +1,11 @@ +--- +outline: shallow +order: 2 +--- +# Set device name + +Hovering over a device name will reveal a pencil icon, which can be clicked to edit the device name. + +Hit `Enter` to save the new name, or `Escape` to cancel. + +![](https://i.imgur.com/22CIjYw.png) diff --git a/apps/admin/devices/rtk.md b/apps/admin/devices/rtk.md new file mode 100644 index 0000000..7c8adfd --- /dev/null +++ b/apps/admin/devices/rtk.md @@ -0,0 +1,9 @@ +--- +outline: shallow +order: 6 +--- +# Manage RTK + +RTK-specific controls are available for RTK-capable devices. Open the RTK dialog from the device row actions to view and modify RTK settings. + +This button only appears for RTK devices. diff --git a/apps/admin/devices/settings.md b/apps/admin/devices/settings.md new file mode 100644 index 0000000..a0928e9 --- /dev/null +++ b/apps/admin/devices/settings.md @@ -0,0 +1,11 @@ +--- +outline: shallow +order: 5 +--- +# Manage Settings + +Settings allows you to change device settings. From the devices list you can open the settings dialog for a single device and apply config pages. + +See also: bulk settings page for applying settings to multiple devices. + +![](https://i.imgur.com/MTQGSiH.png) diff --git a/apps/admin/devices/users.md b/apps/admin/devices/users.md new file mode 100644 index 0000000..e35b01f --- /dev/null +++ b/apps/admin/devices/users.md @@ -0,0 +1,46 @@ +--- +outline: shallow +order: 4 +--- +# Manage Users + +Clicking on the manage users icon will open a dialog allowing you to add or remove users from the device. + +Users can have different global permissions, which can be controlled from the [users page](/apps/admin/users#permissions). + +![](https://i.imgur.com/yIbkNpT.png) + +## Add Existing user + +In order to select an existing user, click on their name from the list. + +![](https://i.imgur.com/5aFeQlp.png) + +On success, you'll see a confirmation message in the top right corner. + +:::warning +If the user already exists, and they are not a sub user of your account, you will not be able to select them. +If this is the case please contact support to get them added to your account. +::: + +## Add New user + +To create a new user, click on the `Create New` button. + +This will open a dialog allowing you to enter the new users details. + +![](https://i.imgur.com/QEnoQwF.png) + +Once you have entered the details, click `Create` to create the user. + +On success, you'll see a confirmation message in the top right corner. + +:::warning +If the user already exists, you will not be able to create an account for them. +::: + +## Remove a user + +To remove a user from the device, click on the `X` icon next to their name. + +![](https://i.imgur.com/HBqlqN1.png) diff --git a/package-lock.json b/package-lock.json index d03a6b3..0b31603 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,8 @@ "ts-node": "^10.9.2", "vitepress": "^1.3.3", "vitepress-plugin-mermaid": "^2.0.17", - "vitepress-plugin-tabs": "^0.5.0" + "vitepress-plugin-tabs": "^0.5.0", + "vitepress-sidebar": "^1.33.0" } }, "node_modules/@adobe/css-tools": { @@ -1324,6 +1325,109 @@ "url": "https://github.com/sponsors/kazupon" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1786,6 +1890,17 @@ ], "peer": true }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.44.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.1.tgz", @@ -4381,6 +4496,13 @@ "node": ">= 0.4" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, "node_modules/ejs": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", @@ -4729,6 +4851,36 @@ "tabbable": "^6.2.0" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", @@ -5359,6 +5511,22 @@ "node": ">=8" } }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jake": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", @@ -6539,6 +6707,16 @@ "node": "*" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/minisearch": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.1.1.tgz", @@ -6709,6 +6887,13 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/package-manager-detector": { "version": "0.2.11", "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.11.tgz", @@ -6813,6 +6998,30 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -7013,6 +7222,16 @@ } ] }, + "node_modules/qsu": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/qsu/-/qsu-1.10.1.tgz", + "integrity": "sha512-LQBaOApxuFZKZfRIZKzsuCRxSM1XWize9/SIWc5cBVlH/w8Pub4YVxx3mHMPQsmg6jM2cS6Uhkg7DPfwyTr0ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/quansync": { "version": "0.2.10", "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.10.tgz", @@ -7447,6 +7666,22 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/stringify-entities": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", @@ -7472,6 +7707,20 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -8226,6 +8475,68 @@ "vue": "^3.3.8" } }, + "node_modules/vitepress-sidebar": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/vitepress-sidebar/-/vitepress-sidebar-1.33.0.tgz", + "integrity": "sha512-+z45vGG6OQRKNU7OMhmrp5y9dk9Y8loFZGE2LwzqloqA4p+ECP3/Ti6qrS8kB3N8Rol4v0Nf8HSnsgZEDU4HXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "10.4.5", + "gray-matter": "4.0.3", + "qsu": "^1.10.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/vitepress-sidebar/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/vitepress-sidebar/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/vitepress-sidebar/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/vscode-jsonrpc": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", @@ -8496,6 +8807,25 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index 66ed34f..3970442 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "ts-node": "^10.9.2", "vitepress": "^1.3.3", "vitepress-plugin-mermaid": "^2.0.17", - "vitepress-plugin-tabs": "^0.5.0" + "vitepress-plugin-tabs": "^0.5.0", + "vitepress-sidebar": "^1.33.0" }, "scripts": { "dev": "cross-env PUBLIC_GOOGLE_ANALYTICS=${PUBLIC_GOOGLE_ANALYTICS} vitepress dev", From cccac3471408b79be2c57fc150131fd7841c368a Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 12 Sep 2025 15:56:03 +0100 Subject: [PATCH 13/38] Add RTK admin portal basic docs --- .vitepress/config.mts | 1 + .vitepress/theme/index.ts | 9 +++- apps/admin/devices/{ids.md => identifiers.md} | 0 apps/admin/devices/rtk.md | 43 ++++++++++++++++++- cspell.json | 3 +- package-lock.json | 7 +++ package.json | 1 + 7 files changed, 60 insertions(+), 4 deletions(-) rename apps/admin/devices/{ids.md => identifiers.md} (100%) diff --git a/.vitepress/config.mts b/.vitepress/config.mts index 96f45a3..0544b70 100644 --- a/.vitepress/config.mts +++ b/.vitepress/config.mts @@ -89,6 +89,7 @@ const generateAdminDevicesSidebar = () => { scanStartPath: 'apps/admin/devices', hyphenToSpace: true, capitalizeFirst: true, + useTitleFromFrontmatter: true, sortMenusByFrontmatterOrder: true, frontmatterOrderDefaultValue: 100, includeRootIndexFile: false, diff --git a/.vitepress/theme/index.ts b/.vitepress/theme/index.ts index 3755ba3..a7c7e81 100644 --- a/.vitepress/theme/index.ts +++ b/.vitepress/theme/index.ts @@ -9,6 +9,8 @@ import * as directives from 'vuetify/directives' import { createVuetify } from 'vuetify' import { VStepperVertical } from 'vuetify/labs/VStepperVertical' import '@mdi/font/css/materialdesignicons.css' // Ensure you are using css-loader +import 'eva-icons/style/eva-icons.css' +import * as eva from 'eva-icons' import { createVCodeBlock } from '@wdns/vue-code-block'; import 'vitepress-openapi/dist/style.css' @@ -38,10 +40,15 @@ export default { enhanceAppWithTabs(app) + // Initialize Eva Icons + if (typeof window !== 'undefined') { + eva.replace() + } + // Setup Theme const themeConfig = useTheme() themeConfig.setI18nConfig({ locale: 'en' }) themeConfig.setResponseCodeSelector('select') - theme.enhanceApp({ app }) + theme.enhanceApp({ app, router, siteData }) } } satisfies Theme diff --git a/apps/admin/devices/ids.md b/apps/admin/devices/identifiers.md similarity index 100% rename from apps/admin/devices/ids.md rename to apps/admin/devices/identifiers.md diff --git a/apps/admin/devices/rtk.md b/apps/admin/devices/rtk.md index 7c8adfd..849bc55 100644 --- a/apps/admin/devices/rtk.md +++ b/apps/admin/devices/rtk.md @@ -1,9 +1,48 @@ --- outline: shallow order: 6 +title: RTK --- # Manage RTK -RTK-specific controls are available for RTK-capable devices. Open the RTK dialog from the device row actions to view and modify RTK settings. +RTK-specific controls are available for RTK-capable devices. -This button only appears for RTK devices. +Open the RTK dialog from the device row actions to view and modify RTK settings by clicking the . + +The settings can be used to configure both rover and base station modes. + +## General + +### Group + +Selected by the user. +Any number 0 - 250. + +All devices assigned the same group number will only connect to a Base unit with the same group number. +This prevents cross communication between groups. +This is useful when you have more than one group working close to each other. + +Each group will be connected to their own Base unit. +Ensure each group is labelled with the corresponding number. + +### NTRIP Settings + +Input these configuration settings as per the supplier and setup. + +This is compulsory for a high level of accuracy. + +## Base mode + +### Fixed Position + +If you have known GPS coordinates select Fixed Position to green and input Altitude, and, Latitude & Longitude coordinates. + +![](https://i.imgur.com/URMQxLP.png) + +### Survey + +Survey mode allows the base station to determine its own position. + +## Rover + +![](https://i.imgur.com/QP5Im8F.png) diff --git a/cspell.json b/cspell.json index 1650e9c..44fea76 100644 --- a/cspell.json +++ b/cspell.json @@ -6,7 +6,8 @@ "package.json", "public/device-specs/**/*.yaml", "public/swagger/v1.json", - "public/files/protocol-v3.yaml" + "public/files/protocol-v3.yaml", + ".vitepress/theme/index.ts" ], "dictionaryDefinitions": [], "dictionaries": [], diff --git a/package-lock.json b/package-lock.json index 0b31603..c6996d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "buffer": "^6.0.3", "crc": "^4.3.2", "cross-env": "^7.0.3", + "eva-icons": "^1.1.3", "highlight.js": "^11.11.0", "jest-environment-jsdom": "^29.7.0", "js-yaml": "^4.1.0", @@ -4715,6 +4716,12 @@ "node": ">=0.10.0" } }, + "node_modules/eva-icons": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/eva-icons/-/eva-icons-1.1.3.tgz", + "integrity": "sha512-QBSEWNbEx1H0numXP1qgxKVCZHonRaky5ft4pGzQBcO4cy7mEja6TuJ8rc7BqX2pmkvetVQWKDH+DK/8y7GTag==", + "license": "MIT" + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", diff --git a/package.json b/package.json index 3970442..59171db 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "buffer": "^6.0.3", "crc": "^4.3.2", "cross-env": "^7.0.3", + "eva-icons": "^1.1.3", "highlight.js": "^11.11.0", "jest-environment-jsdom": "^29.7.0", "js-yaml": "^4.1.0", From a9d6645ef1c04be71e2f8b229baa2619889e1fb4 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 12 Sep 2025 16:09:09 +0100 Subject: [PATCH 14/38] Fix links in spec PDF render --- components/DownloadPdfButton.vue | 164 +++++++++++++++++++++++++++++-- 1 file changed, 157 insertions(+), 7 deletions(-) diff --git a/components/DownloadPdfButton.vue b/components/DownloadPdfButton.vue index 5ec96de..b5211c3 100644 --- a/components/DownloadPdfButton.vue +++ b/components/DownloadPdfButton.vue @@ -65,6 +65,134 @@ onMounted(async () => { } }); +/** + * Parse HTML text and extract links for PDF rendering with clickable links. + * @param {string} html - HTML string that may contain anchor tags + * @returns {Array} Array of text segments with link information + */ +function parseHtmlForPdfLinks(html) { + if (!html || typeof html !== 'string') return [{ text: html || '', isLink: false }]; + + const segments = []; + const anchorRe = /]*href=(?:"|')([^"']*)(?:"|')[^>]*>(.*?)<\/a>/gi; + let lastIndex = 0; + let match; + + while ((match = anchorRe.exec(html)) !== null) { + // Add text before the link + if (match.index > lastIndex) { + const beforeText = html.substring(lastIndex, match.index); + const cleanBefore = beforeText.replace(/<[^>]+>/g, ''); + if (cleanBefore) { + segments.push({ text: cleanBefore, isLink: false }); + } + } + + // Add the link + const href = match[1]; + const linkText = match[2].replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&'); + segments.push({ text: linkText, isLink: true, url: href }); + + lastIndex = anchorRe.lastIndex; + } + + // Add remaining text after last link + if (lastIndex < html.length) { + const remainingText = html.substring(lastIndex); + const cleanRemaining = remainingText.replace(/<[^>]+>/g, ''); + if (cleanRemaining) { + segments.push({ text: cleanRemaining, isLink: false }); + } + } + + // If no links found, return the cleaned HTML + if (segments.length === 0) { + const cleanText = html.replace(/<[^>]+>/g, ''); + return [{ text: cleanText, isLink: false }]; + } + + return segments; +} + +/** + * Render text with clickable links in PDF + * @param {object} doc - jsPDF document + * @param {Array} segments - Array of text segments from parseHtmlForPdfLinks + * @param {number} x - Starting x position + * @param {number} y - Y position + * @param {number} maxWidth - Maximum width for text wrapping + * @returns {number} Height used by the rendered text + */ +function renderTextWithLinks(doc, segments, x, y, maxWidth) { + let currentX = x; + let currentY = y; + const lineHeight = doc.getLineHeight() * 0.352778; // convert pt to mm + let usedHeight = lineHeight; + + for (const segment of segments) { + if (!segment.text) continue; + + // Split text into words for proper wrapping + const words = segment.text.split(/\s+/); + + for (let i = 0; i < words.length; i++) { + const word = words[i]; + const wordWidth = doc.getTextWidth(word + ' '); + + // Check if we need to wrap to next line + if (currentX + wordWidth > x + maxWidth && currentX > x) { + currentX = x; + currentY += lineHeight; + usedHeight += lineHeight; + } + + // Render the word + if (segment.isLink) { + // Make absolute URLs for relative links + let fullUrl = segment.url; + if (fullUrl.startsWith('/')) { + fullUrl = window.location.origin + fullUrl; + } + doc.textWithLink(word, currentX, currentY, { url: fullUrl }); + } else { + doc.text(word, currentX, currentY); + } + + currentX += wordWidth; + + // Add space after word (except for last word) + if (i < words.length - 1) { + const spaceWidth = doc.getTextWidth(' '); + currentX += spaceWidth; + } + } + } + + return usedHeight; +} + +/** + * Convert HTML anchor tags to plain text "label (href)" and strip other HTML tags. + * @param {string} html - HTML string that may contain anchor tags + * @returns {string} Plain text with links converted to "text (href)" format + */ +function htmlAnchorsToPlainText(html) { + if (!html || typeof html !== 'string') return html; + + // Replace anchor tags with 'text (href)' + const anchorRe = /]*href=(?:"|')([^"']*)(?:"|')[^>]*>(.*?)<\/a>/gi; + let out = html.replace(anchorRe, (match, href, text) => { + // Clean up the text content and decode basic HTML entities + const cleanText = text.replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&'); + return `${cleanText} (${href})`; + }); + + // Strip any remaining HTML tags + out = out.replace(/<[^>]+>/g, ''); + + return out; +} + /** * Crops an image to the specified rectangle and returns a PNG data URL. * @@ -142,8 +270,12 @@ async function downloadPdf() { if (data.description) { doc.setFontSize(textSize); doc.setTextColor(60, 60, 60); - doc.text(doc.splitTextToSize(data.description, 180), textSize, y); - y += 10 + 5 * (doc.splitTextToSize(data.description, 180).length - 1); + + // Parse description for links and render with clickable links + const descSegments = parseHtmlForPdfLinks(data.description); + const descHeight = renderTextWithLinks(doc, descSegments, textSize, y, 180); + y += descHeight + 10; + doc.setTextColor(0, 0, 0); } @@ -244,7 +376,13 @@ async function downloadPdf() { for (const [attr, val] of Object.entries(data.mainTable)) { if (val) { const attrLines = doc.splitTextToSize(`${attr}:`, 50 - 2 * cellPadding); - const valLines = doc.splitTextToSize(val, 130 - 2 * cellPadding); + + // Parse value for links + const valSegments = parseHtmlForPdfLinks(String(val)); + + // For now, estimate height using plain text method for layout + const cleanVal = typeof val === 'string' ? htmlAnchorsToPlainText(val) : String(val); + const valLines = doc.splitTextToSize(cleanVal, 130 - 2 * cellPadding); const lineCount = Math.max(attrLines.length, valLines.length); // Calculate height based on font size and line count const lineHeightPt = doc.getLineHeight(); @@ -259,7 +397,10 @@ async function downloadPdf() { doc.setFont(undefined, 'bold'); doc.text(attrLines, 12, y + cellPadding + lineHeight * 0.75, { maxWidth: 50 - 2 * cellPadding }); doc.setFont(undefined, 'normal'); - doc.text(valLines, 62, y + cellPadding + lineHeight * 0.75, { maxWidth: 130 - 2 * cellPadding }); + + // Render value with clickable links + renderTextWithLinks(doc, valSegments, 62, y + cellPadding + lineHeight * 0.75, 130 - 2 * cellPadding); + y += thisRowHeight; } } @@ -306,8 +447,14 @@ async function downloadPdf() { y += rowHeight; for (const row of subsection.rows) { const attrLines = doc.splitTextToSize(`${row.label}:`, 50 - 2 * cellPadding); - const valText = String(row.value).replace(//gi, '\n'); - const valLines = doc.splitTextToSize(valText, 130 - 2 * cellPadding); + + // Convert HTML links and preserve line breaks, then parse for links + const valRaw = String(row.value).replace(//gi, '\n'); + const valSegments = parseHtmlForPdfLinks(valRaw); + + // For layout estimation, use plain text + const cleanVal = htmlAnchorsToPlainText(valRaw); + const valLines = doc.splitTextToSize(cleanVal, 130 - 2 * cellPadding); const lineCount = Math.max(attrLines.length, valLines.length); // Calculate height based on font size and line count const lineHeightPt = doc.getLineHeight(); @@ -335,7 +482,10 @@ async function downloadPdf() { doc.setFont(undefined, 'bold'); doc.text(attrLines, 12, y + cellPadding + lineHeight * 0.75, { maxWidth: 50 - 2 * cellPadding }); doc.setFont(undefined, 'normal'); - doc.text(valLines, 62, y + cellPadding + lineHeight * 0.75, { maxWidth: 130 - 2 * cellPadding }); + + // Render value with clickable links + renderTextWithLinks(doc, valSegments, 62, y + cellPadding + lineHeight * 0.75, 130 - 2 * cellPadding); + y += thisRowHeight; } } From e87340ea49ec3002b882d13039a8f481b7170546 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 15 Sep 2025 13:56:59 +0100 Subject: [PATCH 15/38] Don't use archive.org on history page --- devices/history/index.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/devices/history/index.md b/devices/history/index.md index 23c91aa..ac279ae 100644 --- a/devices/history/index.md +++ b/devices/history/index.md @@ -14,7 +14,7 @@ We have continually iterated on our devices over the years, and have a number of |
![](https://lightbug.io/images/product/lightbug_wired_gps_tracker_angle_hu3852c106dfa3e0439e3eb688c9cfa849_245817_600x600_fit_q100_box_2.png)
| [Vehicle3](/devices/vehicle/)
SKU: VT3
Prefix: 40 | 3rd Generation Wired GPS tracker for vehicles |
Development2023
Release2024
| |
![](https://lightbug.io/images/product/lightbug_environmental_tracker_hu14054740ad5a43a7ce725f924e55a0d7_598053_600x600_fit_q100_box_2.png)
| [EnvrioN](/devices/enviro/) (black)
SKU: EN2
Prefix: 26 | Rugged Tracker with exceptional battery life and built-in sensors
Now with an advanced accelerometer |
Release2021
| |
![](https://docs.lightbug.io/static/2025/history_rthub_front_long_battery_gps_tracker.png)
| [ProN](/devices/pro/)
SKU: PR2
Prefix: 20 | Our most rugged and long-life tracker for harsh environments |
Release2020
| -|
![](https://web.archive.org/web/20200813192447im_/https://thelightbug.com/images/lightbug_front_small_gps_tracker.png)
| [ZeroN](/devices/zero/)
SKU: ZE2
Prefix: 19 | Our smallest core GPS tracking device |
Development2020
Release2020
| +|
![](https://docs.lightbug.io/static/2025/history_lightbug_front_small_gps_tracker_2.png)
| [ZeroN](/devices/zero/)
SKU: ZE2
Prefix: 19 | Our smallest core GPS tracking device |
Development2020
Release2020
| ## Out of Production @@ -22,17 +22,17 @@ We have continually iterated on our devices over the years, and have a number of |-------|---------|------------------|------------| |
![](https://lightbug.io/images/product/lightbug-RH1-front-angle_huaa926e2085c0968ca73e1718b3e5df0c_676321_600x600_fit_q100_box_2.png)
| RtkHandheld
SKU: RH1
Prefix: 21 | Our first high precision RTK handheld tracker |
Since2021
Discontinued2025
| |
![](https://lightbug.io/images/product-front/LB-DEV-EN2_hu840ae98c2675b160231853d09ef00730_192512_150x225_fit_q75_h2_box_2.webp)
| EnviroN (white)
SKU: EN2
Prefix: 26 | Rugged Tracker with exceptional battery life and built-in sensors |
Release2021
Discontinued2025
| -|
![](https://web.archive.org/web/20220123232918im_/https://lightbug.io/images/vehicle_front_hu8c2874194884c59d2a3ac51d539313f6_135807_150x225_fit_q75_h2_box_3.webp)
| VehicleN
SKU: VT2
Prefix: 24 | 2nd Generation Wired GPS tracker for vehicles. See also the [VT2 spec page](./VT2) |
Since2021
Discontinued2024
| -|
![](https://web.archive.org/web/20220123232918im_/https://lightbug.io/images/incognito_front_hu8c2874194884c59d2a3ac51d539313f6_639545_150x225_fit_q75_h2_box_3.webp)
| Incognito
Prefix: 23 | Bike light and Compact GPS tracker for discrete tracking |
Development2021
Discontinued2022
| -| | Rtk BaseStation
SKU: RB1 | Dual RTK base station |
Since2020
Discontinued2024
| -| | RtkHandheld
SKU: RH0 | Prototype RTK tracker developed for Network Rail |
Since2020
Discontinued2021
| -|
![](https://web.archive.org/web/20180818185318im_/https://thelightbug.com/images/lightbug_front_small_gps_tracker.png)
| Zero | Miniature GPS Tracker, The world's smallest 4G GPS tracker |
Since2017
Discontinued2019
| -|
![](https://web.archive.org/web/20180818185318im_/https://thelightbug.com/images/rthub_front_long_battery_gps_tracker.png)
| Pro | Endurance GPS Tracker, Rugged GPS Trackers with exceptional battery life |
Since2017
Discontinued2019
| -|
![](https://web.archive.org/web/20180818185318im_/https://thelightbug.com/images/rthub_front_long_battery_gps_tracker.png)
| Vehicle | Ignition sensing and Bluetooth OBDII support |
Since2017
Discontinued2019
| -| | CattleTag | LoRa and GPS, in an ear tag format. |
Since2019
Discontinued2019
| -| | Hub | Battery powered LoRa Sensor Gateway, Affordable and Versatile LoRa gateway with 4G/2G |
Since2019
Discontinued2019
| -| | Fill | Location Aware Distance Sensor, Compact Level Monitoring with exceptional battery life |
Since?
Discontinued?
| +|
![](https://docs.lightbug.io/static/2025/history_vehicle_front_hu8c2874194884c59d2a3ac51d539313f6_135807_150x225_fit_q75_h2_box_3.webp)
| VehicleN
SKU: VT2
Prefix: 24 | 2nd Generation Wired GPS tracker for vehicles. See also the [VT2 spec page](./VT2) |
Since2021
Discontinued2024
| +|
![](https://docs.lightbug.io/static/2025/history_incognito_front_hu8c2874194884c59d2a3ac51d539313f6_639545_150x225_fit_q75_h2_box_3.webp)
| Incognito
Prefix: 23 | Bike light and Compact GPS tracker for discrete tracking |
Development2021
Discontinued2022
| +|
![](https://upload.wikimedia.org/wikipedia/commons/thumb/3/3f/Placeholder_view_vector.svg/681px-Placeholder_view_vector.svg.png)
| Rtk BaseStation
SKU: RB1 | Dual RTK base station |
Since2020
Discontinued2024
| +|
![](https://upload.wikimedia.org/wikipedia/commons/thumb/3/3f/Placeholder_view_vector.svg/681px-Placeholder_view_vector.svg.png)
| RtkHandheld
SKU: RH0 | Prototype RTK tracker developed for Network Rail |
Since2020
Discontinued2021
| +|
![](https://docs.lightbug.io/static/2025/history_lightbug_front_small_gps_tracker.png)
| Zero | Miniature GPS Tracker, The world's smallest 4G GPS tracker |
Since2017
Discontinued2019
| +|
![](https://docs.lightbug.io/static/2025/history_rthub_front_long_battery_gps_tracker.png)
| Pro | Endurance GPS Tracker, Rugged GPS Trackers with exceptional battery life |
Since2017
Discontinued2019
| +|
![](https://docs.lightbug.io/static/2025/history_rthub_front_long_battery_gps_tracker.png)
| Vehicle | Ignition sensing and Bluetooth OBDII support |
Since2017
Discontinued2019
| +|
![](https://upload.wikimedia.org/wikipedia/commons/thumb/3/3f/Placeholder_view_vector.svg/681px-Placeholder_view_vector.svg.png)
| CattleTag | LoRa and GPS, in an ear tag format. |
Since2019
Discontinued2019
| +|
![](https://upload.wikimedia.org/wikipedia/commons/thumb/3/3f/Placeholder_view_vector.svg/681px-Placeholder_view_vector.svg.png)
| Hub | Battery powered LoRa Sensor Gateway, Affordable and Versatile LoRa gateway with 4G/2G |
Since2019
Discontinued2019
| +|
![](https://upload.wikimedia.org/wikipedia/commons/thumb/3/3f/Placeholder_view_vector.svg/681px-Placeholder_view_vector.svg.png)
| Fill | Location Aware Distance Sensor, Compact Level Monitoring with exceptional battery life |
Since?
Discontinued?
| |
![](https://lightbug.io/raw-renders/misc/lightbug.png)
| LightBug | Tiny GPS Tracker, Powered by sunshine. Smaller than two flat AAs. Probably more useful too. |
Since2015
Discontinued?
| -| | MicroTracker | Original low power GPS 2G tracker, [As featured on the Gadget Show in the UK](https://www.youtube.com/watch?v=dkuY1dUy68o). |
Since2004
Discontinued2012
| +|
![](https://upload.wikimedia.org/wikipedia/commons/thumb/3/3f/Placeholder_view_vector.svg/681px-Placeholder_view_vector.svg.png)
| MicroTracker | Original low power GPS 2G tracker, [As featured on the Gadget Show in the UK](https://www.youtube.com/watch?v=dkuY1dUy68o). |
Since2004
Discontinued2012
| If you have questions about a device that is not listed here, please [contact us](https://lightbug.io/contact/). From e72d7bebb0a09f8441e9ecadd672c9477373ff19 Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 17 Sep 2025 14:07:21 +0100 Subject: [PATCH 16/38] Rework make devices page --- cspell.json | 1 + devices.md | 111 ++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 101 insertions(+), 11 deletions(-) diff --git a/cspell.json b/cspell.json index 44fea76..5ec5433 100644 --- a/cspell.json +++ b/cspell.json @@ -21,6 +21,7 @@ "Toit", "toit", "eink", + "rgba", "geofencelists", "Geofence", "Datapoints", diff --git a/devices.md b/devices.md index dca5c37..dfbdba4 100644 --- a/devices.md +++ b/devices.md @@ -4,22 +4,111 @@ aside: true # Devices -All of our devices are designed and made in the UK from the same modular components. +We make a range of GPS tracking devices for different applications, split into three main categories: Core, RTK, and Custom. + +All of our devices are designed and made in the UK from the same modular components, and are available to purchase via a [sales enquiry](https://lightbug.io/contact/). ## Core -Our core products have been rigorously tested in the field since and iterated on since 2015, are used by customers around the world, and are available to purchase from stock, via a [sales enquiry](https://lightbug.io/contact/) +Our core products have been rigorously tested in the field since and iterated on since 2015, are used by customers around the world. + + + +## RTK + +Our RTK products are designed for high accuracy applications, down to the centimetre, with the latest releases being made in 2025. + + + + -## Other +## Custom We also make [custom devices](/devices/custom), and have a number of devices that are [no longer in production](/devices/history/). From 1cdc8ca0e21ee5a7354a944792c4af5a15e9b2a9 Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 17 Sep 2025 14:35:58 +0100 Subject: [PATCH 17/38] Update device-specs/rtk/v2 from booklet --- public/device-specs/rtk/v2.yaml | 182 ++++++++++++++++++++++++-------- 1 file changed, 138 insertions(+), 44 deletions(-) diff --git a/public/device-specs/rtk/v2.yaml b/public/device-specs/rtk/v2.yaml index c198cc5..237464e 100644 --- a/public/device-specs/rtk/v2.yaml +++ b/public/device-specs/rtk/v2.yaml @@ -12,13 +12,28 @@ product: - "https://lightbug.io/raw-renders/2025-07-01/RH2_w-side.png?x=700&y=70&w=600&h=1000" physical: size: - dimensions_mm: "85 x 44 x 28.4" + dimensions_mm: + - without_antenna: "95 x 72.6 x 30" + - with_antenna: "129.7 x 72.6 x 49.75" weight_g: 155 certifications: - - "CE" + CE: "yes" rating: - ip: "IP66" + ip: "IP67" operating temperature_c: "-20 to +60" + construction: + material: "Polycarbonate-ABS" + properties: + - "Fire-proof" + - "Shock Proof" + - "UV-stable" + connectors: + antenna: "IP67 SMA" + versitile: + - "Power + Data In/Out + Analogue input" + - "Suitable for docking & permanent wiring" + mount: + - "KickFast (wide range of attachments)" integrations: cloud: supported_platforms: @@ -31,42 +46,80 @@ product: - "MQTT" - "HTTP(S)" - "WebSocket" + on_device: + co_processor: "ESP32-C6" + control: "Control of device via APIs" + supported_platforms: + - "Toit (micro-containers)" + - "Arduino" + - "PlatformIO" + - "Espressif IDF" + external_interfaces: + wired_i2c: "Master or Slave" + usb: "Virtual Com Port (USB-C)" connectivity: cellular: technologies: - - "LTE CAT-M1" + - "LTE CAT-1" - "GPRS" - "EDGE" territory: "Global" bands: - - B1 - - B2 - - B3 - - B4 - - B5 - - B8 - - B12 - - B13 - - B18 - - B19 - - B20 - - B26 - - B28 - - B39 - - 850MHz - - 900MHz - - 1800MHz - - 1900MHz + LTE_FDD: + - B1 + - B2 + - B3 + - B4 + - B5 + - B7 + - B8 + - B12 + - B13 + - B18 + - B19 + - B20 + - B25 + - B26 + - B28 + - B66 + LTE_TDD: + - B38 + - B39 + - B40 + - B41 + GSM: + - 850MHz + - 900MHz + - 1800MHz + - 1900MHz + lora: + regions: + - "EU868" + - "US915" + p2p: "yes" + loRaWAN: "capable" bluetooth-le: - technologies: - - "Low Power Broadcasting (iBeacon)" - - "Gateway Scanning" - - "Child (peripheral) device" - territory: "Global" - version: "5.4" + version: "5.3" + roles: + - "Host" + - "Peripheral" + wifi: + version: "802.11ax" + frequency: "2.4GHz" + wifi_generation: "Wi‑Fi 6" + uwb: + channels: + - 5 + - 9 + notes: "Supports common UWB channels; compatible with TDoA/TWR systems (requires infrastructure)" + ieee_802_15_4: yes positioning: gnss: typical_accuracy_meters: "0.01-1" + chipset: "Quectel LC29H (L1/L5)" + chipset_options: + - "AG3335A (L1/L5)" + - "ZEDF9P (L1/L5)" technologies: - "RTK" - "GPS" @@ -94,41 +147,80 @@ product: bands: B1I: 1561.098 B2a: 1176.45 + rtk: + uwb: + typical_accuracy_meters: "0.1-1" + notes: "Typical indoor accuracy; compatible with most UWB TDoA or TWR systems" wifi: - typical_accuracy_meters: 10-30 - technologies: - - "2.4Ghz Wi-Fi AP scanning" + typical_accuracy_meters: "2-20" + notes: "Based on RSSI & triangulation" + bluetooth: + typical_accuracy_meters: "2-20" + notes: "Based on RSSI & triangulation" sensors: accelerometer: typical_accuracy_g: 0.01 + modules: + - "6D Accelerometer + Gyroscope (available to ESP32)" + - "3D Accelerometer (main CPU)" features: - - "3-axis accelerometer" - "Motion Detection" - "Orientation & Tilt detection" - "Shock and Vibration detection" + magnetometer: + description: "3D Magnetometer (Compass)" + pressure: + description: "Barometric pressure sensor" + altitude_resolution_cm: 13 temperature: + description: "Non-calibrated internal temperature sensor" typical_accuracy_c: 5 features: - "Temperature Sensing (low accuracy)" - "Thermal Protection" charging: inputs: - voltage_range_v: "5-14" - current_limit_a: "0.1 to 3.25" + voltage_range_v: "4-14" + current_limit_a: 3.0 features: - fast_charge: "5-14V input with Fast Charge support" + fast_charge: "Configurable up to 3A input" configurable: "Configurable draw and charge rates" + docking: true + usb_c: true + notes: "Charging dock and USB-C available. Automatic power-path management supported." user interface: buttons: "Multi-purpose": + - "x3 Large buttons" - "Status Check" - - "Trigger Update" - - "Software Reset" - - "Hardware Reset" + - "Resets" + - "Menu Navigation" + - "User control" + - "User subscribable via ESP32" leds: - "Status": + "RGB Status": + - "RGB LED x2" + - "Used to display device status (chargins etc.)." + - "Is user controllable" + "RGB Strobe": + - "RGB LED x2" + - "Fully configurable high brightness RGB LED dome" + - "Is user controllable" + "Debug (hidden)": - "Single Color LED x4" - - "Indicates device status (battery level, charging, connected, etc.)" + - "Indicates device status and debug infomation (battery level, charging, connected, etc.)" + - "Hidden behind eink, but visible for maintenance" + haptics: + type: "Vibration Motor" + purpose: "Haptic feedback for user interface" + siren: + decibel: 82 + purpose: "Audible alert for personnel" + waterproof_rating: "IP67" + display: + type: "2.13\" E-Paper" + front_light: "yes" + controllable_via: "ESP32 (user controllable)" components: cellular: manufacturer: "SIMCom Wireless Solutions Limited" @@ -298,16 +390,18 @@ product: operating_temperature_c: "-40 to 85" battery: overall: - capacity_mah: 6600 + capacity_mah: 6700 pack_configuration: "1S2P" - voltage_v: 3.7 + voltage_v: 3.6 + continuous_use_hours: ">50" + operating_temperature_c: "-10 to +60 (standard); -40 to +60 (optional)" cells: type: 18650 spec: model: "18650 3300mAh" type: "Li-ion Cell" - nominal_voltage_v: 3.7 - nominal_capacity_mah: 3300 + nominal_voltage_v: 3.6 + nominal_capacity_mah: 3350 nominal_energy_wh: 12.21 physical_dimensions_mm: "Φ 65.0*18.0" appearance: "Blue cylindrical battery" From cf33e8e7abe67631fc0df77e7782c5d059cbe94d Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 17 Sep 2025 16:42:04 +0100 Subject: [PATCH 18/38] Add RH2 sub pages --- .vitepress/config.mts | 12 +++- components/DeviceSpecTable.vue | 1 - cspell.json | 3 + devices/api/sdks/toit/getting-started.md | 5 ++ devices/api/sdks/toit/index.md | 8 +++ devices/rtk/handheld.md | 10 ---- devices/rtk/handheld/accessories.md | 27 +++++++++ devices/rtk/handheld/esp32.md | 7 +++ devices/rtk/handheld/external.md | 57 ++++++++++++++++++ devices/rtk/handheld/index.md | 10 ++++ devices/rtk/handheld/screen.md | 76 ++++++++++++++++++++++++ 11 files changed, 204 insertions(+), 12 deletions(-) delete mode 100644 devices/rtk/handheld.md create mode 100644 devices/rtk/handheld/accessories.md create mode 100644 devices/rtk/handheld/esp32.md create mode 100644 devices/rtk/handheld/external.md create mode 100644 devices/rtk/handheld/index.md create mode 100644 devices/rtk/handheld/screen.md diff --git a/.vitepress/config.mts b/.vitepress/config.mts index 0544b70..d32ec05 100644 --- a/.vitepress/config.mts +++ b/.vitepress/config.mts @@ -316,7 +316,17 @@ export default withMermaid(defineConfig({ text: 'RTK', link: '/devices/rtk/', items: [ - { text: 'Handheld', link: '/devices/rtk/handheld' }, + { + text: 'Handheld', + link: '/devices/rtk/handheld/', + collapsed: true, + items: [ + { text: 'External', link: '/devices/rtk/handheld/external' }, + { text: 'Screen', link: '/devices/rtk/handheld/screen' }, + { text: 'ESP32', link: '/devices/rtk/handheld/esp32' }, + { text: 'Accessories', link: '/devices/rtk/handheld/accessories' }, + ] + }, { text: 'Vehicle', link: '/devices/rtk/vehicle' }, ] }, diff --git a/components/DeviceSpecTable.vue b/components/DeviceSpecTable.vue index 0de455e..82d77e1 100644 --- a/components/DeviceSpecTable.vue +++ b/components/DeviceSpecTable.vue @@ -13,7 +13,6 @@ -

Specification

Overview

{{ specs.product.description }} diff --git a/cspell.json b/cspell.json index 5ec5433..f19c6fa 100644 --- a/cspell.json +++ b/cspell.json @@ -73,6 +73,9 @@ "VTRTK", "pseudorange", "Centimetre", + "Klick", + "Espressif", + "microcontrollers", "wght", "integ" ], diff --git a/devices/api/sdks/toit/getting-started.md b/devices/api/sdks/toit/getting-started.md index 5402097..dbf64e4 100644 --- a/devices/api/sdks/toit/getting-started.md +++ b/devices/api/sdks/toit/getting-started.md @@ -128,7 +128,12 @@ Once the code is running, you should see some output through the `monitor` comma And you should see the device screen update, saying `Lightbug...` in the top left. +:::tabs +== Graphic ![](https://i.imgur.com/8QP1022.png) +== Photo +![](https://i.imgur.com/7ca0Nda.png){width=500} +::: ### 7. Inspect the code diff --git a/devices/api/sdks/toit/index.md b/devices/api/sdks/toit/index.md index be0bedb..6541a9f 100644 --- a/devices/api/sdks/toit/index.md +++ b/devices/api/sdks/toit/index.md @@ -9,3 +9,11 @@ Lightbug provides a [Toit package](https://pkg.toit.io/package/github.com%2Fligh Together, these tools allow you to quickly develop and deploy applications on Lightbug devices using Toit. Once developed, Toit firmware can be build without the jaguar development tooling enabled, and sent to Lightbug devices over the air for update. + +:::tip + +Access to the device API is not limited to Toit, however it is the current preferred and documented high-level language for Lightbug devices. + +Devices with an ESP32 can also be programmed using C/C++ with the ESP-IDF framework, or anything else that is supported on the ESP32. For such solutions, you'll need to make use of the lower level [messages](./../../messages/), rather than a higher level abstraction like the Toit package. + +::: diff --git a/devices/rtk/handheld.md b/devices/rtk/handheld.md deleted file mode 100644 index d76a070..0000000 --- a/devices/rtk/handheld.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -aside: false ---- - - - - diff --git a/devices/rtk/handheld/accessories.md b/devices/rtk/handheld/accessories.md new file mode 100644 index 0000000..225ca27 --- /dev/null +++ b/devices/rtk/handheld/accessories.md @@ -0,0 +1,27 @@ +# Accessories + +A range of accessories are available for the RH2, including cases, connectors, docking stations and carrying solutions. + +## Cases + +Individual pelican cases are available for the RH2. More will follow. + +## Connectors + +The RH2 features a versatile connector at the base of the device, suitable for docking & permanent wiring. + +A USB-C to versatile connector is available, allowing easy charging and data connection via USB (for development). + +## Docking + +A docking station is available for the RH2, allowing easy charging and data connection via USB (for development). + +## KickFast + +KlickFast is the market-leading carrying solution (See [https://www.peterjonesilg.co.uk/product-category/klick-fast/](https://www.peterjonesilg.co.uk/product-category/klick-fast/)). + +Many accessories are available, including: + - Belt clips + - Bag clips + - Tripod mounts + - Pole mounts diff --git a/devices/rtk/handheld/esp32.md b/devices/rtk/handheld/esp32.md new file mode 100644 index 0000000..cfcaea2 --- /dev/null +++ b/devices/rtk/handheld/esp32.md @@ -0,0 +1,7 @@ +# ESP32 + +The RH2 features a second co-processor, an [ESP32-C6](https://www.espressif.com/en/products/socs/esp32-c6) from Espressif. + +Out of the box, this processor simply provides the WiFi and Bluetooth connectivity of the RH2. + +However custom applications can be developed for the ESP32, making use of the [high level Toit SDK](/devices/api/sdks/toit/) or [lower level messaging APIs](/devices/api/messages) via anything else that can be flashed onto the ESP32. diff --git a/devices/rtk/handheld/external.md b/devices/rtk/handheld/external.md new file mode 100644 index 0000000..352f879 --- /dev/null +++ b/devices/rtk/handheld/external.md @@ -0,0 +1,57 @@ +# External + +IP67 rated rugged enclosure, made out of Polycarbonate-ABS. + +Dimensions: + - With the Antenna: 129.7 x 72.6 x 49.75 mm + - Without the Antenna: 95 x 72.6 x 30 mm + +## Front + +:::tabs +== Diagram +![Front view of the RH2 including dimensions, with and without the antenna](https://i.imgur.com/bzeb1TX.png) +== Render +![](https://lightbug.io/raw-renders/2025-07-01/RH2_w-front+angle.png) +::: + +[2.13" monochrome e-ink display with a resolution of 250x122 pixels](screen), which is visible in direct sunlight and has a very low power consumption. + +IP67 SMA Connectors for external GNSS antennas (antenna seen is included as standard). + +3 large buttons for easy operation with gloves and harsh environments. + +The device features two difference sets of RGB LEDs: + - Status LED (corners): for indicating device status, charging etc. + - Controllable LED (center, above the screen): for user control.. + +## Side + +:::tabs +== Diagram +![Side view of the RH2 including dimensions, and antenna details](https://i.imgur.com/YHljkZW.png) +== Render +![](https://lightbug.io/raw-renders/2025-07-01/RH2_w-side.png) +::: + +## Back + +:::tabs +== Diagram +![Back view of the RH2, including details of the siren, mount and versatile connector](https://i.imgur.com/0bJYs3Y.png) +== Render +![](https://lightbug.io/raw-renders/2025-07-01/RH2_w-back.png) +::: + +An 82dB IP67 rated siren for audible alerts. + +KickFast mount for easy attachment to belts, bags, tripods, poles, etc. + +Versatile connector for charging, data, analog I/O, and more, suitable for docking & permanent wiring. Corrosion resistant gold plated beryllium copper pins. + +## Top and Bottom + +:::tabs +== Diagram +![Top and bottom view of the RH2](https://i.imgur.com/acrCaFV.png) +::: diff --git a/devices/rtk/handheld/index.md b/devices/rtk/handheld/index.md new file mode 100644 index 0000000..ffc00b3 --- /dev/null +++ b/devices/rtk/handheld/index.md @@ -0,0 +1,10 @@ +--- +aside: false +--- + + + + diff --git a/devices/rtk/handheld/screen.md b/devices/rtk/handheld/screen.md new file mode 100644 index 0000000..b05c0a0 --- /dev/null +++ b/devices/rtk/handheld/screen.md @@ -0,0 +1,76 @@ +# Screen + +The RH2 features a 2.13" monochrome e-ink display with a resolution of 250x122 pixels, which is visible in direct sunlight and has a very low power consumption. + +The default firmware of the RH2 has a simple user interface that makes use of the screen to display information such as GNSS status, battery level, and allow basic control of the device. + +The screen can also be used by the ESP32 microcontroller as part of a custom application, for example to display sensor data, custom menus, alerts, messaging and more. + +## Default screens + +The default firmware has a single main [home](#home) screen, with a sub [menu](#menu) screen. + +### Home + +The home screen shows basic device status, and basic device control. + +The status bar at the top shows: + - SIM status + - GSM status and signal strength + - Battery level + - Charge state + +#### Disarmed + +A device would normally start in a disarmed state. + +In this state the device is not attempting to get a GNSS fix or report its location, or connect to GSM. + +:::tabs +== Visualization +![](https://i.imgur.com/lGxzg9U.png) +== Wide Photo +![](https://i.imgur.com/dF5X5Qn.png) +== Zoom Photo +![](https://i.imgur.com/42e4q6P.jpeg) +::: + +The center button can be used to arm the device (See [armed](#armed) below). + +Or the menu button can be used to access the [menu](#menu) below. + +The actions selection currently does nothing, and is intended for future use. + +#### Armed + +When armed the device will find it location using GNSS, and report it to the Lightbug Cloud, then streaming RTK corrections if available for the device. + +Information above the status of this process will be shown on the screen. + +:::tabs +== Visualization +![](https://i.imgur.com/C05HoGx.png) +== Wide Photo +![](https://i.imgur.com/gnhrnfH.png) +== Zoom Photo +![](https://i.imgur.com/1hVj0c0.jpeg) +::: + + +### Menu + +The menu allows selection of: + - Base station mode (allowing the device to work as an RTK base station) + - 1Hz RTK tracking + - 2Hz RTK tracking + - 5Hz RTK tracking + - Displays the Device ID + +:::tabs +== Visualization +![](https://i.imgur.com/pZ22H5j.png) +== Wide Photo +![](https://i.imgur.com/D0PxTPE.png) +== Zoom Photo +![](https://i.imgur.com/a0mwlDn.jpeg) +::: From d4a1663eb2f3460455353b9324d0bf15f9b0a4d2 Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 17 Sep 2025 17:38:04 +0100 Subject: [PATCH 19/38] menu and link tweaks around sdk and rtk --- .vitepress/config.mts | 3 +-- devices/api/sdks/toit/index.md | 27 +++++++++++++++++++++++++++ devices/rtk/handheld/screen.md | 11 ++++++++++- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/.vitepress/config.mts b/.vitepress/config.mts index d32ec05..a0b1876 100644 --- a/.vitepress/config.mts +++ b/.vitepress/config.mts @@ -314,7 +314,6 @@ export default withMermaid(defineConfig({ }, { text: 'RTK', - link: '/devices/rtk/', items: [ { text: 'Handheld', @@ -340,7 +339,7 @@ export default withMermaid(defineConfig({ }, { text: 'Lineage', - link: '/devices/history', + link: '/devices/history/', collapsed: true, items: [ { text: 'VT2', link: '/devices/history/VT2' }, diff --git a/devices/api/sdks/toit/index.md b/devices/api/sdks/toit/index.md index 6541a9f..da787fd 100644 --- a/devices/api/sdks/toit/index.md +++ b/devices/api/sdks/toit/index.md @@ -17,3 +17,30 @@ Access to the device API is not limited to Toit, however it is the current prefe Devices with an ESP32 can also be programmed using C/C++ with the ESP-IDF framework, or anything else that is supported on the ESP32. For such solutions, you'll need to make use of the lower level [messages](./../../messages/), rather than a higher level abstraction like the Toit package. ::: + +Interacting with the device using the Toit SDK can be as simple as this to draw some text on the screen: + +```toit +import lightbug.devices as devices +import lightbug.messages.messages_gen as messages + +main: + device := devices.I2C + device.eink.draw-element --status-bar-enable=false --type=messages.DrawElement.TYPE_BOX --x=0 --y=0 --text="Lightbug..." + while true: + sleep --ms=1000 +``` + +Or this to subscribe to button press events: + +```toit +import lightbug.devices as devices + +main: + device := devices.I2C + device.buttons.subscribe --callback=(:: |button-data| + print "Button pressed: (ID: $button-data.button-id)" + ) + while true: + sleep --ms=1000 +``` diff --git a/devices/rtk/handheld/screen.md b/devices/rtk/handheld/screen.md index b05c0a0..3a27ddb 100644 --- a/devices/rtk/handheld/screen.md +++ b/devices/rtk/handheld/screen.md @@ -1,3 +1,8 @@ +--- +aside: true +outline: deep +--- + # Screen The RH2 features a 2.13" monochrome e-ink display with a resolution of 250x122 pixels, which is visible in direct sunlight and has a very low power consumption. @@ -6,7 +11,7 @@ The default firmware of the RH2 has a simple user interface that makes use of th The screen can also be used by the ESP32 microcontroller as part of a custom application, for example to display sensor data, custom menus, alerts, messaging and more. -## Default screens +## Defaults The default firmware has a single main [home](#home) screen, with a sub [menu](#menu) screen. @@ -74,3 +79,7 @@ The menu allows selection of: == Zoom Photo ![](https://i.imgur.com/a0mwlDn.jpeg) ::: + +## Custom + +Custom applications running on the [ESP32](esp32) can make use of [Toit SDK](/devices/api/sdks/toit/) and or [device API](/devices/api/) to draw custom screens. From 3e0cc4199c6a870589fdd3da5b688076e32f22ae Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 17 Sep 2025 18:07:32 +0100 Subject: [PATCH 20/38] shiki add toit language highlighting --- .vitepress/config.mts | 16 + .vitepress/languages/toit.tmLanguage.json | 855 ++++++++++++++++++++++ cspell.json | 1 + package-lock.json | 158 +++- package.json | 1 + 5 files changed, 1012 insertions(+), 19 deletions(-) create mode 100644 .vitepress/languages/toit.tmLanguage.json diff --git a/.vitepress/config.mts b/.vitepress/config.mts index a0b1876..4d9ce5a 100644 --- a/.vitepress/config.mts +++ b/.vitepress/config.mts @@ -157,6 +157,22 @@ export default withMermaid(defineConfig({ config: (md) => { md.use(tabsMarkdownPlugin) }, + languages: (() => { + try { + const toitGrammar = JSON.parse(fs.readFileSync(path.resolve(__dirname, 'languages/toit.tmLanguage.json'), 'utf8')); + + const toitLang = { + ...toitGrammar, + id: 'toit', + aliases: ['toit'] + }; + + return [toitLang]; + } catch (error) { + console.error('Error loading Toit grammar:', error); + return []; + } + })(), container: { tipLabel: '⚡ Tip', warningLabel: '⚠️ Warning', diff --git a/.vitepress/languages/toit.tmLanguage.json b/.vitepress/languages/toit.tmLanguage.json new file mode 100644 index 0000000..31691e9 --- /dev/null +++ b/.vitepress/languages/toit.tmLanguage.json @@ -0,0 +1,855 @@ +{ + "comment": "Don't edit the JSON file, but edit the YAML file instead. Use npx js-yaml < syntaxes/toit.tmLanguage.yaml > syntaxes/toit.tmLanguage.json Use the scope inspector from the command palette with the Developer: Inspect Editor Tokens and Scopes\n", + "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", + "name": "Toit", + "firstLineMatch": "^#!/.*\\btoit([.]run)?$", + "patterns": [ + { + "include": "#comment" + }, + { + "include": "#import-section" + }, + { + "include": "#export-section" + }, + { + "include": "#class-section" + }, + { + "include": "#toplevel-section" + } + ], + "repository": { + "import-section": { + "name": "meta.import.toit", + "begin": "^import(?!-)\\b", + "end": "^(?!\\s)", + "beginCaptures": { + "0": { + "name": "constant.language.import-export-all.import.toit" + } + }, + "patterns": [ + { + "include": "#comment" + }, + { + "name": "constant.language.import-export-all.show.toit", + "match": "\\b(?>>|>>|<=|>=|<|>|\\+|-|\\*|/|%|\\^|&|\\||\\[\\]\\=|\\[\\]|\\[\\.\\.\\]))", + "end": "(?=\\:(?![\\p{L}_])|^\\s{0,4}(?:[^\\s/]|/[^/*]))", + "beginCaptures": { + "1": { + "name": "keyword.control.toit" + }, + "2": { + "name": "storage.type.function" + }, + "3": { + "name": "storage.type.function" + } + }, + "patterns": [ + { + "include": "#signature-part2" + } + ] + }, + { + "include": "#type-annotation" + }, + { + "name": "meta.member.body", + "begin": "(:=|::=)|(:)|^\\s{4}(?=[^\\s/])", + "end": "(?=^\\s{0,2}([^\\s/]|/[^/*]))", + "beginCaptures": { + "1": { + "name": "keyword.control.toit" + } + }, + "patterns": [ + { + "include": "#expressions" + } + ] + } + ] + }, + "signature-part2": { + "name": "meta.member.signature.part2.toit", + "patterns": [ + { + "include": "#type-annotation" + }, + { + "include": "#comment" + }, + { + "name": "meta.parameter.setting.toit", + "match": "(--)?(this)?\\.([\\p{L}_][\\w-]*)", + "captures": { + "1": { + "name": "variable.parameter.named.setting.toit" + }, + "2": { + "patterns": [ + { + "include": "#special-variable" + } + ] + }, + "3": { + "name": "variable.parameter.named.setting.toit", + "patterns": [ + { + "include": "#invalid_non_expression" + } + ] + } + } + }, + { + "name": "variable.parameter.toit", + "match": "(--)?(:)?([\\p{L}_][\\w-]*)", + "captures": { + "2": { + "name": "keyword.control.block_marker.toit" + }, + "3": { + "patterns": [ + { + "include": "#invalid_non_expression" + } + ] + } + } + }, + { + "name": "meta.parameter.default_value.toit", + "begin": "(=)\\s*", + "end": "^|\\s|:", + "beginCaptures": { + "1": { + "name": "keyword.control.toit" + } + }, + "patterns": [ + { + "include": "#comment" + }, + { + "include": "#character" + }, + { + "include": "#number" + }, + { + "include": "#constant" + }, + { + "include": "#string" + }, + { + "include": "#type-name" + }, + { + "include": "#delimited" + }, + { + "include": "#keyword" + } + ] + } + ] + }, + "type-annotation": { + "patterns": [ + { + "name": "entity.name.type.annotation.toit", + "begin": "(/|->) *(?=[\\p{L}_])", + "end": "(?=[^\\w\\-.?])", + "beginCaptures": { + "1": { + "name": "keyword.control.return_type.toit" + } + } + } + ] + }, + "keyword": { + "patterns": [ + { + "name": "keyword.control.toit", + "match": "\\b(?>>=|>>=|//=|\\+=|-=|/=|\\*=|%=|~=|\\^=|&=)" + } + ] + }, + "operator": { + "patterns": [ + { + "name": "keyword.control.toit", + "match": "(!=|==|>=|<=|<|>|\\*|\\+|-|%|//|/|<<|>>>|>>|&|\\||\\^|~|[.][.])" + } + ] + }, + "control": { + "patterns": [ + { + "name": "keyword.control.toit", + "match": "(:|\\?|;)" + } + ] + }, + "constant": { + "patterns": [ + { + "name": "constant.language.toit", + "match": "\\b(? Date: Wed, 17 Sep 2025 19:12:26 +0100 Subject: [PATCH 21/38] markdown pluginx, mainly for images, css, spelling etc --- .vitepress/config.mts | 8 ++ .vitepress/theme/custom.css | 27 +++++ apps/cloud/creating-account.md | 55 ++++++----- apps/cloud/map/controls.md | 6 +- apps/cloud/map/device-list.md | 24 ++--- apps/cloud/map/device-view.md | 24 ++--- cspell.json | 85 ---------------- cspell.yaml | 109 +++++++++++++++++++++ devices/history/index.md | 2 +- package-lock.json | 174 +++++++++++++++++++++++++++++++++ package.json | 5 + 11 files changed, 379 insertions(+), 140 deletions(-) delete mode 100644 cspell.json create mode 100644 cspell.yaml diff --git a/.vitepress/config.mts b/.vitepress/config.mts index 4d9ce5a..6191582 100644 --- a/.vitepress/config.mts +++ b/.vitepress/config.mts @@ -7,6 +7,10 @@ import { loadSpec } from '../swagger/load'; import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs'; import { pagefindPlugin } from 'vitepress-plugin-pagefind'; import { withMermaid } from "vitepress-plugin-mermaid"; +import { imgSize } from "@mdit/plugin-img-size"; +import { figure } from "@mdit/plugin-figure"; +import { attrs } from "@mdit/plugin-attrs"; +import { align } from "@mdit/plugin-align"; import { withSidebar, generateSidebar } from 'vitepress-sidebar'; @@ -156,6 +160,10 @@ export default withMermaid(defineConfig({ markdown: { config: (md) => { md.use(tabsMarkdownPlugin) + md.use(imgSize) + md.use(figure) + md.use(attrs) + md.use(align) }, languages: (() => { try { diff --git a/.vitepress/theme/custom.css b/.vitepress/theme/custom.css index 159decf..e0b6542 100644 --- a/.vitepress/theme/custom.css +++ b/.vitepress/theme/custom.css @@ -19,3 +19,30 @@ .VPSidebarItem.level-0 { padding-bottom: 10px !important; } + +/* A generic class to center things, mainly images */ +.center { + display: block; + margin-left: auto; + margin-right: auto; + text-align: center; +} +figure:has(> img.center) { + text-align: center; +} + +/* ----------------------------------------------- */ +/* Custom default tab styling... */ +/* ----------------------------------------------- */ + +/* +Custom TAB styling, when the tab is called "Mobile" +Screenshots of mobile devices are not that wide +So we want to limit their height (as they are tall), and center them +*/ +[id^="panel-Mobile-"].plugin-tabs--content[role="tabpanel"] img { + display: block; + margin-left: auto; + margin-right: auto; + max-height: 354.719px; /* Based on desktop image that was 1424x770 */ +} diff --git a/apps/cloud/creating-account.md b/apps/cloud/creating-account.md index facada9..9a02bc7 100644 --- a/apps/cloud/creating-account.md +++ b/apps/cloud/creating-account.md @@ -10,14 +10,14 @@ First, [navigate to the login page](https://lightbug.cloud/#/auth/login). :::tabs == Desktop - +![](https://i.imgur.com/h3AUXJJ.png) == Mobile - +![](https://i.imgur.com/o9kao3O.png) ::: Click `SIGN UP`. - +![](https://i.imgur.com/8OL5n0e.png){.center} And the registration form will appear. @@ -25,9 +25,9 @@ Enter your details in the registration form. :::tabs == Desktop - +![](https://i.imgur.com/srFc6e8.png) == Mobile - +![](https://i.imgur.com/ECuZlD9.png) ::: And click `REGISTER`. @@ -44,9 +44,9 @@ It will guide you through the steps of adding your first device to your account. :::tabs == Desktop - +![](https://i.imgur.com/3iaxLSz.png) == Mobile - +![](https://i.imgur.com/omtoo0k.png) ::: Swipe the screen, or click the `NEXT` button to proceed through the onboarding process. @@ -55,9 +55,9 @@ The first screen tells you some useful initial information about a core device. :::tabs == Desktop - +![](https://i.imgur.com/FSWJ39v.png) == Mobile - +![](https://i.imgur.com/pyLGodN.png) ::: Swipe the screen, or click the `NEXT` button to proceed through the onboarding process. @@ -66,9 +66,9 @@ The second screen allows you to add your first device. :::tabs == Desktop - +![](https://i.imgur.com/X1CFB33.png) == Mobile - +![](https://i.imgur.com/funMDW2.png) ::: Enter the serial number of your device into the input field, and click `REGISTER`. @@ -81,9 +81,9 @@ You will then be asked to select a currency for your account, before you can cho :::tabs == Desktop - +![](https://i.imgur.com/ZNdAxeT.png) == Mobile - +![](https://i.imgur.com/JQbqZTl.png) ::: After selecting a currency, you can choose a plan for your device. @@ -92,33 +92,34 @@ You can find details of each plan, and also buy them in bulk via a [sales enquir :::tabs == Desktop - +![](https://i.imgur.com/wpopzKd.png) == Mobile - +![](https://i.imgur.com/ElW4Bok.png) ::: If your account already has purchased plans assigned to it, you'll be able to select one of those plans. They will be displayed in this list, showing how many months have been paid for. - + +![=450x](https://i.imgur.com/qSNZwZW.png){.center} After selecting a plan, if payment details are not yet attached to your account, you will be asked to enter them. :::tabs == Desktop - +![](https://i.imgur.com/LZku5SU.png) == Mobile - +![](https://i.imgur.com/H9Td6jM.png) ::: Successful payment and plan activation will take you to the last page of the onboarding process. :::tabs == Desktop - +![](https://i.imgur.com/byxRofc.png) == Mobile - +![](https://i.imgur.com/S8BKfUl.png) ::: Follow the instructions on the screen to complete the onboarding process and activate your device. @@ -129,31 +130,31 @@ Once a device has been added to the account, you'll be taken to the main interfa ### Device & Search - +![](https://i.imgur.com/B7g28C5.png) ### Device Information - +![](https://i.imgur.com/B33edbG.png) ### History List - +![](https://i.imgur.com/CmKEheK.png) ### Date selection - +![](https://i.imgur.com/nYaTX1q.png) ### Data & Playback - +![](https://i.imgur.com/K6FwXmo.png) ### Page select - +![](https://i.imgur.com/58mlwp9.png) ### Ready to go - +![](https://i.imgur.com/7WRMdfm.png) ## Map diff --git a/apps/cloud/map/controls.md b/apps/cloud/map/controls.md index b677007..7d99f45 100644 --- a/apps/cloud/map/controls.md +++ b/apps/cloud/map/controls.md @@ -8,7 +8,7 @@ outline: [1,3] To change the Map view settings, click on the layer icon in the top right corner. -![Map View Layer button](https://i.imgur.com/L49Owzb.png) +![Map View Layer button](https://i.imgur.com/L49Owzb.png){.center} This will open a panel where you can change the map view settings. @@ -26,7 +26,7 @@ The two sections allow choice of tile set and layers to be displayed. | Tile set| Description | | --- | --- | | Street | Mapbox provided OSM tiles | -| Satellite | Mapbox provided satellite tiles (maxar) | +| Satellite | Mapbox provided satellite tiles (Maxar) | | Hybrid | A combination of Street and Satellite tiles | | Traffic | Street tiles with traffic information | @@ -56,7 +56,7 @@ Devices are always shown on the map, but you can choose how they are displayed, | Clustered | Not Clustered | | --------------------------------------------- | ------------------------------------------------- | -| ![Clustered](https://i.imgur.com/N7tKJIC.png) | ![Not Clustered](https://i.imgur.com/oo3aNqQ.png) | +| ![Clustered](https://i.imgur.com/N7tKJIC.png){.center} | ![Not Clustered](https://i.imgur.com/oo3aNqQ.png){.center} | | Clusters show the number of devices within an area | Each device is shown individually on the map, [optionally with the name](/apps/cloud/account/preferences) | Clicking on a cluster will zoom in to show the individual devices, or smaller clusters. diff --git a/apps/cloud/map/device-list.md b/apps/cloud/map/device-list.md index a9cf023..653f311 100644 --- a/apps/cloud/map/device-list.md +++ b/apps/cloud/map/device-list.md @@ -8,15 +8,15 @@ The device list provides an overview of all the devices on your account. In order to see the device list, you need to expand the sidebar by using the arrow. -![](https://i.imgur.com/2umqTRF.png) +![](https://i.imgur.com/2umqTRF.png){.center} The List will expand, and you will see all the devices on your account that have been active in the last 90 days including various information about each device. :::tabs == Desktop - +![](https://i.imgur.com/bv8vapO.png) == Mobile - +![](https://i.imgur.com/Ei8Nqvk.png) ::: ::: warning @@ -42,14 +42,14 @@ Here are some examples of what the device rows can look like: | Image | Description | | --- | --- | -| | An active device, that has just transmitted data, and was charging on last transmit | -| | An active device, that transmitted data 3 minutes ago, and was charging on last transmit -| | An active device, that has not connected since 29 August 2024, and had very little battery on last transmitted | -| | A deactivated device, that last reported being on 72% battery | -| | A deactivated device, that last reported being on 74% battery, and in the "Bristol area" zone. | -| | A deactivated device, that last reported being on 100% battery, and in the "Bristol area" zone, and has a [tag](/apps/cloud/device-settings/tags.html) | -| | When using wired trackers, Battery % will not be shown, Powered status (ignition) will be shown | -| | An active device, that has just transmitted data, and is in battery saver mode at 13% | -| | A bluetooth beacon that has been added to the account | +| ![](https://i.imgur.com/6iKfpTu.png){.center} | An active device, that has just transmitted data, and was charging on last transmit | +| ![](https://i.imgur.com/yeynpZl.png){.center} | An active device, that transmitted data 3 minutes ago, and was charging on last transmit +| ![](https://i.imgur.com/0Ws7yNs.png){.center} | An active device, that has not connected since 29 August 2024, and had very little battery on last transmitted | +| ![](https://i.imgur.com/ZYZseSF.png){.center} | A deactivated device, that last reported being on 72% battery | +| ![](https://i.imgur.com/MrymS6o.png){.center} | A deactivated device, that last reported being on 74% battery, and in the "Bristol area" zone. | +| ![](https://i.imgur.com/KND4U7x.png){.center} | A deactivated device, that last reported being on 100% battery, and in the "Bristol area" zone, and has a [tag](/apps/cloud/device-settings/tags.html) | +| ![](https://i.imgur.com/eVBH9OQ.png){.center} | When using wired trackers, Battery % will not be shown, Powered status (ignition) will be shown | +| ![](https://i.imgur.com/MEhzqHR.png){.center} | An active device, that has just transmitted data, and is in battery saver mode at 13% | +| ![](https://i.imgur.com/u3glJ6q.png){.center} | A bluetooth beacon that has been added to the account | Clicking on a device row, will focus the map on that device, and open the device information panel. diff --git a/apps/cloud/map/device-view.md b/apps/cloud/map/device-view.md index 5453f4c..de06c95 100644 --- a/apps/cloud/map/device-view.md +++ b/apps/cloud/map/device-view.md @@ -6,16 +6,16 @@ The device view focuses in on a single device point, and shows detailed informat :::tabs == Desktop - +![](https://i.imgur.com/sIVl24t.png) == Mobile - +![](https://i.imgur.com/xJ3sqRC.png) ::: ## Details In the top left of the device view, you will see the device name, color, as well as information about the last transmission. -![](https://i.imgur.com/rSNOeNC.png) +![](https://i.imgur.com/rSNOeNC.png){.center} @@ -25,7 +25,7 @@ Clicking the dropdown will open the [History list](#history-list) for the device The time range that is currently being used is displayed at the bottom of the map. -![](https://i.imgur.com/5BTlmMJ.png) +![](https://i.imgur.com/5BTlmMJ.png){.center} By default, the map view time range selection is set to midnight yesterday, to the current time today. @@ -35,9 +35,9 @@ Clicking on the time range selector will open a panel where you can change the t :::tabs == Desktop - +![](https://i.imgur.com/NMijD9C.png) == Mobile - +![](https://i.imgur.com/ku85nHB.png) ::: The time range selector allows you to choose a custom time range, or one of the predefined options. @@ -52,13 +52,13 @@ The time range selector can only be used to show at most 90 days of data at once Next to the time range selector, you will see a playback button. -![](https://i.imgur.com/5BTlmMJ.png) +![](https://i.imgur.com/5BTlmMJ.png){.center} Clicking this will open the playback panel at the bottom of the page. Mousing over the graph will displays data names and values. -![](https://i.imgur.com/EEb61bj.png) +![](https://i.imgur.com/EEb61bj.png){.center} :::tip This is best viewed on a desktop, as the playback panel is quite large. @@ -72,7 +72,7 @@ Within the selected time range, you can use the playback controls (direction key You can also choose to drag the playback slider to a specific point in time. -![](https://i.imgur.com/yzEEsvE.png) +![](https://i.imgur.com/yzEEsvE.png){.center} ### Options {#data-playback-options} @@ -98,9 +98,9 @@ The history list can be accessed by clicking the dropdown in the top left of the :::tabs == Desktop - +![](https://i.imgur.com/vjZFWL4.png) == Mobile - +![](https://i.imgur.com/vnZ9r0p.png) ::: The list shows all the locations transmitted by the in the given [time range](#time-range). @@ -126,7 +126,7 @@ General control Some dots represent device seen. The number on each of the dot represents the number of devices seen throughout the time frame. - Blue: BLE Devices (Paired or unpaired) -- Orange: Other nearby Lightbugs +- Orange: Other nearby Lightbug devices Any events that have occurred will be shown on the map as a dot. diff --git a/cspell.json b/cspell.json deleted file mode 100644 index 592d8bc..0000000 --- a/cspell.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "version": "0.2", - "ignorePaths": [ - "cSpell_dict.txt", - "package-lock.json", - "package.json", - "public/device-specs/**/*.yaml", - "public/swagger/v1.json", - "public/files/protocol-v3.yaml", - ".vitepress/languages/*", - ".vitepress/theme/index.ts" - ], - "dictionaryDefinitions": [], - "dictionaries": [], - "words": [ - "Lightbug", - "Geofences", - "onprem", - "vuetify", - "Enviro", - "enviro", - "Toit", - "toit", - "eink", - "rgba", - "geofencelists", - "Geofence", - "Datapoints", - "datapoints", - "sendreason", - "alerttype", - "behaviour", - "modecontrol", - "Envrio", - "OBDII", - "Quadband", - "GLONASS", - "BEIDOU", - "QZSS", - "overvoltage", - "Julet", - "xmodem", - "msgb", - "Dont", - "csum", - "Csum", - "Bitfield", - "bitfield", - "bitfields", - "optimisation", - "Optimisations", - "RTCM", - "initialised", - "EEPROM", - "trycloudflare", - "multipath", - "iccid", - "imei", - "NTRIP", - "microcontroller", - "UART", - "HPSYS", - "SPIWP", - "acking", - "RSSI", - "actioned", - "centi", - "DGNSS", - "NMEA", - "WCDMA", - "GPRS", - "hectopascals", - "codegen", - "VTRTK", - "pseudorange", - "Centimetre", - "Klick", - "Espressif", - "microcontrollers", - "wght", - "integ" - ], - "ignoreWords": [], - "import": [] -} diff --git a/cspell.yaml b/cspell.yaml new file mode 100644 index 0000000..6699b28 --- /dev/null +++ b/cspell.yaml @@ -0,0 +1,109 @@ +# cspell configuration in YAML so maintainers can add comments +version: "0.2" + +# Paths and files cspell should ignore. Glob patterns are supported. +ignorePaths: + - cSpell_dict.txt + - package-lock.json + - package.json + - public/device-specs/**/*.yaml + - public/swagger/v1.json + - public/files/protocol-v3.yaml + - .vitepress/languages/* + - .vitepress/theme/index.ts + +# Custom dictionary definitions (none used currently) +dictionaryDefinitions: [] + +# Dictionaries to enable (none enabled by default) +dictionaries: [] + +# Word list: add project-specific or domain-specific tokens here. +words: + # Company and product names + - Lightbug + - Espressif + - Maxar + - Mapbox + - Klick + - vuetify + - vitepress + + # Lightbug device names + - Enviro + - enviro + - VTRTK + + # Lightbug terms + # Acronyms, initialisms, technical terms + - RTCM + - UART + - OBDII + - Quadband + - GLONASS + - BEIDOU + - QZSS + - EEPROM + - RSSI + - DGNSS + - NMEA + - WCDMA + - NTRIP + - GPRS + - iccid + - imei + + # OK abbreviations + - csum + - Csum + - onprem + - codegen + + # domains + - trycloudflare + + # Misc + - Geofences + - Toit + - toit + - eink + - rgba + - geofencelists + - Geofence + - Datapoints + - datapoints + - sendreason + - alerttype + - behaviour + - modecontrol + - overvoltage + - Julet + - xmodem + - msgb + - Dont + - Bitfield + - bitfield + - bitfields + - optimisation + - Optimisations + - initialised + - multipath + - microcontroller + - HPSYS + - SPIWP + - acking + - actioned + - centi + - hectopascals + - pseudorange + - Centimetre + - microcontrollers + - wght + - mdit + - integ + +# Words to ignore (none configured) +ignoreWords: [] + +# Imported configuration files (none configured) +import: [] diff --git a/devices/history/index.md b/devices/history/index.md index ac279ae..511ae38 100644 --- a/devices/history/index.md +++ b/devices/history/index.md @@ -12,7 +12,7 @@ We have continually iterated on our devices over the years, and have a number of |-------|---------|------------------|------------| |
![](https://lightbug.io/images/product/lightbug-RH2-front-angle_hu5249cd979b1a5fb71ccb0d550c4a59ce_666038_600x600_fit_q100_box_2.png)
| [RtkHandheld2](/devices/rtk/)
SKU: RH2 | Precision RTK handheld tracker |
Development2024
ReleasePre Production
| |
![](https://lightbug.io/images/product/lightbug_wired_gps_tracker_angle_hu3852c106dfa3e0439e3eb688c9cfa849_245817_600x600_fit_q100_box_2.png)
| [Vehicle3](/devices/vehicle/)
SKU: VT3
Prefix: 40 | 3rd Generation Wired GPS tracker for vehicles |
Development2023
Release2024
| -|
![](https://lightbug.io/images/product/lightbug_environmental_tracker_hu14054740ad5a43a7ce725f924e55a0d7_598053_600x600_fit_q100_box_2.png)
| [EnvrioN](/devices/enviro/) (black)
SKU: EN2
Prefix: 26 | Rugged Tracker with exceptional battery life and built-in sensors
Now with an advanced accelerometer |
Release2021
| +|
![](https://lightbug.io/images/product/lightbug_environmental_tracker_hu14054740ad5a43a7ce725f924e55a0d7_598053_600x600_fit_q100_box_2.png)
| [EnviroN](/devices/enviro/) (black)
SKU: EN2
Prefix: 26 | Rugged Tracker with exceptional battery life and built-in sensors
Now with an advanced accelerometer |
Release2021
| |
![](https://docs.lightbug.io/static/2025/history_rthub_front_long_battery_gps_tracker.png)
| [ProN](/devices/pro/)
SKU: PR2
Prefix: 20 | Our most rugged and long-life tracker for harsh environments |
Release2020
| |
![](https://docs.lightbug.io/static/2025/history_lightbug_front_small_gps_tracker_2.png)
| [ZeroN](/devices/zero/)
SKU: ZE2
Prefix: 19 | Our smallest core GPS tracking device |
Development2020
Release2020
| diff --git a/package-lock.json b/package-lock.json index b3d77fd..eb6cc29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,6 +6,10 @@ "": { "dependencies": { "@highlightjs/vue-plugin": "^2.1.0", + "@mdit/plugin-align": "^0.22.1", + "@mdit/plugin-attrs": "^0.23.1", + "@mdit/plugin-figure": "^0.22.1", + "@mdit/plugin-img-size": "^0.22.2", "@wdns/vue-code-block": "^2.3.5", "buffer": "^6.0.3", "crc": "^4.3.2", @@ -14,6 +18,7 @@ "highlight.js": "^11.11.0", "jest-environment-jsdom": "^29.7.0", "js-yaml": "^4.1.0", + "markdown-it": "^14.1.0", "shiki": "^3.12.2", "vitepress-openapi": "^0.1.9", "vitepress-plugin-pagefind": "^0.4.10", @@ -1790,6 +1795,128 @@ "integrity": "sha512-43MtGpd585SNzHZPcYowu/84Vz2a2g31TvPMTm9uTiCSWzaheQySUcSyUH/46fPnuPQWof2yd0pGBtzee/IQWw==", "dev": true }, + "node_modules/@mdit/helper": { + "version": "0.22.1", + "resolved": "https://registry.npmjs.org/@mdit/helper/-/helper-0.22.1.tgz", + "integrity": "sha512-lDpajcdAk84aYCNAM/Mi3djw38DJq7ocLw5VOSMu/u2YKX3/OD37a6Qb59in8Uyp4SiAbQoSHa8px6hgHEpB5g==", + "license": "MIT", + "dependencies": { + "@types/markdown-it": "^14.1.2" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "markdown-it": "^14.1.0" + }, + "peerDependenciesMeta": { + "markdown-it": { + "optional": true + } + } + }, + "node_modules/@mdit/plugin-align": { + "version": "0.22.1", + "resolved": "https://registry.npmjs.org/@mdit/plugin-align/-/plugin-align-0.22.1.tgz", + "integrity": "sha512-KCI9Sa1TW25Th1QvEZUp1OnI5qOE82OeduWKeQ5CHsVIbW2WTyRZjLgxPO0kPWPw15gbSrLvWj4RC7cv+C5p6Q==", + "license": "MIT", + "dependencies": { + "@mdit/plugin-container": "0.22.1", + "@types/markdown-it": "^14.1.2" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "markdown-it": "^14.1.0" + }, + "peerDependenciesMeta": { + "markdown-it": { + "optional": true + } + } + }, + "node_modules/@mdit/plugin-attrs": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@mdit/plugin-attrs/-/plugin-attrs-0.23.1.tgz", + "integrity": "sha512-KY05v0DIBMItOxoniyDxxtyYIiT+0JTQ2Ke0mzyCyvPplqCv4Avus7/uAZ3+IGcaI2oOTlYEHdU288VBFgXjAw==", + "license": "MIT", + "dependencies": { + "@mdit/helper": "0.22.1", + "@types/markdown-it": "^14.1.2" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "markdown-it": "^14.1.0" + }, + "peerDependenciesMeta": { + "markdown-it": { + "optional": true + } + } + }, + "node_modules/@mdit/plugin-container": { + "version": "0.22.1", + "resolved": "https://registry.npmjs.org/@mdit/plugin-container/-/plugin-container-0.22.1.tgz", + "integrity": "sha512-UY1NRRb/Su9YxQerkCF8bWG0fY/V24b9f/jVWh5DhD+Dw4MifVbV6p5TlaeQ854Xz9prkhyXSugiWbjhju6BgQ==", + "license": "MIT", + "dependencies": { + "@types/markdown-it": "^14.1.2" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "markdown-it": "^14.1.0" + }, + "peerDependenciesMeta": { + "markdown-it": { + "optional": true + } + } + }, + "node_modules/@mdit/plugin-figure": { + "version": "0.22.1", + "resolved": "https://registry.npmjs.org/@mdit/plugin-figure/-/plugin-figure-0.22.1.tgz", + "integrity": "sha512-z7uqtKsQ/ILkdM4pLrfuvz2eAhtwNzRPT9xnixFosrMgF7CEHbBtFTF6nc2ht1mOqCTRqoIL+FWg8InYMiBPhQ==", + "license": "MIT", + "dependencies": { + "@types/markdown-it": "^14.1.2" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "markdown-it": "^14.1.0" + }, + "peerDependenciesMeta": { + "markdown-it": { + "optional": true + } + } + }, + "node_modules/@mdit/plugin-img-size": { + "version": "0.22.2", + "resolved": "https://registry.npmjs.org/@mdit/plugin-img-size/-/plugin-img-size-0.22.2.tgz", + "integrity": "sha512-+2+HpV5wZ3ZvFAs2alOiftDO635UbbOTr9uRQ0LZi/1lIZzKa0GE8sxYmtAZXRkdbGCj1uN6puoT7Bc7fdBs7Q==", + "license": "MIT", + "dependencies": { + "@types/markdown-it": "^14.1.2" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "markdown-it": "^14.1.0" + }, + "peerDependenciesMeta": { + "markdown-it": { + "optional": true + } + } + }, "node_modules/@mermaid-js/mermaid-mindmap": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@mermaid-js/mermaid-mindmap/-/mermaid-mindmap-9.3.0.tgz", @@ -6395,6 +6522,15 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/local-pkg": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", @@ -6513,6 +6649,23 @@ "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==" }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, "node_modules/markdown-it-link-attributes": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/markdown-it-link-attributes/-/markdown-it-link-attributes-4.0.1.tgz", @@ -6559,6 +6712,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "license": "MIT" + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -7242,6 +7401,15 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/pure-rand": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", @@ -8158,6 +8326,12 @@ "node": "*" } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT" + }, "node_modules/ufo": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", diff --git a/package.json b/package.json index 1ee877b..5122934 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,10 @@ }, "dependencies": { "@highlightjs/vue-plugin": "^2.1.0", + "@mdit/plugin-align": "^0.22.1", + "@mdit/plugin-attrs": "^0.23.1", + "@mdit/plugin-figure": "^0.22.1", + "@mdit/plugin-img-size": "^0.22.2", "@wdns/vue-code-block": "^2.3.5", "buffer": "^6.0.3", "crc": "^4.3.2", @@ -32,6 +36,7 @@ "highlight.js": "^11.11.0", "jest-environment-jsdom": "^29.7.0", "js-yaml": "^4.1.0", + "markdown-it": "^14.1.0", "shiki": "^3.12.2", "vitepress-openapi": "^0.1.9", "vitepress-plugin-pagefind": "^0.4.10", From c50c7cf6cc26f8baaa488605e48fb130aea02cac Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 17 Sep 2025 21:45:26 +0100 Subject: [PATCH 22/38] no longer import defineProps --- components/DownloadBookletButton.vue | 2 -- components/DownloadPdfButton.vue | 2 +- components/DownloadYamlButton.vue | 2 -- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/components/DownloadBookletButton.vue b/components/DownloadBookletButton.vue index f90b1a2..dc1b4d4 100644 --- a/components/DownloadBookletButton.vue +++ b/components/DownloadBookletButton.vue @@ -5,8 +5,6 @@ ::: danger ⚠️ Not yet public @@ -21,6 +22,10 @@ These pages can be seen as a view of what is to come later this year. {{yaml:public/files/protocol-v3.yaml:messages.11.description}} +## Fields + + + ## Code For convenience, the following constants can be referring to this message type. diff --git a/public/files/protocol-v3.yaml b/public/files/protocol-v3.yaml index 8436c6f..f49886d 100644 --- a/public/files/protocol-v3.yaml +++ b/public/files/protocol-v3.yaml @@ -177,12 +177,14 @@ messages: type: uint16 11: name: Open - description: Explicit indicator for the start of a connection + description: Explicit indicator for the start of a connection. group: general data: 10: name: Device Type - description: Type of device, relates to the SN prefix + description: | + Type of device, relates to the serial number prefix. + Primarily used by inter processor communication to identify the type of device. type: uint8 12: name: Close diff --git a/yaml-data.data.ts b/yaml-data.data.ts new file mode 100644 index 0000000..69e6f16 --- /dev/null +++ b/yaml-data.data.ts @@ -0,0 +1,21 @@ +import fs from 'node:fs' +import jsyaml from 'js-yaml' + +export interface YamlDataInterface { + [key: string]: any +} + +export default { + watch: ['./public/files/protocol-v3.yaml'], + load(watchedFiles: string[]): YamlDataInterface { + if (watchedFiles.length === 0) { + return {} + } + + const yamlFile = watchedFiles[0] + const yamlContent = fs.readFileSync(yamlFile, 'utf-8') + const parsedYaml = jsyaml.load(yamlContent) as YamlDataInterface + + return parsedYaml + } +} From 54a650f31c7a843cdbec4498381ccbf9d5ca356f Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 18 Sep 2025 15:55:44 +0100 Subject: [PATCH 31/38] GenerateConsts with pre loaded data --- components/GenerateConsts.vue | 151 ++++++++++++++------------------ cspell.yaml | 1 + devices/api/messages/11-open.md | 3 +- 3 files changed, 67 insertions(+), 88 deletions(-) diff --git a/components/GenerateConsts.vue b/components/GenerateConsts.vue index 4f2888e..998185f 100644 --- a/components/GenerateConsts.vue +++ b/components/GenerateConsts.vue @@ -96,7 +96,6 @@ ::: danger ⚠️ Not yet public @@ -47,4 +48,4 @@ byteString="3 14 0 24 39 1 0 1 1 126 0 0 25 55" ## Code - + diff --git a/devices/api/messages/10009-text-page.md b/devices/api/messages/10009-text-page.md index 79a16ed..7311564 100644 --- a/devices/api/messages/10009-text-page.md +++ b/devices/api/messages/10009-text-page.md @@ -6,8 +6,9 @@ outline: false ::: danger ⚠️ Not yet public @@ -85,4 +86,4 @@ Only a partial redraw will be performed in order to add the new line, and the ex For convenience, the following constants can be used to reference the payload fields. - + diff --git a/devices/api/messages/10010-menu-page.md b/devices/api/messages/10010-menu-page.md index 27681c0..e82cc05 100644 --- a/devices/api/messages/10010-menu-page.md +++ b/devices/api/messages/10010-menu-page.md @@ -5,8 +5,9 @@ outline: false ::: danger ⚠️ Not yet public @@ -60,4 +61,4 @@ byteString="3 26 0 26 39 1 0 1 1 187 3 0 3 2 102 1 102 1 3 4 111 112 116 51 203 For convenience, the following constants can be used to reference the payload fields. - + diff --git a/devices/api/messages/10011-draw-bitmap.md b/devices/api/messages/10011-draw-bitmap.md index 9854101..9e59cc0 100644 --- a/devices/api/messages/10011-draw-bitmap.md +++ b/devices/api/messages/10011-draw-bitmap.md @@ -5,9 +5,10 @@ outline: false ::: danger ⚠️ Not yet public @@ -24,7 +25,7 @@ These pages can be seen as a view of what is to come later this year. @@ -49,4 +50,4 @@ Basic heartbeat message. For convenience, the following constants can be referring to this message type. - + diff --git a/devices/api/messages/14-config.md b/devices/api/messages/14-config.md index dce04a4..8096c2c 100644 --- a/devices/api/messages/14-config.md +++ b/devices/api/messages/14-config.md @@ -5,9 +5,10 @@ outline: false ::: danger ⚠️ Not yet public @@ -26,7 +27,7 @@ TODO...
@@ -35,4 +36,4 @@ TODO... For convenience, the following constants can be used to reference the payload fields. - + diff --git a/devices/api/messages/15-position.md b/devices/api/messages/15-position.md index e59844d..8538e04 100644 --- a/devices/api/messages/15-position.md +++ b/devices/api/messages/15-position.md @@ -5,9 +5,10 @@ outline: false ::: danger ⚠️ Not yet public @@ -26,7 +27,7 @@ These pages can be seen as a view of what is to come later this year. Used to interact with the devices position. - + @@ -35,4 +36,4 @@ These pages can be seen as a view of what is to come later this year. For convenience, the following constants can be used to reference the payload fields. - + diff --git a/devices/api/messages/32-gsm-request-ownership.md b/devices/api/messages/32-gsm-request-ownership.md index a0a6121..cf6fbc9 100644 --- a/devices/api/messages/32-gsm-request-ownership.md +++ b/devices/api/messages/32-gsm-request-ownership.md @@ -5,9 +5,10 @@ outline: false ::: danger ⚠️ Not yet public @@ -24,7 +25,7 @@ These pages can be seen as a view of what is to come later this year. @@ -33,4 +34,4 @@ These pages can be seen as a view of what is to come later this year. For convenience, the following constants can be used to reference the payload fields. - + diff --git a/devices/api/messages/34-device-status.md b/devices/api/messages/34-device-status.md index 606094d..48c999e 100644 --- a/devices/api/messages/34-device-status.md +++ b/devices/api/messages/34-device-status.md @@ -8,6 +8,7 @@ import ProtocolBytes from '../../../components/ProtocolBytes.vue'; import SplitColumnView from '../../../components/SplitColumnView.vue'; import GenerateConsts from '../../../components/GenerateConsts.vue' import PayloadTable from '../../../components/PayloadTable.vue' +import { data as protocolData } from '../../../yaml-data.data.ts' ::: danger ⚠️ Not yet public @@ -25,7 +26,7 @@ Used to GET the general status of the device. ### Payload - + @@ -48,4 +49,4 @@ byteString="0" For convenience, the following constants can be used to reference the payload fields. - + diff --git a/devices/api/messages/40-haptics-control.md b/devices/api/messages/40-haptics-control.md index 9eed036..702651c 100644 --- a/devices/api/messages/40-haptics-control.md +++ b/devices/api/messages/40-haptics-control.md @@ -5,8 +5,9 @@ outline: false ::: danger ⚠️ Not yet public @@ -51,4 +52,4 @@ byteString="0" For convenience, the following constants can be used to reference the payload fields. - + diff --git a/devices/api/messages/41-temperature.md b/devices/api/messages/41-temperature.md index 392c83d..cd6055c 100644 --- a/devices/api/messages/41-temperature.md +++ b/devices/api/messages/41-temperature.md @@ -5,8 +5,9 @@ outline: false ::: danger ⚠️ Not yet public @@ -49,4 +50,4 @@ byteString="0" For convenience, the following constants can be used to reference the payload fields. - + diff --git a/devices/api/messages/42-buzzer-control.md b/devices/api/messages/42-buzzer-control.md index da0a653..4138f98 100644 --- a/devices/api/messages/42-buzzer-control.md +++ b/devices/api/messages/42-buzzer-control.md @@ -5,8 +5,9 @@ outline: false ::: danger ⚠️ Not yet public @@ -51,4 +52,4 @@ byteString="0" For convenience, the following constants can be used to reference the payload fields. - + diff --git a/devices/api/messages/43-battery-status.md b/devices/api/messages/43-battery-status.md index e968186..fb31fef 100644 --- a/devices/api/messages/43-battery-status.md +++ b/devices/api/messages/43-battery-status.md @@ -5,8 +5,9 @@ outline: false ::: danger ⚠️ Not yet public @@ -60,4 +61,4 @@ byteString="3 32 0 43 0 2 0 3 1 4 99 0 0 0 4 36 0 0 0 2 0 2 1 1 100 4 223 79 141 For convenience, the following constants can be used to reference the payload fields. - + diff --git a/devices/api/messages/44-pressure.md b/devices/api/messages/44-pressure.md index 2f51236..30e85c4 100644 --- a/devices/api/messages/44-pressure.md +++ b/devices/api/messages/44-pressure.md @@ -5,8 +5,9 @@ outline: false ::: danger ⚠️ Not yet public @@ -49,4 +50,4 @@ byteString="0" For convenience, the following constants can be used to reference the payload fields. - + diff --git a/devices/api/messages/45-alarm.md b/devices/api/messages/45-alarm.md index b88caae..ef54506 100644 --- a/devices/api/messages/45-alarm.md +++ b/devices/api/messages/45-alarm.md @@ -5,8 +5,9 @@ outline: false ::: danger ⚠️ Not yet public @@ -38,4 +39,4 @@ TODO. For convenience, the following constants can be used to reference the payload fields. - + diff --git a/devices/api/messages/47-cpu2-sleep.md b/devices/api/messages/47-cpu2-sleep.md index e35bc42..e865309 100644 --- a/devices/api/messages/47-cpu2-sleep.md +++ b/devices/api/messages/47-cpu2-sleep.md @@ -8,6 +8,7 @@ import ProtocolBytes from '../../../components/ProtocolBytes.vue'; import SplitColumnView from '../../../components/SplitColumnView.vue'; import GenerateConsts from '../../../components/GenerateConsts.vue'; import PayloadTable from '../../../components/PayloadTable.vue'; +import { data as protocolData } from '../../../yaml-data.data.ts' ::: danger ⚠️ Not yet public @@ -24,7 +25,7 @@ These pages can be seen as a view of what is to come later this year. @@ -41,4 +42,4 @@ TODO For convenience, the following constants can be used to reference the payload fields. - + diff --git a/devices/api/messages/51-link-data.md b/devices/api/messages/51-link-data.md index f1b6617..c37449f 100644 --- a/devices/api/messages/51-link-data.md +++ b/devices/api/messages/51-link-data.md @@ -5,9 +5,10 @@ outline: false ::: danger ⚠️ Not yet public @@ -26,7 +27,7 @@ Used to send data over a link. @@ -39,4 +40,4 @@ TODO For convenience, the following constants can be used to reference the payload fields. - + diff --git a/devices/api/messages/53-ublox-protection-level.md b/devices/api/messages/53-ublox-protection-level.md index 63fb02e..b880734 100644 --- a/devices/api/messages/53-ublox-protection-level.md +++ b/devices/api/messages/53-ublox-protection-level.md @@ -8,6 +8,7 @@ import ProtocolBytes from '../../../components/ProtocolBytes.vue'; import SplitColumnView from '../../../components/SplitColumnView.vue'; import GenerateConsts from '../../../components/GenerateConsts.vue' import PayloadTable from '../../../components/PayloadTable.vue' +import { data as protocolData } from '../../../yaml-data.data.ts' ::: danger ⚠️ Not yet public @@ -26,7 +27,7 @@ Used to retrieve [ublox protection level](https://www.u-blox.com/en/technologies @@ -39,4 +40,4 @@ TODO For convenience, the following constants can be used to reference the payload fields. - + diff --git a/devices/api/protocol/headers.md b/devices/api/protocol/headers.md index 54028c0..d8b01b8 100644 --- a/devices/api/protocol/headers.md +++ b/devices/api/protocol/headers.md @@ -4,6 +4,7 @@ outline: [1,3] # Headers @@ -127,4 +128,4 @@ Reserved for future use. ## Code generation - + From 73b2a7fb9d813b34a74f0ff00d445d76b44db8aa Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 19 Sep 2025 07:47:28 +0100 Subject: [PATCH 33/38] ProtocolBytes dont load yaml, get provided it --- components/HeaderTable.vue | 26 +++----- components/ProtocolBytes.vue | 64 +++++++++----------- components/ProtocolBytesInput.vue | 6 +- components/ProtocolGenerate.vue | 57 ++++++++--------- devices/api/messages/10009-text-page.md | 2 + devices/api/messages/10014-screen-refresh.md | 1 + devices/api/messages/13-heartbeat.md | 1 + devices/api/messages/30-transmit-now.md | 6 +- devices/api/messages/34-device-status.md | 2 + devices/api/messages/35-device-ids.md | 1 + devices/api/messages/36-device-time.md | 2 + devices/api/messages/38-button-press.md | 1 + devices/api/messages/40-haptics-control.md | 1 + devices/api/messages/41-temperature.md | 3 +- devices/api/messages/44-pressure.md | 4 +- devices/api/messages/5-ack.md | 2 + devices/api/protocol/examples.md | 2 + devices/api/protocol/index.md | 2 + 18 files changed, 94 insertions(+), 89 deletions(-) diff --git a/components/HeaderTable.vue b/components/HeaderTable.vue index fe36779..a8fefcd 100644 --- a/components/HeaderTable.vue +++ b/components/HeaderTable.vue @@ -24,7 +24,6 @@