NTN Support and Application Feasibility on nRF9151

Inquiry: NTN Support and Application Feasibility on nRF9151

I would like to seek clarification regarding Non-Terrestrial Network (NTN) support on the nRF9151.

There have been discussions and references suggesting NTN capability on the nRF9151, and I would like to better understand the current level of support and practical limitations.

Specifically, I am interested in the following:

1. Resource Availability

  • Which nRF Connect SDK versions include sample applications or support for NTN on nRF9151?

  • Are there example projects, reference code, or documentation demonstrating NTN connectivity in practice?

  • If official samples are not yet available, is there a roadmap or expected timeline for when they might be provided?

  • What type of antenna is required or recommended for NTN operation on the nRF9151?

2. Application Feasibility

We are currently using the nRF9151 with a Hologram eSIM in India, and our solution is working reliably:

  • connectivity

  • Cellular IoT stack

  • Data transmission to a CoAP server

Given this setup:

  • Is a similar application architecture feasible over NTN?

  • Can CoAP-based data transmission be supported over NTN in a comparable manner?

3. SDK Support and Examples

  • I could not find any nRF Connect SDK samples, documentation, or application notes specifically related to NTN on nRF9151

  • Are there any example applications, experimental features, or recommended references available?

  • If not, could you please advise on the current status and roadmap for NTN support on nRF9151?

Any guidance, documentation links, or clarification would be greatly appreciated.

Thank you for your support.

