Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Studio RPC Infrastructure #2254

Merged
merged 6 commits into from
Aug 15, 2024

Conversation

petejohanson
Copy link
Contributor

@petejohanson petejohanson commented Apr 5, 2024

Initial PR for the RPC infrastructure to be used by ZMK Studio.

This includes the behavior metadata commit from #2231 as it's used by the first really fleshed out behavior subsystem.

To Do

  • Document framing and protocol buffer messages.
  • Wrap up the sample client library.
  • Testing/validation from others.

@petejohanson petejohanson added enhancement New feature or request studio ZMK Studio (runtime keymaps) labels Apr 5, 2024
@petejohanson petejohanson self-assigned this Apr 5, 2024
@petejohanson petejohanson requested a review from a team as a code owner April 5, 2024 18:36
@petejohanson petejohanson force-pushed the studio/rpc-infrastructure branch 3 times, most recently from a873cb3 to d79d916 Compare April 10, 2024 20:55
@petejohanson petejohanson force-pushed the studio/rpc-infrastructure branch 3 times, most recently from 13a6e9e to 85add43 Compare April 18, 2024 06:10
@petejohanson petejohanson force-pushed the studio/rpc-infrastructure branch from 199d35b to e192575 Compare May 17, 2024 19:06
@and-elf
Copy link

and-elf commented May 28, 2024

Why not start with a cli tool, that handles the actual rpc calls, and layer studio gui ontop of it later?
I'm not sure the gui (library) should be a part of this repo.
The binary could be used by https://github.com/nickcoutsos/keymap-editor

I'm not a maintainer, but it seems to me that the software isn't a part of the firmware.
When I do RPC, I usually keep protocol definitions in an own repo, which server and client references.

@petejohanson petejohanson force-pushed the studio/rpc-infrastructure branch 7 times, most recently from 646513d to f6db153 Compare June 9, 2024 02:04
@petejohanson petejohanson force-pushed the studio/rpc-infrastructure branch 10 times, most recently from ed4aab9 to fdb36fb Compare June 17, 2024 18:04
app/src/studio/gatt_rpc_transport.c Outdated Show resolved Hide resolved
}

ring_buf_put_finish(rpc_buf, claim_len);
} while (len - copied > 0);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checks like this with unsigned integers always make me nervous, but the math above does appear to guarantee that copied <= len.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tweaked it to the simpler copied < len

uint8_t *buffer;
uint32_t claim_len = ring_buf_put_claim(rpc_buf, &buffer, len - copied);

if (claim_len > 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If len = 0, then this would skip doing anything and exit the loop (if len can be 0, would a while loop be better than do-while?), but is it possible for len > 0 and claim_len = 0, which would result in an infinite loop?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So... It will only infinitely look if there's no consumer pulling out of the ring buffer. I will tweak this to be a regular while loop though to be safe.

app/src/studio/gatt_rpc_transport.c Outdated Show resolved Hide resolved
notify_size = conn_info.le.data_len->tx_max_len;
}

uint8_t notify_bytes[notify_size];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a compiler extension? GCC and Clang seem to allow it, but MSVC fails to compile it, and I wasn't aware it was possible to declare an array of dynamic size on the stack in C.

app/src/studio/gatt_rpc_transport.c Outdated Show resolved Hide resolved
rpc_indicate_params.len = added;

