diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..0e0e456 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,24 @@ +{ + "configurations": [ + { + "name": "Applications", + "includePath": [ + "${default}", + "${workspaceFolder}/applications/tasks", + "${workspaceFolder}/applications/common", + "${workspaceFolder}/ubxlib/**", + "${workspaceFolder}/../env/ncs/zephyr/**" + ], + "defines": [ + "_DEBUG", + "UNICODE", + "_UNICODE" + ], + "compilerPath": "${workspaceFolder}/../env/gcc-arm-none-eabi-10-2020-q4-major/bin/arm-none-eabi-gcc.exe", + "cStandard": "c17", + "cppStandard": "c++14", + "intelliSenseMode": "windows-gcc-arm" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/README.md b/README.md index 7150318..e413d46 100644 --- a/README.md +++ b/README.md @@ -1,206 +1,69 @@ -# Learning by examples +# ubxlib cellular applications for XPLR-IoT-1 development kit -This repo is intended to be a starting point on learning how to use the u-blox open source library [ubxlib](https://github.com/u-blox/ubxlib). The intended target system here is the [XPLR-IOT-1](https://www.u-blox.com/en/product/XPLR-IOT-1) device but most of the examples are also applicable to -any u-blox module or EVK supported by ubxlib. +The purpose of this repository is to provide example applications which run on the XPLR-IoT-1 development kit. This repository is forked from the [ubxlib-examples-xplr-iot](https://github.com/u-blox/ubxlib_examples_xplr_iot) repository and is structured very much the same. A simple **do build** script is used to select the application to build and flash on to the XPLR-IoT-1 development kit. -![ubxlib](readme_images/ubxlib-logo.png) -![XPLR_IOT-1](readme_images/XPLR-IOT-1.png) +The [ubxlib-examples-xplr-iot](https://github.com/u-blox/ubxlib_examples_xplr_iot) project contains small examples for individual features of the ubxlib API, whereas this `cellular applications` project is for complete applications based on the ubxlib API. -The main focus here is to make everything as uncomplicated as possible. The host mcu in an XPLR-IOT-1 device is the Nora-B1 with Nordic nRF5340. Unless you are familiar with the nRFConnect SDK and the Zephyr RTOS, the learning curve for mastering the development environment for this chip is quite steep. This repo tries to raise the abstraction layer and let you focus on the actual programming using ubxlib. Once started you can then dig into the more intricate parts of both ubxlib and Zephyr in your own pace. +# Applications -The interface to building, running and debug software for ubxlib can be either the command line or [Microsoft Visual Studio Code](https://code.visualstudio.com/). +(Currently this repository has only one application). -Both Windows (Windows 10 tested) and Debian Linux (Ubuntu 20.04 tested) are supported. +* [Cellular Tracker](applications/cellular_tracker). + Publishes cellular signal strength parameters and location. Can be controlled to publish Cell Query results (+COPS=?) -There are two ways of using this repository depending on what you have installed on your PC. +* [...]() -If you already have the Nordic nRFConnect SDK installed and know how to use it, you can just clone this repository and go on from there directly. If the environment is installed in the standard location it will be detected automatically. +# Application framework - git clone --recursive https://github.com/u-blox/ubxlib_examples_xplr_iot +The design of these applications is based around the same design. +Each application has access to the following functions: -On the other hand if this is your first encounter with the Nordic nrf53 chipset and possibly also with git, a complete installation script which includes everything needed for development is provided. In this case [goto this page](install/README.md) for more information about the installation process. +## Application tasks -You can of course also install everything yourself following the instructions at the [Nordic web site](https://www.nordicsemi.com/Products/Development-software/nrf-connect-sdk). The repository has been tested with nRF Connect versions 1.7.0 up to 2.2.0. However some of the examples do require at least version 1.9.0. +Each application is built from a number of `appTasks` which are either run in their own loop thread or their event executed separately. -It also works if you have your own tailored installation. You just have to specify where to find the nRFConnect environment parts. More about this below. +Each `appTask` is based on the same 'boiler plate' design. Each application task has a `taskMutex`, `taskEventQueue` and `taskHandler` which are provided by the UBXLIB API. -# Requirements +The application `main()` function simply runs the `appTask's` loop or commands an `appTaks` to run individually at certain timing, or possibly other events. This makes up the application. -To use this repo you need at least an XPLR-IOT-1 device. Please note though that this device doesn't contain any debug chip so in order to be able to do debugging you need to have some kind of debugger/programmer device. This would typically be a JLink debugger. +See [here](applications/tasks) for further information of each `appTask` currently implemented. -You also need a 10 pin programmer cable to connect the XPLR-IOT-1 and the programmer. Example shown below: +## Application remote control -![Jlink](readme_images/jlink.png) +Each `appTask` has a `//Control` topic where commands can be sent down to that `appTask`. Start, Stop task, measure 'now' etc. -However it is still possible to build and flash the examples into an XPLR-IOT-1 without a programmer. This is then made through a usb cable connected to the unit. No debugging is however possible in this case. Please note that this type of flashing requires the "newtmgr" program. This is installed automatically by the installation scripts of this repo, but if you have done a manual installation you have to add this manually as well and make sure it is in your path. It is available for download [from here](https://archive.apache.org/dist/mynewt/apache-mynewt-1.4.1) +A typical log output shows what the commands are: +> Subscribed to callback topic: /351457830026040/SensorControl +> +> With these commands: +> +> 1. MEASURE_NOW +> 2. START_TASK +> 3. STOP_TASK -Please note that the usb cable should also be inserted if you want to see printouts from the example programs. +## Application published data -The actual flash process is different if you have a debuger unit or are just using the serial cable. In the first case everything is done automatically if the debug is connected to the XPLR-IOT-1. In the latter case manual interaction is needed. You must first turn off the XPLR-IOT-1 with its on/off button. Then press down button 1 on the top side of the unit and keep it pressed while turning on the device again. The unit is now in bootloader mode and the flashing process can begin. Flashing performed by this repository tools will prompt you for confirmation of that the unit is in bootloader mode before starting. +Each appTask can publish their data/information to the MQTT broker to their own specific topic. This topic is well defined, being `//`. -**Also please note** that by flashing these examples you will overwrite the "Sensor Aggregation" firmware which is installed in a fresh XPLR-IOT-1. Should you later want to restore that firmware, [this page](https://github.com/u-blox/XPLR-IOT-1-software) will show you how. +This data/information should normally be formatted as JSON. -The WiFi captive portal example requires ubxlib version 1.3 or later, to be released July 2023. +## File Logging +The framework contains a file logging system which can be read through the UART, and deleted, at start-up through booting options... -# Getting started +## Booting options +For debuggability purposes, it is possible to extract the file log execution if, during the device boot, the `Button #1` is pressed within 5 seconds from power ON. The log file can also be erased if the `Button #2` is pressed within 5 seconds from power ON. The LED will change colour from blue to green to display the log file, or red to show the log file has been deleted. -Once you have everything installed and have connected your XPLR-IOT-1, you can start exploring the functionality of this repository. +NOTE: If you connect a terminal within the 3 seconds time of turning the device on (red LED), you will see some text remininding you about these button functions. -Begin by starting a command window. Then change working directory to where the repository was cloned. If you have used the installation script this will be in a directory underneath your home directory named xplriot1\ubxlib_examples_xplr_iot. +# Application main() +This is the starting point of the application. It will initialize the UBXLIB system and the device. It will also initialize the LEDS, FileSystem and check the log file. -Please note that if you have installed the nRFConnect SDK via the "Toolchain Manager" desktop app you may have to start the command prompt via the corresponding entry in the dropdown list. This is the case if Python is not available in your standard path. +Before running the application it will wait for either `Button #1` or `Button #2` to be pressed to display or delete the log file. -Then issue the following command: +If present on the file system the application will load the MQTT credentials. If the MQTT credentials are not stored on the file system, it can be specified using a `#define` in the `config.h` file. -Windows: +After the main system is initialized it will initialize the application tasks. Here each task will create a Mutex and eventQueue. These can be used to know if the appTask is running something, and communicate a command to it. - do vscode - -Linux: - - ./do vscode - -This will execute a complete build of the simple blink example and then start Visual Studio Code. - -Once Visual Studio Code has started you will find the "main.c" source code in a window. - -From within Visual Studio Code you can then build, rebuild, flash and run the examples. This is done via selecting the menus *Terminal -> Run Build Task* or by pressing the shortcut *ctrl-shift-b*. Choose *Build and run "blink"* to flash and start the blink example and the red led of your XPLR-IOT-1 should start blinking. - - -You can then select any of the other examples by choosing *Select Example* in the Build Task menu as above. After that you can select *Build and run* again. - -If you have a debug unit you can then also start a debugging session by first selecting the *Run and debug* icon in the left side pane. - -![debug icon](readme_images/debug-icon.png) - -Then select the green arrow in the top section and a debugging session will start. - -![debug start](readme_images/debug-arrow.png) - -The program will stop at the first line in the program. Experiment with the different debug functions in Visual Studio Code such as stepping, checking variables etc. - -You can then start modifying the examples to you liking or add your own, more about that further down. - -# Using the nRFConnect Visual Studio Code extension - -If you are used to using the nRFConnect Visual Studio Code extension and prefer to use it instead of the Visual Studio environment created by the *do* command, then that is of course also possible. - -In this case just open Visual Studio Code and then select *Add an existing application* in the nRF Connect extension. Browse to one of the example directory and choose open. Then do a *Add build configuration* in the normal way using the *nrf5340dk_nrf5340_cpuapp* as board. After that you can use the different operations available from the nRF Connect extension for building and debugging etc. You find all the used overlay and configuration files in the *config* directory of this repo. Please note that builds created this way will not use the bootloader. - - -# Creating your own examples - -There are three ways you can experiment and create new example applications. - -* Edit the existing examples and rebuild. -* Copy one existing example to the *playground* directory and edit and build from there. Everything added here will be excluded from git operations. -* Create a directory outside of this repo and add a corresponding directory structure and source files there. If you then set the application directory as you default and start the *do* command from there it will pick up your example application instead of the ones in the repo. - - -# Advanced usage - -All the operations performed in this repo are controlled by one central command named *do*. This command is executed as described above in a command window with the default directory set to the root of the repo. - -Below is the help information for the command as shown when issuing "do --help". More thorough description below. - - usage: do [-h] [-e EXAMPLE] [-p] [--no-bootloader] [--when-changed] [-d BUILD_DIR] - [-n NCS_DIR] [-t GCC_DIR] [-u UBXLIB_DIR] - operation [operation ...] - - positional arguments: - operation Operation to be performed: vscode, build, flash, run, monitor, debug - - optional arguments: - -h, --help show this help message and exit - -e EXAMPLE, --example EXAMPLE - Name of the example - -p, --pristine Pristine build (rebuild) - --no-bootloader Don't use the bootloader - --when-changed Only flash when build was triggered - -d BUILD_DIR, --build-dir BUILD_DIR - Root directory for the build output - -n NCS_DIR, --ncs-dir NCS_DIR - Nrf connect sdk installation directory - -t GCC_DIR, --gcc-dir GCC_DIR - GCC toolchain installation directory - -u UBXLIB_DIR, --ubxlib-dir UBXLIB_DIR - Ubxlib directory - -The most typical operations are: - -| Operation | Description | -| ----------- | ----------- | -| **build** | Will perform a build of the selected example. Corresponding to "west build" | -| **vscode** | First do a build and then start Visual Studio Code | -| **flash** | Build and then flash the XPLR-IOT-1| -| **monitor** | Start a serial port viewer/input in the current terminal| -| **run** | A combined build-flash-monitor operation | -| **debug** | Starts command line debugging using gdb | - -These operations can also be started from within Visual Studio Code as described above. - -There are some options for the operations which can be specified. These can be temporary for the actual command or be saved as new defaults. In the latter case the operation "save" is used. - -The --example option is used to specify which example to build. If not specified the example used in latest previous run will be used. See the directory structure underneath the examples subdirectory for the names available. Example: - - do build -e socket - -There will be a automatic search for examples among the directories so if you add your own that will be included as well in the list of choosables. - -### Directory specification - -There are some directories which are needed to be defined for the build process. These have default values depending on how the installation is made but can always be overridden by the options below: - -| Option | Description | -| ----------- | ----------- | -| **--ncs-dir** | The root directory for the nRF Connect SDK installation. If the default location (C:\ncs \| ~/ncs) is used the latest version will be found automatically and used. If another location should be used it can be specified here.| -| **--gcc-dir** | The directory for the GCC ARM compiler. If a default installation of nRFConnect is found as above the one here will be used. If another location should be used it can be specified here.| -| **--build-dir** | By default all produced files from the build will be placed in a subdirectory named "-build". Use this option to place it elsewhere.| -| **--ubxlib-dir** | By default the ubxlib version used is the one which is included as a submodule to this repo. This is possible to override with this option.| - -### Other options - -| Option | Description | -| ----------- | ----------- | -| **--pristine** | Force a complete rebuild| -| **--when-changed** | Only flash when a build was triggered| - -## The config directory ## - -This directory contains the specifics for handling the XPLR-IOT-1 in Zephyr. - -The files here are mainly overlay files used to override the specific settings for XPLR-IOT-1 when using the nrf5340dk board files. - -Here is also a c-file for defining the default gpio pins etc and setting them to enable the different u-blox modules automatically from ubxlib. - -There are some cmake variables you can define for controlling these files. - -| Variable | Description | -| ----------- | ----------- | -| NO_SENSORS | When set i2c is not included in the build and this enables the use of all 4 uarts. This means that both the Nina W15 and the Sara R5 modules can be used at the same time | -| EXT_FS | Enables use of a file system on the external SPI-flash memory. Used in the "filesystem" example| -| NO_DEBUG | By default debug optimization is used for compilation. Set this variable to disable that| -| ENABLE_LOGGING | Zephyr logging is disabled by default. Set this variable to enable it. - -### Bootloader - -By default the mcuboot bootloader which is available in a fresh XPLR-IOT-1 will be kept. If you don't want that then use the option: - - --no-bootloader - -Should you later want to change this you can use the operation "flash_bootloader" - -### Network cpu - -The network cpu of the host nrf5340 is not used in most of the examples. However if the Bluetooth functionality is to be used in the host (and not in the Nina module), a firmware for the network cpu has to be built and flashed. The build is done automatically whenever the KConfig setting CONFIG_BT is set. - -To flash the built network cpu firmware the following command must be used after a successful build of the example: - - do flash_net - -This is only required to be done once. - -Please note that this command will only work for examples that has defined CONFIG_BT. The current examples doing that are: ble_ibeacon_z, ble_scan_z and aoa_tag. In this case the command will be for ibeacon_z: - - do flash_net -e ble_ibeacon_z +Once all the application tasks are initialized the Registration and MQTT application tasks will `start()`. Here they will run their task loop, looking after the registration and MQTT broker connection. +The main application will terminate if `Button #1` is pressed, closing the MQTT broker connection, deregistering from the network, and closing the log file. It is important to close down the application by this method as otherwise the log file might not have been saved. \ No newline at end of file diff --git a/applications/README.md b/applications/README.md new file mode 100644 index 0000000..bb99f5d --- /dev/null +++ b/applications/README.md @@ -0,0 +1,38 @@ +# Applications + +This page describes the various application this repository has available and how they generally operate. + +
+ +## LED indicator +--- +The XPLR-IoT-1 development platform has a RGB LED indicator on the front of the box. This application framework allows the application to set the LEDs independently, with various flashing and blinking modes. + +### Application startup LED indicator +The Boot, Registration, MQTT activity and cell scanning activity has the following LED statuses: + +- Turn on: Solid RED +- Option choice (Display/Delete log file): Solid Blue +- XPLR initialization: Flashing red +- Network Registration: Flashing Blue +- Connecting to MQTT: Flashing Green +- Operating: Green +- Shutdown: Solid Red (Button #1) +- Off: No LED (Safely turn off) + +### Activity LED indicator + +- Published MQTT message: Blip Green +- Cell Scan: Blue / Blip Blue + + +
+ +# Application List + +(Currently this repository has only one application). + +* [Cellular Tracker](cellular_tracker/). + Publishes cellular signal strength parameters and location. Can be controlled to publish Cell Query results (+COPS=?) + +* [...]() \ No newline at end of file diff --git a/examples/filesystem/CMakeLists.txt b/applications/cellular_tracker/CMakeLists.txt similarity index 96% rename from examples/filesystem/CMakeLists.txt rename to applications/cellular_tracker/CMakeLists.txt index 5c85c48..aa9684b 100644 --- a/examples/filesystem/CMakeLists.txt +++ b/applications/cellular_tracker/CMakeLists.txt @@ -15,5 +15,4 @@ cmake_minimum_required(VERSION 3.13.1) set(EXT_FS 1) include(../common.cmake) -project(filesystem) - +project(application) diff --git a/applications/cellular_tracker/README.md b/applications/cellular_tracker/README.md new file mode 100644 index 0000000..1f4c867 --- /dev/null +++ b/applications/cellular_tracker/README.md @@ -0,0 +1,50 @@ +# Cellular Tracking Application +The Cellular tracking application purpose is to periodically monitor the signal quality of the cellular environment, its location and scan the visible base stations if the `Button #2` is pressed. + +All collected information is stored in the device filesystem via the application log but also shared into the cloud via the SARA-R5 MQTT embedded client to an MQTT Broker. + +Once turned ON, by default the application monitors the cellular signal quality. Once there is a GNSS fix, the location is also published to the cloud. If the `Button #2` is pressed, a base station scan is initialized. + +If the `Button #1` is pressed the application shuts down and the log file is saved and closed. If you do not press `Button #1` and simply turn off the XPLR-IoT-1 device then the log file will not save the entire log. + +## Building the application +Use the do build script with the -e argument: `do -e cellular_tracker build` + +## Configuring the application +Using the [config.h](config/config.h) file in the [config](config/) folder you will find the cellular URAT and APN settings. + +For the MQTT connection, there is a #define of the MQTT credentials which to use for this application. Please see the [mqttCredentials.c](src/mqtt_credentials.c) file for examples of MQTT broker settings. + +# Application remote commands + +The application can be remotely controlled through various topics which are subscribed to by the application tasks. The listed topics and their commands are here: + +## AppControl + +### SET_DWELL_TIME +Sets the period between the main loop performing the location and signal quality measurements. Default is 5 seconds. + +### SET_LOG_LEVEL +Sets the logging level of the application. Default is '2' for INFO log level. + + 0: TRACE + 1: DEBUG + 2: INFO + 3: WARN + 4: ERROR + 5: FATAL + +It could be possible to increase the logging of an application remotely by changing the logging value from '2' to '1'. + +## CellScanControl + +### START_CELL_SCAN +Starts a cell scan process, just as if you had pressed Button #2 + +# NOTES +## Thingstream SIMS +Thingstream SIMs can be used with two APNS; TSUDP or TSIOT. + +TSUDP is ONLY for MQTT-Anywhere service (using MQTT-SN), and does not allow any other internet traffic. This means when using TSUDP the NTP date/time request is not performed. This 'TSUDP' APN is listed as a 'restricted' APN in the [tasks/registrationTask.h](../tasks/registrationTask.h) file. + +TSIOT can be used for normal internet services and as such should be used when using other MQTT brokers, or even other MQTT-SN gateways. \ No newline at end of file diff --git a/applications/cellular_tracker/config/README.md b/applications/cellular_tracker/config/README.md new file mode 100644 index 0000000..0f5f535 --- /dev/null +++ b/applications/cellular_tracker/config/README.md @@ -0,0 +1,21 @@ +# Application Configuration +## General settings +The [config.h](config.h) file contains the general settings for the cellular module's connection to the network. The APN shall be configured accordingly for the SIM card used. Thingstream SIMs should use `TSIOT` for internet based MQTT brokers, and `TSUDP` for Thingstream MQTT-Anywhere service. + +Other items are for the MNO PROFILE (+UMNOPROF) and Radio Access Technology (+URAT) + +Finally, the MQTT broker needs to be selected from the list in the [mqtt_credentials.c](../src/mqtt_credentials.c) file. + +## MQTT Credentials +Users might need to modify the [mqtt_credentials.c](../src/mqtt_credentials.c) file providing details of the MQTT broker to be used, and set the chosen configuration in the [config.h](config.h) file. + +The application will automatically save the selected mqtt configuration to the file system for later use. +You can now delete/remove the specific credentials from the [mqtt_credentials.c](../src/mqtt_credentials.c) file. This is so that a private configuration for the MQTT credentials can be kept private. +Using the `MQTT_FILE_SYSTEM` definition will cause the application to load the mqtt credentials from the file system. + + 1. Edit/Add to the `mqtt_credentials.c` file which describes the MQTT broken name or IP address and the username/password. + 2. Select this MQTT configuration using the #define in `config.h` + 3. Compile and flash. When the application runs it will save this configuration information to the file system. + 4. If required, delete the private credential information in `mqtt_credentials.c` and then use `#define` `MQTT_FILE_SYSTEM`. + 1. Compile again and re-flash into the XPLR-IoT-1 device. + 2. The previously saved configuration will be loaded from the file system. \ No newline at end of file diff --git a/applications/cellular_tracker/config/config.h b/applications/cellular_tracker/config/config.h new file mode 100644 index 0000000..58e4538 --- /dev/null +++ b/applications/cellular_tracker/config/config.h @@ -0,0 +1,108 @@ +/* + * Copyright 2022 u-blox + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * + * Configuration for the Cellular Tracking Application + * Contains: + * MQTT credentials (in another #include) + * + * Cellular APN + * Cellular MNO Profile, URAT + * + */ + +/* This is the configuration file for the MQTT credentials + If this include file is included its data will be saved as + a 'configuration file' to be used later. + + Compile with the mqtt_credentials.h included, and it will be + automatically saved to the file system to be used again. + You can then delete/remove/comment out this #include so + that it is not saved to the repository :) + */ + +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +/* ---------------------------------------------------------------- + * Application Version number - this includes the common/tasks too + * -------------------------------------------------------------- */ +#define APP_NAME "Cellular Tracker" +#define APP_VERSION "v0.9" + +/* ---------------------------------------------------------------- + * DEBUG LEVEL SETTING - This can be changed remotely using + * "SET_LOG_LEVEL" command via the + * APP_CONTROL MQTT topic + * -------------------------------------------------------------- */ +#define LOGGING_LEVEL eINFO // taken from logLevels_t + +/* ---------------------------------------------------------------- + * APN SELECTION + * + * THINGSTREAM SIMS:- + * - Must use 'TSUDP' for Thingstream MQTT-Anywhere. + * - USE 'TSIOT' for 'normal' internet use. + * + * RESTRICTED APNS:- + * - In the tasks/registrationTask.c file there is a list of APNs + * which are marked as 'restricted'. This means normal internet + * queries are not available, like the NTP service on TSUDP APN. + * Edit this list for other APNs which are restricted/limited. + * -------------------------------------------------------------- */ +#define APN "TSIOT" + +/* ---------------------------------------------------------------- + * MQTT CREDENTIALS SELECTION + * + * Please select, using ONE #define below, which mqtt configuration + * to use for this application. + * + * Each are described in the src/mqtt_credentials.c file + * ----------------------------------------------------------------*/ +// *** Thingstream MQTT Services +//#define MQTT_THINGSTREAM_ANYWHERE +//#define MQTT_THINGSTREAM_FLEX +//#define MQTT_THINGSTREAM_NOW_NoTLS_Auth +//#define MQTT_THINGSTREAM_NOW_TLS_Auth + +// *** Mosquitto MQTT Test Service +//#define MQTT_MOSQUITTO_NoTLS_NoAuth +//#define MQTT_MOSQUITTO_NoTLS_Auth +//#define MQTT_MOSQUITTO_TLS_Cert +#define MQTT_MOSQUITTO_TLS_Auth + +/* ---------------------------------------------------------------- + * RADIO ACCESS TECHNOLOGY SELECTION + * + * Please use the same RAT enum from the UBXLIB uCellNetRat_t list. + * ----------------------------------------------------------------*/ +#define URAT U_CELL_NET_RAT_CATM1 + +/* ---------------------------------------------------------------- + * MNO PROFILE SELECTION + * + * Please use the MNO Profile number for the cellular module being + * used. See the AT command manual appendix for list + * + * Standard Profiles: + * 100 = European + * 90 = Global + * ----------------------------------------------------------------*/ +#define MNO_PROFILE 100 + +#endif \ No newline at end of file diff --git a/examples/nfc/prj.conf b/applications/cellular_tracker/prj.conf similarity index 62% rename from examples/nfc/prj.conf rename to applications/cellular_tracker/prj.conf index f388993..85f8672 100644 --- a/examples/nfc/prj.conf +++ b/applications/cellular_tracker/prj.conf @@ -10,11 +10,12 @@ # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -CONFIG_NFC_T2T_NRFXLIB=y - -CONFIG_NFC_NDEF=y -CONFIG_NFC_NDEF_MSG=y -CONFIG_NFC_NDEF_RECORD=y -CONFIG_NFC_NDEF_URI_REC=y -CONFIG_NFC_NDEF_URI_MSG=y +# Need to enable stack free functions +CONFIG_INIT_STACKS=y +CONFIG_THREAD_STACK_INFO=y +CONFIG_THREAD_NAME=y +CONFIG_SPI=y +# There are two theads per app task (task+queue). +# So here we need to make sure there are enough threads. +CONFIG_COMPILER_OPT="-DU_CFG_OS_MAX_THREADS=30" \ No newline at end of file diff --git a/applications/cellular_tracker/src/.gitignore b/applications/cellular_tracker/src/.gitignore new file mode 100644 index 0000000..0dee2c2 --- /dev/null +++ b/applications/cellular_tracker/src/.gitignore @@ -0,0 +1 @@ +/configFile.h diff --git a/applications/cellular_tracker/src/main.c b/applications/cellular_tracker/src/main.c new file mode 100644 index 0000000..5f57839 --- /dev/null +++ b/applications/cellular_tracker/src/main.c @@ -0,0 +1,92 @@ +/* + * Copyright 2022 u-blox + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * + * Cellular tracking application based on the XPLR-IoT-1 device + * Connects to an MQTT broker and publishes: + * o cellular rsrp/rsrq measurements + * o cellular CellID + * o GNSS Location + * + */ + +#include "common.h" + +#include "appInit.h" +#include "taskControl.h" + +#include "mqttTask.h" +#include "signalQualityTask.h" +#include "locationTask.h" +#include "cellScanTask.h" + +// Application name and version number is in the config.h file + +/* ---------------------------------------------------------------- + * Remote control callbacks for the main application + * Add your application topic message callbacks here + * -------------------------------------------------------------- */ +#define APP_CONTROL_TOPIC "AppControl" +static callbackCommand_t callbacks[] = { + {"SET_DWELL_TIME", setAppDwellTime}, + {"SET_LOG_LEVEL", setAppLogLevel} +}; + +/// @brief The application function(s) which are run every appDwellTime +/// @return A flag to indicate the application should continue (true) +bool appFunction(void) +{ + queueMeasureNow(NULL); + queueLocationNow(NULL); + + return true; +} + +void buttonTwo(void) +{ + queueNetworkScan(NULL); +} + +/* ---------------------------------------------------------------- + * Main startup function for the framework + * -------------------------------------------------------------- */ +void main(void) +{ + if (!startupFramework()) + return; + + // The Network registration task is used to connect to the cellular network + // This will monitor the +CxREG URCs + runTask(NETWORK_REG_TASK); + + // The MQTT task connects and reconnects to the MQTT broker selected in the + // config.h file. This needs to run for MQTT messages to be published and + // for remote control messages to be handled + runTask(MQTT_TASK); + + // Subscribe to the main AppControl topic for remote control the main application (this) + subscribeToTopicAsync(APP_CONTROL_TOPIC, U_MQTT_QOS_AT_MOST_ONCE, callbacks, NUM_ELEMENTS(callbacks)); + + // Set button two to point to the queueCellScan function + setButtonTwoFunction(buttonTwo); + + // Start the application loop with our app function + runApplicationLoop(appFunction); + + // all done, close down and finalise + finalise(SHUTDOWN); +} diff --git a/applications/cellular_tracker/src/mqtt_credentials.c b/applications/cellular_tracker/src/mqtt_credentials.c new file mode 100644 index 0000000..aa423ea --- /dev/null +++ b/applications/cellular_tracker/src/mqtt_credentials.c @@ -0,0 +1,240 @@ +/* + * Copyright 2022 u-blox + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * MQTT Credentials examples + * Select which one to use in the config.h file. + * + * Use a single space to separate the paramater name to the value. + * + * MQTT-TYPE is either "MQTT" or "MQTT-SN" + * You can use NULL for unused parameters, like no username or password + * + * If you set MQTT_CLIENTID to NULL in this configuration UBXLIB will automatically set + * the IMEI of the cellular device being used. So unless you MUST set the clientID for + * your own MQTT broker, you can leave this parameter NULL. + * + * For MQTT brokers that don't use security, set MQTT_SECURITY to FALSE (or NULL) + * For MQTT brokers that require security, set the MQTT_SECURITY to TRUE. + * Use the SECURITY_XXXX Parameters to configure the security profile + * + * NOTE: You will need to have already uploaded certificates. + * + * Missing entries will resolve to NULL if they are used in the application. + * + * Experiment with KeepAlive (TRUE/FALSE) and Timeout Values (seconds) +*/ + +#include "common.h" +#include "../config/config.h" + +// This is the configuration for loading the MQTT configuration that is already on saved on the file system +#ifdef MQTT_FILE_SYSTEM +const char *mqtt_credentials[] = NULL // NULL will cause the application to only load from file system +#endif + +// This is the Thingstream MQTT-Anywhere configuration. +// !!! You MUST use "TSUDP" for the APN !!! +// +// There is no need for username & password or clientID +// Thingstream MQTT-Anywhere uses the SIM security from the network operator +#ifdef MQTT_THINGSTREAM_ANYWHERE +const char *mqttCredentials[] = { + "MQTT_TYPE MQTT-SN", + "MQTT_BROKER_NAME 10.7.0.55:2442", + "MQTT_USERNAME NULL", + "MQTT_PASSWORD NULL", + "MQTT_CLIENTID NULL", + "MQTT_KEEPALIVE NULL", + "MQTT_TIMEOUT NULL", + "MQTT_SECURITY FALSE", + + // no need to set or include these as there is no security + "SECURITY_CERT_VALID_LEVEL 0", + "SECURITY_TLS_VERSION 3", + "SECURITY_CIPHER_SUITE 0", + "SECURITY_CLIENT_NAME NULL", + "SECURITY_CLIENT_KEY NULL", + "SECURITY_SERVER_NAME_IND NULL", +}; +#endif + +// This is the Thingstream MQTT-FLEX service configuration. +// Port 2443 uses TLS security with client certificate and key +#ifdef MQTT_THINGSTREAM_FLEX +const char *mqttCredentials[] = { + "MQTT_TYPE MQTT", + "MQTT_BROKER_NAME mqtt.thingstream.io:2443", + "MQTT_USERNAME NULL", + "MQTT_PASSWORD NULL", + "MQTT_CLIENTID NULL", + "MQTT_KEEPALIVE NULL", + "MQTT_TIMEOUT NULL", + "MQTT_SECURITY TRUE", + + // Set TLS security and specify the security + // manager client certificate name and key + "SECURITY_CERT_VALID_LEVEL 0", + "SECURITY_TLS_VERSION 3", + "SECURITY_CIPHER_SUITE 0", + "SECURITY_CLIENT_NAME device-5378b7ce.pem", + "SECURITY_CLIENT_KEY device-5378b7ce.key", + "SECURITY_SERVER_NAME_IND mqtt-flex.thingstream.io", +}; +#endif + +// This is the Thingstream MQTT-NOW service configuration. +// Port 1883 doesn't use TLS security, just authentication +// Must provide username and password below... +#ifdef MQTT_THINGSTREAM_NOW_NoTLS_Auth +const char *mqttCredentials[] = { + "MQTT_TYPE MQTT", + "MQTT_BROKER_NAME mqtt.thingstream.io:1883", + "MQTT_USERNAME YOUR-USER-NAME-HERE", + "MQTT_PASSWORD YOUR-PASSWORD-HERE", + "MQTT_CLIENTID NULL", + "MQTT_KEEPALIVE NULL", + "MQTT_TIMEOUT NULL", + "MQTT_SECURITY FALSE", + + // no need to set or include these as there is no security + "SECURITY_CERT_VALID_LEVEL 0", + "SECURITY_TLS_VERSION 3", + "SECURITY_CIPHER_SUITE 0", + "SECURITY_CLIENT_NAME NULL", + "SECURITY_CLIENT_KEY NULL", + "SECURITY_SERVER_NAME_IND NULL", +}; +#endif + +// This is the MQTT-NOW service configuration. +// Port 8883 uses TLS security and authentication +// Must provide username and password below... +#ifdef MQTT_THINGSTREAM_NOW_TLS_Auth +const char *mqttCredentials[] = { + "MQTT_TYPE MQTT", + "MQTT_BROKER_NAME mqtt.thingstream.io:8883", + "MQTT_USERNAME YOUR-USER-NAME-HERE", + "MQTT_PASSWORD YOUR-PASSWORD-HERE", + "MQTT_CLIENTID NULL", + "MQTT_KEEPALIVE NULL", + "MQTT_TIMEOUT NULL", + "MQTT_SECURITY TRUE", + + // Just set TLS security + "SECURITY_CERT_VALID_LEVEL 0", + "SECURITY_TLS_VERSION 3", + "SECURITY_CIPHER_SUITE 0", + "SECURITY_CLIENT_NAME NULL", + "SECURITY_CLIENT_KEY NULL", + "SECURITY_SERVER_NAME_IND NULL", +}; +#endif + +// This is the mosquitto test server configuration. +// Port 1883 has no security or authentication +#ifdef MQTT_MOSQUITTO_NoTLS_NoAuth +const char *mqttCredentials[] = { + "MQTT_TYPE MQTT", + "MQTT_BROKER_NAME test.mosquitto.org:1883", + "MQTT_USERNAME NULL", + "MQTT_PASSWORD NULL", + "MQTT_CLIENTID NULL", + "MQTT_KEEPALIVE NULL", + "MQTT_TIMEOUT NULL", + "MQTT_SECURITY FALSE", + + // no need to set or include these as there is no security + "SECURITY_CERT_VALID_LEVEL 0", + "SECURITY_TLS_VERSION 3", + "SECURITY_CIPHER_SUITE 0", + "SECURITY_CLIENT_NAME NULL", + "SECURITY_CLIENT_KEY NULL", + "SECURITY_SERVER_NAME_IND NULL", +}; +#endif + +// This is the mosquitto test server configuration. +// Port 1884 uses username/password authentication only +// "rw" and "readwrite" for the username/password +#ifdef MQTT_MOSQUITTO_NoTLS_Auth +const char *mqttCredentials[] = { + "MQTT_TYPE MQTT", + "MQTT_BROKER_NAME test.mosquitto.org:1884", + "MQTT_USERNAME rw", + "MQTT_PASSWORD readwrite", + "MQTT_CLIENTID NULL", + "MQTT_KEEPALIVE NULL", + "MQTT_TIMEOUT NULL", + "MQTT_SECURITY FALSE", + + // no need to set or include these as there is no security + "SECURITY_CERT_VALID_LEVEL 0", + "SECURITY_TLS_VERSION 3", + "SECURITY_CIPHER_SUITE 0", + "SECURITY_CLIENT_NAME NULL", + "SECURITY_CLIENT_KEY NULL", + "SECURITY_SERVER_NAME_IND NULL", +}; +#endif + +// This is the mosquitto test server configuration. +// Port 8884 uses client certificate for authentication +#ifdef MQTT_MOSQUITTO_TLS_Cert +const char *mqttCredentials[] = { + "MQTT_TYPE MQTT", + "MQTT_BROKER_NAME test.mosquitto.org:8884", + "MQTT_USERNAME NULL", + "MQTT_PASSWORD NULL", + "MQTT_CLIENTID NULL", + "MQTT_KEEPALIVE NULL", + "MQTT_TIMEOUT NULL", + "MQTT_SECURITY TRUE", + + // no need to set or include these as there is no security + "SECURITY_CERT_VALID_LEVEL 0", + "SECURITY_TLS_VERSION 3", + "SECURITY_CIPHER_SUITE 0", + "SECURITY_CLIENT_NAME YOUR_CLIENT_CERTIFICATE_NAME_HERE", + "SECURITY_CLIENT_KEY YOUR_CLIENT_PRIVATE_KEY_HERE", + "SECURITY_SERVER_NAME_IND NULL", +}; +#endif + +// This is the mosquitto test server configuration. +// Port 8885 uses TLS security and username/password authentication +#ifdef MQTT_MOSQUITTO_TLS_Auth +const char *mqttCredentials[] = { + "MQTT_TYPE MQTT", + "MQTT_BROKER_NAME test.mosquitto.org:8885", + "MQTT_USERNAME rw", + "MQTT_PASSWORD readwrite", + "MQTT_CLIENTID NULL", + "MQTT_KEEPALIVE NULL", + "MQTT_TIMEOUT NULL", + "MQTT_SECURITY TRUE", + + // Just enable TLS + "SECURITY_CERT_VALID_LEVEL 0", + "SECURITY_TLS_VERSION 3", + "SECURITY_CIPHER_SUITE 0", + "SECURITY_CLIENT_NAME NULL", + "SECURITY_CLIENT_KEY NULL", + "SECURITY_SERVER_NAME_IND NULL", +}; +#endif + +const int32_t mqttCredentialsSize = NUM_ELEMENTS(mqttCredentials); \ No newline at end of file diff --git a/examples/common.cmake b/applications/common.cmake similarity index 81% rename from examples/common.cmake rename to applications/common.cmake index 3522eee..e790c07 100644 --- a/examples/common.cmake +++ b/applications/common.cmake @@ -33,13 +33,21 @@ if (NOT NO_SENSORS) endif() # And Zephyr find_package(Zephyr HINTS $ENV{ZEPHYR_BASE}) + # All example source code file(GLOB_RECURSE APP_SOURCES "${CMAKE_SOURCE_DIR}/src/*.c") target_sources(app PRIVATE ${APP_SOURCES}) -# And common utilities + +# ...common utilities file(REAL_PATH "${CMAKE_CURRENT_LIST_DIR}/common" APP_COMMON_DIR) file(GLOB APP_COMMONS "${APP_COMMON_DIR}/*.c") -target_sources(app PRIVATE ${APP_COMMONS}) -target_include_directories(app PRIVATE ${APP_COMMON_DIR}) -zephyr_include_directories(${ZEPHYR_BASE}/include/zephyr) +# ...and tasks +file(REAL_PATH "${CMAKE_CURRENT_LIST_DIR}/tasks" APP_TASKS_DIR) +file(GLOB APP_TASKS "${APP_TASKS_DIR}/*.c") + +target_sources(app PRIVATE ${APP_COMMONS} ${APP_TASKS}) + +file(REAL_PATH "${CMAKE_SOURCE_DIR}/config/" APP_CONFIG) + +target_include_directories(app PRIVATE ${APP_COMMON_DIR} ${APP_TASKS_DIR} ${APP_CONFIG}) \ No newline at end of file diff --git a/applications/common/NTPClient.c b/applications/common/NTPClient.c new file mode 100644 index 0000000..3cf8181 --- /dev/null +++ b/applications/common/NTPClient.c @@ -0,0 +1,149 @@ +/* + * Copyright 2022 u-blox + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * + * Simple function to return the clock time from a NTP Server + * + */ + +#include + +#include "common.h" +#include "ntpclient.h" +#include "..\include\zephyr\sys\byteorder.h" + +/* ---------------------------------------------------------------- + * DEFINES + * -------------------------------------------------------------- */ + +#define ntohl(x) sys_be32_to_cpu(x) + +// Timeout for waiting for NTP is 20 seconds, using a 100ms tick +#define NTP_TIMEOUT 200 +#define NTP_SERVER "time.google.com" + +#define NTP_TIMESTAMP_DELTA 2208988800ull + +/* ---------------------------------------------------------------- + * STATIC VARIABLES + * -------------------------------------------------------------- */ + +static bool haveDataToRead = false; + +typedef struct +{ + uint8_t li_vn_mode; // Eight bits. li, vn, and mode. + // li. Two bits. Leap indicator. + // vn. Three bits. Version number of the protocol. + // mode. Three bits. Client will pick mode 3 for client. + + uint8_t stratum; // Eight bits. Stratum level of the local clock. + uint8_t poll; // Eight bits. Maximum interval between successive messages. + uint8_t precision; // Eight bits. Precision of the local clock. + + uint32_t rootDelay; // 32 bits. Total round trip delay time. + uint32_t rootDispersion; // 32 bits. Max error aloud from primary clock source. + uint32_t refId; // 32 bits. Reference clock identifier. + + uint32_t refTm_s; // 32 bits. Reference time-stamp seconds. + uint32_t refTm_f; // 32 bits. Reference time-stamp fraction of a second. + + uint32_t origTm_s; // 32 bits. Originate time-stamp seconds. + uint32_t origTm_f; // 32 bits. Originate time-stamp fraction of a second. + + uint32_t rxTm_s; // 32 bits. Received time-stamp seconds. + uint32_t rxTm_f; // 32 bits. Received time-stamp fraction of a second. + + uint32_t txTm_s; // 32 bits and the most important field the client cares about. Transmit time-stamp seconds. + uint32_t txTm_f; // 32 bits. Transmit time-stamp fraction of a second. + +} ntp_packet; // Total: 384 bits or 48 bytes. + +/* ---------------------------------------------------------------- + * STATIC FUNCTIONS + * -------------------------------------------------------------- */ + +static void dataCallback(void *pParam) +{ + haveDataToRead = true; +} + +static bool waitForSocketData(void) +{ + for(int i = 0; !gExitApp && !haveDataToRead && i < NTP_TIMEOUT; i++) + uPortTaskBlock(100); + + return haveDataToRead; +} + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS + * -------------------------------------------------------------- */ + +/// @brief Gets the time from the NTP Service +/// @return zero on failure, otherwise the unix epoch time +int64_t getNTPTime(void) +{ + ntp_packet packet = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + memset( &packet, 0, sizeof( ntp_packet ) ); + + // Set the first byte's bits to 00,011,011 for li = 0, vn = 3, and mode = 3. + *( ( char * ) &packet + 0 ) = 0x1b; + + uSockDescriptor_t sock = uSockCreate(gDeviceHandle, U_SOCK_TYPE_DGRAM, U_SOCK_PROTOCOL_UDP); + if(sock < 0) { + writeLog("Failed to create UDP socket: %d", sock); + return sock; + } + + haveDataToRead = false; + uSockRegisterCallbackData(sock, &dataCallback, NULL); + + time_t time = 0; + uSockAddress_t remoteAddress; + int32_t errorCode = uSockGetHostByName(gDeviceHandle, NTP_SERVER, &(remoteAddress.ipAddress)); + if (errorCode == 0) { + remoteAddress.port = 123; + + int64_t errorCode = uSockSendTo(sock, &remoteAddress, &packet, sizeof(ntp_packet)); + if (errorCode > 0) { + if (waitForSocketData()) { + errorCode = uSockReceiveFrom(sock, NULL, &packet, sizeof( ntp_packet )); + if (errorCode > 0) { + writeLog("Received NTP time from server"); + packet.txTm_s = ntohl(packet.txTm_s); + time = ( time_t ) ( packet.txTm_s - NTP_TIMESTAMP_DELTA ); + } else { + writeError("Failed to read NTP Packet from server: %d", errorCode); + } + } else { + writeError("Waiting for NTP response timed out."); + } + } else { + writeError("Failed to send NTP Packet to server: %d", errorCode); + } + } else { + writeError("Failed to get Time Service IP Address: %d", sock); + } + + errorCode = uSockClose(sock); + if (errorCode != 0) { + writeError("Failed to close socket: %d", errorCode); + } + + return time; +} \ No newline at end of file diff --git a/examples/hello/src/main.c b/applications/common/NTPClient.h similarity index 75% rename from examples/hello/src/main.c rename to applications/common/NTPClient.h index 40a9d77..07a184a 100644 --- a/examples/hello/src/main.c +++ b/applications/common/NTPClient.h @@ -16,18 +16,8 @@ /* * - * Hello world application using ubxlib + * NTP Client header * */ -#include "ubxlib.h" - -void main() -{ - uPortInit(); - int i = 0; - while (1) { - uPortLog("Hello #%3d\n", ++i); - uPortTaskBlock(5000); - } -} +int64_t getNTPTime(void); \ No newline at end of file diff --git a/applications/common/appInit.c b/applications/common/appInit.c new file mode 100644 index 0000000..a69dae2 --- /dev/null +++ b/applications/common/appInit.c @@ -0,0 +1,460 @@ +/* + * Copyright 2022 u-blox + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common.h" +#include "taskControl.h" +#include "cellInit.h" +#include "config.h" +#include "ext_fs.h" +#include "leds.h" +#include "buttons.h" + +/* ---------------------------------------------------------------- + * TYPE DEFINITIONS + * -------------------------------------------------------------- */ +typedef enum { + NO_BUTTON=-1, + BUTTON_1=0, + BUTTON_2=1 +} buttonNumber_t; + +/* ---------------------------------------------------------------- + * DEFINES + * -------------------------------------------------------------- */ +//#define UBXLIB_LOGGING_ON // comment out for no ubxlib logging + +#define STARTUP_DELAY 250 // 250 * 20ms => 5 seconds +#define LOG_FILENAME "log.csv" +#define MQTT_CREDENTIALS_FILENAME "mqttCredentials.txt" + +// Dwell time of the main loop activity, pause period until the loop runs again +#define APP_DWELL_TIME_MS_MINIMUM 5000 +#define APP_DWELL_TIME_MS_DEFAULT APP_DWELL_TIME_MS_MINIMUM; +#define APP_DWELL_TICK_MS 50 + +/* ---------------------------------------------------------------- + * STATIC VARIABLES + * -------------------------------------------------------------- */ +static uDeviceType_t deviceType = U_DEVICE_TYPE_CELL; +static uDeviceCfg_t deviceCfg; + +static bool buttonCommandEnabled = false; +static buttonNumber_t pressedButton = NO_BUTTON; + +static int32_t appDwellTimeMS = 5000; + +// This flag will pause the main application loop +static bool pauseMainLoopIndicator = false; + +/* ---------------------------------------------------------------- + * STATIC FUNCTIONS + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * GLOBAL VARIABLES + * -------------------------------------------------------------- */ +// serial number of the module +char gSerialNumber[U_SECURITY_SERIAL_NUMBER_MAX_LENGTH_BYTES]; + +applicationStates_t gAppStatus = MANUAL; + +// deviceHandle is not static as this is shared between other modules. +uDeviceHandle_t gDeviceHandle; + +// This flag is set to true when the application should close tasks and log files. +// This flag is set to true when Button #1 is pressed. +bool gExitApp = false; +bool gExitFast = false; // don't wait for closing app Tasks, just close the log file and exit. + +void (*buttonTwoFunc)(void) = NULL; + +// reference to our mqtt credentials which are used for the application's publish/subscription +extern const char *mqttCredentials[]; +extern int32_t mqttCredentialsSize; + +/* ---------------------------------------------------------------- + * STATIC FUNCTIONS + * -------------------------------------------------------------- */ + +/// @brief Function to check for a held button at start up. +/// @return The button that was held at start up. +static buttonNumber_t checkStartButton(void) +{ + SET_RED_LED; + + // just wait 3 seconds for any terminal to be connected + uPortTaskBlock(3000); + + printLog("Button #1 = Display Log file"); + printLog("Button #2 = Delete Log File"); + printLog("Waiting 5 seconds for selection before running application..."); + + SET_BLUE_LED; + // wait for 5 seconds, or if a button is pressed + for(int i = 0; pressedButton == NO_BUTTON && i < STARTUP_DELAY; i++) + uPortTaskBlock(20); + + SET_NO_LEDS; + + buttonNumber_t heldButton = pressedButton; + if (heldButton == NO_BUTTON) + return NO_BUTTON; + + // turn on the correct LED for the operation + int led = 0; // Delete log file = red + if (heldButton == BUTTON_1) + led = 1; // Display log file = green + ledSet(led, true); + + // wait until the button is released + while(pressedButton != NO_BUTTON) + uPortTaskBlock(20); + + return heldButton; +} + +/// @brief Handler for the buttons. On boot, Button 1: Display log, 2: Delete log. +static void button_pressed(int buttonNo, uint32_t holdTime) +{ + if (gExitApp) + return; + + if (!holdTime) { + pressedButton = buttonNo; + } else { + pressedButton = NO_BUTTON; + if (!buttonCommandEnabled) + return; + + switch (buttonNo) { + case BUTTON_1: + writeLog("Exit button pressed, closing down... Please wait for the RED LED to go out..."); + gExitApp = true; + break; + + case BUTTON_2: + if (buttonTwoFunc != NULL) { + writeLog("Button #2 pressed"); + buttonTwoFunc(); + } else { + printDebug("No function defined for Button #2"); + } + break; + + default: + break; + } + } +} + +/// @brief Reads the serial number from the module +/// @return The length of the serial number, or negative on error +static int32_t getSerialNumber(void) +{ + int32_t len = uSecurityGetSerialNumber(gDeviceHandle, gSerialNumber); + if (len > 0) { + if (gSerialNumber[0] == '"') { + // Remove quotes + memmove(gSerialNumber, gSerialNumber + 1, len); + gSerialNumber[len - 2] = 0; + } + + writeLog("Cellular Module Serial Number: %s", gSerialNumber); + } + + return len; +} + +/// @brief Initiate the UBXLIX API +static int32_t initCellularDevice(void) +{ + int32_t errorCode; + + // turn off the UBXLIB printLog() logging + #ifndef UBXLIB_LOGGING_ON + uPortLogOff(); + #endif + + writeLog("Initiating the UBXLIB Device API..."); + errorCode = uDeviceInit(); + if (errorCode != 0) { + writeFatal("* Failed to initiate the UBXLIB device API: %d", errorCode); + return errorCode; + } + + uDeviceGetDefaults(deviceType, &deviceCfg); + + writeLog("Opening/Turning on the cellular module..."); + errorCode = uDeviceOpen(&deviceCfg, &gDeviceHandle); + if (errorCode != 0) { + writeFatal("* Failed to turn on the cellular module: %d", errorCode); + return errorCode; + } + + // get serial number + errorCode = getSerialNumber(); + if (errorCode < 0) { + writeFatal("* Failed to get the serial number of the module: %d", errorCode); + return errorCode; + } + + return configureCellularModule(); +} + +/// @brief Initialises the XPLR device LEDs, Buttons and file system and handles the startup button press +static bool initXplrDevice(void) +{ + if (uPortInit() != 0) { + printFatal("* Failed to initiate UBXLIB - not running application!"); + return false; + } + if (!buttonsInit(button_pressed)) { + printFatal("* Failed to initiate buttons - not running application!"); + return false; + } + if (!ledsInit()) { + printFatal("* Failed to initiate leds - not running application!"); + return false; + } + if (!extFsInit()) { + printFatal("* Failed to mounth File System - not running application!"); + return false; + } + + // User has chance to hold down a button to delete or display the log + buttonNumber_t button = checkStartButton(); + + // deleting the log file is performed now before the start of the application + if (button == BUTTON_2) { + printLog("Deleting log file..."); + deleteFile(LOG_FILENAME); + } + + setLogLevel(LOGGING_LEVEL); + startLogging(LOG_FILENAME); + + // displaying the log file ends the application + if (button == BUTTON_1) { + displayLogFile(); + SET_NO_LEDS; + printLog("Application finished"); + return false; + } + + // Display the file system free size + displayFileSpace(LOG_FILENAME); + + return true; +} + +static bool loadConfigFiles(void) +{ + // Save the mqtt credentials file (if present) + if (mqttCredentials != NULL) { + int32_t saveResult = saveConfigFile(MQTT_CREDENTIALS_FILENAME, + mqttCredentials, + mqttCredentialsSize); + + if (saveResult != 0 && saveResult != U_ERROR_COMMON_NOT_FOUND) { + printFatal("Aborting application as configuration file was not written"); + return false; + } + } else { + printDebug("No mqtt credentials to save to file system"); + } + + // Load the mqtt credentials config file + printInfo("Loading MQTT Credentials..."); + loadConfigFile(MQTT_CREDENTIALS_FILENAME); + + // Note: More configuration files can be loaded. + // Everything is appended to the config list array. + + // this will only print if logging is set to DEBUG or higher - security! + printConfiguration(); + + return true; +} + +static void displayAppVersion() +{ + writeInfo("**************************************************"); + writeInfo("%s %s", APP_NAME, APP_VERSION); + writeInfo("**************************************************\n"); +} + +/// @brief Dwells for appDwellTimeMS time, and exits if this time changes +static void dwellAppLoop(void) +{ + int32_t tick = 0; + int32_t dwellTimeMS = appDwellTimeMS; + + do + { + uPortTaskBlock(APP_DWELL_TICK_MS); + tick = tick + APP_DWELL_TICK_MS; + } while((tick < dwellTimeMS) && + (dwellTimeMS == appDwellTimeMS)); + + printDebug("*** Application Tick ***\n"); +} + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS + * -------------------------------------------------------------- */ + +/// @brief Sets the time between each main loop execution +/// @param params The dwell time parameter for the dwell time +/// @return 0 if successful, or failure if invalid parameters +int32_t setAppDwellTime(commandParamsList_t *params) +{ + int32_t timeMS = getParamValue(params, 1, 5000, 60000, 30000); + + if (timeMS < APP_DWELL_TIME_MS_MINIMUM) { + writeWarn("Failed to set App Dwell Time, %d is less than minimum (%d ms)", timeMS, APP_DWELL_TIME_MS_MINIMUM); + return U_ERROR_COMMON_INVALID_PARAMETER; + } + + appDwellTimeMS = timeMS; + writeLog("Setting App Dwell Time to: %d\n", timeMS); + + return U_ERROR_COMMON_SUCCESS; +} + +/// @brief Sets the application logging level +/// @param params The log level parameter for the dwell time +/// @return 0 if successful, or failure if invalid parameters +int32_t setAppLogLevel(commandParamsList_t *params) +{ + logLevels_t logLevel = (logLevels_t) getParamValue(params, 1, (int32_t) eTRACE, (int32_t) eMAXLOGLEVELS, (int32_t) eINFO); + + if (logLevel < eTRACE) { + writeWarn("Failed to set App Log Level %d. Min: %d, Max: %d", logLevel, eTRACE, eMAXLOGLEVELS); + return U_ERROR_COMMON_INVALID_PARAMETER; + } + + setLogLevel(logLevel); + + return U_ERROR_COMMON_SUCCESS; +} + +/// @brief Sets the function which handles the Button #2 function +/// @param func The function pointer for button #2 code +void setButtonTwoFunction(void (*func)(void)) +{ + buttonTwoFunc = func; +} + +/// @brief Method of pausing the running of the main loop. +/// Useful for when there are other long running activities +/// which need to stop the main loop +/// @param state A flag to indicate if the main loop is paused or not +void pauseMainLoop(bool state) +{ + pauseMainLoopIndicator = state; + printInfo("Main application loop %s", state ? "is paused" : "is unpaused"); +} + +/// @brief This is the main application loop which runs the appFunc which is +/// defined in the application main.c module. +/// @param appFunc The function pointer of the app event code +void runApplicationLoop(bool (*appFunc)(void)) +{ + // now allow the buttons to run their commands + buttonCommandEnabled = true; + printInfo("Buttons #1 and #2 are now enabled"); + + while(!gExitApp) { + dwellAppLoop(); + + if (gExitApp) return; + + if (pauseMainLoopIndicator) { + writeDebug("Application loop paused."); + continue; + } + + if (!appFunc()) { + gExitApp = true; + writeInfo("Application function stopped the app loop"); + } + } +} + +/// @brief Sets the application status, waits for the tasks and closes the log +/// @param appState The application status to set for the shutdown +void finalise(applicationStates_t appState) +{ + gAppStatus = appState; + gExitApp = true; + + waitForAllTasksToStop(); + + // now stop the network registration task. Blue LED + SET_BLUE_LED; + stopAndWait(NETWORK_REG_TASK); + + closeLog(); + uPortDeinit(); + + SET_NO_LEDS; + printLog("XPLR App has finished."); + + while(true); +} + +/// @brief Starts the application framework +/// @return true if successful, false otherwise +bool startupFramework(void) +{ + int32_t errorCode; + + // initialise our LEDs and start up button commmands + if (!initXplrDevice()) + return false; + + displayAppVersion(); + + if (!loadConfigFiles()) + return false; + + errorCode = initSingleTask(LED_TASK); + if (errorCode < 0) { + writeFatal("* Failed to initialise LED task - not running application!"); + finalise(ERROR); + } + + errorCode = runTask(LED_TASK); + if (errorCode != 0) { + writeFatal("* Failed to start LED task - not running application!"); + finalise(ERROR); + } + + // initialise the cellular module + gAppStatus = INIT_DEVICE; + errorCode = initCellularDevice(); + if (errorCode != 0) { + writeFatal("* Failed to initialise the cellular module - not running application!"); + finalise(ERROR); + } + + // Initialise the task runners + if (initTasks() != 0) { + finalise(ERROR); + } + + return true; +} \ No newline at end of file diff --git a/applications/common/appInit.h b/applications/common/appInit.h new file mode 100644 index 0000000..bfa0244 --- /dev/null +++ b/applications/common/appInit.h @@ -0,0 +1,33 @@ +/* + * Copyright 2022 u-blox + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#ifndef _APP_INIT_H_ +#define _APP_INIT_H_ + +int32_t getSerialNumber(void); + +int32_t setAppDwellTime(commandParamsList_t *params); +int32_t setAppLogLevel(commandParamsList_t *params); + +void setButtonTwoFunction(void (*func)(void)); +void runApplicationLoop(bool (*appFunc)(void)); +void pauseMainLoop(bool state); + +bool startupFramework(void); +void finalise(applicationStates_t appState); + +#endif \ No newline at end of file diff --git a/examples/common/ble_aoa.c b/applications/common/ble_aoa.c similarity index 94% rename from examples/common/ble_aoa.c rename to applications/common/ble_aoa.c index cd67a48..8395287 100644 --- a/examples/common/ble_aoa.c +++ b/applications/common/ble_aoa.c @@ -14,8 +14,6 @@ * limitations under the License. */ -#include - #include #include #include @@ -85,7 +83,7 @@ static bool set_adv_params(uint16_t min_ms, uint16_t max_ms) return bt_le_per_adv_set_param(m_ext_adv, &per_adv_param) == 0; } -bool bleAoaInit(char *pBleId) +bool bleAoaInit() { bool ok = bt_enable(NULL) == 0; if (ok) { @@ -102,12 +100,6 @@ bool bleAoaInit(char *pBleId) memcpy(id, addr.a.val, EDDYSTONE_INSTANCE_ID_LEN); } memcpy((uint8_t *)&m_adv_data[2].data[ADV_DATA_OFFSET_INSTANCE], id, EDDYSTONE_INSTANCE_ID_LEN); - if (pBleId) { - for (uint8_t i = 0; i < EDDYSTONE_INSTANCE_ID_LEN; i++) { - sprintf(pBleId, "%02X", id[i]); - pBleId += 2; - } - } ok = bt_le_ext_adv_create(&m_adv_param, NULL, &m_ext_adv) == 0; ok = ok && bt_le_ext_adv_set_data(m_ext_adv, m_adv_data, ARRAY_SIZE(m_adv_data), NULL, 0) == 0; diff --git a/examples/common/ble_aoa.h b/applications/common/ble_aoa.h similarity index 88% rename from examples/common/ble_aoa.h rename to applications/common/ble_aoa.h index 73faca1..d94f480 100644 --- a/examples/common/ble_aoa.h +++ b/applications/common/ble_aoa.h @@ -18,11 +18,9 @@ #include /** Initiate BLE for angle of arrival advertisements - * @param pBleId String to receive the 12 character BLE id (mac). - * Can be set to NULL. * @return Success or failure. */ -bool bleAoaInit(char *pBleId); +bool bleAoaInit(); /** Start or stop angle of arrival advertisements * @param min_ms Minimum advertisement time in milliseconds. diff --git a/examples/common/buttons.c b/applications/common/buttons.c similarity index 99% rename from examples/common/buttons.c rename to applications/common/buttons.c index d3b3a36..bf71a7c 100644 --- a/examples/common/buttons.c +++ b/applications/common/buttons.c @@ -14,7 +14,7 @@ * limitations under the License. */ -#include +#include #include #include diff --git a/examples/common/buttons.h b/applications/common/buttons.h similarity index 100% rename from examples/common/buttons.h rename to applications/common/buttons.h diff --git a/applications/common/cellInit.c b/applications/common/cellInit.c new file mode 100644 index 0000000..51c9bf2 --- /dev/null +++ b/applications/common/cellInit.c @@ -0,0 +1,71 @@ +/* + * Copyright 2022 u-blox + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common.h" +#include "config.h" +#include "cellInit.h" + +static int32_t checkReboot(void) +{ + if(uCellPwrRebootIsRequired(gDeviceHandle)) + { + writeLog("Need to reboot the module as the settings have changed..."); + return uCellPwrReboot(gDeviceHandle, NULL); + } + + return 0; +} + +static int32_t configureMNOProfile(void) +{ + int32_t errorCode = uCellCfgGetMnoProfile(gDeviceHandle); + if (errorCode == MNO_PROFILE) return 0; + + errorCode = uCellCfgSetMnoProfile(gDeviceHandle, MNO_PROFILE); + if(errorCode != 0) + { + writeError("Failed to set MNO Profile %d", MNO_PROFILE); + return errorCode; + } + + return checkReboot(); +} + +static int32_t configureRAT(void) +{ + int32_t rat = uCellCfgGetRat(gDeviceHandle, 0); + if (rat == URAT) return 0; + + int32_t errorCode = uCellCfgSetRat(gDeviceHandle, URAT); + if (errorCode != 0) + { + writeError("Failed to set RAT %d", URAT); + return errorCode; + } + + return checkReboot(); +} + +int32_t configureCellularModule(void) +{ + writeInfo("Configuring the cellular module..."); + + int32_t errorCode = configureMNOProfile(); + if (errorCode == 0) + errorCode = configureRAT(); + + return errorCode; +} \ No newline at end of file diff --git a/examples/sensors/src/main.c b/applications/common/cellInit.h similarity index 60% rename from examples/sensors/src/main.c rename to applications/common/cellInit.h index 3f831e7..d135ace 100644 --- a/examples/sensors/src/main.c +++ b/applications/common/cellInit.h @@ -1,4 +1,3 @@ - /* * Copyright 2022 u-blox * @@ -15,26 +14,9 @@ * limitations under the License. */ -/* - * - * Simple demo program showing how to read some of - * the sensors on the XPLR-IOT-1 board - * - */ - -#include -#include -#include -#include - -#include "sensors.h" +#ifndef _CELLINIT_H_ +#define _CELLINIT_H_ +int32_t configureCellularModule(void); -void main() -{ - sensorsInit(); - while (1) { - printf("%s\n%s\n%s\n", pollTempSensor(), pollAccelerometer(), pollLightSensor()); - k_msleep(2000); - } -} +#endif \ No newline at end of file diff --git a/applications/common/common.c b/applications/common/common.c new file mode 100644 index 0000000..f33989c --- /dev/null +++ b/applications/common/common.c @@ -0,0 +1,196 @@ +/* + * Copyright 2022 u-blox + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * + * Common utility functions + * + */ +#include + +#include "common.h" + +/* ---------------------------------------------------------------- + * DEFINITITIONS + * -------------------------------------------------------------- */ +#define PARAM_DELIMITERS " ,:" + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS + * -------------------------------------------------------------- */ +/// @brief Checks if the mutex is locked/running +/// @param mutex the mutex to check +/// @return false if it not locked/running, true if it is locked/running. +bool isMutexLocked(uPortMutexHandle_t mutex) +{ + if(mutex == NULL) { + return false; + } + + int32_t result = uPortMutexTryLock(mutex, 0); + if (result != 0) + { + return true; // Can't get a lock, so it must be running + } + + // we have locked the mutex, so release we need to release it + if (uPortMutexUnlock(mutex) != 0) { + printFatal("Failed to release mutex from lock check!!!"); + // now you've done it. You've tested the mutex lock + // by locking it and now can't unlock it!! + return true; + } + + // As we could get a lock, the task is demeed "not running" + return false; +} + +/// @brief Duplicates a string via malloc - remember to free()! +/// @param src the string source +/// @returns pointer to the duplicated string, or NULL +char *uStrDup(const char *src) +{ + return (char *)uMemDup((void *)src, strlen(src)+1); +} + +/// @brief Duplicates a block of data via malloc - remember to free()! +/// @param src the data source +/// @returns pointer to the duplicated data, or NULL +void *uMemDup(const void *src, size_t len) +{ + void *dst = pUPortMalloc(len); + if(dst == NULL) { + writeError("No memory for data duplication"); + return NULL; + } + + memcpy(dst, src, len); + return dst; +} + +static commandParamsList_t *createParam(char *param) +{ + commandParamsList_t *newParam = (commandParamsList_t *)pUPortMalloc(sizeof(commandParamsList_t)); + if (newParam == NULL) { + writeError("No memory for createParam()"); + return NULL; + } + + newParam->parameter = uMemDup(param, strlen(param)+1); + if (newParam->parameter == NULL) { + writeError("No memory for createParam()"); + return NULL; + } + + newParam->pNext = NULL; + + return newParam; +} + +/// @brief Puts the command and param parts of a string message into a structure +/// @param message The string to parse into Command: param1, param2 etc +/// @param params The parameters for the command +/// @returns Number of parameters +size_t getParams(char *message, commandParamsList_t **head) +{ + char *token = strtok_r(message, PARAM_DELIMITERS, &message); + if (token == NULL) { + printWarn("Unable to parse message for command/params"); + return -1; + } + + *head = createParam(token); + if (head == NULL) { + writeError("No memory for getParams()"); + return 0; + } + + commandParamsList_t *current = *head; + size_t count=1; + + while((token = strtok_r(NULL, PARAM_DELIMITERS, &message)) != NULL) { + current->pNext = createParam(token); + current = current->pNext; + count++; + } + + return count; +} + +void freeParams(commandParamsList_t *item) +{ + if (item == NULL) { + return; + } + + freeParams(item->pNext); + uPortFree(item->parameter); + uPortFree(item); +} + +int32_t getParamValue(commandParamsList_t *params, size_t index, int32_t minValue, int32_t maxValue, int32_t defValue) +{ + char *ptr; + commandParamsList_t *param = params; + for(int i=0; ipNext; + + if (param == NULL) + return defValue; + + int32_t value = strtol(param->parameter, &ptr, 10); + if (value < minValue) + return minValue; + if (value > maxValue) + return maxValue; + + return value; +} + +/// @brief Notates the timestamp from the network time or boot tick time +/// @param timeStamp The string to write the timestamp to. Must be minimum size of TIMESTAMP_MAX_LENTH_BYTES +void getTimeStamp(char *timeStamp) +{ + // construct the log format, and include the ticks time + int32_t ticks = uPortGetTickTimeMs(); + + // if we have the network time set use this + if (unixNetworkTime > 0) { + time_t tmTime = unixNetworkTime + (ticks/1000); + int32_t milliseconds = ticks % 1000; + struct tm *time = gmtime(&tmTime); + snprintf(timeStamp, TIMESTAMP_MAX_LENTH_BYTES, "%02d:%02d:%02d.%03d", + time->tm_hour, + time->tm_min, + time->tm_sec, + milliseconds); + } else { + snprintf(timeStamp, TIMESTAMP_MAX_LENTH_BYTES, "%d", ticks); + } +} + +void runTaskAndDelete(void *pParams) +{ + if (pParams != NULL) { + void (*func)(void) = pParams; + func(); + } else { + writeWarn("No Task to run!"); + } + + uPortTaskDelete(NULL); + uPortTaskBlock(2); +} \ No newline at end of file diff --git a/applications/common/common.h b/applications/common/common.h new file mode 100644 index 0000000..2770aac --- /dev/null +++ b/applications/common/common.h @@ -0,0 +1,155 @@ +/* + * Copyright 2022 u-blox + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * + * Application header + * + */ + +#ifndef _COMMON_H_ +#define _COMMON_H_ + +#include +#include +#include + +#include +#include + +#include "ubxlib.h" +#include "configUtils.h" +#include "log.h" + +#include "kernel.h" + +/* ---------------------------------------------------------------- + * MACORS for common task usage/access + * -------------------------------------------------------------- */ +#define SET_APP_STATUS(x) tempAppStatus = gAppStatus; gAppStatus = x +#define REVERT_APP_STATUS(x) gAppStatus = tempAppStatus + +#define IS_NETWORK_AVAILABLE (gIsNetworkSignalValid && gIsNetworkUp) + +#define NUM_ELEMENTS(x) (sizeof(x) / sizeof(x[0])) + +#define MAX_NUMBER_COMMAND_PARAMS 5 + +#define MAX_TOPIC_NAME_SIZE 50 + +#define QUEUE_STACK_SIZE(x) MIN(U_PORT_EVENT_QUEUE_MIN_TASK_STACK_SIZE_BYTES, x) +#define QUEUE_STACK_SIZE_DEFAULT U_PORT_EVENT_QUEUE_MIN_TASK_STACK_SIZE_BYTES + +/** The maximum length of the Time Stamp string. + * hh:mm:ss.mmm + */ +#define TIMESTAMP_MAX_LENTH_BYTES 13 + +/* ---------------------------------------------------------------- + * PUBLIC TYPE DEFINITIONS + * -------------------------------------------------------------- */ +// Default set of application statuses +typedef enum { + MANUAL, + INIT_DEVICE, + REGISTERING, + MQTT_CONNECTING, + COPS_QUERY, + SEND_SIGNAL_QUALITY, + REGISTRATION_UNKNOWN, + REGISTERED, + ERROR, + SHUTDOWN, + MQTT_CONNECTED, + MQTT_DISCONNECTED, + START_SIGNAL_QUALITY, + REGISTRATION_DENIED, + NO_NETWORKS_AVAILABLE, + NO_COMPATIBLE_NETWORKS, + MAX_STATUS +} applicationStates_t; + +typedef enum { + NETWORK_REG_TASK = 0, + CELL_SCAN_TASK = 1, + MQTT_TASK = 2, + SIGNAL_QUALITY_TASK = 3, + LED_TASK = 4, + EXAMPLE_TASK = 5, + LOCATION_TASK = 6, + SENSOR_TASK = 7, + MAX_TASKS +} taskTypeId_t; + +/// @brief command information +typedef struct commandParamsList { + char *parameter; + struct commandParamsList *pNext; +} commandParamsList_t; + +/// @brief callback information +typedef struct { + const char *command; + int32_t (*callback)(commandParamsList_t *params); +} callbackCommand_t; + +/* ---------------------------------------------------------------- + * EXTERNAL VARIABLES used in the application tasks + * -------------------------------------------------------------- */ + +// serial number of the cellular module +extern char gSerialNumber[U_SECURITY_SERIAL_NUMBER_MAX_LENGTH_BYTES]; + +// This is the ubxlib deviceHandle for communicating with the celullar module +extern uDeviceHandle_t gDeviceHandle; + +// This flag is set to true when the application's tasks should exit +extern bool gExitApp; + +// This flag is for pausing the normal main loop activity +extern bool gPauseMainLoop; + +// This flag represents the network's registration status +extern bool gIsNetworkUp; + +// This flag represents the module can hear the network signaling (RSRP != 0) +extern bool gIsNetworkSignalValid; + +// application status +extern applicationStates_t gAppStatus; + +/// The unix network time, which is retrieved after first registration +extern int64_t unixNetworkTime; + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS + * -------------------------------------------------------------- */ +bool isMutexLocked(uPortMutexHandle_t mutex); +char *uStrDup(const char *src); +void *uMemDup(const void *data, size_t len); + +int32_t sendAppTaskMessage(int32_t taskId, void *pMessage, size_t msgSize); + +// Simple function to split message into command/params linked list +size_t getParams(char *message, commandParamsList_t **head); +void freeParams(commandParamsList_t *head); +int32_t getParamValue(commandParamsList_t *params, size_t index, int32_t minValue, int32_t maxValue, int32_t defValue); + +void getTimeStamp(char *timeStamp); + +void runTaskAndDelete(void *pParams); + +#endif \ No newline at end of file diff --git a/applications/common/configUtils.c b/applications/common/configUtils.c new file mode 100644 index 0000000..e159254 --- /dev/null +++ b/applications/common/configUtils.c @@ -0,0 +1,292 @@ +/* + * Copyright 2022 u-blox + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * + * Utility functions to help load and save 'config' files + * + */ + +#include "common.h" +#include "config.h" +#include "ext_fs.h" + +/* ---------------------------------------------------------------- + * DEFINES + * -------------------------------------------------------------- */ +#define FILE_READ_BUFFER 50 + +// delimiters are ' ' (space) and '\n' (newline) +#define CONFIG_DELIMITERS " \n" + +/* ---------------------------------------------------------------- + * TYPE DEFINITIONS + * -------------------------------------------------------------- */ +typedef struct APP_CONFIG_LIST { + char *key; + char *value; + struct APP_CONFIG_LIST *pNext; +} appConfigList_t; + +/* ---------------------------------------------------------------- + * STATIC VARIABLES + * -------------------------------------------------------------- */ +static struct fs_file_t configFile; +static appConfigList_t *configList; + +/* ---------------------------------------------------------------- + * STATIC PRIVATE FUNCTIONS + * -------------------------------------------------------------- */ +static appConfigList_t *createConfigKVP(char *key, char *value) { + appConfigList_t *newKVP = (appConfigList_t *)pUPortMalloc(sizeof(appConfigList_t)); + if (newKVP == NULL) { + writeError("Failed to allocate memory for new KeyValuePair config parameter"); + return NULL; + } + + newKVP->key = key; + newKVP->value = value; + newKVP->pNext = NULL; + + return newKVP; +} + +static size_t parseConfiguration(char *configText, appConfigList_t **head) +{ + size_t count = 0; + appConfigList_t *current, *newNode; + + do { + char *key = strtok_r(configText, CONFIG_DELIMITERS, &configText); + char *value = strtok_r(NULL, CONFIG_DELIMITERS, &configText); + if(key == NULL || value == NULL) { + break; + } + + newNode = createConfigKVP(key, value); + if (newNode == NULL) + break; + + if(count == 0) { + *head = newNode; + current = *head; + } else { + current->pNext = newNode; + current = current->pNext; + } + + count++; + } while(true); + + return count; +} + +static bool checkWrittenCount(ssize_t writeCount, int32_t paramSize) +{ + if (writeCount != paramSize) { + if (writeCount < 0) + writeError("Failed to write configuration parameter to file. Error: %d", writeCount); + else + writeError("Failed to write configuration parameter to file. Param size: %d, written size: %d", paramSize, writeCount); + + return false; + } + + return true; +} + +static int32_t writeParam(const char *configParams[], int32_t paramIndex) +{ + int32_t paramSize = strlen(configParams[paramIndex]); + + // write the parameter + ssize_t count = fs_write(&configFile, configParams[paramIndex], paramSize); + if (!checkWrittenCount(count, paramSize)) + return U_ERROR_COMMON_DEVICE_ERROR; + + // write the new line so that we can parse it later + count = fs_write(&configFile, "\n", 1); + if (!checkWrittenCount(count, 1)) + return U_ERROR_COMMON_DEVICE_ERROR; + + return U_ERROR_COMMON_SUCCESS; +} + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS + * -------------------------------------------------------------- */ + +/// @brief Saves the configuration file defined in the header file +/// @param filename The filename of the configuration file +/// @param configParams The char array of the parameters (key value pair) +/// @param configParamsSize The number of parameters in the char array +/// @return 0 on success, negative on failure +int32_t saveConfigFile(const char *filename, const char *configParams[], int32_t configParamsSize) +{ + int32_t success; + int32_t errorCode = U_ERROR_COMMON_SUCCESS; + + if (configParams == NULL) { + printDebug("No configuration file to save to file system"); + return U_ERROR_COMMON_NOT_FOUND; + } + + const char *path = extFsPath(filename); + + if (extFsFileExists(path)) { + printDebug("Found old config file, deleting..."); + if (fs_unlink(path) != 0) { + writeError("Failed to delete old configuration file: %s", filename); + return U_ERROR_COMMON_DEVICE_ERROR; + } + } + + fs_file_t_init(&configFile); + success = fs_open(&configFile, path, FS_O_CREATE | FS_O_WRITE); + if (success < 0) { + writeError("Failed to open configuration file: %d", success); + return U_ERROR_COMMON_DEVICE_ERROR; + } + + for(int i=0; i 0) { + buffer = buffer + count; + } + + parseConfiguration(configText, &configList); + +cleanUp: + if (errorCode != 0) { + uPortFree(configText); + } + + fs_close(&configFile); + + return errorCode; +} + +void printConfiguration(void) +{ + size_t count=1; + appConfigList_t *kvp = configList; + const char *value; + + while(kvp != NULL) { + value = getConfig(kvp->key); + if (value == NULL) value = "N/A"; + printDebug(" Key #%d: %s = %s", count, kvp->key, value); + + kvp = kvp->pNext; + count++; + } + + printDebug(""); +} + +/// @brief Returns the specified configuration value +/// @param key The configuration name to return the value of +/// @return The configuration value on succes, NULL on failure +const char *getConfig(const char *key) +{ + for(appConfigList_t *kvp = configList; kvp != NULL; kvp=kvp->pNext) { + if (strcmp(kvp->key, key) == 0) { + if (strcmp(kvp->value, "NULL") == 0) + return NULL; + else + return kvp->value; + } + } + + printWarn("Failed to find '%s' key", key); + return NULL; +} + +/// @brief Sets an int value from a configuration key, if present +/// @param key The configuration name to return the value of +/// @param param A pointer to the int value to set +/// @return True if the int value was set, False otherwise +bool setIntParamFromConfig(const char *key, int32_t *param) +{ + const char *value = getConfig(key); + if (value == NULL) return false; + + *param = atoi(value); + + return true; +} + +/// @brief Sets a bool value from a configuration key, if present +/// @param key The configuration name to return the value of +/// @param compare The string to compare the config value to +/// @param param A pointer to the bool value to set +/// @return True if the bool value was set, False otherwise +bool setBoolParamFromConfig(const char *key, const char *compare, bool *param) +{ + const char *value = getConfig(key); + if (value == NULL) return false; + + *param = strcmp(value, compare) == 0; + return true; +} \ No newline at end of file diff --git a/applications/common/configUtils.h b/applications/common/configUtils.h new file mode 100644 index 0000000..0c77dac --- /dev/null +++ b/applications/common/configUtils.h @@ -0,0 +1,62 @@ +/* + * Copyright 2022 u-blox + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * + * Configuration file utility functions + * + */ + +#ifndef _CONFIG_UTILS_H_ +#define _CONFIG_UTILS_H_ + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS + * -------------------------------------------------------------- */ + +/// @brief Loads a configuration file ready for indexing +/// @param filename The filename of the configuration file +/// @return 0 on success, negative on failure +int32_t loadConfigFile(const char *filename); + +/// @brief Saves the CONFIG_FILE_CONTENTS defined above +/// @param filename The filename of the configuration file +/// @param configParams The char array of the parameters (key value pair) +/// @param configParamsSize The number of parameters in the char array +/// @return 0 on success, negative on failure +int32_t saveConfigFile(const char *filename, const char *configParams[], int32_t configParamsSize); + +/// @brief Prints the configuration list +void printConfiguration(void); + +/// @brief returns the specified configuration value +/// @param key The configuration name to return the value of +/// @return The configuration value on succes, NULL on failure +const char *getConfig(const char *key); + +/// @brief Sets a int value from a configuration key, if present +/// @param key The configuration name to return the value of +/// @param param A pointer to the int value to set +/// @return True if the int value was set, False otherwise +bool setIntParamFromConfig(const char *key, int32_t *param); + +/// @brief Sets a bool value from a configuration key, if present +/// @param key The configuration name to return the value of +/// @param param A pointer to the bool value to set +/// @return True if the bool value was set, False otherwise +bool setBoolParamFromConfig(const char *key, const char *value, bool *param); + +#endif \ No newline at end of file diff --git a/examples/common/ext_fs.c b/applications/common/ext_fs.c similarity index 91% rename from examples/common/ext_fs.c rename to applications/common/ext_fs.c index 66fd93e..8224834 100644 --- a/examples/common/ext_fs.c +++ b/applications/common/ext_fs.c @@ -44,15 +44,14 @@ const char *extFsPath(const char *fileName) return path; } -int32_t extFsFree() +unsigned long extFsFree() { - int32_t errorOrSize; + unsigned long free = 0; struct fs_statvfs sbuf; - errorOrSize = fs_statvfs(gMountPoint->mnt_point, &sbuf); - if (errorOrSize == 0) { - errorOrSize = sbuf.f_frsize * sbuf.f_bfree / 1024; + if (fs_statvfs(gMountPoint->mnt_point, &sbuf) == 0) { + free = sbuf.f_frsize * sbuf.f_bfree / 1024; } - return errorOrSize; + return free; } bool extFsFileExists(const char *fileName) diff --git a/examples/common/ext_fs.h b/applications/common/ext_fs.h similarity index 94% rename from examples/common/ext_fs.h rename to applications/common/ext_fs.h index cfa2707..4aba03b 100644 --- a/examples/common/ext_fs.h +++ b/applications/common/ext_fs.h @@ -15,7 +15,7 @@ */ #include -#include +#include /** * Initiate little_fs on the external flash memory of the XPLR-IOT-1. @@ -37,9 +37,9 @@ const char *extFsPath(const char *fileName); /** * Get the size of the free space on the file system in kB. - * @return Actual free size or possible negative error code + * @return Actual free size. */ -int32_t extFsFree(); +unsigned long extFsFree(); /** * Check if a file exists diff --git a/examples/common/leds.c b/applications/common/leds.c similarity index 99% rename from examples/common/leds.c rename to applications/common/leds.c index 1e25eb4..92feb32 100644 --- a/examples/common/leds.c +++ b/applications/common/leds.c @@ -17,7 +17,7 @@ #include #include -#include +#include #include "leds.h" diff --git a/examples/common/leds.h b/applications/common/leds.h similarity index 79% rename from examples/common/leds.h rename to applications/common/leds.h index eb6c548..7991603 100644 --- a/examples/common/leds.h +++ b/applications/common/leds.h @@ -22,6 +22,13 @@ #define GREEN_LED 1 #define BLUE_LED 2 +// basic led controls +#define SET_WHITE_LED ledSet(0, true); ledSet(1, true); ledSet(2, true) +#define SET_RED_LED ledSet(0, true); ledSet(1, false); ledSet(2, false) +#define SET_GREEN_LED ledSet(0, false); ledSet(1, true); ledSet(2, false) +#define SET_BLUE_LED ledSet(0, false); ledSet(1, false); ledSet(2, true) +#define SET_NO_LEDS ledSet(0, false); ledSet(1, false); ledSet(2, false) + /** Initiate led handling. Performs led gpio setup. * @return Success or failure. */ diff --git a/applications/common/log.c b/applications/common/log.c new file mode 100644 index 0000000..424ccf7 --- /dev/null +++ b/applications/common/log.c @@ -0,0 +1,240 @@ +/* + * Copyright 2022 u-blox + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * + * Logging functions + * + */ + +#include +#include + +#include "common.h" +#include "log.h" +#include "ext_fs.h" + +/* ---------------------------------------------------------------- + * DEFINITIONS + * -------------------------------------------------------------- */ + +/// DO NOT PUT printLog() INSIDE this MUTEX LOCK!!! +#define MUTEX_LOCK if (pLogMutex != NULL) uPortMutexLock(pLogMutex) +#define MUTEX_UNLOCK if (pLogMutex != NULL) uPortMutexUnlock(pLogMutex) + +#define LOGBUFF1SIZE 1024 +#define LOGBUFF2SIZE 2048 + +#define FILE_READ_BUFFER 100 + +/* ---------------------------------------------------------------- + * STATIC VARIABLES + * -------------------------------------------------------------- */ +static char buff1[LOGBUFF1SIZE]; +static char buff2[LOGBUFF2SIZE]; + +static struct fs_file_t logFile; +static bool logFileOpen = false; + +static uPortMutexHandle_t pLogMutex = NULL; + +static logLevels_t gLogLevel = eINFO; + +/* ---------------------------------------------------------------- + * GLOBAL VARIABLES + * -------------------------------------------------------------- */ +/// The unix network time, which is retrieved after first registration +int64_t unixNetworkTime = 0; + +/* ---------------------------------------------------------------- + * STATIC FUNCTIONS + * -------------------------------------------------------------- */ +static void createTimeStampLog(const char *log) +{ + char timeStamp[TIMESTAMP_MAX_LENTH_BYTES]; + getTimeStamp(timeStamp); + snprintf(buff1, LOGBUFF1SIZE, "%s: %s\n", timeStamp, log); +} + +static bool printHeader(logLevels_t level, bool writeToFile) +{ + const char *header = NULL; + switch(level) { + case eWARN: + header = "\n*** WARNING ************************************************\n"; + break; + + case eERROR: + header = "\n************************************************************\n" \ + "*** ERROR **************************************************\n"; + break; + + case eFATAL: + header = "\n############################################################\n" \ + "#### FATAL ** FATAL ** FATAL ** FATAL ** FATAL ** FATAL ####\n" \ + "############################################################\n"; + break; + + default: + // no header + break; + } + + if (header == NULL) + return false; + + printf("%s", header); + + if (logFileOpen && writeToFile) + fs_write(&logFile, header, strlen(header)); + + return true; +} + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS + * -------------------------------------------------------------- */ +int openFile(const char *filename, struct fs_file_t *file) +{ + fs_file_t_init(file); + const char *path = extFsPath(filename); + return fs_open(file, path, FS_O_APPEND | FS_O_RDWR); +} + +void setLogLevel(logLevels_t logLevel) +{ + printInfo("Setting log level from %d to %d", gLogLevel, logLevel); + gLogLevel = logLevel; +} + +/// @brief Writes a log message to the terminal and the log file +/// @param log The log, which can contain string formating +/// @param ... The variables for the string format +void _writeLog(const char *log, logLevels_t level, bool writeToFile, ...) +{ + // writeLog("The %s value is %d", "rssi", 1234) + // will log "