ESP simplest server

This commit is contained in:
Edward Emelianov 2025-10-17 00:32:56 +03:00
parent 19fbf250a9
commit ca7a53ee1c
7 changed files with 659 additions and 0 deletions

34
ESP8266/CMakeLists.txt Normal file
View File

@ -0,0 +1,34 @@
cmake_minimum_required(VERSION 3.16)
set(PROJ esp8266)
project(${PROJ} LANGUAGES C)
set(CMAKE_COLOR_MAKEFILE ON)
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} SOURCES)
option(DEBUG "Compile in debug mode" OFF)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS} -Og -g3 -ggdb -Werror")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS} -O3 -march=native -fdata-sections -ffunction-sections -flto=auto")
if(DEBUG)
add_definitions(-DEBUG)
set(CMAKE_BUILD_TYPE DEBUG)
set(CMAKE_VERBOSE_MAKEFILE "ON")
else()
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections -flto=auto")
set(CMAKE_BUILD_TYPE RELEASE)
endif()
find_package(PkgConfig REQUIRED)
pkg_check_modules(${PROJ} REQUIRED usefull_macros)
add_executable(esp8266 ${SOURCES})
target_link_libraries(${PROJ} ${${PROJ}_LIBRARIES} -lm)
include(GNUInstallDirs)
install(TARGETS ${PROJ}
# LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

2
ESP8266/Readme Normal file
View File

@ -0,0 +1,2 @@
Simplest server based on AT-commands using ESP8266.
I tried to make code as nearest to MCU as possible.

170
ESP8266/esp8266.c Normal file
View File

@ -0,0 +1,170 @@
/*
* This file is part of the esp8266 project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <usefull_macros.h>
#include <stdio.h>
#include "esp8266.h"
#include "serial.h"
#define ESPMSG(x) do{if(!serial_send_msg(x)) return FALSE;}while(0)
// sometimes ESP hangs with message "WIFI GOT IP" and I can do nothing except waiting
const char *msgptr = NULL; // pointer to received message
static int receivedlen = 0;
// check module working
int esp_check(){
// try simplest check for three times
for(int i = 0; i < 3; ++i) if( ANS_OK == serial_sendwans("AT")) break;
//esp_close();
// now try next even if no answer for "AT"
if( ANS_OK == serial_sendwans("ATE0") // echo off
&& ANS_OK == serial_sendwans("AT+CWMODE_CUR=1") // station mode
) return TRUE;
return FALSE;
}
// check established wifi connection
esp_cipstatus_t esp_cipstatus(){
if(ANS_OK != serial_sendwans("AT+CIPSTATUS")) return ESP_CIP_FAILED;
char *l = serial_getline(NULL, NULL);
const char *n = NULL;
if(!l || !(n = serial_tidx(l, "STATUS:"))) return ESP_CIP_FAILED;
return (esp_cipstatus_t)serial_s2i(n);
}
// connect to AP
int esp_connect(const char *SSID, const char *pass){
ESPMSG("AT+CWJAP_CUR=\"");
ESPMSG(SSID);
ESPMSG("\",\"");
ESPMSG(pass);
if(ANS_OK != serial_sendwans("\"")) return FALSE;
return TRUE;
}
// just send AT+CIFSR
int esp_myip(){
return (ANS_OK == serial_sendwans("AT+CIFSR"));
}
// start server on given port
int esp_start_server(const char *port){
if(ANS_OK != serial_sendwans("AT+CIPMUX=1")){ // can't start server without mux
// what if already ready?
char *x = serial_getline(NULL, NULL);
if(!x || !serial_tidx(x, "link is builded")) return FALSE;
}
ESPMSG("AT+CIPSERVER=1,");
if(ANS_OK != serial_sendwans(port)) return FALSE;
return TRUE;
}
// stop server
int ep_stop_server(){
return (ANS_OK == serial_sendwans("AT+CIPSERVER=0"));
}
// next (or only) line of received data
const char *esp_msgline(){
DBG("receivedlen=%d", receivedlen);
if(msgptr){
const char *p = msgptr;
msgptr = NULL;
return p;
}
if(receivedlen < 1) return NULL;
int l, d;
const char *got = serial_getline(&l, &d);
receivedlen -= l + d;
return got;
}
// process connection/disconnection/messages
// fd - file descriptor of opened/closed connections
esp_clientstat_t esp_process(int *fd){
msgptr = NULL;
receivedlen = 0;
int l, d;
char *got = serial_getline(&l, &d);
if(!got) return ESP_CLT_IDLE;
const char *x = serial_tidx(got, "+IPD,");
if(x){
if(fd) *fd = serial_s2i(x);
x = serial_tidx(x, ",");
if(!x) return ESP_CLT_ERROR;
int r = serial_s2i(x);
x = serial_tidx(x, ":");
if(!x) return ESP_CLT_ERROR;
receivedlen = r - d - (l - (x-got)); // this is a rest of data (if any)
msgptr = x;
return ESP_CLT_GETMESSAGE;
}
// check for CONNECT/CLOSE
if((x = serial_tidx(got, ",CONNECT"))){
if(fd) *fd = serial_s2i(got);
return ESP_CLT_CONNECTED;
}
if((x = serial_tidx(got, ",CLOSED"))){
if(fd) *fd = serial_s2i(got);
return ESP_CLT_DISCONNECTED;
}
DBG("Unknown message: '%s'", got);
return ESP_CLT_IDLE;
}
int esp_send(int fd, const char *msg){
DBG("send '%s' to %d", msg, fd);
ESPMSG("AT+CIPSENDEX=");
if(!serial_putchar('0' + fd)) return FALSE;
if(ANS_OK != serial_sendwans(",2048")) return FALSE;
int got = 0;
// try several times
for(int i = 0; i < 10; ++i){
got = serial_getch();
if(got == '>') break;
}
if(got != '>'){
DBG("Didn't found '>'");
serial_send_msg("\\0"); // terminate message
serial_clr();
return FALSE; // go into terminal mode
}
serial_clr(); // remove space after '>'
ESPMSG(msg);
if(ANS_OK == serial_sendwans("\\0")) return TRUE;
DBG("Didn't sent");
return FALSE;
}
void esp_reset(){
serial_sendwans("AT+RST");
}
void esp_close(){
serial_sendwans("AT+CIPMUX=0");
serial_sendwans("AT+CIPCLOSE");
}
int esp_listAP(){
if(ANS_OK == serial_sendwans("AT+CWLAP")) return TRUE;
return FALSE;
}

53
ESP8266/esp8266.h Normal file
View File

@ -0,0 +1,53 @@
/*
* This file is part of the esp8266 project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
// really only 5 clients allowed
#define ESP_MAX_CLT_NUMBER 8
typedef enum{
ESP_CIP_0,
ESP_CIP_1,
ESP_CIP_GOTIP,
ESP_CIP_CONNECTED,
ESP_CIP_DISCONNECTED,
ESP_CIP_FAILED
} esp_cipstatus_t;
typedef enum{
ESP_CLT_IDLE, // nothing happened
ESP_CLT_CONNECTED, // new client connected
ESP_CLT_DISCONNECTED, // disconnected
ESP_CLT_ERROR, // error writing or other
ESP_CLT_GETMESSAGE, // receive message from client
ESP_CLT_OK, // sent OK
} esp_clientstat_t;
int esp_check();
void esp_close();
void esp_reset();
esp_cipstatus_t esp_cipstatus();
int esp_connect(const char *SSID, const char *pass);
int esp_myip();
int esp_start_server(const char *port);
int ep_stop_server();
esp_clientstat_t esp_process(int *fd);
int esp_send(int fd, const char *msg);
const char *esp_msgline();
int esp_listAP();

182
ESP8266/main.c Normal file
View File

@ -0,0 +1,182 @@
/*
* This file is part of the esp8266 project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <string.h>
#include <usefull_macros.h>
#include "esp8266.h"
#include "serial.h"
static struct{
int help;
char *serialdev;
char *SSID;
char *SSpass;
int speed;
char *port;
int reset;
int list;
} G = {
.speed = 115200,
.serialdev = "/dev/ttyUSB0",
.port = "1111",
};
static sl_option_t options[] = {
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&G.help), "show this help"},
{"device", NEED_ARG, NULL, 'd', arg_string, APTR(&G.serialdev), "serial device path (default: /dev/ttyUSB0)"},
{"baudrate",NEED_ARG, NULL, 'b', arg_int, APTR(&G.speed), "serial speed"},
{"ssid", NEED_ARG, NULL, 0, arg_string, APTR(&G.SSID), "SSID to connect"},
{"pass", NEED_ARG, NULL, 0, arg_string, APTR(&G.SSpass), "SSID password"},
{"port", NEED_ARG, NULL, 'p', arg_string, APTR(&G.port), "servr port (default: 1111)"},
{"reset", NO_ARGS, NULL, 0, arg_int, APTR(&G.reset), "reset ESP"},
{"list", NO_ARGS, NULL, 'l', arg_int, APTR(&G.list), "list available APs"},
end_option
};
void signals(int signo){
esp_close();
serial_close();
exit(signo);
}
static esp_clientstat_t parse_message(int fd){
// this isn't a good idea to send data while not received all; so we use buffer
static sl_ringbuffer_t *rb = NULL;
if(!rb) rb = sl_RB_new(4096);
const char *msg = NULL;
char buf[4096];
while((msg = esp_msgline())){
printf("Received line from fd %d: %s\n", fd, msg);
// do something with this data
if(0 == strcmp(msg, "help")){
sl_RB_writestr(rb, "Hey, we don't have any help yet, try `time`\n");
}else if(0 == strcmp(msg, "time")){
snprintf(buf, 64, "TIME=%.3f\n", sl_dtime());
sl_RB_writestr(rb, buf);
}else{
if(*msg){
snprintf(buf, 127, "Part of your message: _%s_\n", msg);
sl_RB_writestr(rb, buf);
}
}
}
size_t L = 4094;
char *b = buf;
while(sl_RB_readline(rb, b, L) > 0){ // there's a bug in my library! Need to fix (sl_RB_readline returns NOT an amount of bytes)
size_t got = strlen(b);
L -= ++got;
b += got;
b[-1] = '\n';
}
if(L == 4094) return ESP_CLT_OK; // nothing to send
return esp_send(fd, buf) ? ESP_CLT_OK : ESP_CLT_ERROR;
}
static void processing(){
uint8_t connected[ESP_MAX_CLT_NUMBER] = {0};
esp_clientstat_t oldstat[ESP_MAX_CLT_NUMBER] = {0};
double T0 = sl_dtime();
int N = -1;
void chkerr(){
if(oldstat[N] == ESP_CLT_ERROR){
DBG("error again -> turn off");
connected[N] = 0;
}
}
while(1){
esp_clientstat_t s = esp_process(&N);
if(N > -1 && N < ESP_MAX_CLT_NUMBER){ // parsing
//if(s == ESP_CLT_IDLE){ usleep(1000); continue; }
switch(s){
case ESP_CLT_CONNECTED:
connected[N] = 1;
green("Connection on fd=%d\n", N);
break;
case ESP_CLT_DISCONNECTED:
connected[N] = 0;
green("fd=%d disconnected\n", N);
break;
case ESP_CLT_ERROR:
DBG("Error from %d", N);
chkerr();
break;
case ESP_CLT_GETMESSAGE:
DBG("%d have message", N);
s = parse_message(N);
if(s == ESP_CLT_ERROR) chkerr();
break;
default: break;
}
oldstat[N] = s;
}
// and here we can do something for all
int R = rand() % 5000;
// example of `broadcast` message
if(R < 2){
DBG("Send 'broadcasting' message");
char buf[64];
snprintf(buf, 63, "Hello, there's %.2f seconds from start\n", sl_dtime() - T0);
for(int i = 0; i < ESP_MAX_CLT_NUMBER; ++i){
if(!connected[i]) continue;
if(ESP_CLT_ERROR == esp_send(i, buf)) chkerr();
}
}
usleep(1000);
}
}
int main(int argc, char **argv){
sl_init();
sl_parseargs(&argc, &argv, options);
if(G.help) sl_showhelp(-1, options);
if(!serial_init(G.serialdev, G.speed)) ERRX("Can't open %s at speed %d", G.serialdev, G.speed);
if(G.reset){
red("Resetting, please wait!\n");
esp_reset();
usleep(500000);
DBG("Get buff");
char *str;
while((str = serial_getline(NULL, NULL))) if(*str) printf("\t%s\n", str);
signals(0);
}
if(!esp_check()) ERRX("No answer from ESP");
if(G.list){
if(esp_listAP()){
green("Available wifi:\n");
char *str;
while(str = serial_getline(NULL, NULL)) printf("\t%s\n", str);
}else WARNX("Error listing");
}
esp_cipstatus_t st = esp_cipstatus();
if(st != ESP_CIP_GOTIP && st != ESP_CIP_CONNECTED){
DBG("Need to connect");
if(!esp_connect(G.SSID, G.SSpass)) ERRX("Can't connect");
}
if(esp_myip()){
green("Connected\n");
char *str;
while(str = serial_getline(NULL, NULL)) if(*str) printf("\t%s\n", str);
}
ep_stop_server(); // stop just in case
if(!esp_start_server(G.port)) ERRX("Can't start server");
processing();
serial_close();
return 0;
}

178
ESP8266/serial.c Normal file
View File

@ -0,0 +1,178 @@
/*
* This file is part of the esp8266 project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <string.h>
#include <usefull_macros.h>
#include "serial.h"
// ALL functions here aren't thread-independent, as you can't use the same line simultaneously
static sl_tty_t *device = NULL;
static double timeout = 30.; // timeout, s
static sl_ringbuffer_t *rbin = NULL; // input ring buffer
// read all incoming
void serial_clr(){
if(!device) return;
while(sl_tty_read(device) > 0);
}
int serial_init(char *path, int speed){
device = sl_tty_new(path, speed, 256);
if(!device) return FALSE;
device = sl_tty_open(device, 1);
if(!device) return FALSE;
rbin = sl_RB_new(4096);
if(!rbin){
sl_tty_close(&device);
return FALSE;
}
sl_tty_tmout(1000); // set select() timeout to 1ms
// clear buffer
serial_clr();
return TRUE;
}
void serial_close(){
if(device) sl_tty_close(&device);
if(rbin) sl_RB_delete(&rbin);
}
int serial_set_timeout(double tms){
if(tms < 0.1) return FALSE;
timeout = tms;
return TRUE;
}
// send messages over serial,
// without EOL:
int serial_send_msg(const char *msg){
if(!msg || !device) return FALSE;
int l = strlen(msg);
DBG("Write message `%s` (%d bytes)", msg, l);
if(sl_tty_write(device->comfd, msg, l)) return FALSE;
return TRUE;
}
// and with:
int serial_send_cmd(const char *msg){
if(!msg || !device) return FALSE;
if(!serial_send_msg(msg)) return FALSE;
DBG("Write EOL");
if(sl_tty_write(device->comfd, "\r\n", 2)) return FALSE;
return TRUE;
}
int serial_putchar(char ch){
if(!device) return FALSE;
if(sl_tty_write(device->comfd, &ch, 1)) return FALSE;
return TRUE;
}
static void fillinbuff(){
ssize_t got;
while((got = sl_tty_read(device))){
if(got < 0){
WARNX("Serial device disconnected!");
serial_close();
}else if(got){
if((size_t)got != sl_RB_write(rbin, (const uint8_t*)device->buf, got)){
WARNX("Rinbguffer overflow?");
sl_RB_clearbuf(rbin);
return;
}
}
}
}
// read one string line from serial
// @arg deleted - N symbols deleted from rest of string (1 in case of '\n' and 2 in case of "\r\n")
// @arg len - strlen of data
char *serial_getline(int *len, int *deleted){
if(!device) return NULL;
static char buf[BUFSIZ];
fillinbuff();
// read old records
if(!sl_RB_readline(rbin, buf, BUFSIZ-1)) return NULL;
// remove trailing '\r'
int l = strlen(buf), d = 1;
if(l > -1 && buf[l - 1] == '\r'){
++d;
buf[--l] = 0;
}
if(deleted) *deleted = d;
if(len) *len = l;
DBG("read: '%s'", buf);
return buf;
}
// get symbol
int serial_getch(){
if(!device) return -1;
fillinbuff();
char C;
DBG("rb size: %zd", sl_RB_datalen(rbin));
size_t rd = sl_RB_read(rbin, (uint8_t*)&C, 1);
DBG("got %zd : '%c'", rd, C);
if(rd != 1) return -1;
//if(1 != sl_RB_read(rbin, (uint8_t*)&C, 1)) return -1;
return (int) C;
}
serial_ans_t serial_sendwans(const char *msg){
if(!msg || !device) return ANS_FAILED;
if(!serial_send_cmd(msg)) return ANS_FAILED;
double t0 = sl_dtime();
int ret = ANS_FAILED;
while(sl_dtime() - t0 < timeout && device){
char *ans = NULL;
if(!(ans = serial_getline(NULL, NULL))){ usleep(500); continue; }
DBG("Get line: '%s' (%zd bytes)", ans, strlen(ans));
if(!*ans) continue; // empty string
if(strcmp(ans, "OK") == 0 || strcmp(ans, "SEND OK") == 0){ ret = ANS_OK; goto rtn; }
if(strcmp(ans, "ERROR") == 0){ ret = ANS_ERR; goto rtn; }
if(strcmp(ans, "FAIL") == 0){ ret = ANS_FAILED; goto rtn; }
DBG("Return '%s' into buff", ans);
sl_RB_writestr(rbin, ans); // put other data into ringbuffer for further processing
sl_RB_putbyte(rbin, '\n');
}
rtn:
DBG("proc time: %g", sl_dtime() - t0);
return ret;
}
// return NULL if `s` don't contain `t`, else return next symbol in `s`
const char *serial_tidx(const char *s, const char *t){
if(!s) return NULL;
DBG("check '%s' for '%s'", s, t);
int pos = 0;
if(t){
const char *sub = strstr(s, t);
if(!sub) return NULL;
int l = strlen(t);
pos = (sub - s) + l;
}
DBG("pos = %d", pos);
return s + pos;
}
int serial_s2i(const char *s){
if(!s) return -1;
DBG("conv '%s' to %d", s, atoi(s));
return atoi(s);
}

40
ESP8266/serial.h Normal file
View File

@ -0,0 +1,40 @@
/*
* This file is part of the esp8266 project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
typedef enum{
ANS_FAILED,
ANS_OK,
ANS_ERR
} serial_ans_t;
void serial_clr();
int serial_set_timeout(double tms);
int serial_init(char *path, int speed);
void serial_close();
int serial_send_msg(const char *msg);
int serial_send_cmd(const char *msg);
int serial_putchar(char ch);
serial_ans_t serial_sendwans(const char *msg);
char *serial_getline(int *len, int *deleted);
int serial_getch();
// conversion functions for ESP
const char *serial_tidx(const char *s, const char *t);
int serial_s2i(const char *s);