uint8_t notify_attempts = 5;
do {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do-while works, but a for loop might be more conventional? Can also just use an int for the counter so you don't have to explicitly avoid it going negative.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched to an int... I personally favor do/while style for "retries" like this.

app/src/studio/gatt_rpc_transport.c Outdated Show resolved Hide resolved
app/src/studio/gatt_rpc_transport.c Outdated Show resolved Hide resolved
app/src/studio/gatt_rpc_transport.c Show resolved Hide resolved
@petejohanson petejohanson force-pushed the studio/rpc-infrastructure branch 2 times, most recently from c7bd6bd to fbb977c Compare August 9, 2024 00:19
@petejohanson petejohanson requested a review from joelspadin August 9, 2024 00:22
app/src/studio/msg_framing.c Show resolved Hide resolved
#define FRAMING_ESC 0xAC
#define FRAMING_EOF 0xAD

bool studio_framing_process_byte(enum studio_framing_state *frame_state, uint8_t data);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does the return value here represent? I assume it's whether the byte should be used as data, but would be nice to document that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some basic docs here.

app/src/studio/rpc.c Outdated Show resolved Hide resolved
app/src/studio/rpc.c Outdated Show resolved Hide resolved
app/src/studio/rpc.c Outdated Show resolved Hide resolved
app/src/studio/uart_rpc_transport.c Outdated Show resolved Hide resolved
app/src/studio/uart_rpc_transport.c Outdated Show resolved Hide resolved
Comment on lines 107 to 119
do {
uint8_t *buffer;
uint32_t len = ring_buf_put_claim(buf, &buffer, buf->size);
if (len == 0) {
zmk_rpc_rx_notify();
continue;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this just busy loop until it gets a claim? If so, I think this would need to be running on a thread with a lower priority than whatever is consuming the data, or it may deadlock by never allowing that thread to run?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A good question... The zmk_rpc_rx_notify will give on the underlying semaphore, but this is in an ISR context for the UART callback, IIRC, so we can't yield... I'm honestly not sure if we're hitting this (yet) in practice, and if so, how it's functioning. Let me dig a bit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh... Ok, so... I think the answer is: We need to size the RX ring buffer large enough to avoid this happening. From https://docs.zephyrproject.org/3.5.0/doxygen/html/group__uart__interrupt.html#gab10942076ac47ecff29e924098049398:

That means that once uart_irq_rx_ready() is detected, uart_fifo_read() must be called until it reads all available data in the FIFO (i.e. until it returns less data than was requested).

So... we have to keep calling uart_fifo_read from this callback, even if we don't have anywhere to put it... And we're an an ISR so we can't just yield. I think the best we can actually do in this scenario is skip the bytes LOG_ERR
that we're doing so, and bail.

Thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, here's a pretty authoritative Zephyr use of the async API for the shell work. It basically drops the data if there's no room in the ring buffer:

https://github.com/zephyrproject-rtos/zephyr/blob/main/subsys/shell/backends/shell_uart.c#L84

(modified to remove some conditional code for clarity)

	do {
		len = ring_buf_put_claim(&sh_uart->rx_ringbuf, &data,
					 sh_uart->rx_ringbuf.size);

		if (len > 0) {
			rd_len = uart_fifo_read(dev, data, len);

			/* If there is any new data to be either taken into
			 * ring buffer or consumed by the SMP, signal the
			 * shell_thread.
			 */
			if (rd_len > 0) {
				new_data = true;
			}

			int err = ring_buf_put_finish(&sh_uart->rx_ringbuf, rd_len);
			(void)err;
			__ASSERT_NO_MSG(err == 0);
		} else {
			uint8_t dummy;

			/* No space in the ring buffer - consume byte. */
			LOG_WRN("RX ring buffer full.");

			rd_len = uart_fifo_read(dev, &dummy, 1);
		}
	} while (rd_len && (rd_len == len));

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to match.

app/src/studio/uart_rpc_transport.c Outdated Show resolved Hide resolved
app/src/studio/uart_rpc_transport.c Outdated Show resolved Hide resolved
@petejohanson petejohanson force-pushed the studio/rpc-infrastructure branch 2 times, most recently from 89d9e96 to 2a8966a Compare August 9, 2024 23:44
@petejohanson petejohanson force-pushed the studio/rpc-infrastructure branch 4 times, most recently from 227a22d to ff5659c Compare August 15, 2024 07:23
@Nick-Munnich
Copy link
Contributor

Just as a small note, could you either finish (there's a TODO) & add the config and behavior doc pages to the sidebar or remove them from this PR? The RPC page looks good.

@petejohanson petejohanson force-pushed the studio/rpc-infrastructure branch from ff5659c to c0ad99e Compare August 15, 2024 16:34
@petejohanson
Copy link
Contributor Author

Just as a small note, could you either finish (there's a TODO) & add the config and behavior doc pages to the sidebar or remove them from this PR? The RPC page looks good.

I will move the config page to #2268 since that PR makes studio actually end user usable. The RPC docs I will add to the sidebar in this PR since it's part of this core work. That work @Nick-Munnich ?

@petejohanson petejohanson force-pushed the studio/rpc-infrastructure branch from c0ad99e to 883e166 Compare August 15, 2024 16:48
@Nick-Munnich
Copy link
Contributor

Nick-Munnich commented Aug 15, 2024

I will move the config page to #2268 since that PR makes studio actually end user usable. The RPC docs I will add to the sidebar in this PR since it's part of this core work. That work @Nick-Munnich ?

Moving them is a good solution, but be aware that you've already added the RPC docs, the other part which was added but not listed in the sidebar is "Studio Unlock" behaviour. I'd guess that should be moved too?

@petejohanson
Copy link
Contributor Author

I will move the config page to #2268 since that PR makes studio actually end user usable. The RPC docs I will add to the sidebar in this PR since it's part of this core work. That work @Nick-Munnich ?

Moving them is a good solution, but be aware that you've already added the RPC docs, the other part which was added but not listed in the sidebar is "Studio Unlock" behaviour. I'd guess that should be moved too?

Yeah, I saw RPC was already in the sidebar. I've left that for now, but moved the config doc to #2268 and added in the sidebar there.

I wanted to be sure I had the unlock behavior documented, but don't really want to "expose it" until it's actually usable for something. I'd like to keep the doc, but add to sidebar in #2268. Any concerns?

petejohanson and others added 2 commits August 15, 2024 10:55
* UART and BLE/GATT transports for a protobuf encoded RPC
  request/response protocol.
* Custom framing protocol is used to frame a give message.
* Requests/responses are divided into major "subsystems" which
  handle requests and create response messages.
* Notification support, including mapping local events to RPC
  notifications by a given subsystem.
* Meta responses for "no response" and "unlock needed".
* Initial basic lock state support in a new core section, and allow specifying
  if a given RPC callback requires unlocked state or not.
* Add behavior subsystem with full metadata support and examples of
  using callback to serialize a repeated field without extra stack space needed.

Co-authored-by: Cem Aksoylar <[email protected]>
 * Add an easy snippet for enabling USB UART added
   to the `zephyr_udc0` standard node.
@petejohanson petejohanson force-pushed the studio/rpc-infrastructure branch from 883e166 to 9d1c5d3 Compare August 15, 2024 16:55
@Nick-Munnich
Copy link
Contributor

I wanted to be sure I had the unlock behavior documented, but don't really want to "expose it" until it's actually usable for something. I'd like to keep the doc, but add to sidebar in #2268. Any concerns?

Best practice would probably be to add it to sidebar and then comment it out in this PR, but I think that's not necessary unless you wanted to. All good from me.

@petejohanson
Copy link
Contributor Author

I wanted to be sure I had the unlock behavior documented, but don't really want to "expose it" until it's actually usable for something. I'd like to keep the doc, but add to sidebar in #2268. Any concerns?

Best practice would probably be to add it to sidebar and then comment it out in this PR, but I think that's not necessary unless you wanted to. All good from me.

Given the type check from Docusaurus is now angry about unlock docs referencing the config docs that don't exist, I'm just going to move that doc to #2268 also for cohesion.

petejohanson and others added 4 commits August 15, 2024 11:08
* New behavior allows unlocking the keyboard to allow ZMK Studio to
  make changes.

Co-authored-by: Cem Aksoylar <[email protected]>
* Cover stm32, RP2040, and nRF52 builds.
* Reduce RAM usage, no need for heap any more in ZMK.
* Don't attempt to enable FPU that's not present.
@petejohanson petejohanson force-pushed the studio/rpc-infrastructure branch from 9d1c5d3 to c1c91d1 Compare August 15, 2024 17:09
@petejohanson petejohanson merged commit d5061c5 into zmkfirmware:main Aug 15, 2024
49 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request studio ZMK Studio (runtime keymaps)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants