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

ADD: GNSS Driver for U-BLOX M10 & Support for UBX Messages #68350

Conversation

mayankmahajan-nxp
Copy link
Contributor

@mayankmahajan-nxp mayankmahajan-nxp commented Jan 31, 2024

MODEM_UBX: Adds Support for UBX Messages in Modem Subsystem.

GNSS API Supported: get_supported_systems, set_fix_rate, get_fix_rate,
set_enabled_systems, get_enabled_systems, set_navigation_mode,
get_navigation_mode.

Boards Tested: MIMXRT1062_FMURT6, VMU_RT1170.

Note: Partial support for U-BLOX Messages is provided as of now.

Signed-off-by: Sumit Batra sumit.batra@nxp.com
Signed-off-by: Mayank Mahajan mayankmahajan.x@nxp.com

Copy link
Collaborator

@bjarki-andreasen bjarki-andreasen left a comment

Choose a reason for hiding this comment

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

I have quite a few requests, I have limited the review to the u_blox_protocol related files for now to make it easier to attend to and/or discuss the comments :)

drivers/gnss/CMakeLists.txt Outdated Show resolved Hide resolved
drivers/gnss/Kconfig Outdated Show resolved Hide resolved
drivers/gnss/Kconfig.u_blox_m10 Outdated Show resolved Hide resolved
drivers/gnss/u_blox_protocol/u_blox_protocol.c Outdated Show resolved Hide resolved
drivers/gnss/u_blox_protocol/u_blox_protocol.c Outdated Show resolved Hide resolved
drivers/gnss/u_blox_protocol/u_blox_protocol.c Outdated Show resolved Hide resolved
drivers/gnss/u_blox_protocol/u_blox_protocol.c Outdated Show resolved Hide resolved
@mayankmahajan-nxp mayankmahajan-nxp force-pushed the gnss_u_blox_m10_driver_and_modem_ubx branch 2 times, most recently from c651f00 to 0bfdf91 Compare February 6, 2024 11:32
@mayankmahajan-nxp
Copy link
Contributor Author

I have quite a few requests, I have limited the review to the u_blox_protocol related files for now to make it easier to attend to and/or discuss the comments :)

Hi @bjarki-trackunit, thank you for the review. I have addressed the comments. Kindly have a look at your convenience.

Copy link
Collaborator

@bjarki-andreasen bjarki-andreasen left a comment

Choose a reason for hiding this comment

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

Looking good, a few more requests, and a question:

  • Are the definitions in gnss_u_blox_protocol.h and .c generic, as in, match other ublox gnss than the m10?

drivers/gnss/gnss_u_blox_m10.c Outdated Show resolved Hide resolved
drivers/gnss/gnss_u_blox_m10.c Outdated Show resolved Hide resolved
drivers/gnss/gnss_u_blox_m10.c Show resolved Hide resolved
include/zephyr/modem/ubx.h Outdated Show resolved Hide resolved
drivers/gnss/gnss_u_blox_protocol/gnss_u_blox_protocol.h Outdated Show resolved Hide resolved
@mayankmahajan-nxp
Copy link
Contributor Author

Looking good, a few more requests, and a question:

  • Are the definitions in gnss_u_blox_protocol.h and .c generic, as in, match other ublox gnss than the m10?

Yes, they are generic to ublox gnss devices. For example, ublox Neo M8 (if implemented) would use them as well, without any change.

Also, modem_ubx.c needs some macros in gnss_ublox_protocol.h, which is why I plan to transfer it to the include directory of gnss in the next commit.

@mayankmahajan-nxp mayankmahajan-nxp force-pushed the gnss_u_blox_m10_driver_and_modem_ubx branch from 0bfdf91 to 9059d7b Compare February 20, 2024 05:30
@mayankmahajan-nxp
Copy link
Contributor Author

Hi @bjarki-trackunit, thank you for the review. I have addressed the comments. Kindly have a look at your convenience.

Copy link
Collaborator

@bjarki-andreasen bjarki-andreasen left a comment

Choose a reason for hiding this comment

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

Its getting there, but I'm not convinced on the responsibilities of ubx.h and gnss_u_blox_protocol.h, more on that in the comments :)

drivers/gnss/gnss_u_blox_protocol.c Outdated Show resolved Hide resolved
drivers/gnss/gnss_u_blox_protocol.c Outdated Show resolved Hide resolved
drivers/gnss/gnss_u_blox_m10.c Outdated Show resolved Hide resolved
drivers/gnss/gnss_u_blox_m10.c Outdated Show resolved Hide resolved
drivers/gnss/gnss_u_blox_m10.c Outdated Show resolved Hide resolved
include/zephyr/modem/ubx.h Show resolved Hide resolved
@mayankmahajan-nxp mayankmahajan-nxp force-pushed the gnss_u_blox_m10_driver_and_modem_ubx branch from 9059d7b to 7372ac7 Compare March 1, 2024 08:55
@mayankmahajan-nxp
Copy link
Contributor Author

Hi @bjarki-trackunit, thank you for the review. I have addressed the comments. Kindly have a look at your convenience.

@mayankmahajan-nxp mayankmahajan-nxp force-pushed the gnss_u_blox_m10_driver_and_modem_ubx branch from 7372ac7 to 5b203f7 Compare March 6, 2024 11:30
@mayankmahajan-nxp
Copy link
Contributor Author

Hi @bjarki-trackunit, I have addressed this comment - #68350 (comment). Kindly review at your convenience. Thank you.

@mayankmahajan-nxp mayankmahajan-nxp force-pushed the gnss_u_blox_m10_driver_and_modem_ubx branch from 5b203f7 to e28e0f8 Compare March 6, 2024 11:38
Copy link
Collaborator

@bjarki-andreasen bjarki-andreasen left a comment

Choose a reason for hiding this comment

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

The code is looking really good, these (hopefully) last requests are quite minor, Nice work!

}

ubx->pipe = pipe;
(void) modem_pipe_attach(ubx->pipe, modem_ubx_pipe_callback, ubx);
Copy link
Collaborator

Choose a reason for hiding this comment

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

No need to denote (void) on functions which already return void :) usually it is only done to signal that the function does indeed return something, but it is intentionally ignored, like

(void)k_sem_take(&sem, K_FOREVER);

This is done quite a few places in the PR

#define UBX_PAYLOAD_SZ_MAX 256
#define UBX_FRM_SZ_MAX UBX_FRM_SZ(UBX_PAYLOAD_SZ_MAX)

struct ubx_frame_t {
Copy link
Collaborator

Choose a reason for hiding this comment

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

this struct should be named struct ubx_frame, _t denotes that it is a typedef, like typedef ...

Comment on lines 80 to 82
struct ubx_frame_t *request;
struct ubx_frame_t *response;
struct ubx_frame_t *match;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would simply add a const pointer to the script here

const struct modem_ubx_script *script;

instead of copying the pointers from the script. The script and the buffers have the same lifetime so it is safe to do so :)

Comment on lines 90 to 84
int ubx_create_frame(uint8_t *ubx_frame, uint16_t ubx_frame_size, uint8_t message_class,
uint8_t message_id, const void *data, uint16_t payload_size)
{
if (ubx_validate_frame(ubx_frame_size, message_class, message_id, payload_size)) {
return -1;
}

struct ubx_frame_t *frame = (struct ubx_frame_t *) ubx_frame;

frame->preamble_sync_char_1 = UBX_PREAMBLE_SYNC_CHAR_1;
frame->preamble_sync_char_2 = UBX_PREAMBLE_SYNC_CHAR_2;
frame->message_class = message_class;
frame->message_id = message_id;
frame->payload_size_low = payload_size;
frame->payload_size_high = payload_size >> 8;

memcpy(frame->payload_and_checksum, (uint8_t *) data, payload_size);

uint16_t ubx_frame_len = payload_size + UBX_FRM_SZ_WITHOUT_PAYLOAD;

uint8_t ckA = 0, ckB = 0;

for (unsigned int i = UBX_FRM_CHECKSUM_START_IDX;
i < (UBX_FRM_CHECKSUM_STOP_IDX(ubx_frame_len)); i++) {
ckA += ubx_frame[i];
ckB += ckA;
}

frame->payload_and_checksum[payload_size] = ckA;
frame->payload_and_checksum[payload_size + 1] = ckB;

return ubx_frame_len;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This function belongs in modem_ubx.c, there is nothing in it specific to any payload, its just constructing a "generic" ubx frame :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi @bjarki-trackunit, I have moved this to modem_ubx.c, but please note that payload size couldn't be validated in modem_ubx.c because details regarding gnss ublox messages is concern of gnss_u_blox_protocol.c. Hence, I have created a function in ubx_create_and_validate_frame in gnss_u_blox_protocol.c that validates the payload size then calls modem_ubx_create_frame in modem_ubx.c to create the frame. Please check if this is correct.

frame->payload_size_low = payload_size;
frame->payload_size_high = payload_size >> 8;

memcpy(frame->payload_and_checksum, (uint8_t *) data, payload_size);
Copy link
Collaborator

Choose a reason for hiding this comment

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

This typecast is not required, memcpy already takes a const void *, so this is typecasting const void * -> uint8_t * -> const void *, which is probably why the compiler didn't freak out that you are casting const data to non-const data, which is not allowed :)

}

