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.
| Byte | Field | Description |
|---|---|---|
| 2 | ID | Command ID (e.g., 0x0014 for time) |
| 2 | Type | Data type code (1: Bool, 2: Int, 3: Float, 4: Long) |
| 3-10 | Value | Parameter value (size depends on type) |
Basic commands
Below is the data that a smartphone can transfer to a device.
| Byte | Type | Description |
|---|---|---|
| 0x0001 | Bool | Connection check request |
| 0x0002 | Bool | Response to connection check |
| 0x0014 | Long | Current Timestamp |
| 0x0015 | Float | Latitude |
| 0x0016 | Float | Longitude |
| 0x0017 | Float | Sun azimuth |
| 0x0018 | Float | Sun altitude |
| 0x0019 | Float | Sun zenith angle |
| 0x001A | Float | Sunrise azimuth |
| 0x001B | Float | Sunset azimuth |
| 0x001C | Float | Age of the moon |
| 0x001D | Float | Percentage of moon illumination |
| 0x001E | Float | Moon altitude |
| 0x001F | Float | Moon azimuth |
| 0x0020 | Float | Moonrise azimuth |
| 0x0021 | Float | Moonset azimuth |
| 0x0022 | Long | Distance 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 memoryswitch (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 requestif (pkt->id == CMD_PING){send(CMD_PONG, true);return;}// Transferring a complete package to a user handlerif (_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 sizeuint8_t packetSize = 3 + valSize;uint8_t buffer[11]; // Maximum size (for Long)// Copy the headeruint16_t cmdId = (uint16_t)id;memcpy(buffer, &cmdId, 2);buffer[2] = (uint8_t)type;// Copy the valuememcpy(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 switchCommandID id = (CommandID)pkt->id;// Distribution of logic depending on the received identifierswitch (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 Bluetoothif (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 againBLEDevice::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 managerstation.onCommand(handleCommand);station.onOutput(physicalSend);// Classic Bluetooth initialization (SPP profile)SerialBT.begin("ESP32_Tracker_BT");// Initialization of the BLE stackBLEDevice::init("ESP32_Tracker_BLE");BLEServer *pServer = BLEDevice::createServer();pServer->setCallbacks(new MyServerCallbacks());// GATT service configurationBLEService *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 NotifypTxCharacteristic->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 typepAdvertising->addServiceUUID(SERVICE_UUID);pAdvertising->setScanResponse(true);// Setting intervals for energy efficiency on Apple devicespAdvertising->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 bufferif (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 = espressif32board = esp32devframework = arduinomonitor_speed = 115200upload_speed = 115200