Mobile Station

Mobile Station is a functional extension of the Sungres and Moongres mobile applications. It turns a smartphone into a data source for microcontrollers (ESP32, Arduino, STM32), transmitting calculated astronomical coordinates via Bluetooth (BLE).

The protocol allows an external device to obtain the exact position of the Sun and Moon using GPS and the computing power of a smartphone. This eliminates the need to implement complex mathematical formulas directly into the microcontroller code.

Data package specification

Each data packet has a strict structure that allows the device to instantly understand what command has arrived and how to read it.

ByteFieldDescription
2IDCommand ID (e.g., 0x0014 for time)
2TypeData type code (1: Bool, 2: Int, 3: Float, 4: Long)
3-10ValueParameter value (size depends on type)

Basic commands

Below is the data that a smartphone can transfer to a device.

ByteTypeDescription
0x0001BoolConnection check request
0x0002BoolResponse to connection check
0x0014LongCurrent Timestamp
0x0015FloatLatitude
0x0016FloatLongitude
0x0017FloatSun azimuth
0x0018FloatSun altitude
0x0019FloatSun zenith angle
0x001AFloatSunrise azimuth
0x001BFloatSunset azimuth
0x001CFloatAge of the moon
0x001DFloatPercentage of moon illumination
0x001EFloatMoon altitude
0x001FFloatMoon azimuth
0x0020FloatMoonrise azimuth
0x0021FloatMoonset azimuth
0x0022LongDistance to the Moon in kilometers

Program implementation for ESP32

Two files are used to integrate your device with the Sungres/Moongres ecosystem: the protocol manager and the main firmware file.

Protocol core (StationManager.h)

This file is responsible for deserializing incoming bytes, checking packet integrity, and providing a convenient interface for reading data.

