This library provides a framework for interfacing with XBee modules using an XBee Library API that abstracts AT commands and API frames. It supports multiple platforms including Arduino, Unix, STM32, RP2350, and EFM32.
- XBee LR (LoRaWAN)
- More coming soon..
- src: Contains the core source files implementing XBee classes and APIs.
- include: Contains header files exposing the library's API.
- examples: Contains example implementations for various platforms.
- test: Contains unit tests for validating the functionality of the library.
- Choose the example that matches your platform (e.g., Unix, STM32).
- Compile the example using your platform's toolchain.
- Run the compiled binary to see the XBee communication in action.
- Navigate to the
test
directory. - Compile the test files using your platform's toolchain.
- Run the compiled binary to execute the unit tests.
- Extend the XBee API, API Frames, and AT commands files as needed for the new subclass.
- Add the necessary support functions in the source files.
- Update the examples to demonstrate the new functionality.
- xbee.c: Implements the XBee class
- xbee_at_cmds.c: Implements functions for sending and receiving AT commands.
- xbee_lr.c: Implements XBee LR module subclass.
- xbee_api_frames.c: Implements parsing and handling of API frames.
The library is designed to be modular, allowing easy expansion and support for different XBee modules and platforms. The main components include:
- XBee Core: Handles core XBee Class and API layer.
- AT Commands: Manages AT command interface.
- API Frames: Handles API frame communication.
See MIT-LICENSE.txt file
This section provides an overview of the key methods available in the XBee class, which are used to interact with XBee modules.
XBeeInit()
: Initializes the XBee module.XBeeConnect()
: Connects the XBee module to a network.XBeeDisconnect()
: Disconnects the XBee module from the network.XBeeSendData()
: Sends data through the XBee module.XBeeSoftReset()
: Performs a soft reset on the XBee module.XBeeHardReset()
: Performs a hard reset on the XBee module.XBeeProcess()
: Processes incoming and outgoing data for the XBee module.XBeeConnected()
: Checks if the XBee module is connected to a network.XBeeWriteConfig()
: Writes configuration settings to the XBee module.XBeeApplyChanges()
: Applies changes to the configuration of the XBee module.XBeeLRSetApiOptions()
: Sets API options for long-range communication.
Each of these methods provides essential functionality for managing and communicating with XBee devices within a network. Ensure that you refer to these methods when developing applications that involve XBee modules.
This section provides an example of how to use the XBee class, specifically for the XBee LR (LoRaWAN) module. The example covers creating an instance of the XBee LR class, initializing the module, setting up the hardware and command tables, configuring network settings, connecting to a network, sending data, and handling received data.
To create an instance of the XBee LR class, you need to pass the hardware and command tables to the XBeeLRCreate
function:
#include "xbee_lr.h"
#include "port.h"
// Define a pointer for the XBee LR instance
XBeeLR* myXbeeLr;
// Hardware Abstraction Function Pointer Table for XBeeLR
const XBeeHTable XBeeLRHTable = {
.PortUartRead = portUartRead,
.PortUartWrite = portUartWrite,
.PortMillis = portMillis,
.PortFlushRx = portFlushRx,
.PortUartInit = portUartInit,
.PortDelay = portDelay,
};
// Callback Function Pointer Table for XBeeLR
const XBeeCTable XBeeLRCTable = {
.OnReceiveCallback = OnReceiveCallback, // Callback for receiving data
.OnSendCallback = NULL, // Can be left as NULL if no callbacks are needed
};
int main() {
// Create the XBee LR instance with hardware and command tables
myXbeeLr = XBeeLRCreate(&XBeeLRHTable, &XBeeLRCTable);
if (myXbeeLr == NULL) {
printf("Failed to create XBee LR instance.");
return -1;
}
// Proceed with initialization and connection...
}
After creating the XBee LR instance, initialize the XBee LR module, configure the network settings, and connect to the network:
// Initialize the XBee LR module
if (!XBeeLRInit(myXbeeLr, 9600, "COM3")) { // Replace "COM3" with the appropriate serial port for your platform
printf("Failed to initialize XBee LR module.
");
return -1;
}
// Read LoRaWAN DevEUI and print it
uint8_t devEui[17];
XBeeLRGetDevEUI((XBee*)myXbeeLr, devEui, sizeof(devEui));
portDebugPrintf("DEVEUI: %s", devEui);
// Set LoRaWAN Network Settings
portDebugPrintf("Configuring...");
XBeeLRSetAppEUI((XBee*)myXbeeLr, "37D56A3F6CDCF0A5");
XBeeLRSetAppKey((XBee*)myXbeeLr, "CD32AAB41C54175E9060D86F3A8B7F48");
XBeeLRSetNwkKey((XBee*)myXbeeLr, "CD32AAB41C54175E9060D86F3A8B7F48");
XBeeWriteConfig((XBee*)myXbeeLr);
XBeeApplyChanges((XBee*)myXbeeLr);
// Connect to the LoRaWAN network
portDebugPrintf("Connecting...");
XBeeConnect((XBee*)myXbeeLr);
printf("XBee LR module initialized and connected.");
// Proceed with other operations...
To send data over the XBee LR network, use the XBeeLRSendData
method. Here's an example of preparing and sending a payload:
// XBeeLR payload to send
uint8_t examplePayload[] = {0xC0, 0xC0, 0xC0, 0xFF, 0xEE};
uint16_t payloadLen = sizeof(examplePayload) / sizeof(examplePayload[0]);
XBeeLRPacket_t packet = {
.payload = examplePayload,
.payloadSize = payloadLen,
.port = 2,
.ack = 0,
};
if (!XBeeSendData(myXbeeLr, &packet)) {
printf("Failed to send data.");
} else {
printf("Data sent successfully.");
}
Handle data received from the XBee LR module using the OnReceiveCallback
function:
void OnReceiveCallback(XBee* self, void* data){
XBeeLRPacket_t* packet = (XBeeLRPacket_t*) data;
portDebugPrintf("Received Packet: ");
for (int i = 1; i < packet->payloadSize; i++) {
portDebugPrintf("%02X ", packet->payload[i]);
}
portDebugPrintf("");
portDebugPrintf("Ack %u", packet->ack);
portDebugPrintf("Port %u", packet->port);
portDebugPrintf("RSSI %d", packet->rssi);
portDebugPrintf("SNR %d", packet->snr);
portDebugPrintf("Downlink Counter %lu", packet->counter);
}
// Assuming a continuous loop to process incoming data
while (1) {
XBeeProcess(myXbeeLr);
usleep(10000); // Sleep for 10ms to avoid busy-waiting
}
After completing your operations, disconnect the XBee LR module, clean up resources, and free the XBee LR instance:
XBeeDisconnect(myXbeeLr);
XBeeClose(myXbeeLr);
XBeeDestroy(myXbeeLr); // Free the XBee LR instance
printf("XBee LR module disconnected and resources cleaned up.");
Here is the full example code combining the steps mentioned above:
#include "xbee_lr.h"
#include "port.h"
XBeeLR* myXbeeLr;
// Hardware Abstraction Function Pointer Table for XBeeLR
const XBeeHTable XBeeLRHTable = {
.PortUartRead = portUartRead,
.PortUartWrite = portUartWrite,
.PortMillis = portMillis,
.PortFlushRx = portFlushRx,
.PortUartInit = portUartInit,
.PortDelay = portDelay,
};
// Callback Function Pointer Table for XBeeLR
const XBeeCTable XBeeLRCTable = {
.OnReceiveCallback = OnReceiveCallback, // Callback for receiving data
.OnSendCallback = NULL, // Can be left as NULL if no callbacks are needed
};
void OnReceiveCallback(XBee* self, void* data){
XBeeLRPacket_t* packet = (XBeeLRPacket_t*) data;
portDebugPrintf("Received Packet: ");
for (int i = 1; i < packet->payloadSize; i++) {
portDebugPrintf("%02X ", packet->payload[i]);
}
portDebugPrintf("");
portDebugPrintf("Ack %u", packet->ack);
portDebugPrintf("Port %u", packet->port);
portDebugPrintf("RSSI %d", packet->rssi);
portDebugPrintf("SNR %d", packet->snr);
portDebugPrintf("Downlink Counter %lu", packet->counter);
}
int main() {
// Create the XBee LR instance with hardware and command tables
myXbeeLr = XBeeLRCreate(&XBeeLRHTable, &XBeeLRCTable);
if (myXbeeLr == NULL) {
printf("Failed to create XBee LR instance.");
return -1;
}
// Initialize the XBee LR module
if (!XBeeLRInit(myXbeeLr, 9600, "COM3")) { // Replace "COM3" with the appropriate serial port
printf("Failed to initialize XBee LR module.");
return -1;
}
// Read LoRaWAN DevEUI and print it
uint8_t devEui[17];
XBeeLRGetDevEUI((XBee*)myXbeeLr, devEui, sizeof(devEui));
portDebugPrintf("DEVEUI: %s", devEui);
// Set LoRaWAN Network Settings
portDebugPrintf("Configuring...");
XBeeLRSetAppEUI((XBee*)myXbeeLr, "37D56A3F6CDCF0A5");
XBeeLRSetAppKey((XBee*)myXbeeLr, "CD32AAB41C54175E9060D86F3A8B7F48");
XBeeLRSetNwkKey((XBee*)myXbeeLr, "CD32AAB41C54175E9060D86F3A8B7F48");
XBeeWriteConfig((XBee*)myXbeeLr);
XBeeApplyChanges((XBee*)myXbeeLr);
// Connect to the LoRaWAN network
portDebugPrintf("Connecting...");
XBeeConnect((XBee*)myXbeeLr);
uint8_t examplePayload[] = {0xC0, 0xC0, 0xC0, 0xFF, 0xEE};
uint16_t payloadLen = sizeof(examplePayload) / sizeof(examplePayload[0]);
XBeeLRPacket_t packet = {
.payload = examplePayload,
.payloadSize = payloadLen,
.port = 2,
.ack = 0,
};
if (!XBeeSendData(myXbeeLr, &packet)) {
printf("Failed to send data.");
} else {
printf("Data sent successfully.");
}
// Main loop to process incoming data
while (1) {
XBeeProcess(myXbeeLr);
usleep(10000); // Sleep for 10ms
}
XBeeDisconnect(myXbeeLr);
XBeeClose(myXbeeLr);
XBeeDestroy(myXbeeLr); // Free the XBee LR instance
printf("XBee LR module disconnected and resources cleaned up.");
return 0;
}
Porting the XBee C library to other platforms involves adapting the code to the specific hardware and software environment of the new platform. The following steps outline the general process for porting the library:
Before starting the porting process, gather detailed information about the target platform, including:
- CPU architecture
- Operating system (if any)
- Available compilers and development tools
- Peripheral interfaces (UART, SPI, I2C, etc.)
Set up a development environment compatible with the target platform. This includes:
- Installing the appropriate compiler and toolchain
- Configuring makefiles or build scripts for the target platform
The XBee library uses a HAL to interact with hardware peripherals. Modify the HAL implementation to match the target platform's peripherals:
- Update UART initialization and configuration
- Adjust GPIO settings if needed
- Implement any additional platform-specific peripheral control functions
Modify the compiler and linker settings to match the target platform:
- Set the correct optimization flags for the target CPU
- Adjust memory settings and linker scripts if required
Thoroughly test the ported library on the target platform:
- Verify basic communication with the XBee module
- Test all major library functions
- Debug any issues that arise during testing
After successful porting and testing, consider optimizing the library for performance on the target platform:
- Optimize critical sections of code for speed
- Reduce memory usage if necessary
- Take advantage of platform-specific features (e.g., DMA for UART transfers)
Finally, document any changes made during the porting process and provide instructions for others who may need to port the library to similar platforms.
If you want to add support for a new XBee module, you will need to create a new subclass that extends the functionality of the base XBee class. This guide will walk you through the steps to add a new subclass and integrate it into the existing XBee framework.
Start by creating a new header and source file for your subclass. Let's assume your new XBee module is called XBeeNew
. Create xbee_new.h
and xbee_new.c
.
In xbee_new.h
, define the subclass structure:
#ifndef XBEE_NEW_H
#define XBEE_NEW_H
#include "xbee.h"
typedef struct {
XBee base; /**< Base class structure. */
// Add any additional members specific to XBeeNew
} XBeeNew;
// Function prototypes for the subclass
XBeeNew* XBeeNewCreate(const XBeeCTable* cTable, const XBeeHTable* hTable);
void XBeeNewDestroy(XBeeNew* self);
#endif // XBEE_NEW_H
In xbee_new.c
, implement the methods specific to your new XBee module. This will include the creation and destruction methods, as well as any specific behaviors your module requires.
#include "xbee_new.h"
#include <stdlib.h>
static bool XBeeNewInit(XBee* self, uint32_t baudrate, const char* device);
static void XBeeNewProcess(XBee* self);
static bool XBeeNewConnect(XBee* self);
static bool XBeeNewDisconnect(XBee* self);
static bool XBeeNewSendData(XBee* self, const void* data);
static bool XBeeNewConnected(XBee* self);
static void XBeeNewHandleRxPacket(XBee* self, void* param);
static void XBeeNewHandleTransmitStatus(XBee* self, void* param);
// Define the VTable for the new XBee module
static const XBeeVTable XBeeNew_VTable = {
.init = XBeeNewInit,
.process = XBeeNewProcess,
.connect = XBeeNew_Connect,
.disconnect = XBeeewDisconnect,
.sendData = XBeeNewSendData,
.softReset = XBeeSoftReset,
.hardReset = XBeeHardReset,
.connected = XBeeNewConnected,
.handleRxPacketFrame = XBeeNewHandleRxPacket,
.handleTransmitStatusFrame = XBeeNewHandleTransmitStatus,
};
// Implement the create and destroy methods
XBeeNew* XBeeNewCreate(const XBeeCTable* cTable, const XBeeHTable* hTable) {
XBeeNew* instance = (XBeeNew*)malloc(sizeof(XBeeNew));
instance->base.vtable = &XBeeNew_VTable;
instance->base.htable = hTable;
instance->base.ctable = cTable;
return instance;
}
void XBeeNewDestroy(XBeeNew* self) {
free(self);
}
// Implement the specific methods for the new XBee module
static bool XBeeNewInit(XBee* self, uint32_t baudrate, const char* device) {
// Implement initialization logic specific to XBeeNew
return true;
}
static void XBeeNewProcess(XBee* self) {
// Implement process logic specific to XBeeNew
}
static bool XBeeNewConnect(XBee* self) {
// Implement connection logic specific to XBeeNew
return true;
}
static bool XBeeNewDisconnect(XBee* self) {
// Implement disconnection logic specific to XBeeNew
return true;
}
static bool XBeeNewSendData(XBee* self, const void* data) {
// Implement data sending logic specific to XBeeNew
return true;
}
static bool XBeeNewConnected(XBee* self) {
// Implement logic to check if XBeeNew is connected
return true;
}
static void XBeeNewHandleRxPacket(XBee* self, void* param) {
// Implement logic to handle received packets specific to XBeeNew
}
static void XBeeNewHandleTransmitStatus(XBee* self, void* param) {
// Implement logic to handle transmit status specific to XBeeNew
}
After defining and implementing your subclass, you can use it in your application by creating an instance of XBeeNew
and using it like any other XBee module. See various example code.
Depending on the specific features and requirements of your new XBee module, you may need to add or modify methods and behaviors in the subclass. The provided structure and functions offer a flexible framework that can be easily extended to support additional features.
Thank you for considering contributing to our project! To ensure a smooth and consistent development process, please follow these guidelines when contributing:
- Google C++ Style Guide: Our codebase follows the Google C++ Style Guide. Please ensure your contributions adhere to these guidelines.
- Naming Conventions:
- Functions and Variables: Use
lowerCamelCase
for function and variable names. - Class and Struct Names: Use
UpperCamelCase
. - Macros: Use
UPPER_SNAKE_CASE
. - Enum Names: Enum type names should use
UpperCamelCase
, and enum values should usekUpperCamelCase
.
- Functions and Variables: Use
- File Names:
- All file names should be in lowercase with words separated by underscores (
_
). - Use
.h
for header files and.c
for C implementation files.
- All file names should be in lowercase with words separated by underscores (
- Indentation: Use 2 spaces for indentation. Do not use tabs.
- Naming Conventions:
- C Standard: This project adheres to the C99 standard. Ensure your code is compatible with C99 to maintain consistency and compatibility across the codebase.
- Use
stdint.h
for fixed-width integer types. - Avoid using non-standard extensions or compiler-specific features unless absolutely necessary.
- Use
When developing for embedded systems, it's important to be mindful of the resource constraints inherent in such environments:
- Memory Usage: Embedded devices typically have limited memory (both RAM and ROM). Avoid using large libraries or functions that require significant memory. For example:
- Avoid
sprintf
: Instead ofsprintf
, consider using more memory-efficient alternatives likesnprintf
or custom formatting functions. - Avoid Dynamic Memory Allocation: Minimize or avoid the use of dynamic memory allocation (e.g.,
malloc
,free
) to prevent memory fragmentation and leaks.
- Avoid
- Performance: Optimize for low power consumption and execution speed. This includes minimizing CPU cycles, reducing the complexity of algorithms, and leveraging hardware-specific features where possible.
- Code Size: Keep the code size small to fit within the limited flash memory. This may involve stripping out unnecessary features or using compiler optimization flags specific to size reduction.
- Fork the Repository: Start by forking the repository to your own GitHub account.
- Create a Branch: Create a new branch for your feature or bug fix.
- Use descriptive branch names (e.g.,
feature/add-new-api
orbugfix/fix-crash
).
- Use descriptive branch names (e.g.,
- Make Your Changes: Implement your changes in the codebase, ensuring they adhere to the coding style guidelines mentioned above.
- Test Your Changes: Thoroughly test your code to ensure it functions correctly and does not introduce any regressions.
- Commit and Push: Commit your changes with clear and concise commit messages, then push them to your forked repository.
- Create a Pull Request: Open a pull request against the main repository's
main
branch. Include a detailed description of your changes and any relevant issue numbers.
- Unit Tests: If your changes introduce new functionality, please include corresponding unit tests to verify the behavior.
- Documentation: Update the documentation if your changes affect the public API or usage instructions.
- Your pull request will be reviewed by one or more maintainers. Please be responsive to feedback and make any necessary changes promptly.
- By contributing to this project, you agree that your contributions will be licensed under the project's existing license.
We appreciate your contributions and thank you for helping to improve this project!