diff --git a/.cproject b/.cproject
index 428c743..1bacdf4 100644
--- a/.cproject
+++ b/.cproject
@@ -14,7 +14,7 @@
-
+
@@ -25,6 +25,7 @@
+
@@ -123,7 +124,7 @@
-
+
@@ -134,6 +135,7 @@
+
diff --git a/.gitignore b/.gitignore
index 76a8d32..d2c7993 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,12 +4,23 @@
# Build folders
Debug/
-Disable_BLE/
Release/
Binary/
+Deploy/Firmware_As_Built/*
+Deploy/Firmware_To_Deploy/*
# Created in prebuild scrip
/FlySight/version.h
# Locally stored "Eclipse launch configurations"
*.launch
+
+# Test build products
+Tests/*.exe
+
+# OS artifacts
+.DS_Store
+
+# Python
+__pycache__/
+*.pyc
\ No newline at end of file
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..4e7167a
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,94 @@
+# FlySight 2 Firmware
+
+## Project Overview
+
+FlySight 2 is a high-precision GPS data logger for skydiving and BASE jumping by Bionic Avionics Inc. The firmware runs on an STM32WB5MMGHx dual-core MCU (Cortex-M4 application + Cortex-M0+ BLE radio). It provides real-time audio feedback during freefall, multi-sensor data logging, BLE connectivity, USB mass storage, and an ActiveLook HUD display on Engo smart glasses.
+
+### Key Features
+- GNSS/GPS tracking at up to 25 Hz (u-blox MAX-M8)
+- 6-axis IMU (LSM6DSO), barometer, magnetometer, humidity sensor
+- Real-time audio tone/speech feedback based on flight parameters
+- Data logging to SD card via FatFS
+- BLE connectivity (STM32 WPAN stack) for phone app and ActiveLook glasses
+- USB mass storage for file transfer
+- ActiveLook HUD display: 2x2 grid layout with icons, B612 aviation font
+
+### Build System
+- **IDE**: STM32CubeIDE (Eclipse-based, ARM GCC toolchain)
+- **MCU**: STM32WB5MMGHx (dual-core M4+M0+, BLE 5.x)
+- **No Makefile/CMake** -- build is defined in `.cproject`/`.project` files
+- **Pre-build**: `prebuild.sh` generates `version.h` from git tag
+- **Post-build**: generates `UserApp.bin` and copies to `Deploy/Firmware_As_Built/`
+
+### Branch Structure
+- `master` -- stable releases
+- `develop` -- active development (ActiveLook integration, new features)
+
+## Rules for Claude Code
+
+### Language Server Protocol (LSP)
+- **ALWAYS use LSP** (documentSymbol, hover, goToDefinition, findReferences, callHierarchy) when analyzing or reading C/C++ source files
+- Prefer LSP over Read tool or grep for understanding code structure
+- Only fall back to Read for non-code files (configs, scripts, docs) or when LSP can't resolve symbols due to missing STM32 HAL headers
+- Use LSP for Python files as well when available
+
+### ActiveLook Development
+- **ALWAYS consult the ActiveLook API documentation** before any implementation: https://github.com/ActiveLook/Activelook-API-Documentation/blob/main/ActiveLook_API.md
+- **Never guess coordinates or protocol details** -- read the documentation first
+- ActiveLook display coordinate system: (0,0) = bottom-right visually (optical mirror flips both axes)
+ - High X = visually LEFT, Low X = visually RIGHT
+ - High Y = visually TOP, Low Y = visually BOTTOM
+ - Rotation 4 = normal readable text with centered anchor
+- Page coordinates are NOT mirrored (standard: x=0 left, higher y = higher on screen)
+- Layout-internal coordinates (extra sub-commands) ARE mirrored
+- Images must be rotated 180 degrees before upload to compensate for optical mirror
+- Config names use NUL-terminated strings (NOT padded to 12 bytes) when used with Config-Generator, but FlySight firmware pads to 12 bytes
+- `cfgWrite` requires the correct password if config already exists (FlySight uses password `0x01020304`)
+- `cfgSet` must be called to activate a config before its fonts/images are available
+- Font upload uses `fontSave` with header `[id, size_hi, size_lo]` then data chunks
+- Image upload uses `imgSave` with header `[id, size_u32, width_u16, format]` then data chunks
+- Maximum BLE write payload = MTU - 3 (currently 253 bytes with MTU=256)
+- Reference the ActiveLook Visual Assets for pre-loaded icons: https://github.com/ActiveLook/Activelook-Visual-Assets
+- Reference the ActiveLook Config-Generator for font/image conversion: https://github.com/ActiveLook/Config-Generator
+
+### STM32/Embedded Best Practices
+- Follow existing code patterns -- the project uses STM32 HAL, WPAN middleware, and a cooperative scheduler (UTIL_SEQ), not FreeRTOS
+- One BLE command per scheduler task invocation -- never send multiple BLE writes in a tight loop
+- Use `UTIL_SEQ_SetTask()` to schedule the next step, not blocking delays
+- Respect the watchdog timer (IWDG, 3-second timeout)
+- All sensor I/O is DMA-based and interrupt-driven -- never block in ISR context
+- Stack is limited (0x400 bytes) -- avoid large local arrays; use static buffers
+- Flash is limited to 454 KB for the application (bootloader occupies lower flash)
+- RAM1 is 256 KB, RAM_SHARED is 10 KB (BLE radio)
+- Use `static` for file-scope variables and functions
+- The project uses `newlib-nano` with float printf enabled (`-u _printf_float`)
+
+### C Language Style
+- Follow existing project conventions (Bionic Avionics coding style)
+- Prefix all public functions with `FS_` (FlySight namespace)
+- Use `typedef enum` and `typedef struct` with `_t` suffix
+- State machines use function pointer tables (see `mode.c`, `crs.c`, `activelook.c`)
+- Ring buffers for producer/consumer patterns (see `log.c`, `sensor.c`)
+- No dynamic memory allocation (`malloc`/`free`) -- use static arrays
+- Error handling: log events via `FS_Log_WriteEvent()`, set LED color for health status
+- Configuration is file-based (text config on SD card, parsed at startup)
+
+### Code Quality
+- Do not add unnecessary comments -- the code should be self-documenting
+- Do not add features beyond what was requested
+- Do not guess at protocol details -- verify against documentation or test empirically
+- When modifying ActiveLook layout code, make one change at a time and test
+- The clangd LSP will show false errors for STM32 HAL symbols (can't resolve `main.h`) -- these are expected and don't affect the actual STM32CubeIDE build
+
+### Deployment
+- Build with STM32CubeIDE (Debug or Release configuration)
+- Deploy via encrypted `.sfb` firmware update files (see `Deploy/deploy_firmware.py`)
+- Each device batch has a specific public key -- check `flysight.txt` on device for `Pubkey_X`
+- ActiveLook config (fonts/images) uploaded separately via `Config-Generator/upload_ble.py`
+
+### Key Directories
+- `FlySight/` -- main application code (sensors, audio, logging, modes, ActiveLook)
+- `Core/Src/` -- STM32 HAL initialization, main loop, interrupt handlers
+- `STM32_WPAN/App/` -- BLE application layer (app_ble.c, custom services, ActiveLook client)
+- `Deploy/` -- firmware deployment scripts and tools
+- `Config-Generator/` (external, at `~/Projects/Config-Generator/`) -- ActiveLook font/image preparation tool
diff --git a/Core/Inc/app_conf.h b/Core/Inc/app_conf.h
index 0836da2..69abc3d 100644
--- a/Core/Inc/app_conf.h
+++ b/Core/Inc/app_conf.h
@@ -187,7 +187,13 @@
#define L2CAP_TIMEOUT_MULTIPLIER 0x1F4
/* USER CODE BEGIN Specific_Parameters */
-
+#define SCAN_P (0x320)
+#define SCAN_L (0x320)
+#define CONN_P1 (CONN_P(200))
+#define CONN_P2 (CONN_P(1000))
+#define SUPERV_TIMEOUT (400)
+#define CONN_L1 (CONN_L(10))
+#define CONN_L2 (CONN_L(10))
/* USER CODE END Specific_Parameters */
/******************************************************************************
@@ -197,7 +203,7 @@
* Maximum number of simultaneous connections that the device will support.
* Valid values are from 1 to 8
*/
-#define CFG_BLE_NUM_LINK 1
+#define CFG_BLE_NUM_LINK 2
/**
* Maximum number of Services that can be stored in the GATT database.
@@ -673,8 +679,15 @@ typedef enum
#endif
CFG_TASK_HCI_ASYNCH_EVT_ID,
/* USER CODE BEGIN CFG_Task_Id_With_HCI_Cmd_t */
- CFG_TASK_LINK_CONFIG_ID,
- CFG_TASK_ADV_UPDATE_ID,
+ CFG_TASK_LINK_CONFIG_ID,
+ CFG_TASK_ADV_UPDATE_ID,
+ CFG_TASK_START_SCAN_ID,
+ CFG_TASK_CONN_DEV_1_ID,
+ CFG_TASK_DISCONN_DEV_1_ID,
+ CFG_TASK_BLE_TX_QUEUE_TRANSMIT_ID,
+ CFG_TASK_FS_CRS_UPDATE_ID,
+ CFG_TASK_FS_START_UPDATE_ID,
+ CFG_TASK_FS_ACTIVELOOK_ID,
/* USER CODE END CFG_Task_Id_With_HCI_Cmd_t */
CFG_LAST_TASK_ID_WITH_HCICMD, /**< Shall be LAST in the list */
} CFG_Task_Id_With_HCI_Cmd_t;
@@ -693,11 +706,7 @@ typedef enum
CFG_TASK_FS_AUDIO_CONTROL_PRODUCER_ID,
CFG_TASK_FS_AUDIO_CONTROL_CONSUMER_ID,
CFG_TASK_FS_CONFIG_UPDATE_ID,
- CFG_TASK_CUSTOM_CRS_TRANSMIT_ID,
- CFG_TASK_CUSTOM_GNSS_TRANSMIT_ID,
- CFG_TASK_FS_CRS_UPDATE_ID,
- CFG_TASK_FS_START_UPDATE_ID,
- CFG_TASK_CUSTOM_START_TRANSMIT_ID,
+ CFG_TASK_FS_VBAT_LOG_ERROR_ID,
CFG_TASK_FS_WATCHDOG_UPDATE_ID,
/* USER CODE END CFG_Task_Id_With_NO_HCI_Cmd_t */
CFG_LAST_TASK_ID_WITH_NO_HCICMD /**< Shall be LAST in the list */
diff --git a/Core/Inc/hw_conf.h b/Core/Inc/hw_conf.h
index 32ed8e0..299ead8 100644
--- a/Core/Inc/hw_conf.h
+++ b/Core/Inc/hw_conf.h
@@ -109,7 +109,7 @@
* The user may define the maximum number of virtual timers supported.
* It shall not exceed 255
*/
-#define CFG_HW_TS_MAX_NBR_CONCURRENT_TIMER 19
+#define CFG_HW_TS_MAX_NBR_CONCURRENT_TIMER 20
/**
* The user may define the priority in the NVIC of the RTC_WKUP interrupt handler that is used to manage the
diff --git a/Core/Src/main.c b/Core/Src/main.c
index c3daf9b..62f27cb 100644
--- a/Core/Src/main.c
+++ b/Core/Src/main.c
@@ -103,6 +103,7 @@ static uint8_t watchdog_timer_id;
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void PeriphCommonClock_Config(void);
+static void MX_RF_Init(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_IPCC_Init(void);
@@ -110,7 +111,6 @@ static void MX_RTC_Init(void);
static void MX_I2C3_Init(void);
static void MX_SPI1_Init(void);
static void MX_TIM1_Init(void);
-static void MX_RF_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
@@ -165,13 +165,13 @@ int main(void)
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
+ MX_RF_Init();
MX_GPIO_Init();
MX_DMA_Init();
MX_RTC_Init();
MX_I2C3_Init();
MX_SPI1_Init();
MX_TIM1_Init();
- MX_RF_Init();
/* USER CODE BEGIN 2 */
FS_State_Init();
FS_IMU_Init();
diff --git a/Deploy/Public_Keys/pub_key_b2.bin b/Deploy/Public_Keys/pub_key_b2.bin
new file mode 100644
index 0000000..004448d
--- /dev/null
+++ b/Deploy/Public_Keys/pub_key_b2.bin
@@ -0,0 +1 @@
+Hk-=H-T,旱j
,j5mt}X+#n=4m
\ No newline at end of file
diff --git a/Deploy/Public_Keys/pub_key_b3.bin b/Deploy/Public_Keys/pub_key_b3.bin
new file mode 100644
index 0000000..e812003
--- /dev/null
+++ b/Deploy/Public_Keys/pub_key_b3.bin
@@ -0,0 +1 @@
+!rV̻[&V1^)7jyHl \>\͉wѐ`YB
\ No newline at end of file
diff --git a/Deploy/Public_Keys/pub_key_b4.bin b/Deploy/Public_Keys/pub_key_b4.bin
new file mode 100644
index 0000000..d2097c5
--- /dev/null
+++ b/Deploy/Public_Keys/pub_key_b4.bin
@@ -0,0 +1,2 @@
+
%q9
+mBVj7:5ܼ"ktsQ1Q(B4溯A<<
\ No newline at end of file
diff --git a/Deploy/Public_Keys/pub_key_b5.bin b/Deploy/Public_Keys/pub_key_b5.bin
new file mode 100644
index 0000000..18186f5
--- /dev/null
+++ b/Deploy/Public_Keys/pub_key_b5.bin
@@ -0,0 +1 @@
+1W(F*x4~ l7
;Eti5>:N-s1R|BUָ
\ No newline at end of file
diff --git a/Deploy/Public_Keys/pub_key_b6.bin b/Deploy/Public_Keys/pub_key_b6.bin
new file mode 100644
index 0000000..e92f8df
--- /dev/null
+++ b/Deploy/Public_Keys/pub_key_b6.bin
@@ -0,0 +1 @@
+ זt=l.9s<άΈˤbZn^$퀏SSj%2
\ No newline at end of file
diff --git a/Deploy/deploy_firmware.py b/Deploy/deploy_firmware.py
new file mode 100644
index 0000000..2e9bb1a
--- /dev/null
+++ b/Deploy/deploy_firmware.py
@@ -0,0 +1,126 @@
+import os
+import struct
+from coincurve import PrivateKey
+from ecdh_aesgcm import encrypt_with_eph_and_nonce
+
+ZERO = b'\x00'
+GROUP_ORDER = (
+ b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
+ b'\xfe\xba\xae\xdc\xe6\xafH\xa0;\xbf\xd2^\x8c\xd06AA'
+)
+
+def get_valid_secret() -> bytes:
+ """
+ Generate a random 32-byte scalar in range (0, GROUP_ORDER).
+ """
+ while True:
+ secret = os.urandom(32)
+ if ZERO < secret < GROUP_ORDER:
+ return secret
+
+def encrypt_firmware_with_key(
+ fw_file: str,
+ fw_name: str,
+ pk_file: str,
+ pk_name: str,
+ output_dir: str = "Firmware_To_Deploy"
+):
+ """
+ Reads a firmware file and a public key file from disk,
+ encrypts the firmware, creates a signed header, and writes an .sfb
+ to output_dir/{pk_name}_{fw_name}.sfb.
+ """
+ print("-------------------------------------------------")
+ print(f"Encrypting {fw_name} with public key {pk_name}")
+ print("-------------------------------------------------")
+
+ # 1) Read receiver's public key
+ with open(pk_file, 'rb') as f:
+ receiver_pub_bytes = f.read()
+
+ # 2) Generate ephemeral key + nonce
+ eph_sk_bytes = get_valid_secret()
+ eph_sk = PrivateKey(eph_sk_bytes)
+ nonce = os.urandom(12)
+
+ # 3) Read the firmware data
+ with open(fw_file, 'rb') as f:
+ firmware_data = f.read()
+
+ # 4) Encrypt firmware
+ enc_fw = encrypt_with_eph_and_nonce(
+ receiver_pubkey_bytes=receiver_pub_bytes,
+ plaintext=firmware_data,
+ ephemeral_sk=eph_sk,
+ nonce=nonce
+ )
+ # parse out ephemeral pub, nonce, tag, ciphertext
+ fw_eph_pub = enc_fw[1:65] # ignoring leading 0x04
+ fw_nonce = enc_fw[65:77]
+ fw_tag = enc_fw[77:93]
+ fw_encrypted = enc_fw[93:]
+
+ print("Firmware ephemeral public key:", fw_eph_pub.hex())
+ print("Firmware nonce: ", fw_nonce.hex())
+ print("Firmware tag: ", fw_tag.hex())
+
+ # 5) Create the SFU header.
+ header_binary = b"SFU1" + struct.pack(
+ "HHIII", 1, 1, len(firmware_data), 0, len(firmware_data)
+ )
+ header_binary += fw_tag
+ header_binary += fw_tag
+ header_binary += fw_nonce
+ header_binary += fw_eph_pub
+ header_binary += b"\x00" * 48
+
+ # 6) Generate a GMAC tag over this header by encrypting an empty payload
+ # with the same ephemeral key + nonce, passing the header as AAD
+ enc_hdr = encrypt_with_eph_and_nonce(
+ receiver_pubkey_bytes=receiver_pub_bytes,
+ plaintext=b"",
+ ephemeral_sk=eph_sk,
+ nonce=nonce,
+ aad=header_binary
+ )
+ hdr_tag = enc_hdr[77:93] # The 16-byte AES-GCM tag
+
+ print("Header GMAC tag:", hdr_tag.hex())
+
+ # Append the GMAC tag to the header
+ header_binary += hdr_tag
+
+ # Fill out the rest of the header
+ header_binary += b'\xff' * 32
+ header_binary += b'\xff' * 64
+ header_binary += b'\x00' * 32
+ header_binary += b'\xff' * 192
+
+ # 7) Write .sfb file
+ os.makedirs(output_dir, exist_ok=True)
+ output_file = f"{output_dir}/{pk_name}_{fw_name}.sfb"
+ with open(output_file, 'wb') as out:
+ out.write(header_binary)
+ out.write(fw_encrypted)
+
+ print(f"Wrote {output_file}\n")
+
+def main():
+ fw_files = [
+ ('Firmware_As_Built/UserApp.bin', 'UserApp'),
+ ]
+
+ pk_files = [
+ ('Public_Keys/pub_key_b2.bin', 'B2'),
+ ('Public_Keys/pub_key_b3.bin', 'B3'),
+ ('Public_Keys/pub_key_b4.bin', 'B4'),
+ ('Public_Keys/pub_key_b5.bin', 'B5'),
+ ('Public_Keys/pub_key_b6.bin', 'B6')
+ ]
+
+ for fw_file, fw_name in fw_files:
+ for pk_file, pk_name in pk_files:
+ encrypt_firmware_with_key(fw_file, fw_name, pk_file, pk_name)
+
+if __name__ == "__main__":
+ main()
diff --git a/Deploy/ecdh_aesgcm.py b/Deploy/ecdh_aesgcm.py
new file mode 100644
index 0000000..bd44569
--- /dev/null
+++ b/Deploy/ecdh_aesgcm.py
@@ -0,0 +1,113 @@
+import os
+from coincurve import PrivateKey, PublicKey
+from Crypto.Cipher import AES
+from Crypto.Protocol.KDF import HKDF
+from Crypto.Hash import SHA512
+
+def ecdh_derive_key_sha512(
+ sender_sk: PrivateKey,
+ receiver_pubkey_bytes: bytes,
+ key_len: int = 32
+) -> bytes:
+ """
+ Perform ECDH with `sender_sk` (our ephemeral private key)
+ and `receiver_pubkey_bytes`. Then derive a `key_len`-byte
+ AES key with HKDF-SHA512.
+ """
+ # 1. Convert the receiver’s public key bytes into a coincurve.PublicKey
+ receiver_pub = PublicKey(receiver_pubkey_bytes)
+
+ # 2. ECDH: multiply ephemeral_secret * receiver_pub
+ shared_point = receiver_pub.multiply(sender_sk.secret)
+
+ # 3. Get our local ephemeral pubkey (uncompressed, 65 bytes)
+ local_pubkey_bytes = sender_sk.public_key.format(compressed=False)
+
+ # 4. Concatenate local ephemeral pubkey + the shared point (also uncompressed)
+ # to form the HKDF "master" input
+ master = local_pubkey_bytes + shared_point.format(compressed=False)
+
+ # 5. Derive final AES key with HKDF-SHA512
+ derived_key = HKDF(
+ master=master,
+ key_len=key_len,
+ salt=b"",
+ hashmod=SHA512
+ )
+
+ return derived_key
+
+def encrypt_with_eph_and_nonce(
+ receiver_pubkey_bytes: bytes,
+ plaintext: bytes,
+ ephemeral_sk: PrivateKey,
+ nonce: bytes,
+ aad: bytes = b""
+) -> bytes:
+ """
+ Custom ECIES-like encryption:
+ - ephemeral_sk: Your ephemeral PrivateKey
+ - receiver_pubkey_bytes: The target's public key (bytes)
+ - nonce: 12 bytes for AES-GCM
+ - aad: optional additional authenticated data (header, etc.)
+
+ Returns: ephemeral_pub (65 bytes, uncompressed) + nonce(12) + tag(16) + ciphertext
+ """
+ # 1. Derive AES-GCM key via ECDH + HKDF(SHA512)
+ aes_key = ecdh_derive_key_sha512(ephemeral_sk, receiver_pubkey_bytes, key_len=32)
+
+ # 2. Create AES-GCM cipher
+ cipher = AES.new(aes_key, AES.MODE_GCM, nonce=nonce)
+ if aad:
+ cipher.update(aad) # AAD is authenticated but not encrypted
+
+ # 3. Encrypt the plaintext
+ ciphertext = cipher.encrypt(plaintext)
+ tag = cipher.digest()
+
+ # 4. Return ephemeral pubkey + nonce + tag + ciphertext
+ ephemeral_pub_bytes = ephemeral_sk.public_key.format(compressed=False) # 65 bytes, uncompressed
+ return ephemeral_pub_bytes + nonce + tag + ciphertext
+
+def decrypt_with_eph_and_nonce(
+ receiver_sk: PrivateKey,
+ data: bytes,
+ aad: bytes = b""
+) -> bytes:
+ """
+ Inverse of encrypt_with_eph_and_nonce, using the new HKDF input:
+ master = ephemeral_pub(65) + shared_point(65).
+ data layout: ephemeral_pub(65) + nonce(12) + tag(16) + ciphertext
+ """
+ if len(data) < 65 + 12 + 16:
+ raise ValueError("Invalid data length")
+
+ # 1) Parse the input
+ ephemeral_pub_bytes = data[0:65]
+ nonce = data[65:77]
+ tag = data[77:93]
+ ciphertext = data[93:]
+
+ # 2) ECDH: ephemeral_pub * receiver_sk
+ ephemeral_pub = PublicKey(ephemeral_pub_bytes)
+ shared_point = ephemeral_pub.multiply(receiver_sk.secret)
+
+ # 3) Build the HKDF input:
+ # ephemeral_pub_bytes + uncompressed shared_point
+ master = ephemeral_pub_bytes + shared_point.format(compressed=False)
+
+ # 4) Derive the AES key with HKDF-SHA512
+ aes_key = HKDF(
+ master=master,
+ key_len=32,
+ salt=b"",
+ hashmod=SHA512
+ )
+
+ # 5) AES-GCM decrypt
+ cipher = AES.new(aes_key, AES.MODE_GCM, nonce=nonce)
+ if aad:
+ cipher.update(aad) # Additional Authenticated Data
+ plaintext = cipher.decrypt(ciphertext)
+ cipher.verify(tag) # raises ValueError if tag mismatch
+ return plaintext
diff --git a/Deploy/tools/hex_to_c_header.py b/Deploy/tools/hex_to_c_header.py
new file mode 100644
index 0000000..65cce20
--- /dev/null
+++ b/Deploy/tools/hex_to_c_header.py
@@ -0,0 +1,97 @@
+#!/usr/bin/env python3
+"""Convert Config Generator hex output to a C header for firmware embedding.
+
+Usage: python3 hex_to_c_header.py
+
+The input file has one hex-encoded BLE command per line.
+The output is a C header with byte arrays that the firmware sends to the glasses.
+
+Commands starting with FFD0 (cfgWrite) and FFD2 (cfgSet) are skipped —
+the firmware handles those in its own state machine.
+
+Large commands (>240 bytes) are split into BLE-MTU-sized chunks for
+transmission via aci_gatt_write_without_resp.
+"""
+
+import sys
+import os
+
+MAX_BLE_CHUNK = 245 # already pre-chunked by gen_ble_config.py
+
+def main():
+ if len(sys.argv) != 3:
+ print(f"Usage: {sys.argv[0]} ")
+ sys.exit(1)
+
+ input_path = sys.argv[1]
+ output_path = sys.argv[2]
+
+ with open(input_path) as f:
+ raw_lines = [line.strip() for line in f if line.strip()]
+
+ # Filter out cfgWrite (FFD0) and cfgSet (FFD2) commands
+ filtered = []
+ for line in raw_lines:
+ cmd_id = line[2:4].upper()
+ if cmd_id in ('D0', 'D2'):
+ continue
+ filtered.append(bytes.fromhex(line))
+
+ # Split large commands into BLE-MTU-sized chunks
+ commands = []
+ for cmd in filtered:
+ if len(cmd) <= MAX_BLE_CHUNK:
+ commands.append(cmd)
+ else:
+ off = 0
+ while off < len(cmd):
+ sz = min(len(cmd) - off, MAX_BLE_CHUNK)
+ commands.append(cmd[off:off+sz])
+ off += sz
+
+ guard = os.path.basename(output_path).upper().replace('.', '_').replace('-', '_')
+
+ with open(output_path, 'w') as out:
+ out.write(f'#ifndef {guard}\n')
+ out.write(f'#define {guard}\n\n')
+ out.write('#include \n\n')
+ out.write('#define AL_CONFIG_VERSION 4\n')
+ out.write(f'#define AL_CONFIG_CMD_COUNT {len(commands)}\n\n')
+
+ # Lengths array
+ out.write('static const uint16_t al_config_cmd_lengths[AL_CONFIG_CMD_COUNT] = {\n ')
+ out.write(', '.join(str(len(cmd)) for cmd in commands))
+ out.write('\n};\n\n')
+
+ # Offsets array
+ out.write('static const uint16_t al_config_cmd_offsets[AL_CONFIG_CMD_COUNT] = {\n ')
+ offset = 0
+ offsets = []
+ for cmd in commands:
+ offsets.append(str(offset))
+ offset += len(cmd)
+ out.write(', '.join(offsets))
+ out.write('\n};\n\n')
+
+ # Data blob
+ total_size = sum(len(cmd) for cmd in commands)
+ out.write(f'// Total config data: {total_size} bytes, {len(commands)} commands\n')
+ out.write(f'// (large protocol frames pre-split into {MAX_BLE_CHUNK}-byte BLE chunks)\n')
+ out.write(f'static const uint8_t al_config_cmd_data[{total_size}] = {{\n')
+ for i, cmd in enumerate(commands):
+ cmd_type = "font" if cmd[0] == 0xFF and len(cmd) > 1 and cmd[1] == 0x51 else \
+ "image" if cmd[0] == 0xFF and len(cmd) > 1 and cmd[1] == 0x41 else \
+ "chunk"
+ out.write(f' // [{i}] {cmd_type} ({len(cmd)} bytes)\n ')
+ out.write(', '.join(f'0x{b:02X}' for b in cmd))
+ out.write(',\n')
+ out.write('};\n\n')
+ out.write(f'#endif // {guard}\n')
+
+ max_chunk = max(len(cmd) for cmd in commands)
+ print(f"Generated {output_path}:")
+ print(f" {len(filtered)} protocol frames → {len(commands)} BLE chunks")
+ print(f" {total_size} bytes total, max chunk {max_chunk} bytes")
+
+if __name__ == '__main__':
+ main()
diff --git a/Docs/ble.md b/Docs/ble.md
new file mode 100644
index 0000000..52cf07d
--- /dev/null
+++ b/Docs/ble.md
@@ -0,0 +1,399 @@
+# FlySight 2 BLE Interface Developer Documentation
+
+## Overview
+
+This document describes the Bluetooth Low Energy (BLE) interface for the FlySight 2 device. This interface allows applications to interact with the FlySight 2 for file access (reading, writing, deleting files and directories), accessing real-time sensor data, and managing device state.
+
+The interface utilizes standard BLE GATT services where applicable, but primarily relies on custom services for its core functionality. Familiarity with BLE concepts (GATT, Services, Characteristics, UUIDs, Notifications, Writes, Bonding, Security Modes) is recommended.
+
+**Note:** This interface is under active development. Details may change in future firmware versions. This documentation reflects the state based on developer discussions and firmware code (`v2024.11.11.release_candidate` or similar, check `flysight.txt` for exact version).
+
+## Prerequisites
+
+* **Firmware:** A FlySight 2 unit running firmware version `v2024.11.11.release_candidate` or later is recommended to ensure compatibility with the features described here, especially the pairing mechanism and connection timeout. Firmware updates can be performed via the [online tool](https://flysight.ca/firmware/?include_beta=true). Note that different hardware batches (B1-B5, identifiable via `flysight.txt` or serial number) require specific firmware builds.
+* **BLE Client:** A BLE central device (e.g., smartphone, computer) with a compatible BLE stack and application, supporting secure connections (pairing/bonding) and configurable MTU.
+
+## Core Concepts
+
+### Pairing, Bonding, and Security
+
+FlySight 2 employs BLE security features (bonding and whitelisting) to prevent unauthorized access.
+
+1. **Pairing Request Mode:**
+ * To initiate pairing with a *new* central device, the FlySight 2 must be put into "Pairing Request Mode".
+ * **Activation:** While the unit is off (Idle Mode, LED is off), perform **two short presses** of the power button.
+ * **Indication:** The **green LED** will pulse slowly (2-second period).
+ * **Advertising:** In this mode, the FlySight advertises as "limited discoverable" and includes specific manufacturer data indicating pairing mode is active (`0xDB0901`, see Advertising section).
+ * **Duration:** Pairing request mode lasts for 30 seconds, or until a BLE connection is established, or until the button is pressed again (which cancels it and returns to Idle).
+ * **Functionality:** While in this mode, *any* central device can connect and initiate pairing/bonding. The whitelist filter is temporarily disabled.
+
+2. **Whitelisting (Filter Accept List):**
+ * FlySight 2 uses the BLE "filter accept list" (based on the bonded device list). Outside of Pairing Request Mode, it only accepts connections from devices on this list (i.e., devices it has previously bonded with).
+ * **Adding Devices:** When a central device successfully completes the bonding process initiated during Pairing Request Mode, its address/identity is added to the FlySight's internal bonded device list, effectively whitelisting it for future connections.
+ * **Storage:** The bonded device list (including security keys like IRK/ERK) is stored persistently in the FlySight 2's non-volatile memory.
+
+3. **Bonding Process:**
+ * **Initiation:** After connecting to a FlySight 2 in Pairing Request Mode, the central device *must* initiate bonding to establish a secure, encrypted link and be added to the whitelist.
+ * **Security:** FlySight 2 requests **Security Mode 1, Level 2** (Authenticated pairing with encryption, No MITM protection). It uses the **Just Works** association model (as it has NoInput/NoOutput capabilities).
+ * **Triggering Pairing:** Since the peripheral (FlySight) cannot force pairing, the central device must trigger it. A reliable method is to attempt to **read a secure characteristic** (e.g., `FT_Packet_In` UUID `00000002-8e22-4541-9d4c-21edae82ed19`) immediately after connecting. FlySight 2 requires encryption for accessing user data characteristics. This read attempt will fail with an "insufficient authentication/encryption" error from the FlySight's BLE stack, which typically prompts the central device's OS/BLE stack to initiate the pairing/bonding procedure (often showing a system dialog to the user like "Pair with FlySight?").
+ * **Completion:** Once pairing is complete, the central device should store the bonding information (keys) provided by the BLE stack for future use. The FlySight stores the central's identity.
+
+4. **Reconnecting:**
+ * Once a central device is bonded and whitelisted, it can reconnect to the FlySight 2 even when the FlySight is *not* in Pairing Request Mode (e.g., when the LED is off or solid green), provided the FlySight is advertising (typically in slow advertising mode).
+ * The connection attempt will use the stored security keys to establish an encrypted link automatically. No user interaction (like button presses on the FlySight) is needed for reconnection.
+
+5. **Resetting BLE Security:**
+ * **Purpose:** To clear existing bonds if connection issues arise or if the user wants to remove access for previously paired devices.
+ * **FlySight Side:**
+ * Connect the FlySight 2 via USB.
+ * Edit the `FLYSIGHT.TXT` file and set `Reset_BLE = 1`. Save the file.
+ * Eject and unplug the FlySight. The BLE bond list and keys (IRK/ERK) are cleared when the device restarts after unplugging. The `Reset_BLE` value will be automatically set back to `0` in `FLYSIGHT.TXT`.
+ * Alternatively, deleting `flysight.txt` entirely will cause it to be recreated with defaults, including `Reset_BLE = 1`, triggering a reset on the next boot.
+ * **Central Device Side:** Crucially, you must also **"forget" or "unpair"** the FlySight device in the central device's system Bluetooth settings. This clears the stored keys on the central side. Simply clearing app cache or restarting Bluetooth may not be sufficient.
+ * **Hardware Reset (10s Hold):** This forces a processor reset but does *not* clear the BLE bonding information on its own. It's useful for recovering from a stuck state but should be used *in conjunction* with the `Reset_BLE` flag method for a full security reset.
+
+6. **Connection Timeout & Ping:**
+ * **Timeout:** FlySight 2 implements a **30-second** inactivity timeout (`TIMEOUT_MSEC`). If no BLE *write* activity occurs on the `FT_Packet_In` characteristic for this duration, the FlySight will terminate the connection (disconnect reason `0x13`, Remote User Terminated Connection).
+ * **Keep-Alive:** To maintain a connection during periods of inactivity (e.g., user browsing files in the app, waiting between commands), the central application **must** send a command to the `FT_Packet_In` characteristic at least once every 30 seconds.
+ * **Ping Command:** A dedicated "ping" command (`0xfe`) can be used for this. Writing `0xfe` to `FT_Packet_In` resets the timer and elicits an ACK (`0xf1 fe`) response on `FT_Packet_Out`. A recommended interval is every 15 seconds.
+ * **Implicit Reset:** *Any* successful write to `FT_Packet_In` (including file transfer commands) also resets the 30-second timeout timer.
+
+### Device States and BLE Availability
+
+The FlySight 2's operational mode affects BLE service availability, primarily due to resource contention (SD card access). BLE connection itself is generally possible in most states if the device is whitelisted and advertising. The current device mode can be read (and indicated) via the `DS_Mode` characteristic in the `Device_State` service.
+
+* **Idle Mode (LED Off):** Default low-power state. BLE is active (slow advertising). Full BLE functionality (File Transfer, Real-time Data subscription, State Control) is available. This is the **required** mode for file system operations via BLE.
+* **Active Mode (Green LED):** GNSS active, logging, audio feedback. Entered via a ~1s power button press from Idle.
+ * Real-time data (`SD_GNSS_Measurement`) notifications are available if subscribed.
+ * **File Transfer (File_Transfer service) is UNAVAILABLE** due to potential SD card conflicts with logging. Commands will likely return NAK (`0xf0`).
+* **Config Selection Mode (Orange LED):** Entered via short press then long press from Idle. BLE connection may be possible, but file transfer is unavailable.
+* **USB Mode (Red/Green LED):** Mass Storage Device mode. BLE connection may be possible, but **File Transfer (File_Transfer service) is UNAVAILABLE** due to USB using the file system.
+* **Firmware Update Mode (Orange LED when plugged in):** Bootloader mode. BLE is inactive.
+* **Pairing Request Mode (Pulsing Green LED):** Temporary mode (30s) entered via double-press from Idle. Allows connection from *any* device to initiate pairing. File transfer might be available after pairing is complete *within* this mode, but it's generally expected the app will proceed after pairing without extensive file ops immediately.
+* **Start Mode (Orange LED):** Special mode for synchronized start timing. Entered via a ~1s power button press from Idle if `Active_Mode` in `flysight.txt` is set to `1`.
+ * Real-time data (`SD_GNSS_Measurement`) notifications are available if subscribed.
+ * **File Transfer (File_Transfer service) is UNAVAILABLE.**
+ * Starter Pistol service (`SP_Control_Point`, `SP_Result`) is active.
+
+### Advertising Details
+
+* **Modes:** Fast advertising (100ms interval for 30s after reset/button press), then Slow advertising (2.5s interval). Uses Limited Discoverable in Pairing Request Mode, Undirected Connectable otherwise.
+* **Address:** Uses Random Private Resolvable Addresses (RPA). Central devices should support RPAs. Issues were observed when a central tried using Random Static Address after pairing.
+* **Manufacturer Specific Data (Adv. Packet Payload):**
+ * Format: `[Length: 0x04] [Type: 0xFF] [Manuf. ID LSB: 0xDB] [Manuf. ID MSB: 0x09] [Status Byte: 0x00 or 0x01]`
+ * Manufacturer ID: `0x09DB` (Bionic Avionics Inc.)
+ * **Status Byte:**
+ * `0x00`: Normal operation (not in pairing request mode).
+ * `0x01`: **In Pairing Request Mode.**
+ * Use this data to filter scans specifically for FlySight 2 devices and identify those ready for initial pairing.
+
+## BLE Services and Characteristics
+
+### Control Point Protocol (Common for SD, SP, DS Control Points)
+
+Control Point characteristics in the Sensor_Data, Starter_Pistol, and Device_State services follow a common request/response pattern.
+
+* **Request (Central to FlySight):**
+ * Write to the respective Control Point characteristic.
+ * Format: `[Command Opcode (uint8)] [Optional Parameters...]`
+* **Response (FlySight to Central):**
+ * Sent via an **Indication** on the same Control Point characteristic (client must enable indications and send an ACK for each indication received).
+ * Format: `[Response ID (0xF0)] [Request Opcode (echoed)] [Status (uint8)] [Optional Response Data...]`
+ * **Status Codes (`CP_STATUS_*`):**
+ * `0x01`: Success (`CP_STATUS_SUCCESS`)
+ * `0x02`: Command Not Supported (`CP_STATUS_CMD_NOT_SUPPORTED`)
+ * `0x03`: Invalid Parameter (`CP_STATUS_INVALID_PARAMETER`)
+ * `0x04`: Operation Failed (`CP_STATUS_OPERATION_FAILED`)
+ * `0x05`: Operation Not Permitted (`CP_STATUS_OPERATION_NOT_PERMITTED`)
+ * `0x06`: Busy (`CP_STATUS_BUSY`)
+ * Other values: Error Unknown or specific error.
+ * Max length of `Optional Response Data` is 17 bytes (`MAX_CP_OPTIONAL_RESPONSE_DATA_LEN`).
+
+### 1. File_Transfer Service
+
+Provides serial-like file system operations. Requires bonding. File operations only functional in Idle Mode.
+
+* **Service UUID:** `00000000-cc7a-482a-984a-7f2ed5b3e58f`
+* **Characteristics:**
+
+ * **`FT_Packet_Out` (Transmit from FlySight)**
+ * UUID: `00000001-8e22-4541-9d4c-21edae82ed19`
+ * Properties: **Notify**
+ * Permissions: Encrypted Read/Write required for CCCD (Client Characteristic Configuration Descriptor) to enable notifications.
+ * Usage: Sends data packets (`0x10`), file info packets (`0x11`), and ACK/NAK responses (`0xf1`/`0xf0`, `0x12`) *from* FlySight *to* the central device via notifications. Central must enable notifications on this characteristic after connecting and bonding.
+ * Max Length: 244 bytes (`SizeFt_Packet_Out`) - Note: Effective length depends on negotiated MTU.
+
+ * **`FT_Packet_In` (Receive by FlySight)**
+ * UUID: `00000002-8e22-4541-9d4c-21edae82ed19`
+ * Properties: **WriteWithoutResponse, Read**
+ * Permissions: Encrypted Read/Write required.
+ * Usage: Receives commands and file data *from* the central device *to* the FlySight.
+ * Uses WriteWithoutResponse for speed. Reliability via application-layer ACKs and GBN ARQ.
+ * Reading this characteristic can be used to trigger the pairing process after initial connection in Pairing Request Mode.
+ * *Any* write to this characteristic resets the 30s connection inactivity timer.
+ * Max Length: 244 bytes (`SizeFt_Packet_In`) - Note: Effective length depends on negotiated MTU.
+
+#### File Transfer Command Protocol
+
+Packets sent over `FT_Packet_In` (WriteWithoutResponse) and received via notifications on `FT_Packet_Out`.
+
+**Packet Format:** `[Command Byte (uint8)] [Payload...]`
+
+**Commands from Central to FlySight (Write to `FT_Packet_In`):**
+
+| Opcode | Command Name | Payload Format | Notes |
+| :----- | :------------------- | :-------------------------------------------------------------------- | :--------------------------------------------------------------------------------- |
+| `0x01` | Delete File/Dir | `Path (null-terminated string)` | |
+| `0x02` | Read File | `Offset_multiplier (uint32_t), Stride_minus_1_multiplier (uint32_t), Path (null-terminated string)` | Opens file for reading. `Offset = Offset_multiplier * FRAME_LENGTH`. `Stride = (Stride_minus_1_multiplier + 1) * FRAME_LENGTH`. Path follows stride. |
+| `0x03` | Write File (Open) | `Path (null-terminated string)` | Opens/Creates (overwrites) file for writing. FlySight will be ready to receive file data chunks via `0x10` command. |
+| `0x04` | Create Directory | `Path (null-terminated string)` | |
+| `0x05` | List Directory | `Path (null-terminated string)` | |
+| `0x10` | File Data (Write Chunk)| `PacketCounter (uint8), DataBytes (...)` | Chunk of file data sent by Central to FS during write. Max data length = MTU - 3. Requires `0x12` ACK from FS. |
+| `0x12` | ACK File Data Packet | `PacketCounter (uint8)` | Acknowledges receipt of packet `0x10` (during read from FS). |
+| `0xfe` | Ping | (None) | Keeps connection alive. Resets 30s timeout. |
+| `0xff` | Cancel Transfer | (None) | Cancels ongoing Read/Write/List operation. |
+
+*Deprecated Commands (since ~v2024.09.29):*
+* `0x00`: Create File. Use Write File (`0x03` with path) instead; it now creates/overwrites. Sending `0x00` may return NAK.
+
+**Responses/Data from FlySight to Central (Notify on `FT_Packet_Out`):**
+
+| Opcode | Response Name | Payload Format | Notes |
+| :----- | :-------------------- | :------------------------------------------------------------------------ | :--------------------------------------------------------------------------------------- |
+| `0x10` | File Data (Read Chunk)| `PacketCounter (uint8), DataBytes (...)` | Chunk of file data sent by FS *to* Central during read. Max data length = MTU - 3. Zero-length data signals EOF. Requires `0x12` ACK from Central. |
+| `0x11` | File Info | `PacketCounter (uint8), Size (uint32_t), Date (uint16), Time (uint16), Attributes (uint8), Name (13 bytes, null-padded)` | One packet per dir entry. `Name[0]==0` signals end of list. Date/Time format per FATFS standard. |
+| `0x12` | ACK File Data Packet | `PacketCounter (uint8)` | Acknowledges receipt of packet `0x10` (during write to FS). |
+| `0xf0` | NAK | `OriginalCommand (uint8)` | Command failed (e.g., file busy, not found, invalid). |
+| `0xf1` | ACK | `OriginalCommand (uint8)` | Command initiation successful (e.g., file opened, ping received, dir opened, delete OK). |
+
+*(Note: All multi-byte integers are Little Endian unless specified otherwise. `FRAME_LENGTH` is 242 from `crs.c`)*
+
+#### Go-Back-N ARQ for Reliable File Transfer
+
+* **Purpose:** Ensure reliable transfer over unreliable `WriteWithoutResponse` and `Notify`.
+* **Applies To:**
+ * Reading from FlySight: Central sends `0x02` (Read File), FlySight sends `0x10` (File Data Read Chunk) packets.
+ * Writing to FlySight: Central sends initial `0x03` (Write File Open), then Central sends `0x10` (File Data Write Chunk) packets.
+* **Window Size:** 8 packets (`FS_CRS_WINDOW_LENGTH`). The sender can send up to 8 data packets before needing an ACK for the first one.
+* **Packet Counter:** A **1-byte** counter included in each `0x10` data packet (both directions). Increments modulo 256 (wraps 0xff -> 0x00).
+* **Acknowledgement (`0x12`):** The **receiver** of `0x10` data packets sends an `0x12` ACK (containing the received packet's counter) back to the **sender** for *each* `0x10` data packet successfully received *in the expected sequence*.
+* **Timeout & Retransmission:**
+ * The **sender** of `0x10` data packets uses a timeout (`TX_TIMEOUT_MSEC` = 200ms for reads from FS; `RX_TIMEOUT_MSEC` = 10000ms for FS waiting for data from central during a write).
+ * If the sender doesn't receive the expected `0x12` ACK within its timeout, it assumes the data packet or its ACK was lost.
+ * It **goes back** and retransmits all packets starting from the first unacknowledged one.
+ * The receiver discards any duplicate or out-of-order packets based on the packet counter.
+* **End of Transfer:**
+ * **Reading from FS:** FlySight signals EOF by sending a final `0x10` data packet with a payload containing only the packet counter (data length of 1).
+ * **Writing to FS:** Central signals EOF by sending a final `0x10` data packet with a payload containing only the packet counter (data length of 1, indicating zero actual data bytes).
+ * The receiver still ACKs this final zero-data-length packet.
+* **Cancel:** Sending `0xff` to `FT_Packet_In` aborts the current transfer state on FlySight.
+
+**Revised Flow (Writing to FlySight):**
+1. Central sends `0x03 [Path]` to `FT_Packet_In` to open/create the file.
+2. FlySight, if successful, sends `0xf1 03` (ACK) on `FT_Packet_Out`. FlySight is now ready to receive file data chunks.
+3. Central sends a window of `0x10 [PacketCounter] [DataBytes]` packets to `FT_Packet_In`.
+4. FlySight receives `0x10` packets. For each valid sequential packet, it writes data and sends `0x12 [PacketCounter]` ACK on `FT_Packet_Out`.
+5. Repeat until entire file is sent, ending with a `0x10 [PacketCounter]` (zero data bytes) packet.
+
+### 2. Sensor_Data Service
+
+Provides live GNSS data when FlySight is in Active Mode or Start Mode. Requires bonding.
+
+* **Service UUID:** `00000001-cc7a-482a-984a-7f2ed5b3e58f`
+* **Characteristics:**
+
+ * **`SD_GNSS_Measurement` (Position/Velocity/etc.)**
+ * UUID: `00000000-8e22-4541-9d4c-21edae82ed19`
+ * Properties: **Read, Notify**
+ * Permissions: Encrypted Read/Write required.
+ * Usage: Streams selected GNSS data fields. Updates at rate set in `config.txt` *only when FlySight is in Active Mode or Start Mode*. Central must enable notifications. Reading can trigger pairing. The specific fields included are controlled by a bitmask set via `SD_Control_Point`.
+ * Max Length: 44 bytes (`SizeSd_Gnss_Measurement`). Actual length depends on the fields enabled by the mask.
+ * **Data Format (Variable Length, Little Endian):**
+ * Byte 0: `current_mask` (uint8). A bitmask indicating which fields are *actually present* in this specific notification packet. This mask is a subset of or equal to the mask set by `SD_Control_Point`.
+ * `0x80` (`GNSS_BLE_BIT_TOW`): Time of Week included.
+ * `0x40` (`GNSS_BLE_BIT_WEEK`): Week Number included (Not yet implemented in firmware).
+ * `0x20` (`GNSS_BLE_BIT_POSITION`): Position (Lon, Lat, HAE) included.
+ * `0x10` (`GNSS_BLE_BIT_VELOCITY`): Velocity (VelN, VelE, VelD) included.
+ * `0x08` (`GNSS_BLE_BIT_ACCURACY`): Accuracy (HAcc, VAcc, SAcc) included.
+ * `0x04` (`GNSS_BLE_BIT_NUM_SV`): Number of Satellites included.
+ * **Following Bytes (order matters if present, based on `current_mask`):**
+ * If `GNSS_BLE_BIT_TOW` is set: `iTOW` (uint32_t). GNSS Time of Week (ms). (4 bytes)
+ * If `GNSS_BLE_BIT_WEEK` is set: (Data for week number - TBD)
+ * If `GNSS_BLE_BIT_POSITION` is set:
+ * `Longitude` (int32_t). Degrees * 1e7. (4 bytes)
+ * `Latitude` (int32_t). Degrees * 1e7. (4 bytes)
+ * `hMSL` (int32_t). Height above Mean Sea Level (mm). (4 bytes)
+ * If `GNSS_BLE_BIT_VELOCITY` is set:
+ * `velN` (int32_t). Velocity North (mm/s). (4 bytes)
+ * `velE` (int32_t). Velocity East (mm/s). (4 bytes)
+ * `velD` (int32_t). Velocity Down (mm/s). (4 bytes)
+ * If `GNSS_BLE_BIT_ACCURACY` is set:
+ * `hAcc` (uint32_t). Horizontal Accuracy Estimate (mm). (4 bytes)
+ * `vAcc` (uint32_t). Vertical Accuracy Estimate (mm). (4 bytes)
+ * `sAcc` (uint32_t). Speed Accuracy Estimate (mm/s). (4 bytes)
+ * If `GNSS_BLE_BIT_NUM_SV` is set:
+ * `numSV` (uint8_t). Number of satellites in solution. (1 byte)
+ * Default Mask: `0xB0` (iTOW, Position, Velocity).
+
+ * **`SD_Control_Point`**
+ * UUID: `00000006-8e22-4541-9d4c-21edae82ed19`
+ * Properties: **Write, Indicate**
+ * Permissions: Encrypted Read/Write required.
+ * Usage: Used to control which data fields are included in the `SD_GNSS_Measurement` characteristic notifications. Also used to retrieve the current mask. Central enables indications if responses are desired.
+ * Max Length: 20 bytes (`SizeSd_Control_Point`). Variable length.
+ * **Write Operations (Central to FlySight):**
+ * Byte 0: Opcode
+ * `0x01` (`SD_CMD_SET_GNSS_BLE_MASK`): Set the data mask for `SD_GNSS_Measurement`.
+ * Payload: `[new_mask (uint8)]`. (Total length: 2 bytes)
+ * `0x02` (`SD_CMD_GET_GNSS_BLE_MASK`): Request the current data mask.
+ * Payload: (None). (Total length: 1 byte)
+ * **Indication Responses (FlySight to Central, if indications enabled):**
+ * Format: `[0xF0 (CP_RESPONSE_ID)] [Request Opcode] [Status] [Optional Data]`
+ * For `SD_CMD_SET_GNSS_BLE_MASK (0x01)`:
+ * Success: `[0xF0] [0x01] [0x01 (CP_STATUS_SUCCESS)]`
+ * Invalid Param: `[0xF0] [0x01] [0x03 (CP_STATUS_INVALID_PARAMETER)]`
+ * For `SD_CMD_GET_GNSS_BLE_MASK (0x02)`:
+ * Success: `[0xF0] [0x02] [0x01 (CP_STATUS_SUCCESS)] [current_mask (uint8)]`
+ * Invalid Param: `[0xF0] [0x02] [0x03 (CP_STATUS_INVALID_PARAMETER)]`
+ * For unknown command:
+ * `[0xF0] [Received Opcode] [0x02 (CP_STATUS_CMD_NOT_SUPPORTED)]`
+
+### 3. Starter_Pistol Service
+
+Used for synchronized start timing in specific scenarios (e.g., BASE race). Only active in Start Mode. Requires bonding.
+
+* **Service UUID:** `00000002-cc7a-482a-984a-7f2ed5b3e58f`
+* **Characteristics:**
+
+ * **`SP_Control_Point`**
+ * UUID: `00000003-8e22-4541-9d4c-21edae82ed19`
+ * Properties: **Write, Indicate**
+ * Permissions: Encrypted Read/Write required.
+ * Usage: Sends control commands to the start pistol feature. Indications provide feedback.
+ * Max Length: 20 bytes (`SizeSp_Control_Point`). Variable length.
+ * **Write Operations (Central to FlySight):**
+ * Byte 0: Opcode
+ * `0x01` (`SP_CMD_START_COUNTDOWN`): Initiate the start sequence (countdown and tone).
+ * Payload: (None). (Total length: 1 byte)
+ * `0x02` (`SP_CMD_CANCEL_COUNTDOWN`): Cancel an ongoing start sequence.
+ * Payload: (None). (Total length: 1 byte)
+ * **Indication Responses (FlySight to Central, if indications enabled):**
+ * Format: `[0xF0 (CP_RESPONSE_ID)] [Request Opcode] [Status]` (No optional data for these commands)
+ * For `SP_CMD_START_COUNTDOWN (0x01)`:
+ * Success: `[0xF0] [0x01] [0x01 (CP_STATUS_SUCCESS)]`
+ * Busy: `[0xF0] [0x01] [0x06 (CP_STATUS_BUSY)]`
+ * Not Permitted: `[0xF0] [0x01] [0x05 (CP_STATUS_OPERATION_NOT_PERMITTED)]`
+ * Invalid Param (if payload was not empty): `[0xF0] [0x01] [0x03 (CP_STATUS_INVALID_PARAMETER)]`
+ * For `SP_CMD_CANCEL_COUNTDOWN (0x02)`:
+ * Success: `[0xF0] [0x02] [0x01 (CP_STATUS_SUCCESS)]`
+ * Not Permitted: `[0xF0] [0x02] [0x05 (CP_STATUS_OPERATION_NOT_PERMITTED)]`
+ * Invalid Param (if payload was not empty): `[0xF0] [0x02] [0x03 (CP_STATUS_INVALID_PARAMETER)]`
+ * For unknown command:
+ * `[0xF0] [Received Opcode] [0x02 (CP_STATUS_CMD_NOT_SUPPORTED)]`
+
+ * **`SP_Result`**
+ * UUID: `00000004-8e22-4541-9d4c-21edae82ed19`
+ * Properties: **Read, Indicate**
+ * Permissions: Encrypted Read/Write required.
+ * Usage: Provides results from the start pistol function (e.g., precise start time captured by GNSS EXTINT).
+ * Length: 9 bytes (`SizeSp_Result`). Data format (from `Custom_Start_Update`): `Year (u16), Month (u8), Day (u8), Hour (u8), Min (u8), Sec (u8), ms (u16)`. All Little Endian.
+
+### 4. Device_State Service
+
+Provides information about the device's current operational state and allows for state control. Requires bonding.
+
+* **Service UUID:** `00000003-cc7a-482a-984a-7f2ed5b3e58f`
+* **Characteristics:**
+
+ * **`DS_Mode`**
+ * UUID: `00000005-8e22-4541-9d4c-21edae82ed19`
+ * Properties: **Read, Indicate**
+ * Permissions: Encrypted Read required.
+ * Usage: Exposes the current operational mode of the FlySight 2. Central can read this or subscribe to indications to be informed of mode changes.
+ * Length: 1 byte (`SizeDs_Mode`).
+ * **Mode Values (corresponds to `FS_Mode_State_t` enum):**
+ | Value | Mode Name | Description |
+ | :---- | :--------------- | :---------------------------------------------- |
+ | `0x00`| SLEEP | Idle, low power. File access available. |
+ | `0x01`| ACTIVE | GNSS logging, audio feedback. No file access. |
+ | `0x02`| CONFIG | Configuration selection mode. No file access. |
+ | `0x03`| USB | USB Mass Storage mode. No file access. |
+ | `0x04`| PAIRING | BLE pairing request mode. |
+ | `0x05`| START | Starter pistol mode. No file access. |
+
+ * **`DS_Control_Point`**
+ * UUID: `00000007-8e22-4541-9d4c-21edae82ed19`
+ * Properties: **Write, Indicate**
+ * Permissions: Encrypted Read/Write required.
+ * Usage: This characteristic is intended for future device state control commands. Currently, no specific commands are implemented. Writing any command will likely result in a `CP_STATUS_CMD_NOT_SUPPORTED` response.
+ * Max Length: 20 bytes (`SizeDs_Control_Point`). Variable length.
+ * **Write Operations (Central to FlySight):**
+ * Currently, no commands are defined.
+ * **Indication Responses (FlySight to Central, if indications enabled):**
+ * Format: `[0xF0 (CP_RESPONSE_ID)] [Request Opcode] [Status]`
+ * For any command written:
+ * `[0xF0] [Received Opcode] [0x02 (CP_STATUS_CMD_NOT_SUPPORTED)]`
+
+### 5. Standard BLE Services
+
+FlySight 2 also implements standard BLE services:
+
+* **Device Information Service (DIS - 0x180A):** Provides Manufacturer Name ("Bionic Avionics Inc."), Model Number ("FlySight 2"), Firmware Revision (`GIT_TAG`), etc.
+* **Battery Service (BAS - UUID `0x180F`):**
+ * FlySight 2 exposes its own battery level through this standard service.
+ * **Battery Level Characteristic (UUID `0x2A19`):**
+ * **Properties:** Read, Notify.
+ * **Permissions:** Encrypted Read.
+ * **Format:** `uint8` representing the battery level as a percentage (0-100%).
+ * **Usage:** Client applications can read this characteristic to get the current battery level of the FlySight 2. Notifications can be enabled on this characteristic to receive updates once per second.
+
+## Implementation Notes & Best Practices
+
+* **Error Handling:** Expect NAKs (`0xf0`) for commands sent when the FlySight is in an incompatible state (e.g., file transfer during Active mode or USB connection). Handle timeouts appropriately, especially for the GBN ARQ.
+* **State Awareness:** Be mindful of the FlySight's operational mode (Idle, Active, USB), which can be obtained via the `DS_Mode` characteristic. File transfer is generally only reliable in Idle mode. Real-time data (`SD_GNSS_Measurement`) is only sent in Active or Start mode.
+* **MTU Negotiation:** Request a larger MTU (e.g., 247 bytes) after connection for efficient file transfer. Handle potential failures where the MTU remains small (e.g., 23 bytes with BLE 4.0/4.1 devices), potentially by fragmenting File Transfer data packets if necessary (though the current File Transfer implementation seems to assume MTU is large enough for its framing).
+* **Pairing Flow:** Implement the connect-then-read-secure-characteristic flow to reliably trigger pairing, especially if developing a cross-platform application.
+* **Connection Management:** Use the ping command (`0xfe` on `FT_Packet_In`) to keep connections alive during inactivity. Handle disconnects gracefully. Be aware of the Android system pairing behavior possibly leaving connections open.
+* **Code Examples:** Refer extensively to the provided Python, iOS, and Android examples for practical implementation details, especially for the GBN ARQ logic.
+
+## Appendix
+
+### Known UUIDs
+
+| Feature | Characteristic Name | UUID | Service | Properties | Max Length (Bytes) |
+| :---------------------- | :------------------------ | :------------------------------------------- | :----------------- | :--------------------- | :----------------- |
+| File Transfer Service | | `00000000-cc7a-482a-984a-7f2ed5b3e58f` | File_Transfer | | N/A |
+| File Transfer TX | `FT_Packet_Out` | `00000001-8e22-4541-9d4c-21edae82ed19` | File_Transfer | Notify | 244 (Var) |
+| File Transfer RX | `FT_Packet_In` | `00000002-8e22-4541-9d4c-21edae82ed19` | File_Transfer | WriteWithoutResponse, Read | 244 (Var) |
+| Sensor Data Service | | `00000001-cc7a-482a-984a-7f2ed5b3e58f` | Sensor_Data | | N/A |
+| GNSS Measurement | `SD_GNSS_Measurement` | `00000000-8e22-4541-9d4c-21edae82ed19` | Sensor_Data | Read, Notify | 44 (Var) |
+| Sensor Data Control | `SD_Control_Point` | `00000006-8e22-4541-9d4c-21edae82ed19` | Sensor_Data | Write, Indicate | 20 (Var) |
+| Starter Pistol Service | | `00000002-cc7a-482a-984a-7f2ed5b3e58f` | Starter_Pistol | | N/A |
+| Starter Pistol Control | `SP_Control_Point` | `00000003-8e22-4541-9d4c-21edae82ed19` | Starter_Pistol | Write, Indicate | 20 (Var) |
+| Starter Pistol Result | `SP_Result` | `00000004-8e22-4541-9d4c-21edae82ed19` | Starter_Pistol | Read, Indicate | 9 (Const) |
+| Device State Service | | `00000003-cc7a-482a-984a-7f2ed5b3e58f` | Device_State | | N/A |
+| Device Mode | `DS_Mode` | `00000005-8e22-4541-9d4c-21edae82ed19` | Device_State | Read, Indicate | 1 (Const) |
+| Device State Control | `DS_Control_Point` | `00000007-8e22-4541-9d4c-21edae82ed19` | Device_State | Write, Indicate | 20 (Var) |
+| Battery Service | | `0x180F` | Battery | | N/A |
+| Battery Level | `Battery_Level` | `0x2A19` | Battery | Read, Notify | 1 (Const) |
+
+*(Note: "Var" indicates variable length up to the specified maximum. "Const" indicates fixed length.)*
+
+### File Transfer Command Opcodes
+
+| Opcode | Command Name | Direction | Payload Description | Response (on `FT_Packet_Out`) |
+| :----- | :--------------------- | :------------- | :--------------------------------------------- | :------------------------------------------------- |
+| `0x01` | Delete File/Dir | Central -> FS | `[Path]` | `0xf1 01` (ACK) / `0xf0 01` (NAK) |
+| `0x02` | Read File | Central -> FS | `[Offset_mult (u32)] [Stride-1_mult (u32)] [Path]` | `0xf1 02` (ACK) / `0xf0 02` (NAK), then `0x10` data packets |
+| `0x03` | Write File (Open) | Central -> FS | `[Path]` | `0xf1 03` (ACK) / `0xf0 03` (NAK) |
+| `0x04` | Create Directory | Central -> FS | `[Path]` | `0xf1 04` (ACK) / `0xf0 04` (NAK) |
+| `0x05` | List Directory | Central -> FS | `[Path]` | `0xf1 05` (ACK) / `0xf0 05` (NAK), then `0x11` info packets |
+| `0x10` | File Data (Chunk) | Bidirectional | `[Counter (u8)] [Data...]` | Receiver sends `0x12 [Counter]` ACK |
+| `0x11` | File Info | FS -> Central | `[Counter (u8)] [Size] [Date] [Time] [Attr] [Name]` | N/A |
+| `0x12` | ACK File Data Packet | Receiver -> Sender | `[Counter (u8)]` | N/A |
+| `0xf0` | NAK | FS -> Central | `[Original Command (u8)]` | N/A |
+| `0xf1` | ACK | FS -> Central | `[Original Command (u8)]` | N/A |
+| `0xfe` | Ping | Central -> FS | (None) | `0xf1 fe` (ACK) |
+| `0xff` | Cancel Transfer | Central -> FS | (None) | (None expected, state just resets) |
+
+*(Note: Path is a null-terminated string. `0x10` is used by Central to send data chunks to FS (via `FT_Packet_In`) during a write operation (after `0x03` has opened the file), and by FS to send data chunks to Central (via `FT_Packet_Out`) during a read operation. `FRAME_LENGTH` is 242.)*
diff --git a/Docs/deploy.md b/Docs/deploy.md
new file mode 100644
index 0000000..5aa62b4
--- /dev/null
+++ b/Docs/deploy.md
@@ -0,0 +1,142 @@
+# FlySight Firmware Deployment Process
+
+## Overview
+
+This document describes the process for preparing and deploying encrypted firmware updates for FlySight devices. The primary tool for this process is the `deploy_firmware.py` Python script, which encrypts firmware binaries (`.bin`) and packages them into a secure firmware update format (`.sfb`). This script and its associated folders are typically located in the `/Deploy` folder of the firmware distribution.
+
+This documentation is intended for developers involved in building and releasing FlySight firmware. It covers the necessary tools, file organization, script configuration, and the step-by-step procedure for generating deployment-ready firmware files.
+
+## Prerequisites
+
+Before you can deploy FlySight firmware, ensure you have the following:
+
+* **Python Environment:** Python 3.6 or newer.
+* **Required Python Libraries:**
+ * `coincurve`
+ * `ecdh_aesgcm`
+ You can install these libraries using pip:
+ ```bash
+ pip install coincurve ecdh_aesgcm
+ ```
+* **Deployment Script:** The `deploy_firmware.py` script (from the `/Deploy` folder).
+* **Firmware Binaries:** Compiled firmware files (e.g., `UserApp.bin`) ready for deployment.
+* **Public Key Files:** Batch-specific public key files (e.g., `pub_key_b2.bin`) corresponding to the target FlySight units.
+
+## Key Components
+
+### Directory Structure
+
+Within the `/Deploy` folder of the firmware distribution, a specific directory structure is expected by the `deploy_firmware.py` script:
+
+* `Firmware_As_Built/`: This directory is used to store the compiled firmware binaries that you intend to deploy. You will typically copy your `UserApp.bin` (or other named firmware versions) into this folder.
+* `Public_Keys/`: This directory should contain the public key files (`.bin`) for the different production batches of FlySight devices.
+* `Firmware_To_Deploy/`: This is the output directory where the script will place the generated encrypted firmware update files (`.sfb`). The script will create this directory if it does not already exist.
+
+### Input Files
+
+#### Firmware Binaries
+
+* The primary firmware file is typically named `UserApp.bin`. After a successful firmware build, this file can usually be found in the `/Release` folder of the firmware project.
+* These files should be placed in the `Deploy/Firmware_As_Built/` directory.
+* You can place multiple firmware files in this directory if you intend to deploy different versions or types of firmware. Each will be configured in the deployment script.
+
+#### Public Key Files
+
+* Public keys are stored as binary files (e.g., `pub_key_b2.bin`, `pub_key_b3.bin`) in the `Deploy/Public_Keys/` directory.
+* Each public key corresponds to a specific manufacturing batch of FlySight devices (e.g., B2, B3). Firmware encrypted with a particular public key can only be installed on devices from the corresponding batch.
+
+### Identifying the Correct Public Key for a Device
+
+FlySight devices are manufactured in batches, and each batch is provisioned with a specific public key. To determine which public key was used for a particular FlySight unit:
+
+1. Connect the FlySight device to a computer via USB. It will mount as a USB mass storage device (a drive).
+2. Open the `flysight.txt` file located in the root directory of the FlySight's storage.
+3. Look for the `Pubkey_X` value. This hexadecimal string is a hash of the public key used by the device.
+4. Use the table below to map this `Pubkey_X` value to the corresponding public key file and batch name.
+
+**Public Key Index:**
+
+| `Pubkey_X` (from `flysight.txt`) | Public Key File | Batch Name |
+| :--------------------------------------------------------- | :------------------- | :--------- |
+| `486bee2d3dd60fd0de481a072d12ae5492c52c0cb2bbdde697b16aad1ca903f3` | `pub_key_b2.bin` | B2 |
+| `211d721dbd9114a6bbe9ba1256ccbbfbf2c4015b139126ab56c331935e29c016` | `pub_key_b3.bin` | B3 |
+| `dac40d2597e371390a84f3196d4256b5c96a89373afb3584dcbc226b74bcbc0c` | `pub_key_b4.bin` | B4 |
+| `3157e02846ddfd2a78347e099ba1d86c92920b101137f28091ed0dec3b4589c2` | `pub_key_b5.bin` | B5 |
+| `8ee7870905b3e792df0ed796b996c9ff743d196c2e8abbf1a39f39733cceac80` | `pub_key_b6.bin` | B6 |
+
+## The `deploy_firmware.py` Script
+
+The `deploy_firmware.py` script automates the process of encrypting firmware files for secure updates.
+
+### Core Functionality
+
+The script performs the following actions for each specified firmware file and public key combination:
+1. Reads the target device batch's public key.
+2. Generates a new, random ephemeral key pair for this encryption session.
+3. Reads the firmware binary data.
+4. Encrypts the firmware data using an ECDH-derived shared secret and AES-GCM.
+5. Constructs a header (`.sfb` header) containing metadata, cryptographic information (like the ephemeral public key, nonce, and firmware tag), and a MAC for header integrity.
+6. Combines the header and the encrypted firmware into a single `.sfb` file.
+
+### Configuration
+
+At the bottom of the `deploy_firmware.py` script, within the `main()` function, two Python lists control the deployment process:
+
+* **`fw_files`**: A list of tuples, where each tuple specifies a firmware binary to be deployed.
+ * Format: `('path/to/firmware.bin', 'FirmwareBaseName')`
+ * Example: `('Firmware_As_Built/UserApp_v1.2.3.bin', 'UserApp_v1.2.3')`
+* **`pk_files`**: A list of tuples, where each tuple specifies a public key to be used for encryption.
+ * Format: `('path/to/public_key.bin', 'BatchName')`
+ * Example: `('Public_Keys/pub_key_b2.bin', 'B2')`
+
+The script will iterate through each firmware file in `fw_files` and encrypt it with each public key in `pk_files`.
+
+### Running the Script
+
+To execute the deployment process, run the script from your terminal. Navigate to the `/Deploy` folder of the firmware distribution (where the script and its associated folders `Firmware_As_Built`, `Public_Keys` are located):
+
+```bash
+python deploy_firmware.py
+```
+
+The script will print progress information for each encryption operation to the console.
+
+### Output Files
+
+* The encrypted firmware update files are saved in the `Firmware_To_Deploy/` directory.
+* The output files are named using a combination of the batch name and the firmware base name, with an `.sfb` extension.
+ * Format: `{BatchName}_{FirmwareBaseName}.sfb`
+ * Example: `B2_UserApp_v1.2.3.sfb`
+
+## Deployment Workflow (Step-by-Step)
+
+Follow these steps to generate encrypted firmware update files:
+
+1. **Ensure Prerequisites:** Verify that Python 3 and the `coincurve` and `ecdh_aesgcm` libraries are installed.
+2. **Obtain Firmware:** Compile your FlySight firmware. This typically results in a `UserApp.bin` file located in the `/Release` folder of your firmware project.
+3. **Place Firmware:** Copy the `UserApp.bin` (or your specifically named firmware binary) from the `/Release` folder into the `Deploy/Firmware_As_Built/` directory. For example, you might rename it to `UserApp_vX.Y.Z.bin`.
+4. **Verify Public Keys:** Ensure that the public key files for your target batches (e.g., `pub_key_b2.bin`, `pub_key_b3.bin`) are present in the `Deploy/Public_Keys/` directory.
+5. **Configure Script:**
+ * Open `Deploy/deploy_firmware.py` in a text editor.
+ * Locate the `fw_files` list near the end of the script. Modify it to point to the firmware binary you placed in `Firmware_As_Built/` and assign it a descriptive base name.
+ ```python
+ fw_files = [
+ ('Firmware_As_Built/UserApp_vX.Y.Z.bin', 'UserApp_vX.Y.Z'),
+ # Add other firmware files if needed
+ ]
+ ```
+ * Locate the `pk_files` list. Modify it to include the public keys and batch names for all target batches.
+ ```python
+ pk_files = [
+ ('Public_Keys/pub_key_b2.bin', 'B2'),
+ ('Public_Keys/pub_key_b3.bin', 'B3'),
+ # Add other public keys if needed
+ ]
+ ```
+ * Save the changes to `deploy_firmware.py`.
+6. **Run Deployment Script:** Open your terminal, navigate to the `/Deploy` directory within your firmware distribution, and run:
+ ```bash
+ python deploy_firmware.py
+ ```
+7. **Collect Output Files:** After the script completes, find the generated `.sfb` files in the `Deploy/Firmware_To_Deploy/` directory. For the example configuration above, you would find files like `B2_UserApp_vX.Y.Z.sfb` and `B3_UserApp_vX.Y.Z.sfb`.
+8. **Distribute:** These `.sfb` files are now ready for distribution and use in the FlySight firmware update process for the corresponding device batches.
diff --git a/FlySight/active_control.c b/FlySight/active_control.c
index 8364a07..a2aea45 100644
--- a/FlySight/active_control.c
+++ b/FlySight/active_control.c
@@ -284,8 +284,13 @@ void FS_VBAT_ValueReady_Callback(void)
{
if (state != FS_CONTROL_ACTIVE) return;
+ const FS_VBAT_Data_t *vbat_data = FS_VBAT_GetData();
+
// Save to log file
- FS_Log_WriteVBATData(FS_VBAT_GetData());
+ FS_Log_WriteVBATData(vbat_data);
+
+ // Update BLE characteristic
+ Custom_VBAT_Update(vbat_data);
}
void FS_ActiveControl_SetHealthStatus(bool isSystemHealthy)
diff --git a/FlySight/active_mode.c b/FlySight/active_mode.c
index 266d8ca..d93e484 100644
--- a/FlySight/active_mode.c
+++ b/FlySight/active_mode.c
@@ -23,6 +23,7 @@
#include "main.h"
#include "active_control.h"
+#include "activelook.h"
#include "app_common.h"
#include "app_fatfs.h"
#include "audio.h"
@@ -69,6 +70,12 @@ void FS_ActiveMode_Init(void)
FS_Config_Read(FS_State_Get()->config_filename);
}
+ if (FS_Config_Get()->al_mode != 0)
+ {
+ /* Initialize ActiveLook interface */
+ FS_ActiveLook_Init();
+ }
+
if (FS_Config_Get()->enable_logging)
{
// Initialize enable flags
@@ -204,6 +211,12 @@ void FS_ActiveMode_Init(void)
void FS_ActiveMode_DeInit(void)
{
+ if (FS_Config_Get()->al_mode != 0)
+ {
+ /* De-initialize ActiveLook interface */
+ FS_ActiveLook_DeInit();
+ }
+
/* Disable controller */
FS_ActiveControl_DeInit();
diff --git a/FlySight/activelook.c b/FlySight/activelook.c
new file mode 100644
index 0000000..998eb35
--- /dev/null
+++ b/FlySight/activelook.c
@@ -0,0 +1,365 @@
+#include "main.h"
+#include "activelook.h"
+#include "activelook_client.h"
+#include "activelook_config_data.h"
+#include "activelook_mode0.h"
+#include "app_common.h"
+#include "config.h"
+#include "dbg_trace.h"
+#include "log.h"
+#include "stm32_seq.h"
+#include
+#include
+
+typedef enum
+{
+ AL_STATE_INIT = 0,
+ AL_STATE_CFG_READ,
+ AL_STATE_CFG_CHECK,
+ AL_STATE_CFG_WRITE,
+ AL_STATE_CONFIG_UPLOAD,
+ AL_STATE_SETUP,
+ AL_STATE_CFG_SET,
+ AL_STATE_CFG_ACTIVATE,
+ AL_STATE_CLEAR,
+ AL_STATE_READY,
+ AL_STATE_UPDATE,
+} FS_ActiveLook_State_t;
+
+static FS_ActiveLook_State_t s_state = AL_STATE_INIT;
+static uint16_t s_config_cmd_idx = 0;
+static uint8_t timer_id;
+
+typedef struct {
+ void (*init)(void);
+ FS_ActiveLook_SetupStatus_t (*setup)(void);
+ void (*update)(void);
+} FS_ActiveLook_ModeOps;
+
+static const FS_ActiveLook_ModeOps s_modeTable[] =
+{
+ {
+ FS_ActiveLook_Mode0_Init,
+ FS_ActiveLook_Mode0_Setup,
+ FS_ActiveLook_Mode0_Update
+ },
+};
+
+#define FS_ACTIVELOOK_NUM_MODES (sizeof(s_modeTable)/sizeof(s_modeTable[0]))
+static const FS_ActiveLook_ModeOps *s_currentMode = NULL;
+
+static void FS_ActiveLook_Timer(void);
+static void FS_ActiveLook_Task(void);
+static void OnActiveLookDiscoveryComplete(void);
+static void OnCfgReadComplete(uint8_t found, uint32_t version);
+
+static const FS_ActiveLook_ClientCb_t s_alk_cb =
+{
+ .OnDiscoveryComplete = OnActiveLookDiscoveryComplete
+};
+
+static void AL_SelectMode(uint8_t modeId)
+{
+ if (modeId < FS_ACTIVELOOK_NUM_MODES)
+ s_currentMode = &s_modeTable[modeId];
+ else
+ s_currentMode = &s_modeTable[0];
+
+ if (s_currentMode->init)
+ s_currentMode->init();
+}
+
+static uint8_t AL_BuildCfgWritePacket(uint8_t *packet, uint32_t version)
+{
+ uint8_t idx = 0;
+ packet[idx++] = 0xFF;
+ packet[idx++] = 0xD0;
+ packet[idx++] = 0x00;
+ uint8_t lenPos = idx++;
+
+ const char *name = "FLYSIGHT";
+ memcpy(&packet[idx], name, 8);
+ idx += 8;
+ for (uint8_t pad = 8; pad < 12; pad++)
+ packet[idx++] = 0x00;
+
+ packet[idx++] = (version >> 24) & 0xFF;
+ packet[idx++] = (version >> 16) & 0xFF;
+ packet[idx++] = (version >> 8) & 0xFF;
+ packet[idx++] = version & 0xFF;
+
+ packet[idx++] = 0x01;
+ packet[idx++] = 0x02;
+ packet[idx++] = 0x03;
+ packet[idx++] = 0x04;
+
+ packet[idx++] = 0xAA;
+ packet[lenPos] = idx;
+ return idx;
+}
+
+static uint8_t AL_BuildCfgSetPacket(uint8_t *packet)
+{
+ uint8_t idx = 0;
+ packet[idx++] = 0xFF;
+ packet[idx++] = 0xD2;
+ packet[idx++] = 0x00;
+ uint8_t lenPos = idx++;
+
+ const char *name = "FLYSIGHT";
+ memcpy(&packet[idx], name, 8);
+ idx += 8;
+ for (uint8_t pad = 8; pad < 12; pad++)
+ packet[idx++] = 0x00;
+
+ packet[idx++] = 0xAA;
+ packet[lenPos] = idx;
+ return idx;
+}
+
+static void OnActiveLookDiscoveryComplete(void)
+{
+ FS_Log_WriteEvent("AL discovery complete");
+ s_state = AL_STATE_CFG_READ;
+ UTIL_SEQ_SetTask(1 << CFG_TASK_FS_ACTIVELOOK_ID, CFG_SCH_PRIO_0);
+ FS_ActiveLook_Client_ReadBatteryLevel();
+}
+
+static void FS_ActiveLook_Task(void)
+{
+ switch (s_state)
+ {
+ case AL_STATE_INIT:
+ break;
+
+ case AL_STATE_CFG_READ:
+ {
+ FS_ActiveLook_Client_CfgRead(OnCfgReadComplete);
+ s_state = AL_STATE_CFG_CHECK;
+ HW_TS_Start(timer_id, 2000 * 1000 / CFG_TS_TICK_VAL);
+ break;
+ }
+
+ case AL_STATE_CFG_CHECK:
+ break;
+
+ case AL_STATE_CFG_WRITE:
+ {
+ if (!FS_ActiveLook_Client_CanSend())
+ {
+ UTIL_SEQ_SetTask(1 << CFG_TASK_FS_ACTIVELOOK_ID, CFG_SCH_PRIO_1);
+ break;
+ }
+ uint8_t packet[32];
+ uint8_t len = AL_BuildCfgWritePacket(packet, 0);
+ tBleStatus s = FS_ActiveLook_Client_WriteWithResp(packet, len);
+ if (s == BLE_STATUS_SUCCESS)
+ {
+ FS_Log_WriteEvent("AL cfgWrite (open)");
+ FS_ActiveLook_Client_SetUploadActive(1);
+ s_config_cmd_idx = 0;
+ s_state = AL_STATE_CONFIG_UPLOAD;
+ HW_TS_Start(timer_id, 50 * 1000 / CFG_TS_TICK_VAL);
+ }
+ else
+ {
+ UTIL_SEQ_SetTask(1 << CFG_TASK_FS_ACTIVELOOK_ID, CFG_SCH_PRIO_1);
+ }
+ break;
+ }
+
+ case AL_STATE_CONFIG_UPLOAD:
+ {
+ if (s_config_cmd_idx < AL_CONFIG_CMD_COUNT)
+ {
+ if (!FS_ActiveLook_Client_CanSend())
+ {
+ HW_TS_Start(timer_id, 50 * 1000 / CFG_TS_TICK_VAL);
+ break;
+ }
+
+ uint16_t offset = al_config_cmd_offsets[s_config_cmd_idx];
+ uint16_t length = al_config_cmd_lengths[s_config_cmd_idx];
+ tBleStatus s = FS_ActiveLook_Client_WriteWithResp(
+ &al_config_cmd_data[offset], length);
+
+ if (s == BLE_STATUS_SUCCESS)
+ {
+ s_config_cmd_idx++;
+ HW_TS_Start(timer_id, 50 * 1000 / CFG_TS_TICK_VAL);
+ }
+ else
+ {
+ UTIL_SEQ_SetTask(1 << CFG_TASK_FS_ACTIVELOOK_ID, CFG_SCH_PRIO_1);
+ }
+ }
+ else
+ {
+ FS_ActiveLook_Client_SetUploadActive(0);
+ FS_Log_WriteEvent("AL upload done: %d chunks", s_config_cmd_idx);
+ AL_SelectMode(FS_Config_Get()->al_mode - 1);
+ s_state = AL_STATE_SETUP;
+ UTIL_SEQ_SetTask(1 << CFG_TASK_FS_ACTIVELOOK_ID, CFG_SCH_PRIO_0);
+ }
+ break;
+ }
+
+ case AL_STATE_SETUP:
+ {
+ if (!FS_ActiveLook_Client_CanSend())
+ {
+ UTIL_SEQ_SetTask(1 << CFG_TASK_FS_ACTIVELOOK_ID, CFG_SCH_PRIO_1);
+ break;
+ }
+ if (s_currentMode && s_currentMode->setup)
+ {
+ FS_ActiveLook_SetupStatus_t status = s_currentMode->setup();
+ if (status == FS_AL_SETUP_DONE)
+ {
+ FS_Log_WriteEvent("AL setup done");
+ s_state = AL_STATE_CFG_SET;
+ UTIL_SEQ_SetTask(1 << CFG_TASK_FS_ACTIVELOOK_ID, CFG_SCH_PRIO_0);
+ }
+ }
+ break;
+ }
+
+ case AL_STATE_CFG_SET:
+ {
+ if (!FS_ActiveLook_Client_CanSend())
+ {
+ UTIL_SEQ_SetTask(1 << CFG_TASK_FS_ACTIVELOOK_ID, CFG_SCH_PRIO_1);
+ break;
+ }
+ FS_ActiveLook_Client_SetUploadActive(0);
+ uint8_t packet[32];
+ uint8_t len = AL_BuildCfgWritePacket(packet, AL_CONFIG_VERSION);
+ tBleStatus s = FS_ActiveLook_Client_WriteWithResp(packet, len);
+ if (s == BLE_STATUS_SUCCESS)
+ {
+ FS_Log_WriteEvent("AL cfgWrite (v%d)", AL_CONFIG_VERSION);
+ s_state = AL_STATE_CFG_ACTIVATE;
+ }
+ else
+ {
+ UTIL_SEQ_SetTask(1 << CFG_TASK_FS_ACTIVELOOK_ID, CFG_SCH_PRIO_1);
+ }
+ break;
+ }
+
+ case AL_STATE_CFG_ACTIVATE:
+ {
+ if (!FS_ActiveLook_Client_CanSend())
+ {
+ UTIL_SEQ_SetTask(1 << CFG_TASK_FS_ACTIVELOOK_ID, CFG_SCH_PRIO_1);
+ break;
+ }
+ uint8_t packet[20];
+ uint8_t len = AL_BuildCfgSetPacket(packet);
+ tBleStatus s = FS_ActiveLook_Client_WriteWithResp(packet, len);
+ if (s == BLE_STATUS_SUCCESS)
+ {
+ FS_Log_WriteEvent("AL cfgSet (activate)");
+ s_state = AL_STATE_CLEAR;
+ }
+ else
+ {
+ UTIL_SEQ_SetTask(1 << CFG_TASK_FS_ACTIVELOOK_ID, CFG_SCH_PRIO_1);
+ }
+ break;
+ }
+
+ case AL_STATE_CLEAR:
+ {
+ if (!FS_ActiveLook_Client_CanSend())
+ {
+ UTIL_SEQ_SetTask(1 << CFG_TASK_FS_ACTIVELOOK_ID, CFG_SCH_PRIO_1);
+ break;
+ }
+ uint8_t packet[] = {0xFF, 0x01, 0x00, 0x05, 0xAA};
+ tBleStatus s = FS_ActiveLook_Client_WriteWithResp(packet, sizeof(packet));
+ if (s == BLE_STATUS_SUCCESS)
+ {
+ s_state = AL_STATE_READY;
+ HW_TS_Start(timer_id, FS_Config_Get()->al_rate * 1000 / CFG_TS_TICK_VAL);
+ }
+ else
+ {
+ UTIL_SEQ_SetTask(1 << CFG_TASK_FS_ACTIVELOOK_ID, CFG_SCH_PRIO_1);
+ }
+ break;
+ }
+
+ case AL_STATE_READY:
+ break;
+
+ case AL_STATE_UPDATE:
+ if (!FS_ActiveLook_Client_CanSend())
+ {
+ s_state = AL_STATE_READY;
+ break;
+ }
+ if (s_currentMode && s_currentMode->update)
+ s_currentMode->update();
+ s_state = AL_STATE_READY;
+ break;
+ }
+}
+
+static void OnCfgReadComplete(uint8_t found, uint32_t version)
+{
+ if (found && version == AL_CONFIG_VERSION)
+ {
+ FS_Log_WriteEvent("AL cfg v%lu matches, skip upload", (unsigned long)version);
+ FS_ActiveLook_Client_SetUploadActive(0);
+ AL_SelectMode(FS_Config_Get()->al_mode - 1);
+ s_state = AL_STATE_SETUP;
+ }
+ else
+ {
+ FS_Log_WriteEvent("AL cfg %s v%lu, uploading",
+ found ? "outdated" : "not found", (unsigned long)version);
+ s_state = AL_STATE_CFG_WRITE;
+ }
+ UTIL_SEQ_SetTask(1 << CFG_TASK_FS_ACTIVELOOK_ID, CFG_SCH_PRIO_0);
+}
+
+static void FS_ActiveLook_Timer(void)
+{
+ if (s_state == AL_STATE_READY)
+ {
+ s_state = AL_STATE_UPDATE;
+ UTIL_SEQ_SetTask(1 << CFG_TASK_FS_ACTIVELOOK_ID, CFG_SCH_PRIO_0);
+ }
+ else if (s_state == AL_STATE_CFG_CHECK)
+ {
+ FS_Log_WriteEvent("AL cfgRead timeout, uploading");
+ s_state = AL_STATE_CFG_WRITE;
+ UTIL_SEQ_SetTask(1 << CFG_TASK_FS_ACTIVELOOK_ID, CFG_SCH_PRIO_0);
+ }
+ else if (s_state == AL_STATE_CONFIG_UPLOAD)
+ {
+ UTIL_SEQ_SetTask(1 << CFG_TASK_FS_ACTIVELOOK_ID, CFG_SCH_PRIO_0);
+ }
+}
+
+void FS_ActiveLook_Init(void)
+{
+ FS_ActiveLook_Client_RegisterCb(&s_alk_cb);
+ UTIL_SEQ_RegTask(1 << CFG_TASK_FS_ACTIVELOOK_ID, UTIL_SEQ_RFU, FS_ActiveLook_Task);
+ s_state = AL_STATE_INIT;
+ UTIL_SEQ_SetTask(1 << CFG_TASK_START_SCAN_ID, CFG_SCH_PRIO_0);
+ HW_TS_Create(CFG_TIM_PROC_ID_ISR, &timer_id, hw_ts_Repeated, FS_ActiveLook_Timer);
+}
+
+void FS_ActiveLook_DeInit(void)
+{
+ s_state = AL_STATE_INIT;
+ HW_TS_Delete(timer_id);
+ UTIL_SEQ_SetTask(1 << CFG_TASK_DISCONN_DEV_1_ID, CFG_SCH_PRIO_0);
+}
+
+uint8_t FS_ActiveLook_IsActive(void)
+{
+ return (s_state != AL_STATE_INIT) ? 1 : 0;
+}
diff --git a/FlySight/activelook.h b/FlySight/activelook.h
new file mode 100644
index 0000000..d74b443
--- /dev/null
+++ b/FlySight/activelook.h
@@ -0,0 +1,38 @@
+/***************************************************************************
+** **
+** FlySight 2 firmware **
+** Copyright 2025 Bionic Avionics Inc. **
+** **
+** This program is free software: you can redistribute it and/or modify **
+** it under the terms of the GNU General Public License as published by **
+** the Free Software Foundation, either version 3 of the License, or **
+** (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, **
+** but WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program. If not, see . **
+** **
+****************************************************************************
+** Contact: Bionic Avionics Inc. **
+** Website: http://flysight.ca/ **
+****************************************************************************/
+
+#ifndef ACTIVELOOK_H_
+#define ACTIVELOOK_H_
+
+#include
+
+typedef enum {
+ FS_AL_SETUP_IN_PROGRESS = 0,
+ FS_AL_SETUP_DONE
+} FS_ActiveLook_SetupStatus_t;
+
+void FS_ActiveLook_Init(void);
+void FS_ActiveLook_DeInit(void);
+uint8_t FS_ActiveLook_IsActive(void);
+
+#endif /* ACTIVELOOK_H_ */
diff --git a/FlySight/activelook_config_data.c b/FlySight/activelook_config_data.c
new file mode 100644
index 0000000..13501b4
--- /dev/null
+++ b/FlySight/activelook_config_data.c
@@ -0,0 +1,144 @@
+#include "activelook_config_data.h"
+
+const uint16_t al_config_cmd_lengths[AL_CONFIG_CMD_COUNT] = {
+ 8, 245, 245, 245, 245, 245, 245, 245, 245, 245, 69, 8, 245, 245, 245, 245, 148, 8, 245, 245, 203, 13, 245, 157, 13, 245, 157, 13, 245, 157, 13, 245, 157, 13, 245, 157, 13, 245, 157, 13, 245, 157, 13, 245, 157, 13, 245, 157, 13, 245, 157, 13, 245, 157, 13, 245, 157, 13, 245, 157, 13, 245, 245, 245, 69
+};
+
+const uint16_t al_config_cmd_offsets[AL_CONFIG_CMD_COUNT] = {
+ 0, 8, 253, 498, 743, 988, 1233, 1478, 1723, 1968, 2213, 2282, 2290, 2535, 2780, 3025, 3270, 3418, 3426, 3671, 3916, 4119, 4132, 4377, 4534, 4547, 4792, 4949, 4962, 5207, 5364, 5377, 5622, 5779, 5792, 6037, 6194, 6207, 6452, 6609, 6622, 6867, 7024, 7037, 7282, 7439, 7452, 7697, 7854, 7867, 8112, 8269, 8282, 8527, 8684, 8697, 8942, 9099, 9112, 9357, 9514, 9527, 9772, 10017, 10262
+};
+
+// Total config data: 10331 bytes, 65 commands
+// (large protocol frames pre-split into 245-byte BLE chunks)
+const uint8_t al_config_cmd_data[10331] = {
+ // [0] font (8 bytes)
+ 0xFF, 0x51, 0x00, 0x08, 0x0A, 0x08, 0xB0, 0xAA,
+ // [1] font (245 bytes)
+ 0xFF, 0x51, 0x00, 0xF5, 0x02, 0x12, 0x00, 0x20, 0x00, 0x7A, 0x00, 0x00, 0x00, 0x04, 0x00, 0x18, 0x00, 0x27, 0x00, 0x47, 0x00, 0x63, 0x00, 0x88, 0x00, 0xA4, 0x00, 0xAF, 0x00, 0xC3, 0x00, 0xD7, 0x00, 0xE5, 0x00, 0xF4, 0x00, 0xFD, 0x01, 0x04, 0x01, 0x0D, 0x01, 0x21, 0x01, 0x3F, 0x01, 0x54, 0x01, 0x68, 0x01, 0x7C, 0x01, 0x93, 0x01, 0xA7, 0x01, 0xC1, 0x01, 0xD5, 0x01, 0xF2, 0x02, 0x0E, 0x02, 0x1B, 0x02, 0x29, 0x02, 0x39, 0x02, 0x43, 0x02, 0x54, 0x02, 0x69, 0x02, 0x90, 0x02, 0xAC, 0x02, 0xCA, 0x02, 0xDD, 0x02, 0xFD, 0x03, 0x10, 0x03, 0x24, 0x03, 0x3C, 0x03, 0x5C, 0x03, 0x6F, 0x03, 0x84, 0x03, 0xA2, 0x03, 0xB5, 0x03, 0xDC, 0x04, 0x03, 0x04, 0x21, 0x04, 0x3B, 0x04, 0x5A, 0x04, 0x79, 0x04, 0x8E, 0x04, 0xA2, 0x04, 0xC2, 0x04, 0xE0, 0x05, 0x06, 0x05, 0x23, 0x05, 0x3D, 0x05, 0x50, 0x05, 0x63, 0x05, 0x77, 0x05, 0x8A, 0x05, 0x9A, 0x05, 0xA0, 0x05, 0xAA, 0x05, 0xBF, 0x05, 0xDB, 0x05, 0xEA, 0x06, 0x07, 0x06, 0x1A, 0x06, 0x2E, 0x06, 0x45, 0x06, 0x61, 0x06, 0x75, 0x06, 0x88, 0x06, 0xA2, 0x06, 0xB5, 0x06, 0xD7, 0x06, 0xEF, 0x07, 0x07, 0x07, 0x1E, 0x07, 0x35, 0x07, 0x48, 0x07, 0x59, 0x07, 0x6C, 0x07, 0x85, 0x07, 0x9C, 0x07, 0xB9, 0x07, 0xCF, 0x07, 0xE5, 0x04, 0x0C, 0x00, 0x1B, 0x14, 0x0C, 0x00, 0x05, 0x01, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0x00, 0x04, 0x31, 0xB1, 0xF0, 0x40, 0x0F, 0x0C, 0x00, 0x04, 0x72, 0x11, 0x82, 0x11, 0x82, 0x11, 0x81, 0x21, 0x00, 0x11, 0x10, 0x20, 0x0C, 0x00, 0x05, 0x01, 0x32, 0x62, 0x22, 0x61, 0x32, 0x52, 0x31, 0x4A, 0xAA,
+ // [2] font (245 bytes)
+ 0xFF, 0x51, 0x00, 0xF5, 0x42, 0x23, 0x52, 0x22, 0x62, 0x22, 0x62, 0x22, 0x4A, 0x33, 0x13, 0x61, 0x32, 0x52, 0x31, 0x62, 0x22, 0xF0, 0x10, 0x1C, 0x0B, 0xF0, 0xC1, 0x94, 0x64, 0x11, 0x42, 0x11, 0x72, 0x11, 0x72, 0x11, 0x83, 0x94, 0x84, 0x71, 0x12, 0x71, 0x22, 0x61, 0x21, 0x42, 0x14, 0x46, 0x81, 0xA1, 0x50, 0x25, 0x0C, 0x00, 0x04, 0x71, 0x94, 0x31, 0x41, 0x21, 0x22, 0x41, 0x22, 0x11, 0x51, 0x21, 0x12, 0x54, 0x11, 0x72, 0x12, 0xA1, 0xB1, 0x13, 0x64, 0x12, 0x51, 0x12, 0x12, 0x42, 0x12, 0x12, 0x41, 0x22, 0x12, 0x32, 0x33, 0xE0, 0x1C, 0x0C, 0x00, 0x05, 0x03, 0x82, 0x12, 0x62, 0xA2, 0xB1, 0xB2, 0x94, 0x84, 0x31, 0x32, 0x22, 0x22, 0x22, 0x31, 0x21, 0x32, 0x34, 0x32, 0x43, 0x47, 0x64, 0x12, 0xE0, 0x0B, 0x0C, 0x00, 0x04, 0x62, 0xA2, 0xA1, 0xB1, 0x00, 0x11, 0x50, 0x14, 0x0A, 0x00, 0x04, 0x32, 0x72, 0x81, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x92, 0x92, 0x30, 0x14, 0x0A, 0x00, 0x04, 0x22, 0x92, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x72, 0x40, 0x0E, 0x0C, 0x00, 0x06, 0x61, 0xB1, 0x87, 0x73, 0x93, 0x82, 0x11, 0x00, 0x0C, 0x40, 0x0F, 0x0C, 0x00, 0x0B, 0x12, 0xA2, 0xA2, 0xA2, 0x78, 0x72, 0xA2, 0xA2, 0xA1, 0xF0, 0xF0, 0x09, 0x0B, 0x00, 0x13, 0x42, 0x92, 0x92, 0x91, 0x80, 0x07, 0x0C, 0x00, 0x12, 0x44, 0x00, 0x08, 0x09, 0x0C, 0x00, 0x15, 0x22, 0x93, 0xA2, 0xF0, 0x50, 0x14, 0x0C, 0x00, 0x05, 0x42, 0x92, 0xA2, 0x92, 0xA2, 0xA1, 0xA2, 0xA2, 0x92, 0xA2, 0x92, 0xA2, 0xA2, 0x92, 0xA2, 0x80, 0x1E, 0x0C, 0x00, 0xAA,
+ // [3] font (245 bytes)
+ 0xFF, 0x51, 0x00, 0xF5, 0x05, 0x04, 0x76, 0x52, 0x32, 0x52, 0x42, 0x41, 0x52, 0x32, 0x52, 0x32, 0x61, 0x32, 0x61, 0x32, 0x61, 0x41, 0x52, 0x42, 0x42, 0x43, 0x22, 0x66, 0x74, 0xF0, 0x10, 0x15, 0x0C, 0x00, 0x05, 0x12, 0x93, 0x84, 0x72, 0x12, 0x71, 0x22, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0x78, 0xE0, 0x14, 0x0C, 0x00, 0x05, 0x04, 0x76, 0x52, 0x32, 0xB1, 0xB1, 0xA2, 0xA2, 0x92, 0x92, 0x92, 0x93, 0x83, 0x92, 0xA8, 0xE0, 0x14, 0x0C, 0x00, 0x04, 0x67, 0xA2, 0x93, 0x83, 0x83, 0x92, 0x95, 0xB2, 0xB2, 0xA2, 0xA2, 0x92, 0x83, 0x64, 0xF0, 0x30, 0x17, 0x0C, 0x00, 0x05, 0x12, 0xA2, 0xA1, 0xA2, 0xA2, 0x92, 0xA2, 0x21, 0x62, 0x32, 0x52, 0x32, 0x42, 0x42, 0x49, 0x92, 0xA2, 0xA2, 0xF0, 0x14, 0x0C, 0x00, 0x04, 0x67, 0x52, 0xA2, 0xA2, 0xA6, 0x67, 0xA3, 0xA2, 0xA2, 0xA2, 0x92, 0x92, 0x92, 0x73, 0xF0, 0x40, 0x1A, 0x0C, 0x00, 0x05, 0x23, 0x75, 0x63, 0x92, 0x92, 0xA2, 0x13, 0x68, 0x33, 0x42, 0x32, 0x52, 0x41, 0x61, 0x42, 0x42, 0x42, 0x33, 0x56, 0x74, 0xF0, 0x10, 0x14, 0x0C, 0x00, 0x04, 0x68, 0x48, 0xA2, 0x92, 0xA2, 0x92, 0xA2, 0x92, 0xA2, 0x92, 0xA2, 0xA2, 0xA2, 0xA2, 0xF0, 0x30, 0x1D, 0x0C, 0x00, 0x05, 0x04, 0x72, 0x13, 0x52, 0x41, 0x52, 0x41, 0x52, 0x32, 0x65, 0x75, 0x67, 0x52, 0x33, 0x32, 0x52, 0x32, 0x61, 0x41, 0x52, 0x42, 0x32, 0x74, 0xF0, 0x10, 0x1C, 0x0C, 0x00, 0x05, 0x04, 0x62, 0x32, 0x52, 0x42, 0x32, 0x52, 0x32, 0x52, 0x32, 0x52, 0x42, 0x42, 0x42, 0x33, 0x63, 0x12, 0xA2, 0x92, 0x92, 0x92, 0x83, 0xF0, 0x30, 0x0D, 0x0C, 0x00, 0x0F, 0x22, 0x93, 0xAA,
+ // [4] font (245 bytes)
+ 0xFF, 0x51, 0x00, 0xF5, 0xA2, 0xF0, 0x72, 0x93, 0xA2, 0xF0, 0x50, 0x0E, 0x0B, 0x00, 0x0C, 0x51, 0x93, 0x91, 0x00, 0x04, 0x02, 0x92, 0x92, 0x91, 0x80, 0x10, 0x0C, 0x00, 0x0A, 0x11, 0x93, 0x73, 0x73, 0x83, 0x92, 0xB3, 0xB3, 0xB3, 0xB1, 0xF0, 0xB0, 0x0A, 0x0C, 0x00, 0x10, 0x67, 0xF0, 0xE7, 0x00, 0x04, 0x70, 0x11, 0x0C, 0x00, 0x09, 0x21, 0xB3, 0xA4, 0xA4, 0xA3, 0xA2, 0x83, 0x73, 0x73, 0x91, 0x00, 0x04, 0x10, 0x15, 0x0C, 0x00, 0x05, 0x02, 0x86, 0x52, 0x42, 0xA2, 0xA2, 0x92, 0x93, 0x83, 0x92, 0xA2, 0x00, 0x04, 0x21, 0xB1, 0xF0, 0x40, 0x27, 0x0A, 0x00, 0x05, 0x34, 0x52, 0x22, 0x41, 0x41, 0x32, 0x12, 0x11, 0x31, 0x32, 0x11, 0x21, 0x32, 0x11, 0x21, 0x23, 0x11, 0x21, 0x11, 0x21, 0x11, 0x21, 0x11, 0x21, 0x11, 0x21, 0x11, 0x12, 0x11, 0x22, 0x14, 0x41, 0x92, 0x93, 0x40, 0x1C, 0x0C, 0x00, 0x05, 0x12, 0xA2, 0x93, 0x94, 0x81, 0x12, 0x72, 0x21, 0x72, 0x22, 0x61, 0x32, 0x52, 0x41, 0x58, 0x41, 0x52, 0x32, 0x62, 0x21, 0x72, 0x12, 0x72, 0xD0, 0x1E, 0x0C, 0x00, 0x04, 0x63, 0x96, 0x61, 0x42, 0x52, 0x32, 0x52, 0x32, 0x52, 0x22, 0x65, 0x72, 0x23, 0x52, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x14, 0x56, 0xF0, 0x10, 0x13, 0x0C, 0x00, 0x05, 0x14, 0x67, 0x43, 0x92, 0x92, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xB2, 0xA3, 0xA7, 0x74, 0xF0, 0x20, 0x0C, 0x00, 0x04, 0x65, 0x71, 0x42, 0x52, 0x42, 0x42, 0x51, 0x42, 0x52, 0x32, 0x52, 0x32, 0x52, 0x32, 0x52, 0x32, 0x52, 0x32, 0x52, 0x32, 0x42, 0x42, 0x42, 0x42, 0x23, 0x55, 0xF0, 0x20, 0x13, 0x0C, 0x00, 0x04, 0x68, 0x41, 0xB2, 0xAA,
+ // [5] font (245 bytes)
+ 0xFF, 0x51, 0x00, 0xF5, 0xA2, 0xA2, 0xA2, 0xA7, 0x52, 0xA2, 0xA2, 0xA2, 0xA2, 0xA1, 0xB8, 0xE0, 0x14, 0x0C, 0x00, 0x04, 0x68, 0x41, 0xB2, 0xA2, 0xA2, 0xA2, 0xA7, 0x52, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xF0, 0x50, 0x18, 0x0C, 0x00, 0x05, 0x05, 0x67, 0x43, 0x92, 0x92, 0xA2, 0xA2, 0xA2, 0x35, 0x22, 0x62, 0x22, 0x62, 0x32, 0x52, 0x33, 0x33, 0x47, 0x65, 0xF0, 0x20, 0x0C, 0x00, 0x04, 0x61, 0x61, 0x41, 0x61, 0x41, 0x61, 0x41, 0x61, 0x41, 0x61, 0x41, 0x61, 0x48, 0x41, 0x61, 0x41, 0x61, 0x41, 0x61, 0x41, 0x61, 0x41, 0x61, 0x41, 0x61, 0x41, 0x61, 0xE0, 0x13, 0x0C, 0x00, 0x04, 0x68, 0x48, 0x72, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0x78, 0x48, 0xE0, 0x15, 0x0C, 0x00, 0x05, 0x06, 0x66, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0x51, 0x33, 0x47, 0x65, 0xF0, 0x10, 0x1E, 0x0C, 0x00, 0x04, 0x62, 0x42, 0x42, 0x32, 0x52, 0x23, 0x52, 0x22, 0x62, 0x12, 0x74, 0x83, 0x94, 0x82, 0x12, 0x72, 0x12, 0x72, 0x22, 0x62, 0x32, 0x52, 0x33, 0x42, 0x42, 0xE0, 0x13, 0x0C, 0x00, 0x04, 0x62, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA1, 0xB8, 0xE0, 0x27, 0x0C, 0x00, 0x04, 0x52, 0x52, 0x33, 0x42, 0x33, 0x42, 0x33, 0x33, 0x34, 0x21, 0x11, 0x32, 0x11, 0x12, 0x11, 0x32, 0x14, 0x11, 0x32, 0x22, 0x21, 0x32, 0x22, 0x21, 0x32, 0x61, 0x32, 0x61, 0x32, 0x61, 0x32, 0x61, 0x32, 0x61, 0xE0, 0x27, 0x0C, 0x00, 0x04, 0x52, 0x52, 0x33, 0x42, 0x42, 0x42, 0x43, 0x32, 0x41, 0x11, 0x32, 0x41, 0x12, 0x22, 0x41, 0x21, 0x22, 0x41, 0x22, 0x12, 0xAA,
+ // [6] font (245 bytes)
+ 0xFF, 0x51, 0x00, 0xF5, 0x41, 0x31, 0x12, 0x41, 0x32, 0x11, 0x41, 0x43, 0x41, 0x43, 0x41, 0x52, 0x41, 0x52, 0xE0, 0x1E, 0x0C, 0x00, 0x05, 0x04, 0x76, 0x52, 0x33, 0x32, 0x52, 0x32, 0x61, 0x32, 0x62, 0x22, 0x62, 0x22, 0x62, 0x22, 0x62, 0x22, 0x61, 0x32, 0x52, 0x42, 0x33, 0x56, 0x74, 0xF0, 0x10, 0x1A, 0x0C, 0x00, 0x04, 0x66, 0x62, 0x33, 0x42, 0x52, 0x32, 0x52, 0x32, 0x52, 0x32, 0x51, 0x42, 0x33, 0x46, 0x62, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xF0, 0x50, 0x1F, 0x0C, 0x00, 0x05, 0x04, 0x76, 0x52, 0x33, 0x32, 0x52, 0x32, 0x62, 0x22, 0x62, 0x22, 0x62, 0x22, 0x62, 0x22, 0x62, 0x22, 0x62, 0x22, 0x22, 0x21, 0x42, 0x24, 0x56, 0x75, 0xB2, 0x20, 0x1F, 0x0C, 0x00, 0x04, 0x65, 0x71, 0x42, 0x51, 0x51, 0x52, 0x42, 0x42, 0x42, 0x42, 0x32, 0x52, 0x22, 0x65, 0x72, 0x12, 0x72, 0x22, 0x62, 0x22, 0x62, 0x32, 0x52, 0x32, 0x52, 0x42, 0xE0, 0x15, 0x0C, 0x00, 0x05, 0x05, 0x62, 0x92, 0xA2, 0xA3, 0xA3, 0xA4, 0xA3, 0xB2, 0xA2, 0xA2, 0xA2, 0x41, 0x42, 0x56, 0xF0, 0x10, 0x14, 0x0C, 0x00, 0x04, 0x59, 0x72, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xF0, 0x20, 0x20, 0x0C, 0x00, 0x04, 0x61, 0x52, 0x41, 0x52, 0x41, 0x52, 0x41, 0x52, 0x41, 0x52, 0x41, 0x52, 0x41, 0x52, 0x41, 0x52, 0x41, 0x52, 0x41, 0x52, 0x42, 0x42, 0x43, 0x23, 0x56, 0x74, 0xF0, 0x10, 0x1E, 0x0C, 0x00, 0x04, 0x52, 0x61, 0x32, 0x61, 0x41, 0x52, 0x42, 0x42, 0x42, 0x42, 0x51, 0x41, 0x62, 0x22, 0x62, 0x22, 0x62, 0x21, 0x81, 0x12, 0x84, 0x83, 0xA2, 0xA2, 0xF0, 0x20, 0x26, 0x0C, 0x00, 0x04, 0xAA,
+ // [7] font (245 bytes)
+ 0xFF, 0x51, 0x00, 0xF5, 0x42, 0x81, 0x21, 0x81, 0x21, 0x72, 0x22, 0x62, 0x22, 0x22, 0x22, 0x22, 0x22, 0x21, 0x32, 0x22, 0x21, 0x41, 0x13, 0x12, 0x41, 0x11, 0x14, 0x43, 0x14, 0x43, 0x23, 0x43, 0x22, 0x62, 0x22, 0x62, 0x22, 0xF0, 0x1D, 0x0C, 0x00, 0x04, 0x62, 0x52, 0x32, 0x42, 0x52, 0x31, 0x62, 0x22, 0x72, 0x11, 0x84, 0x92, 0xA3, 0x84, 0x72, 0x22, 0x62, 0x22, 0x52, 0x42, 0x42, 0x42, 0x32, 0x62, 0xD0, 0x1A, 0x0C, 0x00, 0x04, 0x52, 0x61, 0x32, 0x52, 0x42, 0x41, 0x52, 0x32, 0x62, 0x21, 0x72, 0x12, 0x83, 0x93, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xF0, 0x20, 0x13, 0x0C, 0x00, 0x04, 0x67, 0xB1, 0xA2, 0x92, 0xA2, 0x92, 0xA2, 0x92, 0xA2, 0x92, 0xA2, 0x92, 0xA2, 0xA8, 0xE0, 0x13, 0x0A, 0x00, 0x05, 0x34, 0x62, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x84, 0x30, 0x14, 0x0C, 0x00, 0x04, 0x62, 0xA2, 0xB2, 0xA2, 0xB1, 0xB2, 0xA2, 0xB2, 0xA2, 0xB2, 0xA2, 0xA2, 0xB2, 0xA2, 0xB2, 0x20, 0x13, 0x0A, 0x00, 0x05, 0x34, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x64, 0x30, 0x10, 0x0C, 0x00, 0x05, 0x12, 0x93, 0x85, 0x72, 0x22, 0x52, 0x42, 0x32, 0x52, 0x00, 0x0D, 0x60, 0x06, 0x0A, 0x00, 0x15, 0x38, 0x10, 0x0A, 0x0C, 0x00, 0x05, 0x11, 0xB2, 0xB1, 0x00, 0x12, 0x50, 0x15, 0x0C, 0x00, 0x0A, 0x75, 0x72, 0x22, 0xB2, 0xA2, 0x57, 0x42, 0x42, 0x41, 0x52, 0x41, 0x52, 0x42, 0x33, 0x54, 0x12, 0xE0, 0x1C, 0x0C, 0xF0, 0xB2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA6, 0x62, 0x32, 0x52, 0x42, 0x42, 0x42, 0x42, 0x51, 0x42, 0x42, 0x42, 0x42, 0xAA,
+ // [8] font (245 bytes)
+ 0xFF, 0x51, 0x00, 0xF5, 0x42, 0x42, 0x42, 0x32, 0x55, 0xF0, 0x20, 0x0F, 0x0C, 0x00, 0x0B, 0x05, 0x62, 0x92, 0xA1, 0xB1, 0xB1, 0xB2, 0xA3, 0xA7, 0x65, 0xF0, 0x1D, 0x0C, 0x00, 0x04, 0x02, 0xA2, 0xA2, 0xA2, 0xA2, 0x66, 0x52, 0x32, 0x42, 0x42, 0x41, 0x52, 0x32, 0x52, 0x32, 0x52, 0x32, 0x52, 0x42, 0x42, 0x42, 0x33, 0x54, 0x12, 0xE0, 0x13, 0x0C, 0x00, 0x0B, 0x04, 0x71, 0x32, 0x52, 0x42, 0x41, 0x52, 0x39, 0x32, 0xB1, 0xB2, 0xB2, 0x31, 0x75, 0xF0, 0x14, 0x0C, 0xF0, 0xF4, 0x62, 0xA1, 0xB1, 0xB1, 0x88, 0x71, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xF0, 0x40, 0x17, 0x09, 0x00, 0x07, 0x15, 0x31, 0x32, 0x22, 0x32, 0x21, 0x42, 0x21, 0x42, 0x22, 0x32, 0x31, 0x32, 0x45, 0x72, 0x71, 0x72, 0x53, 0x30, 0x1C, 0x0C, 0xF0, 0xB2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA7, 0x53, 0x31, 0x52, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0xE0, 0x14, 0x0C, 0x00, 0x05, 0x11, 0xB1, 0x00, 0x04, 0x04, 0x84, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB2, 0xA2, 0x21, 0x84, 0xE0, 0x13, 0x09, 0x00, 0x04, 0x01, 0xF0, 0x93, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x54, 0x53, 0x40, 0x1A, 0x0C, 0xF0, 0xB2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0x32, 0x52, 0x22, 0x62, 0x12, 0x74, 0x83, 0x94, 0x82, 0x12, 0x72, 0x22, 0x62, 0x32, 0x52, 0x42, 0xE0, 0x13, 0x0C, 0x00, 0x04, 0x64, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB2, 0xB3, 0xF0, 0x22, 0x0C, 0x00, 0x0A, 0x51, 0x13, 0x13, 0x37, 0x12, 0x22, 0x22, 0x22, 0x21, 0x32, 0x22, 0xAA,
+ // [9] font (245 bytes)
+ 0xFF, 0x51, 0x00, 0xF5, 0x21, 0x32, 0x22, 0x21, 0x32, 0x22, 0x21, 0x32, 0x22, 0x21, 0x32, 0x22, 0x21, 0x32, 0x22, 0x21, 0x32, 0x22, 0xD0, 0x18, 0x0C, 0x00, 0x0A, 0x66, 0x62, 0x41, 0x52, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0xE0, 0x18, 0x0C, 0x00, 0x0B, 0x04, 0x71, 0x32, 0x52, 0x42, 0x32, 0x52, 0x32, 0x61, 0x32, 0x61, 0x41, 0x52, 0x42, 0x42, 0x51, 0x32, 0x74, 0xF0, 0x10, 0x17, 0x09, 0x00, 0x07, 0x04, 0x42, 0x31, 0x32, 0x32, 0x22, 0x41, 0x22, 0x41, 0x22, 0x32, 0x22, 0x22, 0x35, 0x42, 0x72, 0x72, 0x72, 0x60, 0x17, 0x09, 0x00, 0x07, 0x14, 0x42, 0x22, 0x22, 0x32, 0x22, 0x32, 0x21, 0x42, 0x22, 0x32, 0x31, 0x32, 0x45, 0x72, 0x72, 0x72, 0x72, 0x10, 0x13, 0x0C, 0x00, 0x0A, 0x62, 0x15, 0x43, 0x32, 0x42, 0x51, 0x42, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xF0, 0x50, 0x11, 0x0C, 0x00, 0x0B, 0x04, 0x67, 0x52, 0xA2, 0xA7, 0x61, 0x24, 0xA2, 0x93, 0x47, 0x64, 0xF0, 0x20, 0x13, 0x0C, 0x00, 0x06, 0x41, 0xB1, 0xB1, 0x88, 0x71, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0x31, 0x84, 0xF0, 0x19, 0x0C, 0x00, 0x0A, 0x62, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x51, 0x42, 0x63, 0x12, 0xE0, 0x17, 0x0C, 0x00, 0x0A, 0x61, 0x52, 0x42, 0x41, 0x52, 0x41, 0x61, 0x32, 0x62, 0x21, 0x72, 0x21, 0x81, 0x12, 0x83, 0x93, 0xA2, 0xF0, 0x20, 0x1D, 0x0C, 0x00, 0x0A, 0x51, 0x72, 0x22, 0x62, 0x22, 0x22, 0x21, 0x41, 0x22, 0x21, 0x41, 0x22, 0x12, 0x41, 0x13, 0x12, 0x43, 0x14, 0x43, 0x22, 0xAA,
+ // [10] font (69 bytes)
+ 0xFF, 0x51, 0x00, 0x45, 0x62, 0x22, 0x61, 0x32, 0xF0, 0x16, 0x0C, 0x00, 0x0A, 0x62, 0x42, 0x42, 0x32, 0x62, 0x22, 0x74, 0x92, 0x93, 0x82, 0x12, 0x72, 0x22, 0x52, 0x42, 0x32, 0x52, 0xE0, 0x16, 0x09, 0x00, 0x06, 0x72, 0x41, 0x31, 0x32, 0x31, 0x31, 0x42, 0x21, 0x51, 0x11, 0x61, 0x11, 0x63, 0x71, 0x81, 0x72, 0x62, 0xE0, 0x0F, 0x0C, 0x00, 0x0A, 0x67, 0xA2, 0x92, 0x93, 0x92, 0x92, 0x93, 0x92, 0x92, 0xA8, 0xE0, 0xAA,
+ // [11] font (8 bytes)
+ 0xFF, 0x51, 0x00, 0x08, 0x0B, 0x04, 0x4F, 0xAA,
+ // [12] font (245 bytes)
+ 0xFF, 0x51, 0x00, 0xF5, 0x02, 0x22, 0x00, 0x20, 0x00, 0x39, 0x00, 0x00, 0x00, 0x05, 0x00, 0x3B, 0x00, 0x4D, 0x00, 0x88, 0x00, 0xBC, 0x00, 0xF2, 0x01, 0x28, 0x01, 0x3D, 0x01, 0x5F, 0x01, 0x81, 0x01, 0xA2, 0x01, 0xC8, 0x01, 0xDF, 0x01, 0xEA, 0x01, 0xFC, 0x02, 0x32, 0x02, 0x65, 0x02, 0x98, 0x02, 0xC3, 0x02, 0xEC, 0x03, 0x26, 0x03, 0x51, 0x03, 0x83, 0x03, 0xB7, 0x03, 0xE6, 0x05, 0x16, 0x00, 0x5D, 0x40, 0x36, 0x16, 0x00, 0x11, 0x25, 0xF0, 0x25, 0xF0, 0x25, 0xF0, 0x25, 0xF0, 0x25, 0xF0, 0x25, 0xF0, 0x25, 0xF0, 0x25, 0xF0, 0x25, 0xF0, 0x25, 0xF0, 0x25, 0xF0, 0x25, 0xF0, 0x25, 0xF0, 0x25, 0xF0, 0x25, 0xF0, 0x25, 0xF0, 0x25, 0xF0, 0x25, 0xF0, 0x23, 0x00, 0x08, 0x03, 0xF0, 0x35, 0xF0, 0x25, 0xF0, 0x25, 0xF0, 0x33, 0x00, 0x07, 0x12, 0x16, 0x00, 0x11, 0x1A, 0xCA, 0xCA, 0xCA, 0xC4, 0x24, 0xD3, 0x23, 0xE3, 0x23, 0xE3, 0x23, 0x00, 0x38, 0x3B, 0x16, 0x00, 0x11, 0x25, 0x35, 0x95, 0x35, 0x95, 0x35, 0x95, 0x35, 0x95, 0x35, 0x86, 0x26, 0x85, 0x35, 0x5F, 0x05, 0x2F, 0x05, 0x2F, 0x05, 0x2F, 0x05, 0x65, 0x35, 0x95, 0x35, 0x86, 0x26, 0x85, 0x35, 0x95, 0x35, 0x6F, 0x05, 0x2F, 0x05, 0x2F, 0x05, 0x2F, 0x05, 0x55, 0x35, 0x86, 0x26, 0x85, 0x35, 0x95, 0x35, 0x95, 0x35, 0x95, 0x35, 0x00, 0x06, 0x20, 0x34, 0x15, 0x00, 0x08, 0x75, 0xF0, 0x15, 0xF0, 0x15, 0xF7, 0xCB, 0x9E, 0x6E, 0x75, 0x17, 0x75, 0x24, 0xA5, 0x24, 0xA5, 0x24, 0xA6, 0x14, 0xBA, 0xC9, 0xDA, 0xD9, 0xD9, 0xCA, 0xBB, 0xA4, 0x25, 0xA4, 0x25, 0xA4, 0x25, 0x61, 0x34, 0x25, 0x53, 0x24, 0x15, 0x6F, 0x5F, 0x7D, 0xA9, 0xAA,
+ // [13] font (245 bytes)
+ 0xFF, 0x51, 0x00, 0xF5, 0xF5, 0xF0, 0x15, 0xF0, 0x15, 0x80, 0x36, 0x16, 0x00, 0x13, 0x56, 0x55, 0x58, 0x35, 0x69, 0x25, 0x55, 0x14, 0x16, 0x54, 0x2A, 0x64, 0x2A, 0x64, 0x29, 0x74, 0x29, 0x75, 0x18, 0x9D, 0x9C, 0xBB, 0xF0, 0x16, 0xF0, 0x1B, 0xAD, 0x9D, 0x89, 0x15, 0x79, 0x15, 0x79, 0x15, 0x6A, 0x15, 0x6A, 0x15, 0x55, 0x15, 0x15, 0x55, 0x29, 0x56, 0x29, 0x55, 0x56, 0x00, 0x05, 0x70, 0x36, 0x16, 0x00, 0x11, 0x36, 0xEA, 0xBC, 0xAD, 0x87, 0x24, 0x96, 0x42, 0xA6, 0x51, 0xA6, 0xF0, 0x25, 0xF0, 0x26, 0xF0, 0x26, 0xF7, 0xE9, 0xCA, 0x44, 0x4B, 0x25, 0x36, 0x16, 0x15, 0x36, 0x16, 0x15, 0x35, 0x3B, 0x35, 0x49, 0x45, 0x49, 0x46, 0x47, 0x66, 0x37, 0x6F, 0x01, 0x7F, 0x01, 0x79, 0x16, 0x87, 0x17, 0x00, 0x05, 0x50, 0x15, 0x16, 0x00, 0x11, 0x04, 0xF0, 0x35, 0xF0, 0x24, 0xF0, 0x34, 0xF0, 0x34, 0xF0, 0x34, 0xF0, 0x33, 0xF0, 0x42, 0x00, 0x39, 0x22, 0x13, 0x00, 0x0C, 0x75, 0xD6, 0xC6, 0xD4, 0xF4, 0xF4, 0xF4, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xF4, 0xF4, 0xF4, 0xF5, 0xE6, 0xE6, 0xE5, 0x60, 0x22, 0x13, 0x00, 0x0C, 0x65, 0xE7, 0xE5, 0xF5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xC6, 0xC7, 0xC5, 0x70, 0x21, 0x16, 0x00, 0x14, 0x44, 0xF0, 0x34, 0xF0, 0x34, 0xD3, 0x23, 0x31, 0xA4, 0x13, 0x14, 0x8E, 0x9D, 0xD4, 0xF0, 0x26, 0xF4, 0x14, 0xC5, 0x15, 0xB5, 0x24, 0xC3, 0x33, 0xE1, 0x51, 0x00, 0x24, 0x50, 0x26, 0x16, 0x00, 0x24, 0xAA,
+ // [14] font (245 bytes)
+ 0xFF, 0x51, 0x00, 0xF5, 0x74, 0xF0, 0x34, 0xF0, 0x34, 0xF0, 0x34, 0xF0, 0x34, 0xF0, 0x34, 0xF0, 0x34, 0xCF, 0x01, 0x6F, 0x01, 0x6F, 0x01, 0x7F, 0xC4, 0xF0, 0x34, 0xF0, 0x34, 0xF0, 0x34, 0xF0, 0x34, 0xF0, 0x34, 0x00, 0x0C, 0x10, 0x17, 0x14, 0x00, 0x3C, 0x33, 0xF0, 0x15, 0xF5, 0xF5, 0xF0, 0x14, 0xF0, 0x23, 0xF0, 0x22, 0xF0, 0x32, 0xF0, 0x22, 0xF0, 0x31, 0xF0, 0x10, 0x0B, 0x16, 0x00, 0x3A, 0x4A, 0xCA, 0xCA, 0xCA, 0x00, 0x19, 0x40, 0x12, 0x16, 0x00, 0x47, 0x74, 0xF0, 0x26, 0xF0, 0x16, 0xF0, 0x16, 0xF0, 0x16, 0xF0, 0x24, 0x00, 0x07, 0x30, 0x36, 0x16, 0x00, 0x0F, 0x36, 0xF0, 0x16, 0xF0, 0x15, 0xF0, 0x16, 0xF0, 0x16, 0xF6, 0xF0, 0x16, 0xF0, 0x15, 0xF0, 0x16, 0xF0, 0x16, 0xF6, 0xF0, 0x16, 0xF6, 0xF0, 0x16, 0xF0, 0x16, 0xF6, 0xF0, 0x16, 0xF6, 0xF0, 0x16, 0xF0, 0x15, 0xF0, 0x16, 0xF0, 0x16, 0xF6, 0xF0, 0x16, 0xF6, 0xF0, 0x16, 0xF0, 0x16, 0xF6, 0xF0, 0x16, 0xE0, 0x33, 0x16, 0x00, 0x11, 0x54, 0xF9, 0xCB, 0xAD, 0x87, 0x17, 0x75, 0x55, 0x66, 0x56, 0x55, 0x75, 0x55, 0x76, 0x36, 0x76, 0x36, 0x76, 0x36, 0x76, 0x36, 0x85, 0x36, 0x85, 0x36, 0x85, 0x36, 0x85, 0x36, 0x76, 0x45, 0x76, 0x46, 0x65, 0x56, 0x65, 0x65, 0x56, 0x66, 0x36, 0x8E, 0x9C, 0xBA, 0xD7, 0x00, 0x06, 0x40, 0x33, 0x16, 0x00, 0x11, 0x64, 0xF0, 0x26, 0xF7, 0xE8, 0xD9, 0xCA, 0xB5, 0x15, 0xA5, 0x25, 0xA4, 0x35, 0xB3, 0x35, 0xF0, 0x16, 0xF0, 0x16, 0xF0, 0x16, 0xF0, 0x16, 0xF0, 0x16, 0xF0, 0x16, 0xF0, 0x16, 0xF0, 0x16, 0xF0, 0x16, 0xF0, 0x16, 0xF0, 0x16, 0xF0, 0x16, 0xF0, 0x16, 0xCE, 0x8E, 0x8E, 0x00, 0xAA,
+ // [15] font (245 bytes)
+ 0xFF, 0x51, 0x00, 0xF5, 0x05, 0x70, 0x2B, 0x16, 0x00, 0x11, 0x45, 0xF9, 0xBC, 0x9E, 0x85, 0x46, 0x65, 0x65, 0x82, 0x76, 0xF0, 0x16, 0xF0, 0x16, 0xF0, 0x15, 0xF0, 0x16, 0xF6, 0xF7, 0xE7, 0xE7, 0xF6, 0xF6, 0xF6, 0xF7, 0xE7, 0xE7, 0xF6, 0xF7, 0xFF, 0x02, 0x5F, 0x02, 0x5F, 0x02, 0x00, 0x05, 0x70, 0x29, 0x16, 0x00, 0x11, 0x0D, 0x8F, 0x7F, 0x77, 0x26, 0xF0, 0x15, 0xF0, 0x16, 0xE7, 0xE7, 0xE6, 0xF6, 0xFB, 0xBC, 0xAD, 0xF8, 0xF0, 0x16, 0xF0, 0x16, 0xF0, 0x16, 0xF0, 0x16, 0xF0, 0x16, 0xF6, 0xF7, 0xD8, 0x9C, 0x9B, 0xB9, 0xD5, 0x00, 0x07, 0x20, 0x3A, 0x16, 0x00, 0x11, 0x65, 0xF0, 0x25, 0xF0, 0x15, 0xF0, 0x25, 0xF0, 0x15, 0xF0, 0x25, 0xF0, 0x16, 0xF0, 0x15, 0xF0, 0x16, 0xF0, 0x15, 0xF0, 0x16, 0xF0, 0x15, 0xF0, 0x16, 0x52, 0x95, 0x35, 0x86, 0x35, 0x85, 0x45, 0x84, 0x55, 0x7F, 0x03, 0x4F, 0x03, 0x4F, 0x03, 0x4F, 0x03, 0xE5, 0xF0, 0x25, 0xF0, 0x25, 0xF0, 0x25, 0xF0, 0x25, 0x00, 0x06, 0x10, 0x2B, 0x16, 0x00, 0x10, 0x7E, 0x8F, 0x7F, 0x7F, 0x76, 0xF0, 0x16, 0xF0, 0x16, 0xF0, 0x16, 0xF0, 0x1B, 0xBD, 0x9E, 0x8F, 0x75, 0x47, 0xF0, 0x16, 0xF0, 0x16, 0xF0, 0x25, 0xF0, 0x25, 0xF0, 0x16, 0xF6, 0xF7, 0xE7, 0xC9, 0x9C, 0xAA, 0xC8, 0xE5, 0x00, 0x07, 0x20, 0x32, 0x16, 0x00, 0x12, 0x14, 0xF7, 0xD9, 0xCA, 0xB7, 0xE6, 0xF6, 0xF0, 0x15, 0xF0, 0x16, 0xF0, 0x15, 0xF0, 0x16, 0x26, 0x8F, 0x01, 0x6F, 0x02, 0x58, 0x37, 0x47, 0x56, 0x46, 0x66, 0x45, 0x85, 0x45, 0x85, 0x46, 0x75, 0x46, 0x66, 0x55, 0x66, 0x56, 0x46, 0x77, 0x17, 0x8D, 0xAB, 0xC8, 0x00, 0x06, 0x30, 0x34, 0x16, 0x00, 0xAA,
+ // [16] font (148 bytes)
+ 0xFF, 0x51, 0x00, 0x94, 0x10, 0x7F, 0x01, 0x5F, 0x03, 0x4F, 0x03, 0x4F, 0x02, 0xF0, 0x16, 0xF0, 0x16, 0xF6, 0xF7, 0xF6, 0xF6, 0xF0, 0x16, 0xF6, 0xF0, 0x16, 0xF6, 0xF0, 0x16, 0xF6, 0xF0, 0x16, 0xF0, 0x15, 0xF0, 0x16, 0xF0, 0x16, 0xF0, 0x16, 0xF0, 0x16, 0xF0, 0x16, 0xF0, 0x16, 0xF0, 0x16, 0xF0, 0x16, 0x00, 0x06, 0x60, 0x2F, 0x16, 0x00, 0x11, 0x45, 0xFA, 0xBC, 0x9E, 0x76, 0x36, 0x75, 0x55, 0x75, 0x56, 0x65, 0x55, 0x76, 0x36, 0x8D, 0xAB, 0xC9, 0xCB, 0xAE, 0x76, 0x28, 0x56, 0x56, 0x56, 0x66, 0x36, 0x76, 0x36, 0x85, 0x36, 0x85, 0x45, 0x76, 0x46, 0x57, 0x57, 0x18, 0x6F, 0x9C, 0xB9, 0x00, 0x06, 0x30, 0x2F, 0x16, 0x00, 0x11, 0x45, 0xFA, 0xAD, 0x8F, 0x76, 0x46, 0x56, 0x56, 0x56, 0x66, 0x45, 0x76, 0x45, 0x85, 0x45, 0x85, 0x46, 0x66, 0x46, 0x66, 0x56, 0x38, 0x5F, 0x02, 0x6F, 0x01, 0x87, 0x16, 0xA4, 0x25, 0xF0, 0x25, 0xF0, 0x16, 0xF6, 0xF6, 0xE8, 0xB9, 0xD8, 0xE7, 0xF4, 0x00, 0x07, 0xAA,
+ // [17] font (8 bytes)
+ 0xFF, 0x51, 0x00, 0x08, 0x0C, 0x02, 0xA6, 0xAA,
+ // [18] font (245 bytes)
+ 0xFF, 0x51, 0x00, 0xF5, 0x02, 0x16, 0x00, 0x20, 0x00, 0x39, 0x00, 0x00, 0x00, 0x05, 0x00, 0x1C, 0x00, 0x2D, 0x00, 0x51, 0x00, 0x72, 0x00, 0xA2, 0x00, 0xC3, 0x00, 0xCF, 0x00, 0xE6, 0x00, 0xFD, 0x01, 0x0F, 0x01, 0x21, 0x01, 0x2C, 0x01, 0x34, 0x01, 0x3D, 0x01, 0x55, 0x01, 0x79, 0x01, 0x92, 0x01, 0xAB, 0x01, 0xC2, 0x01, 0xDE, 0x01, 0xF6, 0x02, 0x14, 0x02, 0x2B, 0x02, 0x4D, 0x05, 0x0E, 0x00, 0x26, 0x40, 0x17, 0x0E, 0x00, 0x07, 0x51, 0xC3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0x00, 0x05, 0x01, 0xC3, 0xC1, 0xF0, 0x70, 0x11, 0x0E, 0x00, 0x07, 0x42, 0x12, 0x92, 0x12, 0x92, 0x12, 0x92, 0x12, 0x91, 0x31, 0x00, 0x17, 0x30, 0x24, 0x0E, 0x00, 0x07, 0x52, 0x32, 0x63, 0x32, 0x63, 0x32, 0x63, 0x32, 0x62, 0x33, 0x3D, 0x2B, 0x52, 0x32, 0x72, 0x32, 0x72, 0x32, 0x63, 0x32, 0x4D, 0x1C, 0x42, 0x32, 0x72, 0x32, 0x72, 0x32, 0x72, 0x32, 0xF0, 0x30, 0x21, 0x0E, 0x00, 0x04, 0x22, 0xC2, 0xB5, 0x78, 0x62, 0x21, 0x82, 0x31, 0x82, 0x31, 0x92, 0x21, 0x95, 0xB4, 0xB4, 0xA5, 0x92, 0x13, 0x82, 0x22, 0x82, 0x22, 0x82, 0x12, 0x68, 0x66, 0xB2, 0xC2, 0x60, 0x30, 0x0E, 0x00, 0x07, 0x32, 0x51, 0x54, 0x32, 0x42, 0x22, 0x22, 0x42, 0x22, 0x21, 0x52, 0x22, 0x12, 0x52, 0x22, 0x12, 0x64, 0x12, 0x74, 0x12, 0xB2, 0xC2, 0x12, 0x82, 0x15, 0x62, 0x11, 0x22, 0x61, 0x21, 0x31, 0x52, 0x21, 0x31, 0x51, 0x31, 0x31, 0x42, 0x32, 0x12, 0x42, 0x43, 0xF0, 0x10, 0x21, 0x0E, 0x00, 0x07, 0x54, 0x96, 0x72, 0x41, 0x72, 0xC2, 0xC2, 0xD2, 0xB4, 0x95, 0x92, 0x13, 0x32, 0x23, 0x22, 0x32, 0xAA,
+ // [19] font (245 bytes)
+ 0xFF, 0x51, 0x00, 0xF5, 0x22, 0x42, 0x22, 0x22, 0x45, 0x33, 0x44, 0x42, 0x43, 0x5A, 0x65, 0x13, 0xF0, 0x0C, 0x0E, 0x00, 0x07, 0x22, 0xC2, 0xC2, 0xC2, 0xC1, 0x00, 0x18, 0x10, 0x17, 0x0C, 0x00, 0x06, 0x62, 0x92, 0xA1, 0xB1, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xB1, 0xB1, 0xB2, 0xB2, 0x40, 0x17, 0x0C, 0x00, 0x06, 0x43, 0xB2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0x83, 0x50, 0x12, 0x0E, 0x00, 0x09, 0x51, 0xD1, 0xA1, 0x21, 0xA8, 0x83, 0xB3, 0xA2, 0x12, 0x91, 0x31, 0x00, 0x10, 0x20, 0x12, 0x0E, 0x00, 0x10, 0x42, 0xC2, 0xC2, 0xC2, 0x8A, 0x4A, 0x82, 0xC2, 0xC2, 0xC2, 0xD1, 0x00, 0x04, 0x20, 0x0B, 0x0C, 0x00, 0x18, 0x22, 0x93, 0xA2, 0xB1, 0xA1, 0xB1, 0x90, 0x08, 0x0E, 0x00, 0x19, 0x06, 0x86, 0x00, 0x0B, 0x09, 0x0E, 0x00, 0x1F, 0x63, 0xB3, 0xB3, 0xF0, 0x80, 0x18, 0x0E, 0x00, 0x06, 0x42, 0xB2, 0xC2, 0xB3, 0xB2, 0xB3, 0xB2, 0xC2, 0xB2, 0xC2, 0xB3, 0xB2, 0xC2, 0xB2, 0xC2, 0xB3, 0xB2, 0xC2, 0xB2, 0xA0, 0x24, 0x0E, 0x00, 0x07, 0x54, 0x96, 0x73, 0x23, 0x62, 0x43, 0x42, 0x62, 0x42, 0x62, 0x42, 0x63, 0x32, 0x63, 0x32, 0x63, 0x32, 0x63, 0x32, 0x63, 0x32, 0x62, 0x42, 0x62, 0x52, 0x52, 0x53, 0x32, 0x77, 0x84, 0xF0, 0x40, 0x19, 0x0E, 0x00, 0x07, 0x72, 0xA4, 0x95, 0x83, 0x12, 0x73, 0x13, 0x81, 0x23, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0x8A, 0x4A, 0xF0, 0x19, 0x0E, 0x00, 0x07, 0x54, 0x87, 0x72, 0x33, 0x61, 0x53, 0xB3, 0xB3, 0xB2, 0xB3, 0xA3, 0xA3, 0xB2, 0xB2, 0xB3, 0xA3, 0xAA,
+ // [20] font (203 bytes)
+ 0xFF, 0x51, 0x00, 0xCB, 0xA3, 0xBA, 0x4A, 0xF0, 0x10, 0x17, 0x0E, 0x00, 0x07, 0x38, 0x68, 0xC2, 0xB2, 0xA3, 0xA3, 0xA6, 0x87, 0xC3, 0xC2, 0xC2, 0xC2, 0xB3, 0xA3, 0x95, 0x66, 0x84, 0xF0, 0x60, 0x1C, 0x0E, 0x00, 0x07, 0x72, 0xB2, 0xC2, 0xB3, 0xB2, 0xC2, 0xB2, 0xC2, 0x31, 0x72, 0x33, 0x62, 0x33, 0x52, 0x43, 0x52, 0x43, 0x5B, 0x3B, 0x93, 0xB3, 0xB3, 0xF0, 0x20, 0x18, 0x0E, 0x00, 0x07, 0x38, 0x68, 0x62, 0xC2, 0xC2, 0xC2, 0xC7, 0x78, 0x62, 0x43, 0xB3, 0xC2, 0xC2, 0xB3, 0xA3, 0x94, 0x76, 0x83, 0xF0, 0x70, 0x1E, 0x0E, 0x00, 0x07, 0x74, 0x95, 0x74, 0xA2, 0xB3, 0xB2, 0xB2, 0xC2, 0x15, 0x6A, 0x43, 0x43, 0x42, 0x63, 0x32, 0x63, 0x32, 0x63, 0x33, 0x52, 0x53, 0x33, 0x67, 0x85, 0xF0, 0x30, 0x17, 0x0E, 0x00, 0x07, 0x2A, 0x4A, 0xC2, 0xB3, 0xB2, 0xB3, 0xB2, 0xB2, 0xB3, 0xB2, 0xC2, 0xB2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xF0, 0x60, 0x22, 0x0E, 0x00, 0x07, 0x54, 0x88, 0x63, 0x32, 0x62, 0x43, 0x52, 0x43, 0x52, 0x42, 0x76, 0x95, 0x73, 0x14, 0x53, 0x43, 0x42, 0x62, 0x42, 0x63, 0x32, 0x63, 0x32, 0x62, 0x43, 0x43, 0x58, 0x85, 0xF0, 0x30, 0x1F, 0x0E, 0x00, 0x07, 0x54, 0x88, 0x53, 0x42, 0x52, 0x53, 0x42, 0x62, 0x42, 0x62, 0x42, 0x63, 0x32, 0x63, 0x33, 0x43, 0x59, 0x74, 0x12, 0xB3, 0xB2, 0xB3, 0x94, 0x85, 0x93, 0xF0, 0x60, 0xAA,
+ // [21] image (13 bytes)
+ 0xFF, 0x41, 0x00, 0x0D, 0x64, 0x00, 0x00, 0x01, 0x88, 0x00, 0x1C, 0x00, 0xAA,
+ // [22] image (245 bytes)
+ 0xFF, 0x41, 0x00, 0xF5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x66, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x66, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x66, 0x65, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x56, 0x60, 0x66, 0x66, 0x66, 0x66, 0x66, 0x16, 0x00, 0x00, 0x00, 0x00, 0x10, 0x66, 0x05, 0x40, 0x66, 0x66, 0x66, 0x66, 0x66, 0x16, 0x00, 0x00, 0x00, 0x00, 0x61, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x66, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x44, 0x44, 0x44, 0x02, 0x10, 0x66, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x66, 0x66, 0x66, 0x06, 0x61, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x00, 0x63, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x01, 0x00, 0x00, 0x10, 0x66, 0x05, 0x00, 0x00, 0x00, 0x60, 0x66, 0x66, 0x66, 0x66, 0x46, 0x00, 0x00, 0x00, 0x61, 0xAA,
+ // [23] image (157 bytes)
+ 0xFF, 0x41, 0x00, 0x9D, 0x56, 0x00, 0x00, 0x00, 0x30, 0x44, 0x44, 0x44, 0x44, 0x14, 0x00, 0x00, 0x00, 0x10, 0x66, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x66, 0x05, 0x40, 0x66, 0x66, 0x66, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x56, 0x60, 0x66, 0x66, 0x66, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x66, 0x65, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x66, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x66, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA,
+ // [24] image (13 bytes)
+ 0xFF, 0x41, 0x00, 0x0D, 0x65, 0x00, 0x00, 0x01, 0x88, 0x00, 0x1C, 0x00, 0xAA,
+ // [25] image (245 bytes)
+ 0xFF, 0x41, 0x00, 0xF5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x66, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x66, 0x66, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x66, 0x05, 0x65, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x56, 0x00, 0x50, 0x66, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x66, 0x05, 0x00, 0x00, 0x65, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x56, 0x00, 0x00, 0x00, 0x50, 0x66, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x66, 0x05, 0x00, 0x00, 0x00, 0x00, 0x65, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x66, 0x01, 0x00, 0x00, 0x00, 0x10, 0x66, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x16, 0x00, 0x00, 0x00, 0x61, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x66, 0x01, 0x00, 0x00, 0x66, 0x66, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x66, 0x66, 0x06, 0x00, 0x00, 0x65, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x66, 0x66, 0x05, 0x00, 0x00, 0x00, 0x00, 0x66, 0x00, 0x63, 0x00, 0x00, 0x00, 0x60, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x00, 0x64, 0x01, 0x00, 0x00, 0x60, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x00, 0x64, 0x01, 0x00, 0x00, 0x60, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x00, 0x64, 0x01, 0x00, 0x00, 0x60, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA,
+ // [26] image (157 bytes)
+ 0xFF, 0x41, 0x00, 0x9D, 0x00, 0x66, 0x00, 0x64, 0x01, 0x00, 0x00, 0x60, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x00, 0x64, 0x01, 0x00, 0x00, 0x60, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x64, 0x01, 0x50, 0x02, 0x60, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x01, 0x61, 0x04, 0x60, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x01, 0x61, 0x04, 0x60, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x01, 0x61, 0x04, 0x60, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x00, 0x61, 0x04, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA,
+ // [27] image (13 bytes)
+ 0xFF, 0x41, 0x00, 0x0D, 0x66, 0x00, 0x00, 0x01, 0x88, 0x00, 0x1C, 0x00, 0xAA,
+ // [28] image (245 bytes)
+ 0xFF, 0x41, 0x00, 0xF5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x03, 0x00, 0x00, 0x60, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x06, 0x00, 0x00, 0x40, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x66, 0x66, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x66, 0x66, 0x66, 0x66, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x64, 0x66, 0x66, 0x66, 0x66, 0x46, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x66, 0x66, 0x66, 0x66, 0x66, 0x46, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x63, 0x66, 0x66, 0x66, 0x66, 0x66, 0x36, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x66, 0x66, 0x31, 0x63, 0x66, 0x66, 0x04, 0x00, 0x00, 0x00, 0xAA,
+ // [29] image (157 bytes)
+ 0xFF, 0x41, 0x00, 0x9D, 0x00, 0x00, 0x00, 0x10, 0x66, 0x66, 0x01, 0x00, 0x31, 0x66, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x01, 0x00, 0x00, 0x64, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x66, 0x01, 0x00, 0x00, 0x61, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x66, 0x01, 0x00, 0x00, 0x30, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x66, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x66, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x66, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x66, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA,
+ // [30] image (13 bytes)
+ 0xFF, 0x41, 0x00, 0x0D, 0x67, 0x00, 0x00, 0x01, 0x88, 0x00, 0x1C, 0x00, 0xAA,
+ // [31] image (245 bytes)
+ 0xFF, 0x41, 0x00, 0xF5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x03, 0x00, 0x00, 0x60, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x06, 0x00, 0x00, 0x40, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x41, 0x66, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x64, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x64, 0x66, 0x66, 0x66, 0x66, 0x66, 0x04, 0x00, 0x00, 0x00, 0x00, 0x31, 0x64, 0x66, 0x66, 0x66, 0x66, 0x66, 0x14, 0x64, 0x16, 0x00, 0x00, 0x10, 0x64, 0x66, 0x66, 0x66, 0x66, 0x66, 0x34, 0x01, 0x00, 0x30, 0x36, 0x00, 0x00, 0x63, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x66, 0x66, 0x44, 0x63, 0x66, 0x66, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0xAA,
+ // [32] image (157 bytes)
+ 0xFF, 0x41, 0x00, 0x9D, 0x46, 0x11, 0x00, 0x10, 0x66, 0x66, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x66, 0x66, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x64, 0x66, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x66, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x66, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA,
+ // [33] image (13 bytes)
+ 0xFF, 0x41, 0x00, 0x0D, 0x68, 0x00, 0x00, 0x01, 0x88, 0x00, 0x1C, 0x00, 0xAA,
+ // [34] image (245 bytes)
+ 0xFF, 0x41, 0x00, 0xF5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x01, 0x00, 0x00, 0x00, 0x00, 0x60, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x06, 0x00, 0x00, 0x00, 0x00, 0x63, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x46, 0x00, 0x00, 0x00, 0x30, 0x66, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x66, 0x01, 0x00, 0x00, 0x40, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x04, 0x00, 0x00, 0x60, 0x36, 0x00, 0x00, 0x00, 0x41, 0x33, 0x00, 0x00, 0x00, 0x61, 0x16, 0x00, 0x00, 0x63, 0x06, 0x00, 0x00, 0x30, 0x66, 0x66, 0x03, 0x00, 0x00, 0x61, 0x16, 0x00, 0x00, 0x63, 0x06, 0x00, 0x00, 0x63, 0x66, 0x66, 0x16, 0x00, 0x00, 0x60, 0x36, 0x00, 0x00, 0x63, 0x04, 0x00, 0x00, 0x66, 0x66, 0x66, 0x16, 0x00, 0x00, 0x40, 0x46, 0x00, 0x00, 0x64, 0x04, 0x00, 0x30, 0x66, 0x66, 0x66, 0x36, 0x00, 0x00, 0x40, 0x36, 0x00, 0x00, 0x63, 0x06, 0x00, 0x60, 0x66, 0x66, 0x66, 0x06, 0x00, 0x00, 0x40, 0x36, 0x00, 0x00, 0x61, 0x16, 0x00, 0x63, 0x66, 0x66, 0x66, 0x01, 0x00, 0x00, 0x60, 0x36, 0x00, 0x00, 0x60, 0x06, 0x00, 0x66, 0x66, 0x66, 0x04, 0x00, 0x00, 0x00, 0x63, 0x06, 0x00, 0x00, 0x40, 0xAA,
+ // [35] image (157 bytes)
+ 0xFF, 0x41, 0x00, 0x9D, 0x03, 0x30, 0x66, 0x66, 0x14, 0x00, 0x00, 0x00, 0x00, 0x66, 0x03, 0x00, 0x00, 0x10, 0x00, 0x63, 0x66, 0x14, 0x00, 0x00, 0x00, 0x00, 0x30, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x36, 0x00, 0x00, 0x00, 0x00, 0x30, 0x46, 0x01, 0x00, 0x00, 0x00, 0x00, 0x30, 0x66, 0x03, 0x00, 0x00, 0x00, 0x00, 0x40, 0x01, 0x00, 0x11, 0x00, 0x00, 0x41, 0x66, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x66, 0x66, 0x64, 0x66, 0x66, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x66, 0x66, 0x66, 0x46, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x31, 0x13, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA,
+ // [36] image (13 bytes)
+ 0xFF, 0x41, 0x00, 0x0D, 0x69, 0x00, 0x00, 0x01, 0x88, 0x00, 0x1C, 0x00, 0xAA,
+ // [37] image (245 bytes)
+ 0xFF, 0x41, 0x00, 0xF5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x66, 0x66, 0x03, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x61, 0x66, 0x64, 0x16, 0x00, 0x00, 0x00, 0x63, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x06, 0x40, 0x46, 0x00, 0x00, 0x30, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x36, 0x61, 0x36, 0x00, 0x00, 0x61, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x66, 0x66, 0x16, 0x00, 0x00, 0x63, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x66, 0x03, 0x00, 0x00, 0x63, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x04, 0x00, 0x00, 0x00, 0x10, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x06, 0x00, 0x00, 0x30, 0x66, 0x66, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x36, 0x11, 0x11, 0x61, 0x66, 0x66, 0x36, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x30, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x36, 0x00, 0x00, 0x00, 0x00, 0x63, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x03, 0x00, 0x00, 0x00, 0x00, 0x10, 0x11, 0x63, 0x66, 0x66, 0x16, 0x11, 0x11, 0x63, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x66, 0x66, 0x03, 0x00, 0x00, 0x60, 0x16, 0x00, 0x00, 0x00, 0xAA,
+ // [38] image (157 bytes)
+ 0xFF, 0x41, 0x00, 0x9D, 0x00, 0x00, 0x00, 0x00, 0x30, 0x01, 0x00, 0x00, 0x00, 0x40, 0x46, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x36, 0x00, 0x00, 0x30, 0x66, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x36, 0x00, 0x00, 0x61, 0x66, 0x66, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x16, 0x00, 0x00, 0x63, 0x16, 0x63, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x03, 0x00, 0x00, 0x64, 0x04, 0x60, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x36, 0x00, 0x00, 0x00, 0x61, 0x46, 0x66, 0x16, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, 0x00, 0x00, 0x30, 0x66, 0x66, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA,
+ // [39] image (13 bytes)
+ 0xFF, 0x41, 0x00, 0x0D, 0x6A, 0x00, 0x00, 0x01, 0x88, 0x00, 0x1C, 0x00, 0xAA,
+ // [40] image (245 bytes)
+ 0xFF, 0x41, 0x00, 0xF5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x40, 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x56, 0x00, 0x00, 0x00, 0x40, 0x04, 0x00, 0x00, 0x00, 0x65, 0x01, 0x00, 0x00, 0x00, 0x65, 0x05, 0x00, 0x00, 0x40, 0x04, 0x00, 0x00, 0x50, 0x56, 0x00, 0x00, 0x00, 0x00, 0x50, 0x56, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x65, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x05, 0x00, 0x00, 0x00, 0x00, 0x50, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x56, 0x00, 0x20, 0x02, 0x00, 0x65, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x05, 0x40, 0x04, 0x50, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x56, 0x20, 0x02, 0x65, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x05, 0x50, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x56, 0x65, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA,
+ // [41] image (157 bytes)
+ 0xFF, 0x41, 0x00, 0x9D, 0x00, 0x00, 0x00, 0x40, 0x66, 0x66, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x66, 0x66, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x66, 0x46, 0x64, 0x66, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x66, 0x01, 0x10, 0x66, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x36, 0x00, 0x00, 0x63, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x26, 0x00, 0x00, 0x63, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x46, 0x00, 0x00, 0x64, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x66, 0x14, 0x41, 0x66, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x66, 0x66, 0x66, 0x66, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x66, 0x66, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA,
+ // [42] image (13 bytes)
+ 0xFF, 0x41, 0x00, 0x0D, 0x6B, 0x00, 0x00, 0x01, 0x88, 0x00, 0x1C, 0x00, 0xAA,
+ // [43] image (245 bytes)
+ 0xFF, 0x41, 0x00, 0xF5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x66, 0x16, 0x00, 0x00, 0x00, 0x00, 0x63, 0x66, 0x36, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x13, 0x10, 0x01, 0x11, 0x66, 0x66, 0x66, 0x01, 0x00, 0x00, 0x30, 0x66, 0x00, 0x66, 0x46, 0x61, 0x16, 0x64, 0x66, 0x00, 0x66, 0x01, 0x00, 0x00, 0x10, 0x66, 0x00, 0x66, 0x46, 0x61, 0x16, 0x64, 0x66, 0x00, 0x66, 0x03, 0x00, 0x00, 0x10, 0x66, 0x66, 0x66, 0x11, 0x10, 0x01, 0x31, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x63, 0x66, 0x36, 0x00, 0x00, 0x00, 0x00, 0x63, 0x66, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x66, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x44, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x66, 0x03, 0x00, 0x00, 0x00, 0x00, 0x10, 0x66, 0x01, 0x00, 0x00, 0x00, 0x00, 0x61, 0x66, 0x16, 0x00, 0x00, 0x00, 0x00, 0x10, 0x66, 0x01, 0x00, 0x00, 0x00, 0x00, 0x64, 0x66, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x30, 0x66, 0x66, 0x66, 0x01, 0x00, 0x00, 0x00, 0x10, 0x44, 0x01, 0x00, 0x00, 0x00, 0x60, 0xAA,
+ // [44] image (157 bytes)
+ 0xFF, 0x41, 0x00, 0x9D, 0x66, 0x66, 0x66, 0x04, 0x00, 0x00, 0x00, 0x30, 0x66, 0x01, 0x00, 0x00, 0x00, 0x63, 0x66, 0x64, 0x66, 0x16, 0x00, 0x00, 0x00, 0x61, 0x66, 0x36, 0x00, 0x00, 0x00, 0x63, 0x36, 0x00, 0x64, 0x36, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x64, 0x36, 0x00, 0x63, 0x36, 0x00, 0x00, 0x30, 0x66, 0x00, 0x66, 0x01, 0x00, 0x00, 0x61, 0x46, 0x11, 0x66, 0x36, 0x00, 0x00, 0x10, 0x66, 0x00, 0x66, 0x03, 0x00, 0x00, 0x61, 0x66, 0x66, 0x66, 0x06, 0x00, 0x00, 0x10, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x30, 0x66, 0x66, 0x66, 0x03, 0x00, 0x00, 0x00, 0x63, 0x66, 0x36, 0x00, 0x00, 0x00, 0x00, 0x31, 0x66, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA,
+ // [45] image (13 bytes)
+ 0xFF, 0x41, 0x00, 0x0D, 0x6C, 0x00, 0x00, 0x01, 0x88, 0x00, 0x1C, 0x00, 0xAA,
+ // [46] image (245 bytes)
+ 0xFF, 0x41, 0x00, 0xF5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x61, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x03, 0x00, 0x00, 0x00, 0x00, 0x61, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x11, 0x11, 0x31, 0x66, 0x11, 0x11, 0x64, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x66, 0x01, 0x10, 0x66, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x04, 0x40, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x36, 0x63, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x66, 0x66, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x66, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA,
+ // [47] image (157 bytes)
+ 0xFF, 0x41, 0x00, 0x9D, 0x00, 0x00, 0x00, 0x00, 0x63, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x66, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x66, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA,
+ // [48] image (13 bytes)
+ 0xFF, 0x41, 0x00, 0x0D, 0x6D, 0x00, 0x00, 0x01, 0x88, 0x00, 0x1C, 0x00, 0xAA,
+ // [49] image (245 bytes)
+ 0xFF, 0x41, 0x00, 0xF5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x33, 0x33, 0x33, 0x33, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x66, 0x66, 0x66, 0x66, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x44, 0x44, 0x44, 0x44, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x44, 0x44, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x63, 0x66, 0x66, 0x36, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x31, 0x33, 0x33, 0x13, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x64, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x46, 0x00, 0x00, 0x64, 0x36, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x00, 0x63, 0x46, 0x00, 0x00, 0x64, 0x66, 0x03, 0x64, 0x66, 0x66, 0x66, 0x66, 0x46, 0x30, 0x66, 0x46, 0x00, 0x00, 0x64, 0x66, 0x03, 0x64, 0x66, 0x66, 0x66, 0x66, 0x46, 0x30, 0x66, 0x46, 0x00, 0x00, 0x64, 0x36, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x00, 0x63, 0x46, 0x00, 0x00, 0x64, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x46, 0x00, 0x00, 0x34, 0xAA,
+ // [50] image (157 bytes)
+ 0xFF, 0x41, 0x00, 0x9D, 0x00, 0x00, 0x00, 0x31, 0x33, 0x33, 0x13, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x63, 0x66, 0x66, 0x36, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x44, 0x44, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x44, 0x44, 0x44, 0x44, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x66, 0x66, 0x66, 0x66, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x33, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA,
+ // [51] image (13 bytes)
+ 0xFF, 0x41, 0x00, 0x0D, 0x6E, 0x00, 0x00, 0x01, 0x88, 0x00, 0x1C, 0x00, 0xAA,
+ // [52] image (245 bytes)
+ 0xFF, 0x41, 0x00, 0xF5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x43, 0x33, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x66, 0x66, 0x66, 0x66, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x66, 0x66, 0x44, 0x44, 0x66, 0x66, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x46, 0x01, 0x00, 0x00, 0x30, 0x66, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x66, 0x03, 0x00, 0x00, 0x00, 0x00, 0x30, 0x66, 0x04, 0x00, 0x00, 0x00, 0x00, 0x63, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x36, 0x00, 0x00, 0x00, 0x30, 0x66, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x30, 0x66, 0x01, 0x00, 0x00, 0x30, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x36, 0x00, 0x64, 0x04, 0x00, 0x00, 0x60, 0x36, 0x00, 0x00, 0x00, 0x00, 0x41, 0x66, 0x03, 0x00, 0x61, 0x06, 0x00, 0x00, 0x63, 0x06, 0x00, 0x00, 0x00, 0x41, 0x66, 0x66, 0x03, 0x00, 0x60, 0x16, 0x00, 0x00, 0x63, 0x04, 0x00, 0x00, 0x30, 0x66, 0x66, 0x36, 0x00, 0x00, 0x40, 0x36, 0x00, 0x00, 0x63, 0x04, 0x00, 0x00, 0x60, 0x16, 0x61, 0x36, 0x00, 0x00, 0x40, 0x46, 0x00, 0x00, 0x64, 0x04, 0x00, 0x00, 0x63, 0x16, 0x61, 0x06, 0x00, 0x00, 0x40, 0x36, 0x00, 0x00, 0x63, 0x04, 0x00, 0x00, 0x63, 0x66, 0x66, 0x03, 0x00, 0x00, 0x40, 0x36, 0x00, 0x00, 0x61, 0x06, 0x00, 0x30, 0x66, 0x66, 0x14, 0x00, 0x00, 0x00, 0x60, 0x36, 0x00, 0x00, 0x60, 0xAA,
+ // [53] image (157 bytes)
+ 0xFF, 0x41, 0x00, 0x9D, 0x16, 0x00, 0x30, 0x66, 0x14, 0x00, 0x00, 0x00, 0x00, 0x63, 0x06, 0x00, 0x00, 0x40, 0x46, 0x00, 0x63, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x03, 0x00, 0x00, 0x10, 0x66, 0x03, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x66, 0x03, 0x00, 0x00, 0x00, 0x63, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x36, 0x00, 0x00, 0x00, 0x00, 0x40, 0x66, 0x03, 0x00, 0x00, 0x00, 0x00, 0x30, 0x66, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x66, 0x03, 0x00, 0x00, 0x10, 0x64, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x66, 0x66, 0x44, 0x44, 0x66, 0x66, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x66, 0x66, 0x66, 0x66, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x33, 0x34, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA,
+ // [54] image (13 bytes)
+ 0xFF, 0x41, 0x00, 0x0D, 0x6F, 0x00, 0x00, 0x01, 0x88, 0x00, 0x1C, 0x00, 0xAA,
+ // [55] image (245 bytes)
+ 0xFF, 0x41, 0x00, 0xF5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x66, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x66, 0x66, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x66, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x66, 0x01, 0x10, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x36, 0x00, 0x63, 0x46, 0x00, 0x00, 0x00, 0x30, 0x04, 0x00, 0x00, 0x00, 0x00, 0x63, 0x06, 0x30, 0x66, 0x14, 0x30, 0x00, 0x00, 0x63, 0x46, 0x00, 0x00, 0x00, 0x00, 0x63, 0x06, 0x40, 0x36, 0x00, 0x64, 0x00, 0x10, 0x66, 0x66, 0x04, 0x00, 0x00, 0x00, 0x41, 0x03, 0x40, 0x34, 0x40, 0x66, 0x00, 0x00, 0x61, 0x66, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x66, 0x40, 0x03, 0x10, 0x66, 0x66, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x66, 0x66, 0x66, 0x36, 0x30, 0x66, 0x66, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x66, 0x66, 0x64, 0x46, 0x66, 0x66, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x66, 0x66, 0x66, 0x04, 0x61, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x66, 0x66, 0x66, 0x03, 0x10, 0x04, 0x00, 0x00, 0x00, 0xAA,
+ // [56] image (157 bytes)
+ 0xFF, 0x41, 0x00, 0x9D, 0x00, 0x00, 0x00, 0x00, 0x30, 0x66, 0x66, 0x66, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x16, 0x00, 0x64, 0x66, 0x66, 0x66, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x66, 0x31, 0x66, 0x66, 0x66, 0x66, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x66, 0x66, 0x46, 0x63, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x66, 0x66, 0x04, 0x30, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x66, 0x16, 0x00, 0x63, 0x66, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x66, 0x66, 0x01, 0x30, 0x66, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x66, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA,
+ // [57] image (13 bytes)
+ 0xFF, 0x41, 0x00, 0x0D, 0x70, 0x00, 0x00, 0x01, 0x88, 0x00, 0x1C, 0x00, 0xAA,
+ // [58] image (245 bytes)
+ 0xFF, 0x41, 0x00, 0xF5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x44, 0x44, 0x34, 0x00, 0x00, 0x00, 0x00, 0x44, 0x44, 0x44, 0x03, 0x00, 0x10, 0x66, 0x66, 0x66, 0x66, 0x16, 0x00, 0x00, 0x61, 0x66, 0x66, 0x66, 0x66, 0x01, 0x40, 0x66, 0x34, 0x33, 0x63, 0x46, 0x00, 0x00, 0x66, 0x46, 0x33, 0x33, 0x66, 0x04, 0x60, 0x36, 0x00, 0x00, 0x10, 0x66, 0x01, 0x30, 0x66, 0x03, 0x00, 0x00, 0x61, 0x16, 0x61, 0x06, 0x00, 0x00, 0x00, 0x64, 0x04, 0x30, 0x66, 0x00, 0x00, 0x00, 0x60, 0x16, 0x63, 0x06, 0x00, 0x00, 0x00, 0x64, 0x06, 0x60, 0x36, 0x00, 0x00, 0x00, 0x60, 0x36, 0x63, 0x04, 0x00, 0x00, 0x00, 0x61, 0x36, 0x63, 0x16, 0x00, 0x00, 0x00, 0x40, 0x46, 0x64, 0x04, 0x00, 0x00, 0x00, 0x40, 0x66, 0x66, 0x06, 0x00, 0x00, 0x00, 0x40, 0x46, 0x61, 0x46, 0x44, 0x44, 0x44, 0x64, 0x66, 0x66, 0x46, 0x44, 0x44, 0x44, 0x64, 0x36, 0x60, 0x66, 0xAA,
+ // [59] image (157 bytes)
+ 0xFF, 0x41, 0x00, 0x9D, 0x66, 0x66, 0x66, 0x66, 0x04, 0x40, 0x66, 0x66, 0x66, 0x66, 0x66, 0x04, 0x00, 0x33, 0x44, 0x44, 0x44, 0x14, 0x00, 0x00, 0x33, 0x44, 0x44, 0x44, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA,
+ // [60] image (13 bytes)
+ 0xFF, 0x41, 0x00, 0x0D, 0x71, 0x00, 0x00, 0x03, 0x10, 0x00, 0x1C, 0x08, 0xAA,
+ // [61] image (245 bytes)
+ 0xFF, 0x41, 0x00, 0xF5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x63, 0x00, 0x00, 0x00, 0x00, 0x63, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x6C, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x6C, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x6A, 0x61, 0x68, 0x6C, 0x6C, 0x68, 0x61, 0x6A, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x6D, 0x6D, 0x65, 0x60, 0x60, 0x65, 0x6D, 0x6D, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x68, 0x6C, 0x6E, 0x62, 0x00, 0x00, 0x00, 0x00, 0x62, 0x6E, 0x6C, 0x68, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x69, 0x6D, 0x66, 0x60, 0x67, 0x63, 0x00, 0x00, 0x00, 0x00, 0x63, 0x67, 0x60, 0x66, 0x6D, 0x69, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x6E, 0x68, 0x60, 0x00, 0x00, 0x65, 0x67, 0x00, 0x00, 0x00, 0x00, 0x67, 0x65, 0x00, 0x00, 0x60, 0x68, 0x6E, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x6E, 0x64, 0x00, 0x00, 0x00, 0x00, 0x63, 0x6A, 0x00, 0x00, 0x00, 0x00, 0xAA,
+ // [62] image (245 bytes)
+ 0xFF, 0x41, 0x00, 0xF5, 0x6A, 0x63, 0x00, 0x00, 0x00, 0x00, 0x64, 0x6E, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x6E, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x6B, 0x00, 0x00, 0x00, 0x00, 0x6B, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x6E, 0x68, 0x00, 0x00, 0x00, 0x65, 0x6F, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x6C, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x6F, 0x65, 0x00, 0x61, 0x6F, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x6B, 0x00, 0x00, 0x00, 0x00, 0x6B, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x6F, 0x61, 0x69, 0x6F, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0x69, 0x00, 0x00, 0x00, 0x00, 0x69, 0x6A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x6F, 0x69, 0x6B, 0x6F, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x6E, 0x66, 0x00, 0x00, 0x00, 0x00, 0x66, 0x6E, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x6F, 0x6B, 0x65, 0x6F, 0x6A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x6F, 0x62, 0x00, 0x00, 0x00, 0x00, 0x62, 0x6F, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0x6F, 0x65, 0x50, 0x6E, 0x6F, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6C, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6C, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x6F, 0x6E, 0x60, 0x00, 0x65, 0x6F, 0x6C, 0x50, 0x00, 0x00, 0x00, 0x65, 0x6F, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x6F, 0x65, 0x00, 0x00, 0x00, 0x60, 0x6C, 0x6F, 0x65, 0x00, 0x00, 0x00, 0x6B, 0x6F, 0xAA,
+ // [63] image (245 bytes)
+ 0xFF, 0x41, 0x00, 0xF5, 0x69, 0x00, 0x00, 0x61, 0x6E, 0x6D, 0x50, 0x00, 0x00, 0x67, 0x67, 0x00, 0x00, 0x50, 0x6D, 0x6E, 0x61, 0x00, 0x00, 0x69, 0x6F, 0x6B, 0x00, 0x00, 0x00, 0x00, 0x61, 0x6D, 0x6F, 0x68, 0x70, 0x6C, 0x6F, 0x64, 0x00, 0x00, 0x65, 0x6F, 0x6F, 0x65, 0x00, 0x00, 0x64, 0x6F, 0x6C, 0x60, 0x69, 0x6F, 0x6D, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x6E, 0x6F, 0x6E, 0x6F, 0x68, 0x00, 0x00, 0x00, 0x6A, 0x6A, 0x6A, 0x6A, 0x00, 0x00, 0x00, 0x68, 0x6F, 0x6E, 0x6F, 0x6E, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x6C, 0x6F, 0x6F, 0x6C, 0x68, 0x67, 0x71, 0x6F, 0x63, 0x63, 0x6F, 0x61, 0x67, 0x68, 0x6C, 0x6F, 0x6F, 0x6C, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x67, 0x69, 0x68, 0x64, 0x50, 0x67, 0x6B, 0x00, 0x00, 0x6B, 0x67, 0x50, 0x64, 0x68, 0x69, 0x67, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x6D, 0x61, 0x00, 0x00, 0x61, 0x6D, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x65, 0x00, 0x00, 0x00, 0x00, 0x65, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x64, 0xAA,
+ // [64] image (69 bytes)
+ 0xFF, 0x41, 0x00, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA,
+};
diff --git a/FlySight/activelook_config_data.h b/FlySight/activelook_config_data.h
new file mode 100644
index 0000000..a178076
--- /dev/null
+++ b/FlySight/activelook_config_data.h
@@ -0,0 +1,13 @@
+#ifndef ACTIVELOOK_CONFIG_DATA_H
+#define ACTIVELOOK_CONFIG_DATA_H
+
+#include
+
+#define AL_CONFIG_VERSION 4
+#define AL_CONFIG_CMD_COUNT 65
+
+extern const uint16_t al_config_cmd_lengths[AL_CONFIG_CMD_COUNT];
+extern const uint16_t al_config_cmd_offsets[AL_CONFIG_CMD_COUNT];
+extern const uint8_t al_config_cmd_data[];
+
+#endif // ACTIVELOOK_CONFIG_DATA_H
diff --git a/FlySight/activelook_mode0.c b/FlySight/activelook_mode0.c
new file mode 100644
index 0000000..eda7014
--- /dev/null
+++ b/FlySight/activelook_mode0.c
@@ -0,0 +1,746 @@
+/***************************************************************************
+** **
+** FlySight 2 firmware **
+** Copyright 2025 Bionic Avionics Inc. **
+** **
+** This program is free software: you can redistribute it and/or modify **
+** it under the terms of the GNU General Public License as published by **
+** the Free Software Foundation, either version 3 of the License, or **
+** (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, **
+** but WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program. If not, see . **
+** **
+****************************************************************************
+** Contact: Bionic Avionics Inc. **
+** Website: http://flysight.ca/ **
+****************************************************************************/
+
+#include "activelook_mode0.h"
+#include "activelook_client.h" // For FS_ActiveLook_Client_WriteWithoutResp
+#include "config.h" // For FS_Config_Get()
+#include "flight_params.h"
+#include "gnss.h" // For FS_GNSS_GetData()
+#include "nav.h" // For calcDirection, calcDistance, calcRelBearing
+#include "vbat.h"
+#include "app_common.h"
+#include
+#include
+#include
+#include // For atan2, fabs
+
+/* --------------------------------------------------------------------------
+ 1. Unit System Definitions
+ -------------------------------------------------------------------------- */
+
+// Enum to categorize the physical quantity a parameter represents
+typedef enum {
+ FS_UNIT_TYPE_SPEED,
+ FS_UNIT_TYPE_DISTANCE,
+ FS_UNIT_TYPE_ALTITUDE,
+ FS_UNIT_TYPE_ANGLE,
+ FS_UNIT_TYPE_NONE
+} FS_ParamUnitType_t;
+
+// Structure to hold conversion factor and unit suffix
+typedef struct {
+ double multiplier;
+ const char* suffix;
+} UnitConversionInfo_t;
+
+// --- Conversion Constants ---
+#define M_PER_S_TO_KMH 3.6
+#define M_PER_S_TO_MPH 2.23694
+#define METERS_TO_KM 0.001
+#define METERS_TO_MILES 0.000621371
+#define METERS_TO_FEET 3.28084
+
+// Minimum announced altitude (mm)
+#define ALT_MIN_MM (1500L * 1000L)
+
+/* --------------------------------------------------------------------------
+ 2. Data Structures for Each Line
+ -------------------------------------------------------------------------- */
+
+/**
+ * A function pointer type that, given GNSS data, returns a double
+ * representing the value we want displayed. e.g. heading, alt, etc.
+ */
+typedef double (*LineValueFn_t)(const FS_GNSS_Data_t*);
+
+/* Simple "getter" functions for each known line type */
+static double LN_HSpeed(const FS_GNSS_Data_t *d) {
+ double base_hspeed = (double)d->gSpeed / 100.0; // cm/s to m/s
+ double correction_factor = FS_FlightParams_GetSASCorrectionFactor(d->hMSL);
+ return base_hspeed * correction_factor;
+}
+static double LN_VSpeed(const FS_GNSS_Data_t *d) {
+ double base_vspeed = (double)d->velD / 1000.0; // mm/s to m/s
+ double correction_factor = FS_FlightParams_GetSASCorrectionFactor(d->hMSL);
+ return base_vspeed * correction_factor;
+}
+static double LN_GlideRatio(const FS_GNSS_Data_t *d) {
+ if (d->velD != 0) {
+ return (double)(d->gSpeed / 100.0) / (double)(d->velD / 1000.0);
+ }
+ return 0.0; // Or some indicator for undefined glide ratio
+}
+static double LN_InvGlideRatio(const FS_GNSS_Data_t *d) {
+ if (d->gSpeed != 0) {
+ return (double)(d->velD / 1000.0) / (double)(d->gSpeed / 100.0);
+ }
+ return 0.0; // Or some indicator
+}
+static double LN_TotalSpeed(const FS_GNSS_Data_t *d) {
+ double base_speed = (double)d->speed / 100.0; // cm/s to m/s
+ double correction_factor = FS_FlightParams_GetSASCorrectionFactor(d->hMSL);
+ return base_speed * correction_factor;
+}
+static double LN_DirToDest(const FS_GNSS_Data_t *d) {
+ const FS_Config_Data_t *cfg = FS_Config_Get();
+ // Check if destination is set and within reasonable range if needed
+ if ((calcDistance(d->lat, d->lon, cfg->lat, cfg->lon) < cfg->max_dist) || (cfg->max_dist == 0)) {
+ return (double)calcDirection(d->lat, d->lon, cfg->lat, cfg->lon, d->heading);
+ }
+ return 0.0; // Indicate no destination or out of range
+}
+static double LN_DistToDest(const FS_GNSS_Data_t *d) {
+ const FS_Config_Data_t *cfg = FS_Config_Get();
+ return (double)calcDistance(d->lat, d->lon, cfg->lat, cfg->lon); // in meters
+}
+static double LN_DirToBearing(const FS_GNSS_Data_t *d) {
+ const FS_Config_Data_t *cfg = FS_Config_Get();
+ // Assuming bearing is configured
+ return (double)calcRelBearing(cfg->bearing, d->heading / 100000); // heading is 1e-5 deg
+}
+static double LN_DiveAngle(const FS_GNSS_Data_t *d) {
+ if (d->gSpeed > 0) { // Avoid division by zero
+ // velD is positive down, gSpeed is horizontal speed (always positive)
+ // atan2(y, x) -> atan2(vertical_speed, horizontal_speed)
+ // Need to convert cm/s to m/s or be consistent
+ return atan2((double)(d->velD / 1000.0), (double)(d->gSpeed / 100.0)) * (180.0 / M_PI); // Result in degrees
+ }
+ return 0.0;
+}
+static double LN_Altitude(const FS_GNSS_Data_t *d) {
+ const FS_Config_Data_t *cfg = FS_Config_Get();
+ return ((double)d->hMSL - (double)cfg->dz_elev) / 1000.0; // mm to m, relative to DZ elev
+}
+static double LN_Heading(const FS_GNSS_Data_t *d) {
+ return (double)d->heading / 100000.0; // 1e-5 deg to deg
+}
+
+typedef struct {
+ uint8_t typeId;
+ const char *label;
+ FS_ParamUnitType_t unitType;
+ LineValueFn_t fn;
+ uint8_t icon_id;
+} AL_Mode0_LineMap_t;
+
+static const AL_Mode0_LineMap_t s_lineMap[] =
+{
+ { FS_CONFIG_MODE_HORIZONTAL_SPEED, "HSpd", FS_UNIT_TYPE_SPEED, LN_HSpeed, 100 },
+ { FS_CONFIG_MODE_VERTICAL_SPEED, "VSpd", FS_UNIT_TYPE_SPEED, LN_VSpeed, 101 },
+ { FS_CONFIG_MODE_GLIDE_RATIO, "GR", FS_UNIT_TYPE_NONE, LN_GlideRatio, 102 },
+ { FS_CONFIG_MODE_INVERSE_GLIDE_RATIO, "IGR", FS_UNIT_TYPE_NONE, LN_InvGlideRatio, 103 },
+ { FS_CONFIG_MODE_TOTAL_SPEED, "Spd", FS_UNIT_TYPE_SPEED, LN_TotalSpeed, 104 },
+ { FS_CONFIG_MODE_DIRECTION_TO_DESTINATION, "Dir", FS_UNIT_TYPE_ANGLE, LN_DirToDest, 105 },
+ { FS_CONFIG_MODE_DISTANCE_TO_DESTINATION, "Dist", FS_UNIT_TYPE_DISTANCE, LN_DistToDest, 106 },
+ { FS_CONFIG_MODE_DIRECTION_TO_BEARING, "Brg", FS_UNIT_TYPE_ANGLE, LN_DirToBearing, 107 },
+ { FS_CONFIG_MODE_DIVE_ANGLE, "Dive", FS_UNIT_TYPE_ANGLE, LN_DiveAngle, 108 },
+ { FS_CONFIG_MODE_ALTITUDE, "Alt", FS_UNIT_TYPE_ALTITUDE, LN_Altitude, 109 },
+ { 13, "Hdg", FS_UNIT_TYPE_ANGLE, LN_Heading, 110 },
+};
+static const unsigned s_lineMapCount = sizeof(s_lineMap) / sizeof(s_lineMap[0]);
+
+typedef struct {
+ const AL_Mode0_LineMap_t *mapEntry;
+} AL_Mode0_LineSpec_t;
+
+static AL_Mode0_LineSpec_t s_lineSpecs[4];
+
+/**
+ * We'll also keep a "setup" step variable for multi-step layout creation.
+ */
+static int s_step = 0;
+
+/* --------------------------------------------------------------------------
+ 3. Unit Conversion Logic (to be factored out later)
+ -------------------------------------------------------------------------- */
+
+/**
+ * Gets the conversion multiplier and unit suffix string based on parameter type
+ * and the selected unit system.
+ */
+static UnitConversionInfo_t AL_GetUnitConversion(
+ FS_ParamUnitType_t type,
+ FS_Config_UnitSystem_t system) {
+ UnitConversionInfo_t info = {1.0, ""}; // Default: no conversion, no suffix
+
+ switch (type) {
+ case FS_UNIT_TYPE_SPEED: // Base unit: m/s
+ if (system == FS_UNIT_SYSTEM_METRIC) {
+ info.multiplier = M_PER_S_TO_KMH;
+ info.suffix = "km/h";
+ } else { // Imperial
+ info.multiplier = M_PER_S_TO_MPH;
+ info.suffix = "mph";
+ }
+ break;
+ case FS_UNIT_TYPE_DISTANCE: // Base unit: m
+ if (system == FS_UNIT_SYSTEM_METRIC) {
+ info.multiplier = METERS_TO_KM;
+ info.suffix = "km";
+ } else { // Imperial
+ info.multiplier = METERS_TO_MILES;
+ info.suffix = "mi";
+ }
+ break;
+ case FS_UNIT_TYPE_ALTITUDE: // Base unit: m
+ if (system == FS_UNIT_SYSTEM_METRIC) {
+ // info.multiplier = 1.0; // Default is already 1.0
+ info.suffix = "m";
+ } else { // Imperial
+ info.multiplier = METERS_TO_FEET;
+ info.suffix = "ft";
+ }
+ break;
+ case FS_UNIT_TYPE_ANGLE: // Base unit: degrees
+ // No conversion needed between metric/imperial for angles
+ info.suffix = "deg";
+ break;
+ case FS_UNIT_TYPE_NONE: // Unitless
+ // No conversion, no suffix (defaults are correct)
+ break;
+ }
+ return info;
+}
+
+/* --------------------------------------------------------------------------
+ 4. Helpers for Sending Commands / Building Layout
+ -------------------------------------------------------------------------- */
+
+static void AL_SendRaw(const uint8_t *data, uint16_t length)
+{
+ FS_ActiveLook_Client_WriteWithoutResp(data, length);
+}
+
+static tBleStatus AL_SendRawReliable(const uint8_t *data, uint16_t length)
+{
+ return FS_ActiveLook_Client_WriteWithResp(data, length);
+}
+
+/**
+ * Build status layout
+ */
+static uint8_t AL_BuildStatus(uint8_t layoutId,
+ uint8_t *outBuf)
+{
+ uint8_t idx = 0;
+ outBuf[idx++] = 0xFF;
+ outBuf[idx++] = 0x60; // "layoutSave"
+ outBuf[idx++] = 0x00; // 1B length
+ uint8_t lenPos = idx++;
+
+ outBuf[idx++] = layoutId;
+ uint8_t addCmdSizePos = idx++;
+
+ // Status bar — same geometry as when battery icon worked (height=40, y=220)
+ outBuf[idx++] = 0x00; outBuf[idx++] = 0x00; // clip X = 0
+ outBuf[idx++] = 0x00; // clip Y = 0
+ outBuf[idx++] = 0x01; outBuf[idx++] = 0x30; // width = 304
+ outBuf[idx++] = 0x28; // height = 40
+ outBuf[idx++] = 6; // FG = grey 6
+ outBuf[idx++] = 0; // BG = black
+ outBuf[idx++] = 12; // font 12 (B612 22px)
+ outBuf[idx++] = 1; // textValid
+ outBuf[idx++] = 1; outBuf[idx++] = 0x05; // text X = 261
+ outBuf[idx++] = 35; // text Y = 35
+ outBuf[idx++] = 4; // rotation = 4
+ outBuf[idx++] = 0; // opacity = 0 (transparent bg, icons visible behind text)
+
+ uint8_t extra[64];
+ uint8_t e = 0;
+
+ extra[e++] = 0x03; extra[e++] = 6; // color grey 6
+ extra[e++] = 0x00; // image sub-cmd: glasses (112)
+ extra[e++] = 112;
+ extra[e++] = 0x00; extra[e++] = 0xFA; // x = 250
+ extra[e++] = 0x00; extra[e++] = 0x0A; // y = 10
+
+ extra[e++] = 0x00; // image sub-cmd: flysight (113)
+ extra[e++] = 113;
+ extra[e++] = 0x00; extra[e++] = 0x99; // x = 153
+ extra[e++] = 0x00; extra[e++] = 0x0B; // y = 11
+
+ extra[e++] = 0x00; // image sub-cmd: satellite (111)
+ extra[e++] = 111;
+ extra[e++] = 0x00; extra[e++] = 0x36; // x = 54
+ extra[e++] = 0x00; extra[e++] = 0x08; // y = 8
+
+ outBuf[addCmdSizePos] = e;
+ memcpy(&outBuf[idx], extra, e);
+ idx += e;
+
+ outBuf[idx++] = 0xAA;
+ outBuf[lenPos] = idx;
+
+ return idx;
+}
+
+static uint8_t AL_BuildLayout(uint8_t layoutId,
+ const char *headingText,
+ const char *unitsText,
+ uint8_t icon_id,
+ uint8_t cellIndex,
+ uint8_t *outBuf)
+{
+ uint8_t idx = 0;
+ outBuf[idx++] = 0xFF;
+ outBuf[idx++] = 0x60;
+ outBuf[idx++] = 0x00;
+ uint8_t lenPos = idx++;
+
+ outBuf[idx++] = layoutId;
+ uint8_t addCmdSizePos = idx++;
+
+ outBuf[idx++] = 0x00; outBuf[idx++] = 0x00; // clip X = 0
+ outBuf[idx++] = 0x00; // clip Y = 0
+ outBuf[idx++] = 0x00; outBuf[idx++] = 152; // width
+ outBuf[idx++] = 110; // height
+ outBuf[idx++] = 15; // FG = white
+ outBuf[idx++] = 0; // BG = black
+ outBuf[idx++] = 11; // font 11 (B612 Mono 34px)
+ outBuf[idx++] = 1; // textValid
+ outBuf[idx++] = 0; outBuf[idx++] = 140; // text X = 140
+ outBuf[idx++] = 70; // text Y = 70
+ outBuf[idx++] = 4; // rotation = 4 (centered)
+ outBuf[idx++] = 0; // opacity = 0 (transparent)
+
+ uint8_t extra[120];
+ uint8_t e = 0;
+
+ // --- Icon: visually top-left (high x, high y), grey 6 ---
+ if (icon_id > 0) {
+ extra[e++] = 0x03; extra[e++] = 6; // color grey 6
+ extra[e++] = 0x00; // image sub-cmd
+ extra[e++] = icon_id;
+ extra[e++] = 0; extra[e++] = 115; // x = 115
+ extra[e++] = 0; extra[e++] = 75; // y = 75
+ } else {
+ extra[e++] = 0x03; extra[e++] = 6; // color grey 6
+ extra[e++] = 0x04; extra[e++] = 10; // font 10 (B612 18px)
+ extra[e++] = 0x09; // text sub-cmd
+ extra[e++] = 0; extra[e++] = 140;
+ extra[e++] = 0; extra[e++] = 80;
+ size_t lblLen = strlen(headingText);
+ extra[e++] = (uint8_t)lblLen;
+ memcpy(&extra[e], headingText, lblLen);
+ e += lblLen;
+ }
+
+ // --- Units: visually bottom-right, grey 6 ---
+ if (strlen(unitsText) > 0) {
+ char unitBuf[8];
+ snprintf(unitBuf, sizeof(unitBuf), "%4s", unitsText);
+ size_t untLen = strlen(unitBuf);
+ extra[e++] = 0x03; extra[e++] = 6; // color grey 6
+ extra[e++] = 0x04; extra[e++] = 10; // font 10 (B612 18px)
+ extra[e++] = 0x09; // text sub-cmd
+ extra[e++] = 0; extra[e++] = 60; // x = 60
+ extra[e++] = 0; extra[e++] = 28; // y = 28
+ extra[e++] = (uint8_t)untLen;
+ memcpy(&extra[e], unitBuf, untLen);
+ e += untLen;
+ }
+
+ // --- Divider lines (barely visible) ---
+ extra[e++] = 0x03; extra[e++] = 1; // color grey 1
+
+ // Cells 0,2 (vis left column): vertical divider at center of screen
+ // x=0 appeared at far right of screen, so center must be at high x
+ // Cell width = 152, so x=151 = the left visual edge of the left cell
+ if (cellIndex == 0 || cellIndex == 2) {
+ extra[e++] = 0x05; // line sub-cmd
+ extra[e++] = 0; extra[e++] = 151; // x0 = 151
+ extra[e++] = 0; extra[e++] = 0; // y0 = 0
+ extra[e++] = 0; extra[e++] = 151; // x1 = 151
+ extra[e++] = 0; extra[e++] = 110; // y1 = 110
+ }
+
+ // Cells 0,1 (vis top row): horizontal line at visual bottom edge = row divider
+ // Layout-internal: y=0 = vis bottom, y=115 = vis top
+ // We want the line at the cell's vis-bottom edge = y=0
+ if (cellIndex == 0 || cellIndex == 1) {
+ extra[e++] = 0x05; // line sub-cmd
+ extra[e++] = 0; extra[e++] = 0; // x0 = 0
+ extra[e++] = 0; extra[e++] = 0; // y0 = 0 (vis bottom edge)
+ extra[e++] = 0; extra[e++] = 152; // x1 = 152
+ extra[e++] = 0; extra[e++] = 0; // y1 = 0
+ }
+
+ outBuf[addCmdSizePos] = e;
+ memcpy(&outBuf[idx], extra, e);
+ idx += e;
+
+ outBuf[idx++] = 0xAA;
+ outBuf[lenPos] = idx;
+ return idx;
+}
+
+/**
+ * Build page: 2x2 grid of cells + status bar on top
+ */
+static uint8_t AL_BuildPage(uint8_t pageId, uint8_t *outBuf)
+{
+ const FS_Config_Data_t *cfg = FS_Config_Get();
+
+ // Page positions for each cell: [top-left, top-right, bottom-left, bottom-right]
+ // Each entry: x_hi, x_lo, y
+ static const uint8_t cellPos[][3] = {
+ { 0x00, 0x00, 110 }, // cell 0: x=0, y=110 (upper-left)
+ { 0x00, 152, 110 }, // cell 1: x=152, y=110 (upper-right)
+ { 0x00, 0x00, 0 }, // cell 2: x=0, y=0 (lower-left)
+ { 0x00, 152, 0 }, // cell 3: x=152, y=0 (lower-right)
+ };
+
+ uint8_t idx = 0;
+ outBuf[idx++] = 0xFF;
+ outBuf[idx++] = 0x80;
+ outBuf[idx++] = 0x00;
+ uint8_t lenPos = idx++;
+
+ outBuf[idx++] = pageId;
+
+ // Status bar (layout 14) above grid
+ outBuf[idx++] = 14;
+ outBuf[idx++] = 0x00;
+ outBuf[idx++] = 0x00;
+ outBuf[idx++] = 220;
+
+ // Cell layouts
+ for (int i = 0; i < cfg->num_al_lines && i < 4; i++)
+ {
+ outBuf[idx++] = 10 + i;
+ outBuf[idx++] = cellPos[i][0];
+ outBuf[idx++] = cellPos[i][1];
+ outBuf[idx++] = cellPos[i][2];
+ }
+
+ outBuf[idx++] = 0xAA;
+ outBuf[lenPos] = idx;
+ return idx;
+}
+
+/**
+ * Build a single pageUpdate command with 4 lines separated by '\0'.
+ */
+static uint8_t AL_BuildPageUpdate(uint8_t pageId,
+ const char *heading,
+ const char *line1,
+ const char *line2,
+ const char *line3,
+ const char *line4,
+ uint8_t *outBuf)
+{
+ uint8_t idx = 0;
+ outBuf[idx++] = 0xFF;
+ outBuf[idx++] = 0x86; // "pageClearAndDisplay"
+ outBuf[idx++] = 0x00;
+ uint8_t lenPos = idx++;
+
+ outBuf[idx++] = pageId;
+
+ // heading
+ size_t lh = strlen(heading);
+ memcpy(&outBuf[idx], heading, lh);
+ idx += lh;
+ outBuf[idx++] = 0;
+
+ // line1
+ size_t l1 = strlen(line1);
+ memcpy(&outBuf[idx], line1, l1);
+ idx += l1;
+ outBuf[idx++] = 0;
+
+ // line2
+ size_t l2 = strlen(line2);
+ memcpy(&outBuf[idx], line2, l2);
+ idx += l2;
+ outBuf[idx++] = 0;
+
+ // line3
+ size_t l3 = strlen(line3);
+ memcpy(&outBuf[idx], line3, l3);
+ idx += l3;
+ outBuf[idx++] = 0;
+
+ // line4
+ size_t l4 = strlen(line4);
+ memcpy(&outBuf[idx], line4, l4);
+ idx += l4;
+ outBuf[idx++] = 0;
+
+ outBuf[idx++] = 0xAA;
+ outBuf[lenPos] = idx;
+ return idx;
+}
+
+/* --------------------------------------------------------------------------
+ 3. Mode0 Implementation
+ -------------------------------------------------------------------------- */
+
+// Helper to find the LineMap entry by typeId
+static const AL_Mode0_LineMap_t* FindLineMapEntry(uint8_t typeId) {
+ for (unsigned k = 0; k < s_lineMapCount; k++) {
+ if (s_lineMap[k].typeId == typeId) {
+ return &s_lineMap[k];
+ }
+ }
+ return NULL;
+}
+
+
+/**
+ * FS_ActiveLook_Mode0_Init()
+ *
+ * - Reset our s_step to 0
+ * - Read the config => al_line_1..4
+ * - For each of the 4 lines, find a matching entry in s_lineMap
+ * (by typeId) and store it in s_lineSpecs[i].
+ */
+void FS_ActiveLook_Mode0_Init(void)
+{
+ // Read config
+ const FS_Config_Data_t *cfg = FS_Config_Get();
+
+ s_step = 0;
+
+ for (int i = 0; i < cfg->num_al_lines && i < 4; i++)
+ {
+ s_lineSpecs[i].mapEntry = FindLineMapEntry(cfg->al_lines[i].mode);
+ }
+}
+
+/**
+ * FS_ActiveLook_Mode0_Setup()
+ *
+ * Multi-step routine. In each step (0-3), builds a layout (#10-13).
+ * It now dynamically determines the unit suffix based on the line's parameter type
+ * and the chosen unit system.
+ * Step 4 builds the page definition.
+ */
+FS_ActiveLook_SetupStatus_t FS_ActiveLook_Mode0_Setup(void)
+{
+ // Read config
+ const FS_Config_Data_t *cfg = FS_Config_Get();
+
+ uint8_t buf[128];
+ uint8_t length;
+ FS_ActiveLook_SetupStatus_t status = FS_AL_SETUP_IN_PROGRESS;
+
+ if (s_step < cfg->num_al_lines) {
+ // Steps 0..N-1: Build cell layouts
+ int lineIndex = s_step;
+ uint8_t layoutId = 10 + lineIndex;
+ const AL_Mode0_LineMap_t* mapEntry = s_lineSpecs[lineIndex].mapEntry;
+ const char* label = mapEntry ? mapEntry->label : "?";
+ const char* unitSuffix = "";
+
+ if (mapEntry) {
+ UnitConversionInfo_t unitInfo = AL_GetUnitConversion(
+ mapEntry->unitType,
+ cfg->al_lines[lineIndex].units);
+ unitSuffix = unitInfo.suffix;
+ }
+
+ uint8_t icon = mapEntry ? mapEntry->icon_id : 0;
+ length = AL_BuildLayout(layoutId, label, unitSuffix, icon, lineIndex, buf);
+ if (length > 0 && AL_SendRawReliable(buf, length) == BLE_STATUS_SUCCESS) {
+ s_step++;
+ }
+ } else if (s_step == cfg->num_al_lines) {
+ length = AL_BuildStatus(14, buf);
+ if (length > 0 && AL_SendRawReliable(buf, length) == BLE_STATUS_SUCCESS) {
+ s_step++;
+ }
+ } else if (s_step == cfg->num_al_lines + 1) {
+ length = AL_BuildPage(10, buf);
+ if (length > 0 && AL_SendRawReliable(buf, length) == BLE_STATUS_SUCCESS) {
+ s_step++;
+ status = FS_AL_SETUP_DONE;
+ }
+ } else {
+ status = FS_AL_SETUP_DONE;
+ }
+
+ return status;
+}
+
+/**
+ * FS_ActiveLook_Mode0_Update()
+ *
+ * Called periodically to update the display.
+ * - Gets current GNSS data.
+ * - For each line:
+ * - Finds the corresponding map entry using stored typeId.
+ * - Calls the base value function (fn).
+ * - Gets the correct unit conversion info (multiplier).
+ * - Calculates the display value by applying the multiplier.
+ * - Formats the display value into a string.
+ * - Builds and sends the "pageClearAndDisplay" command with the 4 formatted value strings.
+ */
+void FS_ActiveLook_Mode0_Update(void)
+{
+ // Read config
+ const FS_Config_Data_t *cfg = FS_Config_Get();
+
+ const FS_GNSS_Data_t *gnss = FS_GNSS_GetData();
+ const FS_VBAT_Data_t *vbat = FS_VBAT_GetData();
+ uint8_t alBatt = FS_ActiveLook_Client_GetBatteryLevel();
+
+ char lineValueStr[4][16]; // Buffer for formatted value strings
+
+ // Calculate altitude above ground level once
+ double altitude_agl_mm = (double)gnss->hMSL - (double)cfg->dz_elev;
+
+ // For each line, calculate, convert, and format the value
+ for (int i = 0; i < cfg->num_al_lines; i++)
+ {
+ double baseVal = 0.0;
+ double displayVal = 0.0;
+ bool display_invalid = false; // Flag to indicate if "----" should be shown
+
+ const AL_Mode0_LineMap_t* mapEntry = s_lineSpecs[i].mapEntry;
+
+ if (mapEntry && mapEntry->fn) {
+ // 1. If nav is disabled but line is a navigation mode, mark it invalid
+ // so we display "----"
+ if (!cfg->enable_nav &&
+ (mapEntry->typeId == FS_CONFIG_MODE_DIRECTION_TO_DESTINATION ||
+ mapEntry->typeId == FS_CONFIG_MODE_DISTANCE_TO_DESTINATION ||
+ mapEntry->typeId == FS_CONFIG_MODE_DIRECTION_TO_BEARING ||
+ mapEntry->typeId == FS_CONFIG_MODE_LEFT_RIGHT))
+ {
+ display_invalid = true;
+ }
+
+ // 2. Get base value if not invalid
+ if (!display_invalid) {
+ baseVal = mapEntry->fn(gnss);
+ }
+
+ // 3. Check validity limits
+ if (!display_invalid && gnss->gpsFix != 3) {
+ display_invalid = true;
+ }
+
+ // Check ALT_MIN for Altitude display
+ if (!display_invalid && mapEntry->typeId == FS_CONFIG_MODE_ALTITUDE) {
+ if (altitude_agl_mm < ALT_MIN_MM) {
+ display_invalid = true;
+ }
+ }
+
+ // Check end_nav for Navigation parameters
+ if (!display_invalid &&
+ (mapEntry->typeId == FS_CONFIG_MODE_DIRECTION_TO_DESTINATION ||
+ mapEntry->typeId == FS_CONFIG_MODE_DISTANCE_TO_DESTINATION ||
+ mapEntry->typeId == FS_CONFIG_MODE_DIRECTION_TO_BEARING))
+ {
+ // end_nav is in mm in config struct, compare directly
+ if ((cfg->end_nav != 0) && (altitude_agl_mm < cfg->end_nav)) {
+ display_invalid = true;
+ }
+ }
+
+ // Check max_dist for Destination parameters (only if not already invalid)
+ if (!display_invalid &&
+ (mapEntry->typeId == FS_CONFIG_MODE_DIRECTION_TO_DESTINATION ||
+ mapEntry->typeId == FS_CONFIG_MODE_DISTANCE_TO_DESTINATION))
+ {
+ // baseVal for DistToDest is already the distance in meters
+ // For DirToDest, we need to recalculate distance if baseVal isn't distance
+ double distance_m;
+ if (mapEntry->typeId == FS_CONFIG_MODE_DISTANCE_TO_DESTINATION) {
+ distance_m = baseVal;
+ } else {
+ // Recalculate distance if the primary value isn't distance
+ distance_m = (double)calcDistance(gnss->lat, gnss->lon, cfg->lat, cfg->lon);
+ }
+
+ // max_dist is in meters in config struct
+ if ((cfg->max_dist != 0) && (distance_m > cfg->max_dist)) {
+ display_invalid = true;
+ }
+ }
+
+ if (!display_invalid) {
+ // 4. Get conversion info based on type and chosen system
+ UnitConversionInfo_t unitInfo = AL_GetUnitConversion(
+ mapEntry->unitType,
+ cfg->al_lines[i].units);
+
+ // 5. Calculate display value
+ displayVal = baseVal * unitInfo.multiplier;
+
+ // 6. Format the display value
+ int dec = (int)(cfg->al_lines[i].decimals);
+ if (dec > 1) dec = 1;
+ // Drop decimal for large values to fit cell
+ if (dec > 0 && (displayVal >= 10000.0 || displayVal <= -1000.0))
+ dec = 0;
+ int width = (dec > 0) ? 6 : 5;
+ snprintf(
+ lineValueStr[i],
+ sizeof(lineValueStr[i]),
+ "%*.*f",
+ width, dec,
+ displayVal);
+ }
+ } else {
+ // Handle case where line type or function is invalid
+ display_invalid = true;
+ }
+
+ // If any limit check failed, display placeholder
+ if (display_invalid) {
+ int dec2 = (int)(cfg->al_lines[i].decimals);
+ if (dec2 > 1) dec2 = 1;
+ int width2 = (dec2 > 0) ? 6 : 5;
+ snprintf(lineValueStr[i], sizeof(lineValueStr[i]), "%*s", width2, "--.-");
+ }
+
+ }
+
+ // Build header text
+ char battLevels[30];
+
+ char alBattStr[5];
+ if (alBatt == 255)
+ snprintf(alBattStr, sizeof(alBattStr), "??");
+ else
+ snprintf(alBattStr, sizeof(alBattStr), "%d", alBatt);
+
+ int fs_pct = (100 * (vbat->voltage - 3300)) / (4200 - 3200);
+ fs_pct = MAX(0, MIN(100, fs_pct));
+
+ sprintf(battLevels, " %s%% %d%% %d", alBattStr, fs_pct, gnss->numSV);
+
+ // Build the final packet with the 4 lines
+ uint8_t buf[128];
+ uint8_t length = AL_BuildPageUpdate(10,
+ battLevels,
+ lineValueStr[0],
+ lineValueStr[1],
+ lineValueStr[2],
+ lineValueStr[3],
+ buf);
+ AL_SendRaw(buf, length);
+}
diff --git a/FlySight/activelook_mode0.h b/FlySight/activelook_mode0.h
new file mode 100644
index 0000000..a499555
--- /dev/null
+++ b/FlySight/activelook_mode0.h
@@ -0,0 +1,46 @@
+/***************************************************************************
+** **
+** FlySight 2 firmware **
+** Copyright 2025 Bionic Avionics Inc. **
+** **
+** This program is free software: you can redistribute it and/or modify **
+** it under the terms of the GNU General Public License as published by **
+** the Free Software Foundation, either version 3 of the License, or **
+** (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, **
+** but WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program. If not, see . **
+** **
+****************************************************************************
+** Contact: Bionic Avionics Inc. **
+** Website: http://flysight.ca/ **
+****************************************************************************/
+
+#ifndef ACTIVELOOK_MODE0_H
+#define ACTIVELOOK_MODE0_H
+
+#include "activelook.h"
+
+/**
+ * Called once when the mode is selected, to reset and load config data.
+ */
+void FS_ActiveLook_Mode0_Init(void);
+
+/**
+ * Called repeatedly to perform multi-step layout setup. Each call
+ * sends exactly one BLE command. Returns FS_AL_SETUP_DONE when complete.
+ */
+FS_ActiveLook_SetupStatus_t FS_ActiveLook_Mode0_Setup(void);
+
+/**
+ * Called whenever we want to redraw or update the data on the display.
+ * Uses the config-based line definitions from FS_ActiveLook_Mode0_Init().
+ */
+void FS_ActiveLook_Mode0_Update(void);
+
+#endif /* ACTIVELOOK_MODE0_H */
diff --git a/FlySight/audio_control.c b/FlySight/audio_control.c
index efdc3f8..42f2be5 100644
--- a/FlySight/audio_control.c
+++ b/FlySight/audio_control.c
@@ -223,6 +223,18 @@ static void getValues(
}
}
+ // If navigation is disabled and the requested mode is a
+ // navigation-related feature, mark it as invalid
+ if (!config->enable_nav &&
+ (mode == FS_CONFIG_MODE_DIRECTION_TO_DESTINATION ||
+ mode == FS_CONFIG_MODE_DISTANCE_TO_DESTINATION ||
+ mode == FS_CONFIG_MODE_DIRECTION_TO_BEARING ||
+ mode == FS_CONFIG_MODE_LEFT_RIGHT))
+ {
+ *val = INVALID_VALUE;
+ return;
+ }
+
switch (mode)
{
case FS_CONFIG_MODE_HORIZONTAL_SPEED:
@@ -442,6 +454,17 @@ static void speakValue(
}
}
+ // If navigation is disabled and the requested mode is a
+ // navigation-related feature, mark it as invalid
+ if (!config->enable_nav &&
+ (config->speech[cur_speech].mode == FS_CONFIG_MODE_DIRECTION_TO_DESTINATION ||
+ config->speech[cur_speech].mode == FS_CONFIG_MODE_DISTANCE_TO_DESTINATION ||
+ config->speech[cur_speech].mode == FS_CONFIG_MODE_DIRECTION_TO_BEARING ||
+ config->speech[cur_speech].mode == FS_CONFIG_MODE_LEFT_RIGHT))
+ {
+ return;
+ }
+
switch (config->speech[cur_speech].units)
{
case FS_CONFIG_UNITS_KMH:
diff --git a/FlySight/config.c b/FlySight/config.c
index cdccfa5..4fbb741 100644
--- a/FlySight/config.c
+++ b/FlySight/config.c
@@ -25,10 +25,12 @@
#include "app_common.h"
#include "config.h"
#include "ff.h"
+#include "state.h"
-#define CONFIG_FIRST_ALARM 0x01
-#define CONFIG_FIRST_WINDOW 0x02
-#define CONFIG_FIRST_SPEECH 0x04
+#define CONFIG_FIRST_ALARM 0x01
+#define CONFIG_FIRST_WINDOW 0x02
+#define CONFIG_FIRST_SPEECH 0x04
+#define CONFIG_FIRST_AL_LINE 0x08
static FS_Config_Data_t config;
static FIL configFile;
@@ -57,6 +59,9 @@ static const char defaultConfig[] =
" ; 2 = Glide ratio\n"
" ; 3 = Inverse glide ratio\n"
" ; 4 = Total speed\n"
+ " ; 5 = Direction to destination\n"
+ " ; 6 = Distance to destination\n"
+ " ; 7 = Direction to bearing\n"
" ; 11 = Dive angle\n"
"Min: 0 ; Lowest pitch value\n"
" ; cm/s in Mode 0, 1, or 4\n"
@@ -112,6 +117,9 @@ static const char defaultConfig[] =
" ; 2 = Glide ratio\n"
" ; 3 = Inverse glide ratio\n"
" ; 4 = Total speed\n"
+ " ; 5 = Direction to destination\n"
+ " ; 6 = Distance to destination\n"
+ " ; 7 = Direction to bearing\n"
" ; 11 = Dive angle\n"
" ; 12 = Altitude above DZ_Elev\n"
"Sp_Units: 1 ; Speech units\n"
@@ -200,7 +208,82 @@ static const char defaultConfig[] =
"; alarms will be audible.\n"
"\n"
"Win_Top: 0 ; Silence window top (m)\n"
- "Win_Bottom: 0 ; Silence window bottom (m)\n";
+ "Win_Bottom: 0 ; Silence window bottom (m)\n"
+ "\n"
+ "; Navigation settings\n"
+ "\n"
+ "; Activating navigation features requires the UNIQUE DEVICE ID\n"
+ "; of this specific FlySight device to be entered below. This is\n"
+ "; a deliberate step to ensure users understand the potential\n"
+ "; implications of using navigation features.\n"
+ "\n"
+ "; BEFORE ENABLING THIS FEATURE, YOU MUST ACKNOWLEDGE THE\n"
+ "; FOLLOWING:\n"
+ "\n"
+ "; 1. NAVIGATION DATA IS FOR SUPPLEMENTARY INFORMATION ONLY. It\n"
+ "; should NEVER be your primary source of navigation or\n"
+ "; situational awareness.\n"
+ "\n"
+ "; 2. DO NOT USE THIS FEATURE FOR SAFETY-CRITICAL DECISIONS.\n"
+ "; Always prioritize visual confirmation, established safety\n"
+ "; procedures, and communication.\n"
+ "\n"
+ "; 3. Be aware that other users may independently configure\n"
+ "; similar navigation targets, potentially leading to\n"
+ "; convergent flight paths. This system provides NO collision\n"
+ "; avoidance.\n"
+ "\n"
+ "; 4. You are solely responsible for your flight path and safety.\n"
+ "\n"
+ "; ENTER YOUR FLYSIGHT'S UNIQUE DEVICE ID BELOW TO ACTIVATE\n"
+ "; NAVIGATION FEATURES:\n"
+ "\n"
+ "Device_ID: 000000000000000000000000\n"
+ "\n"
+ "Lat: 510442700 ; Latitude (deg * 10,000,000)\n"
+ "Lon: -1140620190 ; Longitude (deg * 10,000,000)\n"
+ "Bearing: 0 ; Bearing (deg)\n"
+ "End_Nav: 1500 ; Minimum altitude (m)\n"
+ "Max_Dist: 10000 ; Maximum distance (m)\n"
+ "Min_Angle: 5 ; Minimum angle for direction (deg)\n"
+ "\n"
+ "; ActiveLook interface\n"
+ "\n"
+ "AL_ID: 000000 ; ActiveLook device ID\n"
+ "AL_Mode: 1 ; ActiveLook mode\n"
+ " 0 = Not active\n"
+ " 1 = Default mode\n"
+ "AL_Rate: 1000 ; ActiveLook rate (ms)\n"
+ "\n"
+ "AL_Line: 0 ; ActiveLook line value\n"
+ " ; 0 = Horizontal speed\n"
+ " ; 1 = Vertical speed\n"
+ " ; 2 = Glide ratio\n"
+ " ; 3 = Inverse glide ratio\n"
+ " ; 4 = Total speed\n"
+ " ; 5 = Direction to destination\n"
+ " ; 6 = Distance to destination\n"
+ " ; 7 = Direction to bearing\n"
+ " ; 11 = Dive angle\n"
+ " ; 12 = Altitude above DZ_Elev\n"
+ " ; 13 = Course\n"
+ "AL_Units: 0 ; ActiveLook units\n"
+ " ; 0 = km/h or m\n"
+ " ; 1 = mph or feet\n"
+ "AL_Dec: 1 ; ActiveLook precision\n"
+ " ; Decimal places\n"
+ "\n"
+ "AL_Line: 1 ; Vertical speed\n"
+ "AL_Units: 0 ; km/h\n"
+ "AL_Dec: 1 ; Decimal places\n"
+ "\n"
+ "AL_Line: 2 ; Glide ratio\n"
+ "AL_Units: 0 ; No units\n"
+ "AL_Dec: 2 ; Decimal places\n"
+ "\n"
+ "AL_Line: 13 ; Course\n"
+ "AL_Units: 0 ; Degrees\n"
+ "AL_Dec: 0 ; Decimal places\n";
void FS_Config_Init(void)
{
@@ -222,7 +305,6 @@ void FS_Config_Init(void)
config.sp_rate = 0;
config.sp_volume = 0;
-
config.num_speech = 0;
config.threshold = 1000;
@@ -272,6 +354,24 @@ void FS_Config_Init(void)
config.end_nav = 0;
config.max_dist = 10000;
config.min_angle = 5;
+
+ *(config.al_id) = '\0';
+ config.al_mode = 1;
+ config.al_rate = 1000;
+ config.num_al_lines = 0;
+
+ // IMPORTANT: Navigation disabled by default
+ config.enable_nav = 0;
+}
+
+static void FS_Config_WriteHex_32(char *result, const uint32_t *data, uint32_t count)
+{
+ uint32_t i;
+
+ for (i = 0; i < count; ++i)
+ {
+ sprintf(result + 8 * i, "%08lx", data[i]);
+ }
}
FS_Config_Result_t FS_Config_Read(const char *filename)
@@ -362,6 +462,9 @@ FS_Config_Result_t FS_Config_Read(const char *filename)
HANDLE_VALUE("Max_Dist", config.max_dist, val, val >= 0 && val <= 10000);
HANDLE_VALUE("Min_Angle", config.min_angle, val, val >= 0 && val <= 360);
+ HANDLE_VALUE("AL_Mode", config.al_mode, val, val >= 0 && val <= 1);
+ HANDLE_VALUE("AL_Rate", config.al_rate, val, val >= 100);
+
#undef HANDLE_VALUE
if (!strcmp(name, "Init_File"))
@@ -431,6 +534,49 @@ FS_Config_Result_t FS_Config_Read(const char *filename)
{
config.speech[config.num_speech - 1].decimals = val;
}
+
+ if (!strcmp(name, "AL_Line") && config.num_al_lines < FS_CONFIG_MAX_AL_LINES)
+ {
+ if (!(flags & CONFIG_FIRST_AL_LINE))
+ {
+ config.num_al_lines = 0;
+ flags |= CONFIG_FIRST_AL_LINE;
+ }
+
+ ++config.num_al_lines;
+ config.al_lines[config.num_al_lines - 1].mode = val;
+ config.al_lines[config.num_al_lines - 1].units = FS_UNIT_SYSTEM_IMPERIAL;
+ config.al_lines[config.num_al_lines - 1].decimals = 1;
+ }
+ if (!strcmp(name, "AL_Units") && config.num_al_lines <= FS_CONFIG_MAX_AL_LINES)
+ {
+ config.al_lines[config.num_al_lines - 1].units = val;
+ }
+ if (!strcmp(name, "AL_Dec") && config.num_al_lines <= FS_CONFIG_MAX_AL_LINES)
+ {
+ config.al_lines[config.num_al_lines - 1].decimals = val;
+ }
+
+ if (!strcmp(name, "AL_ID"))
+ {
+ result[6] = '\0';
+ strncpy(config.al_id, result, sizeof(config.al_id));
+ }
+
+ // Compare config Device_ID with actual hardware device ID.
+ // If it matches, enable navigation.
+ if (!strcmp(name, "Device_ID"))
+ {
+ // Build 24-hex-digit string from the hardware ID
+ const uint32_t *hwId = FS_State_Get()->device_id; // 3 x 32-bit
+ char hwIdString[25]; // 24 hex digits + null
+ FS_Config_WriteHex_32(hwIdString, hwId, 3);
+
+ if (!strcmp(result, hwIdString))
+ {
+ config.enable_nav = 1;
+ }
+ }
}
f_close(&configFile);
diff --git a/FlySight/config.h b/FlySight/config.h
index 939c7b0..3b76de5 100644
--- a/FlySight/config.h
+++ b/FlySight/config.h
@@ -29,6 +29,7 @@
#define FS_CONFIG_MAX_ALARMS 20
#define FS_CONFIG_MAX_WINDOWS 2
#define FS_CONFIG_MAX_SPEECH 3
+#define FS_CONFIG_MAX_AL_LINES 4
#define FS_CONFIG_MODEL_PORTABLE 0
#define FS_CONFIG_MODEL_STATIONARY 2
@@ -61,6 +62,11 @@
#define FS_CONFIG_UNITS_FEET 1
#define FS_CONFIG_UNITS_NM 2
+typedef enum {
+ FS_UNIT_SYSTEM_METRIC = 0,
+ FS_UNIT_SYSTEM_IMPERIAL = 1
+} FS_Config_UnitSystem_t;
+
#define FS_CONFIG_RATE_ONE_HZ 650
#define FS_CONFIG_RATE_FLATLINE UINT16_MAX
@@ -84,6 +90,13 @@ typedef struct
int32_t decimals;
} FS_Config_Speech_t;
+typedef struct
+{
+ uint8_t mode;
+ FS_Config_UnitSystem_t units;
+ int32_t decimals;
+} FS_Config_AL_Line_t;
+
typedef struct
{
uint8_t model;
@@ -157,6 +170,16 @@ typedef struct
uint16_t end_nav;
uint16_t max_dist;
uint16_t min_angle;
+
+ char al_id[6];
+ uint8_t al_mode;
+ uint32_t al_rate;
+
+ FS_Config_AL_Line_t al_lines[FS_CONFIG_MAX_AL_LINES];
+ uint8_t num_al_lines;
+
+ // Flag to control whether navigation modes are allowed
+ uint8_t enable_nav;
} FS_Config_Data_t;
typedef enum {
diff --git a/FlySight/crs.c b/FlySight/crs.c
index 5c54c64..23c26bb 100644
--- a/FlySight/crs.c
+++ b/FlySight/crs.c
@@ -21,6 +21,7 @@
** Website: http://flysight.ca/ **
****************************************************************************/
+#include
#include "main.h"
#include "app_common.h"
#include "crs.h"
@@ -89,7 +90,6 @@ static FS_CRS_StateFunc_t *const state_table[FS_CRS_STATE_COUNT] =
static FS_CRS_State_t state = FS_CRS_STATE_IDLE;
static FIL file;
-static uint8_t buffer[FRAME_LENGTH + 1];
static DIR dir;
static uint32_t read_offset;
@@ -103,16 +103,21 @@ static uint32_t last_packet;
static uint8_t ack_timer_id;
static volatile uint8_t timeout_flag;
+static void FS_CRS_TxCallback()
+{
+ UTIL_SEQ_SetTask(1<length = length + 1;
- *(tx_packet->data) = command;
- memcpy(tx_packet->data + 1, payload, length);
- Custom_CRS_SendNextTxPacket();
+ tx_buffer[0] = command;
+ memcpy(&tx_buffer[1], payload, length);
+ BLE_TX_Queue_SendNextTxPacket(CUSTOM_STM_FT_PACKET_OUT, length + 1,
+ &SizeFt_Packet_Out, FS_CRS_TxCallback);
}
}
@@ -344,6 +349,7 @@ static FS_CRS_State_t FS_CRS_State_Dir(void)
{
FS_CRS_State_t next_state = FS_CRS_STATE_DIR;
Custom_CRS_Packet_t *packet;
+ uint8_t *tx_buffer;
FILINFO fno;
if (!Custom_APP_IsConnected())
@@ -365,19 +371,22 @@ static FS_CRS_State_t FS_CRS_State_Dir(void)
}
}
- while ((next_state == FS_CRS_STATE_DIR) && (packet = Custom_CRS_GetNextTxPacket()))
+ while ((next_state == FS_CRS_STATE_DIR) &&
+ (tx_buffer = BLE_TX_Queue_GetNextTxPacket()))
{
// Read a directory item
if (f_readdir(&dir, &fno) == FR_OK)
{
- buffer[0] = ((next_packet++) & 0xff);
- memcpy(buffer + 1, &fno.fsize, sizeof(fno.fsize));
- memcpy(buffer + 5, &fno.fdate, sizeof(fno.fdate));
- memcpy(buffer + 7, &fno.ftime, sizeof(fno.ftime));
- buffer[9] = fno.fattrib;
- memcpy(buffer + 10, fno.fname, sizeof(fno.fname));
+ tx_buffer[0] = FS_CRS_COMMAND_FILE_INFO;
+ tx_buffer[1] = ((next_packet++) & 0xff);
+ memcpy(&tx_buffer[2], &fno.fsize, sizeof(fno.fsize));
+ memcpy(&tx_buffer[6], &fno.fdate, sizeof(fno.fdate));
+ memcpy(&tx_buffer[8], &fno.ftime, sizeof(fno.ftime));
+ tx_buffer[10] = fno.fattrib;
+ memcpy(&tx_buffer[11], fno.fname, sizeof(fno.fname));
- FS_CRS_SendPacket(FS_CRS_COMMAND_FILE_INFO, buffer, 23);
+ BLE_TX_Queue_SendNextTxPacket(CUSTOM_STM_FT_PACKET_OUT, 24,
+ &SizeFt_Packet_Out, FS_CRS_TxCallback);
if (fno.fname[0] == 0)
{
@@ -406,6 +415,7 @@ static FS_CRS_State_t FS_CRS_State_Read(void)
{
FS_CRS_State_t next_state = FS_CRS_STATE_READ;
Custom_CRS_Packet_t *packet;
+ uint8_t *tx_buffer;
UINT br;
if (!Custom_APP_IsConnected())
@@ -452,19 +462,21 @@ static FS_CRS_State_t FS_CRS_State_Read(void)
}
while ((next_state == FS_CRS_STATE_READ) &&
- (packet = Custom_CRS_GetNextTxPacket()) &&
+ (tx_buffer = BLE_TX_Queue_GetNextTxPacket()) &&
(next_packet < next_ack + FS_CRS_WINDOW_LENGTH) &&
(next_packet < last_packet))
{
- buffer[0] = (next_packet & 0xff);
+ tx_buffer[0] = FS_CRS_COMMAND_FILE_DATA;
+ tx_buffer[1] = (next_packet & 0xff);
if (f_eof(&file))
{
// Send empty buffer to signal end of file
- FS_CRS_SendPacket(FS_CRS_COMMAND_FILE_DATA, buffer, 1);
+ BLE_TX_Queue_SendNextTxPacket(CUSTOM_STM_FT_PACKET_OUT, 2,
+ &SizeFt_Packet_Out, FS_CRS_TxCallback);
last_packet = ++next_packet;
}
- else if (f_read(&file, &buffer[1], FRAME_LENGTH, &br) == FR_OK)
+ else if (f_read(&file, &tx_buffer[2], FRAME_LENGTH, &br) == FR_OK)
{
if (read_stride > FRAME_LENGTH)
{
@@ -473,7 +485,8 @@ static FS_CRS_State_t FS_CRS_State_Read(void)
}
// Send data
- FS_CRS_SendPacket(FS_CRS_COMMAND_FILE_DATA, buffer, br + 1);
+ BLE_TX_Queue_SendNextTxPacket(CUSTOM_STM_FT_PACKET_OUT, br + 2,
+ &SizeFt_Packet_Out, FS_CRS_TxCallback);
++next_packet;
}
else
diff --git a/FlySight/device_state.c b/FlySight/device_state.c
new file mode 100644
index 0000000..6eab370
--- /dev/null
+++ b/FlySight/device_state.c
@@ -0,0 +1,162 @@
+/***************************************************************************
+** **
+** FlySight 2 firmware **
+** Copyright 2025 Bionic Avionics Inc. **
+** **
+** This program is free software: you can redistribute it and/or modify **
+** it under the terms of the GNU General Public License as published by **
+** the Free Software Foundation, either version 3 of the License, or **
+** (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, **
+** but WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program. If not, see . **
+** **
+****************************************************************************
+** Contact: Bionic Avionics Inc. **
+** Website: http://flysight.ca/ **
+****************************************************************************/
+
+#include // For memcpy, strlen, snprintf
+#include // For snprintf
+
+#include "main.h" // For NVIC_SystemReset and APP_DBG_MSG
+#include "device_state.h"
+#include "control_point_protocol.h"
+#include "custom_stm.h"
+#include "ble_tx_queue.h"
+#include "version.h" // For GIT_TAG
+#include "state.h" // For FS_State_Get()->device_id
+#include "dbg_trace.h"
+
+// Device State (DS) Control Point command opcodes
+#define DS_CMD_GET_FW_VERSION 0x01 // Payload: (none)
+ // Response Data: [version_string (variable length)]
+#define DS_CMD_REBOOT_DEVICE 0x02 // Payload: (none)
+#define DS_CMD_GET_DEVICE_ID 0x03 // Payload: (none)
+ // Response Data: [device_id_hex_string (24 bytes)]
+
+extern uint8_t SizeDs_Control_Point; // From custom_stm.c
+
+void DeviceState_Init(void) {
+ // Initialization for the device state service layer, if any.
+}
+
+// Helper to convert a uint32_t array (like device ID) to a hex string
+static void uint32_array_to_hex_string(const uint32_t *data, uint32_t count, char *out_str, size_t out_str_len) {
+ size_t offset = 0;
+ for (uint32_t i = 0; i < count; ++i) {
+ int written = snprintf(out_str + offset, out_str_len - offset, "%08lx", data[i]);
+ if (written < 0 || (size_t)written >= (out_str_len - offset)) {
+ // Buffer too small or error
+ if (offset < out_str_len) out_str[offset] = '\0'; // Ensure null termination if possible
+ return;
+ }
+ offset += written;
+ }
+}
+
+
+void DeviceState_Handle_DS_ControlPointWrite(const uint8_t *payload, uint8_t length,
+ uint16_t conn_handle, uint8_t notification_enabled_flag) {
+ (void)conn_handle;
+
+ uint8_t received_cmd_opcode = 0xFF;
+ uint8_t status = CP_STATUS_ERROR_UNKNOWN;
+ uint8_t response_data_buf[MAX_CP_OPTIONAL_RESPONSE_DATA_LEN];
+ uint8_t response_data_len = 0;
+
+ if (length < 1) {
+ status = CP_STATUS_INVALID_PARAMETER;
+ } else {
+ received_cmd_opcode = payload[0];
+ // const uint8_t *params = &payload[1]; // No params used yet
+ uint8_t params_len = length - 1;
+
+ switch (received_cmd_opcode) {
+ case DS_CMD_GET_FW_VERSION:
+ if (params_len != 0) {
+ status = CP_STATUS_INVALID_PARAMETER;
+ } else {
+ strncpy((char*)response_data_buf, GIT_TAG, MAX_CP_OPTIONAL_RESPONSE_DATA_LEN - 1);
+ response_data_buf[MAX_CP_OPTIONAL_RESPONSE_DATA_LEN - 1] = '\0'; // Ensure null termination
+ response_data_len = strlen((char*)response_data_buf);
+ status = CP_STATUS_SUCCESS;
+ }
+ break;
+
+ case DS_CMD_REBOOT_DEVICE:
+ if (params_len != 0) {
+ status = CP_STATUS_INVALID_PARAMETER;
+ } else {
+ APP_DBG_MSG("DS_CMD_REBOOT_DEVICE: Rebooting...\n");
+ status = CP_STATUS_SUCCESS; // We'll send success, then reboot
+ // Queue the response *before* reset. The reset might be too fast.
+ // This is a common pattern: acknowledge, then act.
+ }
+ break;
+
+ case DS_CMD_GET_DEVICE_ID:
+ if (params_len != 0) {
+ status = CP_STATUS_INVALID_PARAMETER;
+ } else {
+ char hex_id_str[25]; // 3 * 8 hex chars + null
+ uint32_array_to_hex_string(FS_State_Get()->device_id, 3, hex_id_str, sizeof(hex_id_str));
+
+ response_data_len = strlen(hex_id_str);
+ if (response_data_len <= MAX_CP_OPTIONAL_RESPONSE_DATA_LEN) {
+ memcpy(response_data_buf, hex_id_str, response_data_len);
+ status = CP_STATUS_SUCCESS;
+ } else {
+ status = CP_STATUS_ERROR_UNKNOWN; // Buffer too small for ID string
+ response_data_len = 0;
+ }
+ }
+ break;
+
+ default:
+ status = CP_STATUS_CMD_NOT_SUPPORTED;
+ break;
+ }
+ }
+
+ if (notification_enabled_flag) {
+ uint8_t final_response_packet[3 + MAX_CP_OPTIONAL_RESPONSE_DATA_LEN];
+ uint8_t total_response_len = 3;
+
+ final_response_packet[0] = CP_RESPONSE_ID;
+ final_response_packet[1] = received_cmd_opcode;
+ final_response_packet[2] = status;
+
+ if (status == CP_STATUS_SUCCESS && response_data_len > 0) {
+ if (response_data_len <= MAX_CP_OPTIONAL_RESPONSE_DATA_LEN) {
+ memcpy(&final_response_packet[3], response_data_buf, response_data_len);
+ total_response_len += response_data_len;
+ } else {
+ final_response_packet[2] = CP_STATUS_ERROR_UNKNOWN;
+ response_data_len = 0;
+ total_response_len = 3;
+ APP_DBG_MSG("DeviceState: Response data too long for cmd %02X\n", received_cmd_opcode);
+ }
+ }
+
+ SizeDs_Control_Point = total_response_len;
+ BLE_TX_Queue_SendTxPacket(CUSTOM_STM_DS_CONTROL_POINT,
+ final_response_packet,
+ total_response_len,
+ &SizeDs_Control_Point,
+ 0);
+ }
+
+ // Handle actions that should occur *after* sending the response (like reboot)
+ if (received_cmd_opcode == DS_CMD_REBOOT_DEVICE && status == CP_STATUS_SUCCESS) {
+ // Optional: Add a small delay if needed to ensure BLE packet has a chance to be sent
+ // For critical operations, ensuring the packet is truly on air is hard without deeper BLE stack hooks.
+ // HAL_Delay(100); // Example: Small delay, but generally not ideal in event handlers
+ NVIC_SystemReset();
+ }
+}
diff --git a/FlySight/device_state.h b/FlySight/device_state.h
new file mode 100644
index 0000000..cde56ac
--- /dev/null
+++ b/FlySight/device_state.h
@@ -0,0 +1,45 @@
+/***************************************************************************
+** **
+** FlySight 2 firmware **
+** Copyright 2025 Bionic Avionics Inc. **
+** **
+** This program is free software: you can redistribute it and/or modify **
+** it under the terms of the GNU General Public License as published by **
+** the Free Software Foundation, either version 3 of the License, or **
+** (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, **
+** but WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program. If not, see . **
+** **
+****************************************************************************
+** Contact: Bionic Avionics Inc. **
+** Website: http://flysight.ca/ **
+****************************************************************************/
+
+#ifndef DEVICE_STATE_H_
+#define DEVICE_STATE_H_
+
+#include
+
+/**
+ * @brief Handles commands written to the Device State (DS) Control Point characteristic.
+ *
+ * @param payload Pointer to the incoming command payload (first byte is cmd_opcode).
+ * @param length Length of the payload.
+ * @param conn_handle Connection handle.
+ * @param notification_enabled_flag Flag indicating if notifications are enabled for the DS Control Point.
+ */
+void DeviceState_Handle_DS_ControlPointWrite(const uint8_t *payload, uint8_t length,
+ uint16_t conn_handle, uint8_t notification_enabled_flag);
+
+/**
+ * @brief Initializes the Device State service module.
+ */
+void DeviceState_Init(void);
+
+#endif /* DEVICE_STATE_H_ */
diff --git a/FlySight/flight_params.c b/FlySight/flight_params.c
new file mode 100644
index 0000000..704fbd7
--- /dev/null
+++ b/FlySight/flight_params.c
@@ -0,0 +1,66 @@
+/***************************************************************************
+** **
+** FlySight 2 firmware **
+** Copyright 2025 Bionic Avionics Inc. **
+** **
+** This program is free software: you can redistribute it and/or modify **
+** it under the terms of the GNU General Public License as published by **
+** the Free Software Foundation, either version 3 of the License, or **
+** (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, **
+** but WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program. If not, see . **
+** **
+****************************************************************************
+** Contact: Bionic Avionics Inc. **
+** Website: http://flysight.ca/ **
+****************************************************************************/
+
+#include
+
+#include "flight_params.h"
+#include "config.h"
+
+static const uint16_t sas_table[] =
+{
+ 1024, 1077, 1135, 1197,
+ 1265, 1338, 1418, 1505,
+ 1600, 1704, 1818, 1944
+};
+#define SAS_TABLE_SIZE (sizeof(sas_table)/sizeof(sas_table[0]))
+
+double FS_FlightParams_GetSASCorrectionFactor(int32_t hMSL)
+{
+ const FS_Config_Data_t *cfg = FS_Config_Get();
+
+ if (!cfg->use_sas) {
+ return 1.0; // No correction needed
+ }
+
+ uint16_t speed_mul;
+
+ if (hMSL < 0) {
+ speed_mul = sas_table[0];
+ } else if (hMSL >= ((SAS_TABLE_SIZE - 1) * 1024 * 1024)) {
+ speed_mul = sas_table[SAS_TABLE_SIZE - 1];
+ } else {
+ int32_t h = hMSL / 1024;
+ uint16_t i = h / 1024;
+ uint16_t j = h % 1024;
+ uint16_t y1 = sas_table[i];
+ uint16_t y2 = sas_table[i + 1];
+ speed_mul = y1 + (((uint32_t)(y2 - y1) * j) / 1024);
+ }
+
+ if (speed_mul == 0) {
+ return 1.0; // Avoid division by zero, return no correction
+ }
+
+ // Return the correction factor
+ return 1024.0 / (double)speed_mul;
+}
diff --git a/FlySight/flight_params.h b/FlySight/flight_params.h
new file mode 100644
index 0000000..e011794
--- /dev/null
+++ b/FlySight/flight_params.h
@@ -0,0 +1,30 @@
+/***************************************************************************
+** **
+** FlySight 2 firmware **
+** Copyright 2025 Bionic Avionics Inc. **
+** **
+** This program is free software: you can redistribute it and/or modify **
+** it under the terms of the GNU General Public License as published by **
+** the Free Software Foundation, either version 3 of the License, or **
+** (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, **
+** but WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program. If not, see . **
+** **
+****************************************************************************
+** Contact: Bionic Avionics Inc. **
+** Website: http://flysight.ca/ **
+****************************************************************************/
+
+#ifndef FLIGHT_PARAMS_H_
+#define FLIGHT_PARAMS_H_
+
+#include
+double FS_FlightParams_GetSASCorrectionFactor(int32_t hMSL);
+
+#endif /* FLIGHT_PARAMS_H_ */
diff --git a/FlySight/gnss_ble.c b/FlySight/gnss_ble.c
new file mode 100644
index 0000000..2b65dac
--- /dev/null
+++ b/FlySight/gnss_ble.c
@@ -0,0 +1,82 @@
+/***************************************************************************
+** **
+** FlySight 2 firmware **
+** Copyright 2025 Bionic Avionics Inc. **
+** **
+** This program is free software: you can redistribute it and/or modify **
+** it under the terms of the GNU General Public License as published by **
+** the Free Software Foundation, either version 3 of the License, or **
+** (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, **
+** but WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program. If not, see . **
+** **
+****************************************************************************
+** Contact: Bionic Avionics Inc. **
+** Website: http://flysight.ca/ **
+****************************************************************************/
+
+#include
+
+#include "gnss_ble.h"
+
+static uint8_t s_mask = 0xB0u;
+
+void GNSS_BLE_Init(void)
+{
+ s_mask = 0xB0u;
+}
+
+uint8_t GNSS_BLE_GetMask(void)
+{
+ return s_mask;
+}
+
+void GNSS_BLE_SetMask(uint8_t mask)
+{
+ s_mask = mask;
+}
+
+uint8_t GNSS_BLE_Build(const FS_GNSS_Data_t *src, uint8_t *dst)
+{
+ uint8_t *p = dst;
+
+ *p++ = s_mask; /* byte 0 : mask */
+
+ if (s_mask & GNSS_BLE_BIT_TOW) { /* time of week */
+ memcpy(p, &src->iTOW, sizeof(src->iTOW)); p += 4;
+ }
+
+ if (s_mask & GNSS_BLE_BIT_WEEK) { /* week number */
+ // Not yet implemented
+ }
+
+ if (s_mask & GNSS_BLE_BIT_POSITION) { /* position */
+ memcpy(p, &src->lon, sizeof(src->lon)); p += 4;
+ memcpy(p, &src->lat, sizeof(src->lat)); p += 4;
+ memcpy(p, &src->hMSL, sizeof(src->hMSL)); p += 4;
+ }
+
+ if (s_mask & GNSS_BLE_BIT_VELOCITY) { /* velocity */
+ memcpy(p, &src->velN, sizeof(src->velN)); p += 4;
+ memcpy(p, &src->velE, sizeof(src->velE)); p += 4;
+ memcpy(p, &src->velD, sizeof(src->velD)); p += 4;
+ }
+
+ if (s_mask & GNSS_BLE_BIT_ACCURACY) { /* accuracy */
+ memcpy(p, &src->hAcc, sizeof(src->hAcc)); p += 4;
+ memcpy(p, &src->vAcc, sizeof(src->vAcc)); p += 4;
+ memcpy(p, &src->sAcc, sizeof(src->sAcc)); p += 4;
+ }
+
+ if (s_mask & GNSS_BLE_BIT_NUM_SV) { /* number of satellites */
+ memcpy(p, &src->numSV, sizeof(src->numSV)); p += 1;
+ }
+
+ return (uint8_t)(p - dst); /* total payload length */
+}
diff --git a/FlySight/gnss_ble.h b/FlySight/gnss_ble.h
new file mode 100644
index 0000000..74729f2
--- /dev/null
+++ b/FlySight/gnss_ble.h
@@ -0,0 +1,51 @@
+/***************************************************************************
+** **
+** FlySight 2 firmware **
+** Copyright 2025 Bionic Avionics Inc. **
+** **
+** This program is free software: you can redistribute it and/or modify **
+** it under the terms of the GNU General Public License as published by **
+** the Free Software Foundation, either version 3 of the License, or **
+** (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, **
+** but WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program. If not, see . **
+** **
+****************************************************************************
+** Contact: Bionic Avionics Inc. **
+** Website: http://flysight.ca/ **
+****************************************************************************/
+
+#ifndef GNSS_BLE_H_
+#define GNSS_BLE_H_
+
+#include
+#include "gnss.h"
+
+/* ------------------------------------------------------------------ */
+/* Packet layout control */
+/* ------------------------------------------------------------------ */
+#define GNSS_BLE_MAX_LEN 44u
+
+/* Bit-layout of mask byte (MSB first) */
+#define GNSS_BLE_BIT_TOW 0x80u
+#define GNSS_BLE_BIT_WEEK 0x40u
+#define GNSS_BLE_BIT_POSITION 0x20u
+#define GNSS_BLE_BIT_VELOCITY 0x10u
+#define GNSS_BLE_BIT_ACCURACY 0x08u
+#define GNSS_BLE_BIT_NUM_SV 0x04u
+
+/* ------------------------------------------------------------------ */
+/* Public API */
+/* ------------------------------------------------------------------ */
+void GNSS_BLE_Init(void);
+uint8_t GNSS_BLE_GetMask(void);
+void GNSS_BLE_SetMask(uint8_t mask);
+uint8_t GNSS_BLE_Build(const FS_GNSS_Data_t *src, uint8_t *dst);
+
+#endif /* GNSS_BLE_H_ */
diff --git a/FlySight/log.c b/FlySight/log.c
index 0bbbb51..e942460 100644
--- a/FlySight/log.c
+++ b/FlySight/log.c
@@ -300,6 +300,39 @@ void FS_Log_UpdateMag(void)
++magRdI;
}
+// Helper function to normalize nanoseconds and adjust time accordingly
+void normalizeGNSSTime(const FS_GNSS_Data_t *data,
+ uint16_t *norm_year, uint8_t *norm_month, uint8_t *norm_day,
+ uint8_t *norm_hour, uint8_t *norm_min, uint8_t *norm_sec,
+ int32_t *norm_millis)
+{
+ // Convert original time to epoch seconds
+ uint32_t epoch_seconds = mk_gmtime(data->year, data->month, data->day,
+ data->hour, data->min, data->sec);
+
+ // Convert nanoseconds to milliseconds (rounded)
+ if (data->nano >= 0) {
+ *norm_millis = (data->nano + 500000) / 1000000;
+ } else {
+ *norm_millis = (data->nano - 499999) / 1000000;
+ }
+
+ // Handle negative milliseconds by borrowing from seconds
+ if (*norm_millis >= 1000) {
+ *norm_millis -= 1000;
+ ++epoch_seconds;
+ }
+ else if (*norm_millis < 0) {
+ *norm_millis += 1000;
+ --epoch_seconds;
+ }
+
+ // Convert back to date/time components
+ gmtime_r(epoch_seconds, norm_year, norm_month, norm_day,
+ norm_hour, norm_min, norm_sec);
+}
+
+// Modified logging function
void FS_Log_UpdateGNSS(void)
{
char row[150];
@@ -313,7 +346,15 @@ void FS_Log_UpdateGNSS(void)
// Get current data point
FS_GNSS_Data_t *data = &gnssBuf[gnssRdI % GNSS_COUNT];
- // Write to disk
+ // Normalize the time components
+ uint16_t norm_year;
+ uint8_t norm_month, norm_day, norm_hour, norm_min, norm_sec;
+ int32_t norm_millis;
+
+ normalizeGNSSTime(data, &norm_year, &norm_month, &norm_day,
+ &norm_hour, &norm_min, &norm_sec, &norm_millis);
+
+ // Write to disk using normalized values
char *ptr = row + sizeof(row);
*(--ptr) = '\n';
@@ -328,13 +369,13 @@ void FS_Log_UpdateGNSS(void)
ptr = writeInt32ToBuf(ptr, data->lon, 7, 1, ',');
ptr = writeInt32ToBuf(ptr, data->lat, 7, 1, ',');
*(--ptr) = ',';
- ptr = writeInt32ToBuf(ptr, (data->nano + 500000) / 1000000, 3, 0, 'Z');
- ptr = writeInt32ToBuf(ptr, data->sec, 2, 0, '.');
- ptr = writeInt32ToBuf(ptr, data->min, 2, 0, ':');
- ptr = writeInt32ToBuf(ptr, data->hour, 2, 0, ':');
- ptr = writeInt32ToBuf(ptr, data->day, 2, 0, 'T');
- ptr = writeInt32ToBuf(ptr, data->month, 2, 0, '-');
- ptr = writeInt32ToBuf(ptr, data->year, 4, 0, '-');
+ ptr = writeInt32ToBuf(ptr, norm_millis, 3, 0, 'Z');
+ ptr = writeInt32ToBuf(ptr, norm_sec, 2, 0, '.');
+ ptr = writeInt32ToBuf(ptr, norm_min, 2, 0, ':');
+ ptr = writeInt32ToBuf(ptr, norm_hour, 2, 0, ':');
+ ptr = writeInt32ToBuf(ptr, norm_day, 2, 0, 'T');
+ ptr = writeInt32ToBuf(ptr, norm_month, 2, 0, '-');
+ ptr = writeInt32ToBuf(ptr, norm_year, 4, 0, '-');
*(--ptr) = ',';
*(--ptr) = 'S';
*(--ptr) = 'S';
diff --git a/FlySight/mode.c b/FlySight/mode.c
index 49da91e..b509467 100644
--- a/FlySight/mode.c
+++ b/FlySight/mode.c
@@ -28,7 +28,9 @@
#include "app_ble.h"
#include "app_common.h"
#include "button.h"
+#include "config.h"
#include "config_mode.h"
+#include "custom_app.h"
#include "log.h"
#include "mode.h"
#include "pairing_mode.h"
@@ -234,6 +236,10 @@ static FS_Mode_State_t FS_Mode_State_Active(FS_Mode_Event_t event)
else if (event == FS_MODE_EVENT_BUTTON_RELEASED)
{
HW_TS_Stop(timer_id);
+ if (FS_Config_Get()->al_mode != 0)
+ {
+ UTIL_SEQ_SetTask(1 << CFG_TASK_START_SCAN_ID, CFG_SCH_PRIO_0);
+ }
}
else if (event == FS_MODE_EVENT_TIMER)
{
@@ -316,10 +322,22 @@ static FS_Mode_State_t FS_Mode_State_Start(FS_Mode_Event_t event)
static void FS_Mode_Update(void)
{
- while (!FS_Mode_QueueEmpty())
- {
- mode_state = mode_state_table[mode_state](FS_Mode_PopQueue());
- }
+ while (!FS_Mode_QueueEmpty())
+ {
+ FS_Mode_State_t oldMode = mode_state;
+ FS_Mode_Event_t event = FS_Mode_PopQueue();
+
+ /* The table call: next_mode = mode_state_table[current_mode](event) */
+ FS_Mode_State_t nextMode = mode_state_table[oldMode](event);
+
+ if (nextMode != oldMode)
+ {
+ mode_state = nextMode;
+
+ /* Notify BLE about the updated mode (cast enum -> 1-byte) */
+ Custom_Mode_Update((uint8_t) nextMode);
+ }
+ }
}
static void FS_Mode_Timer(void)
diff --git a/FlySight/sensor_data.c b/FlySight/sensor_data.c
new file mode 100644
index 0000000..13b5837
--- /dev/null
+++ b/FlySight/sensor_data.c
@@ -0,0 +1,123 @@
+/***************************************************************************
+** **
+** FlySight 2 firmware **
+** Copyright 2025 Bionic Avionics Inc. **
+** **
+** This program is free software: you can redistribute it and/or modify **
+** it under the terms of the GNU General Public License as published by **
+** the Free Software Foundation, either version 3 of the License, or **
+** (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, **
+** but WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program. If not, see . **
+** **
+****************************************************************************
+** Contact: Bionic Avionics Inc. **
+** Website: http://flysight.ca/ **
+****************************************************************************/
+
+#include "sensor_data.h"
+#include "control_point_protocol.h"
+#include "custom_stm.h" // For CUSTOM_STM_SD_CONTROL_POINT char opcode
+#include "ble_tx_queue.h" // For BLE_TX_Queue_SendTxPacket
+#include "gnss_ble.h" // For GNSS_BLE_SetMask, GNSS_BLE_GetMask
+#include "string.h" // For memcpy
+#include "app_common.h" // For APP_DBG_MSG (optional)
+#include "dbg_trace.h"
+
+extern uint8_t SizeSd_Control_Point;
+
+void SensorData_Init(void)
+{
+ GNSS_BLE_Init();
+}
+
+void SensorData_Handle_SD_ControlPointWrite(
+ const uint8_t *payload, uint8_t length,
+ uint16_t conn_handle, uint8_t notification_enabled_flag)
+{
+ (void)conn_handle; // Mark as unused if not needed by this handler specifically
+
+ uint8_t received_cmd_opcode = 0xFF; // Default for error case / no valid command
+ uint8_t status = CP_STATUS_ERROR_UNKNOWN;
+ uint8_t response_data_buf[MAX_CP_OPTIONAL_RESPONSE_DATA_LEN] = {0};
+ uint8_t response_data_len = 0;
+
+ if (length < 1)
+ {
+ status = CP_STATUS_INVALID_PARAMETER;
+ // received_cmd_opcode remains 0xFF (or set to a specific "invalid command" opcode if you define one)
+ }
+ else
+ {
+ received_cmd_opcode = payload[0];
+ const uint8_t *params = &payload[1];
+ uint8_t params_len = length - 1;
+
+ switch (received_cmd_opcode)
+ {
+ case SD_CMD_SET_GNSS_BLE_MASK:
+ if (params_len != 1) {
+ status = CP_STATUS_INVALID_PARAMETER;
+ } else {
+ GNSS_BLE_SetMask(params[0]); // Directly call the utility in gnss_ble.c
+ status = CP_STATUS_SUCCESS;
+ }
+ break;
+
+ case SD_CMD_GET_GNSS_BLE_MASK:
+ if (params_len != 0) {
+ status = CP_STATUS_INVALID_PARAMETER;
+ } else {
+ response_data_buf[0] = GNSS_BLE_GetMask(); // Call utility
+ response_data_len = 1;
+ status = CP_STATUS_SUCCESS;
+ }
+ break;
+
+ default:
+ status = CP_STATUS_CMD_NOT_SUPPORTED;
+ break;
+ }
+ }
+
+ if (notification_enabled_flag)
+ {
+ uint8_t final_response_packet[3 + MAX_CP_OPTIONAL_RESPONSE_DATA_LEN];
+ uint8_t total_response_len = 3;
+
+ final_response_packet[0] = CP_RESPONSE_ID;
+ final_response_packet[1] = received_cmd_opcode; // Echo original or 0xFF
+ final_response_packet[2] = status;
+
+ if (status == CP_STATUS_SUCCESS && response_data_len > 0)
+ {
+ if (response_data_len <= MAX_CP_OPTIONAL_RESPONSE_DATA_LEN)
+ {
+ memcpy(&final_response_packet[3], response_data_buf, response_data_len);
+ total_response_len += response_data_len;
+ }
+ else
+ {
+ // This case should ideally be prevented by MAX_CP_OPTIONAL_RESPONSE_DATA_LEN
+ // Send error or success without data. For robustness, an error might be better.
+ final_response_packet[2] = CP_STATUS_ERROR_UNKNOWN; // Override status
+ response_data_len = 0; // Don't copy data
+ total_response_len = 3;
+ APP_DBG_MSG("SensorData: Response data too long for cmd %02X\n", received_cmd_opcode);
+ }
+ }
+
+ SizeSd_Control_Point = total_response_len; // Update the global characteristic size
+ BLE_TX_Queue_SendTxPacket(CUSTOM_STM_SD_CONTROL_POINT,
+ final_response_packet,
+ total_response_len,
+ &SizeSd_Control_Point, // Pass its address
+ 0);
+ }
+}
diff --git a/FlySight/sensor_data.h b/FlySight/sensor_data.h
new file mode 100644
index 0000000..bdd497a
--- /dev/null
+++ b/FlySight/sensor_data.h
@@ -0,0 +1,51 @@
+/***************************************************************************
+** **
+** FlySight 2 firmware **
+** Copyright 2025 Bionic Avionics Inc. **
+** **
+** This program is free software: you can redistribute it and/or modify **
+** it under the terms of the GNU General Public License as published by **
+** the Free Software Foundation, either version 3 of the License, or **
+** (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, **
+** but WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program. If not, see . **
+** **
+****************************************************************************
+** Contact: Bionic Avionics Inc. **
+** Website: http://flysight.ca/ **
+****************************************************************************/
+
+#ifndef SENSOR_DATA_H_
+#define SENSOR_DATA_H_
+
+#include
+
+// --- Sensor Data (SD) Control Point Command Opcodes (Owned by this module) ---
+#define SD_CMD_SET_GNSS_BLE_MASK 0x01 // Payload: [new_mask (1 byte)]
+#define SD_CMD_GET_GNSS_BLE_MASK 0x02 // Payload: (none)
+ // Response Data: [current_mask (1 byte)]
+// ... other future SD specific commands, e.g., for enabling/disabling other sensor streams over BLE ...
+
+/**
+ * @brief Handles commands written to the Sensor Data (SD) Control Point characteristic.
+ *
+ * @param payload Pointer to the incoming command payload (first byte is cmd_opcode).
+ * @param length Length of the payload.
+ * @param conn_handle Connection handle (currently unused by this handler but passed for consistency).
+ * @param notification_enabled_flag Flag indicating if notifications are enabled for the SD Control Point.
+ */
+void SensorData_Handle_SD_ControlPointWrite(const uint8_t *payload, uint8_t length,
+ uint16_t conn_handle, uint8_t notification_enabled_flag);
+
+/**
+ * @brief Initializes the Sensor Data service module.
+ */
+void SensorData_Init(void); // If any specific init is needed for this service layer
+
+#endif /* SENSOR_DATA_H_ */
diff --git a/FlySight/start_control.c b/FlySight/start_control.c
index 9e181de..81816c9 100644
--- a/FlySight/start_control.c
+++ b/FlySight/start_control.c
@@ -22,6 +22,7 @@
****************************************************************************/
#include
+#include
#include "main.h"
#include "app_common.h"
@@ -35,6 +36,9 @@
#include "state.h"
#include "stm32_seq.h"
#include "time.h"
+#include "custom_stm.h"
+#include "ble_tx_queue.h"
+#include "control_point_protocol.h"
#define LED_BLINK_MSEC 900
#define LED_BLINK_TICKS (LED_BLINK_MSEC*1000/CFG_TS_TICK_VAL)
@@ -44,11 +48,9 @@
#define TONE_MAX_PITCH 1760
-typedef enum
-{
- FS_START_COMMAND_START = 0x00,
- FS_START_COMMAND_CANCEL = 0x01
-} FS_Start_Command_t;
+// Starter Pistol (SP) Control Point command opcodes
+#define SP_CMD_START_COUNTDOWN 0x01 // Payload: (none)
+#define SP_CMD_CANCEL_COUNTDOWN 0x02 // Payload: (none)
static uint8_t led_timer_id;
static uint8_t count_timer_id;
@@ -65,6 +67,8 @@ static volatile enum {
static uint8_t countdown;
static FS_GNSS_Int_t gnss_int;
+extern uint8_t SizeSp_Control_Point;
+
void FS_StartControl_DataReady_Callback(void);
void FS_StartControl_TimeReady_Callback(bool validTime);
void FS_StartControl_IntReady_Callback(void);
@@ -221,8 +225,6 @@ void FS_StartControl_Update(void)
uint8_t sec;
uint16_t ms;
- Custom_Start_Packet_t *packet;
-
if (state == FS_CONTROL_INACTIVE) return;
if (state == FS_CONTROL_UPDATE)
@@ -249,30 +251,91 @@ void FS_StartControl_Update(void)
state = FS_CONTROL_IDLE;
}
+}
- while ((packet = Custom_Start_GetNextControlPacket()))
+static uint8_t FS_StartControl_InitiateCommand(uint8_t cmd_opcode)
+{
+ // Handle commands
+ switch (cmd_opcode)
{
- if (packet->length > 0)
+ case SP_CMD_START_COUNTDOWN:
+ if (state == FS_CONTROL_IDLE)
{
- // Handle commands
- switch (packet->data[0])
- {
- case FS_START_COMMAND_START:
- if (state == FS_CONTROL_IDLE)
- {
- countdown = 6;
- state = FS_CONTROL_COUNTING;
- HW_TS_Start(count_timer_id, COUNT_TICKS);
- }
- break;
- case FS_START_COMMAND_CANCEL:
- if (state == FS_CONTROL_COUNTING)
- {
- HW_TS_Stop(count_timer_id);
- state = FS_CONTROL_IDLE;
- }
- break;
- }
+ countdown = 6;
+ state = FS_CONTROL_COUNTING;
+ HW_TS_Start(count_timer_id, COUNT_TICKS);
+ return CP_STATUS_SUCCESS;
}
+ else if (state == FS_CONTROL_COUNTING)
+ {
+ return CP_STATUS_BUSY;
+ }
+ else
+ {
+ return CP_STATUS_OPERATION_NOT_PERMITTED;
+ }
+ case SP_CMD_CANCEL_COUNTDOWN:
+ if (state == FS_CONTROL_COUNTING)
+ {
+ HW_TS_Stop(count_timer_id);
+ state = FS_CONTROL_IDLE;
+ return CP_STATUS_SUCCESS;
+ }
+ else if (state == FS_CONTROL_IDLE)
+ {
+ return CP_STATUS_SUCCESS; // No active countdown to cancel
+ }
+ else
+ {
+ return CP_STATUS_OPERATION_NOT_PERMITTED;
+ }
+ default:
+ return CP_STATUS_CMD_NOT_SUPPORTED;
}
}
+
+void FS_StartControl_Handle_SP_ControlPointWrite(
+ const uint8_t *payload, uint8_t length,
+ uint16_t conn_handle, uint8_t indication_enabled_flag)
+{
+ (void)conn_handle; // Mark as unused
+
+ uint8_t received_cmd_opcode = 0xFF;
+ uint8_t status = CP_STATUS_ERROR_UNKNOWN;
+ // SP commands do not return optional data in the ACK, so no response_data_buf needed here.
+
+ if (length < 1)
+ {
+ status = CP_STATUS_INVALID_PARAMETER;
+ }
+ else
+ {
+ received_cmd_opcode = payload[0]; // This is an SP_CMD_...
+ // const uint8_t *params = &payload[1]; // No params for current SP commands
+ uint8_t params_len = length - 1;
+
+ if (params_len != 0) // Current SP commands don't take parameters
+ {
+ status = CP_STATUS_INVALID_PARAMETER;
+ }
+ else
+ {
+ status = FS_StartControl_InitiateCommand(received_cmd_opcode);
+ }
+ }
+
+ if (indication_enabled_flag)
+ {
+ uint8_t final_response_packet[3]; // Response is always 3 bytes for SP acks
+ final_response_packet[0] = CP_RESPONSE_ID;
+ final_response_packet[1] = received_cmd_opcode;
+ final_response_packet[2] = status;
+
+ SizeSp_Control_Point = 3;
+ BLE_TX_Queue_SendTxPacket(CUSTOM_STM_SP_CONTROL_POINT,
+ final_response_packet,
+ 3,
+ &SizeSp_Control_Point,
+ 0);
+ }
+}
diff --git a/FlySight/start_control.h b/FlySight/start_control.h
index e32bd8a..cc13bb1 100644
--- a/FlySight/start_control.h
+++ b/FlySight/start_control.h
@@ -24,10 +24,15 @@
#ifndef START_CONTROL_H_
#define START_CONTROL_H_
-#define FS_START_WINDOW_LENGTH 2
+#include
void FS_StartControl_RegisterTasks(void);
void FS_StartControl_Init(void);
void FS_StartControl_DeInit(void);
+// Function to handle writes to the SP Control Point
+void FS_StartControl_Handle_SP_ControlPointWrite(
+ const uint8_t *payload, uint8_t length,
+ uint16_t conn_handle, uint8_t indication_enabled_flag);
+
#endif /* START_CONTROL_H_ */
diff --git a/STM32_WPAN/App/activelook_client.c b/STM32_WPAN/App/activelook_client.c
new file mode 100644
index 0000000..46e537f
--- /dev/null
+++ b/STM32_WPAN/App/activelook_client.c
@@ -0,0 +1,960 @@
+/***************************************************************************
+** **
+** FlySight 2 firmware **
+** Copyright 2025 Bionic Avionics Inc. **
+** **
+** This program is free software: you can redistribute it and/or modify **
+** it under the terms of the GNU General Public License as published by **
+** the Free Software Foundation, either version 3 of the License, or **
+** (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, **
+** but WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program. If not, see . **
+** **
+****************************************************************************
+** Contact: Bionic Avionics Inc. **
+** Website: http://flysight.ca/ **
+****************************************************************************/
+
+#include "activelook_client.h"
+#include "app_common.h"
+#include "app_conf.h"
+#include "dbg_trace.h"
+#include "ble.h"
+#include "tl.h"
+#include "log.h"
+#include "stm32_seq.h"
+#include
+
+#ifndef UNPACK_2_BYTE_PARAMETER
+#define UNPACK_2_BYTE_PARAMETER(ptr) \
+ (uint16_t)( ((uint16_t)(*((uint8_t *)(ptr)))) \
+ | ((uint16_t)(*(((uint8_t *)(ptr))+1)) << 8U) )
+#endif
+
+/* ActiveLook Commands service UUID
+ * 0783B03E-8535-B5A0-7140-A304D2495CB7 */
+static const uint8_t ACTIVELK_SERVICE_UUID[16] =
+{
+ 0xB7, 0x5C, 0x49, 0xD2,
+ 0x04, 0xA3, 0x40, 0x71,
+ 0xA0, 0xB5, 0x35, 0x85,
+ 0x3E, 0xB0, 0x83, 0x07
+};
+
+/* Rx characteristic UUID (write to glasses)
+ * 0783B03E-8535-B5A0-7140-A304D2495CBA */
+static const uint8_t ACTIVELK_RX_CHAR_UUID[16] =
+{
+ 0xBA, 0x5C, 0x49, 0xD2,
+ 0x04, 0xA3, 0x40, 0x71,
+ 0xA0, 0xB5, 0x35, 0x85,
+ 0x3E, 0xB0, 0x83, 0x07
+};
+
+/* Tx characteristic UUID (notify from glasses)
+ * 0783B03E-8535-B5A0-7140-A304D2495CB8 */
+static const uint8_t ACTIVELK_TX_CHAR_UUID[16] =
+{
+ 0xB8, 0x5C, 0x49, 0xD2,
+ 0x04, 0xA3, 0x40, 0x71,
+ 0xA0, 0xB5, 0x35, 0x85,
+ 0x3E, 0xB0, 0x83, 0x07
+};
+
+/* CTRL characteristic UUID (flow control, notify from glasses)
+ * 0783B03E-8535-B5A0-7140-A304D2495CB9 */
+static const uint8_t ACTIVELK_CTRL_CHAR_UUID[16] =
+{
+ 0xB9, 0x5C, 0x49, 0xD2,
+ 0x04, 0xA3, 0x40, 0x71,
+ 0xA0, 0xB5, 0x35, 0x85,
+ 0x3E, 0xB0, 0x83, 0x07
+};
+
+/* Discovery states, now extended to handle battery service as well */
+typedef enum {
+ DISC_STATE_IDLE = 0,
+ DISC_STATE_EXCH_MTU,
+ DISC_STATE_SVC_IN_PROGRESS,
+ DISC_STATE_CHAR_IN_PROGRESS,
+ DISC_STATE_DESC_IN_PROGRESS,
+ DISC_STATE_BATTERY_NOTIFY_WRITE,
+ DISC_STATE_TX_DESC_IN_PROGRESS,
+ DISC_STATE_TX_NOTIFY_WRITE,
+ DISC_STATE_CTRL_DESC_IN_PROGRESS,
+ DISC_STATE_CTRL_NOTIFY_WRITE
+} DiscoveryState_t;
+
+/* We define a separate enum to track which service we are scanning for chars. */
+typedef enum {
+ SERVICE_NONE = 0,
+ SERVICE_ACTIVELOOK,
+ SERVICE_BATTERY
+} WhichService_t;
+
+typedef struct
+{
+ uint16_t connHandle;
+ DiscoveryState_t discState;
+
+ /* ActiveLook service discovery */
+ uint8_t serviceFound;
+ uint16_t serviceStartHandle;
+ uint16_t serviceEndHandle;
+
+ uint8_t rxCharFound;
+ uint16_t rxCharHandle;
+
+ uint8_t txCharFound;
+ uint16_t txCharHandle;
+
+ uint16_t txCCCDHandle;
+
+ uint8_t ctrlCharFound;
+ uint16_t ctrlCharHandle;
+ uint16_t ctrlCCCDHandle;
+ uint8_t flowCtrlPaused;
+ uint8_t writePending;
+ uint8_t uploadActive;
+
+ uint32_t cfgReadVersion;
+ uint8_t cfgReadPending;
+ void (*cfgReadCallback)(uint8_t found, uint32_t version);
+
+ /* Store battery service info */
+ uint8_t batteryServiceFound;
+ uint16_t batteryServiceStartHandle;
+ uint16_t batteryServiceEndHandle;
+
+ uint8_t batteryCharFound;
+ uint16_t batteryCharHandle;
+ uint8_t lastBatteryPercent; /* store the last known battery percent */
+
+ /* Sore the discovered CCC descriptor for the battery char */
+ uint16_t batteryCCCDHandle;
+
+ /* Keep track of which service we are currently discovering chars for */
+ WhichService_t whichService;
+
+ /* Track which handle we requested to read, so we know how to interpret the response. */
+ uint16_t currentReadHandle;
+
+ /* Callback interface if set */
+ const FS_ActiveLook_ClientCb_t *cb;
+} FS_ActiveLook_Client_Context_t;
+
+static FS_ActiveLook_Client_Context_t g_ctx;
+
+/******************************************************************************
+ * Initialize
+ ******************************************************************************/
+void FS_ActiveLook_Client_Init(void)
+{
+ memset(&g_ctx, 0, sizeof(g_ctx));
+ g_ctx.discState = DISC_STATE_IDLE;
+}
+
+/******************************************************************************
+ * Register optional callback interface
+ ******************************************************************************/
+void FS_ActiveLook_Client_RegisterCb(const FS_ActiveLook_ClientCb_t *cb)
+{
+ g_ctx.cb = cb;
+}
+
+/******************************************************************************
+ * Start discovery (including MTU exchange) after connecting
+ ******************************************************************************/
+void FS_ActiveLook_Client_StartDiscovery(uint16_t connectionHandle)
+{
+ g_ctx.connHandle = connectionHandle;
+ g_ctx.discState = DISC_STATE_EXCH_MTU; /* Start with MTU exchange */
+
+ /* Clear everything so we re-discover from scratch */
+ g_ctx.serviceFound = 0;
+ g_ctx.serviceStartHandle = 0;
+ g_ctx.serviceEndHandle = 0;
+ g_ctx.rxCharFound = 0;
+ g_ctx.rxCharHandle = 0;
+ g_ctx.txCharFound = 0;
+ g_ctx.txCharHandle = 0;
+ g_ctx.txCCCDHandle = 0;
+ g_ctx.ctrlCharFound = 0;
+ g_ctx.ctrlCharHandle = 0;
+ g_ctx.ctrlCCCDHandle = 0;
+ g_ctx.flowCtrlPaused = 0;
+ g_ctx.writePending = 0;
+ g_ctx.uploadActive = 0;
+ g_ctx.cfgReadPending = 0;
+ g_ctx.cfgReadCallback = NULL;
+
+ g_ctx.batteryServiceFound = 0;
+ g_ctx.batteryServiceStartHandle = 0;
+ g_ctx.batteryServiceEndHandle = 0;
+ g_ctx.batteryCharFound = 0;
+ g_ctx.batteryCharHandle = 0;
+ g_ctx.lastBatteryPercent = 255; /* e.g. 255 can mean "unknown" */
+
+ g_ctx.whichService = SERVICE_NONE;
+
+ /* Step 1: request a bigger ATT MTU from the peripheral */
+ tBleStatus s = aci_gatt_exchange_config(connectionHandle);
+ if (s == BLE_STATUS_SUCCESS)
+ {
+ APP_DBG_MSG("ActiveLook_Client: Requesting MTU exchange...\n");
+ }
+ else
+ {
+ APP_DBG_MSG("ActiveLook_Client: aci_gatt_exchange_config fail=0x%02X\n", s);
+ g_ctx.discState = DISC_STATE_IDLE; // stop
+ }
+}
+
+/******************************************************************************
+ * Event Handler
+ ******************************************************************************/
+void FS_ActiveLook_Client_EventHandler(void *p_blecore_evt, uint8_t hci_event_evt_code)
+{
+ evt_blecore_aci *blecore_evt = (evt_blecore_aci*) p_blecore_evt;
+
+ switch (blecore_evt->ecode)
+ {
+ /**********************************************************************
+ * The peripheral responded to MTU exchange:
+ *********************************************************************/
+ case ACI_ATT_EXCHANGE_MTU_RESP_VSEVT_CODE:
+ {
+ /* This event indicates the peripheral accepted some MTU. */
+ aci_att_exchange_mtu_resp_event_rp0 *mtu_resp =
+ (aci_att_exchange_mtu_resp_event_rp0*) blecore_evt->data;
+ APP_DBG_MSG("ActiveLook_Client: ACI_ATT_EXCHANGE_MTU_RESP, final MTU=%d\r\n",
+ mtu_resp->Server_RX_MTU);
+ /* Wait for ACI_GATT_PROC_COMPLETE_VSEVT_CODE to know the procedure is done. */
+ }
+ break;
+
+ /**********************************************************************
+ * GATT procedure complete => check if we were in MTU exchange, or
+ * discovering service, or discovering char, etc.
+ *********************************************************************/
+ case ACI_GATT_PROC_COMPLETE_VSEVT_CODE:
+ {
+ aci_gatt_proc_complete_event_rp0 *pc =
+ (aci_gatt_proc_complete_event_rp0*) blecore_evt->data;
+
+ if (pc->Connection_Handle != g_ctx.connHandle)
+ break; /* Not for us */
+
+ if (g_ctx.writePending)
+ {
+ g_ctx.writePending = 0;
+ if (!g_ctx.uploadActive)
+ UTIL_SEQ_SetTask(1 << CFG_TASK_FS_ACTIVELOOK_ID, CFG_SCH_PRIO_0);
+ }
+
+ if (g_ctx.discState == DISC_STATE_EXCH_MTU)
+ {
+ /* Done exchanging MTU => discover all primary services */
+ g_ctx.discState = DISC_STATE_SVC_IN_PROGRESS;
+ tBleStatus s = aci_gatt_disc_all_primary_services(g_ctx.connHandle);
+ if (s == BLE_STATUS_SUCCESS)
+ {
+ APP_DBG_MSG("ActiveLook_Client: MTU ok, now discovering all services...\n");
+ }
+ else
+ {
+ APP_DBG_MSG("ActiveLook_Client: disc_all_primary_services fail=0x%02X\n", s);
+ g_ctx.discState = DISC_STATE_IDLE;
+ }
+ }
+ else if (g_ctx.discState == DISC_STATE_SVC_IN_PROGRESS)
+ {
+ /* Done discovering all services. Next, discover chars. */
+ if (g_ctx.serviceFound)
+ {
+ g_ctx.discState = DISC_STATE_CHAR_IN_PROGRESS;
+ g_ctx.whichService = SERVICE_ACTIVELOOK;
+ tBleStatus s = aci_gatt_disc_all_char_of_service(
+ g_ctx.connHandle,
+ g_ctx.serviceStartHandle,
+ g_ctx.serviceEndHandle);
+ if (s == BLE_STATUS_SUCCESS)
+ {
+ APP_DBG_MSG("ActiveLook_Client: Discovering chars in ActiveLook service...\n");
+ }
+ else
+ {
+ APP_DBG_MSG("ActiveLook_Client: disc_all_char_of_service fail=0x%02X\n", s);
+ g_ctx.discState = DISC_STATE_IDLE;
+ }
+ }
+ else if (g_ctx.batteryServiceFound)
+ {
+ g_ctx.discState = DISC_STATE_CHAR_IN_PROGRESS;
+ g_ctx.whichService = SERVICE_BATTERY;
+ tBleStatus s = aci_gatt_disc_all_char_of_service(
+ g_ctx.connHandle,
+ g_ctx.batteryServiceStartHandle,
+ g_ctx.batteryServiceEndHandle);
+ if (s == BLE_STATUS_SUCCESS)
+ {
+ APP_DBG_MSG("ActiveLook_Client: No AL service, but battery found; discovering battery char...\n");
+ }
+ else
+ {
+ APP_DBG_MSG("ActiveLook_Client: disc_all_char_of_service(battery) fail=0x%02X\n", s);
+ g_ctx.discState = DISC_STATE_IDLE;
+ }
+ }
+ else
+ {
+ APP_DBG_MSG("ActiveLook_Client: No known services found.\n");
+ g_ctx.discState = DISC_STATE_IDLE;
+ }
+ }
+ else if (g_ctx.discState == DISC_STATE_CHAR_IN_PROGRESS)
+ {
+ /* Done discovering chars for whichever service we were on. */
+ if (g_ctx.whichService == SERVICE_ACTIVELOOK)
+ {
+ /* If battery service also found, discover battery chars now */
+ if (g_ctx.batteryServiceFound)
+ {
+ g_ctx.whichService = SERVICE_BATTERY;
+ tBleStatus s = aci_gatt_disc_all_char_of_service(
+ g_ctx.connHandle,
+ g_ctx.batteryServiceStartHandle,
+ g_ctx.batteryServiceEndHandle);
+ if (s == BLE_STATUS_SUCCESS)
+ {
+ APP_DBG_MSG("ActiveLook_Client: Now discovering battery char...\n");
+ return; /* Wait next event for the battery char discovery response */
+ }
+ else
+ {
+ APP_DBG_MSG("ActiveLook_Client: disc_all_char_of_service(battery) fail=0x%02X\n", s);
+ }
+ }
+ }
+
+ /* If we found batteryChar, let's now discover descriptors for it, else finalize. */
+ if (g_ctx.batteryCharFound && (g_ctx.batteryCharHandle != 0))
+ {
+ /* Move to descriptor discovery for battery. */
+ g_ctx.discState = DISC_STATE_DESC_IN_PROGRESS;
+ /* The standard approach: discover descriptors from (charValueHandle+1) to serviceEndHandle */
+ tBleStatus s = aci_gatt_disc_all_char_desc(
+ g_ctx.connHandle,
+ g_ctx.batteryCharHandle,
+ g_ctx.batteryCharHandle + 2);
+ if (s == BLE_STATUS_SUCCESS)
+ {
+ APP_DBG_MSG("ActiveLook_Client: Discovering battery descriptors...\n");
+ return;
+ }
+ else
+ {
+ APP_DBG_MSG("ActiveLook_Client: desc_all_char_desc(battery) fail=0x%02X\n", s);
+ }
+ }
+
+ /* If no battery or if the above fails, finalize. */
+ g_ctx.discState = DISC_STATE_IDLE;
+ APP_DBG_MSG("ActiveLook_Client: Char discovery complete. Rx=0x%04X, Battery=0x%04X\n",
+ g_ctx.rxCharHandle, g_ctx.batteryCharHandle);
+
+ if (g_ctx.rxCharFound && g_ctx.cb && g_ctx.cb->OnDiscoveryComplete)
+ {
+ g_ctx.cb->OnDiscoveryComplete();
+ }
+ }
+ else if (g_ctx.discState == DISC_STATE_DESC_IN_PROGRESS)
+ {
+ /* Done discovering battery descriptors. Enable battery notifications. */
+ APP_DBG_MSG("ActiveLook_Client: Battery descriptor discovery complete.\n");
+
+ if (g_ctx.batteryCCCDHandle != 0)
+ {
+ tBleStatus s2 = FS_ActiveLook_Client_EnableBatteryNotifications();
+ APP_DBG_MSG("EnableBatteryNotifications => 0x%02X\n", s2);
+ if (s2 == BLE_STATUS_SUCCESS)
+ {
+ g_ctx.discState = DISC_STATE_BATTERY_NOTIFY_WRITE;
+ }
+ else
+ {
+ /* Battery notify failed, but continue to TX discovery */
+ g_ctx.discState = DISC_STATE_IDLE;
+ if (g_ctx.rxCharFound && g_ctx.cb && g_ctx.cb->OnDiscoveryComplete)
+ g_ctx.cb->OnDiscoveryComplete();
+ }
+ }
+ else
+ {
+ /* No battery CCCD, skip to TX discovery or finalize */
+ g_ctx.discState = DISC_STATE_IDLE;
+ if (g_ctx.rxCharFound && g_ctx.cb && g_ctx.cb->OnDiscoveryComplete)
+ g_ctx.cb->OnDiscoveryComplete();
+ }
+ }
+ else if (g_ctx.discState == DISC_STATE_BATTERY_NOTIFY_WRITE)
+ {
+ if (g_ctx.txCharFound && (g_ctx.txCharHandle != 0))
+ {
+ g_ctx.discState = DISC_STATE_TX_DESC_IN_PROGRESS;
+ tBleStatus s3 = aci_gatt_disc_all_char_desc(
+ g_ctx.connHandle,
+ g_ctx.txCharHandle,
+ g_ctx.txCharHandle + 2);
+ if (s3 == BLE_STATUS_SUCCESS)
+ {
+ APP_DBG_MSG("ActiveLook_Client: Discovering TX descriptors...\n");
+ }
+ else
+ {
+ APP_DBG_MSG("ActiveLook_Client: TX desc discovery fail=0x%02X\n", s3);
+ g_ctx.discState = DISC_STATE_IDLE;
+ if (g_ctx.rxCharFound && g_ctx.cb && g_ctx.cb->OnDiscoveryComplete)
+ g_ctx.cb->OnDiscoveryComplete();
+ }
+ }
+ else
+ {
+ g_ctx.discState = DISC_STATE_IDLE;
+ if (g_ctx.rxCharFound && g_ctx.cb && g_ctx.cb->OnDiscoveryComplete)
+ g_ctx.cb->OnDiscoveryComplete();
+ }
+ }
+ else if (g_ctx.discState == DISC_STATE_TX_DESC_IN_PROGRESS)
+ {
+ /* TX descriptor discovery done. Enable TX notifications. */
+ g_ctx.discState = DISC_STATE_IDLE;
+ if (g_ctx.txCCCDHandle != 0)
+ {
+ uint16_t enable = 0x0001;
+ tBleStatus s4 = aci_gatt_write_char_desc(
+ g_ctx.connHandle,
+ g_ctx.txCCCDHandle,
+ 2,
+ (uint8_t*)&enable);
+ if (s4 == BLE_STATUS_SUCCESS)
+ {
+ APP_DBG_MSG("ActiveLook_Client: TX notifications enabled\n");
+ g_ctx.discState = DISC_STATE_TX_NOTIFY_WRITE;
+ }
+ else
+ {
+ APP_DBG_MSG("ActiveLook_Client: TX notify enable fail=0x%02X\n", s4);
+ if (g_ctx.rxCharFound && g_ctx.cb && g_ctx.cb->OnDiscoveryComplete)
+ g_ctx.cb->OnDiscoveryComplete();
+ }
+ }
+ else
+ {
+ APP_DBG_MSG("ActiveLook_Client: No TX CCCD found\n");
+ if (g_ctx.rxCharFound && g_ctx.cb && g_ctx.cb->OnDiscoveryComplete)
+ g_ctx.cb->OnDiscoveryComplete();
+ }
+ }
+ else if (g_ctx.discState == DISC_STATE_TX_NOTIFY_WRITE)
+ {
+ APP_DBG_MSG("ActiveLook_Client: TX notifications enabled OK\n");
+
+ if (g_ctx.ctrlCharFound && (g_ctx.ctrlCharHandle != 0))
+ {
+ g_ctx.discState = DISC_STATE_CTRL_DESC_IN_PROGRESS;
+ tBleStatus s5 = aci_gatt_disc_all_char_desc(
+ g_ctx.connHandle,
+ g_ctx.ctrlCharHandle,
+ g_ctx.ctrlCharHandle + 2);
+ if (s5 == BLE_STATUS_SUCCESS)
+ {
+ APP_DBG_MSG("ActiveLook_Client: Discovering CTRL descriptors...\n");
+ }
+ else
+ {
+ APP_DBG_MSG("ActiveLook_Client: CTRL desc discovery fail=0x%02X\n", s5);
+ g_ctx.discState = DISC_STATE_IDLE;
+ if (g_ctx.rxCharFound && g_ctx.cb && g_ctx.cb->OnDiscoveryComplete)
+ g_ctx.cb->OnDiscoveryComplete();
+ }
+ }
+ else
+ {
+ g_ctx.discState = DISC_STATE_IDLE;
+ APP_DBG_MSG("ActiveLook_Client: All discovery complete (no CTRL)\n");
+ if (g_ctx.rxCharFound && g_ctx.cb && g_ctx.cb->OnDiscoveryComplete)
+ g_ctx.cb->OnDiscoveryComplete();
+ }
+ }
+ else if (g_ctx.discState == DISC_STATE_CTRL_DESC_IN_PROGRESS)
+ {
+ g_ctx.discState = DISC_STATE_IDLE;
+ if (g_ctx.ctrlCCCDHandle != 0)
+ {
+ uint16_t enable = 0x0001;
+ tBleStatus s6 = aci_gatt_write_char_desc(
+ g_ctx.connHandle,
+ g_ctx.ctrlCCCDHandle,
+ 2,
+ (uint8_t*)&enable);
+ if (s6 == BLE_STATUS_SUCCESS)
+ {
+ APP_DBG_MSG("ActiveLook_Client: CTRL notifications enabling...\n");
+ g_ctx.discState = DISC_STATE_CTRL_NOTIFY_WRITE;
+ }
+ else
+ {
+ APP_DBG_MSG("ActiveLook_Client: CTRL notify enable fail=0x%02X\n", s6);
+ if (g_ctx.rxCharFound && g_ctx.cb && g_ctx.cb->OnDiscoveryComplete)
+ g_ctx.cb->OnDiscoveryComplete();
+ }
+ }
+ else
+ {
+ APP_DBG_MSG("ActiveLook_Client: No CTRL CCCD found\n");
+ if (g_ctx.rxCharFound && g_ctx.cb && g_ctx.cb->OnDiscoveryComplete)
+ g_ctx.cb->OnDiscoveryComplete();
+ }
+ }
+ else if (g_ctx.discState == DISC_STATE_CTRL_NOTIFY_WRITE)
+ {
+ g_ctx.discState = DISC_STATE_IDLE;
+ FS_Log_WriteEvent("AL CTRL notifications enabled");
+ APP_DBG_MSG("ActiveLook_Client: All discovery complete, TX+CTRL notifications active\n");
+ if (g_ctx.rxCharFound && g_ctx.cb && g_ctx.cb->OnDiscoveryComplete)
+ g_ctx.cb->OnDiscoveryComplete();
+ }
+ }
+ break;
+
+ /**********************************************************************
+ * "Read by group type" => primary service
+ *********************************************************************/
+ case ACI_ATT_READ_BY_GROUP_TYPE_RESP_VSEVT_CODE:
+ {
+ if (g_ctx.discState == DISC_STATE_SVC_IN_PROGRESS)
+ {
+ aci_att_read_by_group_type_resp_event_rp0 *pr =
+ (aci_att_read_by_group_type_resp_event_rp0*) blecore_evt->data;
+
+ uint8_t idx = 0;
+ while (idx < pr->Data_Length)
+ {
+ uint16_t startHdl = UNPACK_2_BYTE_PARAMETER(&pr->Attribute_Data_List[idx]);
+ uint16_t endHdl = UNPACK_2_BYTE_PARAMETER(&pr->Attribute_Data_List[idx+2]);
+ const uint8_t *uuid = &pr->Attribute_Data_List[idx+4];
+
+ /* The length is pr->Attribute_Data_Length for each record. */
+ /* If it’s 6, the UUID is 16 bits. If it’s 20, the UUID is 128 bits. */
+
+ if (pr->Attribute_Data_Length == 20)
+ {
+ /* 128-bit UUID */
+ if (memcmp(uuid, ACTIVELK_SERVICE_UUID, 16) == 0)
+ {
+ APP_DBG_MSG("ActiveLook_Client: Found ActiveLook Service 0x%04X–0x%04X\n", startHdl, endHdl);
+ g_ctx.serviceFound = 1;
+ g_ctx.serviceStartHandle = startHdl;
+ g_ctx.serviceEndHandle = endHdl;
+ }
+ }
+ else if (pr->Attribute_Data_Length == 6)
+ {
+ /* 16-bit UUID => check if 0x180F (Battery) */
+ uint16_t short_uuid = UNPACK_2_BYTE_PARAMETER(uuid);
+ if (short_uuid == BATTERY_SERVICE_UUID)
+ {
+ APP_DBG_MSG("ActiveLook_Client: Found Battery Service 0x%04X–0x%04X\n", startHdl, endHdl);
+ g_ctx.batteryServiceFound = 1;
+ g_ctx.batteryServiceStartHandle = startHdl;
+ g_ctx.batteryServiceEndHandle = endHdl;
+ }
+ }
+
+ idx += pr->Attribute_Data_Length;
+ }
+ }
+ }
+ break;
+
+ /**********************************************************************
+ * "Read by type response" => characteristics
+ *********************************************************************/
+ case ACI_ATT_READ_BY_TYPE_RESP_VSEVT_CODE:
+ {
+ if (g_ctx.discState == DISC_STATE_CHAR_IN_PROGRESS)
+ {
+ aci_att_read_by_type_resp_event_rp0 *pr =
+ (aci_att_read_by_type_resp_event_rp0*) blecore_evt->data;
+ uint8_t idx = 0;
+ while (idx < pr->Data_Length)
+ {
+ /* typical format: 2B decl handle, 1B props, 2B value handle, then the UUID */
+ uint16_t declHandle = UNPACK_2_BYTE_PARAMETER(&pr->Handle_Value_Pair_Data[idx]);
+ idx += 2;
+
+ uint8_t properties = pr->Handle_Value_Pair_Data[idx++];
+ uint16_t valHandle = UNPACK_2_BYTE_PARAMETER(&pr->Handle_Value_Pair_Data[idx]);
+ idx += 2;
+
+ /* length of the UUID depends on pr->Handle_Value_Pair_Length */
+ uint8_t uuidLen = pr->Handle_Value_Pair_Length - 5; // we used up 5 bytes so far
+ const uint8_t *uuid = &pr->Handle_Value_Pair_Data[idx];
+ idx += uuidLen;
+
+ if (g_ctx.whichService == SERVICE_ACTIVELOOK)
+ {
+ if ((uuidLen == 16) && (memcmp(uuid, ACTIVELK_RX_CHAR_UUID, 16) == 0))
+ {
+ APP_DBG_MSG("ActiveLook_Client: Found RxChar=0x%04X\n", valHandle);
+ g_ctx.rxCharFound = 1;
+ g_ctx.rxCharHandle = valHandle;
+ }
+ else if ((uuidLen == 16) && (memcmp(uuid, ACTIVELK_TX_CHAR_UUID, 16) == 0))
+ {
+ APP_DBG_MSG("ActiveLook_Client: Found TxChar=0x%04X\n", valHandle);
+ g_ctx.txCharFound = 1;
+ g_ctx.txCharHandle = valHandle;
+ }
+ else if ((uuidLen == 16) && (memcmp(uuid, ACTIVELK_CTRL_CHAR_UUID, 16) == 0))
+ {
+ APP_DBG_MSG("ActiveLook_Client: Found CtrlChar=0x%04X\n", valHandle);
+ g_ctx.ctrlCharFound = 1;
+ g_ctx.ctrlCharHandle = valHandle;
+ }
+ }
+ else if (g_ctx.whichService == SERVICE_BATTERY)
+ {
+ /* Compare to the known 16-bit Battery Level char UUID=0x2A19 */
+ if (uuidLen == 2)
+ {
+ uint16_t short_uuid = UNPACK_2_BYTE_PARAMETER(uuid);
+ if (short_uuid == BATTERY_LEVEL_CHAR_UUID)
+ {
+ APP_DBG_MSG("ActiveLook_Client: Found BatteryChar=0x%04X\n", valHandle);
+ g_ctx.batteryCharFound = 1;
+ g_ctx.batteryCharHandle = valHandle;
+ }
+ }
+ }
+ }
+ }
+ }
+ break;
+
+ /* Listen for notifications (including Battery Level) */
+ case ACI_GATT_NOTIFICATION_VSEVT_CODE:
+ {
+ aci_gatt_notification_event_rp0 *pNotif =
+ (aci_gatt_notification_event_rp0*) blecore_evt->data;
+
+ if (pNotif->Connection_Handle == g_ctx.connHandle)
+ {
+ /* If it’s from the battery char handle, parse it. It's a 1-byte value [0..100]. */
+ if (pNotif->Attribute_Handle == g_ctx.batteryCharHandle)
+ {
+ if (pNotif->Attribute_Value_Length >= 1)
+ {
+ g_ctx.lastBatteryPercent = pNotif->Attribute_Value[0];
+ APP_DBG_MSG("ActiveLook_Client: Battery notification => %d%%\n", g_ctx.lastBatteryPercent);
+ }
+ }
+ /* Parse ActiveLook TX notifications (command responses) */
+ if (g_ctx.txCharFound &&
+ pNotif->Attribute_Handle == g_ctx.txCharHandle &&
+ pNotif->Attribute_Value_Length >= 2)
+ {
+ uint8_t *d = pNotif->Attribute_Value;
+ uint16_t len = pNotif->Attribute_Value_Length;
+
+ /* cfgRead response: FF D1 [fmt] [len] [version:4] [counts:5] AA */
+ if (d[0] == 0xFF && d[1] == 0xD1 && g_ctx.cfgReadPending)
+ {
+ g_ctx.cfgReadPending = 0;
+ /* Parse version from payload (after header) */
+ uint8_t hdrLen = (d[2] & 0x20) ? 5 : 4;
+ if (len >= hdrLen + 4)
+ {
+ uint32_t ver = ((uint32_t)d[hdrLen] << 24) |
+ ((uint32_t)d[hdrLen+1] << 16) |
+ ((uint32_t)d[hdrLen+2] << 8) |
+ (uint32_t)d[hdrLen+3];
+ g_ctx.cfgReadVersion = ver;
+ APP_DBG_MSG("ActiveLook_Client: cfgRead version=%lu\n",
+ (unsigned long)ver);
+ if (g_ctx.cfgReadCallback)
+ g_ctx.cfgReadCallback(1, ver);
+ }
+ }
+ else if (d[0] == 0xFF && d[1] == 0xE2)
+ {
+ uint8_t errCmd = (len > 4) ? d[4] : 0;
+ uint8_t errCode = (len > 5) ? d[5] : 0;
+ FS_Log_WriteEvent("AL TX err: cmd=0x%02X code=%d", errCmd, errCode);
+
+ if (g_ctx.cfgReadPending)
+ {
+ g_ctx.cfgReadPending = 0;
+ if (g_ctx.cfgReadCallback)
+ g_ctx.cfgReadCallback(0, 0);
+ }
+ }
+ }
+ /* CTRL characteristic notifications (flow control) */
+ if (g_ctx.ctrlCharFound &&
+ pNotif->Attribute_Handle == g_ctx.ctrlCharHandle &&
+ pNotif->Attribute_Value_Length >= 1)
+ {
+ uint8_t val = pNotif->Attribute_Value[0];
+ switch (val)
+ {
+ case 0x01:
+ g_ctx.flowCtrlPaused = 0;
+ FS_Log_WriteEvent("AL CTRL: flow ON");
+ break;
+ case 0x02:
+ g_ctx.flowCtrlPaused = 1;
+ FS_Log_WriteEvent("AL CTRL: flow OFF");
+ break;
+ case 0x03:
+ FS_Log_WriteEvent("AL CTRL err: corrupt cmd");
+ break;
+ case 0x04:
+ FS_Log_WriteEvent("AL CTRL err: RX overflow");
+ break;
+ case 0x06:
+ FS_Log_WriteEvent("AL CTRL err: missing cfgWrite");
+ break;
+ default:
+ FS_Log_WriteEvent("AL CTRL unknown=0x%02X", val);
+ break;
+ }
+ }
+ }
+ }
+ break;
+
+ case ACI_ATT_FIND_INFO_RESP_VSEVT_CODE:
+ {
+ if (g_ctx.discState == DISC_STATE_DESC_IN_PROGRESS ||
+ g_ctx.discState == DISC_STATE_TX_DESC_IN_PROGRESS ||
+ g_ctx.discState == DISC_STATE_CTRL_DESC_IN_PROGRESS)
+ {
+ aci_att_find_info_resp_event_rp0 *resp =
+ (aci_att_find_info_resp_event_rp0 *)blecore_evt->data;
+
+ if (resp->Format == 0x01)
+ {
+ uint8_t numDesc = (resp->Event_Data_Length - 1) / 4;
+ uint8_t *ptr = resp->Handle_UUID_Pair;
+ for (uint8_t i = 0; i < numDesc; i++)
+ {
+ uint16_t descHandle = UNPACK_2_BYTE_PARAMETER(ptr);
+ ptr += 2;
+ uint16_t descUUID = UNPACK_2_BYTE_PARAMETER(ptr);
+ ptr += 2;
+
+ if (descUUID == 0x2902)
+ {
+ if (g_ctx.discState == DISC_STATE_DESC_IN_PROGRESS)
+ {
+ g_ctx.batteryCCCDHandle = descHandle;
+ APP_DBG_MSG("ActiveLook_Client: Found Battery CCCD=0x%04X\n", descHandle);
+ }
+ else if (g_ctx.discState == DISC_STATE_TX_DESC_IN_PROGRESS)
+ {
+ g_ctx.txCCCDHandle = descHandle;
+ APP_DBG_MSG("ActiveLook_Client: Found TX CCCD=0x%04X\n", descHandle);
+ }
+ else if (g_ctx.discState == DISC_STATE_CTRL_DESC_IN_PROGRESS)
+ {
+ g_ctx.ctrlCCCDHandle = descHandle;
+ APP_DBG_MSG("ActiveLook_Client: Found CTRL CCCD=0x%04X\n", descHandle);
+ }
+ }
+ }
+ }
+ }
+ }
+ break;
+
+ case ACI_ATT_READ_RESP_VSEVT_CODE:
+ {
+ aci_att_read_resp_event_rp0 *pRead =
+ (aci_att_read_resp_event_rp0*) blecore_evt->data;
+
+ /* Check if it's our connection */
+ if (pRead->Connection_Handle == g_ctx.connHandle)
+ {
+ /* If we had queued up a read to batteryCharHandle, interpret it now */
+ if (g_ctx.currentReadHandle == g_ctx.batteryCharHandle)
+ {
+ if (pRead->Event_Data_Length >= 1)
+ {
+ g_ctx.lastBatteryPercent = pRead->Attribute_Value[0];
+ APP_DBG_MSG("ActiveLook_Client: Immediate battery read => %d%%\n",
+ g_ctx.lastBatteryPercent);
+ }
+ }
+
+ /* Reset it so we don't confuse subsequent read responses. */
+ g_ctx.currentReadHandle = 0;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+/******************************************************************************
+ * Check if Rx handle is ready (ActiveLook commands)
+ ******************************************************************************/
+uint8_t FS_ActiveLook_Client_IsReady(void)
+{
+ return (g_ctx.rxCharFound && g_ctx.rxCharHandle != 0);
+}
+
+/******************************************************************************
+ * Write data to Rx characteristic (WWR)
+ ******************************************************************************/
+tBleStatus FS_ActiveLook_Client_WriteWithoutResp(const uint8_t *data, uint16_t length)
+{
+ if (!FS_ActiveLook_Client_IsReady())
+ {
+ APP_DBG_MSG("ActiveLook_Client: Not ready, no Rx handle.\n");
+ return BLE_STATUS_FAILED;
+ }
+
+ tBleStatus s = aci_gatt_write_without_resp(g_ctx.connHandle,
+ g_ctx.rxCharHandle,
+ length,
+ (uint8_t*)data);
+ if (s != BLE_STATUS_SUCCESS)
+ {
+ APP_DBG_MSG("ActiveLook_Client: WWR error=0x%02X\n", s);
+ }
+ return s;
+}
+
+/******************************************************************************
+ * Write data to Rx characteristic (Write With Response)
+ ******************************************************************************/
+tBleStatus FS_ActiveLook_Client_WriteWithResp(const uint8_t *data, uint16_t length)
+{
+ if (!FS_ActiveLook_Client_IsReady())
+ return BLE_STATUS_FAILED;
+
+ if (g_ctx.writePending)
+ return BLE_STATUS_BUSY;
+
+ tBleStatus s = aci_gatt_write_char_value(g_ctx.connHandle,
+ g_ctx.rxCharHandle,
+ length,
+ (uint8_t*)data);
+ if (s == BLE_STATUS_SUCCESS)
+ g_ctx.writePending = 1;
+
+ return s;
+}
+
+/******************************************************************************
+ * Enable battery notifications
+ ******************************************************************************/
+tBleStatus FS_ActiveLook_Client_EnableBatteryNotifications(void)
+{
+ if (!g_ctx.batteryCharFound || (g_ctx.batteryCharHandle == 0))
+ {
+ APP_DBG_MSG("ActiveLook_Client: No Battery char found!\n");
+ return BLE_STATUS_FAILED;
+ }
+
+ if (g_ctx.batteryCCCDHandle == 0)
+ {
+ APP_DBG_MSG("ActiveLook_Client: No Battery CCCD handle discovered!\n");
+ return BLE_STATUS_FAILED;
+ }
+
+ /* The BLE spec for notifications: 0x0001 in little-endian => 0x01,0x00 */
+ uint16_t enable = 0x0001;
+ tBleStatus s = aci_gatt_write_char_desc(
+ g_ctx.connHandle,
+ g_ctx.batteryCCCDHandle,
+ 2,
+ (uint8_t*)&enable);
+ if (s == BLE_STATUS_SUCCESS)
+ {
+ APP_DBG_MSG("ActiveLook_Client: Battery notifications enabled.\n");
+ }
+ else
+ {
+ APP_DBG_MSG("ActiveLook_Client: EnableBatteryNotifications fail=0x%02X\n", s);
+ }
+ return s;
+}
+
+/******************************************************************************
+ * Get the last known battery level
+ ******************************************************************************/
+uint8_t FS_ActiveLook_Client_GetBatteryLevel(void)
+{
+ return g_ctx.lastBatteryPercent;
+}
+
+void FS_ActiveLook_Client_ReadBatteryLevel(void)
+{
+ if (g_ctx.batteryCharFound && (g_ctx.batteryCharHandle != 0))
+ {
+ g_ctx.currentReadHandle = g_ctx.batteryCharHandle;
+ aci_gatt_read_char_value(g_ctx.connHandle, g_ctx.batteryCharHandle);
+ }
+}
+
+/******************************************************************************
+ * Send cfgRead command to query config version
+ ******************************************************************************/
+void FS_ActiveLook_Client_CfgRead(void (*callback)(uint8_t found, uint32_t version))
+{
+ g_ctx.cfgReadCallback = callback;
+ g_ctx.cfgReadPending = 1;
+
+ /* Build cfgRead frame: FF D1 00 [len] "FLYSIGHT\0\0\0\0" AA */
+ uint8_t packet[20];
+ uint8_t idx = 0;
+ packet[idx++] = 0xFF;
+ packet[idx++] = 0xD1;
+ packet[idx++] = 0x00;
+ uint8_t lenPos = idx++;
+
+ /* Config name padded to 12 bytes */
+ const char *name = "FLYSIGHT";
+ uint8_t nameLen = 8;
+ memcpy(&packet[idx], name, nameLen);
+ idx += nameLen;
+ for (uint8_t pad = nameLen; pad < 12; pad++)
+ packet[idx++] = 0x00;
+
+ packet[idx++] = 0xAA;
+ packet[lenPos] = idx;
+
+ FS_ActiveLook_Client_WriteWithoutResp(packet, idx);
+}
+
+uint8_t FS_ActiveLook_Client_CanSend(void)
+{
+ return !g_ctx.flowCtrlPaused;
+}
+
+void FS_ActiveLook_Client_SetUploadActive(uint8_t active)
+{
+ g_ctx.uploadActive = active;
+}
+
diff --git a/STM32_WPAN/App/activelook_client.h b/STM32_WPAN/App/activelook_client.h
new file mode 100644
index 0000000..15eb8bf
--- /dev/null
+++ b/STM32_WPAN/App/activelook_client.h
@@ -0,0 +1,84 @@
+/***************************************************************************
+** **
+** FlySight 2 firmware **
+** Copyright 2025 Bionic Avionics Inc. **
+** **
+** This program is free software: you can redistribute it and/or modify **
+** it under the terms of the GNU General Public License as published by **
+** the Free Software Foundation, either version 3 of the License, or **
+** (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, **
+** but WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program. If not, see . **
+** **
+****************************************************************************
+** Contact: Bionic Avionics Inc. **
+** Website: http://flysight.ca/ **
+****************************************************************************/
+
+#ifndef ACTIVELOOK_CLIENT_H
+#define ACTIVELOOK_CLIENT_H
+
+#include
+#include "ble_types.h"
+
+typedef struct
+{
+ void (*OnDiscoveryComplete)(void);
+} FS_ActiveLook_ClientCb_t;
+
+/* Initialize the ActiveLook client state machine */
+void FS_ActiveLook_Client_Init(void);
+
+/* Register optional callback interface */
+void FS_ActiveLook_Client_RegisterCb(const FS_ActiveLook_ClientCb_t *cb);
+
+/* Start discovery (including MTU exchange) after connecting */
+void FS_ActiveLook_Client_StartDiscovery(uint16_t connectionHandle);
+
+/* The main BLE event handler for ActiveLook */
+void FS_ActiveLook_Client_EventHandler(void *p_blecore_evt, uint8_t hci_event_evt_code);
+
+/* Check if ActiveLook Rx characteristic is ready */
+uint8_t FS_ActiveLook_Client_IsReady(void);
+
+/* Write data to Rx characteristic (WriteWithoutResp) */
+tBleStatus FS_ActiveLook_Client_WriteWithoutResp(const uint8_t *data, uint16_t length);
+
+/* Write data to Rx characteristic (Write With Response).
+ * Triggers ACI_GATT_PROC_COMPLETE when the glasses acknowledge.
+ * Returns BLE_STATUS_BUSY if a previous write is still pending. */
+tBleStatus FS_ActiveLook_Client_WriteWithResp(const uint8_t *data, uint16_t length);
+
+/**
+ * @brief Enable battery notifications for the standard Battery Service.
+ * This writes 0x01 to the CCC descriptor of the Battery Level char.
+ * Should be called after service/char discovery is complete.
+ *
+ * @return tBleStatus BLE_STATUS_SUCCESS or an error code.
+ */
+tBleStatus FS_ActiveLook_Client_EnableBatteryNotifications(void);
+
+/**
+ * @brief Get the last known battery level from the glasses (0–100%).
+ * @return uint8_t
+ */
+uint8_t FS_ActiveLook_Client_GetBatteryLevel(void);
+void FS_ActiveLook_Client_ReadBatteryLevel(void);
+
+/**
+ * @brief Send cfgRead command and register callback for the response.
+ * @param callback Called with (found=1, version) if config exists,
+ * or (found=0, 0) if config not found.
+ */
+void FS_ActiveLook_Client_CfgRead(void (*callback)(uint8_t found, uint32_t version));
+
+uint8_t FS_ActiveLook_Client_CanSend(void);
+void FS_ActiveLook_Client_SetUploadActive(uint8_t active);
+
+#endif /* ACTIVELOOK_CLIENT_H */
diff --git a/STM32_WPAN/App/app_ble.c b/STM32_WPAN/App/app_ble.c
index 0739060..386239b 100644
--- a/STM32_WPAN/App/app_ble.c
+++ b/STM32_WPAN/App/app_ble.c
@@ -44,6 +44,9 @@
/* USER CODE BEGIN Includes */
#include "common.h"
#include "state.h"
+#include "activelook.h"
+#include "activelook_client.h"
+#include "config.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
@@ -137,12 +140,6 @@ typedef struct _tBLEProfileGlobalContext
*/
uint16_t appearanceCharHandle;
- /**
- * connection handle of the current active connection
- * When not in connection, the handle is set to 0xFFFF
- */
- uint16_t connectionHandle;
-
/**
* length of the UUID list to be used while advertising
*/
@@ -160,11 +157,38 @@ typedef struct _tBLEProfileGlobalContext
typedef struct
{
BleGlobalContext_t BleApplicationContext_legacy;
- APP_BLE_ConnStatus_t Device_Connection_Status;
- /* USER CODE BEGIN PTD_1*/
- uint8_t Advertising_mgr_timer_Id;
- /* USER CODE END PTD_1 */
-}BleApplicationContext_t;
+ /**
+ * used to identify the GAP State
+ */
+ APP_BLE_ConnStatus_t SmartPhone_Connection_Status;
+
+ /**
+ * used to identify the GAP State
+ */
+ APP_BLE_ConnStatus_t EndDevice_Connection_Status[6];
+
+ /**
+ * connection handle with the Central connection (Smart Phone)
+ * When not in connection, the handle is set to 0xFFFF
+ */
+ uint16_t connectionHandleCentral;
+
+ /**
+ * connection handle with the Server 1 connection (End Device 1)
+ * When not in connection, the handle is set to 0xFFFF
+ */
+ uint16_t connectionHandleEndDevice1;
+
+ /**
+ * used when doing advertising to find end device 1
+ */
+ uint8_t EndDevice1Found;
+
+ /**
+ * address of end device 1
+ */
+ tBDAddr end_device_1_bdaddr;
+} BleApplicationContext_t;
/* USER CODE BEGIN PTD */
@@ -226,12 +250,17 @@ uint8_t a_AdvData[15] =
};
/* USER CODE BEGIN PV */
+uint8_t Advertising_mgr_timer_Id;
+
/* Advertising callback */
void (*Adv_Callback)(void) = 0;
void (*Next_Adv_Callback)(void) = 0;
/* Pairing request flag */
uint8_t request_pairing = 0;
+
+/* BD Address of device to be connected once discovered */
+tBDAddr P2P_SERVER1_BDADDR;
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
@@ -253,6 +282,9 @@ static void Adv_Update_Req(void);
static void Adv_Update(void);
static void APP_BLE_UpdateAdvertisingData(APP_BLE_ConnStatus_t NewStatus);
static int8_t ble_count_bonded_devices(void);
+static void Scan_Request(void);
+static void Connect_Request(void);
+static void Disconnect_Request(void);
/* USER CODE END PFP */
/* External variables --------------------------------------------------------*/
@@ -349,8 +381,9 @@ void APP_BLE_Init(void)
/**
* Initialization of the BLE App Context
*/
- BleApplicationContext.Device_Connection_Status = APP_BLE_IDLE;
- BleApplicationContext.BleApplicationContext_legacy.connectionHandle = 0xFFFF;
+ BleApplicationContext.SmartPhone_Connection_Status = APP_BLE_IDLE;
+ BleApplicationContext.EndDevice_Connection_Status[0] = APP_BLE_IDLE;
+ BleApplicationContext.EndDevice1Found = 0x00;
/**
* From here, all initialization are BLE application specific
@@ -361,6 +394,9 @@ void APP_BLE_Init(void)
/* USER CODE BEGIN APP_BLE_Init_4 */
UTIL_SEQ_RegTask(1<evt)
{
case HCI_DISCONNECTION_COMPLETE_EVT_CODE:
- {
p_disconnection_complete_event = (hci_disconnection_complete_event_rp0 *) p_event_pckt->data;
- if (p_disconnection_complete_event->Connection_Handle == BleApplicationContext.BleApplicationContext_legacy.connectionHandle)
- {
- BleApplicationContext.BleApplicationContext_legacy.connectionHandle = 0;
- BleApplicationContext.Device_Connection_Status = APP_BLE_IDLE;
- APP_DBG_MSG(">>== HCI_DISCONNECTION_COMPLETE_EVT_CODE\n");
- APP_DBG_MSG(" - Connection Handle: 0x%x\n - Reason: 0x%x\n\r",
- p_disconnection_complete_event->Connection_Handle,
- p_disconnection_complete_event->Reason);
-
- /* USER CODE BEGIN EVT_DISCONN_COMPLETE_2 */
+ /* USER CODE BEGIN EVT_DISCONN_COMPLETE */
- /* USER CODE END EVT_DISCONN_COMPLETE_2 */
+ /* USER CODE END EVT_DISCONN_COMPLETE */
+ if (p_disconnection_complete_event->Connection_Handle == BleApplicationContext.connectionHandleEndDevice1)
+ {
+ APP_DBG_MSG("\r\n\r** DISCONNECTION EVENT OF END DEVICE 1 \n\r");
+ BleApplicationContext.EndDevice_Connection_Status[0] = APP_BLE_IDLE;
+ BleApplicationContext.connectionHandleEndDevice1 = 0xFFFF;
+ if (FS_ActiveLook_IsActive())
+ {
+ UTIL_SEQ_SetTask(1 << CFG_TASK_START_SCAN_ID, CFG_SCH_PRIO_0);
+ }
}
- /* USER CODE BEGIN EVT_DISCONN_COMPLETE_1 */
-#if 0
- /* USER CODE END EVT_DISCONN_COMPLETE_1 */
-
- /* restart advertising */
- Adv_Request(APP_BLE_FAST_ADV);
+ if (p_disconnection_complete_event->Connection_Handle == BleApplicationContext.connectionHandleCentral)
+ {
+ APP_DBG_MSG("\r\n\r** DISCONNECTION EVENT OF SMART PHONE \n\r");
+ BleApplicationContext.SmartPhone_Connection_Status = APP_BLE_IDLE;
+ BleApplicationContext.connectionHandleCentral = 0xFFFF;
- /**
- * SPECIFIC to Custom Template APP
- */
- HandleNotification.Custom_Evt_Opcode = CUSTOM_DISCON_HANDLE_EVT;
- HandleNotification.ConnectionHandle = BleApplicationContext.BleApplicationContext_legacy.connectionHandle;
- Custom_APP_Notification(&HandleNotification);
- /* USER CODE BEGIN EVT_DISCONN_COMPLETE */
-#endif
- /* restart advertising */
- FS_Adv_Request(APP_BLE_FAST_ADV);
+ if (FS_State_Get()->enable_ble)
+ {
+ /* restart advertising */
+ FS_Adv_Request(APP_BLE_FAST_ADV);
+ }
- /**
- * SPECIFIC to Custom Template APP
- */
- HandleNotification.Custom_Evt_Opcode = CUSTOM_DISCON_HANDLE_EVT;
- HandleNotification.ConnectionHandle = BleApplicationContext.BleApplicationContext_legacy.connectionHandle;
- Custom_APP_Notification(&HandleNotification);
- /* USER CODE END EVT_DISCONN_COMPLETE */
+ HandleNotification.Custom_Evt_Opcode = CUSTOM_DISCON_HANDLE_EVT;
+ HandleNotification.ConnectionHandle = 0xFFFF;
+ Custom_APP_Notification(&HandleNotification);
+ }
break; /* HCI_DISCONNECTION_COMPLETE_EVT_CODE */
- }
case HCI_LE_META_EVT_CODE:
{
@@ -516,59 +549,141 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void *p_Pckt)
break;
case HCI_LE_ENHANCED_CONNECTION_COMPLETE_SUBEVT_CODE:
- {
p_enhanced_connection_complete_event = (hci_le_enhanced_connection_complete_event_rp0 *) p_meta_evt->data;
/**
* The connection is done, there is no need anymore to schedule the LP ADV
*/
- APP_DBG_MSG(">>== HCI_LE_ENHANCED_CONNECTION_COMPLETE_SUBEVT_CODE - Connection handle: 0x%x\n", p_enhanced_connection_complete_event->Connection_Handle);
- APP_DBG_MSG(" - Connection established with Central: @:%02x:%02x:%02x:%02x:%02x:%02x\n",
- p_enhanced_connection_complete_event->Peer_Address[5],
- p_enhanced_connection_complete_event->Peer_Address[4],
- p_enhanced_connection_complete_event->Peer_Address[3],
- p_enhanced_connection_complete_event->Peer_Address[2],
- p_enhanced_connection_complete_event->Peer_Address[1],
- p_enhanced_connection_complete_event->Peer_Address[0]);
- APP_DBG_MSG(" - Connection Interval: %.2f ms\n - Connection latency: %d\n - Supervision Timeout: %d ms\n\r",
- p_enhanced_connection_complete_event->Conn_Interval*1.25,
- p_enhanced_connection_complete_event->Conn_Latency,
- p_enhanced_connection_complete_event->Supervision_Timeout*10
- );
-
- if (BleApplicationContext.Device_Connection_Status == APP_BLE_LP_CONNECTING)
- {
- /* Connection as client */
- BleApplicationContext.Device_Connection_Status = APP_BLE_CONNECTED_CLIENT;
+ connection_handle = p_enhanced_connection_complete_event->Connection_Handle;
+ role = p_enhanced_connection_complete_event->Role;
+ if (role == 0x00)
+ { /* ROLE CENTRAL */
+ APP_DBG_MSG("-- CONNECTION SUCCESS WITH END DEVICE 1\n\r");
+ BleApplicationContext.EndDevice_Connection_Status[0] = APP_BLE_CONNECTED;
+ BleApplicationContext.connectionHandleEndDevice1 = connection_handle;
+ FS_ActiveLook_Client_StartDiscovery(connection_handle);
}
else
{
- /* Connection as server */
- BleApplicationContext.Device_Connection_Status = APP_BLE_CONNECTED_SERVER;
+ APP_DBG_MSG("-- CONNECTION SUCCESS WITH SMART PHONE\n\r");
+ BleApplicationContext.SmartPhone_Connection_Status = APP_BLE_CONNECTED;
+ BleApplicationContext.connectionHandleCentral = connection_handle;
+ HandleNotification.Custom_Evt_Opcode = CUSTOM_CONN_HANDLE_EVT;
+ HandleNotification.ConnectionHandle = connection_handle;
+ Custom_APP_Notification(&HandleNotification);
+
+ /* Stop the timer */
+ HW_TS_Stop(Advertising_mgr_timer_Id);
+
+ /* Call advertising callback */
+ if (Adv_Callback) Adv_Callback();
+
+ /* Update advertising callback */
+ Adv_Callback = Next_Adv_Callback;
+ Next_Adv_Callback = 0;
+
+ /* Configure the link */
+ UTIL_SEQ_SetTask(1 << CFG_TASK_LINK_CONFIG_ID, CFG_SCH_PRIO_1);
}
- BleApplicationContext.BleApplicationContext_legacy.connectionHandle = p_enhanced_connection_complete_event->Connection_Handle;
- /**
- * SPECIFIC to Custom Template APP
- */
- HandleNotification.Custom_Evt_Opcode = CUSTOM_CONN_HANDLE_EVT;
- HandleNotification.ConnectionHandle = BleApplicationContext.BleApplicationContext_legacy.connectionHandle;
- Custom_APP_Notification(&HandleNotification);
- /* USER CODE BEGIN HCI_LE_ENHANCED_CONNECTION_COMPLETE_SUBEVT_CODE */
- /* Stop the timer */
- HW_TS_Stop(BleApplicationContext.Advertising_mgr_timer_Id);
-
- /* Call advertising callback */
- if (Adv_Callback) Adv_Callback();
-
- /* Update advertising callback */
- Adv_Callback = Next_Adv_Callback;
- Next_Adv_Callback = 0;
-
- /* Configure the link */
- UTIL_SEQ_SetTask(1 << CFG_TASK_LINK_CONFIG_ID, CFG_SCH_PRIO_1);
- /* USER CODE END HCI_LE_ENHANCED_CONNECTION_COMPLETE_SUBEVT_CODE */
- break; /* HCI_LE_ENHANCED_CONNECTION_COMPLETE_SUBEVT_CODE */
- }
+ break; /* HCI_LE_CONNECTION_COMPLETE_SUBEVT_CODE */
+
+ case HCI_LE_ADVERTISING_REPORT_SUBEVT_CODE:
+ /* USER CODE BEGIN EVT_LE_ADVERTISING_REPORT */
+
+ /* USER CODE END EVT_LE_ADVERTISING_REPORT */
+ le_advertising_event = (hci_le_advertising_report_event_rp0 *) p_meta_evt->data;
+
+ event_type = le_advertising_event->Advertising_Report[0].Event_Type;
+
+ event_data_size = le_advertising_event->Advertising_Report[0].Length_Data;
+
+ /* WARNING: be careful when decoding advertising report as its raw format cannot be mapped on a C structure.
+ The data and RSSI values could not be directly decoded from the RAM using the data and RSSI field from hci_le_advertising_report_event_rp0 structure.
+ Instead they must be read by using offsets (please refer to BLE specification).
+ RSSI = (int8_t)*(uint8_t*) (adv_report_data + le_advertising_event->Advertising_Report[0].Length_Data);
+ */
+ adv_report_data = (uint8_t*)(&le_advertising_event->Advertising_Report[0].Length_Data) + 1;
+ k = 0;
+
+ // Define the target device name we are looking for
+ char targetDeviceName[14];
+ snprintf(targetDeviceName, sizeof(targetDeviceName),
+ "ENGO 2 %.*s", 6, FS_Config_Get()->al_id);
+ const size_t targetDeviceNameLen = strlen(targetDeviceName);
+
+ // Flags to track if we found the required data within *this specific* advertising report
+ uint8_t foundMfgData = 0; // Flag for correct manufacturer data
+ uint8_t foundDeviceName = 0; // Flag for correct device name
+
+ /* search AD TYPE 0x09 (Complete Local Name) */
+ /* search AD Type 0x02 (16 bits UUIDS) */
+ if (event_type == ADV_IND)
+ {
+ while(k < event_data_size)
+ {
+ adlength = adv_report_data[k];
+ adtype = adv_report_data[k + 1];
+ if (adtype == AD_TYPE_MANUFACTURER_SPECIFIC_DATA)
+ {
+ /* The payload starts at adv_report_data[k + 2], and its length is (adlength - 1). */
+ const uint8_t *mfg_data = &adv_report_data[k + 2];
+ uint8_t mfg_len = adlength - 1; // total bytes in manufacturer data
+
+ /* The ActiveLook doc says we should see 0x08F2 at the end.
+ For example: 0xDAFA08F2 or some variation. So let's do: */
+ if (mfg_len >= 2)
+ {
+ /* Check the last two bytes of manufacturer data. */
+ uint16_t last2 = (mfg_data[mfg_len - 2] << 8) | mfg_data[mfg_len - 1];
+ if (last2 == 0x08F2)
+ {
+ APP_DBG_MSG("-- Found ActiveLook device (mfg data ends with 0x08F2)\n\r");
+ foundMfgData = 1; // Set flag indicating manufacturer data is correct
+ }
+ }
+ }
+ else if (adtype == AD_TYPE_COMPLETE_LOCAL_NAME || adtype == AD_TYPE_SHORTENED_LOCAL_NAME)
+ {
+ const uint8_t *name_data = &adv_report_data[k + 2];
+ uint8_t name_len = adlength - 1;
+
+ APP_DBG_MSG("-- Found Device Name: '");
+ for(int i=0; iAdvertising_Report[0].Address[0];
+ P2P_SERVER1_BDADDR[1] = le_advertising_event->Advertising_Report[0].Address[1];
+ P2P_SERVER1_BDADDR[2] = le_advertising_event->Advertising_Report[0].Address[2];
+ P2P_SERVER1_BDADDR[3] = le_advertising_event->Advertising_Report[0].Address[3];
+ P2P_SERVER1_BDADDR[4] = le_advertising_event->Advertising_Report[0].Address[4];
+ P2P_SERVER1_BDADDR[5] = le_advertising_event->Advertising_Report[0].Address[5];
+
+ APP_DBG_MSG(" Address: %02X:%02X:%02X:%02X:%02X:%02X\n\r",
+ P2P_SERVER1_BDADDR[5], P2P_SERVER1_BDADDR[4], P2P_SERVER1_BDADDR[3],
+ P2P_SERVER1_BDADDR[2], P2P_SERVER1_BDADDR[1], P2P_SERVER1_BDADDR[0]);
+
+ aci_gap_terminate_gap_proc(GAP_GENERAL_DISCOVERY_PROC);
+ }
+ } /* end if (event_type == ADV_IND) */
+ break;
default:
/* USER CODE BEGIN SUBEVENT_DEFAULT */
@@ -597,6 +712,31 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void *p_Pckt)
/**
* SPECIFIC to Custom Template APP
*/
+ case ACI_L2CAP_CONNECTION_UPDATE_REQ_VSEVT_CODE:
+ {
+ /* USER CODE BEGIN EVT_BLUE_L2CAP_CONNECTION_UPDATE_REQ */
+
+ /* USER CODE END EVT_BLUE_L2CAP_CONNECTION_UPDATE_REQ */
+ pr = (aci_l2cap_connection_update_req_event_rp0 *) p_blecore_evt->data;
+ result = aci_l2cap_connection_parameter_update_resp(pr->Connection_Handle,
+ pr->Interval_Min,
+ pr->Interval_Max,
+ pr->Latency,
+ pr->Timeout_Multiplier,
+ CONN_L1,
+ CONN_L2,
+ pr->Identifier,
+ 0x00);
+ APP_DBG_MSG("\r\n\r** NO UPDATE \n\r");
+ if(result != BLE_STATUS_SUCCESS)
+ {
+ /* USER CODE BEGIN BLE_STATUS_SUCCESS */
+
+ /* USER CODE END BLE_STATUS_SUCCESS */
+ }
+ }
+ break;
+
case ACI_L2CAP_CONNECTION_UPDATE_RESP_VSEVT_CODE:
#if (L2CAP_REQUEST_NEW_CONN_PARAM != 0)
mutex = 1;
@@ -607,11 +747,35 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void *p_Pckt)
break;
case ACI_GAP_PROC_COMPLETE_VSEVT_CODE:
- APP_DBG_MSG(">>== ACI_GAP_PROC_COMPLETE_VSEVT_CODE \r");
- /* USER CODE BEGIN EVT_BLUE_GAP_PROCEDURE_COMPLETE */
-
- /* USER CODE END EVT_BLUE_GAP_PROCEDURE_COMPLETE */
- break; /* ACI_GAP_PROC_COMPLETE_VSEVT_CODE */
+ {
+ /* USER CODE BEGIN EVT_BLUE_GAP_PROCEDURE_COMPLETE */
+
+ /* USER CODE END EVT_BLUE_GAP_PROCEDURE_COMPLETE */
+ aci_gap_proc_complete_event_rp0 *gap_evt_proc_complete = (void*) p_blecore_evt->data;
+ /* CHECK GAP GENERAL DISCOVERY PROCEDURE COMPLETED & SUCCEED */
+ if (gap_evt_proc_complete->Procedure_Code == GAP_GENERAL_DISCOVERY_PROC
+ && gap_evt_proc_complete->Status == 0x00)
+ {
+ /* USER CODE BEGIN GAP_GENERAL_DISCOVERY_PROC */
+
+ /* USER CODE END GAP_GENERAL_DISCOVERY_PROC */
+
+ APP_DBG_MSG("-- GAP GENERAL DISCOVERY PROCEDURE_COMPLETED\n\r");
+ /*if a device found, connect to it, device 1 being chosen first if both found*/
+ if (BleApplicationContext.EndDevice1Found == 0x01
+ && BleApplicationContext.EndDevice_Connection_Status[0] != APP_BLE_CONNECTED)
+ {
+ APP_DBG_MSG("-- Setting task CFG_TASK_CONN_DEV_1_ID\n\r");
+ UTIL_SEQ_SetTask(1 << CFG_TASK_CONN_DEV_1_ID, CFG_SCH_PRIO_0);
+ }
+#if (CFG_P2P_DEMO_MULTI != 0)
+ /* USER CODE BEGIN EVT_BLUE_GAP_PROCEDURE_COMPLETE_Multi */
+
+ /* USER CODE END EVT_BLUE_GAP_PROCEDURE_COMPLETE_Multi */
+#endif
+ }
+ }
+ break; /* ACI_GAP_PAIRING_COMPLETE_VSEVT_CODE */
#if (RADIO_ACTIVITY_EVENT != 0)
case ACI_HAL_END_OF_RADIO_ACTIVITY_VSEVT_CODE:
@@ -632,7 +796,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void *p_Pckt)
case ACI_GAP_PASS_KEY_REQ_VSEVT_CODE:
APP_DBG_MSG(">>== ACI_GAP_PASS_KEY_REQ_VSEVT_CODE \n");
- ret = aci_gap_pass_key_resp(BleApplicationContext.BleApplicationContext_legacy.connectionHandle, CFG_FIXED_PIN);
+ ret = aci_gap_pass_key_resp(BleApplicationContext.connectionHandleCentral, CFG_FIXED_PIN);
if (ret != BLE_STATUS_SUCCESS)
{
APP_DBG_MSG("==>> aci_gap_pass_key_resp : Fail, reason: 0x%x\n", ret);
@@ -652,7 +816,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void *p_Pckt)
((aci_gap_numeric_comparison_value_event_rp0 *)(p_blecore_evt->data))->Numeric_Value);
APP_DBG_MSG(" - Hex_value = %lx\n",
((aci_gap_numeric_comparison_value_event_rp0 *)(p_blecore_evt->data))->Numeric_Value);
- ret = aci_gap_numeric_comparison_value_confirm_yesno(BleApplicationContext.BleApplicationContext_legacy.connectionHandle, YES);
+ ret = aci_gap_numeric_comparison_value_confirm_yesno(BleApplicationContext.connectionHandleCentral, YES);
if (ret != BLE_STATUS_SUCCESS)
{
APP_DBG_MSG("==>> aci_gap_numeric_comparison_value_confirm_yesno-->YES : Fail, reason: 0x%x\n", ret);
@@ -688,7 +852,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void *p_Pckt)
case ACI_GATT_INDICATION_VSEVT_CODE:
{
APP_DBG_MSG(">>== ACI_GATT_INDICATION_VSEVT_CODE \r");
- aci_gatt_confirm_indication(BleApplicationContext.BleApplicationContext_legacy.connectionHandle);
+ aci_gatt_confirm_indication(BleApplicationContext.connectionHandleCentral);
}
break;
@@ -698,6 +862,10 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void *p_Pckt)
break;
/* USER CODE END BLUE_EVT */
}
+
+ /* AFTER you handle your own cases, also pass the event to the ActiveLook client. */
+ FS_ActiveLook_Client_EventHandler((void*)p_blecore_evt, HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE);
+
break; /* HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE */
/* USER CODE BEGIN EVENT_PCKT */
@@ -716,7 +884,29 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void *p_Pckt)
APP_BLE_ConnStatus_t APP_BLE_Get_Server_Connection_Status(void)
{
- return BleApplicationContext.Device_Connection_Status;
+ return BleApplicationContext.SmartPhone_Connection_Status;
+}
+
+APP_BLE_ConnStatus_t APP_BLE_Get_Client_Connection_Status(uint16_t Connection_Handle)
+{
+ /* USER CODE BEGIN APP_BLE_Get_Client_Connection_Status_1 */
+
+ /* USER CODE END APP_BLE_Get_Client_Connection_Status_1 */
+ APP_BLE_ConnStatus_t return_value;
+
+ if (BleApplicationContext.connectionHandleEndDevice1 == Connection_Handle)
+ {
+ return_value = BleApplicationContext.EndDevice_Connection_Status[0];
+ }
+ else
+ {
+ return_value = APP_BLE_IDLE;
+ }
+ /* USER CODE BEGIN APP_BLE_Get_Client_Connection_Status_2 */
+
+ /* USER CODE END APP_BLE_Get_Client_Connection_Status_2 */
+
+ return (return_value);
}
/* USER CODE BEGIN FD*/
@@ -1000,7 +1190,7 @@ static void Adv_Request(APP_BLE_ConnStatus_t NewStatus)
{
tBleStatus ret = BLE_STATUS_INVALID_PARAMS;
- BleApplicationContext.Device_Connection_Status = NewStatus;
+ BleApplicationContext.SmartPhone_Connection_Status = NewStatus;
/* Start Fast or Low Power Advertising */
ret = aci_gap_set_discoverable(ADV_TYPE,
CFG_FAST_CONN_ADV_INTERVAL_MIN,
@@ -1055,13 +1245,13 @@ static void Adv_Cancel(void)
/* USER CODE END Adv_Cancel_1 */
- if (BleApplicationContext.Device_Connection_Status != APP_BLE_CONNECTED_SERVER)
+ if (BleApplicationContext.SmartPhone_Connection_Status != APP_BLE_CONNECTED)
{
tBleStatus ret = BLE_STATUS_INVALID_PARAMS;
ret = aci_gap_set_non_discoverable();
- BleApplicationContext.Device_Connection_Status = APP_BLE_IDLE;
+ BleApplicationContext.SmartPhone_Connection_Status = APP_BLE_IDLE;
if (ret != BLE_STATUS_SUCCESS)
{
APP_DBG_MSG("** STOP ADVERTISING ** Failed \r\n\r");
@@ -1097,7 +1287,7 @@ void BLE_SVC_L2CAP_Conn_Update(uint16_t ConnectionHandle)
uint16_t timeout_multiplier = L2CAP_TIMEOUT_MULTIPLIER;
tBleStatus ret;
- ret = aci_l2cap_connection_parameter_update_req(BleApplicationContext.BleApplicationContext_legacy.connectionHandle,
+ ret = aci_l2cap_connection_parameter_update_req(BleApplicationContext.connectionHandleCentral,
interval_min, interval_max,
peripheral_latency, timeout_multiplier);
if (ret != BLE_STATUS_SUCCESS)
@@ -1121,9 +1311,9 @@ void BLE_SVC_L2CAP_Conn_Update(uint16_t ConnectionHandle)
#if (L2CAP_REQUEST_NEW_CONN_PARAM != 0)
static void Connection_Interval_Update_Req(void)
{
- if (BleApplicationContext.Device_Connection_Status != APP_BLE_FAST_ADV && BleApplicationContext.Device_Connection_Status != APP_BLE_IDLE)
+ if (BleApplicationContext.SmartPhone_Connection_Status != APP_BLE_FAST_ADV && BleApplicationContext.SmartPhone_Connection_Status != APP_BLE_IDLE)
{
- BLE_SVC_L2CAP_Conn_Update(BleApplicationContext.BleApplicationContext_legacy.connectionHandle);
+ BLE_SVC_L2CAP_Conn_Update(BleApplicationContext.connectionHandleCentral);
}
return;
@@ -1137,7 +1327,7 @@ static void LinkConfiguration(void)
/* See AN5289: How to maximize data throughput */
APP_DBG_MSG("set data length \n");
- status = hci_le_set_data_length(BleApplicationContext.BleApplicationContext_legacy.connectionHandle,251,2120);
+ status = hci_le_set_data_length(BleApplicationContext.connectionHandleCentral,251,2120);
if (status != BLE_STATUS_SUCCESS)
{
APP_DBG_MSG("set data length command error \n");
@@ -1153,13 +1343,13 @@ static void FS_Adv_Request(APP_BLE_ConnStatus_t NewStatus)
if (!FS_State_Get()->enable_ble)
{
/* Ensure radio is off and state is consistent */
- if ((BleApplicationContext.Device_Connection_Status == APP_BLE_FAST_ADV)
- || (BleApplicationContext.Device_Connection_Status == APP_BLE_LP_ADV))
+ if ((BleApplicationContext.SmartPhone_Connection_Status == APP_BLE_FAST_ADV)
+ || (BleApplicationContext.SmartPhone_Connection_Status == APP_BLE_LP_ADV))
{
aci_gap_set_non_discoverable();
}
- HW_TS_Stop(BleApplicationContext.Advertising_mgr_timer_Id);
- BleApplicationContext.Device_Connection_Status = APP_BLE_IDLE;
+ HW_TS_Stop(Advertising_mgr_timer_Id);
+ BleApplicationContext.SmartPhone_Connection_Status = APP_BLE_IDLE;
return;
}
/* ===== END GATEKEEPER ===== */
@@ -1179,10 +1369,10 @@ static void FS_Adv_Request(APP_BLE_ConnStatus_t NewStatus)
* Stop the timer, it will be restarted for a new shot
* It does not hurt if the timer was not running
*/
- HW_TS_Stop(BleApplicationContext.Advertising_mgr_timer_Id);
+ HW_TS_Stop(Advertising_mgr_timer_Id);
- if ((BleApplicationContext.Device_Connection_Status == APP_BLE_FAST_ADV)
- || (BleApplicationContext.Device_Connection_Status == APP_BLE_LP_ADV))
+ if ((BleApplicationContext.SmartPhone_Connection_Status == APP_BLE_FAST_ADV)
+ || (BleApplicationContext.SmartPhone_Connection_Status == APP_BLE_LP_ADV))
{
/* Connection in ADVERTISE mode have to stop the current advertising */
ret = aci_gap_set_non_discoverable();
@@ -1203,7 +1393,7 @@ static void FS_Adv_Request(APP_BLE_ConnStatus_t NewStatus)
Adv_Callback = Next_Adv_Callback;
Next_Adv_Callback = 0;
- BleApplicationContext.Device_Connection_Status = NewStatus;
+ BleApplicationContext.SmartPhone_Connection_Status = NewStatus;
/**
* Prepare white list as described in PM0271 5.3.1
@@ -1258,7 +1448,7 @@ static void FS_Adv_Request(APP_BLE_ConnStatus_t NewStatus)
if (NewStatus == APP_BLE_FAST_ADV)
{
- HW_TS_Start(BleApplicationContext.Advertising_mgr_timer_Id, FAST_ADV_TIMEOUT);
+ HW_TS_Start(Advertising_mgr_timer_Id, FAST_ADV_TIMEOUT);
}
return;
@@ -1295,7 +1485,7 @@ void APP_BLE_CancelPairing(void)
Next_Adv_Callback = 0;
request_pairing = 0;
- if (BleApplicationContext.Device_Connection_Status == APP_BLE_FAST_ADV)
+ if (BleApplicationContext.SmartPhone_Connection_Status == APP_BLE_FAST_ADV)
{
/* Request low power advertising */
FS_Adv_Request(APP_BLE_LP_ADV);
@@ -1336,7 +1526,7 @@ void APP_BLE_UpdateDeviceName(void)
}
/* Update advertising data */
- APP_BLE_UpdateAdvertisingData(BleApplicationContext.Device_Connection_Status);
+ APP_BLE_UpdateAdvertisingData(BleApplicationContext.SmartPhone_Connection_Status);
}
void APP_BLE_Reset(void)
@@ -1355,7 +1545,7 @@ void APP_BLE_Reset(void)
}
/* Re-initialize advertising */
- FS_Adv_Request(BleApplicationContext.Device_Connection_Status);
+ FS_Adv_Request(BleApplicationContext.SmartPhone_Connection_Status);
}
static void APP_BLE_UpdateAdvertisingData(APP_BLE_ConnStatus_t NewStatus)
{
@@ -1461,6 +1651,124 @@ static int8_t ble_count_bonded_devices(void)
APP_DBG_MSG("Resolving List populated with %u bonded device(s)\r\n", total);
return (int8_t)total;
}
+
+/**
+ * @brief Scan Request
+ * @param None
+ * @retval None
+ */
+static void Scan_Request(void)
+{
+ /* USER CODE BEGIN Scan_Request_1 */
+
+ /* USER CODE END Scan_Request_1 */
+ tBleStatus result;
+
+ if (BleApplicationContext.EndDevice_Connection_Status[0] != APP_BLE_CONNECTED)
+ {
+ /* USER CODE BEGIN APP_BLE_CONNECTED */
+ BleApplicationContext.EndDevice1Found = 0x00;
+ /* USER CODE END APP_BLE_CONNECTED */
+ result = aci_gap_start_general_discovery_proc(SCAN_P,
+ SCAN_L,
+ CFG_BLE_ADDRESS_TYPE,
+ 1);
+ if (result == BLE_STATUS_SUCCESS)
+ {
+ /* USER CODE BEGIN BLE_SCAN_SUCCESS */
+
+ /* USER CODE END BLE_SCAN_SUCCESS */
+ APP_DBG_MSG(" \r\n\r** START GENERAL DISCOVERY (SCAN) ** \r\n\r");
+ }
+ else
+ {
+ /* USER CODE BEGIN BLE_SCAN_FAILED */
+
+ /* USER CODE END BLE_SCAN_FAILED */
+ APP_DBG_MSG("-- BLE_App_Start_Limited_Disc_Req, Failed %02x \r\n\r", result);
+ }
+ }
+ /* USER CODE BEGIN Scan_Request_2 */
+
+ /* USER CODE END Scan_Request_2 */
+
+ return;
+}
+
+/**
+ * @brief Connection Establishement on SERVER 1
+ * @param None
+ * @retval None
+ */
+static void Connect_Request(void)
+{
+ tBleStatus result;
+ APP_DBG_MSG("\r\n\r** CREATE CONNECTION TO END DEVICE 1 ** \r\n\r");
+ if (BleApplicationContext.EndDevice_Connection_Status[0] != APP_BLE_CONNECTED)
+ {
+ /* USER CODE BEGIN APP_BLE_CONNECTED_SUCCESS_END_DEVICE_1 */
+
+ /* USER CODE END APP_BLE_CONNECTED_SUCCESS_END_DEVICE_1 */
+ result = aci_gap_create_connection(SCAN_P,
+ SCAN_L,
+ GAP_PUBLIC_ADDR,
+ P2P_SERVER1_BDADDR,
+ CFG_BLE_ADDRESS_TYPE,
+ 0x0006,
+ 0x0006,
+ 0,
+ SUPERV_TIMEOUT,
+ CONN_L1,
+ CONN_L2);
+
+ if (result == BLE_STATUS_SUCCESS)
+ {
+ /* USER CODE BEGIN BLE_STATUS_END_DEVICE_1_SUCCESS */
+
+ /* USER CODE END BLE_STATUS_END_DEVICE_1_SUCCESS */
+ BleApplicationContext.EndDevice_Connection_Status[0] = APP_BLE_CONNECTING;
+ APP_DBG_MSG("==> Connect_Request Succeeded \n\r");
+ }
+ else
+ {
+ /* USER CODE BEGIN BLE_STATUS_END_DEVICE_1_FAILED */
+
+ /* USER CODE END BLE_STATUS_END_DEVICE_1_FAILED */
+ BleApplicationContext.EndDevice_Connection_Status[0] = APP_BLE_IDLE;
+ APP_DBG_MSG("==> Connect_Request Failed \n\r");
+ }
+ }
+
+ return;
+}
+
+/**
+ * @brief Disconnect on SERVER 1
+ * @param None
+ * @retval None
+ */
+static void Disconnect_Request(void)
+{
+ tBleStatus result;
+ uint16_t connection_handle = BleApplicationContext.connectionHandleEndDevice1;
+
+ APP_DBG_MSG("\r\n\r** DISCONNECT FROM END DEVICE 1 ** \r\n\r");
+ if (BleApplicationContext.EndDevice_Connection_Status[0] == APP_BLE_CONNECTED)
+ {
+ result = aci_gap_terminate(connection_handle, 0x13);
+ if (result == BLE_STATUS_SUCCESS)
+ {
+ BleApplicationContext.EndDevice_Connection_Status[0] = APP_BLE_IDLE;
+ APP_DBG_MSG("Disconnection request sent successfully.\n");
+ }
+ else
+ {
+ BleApplicationContext.EndDevice_Connection_Status[0] = APP_BLE_IDLE;
+ APP_DBG_MSG("Failed to send disconnection request. Error: 0x%02X\n", result);
+ }
+ }
+}
+
/* USER CODE END FD_SPECIFIC_FUNCTIONS */
/*************************************************************
*
diff --git a/STM32_WPAN/App/app_ble.h b/STM32_WPAN/App/app_ble.h
index 70a6f1e..6363f2c 100644
--- a/STM32_WPAN/App/app_ble.h
+++ b/STM32_WPAN/App/app_ble.h
@@ -47,9 +47,8 @@ typedef enum
APP_BLE_FAST_ADV,
APP_BLE_LP_ADV,
APP_BLE_SCAN,
- APP_BLE_LP_CONNECTING,
- APP_BLE_CONNECTED_SERVER,
- APP_BLE_CONNECTED_CLIENT
+ APP_BLE_CONNECTING,
+ APP_BLE_CONNECTED
} APP_BLE_ConnStatus_t;
/* USER CODE BEGIN ET */
@@ -74,6 +73,7 @@ typedef enum
/* Exported functions ---------------------------------------------*/
void APP_BLE_Init(void);
APP_BLE_ConnStatus_t APP_BLE_Get_Server_Connection_Status(void);
+APP_BLE_ConnStatus_t APP_BLE_Get_Client_Connection_Status(uint16_t Connection_Handle);
/* USER CODE BEGIN EF */
void APP_BLE_RequestPairing(void (*Callback)(void));
diff --git a/STM32_WPAN/App/ble_conf.h b/STM32_WPAN/App/ble_conf.h
index 8c78c02..a28a5b8 100644
--- a/STM32_WPAN/App/ble_conf.h
+++ b/STM32_WPAN/App/ble_conf.h
@@ -46,7 +46,7 @@
* This setting shall be set to '1' if the device needs to support the Central Role
* In the MS configuration, both BLE_CFG_PERIPHERAL and BLE_CFG_CENTRAL shall be set to '1'
*/
-#define BLE_CFG_CENTRAL 0
+#define BLE_CFG_CENTRAL 1
/**
* There is one handler per service enabled
@@ -57,7 +57,7 @@
*/
#define BLE_CFG_SVC_MAX_NBR_CB 7
-#define BLE_CFG_CLT_MAX_NBR_CB 0
+#define BLE_CFG_CLT_MAX_NBR_CB 6
/******************************************************************************
* GAP Service - Appearance
diff --git a/STM32_WPAN/App/ble_tx_queue.c b/STM32_WPAN/App/ble_tx_queue.c
new file mode 100644
index 0000000..c88da17
--- /dev/null
+++ b/STM32_WPAN/App/ble_tx_queue.c
@@ -0,0 +1,145 @@
+/***************************************************************************
+** **
+** FlySight 2 firmware **
+** Copyright 2025 Bionic Avionics Inc. **
+** **
+** This program is free software: you can redistribute it and/or modify **
+** it under the terms of the GNU General Public License as published by **
+** the Free Software Foundation, either version 3 of the License, or **
+** (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, **
+** but WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program. If not, see . **
+** **
+****************************************************************************
+** Contact: Bionic Avionics Inc. **
+** Website: http://flysight.ca/ **
+****************************************************************************/
+
+#include
+#include "app_common.h"
+#include "dbg_trace.h"
+#include "ble.h"
+#include "crs.h"
+#include "stm32_seq.h"
+#include "custom_stm.h"
+
+typedef struct
+{
+ Custom_STM_Char_Opcode_t opcode;
+ uint8_t data[244];
+ uint8_t length;
+ uint8_t *size_ptr;
+ BLE_TX_Queue_callback_t callback;
+} BLE_TX_Queue_Packet_t;
+
+static BLE_TX_Queue_Packet_t tx_buffer[FS_CRS_WINDOW_LENGTH+1];
+static uint32_t tx_read_index, tx_write_index;
+static uint8_t tx_flow_status;
+
+static void BLE_TX_Queue_Transmit(void);
+
+void BLE_TX_Queue_Init(void)
+{
+ tx_read_index = 0;
+ tx_write_index = 0;
+ tx_flow_status = 1;
+
+ UTIL_SEQ_RegTask(1<opcode = opcode;
+ packet->length = length;
+ packet->size_ptr = size_ptr;
+ packet->callback = callback;
+
+ ++tx_write_index;
+ UTIL_SEQ_SetTask(1<size_ptr)
+ {
+ *(packet->size_ptr) = packet->length;
+ }
+
+ status = Custom_STM_App_Update_Char(packet->opcode, packet->data);
+ if (status == BLE_STATUS_INSUFFICIENT_RESOURCES)
+ {
+ tx_flow_status = 0;
+ }
+ else
+ {
+ ++tx_read_index;
+
+ // Call callback and transmit next packet
+ if (packet->callback)
+ {
+ packet->callback();
+ }
+ UTIL_SEQ_SetTask(1<. **
+** **
+****************************************************************************
+** Contact: Bionic Avionics Inc. **
+** Website: http://flysight.ca/ **
+****************************************************************************/
+
+#ifndef APP_BLE_TX_QUEUE_H_
+#define APP_BLE_TX_QUEUE_H_
+
+#include "custom_stm.h"
+
+typedef void (*BLE_TX_Queue_callback_t)(void);
+
+void BLE_TX_Queue_Init(void);
+void BLE_TX_Queue_TxPoolAvailableNotification(void);
+
+uint8_t *BLE_TX_Queue_GetNextTxPacket(void);
+void BLE_TX_Queue_SendNextTxPacket(
+ Custom_STM_Char_Opcode_t opcode, uint8_t length,
+ uint8_t *size_ptr, BLE_TX_Queue_callback_t callback);
+
+void BLE_TX_Queue_SendTxPacket(
+ Custom_STM_Char_Opcode_t opcode, uint8_t *data, uint8_t length,
+ uint8_t *size_ptr, BLE_TX_Queue_callback_t callback);
+
+#endif /* APP_BLE_TX_QUEUE_H_ */
diff --git a/STM32_WPAN/App/control_point_protocol.h b/STM32_WPAN/App/control_point_protocol.h
new file mode 100644
index 0000000..54e2ce3
--- /dev/null
+++ b/STM32_WPAN/App/control_point_protocol.h
@@ -0,0 +1,42 @@
+/***************************************************************************
+** **
+** FlySight 2 firmware **
+** Copyright 2025 Bionic Avionics Inc. **
+** **
+** This program is free software: you can redistribute it and/or modify **
+** it under the terms of the GNU General Public License as published by **
+** the Free Software Foundation, either version 3 of the License, or **
+** (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, **
+** but WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program. If not, see . **
+** **
+****************************************************************************
+** Contact: Bionic Avionics Inc. **
+** Website: http://flysight.ca/ **
+****************************************************************************/
+
+#ifndef APP_CONTROL_POINT_PROTOCOL_H_
+#define APP_CONTROL_POINT_PROTOCOL_H_
+
+#include
+
+#define CP_RESPONSE_ID (0xF0) // Identifier for a response packet
+
+// Common Status Codes for Responses
+#define CP_STATUS_SUCCESS (0x01)
+#define CP_STATUS_CMD_NOT_SUPPORTED (0x02)
+#define CP_STATUS_INVALID_PARAMETER (0x03)
+#define CP_STATUS_OPERATION_FAILED (0x04)
+#define CP_STATUS_OPERATION_NOT_PERMITTED (0x05)
+#define CP_STATUS_BUSY (0x06)
+#define CP_STATUS_ERROR_UNKNOWN (0x07)
+
+#define MAX_CP_OPTIONAL_RESPONSE_DATA_LEN 17 // Max optional data in a response
+
+#endif /* APP_CONTROL_POINT_PROTOCOL_H_ */
diff --git a/STM32_WPAN/App/custom_app.c b/STM32_WPAN/App/custom_app.c
index db63f3a..c8b647b 100644
--- a/STM32_WPAN/App/custom_app.c
+++ b/STM32_WPAN/App/custom_app.c
@@ -31,20 +31,32 @@
/* USER CODE BEGIN Includes */
#include "crs.h"
#include "start_control.h"
+#include "gnss_ble.h"
+#include "mode.h"
+#include "ble_tx_queue.h"
+#include "sensor_data.h"
+#include "device_state.h"
+#include "control_point_protocol.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
typedef struct
{
- /* CRS */
- uint8_t Crs_tx_Notification_Status;
- /* GNSS */
- uint8_t Gnss_pv_Notification_Status;
- /* Start */
- uint8_t Start_control_Indication_Status;
- uint8_t Start_result_Indication_Status;
+ /* File_Transfer */
+ uint8_t Ft_packet_out_Notification_Status;
+ /* Sensor_Data */
+ uint8_t Sd_gnss_measurement_Notification_Status;
+ uint8_t Sd_control_point_Indication_Status;
+ /* Starter_Pistol */
+ uint8_t Sp_control_point_Indication_Status;
+ uint8_t Sp_result_Indication_Status;
+ /* Device_State */
+ uint8_t Ds_mode_Indication_Status;
+ uint8_t Ds_control_point_Indication_Status;
+ /* Battery */
+ uint8_t Battery_level_Notification_Status;
/* USER CODE BEGIN CUSTOM_APP_Context_t */
- uint8_t Crs_tx_Flow_Status;
+
/* USER CODE END CUSTOM_APP_Context_t */
uint16_t ConnectionHandle;
@@ -80,48 +92,50 @@ uint8_t UpdateCharData[247];
uint8_t NotifyCharData[247];
/* USER CODE BEGIN PV */
-static Custom_CRS_Packet_t tx_buffer[FS_CRS_WINDOW_LENGTH+1];
-static uint32_t tx_read_index, tx_write_index;
-
static Custom_CRS_Packet_t rx_buffer[FS_CRS_WINDOW_LENGTH+1];
static uint32_t rx_read_index, rx_write_index;
-static uint8_t gnss_pv_packet[29];
-
-static Custom_Start_Packet_t start_buffer[FS_START_WINDOW_LENGTH+1];
-static uint32_t start_read_index, start_write_index;
+static uint8_t gnss_pv_packet[GNSS_BLE_MAX_LEN];
static uint8_t start_result_packet[9];
static uint8_t connected_flag = 0;
-extern uint8_t SizeCrs_Tx;
-extern uint8_t SizeCrs_Rx;
+extern uint8_t SizeSd_Gnss_Measurement;
+extern uint8_t SizeSd_Control_Point;
static uint8_t timeout_timer_id;
+
+static uint8_t current_battery_level_percent = 0;
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
-/* CRS */
-static void Custom_Crs_tx_Update_Char(void);
-static void Custom_Crs_tx_Send_Notification(void);
-/* GNSS */
-static void Custom_Gnss_pv_Update_Char(void);
-static void Custom_Gnss_pv_Send_Notification(void);
-/* Start */
-static void Custom_Start_control_Update_Char(void);
-static void Custom_Start_control_Send_Indication(void);
-static void Custom_Start_result_Update_Char(void);
-static void Custom_Start_result_Send_Indication(void);
+/* File_Transfer */
+static void Custom_Ft_packet_out_Update_Char(void);
+static void Custom_Ft_packet_out_Send_Notification(void);
+/* Sensor_Data */
+static void Custom_Sd_gnss_measurement_Update_Char(void);
+static void Custom_Sd_gnss_measurement_Send_Notification(void);
+static void Custom_Sd_control_point_Update_Char(void);
+static void Custom_Sd_control_point_Send_Indication(void);
+/* Starter_Pistol */
+static void Custom_Sp_control_point_Update_Char(void);
+static void Custom_Sp_control_point_Send_Indication(void);
+static void Custom_Sp_result_Update_Char(void);
+static void Custom_Sp_result_Send_Indication(void);
+/* Device_State */
+static void Custom_Ds_mode_Update_Char(void);
+static void Custom_Ds_mode_Send_Indication(void);
+static void Custom_Ds_control_point_Update_Char(void);
+static void Custom_Ds_control_point_Send_Indication(void);
+/* Battery */
+static void Custom_Battery_level_Update_Char(void);
+static void Custom_Battery_level_Send_Notification(void);
/* USER CODE BEGIN PFP */
static void Custom_CRS_OnConnect(Custom_App_ConnHandle_Not_evt_t *pNotification);
static void Custom_CRS_OnDisconnect(void);
static void Custom_CRS_OnRxWrite(Custom_STM_App_Notification_evt_t *pNotification);
-static void Custom_CRS_Transmit(void);
-static void Custom_GNSS_Transmit(void);
-static void Custom_Start_OnControlWrite(Custom_STM_App_Notification_evt_t *pNotification);
-static void Custom_Start_Transmit(void);
static void Custom_App_Timeout(void);
/* USER CODE END PFP */
@@ -137,92 +151,186 @@ void Custom_STM_App_Notification(Custom_STM_App_Notification_evt_t *pNotificatio
/* USER CODE END CUSTOM_STM_App_Notification_Custom_Evt_Opcode */
- /* CRS */
- case CUSTOM_STM_CRS_TX_NOTIFY_ENABLED_EVT:
- /* USER CODE BEGIN CUSTOM_STM_CRS_TX_NOTIFY_ENABLED_EVT */
- Custom_App_Context.Crs_tx_Notification_Status = 1;
- UTIL_SEQ_SetTask(1<DataTransfered.pPayload,
+ pNotification->DataTransfered.Length,
+ pNotification->ConnectionHandle,
+ Custom_App_Context.Sd_control_point_Indication_Status);
+ /* USER CODE END CUSTOM_STM_SD_CONTROL_POINT_WRITE_EVT */
break;
- case CUSTOM_STM_START_CONTROL_INDICATE_ENABLED_EVT:
- /* USER CODE BEGIN CUSTOM_STM_START_CONTROL_INDICATE_ENABLED_EVT */
- Custom_App_Context.Start_control_Indication_Status = 1;
- /* USER CODE END CUSTOM_STM_START_CONTROL_INDICATE_ENABLED_EVT */
+ case CUSTOM_STM_SD_CONTROL_POINT_INDICATE_ENABLED_EVT:
+ /* USER CODE BEGIN CUSTOM_STM_SD_CONTROL_POINT_INDICATE_ENABLED_EVT */
+ Custom_App_Context.Sd_control_point_Indication_Status = 1;
+ /* USER CODE END CUSTOM_STM_SD_CONTROL_POINT_INDICATE_ENABLED_EVT */
break;
- case CUSTOM_STM_START_CONTROL_INDICATE_DISABLED_EVT:
- /* USER CODE BEGIN CUSTOM_STM_START_CONTROL_INDICATE_DISABLED_EVT */
- Custom_App_Context.Start_control_Indication_Status = 0;
- /* USER CODE END CUSTOM_STM_START_CONTROL_INDICATE_DISABLED_EVT */
+ case CUSTOM_STM_SD_CONTROL_POINT_INDICATE_DISABLED_EVT:
+ /* USER CODE BEGIN CUSTOM_STM_SD_CONTROL_POINT_INDICATE_DISABLED_EVT */
+ Custom_App_Context.Sd_control_point_Indication_Status = 0;
+ /* USER CODE END CUSTOM_STM_SD_CONTROL_POINT_INDICATE_DISABLED_EVT */
break;
- case CUSTOM_STM_START_RESULT_READ_EVT:
- /* USER CODE BEGIN CUSTOM_STM_START_RESULT_READ_EVT */
- if (!Custom_App_Context.Start_result_Indication_Status)
+ /* Starter_Pistol */
+ case CUSTOM_STM_SP_CONTROL_POINT_WRITE_EVT:
+ /* USER CODE BEGIN CUSTOM_STM_SP_CONTROL_POINT_WRITE_EVT */
+ FS_StartControl_Handle_SP_ControlPointWrite(
+ pNotification->DataTransfered.pPayload,
+ pNotification->DataTransfered.Length,
+ pNotification->ConnectionHandle,
+ Custom_App_Context.Sp_control_point_Indication_Status);
+ /* USER CODE END CUSTOM_STM_SP_CONTROL_POINT_WRITE_EVT */
+ break;
+
+ case CUSTOM_STM_SP_CONTROL_POINT_INDICATE_ENABLED_EVT:
+ /* USER CODE BEGIN CUSTOM_STM_SP_CONTROL_POINT_INDICATE_ENABLED_EVT */
+ Custom_App_Context.Sp_control_point_Indication_Status = 1;
+ /* USER CODE END CUSTOM_STM_SP_CONTROL_POINT_INDICATE_ENABLED_EVT */
+ break;
+
+ case CUSTOM_STM_SP_CONTROL_POINT_INDICATE_DISABLED_EVT:
+ /* USER CODE BEGIN CUSTOM_STM_SP_CONTROL_POINT_INDICATE_DISABLED_EVT */
+ Custom_App_Context.Sp_control_point_Indication_Status = 0;
+ /* USER CODE END CUSTOM_STM_SP_CONTROL_POINT_INDICATE_DISABLED_EVT */
+ break;
+
+ case CUSTOM_STM_SP_RESULT_READ_EVT:
+ /* USER CODE BEGIN CUSTOM_STM_SP_RESULT_READ_EVT */
+ if (!Custom_App_Context.Sp_result_Indication_Status)
{
- Custom_STM_App_Update_Char(CUSTOM_STM_START_RESULT, start_result_packet);
+ Custom_STM_App_Update_Char(CUSTOM_STM_SP_RESULT, start_result_packet);
}
- /* USER CODE END CUSTOM_STM_START_RESULT_READ_EVT */
+ /* USER CODE END CUSTOM_STM_SP_RESULT_READ_EVT */
break;
- case CUSTOM_STM_START_RESULT_INDICATE_ENABLED_EVT:
- /* USER CODE BEGIN CUSTOM_STM_START_RESULT_INDICATE_ENABLED_EVT */
- Custom_App_Context.Start_result_Indication_Status = 1;
- /* USER CODE END CUSTOM_STM_START_RESULT_INDICATE_ENABLED_EVT */
+ case CUSTOM_STM_SP_RESULT_INDICATE_ENABLED_EVT:
+ /* USER CODE BEGIN CUSTOM_STM_SP_RESULT_INDICATE_ENABLED_EVT */
+ Custom_App_Context.Sp_result_Indication_Status = 1;
+ /* USER CODE END CUSTOM_STM_SP_RESULT_INDICATE_ENABLED_EVT */
break;
- case CUSTOM_STM_START_RESULT_INDICATE_DISABLED_EVT:
- /* USER CODE BEGIN CUSTOM_STM_START_RESULT_INDICATE_DISABLED_EVT */
- Custom_App_Context.Start_result_Indication_Status = 0;
- /* USER CODE END CUSTOM_STM_START_RESULT_INDICATE_DISABLED_EVT */
+ case CUSTOM_STM_SP_RESULT_INDICATE_DISABLED_EVT:
+ /* USER CODE BEGIN CUSTOM_STM_SP_RESULT_INDICATE_DISABLED_EVT */
+ Custom_App_Context.Sp_result_Indication_Status = 0;
+ /* USER CODE END CUSTOM_STM_SP_RESULT_INDICATE_DISABLED_EVT */
+ break;
+
+ /* Device_State */
+ case CUSTOM_STM_DS_MODE_READ_EVT:
+ /* USER CODE BEGIN CUSTOM_STM_DS_MODE_READ_EVT */
+ uint8_t modeVal = (uint8_t) FS_Mode_State(); // Get current state from mode module
+ Custom_STM_App_Update_Char(CUSTOM_STM_DS_MODE, &modeVal);
+ /* USER CODE END CUSTOM_STM_DS_MODE_READ_EVT */
+ break;
+
+ case CUSTOM_STM_DS_MODE_INDICATE_ENABLED_EVT:
+ /* USER CODE BEGIN CUSTOM_STM_DS_MODE_INDICATE_ENABLED_EVT */
+ Custom_App_Context.Ds_mode_Indication_Status = 1;
+ /* USER CODE END CUSTOM_STM_DS_MODE_INDICATE_ENABLED_EVT */
+ break;
+
+ case CUSTOM_STM_DS_MODE_INDICATE_DISABLED_EVT:
+ /* USER CODE BEGIN CUSTOM_STM_DS_MODE_INDICATE_DISABLED_EVT */
+ Custom_App_Context.Ds_mode_Indication_Status = 0;
+ /* USER CODE END CUSTOM_STM_DS_MODE_INDICATE_DISABLED_EVT */
+ break;
+
+ case CUSTOM_STM_DS_CONTROL_POINT_WRITE_EVT:
+ /* USER CODE BEGIN CUSTOM_STM_DS_CONTROL_POINT_WRITE_EVT */
+ DeviceState_Handle_DS_ControlPointWrite(
+ pNotification->DataTransfered.pPayload,
+ pNotification->DataTransfered.Length,
+ pNotification->ConnectionHandle,
+ Custom_App_Context.Ds_control_point_Indication_Status);
+ /* USER CODE END CUSTOM_STM_DS_CONTROL_POINT_WRITE_EVT */
+ break;
+
+ case CUSTOM_STM_DS_CONTROL_POINT_INDICATE_ENABLED_EVT:
+ /* USER CODE BEGIN CUSTOM_STM_DS_CONTROL_POINT_INDICATE_ENABLED_EVT */
+ Custom_App_Context.Ds_control_point_Indication_Status = 1;
+ /* USER CODE END CUSTOM_STM_DS_CONTROL_POINT_INDICATE_ENABLED_EVT */
+ break;
+
+ case CUSTOM_STM_DS_CONTROL_POINT_INDICATE_DISABLED_EVT:
+ /* USER CODE BEGIN CUSTOM_STM_DS_CONTROL_POINT_INDICATE_DISABLED_EVT */
+ Custom_App_Context.Ds_control_point_Indication_Status = 0;
+ /* USER CODE END CUSTOM_STM_DS_CONTROL_POINT_INDICATE_DISABLED_EVT */
+ break;
+
+ /* Battery */
+ case CUSTOM_STM_BATTERY_LEVEL_READ_EVT:
+ /* USER CODE BEGIN CUSTOM_STM_BATTERY_LEVEL_READ_EVT */
+ APP_DBG_MSG("CUSTOM_STM_BATTERY_LEVEL_READ_EVT received\n");
+ // The actual calculation and update should happen when FS_VBAT_ValueReady_Callback runs,
+ // or we need a way to trigger FS_VBAT_ValueReady_Callback or get the latest value here.
+ // For simplicity, let's assume current_battery_level_percent is up-to-date.
+ Custom_STM_App_Update_Char(CUSTOM_STM_BATTERY_LEVEL, ¤t_battery_level_percent);
+ /* USER CODE END CUSTOM_STM_BATTERY_LEVEL_READ_EVT */
+ break;
+
+ case CUSTOM_STM_BATTERY_LEVEL_NOTIFY_ENABLED_EVT:
+ /* USER CODE BEGIN CUSTOM_STM_BATTERY_LEVEL_NOTIFY_ENABLED_EVT */
+ APP_DBG_MSG("CUSTOM_STM_BATTERY_LEVEL_NOTIFY_ENABLED_EVT received\n");
+ Custom_App_Context.Battery_level_Notification_Status = 1;
+ // Optionally, send the current level immediately upon enabling notifications
+ Custom_STM_App_Update_Char(CUSTOM_STM_BATTERY_LEVEL, ¤t_battery_level_percent);
+ /* USER CODE END CUSTOM_STM_BATTERY_LEVEL_NOTIFY_ENABLED_EVT */
+ break;
+
+ case CUSTOM_STM_BATTERY_LEVEL_NOTIFY_DISABLED_EVT:
+ /* USER CODE BEGIN CUSTOM_STM_BATTERY_LEVEL_NOTIFY_DISABLED_EVT */
+ APP_DBG_MSG("CUSTOM_STM_BATTERY_LEVEL_NOTIFY_DISABLED_EVT received\n");
+ Custom_App_Context.Battery_level_Notification_Status = 0;
+ /* USER CODE END CUSTOM_STM_BATTERY_LEVEL_NOTIFY_DISABLED_EVT */
break;
case CUSTOM_STM_NOTIFICATION_COMPLETE_EVT:
@@ -283,15 +391,30 @@ void Custom_APP_Notification(Custom_App_ConnHandle_Not_evt_t *pNotification)
void Custom_APP_Init(void)
{
/* USER CODE BEGIN CUSTOM_APP_Init */
- UTIL_SEQ_RegTask(1<ConnectionHandle;
+ /* USER CODE BEGIN Sp_result_UC_Last*/
- // Start timeout timer
- HW_TS_Start(timeout_timer_id, TIMEOUT_TICKS);
+ /* USER CODE END Sp_result_UC_Last*/
+ return;
}
-static void Custom_CRS_OnDisconnect(void)
+void Custom_Sp_result_Send_Indication(void) /* Property Indication */
{
- // Stop timeout timer
- HW_TS_Stop(timeout_timer_id);
+ uint8_t updateflag = 0;
- // Update state
- connected_flag = 0;
+ /* USER CODE BEGIN Sp_result_IS_1*/
- // Call update task
- UTIL_SEQ_SetTask(1<length = pNotification->DataTransfered.Length;
- memcpy(packet->data, pNotification->DataTransfered.pPayload, packet->length);
+ /* USER CODE BEGIN Ds_mode_UC_1*/
- // Call update task
- UTIL_SEQ_SetTask(1<length;
-
- status = Custom_STM_App_Update_Char(CUSTOM_STM_CRS_TX, packet->data);
- if (status == BLE_STATUS_INSUFFICIENT_RESOURCES)
- {
- Custom_App_Context.Crs_tx_Flow_Status = 0;
- }
- else
- {
- ++tx_read_index;
-
- // Call update task and transmit next packet
- UTIL_SEQ_SetTask(1<ConnectionHandle;
+
+ // Start timeout timer
+ APP_DBG_MSG("Start connection timeout\n");
+ HW_TS_Start(timeout_timer_id, TIMEOUT_TICKS);
}
-void Custom_GNSS_Update(const FS_GNSS_Data_t *current)
+static void Custom_CRS_OnDisconnect(void)
{
- memset(&gnss_pv_packet[0], 0xb0, 1);
- memcpy(&gnss_pv_packet[1], &(current->iTOW), sizeof(current->iTOW));
- memcpy(&gnss_pv_packet[5], &(current->lon), sizeof(current->lon));
- memcpy(&gnss_pv_packet[9], &(current->lat), sizeof(current->lat));
- memcpy(&gnss_pv_packet[13], &(current->hMSL), sizeof(current->hMSL));
- memcpy(&gnss_pv_packet[17], &(current->velN), sizeof(current->velN));
- memcpy(&gnss_pv_packet[21], &(current->velE), sizeof(current->velE));
- memcpy(&gnss_pv_packet[25], &(current->velD), sizeof(current->velD));
-
- if (Custom_App_Context.Gnss_pv_Notification_Status)
- {
- UTIL_SEQ_SetTask(1<length = pNotification->DataTransfered.Length;
memcpy(packet->data, pNotification->DataTransfered.pPayload, packet->length);
// Call update task
- UTIL_SEQ_SetTask(1<= MAX_VOLTAGE_MV) {
+ percentage = 100;
+ } else {
+ percentage = ((int32_t)voltage_mv - MIN_VOLTAGE_MV) * 100 / (MAX_VOLTAGE_MV - MIN_VOLTAGE_MV);
+ }
+ return (uint8_t)percentage;
+}
+
+void Custom_VBAT_Update(const FS_VBAT_Data_t *current)
+{
+ uint8_t new_battery_level = calculate_battery_percentage(current->voltage);
+
+ // Update the global/context variable
+ // This ensures that read requests get the most recent value
+ current_battery_level_percent = new_battery_level;
+
+ // If notifications are enabled for the Battery Level characteristic, send an update
+ if (Custom_App_Context.Battery_level_Notification_Status)
+ {
+ // APP_DBG_MSG("Sending battery level notification: %d%%\n", new_battery_level);
+ BLE_TX_Queue_SendTxPacket(CUSTOM_STM_BATTERY_LEVEL,
+ &new_battery_level, sizeof(new_battery_level), &SizeBattery_Level, 0);
+ }
+}
+
/* USER CODE END FD_LOCAL_FUNCTIONS*/
diff --git a/STM32_WPAN/App/custom_app.h b/STM32_WPAN/App/custom_app.h
index 0cc7307..cbb5dbb 100644
--- a/STM32_WPAN/App/custom_app.h
+++ b/STM32_WPAN/App/custom_app.h
@@ -31,6 +31,7 @@ extern "C" {
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "gnss.h"
+#include "vbat.h"
/* USER CODE END Includes */
/* Exported types ------------------------------------------------------------*/
@@ -54,7 +55,7 @@ typedef struct
typedef struct
{
- uint8_t data[1];
+ uint8_t data[20];
uint8_t length;
} Custom_Start_Packet_t;
/* USER CODE END ET */
@@ -81,8 +82,6 @@ void Custom_APP_Notification(Custom_App_ConnHandle_Not_evt_t *pNotification);
void Custom_APP_TxPoolAvailableNotification(void);
uint8_t Custom_APP_IsConnected(void);
-Custom_CRS_Packet_t *Custom_CRS_GetNextTxPacket(void);
-void Custom_CRS_SendNextTxPacket(void);
Custom_CRS_Packet_t *Custom_CRS_GetNextRxPacket(void);
void Custom_GNSS_Update(const FS_GNSS_Data_t *current);
@@ -90,6 +89,10 @@ void Custom_GNSS_Update(const FS_GNSS_Data_t *current);
Custom_Start_Packet_t *Custom_Start_GetNextControlPacket(void);
void Custom_Start_Update(uint16_t year, uint8_t month, uint8_t day,
uint8_t hour, uint8_t min, uint8_t sec, uint16_t ms);
+
+void Custom_Mode_Update(uint8_t newMode);
+
+void Custom_VBAT_Update(const FS_VBAT_Data_t *current);
/* USER CODE END EF */
#ifdef __cplusplus
diff --git a/STM32_WPAN/App/custom_stm.c b/STM32_WPAN/App/custom_stm.c
index 4e7335f..1b0de62 100644
--- a/STM32_WPAN/App/custom_stm.c
+++ b/STM32_WPAN/App/custom_stm.c
@@ -28,14 +28,20 @@
/* Private typedef -----------------------------------------------------------*/
typedef struct{
- uint16_t CustomCrsHdle; /**< CRS handle */
- uint16_t CustomCrs_TxHdle; /**< CRS_TX handle */
- uint16_t CustomCrs_RxHdle; /**< CRS_RX handle */
- uint16_t CustomGnssHdle; /**< GNSS handle */
- uint16_t CustomGnss_PvHdle; /**< GNSS_PV handle */
- uint16_t CustomStartHdle; /**< Start handle */
- uint16_t CustomStart_ControlHdle; /**< Start_Control handle */
- uint16_t CustomStart_ResultHdle; /**< Start_Result handle */
+ uint16_t CustomFile_TransferHdle; /**< File_Transfer handle */
+ uint16_t CustomFt_Packet_OutHdle; /**< FT_Packet_Out handle */
+ uint16_t CustomFt_Packet_InHdle; /**< FT_Packet_In handle */
+ uint16_t CustomSensor_DataHdle; /**< Sensor_Data handle */
+ uint16_t CustomSd_Gnss_MeasurementHdle; /**< SD_GNSS_Measurement handle */
+ uint16_t CustomSd_Control_PointHdle; /**< SD_Control_Point handle */
+ uint16_t CustomStarter_PistolHdle; /**< Starter_Pistol handle */
+ uint16_t CustomSp_Control_PointHdle; /**< SP_Control_Point handle */
+ uint16_t CustomSp_ResultHdle; /**< SP_Result handle */
+ uint16_t CustomDevice_StateHdle; /**< Device_State handle */
+ uint16_t CustomDs_ModeHdle; /**< DS_Mode handle */
+ uint16_t CustomDs_Control_PointHdle; /**< DS_Control_Point handle */
+ uint16_t CustomBatteryHdle; /**< Battery handle */
+ uint16_t CustomBattery_LevelHdle; /**< Battery_Level handle */
/* USER CODE BEGIN Context */
/* Place holder for Characteristic Descriptors Handle*/
@@ -69,11 +75,15 @@ typedef struct{
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
-uint8_t SizeCrs_Tx = 244;
-uint8_t SizeCrs_Rx = 244;
-uint8_t SizeGnss_Pv = 29;
-uint8_t SizeStart_Control = 1;
-uint8_t SizeStart_Result = 9;
+uint8_t SizeFt_Packet_Out = 244;
+uint8_t SizeFt_Packet_In = 244;
+uint8_t SizeSd_Gnss_Measurement = 44;
+uint8_t SizeSd_Control_Point = 20;
+uint8_t SizeSp_Control_Point = 20;
+uint8_t SizeSp_Result = 9;
+uint8_t SizeDs_Mode = 1;
+uint8_t SizeDs_Control_Point = 20;
+uint8_t SizeBattery_Level = 1;
/**
* START of Section BLE_DRIVER_CONTEXT
@@ -123,15 +133,26 @@ do {\
uuid_struct[8] = uuid_8; uuid_struct[9] = uuid_9; uuid_struct[10] = uuid_10; uuid_struct[11] = uuid_11; \
uuid_struct[12] = uuid_12; uuid_struct[13] = uuid_13; uuid_struct[14] = uuid_14; uuid_struct[15] = uuid_15; \
}while(0)
+#define COPY_UUID_128(uuid_struct, uuid_15, uuid_14, uuid_13, uuid_12, uuid_11, uuid_10, uuid_9, uuid_8, uuid_7, uuid_6, uuid_5, uuid_4, uuid_3, uuid_2, uuid_1, uuid_0) \
+do {\
+ uuid_struct[0] = uuid_0; uuid_struct[1] = uuid_1; uuid_struct[2] = uuid_2; uuid_struct[3] = uuid_3; \
+ uuid_struct[4] = uuid_4; uuid_struct[5] = uuid_5; uuid_struct[6] = uuid_6; uuid_struct[7] = uuid_7; \
+ uuid_struct[8] = uuid_8; uuid_struct[9] = uuid_9; uuid_struct[10] = uuid_10; uuid_struct[11] = uuid_11; \
+ uuid_struct[12] = uuid_12; uuid_struct[13] = uuid_13; uuid_struct[14] = uuid_14; uuid_struct[15] = uuid_15; \
+}while(0)
-#define COPY_CRS_UUID(uuid_struct) COPY_UUID_128(uuid_struct,0x00,0x00,0x00,0x00,0xcc,0x7a,0x48,0x2a,0x98,0x4a,0x7f,0x2e,0xd5,0xb3,0xe5,0x8f)
-#define COPY_CRS_TX_UUID(uuid_struct) COPY_UUID_128(uuid_struct,0x00,0x00,0x00,0x01,0x8e,0x22,0x45,0x41,0x9d,0x4c,0x21,0xed,0xae,0x82,0xed,0x19)
-#define COPY_CRS_RX_UUID(uuid_struct) COPY_UUID_128(uuid_struct,0x00,0x00,0x00,0x02,0x8e,0x22,0x45,0x41,0x9d,0x4c,0x21,0xed,0xae,0x82,0xed,0x19)
-#define COPY_GNSS_UUID(uuid_struct) COPY_UUID_128(uuid_struct,0x00,0x00,0x00,0x01,0xcc,0x7a,0x48,0x2a,0x98,0x4a,0x7f,0x2e,0xd5,0xb3,0xe5,0x8f)
-#define COPY_GNSS_PV_UUID(uuid_struct) COPY_UUID_128(uuid_struct,0x00,0x00,0x00,0x00,0x8e,0x22,0x45,0x41,0x9d,0x4c,0x21,0xed,0xae,0x82,0xed,0x19)
-#define COPY_START_UUID(uuid_struct) COPY_UUID_128(uuid_struct,0x00,0x00,0x00,0x02,0xcc,0x7a,0x48,0x2a,0x98,0x4a,0x7f,0x2e,0xd5,0xb3,0xe5,0x8f)
-#define COPY_START_CONTROL_UUID(uuid_struct) COPY_UUID_128(uuid_struct,0x00,0x00,0x00,0x03,0x8e,0x22,0x45,0x41,0x9d,0x4c,0x21,0xed,0xae,0x82,0xed,0x19)
-#define COPY_START_RESULT_UUID(uuid_struct) COPY_UUID_128(uuid_struct,0x00,0x00,0x00,0x04,0x8e,0x22,0x45,0x41,0x9d,0x4c,0x21,0xed,0xae,0x82,0xed,0x19)
+#define COPY_FILE_TRANSFER_UUID(uuid_struct) COPY_UUID_128(uuid_struct,0x00,0x00,0x00,0x00,0xcc,0x7a,0x48,0x2a,0x98,0x4a,0x7f,0x2e,0xd5,0xb3,0xe5,0x8f)
+#define COPY_FT_PACKET_OUT_UUID(uuid_struct) COPY_UUID_128(uuid_struct,0x00,0x00,0x00,0x01,0x8e,0x22,0x45,0x41,0x9d,0x4c,0x21,0xed,0xae,0x82,0xed,0x19)
+#define COPY_FT_PACKET_IN_UUID(uuid_struct) COPY_UUID_128(uuid_struct,0x00,0x00,0x00,0x02,0x8e,0x22,0x45,0x41,0x9d,0x4c,0x21,0xed,0xae,0x82,0xed,0x19)
+#define COPY_SENSOR_DATA_UUID(uuid_struct) COPY_UUID_128(uuid_struct,0x00,0x00,0x00,0x01,0xcc,0x7a,0x48,0x2a,0x98,0x4a,0x7f,0x2e,0xd5,0xb3,0xe5,0x8f)
+#define COPY_SD_GNSS_MEASUREMENT_UUID(uuid_struct) COPY_UUID_128(uuid_struct,0x00,0x00,0x00,0x00,0x8e,0x22,0x45,0x41,0x9d,0x4c,0x21,0xed,0xae,0x82,0xed,0x19)
+#define COPY_SD_CONTROL_POINT_UUID(uuid_struct) COPY_UUID_128(uuid_struct,0x00,0x00,0x00,0x06,0x8e,0x22,0x45,0x41,0x9d,0x4c,0x21,0xed,0xae,0x82,0xed,0x19)
+#define COPY_STARTER_PISTOL_UUID(uuid_struct) COPY_UUID_128(uuid_struct,0x00,0x00,0x00,0x02,0xcc,0x7a,0x48,0x2a,0x98,0x4a,0x7f,0x2e,0xd5,0xb3,0xe5,0x8f)
+#define COPY_SP_CONTROL_POINT_UUID(uuid_struct) COPY_UUID_128(uuid_struct,0x00,0x00,0x00,0x03,0x8e,0x22,0x45,0x41,0x9d,0x4c,0x21,0xed,0xae,0x82,0xed,0x19)
+#define COPY_SP_RESULT_UUID(uuid_struct) COPY_UUID_128(uuid_struct,0x00,0x00,0x00,0x04,0x8e,0x22,0x45,0x41,0x9d,0x4c,0x21,0xed,0xae,0x82,0xed,0x19)
+#define COPY_DEVICE_STATE_UUID(uuid_struct) COPY_UUID_128(uuid_struct,0x00,0x00,0x00,0x03,0xcc,0x7a,0x48,0x2a,0x98,0x4a,0x7f,0x2e,0xd5,0xb3,0xe5,0x8f)
+#define COPY_DS_MODE_UUID(uuid_struct) COPY_UUID_128(uuid_struct,0x00,0x00,0x00,0x05,0x8e,0x22,0x45,0x41,0x9d,0x4c,0x21,0xed,0xae,0x82,0xed,0x19)
+#define COPY_DS_CONTROL_POINT_UUID(uuid_struct) COPY_UUID_128(uuid_struct,0x00,0x00,0x00,0x07,0x8e,0x22,0x45,0x41,0x9d,0x4c,0x21,0xed,0xae,0x82,0xed,0x19)
/* USER CODE BEGIN PF */
@@ -169,7 +190,7 @@ static SVCCTL_EvtAckStatus_t Custom_STM_Event_Handler(void *Event)
/* USER CODE END EVT_BLUE_GATT_ATTRIBUTE_MODIFIED_BEGIN */
attribute_modified = (aci_gatt_attribute_modified_event_rp0*)blecore_evt->data;
- if (attribute_modified->Attr_Handle == (CustomContext.CustomCrs_TxHdle + CHARACTERISTIC_DESCRIPTOR_ATTRIBUTE_OFFSET))
+ if (attribute_modified->Attr_Handle == (CustomContext.CustomFt_Packet_OutHdle + CHARACTERISTIC_DESCRIPTOR_ATTRIBUTE_OFFSET))
{
return_value = SVCCTL_EvtAckFlowEnable;
/* USER CODE BEGIN CUSTOM_STM_Service_1_Char_1 */
@@ -186,7 +207,7 @@ static SVCCTL_EvtAckStatus_t Custom_STM_Event_Handler(void *Event)
/* USER CODE BEGIN CUSTOM_STM_Service_1_Char_1_Disabled_BEGIN */
/* USER CODE END CUSTOM_STM_Service_1_Char_1_Disabled_BEGIN */
- Notification.Custom_Evt_Opcode = CUSTOM_STM_CRS_TX_NOTIFY_DISABLED_EVT;
+ Notification.Custom_Evt_Opcode = CUSTOM_STM_FT_PACKET_OUT_NOTIFY_DISABLED_EVT;
Custom_STM_App_Notification(&Notification);
/* USER CODE BEGIN CUSTOM_STM_Service_1_Char_1_Disabled_END */
@@ -198,7 +219,7 @@ static SVCCTL_EvtAckStatus_t Custom_STM_Event_Handler(void *Event)
/* USER CODE BEGIN CUSTOM_STM_Service_1_Char_1_COMSVC_Notification_BEGIN */
/* USER CODE END CUSTOM_STM_Service_1_Char_1_COMSVC_Notification_BEGIN */
- Notification.Custom_Evt_Opcode = CUSTOM_STM_CRS_TX_NOTIFY_ENABLED_EVT;
+ Notification.Custom_Evt_Opcode = CUSTOM_STM_FT_PACKET_OUT_NOTIFY_ENABLED_EVT;
Custom_STM_App_Notification(&Notification);
/* USER CODE BEGIN CUSTOM_STM_Service_1_Char_1_COMSVC_Notification_END */
@@ -211,9 +232,9 @@ static SVCCTL_EvtAckStatus_t Custom_STM_Event_Handler(void *Event)
/* USER CODE END CUSTOM_STM_Service_1_Char_1_default */
break;
}
- } /* if (attribute_modified->Attr_Handle == (CustomContext.CustomCrs_TxHdle + CHARACTERISTIC_DESCRIPTOR_ATTRIBUTE_OFFSET))*/
+ } /* if (attribute_modified->Attr_Handle == (CustomContext.CustomFt_Packet_OutHdle + CHARACTERISTIC_DESCRIPTOR_ATTRIBUTE_OFFSET))*/
- else if (attribute_modified->Attr_Handle == (CustomContext.CustomGnss_PvHdle + CHARACTERISTIC_DESCRIPTOR_ATTRIBUTE_OFFSET))
+ else if (attribute_modified->Attr_Handle == (CustomContext.CustomSd_Gnss_MeasurementHdle + CHARACTERISTIC_DESCRIPTOR_ATTRIBUTE_OFFSET))
{
return_value = SVCCTL_EvtAckFlowEnable;
/* USER CODE BEGIN CUSTOM_STM_Service_2_Char_1 */
@@ -230,7 +251,7 @@ static SVCCTL_EvtAckStatus_t Custom_STM_Event_Handler(void *Event)
/* USER CODE BEGIN CUSTOM_STM_Service_2_Char_1_Disabled_BEGIN */
/* USER CODE END CUSTOM_STM_Service_2_Char_1_Disabled_BEGIN */
- Notification.Custom_Evt_Opcode = CUSTOM_STM_GNSS_PV_NOTIFY_DISABLED_EVT;
+ Notification.Custom_Evt_Opcode = CUSTOM_STM_SD_GNSS_MEASUREMENT_NOTIFY_DISABLED_EVT;
Custom_STM_App_Notification(&Notification);
/* USER CODE BEGIN CUSTOM_STM_Service_2_Char_1_Disabled_END */
@@ -242,7 +263,7 @@ static SVCCTL_EvtAckStatus_t Custom_STM_Event_Handler(void *Event)
/* USER CODE BEGIN CUSTOM_STM_Service_2_Char_1_COMSVC_Notification_BEGIN */
/* USER CODE END CUSTOM_STM_Service_2_Char_1_COMSVC_Notification_BEGIN */
- Notification.Custom_Evt_Opcode = CUSTOM_STM_GNSS_PV_NOTIFY_ENABLED_EVT;
+ Notification.Custom_Evt_Opcode = CUSTOM_STM_SD_GNSS_MEASUREMENT_NOTIFY_ENABLED_EVT;
Custom_STM_App_Notification(&Notification);
/* USER CODE BEGIN CUSTOM_STM_Service_2_Char_1_COMSVC_Notification_END */
@@ -255,9 +276,54 @@ static SVCCTL_EvtAckStatus_t Custom_STM_Event_Handler(void *Event)
/* USER CODE END CUSTOM_STM_Service_2_Char_1_default */
break;
}
- } /* if (attribute_modified->Attr_Handle == (CustomContext.CustomGnss_PvHdle + CHARACTERISTIC_DESCRIPTOR_ATTRIBUTE_OFFSET))*/
+ } /* if (attribute_modified->Attr_Handle == (CustomContext.CustomSd_Gnss_MeasurementHdle + CHARACTERISTIC_DESCRIPTOR_ATTRIBUTE_OFFSET))*/
- else if (attribute_modified->Attr_Handle == (CustomContext.CustomStart_ControlHdle + CHARACTERISTIC_DESCRIPTOR_ATTRIBUTE_OFFSET))
+ else if (attribute_modified->Attr_Handle == (CustomContext.CustomSd_Control_PointHdle + CHARACTERISTIC_DESCRIPTOR_ATTRIBUTE_OFFSET))
+ {
+ return_value = SVCCTL_EvtAckFlowEnable;
+ /* USER CODE BEGIN CUSTOM_STM_Service_2_Char_2 */
+
+ /* USER CODE END CUSTOM_STM_Service_2_Char_2 */
+
+ switch (attribute_modified->Attr_Data[0])
+ {
+ /* USER CODE BEGIN CUSTOM_STM_Service_2_Char_2_attribute_modified */
+
+ /* USER CODE END CUSTOM_STM_Service_2_Char_2_attribute_modified */
+
+ /* Disabled Indication management */
+ case (!(COMSVC_Indication)):
+ /* USER CODE BEGIN CUSTOM_STM_Service_2_Char_2_Disabled_BEGIN */
+
+ /* USER CODE END CUSTOM_STM_Service_2_Char_2_attribute_modified */
+ Notification.Custom_Evt_Opcode = CUSTOM_STM_SD_CONTROL_POINT_INDICATE_DISABLED_EVT;
+ Custom_STM_App_Notification(&Notification);
+ /* USER CODE BEGIN CUSTOM_STM_Service_2_Char_2_Disabled_END */
+
+ /* USER CODE END CUSTOM_STM_Service_2_Char_2_Disabled_END */
+ break;
+
+ /* Enabled Indication management */
+ case COMSVC_Indication:
+ /* USER CODE BEGIN CUSTOM_STM_Service_2_Char_2_COMSVC_Indication_BEGIN */
+
+ /* USER CODE END CUSTOM_STM_Service_2_Char_2_COMSVC_Indication_BEGIN */
+ Notification.Custom_Evt_Opcode = CUSTOM_STM_SD_CONTROL_POINT_INDICATE_ENABLED_EVT;
+ Custom_STM_App_Notification(&Notification);
+ /* USER CODE BEGIN CUSTOM_STM_Service_2_Char_2_COMSVC_Indication_END */
+
+ /* USER CODE END CUSTOM_STM_Service_2_Char_2_COMSVC_Indication_END */
+ break;
+
+ default:
+ /* USER CODE BEGIN CUSTOM_STM_Service_2_Char_2_default */
+
+ /* USER CODE END CUSTOM_STM_Service_2_Char_2_default */
+ break;
+ }
+ } /* if (attribute_modified->Attr_Handle == (CustomContext.CustomSD_Control_PointHdle + CHARACTERISTIC_DESCRIPTOR_ATTRIBUTE_OFFSET))*/
+
+ else if (attribute_modified->Attr_Handle == (CustomContext.CustomSp_Control_PointHdle + CHARACTERISTIC_DESCRIPTOR_ATTRIBUTE_OFFSET))
{
return_value = SVCCTL_EvtAckFlowEnable;
/* USER CODE BEGIN CUSTOM_STM_Service_3_Char_1 */
@@ -275,7 +341,7 @@ static SVCCTL_EvtAckStatus_t Custom_STM_Event_Handler(void *Event)
/* USER CODE BEGIN CUSTOM_STM_Service_3_Char_1_Disabled_BEGIN */
/* USER CODE END CUSTOM_STM_Service_3_Char_1_attribute_modified */
- Notification.Custom_Evt_Opcode = CUSTOM_STM_START_CONTROL_INDICATE_DISABLED_EVT;
+ Notification.Custom_Evt_Opcode = CUSTOM_STM_SP_CONTROL_POINT_INDICATE_DISABLED_EVT;
Custom_STM_App_Notification(&Notification);
/* USER CODE BEGIN CUSTOM_STM_Service_3_Char_1_Disabled_END */
@@ -287,7 +353,7 @@ static SVCCTL_EvtAckStatus_t Custom_STM_Event_Handler(void *Event)
/* USER CODE BEGIN CUSTOM_STM_Service_3_Char_1_COMSVC_Indication_BEGIN */
/* USER CODE END CUSTOM_STM_Service_3_Char_1_COMSVC_Indication_BEGIN */
- Notification.Custom_Evt_Opcode = CUSTOM_STM_START_CONTROL_INDICATE_ENABLED_EVT;
+ Notification.Custom_Evt_Opcode = CUSTOM_STM_SP_CONTROL_POINT_INDICATE_ENABLED_EVT;
Custom_STM_App_Notification(&Notification);
/* USER CODE BEGIN CUSTOM_STM_Service_3_Char_1_COMSVC_Indication_END */
@@ -300,9 +366,9 @@ static SVCCTL_EvtAckStatus_t Custom_STM_Event_Handler(void *Event)
/* USER CODE END CUSTOM_STM_Service_3_Char_1_default */
break;
}
- } /* if (attribute_modified->Attr_Handle == (CustomContext.CustomStart_ControlHdle + CHARACTERISTIC_DESCRIPTOR_ATTRIBUTE_OFFSET))*/
+ } /* if (attribute_modified->Attr_Handle == (CustomContext.CustomSP_Control_PointHdle + CHARACTERISTIC_DESCRIPTOR_ATTRIBUTE_OFFSET))*/
- else if (attribute_modified->Attr_Handle == (CustomContext.CustomStart_ResultHdle + CHARACTERISTIC_DESCRIPTOR_ATTRIBUTE_OFFSET))
+ else if (attribute_modified->Attr_Handle == (CustomContext.CustomSp_ResultHdle + CHARACTERISTIC_DESCRIPTOR_ATTRIBUTE_OFFSET))
{
return_value = SVCCTL_EvtAckFlowEnable;
/* USER CODE BEGIN CUSTOM_STM_Service_3_Char_2 */
@@ -320,7 +386,7 @@ static SVCCTL_EvtAckStatus_t Custom_STM_Event_Handler(void *Event)
/* USER CODE BEGIN CUSTOM_STM_Service_3_Char_2_Disabled_BEGIN */
/* USER CODE END CUSTOM_STM_Service_3_Char_2_attribute_modified */
- Notification.Custom_Evt_Opcode = CUSTOM_STM_START_RESULT_INDICATE_DISABLED_EVT;
+ Notification.Custom_Evt_Opcode = CUSTOM_STM_SP_RESULT_INDICATE_DISABLED_EVT;
Custom_STM_App_Notification(&Notification);
/* USER CODE BEGIN CUSTOM_STM_Service_3_Char_2_Disabled_END */
@@ -332,7 +398,7 @@ static SVCCTL_EvtAckStatus_t Custom_STM_Event_Handler(void *Event)
/* USER CODE BEGIN CUSTOM_STM_Service_3_Char_2_COMSVC_Indication_BEGIN */
/* USER CODE END CUSTOM_STM_Service_3_Char_2_COMSVC_Indication_BEGIN */
- Notification.Custom_Evt_Opcode = CUSTOM_STM_START_RESULT_INDICATE_ENABLED_EVT;
+ Notification.Custom_Evt_Opcode = CUSTOM_STM_SP_RESULT_INDICATE_ENABLED_EVT;
Custom_STM_App_Notification(&Notification);
/* USER CODE BEGIN CUSTOM_STM_Service_3_Char_2_COMSVC_Indication_END */
@@ -345,28 +411,182 @@ static SVCCTL_EvtAckStatus_t Custom_STM_Event_Handler(void *Event)
/* USER CODE END CUSTOM_STM_Service_3_Char_2_default */
break;
}
- } /* if (attribute_modified->Attr_Handle == (CustomContext.CustomStart_ResultHdle + CHARACTERISTIC_DESCRIPTOR_ATTRIBUTE_OFFSET))*/
+ } /* if (attribute_modified->Attr_Handle == (CustomContext.CustomSP_ResultHdle + CHARACTERISTIC_DESCRIPTOR_ATTRIBUTE_OFFSET))*/
- else if (attribute_modified->Attr_Handle == (CustomContext.CustomCrs_RxHdle + CHARACTERISTIC_VALUE_ATTRIBUTE_OFFSET))
+ else if (attribute_modified->Attr_Handle == (CustomContext.CustomDs_ModeHdle + CHARACTERISTIC_DESCRIPTOR_ATTRIBUTE_OFFSET))
+ {
+ return_value = SVCCTL_EvtAckFlowEnable;
+ /* USER CODE BEGIN CUSTOM_STM_Service_4_Char_1 */
+
+ /* USER CODE END CUSTOM_STM_Service_4_Char_1 */
+
+ switch (attribute_modified->Attr_Data[0])
+ {
+ /* USER CODE BEGIN CUSTOM_STM_Service_4_Char_1_attribute_modified */
+
+ /* USER CODE END CUSTOM_STM_Service_4_Char_1_attribute_modified */
+
+ /* Disabled Indication management */
+ case (!(COMSVC_Indication)):
+ /* USER CODE BEGIN CUSTOM_STM_Service_4_Char_1_Disabled_BEGIN */
+
+ /* USER CODE END CUSTOM_STM_Service_4_Char_1_attribute_modified */
+ Notification.Custom_Evt_Opcode = CUSTOM_STM_DS_MODE_INDICATE_DISABLED_EVT;
+ Custom_STM_App_Notification(&Notification);
+ /* USER CODE BEGIN CUSTOM_STM_Service_4_Char_1_Disabled_END */
+
+ /* USER CODE END CUSTOM_STM_Service_4_Char_1_Disabled_END */
+ break;
+
+ /* Enabled Indication management */
+ case COMSVC_Indication:
+ /* USER CODE BEGIN CUSTOM_STM_Service_4_Char_1_COMSVC_Indication_BEGIN */
+
+ /* USER CODE END CUSTOM_STM_Service_4_Char_1_COMSVC_Indication_BEGIN */
+ Notification.Custom_Evt_Opcode = CUSTOM_STM_DS_MODE_INDICATE_ENABLED_EVT;
+ Custom_STM_App_Notification(&Notification);
+ /* USER CODE BEGIN CUSTOM_STM_Service_4_Char_1_COMSVC_Indication_END */
+
+ /* USER CODE END CUSTOM_STM_Service_4_Char_1_COMSVC_Indication_END */
+ break;
+
+ default:
+ /* USER CODE BEGIN CUSTOM_STM_Service_4_Char_1_default */
+
+ /* USER CODE END CUSTOM_STM_Service_4_Char_1_default */
+ break;
+ }
+ } /* if (attribute_modified->Attr_Handle == (CustomContext.CustomDS_ModeHdle + CHARACTERISTIC_DESCRIPTOR_ATTRIBUTE_OFFSET))*/
+
+ else if (attribute_modified->Attr_Handle == (CustomContext.CustomDs_Control_PointHdle + CHARACTERISTIC_DESCRIPTOR_ATTRIBUTE_OFFSET))
+ {
+ return_value = SVCCTL_EvtAckFlowEnable;
+ /* USER CODE BEGIN CUSTOM_STM_Service_4_Char_2 */
+
+ /* USER CODE END CUSTOM_STM_Service_4_Char_2 */
+
+ switch (attribute_modified->Attr_Data[0])
+ {
+ /* USER CODE BEGIN CUSTOM_STM_Service_4_Char_2_attribute_modified */
+
+ /* USER CODE END CUSTOM_STM_Service_4_Char_2_attribute_modified */
+
+ /* Disabled Indication management */
+ case (!(COMSVC_Indication)):
+ /* USER CODE BEGIN CUSTOM_STM_Service_4_Char_2_Disabled_BEGIN */
+
+ /* USER CODE END CUSTOM_STM_Service_4_Char_2_attribute_modified */
+ Notification.Custom_Evt_Opcode = CUSTOM_STM_DS_CONTROL_POINT_INDICATE_DISABLED_EVT;
+ Custom_STM_App_Notification(&Notification);
+ /* USER CODE BEGIN CUSTOM_STM_Service_4_Char_2_Disabled_END */
+
+ /* USER CODE END CUSTOM_STM_Service_4_Char_2_Disabled_END */
+ break;
+
+ /* Enabled Indication management */
+ case COMSVC_Indication:
+ /* USER CODE BEGIN CUSTOM_STM_Service_4_Char_2_COMSVC_Indication_BEGIN */
+
+ /* USER CODE END CUSTOM_STM_Service_4_Char_2_COMSVC_Indication_BEGIN */
+ Notification.Custom_Evt_Opcode = CUSTOM_STM_DS_CONTROL_POINT_INDICATE_ENABLED_EVT;
+ Custom_STM_App_Notification(&Notification);
+ /* USER CODE BEGIN CUSTOM_STM_Service_4_Char_2_COMSVC_Indication_END */
+
+ /* USER CODE END CUSTOM_STM_Service_4_Char_2_COMSVC_Indication_END */
+ break;
+
+ default:
+ /* USER CODE BEGIN CUSTOM_STM_Service_4_Char_2_default */
+
+ /* USER CODE END CUSTOM_STM_Service_4_Char_2_default */
+ break;
+ }
+ } /* if (attribute_modified->Attr_Handle == (CustomContext.CustomDS_Control_PointHdle + CHARACTERISTIC_DESCRIPTOR_ATTRIBUTE_OFFSET))*/
+
+ else if (attribute_modified->Attr_Handle == (CustomContext.CustomBattery_LevelHdle + CHARACTERISTIC_DESCRIPTOR_ATTRIBUTE_OFFSET))
+ {
+ return_value = SVCCTL_EvtAckFlowEnable;
+ /* USER CODE BEGIN CUSTOM_STM_Service_5_Char_1 */
+
+ /* USER CODE END CUSTOM_STM_Service_5_Char_1 */
+ switch (attribute_modified->Attr_Data[0])
+ {
+ /* USER CODE BEGIN CUSTOM_STM_Service_5_Char_1_attribute_modified */
+
+ /* USER CODE END CUSTOM_STM_Service_5_Char_1_attribute_modified */
+
+ /* Disabled Notification management */
+ case (!(COMSVC_Notification)):
+ /* USER CODE BEGIN CUSTOM_STM_Service_5_Char_1_Disabled_BEGIN */
+
+ /* USER CODE END CUSTOM_STM_Service_5_Char_1_Disabled_BEGIN */
+ Notification.Custom_Evt_Opcode = CUSTOM_STM_BATTERY_LEVEL_NOTIFY_DISABLED_EVT;
+ Custom_STM_App_Notification(&Notification);
+ /* USER CODE BEGIN CUSTOM_STM_Service_5_Char_1_Disabled_END */
+
+ /* USER CODE END CUSTOM_STM_Service_5_Char_1_Disabled_END */
+ break;
+
+ /* Enabled Notification management */
+ case COMSVC_Notification:
+ /* USER CODE BEGIN CUSTOM_STM_Service_5_Char_1_COMSVC_Notification_BEGIN */
+
+ /* USER CODE END CUSTOM_STM_Service_5_Char_1_COMSVC_Notification_BEGIN */
+ Notification.Custom_Evt_Opcode = CUSTOM_STM_BATTERY_LEVEL_NOTIFY_ENABLED_EVT;
+ Custom_STM_App_Notification(&Notification);
+ /* USER CODE BEGIN CUSTOM_STM_Service_5_Char_1_COMSVC_Notification_END */
+
+ /* USER CODE END CUSTOM_STM_Service_5_Char_1_COMSVC_Notification_END */
+ break;
+
+ default:
+ /* USER CODE BEGIN CUSTOM_STM_Service_5_Char_1_default */
+
+ /* USER CODE END CUSTOM_STM_Service_5_Char_1_default */
+ break;
+ }
+ } /* if (attribute_modified->Attr_Handle == (CustomContext.CustomBattery_LevelHdle + CHARACTERISTIC_DESCRIPTOR_ATTRIBUTE_OFFSET))*/
+
+ else if (attribute_modified->Attr_Handle == (CustomContext.CustomFt_Packet_InHdle + CHARACTERISTIC_VALUE_ATTRIBUTE_OFFSET))
{
return_value = SVCCTL_EvtAckFlowEnable;
/* USER CODE BEGIN CUSTOM_STM_Service_1_Char_2_ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE */
- Notification.Custom_Evt_Opcode = CUSTOM_STM_CRS_RX_WRITE_NO_RESP_EVT;
+ Notification.Custom_Evt_Opcode = CUSTOM_STM_FT_PACKET_IN_WRITE_NO_RESP_EVT;
Notification.DataTransfered.Length = attribute_modified->Attr_Data_Length;
Notification.DataTransfered.pPayload = attribute_modified->Attr_Data;
Custom_STM_App_Notification(&Notification);
/* USER CODE END CUSTOM_STM_Service_1_Char_2_ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE */
- } /* if (attribute_modified->Attr_Handle == (CustomContext.CustomCrs_RxHdle + CHARACTERISTIC_VALUE_ATTRIBUTE_OFFSET))*/
- else if (attribute_modified->Attr_Handle == (CustomContext.CustomStart_ControlHdle + CHARACTERISTIC_VALUE_ATTRIBUTE_OFFSET))
+ } /* if (attribute_modified->Attr_Handle == (CustomContext.CustomFt_Packet_InHdle + CHARACTERISTIC_VALUE_ATTRIBUTE_OFFSET))*/
+ else if (attribute_modified->Attr_Handle == (CustomContext.CustomSd_Control_PointHdle + CHARACTERISTIC_VALUE_ATTRIBUTE_OFFSET))
+ {
+ return_value = SVCCTL_EvtAckFlowEnable;
+ /* USER CODE BEGIN CUSTOM_STM_Service_2_Char_2_ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE */
+ Notification.Custom_Evt_Opcode = CUSTOM_STM_SD_CONTROL_POINT_WRITE_EVT;
+ Notification.DataTransfered.Length = attribute_modified->Attr_Data_Length;
+ Notification.DataTransfered.pPayload = attribute_modified->Attr_Data;
+ Custom_STM_App_Notification(&Notification);
+ /* USER CODE END CUSTOM_STM_Service_2_Char_2_ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE */
+ } /* if (attribute_modified->Attr_Handle == (CustomContext.CustomSd_Control_PointHdle + CHARACTERISTIC_VALUE_ATTRIBUTE_OFFSET))*/
+ else if (attribute_modified->Attr_Handle == (CustomContext.CustomSp_Control_PointHdle + CHARACTERISTIC_VALUE_ATTRIBUTE_OFFSET))
{
return_value = SVCCTL_EvtAckFlowEnable;
/* USER CODE BEGIN CUSTOM_STM_Service_3_Char_1_ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE */
- Notification.Custom_Evt_Opcode = CUSTOM_STM_START_CONTROL_WRITE_EVT;
+ Notification.Custom_Evt_Opcode = CUSTOM_STM_SP_CONTROL_POINT_WRITE_EVT;
Notification.DataTransfered.Length = attribute_modified->Attr_Data_Length;
Notification.DataTransfered.pPayload = attribute_modified->Attr_Data;
Custom_STM_App_Notification(&Notification);
/* USER CODE END CUSTOM_STM_Service_3_Char_1_ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE */
- } /* if (attribute_modified->Attr_Handle == (CustomContext.CustomStart_ControlHdle + CHARACTERISTIC_VALUE_ATTRIBUTE_OFFSET))*/
+ } /* if (attribute_modified->Attr_Handle == (CustomContext.CustomSp_Control_PointHdle + CHARACTERISTIC_VALUE_ATTRIBUTE_OFFSET))*/
+ else if (attribute_modified->Attr_Handle == (CustomContext.CustomDs_Control_PointHdle + CHARACTERISTIC_VALUE_ATTRIBUTE_OFFSET))
+ {
+ return_value = SVCCTL_EvtAckFlowEnable;
+ /* USER CODE BEGIN CUSTOM_STM_Service_4_Char_2_ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE */
+ Notification.Custom_Evt_Opcode = CUSTOM_STM_DS_CONTROL_POINT_WRITE_EVT;
+ Notification.DataTransfered.Length = attribute_modified->Attr_Data_Length;
+ Notification.DataTransfered.pPayload = attribute_modified->Attr_Data;
+ Custom_STM_App_Notification(&Notification);
+ /* USER CODE END CUSTOM_STM_Service_4_Char_2_ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE */
+ } /* if (attribute_modified->Attr_Handle == (CustomContext.CustomDs_Control_PointHdle + CHARACTERISTIC_VALUE_ATTRIBUTE_OFFSET))*/
/* USER CODE BEGIN EVT_BLUE_GATT_ATTRIBUTE_MODIFIED_END */
/* USER CODE END EVT_BLUE_GATT_ATTRIBUTE_MODIFIED_END */
@@ -377,30 +597,55 @@ static SVCCTL_EvtAckStatus_t Custom_STM_Event_Handler(void *Event)
/* USER CODE END EVT_BLUE_GATT_READ_PERMIT_REQ_BEGIN */
read_req = (aci_gatt_read_permit_req_event_rp0*)blecore_evt->data;
- if (read_req->Attribute_Handle == (CustomContext.CustomGnss_PvHdle + CHARACTERISTIC_VALUE_ATTRIBUTE_OFFSET))
+ if (read_req->Attribute_Handle == (CustomContext.CustomSd_Gnss_MeasurementHdle + CHARACTERISTIC_VALUE_ATTRIBUTE_OFFSET))
{
return_value = SVCCTL_EvtAckFlowEnable;
/*USER CODE BEGIN CUSTOM_STM_Service_2_Char_1_ACI_GATT_READ_PERMIT_REQ_VSEVT_CODE_1 */
- Notification.Custom_Evt_Opcode = CUSTOM_STM_GNSS_PV_READ_EVT;
+ Notification.Custom_Evt_Opcode = CUSTOM_STM_SD_GNSS_MEASUREMENT_READ_EVT;
Custom_STM_App_Notification(&Notification);
/*USER CODE END CUSTOM_STM_Service_2_Char_1_ACI_GATT_READ_PERMIT_REQ_VSEVT_CODE_1*/
aci_gatt_allow_read(read_req->Connection_Handle);
/*USER CODE BEGIN CUSTOM_STM_Service_2_Char_1_ACI_GATT_READ_PERMIT_REQ_VSEVT_CODE_2 */
/*USER CODE END CUSTOM_STM_Service_2_Char_1_ACI_GATT_READ_PERMIT_REQ_VSEVT_CODE_2*/
- } /* if (read_req->Attribute_Handle == (CustomContext.CustomGnss_PvHdle + CHARACTERISTIC_VALUE_ATTRIBUTE_OFFSET))*/
- else if (read_req->Attribute_Handle == (CustomContext.CustomStart_ResultHdle + CHARACTERISTIC_VALUE_ATTRIBUTE_OFFSET))
+ } /* if (read_req->Attribute_Handle == (CustomContext.CustomSd_Gnss_MeasurementHdle + CHARACTERISTIC_VALUE_ATTRIBUTE_OFFSET))*/
+ else if (read_req->Attribute_Handle == (CustomContext.CustomSp_ResultHdle + CHARACTERISTIC_VALUE_ATTRIBUTE_OFFSET))
{
return_value = SVCCTL_EvtAckFlowEnable;
/*USER CODE BEGIN CUSTOM_STM_Service_3_Char_2_ACI_GATT_READ_PERMIT_REQ_VSEVT_CODE_1 */
- Notification.Custom_Evt_Opcode = CUSTOM_STM_START_RESULT_READ_EVT;
+ Notification.Custom_Evt_Opcode = CUSTOM_STM_SP_RESULT_READ_EVT;
Custom_STM_App_Notification(&Notification);
/*USER CODE END CUSTOM_STM_Service_3_Char_2_ACI_GATT_READ_PERMIT_REQ_VSEVT_CODE_1*/
aci_gatt_allow_read(read_req->Connection_Handle);
/*USER CODE BEGIN CUSTOM_STM_Service_3_Char_2_ACI_GATT_READ_PERMIT_REQ_VSEVT_CODE_2 */
/*USER CODE END CUSTOM_STM_Service_3_Char_2_ACI_GATT_READ_PERMIT_REQ_VSEVT_CODE_2*/
- } /* if (read_req->Attribute_Handle == (CustomContext.CustomStart_ResultHdle + CHARACTERISTIC_VALUE_ATTRIBUTE_OFFSET))*/
+ } /* if (read_req->Attribute_Handle == (CustomContext.CustomSp_ResultHdle + CHARACTERISTIC_VALUE_ATTRIBUTE_OFFSET))*/
+ else if (read_req->Attribute_Handle == (CustomContext.CustomDs_ModeHdle + CHARACTERISTIC_VALUE_ATTRIBUTE_OFFSET))
+ {
+ return_value = SVCCTL_EvtAckFlowEnable;
+ /*USER CODE BEGIN CUSTOM_STM_Service_4_Char_1_ACI_GATT_READ_PERMIT_REQ_VSEVT_CODE_1 */
+ Notification.Custom_Evt_Opcode = CUSTOM_STM_DS_MODE_READ_EVT;
+ Custom_STM_App_Notification(&Notification);
+ /*USER CODE END CUSTOM_STM_Service_4_Char_1_ACI_GATT_READ_PERMIT_REQ_VSEVT_CODE_1*/
+ aci_gatt_allow_read(read_req->Connection_Handle);
+ /*USER CODE BEGIN CUSTOM_STM_Service_4_Char_1_ACI_GATT_READ_PERMIT_REQ_VSEVT_CODE_2 */
+
+ /*USER CODE END CUSTOM_STM_Service_4_Char_1_ACI_GATT_READ_PERMIT_REQ_VSEVT_CODE_2*/
+ } /* if (read_req->Attribute_Handle == (CustomContext.CustomDs_ModeHdle + CHARACTERISTIC_VALUE_ATTRIBUTE_OFFSET))*/
+ else if (read_req->Attribute_Handle == (CustomContext.CustomBattery_LevelHdle + CHARACTERISTIC_VALUE_ATTRIBUTE_OFFSET))
+ {
+ return_value = SVCCTL_EvtAckFlowEnable;
+ /*USER CODE BEGIN CUSTOM_STM_Service_5_Char_1_ACI_GATT_READ_PERMIT_REQ_VSEVT_CODE_1 */
+ Notification.Custom_Evt_Opcode = CUSTOM_STM_BATTERY_LEVEL_READ_EVT;
+ Notification.ConnectionHandle = read_req->Connection_Handle;
+ Custom_STM_App_Notification(&Notification);
+ /*USER CODE END CUSTOM_STM_Service_5_Char_1_ACI_GATT_READ_PERMIT_REQ_VSEVT_CODE_1*/
+ aci_gatt_allow_read(read_req->Connection_Handle);
+ /*USER CODE BEGIN CUSTOM_STM_Service_5_Char_1_ACI_GATT_READ_PERMIT_REQ_VSEVT_CODE_2 */
+
+ /*USER CODE END CUSTOM_STM_Service_5_Char_1_ACI_GATT_READ_PERMIT_REQ_VSEVT_CODE_2*/
+ } /* if (read_req->Attribute_Handle == (CustomContext.CustomBattery_LevelHdle + CHARACTERISTIC_VALUE_ATTRIBUTE_OFFSET))*/
/* USER CODE BEGIN EVT_BLUE_GATT_READ_PERMIT_REQ_END */
/* USER CODE END EVT_BLUE_GATT_READ_PERMIT_REQ_END */
@@ -486,13 +731,13 @@ void SVCCTL_InitCustomSvc(void)
SVCCTL_RegisterSvcHandler(Custom_STM_Event_Handler);
/**
- * CRS
+ * File_Transfer
*
* Max_Attribute_Records = 1 + 2*2 + 1*no_of_char_with_notify_or_indicate_property + 1*no_of_char_with_broadcast_property
- * service_max_attribute_record = 1 for CRS +
- * 2 for CRS_TX +
- * 2 for CRS_RX +
- * 1 for CRS_TX configuration descriptor +
+ * service_max_attribute_record = 1 for File_Transfer +
+ * 2 for FT_Packet_Out +
+ * 2 for FT_Packet_In +
+ * 1 for FT_Packet_Out configuration descriptor +
* = 6
*
* This value doesn't take into account number of descriptors manually added
@@ -505,41 +750,41 @@ void SVCCTL_InitCustomSvc(void)
/* USER CODE END SVCCTL_InitService */
- COPY_CRS_UUID(uuid.Char_UUID_128);
+ COPY_FILE_TRANSFER_UUID(uuid.Char_UUID_128);
ret = aci_gatt_add_service(UUID_TYPE_128,
(Service_UUID_t *) &uuid,
PRIMARY_SERVICE,
max_attr_record,
- &(CustomContext.CustomCrsHdle));
+ &(CustomContext.CustomFile_TransferHdle));
if (ret != BLE_STATUS_SUCCESS)
{
- APP_DBG_MSG(" Fail : aci_gatt_add_service command: CRS, error code: 0x%x \n\r", ret);
+ APP_DBG_MSG(" Fail : aci_gatt_add_service command: File_Transfer, error code: 0x%x \n\r", ret);
}
else
{
- APP_DBG_MSG(" Success: aci_gatt_add_service command: CRS \n\r");
+ APP_DBG_MSG(" Success: aci_gatt_add_service command: File_Transfer \n\r");
}
/**
- * CRS_TX
+ * FT_Packet_Out
*/
- COPY_CRS_TX_UUID(uuid.Char_UUID_128);
- ret = aci_gatt_add_char(CustomContext.CustomCrsHdle,
+ COPY_FT_PACKET_OUT_UUID(uuid.Char_UUID_128);
+ ret = aci_gatt_add_char(CustomContext.CustomFile_TransferHdle,
UUID_TYPE_128, &uuid,
- SizeCrs_Tx,
+ SizeFt_Packet_Out,
CHAR_PROP_NOTIFY,
ATTR_PERMISSION_ENCRY_READ | ATTR_PERMISSION_ENCRY_WRITE,
GATT_DONT_NOTIFY_EVENTS,
0x10,
CHAR_VALUE_LEN_VARIABLE,
- &(CustomContext.CustomCrs_TxHdle));
+ &(CustomContext.CustomFt_Packet_OutHdle));
if (ret != BLE_STATUS_SUCCESS)
{
- APP_DBG_MSG(" Fail : aci_gatt_add_char command : CRS_TX, error code: 0x%x \n\r", ret);
+ APP_DBG_MSG(" Fail : aci_gatt_add_char command : FT_PACKET_OUT, error code: 0x%x \n\r", ret);
}
else
{
- APP_DBG_MSG(" Success: aci_gatt_add_char command : CRS_TX \n\r");
+ APP_DBG_MSG(" Success: aci_gatt_add_char command : FT_PACKET_OUT \n\r");
}
/* USER CODE BEGIN SVCCTL_Init_Service1_Char1/ */
@@ -547,25 +792,25 @@ void SVCCTL_InitCustomSvc(void)
/* USER CODE END SVCCTL_Init_Service1_Char1 */
/**
- * CRS_RX
+ * FT_Packet_In
*/
- COPY_CRS_RX_UUID(uuid.Char_UUID_128);
- ret = aci_gatt_add_char(CustomContext.CustomCrsHdle,
+ COPY_FT_PACKET_IN_UUID(uuid.Char_UUID_128);
+ ret = aci_gatt_add_char(CustomContext.CustomFile_TransferHdle,
UUID_TYPE_128, &uuid,
- SizeCrs_Rx,
+ SizeFt_Packet_In,
CHAR_PROP_READ | CHAR_PROP_WRITE_WITHOUT_RESP,
ATTR_PERMISSION_ENCRY_READ | ATTR_PERMISSION_ENCRY_WRITE,
GATT_NOTIFY_ATTRIBUTE_WRITE,
0x10,
CHAR_VALUE_LEN_VARIABLE,
- &(CustomContext.CustomCrs_RxHdle));
+ &(CustomContext.CustomFt_Packet_InHdle));
if (ret != BLE_STATUS_SUCCESS)
{
- APP_DBG_MSG(" Fail : aci_gatt_add_char command : CRS_RX, error code: 0x%x \n\r", ret);
+ APP_DBG_MSG(" Fail : aci_gatt_add_char command : FT_PACKET_IN, error code: 0x%x \n\r", ret);
}
else
{
- APP_DBG_MSG(" Success: aci_gatt_add_char command : CRS_RX \n\r");
+ APP_DBG_MSG(" Success: aci_gatt_add_char command : FT_PACKET_IN \n\r");
}
/* USER CODE BEGIN SVCCTL_Init_Service1_Char2/ */
@@ -574,75 +819,103 @@ void SVCCTL_InitCustomSvc(void)
/* USER CODE END SVCCTL_Init_Service1_Char2 */
/**
- * GNSS
+ * Sensor_Data
*
- * Max_Attribute_Records = 1 + 2*1 + 1*no_of_char_with_notify_or_indicate_property + 1*no_of_char_with_broadcast_property
- * service_max_attribute_record = 1 for GNSS +
- * 2 for GNSS_PV +
- * 1 for GNSS_PV configuration descriptor +
- * = 4
+ * Max_Attribute_Records = 1 + 2*2 + 1*no_of_char_with_notify_or_indicate_property + 1*no_of_char_with_broadcast_property
+ * service_max_attribute_record = 1 for Sensor_Data +
+ * 2 for SD_GNSS_Measurement +
+ * 2 for SD_Control_Point +
+ * 1 for SD_GNSS_Measurement configuration descriptor +
+ * 1 for SD_Control_Point configuration descriptor +
+ * = 7
*
* This value doesn't take into account number of descriptors manually added
* In case of descriptors added, please update the max_attr_record value accordingly in the next SVCCTL_InitService User Section
*/
- max_attr_record = 4;
+ max_attr_record = 7;
/* USER CODE BEGIN SVCCTL_InitService */
/* max_attr_record to be updated if descriptors have been added */
/* USER CODE END SVCCTL_InitService */
- COPY_GNSS_UUID(uuid.Char_UUID_128);
+ COPY_SENSOR_DATA_UUID(uuid.Char_UUID_128);
ret = aci_gatt_add_service(UUID_TYPE_128,
(Service_UUID_t *) &uuid,
PRIMARY_SERVICE,
max_attr_record,
- &(CustomContext.CustomGnssHdle));
+ &(CustomContext.CustomSensor_DataHdle));
if (ret != BLE_STATUS_SUCCESS)
{
- APP_DBG_MSG(" Fail : aci_gatt_add_service command: GNSS, error code: 0x%x \n\r", ret);
+ APP_DBG_MSG(" Fail : aci_gatt_add_service command: Sensor_Data, error code: 0x%x \n\r", ret);
}
else
{
- APP_DBG_MSG(" Success: aci_gatt_add_service command: GNSS \n\r");
+ APP_DBG_MSG(" Success: aci_gatt_add_service command: Sensor_Data \n\r");
}
/**
- * GNSS_PV
+ * SD_GNSS_Measurement
*/
- COPY_GNSS_PV_UUID(uuid.Char_UUID_128);
- ret = aci_gatt_add_char(CustomContext.CustomGnssHdle,
+ COPY_SD_GNSS_MEASUREMENT_UUID(uuid.Char_UUID_128);
+ ret = aci_gatt_add_char(CustomContext.CustomSensor_DataHdle,
UUID_TYPE_128, &uuid,
- SizeGnss_Pv,
+ SizeSd_Gnss_Measurement,
CHAR_PROP_READ | CHAR_PROP_NOTIFY,
ATTR_PERMISSION_ENCRY_READ | ATTR_PERMISSION_ENCRY_WRITE,
GATT_NOTIFY_READ_REQ_AND_WAIT_FOR_APPL_RESP,
0x10,
- CHAR_VALUE_LEN_CONSTANT,
- &(CustomContext.CustomGnss_PvHdle));
+ CHAR_VALUE_LEN_VARIABLE,
+ &(CustomContext.CustomSd_Gnss_MeasurementHdle));
if (ret != BLE_STATUS_SUCCESS)
{
- APP_DBG_MSG(" Fail : aci_gatt_add_char command : GNSS_PV, error code: 0x%x \n\r", ret);
+ APP_DBG_MSG(" Fail : aci_gatt_add_char command : SD_GNSS_MEASUREMENT, error code: 0x%x \n\r", ret);
}
else
{
- APP_DBG_MSG(" Success: aci_gatt_add_char command : GNSS_PV \n\r");
+ APP_DBG_MSG(" Success: aci_gatt_add_char command : SD_GNSS_MEASUREMENT \n\r");
}
/* USER CODE BEGIN SVCCTL_Init_Service2_Char1/ */
/* Place holder for Characteristic Descriptors */
/* USER CODE END SVCCTL_Init_Service2_Char1 */
+ /**
+ * SD_Control_Point
+ */
+ COPY_SD_CONTROL_POINT_UUID(uuid.Char_UUID_128);
+ ret = aci_gatt_add_char(CustomContext.CustomSensor_DataHdle,
+ UUID_TYPE_128, &uuid,
+ SizeSd_Control_Point,
+ CHAR_PROP_WRITE | CHAR_PROP_INDICATE,
+ ATTR_PERMISSION_ENCRY_READ | ATTR_PERMISSION_ENCRY_WRITE,
+ GATT_NOTIFY_ATTRIBUTE_WRITE,
+ 0x10,
+ CHAR_VALUE_LEN_VARIABLE,
+ &(CustomContext.CustomSd_Control_PointHdle));
+ if (ret != BLE_STATUS_SUCCESS)
+ {
+ APP_DBG_MSG(" Fail : aci_gatt_add_char command : SD_CONTROL_POINT, error code: 0x%x \n\r", ret);
+ }
+ else
+ {
+ APP_DBG_MSG(" Success: aci_gatt_add_char command : SD_CONTROL_POINT \n\r");
+ }
+
+ /* USER CODE BEGIN SVCCTL_Init_Service2_Char2/ */
+ /* Place holder for Characteristic Descriptors */
+
+ /* USER CODE END SVCCTL_Init_Service2_Char2 */
/**
- * Start
+ * Starter_Pistol
*
* Max_Attribute_Records = 1 + 2*2 + 1*no_of_char_with_notify_or_indicate_property + 1*no_of_char_with_broadcast_property
- * service_max_attribute_record = 1 for Start +
- * 2 for Start_Control +
- * 2 for Start_Result +
- * 1 for Start_Control configuration descriptor +
- * 1 for Start_Result configuration descriptor +
+ * service_max_attribute_record = 1 for Starter_Pistol +
+ * 2 for SP_Control_Point +
+ * 2 for SP_Result +
+ * 1 for SP_Control_Point configuration descriptor +
+ * 1 for SP_Result configuration descriptor +
* = 7
*
* This value doesn't take into account number of descriptors manually added
@@ -655,41 +928,41 @@ void SVCCTL_InitCustomSvc(void)
/* USER CODE END SVCCTL_InitService */
- COPY_START_UUID(uuid.Char_UUID_128);
+ COPY_STARTER_PISTOL_UUID(uuid.Char_UUID_128);
ret = aci_gatt_add_service(UUID_TYPE_128,
(Service_UUID_t *) &uuid,
PRIMARY_SERVICE,
max_attr_record,
- &(CustomContext.CustomStartHdle));
+ &(CustomContext.CustomStarter_PistolHdle));
if (ret != BLE_STATUS_SUCCESS)
{
- APP_DBG_MSG(" Fail : aci_gatt_add_service command: Start, error code: 0x%x \n\r", ret);
+ APP_DBG_MSG(" Fail : aci_gatt_add_service command: Starter_Pistol, error code: 0x%x \n\r", ret);
}
else
{
- APP_DBG_MSG(" Success: aci_gatt_add_service command: Start \n\r");
+ APP_DBG_MSG(" Success: aci_gatt_add_service command: Starter_Pistol \n\r");
}
/**
- * Start_Control
+ * SP_Control_Point
*/
- COPY_START_CONTROL_UUID(uuid.Char_UUID_128);
- ret = aci_gatt_add_char(CustomContext.CustomStartHdle,
+ COPY_SP_CONTROL_POINT_UUID(uuid.Char_UUID_128);
+ ret = aci_gatt_add_char(CustomContext.CustomStarter_PistolHdle,
UUID_TYPE_128, &uuid,
- SizeStart_Control,
+ SizeSp_Control_Point,
CHAR_PROP_WRITE | CHAR_PROP_INDICATE,
ATTR_PERMISSION_ENCRY_READ | ATTR_PERMISSION_ENCRY_WRITE,
GATT_NOTIFY_ATTRIBUTE_WRITE,
0x10,
- CHAR_VALUE_LEN_CONSTANT,
- &(CustomContext.CustomStart_ControlHdle));
+ CHAR_VALUE_LEN_VARIABLE,
+ &(CustomContext.CustomSp_Control_PointHdle));
if (ret != BLE_STATUS_SUCCESS)
{
- APP_DBG_MSG(" Fail : aci_gatt_add_char command : START_CONTROL, error code: 0x%x \n\r", ret);
+ APP_DBG_MSG(" Fail : aci_gatt_add_char command : SP_CONTROL_POINT, error code: 0x%x \n\r", ret);
}
else
{
- APP_DBG_MSG(" Success: aci_gatt_add_char command : START_CONTROL \n\r");
+ APP_DBG_MSG(" Success: aci_gatt_add_char command : SP_CONTROL_POINT \n\r");
}
/* USER CODE BEGIN SVCCTL_Init_Service3_Char1/ */
@@ -697,25 +970,25 @@ void SVCCTL_InitCustomSvc(void)
/* USER CODE END SVCCTL_Init_Service3_Char1 */
/**
- * Start_Result
+ * SP_Result
*/
- COPY_START_RESULT_UUID(uuid.Char_UUID_128);
- ret = aci_gatt_add_char(CustomContext.CustomStartHdle,
+ COPY_SP_RESULT_UUID(uuid.Char_UUID_128);
+ ret = aci_gatt_add_char(CustomContext.CustomStarter_PistolHdle,
UUID_TYPE_128, &uuid,
- SizeStart_Result,
+ SizeSp_Result,
CHAR_PROP_READ | CHAR_PROP_INDICATE,
ATTR_PERMISSION_ENCRY_READ | ATTR_PERMISSION_ENCRY_WRITE,
GATT_NOTIFY_READ_REQ_AND_WAIT_FOR_APPL_RESP,
0x10,
CHAR_VALUE_LEN_CONSTANT,
- &(CustomContext.CustomStart_ResultHdle));
+ &(CustomContext.CustomSp_ResultHdle));
if (ret != BLE_STATUS_SUCCESS)
{
- APP_DBG_MSG(" Fail : aci_gatt_add_char command : START_RESULT, error code: 0x%x \n\r", ret);
+ APP_DBG_MSG(" Fail : aci_gatt_add_char command : SP_RESULT, error code: 0x%x \n\r", ret);
}
else
{
- APP_DBG_MSG(" Success: aci_gatt_add_char command : START_RESULT \n\r");
+ APP_DBG_MSG(" Success: aci_gatt_add_char command : SP_RESULT \n\r");
}
/* USER CODE BEGIN SVCCTL_Init_Service3_Char2/ */
@@ -723,6 +996,156 @@ void SVCCTL_InitCustomSvc(void)
/* USER CODE END SVCCTL_Init_Service3_Char2 */
+ /**
+ * Device_State
+ *
+ * Max_Attribute_Records = 1 + 2*2 + 1*no_of_char_with_notify_or_indicate_property + 1*no_of_char_with_broadcast_property
+ * service_max_attribute_record = 1 for Device_State +
+ * 2 for DS_Mode +
+ * 2 for DS_Control_Point +
+ * 1 for DS_Mode configuration descriptor +
+ * 1 for DS_Control_Point configuration descriptor +
+ * = 7
+ *
+ * This value doesn't take into account number of descriptors manually added
+ * In case of descriptors added, please update the max_attr_record value accordingly in the next SVCCTL_InitService User Section
+ */
+ max_attr_record = 7;
+
+ /* USER CODE BEGIN SVCCTL_InitService */
+ /* max_attr_record to be updated if descriptors have been added */
+
+ /* USER CODE END SVCCTL_InitService */
+
+ COPY_DEVICE_STATE_UUID(uuid.Char_UUID_128);
+ ret = aci_gatt_add_service(UUID_TYPE_128,
+ (Service_UUID_t *) &uuid,
+ PRIMARY_SERVICE,
+ max_attr_record,
+ &(CustomContext.CustomDevice_StateHdle));
+ if (ret != BLE_STATUS_SUCCESS)
+ {
+ APP_DBG_MSG(" Fail : aci_gatt_add_service command: Device_State, error code: 0x%x \n\r", ret);
+ }
+ else
+ {
+ APP_DBG_MSG(" Success: aci_gatt_add_service command: Device_State \n\r");
+ }
+
+ /**
+ * DS_Mode
+ */
+ COPY_DS_MODE_UUID(uuid.Char_UUID_128);
+ ret = aci_gatt_add_char(CustomContext.CustomDevice_StateHdle,
+ UUID_TYPE_128, &uuid,
+ SizeDs_Mode,
+ CHAR_PROP_READ | CHAR_PROP_INDICATE,
+ ATTR_PERMISSION_ENCRY_READ,
+ GATT_NOTIFY_READ_REQ_AND_WAIT_FOR_APPL_RESP,
+ 0x10,
+ CHAR_VALUE_LEN_CONSTANT,
+ &(CustomContext.CustomDs_ModeHdle));
+ if (ret != BLE_STATUS_SUCCESS)
+ {
+ APP_DBG_MSG(" Fail : aci_gatt_add_char command : DS_MODE, error code: 0x%x \n\r", ret);
+ }
+ else
+ {
+ APP_DBG_MSG(" Success: aci_gatt_add_char command : DS_MODE \n\r");
+ }
+
+ /* USER CODE BEGIN SVCCTL_Init_Service4_Char1/ */
+ /* Place holder for Characteristic Descriptors */
+
+ /* USER CODE END SVCCTL_Init_Service4_Char1 */
+ /**
+ * DS_Control_Point
+ */
+ COPY_DS_CONTROL_POINT_UUID(uuid.Char_UUID_128);
+ ret = aci_gatt_add_char(CustomContext.CustomDevice_StateHdle,
+ UUID_TYPE_128, &uuid,
+ SizeDs_Control_Point,
+ CHAR_PROP_WRITE | CHAR_PROP_INDICATE,
+ ATTR_PERMISSION_ENCRY_READ | ATTR_PERMISSION_ENCRY_WRITE,
+ GATT_NOTIFY_ATTRIBUTE_WRITE,
+ 0x10,
+ CHAR_VALUE_LEN_VARIABLE,
+ &(CustomContext.CustomDs_Control_PointHdle));
+ if (ret != BLE_STATUS_SUCCESS)
+ {
+ APP_DBG_MSG(" Fail : aci_gatt_add_char command : DS_CONTROL_POINT, error code: 0x%x \n\r", ret);
+ }
+ else
+ {
+ APP_DBG_MSG(" Success: aci_gatt_add_char command : DS_CONTROL_POINT \n\r");
+ }
+
+ /* USER CODE BEGIN SVCCTL_Init_Service4_Char2/ */
+ /* Place holder for Characteristic Descriptors */
+
+ /* USER CODE END SVCCTL_Init_Service4_Char2 */
+
+ /**
+ * Battery
+ *
+ * Max_Attribute_Records = 1 + 2*1 + 1*no_of_char_with_notify_or_indicate_property + 1*no_of_char_with_broadcast_property
+ * service_max_attribute_record = 1 for Battery +
+ * 2 for Battery_Level +
+ * 1 for Battery_Level configuration descriptor +
+ * = 4
+ *
+ * This value doesn't take into account number of descriptors manually added
+ * In case of descriptors added, please update the max_attr_record value accordingly in the next SVCCTL_InitService User Section
+ */
+ max_attr_record = 4;
+
+ /* USER CODE BEGIN SVCCTL_InitService */
+ /* max_attr_record to be updated if descriptors have been added */
+
+ /* USER CODE END SVCCTL_InitService */
+
+ uuid.Char_UUID_16 = 0x180f;
+ ret = aci_gatt_add_service(UUID_TYPE_16,
+ (Service_UUID_t *) &uuid,
+ PRIMARY_SERVICE,
+ max_attr_record,
+ &(CustomContext.CustomBatteryHdle));
+ if (ret != BLE_STATUS_SUCCESS)
+ {
+ APP_DBG_MSG(" Fail : aci_gatt_add_service command: Battery, error code: 0x%x \n\r", ret);
+ }
+ else
+ {
+ APP_DBG_MSG(" Success: aci_gatt_add_service command: Battery \n\r");
+ }
+
+ /**
+ * Battery_Level
+ */
+ uuid.Char_UUID_16 = 0x2a19;
+ ret = aci_gatt_add_char(CustomContext.CustomBatteryHdle,
+ UUID_TYPE_16, &uuid,
+ SizeBattery_Level,
+ CHAR_PROP_READ | CHAR_PROP_NOTIFY,
+ ATTR_PERMISSION_ENCRY_READ,
+ GATT_NOTIFY_READ_REQ_AND_WAIT_FOR_APPL_RESP,
+ 0x10,
+ CHAR_VALUE_LEN_CONSTANT,
+ &(CustomContext.CustomBattery_LevelHdle));
+ if (ret != BLE_STATUS_SUCCESS)
+ {
+ APP_DBG_MSG(" Fail : aci_gatt_add_char command : BATTERY_LEVEL, error code: 0x%x \n\r", ret);
+ }
+ else
+ {
+ APP_DBG_MSG(" Success: aci_gatt_add_char command : BATTERY_LEVEL \n\r");
+ }
+
+ /* USER CODE BEGIN SVCCTL_Init_Service5_Char1/ */
+ /* Place holder for Characteristic Descriptors */
+
+ /* USER CODE END SVCCTL_Init_Service5_Char1 */
+
/* USER CODE BEGIN SVCCTL_InitCustomSvc_2 */
/* USER CODE END SVCCTL_InitCustomSvc_2 */
@@ -746,101 +1169,177 @@ tBleStatus Custom_STM_App_Update_Char(Custom_STM_Char_Opcode_t CharOpcode, uint8
switch (CharOpcode)
{
- case CUSTOM_STM_CRS_TX:
- ret = aci_gatt_update_char_value(CustomContext.CustomCrsHdle,
- CustomContext.CustomCrs_TxHdle,
+ case CUSTOM_STM_FT_PACKET_OUT:
+ ret = aci_gatt_update_char_value(CustomContext.CustomFile_TransferHdle,
+ CustomContext.CustomFt_Packet_OutHdle,
0, /* charValOffset */
- SizeCrs_Tx, /* charValueLen */
+ SizeFt_Packet_Out, /* charValueLen */
(uint8_t *) pPayload);
if (ret != BLE_STATUS_SUCCESS)
{
- APP_DBG_MSG(" Fail : aci_gatt_update_char_value CRS_TX command, result : 0x%x \n\r", ret);
+ APP_DBG_MSG(" Fail : aci_gatt_update_char_value FT_PACKET_OUT command, result : 0x%x \n\r", ret);
}
else
{
- APP_DBG_MSG(" Success: aci_gatt_update_char_value CRS_TX command\n\r");
+ APP_DBG_MSG(" Success: aci_gatt_update_char_value FT_PACKET_OUT command\n\r");
}
/* USER CODE BEGIN CUSTOM_STM_App_Update_Service_1_Char_1*/
/* USER CODE END CUSTOM_STM_App_Update_Service_1_Char_1*/
break;
- case CUSTOM_STM_CRS_RX:
- ret = aci_gatt_update_char_value(CustomContext.CustomCrsHdle,
- CustomContext.CustomCrs_RxHdle,
+ case CUSTOM_STM_FT_PACKET_IN:
+ ret = aci_gatt_update_char_value(CustomContext.CustomFile_TransferHdle,
+ CustomContext.CustomFt_Packet_InHdle,
0, /* charValOffset */
- SizeCrs_Rx, /* charValueLen */
+ SizeFt_Packet_In, /* charValueLen */
(uint8_t *) pPayload);
if (ret != BLE_STATUS_SUCCESS)
{
- APP_DBG_MSG(" Fail : aci_gatt_update_char_value CRS_RX command, result : 0x%x \n\r", ret);
+ APP_DBG_MSG(" Fail : aci_gatt_update_char_value FT_PACKET_IN command, result : 0x%x \n\r", ret);
}
else
{
- APP_DBG_MSG(" Success: aci_gatt_update_char_value CRS_RX command\n\r");
+ APP_DBG_MSG(" Success: aci_gatt_update_char_value FT_PACKET_IN command\n\r");
}
/* USER CODE BEGIN CUSTOM_STM_App_Update_Service_1_Char_2*/
/* USER CODE END CUSTOM_STM_App_Update_Service_1_Char_2*/
break;
- case CUSTOM_STM_GNSS_PV:
- ret = aci_gatt_update_char_value(CustomContext.CustomGnssHdle,
- CustomContext.CustomGnss_PvHdle,
+ case CUSTOM_STM_SD_GNSS_MEASUREMENT:
+ ret = aci_gatt_update_char_value(CustomContext.CustomSensor_DataHdle,
+ CustomContext.CustomSd_Gnss_MeasurementHdle,
0, /* charValOffset */
- SizeGnss_Pv, /* charValueLen */
+ SizeSd_Gnss_Measurement, /* charValueLen */
(uint8_t *) pPayload);
if (ret != BLE_STATUS_SUCCESS)
{
- APP_DBG_MSG(" Fail : aci_gatt_update_char_value GNSS_PV command, result : 0x%x \n\r", ret);
+ APP_DBG_MSG(" Fail : aci_gatt_update_char_value SD_GNSS_MEASUREMENT command, result : 0x%x \n\r", ret);
}
else
{
- APP_DBG_MSG(" Success: aci_gatt_update_char_value GNSS_PV command\n\r");
+ APP_DBG_MSG(" Success: aci_gatt_update_char_value SD_GNSS_MEASUREMENT command\n\r");
}
/* USER CODE BEGIN CUSTOM_STM_App_Update_Service_2_Char_1*/
/* USER CODE END CUSTOM_STM_App_Update_Service_2_Char_1*/
break;
- case CUSTOM_STM_START_CONTROL:
- ret = aci_gatt_update_char_value(CustomContext.CustomStartHdle,
- CustomContext.CustomStart_ControlHdle,
+ case CUSTOM_STM_SD_CONTROL_POINT:
+ ret = aci_gatt_update_char_value(CustomContext.CustomSensor_DataHdle,
+ CustomContext.CustomSd_Control_PointHdle,
+ 0, /* charValOffset */
+ SizeSd_Control_Point, /* charValueLen */
+ (uint8_t *) pPayload);
+ if (ret != BLE_STATUS_SUCCESS)
+ {
+ APP_DBG_MSG(" Fail : aci_gatt_update_char_value SD_CONTROL_POINT command, result : 0x%x \n\r", ret);
+ }
+ else
+ {
+ APP_DBG_MSG(" Success: aci_gatt_update_char_value SD_CONTROL_POINT command\n\r");
+ }
+ /* USER CODE BEGIN CUSTOM_STM_App_Update_Service_2_Char_2*/
+
+ /* USER CODE END CUSTOM_STM_App_Update_Service_2_Char_2*/
+ break;
+
+ case CUSTOM_STM_SP_CONTROL_POINT:
+ ret = aci_gatt_update_char_value(CustomContext.CustomStarter_PistolHdle,
+ CustomContext.CustomSp_Control_PointHdle,
0, /* charValOffset */
- SizeStart_Control, /* charValueLen */
+ SizeSp_Control_Point, /* charValueLen */
(uint8_t *) pPayload);
if (ret != BLE_STATUS_SUCCESS)
{
- APP_DBG_MSG(" Fail : aci_gatt_update_char_value START_CONTROL command, result : 0x%x \n\r", ret);
+ APP_DBG_MSG(" Fail : aci_gatt_update_char_value SP_CONTROL_POINT command, result : 0x%x \n\r", ret);
}
else
{
- APP_DBG_MSG(" Success: aci_gatt_update_char_value START_CONTROL command\n\r");
+ APP_DBG_MSG(" Success: aci_gatt_update_char_value SP_CONTROL_POINT command\n\r");
}
/* USER CODE BEGIN CUSTOM_STM_App_Update_Service_3_Char_1*/
/* USER CODE END CUSTOM_STM_App_Update_Service_3_Char_1*/
break;
- case CUSTOM_STM_START_RESULT:
- ret = aci_gatt_update_char_value(CustomContext.CustomStartHdle,
- CustomContext.CustomStart_ResultHdle,
+ case CUSTOM_STM_SP_RESULT:
+ ret = aci_gatt_update_char_value(CustomContext.CustomStarter_PistolHdle,
+ CustomContext.CustomSp_ResultHdle,
0, /* charValOffset */
- SizeStart_Result, /* charValueLen */
+ SizeSp_Result, /* charValueLen */
(uint8_t *) pPayload);
if (ret != BLE_STATUS_SUCCESS)
{
- APP_DBG_MSG(" Fail : aci_gatt_update_char_value START_RESULT command, result : 0x%x \n\r", ret);
+ APP_DBG_MSG(" Fail : aci_gatt_update_char_value SP_RESULT command, result : 0x%x \n\r", ret);
}
else
{
- APP_DBG_MSG(" Success: aci_gatt_update_char_value START_RESULT command\n\r");
+ APP_DBG_MSG(" Success: aci_gatt_update_char_value SP_RESULT command\n\r");
}
/* USER CODE BEGIN CUSTOM_STM_App_Update_Service_3_Char_2*/
/* USER CODE END CUSTOM_STM_App_Update_Service_3_Char_2*/
break;
+ case CUSTOM_STM_DS_MODE:
+ ret = aci_gatt_update_char_value(CustomContext.CustomDevice_StateHdle,
+ CustomContext.CustomDs_ModeHdle,
+ 0, /* charValOffset */
+ SizeDs_Mode, /* charValueLen */
+ (uint8_t *) pPayload);
+ if (ret != BLE_STATUS_SUCCESS)
+ {
+ APP_DBG_MSG(" Fail : aci_gatt_update_char_value DS_MODE command, result : 0x%x \n\r", ret);
+ }
+ else
+ {
+ APP_DBG_MSG(" Success: aci_gatt_update_char_value DS_MODE command\n\r");
+ }
+ /* USER CODE BEGIN CUSTOM_STM_App_Update_Service_4_Char_1*/
+
+ /* USER CODE END CUSTOM_STM_App_Update_Service_4_Char_1*/
+ break;
+
+ case CUSTOM_STM_DS_CONTROL_POINT:
+ ret = aci_gatt_update_char_value(CustomContext.CustomDevice_StateHdle,
+ CustomContext.CustomDs_Control_PointHdle,
+ 0, /* charValOffset */
+ SizeDs_Control_Point, /* charValueLen */
+ (uint8_t *) pPayload);
+ if (ret != BLE_STATUS_SUCCESS)
+ {
+ APP_DBG_MSG(" Fail : aci_gatt_update_char_value DS_CONTROL_POINT command, result : 0x%x \n\r", ret);
+ }
+ else
+ {
+ APP_DBG_MSG(" Success: aci_gatt_update_char_value DS_CONTROL_POINT command\n\r");
+ }
+ /* USER CODE BEGIN CUSTOM_STM_App_Update_Service_4_Char_2*/
+
+ /* USER CODE END CUSTOM_STM_App_Update_Service_4_Char_2*/
+ break;
+
+ case CUSTOM_STM_BATTERY_LEVEL:
+ ret = aci_gatt_update_char_value(CustomContext.CustomBatteryHdle,
+ CustomContext.CustomBattery_LevelHdle,
+ 0, /* charValOffset */
+ SizeBattery_Level, /* charValueLen */
+ (uint8_t *) pPayload);
+ if (ret != BLE_STATUS_SUCCESS)
+ {
+ APP_DBG_MSG(" Fail : aci_gatt_update_char_value BATTERY_LEVEL command, result : 0x%x \n\r", ret);
+ }
+ else
+ {
+ APP_DBG_MSG(" Success: aci_gatt_update_char_value BATTERY_LEVEL command\n\r");
+ }
+ /* USER CODE BEGIN CUSTOM_STM_App_Update_Service_5_Char_1*/
+
+ /* USER CODE END CUSTOM_STM_App_Update_Service_5_Char_1*/
+ break;
+
default:
break;
}
diff --git a/STM32_WPAN/App/custom_stm.h b/STM32_WPAN/App/custom_stm.h
index bc0db0f..0dae193 100644
--- a/STM32_WPAN/App/custom_stm.h
+++ b/STM32_WPAN/App/custom_stm.h
@@ -28,42 +28,64 @@ extern "C" {
/* Includes ------------------------------------------------------------------*/
/* USER CODE BEGIN Includes */
-
+#include "ble_types.h"
/* USER CODE END Includes */
/* Exported types ------------------------------------------------------------*/
typedef enum
{
- /* CRS */
- CUSTOM_STM_CRS_TX,
- CUSTOM_STM_CRS_RX,
- /* GNSS */
- CUSTOM_STM_GNSS_PV,
- /* Start */
- CUSTOM_STM_START_CONTROL,
- CUSTOM_STM_START_RESULT,
+ /* File_Transfer */
+ CUSTOM_STM_FT_PACKET_OUT,
+ CUSTOM_STM_FT_PACKET_IN,
+ /* Sensor_Data */
+ CUSTOM_STM_SD_GNSS_MEASUREMENT,
+ CUSTOM_STM_SD_CONTROL_POINT,
+ /* Starter_Pistol */
+ CUSTOM_STM_SP_CONTROL_POINT,
+ CUSTOM_STM_SP_RESULT,
+ /* Device_State */
+ CUSTOM_STM_DS_MODE,
+ CUSTOM_STM_DS_CONTROL_POINT,
+ /* Battery */
+ CUSTOM_STM_BATTERY_LEVEL,
} Custom_STM_Char_Opcode_t;
typedef enum
{
- /* CRS_TX */
- CUSTOM_STM_CRS_TX_NOTIFY_ENABLED_EVT,
- CUSTOM_STM_CRS_TX_NOTIFY_DISABLED_EVT,
- /* CRS_RX */
- CUSTOM_STM_CRS_RX_READ_EVT,
- CUSTOM_STM_CRS_RX_WRITE_NO_RESP_EVT,
- /* GNSS_PV */
- CUSTOM_STM_GNSS_PV_READ_EVT,
- CUSTOM_STM_GNSS_PV_NOTIFY_ENABLED_EVT,
- CUSTOM_STM_GNSS_PV_NOTIFY_DISABLED_EVT,
- /* Start_Control */
- CUSTOM_STM_START_CONTROL_WRITE_EVT,
- CUSTOM_STM_START_CONTROL_INDICATE_ENABLED_EVT,
- CUSTOM_STM_START_CONTROL_INDICATE_DISABLED_EVT,
- /* Start_Result */
- CUSTOM_STM_START_RESULT_READ_EVT,
- CUSTOM_STM_START_RESULT_INDICATE_ENABLED_EVT,
- CUSTOM_STM_START_RESULT_INDICATE_DISABLED_EVT,
+ /* FT_Packet_Out */
+ CUSTOM_STM_FT_PACKET_OUT_NOTIFY_ENABLED_EVT,
+ CUSTOM_STM_FT_PACKET_OUT_NOTIFY_DISABLED_EVT,
+ /* FT_Packet_In */
+ CUSTOM_STM_FT_PACKET_IN_READ_EVT,
+ CUSTOM_STM_FT_PACKET_IN_WRITE_NO_RESP_EVT,
+ /* SD_GNSS_Measurement */
+ CUSTOM_STM_SD_GNSS_MEASUREMENT_READ_EVT,
+ CUSTOM_STM_SD_GNSS_MEASUREMENT_NOTIFY_ENABLED_EVT,
+ CUSTOM_STM_SD_GNSS_MEASUREMENT_NOTIFY_DISABLED_EVT,
+ /* SD_Control_Point */
+ CUSTOM_STM_SD_CONTROL_POINT_WRITE_EVT,
+ CUSTOM_STM_SD_CONTROL_POINT_INDICATE_ENABLED_EVT,
+ CUSTOM_STM_SD_CONTROL_POINT_INDICATE_DISABLED_EVT,
+ /* SP_Control_Point */
+ CUSTOM_STM_SP_CONTROL_POINT_WRITE_EVT,
+ CUSTOM_STM_SP_CONTROL_POINT_INDICATE_ENABLED_EVT,
+ CUSTOM_STM_SP_CONTROL_POINT_INDICATE_DISABLED_EVT,
+ /* SP_Result */
+ CUSTOM_STM_SP_RESULT_READ_EVT,
+ CUSTOM_STM_SP_RESULT_INDICATE_ENABLED_EVT,
+ CUSTOM_STM_SP_RESULT_INDICATE_DISABLED_EVT,
+ /* DS_Mode */
+ CUSTOM_STM_DS_MODE_READ_EVT,
+ CUSTOM_STM_DS_MODE_INDICATE_ENABLED_EVT,
+ CUSTOM_STM_DS_MODE_INDICATE_DISABLED_EVT,
+ /* DS_Control_Point */
+ CUSTOM_STM_DS_CONTROL_POINT_WRITE_EVT,
+ CUSTOM_STM_DS_CONTROL_POINT_INDICATE_ENABLED_EVT,
+ CUSTOM_STM_DS_CONTROL_POINT_INDICATE_DISABLED_EVT,
+ /* Battery_Level */
+ CUSTOM_STM_BATTERY_LEVEL_READ_EVT,
+ CUSTOM_STM_BATTERY_LEVEL_NOTIFY_ENABLED_EVT,
+ CUSTOM_STM_BATTERY_LEVEL_NOTIFY_DISABLED_EVT,
CUSTOM_STM_NOTIFICATION_COMPLETE_EVT,
CUSTOM_STM_BOOT_REQUEST_EVT
@@ -89,11 +111,15 @@ typedef struct
/* USER CODE END ET */
/* Exported constants --------------------------------------------------------*/
-extern uint8_t SizeCrs_Tx;
-extern uint8_t SizeCrs_Rx;
-extern uint8_t SizeGnss_Pv;
-extern uint8_t SizeStart_Control;
-extern uint8_t SizeStart_Result;
+extern uint8_t SizeFt_Packet_Out;
+extern uint8_t SizeFt_Packet_In;
+extern uint8_t SizeSd_Gnss_Measurement;
+extern uint8_t SizeSd_Control_Point;
+extern uint8_t SizeSp_Control_Point;
+extern uint8_t SizeSp_Result;
+extern uint8_t SizeDs_Mode;
+extern uint8_t SizeDs_Control_Point;
+extern uint8_t SizeBattery_Level;
/* USER CODE BEGIN EC */
diff --git a/Tests/Makefile b/Tests/Makefile
new file mode 100644
index 0000000..7a64b0a
--- /dev/null
+++ b/Tests/Makefile
@@ -0,0 +1,16 @@
+# tests/Makefile
+CC = gcc
+CFLAGS = -Wall -Wextra -std=c99 -g
+TARGET = test_time
+SOURCE = test_time.c
+
+$(TARGET): $(SOURCE)
+ $(CC) $(CFLAGS) -o $(TARGET) $(SOURCE)
+
+run: $(TARGET)
+ ./$(TARGET)
+
+clean:
+ rm -f $(TARGET)
+
+.PHONY: run clean
\ No newline at end of file
diff --git a/Tests/mock_stm32.h b/Tests/mock_stm32.h
new file mode 100644
index 0000000..7d11dd2
--- /dev/null
+++ b/Tests/mock_stm32.h
@@ -0,0 +1,35 @@
+// tests/mock_stm32.h
+#ifndef MOCK_STM32_H
+#define MOCK_STM32_H
+
+#include
+#include
+
+// Mock your GNSS structure (copy from your actual header)
+typedef struct {
+ uint32_t iTOW;
+ uint16_t year;
+ uint8_t month;
+ uint8_t day;
+ uint8_t hour;
+ uint8_t min;
+ uint8_t sec;
+ int32_t nano;
+ int32_t lon;
+ int32_t lat;
+ int32_t hMSL;
+ int32_t velN;
+ int32_t velE;
+ int32_t velD;
+ int32_t speed;
+ int32_t gSpeed;
+ int32_t heading;
+ uint32_t tAcc;
+ uint32_t hAcc;
+ uint32_t vAcc;
+ uint32_t sAcc;
+ uint8_t gpsFix;
+ uint8_t numSV;
+} FS_GNSS_Data_t;
+
+#endif
diff --git a/Tests/test_time.c b/Tests/test_time.c
new file mode 100644
index 0000000..cd644bf
--- /dev/null
+++ b/Tests/test_time.c
@@ -0,0 +1,207 @@
+// tests/test_time.c
+#include
+#include
+#include
+#include "mock_stm32.h"
+
+// Include your time functions - you may need to copy/paste or adjust paths
+#include "../FlySight/time.c"
+
+// Your normalize function (copy from your implementation)
+// Helper function to normalize nanoseconds and adjust time accordingly
+void normalizeGNSSTime(const FS_GNSS_Data_t *data,
+ uint16_t *norm_year, uint8_t *norm_month, uint8_t *norm_day,
+ uint8_t *norm_hour, uint8_t *norm_min, uint8_t *norm_sec,
+ int32_t *norm_millis)
+{
+ // Convert original time to epoch seconds
+ uint32_t epoch_seconds = mk_gmtime(data->year, data->month, data->day,
+ data->hour, data->min, data->sec);
+
+ // Convert nanoseconds to milliseconds (rounded)
+ if (data->nano >= 0) {
+ *norm_millis = (data->nano + 500000) / 1000000;
+ } else {
+ *norm_millis = (data->nano - 499999) / 1000000;
+ }
+
+ // Handle negative milliseconds by borrowing from seconds
+ if (*norm_millis >= 1000) {
+ *norm_millis -= 1000;
+ ++epoch_seconds;
+ }
+ else if (*norm_millis < 0) {
+ *norm_millis += 1000;
+ --epoch_seconds;
+ }
+
+ // Convert back to date/time components
+ gmtime_r(epoch_seconds, norm_year, norm_month, norm_day,
+ norm_hour, norm_min, norm_sec);
+}
+
+// Simple test framework
+#define TEST(name) \
+ printf("Testing: %s\n", #name); \
+ test_##name(); \
+ printf("✓ %s passed\n\n", #name);
+
+#define ASSERT_EQ(expected, actual) \
+ if ((expected) != (actual)) { \
+ printf("✗ FAIL: Expected %d, got %d\n", (int)(expected), (int)(actual)); \
+ exit(1); \
+ }
+
+// Individual test functions
+void test_positive_nano() {
+ FS_GNSS_Data_t data = {
+ .year = 2024, .month = 5, .day = 30,
+ .hour = 12, .min = 30, .sec = 45,
+ .nano = 123456789
+ };
+
+ uint16_t year; uint8_t month, day, hour, min, sec; int32_t millis;
+ normalizeGNSSTime(&data, &year, &month, &day, &hour, &min, &sec, &millis);
+
+ ASSERT_EQ(2024, year);
+ ASSERT_EQ(5, month);
+ ASSERT_EQ(30, day);
+ ASSERT_EQ(12, hour);
+ ASSERT_EQ(30, min);
+ ASSERT_EQ(45, sec);
+ ASSERT_EQ(123, millis);
+}
+
+void test_nano_minus_one() {
+ FS_GNSS_Data_t data = {
+ .year = 2024, .month = 5, .day = 30,
+ .hour = 12, .min = 30, .sec = 45,
+ .nano = -1
+ };
+
+ uint16_t year; uint8_t month, day, hour, min, sec; int32_t millis;
+ normalizeGNSSTime(&data, &year, &month, &day, &hour, &min, &sec, &millis);
+
+ // -1 nanosecond rounds to 0 milliseconds, no borrowing needed
+ ASSERT_EQ(2024, year);
+ ASSERT_EQ(5, month);
+ ASSERT_EQ(30, day);
+ ASSERT_EQ(12, hour);
+ ASSERT_EQ(30, min);
+ ASSERT_EQ(45, sec); // Should stay at 45 seconds
+ ASSERT_EQ(0, millis); // Should round to 0 ms
+}
+
+void test_nano_needs_borrowing() {
+ FS_GNSS_Data_t data = {
+ .year = 2024, .month = 5, .day = 30,
+ .hour = 12, .min = 30, .sec = 45,
+ .nano = -500001
+ };
+
+ uint16_t year; uint8_t month, day, hour, min, sec; int32_t millis;
+ normalizeGNSSTime(&data, &year, &month, &day, &hour, &min, &sec, &millis);
+
+ ASSERT_EQ(2024, year);
+ ASSERT_EQ(5, month);
+ ASSERT_EQ(30, day);
+ ASSERT_EQ(12, hour);
+ ASSERT_EQ(30, min);
+ ASSERT_EQ(44, sec); // Should borrow 1 second
+ ASSERT_EQ(999, millis); // Should be 500ms after borrowing
+}
+
+void test_large_negative_nano() {
+ FS_GNSS_Data_t data = {
+ .year = 2024, .month = 5, .day = 30,
+ .hour = 12, .min = 30, .sec = 45,
+ .nano = -500000000 // -0.5 seconds
+ };
+
+ uint16_t year; uint8_t month, day, hour, min, sec; int32_t millis;
+ normalizeGNSSTime(&data, &year, &month, &day, &hour, &min, &sec, &millis);
+
+ ASSERT_EQ(2024, year);
+ ASSERT_EQ(5, month);
+ ASSERT_EQ(30, day);
+ ASSERT_EQ(12, hour);
+ ASSERT_EQ(30, min);
+ ASSERT_EQ(44, sec); // Should borrow 1 second
+ ASSERT_EQ(500, millis); // Should be 500ms
+}
+
+void test_seconds_underflow() {
+ FS_GNSS_Data_t data = {
+ .year = 2024, .month = 5, .day = 30,
+ .hour = 12, .min = 30, .sec = 0,
+ .nano = -500001
+ };
+
+ uint16_t year; uint8_t month, day, hour, min, sec; int32_t millis;
+ normalizeGNSSTime(&data, &year, &month, &day, &hour, &min, &sec, &millis);
+
+ ASSERT_EQ(2024, year);
+ ASSERT_EQ(5, month);
+ ASSERT_EQ(30, day);
+ ASSERT_EQ(12, hour);
+ ASSERT_EQ(29, min); // Should underflow to previous minute
+ ASSERT_EQ(59, sec); // Should be 59 seconds
+ ASSERT_EQ(999, millis);
+}
+
+void test_day_underflow() {
+ FS_GNSS_Data_t data = {
+ .year = 2024, .month = 6, .day = 1,
+ .hour = 0, .min = 0, .sec = 0,
+ .nano = -500001
+ };
+
+ uint16_t year; uint8_t month, day, hour, min, sec; int32_t millis;
+ normalizeGNSSTime(&data, &year, &month, &day, &hour, &min, &sec, &millis);
+
+ ASSERT_EQ(2024, year);
+ ASSERT_EQ(5, month); // Should underflow to May
+ ASSERT_EQ(31, day); // May has 31 days
+ ASSERT_EQ(23, hour);
+ ASSERT_EQ(59, min);
+ ASSERT_EQ(59, sec);
+ ASSERT_EQ(999, millis);
+}
+
+void test_rounding() {
+ // Test rounding down
+ FS_GNSS_Data_t data1 = {
+ .year = 2024, .month = 5, .day = 30,
+ .hour = 12, .min = 30, .sec = 45,
+ .nano = 499999 // Should round down to 0
+ };
+
+ uint16_t year; uint8_t month, day, hour, min, sec; int32_t millis;
+ normalizeGNSSTime(&data1, &year, &month, &day, &hour, &min, &sec, &millis);
+ ASSERT_EQ(0, millis);
+
+ // Test rounding up
+ FS_GNSS_Data_t data2 = {
+ .year = 2024, .month = 5, .day = 30,
+ .hour = 12, .min = 30, .sec = 45,
+ .nano = 500000 // Should round up to 1
+ };
+
+ normalizeGNSSTime(&data2, &year, &month, &day, &hour, &min, &sec, &millis);
+ ASSERT_EQ(1, millis);
+}
+
+int main() {
+ printf("Starting time normalization tests...\n\n");
+
+ TEST(positive_nano);
+ TEST(nano_minus_one);
+ TEST(large_negative_nano);
+ TEST(nano_needs_borrowing);
+ TEST(seconds_underflow);
+ TEST(day_underflow);
+ TEST(rounding);
+
+ printf("All tests passed! ✓\n");
+ return 0;
+}
diff --git a/flysight-2-firmware.ioc b/flysight-2-firmware.ioc
index 6b3a798..727783d 100644
--- a/flysight-2-firmware.ioc
+++ b/flysight-2-firmware.ioc
@@ -674,7 +674,7 @@ STM32_WPAN.AD_TYPE_COMPLETE_LOCAL_NAME=FlySight
STM32_WPAN.AD_TYPE_MANUFACTURER_DATA_COMMENT_1=Structure version
STM32_WPAN.AD_TYPE_MANUFACTURER_SPECIFIC_DATA_COMPANY_IDENTIFIER=DB,09
STM32_WPAN.CFG_BLE_MAX_ATT_MTU=250
-STM32_WPAN.CFG_BLE_NUM_LINK=1
+STM32_WPAN.CFG_BLE_NUM_LINK=2
STM32_WPAN.CFG_BLE_OPTIONS_DEVICE_NAME=SHCI_C2_BLE_INIT_OPTIONS_DEVICE_NAME_RO
STM32_WPAN.CFG_BONDING_MODE=1
STM32_WPAN.CFG_DEBUGGER_SUPPORTED=0
@@ -682,11 +682,10 @@ STM32_WPAN.CFG_DEBUG_TRACE_UART=hw_lpuart1
STM32_WPAN.CFG_GAP_DEVICE_NAME=123456789012345678901234567890
STM32_WPAN.CFG_GAP_DEVICE_NAME_LENGTH=30
STM32_WPAN.CFG_HW_LPUART1_ENABLED=1
-STM32_WPAN.CFG_HW_TS_MAX_NBR_CONCURRENT_TIMER=18
+STM32_WPAN.CFG_HW_TS_MAX_NBR_CONCURRENT_TIMER=19
STM32_WPAN.CFG_IDENTITY_ADDRESS=GAP_STATIC_RANDOM_ADDR
STM32_WPAN.CFG_IO_CAPABILITY=\ CFG_IO_CAPABILITY_NO_INPUT_NO_OUTPUT
STM32_WPAN.CFG_LPM_SUPPORTED=1
-STM32_WPAN.CFG_LSCLK_LSE=0
STM32_WPAN.CFG_MITM_PROTECTION=CFG_MITM_PROTECTION_NOT_REQUIRED
STM32_WPAN.CFG_PRIVACY=PRIVACY_ENABLED
STM32_WPAN.CFG_RTCCLK_DIVIDER_CONF=\ 16
@@ -697,8 +696,8 @@ STM32_WPAN.CUSTOM_TEMPLATE=Enabled
STM32_WPAN.GAP_PERIPHERAL_ROLE=1
STM32_WPAN.INCLUDE_AD_TYPE_COMPLETE_LOCAL_NAME=1
STM32_WPAN.INCLUDE_AD_TYPE_MANUFACTURER_SPECIFIC_DATA=1
-STM32_WPAN.IPParameters=CUSTOM_P2P_SERVER,CUSTOM_TEMPLATE,CFG_HW_TS_MAX_NBR_CONCURRENT_TIMER,CFG_HW_LPUART1_ENABLED,CFG_USE_SMPS,CFG_LPM_SUPPORTED,CFG_DEBUG_TRACE_UART,CFG_IO_CAPABILITY,CFG_MITM_PROTECTION,CFG_BLE_NUM_LINK,CFG_BLE_MAX_ATT_MTU,CFG_IDENTITY_ADDRESS,CFG_STATIC_RANDOM_ADDRESS,CFG_PRIVACY,GAP_PERIPHERAL_ROLE,CFG_GAP_DEVICE_NAME,CFG_GAP_DEVICE_NAME_LENGTH,INCLUDE_AD_TYPE_COMPLETE_LOCAL_NAME,AD_TYPE_COMPLETE_LOCAL_NAME,INCLUDE_AD_TYPE_MANUFACTURER_SPECIFIC_DATA,AD_TYPE_MANUFACTURER_SPECIFIC_DATA_COMPANY_IDENTIFIER,AD_TYPE_MANUFACTURER_DATA_COMMENT_1,PAIRING_PARAMETERS,CFG_BONDING_MODE,NUMBER_OF_SERVICES,SERVICE1_LONG_NAME,SERVICE1_SHORT_NAME,SERVICE2_LONG_NAME,SERVICE2_SHORT_NAME,SERVICE1_NUMBER_OF_CHARACTERISTICS,SERVICE1_CHAR1_LONG_NAME,SERVICE1_CHAR1_SHORT_NAME,SERVICE1_CHAR2_LONG_NAME,SERVICE1_CHAR2_SHORT_NAME,SERVICE1_CHAR1_VALUE_LENGTH,SERVICE1_CHAR1_UUID,SERVICE1_CHAR1_LENGTH_CHARACTERISTIC,SERVICE1_CHAR1_PROP_NOTIFY,SERVICE1_CHAR1_ATTR_PERMISSION_ENCRY_READ,SERVICE1_CHAR1_GATT_NOTIFY_ATTRIBUTE_WRITE,SERVICE1_CHAR1_GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP,SERVICE1_CHAR1_GATT_NOTIFY_READ_REQ_AND_WAIT_FOR_APPL_RESP,SERVICE1_CHAR2_UUID,SERVICE1_CHAR2_VALUE_LENGTH,SERVICE1_CHAR2_LENGTH_CHARACTERISTIC,SERVICE1_CHAR2_PROP_READ,SERVICE1_CHAR2_PROP_WRITE_WITHOUT_RESP,SERVICE1_CHAR2_ATTR_PERMISSION_ENCRY_READ,SERVICE1_CHAR2_ATTR_PERMISSION_ENCRY_WRITE,SERVICE1_CHAR2_GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP,SERVICE1_CHAR2_GATT_NOTIFY_READ_REQ_AND_WAIT_FOR_APPL_RESP,SERVICE2_CHAR1_LONG_NAME,SERVICE2_CHAR1_SHORT_NAME,SERVICE2_CHAR1_VALUE_LENGTH,SERVICE2_CHAR1_PROP_READ,SERVICE2_CHAR1_PROP_NOTIFY,SERVICE2_CHAR1_ATTR_PERMISSION_ENCRY_READ,SERVICE2_CHAR1_GATT_NOTIFY_ATTRIBUTE_WRITE,SERVICE2_CHAR1_GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP,SERVICE2_CHAR1_GATT_NOTIFY_READ_REQ_AND_WAIT_FOR_APPL_RESP,CFG_BLE_OPTIONS_DEVICE_NAME,CFG_DEBUGGER_SUPPORTED,SERVICE3_LONG_NAME,SERVICE3_SHORT_NAME,SERVICE3_CHAR1_LONG_NAME,SERVICE3_CHAR1_SHORT_NAME,SERVICE3_CHAR1_PROP_WRITE,SERVICE3_CHAR1_PROP_INDICATE,SERVICE3_CHAR1_ATTR_PERMISSION_ENCRY_READ,SERVICE3_CHAR1_ATTR_PERMISSION_ENCRY_WRITE,SERVICE3_CHAR1_GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP,SERVICE3_CHAR1_GATT_NOTIFY_READ_REQ_AND_WAIT_FOR_APPL_RESP,SERVICE2_UUID,SERVICE3_UUID,SERVICE3_CHAR1_UUID,SERVICE3_NUMBER_OF_CHARACTERISTICS,SERVICE3_CHAR2_LONG_NAME,SERVICE3_CHAR2_SHORT_NAME,SERVICE3_CHAR2_UUID,SERVICE3_CHAR2_VALUE_LENGTH,SERVICE3_CHAR2_PROP_READ,SERVICE3_CHAR2_PROP_INDICATE,SERVICE3_CHAR2_ATTR_PERMISSION_ENCRY_READ,SERVICE3_CHAR2_GATT_NOTIFY_ATTRIBUTE_WRITE,SERVICE3_CHAR2_GATT_NOTIFY_READ_REQ_AND_WAIT_FOR_APPL_RESP,SERVICE3_CHAR2_GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP,CFG_RTCCLK_DIVIDER_CONF,SERVICE1_CHAR1_ATTR_PERMISSION_ENCRY_WRITE,SERVICE2_CHAR1_ATTR_PERMISSION_ENCRY_WRITE,SERVICE3_CHAR2_ATTR_PERMISSION_ENCRY_WRITE,CFG_LSCLK_LSE
-STM32_WPAN.NUMBER_OF_SERVICES=3
+STM32_WPAN.IPParameters=CUSTOM_P2P_SERVER,CUSTOM_TEMPLATE,CFG_HW_TS_MAX_NBR_CONCURRENT_TIMER,CFG_HW_LPUART1_ENABLED,CFG_USE_SMPS,CFG_LPM_SUPPORTED,CFG_DEBUG_TRACE_UART,CFG_IO_CAPABILITY,CFG_MITM_PROTECTION,CFG_BLE_NUM_LINK,CFG_BLE_MAX_ATT_MTU,CFG_IDENTITY_ADDRESS,CFG_STATIC_RANDOM_ADDRESS,CFG_PRIVACY,GAP_PERIPHERAL_ROLE,CFG_GAP_DEVICE_NAME,CFG_GAP_DEVICE_NAME_LENGTH,INCLUDE_AD_TYPE_COMPLETE_LOCAL_NAME,AD_TYPE_COMPLETE_LOCAL_NAME,INCLUDE_AD_TYPE_MANUFACTURER_SPECIFIC_DATA,AD_TYPE_MANUFACTURER_SPECIFIC_DATA_COMPANY_IDENTIFIER,AD_TYPE_MANUFACTURER_DATA_COMMENT_1,PAIRING_PARAMETERS,CFG_BONDING_MODE,NUMBER_OF_SERVICES,SERVICE1_LONG_NAME,SERVICE1_SHORT_NAME,SERVICE2_LONG_NAME,SERVICE2_SHORT_NAME,SERVICE1_NUMBER_OF_CHARACTERISTICS,SERVICE1_CHAR1_LONG_NAME,SERVICE1_CHAR1_SHORT_NAME,SERVICE1_CHAR2_LONG_NAME,SERVICE1_CHAR2_SHORT_NAME,SERVICE1_CHAR1_VALUE_LENGTH,SERVICE1_CHAR1_UUID,SERVICE1_CHAR1_LENGTH_CHARACTERISTIC,SERVICE1_CHAR1_PROP_NOTIFY,SERVICE1_CHAR1_ATTR_PERMISSION_ENCRY_READ,SERVICE1_CHAR1_GATT_NOTIFY_ATTRIBUTE_WRITE,SERVICE1_CHAR1_GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP,SERVICE1_CHAR1_GATT_NOTIFY_READ_REQ_AND_WAIT_FOR_APPL_RESP,SERVICE1_CHAR2_UUID,SERVICE1_CHAR2_VALUE_LENGTH,SERVICE1_CHAR2_LENGTH_CHARACTERISTIC,SERVICE1_CHAR2_PROP_READ,SERVICE1_CHAR2_PROP_WRITE_WITHOUT_RESP,SERVICE1_CHAR2_ATTR_PERMISSION_ENCRY_READ,SERVICE1_CHAR2_ATTR_PERMISSION_ENCRY_WRITE,SERVICE1_CHAR2_GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP,SERVICE1_CHAR2_GATT_NOTIFY_READ_REQ_AND_WAIT_FOR_APPL_RESP,SERVICE2_CHAR1_LONG_NAME,SERVICE2_CHAR1_SHORT_NAME,SERVICE2_CHAR1_VALUE_LENGTH,SERVICE2_CHAR1_PROP_READ,SERVICE2_CHAR1_PROP_NOTIFY,SERVICE2_CHAR1_ATTR_PERMISSION_ENCRY_READ,SERVICE2_CHAR1_GATT_NOTIFY_ATTRIBUTE_WRITE,SERVICE2_CHAR1_GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP,SERVICE2_CHAR1_GATT_NOTIFY_READ_REQ_AND_WAIT_FOR_APPL_RESP,CFG_BLE_OPTIONS_DEVICE_NAME,CFG_DEBUGGER_SUPPORTED,SERVICE3_LONG_NAME,SERVICE3_SHORT_NAME,SERVICE3_CHAR1_LONG_NAME,SERVICE3_CHAR1_SHORT_NAME,SERVICE3_CHAR1_PROP_WRITE,SERVICE3_CHAR1_PROP_INDICATE,SERVICE3_CHAR1_ATTR_PERMISSION_ENCRY_READ,SERVICE3_CHAR1_ATTR_PERMISSION_ENCRY_WRITE,SERVICE3_CHAR1_GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP,SERVICE3_CHAR1_GATT_NOTIFY_READ_REQ_AND_WAIT_FOR_APPL_RESP,SERVICE2_UUID,SERVICE3_UUID,SERVICE3_CHAR1_UUID,SERVICE3_NUMBER_OF_CHARACTERISTICS,SERVICE3_CHAR2_LONG_NAME,SERVICE3_CHAR2_SHORT_NAME,SERVICE3_CHAR2_UUID,SERVICE3_CHAR2_VALUE_LENGTH,SERVICE3_CHAR2_PROP_READ,SERVICE3_CHAR2_PROP_INDICATE,SERVICE3_CHAR2_ATTR_PERMISSION_ENCRY_READ,SERVICE3_CHAR2_GATT_NOTIFY_ATTRIBUTE_WRITE,SERVICE3_CHAR2_GATT_NOTIFY_READ_REQ_AND_WAIT_FOR_APPL_RESP,SERVICE3_CHAR2_GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP,CFG_RTCCLK_DIVIDER_CONF,SERVICE1_CHAR1_ATTR_PERMISSION_ENCRY_WRITE,SERVICE2_CHAR1_ATTR_PERMISSION_ENCRY_WRITE,SERVICE3_CHAR2_ATTR_PERMISSION_ENCRY_WRITE,SERVICE2_CHAR1_LENGTH_CHARACTERISTIC,SERVICE2_NUMBER_OF_CHARACTERISTICS,SERVICE2_CHAR2_LONG_NAME,SERVICE2_CHAR2_SHORT_NAME,SERVICE2_CHAR2_VALUE_LENGTH,SERVICE2_CHAR2_LENGTH_CHARACTERISTIC,SERVICE2_CHAR2_PROP_WRITE,SERVICE1_UUID,SERVICE2_CHAR2_UUID,SERVICE2_CHAR2_ATTR_PERMISSION_ENCRY_READ,SERVICE2_CHAR2_ATTR_PERMISSION_ENCRY_WRITE,SERVICE2_CHAR2_GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP,SERVICE2_CHAR2_GATT_NOTIFY_READ_REQ_AND_WAIT_FOR_APPL_RESP,SERVICE4_LONG_NAME,SERVICE4_SHORT_NAME,SERVICE4_CHAR1_LONG_NAME,SERVICE4_CHAR1_SHORT_NAME,SERVICE4_NUMBER_OF_CHARACTERISTICS,SERVICE4_CHAR2_LONG_NAME,SERVICE4_CHAR2_SHORT_NAME,SERVICE4_CHAR2_VALUE_LENGTH,SERVICE4_CHAR2_PROP_WRITE,SERVICE4_CHAR2_ATTR_PERMISSION_ENCRY_READ,SERVICE4_CHAR2_ATTR_PERMISSION_ENCRY_WRITE,SERVICE4_CHAR2_GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP,SERVICE4_CHAR2_GATT_NOTIFY_READ_REQ_AND_WAIT_FOR_APPL_RESP,SERVICE4_CHAR1_UUID,SERVICE4_CHAR1_PROP_READ,SERVICE4_CHAR1_ATTR_PERMISSION_ENCRY_READ,SERVICE4_CHAR1_GATT_NOTIFY_ATTRIBUTE_WRITE,SERVICE4_CHAR1_GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP,SERVICE4_CHAR2_UUID,SERVICE4_UUID,SERVICE3_CHAR1_VALUE_LENGTH,SERVICE3_CHAR1_LENGTH_CHARACTERISTIC,SERVICE4_CHAR2_LENGTH_CHARACTERISTIC,SERVICE2_CHAR2_PROP_INDICATE,SERVICE4_CHAR1_PROP_INDICATE,SERVICE4_CHAR2_PROP_INDICATE,SERVICE5_LONG_NAME,SERVICE5_SHORT_NAME,SERVICE5_UUID,SERVICE5_UUID_TYPE,SERVICE5_CHAR1_LONG_NAME,SERVICE5_CHAR1_SHORT_NAME,SERVICE5_CHAR1_UUID_TYPE,SERVICE5_CHAR1_UUID,SERVICE5_CHAR1_PROP_READ,SERVICE5_CHAR1_PROP_NOTIFY,SERVICE5_CHAR1_ATTR_PERMISSION_ENCRY_READ,SERVICE5_CHAR1_GATT_NOTIFY_ATTRIBUTE_WRITE,SERVICE5_CHAR1_GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP
+STM32_WPAN.NUMBER_OF_SERVICES=5
STM32_WPAN.PAIRING_PARAMETERS=ON
STM32_WPAN.SERVICE1_CHAR1_ATTR_PERMISSION_ENCRY_READ=ATTR_PERMISSION_ENCRY_READ
STM32_WPAN.SERVICE1_CHAR1_ATTR_PERMISSION_ENCRY_WRITE=ATTR_PERMISSION_ENCRY_WRITE
@@ -706,9 +705,9 @@ STM32_WPAN.SERVICE1_CHAR1_GATT_NOTIFY_ATTRIBUTE_WRITE=\
STM32_WPAN.SERVICE1_CHAR1_GATT_NOTIFY_READ_REQ_AND_WAIT_FOR_APPL_RESP=\
STM32_WPAN.SERVICE1_CHAR1_GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP=\
STM32_WPAN.SERVICE1_CHAR1_LENGTH_CHARACTERISTIC=CHAR_VALUE_LEN_VARIABLE
-STM32_WPAN.SERVICE1_CHAR1_LONG_NAME=CRS_TX
+STM32_WPAN.SERVICE1_CHAR1_LONG_NAME=FT_Packet_Out
STM32_WPAN.SERVICE1_CHAR1_PROP_NOTIFY=CHAR_PROP_NOTIFY
-STM32_WPAN.SERVICE1_CHAR1_SHORT_NAME=CRS_TX
+STM32_WPAN.SERVICE1_CHAR1_SHORT_NAME=FT_Packet_Out
STM32_WPAN.SERVICE1_CHAR1_UUID=00 01
STM32_WPAN.SERVICE1_CHAR1_VALUE_LENGTH=244
STM32_WPAN.SERVICE1_CHAR2_ATTR_PERMISSION_ENCRY_READ=ATTR_PERMISSION_ENCRY_READ
@@ -716,52 +715,104 @@ STM32_WPAN.SERVICE1_CHAR2_ATTR_PERMISSION_ENCRY_WRITE=ATTR_PERMISSION_ENCRY_WRIT
STM32_WPAN.SERVICE1_CHAR2_GATT_NOTIFY_READ_REQ_AND_WAIT_FOR_APPL_RESP=\
STM32_WPAN.SERVICE1_CHAR2_GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP=\
STM32_WPAN.SERVICE1_CHAR2_LENGTH_CHARACTERISTIC=CHAR_VALUE_LEN_VARIABLE
-STM32_WPAN.SERVICE1_CHAR2_LONG_NAME=CRS_RX
+STM32_WPAN.SERVICE1_CHAR2_LONG_NAME=FT_Packet_In
STM32_WPAN.SERVICE1_CHAR2_PROP_READ=CHAR_PROP_READ
STM32_WPAN.SERVICE1_CHAR2_PROP_WRITE_WITHOUT_RESP=CHAR_PROP_WRITE_WITHOUT_RESP
-STM32_WPAN.SERVICE1_CHAR2_SHORT_NAME=CRS_RX
+STM32_WPAN.SERVICE1_CHAR2_SHORT_NAME=FT_Packet_In
STM32_WPAN.SERVICE1_CHAR2_UUID=00 02
STM32_WPAN.SERVICE1_CHAR2_VALUE_LENGTH=244
-STM32_WPAN.SERVICE1_LONG_NAME=CRS
+STM32_WPAN.SERVICE1_LONG_NAME=File_Transfer
STM32_WPAN.SERVICE1_NUMBER_OF_CHARACTERISTICS=2
-STM32_WPAN.SERVICE1_SHORT_NAME=CRS
+STM32_WPAN.SERVICE1_SHORT_NAME=File_Transfer
+STM32_WPAN.SERVICE1_UUID=00 00
STM32_WPAN.SERVICE2_CHAR1_ATTR_PERMISSION_ENCRY_READ=ATTR_PERMISSION_ENCRY_READ
STM32_WPAN.SERVICE2_CHAR1_ATTR_PERMISSION_ENCRY_WRITE=ATTR_PERMISSION_ENCRY_WRITE
STM32_WPAN.SERVICE2_CHAR1_GATT_NOTIFY_ATTRIBUTE_WRITE=\
STM32_WPAN.SERVICE2_CHAR1_GATT_NOTIFY_READ_REQ_AND_WAIT_FOR_APPL_RESP=GATT_NOTIFY_READ_REQ_AND_WAIT_FOR_APPL_RESP
STM32_WPAN.SERVICE2_CHAR1_GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP=\
-STM32_WPAN.SERVICE2_CHAR1_LONG_NAME=GNSS_PV
+STM32_WPAN.SERVICE2_CHAR1_LENGTH_CHARACTERISTIC=CHAR_VALUE_LEN_VARIABLE
+STM32_WPAN.SERVICE2_CHAR1_LONG_NAME=SD_GNSS_Measurement
STM32_WPAN.SERVICE2_CHAR1_PROP_NOTIFY=CHAR_PROP_NOTIFY
STM32_WPAN.SERVICE2_CHAR1_PROP_READ=CHAR_PROP_READ
-STM32_WPAN.SERVICE2_CHAR1_SHORT_NAME=GNSS_PV
-STM32_WPAN.SERVICE2_CHAR1_VALUE_LENGTH=29
-STM32_WPAN.SERVICE2_LONG_NAME=GNSS
-STM32_WPAN.SERVICE2_SHORT_NAME=GNSS
+STM32_WPAN.SERVICE2_CHAR1_SHORT_NAME=SD_GNSS_Measurement
+STM32_WPAN.SERVICE2_CHAR1_VALUE_LENGTH=44
+STM32_WPAN.SERVICE2_CHAR2_ATTR_PERMISSION_ENCRY_READ=ATTR_PERMISSION_ENCRY_READ
+STM32_WPAN.SERVICE2_CHAR2_ATTR_PERMISSION_ENCRY_WRITE=ATTR_PERMISSION_ENCRY_WRITE
+STM32_WPAN.SERVICE2_CHAR2_GATT_NOTIFY_READ_REQ_AND_WAIT_FOR_APPL_RESP=\
+STM32_WPAN.SERVICE2_CHAR2_GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP=\
+STM32_WPAN.SERVICE2_CHAR2_LENGTH_CHARACTERISTIC=CHAR_VALUE_LEN_VARIABLE
+STM32_WPAN.SERVICE2_CHAR2_LONG_NAME=SD_Control_Point
+STM32_WPAN.SERVICE2_CHAR2_PROP_INDICATE=CHAR_PROP_INDICATE
+STM32_WPAN.SERVICE2_CHAR2_PROP_WRITE=CHAR_PROP_WRITE
+STM32_WPAN.SERVICE2_CHAR2_SHORT_NAME=SD_Control_Point
+STM32_WPAN.SERVICE2_CHAR2_UUID=00 06
+STM32_WPAN.SERVICE2_CHAR2_VALUE_LENGTH=20
+STM32_WPAN.SERVICE2_LONG_NAME=Sensor_Data
+STM32_WPAN.SERVICE2_NUMBER_OF_CHARACTERISTICS=2
+STM32_WPAN.SERVICE2_SHORT_NAME=Sensor_Data
STM32_WPAN.SERVICE2_UUID=00 01
STM32_WPAN.SERVICE3_CHAR1_ATTR_PERMISSION_ENCRY_READ=ATTR_PERMISSION_ENCRY_READ
STM32_WPAN.SERVICE3_CHAR1_ATTR_PERMISSION_ENCRY_WRITE=ATTR_PERMISSION_ENCRY_WRITE
STM32_WPAN.SERVICE3_CHAR1_GATT_NOTIFY_READ_REQ_AND_WAIT_FOR_APPL_RESP=\
STM32_WPAN.SERVICE3_CHAR1_GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP=\
-STM32_WPAN.SERVICE3_CHAR1_LONG_NAME=Start_Control
+STM32_WPAN.SERVICE3_CHAR1_LENGTH_CHARACTERISTIC=CHAR_VALUE_LEN_VARIABLE
+STM32_WPAN.SERVICE3_CHAR1_LONG_NAME=SP_Control_Point
STM32_WPAN.SERVICE3_CHAR1_PROP_INDICATE=CHAR_PROP_INDICATE
STM32_WPAN.SERVICE3_CHAR1_PROP_WRITE=CHAR_PROP_WRITE
-STM32_WPAN.SERVICE3_CHAR1_SHORT_NAME=Start_Control
+STM32_WPAN.SERVICE3_CHAR1_SHORT_NAME=SP_Control_Point
STM32_WPAN.SERVICE3_CHAR1_UUID=00 03
+STM32_WPAN.SERVICE3_CHAR1_VALUE_LENGTH=20
STM32_WPAN.SERVICE3_CHAR2_ATTR_PERMISSION_ENCRY_READ=ATTR_PERMISSION_ENCRY_READ
STM32_WPAN.SERVICE3_CHAR2_ATTR_PERMISSION_ENCRY_WRITE=ATTR_PERMISSION_ENCRY_WRITE
STM32_WPAN.SERVICE3_CHAR2_GATT_NOTIFY_ATTRIBUTE_WRITE=\
STM32_WPAN.SERVICE3_CHAR2_GATT_NOTIFY_READ_REQ_AND_WAIT_FOR_APPL_RESP=GATT_NOTIFY_READ_REQ_AND_WAIT_FOR_APPL_RESP
STM32_WPAN.SERVICE3_CHAR2_GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP=\
-STM32_WPAN.SERVICE3_CHAR2_LONG_NAME=Start_Result
+STM32_WPAN.SERVICE3_CHAR2_LONG_NAME=SP_Result
STM32_WPAN.SERVICE3_CHAR2_PROP_INDICATE=CHAR_PROP_INDICATE
STM32_WPAN.SERVICE3_CHAR2_PROP_READ=CHAR_PROP_READ
-STM32_WPAN.SERVICE3_CHAR2_SHORT_NAME=Start_Result
+STM32_WPAN.SERVICE3_CHAR2_SHORT_NAME=SP_Result
STM32_WPAN.SERVICE3_CHAR2_UUID=00 04
STM32_WPAN.SERVICE3_CHAR2_VALUE_LENGTH=9
-STM32_WPAN.SERVICE3_LONG_NAME=Start
+STM32_WPAN.SERVICE3_LONG_NAME=Starter_Pistol
STM32_WPAN.SERVICE3_NUMBER_OF_CHARACTERISTICS=2
-STM32_WPAN.SERVICE3_SHORT_NAME=Start
+STM32_WPAN.SERVICE3_SHORT_NAME=Starter_Pistol
STM32_WPAN.SERVICE3_UUID=00 02
+STM32_WPAN.SERVICE4_CHAR1_ATTR_PERMISSION_ENCRY_READ=ATTR_PERMISSION_ENCRY_READ
+STM32_WPAN.SERVICE4_CHAR1_GATT_NOTIFY_ATTRIBUTE_WRITE=\
+STM32_WPAN.SERVICE4_CHAR1_GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP=\
+STM32_WPAN.SERVICE4_CHAR1_LONG_NAME=DS_Mode
+STM32_WPAN.SERVICE4_CHAR1_PROP_INDICATE=CHAR_PROP_INDICATE
+STM32_WPAN.SERVICE4_CHAR1_PROP_READ=CHAR_PROP_READ
+STM32_WPAN.SERVICE4_CHAR1_SHORT_NAME=DS_Mode
+STM32_WPAN.SERVICE4_CHAR1_UUID=00 05
+STM32_WPAN.SERVICE4_CHAR2_ATTR_PERMISSION_ENCRY_READ=ATTR_PERMISSION_ENCRY_READ
+STM32_WPAN.SERVICE4_CHAR2_ATTR_PERMISSION_ENCRY_WRITE=ATTR_PERMISSION_ENCRY_WRITE
+STM32_WPAN.SERVICE4_CHAR2_GATT_NOTIFY_READ_REQ_AND_WAIT_FOR_APPL_RESP=\
+STM32_WPAN.SERVICE4_CHAR2_GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP=\
+STM32_WPAN.SERVICE4_CHAR2_LENGTH_CHARACTERISTIC=CHAR_VALUE_LEN_VARIABLE
+STM32_WPAN.SERVICE4_CHAR2_LONG_NAME=DS_Control_Point
+STM32_WPAN.SERVICE4_CHAR2_PROP_INDICATE=CHAR_PROP_INDICATE
+STM32_WPAN.SERVICE4_CHAR2_PROP_WRITE=CHAR_PROP_WRITE
+STM32_WPAN.SERVICE4_CHAR2_SHORT_NAME=DS_Control_Point
+STM32_WPAN.SERVICE4_CHAR2_UUID=00 07
+STM32_WPAN.SERVICE4_CHAR2_VALUE_LENGTH=20
+STM32_WPAN.SERVICE4_LONG_NAME=Device_State
+STM32_WPAN.SERVICE4_NUMBER_OF_CHARACTERISTICS=2
+STM32_WPAN.SERVICE4_SHORT_NAME=Device_State
+STM32_WPAN.SERVICE4_UUID=00 03
+STM32_WPAN.SERVICE5_CHAR1_ATTR_PERMISSION_ENCRY_READ=ATTR_PERMISSION_ENCRY_READ
+STM32_WPAN.SERVICE5_CHAR1_GATT_NOTIFY_ATTRIBUTE_WRITE=\
+STM32_WPAN.SERVICE5_CHAR1_GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP=\
+STM32_WPAN.SERVICE5_CHAR1_LONG_NAME=Battery_Level
+STM32_WPAN.SERVICE5_CHAR1_PROP_NOTIFY=CHAR_PROP_NOTIFY
+STM32_WPAN.SERVICE5_CHAR1_PROP_READ=CHAR_PROP_READ
+STM32_WPAN.SERVICE5_CHAR1_SHORT_NAME=Battery_Level
+STM32_WPAN.SERVICE5_CHAR1_UUID=2A 19
+STM32_WPAN.SERVICE5_CHAR1_UUID_TYPE=0x01
+STM32_WPAN.SERVICE5_LONG_NAME=Battery
+STM32_WPAN.SERVICE5_SHORT_NAME=Battery
+STM32_WPAN.SERVICE5_UUID=18 0F
+STM32_WPAN.SERVICE5_UUID_TYPE=0x01
TIM1.Channel-PWM\ Generation1\ CH1=TIM_CHANNEL_1
TIM1.Channel-PWM\ Generation2\ CH2=TIM_CHANNEL_2
TIM1.IPParameters=Channel-PWM Generation1 CH1,Channel-PWM Generation2 CH2,Prescaler,Period