Parents
  • Hi Priyesh,
    To fill out some more information not already answered by Achim:

    Which nRF Connect SDK versions include sample applications or support for NTN on nRF9151?

    The NCS version you need is v3.2.0. 

    We recommend starting with either of this two options:

    • The Serial Modem Application (modem only apporach, AT commands)
    • Asset Tracker Template configured for satellite connectivity.

    Note: To ensure NTN support is correctly implemented, please refer to our dedicated NTN use case doc and NTN and TN use case doc, as the standard template is not configured for NTN by default.

    If official samples are not yet available, is there a roadmap or expected timeline for when they might be provided?

    Please reach out to the regional sales manager for your area as we cannot talk about roadmap on DevZone.

    What type of antenna is required or recommended for NTN operation on the nRF9151?

    We recommend reaching out to our antenna solution partners. They are equipped to provide detailed guidance on the most suitable antenna for your specific application.

    NTN NB-IoT require optimal link conditions, and the choice of antenna depends on multiple factors, including whether the application supports both LTE and NTN or NTN alone, as well as the physical constraints of the product.

    For applications targeting both LTE and NTN, it is essential to select a wideband antenna that can meet the performance and form factor requirements of both technologies. The antenna must support a broad frequency range to satisfy the performance criteria of LTE networks and the relevant satellite operators.

    The nRF9151 SMA DK includes Taoglas (LTE/NTN/NR+) and Kyocera (GNSS) antennas.

    If not, could you please advise on the current status and roadmap for NTN support on nRF9151?

    The alpha version of the NTN modem FW is now public and can be downloaded here. This firmware is only for nRF9151 LACA A1 SiP.

    Regards,
    Benjamin

  • Hi Benjamin,

    Thanks for the info.

    Quick question:


    Can we connect to a CoAP server over NTN like we do on cellular, or is NTN only for location updates?
    Is custom payload upload supported beyond basic asset tracking?

    Regards ,
    Priyesh

  • Hi Priyesh,

    The AT#XPING (and other # style commands) are native to the Serial Modem application. They will not work on regular samples.

    For the failing sample, what exact CoAP sample are you referring to?

    Regards,
    Benjamin

  • Hi Benjamin,

    The #PING function was not working initially. However, I made a few changes to the code. Up to the connection stage, I used AT commands, and for the remaining operations I utilized functions from the socket library and CoAP library. After these modifications, it worked successfully, and I am now able to send payloads to my CoAP server.

    /*
     * Copyright (c) 2021 Nordic Semiconductor ASA
     *
     * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
     */
    
    #include <string.h>
    #include <zephyr/kernel.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <nrf_modem_at.h>
    #include <modem/nrf_modem_lib.h>
    #include <modem/at_monitor.h>
    #include <modem/modem_info.h>
    
    #include <stdio.h>
    #include <ncs_version.h>
    
    #include <zephyr/kernel.h>
    #include <zephyr/net/socket.h>
    
    #include <zephyr/logging/log.h>
    #include <modem/nrf_modem_lib.h>
    #include <modem/lte_lc.h>
    
    #include <zephyr/random/random.h>
    #include <zephyr/net/coap.h>
    
    #include <modem/ntn.h>
    
    #define SEND_REQ_THREAD_STACK_SIZE 1024
    #define SEND_REQ_THREA_PRIORITY 5
    
    /* Define the macros for the CoAP version and message length */
    #define APP_COAP_VERSION 1
    #define APP_COAP_MAX_MSG_LEN 1280
    
    K_SEM_DEFINE(con_sem, 0, 1);
    
    /* Declare the buffer coap_buf to receive the response. */
    static uint8_t coap_buf[APP_COAP_MAX_MSG_LEN];
    
    /* Define the CoAP message token next_token */
    static uint16_t next_token;
    
    static int sock;
    static struct sockaddr_storage server;
    
    K_SEM_DEFINE(cereg_sem, 0, 1);
    
    uint8_t response[128] = {0};
    
    LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);
    
    AT_MONITOR(cmd_rsp, ANY, cmd_mon, ACTIVE);
    
    /* AT monitor for network notifications */
    AT_MONITOR(network, "CEREG", cereg_mon);
    
    static int cereg_status;
    enum cereg_status {
    	NO_NETWORK = 0,
    	HOME = 1,
    	SEARCHING = 2,
    	DENIED = 3,
    	UNKNOWN = 4,
    	ROAMING = 5,
    	UICC_FAILURE = 90
    };
    
    static const char *cereg_str_get(enum cereg_status status)
    {
    	switch (status) {
    	case NO_NETWORK:
    		return "no network";
    	case HOME:
    		return "home";
    	case SEARCHING:
    		return "searching";
    	case DENIED:
    		return "denied";
    	case UNKNOWN:
    		return "unknown";
    	case ROAMING:
    		return "roaming";
    	case UICC_FAILURE:
    		return "UICC failure";
    	default:
    		return NULL;
    	}
    }
    
    static void cereg_mon(const char *notif)
    {
    	const char *cereg_status_str;
    
    	cereg_status = atoi(notif + strlen("+CEREG: "));
    	cereg_status_str = cereg_str_get(cereg_status);
    
    	if (!cereg_status_str) {
    		LOG_ERR("Network registration status unknown: %d", cereg_status);
    		return;
    	}
    
    	LOG_INF("Network registration status: %s", cereg_status_str);
    
    	if ((cereg_status == HOME) || (cereg_status == ROAMING)) {
    		k_sem_give(&cereg_sem);
    	}
    }
    
    static void cmd_mon(const char *notif)
    {
    	LOG_INF("%s", notif);
    }
    
    /**@brief Resolves the configured hostname. */
    static int server_resolve(void)
    {
    	int err;
    	struct addrinfo *result;
    	struct addrinfo hints = {
    		.ai_family = AF_INET,
    		.ai_socktype = SOCK_DGRAM};
    	char ipv4_addr[NET_IPV4_ADDR_LEN];
    
    	err = getaddrinfo(CONFIG_COAP_SERVER_HOSTNAME, NULL, &hints, &result);
    	if (err != 0)
    	{
    		LOG_ERR("ERROR: getaddrinfo failed %d", err);
    		return -EIO;
    	}
    
    	if (result == NULL)
    	{
    		LOG_ERR("ERROR: Address not found");
    		return -ENOENT;
    	}
    
    	/* IPv4 Address. */
    	struct sockaddr_in *server4 = ((struct sockaddr_in *)&server);
    
    	server4->sin_addr.s_addr =
    		((struct sockaddr_in *)result->ai_addr)->sin_addr.s_addr;
    	server4->sin_family = AF_INET;
    	server4->sin_port = htons(CONFIG_COAP_SERVER_PORT);
    
    	inet_ntop(AF_INET, &server4->sin_addr.s_addr, ipv4_addr,
    			  sizeof(ipv4_addr));
    	LOG_INF("IPv4 Address found %s", ipv4_addr);
    
    	/* Free the address. */
    	freeaddrinfo(result);
    
    	return 0;
    }
    
    /**@brief Initialize the CoAP client */
    static int client_init(void)
    {
    	int err;
    
    	sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    	if (sock < 0)
    	{
    		LOG_ERR("Failed to create CoAP socket: %d.", errno);
    		return -errno;
    	}
    
    	err = connect(sock, (struct sockaddr *)&server,
    				  sizeof(struct sockaddr_in));
    	if (err < 0)
    	{
    		LOG_ERR("Connect failed : %d", errno);
    		return -errno;
    	}
    
    	LOG_INF("Successfully connected to server");
    
    	/* Generate a random token after the socket is connected */
    	next_token = sys_rand32_get();
    
    	k_sem_give(&con_sem);
    
    	return 0;
    }
    
    /**@brief Send CoAP POST request. */
    static int client_post_send(void)
    {
    	int err;
    	struct coap_packet request;
    
    	next_token = sys_rand32_get();
    
    	/* Initialize the CoAP packet and append the resource path */
    	err = coap_packet_init(&request, coap_buf, sizeof(coap_buf),
    						   APP_COAP_VERSION, COAP_TYPE_CON,
    						   sizeof(next_token), (uint8_t *)&next_token,
    						   COAP_METHOD_POST, coap_next_id());
    	if (err < 0)
    	{
    		LOG_ERR("Failed to create CoAP request, %d", err);
    		return err;
    	}
    
    	err = coap_packet_append_option(&request, COAP_OPTION_URI_HOST,
    									(uint8_t *)CONFIG_COAP_SERVER_HOSTNAME,
    									strlen(CONFIG_COAP_SERVER_HOSTNAME));
    	if (err < 0)
    	{
    		LOG_ERR("Failed to encode CoAP option, %d", err);
    		return err;
    	}
    
    	err = coap_packet_append_option(&request, COAP_OPTION_URI_PATH,
    									(uint8_t *)CONFIG_COAP_TX_RESOURCE,
    									strlen(CONFIG_COAP_TX_RESOURCE));
    	if (err < 0)
    	{
    		LOG_ERR("Failed to encode CoAP option, %d", err);
    		return err;
    	}
    
    	/* Add payload marker */
    	err = coap_packet_append_payload_marker(&request);
    	if (err < 0)
    	{
    		LOG_ERR("Failed to append payload marker, %d", err);
    		return err;
    	}
    
    	/* Format and append as payload */
    	char payload[64];
    
    	int len = snprintf(payload, sizeof(payload), "1234567890123456789012345678901");
    	if (len < 0 || len >= sizeof(payload))
    	{
    		LOG_ERR("Failed to format counter payload");
    		return -EINVAL;
    	}
    
    	err = coap_packet_append_payload(&request, (uint8_t *)payload, len);
    	if (err < 0)
    	{
    		LOG_ERR("Failed to append payload, %d", err);
    		return err;
    	}
    
    	err = send(sock, request.data, request.offset, 0);
    	if (err < 0)
    	{
    		LOG_ERR("Failed to send CoAP request, %d", errno);
    		return -errno;
    	}
    
    	LOG_INF("CoAP POST request sent: Token 0x%04x", next_token);
    
    	return 0;
    }
    
    /**@brief Handles responses from the remote CoAP server. */
    static int client_handle_response(uint8_t *buf, int received)
    {
    	struct coap_packet reply;
    	uint8_t token[8];
    	uint16_t token_len;
    	const uint8_t *payload;
    	uint16_t payload_len;
    	uint8_t temp_buf[128];
    	/* Parse the received CoAP packet */
    	int err = coap_packet_parse(&reply, buf, received, NULL, 0);
    	if (err < 0)
    	{
    		LOG_ERR("Malformed response received: %d", err);
    		return err;
    	}
    
    	/* Confirm the token in the response matches the token sent */
    	token_len = coap_header_get_token(&reply, token);
    	if ((token_len != sizeof(next_token)) ||
    		(memcmp(&next_token, token, sizeof(next_token)) != 0))
    	{
    		LOG_ERR("Invalid token received: 0x%02x%02x",
    				token[1], token[0]);
    		return 0;
    	}
    
    	/* Retrieve the payload and confirm it's nonzero */
    	payload = coap_packet_get_payload(&reply, &payload_len);
    
    	if (payload_len > 0)
    	{
    		snprintf(temp_buf, MIN(payload_len + 1, sizeof(temp_buf)), "%s", payload);
    	}
    	else
    	{
    		strcpy(temp_buf, "EMPTY");
    	}
    
    	/* Log the header code, token and payload of the response */
    	LOG_INF("CoAP response: Code 0x%x, Token 0x%02x%02x, Payload: %s",
    			coap_header_get_code(&reply), token[1], token[0], (char *)temp_buf);
    
    	return 0;
    }
    
    int main(void)
    {
    	int err;
    
    	int received;
    
    	/* Register NTN handler (safe even if you start in TN mode) */
    	// ntn_register_handler(app_ntn_event_handler);
    
    	LOG_INF("AT Monitor sample started");
    
    	err = nrf_modem_lib_init();
    	if (err)
    	{
    		LOG_ERR("Modem library initialization failed, error: %d", err);
    		return 0;
    	}
    
    	err = nrf_modem_at_cmd(response, sizeof(response), "AT+CFUN=0");
    	if (err)
    	{
    		LOG_ERR("Failed to read AT+CFUN=0, err %d", err);
    		return 0;
    	}
    
    	LOG_INF("%s", response);
    	k_msleep(1000);
    	err = nrf_modem_at_cmd(response, sizeof(response), "AT%%XFACTORYRESET=0");
    	if (err)
    	{
    		LOG_ERR("Failed to read xfactoryreset, err %d", err);
    		return 0;
    	}
    
    	LOG_INF("%s", response);
    	k_msleep(1000);
    	err = nrf_modem_at_cmd(response, sizeof(response), "AT%%XSYSTEMMODE=0,0,0,0,1");
    	if (err)
    	{
    		LOG_ERR("Failed to read XSYSTEMMODE, err %d", err);
    		return 0;
    	}
    
    	LOG_INF("%s", response);
    	k_msleep(1000);
    	err = nrf_modem_at_cmd(response, sizeof(response), "AT%%XBANDLOCK=2,,\"255,256\"");
    	if (err)
    	{
    		LOG_ERR("Failed to read XBANDLOCK, err %d", err);
    		return 0;
    	}
    
    	LOG_INF("%s", response);
    	k_msleep(1000);
    	err = nrf_modem_at_cmd(response, sizeof(response), "AT%%LOCATION=2,\"36.33144938\",\"-119.292130\",\"0\",0,0");
    	if (err)
    	{
    		LOG_ERR("Failed to read LOCATION, err %d", err);
    		return 0;
    	}
    
    	LOG_INF("%s", response);
    	k_msleep(1000);
    	err = nrf_modem_at_cmd(response, sizeof(response), "AT+CGDCONT=0,\"IP\",\"data.mono\"");
    	if (err)
    	{
    		LOG_ERR("Failed to read CGDCONT, err %d", err);
    		return 0;
    	}
    
    	LOG_INF("%s", response);
    	k_msleep(1000);
    	err = nrf_modem_at_cmd(response, sizeof(response), "AT%%MDMEV=2");
    	if (err)
    	{
    		LOG_ERR("Failed to read MDMEV, err %d", err);
    		return 0;
    	}
    
    	LOG_INF("%s", response);
    	k_msleep(1000);
    	err = nrf_modem_at_cmd(response, sizeof(response), "AT+CSCON=3");
    	if (err)
    	{
    		LOG_ERR("Failed to read CSCON, err %d", err);
    		return 0;
    	}
    
    	LOG_INF("%s", response);
    	k_msleep(1000);
    
    	err = nrf_modem_at_cmd(response, sizeof(response), "AT+CNEC=24");
    	if (err)
    	{
    		LOG_ERR("Failed to read CEREG, err %d", err);
    		return 0;
    	}
    
    	LOG_INF("%s", response);
    	k_msleep(1000);
    
    	err = nrf_modem_at_cmd(response, sizeof(response), "AT+CEREG=5");
    	if (err)
    	{
    		LOG_ERR("Failed to read CEREG, err %d", err);
    		return 0;
    	}
    
    	LOG_INF("%s", response);
    	k_msleep(1000);
    
    	err = nrf_modem_at_cmd(response, sizeof(response), "AT+CFUN=1");
    	if (err)
    	{
    		LOG_ERR("Failed to read CEREG, err %d", err);
    		return 0;
    	}
    
    	LOG_INF("%s", response);
    
    	LOG_INF("Waiting for network");
    	err = k_sem_take(&cereg_sem, K_SECONDS(200));
    
    	if ((cereg_status == HOME) || (cereg_status == ROAMING)) {
    		LOG_INF("Network connection ready");
    	} else {
    		if (err == -EAGAIN) {
    			LOG_ERR("Network connection timed out");
    		}
    		LOG_WRN("Continuing without network");
    	}
    
    	k_msleep(1000);
    
    	if (server_resolve() != 0)
    	{
    		LOG_ERR("Failed to resolve server name");
    		return 0;
    	}
    
    	if (client_init() != 0)
    	{
    		LOG_ERR("Failed to initialize client");
    		return 0;
    	}
    
    	while (1)
    	{
    		/* Receive response from the CoAP server */
    		received = recv(sock, coap_buf, sizeof(coap_buf), 0);
    
    		if (received < 0)
    		{
    			LOG_ERR("Socket error: %d, exit", errno);
    			break;
    		}
    		else if (received == 0)
    		{
    			LOG_INF("Empty datagram");
    			continue;
    		}
    
    		/* Parse the received CoAP packet */
    		err = client_handle_response(coap_buf, received);
    		if (err < 0)
    		{
    			LOG_ERR("Invalid response, exit");
    			break;
    		}
    	}
    
    	(void)close(sock);
    
    	return 0;
    }
    
    void send_req_thread(void *arg1, void *arg2, void *arg3)
    {
    	k_sem_take(&con_sem, K_FOREVER);
    	while (1)
    	{
    		client_post_send();
    		k_sleep(K_SECONDS(30));
    	}
    }
    
    K_THREAD_DEFINE(send_req, SEND_REQ_THREAD_STACK_SIZE, send_req_thread, NULL, NULL, NULL,
    				SEND_REQ_THREA_PRIORITY, 0, 0);

    How would I confirm whether the network used to send the payload was exclusively NTN. I tested this in two different locations: first while inside a room, where it still connected successfully, and then approximately 30 km away, where it continued to connect without any issues.

    Could you please advise how I can confirm that the connection was established specifically over NTN? Additionally, could you explain how the set location functionality works in this context?

    Kind regards,
    Priyesh shahi

  • Hi,
    You could use AT+COPS? to check what type of network you are connected to. The value for NTN NB-IoT is 14.

    I guess you are referring to the %LOCATION set command? What specific questions do you have?

    Best regards,
    Benjamin

  • Hi Benjamin,

    Thank you for the clarification regarding AT+COPS?.

    In my testing, I set the location of the room where the board was initially operating, and NTN connectivity worked there as expected. However, I then moved approximately 30 km away from that location, and the device continued to connect successfully using the same previously configured location.

    This is where my confusion arises — how is the device able to connect to NTN with an incorrect location configured? Does the modem internally determine or refine its location even if the %LOCATION value is not accurate? Or is the configured location only used as an initial hint for acquisition?

    Best regards,

    Priyesh

  • Hi Priyesh,
    That sounds strange indeed. If the modem is moved more than 400m during or in between connections you need to update the location. This can be done externally by using the %LOCAITON command or internally using a procedure involving the GPS. What exact firmware are you using?

    Could you share a modem trace? That will help us see what exactly is going on.

    Best regards,
    Benjamin

Reply
  • Hi Priyesh,
    That sounds strange indeed. If the modem is moved more than 400m during or in between connections you need to update the location. This can be done externally by using the %LOCAITON command or internally using a procedure involving the GPS. What exact firmware are you using?

    Could you share a modem trace? That will help us see what exactly is going on.

    Best regards,
    Benjamin

Children
Related