diff --git a/drivers/gnss/Kconfig.emul b/drivers/gnss/Kconfig.emul index b6667f36864bd..cb3cfdd3e6647 100644 --- a/drivers/gnss/Kconfig.emul +++ b/drivers/gnss/Kconfig.emul @@ -9,3 +9,12 @@ config GNSS_EMUL select TIMEOUT_64BIT help Enable emulated GNSS driver. + +config GNSS_EMUL_MANUAL_UPDATE + bool "Internal state manually updated through gnss_emul_set_data" + depends on GNSS_EMUL + help + The internal state of the GNSS emulator (location, time, etc) + must be updated through gnss_emul_set_data, instead of automatically + transitioning to hardcoded states at hardcoded times. Once the current + time is set, the published time automatically increments. diff --git a/drivers/gnss/gnss_emul.c b/drivers/gnss/gnss_emul.c index ac1761d84c855..5c706c9ce105f 100644 --- a/drivers/gnss/gnss_emul.c +++ b/drivers/gnss/gnss_emul.c @@ -35,10 +35,12 @@ struct gnss_emul_data { struct k_sem lock; int64_t resume_timestamp_ms; int64_t fix_timestamp_ms; + int64_t boot_realtime_ms; uint32_t fix_interval_ms; enum gnss_navigation_mode nav_mode; gnss_systems_t enabled_systems; struct gnss_data data; + bool active; #ifdef CONFIG_GNSS_SATELLITES struct gnss_satellite satellites[GNSS_EMUL_SUPPORTED_SYSTEMS_COUNT]; @@ -73,24 +75,6 @@ static void gnss_emul_update_fix_timestamp(const struct device *dev, bool resumi } } -static bool gnss_emul_fix_is_acquired(const struct device *dev) -{ - struct gnss_emul_data *data = dev->data; - int64_t time_since_resume; - - time_since_resume = data->fix_timestamp_ms - data->resume_timestamp_ms; - return time_since_resume >= GNSS_EMUL_FIX_ACQUIRE_TIME_MS; -} - -#ifdef CONFIG_PM_DEVICE -static void gnss_emul_clear_fix_timestamp(const struct device *dev) -{ - struct gnss_emul_data *data = dev->data; - - data->fix_timestamp_ms = 0; -} -#endif - static void gnss_emul_schedule_work(const struct device *dev) { struct gnss_emul_data *data = dev->data; @@ -110,7 +94,7 @@ static bool gnss_emul_is_resumed(const struct device *dev) { struct gnss_emul_data *data = dev->data; - return data->fix_timestamp_ms > 0; + return data->active; } static void gnss_emul_lock(const struct device *dev) @@ -140,7 +124,7 @@ static int gnss_emul_set_fix_rate(const struct device *dev, uint32_t fix_interva return 0; } -static int gnss_emul_get_fix_rate(const struct device *dev, uint32_t *fix_interval_ms) +int gnss_emul_get_fix_rate(const struct device *dev, uint32_t *fix_interval_ms) { struct gnss_emul_data *data = dev->data; @@ -161,8 +145,7 @@ static int gnss_emul_set_navigation_mode(const struct device *dev, return 0; } -static int gnss_emul_get_navigation_mode(const struct device *dev, - enum gnss_navigation_mode *mode) +int gnss_emul_get_navigation_mode(const struct device *dev, enum gnss_navigation_mode *mode) { struct gnss_emul_data *data = dev->data; @@ -182,7 +165,7 @@ static int gnss_emul_set_enabled_systems(const struct device *dev, gnss_systems_ return 0; } -static int gnss_emul_get_enabled_systems(const struct device *dev, gnss_systems_t *systems) +int gnss_emul_get_enabled_systems(const struct device *dev, gnss_systems_t *systems) { struct gnss_emul_data *data = dev->data; @@ -190,42 +173,6 @@ static int gnss_emul_get_enabled_systems(const struct device *dev, gnss_systems_ return 0; } -#ifdef CONFIG_PM_DEVICE -static void gnss_emul_resume(const struct device *dev) -{ - gnss_emul_update_fix_timestamp(dev, true); -} - -static void gnss_emul_suspend(const struct device *dev) -{ - gnss_emul_clear_fix_timestamp(dev); -} - -static int gnss_emul_pm_action(const struct device *dev, enum pm_device_action action) -{ - int ret = 0; - - gnss_emul_lock(dev); - - switch (action) { - case PM_DEVICE_ACTION_SUSPEND: - gnss_emul_suspend(dev); - break; - - case PM_DEVICE_ACTION_RESUME: - gnss_emul_resume(dev); - break; - - default: - ret = -ENOTSUP; - break; - } - - gnss_emul_unlock(dev); - return ret; -} -#endif - static int gnss_emul_api_set_fix_rate(const struct device *dev, uint32_t fix_interval_ms) { int ret = -ENODEV; @@ -346,35 +293,26 @@ static DEVICE_API(gnss, api) = { .get_supported_systems = gnss_emul_api_get_supported_systems, }; -static void gnss_emul_clear_data(const struct device *dev) +void gnss_emul_clear_data(const struct device *dev) { struct gnss_emul_data *data = dev->data; memset(&data->data, 0, sizeof(data->data)); } -static void gnss_emul_set_fix(const struct device *dev) -{ - struct gnss_emul_data *data = dev->data; - - data->data.info.satellites_cnt = 8; - data->data.info.hdop = 100; - data->data.info.fix_status = GNSS_FIX_STATUS_GNSS_FIX; - data->data.info.fix_quality = GNSS_FIX_QUALITY_GNSS_SPS; -} - static void gnss_emul_set_utc(const struct device *dev) { struct gnss_emul_data *data = dev->data; + int64_t timestamp_realtime; time_t timestamp; struct tm datetime; uint16_t millisecond; - timestamp = (time_t)(data->fix_timestamp_ms / 1000); + timestamp_realtime = data->boot_realtime_ms + data->fix_timestamp_ms; + timestamp = (time_t)(timestamp_realtime / 1000); gmtime_r(×tamp, &datetime); - millisecond = (uint16_t)(data->fix_timestamp_ms % 1000) - + (uint16_t)(datetime.tm_sec * 1000); + millisecond = (uint16_t)(timestamp_realtime % 1000) + (uint16_t)(datetime.tm_sec * 1000); data->data.utc.hour = datetime.tm_hour; data->data.utc.millisecond = millisecond; @@ -384,6 +322,40 @@ static void gnss_emul_set_utc(const struct device *dev) data->data.utc.century_year = datetime.tm_year % 100; } +#ifdef CONFIG_GNSS_EMUL_MANUAL_UPDATE + +void gnss_emul_set_data(const struct device *dev, const struct navigation_data *nav, + const struct gnss_info *info, int64_t boot_realtime_ms) +{ + struct gnss_emul_data *data = dev->data; + + data->data.nav_data = *nav; + data->data.info = *info; + data->boot_realtime_ms = boot_realtime_ms; + gnss_emul_set_utc(dev); +} + +#else + +static bool gnss_emul_fix_is_acquired(const struct device *dev) +{ + struct gnss_emul_data *data = dev->data; + int64_t time_since_resume; + + time_since_resume = data->fix_timestamp_ms - data->resume_timestamp_ms; + return time_since_resume >= GNSS_EMUL_FIX_ACQUIRE_TIME_MS; +} + +static void gnss_emul_set_fix(const struct device *dev) +{ + struct gnss_emul_data *data = dev->data; + + data->data.info.satellites_cnt = 8; + data->data.info.hdop = 100; + data->data.info.fix_status = GNSS_FIX_STATUS_GNSS_FIX; + data->data.info.fix_quality = GNSS_FIX_QUALITY_GNSS_SPS; +} + static void gnss_emul_set_nav_data(const struct device *dev) { struct gnss_emul_data *data = dev->data; @@ -395,6 +367,8 @@ static void gnss_emul_set_nav_data(const struct device *dev) data->data.nav_data.altitude = 20000; } +#endif /* CONFIG_GNSS_EMUL_MANUAL_UPDATE */ + #ifdef CONFIG_GNSS_SATELLITES static void gnss_emul_clear_satellites(const struct device *dev) { @@ -444,6 +418,11 @@ static void gnss_emul_work_handler(struct k_work *work) struct gnss_emul_data *data = CONTAINER_OF(dwork, struct gnss_emul_data, data_dwork); const struct device *dev = data->dev; +#ifdef CONFIG_GNSS_EMUL_MANUAL_UPDATE + /* Tick the timestamp */ + gnss_emul_set_utc(dev); +#else + /* Automatically update internal state if not done manually */ if (!gnss_emul_fix_is_acquired(dev)) { gnss_emul_clear_data(dev); } else { @@ -451,6 +430,7 @@ static void gnss_emul_work_handler(struct k_work *work) gnss_emul_set_utc(dev); gnss_emul_set_nav_data(dev); } +#endif /* CONFIG_GNSS_EMUL_MANUAL_UPDATE */ gnss_publish_data(dev, &data->data); @@ -463,27 +443,57 @@ static void gnss_emul_work_handler(struct k_work *work) gnss_emul_schedule_work(dev); } -static void gnss_emul_init_data(const struct device *dev) +static void gnss_emul_resume(const struct device *dev) { struct gnss_emul_data *data = dev->data; - data->dev = dev; - k_sem_init(&data->lock, 1, 1); - k_work_init_delayable(&data->data_dwork, gnss_emul_work_handler); + data->active = true; + gnss_emul_update_fix_timestamp(dev, true); + gnss_emul_schedule_work(dev); } -static int gnss_emul_init(const struct device *dev) +static void gnss_emul_suspend(const struct device *dev) { - gnss_emul_init_data(dev); + struct gnss_emul_data *data = dev->data; - if (pm_device_is_powered(dev)) { - gnss_emul_update_fix_timestamp(dev, true); - gnss_emul_schedule_work(dev); - } else { - pm_device_init_off(dev); + data->active = false; + gnss_emul_cancel_work(dev); + gnss_emul_clear_data(dev); +} + +static int gnss_emul_pm_action(const struct device *dev, enum pm_device_action action) +{ + int ret = 0; + + gnss_emul_lock(dev); + + switch (action) { + case PM_DEVICE_ACTION_SUSPEND: + gnss_emul_suspend(dev); + break; + + case PM_DEVICE_ACTION_RESUME: + gnss_emul_resume(dev); + break; + + default: + ret = -ENOTSUP; + break; } - return pm_device_runtime_enable(dev); + gnss_emul_unlock(dev); + return ret; +} + +static int gnss_emul_init(const struct device *dev) +{ + struct gnss_emul_data *data = dev->data; + + data->dev = dev; + k_sem_init(&data->lock, 1, 1); + k_work_init_delayable(&data->data_dwork, gnss_emul_work_handler); + + return pm_device_driver_init(dev, gnss_emul_pm_action); } #define GNSS_EMUL_NAME(inst, name) _CONCAT(name, inst) diff --git a/dts/bindings/gnss/zephyr,gnss-emul.yaml b/dts/bindings/gnss/zephyr,gnss-emul.yaml index eb7d9eec8acc3..10c4659dc0785 100644 --- a/dts/bindings/gnss/zephyr,gnss-emul.yaml +++ b/dts/bindings/gnss/zephyr,gnss-emul.yaml @@ -4,3 +4,5 @@ description: Zephyr emulated GNSS device compatible: "zephyr,gnss-emul" + +include: base.yaml diff --git a/include/zephyr/drivers/gnss/gnss_emul.h b/include/zephyr/drivers/gnss/gnss_emul.h new file mode 100644 index 0000000000000..4416ce26cfed6 --- /dev/null +++ b/include/zephyr/drivers/gnss/gnss_emul.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025 Embeint Pty Ltd + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_GNSS_GNSS_EMUL_H_ +#define ZEPHYR_DRIVERS_GNSS_GNSS_EMUL_H_ + +#include + +#include +#include + +/** + * @brief Clear all internal GNSS data of the emulator + * + * @param dev GNSS emulator device + */ +void gnss_emul_clear_data(const struct device *dev); + +/** + * @brief Set the internal GNSS data of the emulator + * + * @param dev GNSS emulator device + * @param nav Updated navigation state + * @param info Updated GNSS fix information + * @param boot_realtime_ms Unix timestamp associated with system boot + */ +void gnss_emul_set_data(const struct device *dev, const struct navigation_data *nav, + const struct gnss_info *info, int64_t boot_realtime_ms); + +/** + * @brief Retrieve the last configured fix rate, regardless of PM state + * + * @param dev GNSS emulator device + * @param fix_interval_ms Output fix interval + * + * @retval 0 On success + */ +int gnss_emul_get_fix_rate(const struct device *dev, uint32_t *fix_interval_ms); + +/** + * @brief Retrieve the last configured navigation mode, regardless of PM state + * + * @param dev GNSS emulator device + * @param mode Output navigation mode + * + * @retval 0 On success + */ +int gnss_emul_get_navigation_mode(const struct device *dev, enum gnss_navigation_mode *mode); + +/** + * @brief Retrieve the last configured systems, regardless of PM state + * + * @param dev GNSS emulator device + * @param systems Output GNSS systems + * + * @retval 0 On success + */ +int gnss_emul_get_enabled_systems(const struct device *dev, gnss_systems_t *systems); + +#endif /* ZEPHYR_DRIVERS_GNSS_GNSS_EMUL_H_ */ diff --git a/tests/drivers/gnss/gnss_emul/CMakeLists.txt b/tests/drivers/gnss/gnss_emul/CMakeLists.txt new file mode 100644 index 0000000000000..03db665f34ab0 --- /dev/null +++ b/tests/drivers/gnss/gnss_emul/CMakeLists.txt @@ -0,0 +1,12 @@ +# Copyright (c) 2025 Embeint Pty Ltd +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +project(gnss_emul) + +target_sources(app PRIVATE + src/main.c +) diff --git a/tests/drivers/gnss/gnss_emul/app.overlay b/tests/drivers/gnss/gnss_emul/app.overlay new file mode 100644 index 0000000000000..1746d8ccb9486 --- /dev/null +++ b/tests/drivers/gnss/gnss_emul/app.overlay @@ -0,0 +1,17 @@ +/* + * Copyright 2025 Embeint Pty Ltd + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + gnss = &gnss; + }; + + gnss: gnss { + compatible = "zephyr,gnss-emul"; + status = "okay"; + zephyr,pm-device-runtime-auto; + }; +}; diff --git a/tests/drivers/gnss/gnss_emul/prj.conf b/tests/drivers/gnss/gnss_emul/prj.conf new file mode 100644 index 0000000000000..dde94f372248d --- /dev/null +++ b/tests/drivers/gnss/gnss_emul/prj.conf @@ -0,0 +1,12 @@ +# Copyright (c) 2025 Embeint Pty Ltd +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_ZTEST=y +CONFIG_ZTEST_STACK_SIZE=4096 + +CONFIG_PM_DEVICE=y +CONFIG_PM_DEVICE_RUNTIME=y + +CONFIG_GNSS=y +CONFIG_GNSS_EMUL=y +CONFIG_GNSS_EMUL_MANUAL_UPDATE=y diff --git a/tests/drivers/gnss/gnss_emul/src/main.c b/tests/drivers/gnss/gnss_emul/src/main.c new file mode 100644 index 0000000000000..a76f2c4337880 --- /dev/null +++ b/tests/drivers/gnss/gnss_emul/src/main.c @@ -0,0 +1,161 @@ +/* + * Copyright 2025 Embeint Pty Ltd + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include +#include +#include +#include + +static void gnss_data_callback(const struct device *dev, const struct gnss_data *data); + +GNSS_DATA_CALLBACK_DEFINE(DEVICE_DT_GET(DT_ALIAS(gnss)), gnss_data_callback); +static K_SEM_DEFINE(gnss_data_published, 0, 1); +static struct gnss_data gnss_published_data; + +static void expected_pm_state(const struct device *dev, enum pm_device_state expected) +{ + enum pm_device_state state; + + zassert_equal(0, pm_device_state_get(dev, &state)); + zassert_equal(expected, state); +} + +static void gnss_data_callback(const struct device *dev, const struct gnss_data *data) +{ + gnss_published_data = *data; + k_sem_give(&gnss_data_published); +} + +static void print_time(const struct gnss_time *utc) +{ + printk("TIME: %02d/%02d/%02d %02d:%02d:%02d.%03d\n", utc->century_year, utc->month, + utc->month_day, utc->hour, utc->minute, utc->millisecond / 1000, + utc->millisecond % 1000); +} + +ZTEST(gnss_emul, test_config_functions) +{ + const struct device *dev = DEVICE_DT_GET(DT_ALIAS(gnss)); + enum gnss_navigation_mode mode; + gnss_systems_t systems; + uint32_t fix_rate; + + /* Booted into suspend mode */ + expected_pm_state(dev, PM_DEVICE_STATE_SUSPENDED); + + /* Configuration get API functions fail when suspended */ + zassert_equal(-ENODEV, gnss_get_enabled_systems(dev, &systems)); + zassert_equal(-ENODEV, gnss_get_navigation_mode(dev, &mode)); + zassert_equal(-ENODEV, gnss_get_fix_rate(dev, &fix_rate)); + + /* Configuration can be queried when enabled */ + zassert_equal(0, pm_device_runtime_get(dev)); + zassert_equal(0, gnss_set_enabled_systems(dev, GNSS_SYSTEM_GPS | GNSS_SYSTEM_GALILEO)); + zassert_equal(0, gnss_set_navigation_mode(dev, GNSS_NAVIGATION_MODE_HIGH_DYNAMICS)); + zassert_equal(0, gnss_set_fix_rate(dev, 1500)); + + zassert_equal(0, gnss_get_enabled_systems(dev, &systems)); + zassert_equal(0, gnss_get_navigation_mode(dev, &mode)); + zassert_equal(0, gnss_get_fix_rate(dev, &fix_rate)); + zassert_equal(GNSS_SYSTEM_GPS | GNSS_SYSTEM_GALILEO, systems); + zassert_equal(GNSS_NAVIGATION_MODE_HIGH_DYNAMICS, mode); + zassert_equal(1500, fix_rate); + + zassert_equal(0, pm_device_runtime_put(dev)); + + /* Fails again when suspended */ + zassert_equal(-ENODEV, gnss_get_enabled_systems(dev, &systems)); + zassert_equal(-ENODEV, gnss_get_navigation_mode(dev, &mode)); + zassert_equal(-ENODEV, gnss_get_fix_rate(dev, &fix_rate)); + + /* But escape hatches work */ + systems = 0; + mode = 0; + fix_rate = 0; + zassert_equal(0, gnss_emul_get_enabled_systems(dev, &systems)); + zassert_equal(0, gnss_emul_get_navigation_mode(dev, &mode)); + zassert_equal(0, gnss_emul_get_fix_rate(dev, &fix_rate)); + zassert_equal(GNSS_SYSTEM_GPS | GNSS_SYSTEM_GALILEO, systems); + zassert_equal(GNSS_NAVIGATION_MODE_HIGH_DYNAMICS, mode); + zassert_equal(1500, fix_rate); +} + +ZTEST(gnss_emul, test_callback_behaviour) +{ + const struct device *dev = DEVICE_DT_GET(DT_ALIAS(gnss)); + const struct navigation_data nav = { + .latitude = 150000000000, + .longitude = -15199000000, + .altitude = 123456, + }; + const struct gnss_info info = { + .satellites_cnt = 7, + .hdop = 1999, + .geoid_separation = 1000, + .fix_status = GNSS_FIX_STATUS_GNSS_FIX, + .fix_quality = GNSS_FIX_QUALITY_GNSS_SPS, + }; + const struct gnss_time *utc; + uint32_t timestamp; + + /* Booted into suspend mode */ + expected_pm_state(dev, PM_DEVICE_STATE_SUSPENDED); + + /* No data published while suspended */ + zassert_equal(-EAGAIN, k_sem_take(&gnss_data_published, K_SECONDS(5))); + + /* Power up and configure for 1Hz */ + zassert_equal(0, pm_device_runtime_get(dev)); + zassert_equal(0, gnss_set_fix_rate(dev, 1000)); + timestamp = k_uptime_get_32(); + + /* Monitor data for a while */ + for (int i = 0; i < 10; i++) { + zassert_equal(0, k_sem_take(&gnss_data_published, K_MSEC(1100))); + zassert_equal(0, gnss_published_data.nav_data.latitude); + zassert_equal(0, gnss_published_data.nav_data.longitude); + zassert_equal(0, gnss_published_data.nav_data.altitude); + zassert_equal(0, gnss_published_data.info.satellites_cnt); + print_time(&gnss_published_data.utc); + } + + /* Set a location, approximately 14th July 2017, 02:40:xx am */ + gnss_emul_set_data(dev, &nav, &info, 1500000000000LL); + for (int i = 0; i < 3; i++) { + utc = &gnss_published_data.utc; + /* Published data should match that configured */ + zassert_equal(0, k_sem_take(&gnss_data_published, K_MSEC(1100))); + zassert_mem_equal(&gnss_published_data.nav_data, &nav, sizeof(nav)); + zassert_mem_equal(&gnss_published_data.info, &info, sizeof(info)); + zassert_equal(17, utc->century_year); + zassert_equal(7, utc->month); + zassert_equal(14, utc->month_day); + zassert_equal(2, utc->hour); + zassert_equal(40, utc->minute); + print_time(&gnss_published_data.utc); + } + + /* Reset back to no location */ + gnss_emul_clear_data(dev); + for (int i = 0; i < 5; i++) { + zassert_equal(0, k_sem_take(&gnss_data_published, K_MSEC(1100))); + zassert_equal(0, gnss_published_data.nav_data.latitude); + zassert_equal(0, gnss_published_data.nav_data.longitude); + zassert_equal(0, gnss_published_data.nav_data.altitude); + zassert_equal(0, gnss_published_data.info.satellites_cnt); + print_time(&gnss_published_data.utc); + } + + /* Once again no callbacks once suspended */ + zassert_equal(0, pm_device_runtime_put(dev)); + zassert_equal(-EAGAIN, k_sem_take(&gnss_data_published, K_SECONDS(5))); +} + +ZTEST_SUITE(gnss_emul, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/drivers/gnss/gnss_emul/testcase.yaml b/tests/drivers/gnss/gnss_emul/testcase.yaml new file mode 100644 index 0000000000000..f3a5780fd06c3 --- /dev/null +++ b/tests/drivers/gnss/gnss_emul/testcase.yaml @@ -0,0 +1,11 @@ +# Copyright (c) 2025 Embeint Pty Ltd +# SPDX-License-Identifier: Apache-2.0 + +tests: + drivers.gnss.gnss_emul: + platform_allow: + - native_sim + integration_platforms: + - native_sim + tags: + - gnss