Initial commit of PlatformIO project structure and code
This commit is contained in:
5
Lora Transmitter type 01/.gitignore
vendored
Normal file
5
Lora Transmitter type 01/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
10
Lora Transmitter type 01/.vscode/extensions.json
vendored
Normal file
10
Lora Transmitter type 01/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"platformio.platformio-ide"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
"ms-vscode.cpptools-extension-pack"
|
||||
]
|
||||
}
|
||||
37
Lora Transmitter type 01/include/README
Normal file
37
Lora Transmitter type 01/include/README
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
This directory is intended for project header files.
|
||||
|
||||
A header file is a file containing C declarations and macro definitions
|
||||
to be shared between several project source files. You request the use of a
|
||||
header file in your project source file (C, C++, etc) located in `src` folder
|
||||
by including it, with the C preprocessing directive `#include'.
|
||||
|
||||
```src/main.c
|
||||
|
||||
#include "header.h"
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Including a header file produces the same results as copying the header file
|
||||
into each source file that needs it. Such copying would be time-consuming
|
||||
and error-prone. With a header file, the related declarations appear
|
||||
in only one place. If they need to be changed, they can be changed in one
|
||||
place, and programs that include the header file will automatically use the
|
||||
new version when next recompiled. The header file eliminates the labor of
|
||||
finding and changing all the copies as well as the risk that a failure to
|
||||
find one copy will result in inconsistencies within a program.
|
||||
|
||||
In C, the convention is to give header files names that end with `.h'.
|
||||
|
||||
Read more about using header files in official GCC documentation:
|
||||
|
||||
* Include Syntax
|
||||
* Include Operation
|
||||
* Once-Only Headers
|
||||
* Computed Includes
|
||||
|
||||
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
||||
46
Lora Transmitter type 01/lib/README
Normal file
46
Lora Transmitter type 01/lib/README
Normal file
@@ -0,0 +1,46 @@
|
||||
|
||||
This directory is intended for project specific (private) libraries.
|
||||
PlatformIO will compile them to static libraries and link into the executable file.
|
||||
|
||||
The source code of each library should be placed in a separate directory
|
||||
("lib/your_library_name/[Code]").
|
||||
|
||||
For example, see the structure of the following example libraries `Foo` and `Bar`:
|
||||
|
||||
|--lib
|
||||
| |
|
||||
| |--Bar
|
||||
| | |--docs
|
||||
| | |--examples
|
||||
| | |--src
|
||||
| | |- Bar.c
|
||||
| | |- Bar.h
|
||||
| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||
| |
|
||||
| |--Foo
|
||||
| | |- Foo.c
|
||||
| | |- Foo.h
|
||||
| |
|
||||
| |- README --> THIS FILE
|
||||
|
|
||||
|- platformio.ini
|
||||
|--src
|
||||
|- main.c
|
||||
|
||||
Example contents of `src/main.c` using Foo and Bar:
|
||||
```
|
||||
#include <Foo.h>
|
||||
#include <Bar.h>
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
The PlatformIO Library Dependency Finder will find automatically dependent
|
||||
libraries by scanning project source files.
|
||||
|
||||
More information about PlatformIO Library Dependency Finder
|
||||
- https://docs.platformio.org/page/librarymanager/ldf.html
|
||||
18
Lora Transmitter type 01/platformio.ini
Normal file
18
Lora Transmitter type 01/platformio.ini
Normal file
@@ -0,0 +1,18 @@
|
||||
; PlatformIO Project Configuration File
|
||||
;
|
||||
; Build options: build flags, source filter
|
||||
; Upload options: custom upload port, speed and extra flags
|
||||
; Library options: dependencies, extra library storages
|
||||
; Advanced options: extra scripting
|
||||
;
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[env:esp32dev]
|
||||
platform = espressif32
|
||||
board = esp32dev
|
||||
framework = arduino
|
||||
lib_deps =
|
||||
knolleary/PubSubClient@^2.8
|
||||
bblanchon/ArduinoJson@^7.4.1
|
||||
sandeepmistry/LoRa@^0.8.0
|
||||
387
Lora Transmitter type 01/src/main.cpp
Normal file
387
Lora Transmitter type 01/src/main.cpp
Normal file
@@ -0,0 +1,387 @@
|
||||
#include <WiFi.h>
|
||||
#include <PubSubClient.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <LoRa.h>
|
||||
#include <SPI.h>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
// --- Pin Definitions ---
|
||||
// LoRa Module pin definition
|
||||
#define LORA_CS 5 // NSS
|
||||
#define LORA_RST 14 // Reset
|
||||
#define LORA_IRQ 26 // DIO0 (Used by library, not explicitly for interrupts here)
|
||||
|
||||
// --- WiFi & MQTT Configuration ---
|
||||
const char* ssid = "Aussie Broadband 8729"; // Replace with your SSID
|
||||
const char* password = "Ffdfmunfca"; // Replace with your WiFi password
|
||||
const char* mqtt_server = "192.168.20.30"; // Replace with your MQTT broker IP/hostname
|
||||
const char* mqtt_user = "mqtt-user"; // Replace with your MQTT username (if any)
|
||||
const char* mqtt_password = "sam4jo"; // Replace with your MQTT password (if any)
|
||||
// MQTT Topics
|
||||
const char* mqtt_topic_subscribe = "homeassistant/ESP32_RA/control"; // Topic to receive commands FOR LoRa devices
|
||||
const char* mqtt_topic_publish = "homeassistant/ESP32_RA/state"; // Topic to publish status FROM LoRa devices
|
||||
|
||||
WiFiClient espClient;
|
||||
PubSubClient client(espClient);
|
||||
|
||||
// --- LoRa Configuration ---
|
||||
#define LORA_FREQUENCY 434E6 // 434 MHz
|
||||
|
||||
// --- Resend Queue Logic ---
|
||||
struct Message {
|
||||
String message; // Stores the FULL JSON string sent via LoRa
|
||||
String message_id; // Stores the unique ID generated for this specific transmission attempt
|
||||
String targetDevice; // Stores the target device for queue management
|
||||
unsigned long lastSentTime;
|
||||
int retryCount;
|
||||
};
|
||||
|
||||
std::vector<Message> resendQueue;
|
||||
const int resend_times = 80; // Max retries
|
||||
const unsigned long timerInterval = 1000; // Check queue every 1 second
|
||||
const int effectiveIntervalSeconds = 5; // Resend attempts every 5 seconds
|
||||
unsigned long lastTimerCheck = 0;
|
||||
int sendCounter = 0;
|
||||
|
||||
// --- Function Declarations ---
|
||||
void connectToWiFi();
|
||||
void reconnect();
|
||||
void callback(char* topic, byte* message, unsigned int length); // MQTT Callback
|
||||
void sendMessage(String jsonMessageToSendViaLoRa, String message_id, String targetDevice); // Sends LoRa & adds to queue
|
||||
void checkResendQueue(); // Handles retries
|
||||
void handleReceivedLoRaMessage(String receivedMessage); // Processes incoming LoRa (ACKs/Status)
|
||||
// --- End Function Declarations ---
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
while (!Serial);
|
||||
Serial.println("\n\n--- ESP32 RA Transmitter/Gateway ---");
|
||||
|
||||
// Initialize LoRa
|
||||
Serial.println("Initializing LoRa...");
|
||||
SPI.begin(); // Initialize SPI explicitly
|
||||
LoRa.setPins(LORA_CS, LORA_RST, LORA_IRQ);
|
||||
if (!LoRa.begin(LORA_FREQUENCY)) {
|
||||
Serial.println("Starting LoRa failed!");
|
||||
while (1);
|
||||
}
|
||||
// Set LoRa parameters (ensure these match ALL receivers)
|
||||
LoRa.setSpreadingFactor(9);
|
||||
LoRa.setSignalBandwidth(125E3);
|
||||
LoRa.setCodingRate4(5);
|
||||
LoRa.setPreambleLength(8);
|
||||
LoRa.setSyncWord(0x34); // Use a non-default sync word
|
||||
Serial.println("LoRa initialized successfully!");
|
||||
|
||||
connectToWiFi();
|
||||
client.setServer(mqtt_server, 1883);
|
||||
client.setCallback(callback);
|
||||
reconnect(); // Connects to MQTT and subscribes
|
||||
|
||||
Serial.println("ESP32 RA Transmitter is ready.");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// 1. Maintain MQTT connection
|
||||
if (!client.connected()) {
|
||||
reconnect();
|
||||
}
|
||||
client.loop(); // Process MQTT messages & keepalive
|
||||
|
||||
// 2. Check for incoming LoRa messages (ACKs or Status Updates)
|
||||
int packetSize = LoRa.parsePacket();
|
||||
if (packetSize) {
|
||||
String receivedMessage = "";
|
||||
while (LoRa.available()) {
|
||||
receivedMessage += (char)LoRa.read();
|
||||
}
|
||||
receivedMessage.trim();
|
||||
if (receivedMessage.length() > 0) {
|
||||
handleReceivedLoRaMessage(receivedMessage); // Process incoming LoRa
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Check resend queue periodically (non-blocking timer)
|
||||
unsigned long currentTime = millis();
|
||||
if (currentTime - lastTimerCheck >= timerInterval) {
|
||||
lastTimerCheck = currentTime;
|
||||
sendCounter++;
|
||||
if (sendCounter >= effectiveIntervalSeconds) {
|
||||
checkResendQueue(); // Check if any messages need resending
|
||||
sendCounter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Handle Serial Monitor Input (for debugging/testing)
|
||||
if (Serial.available() > 0) {
|
||||
String command = Serial.readStringUntil('\n');
|
||||
command.trim();
|
||||
Serial.print("Received from Serial Monitor: "); Serial.println(command);
|
||||
|
||||
// Allow sending test JSON via Serial Monitor
|
||||
// Example: {"targetDevice":"driveway_light", "action":"turn_on"}
|
||||
JsonDocument testDoc; // Parse the input from Serial
|
||||
DeserializationError testErr = deserializeJson(testDoc, command);
|
||||
if (!testErr) {
|
||||
// Check if targetDevice exists using isNull()
|
||||
if (testDoc["targetDevice"].isNull()) {
|
||||
Serial.println("Error: JSON from Serial Monitor must contain 'targetDevice'.");
|
||||
} else {
|
||||
// Proceed only if targetDevice exists
|
||||
String message_id = String(random(10000, 99999));
|
||||
JsonDocument loraDoc; // Create the document specifically for LoRa
|
||||
|
||||
// Extract targetDevice as String
|
||||
String targetDeviceString = testDoc["targetDevice"].as<String>();
|
||||
|
||||
// Build the LoRa JSON document
|
||||
loraDoc["message_id"] = message_id;
|
||||
loraDoc["targetDevice"] = targetDeviceString; // Use the extracted string
|
||||
|
||||
// Copy action if present using isNull()
|
||||
if (!testDoc["action"].isNull()) {
|
||||
loraDoc["action"] = testDoc["action"];
|
||||
}
|
||||
// Add other fields if needed for test, copying from testDoc to loraDoc
|
||||
// Example: if(!testDoc["someValue"].isNull()) loraDoc["someValue"] = testDoc["someValue"];
|
||||
|
||||
String jsonMessage;
|
||||
serializeJson(loraDoc, jsonMessage);
|
||||
|
||||
// Call sendMessage with all required arguments
|
||||
sendMessage(jsonMessage, message_id, targetDeviceString);
|
||||
}
|
||||
} else {
|
||||
Serial.println("Invalid JSON via Serial Monitor.");
|
||||
}
|
||||
} // End Serial Monitor handling
|
||||
|
||||
delay(5); // Small delay to prevent tight loop
|
||||
} // End loop()
|
||||
|
||||
// --- WiFi and MQTT Functions ---
|
||||
|
||||
void connectToWiFi() {
|
||||
Serial.print("Initializing WiFi...");
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.disconnect(true);
|
||||
delay(100);
|
||||
Serial.print("Connecting to WiFi...");
|
||||
WiFi.begin(ssid, password);
|
||||
int retryCount = 0;
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(1000);
|
||||
Serial.print(".");
|
||||
retryCount++;
|
||||
if (retryCount > 30) {
|
||||
Serial.println("\nFailed to connect to WiFi. Restarting...");
|
||||
ESP.restart();
|
||||
}
|
||||
}
|
||||
Serial.println("\nConnected to WiFi!");
|
||||
Serial.print("IP Address: "); Serial.println(WiFi.localIP());
|
||||
}
|
||||
|
||||
void reconnect() {
|
||||
while (!client.connected()) {
|
||||
Serial.print("Connecting to MQTT...");
|
||||
// Attempt to connect with client ID, user, and password
|
||||
if (client.connect("ESP32_RA_Transmitter", mqtt_user, mqtt_password)) {
|
||||
Serial.println("connected");
|
||||
// Subscribe to the control topic upon connection
|
||||
client.subscribe(mqtt_topic_subscribe);
|
||||
Serial.print("Subscribed to: "); Serial.println(mqtt_topic_subscribe);
|
||||
} else {
|
||||
Serial.print("failed, rc="); Serial.print(client.state());
|
||||
Serial.println(" trying again in 5 seconds");
|
||||
// Wait 5 seconds before retrying
|
||||
delay(5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Handles messages received FROM MQTT (structured JSON commands for LoRa devices)
|
||||
void callback(char* topic, byte* message, unsigned int length) {
|
||||
String messageReceived; // This is the JSON string from Home Assistant
|
||||
messageReceived.reserve(length + 1); // Pre-allocate string memory
|
||||
for (unsigned int i = 0; i < length; i++) {
|
||||
messageReceived += (char)message[i];
|
||||
}
|
||||
Serial.println("--- MQTT Callback ---");
|
||||
Serial.println("Topic: " + String(topic));
|
||||
Serial.println("Raw Payload (" + String(length) + " bytes): " + messageReceived); // Print raw payload
|
||||
|
||||
if (String(topic) == mqtt_topic_subscribe) {
|
||||
// Use dynamic JsonDocument (recommended for ArduinoJson v7+)
|
||||
JsonDocument doc;
|
||||
Serial.println("Attempting to parse MQTT JSON...");
|
||||
DeserializationError mqttError = deserializeJson(doc, messageReceived);
|
||||
|
||||
// Check for parsing errors, including memory allocation failure
|
||||
if (mqttError == DeserializationError::NoMemory) {
|
||||
Serial.println("!!! Failed to allocate memory for MQTT JSON. Increase heap?");
|
||||
return; // Exit if memory allocation failed
|
||||
} else if (mqttError) {
|
||||
Serial.print("!!! Failed to parse JSON from MQTT: ");
|
||||
Serial.println(mqttError.c_str());
|
||||
Serial.println("Payload was: " + messageReceived);
|
||||
return; // Ignore invalid MQTT messages
|
||||
}
|
||||
Serial.println("MQTT JSON Parsed Successfully.");
|
||||
|
||||
// Extract targetDevice using isNull() check
|
||||
if (doc["targetDevice"].isNull()) {
|
||||
Serial.println("!!! MQTT message missing 'targetDevice'. Ignoring.");
|
||||
return; // Cannot process without a target
|
||||
}
|
||||
String targetDevice = doc["targetDevice"].as<String>();
|
||||
Serial.println("Target Device: " + targetDevice);
|
||||
|
||||
// Generate a unique message ID for LoRa tracking
|
||||
String message_id = String(random(10000, 99999));
|
||||
|
||||
// Add/overwrite the message_id in the document
|
||||
doc["message_id"] = message_id;
|
||||
Serial.println("Added message_id: " + message_id);
|
||||
|
||||
// Serialize the *modified* document for LoRa transmission
|
||||
String jsonMessageToSendViaLoRa;
|
||||
Serial.println("Serializing document for LoRa...");
|
||||
size_t jsonSize = serializeJson(doc, jsonMessageToSendViaLoRa);
|
||||
Serial.println("Serialized LoRa JSON (" + String(jsonSize) + " bytes): " + jsonMessageToSendViaLoRa);
|
||||
|
||||
// Send the message via LoRa (will also add/replace in queue)
|
||||
// Pass targetDevice to sendMessage
|
||||
sendMessage(jsonMessageToSendViaLoRa, message_id, targetDevice);
|
||||
} else {
|
||||
Serial.println("Message received on incorrect topic.");
|
||||
}
|
||||
Serial.println("--- End MQTT Callback ---");
|
||||
}
|
||||
|
||||
|
||||
// --- LoRa Communication Functions ---
|
||||
|
||||
// Sends a LoRa message AND adds/replaces it in the resend queue
|
||||
void sendMessage(String jsonMessageToSendViaLoRa, String message_id, String targetDevice) {
|
||||
// Basic validation of the JSON before sending (optional but good)
|
||||
JsonDocument doc;
|
||||
DeserializationError error = deserializeJson(doc, jsonMessageToSendViaLoRa);
|
||||
if (error) {
|
||||
Serial.print("sendMessage: Invalid JSON format, cannot send: ");
|
||||
Serial.println(error.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove existing message for this targetDevice from queue
|
||||
// Capture targetDevice by value in the lambda
|
||||
auto it = std::find_if(resendQueue.begin(), resendQueue.end(),
|
||||
[targetDevice](const Message& msg_in_queue) {
|
||||
return msg_in_queue.targetDevice == targetDevice;
|
||||
});
|
||||
|
||||
if (it != resendQueue.end()) {
|
||||
Serial.println("Found existing message for target '" + targetDevice + "' (ID: " + it->message_id + ") in queue. Removing it.");
|
||||
resendQueue.erase(it);
|
||||
}
|
||||
|
||||
// Send the LoRa packet
|
||||
Serial.print("Sending LoRa packet for target '" + targetDevice + "': "); Serial.println(jsonMessageToSendViaLoRa);
|
||||
LoRa.beginPacket();
|
||||
LoRa.print(jsonMessageToSendViaLoRa);
|
||||
int success = LoRa.endPacket(); // This blocks until transmission is complete
|
||||
if(success){
|
||||
Serial.println("LoRa packet sent initially.");
|
||||
} else {
|
||||
Serial.println("LoRa packet initial send FAILED.");
|
||||
// Consider not adding to queue if initial send fails? Or let retry handle it.
|
||||
}
|
||||
|
||||
// Add the new message to the queue for potential resends
|
||||
Message newMsg = {jsonMessageToSendViaLoRa, message_id, targetDevice, millis(), 0};
|
||||
resendQueue.push_back(newMsg);
|
||||
Serial.println("Added message ID " + message_id + " for target '" + targetDevice + "' to queue.");
|
||||
}
|
||||
|
||||
// Checks the queue and resends messages if no ACK received
|
||||
void checkResendQueue() {
|
||||
unsigned long currentMillis = millis();
|
||||
unsigned long resendIntervalMillis = effectiveIntervalSeconds * 1000;
|
||||
|
||||
if (resendQueue.empty()) {
|
||||
return; // Nothing to do
|
||||
}
|
||||
// Serial.println("--- checkResendQueue ---"); // Enable for debugging queue
|
||||
|
||||
for (auto it = resendQueue.begin(); it != resendQueue.end(); /* increment inside loop */) {
|
||||
if (it->retryCount >= resend_times) {
|
||||
Serial.println("Message ID " + it->message_id + " for target '" + it->targetDevice + "' failed after max retries. Removing.");
|
||||
it = resendQueue.erase(it); // Erase and get next iterator
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if enough time has passed since the last send attempt for this message
|
||||
if (currentMillis - it->lastSentTime >= resendIntervalMillis) {
|
||||
Serial.print("Resending message ID " + it->message_id + " for target '" + it->targetDevice + "': " + it->message);
|
||||
LoRa.beginPacket();
|
||||
LoRa.print(it->message); // Resend the full JSON string
|
||||
LoRa.endPacket();
|
||||
Serial.println(" ...Resent.");
|
||||
|
||||
it->lastSentTime = currentMillis;
|
||||
it->retryCount++;
|
||||
++it; // Move to next item after processing
|
||||
} else {
|
||||
++it; // Move to next item if not time to resend
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handles messages received VIA LORA (ACKs, Status Updates, etc.)
|
||||
void handleReceivedLoRaMessage(String receivedMessage) {
|
||||
Serial.print(">>> Transmitter Received LoRa: "); Serial.println(receivedMessage);
|
||||
|
||||
JsonDocument doc;
|
||||
DeserializationError error = deserializeJson(doc, receivedMessage);
|
||||
if (error) {
|
||||
Serial.print(">>> Transmitter: Failed to parse received LoRa JSON: "); Serial.println(error.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for Acknowledgment
|
||||
String msgContent = doc["message"] | "no_message";
|
||||
String ackMsgId = doc["message_id"] | "no_id";
|
||||
|
||||
if (msgContent == "acknowledged" && ackMsgId != "no_id") {
|
||||
Serial.print(">>> Transmitter: Received ACK for message_id: "); Serial.println(ackMsgId);
|
||||
// Find and remove the message from the resendQueue based on the ACK's message_id
|
||||
// Capture ackMsgId by value in the lambda
|
||||
auto it = std::find_if(resendQueue.begin(), resendQueue.end(),
|
||||
[ackMsgId](const Message& msg_in_queue) {
|
||||
// Compare the ID from the ACK with the ID stored when the message was added to the queue
|
||||
return msg_in_queue.message_id == ackMsgId;
|
||||
});
|
||||
if (it != resendQueue.end()) {
|
||||
Serial.println(">>> Transmitter: Found matching message (Target: " + it->targetDevice + ") in queue. Erasing.");
|
||||
resendQueue.erase(it);
|
||||
} else {
|
||||
Serial.println(">>> Transmitter: ACK received, but no matching message found in queue (might have timed out or already ACKed).");
|
||||
}
|
||||
return; // Handled ACK, nothing more to do with this message
|
||||
}
|
||||
|
||||
// If not an ACK, assume it's a Status Update from a receiver device
|
||||
Serial.println(">>> Transmitter: Received Status Update from LoRa device.");
|
||||
// Check if essential status info is present (e.g., device name) using isNull()
|
||||
if (!doc["device"].isNull()) {
|
||||
// Publish the *entire received JSON* to the state MQTT topic
|
||||
Serial.print(">>> Transmitter: Publishing to MQTT topic [");
|
||||
Serial.print(mqtt_topic_publish); Serial.print("]: ");
|
||||
Serial.println(receivedMessage); // Publish the raw JSON received
|
||||
client.publish(mqtt_topic_publish, receivedMessage.c_str());
|
||||
} else {
|
||||
Serial.println(">>> Transmitter: Received LoRa message is not ACK and lacks 'device' field for status update.");
|
||||
}
|
||||
}
|
||||
11
Lora Transmitter type 01/test/README
Normal file
11
Lora Transmitter type 01/test/README
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
This directory is intended for PlatformIO Test Runner and project tests.
|
||||
|
||||
Unit Testing is a software testing method by which individual units of
|
||||
source code, sets of one or more MCU program modules together with associated
|
||||
control data, usage procedures, and operating procedures, are tested to
|
||||
determine whether they are fit for use. Unit testing finds problems early
|
||||
in the development cycle.
|
||||
|
||||
More information about PlatformIO Unit Testing:
|
||||
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html
|
||||
Reference in New Issue
Block a user