first commit

This commit is contained in:
2025-11-30 01:00:14 +03:00
commit ad40e245b7
10 changed files with 2805 additions and 0 deletions

5
.gitignore vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

7
src/CMakeLists.txt Normal file
View 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
View 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
View 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