| /* |
| * Copyright (c) 2024 Trackunit Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #undef _POSIX_C_SOURCE |
| #define _POSIX_C_SOURCE 200809L /* Required for gmtime_r */ |
| |
| #include <zephyr/drivers/gnss.h> |
| #include <zephyr/drivers/gnss/gnss_publish.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/pm/device.h> |
| #include <zephyr/pm/device_runtime.h> |
| |
| #include <string.h> |
| #include <time.h> |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(gnss_emul, CONFIG_GNSS_LOG_LEVEL); |
| |
| #define DT_DRV_COMPAT zephyr_gnss_emul |
| |
| #define GNSS_EMUL_DEFAULT_FIX_INTERVAL_MS 1000 |
| #define GNSS_EMUL_MIN_FIX_INTERVAL_MS 100 |
| #define GNSS_EMUL_FIX_ACQUIRE_TIME_MS 5000 |
| #define GNSS_EMUL_DEFAULT_NAV_MODE GNSS_NAVIGATION_MODE_BALANCED_DYNAMICS |
| #define GNSS_EMUL_SUPPORTED_SYSTEMS_MASK 0xFF |
| #define GNSS_EMUL_SUPPORTED_SYSTEMS_COUNT 8 |
| #define GNSS_EMUL_DEFAULT_ENABLED_SYSTEMS_MASK GNSS_EMUL_SUPPORTED_SYSTEMS_MASK |
| |
| struct gnss_emul_data { |
| const struct device *dev; |
| struct k_work_delayable data_dwork; |
| struct k_sem lock; |
| int64_t resume_timestamp_ms; |
| int64_t fix_timestamp_ms; |
| uint32_t fix_interval_ms; |
| enum gnss_navigation_mode nav_mode; |
| gnss_systems_t enabled_systems; |
| struct gnss_data data; |
| |
| #ifdef CONFIG_GNSS_SATELLITES |
| struct gnss_satellite satellites[GNSS_EMUL_SUPPORTED_SYSTEMS_COUNT]; |
| uint8_t satellites_len; |
| #endif |
| }; |
| |
| static void gnss_emul_lock_sem(const struct device *dev) |
| { |
| struct gnss_emul_data *data = dev->data; |
| |
| (void)k_sem_take(&data->lock, K_FOREVER); |
| } |
| |
| static void gnss_emul_unlock_sem(const struct device *dev) |
| { |
| struct gnss_emul_data *data = dev->data; |
| |
| k_sem_give(&data->lock); |
| } |
| |
| static void gnss_emul_update_fix_timestamp(const struct device *dev, bool resuming) |
| { |
| struct gnss_emul_data *data = dev->data; |
| int64_t uptime_ms; |
| |
| uptime_ms = k_uptime_get(); |
| data->fix_timestamp_ms = ((uptime_ms / data->fix_interval_ms) + 1) * data->fix_interval_ms; |
| |
| if (resuming) { |
| data->resume_timestamp_ms = data->fix_timestamp_ms; |
| } |
| } |
| |
| 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; |
| |
| k_work_schedule(&data->data_dwork, K_TIMEOUT_ABS_MS(data->fix_timestamp_ms)); |
| } |
| |
| static bool gnss_emul_cancel_work(const struct device *dev) |
| { |
| struct gnss_emul_data *data = dev->data; |
| struct k_work_sync sync; |
| |
| return k_work_cancel_delayable_sync(&data->data_dwork, &sync); |
| } |
| |
| static bool gnss_emul_is_resumed(const struct device *dev) |
| { |
| struct gnss_emul_data *data = dev->data; |
| |
| return data->fix_timestamp_ms > 0; |
| } |
| |
| static void gnss_emul_lock(const struct device *dev) |
| { |
| gnss_emul_lock_sem(dev); |
| gnss_emul_cancel_work(dev); |
| } |
| |
| static void gnss_emul_unlock(const struct device *dev) |
| { |
| if (gnss_emul_is_resumed(dev)) { |
| gnss_emul_schedule_work(dev); |
| } |
| |
| gnss_emul_unlock_sem(dev); |
| } |
| |
| static int gnss_emul_set_fix_rate(const struct device *dev, uint32_t fix_interval_ms) |
| { |
| struct gnss_emul_data *data = dev->data; |
| |
| if (fix_interval_ms < GNSS_EMUL_MIN_FIX_INTERVAL_MS) { |
| return -EINVAL; |
| } |
| |
| data->fix_interval_ms = fix_interval_ms; |
| return 0; |
| } |
| |
| static int gnss_emul_get_fix_rate(const struct device *dev, uint32_t *fix_interval_ms) |
| { |
| struct gnss_emul_data *data = dev->data; |
| |
| *fix_interval_ms = data->fix_interval_ms; |
| return 0; |
| } |
| |
| static int gnss_emul_set_navigation_mode(const struct device *dev, |
| enum gnss_navigation_mode mode) |
| { |
| struct gnss_emul_data *data = dev->data; |
| |
| if (mode > GNSS_NAVIGATION_MODE_HIGH_DYNAMICS) { |
| return -EINVAL; |
| } |
| |
| data->nav_mode = mode; |
| return 0; |
| } |
| |
| static int gnss_emul_get_navigation_mode(const struct device *dev, |
| enum gnss_navigation_mode *mode) |
| { |
| struct gnss_emul_data *data = dev->data; |
| |
| *mode = data->nav_mode; |
| return 0; |
| } |
| |
| static int gnss_emul_set_enabled_systems(const struct device *dev, gnss_systems_t systems) |
| { |
| struct gnss_emul_data *data = dev->data; |
| |
| if (systems > GNSS_EMUL_SUPPORTED_SYSTEMS_MASK) { |
| return -EINVAL; |
| } |
| |
| data->enabled_systems = systems; |
| return 0; |
| } |
| |
| static int gnss_emul_get_enabled_systems(const struct device *dev, gnss_systems_t *systems) |
| { |
| struct gnss_emul_data *data = dev->data; |
| |
| *systems = data->enabled_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; |
| |
| gnss_emul_lock(dev); |
| |
| if (!gnss_emul_is_resumed(dev)) { |
| goto unlock_return; |
| } |
| |
| ret = gnss_emul_set_fix_rate(dev, fix_interval_ms); |
| |
| unlock_return: |
| gnss_emul_unlock(dev); |
| return ret; |
| } |
| |
| static int gnss_emul_api_get_fix_rate(const struct device *dev, uint32_t *fix_interval_ms) |
| { |
| int ret = -ENODEV; |
| |
| gnss_emul_lock(dev); |
| |
| if (!gnss_emul_is_resumed(dev)) { |
| goto unlock_return; |
| } |
| |
| ret = gnss_emul_get_fix_rate(dev, fix_interval_ms); |
| |
| unlock_return: |
| gnss_emul_unlock(dev); |
| return ret; |
| } |
| |
| static int gnss_emul_api_set_navigation_mode(const struct device *dev, |
| enum gnss_navigation_mode mode) |
| { |
| int ret = -ENODEV; |
| |
| gnss_emul_lock(dev); |
| |
| if (!gnss_emul_is_resumed(dev)) { |
| goto unlock_return; |
| } |
| |
| ret = gnss_emul_set_navigation_mode(dev, mode); |
| |
| unlock_return: |
| gnss_emul_unlock(dev); |
| return ret; |
| } |
| |
| static int gnss_emul_api_get_navigation_mode(const struct device *dev, |
| enum gnss_navigation_mode *mode) |
| { |
| int ret = -ENODEV; |
| |
| gnss_emul_lock(dev); |
| |
| if (!gnss_emul_is_resumed(dev)) { |
| goto unlock_return; |
| } |
| |
| ret = gnss_emul_get_navigation_mode(dev, mode); |
| |
| unlock_return: |
| gnss_emul_unlock(dev); |
| return ret; |
| } |
| |
| static int gnss_emul_api_set_enabled_systems(const struct device *dev, gnss_systems_t systems) |
| { |
| int ret = -ENODEV; |
| |
| gnss_emul_lock(dev); |
| |
| if (!gnss_emul_is_resumed(dev)) { |
| goto unlock_return; |
| } |
| |
| ret = gnss_emul_set_enabled_systems(dev, systems); |
| |
| unlock_return: |
| gnss_emul_unlock(dev); |
| return ret; |
| } |
| |
| static int gnss_emul_api_get_enabled_systems(const struct device *dev, gnss_systems_t *systems) |
| { |
| int ret = -ENODEV; |
| |
| gnss_emul_lock(dev); |
| |
| if (!gnss_emul_is_resumed(dev)) { |
| goto unlock_return; |
| } |
| |
| ret = gnss_emul_get_enabled_systems(dev, systems); |
| |
| unlock_return: |
| gnss_emul_unlock(dev); |
| return ret; |
| } |
| |
| static int gnss_emul_api_get_supported_systems(const struct device *dev, gnss_systems_t *systems) |
| { |
| *systems = GNSS_EMUL_SUPPORTED_SYSTEMS_MASK; |
| return 0; |
| } |
| |
| static const struct gnss_driver_api api = { |
| .set_fix_rate = gnss_emul_api_set_fix_rate, |
| .get_fix_rate = gnss_emul_api_get_fix_rate, |
| .set_navigation_mode = gnss_emul_api_set_navigation_mode, |
| .get_navigation_mode = gnss_emul_api_get_navigation_mode, |
| .set_enabled_systems = gnss_emul_api_set_enabled_systems, |
| .get_enabled_systems = gnss_emul_api_get_enabled_systems, |
| .get_supported_systems = gnss_emul_api_get_supported_systems, |
| }; |
| |
| static 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; |
| time_t timestamp; |
| struct tm datetime; |
| uint16_t millisecond; |
| |
| timestamp = (time_t)(data->fix_timestamp_ms / 1000); |
| gmtime_r(×tamp, &datetime); |
| |
| millisecond = (uint16_t)(data->fix_timestamp_ms % 1000) |
| + (uint16_t)(datetime.tm_sec * 1000); |
| |
| data->data.utc.hour = datetime.tm_hour; |
| data->data.utc.millisecond = millisecond; |
| data->data.utc.minute = datetime.tm_min; |
| data->data.utc.month = datetime.tm_mon + 1; |
| data->data.utc.century_year = datetime.tm_year % 100; |
| } |
| |
| static void gnss_emul_set_nav_data(const struct device *dev) |
| { |
| struct gnss_emul_data *data = dev->data; |
| |
| data->data.nav_data.latitude = 10000000000; |
| data->data.nav_data.longitude = -10000000000; |
| data->data.nav_data.bearing = 3000; |
| data->data.nav_data.speed = 0; |
| data->data.nav_data.altitude = 20000; |
| } |
| |
| #ifdef CONFIG_GNSS_SATELLITES |
| static void gnss_emul_clear_satellites(const struct device *dev) |
| { |
| struct gnss_emul_data *data = dev->data; |
| |
| data->satellites_len = 0; |
| } |
| |
| static bool gnss_emul_system_enabled(const struct device *dev, uint8_t system_bit) |
| { |
| struct gnss_emul_data *data = dev->data; |
| |
| return BIT(system_bit) & data->enabled_systems; |
| } |
| |
| static void gnss_emul_add_satellite(const struct device *dev, uint8_t system_bit) |
| { |
| struct gnss_emul_data *data = dev->data; |
| |
| /* Unique values synthesized from GNSS system */ |
| data->satellites[data->satellites_len].prn = system_bit; |
| data->satellites[data->satellites_len].snr = system_bit + 20; |
| data->satellites[data->satellites_len].elevation = system_bit + 40; |
| data->satellites[data->satellites_len].azimuth = system_bit + 60; |
| data->satellites[data->satellites_len].system = BIT(system_bit); |
| data->satellites[data->satellites_len].is_tracked = true; |
| data->satellites_len++; |
| } |
| |
| static void gnss_emul_set_satellites(const struct device *dev) |
| { |
| gnss_emul_clear_satellites(dev); |
| |
| for (uint8_t i = 0; i < GNSS_EMUL_SUPPORTED_SYSTEMS_COUNT; i++) { |
| if (!gnss_emul_system_enabled(dev, i)) { |
| continue; |
| } |
| |
| gnss_emul_add_satellite(dev, i); |
| } |
| } |
| #endif |
| |
| static void gnss_emul_work_handler(struct k_work *work) |
| { |
| struct k_work_delayable *dwork = k_work_delayable_from_work(work); |
| struct gnss_emul_data *data = CONTAINER_OF(dwork, struct gnss_emul_data, data_dwork); |
| const struct device *dev = data->dev; |
| |
| if (!gnss_emul_fix_is_acquired(dev)) { |
| gnss_emul_clear_data(dev); |
| } else { |
| gnss_emul_set_fix(dev); |
| gnss_emul_set_utc(dev); |
| gnss_emul_set_nav_data(dev); |
| } |
| |
| gnss_publish_data(dev, &data->data); |
| |
| #ifdef CONFIG_GNSS_SATELLITES |
| gnss_emul_set_satellites(dev); |
| gnss_publish_satellites(dev, data->satellites, data->satellites_len); |
| #endif |
| |
| gnss_emul_update_fix_timestamp(dev, false); |
| gnss_emul_schedule_work(dev); |
| } |
| |
| static void gnss_emul_init_data(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); |
| } |
| |
| static int gnss_emul_init(const struct device *dev) |
| { |
| gnss_emul_init_data(dev); |
| |
| if (pm_device_is_powered(dev)) { |
| gnss_emul_update_fix_timestamp(dev, true); |
| gnss_emul_schedule_work(dev); |
| } else { |
| pm_device_init_off(dev); |
| } |
| |
| return pm_device_runtime_enable(dev); |
| } |
| |
| #define GNSS_EMUL_NAME(inst, name) _CONCAT(name, inst) |
| |
| #define GNSS_EMUL_DEVICE(inst) \ |
| static struct gnss_emul_data GNSS_EMUL_NAME(inst, data) = { \ |
| .fix_interval_ms = GNSS_EMUL_DEFAULT_FIX_INTERVAL_MS, \ |
| .nav_mode = GNSS_EMUL_DEFAULT_NAV_MODE, \ |
| .enabled_systems = GNSS_EMUL_DEFAULT_ENABLED_SYSTEMS_MASK, \ |
| }; \ |
| \ |
| PM_DEVICE_DT_INST_DEFINE(inst, gnss_emul_pm_action); \ |
| \ |
| DEVICE_DT_INST_DEFINE( \ |
| inst, \ |
| gnss_emul_init, \ |
| PM_DEVICE_DT_INST_GET(inst), \ |
| &GNSS_EMUL_NAME(inst, data), \ |
| NULL, \ |
| POST_KERNEL, \ |
| CONFIG_GNSS_INIT_PRIORITY, \ |
| &api \ |
| ); |
| |
| DT_INST_FOREACH_STATUS_OKAY(GNSS_EMUL_DEVICE) |