/**
* @file StationManager.h
* @brief The core of the binary protocol.
*/
#ifndef STATION_MANAGER_H
#define STATION_MANAGER_H
#include <Arduino.h>
#include <functional>
/**
* @brief Command Identifier Dictionary.
* Must be tightly synchronized with mobile applications.
*/
enum CommandID : uint16_t
{
CMD_PING = 0x0001,
CMD_PONG = 0x0002,
CMD_TIME = 0x0014,
CMD_LATITUDE = 0x0015,
CMD_LONGITUDE = 0x0016,
CMD_SUN_AZIMUTH = 0x0017,
CMD_SUN_ALTITUDE = 0x0018,
CMD_SUN_ZENITH = 0x0019,
CMD_SUN_RISE_AZIMUTH = 0x001A,
CMD_SUN_SET_AZIMUTH = 0x001B,
CMD_MOON_AGE = 0x001C,
CMD_MOON_ILLUMINATION = 0x001D,
CMD_MOON_ALTITUDE = 0x001E,
CMD_MOON_AZIMUTH = 0x001F,
CMD_MOON_RISE_AZIMUTH = 0x0020,
CMD_MOON_SET_AZIMUTH = 0x0021,
CMD_MOON_DISTANCE = 0x0022
};
/** @brief Data types supported by the protocol */
enum DataType : uint8_t
{
/** 1 byte */
TYPE_BOOL = 1,
/** 4 byte */
TYPE_INT = 2,
/** 4 byte */
TYPE_FLOAT = 3,
/** 8 byte */
TYPE_LONG = 4
};
/**
* @brief Binary package structure.
*/
struct __attribute__((packed)) StationPacket
{
uint16_t id;
uint8_t type;
union
{
bool bVal;
int32_t iVal;
float fVal;
int64_t lVal;
} payload;
};
/**
* @class StationManager
* @brief Class for processing and forming data packages.
*/
class StationManager
{
public:
/**
* @brief Type of callback for command processing.
* Passes a pointer to the packet, allowing the user to choose the decoding method.
*/
using OnCommandCallback = std::function<void(StationPacket *)>;
/** @brief Callback type for physical layer dispatch (BLE/Serial) */
using SendRawCallback = std::function<void(uint8_t *, size_t)>;
StationManager() {}
// --- Handler settings ---
void onCommand(OnCommandCallback cb) { _onCommand = cb; }
void onOutput(SendRawCallback cb) { _sendRaw = cb; }
// --- Methods to assist in the secure extraction of data ---
/** @brief Returns a value as a float */
float asFloat(StationPacket *pkt) { return pkt->payload.fVal; }
/** @brief Returns a value as a precise 64-bit integer */
int64_t asLong(StationPacket *pkt) { return pkt->payload.lVal; }
/** @brief Returns a value as a 32-bit integer */
int32_t asInt(StationPacket *pkt) { return pkt->payload.iVal; }
/** @brief Returns a value as a boolean */
bool asBool(StationPacket *pkt) { return pkt->payload.bVal; }
/**
* @brief The primary input method for processing bytes.
* Performs packet length validation depending on the declared type.
*/
void processIncoming(uint8_t *data, size_t len)
{
if (len < 3)
return; // Minimum header (ID + Type)
StationPacket *pkt = (StationPacket *)data;
// Packet length validation to prevent reading “garbage” from memory
switch (pkt->type)
{
case TYPE_BOOL:
if (len < 4)
return;
break;
case TYPE_INT:
case TYPE_FLOAT:
if (len < 7)
return;
break;
case TYPE_LONG:
if (len < 11)
return;
break;
default:
return; // Unknown type - ignore the package
}
// Automatic response to a PING system request
if (pkt->id == CMD_PING)
{
send(CMD_PONG, true);
return;
}
// Transferring a complete package to a user handler
if (_onCommand)
{
_onCommand(pkt);
}
}
/** @brief Sending a logical value (1 byte of payload) */
void send(CommandID id, bool value)
{
sendRawPacket(id, TYPE_BOOL, &value, sizeof(bool));
}
/** @brief Sending an integer (4 bytes of payload) */
void send(CommandID id, int32_t value)
{
sendRawPacket(id, TYPE_INT, &value, sizeof(int32_t));
}
/** @brief Sending a floating point number (4 bytes of payload) */
void send(CommandID id, float value)
{
sendRawPacket(id, TYPE_FLOAT, &value, sizeof(float));
}
/** @brief Sending a long integer (8 bytes of payload) */
void send(CommandID id, int64_t value)
{
sendRawPacket(id, TYPE_LONG, &value, sizeof(int64_t));
}
private:
OnCommandCallback _onCommand;
SendRawCallback _sendRaw;
/**
* @brief Sending data (in Float format by default).
*/
void sendRawPacket(CommandID id, DataType type, const void *valPtr, size_t valSize)
{
// Size: 2 (ID) + 1 (Type) + Value size
uint8_t packetSize = 3 + valSize;
uint8_t buffer[11]; // Maximum size (for Long)
// Copy the header
uint16_t cmdId = (uint16_t)id;
memcpy(buffer, &cmdId, 2);
buffer[2] = (uint8_t)type;
// Copy the value
memcpy(buffer + 3, valPtr, valSize);
if (_sendRaw)
{
_sendRaw(buffer, packetSize);
}
}
};
#endif // STATION_MANAGER_H

Usage example (main.cpp)

This code initializes the Bluetooth stacks and links the physical transmission layer to the protocol logic.

