This is a medium-ish refactor to attempt to clean up sensor handling logic both for board stability and future potential growth before the code becomes all spaghetti and meatballs. I'd be curious to see if anyone running sensors out there that knows how to build and flash MeshCore code could give this a try and see how it behaves. It is working fine on my end on multiple nodes. PR notes are gigantic because it is a fundamental behavior repair for sensors, so I wanted to over-explain. Also, if it hadn't been mentioned previously, push-back is always welcome. I'm just spending my time trying to clean up / fix / enhance this corner of the firmware, and want to contribute my improvements back to the project. **Problem:** Current MeshCore code makes no attempt to see what sensors are actually available on the I2C bus at startup and blindly tries to interact with sensors. This has some very bad side-effects, like if a sensor that is unsupported, or has a weird initialization process, the MeshCore node will just hang at boot and never successfully start up and ostensibly looks bricked, or the INA226 and SHT4X both sharing the same address and the code just silently fighting. The current implementation also gloms sensor readouts from the MCU and environment sensors onto the same telemetry channel, with some arbitrary exceptions for incrementing channels based on certain behavioral situations. The MCU temperature and external temperature sensors would appear on channel 1, and it wouldn't be possible to tell which sensor the temperature value was coming from. Per [CayenneLPP](https://github.com/myDevicesIoT/CayenneLPP): *Data Channel: Uniquely identifies each sensor in the device across frames, eg. “indoor sensor”* So this channel division implementation falls inline with what CayenneLPP intended. There are up to 256 channels available. So I tried to model this change in that behavioral style. **Proposed Improvement:** This implementation scans the I2C bus for what devices are present, sets each sensor to its own CayenneLPP channel, and keeps MCU telemetry on channel 1 only. So Channel 1 is always "self" and no confusion can result. Details: - Channel 1 is always the MCU and things about it, so you always know that telemetry is from the board itself. Exception is GPS, GPS stays on channel 1 as well since it is "about the board" even though it's a bit gray-area as GPS can often be a secondary chip. - Each sensor board is allocated to a dedicated CayenneLPP channel, so if you are reading from that channel, you know the data is from that sensor only. (Sensors emitting more than one of the same type of measurement are exceptions.) - `scanI2CBus()` probes addresses 0x08–0x77 with raw `beginTransmission`/`endTransmission`. No sensor library is touched until after this completes. This will prevent sensor-based boot hangs, unknown or unresponsive devices never reach a library init call. - Created `SENSOR_TABLE` a compile-time array that is gated by the existing `ENV_INCLUDE_*` macros. A sentinel `{ 0, nullptr, nullptr, nullptr }` at the end keeps the array non-empty regardless of which sensors are enabled, avoiding zero-length array warnings. - When `begin()` is called, scan first, then loop: skip if address not detected, skip if `init()` returns 0, otherwise register one ActiveSensor entry per sub-channel. - `querySensors()` I replaced the entire #ifdef chain with a 3-line loop. - T1000-E has its own T1000SensorManager, so it should be completely unaffected by this change. - SHT4X quirky initialization behavior is retained. - MLX90614 - git commits around this didn't have any notes as to why it is reporting ambient temperature on a separate channel as well as the object temperature, as the ambient temperature is used internally to compute the object temperature and not really needed for the sensor's purpose - just the same, kept the existing behavior of reporting the ambient temperature one channel above the channel assigned to the sensor - All `bool *_initialized` fields are gone, replaced with `ActiveSensor _active_sensors[16]` (query function pointer and sub-channel index) and `_active_sensor_count. SensorDef` lives entirely in the `.cpp` so the header has no dependency on it. - Details on the INA226 and SHT4X: both default to address 0x44, the old code had a bug and would have both begin() calls fire and they would just fight each other silently. In the new code, the respective sensor code is only called if the device is actually present, however, if both were present simultaneously, SHT4X comes first in the table and would win, and INA226 would return false and be skipped. The INA226 has 16 possible addresses that are configurable in the hardware itself, so in a potential scenario where both sensors would be present, the person implementing that design could take that into account. - BME680 gas resistance will now transmit on the same channel as the rest of BME680 telemetry which is inline with CayenneLPP standards. Coupling this PR with https://github.com/meshcore-dev/MeshCore/pull/2146 streamline the whole sensor telemetry, and with https://github.com/meshcore-dev/MeshCore/pull/2149 will overall improve BME680 handling. The gas resistance sensor actually has a binary library to make it more useful, calibration, accounting for age of sensor, and other improvements, but since that adds more flash consumption, I have omitted that in PRs thus far. - RAK12035 and other current upstream dev branch changes integrated.
About MeshCore
MeshCore is a lightweight, portable C++ library that enables multi-hop packet routing for embedded projects using LoRa and other packet radios. It is designed for developers who want to create resilient, decentralized communication networks that work without the internet.
🔍 What is MeshCore?
MeshCore now supports a range of LoRa devices, allowing for easy flashing without the need to compile firmware manually. Users can flash a pre-built binary using tools like Adafruit ESPTool and interact with the network through a serial console. MeshCore provides the ability to create wireless mesh networks, similar to Meshtastic and Reticulum but with a focus on lightweight multi-hop packet routing for embedded projects. Unlike Meshtastic, which is tailored for casual LoRa communication, or Reticulum, which offers advanced networking, MeshCore balances simplicity with scalability, making it ideal for custom embedded solutions., where devices (nodes) can communicate over long distances by relaying messages through intermediate nodes. This is especially useful in off-grid, emergency, or tactical situations where traditional communication infrastructure is unavailable.
⚡ Key Features
- Multi-Hop Packet Routing
- Devices can forward messages across multiple nodes, extending range beyond a single radio's reach.
- Supports up to a configurable number of hops to balance network efficiency and prevent excessive traffic.
- Nodes use fixed roles where "Companion" nodes are not repeating messages at all to prevent adverse routing paths from being used.
- Supports LoRa Radios – Works with Heltec, RAK Wireless, and other LoRa-based hardware.
- Decentralized & Resilient – No central server or internet required; the network is self-healing.
- Low Power Consumption – Ideal for battery-powered or solar-powered devices.
- Simple to Deploy – Pre-built example applications make it easy to get started.
🎯 What Can You Use MeshCore For?
- Off-Grid Communication: Stay connected even in remote areas.
- Emergency Response & Disaster Recovery: Set up instant networks where infrastructure is down.
- Outdoor Activities: Hiking, camping, and adventure racing communication.
- Tactical & Security Applications: Military, law enforcement, and private security use cases.
- IoT & Sensor Networks: Collect data from remote sensors and relay it back to a central location.
🚀 How to Get Started
- Watch the MeshCore Intro Video by Andy Kirby.
- Read through our Frequently Asked Questions section.
- Flash the MeshCore firmware on a supported device.
- Connect with a supported client.
For developers;
- Install PlatformIO in Visual Studio Code.
- Clone and open the MeshCore repository in Visual Studio Code.
- See the example applications you can modify and run:
- Companion Radio - For use with an external chat app, over BLE, USB or WiFi.
- KISS Modem - Serial KISS protocol bridge for host applications. (protocol docs)
- Simple Repeater - Extends network coverage by relaying messages.
- Simple Room Server - A simple BBS server for shared Posts.
- Simple Secure Chat - Secure terminal based text communication between devices.
- Simple Sensor - Remote sensor node with telemetry and alerting.
The Simple Secure Chat example can be interacted with through the Serial Monitor in Visual Studio Code, or with a Serial USB Terminal on Android.
⚡️ MeshCore Flasher
We have prebuilt firmware ready to flash on supported devices.
- Launch https://flasher.meshcore.co.uk
- Select a supported device
- Flash one of the firmware types:
- Companion, Repeater or Room Server
- Once flashing is complete, you can connect with one of the MeshCore clients below.
📱 MeshCore Clients
Companion Firmware
The companion firmware can be connected to via BLE, USB or WiFi depending on the firmware type you flashed.
- Web: https://app.meshcore.nz
- Android: https://play.google.com/store/apps/details?id=com.liamcottle.meshcore.android
- iOS: https://apps.apple.com/us/app/meshcore/id6742354151?platform=iphone
- NodeJS: https://github.com/liamcottle/meshcore.js
- Python: https://github.com/fdlamotte/meshcore-cli
Repeater and Room Server Firmware
The repeater and room server firmwares can be setup via USB in the web config tool.
They can also be managed via LoRa in the mobile app by using the Remote Management feature.
🛠 Hardware Compatibility
MeshCore is designed for devices listed in the MeshCore Flasher
📜 License
MeshCore is open-source software released under the MIT License. You are free to use, modify, and distribute it for personal and commercial projects.
Contributing
Please submit PR's using 'dev' as the base branch! For minor changes just submit your PR and we'll try to review it, but for anything more 'impactful' please open an Issue first and start a discussion. Is better to sound out what it is you want to achieve first, and try to come to a consensus on what the best approach is, especially when it impacts the structure or architecture of this codebase.
Here are some general principals you should try to adhere to:
- Keep it simple. Please, don't think like a high-level lang programmer. Think embedded, and keep code concise, without any unnecessary layers.
- No dynamic memory allocation, except during setup/begin functions.
- Use the same brace and indenting style that's in the core source modules. (A .clang-format is prob going to be added soon, but please do NOT retroactively re-format existing code. This just creates unnecessary diffs that make finding problems harder)
Help us prioritize! Please react with thumbs-up to issues/PRs you care about most. We look at reaction counts when planning work.
Road-Map / To-Do
There are a number of fairly major features in the pipeline, with no particular time-frames attached yet. In very rough chronological order:
- Companion radio: UI redesign
- Repeater + Room Server: add ACL's (like Sensor Node has)
- Standardise Bridge mode for repeaters
- Repeater/Bridge: Standardise the Transport Codes for zoning/filtering
- Core + Repeater: enhanced zero-hop neighbour discovery
- Core: round-trip manual path support
- Companion + Apps: support for multiple sub-meshes (and 'off-grid' client repeat mode)
- Core + Apps: support for LZW message compression
- Core: dynamic CR (Coding Rate) for weak vs strong hops
- Core: new framework for hosting multiple virtual nodes on one physical device
- V2 protocol spec: discussion and consensus around V2 packet protocol, including path hashes, new encryption specs, etc
📞 Get Support
- Report bugs and request features on the GitHub Issues page.
- Find additional guides and components on my site.
- Join MeshCore Discord to chat with the developers and get help from the community.