int ubx_create_frame(uint8_t *ubx_frame, uint16_t ubx_frame_size, uint8_t message_class,
uint8_t message_id, const void *data, uint16_t payload_size)
Copy link
Collaborator

Choose a reason for hiding this comment

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

The data member should probalby be renamed payload to match payload_size


if (modem_ubx_match_frame_full(received, ubx->match) == true) {
k_sem_give(&ubx->script_stopped_sem);
ret = 0; /* Frame matched successfully. Terminate the script. */
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please move the comment above and before what it describes occurs :)

	/* Frame matched successfully. Terminate the script. */
	k_sem_give(&ubx->script_stopped_sem);
	ret = 0;

This is easier to follow, prescription then action

ret = 0; /* Frame matched successfully. Terminate the script. */
} else if (modem_ubx_match_frame_type(received, ubx->request) == true) {
memcpy(ubx->response, ubx->work_buf, ubx->work_buf_len);
ret = -1; /* Response receveid successfully. Script not ended. */
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same as above

460800,
};

static int ubx_validate_frame(uint16_t ubx_frame_size, uint8_t message_class, uint8_t message_id,
Copy link
Collaborator

Choose a reason for hiding this comment

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

The implementation of this function is quite hard to read with switches inside switches, and mixing returns and breaks a bit. I would suggest refactoring it so there is only one switch "level"/"indent" in each scope, like this:

static inline int ubx_validate_ack_frame(uint16_t ubx_frame_size, uint8_t message_class, uint8_t message_id, uint16_t payload_size)
{
	switch (message_id) {
	case UBX_ACK_ACK:
		payload_size_expected = UBX_CFG_ACK_PAYLOAD_SZ;
		break;
	case UBX_ACK_NAK:
		payload_size_expected = UBX_CFG_NAK_PAYLOAD_SZ;
		break;
	default:
		break;
	}
	return -1;
}

switch (message_class) {
case UBX_CLASS_ACK:
	return ubx_validate_ack_frame(...);
case UBX_CLASS_CFG:
	return ubx_validate_cfg_frame(...);
default:
	return -1;
}

The above code is only an example of course, but splitting it into static inline helpers helps keep the indentations low and code more readable :)

case UBX_CFG_MSG:
payload_size_expected = UBX_CFG_MSG_PAYLOAD_SZ;
break;
default: return -1;
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is personal preference, but switches especially should keep consistent indentation

default:
        return -1;

Copy link
Member

Choose a reason for hiding this comment

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

it's in the coding style https://www.kernel.org/doc/html/v4.10/process/coding-style.html#indentation

Don’t put multiple statements on a single line unless you have something to hide

select GNSS_U_BLOX_PROTOCOL
select UART_USE_RUNTIME_CONFIGURE
help
Enable U-BLOX M10 GNSS modem driver.
Copy link
Member

Choose a reason for hiding this comment

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

nit: indentation is off

.message_id = msg_id,
};

(void) ubx_m10_modem_ubx_script_fill(dev);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
(void) ubx_m10_modem_ubx_script_fill(dev);
(void)ubx_m10_modem_ubx_script_fill(dev);

same for the rest of the file

Comment on lines 249 to 251
data->script.request = (struct ubx_frame_t *) data->request_buf;
data->script.response = (struct ubx_frame_t *) data->response_buf;
data->script.match = (struct ubx_frame_t *) data->match_buf;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
data->script.request = (struct ubx_frame_t *) data->request_buf;
data->script.response = (struct ubx_frame_t *) data->response_buf;
data->script.match = (struct ubx_frame_t *) data->match_buf;
data->script.request = (struct ubx_frame_t *)data->request_buf;
data->script.response = (struct ubx_frame_t *)data->response_buf;
data->script.match = (struct ubx_frame_t *)data->match_buf;

Comment on lines 674 to 675
payload->config_blocks[index].flags = enable |
UBX_CFG_GNSS_FLAG_SGN_CNF_GPS_L1C_A;
Copy link
Member

Choose a reason for hiding this comment

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