/**
* @file main.cpp
* @brief Example of using the library for ESP32.
*/
#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <BluetoothSerial.h>
#include "StationManager.h"
// --- UUID configuration ---
/** @brief Service UUID (Nordic UART style) */
#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
/** @brief Characteristic for obtaining data from the phone (Write) */
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
/** @brief Characteristics for sending data to a phone (Notify) */
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
// --- Global objects ---
/** Binary Protocol Manager */
StationManager station;
/** Bluetooth */
BluetoothSerial SerialBT;
/** Link to TX channel for Notify */
BLECharacteristic *pTxCharacteristic = NULL;
/** Active BLE connection flag */
bool deviceConnected = false;
/**
* @brief The main processor of the system's logical commands.
* This function is automatically called by the StationManager object when the device receives a valid binary packet via Bluetooth.
* @param pkt A pointer to the StationPacket structure containing the command ID, data type, and the value itself in union format.
*/
void handleCommand(StationPacket *pkt)
{
// Converting command IDs to the CommandID list for convenient operation in switch
CommandID id = (CommandID)pkt->id;
// Distribution of logic depending on the received identifier
switch (id)
{
case CMD_TIME:
{
/**
* @note Time synchronisation processing.
* We use the asLong() method to obtain a 64-bit number (Unix Timestamp).
*/
int64_t timestamp = station.asLong(pkt);
Serial.printf("> Unix Timestamp: %lld
", timestamp);
// Here you can call the RTC (Internal Real Time Clock) update function.
break;
}
case CMD_SUN_SET_AZIMUTH:
case CMD_SUN_RISE_AZIMUTH:
case CMD_SUN_ZENITH:
case CMD_SUN_AZIMUTH:
case CMD_SUN_ALTITUDE:
case CMD_MOON_SET_AZIMUTH:
case CMD_MOON_RISE_AZIMUTH:
case CMD_MOON_AZIMUTH:
case CMD_MOON_ALTITUDE:
{
/** @note Get calculated data for the Sun or Moon from your phone. */
float sunPos = station.asFloat(pkt);
Serial.printf("> Command 0x%04X: value %.2f
", id, sunPos);
break;
}
case CMD_PING:
/**
* @note System connection check request.
* StationManager usually responds to it automatically (PONG), but here we can add a visual indication (e.g., LED flashing).
*/
Serial.println("> PING");
break;
default:
/** @note Processing unknown commands */
Serial.printf("> Unknown team ID received: 0x%04X, type: %d
", id, pkt->type);
break;
}
}
/**
* @brief Physical level of data transmission.
* @param data Pointer to an array of bytes
* @param len Array length
*/
void physicalSend(uint8_t *data, size_t len)
{
// Sending via BLE Notify (if a client is connected)
if (deviceConnected && pTxCharacteristic)
{
pTxCharacteristic->setValue(data, len);
pTxCharacteristic->notify();
}
// Sending via Classic Bluetooth
if (SerialBT.hasClient())
{
SerialBT.write(data, len);
}
}
/**
* @brief Callbacks for monitoring the status of the BLE server.
*/
class MyServerCallbacks : public BLEServerCallbacks
{
/** Called when establishing a connection */
void onConnect(BLEServer *pServer)
{
deviceConnected = true;
Serial.println("BLE: Connected");
};
/** Called when the connection is disconnected */
void onDisconnect(BLEServer *pServer)
{
deviceConnected = false;
Serial.println("BLE: Disconnected");
// Restart to make the device visible to search again
BLEDevice::startAdvertising();
}
};
/**
* @brief Callbacks for processing incoming data via BLE.
*/
class MyCharacteristicCallbacks : public BLECharacteristicCallbacks
{
/**
* Called when an external device (phone) writes data to the RX characteristic and transmits raw bytes to the StationManager binary parser.
*/
void onWrite(BLECharacteristic *pChar)
{
std::string rx = pChar->getValue();
if (rx.length() > 0)
{
station.processIncoming((uint8_t *)rx.data(), rx.length());
}
}
};
/**
* @brief Initial system initialization.
*/
void setup()
{
Serial.begin(115200);
// Binding logical methods to the protocol manager
station.onCommand(handleCommand);
station.onOutput(physicalSend);
// Classic Bluetooth initialization (SPP profile)
SerialBT.begin("ESP32_Tracker_BT");
// Initialization of the BLE stack
BLEDevice::init("ESP32_Tracker_BLE");
BLEServer *pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
// GATT service configuration
BLEService *pService = pServer->createService(SERVICE_UUID);
// Creating TX characteristics (for phone notifications)
pTxCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID_TX,
BLECharacteristic::PROPERTY_NOTIFY);
// Allows the customer to subscribe to Notify
pTxCharacteristic->addDescriptor(new BLE2902());
// Creating an RX characteristic (for commands from a phone)
BLECharacteristic *pRxCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID_RX,
BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR);
pRxCharacteristic->setCallbacks(new MyCharacteristicCallbacks());
pService->start();
// These settings make the device visible to iOS/Android when scanning.
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
// Adding UUID to the package so that the phone knows the device type
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
// Setting intervals for energy efficiency on Apple devices
pAdvertising->setMinPreferred(0x06);
pAdvertising->setMinPreferred(0x12);
BLEDevice::startAdvertising();
Serial.println("System Ready: BLE Advertising started.");
}
/**
* @brief The main cycle of the program.
*/
void loop()
{
// Processing the Classic Bluetooth input buffer
if (SerialBT.available() >= 3)
{
uint8_t buf[64];
size_t len = SerialBT.readBytes(buf, SerialBT.available());
station.processIncoming(buf, len);
}
// Example of cyclic ping transmission (once every 5 seconds)
static unsigned long lastTele = 0;
if (millis() - lastTele > 60000)
{
if (deviceConnected)
{
station.send(CMD_PING, true);
}
lastTele = millis();
}
}

Project configuration (platformio.ini)

This file will automatically install the necessary dependencies and configure the compilation settings for your ESP32.

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
upload_speed = 115200