Hiking - Hiking Activity Trackingο
Overviewο
The Hiking app is a comprehensive hiking activity tracking application designed for wearable devices, specifically targeting hiking activities. It provides real-time tracking of distance, speed, pace, heart rate, elevation, steps taken, and floors climbed to deliver accurate and detailed activity data for hikers. The app integrates multiple sensors including GPS, heart rate monitor, barometric pressure sensor, step counter, floor counter, and battery monitoring to provide comprehensive hiking metrics.
The application follows a modular architecture with separate service and GUI components, communicating through a custom message system. It supports features like automatic lap splitting based on distance, time, or step count, activity data persistence in FIT file format, and a rich TouchGFX-based user interface with multiple watch faces and screens.
Key features include:
Real-time GPS tracking with position and speed data
Heart rate monitoring with trust level assessment
Elevation tracking using barometric pressure
Step counting for distance validation
Automatic and manual lap recording
Multiple watch face layouts
Activity summary
Battery level monitoring
Settings for alerts, and notifications
Architectureο
The Hiking app follows a client-server architecture pattern where the service component handles all backend logic, sensor management, and data processing, while the GUI component manages user interaction and display. Communication between these components occurs through a message-based system using the UNA SDKβs kernel infrastructure.
High-Level Componentsο
Service Layer: Core business logic, sensor integration, data processing
GUI Layer: TouchGFX-based user interface, screen management
SDK Integration: Kernel, sensor layer, file system, messaging
Data Persistence: FIT file format for activity data, JSON for settings
Component Interactionο
[Hardware Sensors] <-> [Sensor Layer] <-> [Service]
^ ^ ^
| | |
[Kernel Messages] <-- [Message System] --> [GUI]
The service runs as a separate process/thread, continuously processing sensor data and maintaining activity state. The GUI runs on the TouchGFX framework, handling user input and displaying data received from the service.
Service Backendο
The service backend is implemented in Service.hpp and Service.cpp, providing the core functionality for activity tracking and sensor management.
Core Classes and Structuresο
Service Classο
The main service class handles all backend logic for activity tracking. It manages sensor connections, processes sensor data, maintains activity state, and communicates with the GUI through the UNA SDKβs messaging system. The service runs as a separate process/thread and handles lifecycle management through the kernel interface.
class Service
{
public:
Service(SDK::Kernel &kernel);
virtual ~Service();
void run();
private:
SDK::Kernel& mKernel;
bool mGUIStarted;
CustomMessage::Sender mGuiSender;
// ... additional members
};
Key Data Structuresο
Track Data (Track::Data) β real-time metrics snapshot sent to the GUI every second. Contains pace, distance, time, lap counters, HR (current/avg/max/lap), speed (current/avg/max/lap), elevation, ascent (total and lap), steps, and floors β all in SI units (m, m/s, s/m, bpm). Defined in Libs/Header/Track.hpp.
Sensor Integrationο
Each sensor is represented by an SDK::Sensor::Connection object. Polled sensors are configured with a period and latency; event-based sensors fire when the hardware generates an event and ignore those parameters.
Polled sensors (1000 ms period / 1000 ms latency):
Sensor |
Type constant |
Purpose |
|---|---|---|
GPS Location |
|
Coordinates, altitude, fix state; used for map building |
GPS Speed |
|
Instantaneous speed β |
GPS Distance |
|
Incremental distance β |
Heart Rate |
|
BPM + trust level (1β3) β |
Pressure |
|
Barometric altitude (filtered) β |
Event-based sensors (period/latency ignored):
Sensor |
Type constant |
Purpose |
|---|---|---|
Step Counter |
|
Steps β |
Floor Counter |
|
Floors up + down β |
Battery Level |
|
State of charge β |
Wrist Motion |
|
Wrist raise β backlight activation |
Sensor Connection Managementο
GPS and Wrist Motion connected on GUI start (for fix acquisition before tracking begins)
All remaining sensors connected when tracking starts
All sensors disconnected when tracking stops
Data Processing Pipelineο
The data processing pipeline transforms raw sensor data into meaningful fitness metrics.
1. Sensor Data Receptionο
Sensor data arrives through the kernelβs message system. The handleSensorsData() method identifies the source via matchesDriver(), constructs the appropriate parser, and forwards the value:
void Service::handleSensorsData(uint16_t handle, SDK::Sensor::DataBatch& data) {
if (mSensorGpsLocation.matchesDriver(handle)) {
SDK::SensorDataParser::GpsLocation parser(data[0]);
if (parser.isDataValid()) {
mGps.timestamp = parser.getTimestamp();
mGps.fix = parser.isCoordinatesValid();
if (mGps.fix) {
parser.getCoordinates(mGps.latitude, mGps.longitude, mGps.altitude);
}
LOG_DEBUG("Location: fix %u, lat %f, lon %f\n", mGps.fix, mGps.latitude, mGps.longitude);
}
}
// ... additional sensor handlers
}
2. Data Capture Infrastructureο
GPS coordinates, battery level, and sensor metrics are collected through three complementary mechanisms that together ensure accurate, pause-aware data:
GPS state (mGps) β a simple struct updated on every GPS location event: fix flag, latitude/longitude, altitude (m), and timestamp. Used both for map building and for flagging whether coordinate/speed data is valid before writing to FIT.
Battery samplers β two SDK::Metric::ThrottledSample instances (mBatterySoc, mBatteryVoltage) that periodically write state-of-charge (%) and voltage (V) into the FIT file without flooding it with redundant records.
SDK Metric counters β all sensor values that feed into Track::Data pass through one of three SDK counter types from SDK::Metric. Counters are the central reason accurate per-lap and per-activity statistics are possible with minimal service logic: each counter automatically excludes time spent in the paused state from active totals, separates per-lap values from session totals via resetLap(), and silently rejects sensor anomalies (rollbacks, out-of-range spikes) before they corrupt averages.
Counter |
Suitable for |
Used for |
|---|---|---|
|
Cumulative values that only increase; ignores decreasing sensor readings |
|
|
Fluctuating values; filters readings outside a valid range; tracks avg/min/max per lap |
|
|
Bidirectional changes; accumulates ascent and descent separately with a noise threshold |
|
Raw barometric altitude is pre-filtered through a SimpleLPF (Ξ± = 0.8) before being passed to mAltitudeCounter, because DeltaCounter does not perform any filtering itself.
3. Parser Classesο
Each sensor has a corresponding SDK::SensorDataParser::* class. The pattern is always the same: construct the parser from data[0], check isDataValid(), then extract the value and feed it into the appropriate counter or struct field. The pressure sensor is slightly more complex because it requires a sea-level calibration on the first reading:
SDK::SensorDataParser::Pressure parser(data[0]);
if (parser.isDataValid()) {
if (!mAltitudeCounter.isValid()) {
mSeaLevelPressure = parser.getP0(); // calibrate on first reading
}
float altitude = parser.getAltitude(parser.getPressure(), mSeaLevelPressure);
mAltitudeCounter.add(mAltitudeFilter.execute(altitude)); // pre-filter before DeltaCounter
}
All other parsers (GpsLocation, GpsSpeed, GpsDistance, HeartRate, StepCounter, FloorCounter) follow the simpler isDataValid() β counter.add(value) pattern.
4. Track Processing Logicο
The processTrack() method runs every second during active tracking:
void Service::processTrack() {
// GPS map building
if (mGps.fix) {
mTrackMapBuilder.addPoint({mGps.latitude, mGps.longitude});
}
// Aggregate data for GUI
mTrackData.totalTime = mTimeCounter.getValueActive();
mTrackData.distance = mDistanceCounter.getValueActive();
mTrackData.speed = mSpeedCounter.getCurrent();
// Calculate pace (min/km or min/mile)
mTrackData.pace = getPace(mTrackData.speed, mSpeedCounter.getMinValid());
// Update GUI
mGuiSender.trackData(mTrackData);
// FIT file recording
if (mTrackState == Track::State::ACTIVE) {
ActivityWriter::RecordData fitRecord = prepareRecordData();
mActivityWriter.addRecord(fitRecord);
}
}
5. FIT File Recordingο
Activity data is recorded in FIT (Flexible and Interoperable Data Transfer) format, the standard for fitness devices.
Each record is assembled from the current counter values and GPS state. Fields are optional β each is only written to the FIT file when marked valid via RecordData::set(Field, bool). Fields include: coordinates, speed, altitude, heart rate, and battery state of charge.
6. Error Handling and Data Validationο
GPS Fix State Management:
Fix state changes are tracked via mPreviousGpsFixState. On every change the GUI is notified via mGuiSender.fix(). The very first acquired fix also triggers notifyFirstFix() β backlight on, buzzer pattern (150 ms Γ 3), and a strong vibro click:
if (mPreviousGpsFixState != mGps.fix) {
mPreviousGpsFixState = mGps.fix;
if (!firstFix) {
notifyFirstFix();
firstFix = true;
}
mGuiSender.fix(mGps.fix);
}
Heart Rate Trust Level Filtering:
HR readings are only written to the FIT file when the value is above 20 bpm and the sensor trust level is in the valid range 1β3 (0 means no signal):
bool hasHeartRate = (mHrCounter.getCurrent() > 20 &&
mTrackData.hrTrustLevel >= 1 &&
mTrackData.hrTrustLevel <= 3);
fitRecord.set(ActivityWriter::RecordData::Field::HEART_RATE, hasHeartRate);
Wrist Motion Backlight Activation:
SDK::SensorDataParser::WristMotion parser(data[0]);
if (parser.isDataValid()) {
backlightOn(); // brightness 100%, auto-off after skBacklightTimeout (5000 ms)
}
backlightOn() is a shared helper used across the service (first GPS fix, lap end, wrist motion). It sends a RequestBacklightSet message with brightness 100% and the default 5-second auto-off timeout.
Activity State Managementο
The service maintains track state through Track::State enum:
INACTIVE: No active trackingACTIVE: Currently recording activityPAUSED: Tracking suspended
State transitions are handled by methods like startTrack(), stopTrack(), pauseTrack().
Lap Managementο
Laps can be triggered automatically based on configurable thresholds:
Distance-based: Configurable via
MenuDistanceView(Settings::Alerts::Distance::Id)Time-based: Configurable via
MenuTimeView(Settings::Alerts::Time::Id)Manual: User-initiated via R2 button during tracking
Lap data includes timing, distance, elevation changes, steps, and floors climbed.
Settings and Persistenceο
Settings are stored in JSON format and include:
Alert distance threshold (
Settings::Alerts::Distance::Id)Alert time threshold (
Settings::Alerts::Time::Id)Auto-pause on/off
Phone notification enablement
Activity summaries are persisted for historical data.
Activity Data Management and Persistenceο
The Hiking app implements comprehensive data persistence using multiple storage mechanisms.
FIT File Format Implementationο
ActivityWriter β writes activity data to a FIT file during tracking. Key methods: start(), addRecord() (called every second), addLap(), pause(), resume(), stop(), discard(). Each RecordData carries an optional-field bitmask so only valid sensor readings are written.
Activity Summary Persistenceο
ActivitySummarySerializer β loads and saves ActivitySummary as JSON. Used at activity end (save) and on next app launch (load) to restore the last session for display in the summary screens.
ActivitySummary Structure:
struct LapSummary {
time_t duration; // Lap duration in seconds
float distance; // Lap distance in m
uint32_t steps; // Steps count per lap
};
struct ActivitySummary {
time_t utc; // Last activity UTC time
time_t time; // Total track time in seconds
float distance; // Total track distance in m
float speedAvg; // Average speed in m/s
uint32_t steps; // Total steps count
float elevation; // Elevation in m
float paceAvg; // Average pace in s/m
float hrMax; // Maximum heart rate in bpm
float hrAvg; // Average heart rate in bpm
SDK::TrackMapScreen map; // Track map
std::vector<LapSummary> laps; // Per-lap summary data
};
Settings Persistenceο
SettingsSerializer β loads and saves Settings as JSON. Called on startup (load) and whenever the user changes a setting (save).
Settings Structure:
struct Settings {
bool phoneNotifEn = true;
bool autoPause = false;
Alerts::Distance::Id alertDistance = Alerts::Distance::Id::KM_1;
Alerts::Time::Id alertTime = Alerts::Time::Id::MIN_10;
};
Data Synchronizationο
Real-time GUI Updates:
Track data sent every second during active tracking
Battery level updates on change
GPS fix status notifications
Lap completion events
Persistent Storage:
FIT files written continuously during activity
Summary updated on activity completion
Settings saved on change
GUIο
The GUI is built using the TouchGFX framework and follows the Model-View-Presenter pattern.
Project Structureο
Hiking/Software/Apps/TouchGFX-GUI/
βββ HikingGUI.touchgfx # TouchGFX Designer project
βββ application.config # Application configuration
βββ target.config # Target hardware settings
βββ touchgfx.cmake # CMake integration
βββ gui/ # Generated and custom GUI code
βββ assets/ # Images, fonts, texts
βββ generated/ # Auto-generated code
βββ simulator/ # Simulator builds
Model-View-Presenter Patternο
The GUI follows MVP architecture:
Model:
Model.hpp/cppβ Data management and service communicationView: Various view classes (
TrackView, etc.) β UI renderingPresenter: Presenter classes β Logic binding model and view
Modelο
The Model class (gui/model/Model.hpp) serves as the central data hub:
class Model : public touchgfx::UIEventListener,
public SDK::Interface::IGuiLifeCycleCallback,
public SDK::Interface::ICustomMessageHandler {
public:
void bind(ModelListener* listener);
void tick();
void handleKeyEvent(uint8_t key);
void resetIdleTimer();
void exitApp();
// Time / date
void getDate(uint8_t& month, uint8_t& day, uint8_t& weekday);
void getTime(uint8_t& h, uint8_t& m, uint8_t& s);
// Sensors & settings
uint8_t getBatteryLevel() const;
bool isUnitsImperial() const;
const uint8_t* getHrThresholds() const;
uint8_t getHrThresholdsCount() const;
const Settings& getSettings() const;
void saveSettings(const Settings& sett);
bool hasGpsFix() const;
// Track lifecycle
void trackStart();
bool isTrackActive() const;
void trackPause();
void trackResume();
bool isTrackPaused() const;
const Track::Data& getTrackData() const;
void saveLap();
void saveTrack();
void discardTrack();
// Summary
bool isTrackSummaryAvailable() const;
const ActivitySummary& getTrackSummary() const;
};
Key responsibilities:
Lifecycle management (
onStart,onResume,onSuspend,onStop)Message handling from service (
ICustomMessageHandler)Idle timeout management
Menu position tracking
Screens (15 total)ο
Entry & Main Menu:
MainViewβ App entry point; scroll-wheel menu (Start, Settings); GPS acquisition
Tracking (all face cycling happens inside TrackView via swipeable containers):
TrackViewβ Active tracking screen; hostsTrackFaceTotal,TrackFaceOverview,TrackFaceElevation,TrackFaceStatus(swipe L1/L2)TrackActionViewβ Pause menu: Resume / Summary / Save / DiscardTrackLapViewβ Lap-saved notification (auto-dismisses after 3 s)
Confirmations:
TrackStartConfirmationViewβ Prompt to start without GPS fix; idle timeout β exits appTrackDiscardConfirmationViewβ Confirm discard activity; idle timeout βTrackActionViewTrackDiscardedViewβ Discard feedback (auto-dismisses after 3 s β exits app)TrackSavedViewβ Save feedback (auto-dismisses after 3 s βTrackSummaryView)
Summary (all face cycling happens inside TrackSummaryView):
TrackSummaryViewβ Post-activity summary; hostsSummaryFaceMap,SummaryFaceOverview,SummaryFaceHeartRate(swipe L1/L2)SummaryFaceMapβ route map + total distanceSummaryFaceOverviewβ total distance, elevation, elapsed timeSummaryFaceHeartRateβ max HR, average HR
Settings:
MenuSettingsViewβ Root settings wheel (Alerts, Auto-Pause, Phone Notifications)MenuAlertsViewβ Alert type selection (Distance, Time)MenuDistanceViewβ Distance alert value pickerMenuDistanceSavedViewβ Save confirmation (auto-dismisses after 3 s βMenuAlertsView)MenuTimeViewβ Time alert value pickerMenuTimeSavedViewβ Save confirmation (auto-dismisses after 3 s βMenuAlertsView)
Message Handling Systemο
The Model implements ICustomMessageHandler to receive asynchronous updates from the service. Each message is cast to its concrete type, stored in a Model field, and forwarded to the active presenter via ModelListener:
bool Model::customMessageHandler(SDK::MessageBase *msg) {
switch (msg->getType()) {
case CustomMessage::TRACK_DATA_UPDATE: {
auto *cmsg = static_cast<CustomMessage::TrackDataUpd*>(msg);
mTrackData = cmsg->data; // store
modelListener->onTrackData(mTrackData); // notify presenter
} break;
// ... other message types follow the same pattern
default: break;
}
return true;
}
Messages handled: SETTINGS_UPDATE, LOCAL_TIME, BATTERY, GPS_FIX, TRACK_STATE_UPDATE, TRACK_DATA_UPDATE, LAP_END, SUMMARY.
Custom Containersο
The app uses two categories of containers: ready-made SDK widgets from Templates/TouchGFX-Widgets (imported as .tpkg packages) and app-specific containers built for this appβs data and layout.
SDK Widget Containersο
Imported from Templates/TouchGFX-Widgets and used without modification:
Batteryβ 4-segment battery level indicatorButtonsβ button arc indicators (L1/L2/R1/R2)GpsIndicatorβ blinking GPS fix status dotHeartRateZoneβ 5-zone HR bar visualizationScrollIndicatorβ arc position indicator for face and menu navigationMainMenuβ scroll-wheel menu with bundledScrollIndicatorandToggleMapβ route map with start/end markersPauseIndicatorβ full-width pause overlay with elapsed pause timeTitleβ screen title with underlineInfoCarouselβ auto-cycling multi-value info panel
App-Specific Containersο
Built specifically for this app and found in gui/include/gui/containers/:
Track watch faces (swipeable inside TrackView):
void TrackFaceTotal::setSteps(uint32_t steps);
void TrackFaceTotal::setDistance(float dist, bool isImperial);
void TrackFaceTotal::setTimer(std::time_t sec);
void TrackFaceOverview::setHR(float hr, const uint8_t* thresholds, uint8_t thresholdCount);
void TrackFaceOverview::setPace(float pace);
void TrackFaceOverview::setElevation(float elevation, bool isImperial);
void TrackFaceElevation::setLapAscent(float ascent, bool isImperial);
void TrackFaceElevation::setTotalAscent(float ascent, bool isImperial);
void TrackFaceElevation::setElevation(float elevation, bool isImperial);
void TrackFaceStatus::setTime(uint8_t h, uint8_t m);
void TrackFaceStatus::setBatteryLevel(uint8_t level);
Summary faces (swipeable inside TrackSummaryView):
void SummaryFaceMap::setDistance(float dist, bool isImperial);
void SummaryFaceMap::setMap(const SDK::TrackMapScreen& map);
void SummaryFaceOverview::setDistance(float dist, bool isImperial);
void SummaryFaceOverview::setElevation(float elevation, bool isImperial);
void SummaryFaceOverview::setTimer(std::time_t sec);
void SummaryFaceHeartRate::setMaxHR(float hr);
void SummaryFaceHeartRate::setAvgHR(float hr);
Utility:
CountdownTimerβ self-registering countdown used by auto-dismiss screens (TrackLapView,TrackSavedView,TrackDiscardedView,MenuDistanceSavedView,MenuTimeSavedView)
Input Handlingο
User input is processed through a hierarchical system:
Hardware Events: Button presses detected by TouchGFX HAL
Key Event Processing:
Model::handleKeyEvent()for global actionsScreen-Specific Handling: View classes handle context-specific input
Button Mapping:
L1: Previous item/navigation left
L2: Next item/navigation right
R1: Primary action (menu access)
R2: Secondary action (lap/manual trigger)
Data Formatting and Unitsο
The GUI handles unit conversions and formatting before passing values to containers:
Distance: meters β km (metric) or miles (imperial)
Pace: seconds-per-metre β min/km or min/mile depending on unit setting
Elevation / ascent: metres β feet in imperial mode
Time:
std::time_tseconds passed directly to containers; formatted asMM:SSorH:MM:SSby the containerSteps / floors: raw
uint32_tcounts, no conversion required
Idle Timeoutο
The app implements automatic screen timeout to conserve battery:
void Model::decIdleTimer() {
if (mIdleTimer > 0) {
mIdleTimer--;
if (mIdleTimer == 0) {
modelListener->onIdleTimeout();
}
}
}
void Model::resetIdleTimer() {
mIdleTimer = App::Config::kScreenTimeoutSteps;
}
Idle timer resets on any user interaction, preventing accidental timeouts during active use.
TouchGFX Integration with UNA SDKο
The integration between TouchGFX and UNA SDK is handled through several key components.
TouchGFXCommandProcessorο
SDK::TouchGFXCommandProcessor is a singleton that bridges the SDK kernel and the TouchGFX event loop. It holds a const SDK::Kernel& reference internally and manages GUI lifecycle and incoming custom messages.
namespace SDK {
class TouchGFXCommandProcessor {
public:
static TouchGFXCommandProcessor& GetInstance();
void setAppLifeCycleCallback(SDK::Interface::IGuiLifeCycleCallback* cb);
void setCustomMessageHandler(SDK::Interface::ICustomMessageHandler* h);
bool waitForFrameTick();
bool getKeySample(uint8_t& key);
void writeDisplayFrameBuffer(const uint8_t* data);
// Called before Model::tick() on every frame
void callCustomMessageHandler();
};
} // namespace SDK
FrontendApplication::handleTickEvent() calls callCustomMessageHandler() first, then model.tick(), ensuring pending service messages are dispatched to ModelListener before the presenter updates the view.
Kernel Provider Architectureο
SDK::KernelProviderGUI is a singleton that stores a pointer to the SDK::Kernel object. It is initialised early in the program (before the GUI starts) and later retrieved by the Model:
namespace SDK {
class KernelProviderGUI {
public:
static KernelProviderGUI& CreateInstance(const SDK::Kernel* kernel);
static KernelProviderGUI& GetInstance();
const SDK::Kernel& getKernel();
};
} // namespace SDK
The Model constructor retrieves the kernel via SDK::KernelProviderGUI::GetInstance().getKernel(), ensuring the GUI has access to the same kernel instance used by the service.
Custom Message Systemο
All serviceβGUI messages are declared in Commands.hpp as plain constexpr SDK::MessageType::Type constants (not an enum) with fixed hex IDs. Every message struct inherits from SDK::MessageBase:
namespace CustomMessage {
// Service --> GUI
constexpr SDK::MessageType::Type SETTINGS_UPDATE = 0x00000001;
constexpr SDK::MessageType::Type LOCAL_TIME = 0x00000002;
constexpr SDK::MessageType::Type BATTERY = 0x00000003;
constexpr SDK::MessageType::Type GPS_FIX = 0x00000004;
constexpr SDK::MessageType::Type TRACK_STATE_UPDATE = 0x00000005;
constexpr SDK::MessageType::Type TRACK_DATA_UPDATE = 0x00000006;
constexpr SDK::MessageType::Type LAP_END = 0x00000007;
constexpr SDK::MessageType::Type SUMMARY = 0x00000008;
// GUI --> Service
constexpr SDK::MessageType::Type SETTINGS_SAVE = 0x0000000A;
constexpr SDK::MessageType::Type TRACK_START = 0x0000000B;
constexpr SDK::MessageType::Type TRACK_STOP = 0x0000000C;
constexpr SDK::MessageType::Type TRACK_PAUSE = 0x0000000D;
constexpr SDK::MessageType::Type TRACK_RESUME = 0x0000000E;
constexpr SDK::MessageType::Type MANUAL_LAP = 0x0000000F;
struct SettingsUpd : public SDK::MessageBase {
Settings settings;
bool unitsImperial;
uint8_t hrThresholds[kHrThresholdsCount]; // C-array of 6
uint8_t hrThresholdsCount;
};
struct TrackDataUpd : public SDK::MessageBase {
Track::Data data;
};
// ... other structs follow the same pattern
} // namespace CustomMessage
Message Senderο
CustomMessage::Sender provides type-safe message sending from the service to the GUI. Each method allocates a typed message via mKernel.comm.allocateMessage<>(), fills its fields, sends it, and releases it, returning bool:
class Sender {
public:
Sender(const SDK::Kernel& kernel);
// Service --> GUI
bool settingsUpd(Settings settings, bool units,
const uint8_t (&thresholds)[kHrThresholdsCount],
uint8_t thresholdCount);
bool time(std::tm localTime);
bool battery(uint8_t level);
bool fix(bool state);
bool trackState(Track::State state);
bool trackData(const Track::Data& data);
bool lapEnd(uint32_t lapNum);
bool summary(const ActivitySummary* summaryPtr);
// GUI --> Service
bool settingsSave(Settings settings);
bool trackStart();
bool trackStop(bool discard);
bool trackPause();
bool trackResume();
bool manualLap();
};
Asset Managementο
Images: PNG assets for backgrounds, buttons, icons Fonts: Poppins family for various weights and styles Texts: Localized strings in XML format
Code Generationο
TouchGFX Designer generates:
Screen base classes (
TrackViewBase, etc.)Container implementations
Bitmap databases
Font and text resources
Custom code extends base classes with app-specific logic.
Simulator Supportο
The TouchGFX project includes simulator builds for development:
Visual Studio project for Windows
Mock sensor data for development
Visual debugging capabilities
Build and Setupο
The Hiking app uses CMake for cross-platform builds targeting embedded hardware and simulation.
Build System Overviewο
Primary Build File: CMakeLists.txt in Hiking/Software/Apps/Hiking-CMake/
# App configuration
set(APP_NAME "Hiking")
set(APP_TYPE "Activity")
set(DEV_ID "UNA")
set(APP_ID "A1F3C92B7E4D8A10")
# Include SDK build scripts
include($ENV{UNA_SDK}/cmake/una-app.cmake)
include($ENV{UNA_SDK}/cmake/una-sdk.cmake)
Build Targetsο
Service Build:
set(SERVICE_SOURCES
${LIBS_SOURCES}
${UNA_SDK_SOURCES_COMMON}
${UNA_SDK_SOURCES_SERVICE}
)
una_app_build_service(${APP_NAME}Service.elf)
GUI Build:
set(GUI_SOURCES
${TOUCHGFX_SOURCES}
${UNA_SDK_SOURCES_COMMON}
${UNA_SDK_SOURCES_GUI}
)
una_app_build_gui(${APP_NAME}GUI.elf)
Complete App:
una_app_build_app()
Dependenciesο
SDK Components:
UNA SDK common, service, and GUI sources
Sensor layer interfaces
Kernel and messaging systems
External Libraries:
TouchGFX framework
Custom app libraries (ActivityWriter, etc.)
Build Processο
CMake Configuration: Sets up toolchain and paths
Source Collection: Gathers all source files
Compilation: Separate builds for service and GUI
Linking: Creates ELF executables
Packaging: Combines into deployable app package
Development Setupο
See SDK Setup and Build Overview for comprehensive development environment setup, build instructions, and toolchain requirements.
Simulator Buildο
TouchGFX provides simulator builds for PC development:
Visual Studio project for Windows
Makefile for Linux
Includes mock hardware interfaces
Conclusionο
The Hiking app demonstrates a sophisticated implementation of a hiking activity tracking application on wearable devices. Its modular architecture separates concerns effectively between service logic and user interface, enabling robust sensor integration and real-time data processing.
Key architectural strengths include:
Separation of Concerns: Clear division between service and GUI
Message-Based Communication: Loose coupling between components
Extensible Sensor Framework: Easy addition of new sensors
Rich UI Framework: TouchGFX provides professional user experience
Data Persistence: FIT file format ensures interoperability