is this correct? boolean | value? guess it's fine it just looks odd to me, maybe add a define for the enable/disable bit and use an intermediate variable or something, just for clarity

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi @fabiobaltieri, i have addressed this comment in the latest push. Kindly check if the resolution is correct.

};

enum ubx_utc_standard {
UBX_UTC_AutoUTC = 0,
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
UBX_UTC_AutoUTC = 0,
UBX_UTC_AUTOUTC = 0,

case UBX_CFG_MSG:
payload_size_expected = UBX_CFG_MSG_PAYLOAD_SZ;
break;
default: return -1;
Copy link
Member

Choose a reason for hiding this comment

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

it's in the coding style https://www.kernel.org/doc/html/v4.10/process/coding-style.html#indentation

Don’t put multiple statements on a single line unless you have something to hide

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(modem_ubx, CONFIG_MODEM_MODULES_LOG_LEVEL);

#define MODEM_UBX_STATE_ATTACHED_BIT (0)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
#define MODEM_UBX_STATE_ATTACHED_BIT (0)
#define MODEM_UBX_STATE_ATTACHED_BIT 0

@mayankmahajan-nxp mayankmahajan-nxp force-pushed the gnss_u_blox_m10_driver_and_modem_ubx branch from e28e0f8 to cfb21fd Compare March 21, 2024 11:32
@zephyrbot zephyrbot requested a review from tomi-font March 21, 2024 11:32
@mayankmahajan-nxp
Copy link
Contributor Author

Hi @bjarki-trackunit and @fabiobaltieri, thank you for the review. I have addressed the latest comments. Kindly have a look at your convenience.

@mayankmahajan-nxp mayankmahajan-nxp force-pushed the gnss_u_blox_m10_driver_and_modem_ubx branch from cfb21fd to 663b3b6 Compare March 21, 2024 11:41
drivers/gnss/CMakeLists.txt Outdated Show resolved Hide resolved
/* The datasheet of the device doesn't specify boot time. But 1 sec helped significantly. */
#define UBX_M10_BOOT_TIME_MS 1000

enum MODEM_MODULE {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
enum MODEM_MODULE {
enum modem_module {

if you really want an enum here, though to me it looks like you could just replace this with a boolean and everything would be come a tiny bit simpler, do you forsee this getting more values in the future? The whole ubx_m10_modem_module_switch just looks really really weird right now

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@fabiobaltieri We don't foresee more modem modules being used in this driver. We could utilize a boolean macro, but i thought enum improves readability. I would prefer an enum here, but if you want i will change this as you said.

Copy link
Member

@fabiobaltieri fabiobaltieri Mar 21, 2024

Choose a reason for hiding this comment

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

Ok, I think it should be changed, I mean this would maybe make sense if you wanted to do stuff like ubx_m10_modem_module_switch(MODEM_MODULE_CHAT, MODEM_MODULE_CHAT);, it's just odd to have an enum for what is effectively a boolean and having having switch with default statement and error path for what should just be a if. @bjarki-trackunit?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes @fabiobaltieri, I understand your point now. Passing two arguments to ubx_m10_modem_module_switch isn't required. Would the following work?

/**
 * @brief Changes modem module (chat or ubx) attached to the uart pipe.
 * @param dev Dev instance
 * @param change_from_to 0 for changing from "chat" to "ubx", 1 for changing from "ubx" to "chat"
 * @returns 0 if successful
 * @returns negative errno code if failure
 */
static int ubx_m10_modem_module_change(const struct device *dev, bool change_from_to)
{
	struct ubx_m10_data *data = dev->data;
	int ret;

	if (change_from_to == 0) {
		modem_chat_release(&data->chat);
		ret = modem_ubx_attach(&data->ubx, data->uart_pipe);
	} else { /* change_from_to == 1 */
		modem_ubx_release(&data->ubx);
		ret = modem_chat_attach(&data->chat, data->uart_pipe);
	}

	if (ret < 0) {
		(void)modem_pipe_close(data->uart_pipe);
	}

	return ret;
}

Ps. I would prefer an enum here (or a couple of macros). Because without these, while calling ubx_m10_modem_module_change, we have to pass a literal. For now I have pushed the changes as per your comments.

enum modem_module_change {
	MODEM_MODULE_CHAT_TO_UBX = 0,
	MODEM_MODULE_UBX_TO_CHAT = 1,
};

Copy link
Collaborator

@bjarki-andreasen bjarki-andreasen Mar 26, 2024

Choose a reason for hiding this comment

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

you could just call release followed by attach. not sure a helper function is required for this :) I would create two helpers named switch_to_chat and switch_to_ubx, instead of passing arguments to a single function :)

subsys/modem/Kconfig Outdated Show resolved Hide resolved
@tomi-font tomi-font removed their request for review March 21, 2024 11:49
@mayankmahajan-nxp mayankmahajan-nxp force-pushed the gnss_u_blox_m10_driver_and_modem_ubx branch from 7845805 to da39d63 Compare March 22, 2024 04:34
@mayankmahajan-nxp
Copy link
Contributor Author

Hi @fabiobaltieri, thank you for the review. I have addressed the comments.

Copy link
Member

@fabiobaltieri fabiobaltieri left a comment

Choose a reason for hiding this comment

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

Heya, just a comment on definitions, sorry I think I asked you to drop the parenthesis to the literal ones before, I meant to ask that only for definitions with a single literal, the others should have them otherwise weird things can happen depending on how they are used.

#define UBX_CFG_NAK_PAYLOAD_SZ 2
#define UBX_CFG_RATE_PAYLOAD_SZ 6
#define UBX_CFG_PRT_POLL_PAYLOAD_SZ 1
#define UBX_CFG_PRT_POLL_FRM_SZ UBX_FRM_SZ_WO_PAYLOAD + UBX_CFG_PRT_POLL_PAYLOAD_SZ
Copy link
Member

Choose a reason for hiding this comment

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

this and the other defines with operations should be parenthesized, just to be sure there's no hidden operation priority errors

#define UBX_CFG_PRT_PORT_MODE_STOP_BITS_1 0U
#define UBX_CFG_PRT_PORT_MODE_STOP_BITS_1_HALF BIT(12)
#define UBX_CFG_PRT_PORT_MODE_STOP_BITS_2 BIT(13)
#define UBX_CFG_PRT_PORT_MODE_STOP_BITS_HALF BIT(12) | BIT(13)
Copy link
Member

Choose a reason for hiding this comment

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

same for this one, just the ones with multiple operands

#define UBX_CFG_GNSS_FLAG_DISABLE 0U
#define UBX_CFG_GNSS_FLAG_SGN_CNF_SHIFT 16
/* When gnss_id is 0 (GPS) */
#define UBX_CFG_GNSS_FLAG_SGN_CNF_GPS_L1C_A 0x01 << UBX_CFG_GNSS_FLAG_SGN_CNF_SHIFT
Copy link
Member

Choose a reason for hiding this comment

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

same

MODEM_UBX: Adds Support for UBX Messages in Modem Subsystem.

GNSS API Supported: get_supported_systems, set_fix_rate, get_fix_rate,
	set_enabled_systems, get_enabled_systems, set_navigation_mode,
	get_navigation_mode.

Boards Tested: MIMXRT1062_FMURT6, VMU_RT1170.

Note: Partial support for U-BLOX Messages is provided as of now.

Signed-off-by: Sumit Batra <sumit.batra@nxp.com>
Signed-off-by: Mayank Mahajan <mayankmahajan.x@nxp.com>
@mayankmahajan-nxp mayankmahajan-nxp force-pushed the gnss_u_blox_m10_driver_and_modem_ubx branch from da39d63 to 1be3a1d Compare March 25, 2024 10:24
@mayankmahajan-nxp
Copy link
Contributor Author

Heya, just a comment on definitions, sorry I think I asked you to drop the parenthesis to the literal ones before, I meant to ask that only for definitions with a single literal, the others should have them otherwise weird things can happen depending on how they are used.

Hi @fabiobaltieri, appreciate the review. I have addressed the comments. Kindly check at your convenience.

Copy link
Collaborator

@bjarki-andreasen bjarki-andreasen left a comment

Choose a reason for hiding this comment

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

Nice work!

@fabiobaltieri fabiobaltieri merged commit 3d81167 into zephyrproject-rtos:main Mar 26, 2024
21 checks passed
@JordanYates
Copy link
Collaborator

JordanYates commented Jul 13, 2024

I know I'm a few months late here, but I'm curious how this works given that the Ublox protocol used on the M10 chipsets no longer support the old dedicated CFG-* messages?
image

The following struct for example no longer exists in the M10 interface description:

struct ubx_cfg_rate_payload {
uint16_t meas_rate_ms;
uint16_t nav_rate;
uint16_t time_ref;
};

image

@mayankmahajan-nxp
Copy link
Contributor Author

Hi @JordanYates, thank you for the comment. I will look into this and get back to you soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: Devicetree Binding PR modifies or adds a Device Tree binding area: GNSS area: Modem area: Samples Samples
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants