mirror of https://github.com/meshcore-dev/MeshCore
408 changed files with 11590 additions and 3614 deletions
@ -0,0 +1 @@ |
|||
github: meshcore-dev |
|||
@ -0,0 +1,31 @@ |
|||
name: Run Unit Tests |
|||
|
|||
on: |
|||
push: |
|||
branches: |
|||
- main |
|||
- master |
|||
pull_request: |
|||
workflow_dispatch: |
|||
|
|||
jobs: |
|||
test: |
|||
runs-on: ubuntu-latest |
|||
steps: |
|||
|
|||
- name: Clone Repo |
|||
uses: actions/checkout@v4 |
|||
|
|||
- name: Setup Build Environment |
|||
uses: ./.github/actions/setup-build-environment |
|||
|
|||
- name: Run Unit Tests |
|||
run: pio test -e native -vv |
|||
|
|||
- name: Upload Test Results |
|||
# Upload test results even if the test step failed. |
|||
if: always() |
|||
uses: actions/upload-artifact@v4 |
|||
with: |
|||
name: test-results |
|||
path: .pio/build/native/ |
|||
@ -0,0 +1,32 @@ |
|||
name: 'Run Stale Bot' |
|||
on: |
|||
schedule: |
|||
- cron: '30 1 * * *' # daily at 1:30am |
|||
workflow_dispatch: {} |
|||
|
|||
permissions: |
|||
actions: write |
|||
issues: write |
|||
pull-requests: write |
|||
|
|||
jobs: |
|||
close-issues: |
|||
# only run on main repo, not forks |
|||
if: github.repository == 'meshcore-dev/MeshCore' |
|||
runs-on: ubuntu-latest |
|||
steps: |
|||
- name: Close Stale Issues |
|||
uses: actions/stale@v10 |
|||
with: |
|||
repo-token: ${{ secrets.GITHUB_TOKEN }} |
|||
# auto close issues |
|||
days-before-issue-stale: 60 |
|||
days-before-issue-close: 7 |
|||
exempt-issue-labels: "keep-open" |
|||
stale-issue-label: "stale" |
|||
stale-issue-message: "This issue is stale because it has been open for 60 days with no activity. Remove the stale label or add a comment if this issue is still relevant, otherwise this issue will automatically close in 7 days." |
|||
close-issue-message: "This issue was closed because it has been inactive for 7 days since being marked as stale." |
|||
# don't auto close prs |
|||
days-before-pr-stale: -1 |
|||
days-before-pr-close: -1 |
|||
|
|||
@ -0,0 +1,57 @@ |
|||
# Security Policy |
|||
|
|||
## Supported Versions |
|||
|
|||
Security fixes are applied to the latest release only. We do not backport |
|||
fixes to older versions. |
|||
|
|||
| Version | Supported | |
|||
|---------|-----------| |
|||
| 1.15+ | ✅ | |
|||
| <1.15 | ❌ | |
|||
|
|||
## Reporting a Vulnerability |
|||
|
|||
**Please do not report security vulnerabilities through public GitHub issues.** |
|||
|
|||
Use GitHub's private vulnerability reporting instead: |
|||
1. Go to the **Security** tab of this repository |
|||
2. Click **Report a vulnerability** |
|||
3. Fill in the details and submit |
|||
|
|||
### What to include |
|||
|
|||
A useful report tells us: |
|||
- Which component or file is affected |
|||
- What an attacker can do (impact) and under what conditions |
|||
- A minimal reproduction case or proof-of-concept if you have one |
|||
- Whether you believe it is remotely exploitable |
|||
|
|||
You do not need a working exploit to report. An incomplete report is better |
|||
than no report. |
|||
|
|||
## What to expect |
|||
|
|||
This is a volunteer-maintained open-source project. We will do our best to |
|||
respond in a reasonable timeframe, but cannot commit to specific deadlines. |
|||
|
|||
We ask that you give us a fair opportunity to investigate and address the |
|||
issue before any public disclosure. If you have not heard back after |
|||
**90 days**, feel free to follow up or proceed with disclosure at your |
|||
discretion. |
|||
|
|||
## Scope |
|||
|
|||
In scope: |
|||
- Remote code execution, memory corruption, or denial-of-service via crafted |
|||
radio packets |
|||
- Authentication or encryption bypasses |
|||
- Vulnerabilities in the packet routing or path handling logic |
|||
|
|||
Out of scope: |
|||
- Physical access attacks (e.g., JTAG, UART extraction of keys) |
|||
- Regulatory compliance (duty cycle, frequency restrictions) |
|||
- Jamming or other physical-layer radio interference |
|||
- Issues in third-party libraries (RadioLib, Crypto, etc.) — report those |
|||
upstream |
|||
- "Best practice" suggestions without a demonstrated attack path |
|||
@ -1,198 +0,0 @@ |
|||
""" |
|||
Bluefruit BLE Patch Script |
|||
|
|||
Patches Bluefruit library to fix semaphore leak bug that causes device lockup |
|||
when BLE central disconnects unexpectedly (e.g., going out of range, supervision timeout). |
|||
|
|||
Patches applied: |
|||
1. BLEConnection.h: Add _hvn_qsize member to track semaphore queue size |
|||
2. BLEConnection.cpp: Store hvn_qsize and restore semaphore on disconnect |
|||
|
|||
Bug description: |
|||
- When a BLE central disconnects unexpectedly (reason=8 supervision timeout), |
|||
the BLE_GATTS_EVT_HVN_TX_COMPLETE event may never fire |
|||
- This leaves the _hvn_sem counting semaphore in a decremented state |
|||
- Since BLEConnection objects are reused (destructor never called), the |
|||
semaphore count is never restored |
|||
- Eventually all semaphore counts are exhausted and notify() blocks/fails |
|||
|
|||
""" |
|||
|
|||
from pathlib import Path |
|||
|
|||
Import("env") # pylint: disable=undefined-variable |
|||
|
|||
|
|||
def _patch_ble_connection_header(source: Path) -> bool: |
|||
""" |
|||
Add _hvn_qsize member variable to BLEConnection class. |
|||
|
|||
This is needed to restore the semaphore to its correct count on disconnect. |
|||
|
|||
Returns True if patch was applied or already applied, False on error. |
|||
""" |
|||
try: |
|||
content = source.read_text() |
|||
|
|||
# Check if already patched |
|||
if "_hvn_qsize" in content: |
|||
return True # Already patched |
|||
|
|||
# Find the location to insert - after _phy declaration |
|||
original_pattern = ''' uint8_t _phy; |
|||
|
|||
uint8_t _role;''' |
|||
|
|||
patched_pattern = ''' uint8_t _phy; |
|||
uint8_t _hvn_qsize; |
|||
|
|||
uint8_t _role;''' |
|||
|
|||
if original_pattern not in content: |
|||
print("Bluefruit patch: WARNING - BLEConnection.h pattern not found") |
|||
return False |
|||
|
|||
content = content.replace(original_pattern, patched_pattern) |
|||
source.write_text(content) |
|||
|
|||
# Verify |
|||
if "_hvn_qsize" not in source.read_text(): |
|||
return False |
|||
|
|||
return True |
|||
except Exception as e: |
|||
print(f"Bluefruit patch: ERROR patching BLEConnection.h: {e}") |
|||
return False |
|||
|
|||
|
|||
def _patch_ble_connection_source(source: Path) -> bool: |
|||
""" |
|||
Patch BLEConnection.cpp to: |
|||
1. Store hvn_qsize in constructor |
|||
2. Restore _hvn_sem semaphore to full count on disconnect |
|||
|
|||
Returns True if patch was applied or already applied, False on error. |
|||
""" |
|||
try: |
|||
content = source.read_text() |
|||
|
|||
# Check if already patched (look for the restore loop) |
|||
if "uxSemaphoreGetCount(_hvn_sem)" in content: |
|||
return True # Already patched |
|||
|
|||
# Patch 1: Store queue size in constructor |
|||
constructor_original = ''' _hvn_sem = xSemaphoreCreateCounting(hvn_qsize, hvn_qsize);''' |
|||
|
|||
constructor_patched = ''' _hvn_qsize = hvn_qsize; |
|||
_hvn_sem = xSemaphoreCreateCounting(hvn_qsize, hvn_qsize);''' |
|||
|
|||
if constructor_original not in content: |
|||
print("Bluefruit patch: WARNING - BLEConnection.cpp constructor pattern not found") |
|||
return False |
|||
|
|||
content = content.replace(constructor_original, constructor_patched) |
|||
|
|||
# Patch 2: Restore semaphore on disconnect |
|||
disconnect_original = ''' case BLE_GAP_EVT_DISCONNECTED: |
|||
// mark as disconnected |
|||
_connected = false; |
|||
break;''' |
|||
|
|||
disconnect_patched = ''' case BLE_GAP_EVT_DISCONNECTED: |
|||
// Restore notification semaphore to full count |
|||
// This fixes lockup when disconnect occurs with notifications in flight |
|||
while (uxSemaphoreGetCount(_hvn_sem) < _hvn_qsize) { |
|||
xSemaphoreGive(_hvn_sem); |
|||
} |
|||
// Release indication semaphore if waiting |
|||
if (_hvc_sem) { |
|||
_hvc_received = false; |
|||
xSemaphoreGive(_hvc_sem); |
|||
} |
|||
// mark as disconnected |
|||
_connected = false; |
|||
break;''' |
|||
|
|||
if disconnect_original not in content: |
|||
print("Bluefruit patch: WARNING - BLEConnection.cpp disconnect pattern not found") |
|||
return False |
|||
|
|||
content = content.replace(disconnect_original, disconnect_patched) |
|||
source.write_text(content) |
|||
|
|||
# Verify |
|||
verify_content = source.read_text() |
|||
if "uxSemaphoreGetCount(_hvn_sem)" not in verify_content: |
|||
return False |
|||
if "_hvn_qsize = hvn_qsize" not in verify_content: |
|||
return False |
|||
|
|||
return True |
|||
except Exception as e: |
|||
print(f"Bluefruit patch: ERROR patching BLEConnection.cpp: {e}") |
|||
return False |
|||
|
|||
|
|||
def _apply_bluefruit_patches(target, source, env): # pylint: disable=unused-argument |
|||
framework_path = env.get("PLATFORMFW_DIR") |
|||
if not framework_path: |
|||
framework_path = env.PioPlatform().get_package_dir("framework-arduinoadafruitnrf52") |
|||
|
|||
if not framework_path: |
|||
print("Bluefruit patch: ERROR - framework directory not found") |
|||
env.Exit(1) |
|||
return |
|||
|
|||
framework_dir = Path(framework_path) |
|||
bluefruit_lib = framework_dir / "libraries" / "Bluefruit52Lib" / "src" |
|||
patch_failed = False |
|||
|
|||
# Patch BLEConnection.h |
|||
conn_header = bluefruit_lib / "BLEConnection.h" |
|||
if conn_header.exists(): |
|||
before = conn_header.read_text() |
|||
success = _patch_ble_connection_header(conn_header) |
|||
after = conn_header.read_text() |
|||
|
|||
if success: |
|||
if before != after: |
|||
print("Bluefruit patch: OK - Applied BLEConnection.h fix (added _hvn_qsize member)") |
|||
else: |
|||
print("Bluefruit patch: OK - BLEConnection.h already patched") |
|||
else: |
|||
print("Bluefruit patch: FAILED - BLEConnection.h") |
|||
patch_failed = True |
|||
else: |
|||
print(f"Bluefruit patch: ERROR - BLEConnection.h not found at {conn_header}") |
|||
patch_failed = True |
|||
|
|||
# Patch BLEConnection.cpp |
|||
conn_source = bluefruit_lib / "BLEConnection.cpp" |
|||
if conn_source.exists(): |
|||
before = conn_source.read_text() |
|||
success = _patch_ble_connection_source(conn_source) |
|||
after = conn_source.read_text() |
|||
|
|||
if success: |
|||
if before != after: |
|||
print("Bluefruit patch: OK - Applied BLEConnection.cpp fix (restore semaphore on disconnect)") |
|||
else: |
|||
print("Bluefruit patch: OK - BLEConnection.cpp already patched") |
|||
else: |
|||
print("Bluefruit patch: FAILED - BLEConnection.cpp") |
|||
patch_failed = True |
|||
else: |
|||
print(f"Bluefruit patch: ERROR - BLEConnection.cpp not found at {conn_source}") |
|||
patch_failed = True |
|||
|
|||
if patch_failed: |
|||
print("Bluefruit patch: CRITICAL - Patch failed! Build aborted.") |
|||
env.Exit(1) |
|||
|
|||
|
|||
# Register the patch to run before build |
|||
bluefruit_action = env.VerboseAction(_apply_bluefruit_patches, "Applying Bluefruit BLE patches...") |
|||
env.AddPreAction("$BUILD_DIR/${PROGNAME}.elf", bluefruit_action) |
|||
|
|||
# Also run immediately to patch before any compilation |
|||
_apply_bluefruit_patches(None, None, env) |
|||
@ -1,40 +1,40 @@ |
|||
{ |
|||
"build": { |
|||
"arduino": { |
|||
"ldscript": "esp32s3_out.ld" |
|||
}, |
|||
"core": "esp32", |
|||
"extra_flags": [ |
|||
"-D ARDUINO_USB_CDC_ON_BOOT=1", |
|||
"-D ARDUINO_USB_MSC_ON_BOOT=0", |
|||
"-D ARDUINO_USB_DFU_ON_BOOT=0", |
|||
"-D ARDUINO_USB_MODE=1", |
|||
"-D ARDUINO_RUNNING_CORE=1", |
|||
"-D ARDUINO_EVENT_RUNNING_CORE=1" |
|||
], |
|||
"f_cpu": "240000000L", |
|||
"f_flash": "80000000L", |
|||
"flash_mode": "qio", |
|||
"hwids": [["0x303A", "0x1001"]], |
|||
"mcu": "esp32s3", |
|||
"variant": "esp32s3" |
|||
}, |
|||
"connectivity": ["wifi", "bluetooth"], |
|||
"debug": { |
|||
"default_tool": "esp-builtin", |
|||
"onboard_tools": ["esp-builtin"], |
|||
"openocd_target": "esp32s3.cfg" |
|||
}, |
|||
"frameworks": ["arduino", "espidf"], |
|||
"name": "ESP32-S3-Zero", |
|||
"upload": { |
|||
"flash_size": "4MB", |
|||
"maximum_ram_size": 327680, |
|||
"maximum_size": 4194304, |
|||
"require_upload_port": true, |
|||
"speed": 921600 |
|||
}, |
|||
"url": "https://www.espressif.com", |
|||
"vendor": "Espressif" |
|||
} |
|||
|
|||
{ |
|||
"build": { |
|||
"arduino": { |
|||
"ldscript": "esp32s3_out.ld" |
|||
}, |
|||
"core": "esp32", |
|||
"extra_flags": [ |
|||
"-D ARDUINO_USB_CDC_ON_BOOT=1", |
|||
"-D ARDUINO_USB_MSC_ON_BOOT=0", |
|||
"-D ARDUINO_USB_DFU_ON_BOOT=0", |
|||
"-D ARDUINO_USB_MODE=1", |
|||
"-D ARDUINO_RUNNING_CORE=1", |
|||
"-D ARDUINO_EVENT_RUNNING_CORE=1" |
|||
], |
|||
"f_cpu": "240000000L", |
|||
"f_flash": "80000000L", |
|||
"flash_mode": "qio", |
|||
"hwids": [["0x303A", "0x1001"]], |
|||
"mcu": "esp32s3", |
|||
"variant": "esp32s3" |
|||
}, |
|||
"connectivity": ["wifi", "bluetooth"], |
|||
"debug": { |
|||
"default_tool": "esp-builtin", |
|||
"onboard_tools": ["esp-builtin"], |
|||
"openocd_target": "esp32s3.cfg" |
|||
}, |
|||
"frameworks": ["arduino", "espidf"], |
|||
"name": "ESP32-S3-Zero", |
|||
"upload": { |
|||
"flash_size": "4MB", |
|||
"maximum_ram_size": 327680, |
|||
"maximum_size": 4194304, |
|||
"require_upload_port": true, |
|||
"speed": 921600 |
|||
}, |
|||
"url": "https://www.espressif.com", |
|||
"vendor": "Espressif" |
|||
} |
|||
|
|||
|
|||
@ -0,0 +1,61 @@ |
|||
{ |
|||
"build": { |
|||
"arduino": { |
|||
"ldscript": "nrf52840_s140_v6.ld" |
|||
}, |
|||
"core": "nRF5", |
|||
"cpu": "cortex-m4", |
|||
"extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA", |
|||
"f_cpu": "64000000L", |
|||
"hwids": [ |
|||
["0x239A","0x8029"], |
|||
["0x239A","0x0029"], |
|||
["0x239A","0x002A"], |
|||
["0x239A","0x802A"] |
|||
], |
|||
"usb_product": "HT-n5262G", |
|||
"mcu": "nrf52840", |
|||
"variant": "Heltec_T096_Board", |
|||
"bsp": { |
|||
"name": "adafruit" |
|||
}, |
|||
"softdevice": { |
|||
"sd_flags": "-DS140", |
|||
"sd_name": "s140", |
|||
"sd_version": "6.1.1", |
|||
"sd_fwid": "0x00B6" |
|||
}, |
|||
"bootloader": { |
|||
"settings_addr": "0xFF000" |
|||
} |
|||
}, |
|||
"connectivity": [ |
|||
"bluetooth" |
|||
], |
|||
"debug": { |
|||
"jlink_device": "nRF52840_xxAA", |
|||
"svd_path": "nrf52840.svd", |
|||
"openocd_target": "nrf52.cfg" |
|||
}, |
|||
"frameworks": [ |
|||
"arduino" |
|||
], |
|||
"name": "Heltec T096 Board", |
|||
"upload": { |
|||
"maximum_ram_size": 235520, |
|||
"maximum_size": 815104, |
|||
"speed": 115200, |
|||
"protocol": "nrfutil", |
|||
"protocols": [ |
|||
"jlink", |
|||
"nrfjprog", |
|||
"nrfutil", |
|||
"stlink" |
|||
], |
|||
"use_1200bps_touch": true, |
|||
"require_upload_port": true, |
|||
"wait_for_upload_port": true |
|||
}, |
|||
"url": "https://heltec.org/", |
|||
"vendor": "Heltec" |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
{ |
|||
"build": { |
|||
"arduino": { |
|||
"ldscript": "nrf52840_s140_v6.ld" |
|||
}, |
|||
"core": "nRF5", |
|||
"cpu": "cortex-m4", |
|||
"extra_flags": "-DNRF52840_XXAA", |
|||
"f_cpu": "64000000L", |
|||
"hwids": [ |
|||
["0x239A","0x8029"], |
|||
["0x239A","0x0029"], |
|||
["0x239A","0x002A"], |
|||
["0x239A","0x802A"] |
|||
], |
|||
"usb_product": "HT-n5262T1", |
|||
"mcu": "nrf52840", |
|||
"variant": "heltec_t1", |
|||
"bsp": { |
|||
"name": "adafruit" |
|||
}, |
|||
"softdevice": { |
|||
"sd_flags": "-DS140", |
|||
"sd_name": "s140", |
|||
"sd_version": "6.1.1", |
|||
"sd_fwid": "0x00B6" |
|||
}, |
|||
"bootloader": { |
|||
"settings_addr": "0xFF000" |
|||
} |
|||
}, |
|||
"connectivity": [ |
|||
"bluetooth" |
|||
], |
|||
"debug": { |
|||
"jlink_device": "nRF52840_xxAA", |
|||
"svd_path": "nrf52840.svd", |
|||
"openocd_target": "nrf52.cfg" |
|||
}, |
|||
"frameworks": [ |
|||
"arduino" |
|||
], |
|||
"name": "Heltec Mesh Node T1 Board", |
|||
"upload": { |
|||
"maximum_ram_size": 235520, |
|||
"maximum_size": 815104, |
|||
"speed": 115200, |
|||
"protocol": "nrfutil", |
|||
"protocols": [ |
|||
"jlink", |
|||
"nrfjprog", |
|||
"nrfutil", |
|||
"stlink" |
|||
], |
|||
"use_1200bps_touch": true, |
|||
"require_upload_port": true, |
|||
"wait_for_upload_port": true |
|||
}, |
|||
"url": "https://heltec.org/", |
|||
"vendor": "Heltec" |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
{ |
|||
"build": { |
|||
"arduino":{ |
|||
"ldscript": "nrf52840_s140_v6.ld" |
|||
}, |
|||
"core": "nRF5", |
|||
"cpu": "cortex-m4", |
|||
"extra_flags": "-DARDUINO_NRF52840_T_IMPULSE_PLUS -DNRF52840_XXAA", |
|||
"f_cpu": "64000000L", |
|||
"hwids": [ |
|||
[ |
|||
"0x239A", |
|||
"0x8029" |
|||
] |
|||
], |
|||
"usb_product": "T-Impulse-Plus-nRF52840", |
|||
"mcu": "nrf52840", |
|||
"variant": "t_impulse_plus_nrf52840", |
|||
"bsp": { |
|||
"name": "adafruit" |
|||
}, |
|||
"softdevice": { |
|||
"sd_flags": "-DS140", |
|||
"sd_name": "s140", |
|||
"sd_version": "6.1.1", |
|||
"sd_fwid": "0x00B6" |
|||
}, |
|||
"bootloader": { |
|||
"settings_addr": "0xFF000" |
|||
} |
|||
}, |
|||
"connectivity": [ |
|||
"bluetooth" |
|||
], |
|||
"debug": { |
|||
"jlink_device": "nRF52840_xxAA", |
|||
"onboard_tools": [ |
|||
"jlink" |
|||
], |
|||
"svd_path": "nrf52840.svd", |
|||
"openocd_target": "nrf52.cfg" |
|||
}, |
|||
"frameworks": [ |
|||
"arduino" |
|||
], |
|||
"name": "Lilygo T-Impulse-Plus-nRF52840", |
|||
"upload": { |
|||
"maximum_ram_size": 248832, |
|||
"maximum_size": 815104, |
|||
"require_upload_port": true, |
|||
"speed": 115200, |
|||
"protocol": "nrfutil", |
|||
"protocols": [ |
|||
"jlink", |
|||
"nrfjprog", |
|||
"stlink", |
|||
"cmsis-dap", |
|||
"blackmagic" |
|||
], |
|||
"use_1200bps_touch": true, |
|||
"wait_for_upload_port": true |
|||
}, |
|||
"url": "https://www.lilygo.cc/", |
|||
"vendor": "Lilygo" |
|||
} |
|||
@ -1,59 +1,59 @@ |
|||
{ |
|||
"build": { |
|||
"arduino": { |
|||
"ldscript": "nrf52840_s140_v7.ld" |
|||
}, |
|||
"core": "nRF5", |
|||
"cpu": "cortex-m4", |
|||
"extra_flags": "-DARDUINO_WIO_WM1110 -DNRF52840_XXAA", |
|||
"f_cpu": "64000000L", |
|||
"hwids": [ |
|||
["0x239A", "0x8029"], |
|||
["0x239A", "0x0029"], |
|||
["0x239A", "0x002A"], |
|||
["0x239A", "0x802A"] |
|||
], |
|||
"usb_product": "me25ls01-BOOT", |
|||
"mcu": "nrf52840", |
|||
"variant": "minewsemi_me25ls01", |
|||
"bsp": { |
|||
"name": "adafruit" |
|||
}, |
|||
"softdevice": { |
|||
"sd_flags": "-DS140", |
|||
"sd_name": "s140", |
|||
"sd_version": "7.3.0", |
|||
"sd_fwid": "0x0123" |
|||
}, |
|||
"bootloader": { |
|||
"settings_addr": "0xFF000" |
|||
} |
|||
}, |
|||
"connectivity": ["bluetooth"], |
|||
"debug": { |
|||
"jlink_device": "nRF52840_xxAA", |
|||
"svd_path": "nrf52840.svd", |
|||
"openocd_target": "nrf52.cfg" |
|||
}, |
|||
"frameworks": ["arduino"], |
|||
"name": "Minewsemi ME25LS01", |
|||
"upload": { |
|||
"maximum_ram_size": 235520, |
|||
"maximum_size": 811008, |
|||
"speed": 115200, |
|||
"protocol": "nrfutil", |
|||
"protocols": [ |
|||
"jlink", |
|||
"nrfjprog", |
|||
"nrfutil", |
|||
"stlink", |
|||
"cmsis-dap", |
|||
"blackmagic" |
|||
], |
|||
"use_1200bps_touch": true, |
|||
"require_upload_port": true, |
|||
"wait_for_upload_port": true |
|||
}, |
|||
"url": "https://en.minewsemi.com/lora-module/lr1110-nrf52840-me25LS01", |
|||
"vendor": "MINEWSEMI" |
|||
} |
|||
{ |
|||
"build": { |
|||
"arduino": { |
|||
"ldscript": "nrf52840_s140_v7.ld" |
|||
}, |
|||
"core": "nRF5", |
|||
"cpu": "cortex-m4", |
|||
"extra_flags": "-DARDUINO_WIO_WM1110 -DNRF52840_XXAA", |
|||
"f_cpu": "64000000L", |
|||
"hwids": [ |
|||
["0x239A", "0x8029"], |
|||
["0x239A", "0x0029"], |
|||
["0x239A", "0x002A"], |
|||
["0x239A", "0x802A"] |
|||
], |
|||
"usb_product": "me25ls01-BOOT", |
|||
"mcu": "nrf52840", |
|||
"variant": "minewsemi_me25ls01", |
|||
"bsp": { |
|||
"name": "adafruit" |
|||
}, |
|||
"softdevice": { |
|||
"sd_flags": "-DS140", |
|||
"sd_name": "s140", |
|||
"sd_version": "7.3.0", |
|||
"sd_fwid": "0x0123" |
|||
}, |
|||
"bootloader": { |
|||
"settings_addr": "0xFF000" |
|||
} |
|||
}, |
|||
"connectivity": ["bluetooth"], |
|||
"debug": { |
|||
"jlink_device": "nRF52840_xxAA", |
|||
"svd_path": "nrf52840.svd", |
|||
"openocd_target": "nrf52.cfg" |
|||
}, |
|||
"frameworks": ["arduino"], |
|||
"name": "Minewsemi ME25LS01", |
|||
"upload": { |
|||
"maximum_ram_size": 235520, |
|||
"maximum_size": 811008, |
|||
"speed": 115200, |
|||
"protocol": "nrfutil", |
|||
"protocols": [ |
|||
"jlink", |
|||
"nrfjprog", |
|||
"nrfutil", |
|||
"stlink", |
|||
"cmsis-dap", |
|||
"blackmagic" |
|||
], |
|||
"use_1200bps_touch": true, |
|||
"require_upload_port": true, |
|||
"wait_for_upload_port": true |
|||
}, |
|||
"url": "https://en.minewsemi.com/lora-module/lr1110-nrf52840-me25LS01", |
|||
"vendor": "MINEWSEMI" |
|||
} |
|||
|
|||
@ -0,0 +1,43 @@ |
|||
{ |
|||
"build": { |
|||
"arduino": { |
|||
"ldscript": "esp32s3_out.ld", |
|||
"memory_type": "qio_opi" |
|||
}, |
|||
"core": "esp32", |
|||
"extra_flags": [ |
|||
"-DBOARD_HAS_PSRAM", |
|||
"-DARDUINO_USB_CDC_ON_BOOT=1", |
|||
"-DARDUINO_USB_MODE=0", |
|||
"-DARDUINO_RUNNING_CORE=1", |
|||
"-DARDUINO_EVENT_RUNNING_CORE=0" |
|||
], |
|||
"f_cpu": "240000000L", |
|||
"f_flash": "80000000L", |
|||
"flash_mode": "qio", |
|||
"hwids": [["0x303A", "0x1001"]], |
|||
"mcu": "esp32s3", |
|||
"variant": "esp32s3" |
|||
}, |
|||
"connectivity": [ |
|||
"wifi" |
|||
], |
|||
"debug": { |
|||
"default_tool": "esp-builtin", |
|||
"onboard_tools": ["esp-builtin"], |
|||
"openocd_target": "esp32s3.cfg" |
|||
}, |
|||
"frameworks": ["arduino", "espidf"], |
|||
"name": "Station G3 ESP32", |
|||
"upload": { |
|||
"flash_size": "16MB", |
|||
"maximum_ram_size": 327680, |
|||
"maximum_size": 16777216, |
|||
"use_1200bps_touch": true, |
|||
"wait_for_upload_port": true, |
|||
"require_upload_port": true, |
|||
"speed": 921600 |
|||
}, |
|||
"url": "https://wiki.bqvoy.com/en/devkits/station-g3", |
|||
"vendor": "BQ Consulting" |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,22 @@ |
|||
# Number Allocations |
|||
|
|||
This document lists unique numbers/identifiers used in various MeshCore protocol payloads. |
|||
|
|||
# Group Data Types |
|||
|
|||
The `PAYLOAD_TYPE_GRP_DATA` payloads have a 16-bit data-type field, which identifies which application the packet is for. |
|||
|
|||
To make sure multiple applications can function without interfering with each other, the table below is for reserving various ranges of data-type values. Just modify this table, adding a row, then submit a PR to have it authorised/merged. |
|||
|
|||
NOTE: the range FF00 - FFFF is for use while you're developing, doing POC, and for these you don't need to request to use/allocate. |
|||
|
|||
Once you have a working app/project, you need to be able to demonstrate it exists/works, and THEN request type IDs. So, just use the testing/dev range while developing, then request IDs before you transition to publishing your project. |
|||
|
|||
| Data-Type range | App name | Contact | |
|||
|-----------------|-----------------------------|-------------------------------------------------------------------| |
|||
| 0000 - 00FF | -reserved for internal use- | | |
|||
| 0100 | MeshCore Open | [email protected] — https://github.com/zjs81/meshcore-open | |
|||
| 0110 - 011F | Ripple | [email protected] — https://buymeacoffee.com/ripplebiz | |
|||
| FF00 - FFFF | -reserved for testing/dev- | | |
|||
|
|||
(add rows, inside the range 0100 - FEFF for custom apps) |
|||
@ -0,0 +1,137 @@ |
|||
#pragma once |
|||
|
|||
#include <helpers/ui/DisplayDriver.h> |
|||
|
|||
|
|||
#ifndef STATUS_BAR_SCROLL_MS |
|||
#define STATUS_BAR_SCROLL_MS 80 |
|||
#endif |
|||
|
|||
#ifndef STATUS_BAR_SEPARATOR |
|||
#define STATUS_BAR_SEPARATOR " | " |
|||
#endif |
|||
|
|||
#ifndef STATUS_BAR_UPDATE_MS |
|||
#define STATUS_BAR_UPDATE_MS 2000 // rebuild status string every 2s
|
|||
#endif |
|||
|
|||
class ScrollingStatusBar { |
|||
char _status[160]; |
|||
int _text_width; |
|||
int _scroll_x; |
|||
int _display_width; |
|||
unsigned long _next_scroll; |
|||
unsigned long _next_update; |
|||
bool _needs_redraw; |
|||
|
|||
// cached state for change detection
|
|||
char _last_name[32]; |
|||
uint16_t _last_batt_mv; |
|||
bool _last_buzzer_quiet; |
|||
bool _last_gps_on; |
|||
bool _last_ble_on; |
|||
|
|||
public: |
|||
ScrollingStatusBar() : _text_width(0), _scroll_x(0), _display_width(72), |
|||
_next_scroll(0), _next_update(0), _needs_redraw(true), |
|||
_last_batt_mv(0), _last_buzzer_quiet(false), |
|||
_last_gps_on(false), _last_ble_on(false) { |
|||
_status[0] = 0; |
|||
_last_name[0] = 0; |
|||
} |
|||
|
|||
void begin(int display_width) { |
|||
_display_width = display_width; |
|||
_scroll_x = 0; |
|||
_next_scroll = 0; |
|||
_next_update = 0; |
|||
} |
|||
|
|||
// Call periodically to update the status string content.
|
|||
// Only rebuilds if values have changed or update interval has elapsed.
|
|||
void update(DisplayDriver& display, const char* node_name, uint16_t batt_millivolts, |
|||
bool buzzer_quiet, bool gps_on, bool ble_on) { |
|||
|
|||
bool changed = (batt_millivolts != _last_batt_mv) |
|||
|| (buzzer_quiet != _last_buzzer_quiet) |
|||
|| (gps_on != _last_gps_on) |
|||
|| (ble_on != _last_ble_on) |
|||
|| (strcmp(node_name, _last_name) != 0); |
|||
|
|||
if (!changed) return; |
|||
|
|||
// cache current values
|
|||
strncpy(_last_name, node_name, sizeof(_last_name) - 1); |
|||
_last_name[sizeof(_last_name) - 1] = 0; |
|||
_last_batt_mv = batt_millivolts; |
|||
_last_buzzer_quiet = buzzer_quiet; |
|||
_last_gps_on = gps_on; |
|||
_last_ble_on = ble_on; |
|||
|
|||
float volts = batt_millivolts / 1000.0f; |
|||
|
|||
snprintf(_status, sizeof(_status), |
|||
"%s" STATUS_BAR_SEPARATOR |
|||
"%.2fV" STATUS_BAR_SEPARATOR |
|||
"BUZ:%s" STATUS_BAR_SEPARATOR |
|||
"GPS:%s" STATUS_BAR_SEPARATOR |
|||
"BLE:%s" |
|||
" - ", // trailing gap before the text loops
|
|||
node_name, |
|||
volts, |
|||
buzzer_quiet ? "OFF" : "ON", |
|||
gps_on ? "ON" : "OFF", |
|||
ble_on ? "ON" : "OFF" |
|||
); |
|||
|
|||
display.setTextSize(1); |
|||
_text_width = display.getTextWidth(_status); |
|||
_next_update = millis() + STATUS_BAR_UPDATE_MS; |
|||
_needs_redraw = true; |
|||
} |
|||
|
|||
// Returns true if the status bar needs a redraw this frame.
|
|||
bool needsRedraw() { |
|||
if (_text_width <= _display_width) return _needs_redraw; // static, no scrolling
|
|||
return millis() >= _next_scroll; |
|||
} |
|||
|
|||
// Render the status bar via DisplayDriver.
|
|||
// U8g2 full-buffer mode clips to display bounds automatically,
|
|||
// and the font height stays within STATUS_BAR_HEIGHT, so no
|
|||
// explicit clip window is needed.
|
|||
void render(DisplayDriver& display) { |
|||
if (_status[0] == 0) return; |
|||
|
|||
display.setTextSize(1); |
|||
display.setColor(DisplayDriver::GREEN); |
|||
|
|||
// if (_needs_redraw) {
|
|||
// _text_width = display.getTextWidth(_status);
|
|||
// }
|
|||
|
|||
// static text: no scrolling needed
|
|||
if (_text_width <= _display_width) { |
|||
display.setCursor(0, 0); |
|||
display.print(_status); |
|||
_needs_redraw = false; |
|||
return; |
|||
} |
|||
|
|||
int x = _scroll_x; |
|||
do { |
|||
display.setCursor(x, 0); |
|||
display.print(_status); |
|||
x += _text_width; |
|||
} while (x < _display_width); |
|||
|
|||
|
|||
// advance scroll position
|
|||
_scroll_x--; |
|||
if (_scroll_x <= -_text_width) _scroll_x = 0; |
|||
|
|||
_next_scroll = millis() + STATUS_BAR_SCROLL_MS; |
|||
_needs_redraw = false; |
|||
} |
|||
|
|||
}; |
|||
@ -0,0 +1,837 @@ |
|||
#include "UITask.h" |
|||
#include <helpers/TxtDataHelpers.h> |
|||
#include "../MyMesh.h" |
|||
#include "target.h" |
|||
#include "u8g2_icons.h" |
|||
|
|||
#ifdef WIFI_SSID |
|||
#include <WiFi.h> |
|||
#endif |
|||
|
|||
#ifndef AUTO_OFF_MILLIS |
|||
#define AUTO_OFF_MILLIS 15000 // 15 seconds
|
|||
#endif |
|||
#define BOOT_SCREEN_MILLIS 4000 // 4 seconds
|
|||
|
|||
#ifdef PIN_STATUS_LED |
|||
#define LED_ON_MILLIS 20 |
|||
#define LED_ON_MSG_MILLIS 200 |
|||
#define LED_CYCLE_MILLIS 4000 |
|||
#endif |
|||
|
|||
#define LONG_PRESS_MILLIS 1200 |
|||
|
|||
#ifndef UI_RECENT_LIST_SIZE |
|||
#define UI_RECENT_LIST_SIZE 4 |
|||
#endif |
|||
|
|||
#if UI_HAS_JOYSTICK |
|||
#define PRESS_LABEL "press Enter" |
|||
#else |
|||
#define PRESS_LABEL "long press" |
|||
#endif |
|||
|
|||
class SplashScreen : public UIScreen { |
|||
UITask* _task; |
|||
unsigned long dismiss_after; |
|||
unsigned long version_after; |
|||
char _version_info[12]; |
|||
|
|||
public: |
|||
SplashScreen(UITask* task) : _task(task) { |
|||
// strip off dash and commit hash by changing dash to null terminator
|
|||
// e.g: v1.2.3-abcdef -> v1.2.3
|
|||
const char *ver = FIRMWARE_VERSION; |
|||
const char *dash = strchr(ver, '-'); |
|||
|
|||
int len = dash ? dash - ver : strlen(ver); |
|||
if (len >= sizeof(_version_info)) len = sizeof(_version_info) - 1; |
|||
memcpy(_version_info, ver, len); |
|||
_version_info[len] = 0; |
|||
|
|||
version_after = millis() + BOOT_SCREEN_MILLIS / 2; |
|||
dismiss_after = millis() + BOOT_SCREEN_MILLIS; |
|||
} |
|||
|
|||
int render(DisplayDriver& display) override { |
|||
if (millis() < version_after) { |
|||
// meshcore logo
|
|||
display.setColor(DisplayDriver::BLUE); |
|||
int logoWidth = 72; |
|||
display.drawXbm(0, 0, meshcore_logo, 72, 36); |
|||
} else { |
|||
|
|||
// meshcore website
|
|||
const char* website = "meshcore.io"; |
|||
display.setColor(DisplayDriver::LIGHT); |
|||
display.setTextSize(1); |
|||
uint16_t websiteWidth = display.getTextWidth(website); |
|||
display.setCursor((display.width() - websiteWidth) / 2, 9); |
|||
display.print(website); |
|||
|
|||
// version info
|
|||
display.setColor(DisplayDriver::LIGHT); |
|||
display.setTextSize(1); |
|||
display.drawTextCentered(display.width()/2, 18, _version_info); |
|||
|
|||
display.setTextSize(1); |
|||
display.drawTextCentered(display.width()/2, 27, FIRMWARE_BUILD_DATE); |
|||
} |
|||
return 1000; |
|||
} |
|||
|
|||
void poll() override { |
|||
if (millis() >= dismiss_after) { |
|||
_task->gotoHomeScreen(); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
class HomeScreen : public UIScreen { |
|||
enum HomePage { |
|||
FIRST, |
|||
RECENT, |
|||
RADIO, |
|||
BLUETOOTH, |
|||
ADVERT, |
|||
#if ENV_INCLUDE_GPS == 1 |
|||
GPS, |
|||
#endif |
|||
#if UI_SENSORS_PAGE == 1 |
|||
SENSORS, |
|||
#endif |
|||
SHUTDOWN, |
|||
Count // keep as last
|
|||
}; |
|||
|
|||
UITask* _task; |
|||
mesh::RTCClock* _rtc; |
|||
SensorManager* _sensors; |
|||
NodePrefs* _node_prefs; |
|||
uint8_t _page; |
|||
bool _shutdown_init; |
|||
AdvertPath recent[UI_RECENT_LIST_SIZE]; |
|||
|
|||
CayenneLPP sensors_lpp; |
|||
int sensors_nb = 0; |
|||
bool sensors_scroll = false; |
|||
int sensors_scroll_offset = 0; |
|||
int next_sensors_refresh = 0; |
|||
|
|||
void refresh_sensors() { |
|||
if (millis() > next_sensors_refresh) { |
|||
sensors_lpp.reset(); |
|||
sensors_nb = 0; |
|||
sensors_lpp.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); |
|||
sensors.querySensors(0xFF, sensors_lpp); |
|||
LPPReader reader (sensors_lpp.getBuffer(), sensors_lpp.getSize()); |
|||
uint8_t channel, type; |
|||
while(reader.readHeader(channel, type)) { |
|||
reader.skipData(type); |
|||
sensors_nb ++; |
|||
} |
|||
sensors_scroll = sensors_nb > UI_RECENT_LIST_SIZE; |
|||
#if AUTO_OFF_MILLIS > 0 |
|||
next_sensors_refresh = millis() + 5000; // refresh sensor values every 5 sec
|
|||
#else |
|||
next_sensors_refresh = millis() + 60000; // refresh sensor values every 1 min
|
|||
#endif |
|||
} |
|||
} |
|||
|
|||
public: |
|||
HomeScreen(UITask* task, mesh::RTCClock* rtc, SensorManager* sensors, NodePrefs* node_prefs) |
|||
: _task(task), _rtc(rtc), _sensors(sensors), _node_prefs(node_prefs), _page(0), |
|||
_shutdown_init(false), sensors_lpp(200) { } |
|||
|
|||
void poll() override { |
|||
if (_shutdown_init && !_task->isButtonPressed()) { // must wait for USR button to be released
|
|||
_task->shutdown(); |
|||
} |
|||
} |
|||
|
|||
int render(DisplayDriver& display) override { |
|||
char tmp[80]; |
|||
|
|||
if (_page == HomePage::FIRST) { |
|||
// // node name
|
|||
// display.setTextSize(1);
|
|||
// display.setColor(DisplayDriver::GREEN);
|
|||
// char filtered_name[sizeof(_node_prefs->node_name)];
|
|||
// display.translateUTF8ToBlocks(filtered_name, _node_prefs->node_name, sizeof(filtered_name));
|
|||
// display.setCursor(0, 0);
|
|||
// display.print(filtered_name);
|
|||
|
|||
|
|||
display.setColor(DisplayDriver::YELLOW); |
|||
display.setTextSize(2); |
|||
sprintf(tmp, "MSG: %d", _task->getMsgCount()); |
|||
display.setCursor(0, 10); |
|||
display.print(tmp); |
|||
|
|||
sprintf(tmp, "BATT: %.2fV", _task->getCachedBattMV() / 1000.0f); |
|||
display.setCursor(0, 19); |
|||
display.print(tmp); |
|||
|
|||
#ifdef WIFI_SSID |
|||
IPAddress ip = WiFi.localIP(); |
|||
snprintf(tmp, sizeof(tmp), "IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); |
|||
display.setTextSize(1); |
|||
display.drawTextCentered(display.width() / 2, 54, tmp); |
|||
#endif |
|||
if (_task->hasConnection()) { |
|||
display.setColor(DisplayDriver::GREEN); |
|||
display.setTextSize(1); |
|||
display.drawTextCentered(display.width() / 2, display.height()-8, "< Connected >"); |
|||
|
|||
} else if (the_mesh.getBLEPin() != 0) { // BT pin
|
|||
display.setColor(DisplayDriver::RED); |
|||
display.setTextSize(2); |
|||
sprintf(tmp, "Pin:%d", the_mesh.getBLEPin()); |
|||
display.drawTextCentered(display.width() / 2, display.height()-8, tmp); |
|||
} |
|||
} else if (_page == HomePage::RECENT) { |
|||
the_mesh.getRecentlyHeard(recent, UI_RECENT_LIST_SIZE); |
|||
display.setColor(DisplayDriver::GREEN); |
|||
int y = 8; |
|||
for (int i = 0; i < UI_RECENT_LIST_SIZE; i++, y += 11) { |
|||
auto a = &recent[i]; |
|||
if (a->name[0] == 0) continue; // empty slot
|
|||
int secs = _rtc->getCurrentTime() - a->recv_timestamp; |
|||
if (secs < 60) { |
|||
sprintf(tmp, "%ds", secs); |
|||
} else if (secs < 60*60) { |
|||
sprintf(tmp, "%dm", secs / 60); |
|||
} else { |
|||
sprintf(tmp, "%dh", secs / (60*60)); |
|||
} |
|||
|
|||
int timestamp_width = display.getTextWidth(tmp); |
|||
int max_name_width = display.width() - timestamp_width - 1; |
|||
|
|||
char filtered_recent_name[sizeof(a->name)]; |
|||
display.translateUTF8ToBlocks(filtered_recent_name, a->name, sizeof(filtered_recent_name)); |
|||
display.drawTextEllipsized(0, y, max_name_width, filtered_recent_name); |
|||
display.setCursor(display.width() - timestamp_width - 1, y); |
|||
display.print(tmp); |
|||
} |
|||
} else if (_page == HomePage::RADIO) { |
|||
display.setColor(DisplayDriver::YELLOW); |
|||
display.setTextSize(1); |
|||
// frequency and spreading factor
|
|||
display.setCursor(0, 8); |
|||
sprintf(tmp, "FQ %06.3f", _node_prefs->freq); |
|||
display.print(tmp); |
|||
sprintf(tmp, "SF%d", _node_prefs->sf); |
|||
display.drawTextRightAlign(display.width(), 8, tmp); |
|||
// bandwidth and coding rate
|
|||
display.setCursor(0, 17); |
|||
sprintf(tmp, "BW %03.2f", _node_prefs->bw); |
|||
display.print(tmp); |
|||
sprintf(tmp, "CR%d", _node_prefs->cr); |
|||
display.drawTextRightAlign(display.width(), 17, tmp); |
|||
// tx power and noise floor
|
|||
display.setCursor(0, 26); |
|||
sprintf(tmp, "NF %ddB", radio_driver.getNoiseFloor()); |
|||
display.print(tmp); |
|||
sprintf(tmp, "TX%d", _node_prefs->tx_power_dbm); |
|||
display.drawTextRightAlign(display.width(), 26, tmp); |
|||
|
|||
} else if (_page == HomePage::BLUETOOTH) { |
|||
display.setColor(DisplayDriver::GREEN); |
|||
display.drawXbm((display.width() - 32) / 2, 8, |
|||
_task->isSerialEnabled() ? bluetooth_on : bluetooth_off, |
|||
32, 32); |
|||
display.setTextSize(1); |
|||
// display.drawTextCentered(display.width() / 2, 40 - 11, "toggle: " PRESS_LABEL);
|
|||
} else if (_page == HomePage::ADVERT) { |
|||
display.setColor(DisplayDriver::GREEN); |
|||
display.drawXbm((display.width() - 32) / 2, 8, advert_icon, 32, 32); |
|||
// display.drawTextCentered(display.width() / 2, 40 - 11, "advert: " PRESS_LABEL);
|
|||
#if ENV_INCLUDE_GPS == 1 |
|||
} else if (_page == HomePage::GPS) { |
|||
LocationProvider* nmea = sensors.getLocationProvider(); |
|||
char buf[50]; |
|||
int y = 8; |
|||
bool gps_state = _task->getGPSState(); |
|||
#ifdef PIN_GPS_SWITCH |
|||
bool hw_gps_state = digitalRead(PIN_GPS_SWITCH); |
|||
if (gps_state != hw_gps_state) { |
|||
strcpy(buf, gps_state ? "gps off(hw)" : "gps off(sw)"); |
|||
} else { |
|||
strcpy(buf, gps_state ? "gps on" : "gps off"); |
|||
} |
|||
#else |
|||
strcpy(buf, gps_state ? "gps on" : "gps off"); |
|||
#endif |
|||
display.drawTextLeftAlign(0, y, buf); |
|||
if (nmea == NULL) { |
|||
// y = y + 8;
|
|||
display.drawTextLeftAlign(0, y, "Can't access GPS"); |
|||
} else { |
|||
if (!gps_state || !nmea->isValid()) { |
|||
strcpy(buf, "no fix"); |
|||
} else { |
|||
sprintf(buf, "%d sat", nmea->satellitesCount()); |
|||
} |
|||
display.drawTextRightAlign(display.width()-1, y, buf); |
|||
y = y + 8; |
|||
sprintf(buf, "lat %.4f", |
|||
nmea->getLatitude()/1000000.); |
|||
display.drawTextLeftAlign(0, y, buf); |
|||
y = y + 8; |
|||
sprintf(buf, "lon %.4f", |
|||
nmea->getLongitude()/1000000.); |
|||
display.drawTextLeftAlign(0, y, buf); |
|||
y = y + 8; |
|||
sprintf(buf, "alt %.1f", nmea->getAltitude()/1000.); |
|||
display.drawTextLeftAlign(0, y, buf); |
|||
} |
|||
#endif |
|||
#if UI_SENSORS_PAGE == 1 |
|||
} else if (_page == HomePage::SENSORS) { |
|||
int y = 8; |
|||
refresh_sensors(); |
|||
char buf[30]; |
|||
char name[30]; |
|||
LPPReader r(sensors_lpp.getBuffer(), sensors_lpp.getSize()); |
|||
|
|||
for (int i = 0; i < sensors_scroll_offset; i++) { |
|||
uint8_t channel, type; |
|||
r.readHeader(channel, type); |
|||
r.skipData(type); |
|||
} |
|||
|
|||
for (int i = 0; i < (sensors_scroll?UI_RECENT_LIST_SIZE:sensors_nb); i++) { |
|||
uint8_t channel, type; |
|||
if (!r.readHeader(channel, type)) { // reached end, reset
|
|||
r.reset(); |
|||
r.readHeader(channel, type); |
|||
} |
|||
|
|||
display.setCursor(0, y); |
|||
float v; |
|||
switch (type) { |
|||
case LPP_GPS: // GPS
|
|||
float lat, lon, alt; |
|||
r.readGPS(lat, lon, alt); |
|||
strcpy(name, "gps"); sprintf(buf, "%.4f %.4f", lat, lon); |
|||
break; |
|||
case LPP_VOLTAGE: |
|||
r.readVoltage(v); |
|||
strcpy(name, "voltage"); sprintf(buf, "%6.2f", v); |
|||
break; |
|||
case LPP_CURRENT: |
|||
r.readCurrent(v); |
|||
strcpy(name, "current"); sprintf(buf, "%.3f", v); |
|||
break; |
|||
case LPP_TEMPERATURE: |
|||
r.readTemperature(v); |
|||
strcpy(name, "temperature"); sprintf(buf, "%.2f", v); |
|||
break; |
|||
case LPP_RELATIVE_HUMIDITY: |
|||
r.readRelativeHumidity(v); |
|||
strcpy(name, "humidity"); sprintf(buf, "%.2f", v); |
|||
break; |
|||
case LPP_BAROMETRIC_PRESSURE: |
|||
r.readPressure(v); |
|||
strcpy(name, "pressure"); sprintf(buf, "%.2f", v); |
|||
break; |
|||
case LPP_ALTITUDE: |
|||
r.readAltitude(v); |
|||
strcpy(name, "altitude"); sprintf(buf, "%.0f", v); |
|||
break; |
|||
case LPP_POWER: |
|||
r.readPower(v); |
|||
strcpy(name, "power"); sprintf(buf, "%6.2f", v); |
|||
break; |
|||
default: |
|||
r.skipData(type); |
|||
strcpy(name, "unk"); sprintf(buf, ""); |
|||
} |
|||
display.setCursor(0, y); |
|||
display.print(name); |
|||
display.setCursor( |
|||
display.width()-display.getTextWidth(buf)-1, y |
|||
); |
|||
display.print(buf); |
|||
y = y + 12; |
|||
} |
|||
if (sensors_scroll) sensors_scroll_offset = (sensors_scroll_offset+1)%sensors_nb; |
|||
else sensors_scroll_offset = 0; |
|||
#endif |
|||
} else if (_page == HomePage::SHUTDOWN) { |
|||
display.setColor(DisplayDriver::GREEN); |
|||
display.setTextSize(1); |
|||
if (_shutdown_init) { |
|||
display.drawTextCentered(display.width() / 2, 20, "hibernating..."); |
|||
} else { |
|||
display.drawXbm((display.width() - 32) / 2, 8, power_icon, 32, 32); |
|||
// display.drawTextCentered(display.width() / 2, 40 - 11, "hibernate:" PRESS_LABEL);
|
|||
} |
|||
} |
|||
return 5000; // next render after 5000 ms
|
|||
} |
|||
|
|||
bool handleInput(char c) override { |
|||
if (c == KEY_LEFT || c == KEY_PREV) { |
|||
_page = (_page + HomePage::Count - 1) % HomePage::Count; |
|||
return true; |
|||
} |
|||
if (c == KEY_NEXT || c == KEY_RIGHT) { |
|||
_page = (_page + 1) % HomePage::Count; |
|||
if (_page == HomePage::RECENT) { |
|||
_task->showAlert("Recent adverts", 800); |
|||
} |
|||
return true; |
|||
} |
|||
if (c == KEY_ENTER && _page == HomePage::BLUETOOTH) { |
|||
if (_task->isSerialEnabled()) { // toggle Bluetooth on/off
|
|||
_task->disableSerial(); |
|||
} else { |
|||
_task->enableSerial(); |
|||
} |
|||
return true; |
|||
} |
|||
if (c == KEY_ENTER && _page == HomePage::ADVERT) { |
|||
_task->notify(UIEventType::ack); |
|||
if (the_mesh.advert()) { |
|||
_task->showAlert("Advert sent!", 1000); |
|||
} else { |
|||
_task->showAlert("Advert failed..", 1000); |
|||
} |
|||
return true; |
|||
} |
|||
#if ENV_INCLUDE_GPS == 1 |
|||
if (c == KEY_ENTER && _page == HomePage::GPS) { |
|||
_task->toggleGPS(); |
|||
return true; |
|||
} |
|||
#endif |
|||
#if UI_SENSORS_PAGE == 1 |
|||
if (c == KEY_ENTER && _page == HomePage::SENSORS) { |
|||
_task->toggleGPS(); |
|||
next_sensors_refresh=0; |
|||
return true; |
|||
} |
|||
#endif |
|||
if (c == KEY_ENTER && _page == HomePage::SHUTDOWN) { |
|||
_shutdown_init = true; // need to wait for button to be released
|
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
}; |
|||
|
|||
|
|||
void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* node_prefs) { |
|||
_display = display; |
|||
_sensors = sensors; |
|||
_auto_off = millis() + AUTO_OFF_MILLIS; |
|||
_cached_batt_mv = getBattMilliVolts(); |
|||
|
|||
#if defined(PIN_USER_BTN) |
|||
user_btn.begin(); |
|||
#endif |
|||
#if defined(PIN_USER_BTN_ANA) |
|||
analog_btn.begin(); |
|||
#endif |
|||
|
|||
_node_prefs = node_prefs; |
|||
|
|||
if (_display != NULL) { |
|||
_display->turnOn(); |
|||
} |
|||
_statusBar.begin(_display->width()); |
|||
|
|||
|
|||
#ifdef PIN_BUZZER |
|||
buzzer.begin(); |
|||
buzzer.quiet(_node_prefs->buzzer_quiet); |
|||
buzzer.startup(); |
|||
#endif |
|||
|
|||
#ifdef PIN_VIBRATION |
|||
vibration.begin(); |
|||
#endif |
|||
|
|||
ui_started_at = millis(); |
|||
_alert_expiry = 0; |
|||
|
|||
splash = new SplashScreen(this); |
|||
home = new HomeScreen(this, &rtc_clock, sensors, node_prefs); |
|||
setCurrScreen(splash); |
|||
} |
|||
|
|||
void UITask::showAlert(const char* text, int duration_millis) { |
|||
strcpy(_alert, text); |
|||
_alert_expiry = millis() + duration_millis; |
|||
} |
|||
|
|||
void UITask::notify(UIEventType t) { |
|||
#if defined(PIN_BUZZER) |
|||
switch(t){ |
|||
case UIEventType::contactMessage: |
|||
// gemini's pick
|
|||
buzzer.play("MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7"); |
|||
break; |
|||
case UIEventType::channelMessage: |
|||
buzzer.play("kerplop:d=16,o=6,b=120:32g#,32c#"); |
|||
break; |
|||
case UIEventType::ack: |
|||
buzzer.play("ack:d=32,o=8,b=120:c"); |
|||
break; |
|||
case UIEventType::roomMessage: |
|||
case UIEventType::newContactMessage: |
|||
case UIEventType::none: |
|||
default: |
|||
break; |
|||
} |
|||
#endif |
|||
|
|||
#ifdef PIN_VIBRATION |
|||
// Trigger vibration for all UI events except none
|
|||
if (t != UIEventType::none) { |
|||
vibration.trigger(); |
|||
} |
|||
#endif |
|||
} |
|||
|
|||
|
|||
void UITask::msgRead(int msgcount) { |
|||
_msgcount = msgcount; |
|||
if (msgcount == 0) { |
|||
gotoHomeScreen(); |
|||
} |
|||
} |
|||
|
|||
void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) { |
|||
_msgcount = msgcount; |
|||
|
|||
if (_display != NULL) { |
|||
if (!_display->isOn() && !hasConnection()) { |
|||
_display->turnOn(); |
|||
} |
|||
if (_display->isOn()) { |
|||
_auto_off = millis() + AUTO_OFF_MILLIS; // extend the auto-off timer
|
|||
_next_refresh = 100; // trigger refresh
|
|||
} |
|||
} |
|||
} |
|||
|
|||
void UITask::userLedHandler() { |
|||
#ifdef PIN_STATUS_LED |
|||
int cur_time = millis(); |
|||
if (cur_time > next_led_change) { |
|||
if (led_state == 0) { |
|||
led_state = 1; |
|||
if (_msgcount > 0) { |
|||
last_led_increment = LED_ON_MSG_MILLIS; |
|||
} else { |
|||
last_led_increment = LED_ON_MILLIS; |
|||
} |
|||
next_led_change = cur_time + last_led_increment; |
|||
} else { |
|||
led_state = 0; |
|||
next_led_change = cur_time + LED_CYCLE_MILLIS - last_led_increment; |
|||
} |
|||
digitalWrite(PIN_STATUS_LED, led_state == LED_STATE_ON); |
|||
} |
|||
#endif |
|||
} |
|||
|
|||
void UITask::setCurrScreen(UIScreen* c) { |
|||
curr = c; |
|||
_next_refresh = 100; |
|||
} |
|||
|
|||
/*
|
|||
hardware-agnostic pre-shutdown activity should be done here |
|||
*/ |
|||
void UITask::shutdown(bool restart){ |
|||
|
|||
#ifdef PIN_BUZZER |
|||
/* note: we have a choice here -
|
|||
we can do a blocking buzzer.loop() with non-deterministic consequences |
|||
or we can set a flag and delay the shutdown for a couple of seconds |
|||
while a non-blocking buzzer.loop() plays out in UITask::loop() |
|||
*/ |
|||
buzzer.shutdown(); |
|||
uint32_t buzzer_timer = millis(); // fail-safe shutdown
|
|||
while (buzzer.isPlaying() && (millis() - 2500) < buzzer_timer) |
|||
buzzer.loop(); |
|||
|
|||
#endif // PIN_BUZZER
|
|||
|
|||
if (restart) { |
|||
_board->reboot(); |
|||
} else { |
|||
_display->turnOff(); |
|||
radio_driver.powerOff(); |
|||
_board->powerOff(); |
|||
} |
|||
} |
|||
|
|||
bool UITask::isButtonPressed() const { |
|||
#ifdef PIN_USER_BTN |
|||
return user_btn.isPressed(); |
|||
#else |
|||
return false; |
|||
#endif |
|||
} |
|||
|
|||
void UITask::loop() { |
|||
char c = 0; |
|||
#if UI_HAS_JOYSTICK |
|||
int ev = user_btn.check(); |
|||
if (ev == BUTTON_EVENT_CLICK) { |
|||
c = checkDisplayOn(KEY_ENTER); |
|||
} else if (ev == BUTTON_EVENT_LONG_PRESS) { |
|||
c = handleLongPress(KEY_ENTER); // REVISIT: could be mapped to different key code
|
|||
} |
|||
ev = joystick_left.check(); |
|||
if (ev == BUTTON_EVENT_CLICK) { |
|||
c = checkDisplayOn(KEY_LEFT); |
|||
} else if (ev == BUTTON_EVENT_LONG_PRESS) { |
|||
c = handleLongPress(KEY_LEFT); |
|||
} |
|||
ev = joystick_right.check(); |
|||
if (ev == BUTTON_EVENT_CLICK) { |
|||
c = checkDisplayOn(KEY_RIGHT); |
|||
} else if (ev == BUTTON_EVENT_LONG_PRESS) { |
|||
c = handleLongPress(KEY_RIGHT); |
|||
} |
|||
ev = back_btn.check(); |
|||
if (ev == BUTTON_EVENT_TRIPLE_CLICK) { |
|||
c = handleTripleClick(KEY_SELECT); |
|||
} |
|||
#elif defined(PIN_USER_BTN) |
|||
int ev = user_btn.check(); |
|||
if (ev == BUTTON_EVENT_CLICK) { |
|||
c = checkDisplayOn(KEY_NEXT); |
|||
} else if (ev == BUTTON_EVENT_LONG_PRESS) { |
|||
c = handleLongPress(KEY_ENTER); |
|||
} else if (ev == BUTTON_EVENT_DOUBLE_CLICK) { |
|||
c = handleDoubleClick(KEY_PREV); |
|||
} else if (ev == BUTTON_EVENT_TRIPLE_CLICK) { |
|||
c = handleTripleClick(KEY_SELECT); |
|||
} |
|||
#endif |
|||
#if defined(PIN_USER_BTN_ANA) |
|||
if (abs(millis() - _analogue_pin_read_millis) > 10) { |
|||
int ev = analog_btn.check(); |
|||
if (ev == BUTTON_EVENT_CLICK) { |
|||
c = checkDisplayOn(KEY_NEXT); |
|||
} else if (ev == BUTTON_EVENT_LONG_PRESS) { |
|||
c = handleLongPress(KEY_ENTER); |
|||
} else if (ev == BUTTON_EVENT_DOUBLE_CLICK) { |
|||
c = handleDoubleClick(KEY_PREV); |
|||
} else if (ev == BUTTON_EVENT_TRIPLE_CLICK) { |
|||
c = handleTripleClick(KEY_SELECT); |
|||
} |
|||
_analogue_pin_read_millis = millis(); |
|||
} |
|||
#endif |
|||
#if defined(BACKLIGHT_BTN) |
|||
if (millis() > next_backlight_btn_check) { |
|||
bool touch_state = digitalRead(PIN_BUTTON2); |
|||
#if defined(DISP_BACKLIGHT) |
|||
digitalWrite(DISP_BACKLIGHT, !touch_state); |
|||
#elif defined(EXP_PIN_BACKLIGHT) |
|||
expander.digitalWrite(EXP_PIN_BACKLIGHT, !touch_state); |
|||
#endif |
|||
next_backlight_btn_check = millis() + 300; |
|||
} |
|||
#endif |
|||
#if defined(HAS_TORCH) |
|||
ev = back_btn.check(); |
|||
if (ev == BUTTON_EVENT_CLICK && c == 0) { |
|||
c = checkDisplayOn(KEY_PREV); |
|||
} else if (ev == BUTTON_EVENT_DOUBLE_CLICK) { |
|||
board.toggleTorch(); |
|||
c = 0; |
|||
} |
|||
#endif |
|||
|
|||
if (c != 0 && curr) { |
|||
curr->handleInput(c); |
|||
_auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer
|
|||
_next_refresh = 100; // trigger refresh
|
|||
} |
|||
|
|||
userLedHandler(); |
|||
|
|||
#ifdef PIN_BUZZER |
|||
if (buzzer.isPlaying()) buzzer.loop(); |
|||
#endif |
|||
|
|||
if (curr) curr->poll(); |
|||
|
|||
if (_display != NULL && _display->isOn()) { |
|||
_statusBar.update(*_display, |
|||
_node_prefs->node_name, |
|||
_cached_batt_mv, |
|||
isBuzzerQuiet(), |
|||
getGPSState(), |
|||
isSerialEnabled()); |
|||
|
|||
bool status_dirty = _statusBar.needsRedraw(); |
|||
bool content_dirty = (millis() >= _next_refresh && curr); |
|||
|
|||
if (status_dirty || content_dirty) { |
|||
_display->startFrame(); |
|||
_statusBar.render(*_display); |
|||
|
|||
if (curr) { |
|||
int delay_millis = curr->render(*_display); |
|||
if (content_dirty) { |
|||
_next_refresh = millis() + delay_millis; |
|||
} |
|||
} |
|||
|
|||
if (millis() < _alert_expiry) { // render alert popup
|
|||
_display->setTextSize(1); |
|||
int y = _display->height() / 3; |
|||
int p = _display->height() / 32; |
|||
_display->setColor(DisplayDriver::DARK); |
|||
_display->fillRect(p, y, _display->width() - p*2, y); |
|||
_display->setColor(DisplayDriver::LIGHT); // draw box border
|
|||
_display->drawRect(p, y, _display->width() - p*2, y); |
|||
_display->drawTextCentered(_display->width() / 2, y + p*3, _alert); |
|||
_next_refresh = _alert_expiry; // will need refresh when alert is dismissed
|
|||
} |
|||
|
|||
_display->endFrame(); |
|||
} |
|||
#if AUTO_OFF_MILLIS > 0 |
|||
#ifdef KEEP_DISPLAY_ON_USB |
|||
// Opt-in: refresh the auto-off deadline while externally powered, so the
|
|||
// timer counts from the moment external power is removed. Off by default
|
|||
// because OLED panels burn in quickly; only enable for LCD targets or
|
|||
// where the display is replaceable.
|
|||
if (board.isExternalPowered()) { |
|||
_auto_off = millis() + AUTO_OFF_MILLIS; |
|||
} |
|||
#endif |
|||
if (millis() > _auto_off) { |
|||
_display->turnOff(); |
|||
} |
|||
#endif |
|||
} |
|||
|
|||
#ifdef PIN_VIBRATION |
|||
vibration.loop(); |
|||
#endif |
|||
|
|||
#ifdef AUTO_SHUTDOWN_MILLIVOLTS |
|||
if (millis() > next_batt_chck) { |
|||
_cached_batt_mv = getBattMilliVolts(); |
|||
if (_cached_batt_mv > 0 && _cached_batt_mv < AUTO_SHUTDOWN_MILLIVOLTS) { |
|||
if(!board.isExternalPowered()) { |
|||
if (_display != NULL) { |
|||
_display->startFrame(); |
|||
_display->setTextSize(2); |
|||
_display->drawTextCentered(_display->width() / 2, 6, "Low battery!"); |
|||
_display->setTextSize(1); |
|||
_display->drawTextCentered(_display->width() / 2, 18, "Shutting down!"); |
|||
_display->endFrame(); |
|||
if (_display->isEink() == false) { delay(3000); } |
|||
} |
|||
shutdown(); |
|||
} |
|||
} |
|||
next_batt_chck = millis() + 8000; |
|||
} |
|||
#else |
|||
if (_display != NULL && _display->isOn() && millis() >= next_batt_chck) { |
|||
_cached_batt_mv = getBattMilliVolts(); |
|||
next_batt_chck = millis() + 8000; |
|||
} |
|||
#endif |
|||
} |
|||
|
|||
char UITask::checkDisplayOn(char c) { |
|||
if (_display != NULL) { |
|||
if (!_display->isOn()) { |
|||
_display->turnOn(); // turn display on and consume event
|
|||
c = 0; |
|||
} |
|||
_auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer
|
|||
_next_refresh = 0; // trigger refresh
|
|||
} |
|||
return c; |
|||
} |
|||
|
|||
char UITask::handleLongPress(char c) { |
|||
if (millis() - ui_started_at < 8000) { // long press in first 8 seconds since startup -> CLI/rescue
|
|||
the_mesh.enterCLIRescue(); |
|||
c = 0; // consume event
|
|||
} |
|||
return c; |
|||
} |
|||
|
|||
char UITask::handleDoubleClick(char c) { |
|||
MESH_DEBUG_PRINTLN("UITask: double-click triggered"); |
|||
checkDisplayOn(c); |
|||
return c; |
|||
} |
|||
|
|||
char UITask::handleTripleClick(char c) { |
|||
MESH_DEBUG_PRINTLN("UITask: triple click triggered"); |
|||
checkDisplayOn(c); |
|||
toggleBuzzer(); |
|||
c = 0; |
|||
return c; |
|||
} |
|||
|
|||
bool UITask::getGPSState() { |
|||
if (_sensors != NULL) { |
|||
int num = _sensors->getNumSettings(); |
|||
for (int i = 0; i < num; i++) { |
|||
if (strcmp(_sensors->getSettingName(i), "gps") == 0) { |
|||
return !strcmp(_sensors->getSettingValue(i), "1"); |
|||
} |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
void UITask::toggleGPS() { |
|||
if (_sensors != NULL) { |
|||
// toggle GPS on/off
|
|||
int num = _sensors->getNumSettings(); |
|||
for (int i = 0; i < num; i++) { |
|||
if (strcmp(_sensors->getSettingName(i), "gps") == 0) { |
|||
if (strcmp(_sensors->getSettingValue(i), "1") == 0) { |
|||
_sensors->setSettingValue("gps", "0"); |
|||
_node_prefs->gps_enabled = 0; |
|||
notify(UIEventType::ack); |
|||
} else { |
|||
_sensors->setSettingValue("gps", "1"); |
|||
_node_prefs->gps_enabled = 1; |
|||
notify(UIEventType::ack); |
|||
} |
|||
the_mesh.savePrefs(); |
|||
showAlert(_node_prefs->gps_enabled ? "GPS: Enabled" : "GPS: Disabled", 800); |
|||
_next_refresh = 0; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
void UITask::toggleBuzzer() { |
|||
// Toggle buzzer quiet mode
|
|||
#ifdef PIN_BUZZER |
|||
if (buzzer.isQuiet()) { |
|||
buzzer.quiet(false); |
|||
notify(UIEventType::ack); |
|||
} else { |
|||
buzzer.quiet(true); |
|||
} |
|||
_node_prefs->buzzer_quiet = buzzer.isQuiet(); |
|||
the_mesh.savePrefs(); |
|||
showAlert(buzzer.isQuiet() ? "Buzzer: OFF" : "Buzzer: ON", 800); |
|||
_next_refresh = 0; // trigger refresh
|
|||
#endif |
|||
} |
|||
@ -0,0 +1,109 @@ |
|||
#pragma once |
|||
|
|||
#include <MeshCore.h> |
|||
#include <helpers/ui/DisplayDriver.h> |
|||
#include <helpers/ui/UIScreen.h> |
|||
#include <helpers/SensorManager.h> |
|||
#include <helpers/BaseSerialInterface.h> |
|||
#include <Arduino.h> |
|||
#include <helpers/sensors/LPPDataHelpers.h> |
|||
|
|||
#include "ScrollingStatusBar.h" |
|||
|
|||
|
|||
#ifndef LED_STATE_ON |
|||
#define LED_STATE_ON 1 |
|||
#endif |
|||
|
|||
#ifdef PIN_BUZZER |
|||
#include <helpers/ui/buzzer.h> |
|||
#endif |
|||
#ifdef PIN_VIBRATION |
|||
#include <helpers/ui/GenericVibration.h> |
|||
#endif |
|||
|
|||
#include "../AbstractUITask.h" |
|||
#include "../NodePrefs.h" |
|||
|
|||
class UITask : public AbstractUITask { |
|||
DisplayDriver* _display; |
|||
SensorManager* _sensors; |
|||
ScrollingStatusBar _statusBar; |
|||
#ifdef PIN_BUZZER |
|||
genericBuzzer buzzer; |
|||
#endif |
|||
#ifdef PIN_VIBRATION |
|||
GenericVibration vibration; |
|||
#endif |
|||
unsigned long _next_refresh, _auto_off; |
|||
NodePrefs* _node_prefs; |
|||
char _alert[80]; |
|||
unsigned long _alert_expiry; |
|||
int _msgcount; |
|||
unsigned long ui_started_at, next_batt_chck; |
|||
int next_backlight_btn_check = 0; |
|||
uint16_t _cached_batt_mv; |
|||
#ifdef PIN_STATUS_LED |
|||
int led_state = 0; |
|||
int next_led_change = 0; |
|||
int last_led_increment = 0; |
|||
#endif |
|||
|
|||
#ifdef PIN_USER_BTN_ANA |
|||
unsigned long _analogue_pin_read_millis = millis(); |
|||
#endif |
|||
|
|||
UIScreen* splash; |
|||
UIScreen* home; |
|||
// UIScreen* msg_preview;
|
|||
UIScreen* curr; |
|||
|
|||
|
|||
void userLedHandler(); |
|||
|
|||
// Button action handlers
|
|||
char checkDisplayOn(char c); |
|||
char handleLongPress(char c); |
|||
char handleDoubleClick(char c); |
|||
char handleTripleClick(char c); |
|||
|
|||
void setCurrScreen(UIScreen* c); |
|||
|
|||
public: |
|||
|
|||
UITask(mesh::MainBoard* board, BaseSerialInterface* serial) : AbstractUITask(board, serial), _display(NULL), _sensors(NULL) { |
|||
next_batt_chck = _next_refresh = 0; |
|||
_cached_batt_mv = 0; |
|||
ui_started_at = 0; |
|||
curr = NULL; |
|||
} |
|||
void begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* node_prefs); |
|||
|
|||
void gotoHomeScreen() { setCurrScreen(home); } |
|||
void showAlert(const char* text, int duration_millis); |
|||
int getMsgCount() const { return _msgcount; } |
|||
uint16_t getCachedBattMV() const { return _cached_batt_mv; } |
|||
bool hasDisplay() const { return _display != NULL; } |
|||
bool isButtonPressed() const; |
|||
|
|||
bool isBuzzerQuiet() { |
|||
#ifdef PIN_BUZZER |
|||
return buzzer.isQuiet(); |
|||
#else |
|||
return true; |
|||
#endif |
|||
} |
|||
|
|||
void toggleBuzzer(); |
|||
bool getGPSState(); |
|||
void toggleGPS(); |
|||
|
|||
|
|||
// from AbstractUITask
|
|||
void msgRead(int msgcount) override; |
|||
void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) override; |
|||
void notify(UIEventType t = UIEventType::none) override; |
|||
void loop() override; |
|||
|
|||
void shutdown(bool restart = false); |
|||
}; |
|||
@ -0,0 +1,104 @@ |
|||
#pragma once |
|||
|
|||
#include <stdint.h> |
|||
// icons converted for use with U8g2 which needs a different format of xbm data.
|
|||
|
|||
// 'meshcore', 72x36px
|
|||
static const uint8_t meshcore_logo [] = { |
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
|||
0x00, 0x00, 0x00, 0xf0, 0x00, 0x3e, 0xfe, 0x3f, 0xfe, 0x3f, 0x1e, 0x78, |
|||
0xf8, 0x00, 0x1f, 0xff, 0x3f, 0xff, 0x3f, 0x1e, 0x78, 0xf8, 0x01, 0x1f, |
|||
0xff, 0x9f, 0xff, 0x1f, 0x0e, 0x78, 0xf8, 0x81, 0x1f, 0x0f, 0x80, 0x07, |
|||
0x00, 0x0f, 0x38, 0xf8, 0xc1, 0x1f, 0x0f, 0x80, 0x07, 0x00, 0x0f, 0x3c, |
|||
0xf8, 0xc3, 0x1f, 0xff, 0x87, 0xff, 0x07, 0xff, 0x3f, 0xf8, 0xe3, 0x1f, |
|||
0xff, 0x87, 0xff, 0x0f, 0xff, 0x3f, 0xfc, 0xf3, 0x8f, 0xff, 0x07, 0xff, |
|||
0x1f, 0xff, 0x3f, 0xfc, 0xf3, 0x8f, 0x07, 0x00, 0x00, 0x9e, 0x0f, 0x1e, |
|||
0xbc, 0x7f, 0x8f, 0x07, 0x00, 0x00, 0x9e, 0x07, 0x1e, 0x9c, 0x3f, 0x8f, |
|||
0x07, 0x00, 0x00, 0x9f, 0x07, 0x1e, 0x9c, 0x3f, 0x8f, 0xff, 0xcf, 0xff, |
|||
0x8f, 0x07, 0x1e, 0x1e, 0x1f, 0xc7, 0xff, 0xcf, 0xff, 0x87, 0x07, 0x1e, |
|||
0x1e, 0x0f, 0xc7, 0xff, 0xc7, 0xff, 0x83, 0x03, 0x0e, 0x00, 0x00, 0x00, |
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
|||
0xc0, 0xff, 0xf0, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0x07, 0xf0, 0xff, 0xf8, |
|||
0xff, 0xf1, 0xff, 0xc3, 0xff, 0x07, 0xf0, 0xff, 0xfc, 0xff, 0xf1, 0xff, |
|||
0xc7, 0xff, 0x07, 0x78, 0x00, 0x3c, 0xe0, 0xf1, 0xc0, 0xe7, 0x01, 0x00, |
|||
0x78, 0x00, 0x1e, 0xe0, 0xf1, 0x80, 0xe7, 0x01, 0x00, 0x78, 0x00, 0x1e, |
|||
0xe0, 0xf1, 0xc0, 0xe3, 0x01, 0x00, 0x78, 0x00, 0x1e, 0xe0, 0x71, 0xc0, |
|||
0xe3, 0xff, 0x00, 0x3c, 0x00, 0x1e, 0xe0, 0xf9, 0xff, 0xe3, 0xff, 0x00, |
|||
0x3c, 0x00, 0x1e, 0xe0, 0xf8, 0xff, 0xf1, 0xff, 0x00, 0x3c, 0x00, 0x0e, |
|||
0xf0, 0xf8, 0xff, 0xf0, 0x00, 0x00, 0x3c, 0x00, 0x1f, 0xf0, 0x78, 0x7c, |
|||
0xf0, 0x00, 0x00, 0xfc, 0x3f, 0xff, 0xff, 0x38, 0xf8, 0xf0, 0xff, 0x01, |
|||
0xfc, 0x3f, 0xfe, 0x7f, 0x3c, 0xf0, 0xf0, 0xff, 0x01, 0xf8, 0x3f, 0xfe, |
|||
0x3f, 0x3c, 0xf0, 0xf1, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 |
|||
|
|||
}; |
|||
|
|||
|
|||
// bluetooth on icon, 32x32px, horizontal
|
|||
static const uint8_t bluetooth_on[] = { |
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
|||
0x00, 0x0c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, |
|||
0x00, 0xfc, 0x01, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xdc, 0x07, 0x00, |
|||
0x0c, 0x1c, 0x1f, 0x00, 0x3c, 0x1c, 0x3e, 0x00, 0x7c, 0x1c, 0x3e, 0x00, |
|||
0xf8, 0x1d, 0x1f, 0x0e, 0xe0, 0x9f, 0x0f, 0x1e, 0xc0, 0xff, 0x03, 0x1e, |
|||
0x00, 0xff, 0x01, 0x3c, 0x00, 0xfe, 0xe0, 0x38, 0x00, 0x7e, 0xe0, 0x38, |
|||
0xc0, 0xff, 0x41, 0x38, 0xc0, 0xff, 0x03, 0x1e, 0xe0, 0xdf, 0x07, 0x1e, |
|||
0xf0, 0x1d, 0x1f, 0x0e, 0x7c, 0x1c, 0x3e, 0x00, 0x3c, 0x1c, 0x3e, 0x00, |
|||
0x1c, 0x1c, 0x1f, 0x00, 0x00, 0x9c, 0x0f, 0x00, 0x00, 0xfc, 0x03, 0x00, |
|||
0x00, 0xfc, 0x01, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, |
|||
0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; |
|||
|
|||
|
|||
// bluetooth off icon, 32x32px, horizontal
|
|||
static const uint8_t bluetooth_off[] = { |
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00, |
|||
0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0, 0x07, 0x00, 0x1c, 0xc0, 0x1f, 0x00, |
|||
0x3c, 0xc0, 0x3f, 0x00, 0x7c, 0xc0, 0xfd, 0x00, 0xf0, 0xc1, 0xf1, 0x01, |
|||
0xe0, 0xc3, 0xe1, 0x03, 0xc0, 0x0f, 0xc0, 0x03, 0x00, 0x1f, 0xf0, 0x01, |
|||
0x00, 0x3e, 0xf0, 0x00, 0x00, 0xf8, 0x70, 0x00, 0x00, 0xf0, 0x01, 0x00, |
|||
0x00, 0xe0, 0x07, 0x00, 0x00, 0xe0, 0x0f, 0x00, 0x00, 0xf0, 0x1f, 0x00, |
|||
0x00, 0xfc, 0x7d, 0x00, 0x00, 0xfe, 0xf9, 0x00, 0x00, 0xdf, 0xf1, 0x03, |
|||
0xc0, 0xc7, 0xc1, 0x07, 0xc0, 0xc3, 0xe1, 0x0f, 0xc0, 0xc1, 0xf1, 0x3f, |
|||
0x00, 0xc0, 0xfd, 0x3c, 0x00, 0xc0, 0x3f, 0x38, 0x00, 0xc0, 0x1f, 0x00, |
|||
0x00, 0xc0, 0x07, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0, 0x01, 0x00, |
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; |
|||
|
|||
|
|||
// power icon, 32x32px, horizontal
|
|||
static const uint8_t power_icon[] = { |
|||
0x00, 0x80, 0x01, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0, 0x03, 0x00, |
|||
0x00, 0xcc, 0x33, 0x00, 0x00, 0xcf, 0xf3, 0x00, 0x80, 0xcf, 0xf3, 0x01, |
|||
0xc0, 0xcf, 0xf3, 0x03, 0xe0, 0xcf, 0xf3, 0x07, 0xf0, 0xc7, 0xe3, 0x0f, |
|||
0xf8, 0xc3, 0xc3, 0x1f, 0xf8, 0xc1, 0x83, 0x1f, 0xfc, 0xc0, 0x03, 0x3f, |
|||
0x7c, 0xc0, 0x03, 0x3e, 0x7c, 0xc0, 0x03, 0x3e, 0x7e, 0x80, 0x01, 0x7e, |
|||
0x3e, 0x00, 0x00, 0x7c, 0x3e, 0x00, 0x00, 0x7c, 0x3e, 0x00, 0x00, 0x7c, |
|||
0x3e, 0x00, 0x00, 0x7c, 0x3e, 0x00, 0x00, 0x7c, 0x7c, 0x00, 0x00, 0x3e, |
|||
0x7c, 0x00, 0x00, 0x3e, 0xfc, 0x00, 0x00, 0x3f, 0xf8, 0x01, 0x80, 0x1f, |
|||
0xf8, 0x03, 0xc0, 0x1f, 0xf0, 0x07, 0xe0, 0x0f, 0xf0, 0x1f, 0xf8, 0x0f, |
|||
0xe0, 0xff, 0xff, 0x07, 0xc0, 0xff, 0xff, 0x03, 0x00, 0xff, 0xff, 0x00, |
|||
0x00, 0xfc, 0x3f, 0x00, 0x00, 0xf0, 0x0f, 0x00 }; |
|||
|
|||
|
|||
|
|||
|
|||
// 'advert', 32x32px, horizontal
|
|||
static const uint8_t advert_icon[] = { |
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x0c, |
|||
0x38, 0x00, 0x00, 0x1c, 0x18, 0x00, 0x00, 0x18, 0x0c, 0x00, 0x00, 0x30, |
|||
0x0c, 0x06, 0x60, 0x30, 0x06, 0x07, 0xe0, 0x60, 0x86, 0x03, 0xc0, 0x61, |
|||
0x87, 0x81, 0x81, 0xe1, 0xc3, 0xe0, 0x07, 0xc3, 0xc3, 0xf0, 0x0f, 0xc3, |
|||
0xc3, 0xf0, 0x0f, 0xc3, 0xc3, 0xf0, 0x0f, 0xc3, 0xc3, 0xf0, 0x0f, 0xc3, |
|||
0xc3, 0xe0, 0x07, 0xc3, 0x83, 0xc1, 0x83, 0xc1, 0x86, 0x01, 0x80, 0x61, |
|||
0x06, 0x03, 0xc0, 0x60, 0x0e, 0x07, 0xe0, 0x70, 0x0c, 0x02, 0x40, 0x30, |
|||
0x1c, 0x00, 0x00, 0x38, 0x18, 0x00, 0x00, 0x18, 0x30, 0x00, 0x00, 0x0c, |
|||
0x20, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; |
|||
|
|||
|
|||
|
|||
// 'muted, 8x8px, horizontal
|
|||
static const uint8_t muted_icon[] = { |
|||
0x20, 0x6a, 0xea, 0xe4, 0xe4, 0xea, 0x6a, 0x20 }; |
|||
@ -10,7 +10,8 @@ |
|||
|
|||
[platformio] |
|||
extra_configs = |
|||
variants/*/platformio.ini |
|||
variants/*/platformio.ini |
|||
platformio.local.ini |
|||
|
|||
[arduino_base] |
|||
framework = arduino |
|||
@ -18,7 +19,7 @@ monitor_speed = 115200 |
|||
lib_deps = |
|||
SPI |
|||
Wire |
|||
jgromes/RadioLib @ ^7.3.0 |
|||
jgromes/RadioLib @ ^7.6.0 |
|||
rweather/Crypto @ ^0.4.0 |
|||
adafruit/RTClib @ ^2.1.3 |
|||
melopero/Melopero RV3028 @ ^1.1.0 |
|||
@ -65,7 +66,7 @@ build_src_filter = ${arduino_base.build_src_filter} |
|||
|
|||
[esp32_ota] |
|||
lib_deps = |
|||
me-no-dev/ESPAsyncWebServer @ ^3.6.0 |
|||
ESP32Async/ESPAsyncWebServer @ 3.10.3 |
|||
file://arch/esp32/AsyncElegantOTA |
|||
|
|||
; esp32c6 uses arduino framework 3.x |
|||
@ -80,24 +81,25 @@ platform = https://github.com/pioarduino/platform-espressif32/releases/download/ |
|||
extends = arduino_base |
|||
platform = nordicnrf52 |
|||
platform_packages = |
|||
framework-arduinoadafruitnrf52 @ 1.10700.0 |
|||
extra_scripts = |
|||
create-uf2.py |
|||
arch/nrf52/extra_scripts/patch_bluefruit.py |
|||
; use internal fork that includes patch to ble stack to prevent firmware lockup during rapid connect/disconnect |
|||
; https://github.com/meshcore-dev/MeshCore/pull/1177 |
|||
; https://github.com/meshcore-dev/MeshCore/pull/1295 |
|||
framework-arduinoadafruitnrf52 @ https://github.com/meshcore-dev/Adafruit_nRF52_Arduino#d541301 |
|||
extra_scripts = create-uf2.py |
|||
build_flags = ${arduino_base.build_flags} |
|||
-D NRF52_PLATFORM |
|||
-D LFS_NO_ASSERT=1 |
|||
-D EXTRAFS=1 |
|||
lib_deps = |
|||
${arduino_base.lib_deps} |
|||
https://github.com/oltaco/CustomLFS @ 0.2.1 |
|||
https://github.com/oltaco/CustomLFS#0.2.2 |
|||
; ----------------- RP2040 --------------------- |
|||
|
|||
[rp2040_base] |
|||
extends = arduino_base |
|||
upload_protocol = picotool |
|||
board_build.core = earlephilhower |
|||
platform = https://github.com/maxgerhardt/platform-raspberrypi.git |
|||
platform = https://github.com/maxgerhardt/platform-raspberrypi.git#4e22a0d ; framework-arduinopico @ 1.50600.0+sha.6a1d13e9 |
|||
build_flags = ${arduino_base.build_flags} |
|||
-D RP2040_PLATFORM |
|||
|
|||
@ -105,17 +107,17 @@ build_flags = ${arduino_base.build_flags} |
|||
|
|||
[stm32_base] |
|||
extends = arduino_base |
|||
platform = platformio/[email protected] |
|||
platform_packages = platformio/framework-arduinoststm32@https://github.com/stm32duino/Arduino_Core_STM32/archive/2.10.1.zip |
|||
platform = ststm32 |
|||
extra_scripts = post:arch/stm32/build_hex.py |
|||
build_flags = ${arduino_base.build_flags} |
|||
-D STM32_PLATFORM |
|||
-I src/helpers/stm32 |
|||
-I $PROJECT_PACKAGES_DIR/framework-arduinoststm32/libraries/SubGhz/src |
|||
build_src_filter = ${arduino_base.build_src_filter} |
|||
+<helpers/stm32> |
|||
lib_deps = ${arduino_base.lib_deps} |
|||
file://arch/stm32/Adafruit_LittleFS_stm32 |
|||
adafruit/Adafruit BusIO @ 1.17.2 |
|||
SubGhz |
|||
|
|||
[sensor_base] |
|||
build_flags = |
|||
@ -150,3 +152,17 @@ lib_deps = |
|||
stevemarple/MicroNMEA @ ^2.0.6 |
|||
adafruit/Adafruit BME680 Library @ ^2.0.4 |
|||
adafruit/Adafruit BMP085 Library @ ^1.2.4 |
|||
|
|||
; ----------------- TESTING --------------------- |
|||
|
|||
[env:native] |
|||
platform = native |
|||
build_flags = -std=c++17 |
|||
-I src |
|||
-I test/mocks |
|||
test_build_src = yes |
|||
build_src_filter = |
|||
-<*> |
|||
+<../src/Utils.cpp> |
|||
lib_deps = |
|||
google/googletest @ 1.17.0 |
|||
|
|||
File diff suppressed because it is too large
@ -0,0 +1,197 @@ |
|||
#include "RTC_RX8130CE.h" |
|||
#include "RTClib.h" |
|||
|
|||
|
|||
bool RTC_RX8130CE::stop(bool stop) { |
|||
write_register(0x1E, stop ? 0x040 : 0x00); |
|||
return true; |
|||
} |
|||
|
|||
bool RTC_RX8130CE::begin(TwoWire *wire) { |
|||
if (i2c_dev) { |
|||
delete i2c_dev; |
|||
} |
|||
|
|||
i2c_dev = new Adafruit_I2CDevice(this->_addr, wire); |
|||
if (!i2c_dev->begin()) { |
|||
return false; |
|||
} |
|||
|
|||
/*
|
|||
* Digital offset register: |
|||
* [7] DET: 0 -> disabled |
|||
* [6:0] L7-L1: 0 -> no offset |
|||
*/ |
|||
write_register(0x30, 0x00); |
|||
|
|||
/*
|
|||
* Extension Register register: |
|||
* [7:6] FSEL: 0 -> 0 |
|||
* [5] USEL: 0 -> 0 |
|||
* [4] TE: 0 -> |
|||
* [3] WADA: 0 -> 0 |
|||
* [2-0] TSEL: 0 -> 0 |
|||
*/ |
|||
write_register(0x1C, 0x00); |
|||
|
|||
/*
|
|||
* Flag Register register: |
|||
* [7] VBLF: 0 -> 0 |
|||
* [6] 0: 0 -> |
|||
* [5] UF: 0 -> |
|||
* [4] TF: 0 -> |
|||
* [3] AF: 0 -> 0 |
|||
* [2] RSF: 0 -> 0 |
|||
* [1] VLF: 0 -> 0 |
|||
* [0] VBFF: 0 -> 0 |
|||
*/ |
|||
write_register(0x1D, 0x00); |
|||
|
|||
/*
|
|||
* Control Register0 register: |
|||
* [7] TEST: 0 -> 0 |
|||
* [6] STOP: 0 -> |
|||
* [5] UIE: 0 -> |
|||
* [4] TIE: 0 -> |
|||
* [3] AIE: 0 -> 0 |
|||
* [2] TSTP: 0 -> 0 |
|||
* [1] TBKON: 0 -> 0 |
|||
* [0] TBKE: 0 -> 0 |
|||
*/ |
|||
write_register(0x1E, 0x00); |
|||
|
|||
/*
|
|||
* Control Register1 register: |
|||
* [7-6] SMPTSEL: 0 -> 0 |
|||
* [5] CHGEN: 0 -> |
|||
* [4] INIEN: 0 -> |
|||
* [3] 0: 0 -> |
|||
* [2] RSVSEL: 0 -> 0 |
|||
* [1-0] BFVSEL: 0 -> 0 |
|||
*/ |
|||
write_register(0x1F, 0x00); |
|||
|
|||
this->stop(false); // clear STOP bit
|
|||
|
|||
/*
|
|||
* Function register: |
|||
* [7] 100TH: 0 -> disabled |
|||
* [6:5] Periodic interrupt: 0 -> no periodic interrupt |
|||
* [4] RTCM: 0 -> real-time clock mode |
|||
* [3] STOPM: 0 -> RTC stop is controlled by STOP bit only |
|||
* [2:0] Clock output frequency: 000 (Default value) |
|||
*/ |
|||
write_register(0x28, 0x00); |
|||
|
|||
// Battery switch register
|
|||
write_register(0x26, 0x00); // enable battery switch feature
|
|||
|
|||
return true; |
|||
} |
|||
|
|||
bool RTC_RX8130CE::setTime(struct tm *t) { |
|||
uint8_t buf[8]; |
|||
buf[0] = 0x10; |
|||
buf[1] = bin2bcd(t->tm_sec) & 0x7F; |
|||
buf[2] = bin2bcd(t->tm_min) & 0x7F; |
|||
buf[3] = bin2bcd(t->tm_hour) & 0x3F; |
|||
buf[4] = bin2bcd(t->tm_wday) & 0x07; |
|||
buf[5] = bin2bcd(t->tm_mday) & 0x3F; |
|||
buf[6] = bin2bcd(t->tm_mon + 1) & 0x1F; |
|||
buf[7] = bin2bcd((t->tm_year - 100)); |
|||
|
|||
this->stop(true); |
|||
i2c_dev->write(buf, sizeof(buf)); |
|||
this->stop(false); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
void RTC_RX8130CE::adjust(DateTime dt) { |
|||
struct tm *atv; |
|||
time_t utime; |
|||
|
|||
utime = (time_t)dt.unixtime(); |
|||
atv = gmtime(&utime); |
|||
|
|||
this->setTime(atv); |
|||
} |
|||
|
|||
DateTime RTC_RX8130CE::now() { |
|||
struct tm atv; |
|||
this->getTime(&atv); |
|||
|
|||
return DateTime((uint32_t)mktime(&atv)); |
|||
} |
|||
|
|||
uint32_t RTC_RX8130CE::unixtime() { |
|||
struct tm atv; |
|||
this->getTime(&atv); |
|||
|
|||
return (uint32_t)mktime(&atv); |
|||
} |
|||
|
|||
bool RTC_RX8130CE::getTime(struct tm *t) { |
|||
uint8_t buff[7]; |
|||
|
|||
buff[0] = 0x10; |
|||
|
|||
i2c_dev->write_then_read(buff, 1, buff, 7); |
|||
|
|||
t->tm_sec = bcd2bin(buff[0] & 0x7F); |
|||
t->tm_min = bcd2bin(buff[1] & 0x7F); |
|||
t->tm_hour = bcd2bin(buff[2] & 0x3F); |
|||
t->tm_wday = bcd2bin(buff[3] & 0x07); |
|||
t->tm_mday = bcd2bin(buff[4] & 0x3F); |
|||
t->tm_mon = bcd2bin(buff[5] & 0x1F) - 1; |
|||
t->tm_year = bcd2bin(buff[6]) + 100; |
|||
|
|||
return true; |
|||
} |
|||
|
|||
bool RTC_RX8130CE::writeRAM(uint8_t address, uint8_t value) { |
|||
return this->writeRAM(address, &value, 1); |
|||
} |
|||
|
|||
size_t RTC_RX8130CE::writeRAM(uint8_t address, uint8_t *value, size_t len) { |
|||
uint8_t buf[len + 1]; |
|||
|
|||
if (address > 3) { |
|||
return 0; |
|||
} |
|||
|
|||
if ((address + len) > 3) { |
|||
len = 3 - address; |
|||
} |
|||
|
|||
buf[0] = 0x20 + address; |
|||
|
|||
for (int i = 1; i <= len + 1; i++) { |
|||
buf[i] = value[i - 1]; |
|||
} |
|||
|
|||
i2c_dev->write(buf, len + 1); |
|||
|
|||
return len; |
|||
} |
|||
|
|||
bool RTC_RX8130CE::readRAM(uint8_t address, uint8_t *value, size_t len) { |
|||
uint8_t real_address = 0x20 + address; |
|||
|
|||
if (address > 3) { // Oversize of 64-bytes RAM
|
|||
return false; |
|||
} |
|||
|
|||
if ((address + len) > 3) { // Data size over RAM size
|
|||
len = 3 - address; |
|||
} |
|||
|
|||
i2c_dev->write_then_read(&real_address, 1, value, len); |
|||
return true; |
|||
} |
|||
|
|||
uint8_t RTC_RX8130CE::readRAM(uint8_t address) { |
|||
uint8_t value = 0xFF; |
|||
this->readRAM(address, &value, 1); |
|||
return value; |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
#ifndef __RTC_RX8130CE_H__ |
|||
#define __RTC_RX8130CE_H__ |
|||
|
|||
#include <Arduino.h> |
|||
#include <Wire.h> |
|||
#include <time.h> |
|||
#include "RTClib.h" |
|||
|
|||
class RTC_RX8130CE : RTC_I2C { |
|||
private: |
|||
const uint8_t _addr = 0x32; |
|||
|
|||
bool stop(bool stop); |
|||
|
|||
protected: |
|||
|
|||
public: |
|||
bool begin(TwoWire *wire); |
|||
bool setTime(struct tm *t); |
|||
bool getTime(struct tm *t); |
|||
void adjust(DateTime t); |
|||
|
|||
DateTime now(); |
|||
uint32_t unixtime(); |
|||
|
|||
bool writeRAM(uint8_t address, uint8_t value); |
|||
size_t writeRAM(uint8_t address, uint8_t *value, size_t len); |
|||
bool readRAM(uint8_t address, uint8_t *value, size_t len); |
|||
uint8_t readRAM(uint8_t address); |
|||
|
|||
}; |
|||
|
|||
#endif |
|||
File diff suppressed because it is too large
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue