Examples and samples ready for Zephyr release
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
# Zephyr samples
|
||||
|
||||
Zephyr applications live here; `examples/` holds the Arduino/PlatformIO ones.
|
||||
|
||||
| Sample | What it does |
|
||||
|---|---|
|
||||
| [`scan/`](scan/) | Lists every Victron device advertising nearby. No keys needed. **Start here.** |
|
||||
| [`observer/`](observer/) | Monitors known devices and decodes their records. |
|
||||
|
||||
Both need `CONFIG_VICTRONBLE=y`, which depends on `CONFIG_BT_OBSERVER=y`. See
|
||||
the Zephyr section of the top-level [README](../README.md) for how to add this
|
||||
library to a west workspace.
|
||||
|
||||
## Building
|
||||
|
||||
The library is a Zephyr module. If it is already in your `west.yml`, the
|
||||
samples build with no extra flags:
|
||||
|
||||
```sh
|
||||
west build -p -b nrf52840dk/nrf52840 samples/observer
|
||||
```
|
||||
|
||||
For local development against a checkout that is *not* in the manifest, point
|
||||
Zephyr at it directly:
|
||||
|
||||
```sh
|
||||
west build -p -b nrf52840dk/nrf52840 -d /tmp/vb_obs \
|
||||
/path/to/VictronBLE/samples/observer \
|
||||
-- -DZEPHYR_EXTRA_MODULES=/path/to/VictronBLE
|
||||
```
|
||||
|
||||
Then `west flash`, and watch the console at 115200 baud.
|
||||
|
||||
Tested on `nrf52840dk/nrf52840` and `rak4631/nrf52840` with Zephyr v4.4.0. Any
|
||||
board with a Bluetooth controller and the observer role should work — nothing
|
||||
in the library is nRF-specific.
|
||||
|
||||
Build-test both samples without hardware. For a checkout outside the manifest,
|
||||
twister needs the same module hint via the environment:
|
||||
|
||||
```sh
|
||||
ZEPHYR_EXTRA_MODULES=$PWD \
|
||||
west twister -T samples -p nrf52840dk/nrf52840 -p rak4631/nrf52840
|
||||
```
|
||||
@@ -0,0 +1,7 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
cmake_minimum_required(VERSION 3.20.0)
|
||||
|
||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||
project(victronble_observer)
|
||||
|
||||
target_sources(app PRIVATE src/main.c)
|
||||
@@ -0,0 +1,88 @@
|
||||
# observer — decode records from known devices
|
||||
|
||||
Monitors a list of Victron devices, decrypts their Instant Readout
|
||||
advertisements and logs every field. This is the reference for using the
|
||||
Zephyr API.
|
||||
|
||||
Don't know your devices' addresses yet? Build [`../scan`](../scan/) first.
|
||||
|
||||
## Configure your devices
|
||||
|
||||
Edit the `known_devices` table at the top of [`src/main.c`](src/main.c):
|
||||
|
||||
```c
|
||||
static const struct { ... } known_devices[] = {
|
||||
{
|
||||
.name = "Rainbow48V",
|
||||
.addr = "E4:05:42:34:14:F3",
|
||||
.addr_type = "random",
|
||||
.key = "0ec3adf7433dd61793ff2f3b8ad32ed8",
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
The key is in VictronConnect: device → gear icon → Product info → *Instant
|
||||
readout via Bluetooth* → show/copy the encryption key (32 hex characters).
|
||||
|
||||
**The address type must be `random`.** Victron devices use random static
|
||||
Bluetooth addresses, and the registry lookup compares the type as well as the
|
||||
bytes. Get it wrong and the symptom is quiet and confusing: `adverts` climbs
|
||||
in the stats line but `decoded` stays at zero, because the adverts arrive and
|
||||
never match a registered device.
|
||||
|
||||
Add more than four devices and you also need to raise
|
||||
`CONFIG_VICTRONBLE_MAX_DEVICES` in `prj.conf`.
|
||||
|
||||
## Build and run
|
||||
|
||||
```sh
|
||||
west build -p -b nrf52840dk/nrf52840 -d /tmp/vb_obs samples/observer \
|
||||
-- -DZEPHYR_EXTRA_MODULES=$PWD
|
||||
west flash -d /tmp/vb_obs
|
||||
```
|
||||
|
||||
Console (115200 baud):
|
||||
|
||||
```
|
||||
<inf> observer: VictronBLE observer starting
|
||||
<inf> observer: monitoring Rainbow48V (E4:05:42:34:14:F3)
|
||||
<inf> victronble: observing (interval 2048 window 18)
|
||||
<inf> observer: E4:05:42:34:14:F3 (random) solar charger rssi -67 model 0xa060 nonce 4660
|
||||
<inf> observer: state bulk error 0
|
||||
<inf> observer: battery 13.24 V 5.4 A pv 340 W yield today 1200 Wh
|
||||
<inf> observer: load n/a
|
||||
<inf> observer: stats: adverts 61 queued 30 dropped 0 decoded 28 dup 2 err 0
|
||||
```
|
||||
|
||||
## Reading the stats line
|
||||
|
||||
Printed every 30 s, and the fastest way to diagnose a quiet console:
|
||||
|
||||
| Counter | Meaning if it misbehaves |
|
||||
|---|---|
|
||||
| `adverts` | Victron adverts seen from **any** device. Zero → nothing in range, or Instant Readout is off on the device. |
|
||||
| `queued` | adverts from your registered devices. Zero while `adverts` climbs → wrong address or wrong address type. |
|
||||
| `dropped` | queue was full. Raise `CONFIG_VICTRONBLE_QUEUE_DEPTH`. |
|
||||
| `decoded` | records delivered to the callback. |
|
||||
| `dup` | repeats suppressed by nonce dedup. A steady trickle is normal — each advert is broadcast on three channels. |
|
||||
| `err` | decode failures. A wrong key shows up here, and in the `decode failed: key mismatch` warning. |
|
||||
|
||||
## What the code demonstrates
|
||||
|
||||
- `bt_enable()` **before** `victronble_start()` — the application owns the
|
||||
Bluetooth stack, the library only scans.
|
||||
- `bt_addr_le_from_str()` + `victronble_parse_key()` to turn human-readable
|
||||
config into what `victronble_device_add()` wants.
|
||||
- `victronble_cb_register()` with both `record` and `decode_error` set.
|
||||
Callbacks run on the library's decode thread, not the Bluetooth RX thread,
|
||||
so logging in them is safe.
|
||||
- Formatting a record: `isnan()` guards on every float, and
|
||||
`remaining_minutes == 0xFFFF` for time-to-go. Absent fields are normal —
|
||||
an MPPT with no load output always reports `load n/a`.
|
||||
- `victronble_get_stats()` for the periodic health line.
|
||||
|
||||
Printing floats needs `CONFIG_CBPRINTF_FP_SUPPORT=y`; without it the numbers
|
||||
come out empty.
|
||||
|
||||
To decode a captured advert on your PC instead — no board involved — see
|
||||
[`examples/NativeDecode`](../../examples/NativeDecode/).
|
||||
@@ -0,0 +1,19 @@
|
||||
# Bluetooth: observer role only — no connections, no pairing, no advertising.
|
||||
CONFIG_BT=y
|
||||
CONFIG_BT_OBSERVER=y
|
||||
CONFIG_BT_DEVICE_NAME="victron-observer"
|
||||
|
||||
# Advertising reports are "discardable" HCI events. The default pool is easy
|
||||
# to exhaust with a passive scan in a dense RF environment; if the stats line
|
||||
# shows adverts arriving in bursts and then stalling, raise this further.
|
||||
CONFIG_BT_BUF_EVT_DISCARDABLE_COUNT=20
|
||||
|
||||
CONFIG_VICTRONBLE=y
|
||||
CONFIG_VICTRONBLE_MAX_DEVICES=4
|
||||
|
||||
CONFIG_LOG=y
|
||||
CONFIG_VICTRONBLE_LOG_LEVEL_INF=y
|
||||
|
||||
# Records carry floats (volts, amps, watts). Without this, %f in LOG_INF()
|
||||
# and snprintk() prints nothing useful.
|
||||
CONFIG_CBPRINTF_FP_SUPPORT=y
|
||||
@@ -0,0 +1,10 @@
|
||||
sample:
|
||||
name: VictronBLE observer
|
||||
description: Decode Victron Instant Readout advertisements from known devices
|
||||
tests:
|
||||
sample.victronble.observer:
|
||||
build_only: true
|
||||
platform_allow:
|
||||
- nrf52840dk/nrf52840
|
||||
- rak4631/nrf52840
|
||||
tags: bluetooth victron
|
||||
@@ -0,0 +1,278 @@
|
||||
/**
|
||||
* VictronBLE Zephyr observer sample.
|
||||
*
|
||||
* Monitors a fixed list of Victron devices, decodes their Instant Readout
|
||||
* advertisements and logs every record. Prints a statistics line every 30 s
|
||||
* so a silent console can be diagnosed without a sniffer.
|
||||
*
|
||||
* Don't know your devices' MAC addresses yet? Build samples/scan first — it
|
||||
* lists every Victron device in range.
|
||||
*
|
||||
* Copyright (c) 2026 Scott Penrose
|
||||
* License: MIT
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/bluetooth/bluetooth.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
|
||||
#include "victronble_zephyr.h"
|
||||
|
||||
LOG_MODULE_REGISTER(observer, LOG_LEVEL_INF);
|
||||
|
||||
#define STATS_INTERVAL K_SECONDS(30)
|
||||
|
||||
/*
|
||||
* Your devices. The advertisement key is in VictronConnect:
|
||||
* device -> gear icon -> Product info -> "Instant readout via Bluetooth"
|
||||
* -> SHOW / copy the encryption key (32 hex characters).
|
||||
*
|
||||
* Victron devices use a random static Bluetooth address, so the address type
|
||||
* is "random" — not "public". Getting this wrong is the usual reason a device
|
||||
* never matches: the adverts arrive (the stats line counts them) but no
|
||||
* record is ever decoded, because the registry lookup compares the type too.
|
||||
*/
|
||||
static const struct {
|
||||
const char *name;
|
||||
const char *addr;
|
||||
const char *addr_type;
|
||||
const char *key;
|
||||
} known_devices[] = {
|
||||
{
|
||||
.name = "Rainbow48V",
|
||||
.addr = "E4:05:42:34:14:F3",
|
||||
.addr_type = "random",
|
||||
.key = "0ec3adf7433dd61793ff2f3b8ad32ed8",
|
||||
},
|
||||
{
|
||||
.name = "ScottTrailer",
|
||||
.addr = "E6:45:59:78:3C:FB",
|
||||
.addr_type = "random",
|
||||
.key = "3fa658aded4f309b9bc17a2318cb1f56",
|
||||
},
|
||||
};
|
||||
|
||||
/* --- record formatting ------------------------------------------------- */
|
||||
|
||||
#define FBUF_LEN 20
|
||||
|
||||
/*
|
||||
* A float field the device did not send comes back as NAN (see
|
||||
* include/victronble.h). Render that as "n/a" rather than letting "nan" leak
|
||||
* into the log — a missing load-current reading is not a fault. The unit goes
|
||||
* in here too, so an absent field reads "n/a" and not "n/a A".
|
||||
*
|
||||
* Needs CONFIG_CBPRINTF_FP_SUPPORT=y, or every number comes out empty.
|
||||
*/
|
||||
static const char *flt(char *buf, float v, int dp, const char *unit)
|
||||
{
|
||||
if (isnan(v)) {
|
||||
strcpy(buf, "n/a");
|
||||
} else {
|
||||
snprintk(buf, FBUF_LEN, "%.*f %s", dp, (double)v, unit);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void print_solar(const victronble_solar_charger_t *s)
|
||||
{
|
||||
char a[FBUF_LEN], b[FBUF_LEN], c[FBUF_LEN], d[FBUF_LEN];
|
||||
|
||||
LOG_INF(" state %s error %u", victronble_state_str(s->state),
|
||||
s->error);
|
||||
LOG_INF(" battery %s %s pv %s yield today %u Wh",
|
||||
flt(a, s->battery_voltage, 2, "V"),
|
||||
flt(b, s->battery_current, 1, "A"),
|
||||
flt(c, s->pv_power, 0, "W"), s->yield_today_wh);
|
||||
LOG_INF(" load %s", flt(d, s->load_current, 1, "A"));
|
||||
}
|
||||
|
||||
static void print_batmon(const victronble_battery_monitor_t *m)
|
||||
{
|
||||
char a[FBUF_LEN], b[FBUF_LEN], c[FBUF_LEN], d[FBUF_LEN];
|
||||
|
||||
LOG_INF(" battery %s %s soc %s", flt(a, m->voltage, 2, "V"),
|
||||
flt(b, m->current, 2, "A"), flt(c, m->soc, 1, "%"));
|
||||
LOG_INF(" consumed %s alarm 0x%04x",
|
||||
flt(d, m->consumed_ah, 1, "Ah"), m->alarm);
|
||||
|
||||
/* 0xFFFF is the wire's "not available", not 45 days of runtime. */
|
||||
if (m->remaining_minutes == 0xFFFF) {
|
||||
LOG_INF(" time to go n/a");
|
||||
} else {
|
||||
LOG_INF(" time to go %u min", m->remaining_minutes);
|
||||
}
|
||||
|
||||
/* The aux channel is one of three things, chosen on the device. */
|
||||
switch (m->aux_mode) {
|
||||
case 0:
|
||||
LOG_INF(" aux voltage %s", flt(a, m->aux_voltage, 2, "V"));
|
||||
break;
|
||||
case 2:
|
||||
LOG_INF(" temperature %s", flt(a, m->temperature, 1, "degC"));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void print_record(const bt_addr_le_t *addr, int8_t rssi,
|
||||
const victronble_record_t *rec)
|
||||
{
|
||||
char addr_str[BT_ADDR_LE_STR_LEN];
|
||||
char a[FBUF_LEN], b[FBUF_LEN], c[FBUF_LEN];
|
||||
|
||||
bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));
|
||||
|
||||
LOG_INF("%s %s rssi %d model 0x%04x nonce %u", addr_str,
|
||||
victronble_device_type_str(rec->type), rssi, rec->model_id,
|
||||
rec->nonce);
|
||||
|
||||
switch (rec->type) {
|
||||
case VICTRONBLE_DEV_SOLAR_CHARGER:
|
||||
print_solar(&rec->u.solar);
|
||||
break;
|
||||
case VICTRONBLE_DEV_BATTERY_MONITOR:
|
||||
print_batmon(&rec->u.batmon);
|
||||
break;
|
||||
case VICTRONBLE_DEV_INVERTER:
|
||||
LOG_INF(" state %s battery %s %s ac %s",
|
||||
victronble_state_str(rec->u.inverter.state),
|
||||
flt(a, rec->u.inverter.battery_voltage, 2, "V"),
|
||||
flt(b, rec->u.inverter.battery_current, 2, "A"),
|
||||
flt(c, rec->u.inverter.ac_power, 0, "W"));
|
||||
break;
|
||||
case VICTRONBLE_DEV_DCDC_CONVERTER:
|
||||
LOG_INF(" state %s in %s out %s %s",
|
||||
victronble_state_str(rec->u.dcdc.state),
|
||||
flt(a, rec->u.dcdc.input_voltage, 2, "V"),
|
||||
flt(b, rec->u.dcdc.output_voltage, 2, "V"),
|
||||
flt(c, rec->u.dcdc.output_current, 1, "A"));
|
||||
break;
|
||||
case VICTRONBLE_DEV_AC_CHARGER:
|
||||
LOG_INF(" state %s out1 %s %s temp %s",
|
||||
victronble_state_str(rec->u.ac.state),
|
||||
flt(a, rec->u.ac.voltage1, 2, "V"),
|
||||
flt(b, rec->u.ac.current1, 1, "A"),
|
||||
flt(c, rec->u.ac.temperature, 1, "degC"));
|
||||
break;
|
||||
default:
|
||||
LOG_INF(" (no decoder for record type 0x%02x)",
|
||||
rec->record_type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- callbacks --------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* These run on the victronble decode thread, not the Bluetooth RX thread, so
|
||||
* logging here is fine — it cannot stall the controller.
|
||||
*/
|
||||
static void on_record(const bt_addr_le_t *addr, int8_t rssi,
|
||||
const victronble_record_t *rec)
|
||||
{
|
||||
print_record(addr, rssi, rec);
|
||||
}
|
||||
|
||||
static void on_decode_error(const bt_addr_le_t *addr, victronble_err_t err)
|
||||
{
|
||||
char addr_str[BT_ADDR_LE_STR_LEN];
|
||||
|
||||
bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));
|
||||
LOG_WRN("%s decode failed: %s", addr_str, victronble_strerror(err));
|
||||
}
|
||||
|
||||
static struct victronble_cb callbacks = {
|
||||
.record = on_record,
|
||||
.decode_error = on_decode_error,
|
||||
};
|
||||
|
||||
/* --- setup ------------------------------------------------------------- */
|
||||
|
||||
static int register_devices(void)
|
||||
{
|
||||
int registered = 0;
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(known_devices); i++) {
|
||||
bt_addr_le_t addr;
|
||||
uint8_t key[VICTRONBLE_KEY_LEN];
|
||||
int err;
|
||||
|
||||
err = bt_addr_le_from_str(known_devices[i].addr,
|
||||
known_devices[i].addr_type, &addr);
|
||||
if (err != 0) {
|
||||
LOG_ERR("%s: bad address '%s' (%d)",
|
||||
known_devices[i].name, known_devices[i].addr,
|
||||
err);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!victronble_parse_key(known_devices[i].key, key)) {
|
||||
LOG_ERR("%s: key must be 32 hex characters",
|
||||
known_devices[i].name);
|
||||
continue;
|
||||
}
|
||||
|
||||
err = victronble_device_add(&addr, key);
|
||||
if (err != 0) {
|
||||
LOG_ERR("%s: victronble_device_add failed (%d)",
|
||||
known_devices[i].name, err);
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG_INF("monitoring %s (%s)", known_devices[i].name,
|
||||
known_devices[i].addr);
|
||||
registered++;
|
||||
}
|
||||
|
||||
return registered;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
struct victronble_stats stats;
|
||||
int err;
|
||||
|
||||
LOG_INF("VictronBLE observer starting");
|
||||
|
||||
/* The application owns the Bluetooth stack: victronble_start() needs
|
||||
* it up already. */
|
||||
err = bt_enable(NULL);
|
||||
if (err != 0) {
|
||||
LOG_ERR("bt_enable failed (%d)", err);
|
||||
return 0;
|
||||
}
|
||||
|
||||
err = victronble_cb_register(&callbacks);
|
||||
if (err != 0) {
|
||||
LOG_ERR("victronble_cb_register failed (%d)", err);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (register_devices() == 0) {
|
||||
LOG_ERR("no devices registered — nothing to observe");
|
||||
return 0;
|
||||
}
|
||||
|
||||
err = victronble_start();
|
||||
if (err != 0) {
|
||||
LOG_ERR("victronble_start failed (%d)", err);
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
k_sleep(STATS_INTERVAL);
|
||||
|
||||
victronble_get_stats(&stats);
|
||||
LOG_INF("stats: adverts %u queued %u dropped %u decoded %u dup %u err %u",
|
||||
stats.adverts, stats.queued, stats.dropped,
|
||||
stats.decoded, stats.duplicates, stats.errors);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
cmake_minimum_required(VERSION 3.20.0)
|
||||
|
||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||
project(victronble_scan)
|
||||
|
||||
target_sources(app PRIVATE src/main.c)
|
||||
@@ -0,0 +1,58 @@
|
||||
# scan — find your Victron devices
|
||||
|
||||
Logs every Victron "Instant Readout" advertisement in range. **No
|
||||
advertisement keys and no device list required**, so this is the first thing
|
||||
to run: it tells you what you have and what its Bluetooth address is.
|
||||
|
||||
The whole sample is `victronble_watch_set(true)` plus `victronble_start()` —
|
||||
all the output comes from the library's own log module.
|
||||
|
||||
## Build and run
|
||||
|
||||
```sh
|
||||
west build -p -b nrf52840dk/nrf52840 -d /tmp/vb_scan samples/scan \
|
||||
-- -DZEPHYR_EXTRA_MODULES=$PWD
|
||||
west flash -d /tmp/vb_scan
|
||||
```
|
||||
|
||||
Console (115200 baud):
|
||||
|
||||
```
|
||||
<inf> scan: VictronBLE discovery — logging every Victron advert in range
|
||||
<inf> victronble: observing (interval 96 window 48)
|
||||
<inf> victronble: watch: E4:05:42:34:14:F3 (random) rssi -67 type 0x01 (solar charger) len 31 keycheck 0x0d
|
||||
<inf> victronble: watch: E6:45:59:78:3C:FB (random) rssi -82 type 0x02 (battery monitor) len 31 keycheck 0x3f
|
||||
<inf> scan: stats: adverts 61 queued 61 dropped 0
|
||||
```
|
||||
|
||||
Each line gives you everything you need for `samples/observer`:
|
||||
|
||||
| Field | Use |
|
||||
|---|---|
|
||||
| address + `(random)` | Victron uses **random** static addresses — that address type matters |
|
||||
| `type` | which device family it is, before any decryption |
|
||||
| `keycheck` | first byte of the advertisement key; confirms you copied the right key |
|
||||
| `rssi` | how well you can hear it — useful for placing the node |
|
||||
|
||||
Nothing here is decrypted. Watch mode reads only the plaintext header, which
|
||||
is why it works without keys.
|
||||
|
||||
## Next step
|
||||
|
||||
Copy the addresses into the `known_devices` table in
|
||||
[`../observer/src/main.c`](../observer/src/main.c) along with each device's key
|
||||
from VictronConnect (device → gear icon → Product info → *Instant readout via
|
||||
Bluetooth*), then build `observer`.
|
||||
|
||||
## Notes
|
||||
|
||||
- Unregistered devices are **not** deduplicated by nonce, so expect roughly
|
||||
one line per device per second. That is the point — it shows liveness.
|
||||
- `prj.conf` raises the scan duty cycle to 60 ms / 30 ms
|
||||
(`BT_GAP_SCAN_FAST_*`) instead of the library default 1.28 s / 11.25 ms.
|
||||
Discovery should be quick; `observer` uses the low-power default.
|
||||
- `CONFIG_VICTRONBLE_QUEUE_DEPTH=16` because watch mode queues every advert,
|
||||
not just the ones from known devices. If `dropped` climbs on a busy site,
|
||||
raise it further.
|
||||
- `adverts 0` after a minute means either nothing is in range or the device
|
||||
has *Instant readout via Bluetooth* switched off in VictronConnect.
|
||||
@@ -0,0 +1,25 @@
|
||||
# Bluetooth: observer role only — no connections, no pairing, no advertising.
|
||||
CONFIG_BT=y
|
||||
CONFIG_BT_OBSERVER=y
|
||||
CONFIG_BT_DEVICE_NAME="victron-scan"
|
||||
|
||||
# Discovery hears every Victron in range, so the advertising-report pool and
|
||||
# the library's decode queue both see more traffic than in normal operation.
|
||||
CONFIG_BT_BUF_EVT_DISCARDABLE_COUNT=20
|
||||
|
||||
CONFIG_VICTRONBLE=y
|
||||
|
||||
# Watch mode queues every Victron advert, not just the registered ones.
|
||||
CONFIG_VICTRONBLE_QUEUE_DEPTH=16
|
||||
|
||||
# No devices are registered — this sample never decrypts anything.
|
||||
CONFIG_VICTRONBLE_MAX_DEVICES=1
|
||||
|
||||
# Discovery wants to find things quickly, so trade power for a much higher
|
||||
# duty cycle than the library's defaults: 60 ms interval / 30 ms window
|
||||
# (BT_GAP_SCAN_FAST_*) instead of 1.28 s / 11.25 ms.
|
||||
CONFIG_VICTRONBLE_SCAN_INTERVAL=96
|
||||
CONFIG_VICTRONBLE_SCAN_WINDOW=48
|
||||
|
||||
CONFIG_LOG=y
|
||||
CONFIG_VICTRONBLE_LOG_LEVEL_INF=y
|
||||
@@ -0,0 +1,10 @@
|
||||
sample:
|
||||
name: VictronBLE scan
|
||||
description: List every Victron device advertising nearby, no keys required
|
||||
tests:
|
||||
sample.victronble.scan:
|
||||
build_only: true
|
||||
platform_allow:
|
||||
- nrf52840dk/nrf52840
|
||||
- rak4631/nrf52840
|
||||
tags: bluetooth victron
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* VictronBLE Zephyr discovery sample.
|
||||
*
|
||||
* Turns on the library's watch mode and does nothing else. No advertisement
|
||||
* keys, no device list: every Victron Instant Readout advert in range is
|
||||
* logged with its address, RSSI, device type and key-check byte.
|
||||
*
|
||||
* Run this first to find out what you have and what its MAC address is, then
|
||||
* put the addresses and keys into samples/observer.
|
||||
*
|
||||
* Copyright (c) 2026 Scott Penrose
|
||||
* License: MIT
|
||||
*/
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/bluetooth/bluetooth.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
#include "victronble_zephyr.h"
|
||||
|
||||
LOG_MODULE_REGISTER(scan, LOG_LEVEL_INF);
|
||||
|
||||
#define STATS_INTERVAL K_SECONDS(30)
|
||||
|
||||
int main(void)
|
||||
{
|
||||
struct victronble_stats stats;
|
||||
int err;
|
||||
|
||||
LOG_INF("VictronBLE discovery — logging every Victron advert in range");
|
||||
|
||||
/* The application owns the Bluetooth stack: victronble_start() needs
|
||||
* it up already. */
|
||||
err = bt_enable(NULL);
|
||||
if (err != 0) {
|
||||
LOG_ERR("bt_enable failed (%d)", err);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* All output comes from the library's own log module (victronble).
|
||||
* Unregistered devices are not nonce-deduplicated, so expect roughly
|
||||
* one line per device per second. */
|
||||
victronble_watch_set(true);
|
||||
|
||||
err = victronble_start();
|
||||
if (err != 0) {
|
||||
LOG_ERR("victronble_start failed (%d)", err);
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
k_sleep(STATS_INTERVAL);
|
||||
|
||||
victronble_get_stats(&stats);
|
||||
LOG_INF("stats: adverts %u queued %u dropped %u", stats.adverts,
|
||||
stats.queued, stats.dropped);
|
||||
|
||||
if (stats.adverts == 0) {
|
||||
LOG_WRN("nothing heard yet — check the device has "
|
||||
"'Instant readout via Bluetooth' enabled in "
|
||||
"VictronConnect");
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user