first commit
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.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
.vscode/extensions.json
vendored
Normal file
10
.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"
|
||||
]
|
||||
}
|
||||
3
CMakeLists.txt
Normal file
3
CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
cmake_minimum_required(VERSION 3.16.0)
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(KgeuTraining)
|
||||
37
include/README
Normal file
37
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
lib/README
Normal file
46
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
platformio.ini
Normal file
18
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:wemos_d1_uno32]
|
||||
platform = espressif32
|
||||
board = wemos_d1_uno32
|
||||
framework = espidf
|
||||
monitor_speed = 115200
|
||||
monitor_filters =
|
||||
default
|
||||
esp32_exception_decoder
|
||||
2232
sdkconfig.wemos_d1_uno32
Normal file
2232
sdkconfig.wemos_d1_uno32
Normal file
File diff suppressed because it is too large
Load Diff
7
src/CMakeLists.txt
Normal file
7
src/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
# This file was automatically generated for projects
|
||||
# without default 'CMakeLists.txt' file.
|
||||
|
||||
FILE(GLOB_RECURSE app_sources ${CMAKE_SOURCE_DIR}/src/*.*)
|
||||
|
||||
idf_component_register(SRCS ${app_sources}
|
||||
REQUIRES nvs_flash)
|
||||
436
src/main.c
Normal file
436
src/main.c
Normal file
@@ -0,0 +1,436 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "lwip/sockets.h"
|
||||
#include "lwip/dns.h"
|
||||
#include "lwip/netdb.h"
|
||||
|
||||
static const char *TAG = "KgeuTraining";
|
||||
|
||||
// WiFi AP Configuration
|
||||
#define WIFI_SSID "KgeuTraining_AP"
|
||||
#define WIFI_PASS "12345678"
|
||||
#define WIFI_CHANNEL 1
|
||||
#define MAX_STA_CONN 4
|
||||
|
||||
// Hall Sensor GPIO pins (KY-003)
|
||||
#define HALL_SENSOR_TOP_PIN GPIO_NUM_4
|
||||
#define HALL_SENSOR_BOTTOM_PIN GPIO_NUM_5
|
||||
|
||||
// TCP server configuration
|
||||
#define TCP_PORT 8080
|
||||
#define MAX_CLIENTS 4
|
||||
#define TCP_BUFFER_SIZE 128
|
||||
|
||||
// Position states
|
||||
typedef enum {
|
||||
POSITION_TOP,
|
||||
POSITION_BOTTOM,
|
||||
POSITION_MIDDLE
|
||||
} position_state_t;
|
||||
|
||||
static position_state_t current_position = POSITION_MIDDLE;
|
||||
static position_state_t last_position = POSITION_MIDDLE;
|
||||
|
||||
// TCP client list
|
||||
static int tcp_clients[MAX_CLIENTS] = {-1, -1, -1, -1};
|
||||
static int tcp_client_count = 0;
|
||||
static int tcp_server_socket = -1;
|
||||
|
||||
// Mutex for client list access
|
||||
static SemaphoreHandle_t client_mutex = NULL;
|
||||
|
||||
// Forward declaration
|
||||
static void send_position_to_client(int sock_fd, const char *position);
|
||||
|
||||
/**
|
||||
* Add TCP client to the list
|
||||
*/
|
||||
static void add_tcp_client(int sock_fd) {
|
||||
xSemaphoreTake(client_mutex, portMAX_DELAY);
|
||||
for (int i = 0; i < MAX_CLIENTS; i++) {
|
||||
if (tcp_clients[i] == -1) {
|
||||
tcp_clients[i] = sock_fd;
|
||||
tcp_client_count++;
|
||||
ESP_LOGI(TAG, "TCP client connected, total: %d", tcp_client_count);
|
||||
|
||||
// Send current position immediately
|
||||
const char *current_pos_str;
|
||||
switch (current_position) {
|
||||
case POSITION_TOP:
|
||||
current_pos_str = "top";
|
||||
break;
|
||||
case POSITION_BOTTOM:
|
||||
current_pos_str = "bottom";
|
||||
break;
|
||||
case POSITION_MIDDLE:
|
||||
current_pos_str = "middle";
|
||||
break;
|
||||
default:
|
||||
current_pos_str = "middle";
|
||||
break;
|
||||
}
|
||||
send_position_to_client(sock_fd, current_pos_str);
|
||||
|
||||
xSemaphoreGive(client_mutex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
ESP_LOGW(TAG, "Maximum clients reached");
|
||||
xSemaphoreGive(client_mutex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove TCP client from the list
|
||||
*/
|
||||
static void remove_tcp_client(int sock_fd) {
|
||||
xSemaphoreTake(client_mutex, portMAX_DELAY);
|
||||
for (int i = 0; i < MAX_CLIENTS; i++) {
|
||||
if (tcp_clients[i] == sock_fd) {
|
||||
tcp_clients[i] = -1;
|
||||
tcp_client_count--;
|
||||
ESP_LOGI(TAG, "TCP client disconnected, total: %d", tcp_client_count);
|
||||
xSemaphoreGive(client_mutex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
xSemaphoreGive(client_mutex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send position to a specific TCP client
|
||||
*/
|
||||
static void send_position_to_client(int sock_fd, const char *position) {
|
||||
// Simple JSON formatting: {"position": "top"}
|
||||
char json_string[64];
|
||||
snprintf(json_string, sizeof(json_string), "{\"position\":\"%s\"}\n", position);
|
||||
|
||||
ESP_LOGI(TAG, "Sending to client %d: %s", sock_fd, json_string);
|
||||
|
||||
int len = strlen(json_string);
|
||||
int sent = send(sock_fd, json_string, len, 0);
|
||||
|
||||
if (sent < 0) {
|
||||
ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
|
||||
remove_tcp_client(sock_fd);
|
||||
close(sock_fd);
|
||||
} else if (sent != len) {
|
||||
ESP_LOGW(TAG, "Sent %d bytes instead of %d", sent, len);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast JSON message to all connected TCP clients
|
||||
*/
|
||||
static void broadcast_position(const char *position) {
|
||||
if (tcp_client_count == 0) {
|
||||
return; // No clients connected
|
||||
}
|
||||
|
||||
// Simple JSON formatting: {"position": "top"}
|
||||
char json_string[64];
|
||||
snprintf(json_string, sizeof(json_string), "{\"position\":\"%s\"}\n", position);
|
||||
|
||||
ESP_LOGI(TAG, "Broadcasting: %s", json_string);
|
||||
|
||||
xSemaphoreTake(client_mutex, portMAX_DELAY);
|
||||
for (int i = 0; i < MAX_CLIENTS; i++) {
|
||||
if (tcp_clients[i] != -1) {
|
||||
int sock_fd = tcp_clients[i];
|
||||
int len = strlen(json_string);
|
||||
int sent = send(sock_fd, json_string, len, 0);
|
||||
|
||||
if (sent < 0) {
|
||||
ESP_LOGE(TAG, "Error sending to client %d: errno %d", sock_fd, errno);
|
||||
remove_tcp_client(sock_fd);
|
||||
close(sock_fd);
|
||||
} else if (sent != len) {
|
||||
ESP_LOGW(TAG, "Sent %d bytes instead of %d to client %d", sent, len, sock_fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
xSemaphoreGive(client_mutex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle individual TCP client connection
|
||||
*/
|
||||
static void tcp_client_task(void *pvParameters) {
|
||||
int sock_fd = (int)(intptr_t)pvParameters;
|
||||
char rx_buffer[TCP_BUFFER_SIZE];
|
||||
|
||||
ESP_LOGI(TAG, "New client task started for socket %d", sock_fd);
|
||||
|
||||
// Set socket timeout
|
||||
struct timeval timeout;
|
||||
timeout.tv_sec = 10;
|
||||
timeout.tv_usec = 0;
|
||||
setsockopt(sock_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
|
||||
|
||||
while (1) {
|
||||
int len = recv(sock_fd, rx_buffer, sizeof(rx_buffer) - 1, 0);
|
||||
|
||||
if (len < 0) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
// Timeout - continue
|
||||
continue;
|
||||
}
|
||||
ESP_LOGI(TAG, "recv failed: errno %d", errno);
|
||||
break;
|
||||
} else if (len == 0) {
|
||||
ESP_LOGI(TAG, "Connection closed by client");
|
||||
break;
|
||||
}
|
||||
|
||||
// Null-terminate the received data
|
||||
rx_buffer[len] = '\0';
|
||||
ESP_LOGI(TAG, "Received %d bytes: %s", len, rx_buffer);
|
||||
|
||||
// Echo back or process the received data if needed
|
||||
// For now, we just log it
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Closing client socket %d", sock_fd);
|
||||
remove_tcp_client(sock_fd);
|
||||
close(sock_fd);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* TCP server task - accepts new connections
|
||||
*/
|
||||
static void tcp_server_task(void *pvParameters) {
|
||||
struct sockaddr_in server_addr;
|
||||
struct sockaddr_in client_addr;
|
||||
socklen_t client_addr_len = sizeof(client_addr);
|
||||
|
||||
// Create socket
|
||||
tcp_server_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (tcp_server_socket < 0) {
|
||||
ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set socket options
|
||||
int opt = 1;
|
||||
setsockopt(tcp_server_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||
|
||||
// Bind socket
|
||||
server_addr.sin_family = AF_INET;
|
||||
server_addr.sin_addr.s_addr = INADDR_ANY;
|
||||
server_addr.sin_port = htons(TCP_PORT);
|
||||
|
||||
if (bind(tcp_server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
|
||||
ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
|
||||
close(tcp_server_socket);
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
// Listen for connections
|
||||
if (listen(tcp_server_socket, MAX_CLIENTS) < 0) {
|
||||
ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno);
|
||||
close(tcp_server_socket);
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "TCP server started on port %d", TCP_PORT);
|
||||
|
||||
while (1) {
|
||||
// Accept new connection
|
||||
int client_sock = accept(tcp_server_socket, (struct sockaddr *)&client_addr, &client_addr_len);
|
||||
|
||||
if (client_sock < 0) {
|
||||
ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno);
|
||||
continue;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "New client connected from %s", inet_ntoa(client_addr.sin_addr));
|
||||
|
||||
// Add client to list
|
||||
add_tcp_client(client_sock);
|
||||
|
||||
// Create task for this client
|
||||
xTaskCreate(tcp_client_task, "tcp_client", 4096, (void*)(intptr_t)client_sock, 5, NULL);
|
||||
}
|
||||
|
||||
close(tcp_server_socket);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* WiFi event handler
|
||||
*/
|
||||
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void* event_data) {
|
||||
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STACONNECTED) {
|
||||
wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;
|
||||
ESP_LOGI(TAG, "Station joined, AID=%d", event->aid);
|
||||
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STADISCONNECTED) {
|
||||
wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;
|
||||
ESP_LOGI(TAG, "Station left, AID=%d", event->aid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize WiFi Access Point
|
||||
*/
|
||||
static void wifi_init_softap(void) {
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
esp_netif_create_default_wifi_ap();
|
||||
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||
|
||||
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
|
||||
ESP_EVENT_ANY_ID,
|
||||
&wifi_event_handler,
|
||||
NULL,
|
||||
NULL));
|
||||
|
||||
wifi_config_t wifi_config = {
|
||||
.ap = {
|
||||
.ssid = WIFI_SSID,
|
||||
.ssid_len = strlen(WIFI_SSID),
|
||||
.channel = WIFI_CHANNEL,
|
||||
.password = WIFI_PASS,
|
||||
.max_connection = MAX_STA_CONN,
|
||||
.authmode = WIFI_AUTH_WPA2_PSK
|
||||
},
|
||||
};
|
||||
|
||||
if (strlen(WIFI_PASS) == 0) {
|
||||
wifi_config.ap.authmode = WIFI_AUTH_OPEN;
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
|
||||
ESP_ERROR_CHECK(esp_wifi_start());
|
||||
|
||||
ESP_LOGI(TAG, "WiFi AP initialized. SSID:%s password:%s channel:%d",
|
||||
WIFI_SSID, WIFI_PASS, WIFI_CHANNEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize GPIO for hall sensors
|
||||
*/
|
||||
static void init_hall_sensors(void) {
|
||||
gpio_config_t io_conf = {
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
.pin_bit_mask = (1ULL << HALL_SENSOR_TOP_PIN) | (1ULL << HALL_SENSOR_BOTTOM_PIN),
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.pull_up_en = GPIO_PULLUP_ENABLE // KY-003 has pull-up resistor, enable internal pull-up
|
||||
};
|
||||
gpio_config(&io_conf);
|
||||
|
||||
ESP_LOGI(TAG, "Hall sensors initialized on GPIO %d (top) and %d (bottom)",
|
||||
HALL_SENSOR_TOP_PIN, HALL_SENSOR_BOTTOM_PIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read position from hall sensors
|
||||
*/
|
||||
static position_state_t read_position(void) {
|
||||
int top_sensor = gpio_get_level(HALL_SENSOR_TOP_PIN);
|
||||
int bottom_sensor = gpio_get_level(HALL_SENSOR_BOTTOM_PIN);
|
||||
|
||||
// KY-003: LOW when magnet is near, HIGH when no magnet
|
||||
// Top sensor LOW = weight at top
|
||||
// Bottom sensor LOW = weight at bottom
|
||||
// Both HIGH = weight in middle
|
||||
|
||||
if (top_sensor == 0) {
|
||||
return POSITION_TOP;
|
||||
} else if (bottom_sensor == 0) {
|
||||
return POSITION_BOTTOM;
|
||||
} else {
|
||||
return POSITION_MIDDLE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Task to monitor position changes and broadcast updates
|
||||
*/
|
||||
static void position_monitor_task(void *pvParameters) {
|
||||
ESP_LOGI(TAG, "Position monitoring task started");
|
||||
|
||||
while (1) {
|
||||
current_position = read_position();
|
||||
|
||||
// Broadcast if position changed
|
||||
if (current_position != last_position) {
|
||||
const char *position_str;
|
||||
switch (current_position) {
|
||||
case POSITION_TOP:
|
||||
position_str = "top";
|
||||
break;
|
||||
case POSITION_BOTTOM:
|
||||
position_str = "bottom";
|
||||
break;
|
||||
case POSITION_MIDDLE:
|
||||
position_str = "middle";
|
||||
break;
|
||||
default:
|
||||
position_str = "middle";
|
||||
break;
|
||||
}
|
||||
|
||||
broadcast_position(position_str);
|
||||
last_position = current_position;
|
||||
}
|
||||
|
||||
// Check every 100ms
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main application entry point
|
||||
*/
|
||||
void app_main(void) {
|
||||
// Initialize NVS
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(ret);
|
||||
|
||||
// Initialize WiFi AP
|
||||
wifi_init_softap();
|
||||
|
||||
// Wait for WiFi to be ready
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
|
||||
// Initialize hall sensors
|
||||
init_hall_sensors();
|
||||
|
||||
// Create mutex for client list
|
||||
client_mutex = xSemaphoreCreateMutex();
|
||||
if (client_mutex == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to create mutex");
|
||||
return;
|
||||
}
|
||||
|
||||
// Start TCP server task
|
||||
xTaskCreate(tcp_server_task, "tcp_server", 4096, NULL, 5, NULL);
|
||||
|
||||
// Start position monitoring task
|
||||
xTaskCreate(position_monitor_task, "position_monitor", 2048, NULL, 5, NULL);
|
||||
|
||||
ESP_LOGI(TAG, "KgeuTraining application started");
|
||||
ESP_LOGI(TAG, "Connect to TCP server at 192.168.4.1:%d", TCP_PORT);
|
||||
}
|
||||
11
test/README
Normal file
11
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