docs: publish channels: v2.8.2-open.6

This commit is contained in:
github-actions[bot] committed 2026-09-25 19:51:05 +00:00
1 parent 07ff509e11
commit 589debb953
1840 files changed
+234283

No files matched your search

File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
@@ -0,0 +1,156 @@
\| Lightning, strikes in the last hour and storm distance | count, km or mi | Card and listed with each reading on the Environment Metrics screen; not charted. From an AS3935 detector. Storm distance is always in km or mi, since the detector resolves whole kilometres |---
title: Telemetry & Sensors
parent: User Guide
nav_order: 9
last_updated: 2026-09-18
description: Sensor data on the mesh — supported environment, air quality, and power sensors, plus configuration and viewing guides.
aliases:
- sensors
- environment
- weather
- power-metrics
---
# Telemetry & Sensors
Meshtastic nodes can collect and share sensor data across the mesh network. Telemetry allows nodes equipped with sensors to broadcast environmental, power, and device health information, visible on the node detail screen and logged over time.
## Device Telemetry
All Meshtastic nodes report basic device telemetry:
| Metric | الوصف | Typical Range |
| ------------- | ----------------------------------------------- | ------------------------------------------------------------------ |
| Battery Level | Charge percentage | 0–100% |
| شدة التيار | Battery voltage | 3.0–4.2V (LiPo) |
| ChUtil | % of local airtime in use | 0–100% |
| AirUtil | % of the last hour this node spent transmitting | 0–100% |
| Uptime | Seconds since last boot | Varies |
## Environment Sensors
Supported environmental sensors:
### Temperature & Humidity
| Sensor | الحرارة | الرطوبة | Pressure | Notes |
| ------- | ------- | ------- | -------- | ----------------------- |
| BME280 | ✓ | ✓ | ✓ | Recommended all-in-one |
| BME680 | ✓ | ✓ | ✓ | Adds gas resistance/IAQ |
| SHT31 | ✓ | ✓ | — | High accuracy |
| MCP9808 | ✓ | — | — | Precision temperature |
| LPS22 | — | — | ✓ | Pressure only |
### Air Quality
| Sensor | Metric | Notes |
| -------- | -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| BME680 | Gas Resistance / IAQ | Volatile organic compounds |
| PMSA003I | PM1.0, PM2.5, PM10 | See [Air Quality Metrics](#air-quality-metrics) |
| SEN55 | PM, Temp, Humidity | Multi-sensor. Its NOx and VOC indices are recorded and included in a CSV export, but are not shown as cards or charts |
| SEN6x | PM, CO₂, Temp, Humidity | Its status register is always exported; when it reports a fault (fan, RH&T, gas, CO₂, HCHO or PM), the fault names appear as a Sensor Status card and on each Air Quality log entry |
### Soil
| Metric | Unit | Notes |
| ---------- | ------- | ----------------------------------------------- |
| Soil Temp | °C / °F | Reported alongside soil moisture by soil probes |
| Soil Moist | % | Volumetric water content |
Both appear as info cards on the node detail screen, next to the other environment readings.
Soil probe and water-quality sonde chemistry (pH, conductivity, salinity, NPK, dissolved oxygen, ORP, turbidity, nitrate, ammonium, oxygen demand, solar irradiance) appears as a **Soil & Water** row of info cards on the node detail screen once a node reports any of it, and is included in the node database export. There is no chart or log screen for it yet.
### Light & UV
| Sensor | Metric |
| -------- | -------------------------------------- |
| OPT3001 | Ambient light (lux) |
| VEML7700 | Ambient light (lux) |
| LTR390 | UV index |
### Weather and Other Readings
| Metric | Unit | Where it appears |
| ------------------------------------------------------ | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Wind speed | km/h or mph | Card and chart. Sensors report meters per second; the app converts to match your unit setting, and the chart uses the same unit as the card |
| Wind direction, gust, and lull | degrees, km/h or mph | Listed with each reading on the Environment Metrics screen; not charted |
| Rainfall, last hour and last 24 hours | mm or in | Listed with each reading on the Environment Metrics screen; not charted |
| Lightning, strikes in the last hour and storm distance | count, km or mi | Card and listed with each reading on the Environment Metrics screen; not charted. From an AS3935 detector |
| Radiation | µR/h | Card and chart |
| Weight | kg or lb | Card only — load cells, such as a beehive scale |
| المسافة | mm or in | Card only — water level, from a distance sensor |
| Dew point | °C or °F | Card only — computed from temperature and humidity |
| 1-Wire temperature | °C or °F | Card and chart, up to eight DS18B20-style probes |
| ADC voltage | V | Card and chart, up to eight raw analog channels |
## Power Metrics
Nodes with INA-series power sensors can report:
| Metric | الوصف |
| ---------- | ------------------------------- |
| شدة التيار | Per-channel voltage reading |
| الحالي | Per-channel current draw, in mA |
The node detail screen shows read-only cards for channels 1 to 3. Use the chart button on the **Power Metrics** row to open the chart screen, which lists a chip for every channel that reported data — up to eight — and charts the one you select. Rename a channel there, in the label field under the chips, to something like Solar or Battery. There is no separate wattage reading; the app charts voltage and current, and does not compute power from them.
Useful for monitoring solar charging or battery health on remote nodes.
## Configuring Telemetry
1. Navigate to **Settings → Module configuration → Telemetry**.
2. Each metric group has its own enable toggle and its own interval:
- **Device Metrics** — battery, voltage, uptime, ChUtil, and AirUtil. Its enable toggle, **Send Device Telemetry**, appears only on firmware 2.7.12 and later; on older firmware you can change the interval but not turn the group off
- **Environment Metrics** — temperature, humidity, pressure and the other sensor readings
- **Air Quality Metrics** — particulate and CO₂ readings
- **Power Metrics** — the per-channel voltage and current readings
Environment and Power each have an extra toggle to show their readings on the radio's own
screen, and Environment has one more to show its temperatures there in Fahrenheit.
### Choosing an Interval
These are nominal values, not hard schedules. On a congested mesh the firmware automatically
backs off to longer intervals based on how many nodes are online, so you do not need to
hand-tune them for mesh size. Lengthen them deliberately only to save battery.
## Air Quality Metrics
Nodes with particulate matter or CO₂ sensors report air quality data:
| Metric | Unit | الوصف |
| --------------------- | ----- | ---------------------------- |
| PM1.0 | µg/m³ | Ultrafine particulate matter |
| PM2.5 | µg/m³ | Fine particulate matter |
| PM10 | µg/m³ | Coarse particulate matter |
| CO₂ | ppm | Carbon dioxide concentration |
CO₂ sensors such as the SCD4x also report their own temperature and humidity, which appear alongside the readings above. From PM2.5 history the app additionally derives an **EPA NowCast AQI** value.
The CO₂ reading is color-coded by severity (Good → Stuffy → Poor → Unsafe → Evacuate). See [Node Metrics — Air Quality](node-metrics#air-quality-metrics) for the exact ppm bands, colors, and AQI detail.
Air quality data can be viewed as info cards on the node detail screen, charted over time, and exported to CSV.
## Viewing Telemetry
1. Navigate to **Nodes** and select a node.
2. The **Telemetry** section lists a row for every metric type — Device, Environment, Air Quality, Power, and the rest — whether or not this node has reported it. A row fills in with readings, and grows a chart button, once that node has actually sent that kind of telemetry. An empty row means nothing has arrived yet, not that the sensor is missing.
3. Use the chart button on a row to open that metric's history, where you can pick a time frame and export the readings as CSV.
![Node detail screen with the telemetry chart action menu open](../../assets/screenshots/node-metrics_telemetric_actions.png)
## Troubleshooting
- **No environment data showing?** The remote node needs a physical sensor connected (e.g., BME280 on I2C). Device telemetry (battery, uptime) is always available, but environment metrics require hardware.
- **Stale readings?** Check the reporting interval — very long intervals (7200s+) mean data updates infrequently. Also verify the remote node is still online.
- **Sensor conflict on I2C bus?** Some sensors share I2C addresses. If you have multiple sensors on the same bus, check for address collisions in the radio's serial debug output.
## Related Topics
- [Node Metrics](node-metrics) — view telemetry data on the node detail screen
- [Settings — Modules & Admin](settings-module-admin) — telemetry module configuration
- [Units & Locale](units-and-locale) — temperature and pressure display units
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because it is too large. Load diff
File diff suppressed because one or more lines are too long.
File diff suppressed because it is too large. Load diff
File diff suppressed because one or more lines are too long.
@@ -0,0 +1,3 @@
.site-nav ul li a {
background-image: linear-gradient(-90deg, #e0e1eb 0%, rgba(224, 225, 235, 0.8) 80%, rgba(224, 225, 235, 0) 100%);
}
File diff suppressed because it is too large. Load diff
File diff suppressed because one or more lines are too long.
File diff suppressed because it is too large. Load diff
File diff suppressed because one or more lines are too long.
File diff suppressed because it is too large. Load diff
File diff suppressed because one or more lines are too long.
Binary file not shown.

After

Width:  |  Height:  |  Size: 532 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

+584
View File
@@ -0,0 +1,584 @@
(function (jtd, undefined) {
// Event handling
jtd.addEvent = function(el, type, handler) {
if (el.attachEvent) el.attachEvent('on'+type, handler); else el.addEventListener(type, handler);
}
jtd.removeEvent = function(el, type, handler) {
if (el.detachEvent) el.detachEvent('on'+type, handler); else el.removeEventListener(type, handler);
}
jtd.onReady = function(ready) {
// in case the document is already rendered
if (document.readyState!='loading') ready();
// modern browsers
else if (document.addEventListener) document.addEventListener('DOMContentLoaded', ready);
// IE <= 8
else document.attachEvent('onreadystatechange', function(){
if (document.readyState=='complete') ready();
});
}
// Show/hide mobile menu
function initNav() {
jtd.addEvent(document, 'click', function(e){
var target = e.target;
while (target && !(target.classList && target.classList.contains('nav-list-expander'))) {
target = target.parentNode;
}
if (target) {
e.preventDefault();
target.ariaExpanded = target.parentNode.classList.toggle('active');
}
});
const siteNav = document.getElementById('site-nav');
const mainHeader = document.getElementById('main-header');
const menuButton = document.getElementById('menu-button');
disableHeadStyleSheets();
jtd.addEvent(menuButton, 'click', function(e){
e.preventDefault();
if (menuButton.classList.toggle('nav-open')) {
siteNav.classList.add('nav-open');
mainHeader.classList.add('nav-open');
menuButton.ariaExpanded = true;
} else {
siteNav.classList.remove('nav-open');
mainHeader.classList.remove('nav-open');
menuButton.ariaExpanded = false;
}
});
}
// The <head> element is assumed to include the following stylesheets:
// - a <link> to /assets/css/just-the-docs-head-nav.css,
// with id 'jtd-head-nav-stylesheet'
// - a <style> containing the result of _includes/css/activation.scss.liquid.
// To avoid relying on the order of stylesheets (which can change with HTML
// compression, user-added JavaScript, and other side effects), stylesheets
// are only interacted with via ID
function disableHeadStyleSheets() {
const headNav = document.getElementById('jtd-head-nav-stylesheet');
if (headNav) {
headNav.disabled = true;
}
const activation = document.getElementById('jtd-nav-activation');
if (activation) {
activation.disabled = true;
}
}
// Site search
function initSearch() {
var request = new XMLHttpRequest();
request.open('GET', '/Meshtastic-Android/v2.8.2-open.6/assets/js/search-data.json', true);
request.onload = function(){
if (request.status >= 200 && request.status < 400) {
var docs = JSON.parse(request.responseText);
lunr.tokenizer.separator = /[\s\-/]+/
var index = lunr(function(){
this.ref('id');
this.field('title', { boost: 200 });
this.field('content', { boost: 2 });
this.field('relUrl');
this.metadataWhitelist = ['position']
for (var i in docs) {
this.add({
id: i,
title: docs[i].title,
content: docs[i].content,
relUrl: docs[i].relUrl
});
}
});
searchLoaded(index, docs);
} else {
console.log('Error loading ajax request. Request status:' + request.status);
}
};
request.onerror = function(){
console.log('There was a connection error');
};
request.send();
}
function searchLoaded(index, docs) {
var index = index;
var docs = docs;
var searchInput = document.getElementById('search-input');
var searchResults = document.getElementById('search-results');
var mainHeader = document.getElementById('main-header');
var currentInput;
var currentSearchIndex = 0;
function showSearch() {
document.documentElement.classList.add('search-active');
}
function hideSearch() {
document.documentElement.classList.remove('search-active');
}
function update() {
currentSearchIndex++;
var input = searchInput.value;
if (input === '') {
hideSearch();
} else {
showSearch();
// scroll search input into view, workaround for iOS Safari
window.scroll(0, -1);
setTimeout(function(){ window.scroll(0, 0); }, 0);
}
if (input === currentInput) {
return;
}
currentInput = input;
searchResults.innerHTML = '';
if (input === '') {
return;
}
var results = index.query(function (query) {
var tokens = lunr.tokenizer(input)
query.term(tokens, {
boost: 10
});
query.term(tokens, {
wildcard: lunr.Query.wildcard.TRAILING
});
});
if ((results.length == 0) && (input.length > 2)) {
var tokens = lunr.tokenizer(input).filter(function(token, i) {
return token.str.length < 20;
})
if (tokens.length > 0) {
results = index.query(function (query) {
query.term(tokens, {
editDistance: Math.round(Math.sqrt(input.length / 2 - 1))
});
});
}
}
if (results.length == 0) {
var noResultsDiv = document.createElement('div');
noResultsDiv.classList.add('search-no-result');
noResultsDiv.innerText = 'No results found';
searchResults.appendChild(noResultsDiv);
} else {
var resultsList = document.createElement('ul');
resultsList.classList.add('search-results-list');
searchResults.appendChild(resultsList);
addResults(resultsList, results, 0, 10, 100, currentSearchIndex);
}
function addResults(resultsList, results, start, batchSize, batchMillis, searchIndex) {
if (searchIndex != currentSearchIndex) {
return;
}
for (var i = start; i < (start + batchSize); i++) {
if (i == results.length) {
return;
}
addResult(resultsList, results[i]);
}
setTimeout(function() {
addResults(resultsList, results, start + batchSize, batchSize, batchMillis, searchIndex);
}, batchMillis);
}
function addResult(resultsList, result) {
var doc = docs[result.ref];
var resultsListItem = document.createElement('li');
resultsListItem.classList.add('search-results-list-item');
resultsList.appendChild(resultsListItem);
var resultLink = document.createElement('a');
resultLink.classList.add('search-result');
resultLink.setAttribute('href', doc.url);
resultsListItem.appendChild(resultLink);
var resultTitle = document.createElement('div');
resultTitle.classList.add('search-result-title');
resultLink.appendChild(resultTitle);
// note: the SVG svg-doc is only loaded as a Jekyll include if site.search_enabled is true; see _includes/icons/icons.html
var resultDoc = document.createElement('div');
resultDoc.classList.add('search-result-doc');
resultDoc.innerHTML = '<svg viewBox="0 0 24 24" class="search-result-icon" aria-hidden="true"><use xlink:href="#svg-doc"></use></svg>';
resultTitle.appendChild(resultDoc);
var resultDocTitle = document.createElement('div');
resultDocTitle.classList.add('search-result-doc-title');
resultDocTitle.innerHTML = doc.doc;
resultDoc.appendChild(resultDocTitle);
var resultDocOrSection = resultDocTitle;
if (doc.doc != doc.title) {
resultDoc.classList.add('search-result-doc-parent');
var resultSection = document.createElement('div');
resultSection.classList.add('search-result-section');
resultSection.innerHTML = doc.title;
resultTitle.appendChild(resultSection);
resultDocOrSection = resultSection;
}
var metadata = result.matchData.metadata;
var titlePositions = [];
var contentPositions = [];
for (var j in metadata) {
var meta = metadata[j];
if (meta.title) {
var positions = meta.title.position;
for (var k in positions) {
titlePositions.push(positions[k]);
}
}
if (meta.content) {
var positions = meta.content.position;
for (var k in positions) {
var position = positions[k];
var previewStart = position[0];
var previewEnd = position[0] + position[1];
var ellipsesBefore = true;
var ellipsesAfter = true;
for (var k = 0; k < 5; k++) {
var nextSpace = doc.content.lastIndexOf(' ', previewStart - 2);
var nextDot = doc.content.lastIndexOf('. ', previewStart - 2);
if ((nextDot >= 0) && (nextDot > nextSpace)) {
previewStart = nextDot + 1;
ellipsesBefore = false;
break;
}
if (nextSpace < 0) {
previewStart = 0;
ellipsesBefore = false;
break;
}
previewStart = nextSpace + 1;
}
for (var k = 0; k < 10; k++) {
var nextSpace = doc.content.indexOf(' ', previewEnd + 1);
var nextDot = doc.content.indexOf('. ', previewEnd + 1);
if ((nextDot >= 0) && (nextDot < nextSpace)) {
previewEnd = nextDot;
ellipsesAfter = false;
break;
}
if (nextSpace < 0) {
previewEnd = doc.content.length;
ellipsesAfter = false;
break;
}
previewEnd = nextSpace;
}
contentPositions.push({
highlight: position,
previewStart: previewStart, previewEnd: previewEnd,
ellipsesBefore: ellipsesBefore, ellipsesAfter: ellipsesAfter
});
}
}
}
if (titlePositions.length > 0) {
titlePositions.sort(function(p1, p2){ return p1[0] - p2[0] });
resultDocOrSection.innerHTML = '';
addHighlightedText(resultDocOrSection, doc.title, 0, doc.title.length, titlePositions);
}
if (contentPositions.length > 0) {
contentPositions.sort(function(p1, p2){ return p1.highlight[0] - p2.highlight[0] });
var contentPosition = contentPositions[0];
var previewPosition = {
highlight: [contentPosition.highlight],
previewStart: contentPosition.previewStart, previewEnd: contentPosition.previewEnd,
ellipsesBefore: contentPosition.ellipsesBefore, ellipsesAfter: contentPosition.ellipsesAfter
};
var previewPositions = [previewPosition];
for (var j = 1; j < contentPositions.length; j++) {
contentPosition = contentPositions[j];
if (previewPosition.previewEnd < contentPosition.previewStart) {
previewPosition = {
highlight: [contentPosition.highlight],
previewStart: contentPosition.previewStart, previewEnd: contentPosition.previewEnd,
ellipsesBefore: contentPosition.ellipsesBefore, ellipsesAfter: contentPosition.ellipsesAfter
}
previewPositions.push(previewPosition);
} else {
previewPosition.highlight.push(contentPosition.highlight);
previewPosition.previewEnd = contentPosition.previewEnd;
previewPosition.ellipsesAfter = contentPosition.ellipsesAfter;
}
}
var resultPreviews = document.createElement('div');
resultPreviews.classList.add('search-result-previews');
resultLink.appendChild(resultPreviews);
var content = doc.content;
for (var j = 0; j < Math.min(previewPositions.length, 3); j++) {
var position = previewPositions[j];
var resultPreview = document.createElement('div');
resultPreview.classList.add('search-result-preview');
resultPreviews.appendChild(resultPreview);
if (position.ellipsesBefore) {
resultPreview.appendChild(document.createTextNode('... '));
}
addHighlightedText(resultPreview, content, position.previewStart, position.previewEnd, position.highlight);
if (position.ellipsesAfter) {
resultPreview.appendChild(document.createTextNode(' ...'));
}
}
}
var resultRelUrl = document.createElement('span');
resultRelUrl.classList.add('search-result-rel-url');
resultRelUrl.innerText = doc.relUrl;
resultTitle.appendChild(resultRelUrl);
}
function addHighlightedText(parent, text, start, end, positions) {
var index = start;
for (var i in positions) {
var position = positions[i];
var span = document.createElement('span');
span.innerHTML = text.substring(index, position[0]);
parent.appendChild(span);
index = position[0] + position[1];
var highlight = document.createElement('span');
highlight.classList.add('search-result-highlight');
highlight.innerHTML = text.substring(position[0], index);
parent.appendChild(highlight);
}
var span = document.createElement('span');
span.innerHTML = text.substring(index, end);
parent.appendChild(span);
}
}
jtd.addEvent(searchInput, 'focus', function(){
setTimeout(update, 0);
});
jtd.addEvent(searchInput, 'keyup', function(e){
switch (e.keyCode) {
case 27: // When esc key is pressed, hide the results and clear the field
searchInput.value = '';
break;
case 38: // arrow up
case 40: // arrow down
case 13: // enter
e.preventDefault();
return;
}
update();
});
jtd.addEvent(searchInput, 'keydown', function(e){
switch (e.keyCode) {
case 38: // arrow up
e.preventDefault();
var active = document.querySelector('.search-result.active');
if (active) {
active.classList.remove('active');
if (active.parentElement.previousSibling) {
var previous = active.parentElement.previousSibling.querySelector('.search-result');
previous.classList.add('active');
}
}
return;
case 40: // arrow down
e.preventDefault();
var active = document.querySelector('.search-result.active');
if (active) {
if (active.parentElement.nextSibling) {
var next = active.parentElement.nextSibling.querySelector('.search-result');
active.classList.remove('active');
next.classList.add('active');
}
} else {
var next = document.querySelector('.search-result');
if (next) {
next.classList.add('active');
}
}
return;
case 13: // enter
e.preventDefault();
var active = document.querySelector('.search-result.active');
if (active) {
active.click();
} else {
var first = document.querySelector('.search-result');
if (first) {
first.click();
}
}
return;
}
});
jtd.addEvent(document, 'click', function(e){
if (e.target != searchInput) {
hideSearch();
}
});
}
// Switch theme
jtd.getTheme = function() {
var cssFileHref = document.querySelector('[rel="stylesheet"]').getAttribute('href');
return cssFileHref.substring(cssFileHref.lastIndexOf('-') + 1, cssFileHref.length - 4);
}
jtd.setTheme = function(theme) {
var cssFile = document.querySelector('[rel="stylesheet"]');
cssFile.setAttribute('href', '/Meshtastic-Android/v2.8.2-open.6/assets/css/just-the-docs-' + theme + '.css');
}
// Note: pathname can have a trailing slash on a local jekyll server
// and not have the slash on GitHub Pages
function navLink() {
var pathname = document.location.pathname;
var navLink = document.getElementById('site-nav').querySelector('a[href="' + pathname + '"]');
if (navLink) {
return navLink;
}
// The `permalink` setting may produce navigation links whose `href` ends with `/` or `.html`.
// To find these links when `/` is omitted from or added to pathname, or `.html` is omitted:
if (pathname.endsWith('/') && pathname != '/') {
pathname = pathname.slice(0, -1);
}
if (pathname != '/') {
navLink = document.getElementById('site-nav').querySelector('a[href="' + pathname + '"], a[href="' + pathname + '/"], a[href="' + pathname + '.html"]');
if (navLink) {
return navLink;
}
}
return null; // avoids `undefined`
}
// Scroll site-nav to ensure the link to the current page is visible
function scrollNav() {
const targetLink = navLink();
if (targetLink) {
targetLink.scrollIntoView({ block: "center" });
targetLink.removeAttribute('href');
}
}
// Find the nav-list-link that refers to the current page
// then make it and all enclosing nav-list-item elements active.
function activateNav() {
var target = navLink();
if (target) {
target.classList.toggle('active', true);
}
while (target) {
while (target && !(target.classList && target.classList.contains('nav-list-item'))) {
target = target.parentNode;
}
if (target) {
target.classList.toggle('active', true);
target = target.parentNode;
}
}
}
// Document ready
jtd.onReady(function(){
if (document.getElementById('site-nav')) {
initNav();
activateNav();
scrollNav();
}
initSearch();
});
// Accessibility: set tabindex=0 on each code highlight block, so screenreaders
// can focus over (particularly important if there's horizontal scroll)
// see: https://dequeuniversity.com/rules/axe/4.9/scrollable-region-focusable?application=axeAPI
jtd.onReady(() => {
document
.querySelectorAll("div.highlight")
.forEach(codeBlock => codeBlock.setAttribute("tabindex", "0"));
});
// Copy button on code
jtd.onReady(function(){
if (!window.isSecureContext) {
console.log('Window does not have a secure context, therefore code clipboard copy functionality will not be available. For more details see https://web.dev/async-clipboard/#security-and-permissions');
return;
}
var codeBlocks = document.querySelectorAll('div.highlighter-rouge, div.listingblock > div.content, figure.highlight');
// note: the SVG svg-copied and svg-copy is only loaded as a Jekyll include if site.enable_copy_code_button is true; see _includes/icons/icons.html
var svgCopied = '<svg viewBox="0 0 24 24" class="copy-icon"><use xlink:href="#svg-copied"></use></svg>';
var svgCopy = '<svg viewBox="0 0 24 24" class="copy-icon"><use xlink:href="#svg-copy"></use></svg>';
codeBlocks.forEach(codeBlock => {
var copyButton = document.createElement('button');
var timeout = null;
copyButton.type = 'button';
copyButton.ariaLabel = 'Copy code to clipboard';
copyButton.innerHTML = svgCopy;
codeBlock.append(copyButton);
copyButton.addEventListener('click', function () {
if(timeout === null) {
var code = (codeBlock.querySelector('pre:not(.lineno, .highlight)') || codeBlock.querySelector('code')).innerText;
window.navigator.clipboard.writeText(code);
copyButton.innerHTML = svgCopied;
var timeoutSetting = 4000;
timeout = setTimeout(function () {
copyButton.innerHTML = svgCopy;
timeout = null;
}, timeoutSetting);
}
});
});
});
})(window.jtd = window.jtd || {});
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
@@ -0,0 +1,69 @@
# Screenshots
This directory is the **single source of truth** for screenshot assets referenced by the
documentation pages. It is consumed by both:
- the **Jekyll docs site** (markdown references `../../assets/screenshots/{name}.png`), and
- the **in-app docs browser** — `:feature:docs:syncDocsToComposeResources` bundles this
directory into compose resources at `files/docs/assets/screenshots/`.
`DocImageWiringTest` (in `:feature:docs`) fails the build if a doc page references an image
that is not present here.
## Two source modules
Doc screenshots come from Compose Preview Screenshot Testing references in **two** modules:
- **`:screenshot-tests`** — visual-regression gate (CI runs `:screenshot-tests:validateDebugScreenshotTest`).
Holds atomic, dual-purpose components (signal/battery/hops info, list items, preference widgets,
alerts) that are both regression-checked **and** used as doc images. Don't reframe these for docs.
- **`:docs-screenshots`** — generate-only, **not** gated in CI. Holds doc-framed compositions whose
framing is tuned for the docs site (e.g. the firmware status crops, the connections BLE-scan /
empty-state crops). Reframing one here never churns the regression gate.
`copyDocsScreenshots` (in `:screenshot-tests`) aggregates the reference images from **both** modules.
## Updating Screenshots
After changing a UI component, regenerate references for whichever module owns the wrapper, then copy:
```bash
./gradlew :screenshot-tests:updateDebugScreenshotTest # regression references
./gradlew :docs-screenshots:updateDebugScreenshotTest # doc-framed composition references
./gradlew :screenshot-tests:copyDocsScreenshots # refresh this directory from both
```
`copyDocsScreenshots` copies **only** the light-mode reference images that have a semantic
alias in `screenshot-tests/docs-screenshot-aliases.properties`, renaming them on the way.
Commit the refreshed PNGs together with the reference-image changes.
## Adding a Screenshot for a New Doc Page
1. Add (or reuse) a `Preview*`/`*Preview` composable with representative mock data in the
feature module. Add a `Screenshot*` wrapper: in **`:docs-screenshots`** if it's a doc-framed
composition (full screen / doc-specific crop), or in **`:screenshot-tests`** if it's an atomic
component you also want regression-gated. If the component renders timestamps, give it a
`timeTextOverride`-style parameter so renders stay deterministic across machines.
2. Make sure the test class is covered by a pattern in `screenshot-tests/docs-screenshots-manifest.txt`
(the patterns are `**/{Class}Kt/...`, so they match in either module).
3. Map the semantic name in `screenshot-tests/docs-screenshot-aliases.properties`:
`{page-id}_{description}.png=Screenshot{Name}_Light_{hash}_0.png`
4. Run the relevant update task(s) + `copyDocsScreenshots`, then reference the image from the doc page.
## Naming Convention
```
{page-id}_{description}.png
```
Examples: `onboarding_welcome.png`, `connections_bluetooth_scan.png`, `discovery_preset_result.png`.
## Guidelines
- PNG format, light-mode only (dark variants live in the reference directory)
- Name screenshots to match the docs page they appear in
- Keep filenames lowercase with underscores
- Prefer CST-generated screenshots — they render real app composables, so they cannot drift from
reality. Avoid hand-pasted captures: a stray screenshot from another app slipped in this way
before (the old `connections_wifi_*.png` were from an unrelated WiFi-provisioning app, not
Meshtastic). If a manual capture is unavoidable, it must be a genuine Meshtastic-Android screen.
Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
Loaded 100 of 1840 files, more files were not shown because too many files have changed in this diff. Show more