including tftp based shell and sockets from different project

This commit is contained in:
2024-12-15 12:09:32 +01:00
parent bd923e4fdc
commit 1cf86768c0
26 changed files with 2917 additions and 10 deletions
Binary file not shown.
Binary file not shown.
+2 -4
View File
@@ -2,8 +2,6 @@
"C_Cpp.clang_format_fallbackStyle": "{ BasedOnStyle: LLVM, UseTab: Never, IndentWidth: 4, TabWidth: 4, AllowShortIfStatementsOnASingleLine: false, IndentCaseLabels: false, ColumnLimit: 0, AccessModifierOffset: -2, NamespaceIndentation: All, FixNamespaceComments: false }",
"C_Cpp.intelliSenseEngine": "default",
"idf.adapterTargetName": "esp32",
"idf.customExtraPaths": "C:\\Tools\\ESP\\tools\\xtensa-esp-elf-gdb\\14.2_20240403\\xtensa-esp-elf-gdb\\bin;C:\\Tools\\ESP\\tools\\riscv32-esp-elf-gdb\\14.2_20240403\\riscv32-esp-elf-gdb\\bin;C:\\Tools\\ESP\\tools\\xtensa-esp-elf\\esp-13.2.0_20240530\\xtensa-esp-elf\\bin;C:\\Tools\\ESP\\tools\\riscv32-esp-elf\\esp-13.2.0_20240530\\riscv32-esp-elf\\bin;C:\\Tools\\ESP\\tools\\esp32ulp-elf\\2.38_20240113\\esp32ulp-elf\\bin;C:\\Tools\\ESP\\tools\\cmake\\3.24.0\\bin;C:\\Tools\\ESP\\tools\\openocd-esp32\\v0.12.0-esp32-20240318\\openocd-esp32\\bin;C:\\Tools\\ESP\\tools\\ninja\\1.11.1;C:\\Tools\\ESP\\tools\\idf-exe\\1.0.3;C:\\Tools\\ESP\\tools\\ccache\\4.8\\ccache-4.8-windows-x86_64;C:\\Tools\\ESP\\tools\\dfu-util\\0.11\\dfu-util-0.11-win64;C:\\Tools\\ESP\\tools\\esp-rom-elfs\\20240305",
"idf.customExtraVars": {
"OPENOCD_SCRIPTS": "C:\\Tools\\ESP\\tools\\openocd-esp32\\v0.12.0-esp32-20240318/openocd-esp32/share/openocd/scripts",
"IDF_CCACHE_ENABLE": "1",
@@ -14,8 +12,8 @@
"board/esp32c6-builtin.cfg"
],
"idf.portWin": "COM9",
"idf.pythonBinPathWin": "C:\\Tools\\ESP\\python_env\\idf5.3_py3.11_env\\Scripts\\python.exe",
"idf.toolsPathWin": "C:\\Tools\\ESP",
"idf.flashType": "UART",
"C_Cpp.autoAddFileAssociations": false
"C_Cpp.autoAddFileAssociations": false,
"project.deviceIp": "172.16.17.56"
}
+12
View File
@@ -254,6 +254,18 @@
}
}
}
},
{
"label": "Start TeraTerm Telnet",
"type": "shell",
"command": "C:/Program Files (x86)/teraterm5/ttermpro.exe",
"args": [
"telnet://${config:project.deviceIp}:23",
"/W=\"Device-Platform\"",
"/T=1",
"/F=\"${workspaceFolder}/.configs/TelnetTeraTerm.INI\""
],
"problemMatcher": []
}
]
}
+2 -2
View File
@@ -1,4 +1,4 @@
idf_component_register(
SRC_DIRS "."
INCLUDE_DIRS "."
SRC_DIRS "src"
INCLUDE_DIRS "include"
)
@@ -0,0 +1,36 @@
/**
* @file countDown.hpp
* @author Gustice
* @brief Classes for typical index manipulation
*
* @copyright Copyright (c) 2020
*/
#pragma once
class CountDown {
public:
CountDown(unsigned max) : Max(max) {}
void Tick() {
_cnt++;
if (_cnt >= Max) {
_cnt = 0;
_pFlag = true;
}
}
bool GetVolatileFlag() {
if (!_pFlag)
return false;
// Reset _pFlag in any case, so it only be consumed once
// hence volatile
_pFlag = false;
return true;
}
private:
const unsigned Max;
unsigned _cnt;
bool _pFlag;
};
@@ -0,0 +1,58 @@
/**
* @file rotatingIndex.hpp
* @author Gustice
* @brief Classes for typical index manipulation
*
* @copyright Copyright (c) 2020
*/
#pragma once
#include <cstdint>
/**
* @brief Index with automatic revolution back to start position
* @details Useful for Buffer-Pointer, or similar tasks
*/
class RotatingIndex {
public:
/// @brief Value at witch the index rotates back to beginning
const uint16_t MaxIndex;
/// @brief Constructor for rotating index
/// @brief output index will be set to 0 if incremented value reaches Maximum
/// @param max Maximum index for overflow-condition
RotatingIndex(uint16_t max) : MaxIndex(max) { _index = 0; }
/// @brief Get the current index and inkrement index afterwards
/// @return uint16_t current index
uint16_t getIndexAndIncrement(void) {
auto retVal = _index;
_index++;
if (_index >= MaxIndex) {
_index = 0;
}
return retVal;
};
/// @brief Get the current index without incrementing the index
/// @return uint16_t current index
uint16_t getIndex(void) { return _index; }
/// @brief Check if next get will lead to overflow condition
bool peekRevolution(void) {
if (MaxIndex == 0)
return true;
if (_index >= MaxIndex - 1) {
return true;
}
return false;
}
/// @brief Reset Index to 0
void resetIndex(void) { _index = 0; }
private:
/// Current Index
uint16_t _index;
};
@@ -0,0 +1,95 @@
/**
* @file utils.hpp
* @author Gustice
* @brief Template
*
* @copyright Copyright (c) 2024
*/
#pragma once
#include <cassert>
#include <exception>
#include <string>
#include <vector>
#include <cstdint>
/**
* @brief Simple exception
*/
class CoreException : public std::exception {
char const *Text;
public:
/// @brief Constructor with simple pointer to text
CoreException(char const *text) : Text(text) {
}
/// @brief error message getter
char const *what() const throw() override { return Text; }
};
/**
* @brief Common interface for character streams
*/
class CharStream {
public:
/// @brief Read method
/// @return string
virtual std::string read() = 0;
/// @brief Write character
/// @param c character to write
virtual void write(char c) = 0;
/// @brief Write string
/// @param str string to write
virtual void write(std::string str) = 0;
};
/**
* @brief Common interface
*/
class ByteStream {
/// @brief Read method
/// @return series of bytes
virtual std::vector<uint8_t> read() = 0;
/// @brief write single byte
/// @param b byte to write
virtual void write(uint8_t b) = 0;
/// @brief write series of bytes
/// @param data series to write
virtual void write(std::vector<uint8_t> data) = 0;
};
/**
* @brief Base for constructs that allow only one concurrently active instance
*
* @tparam T
* @note needs a `T * T::SingleActive<T>::active{};` definition in source
*/
template <typename T>
class SingleActive {
static bool active;
public:
/// @brief Constructor, memorizes if already on instance was successfully build
SingleActive() {
if (active) {
throw "Instance already active";
}
assert(active == false && "Constructor my only be run if no instance active");
active = true;
}
/// @brief Destructor, clears memory of existing instance
~SingleActive() {
assert(active == true && "Destructor my only be run on active instance");
active = false;
}
// Disable move and assignment
SingleActive(SingleActive &other) = delete;
void operator=(const SingleActive &) = delete;
};
+9
View File
@@ -0,0 +1,9 @@
/**
* @file utils.cpp
* @author Gustice
* @brief Implementation of utilities
*
* @copyright Copyright (c) 2024
*/
#include "utils.hpp"
+5
View File
@@ -0,0 +1,5 @@
idf_component_register(
SRC_DIRS "src"
INCLUDE_DIRS "include"
REQUIRES esp_netif lwip project_bases freeRtosApi
)
+196
View File
@@ -0,0 +1,196 @@
/**
* @file etherCommon.hpp
* @author Gustice
* @brief Helper Functions for Ethernet usage
*
* @copyright Copyright (c) 2024
*/
#pragma once
#include "esp_log.h"
#include "esp_netif.h"
#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include <array>
#include <cstring>
#include <lwip/netdb.h>
#include <string>
#if defined(CONFIG_LWIP_NETBUF_RECVINFO)
constexpr bool NETBUF_RECVINFO = true;
#else
constexpr bool NETBUF_RECVINFO = false;
#endif
#if defined(CONFIG_EXAMPLE_IPV4)
constexpr bool USE_IPV4 = true;
#else
constexpr bool USE_IPV4 = false;
#endif
#if defined(CONFIG_EXAMPLE_IPV6)
constexpr bool USE_IPV6 = true;
#else
constexpr bool USE_IPV6 = false;
#endif
class Ip {
public:
Ip(uint32_t ipV4) {
_address.word = ipV4;
}
Ip(esp_ip4_addr_t ipV4) {
_address.word = ipV4.addr;
}
Ip(uint8_t b0, uint8_t b1, uint8_t b2, uint8_t b3) {
_address.bytes[0] = b0;
_address.bytes[1] = b1;
_address.bytes[2] = b2;
_address.bytes[3] = b3;
}
Ip(std::string ipStr) {
_address.word = ipaddr_addr(ipStr.c_str());
}
uint8_t operator[](int index) const { return _address.bytes[index]; };
uint32_t asWord() const { return _address.word; }
std::string asString() const { return inet_ntoa(_address.word); }
private:
union {
uint8_t bytes[4]; // IPv4 address
uint32_t word;
} _address;
};
class Mac {
public:
Mac(uint8_t val[NETIF_MAX_HWADDR_LEN]) {
std::memcpy(bytes.begin(), val, NETIF_MAX_HWADDR_LEN);
}
uint8_t operator[](int index) const {
return bytes[index];
};
std::string asString() const {
char buffer[64];
sprintf(buffer, "%02x:%02x:%02x:%02x:%02x:%02x", bytes[0], bytes[1],
bytes[2], bytes[3], bytes[4], bytes[5]);
return {buffer};
}
const std::array<uint8_t, NETIF_MAX_HWADDR_LEN> &asArray() const {
return bytes;
}
private:
std::array<uint8_t, NETIF_MAX_HWADDR_LEN> bytes; // IPv4 address
};
/**
* @brief IP-set class
*/
struct IpInfo {
/// @brief IP address
Ip address{0u};
/// @brief IP subnet
Ip subnet{0u};
/// @brief Gateway IP
Ip gateway{0u};
/// @brief MAC-Address
Mac macAddress;
/// @brief Get broadcast address from IP and subnet
/// @return
Ip getBradcastAddress() const {
auto sn = address.asWord() | ~subnet.asWord();
return Ip(sn);
}
};
struct IpAdr {
public:
IpAdr(std::string ipStr, uint16_t port);
IpAdr(uint32_t ip, uint16_t port);
IpAdr(uint16_t port);
IpAdr(sockaddr_in ipV4data);
static constexpr int IpProtocol = IPPROTO_IP; // IPPROTO_IPV6 for version 6
static constexpr int ProtocolFamily = AF_INET; // AF_INET6 for version 6
/// @brief StackFamily e.g. AF_INET or AF_INET6
sa_family_t getProtocolFamily() const { return addrStore.ss_family; }
std::string getIp() {
struct sockaddr_in *ipV4 = (struct sockaddr_in *)&addrStore;
return inet_ntoa(ipV4->sin_addr.s_addr);
}
uint16_t getPort() const {
struct sockaddr_in *ipV4 = (struct sockaddr_in *)&addrStore;
return ntohs(ipV4->sin_port);
};
struct sockaddr *asSockAddrPtr() const { return (struct sockaddr *)&addrStore; }
static constexpr int Size = sizeof(sockaddr_storage);
private:
struct sockaddr_storage addrStore;
};
struct IpAdrV6 {
public:
IpAdrV6(std::string ipStr, uint16_t port);
IpAdrV6(in6_addr ip, uint16_t port);
IpAdrV6(uint16_t port);
IpAdrV6(sockaddr_in6 ipV6data);
static constexpr int IpProtocol = IPPROTO_IPV6;
static constexpr int ProtocolFamily = AF_INET6;
/// @brief StackFamily e.g. AF_INET or AF_INET6
sa_family_t getProtocolFamily() const { return addrStore.ss_family; }
std::string getIp() {
struct sockaddr_in6 *ipV6 = (struct sockaddr_in6 *)&addrStore;
return inet6_ntoa(ipV6->sin6_addr);
}
uint16_t getPort() const {
struct sockaddr_in6 *ipV6 = (struct sockaddr_in6 *)&addrStore;
return ntohs(ipV6->sin6_port);
};
struct sockaddr *asSockAddrPtr() const { return (struct sockaddr *)&addrStore; }
static constexpr int Size = sizeof(sockaddr_storage);
private:
struct sockaddr_storage addrStore;
};
/**
* @brief Simplified abstraction for sockets
*/
class SocketBase {
public:
/// @brief Get if stream is active
/// @return true if active
virtual bool isActive() {
return active;
}
IpInfo getIpInfo() {
auto *netIf = esp_netif_get_default_netif();
esp_netif_ip_info_t ip;
uint8_t mac[NETIF_MAX_HWADDR_LEN];
ESP_ERROR_CHECK(esp_netif_get_ip_info(netIf, &ip));
ESP_ERROR_CHECK(esp_netif_get_mac(netIf, mac));
IpInfo ips{
.address = Ip{ip.ip},
.subnet = Ip(ip.netmask),
.gateway = Ip(ip.netmask),
.macAddress = Mac(mac)};
return ips;
}
protected:
bool active;
};
+307
View File
@@ -0,0 +1,307 @@
/**
* @file tcpSockets.hpp
* @author Gustice
* @brief Function wrapper for ESP-IDF socket implementation
*
* @copyright Copyright (c) 2024
*/
#pragma once
#include "esp_log.h"
#include "etherCommon.hpp"
#include "utils.hpp"
#include <functional>
#include <iostream>
/**
* @brief TCP-Socket wrapper for ESP-IDF socket implementation
*/
class TcpSocket {
public:
/// @brief Constructor
/// @note Socket not active until call of attach method
/// @param addr Server/Client address
TcpSocket(const IpAdr &addr)
: Local(addr) {
_listenSockId = socket(addr.ProtocolFamily, SOCK_STREAM, addr.IpProtocol);
if (_listenSockId < 0) {
ESP_LOGE("TCP", "Unable to create socket: errno %d", errno);
throw CoreException("Unable to create socket");
}
if (addr.ProtocolFamily == IPPROTO_IP) {
int opt = 1;
setsockopt(_listenSockId, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
}
if constexpr (USE_IPV4 && USE_IPV6) {
int opt = 1;
// Note that by default IPV6 binds to both protocols, it is must be disabled
// if both protocols used at the same time (used in CI)
setsockopt(_listenSockId, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
}
ESP_LOGI("TCP", "Socket created");
}
/// @brief Destructor
~TcpSocket() {
close(_listenSockId);
}
/// @brief Attach to configured socket
/// @note Blocks until successful or failed
/// @return true if successful
bool bindTo() {
int err = bind(_listenSockId, Local.asSockAddrPtr(), sizeof(sockaddr_storage));
if (err != 0) {
ESP_LOGE("TCP", "Socket unable to bind: errno %d", errno);
ESP_LOGE("TCP", "IPPROTO: %d", Local.ProtocolFamily);
throw CoreException("Socket unable to bind");
}
ESP_LOGI("TCP", "Socket bound, port %d", Local.getPort());
err = listen(_listenSockId, 1);
if (err != 0) {
ESP_LOGE("TCP", "Error occurred during listen: errno %d", errno);
throw CoreException("Error occurred during listen");
}
return true;
}
/// @brief Get socket id for stream-operations
/// @return Id of socket
int getSocketId() {
return _listenSockId;
}
/// @brief Address of this station (server perspective)
const IpAdr &Local;
private:
int _listenSockId;
};
/**
* @brief TCP session
*/
class TcpSession : public SocketBase, public CharStream {
public:
/**
* @brief Configuration structure for Session
*/
struct Config {
int keepAlive;
int keepIdle;
int keepInterval;
int keepCount;
};
/// @brief Constructor
/// @note Constructor blocks
/// @param config Configuration for session
/// @param listener reference to socket
TcpSession(Config &config, TcpSocket &listener) : _other(IPADDR_ANY, 0) {
socklen_t addr_len = _other.Size;
_socket = accept(listener.getSocketId(), _other.asSockAddrPtr(), &addr_len);
if (_socket < 0) {
ESP_LOGE("TCP", "Unable to accept connection: errno %d", errno);
active = false;
throw CoreException("Unable to accept connection");
}
// Set tcp keepalive option
setsockopt(_socket, SOL_SOCKET, SO_KEEPALIVE, &config.keepAlive, sizeof(int));
setsockopt(_socket, IPPROTO_TCP, TCP_KEEPIDLE, &config.keepIdle, sizeof(int));
setsockopt(_socket, IPPROTO_TCP, TCP_KEEPINTVL, &config.keepInterval, sizeof(int));
setsockopt(_socket, IPPROTO_TCP, TCP_KEEPCNT, &config.keepCount, sizeof(int));
std::string addrStr;
if (_other.ProtocolFamily == listener.Local.ProtocolFamily) {
if (_other.getProtocolFamily() == PF_INET) {
addrStr = _other.getIp();
// } else if (_other.addrStore.ss_family == PF_INET6) {
// inet6_ntoa_r(((struct sockaddr_in6 *)&_other.addrStore)->sin6_addr, addrStr, sizeof(addrStr) - 1);
} else {
throw CoreException("Unknown case");
}
} else {
ESP_LOGW("TCP", "Wrong IP-version request");
active = false;
}
ESP_LOGI("TCP", "Socket accepted ip address: %s", addrStr.c_str());
active = true;
}
/// @brief Destructor
~TcpSession() {
if (_socket < 0)
return;
ESP_LOGD("TCP", "Tearing down socket");
shutdown(_socket, 0);
}
/// @brief Get IP of remote
/// @return
IpAdr getSource() {
return _other;
}
/// @brief Read string
/// @note This call blocks until data is received
/// @return read string
std::string read() override {
auto data = readRaw();
if (data.size() == 0) {
return "";
}
std::string ret((const char *)&data.data()[0]);
return ret;
}
/// @brief Write character to socket
/// @param b character to write
void write(char c) override {
if (!isActive()) {
return;
}
int written = send(_socket, &c, 1, 0);
if (written < 0) {
ESP_LOGE("TCP", "Error occurred during sending: errno %d", errno);
}
}
/// @brief Write string to specific socket
/// @param message payload
/// @param dest Destination for message
void write(std::string message) override {
if (!isActive()) {
return;
}
int written = send(_socket, message.c_str(), message.size(), 0);
if (written < 0) {
ESP_LOGE("TCP", "Error occurred during sending: errno %d", errno);
}
}
IpAdr getOther() {
return _other;
}
protected:
int _socket = 0;
IpAdr _other;
std::vector<uint8_t> readRaw() {
uint8_t data[128];
int len = recv(_socket, data, sizeof(data) - 1, 0);
if (len < 0) {
ESP_LOGE("TCP", "Error occurred during receiving: errno %d", errno);
active = false;
return {};
}
data[len] = 0;
if (len == 0) {
ESP_LOGW("TCP", "Connection closed");
active = false;
return {};
}
ESP_LOGD("TCP", "Received %d bytes: %s", len, data);
std::vector<uint8_t> vec(data, data + len + 1);
return vec;
}
};
constexpr TcpSession::Config DefaultTcpConfig{
.keepAlive = 1,
.keepIdle = 5,
.keepInterval = 5,
.keepCount = 3,
};
constexpr uint16_t TelnetPort = 23;
/**
* @brief TCP-Socket for Telnet-Applications
*/
class TelnetServer {
/**
* @brief Specialization of TCP-Session for Telnet
*/
class TelnetSession : public TcpSession {
public:
// Constructor
TelnetSession(Config &config, TcpSocket &listener) : TcpSession(config, listener) {
}
/// @brief Read string
/// @note This call blocks until data is received
/// @return read string
std::string read() override {
auto data = readRaw();
if (data.size() == 0) {
return "";
}
data.pop_back(); // 0-termination is not needed here
int i = 0;
while (data[i] == 0xFF) { // while telnet command
i += 3; // skip this command
}
if (i == data.size()) { // only configuration
send(_socket, data.data(), data.size(), 0);
return "";
}
std::string ret((const char *)&data[i]);
return ret;
}
};
public:
/// @brief Constructor
/// @param addrFamily IPV4 or IPV6
TelnetServer(int addrFamily)
: Local(addrFamily == AF_INET ? IpAdr(INADDR_ANY, TelnetPort) : IpAdr(TelnetPort)),
Config(DefaultTcpConfig) {
}
/**
* @brief Start server
*
* @param initCall Callback for shell init
* @param cyclicCall Callback for cyclic polling
*/
void run(std::function<void(TcpSession &)> initCall, std::function<void(TcpSession &)> cyclicCall) {
TcpSocket socket(Local);
socket.bindTo();
while (true) {
// Run telnet-session as long socket is active else restart socket
ESP_LOGI("Telnet", "Socket waiting for connect");
TelnetSession session(Config, socket);
initCall(session);
while (session.isActive()) {
cyclicCall(session);
}
}
}
private:
IpAdr Local;
TcpSession::Config Config;
};
+303
View File
@@ -0,0 +1,303 @@
/**
* @file udpSockets.hpp
* @author Gustice
* @brief UDP-Socket wrapper for ESP-IDF socket implementation
*
* @copyright Copyright (c) 2024
*/
#pragma once
#include "Task.hpp"
#include "esp_log.h"
#include "etherCommon.hpp"
#include "utils.hpp"
#include <cstdarg>
#include <iostream>
#include <string>
constexpr bool UDP_WITH_TIMEOUT = false;
/**
* @brief UDP-Socket wrapper for ESP-IDF socket implementation
*/
class UdpSocket : public ByteStream, public SocketBase {
public:
/// @brief Constructor for UDP-Socket-Server
/// @note Socket not active until call of attach method
/// @param addr Server/Client address
UdpSocket(const IpAdr &addr)
: Local(addr), _other(INADDR_ANY, 0) {
_listenSockId = socket(addr.ProtocolFamily, SOCK_DGRAM, addr.IpProtocol);
if (_listenSockId < 0) {
ESP_LOGE("UDP", "Unable to create socket: errno %d", errno);
throw CoreException("Unable to create socket");
}
ESP_LOGD("UDP", "Socket created");
if constexpr (NETBUF_RECVINFO && !USE_IPV6) {
int enable = 1;
lwip_setsockopt(_listenSockId, IPPROTO_IP, IP_PKTINFO, &enable, sizeof(enable));
}
if constexpr (USE_IPV4 && USE_IPV6) {
if (addr.ProtocolFamily == AF_INET6) {
// Note that by default IPV6 binds to both protocols, it is must be disabled
// if both protocols used at the same time (used in CI)
int opt = 1;
setsockopt(_listenSockId, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
setsockopt(_listenSockId, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
}
}
if constexpr (UDP_WITH_TIMEOUT) { // Set timeout if desired
struct timeval timeout {
.tv_sec = 10,
.tv_usec = 0,
};
setsockopt(_listenSockId, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
}
}
/// @brief Destructor
~UdpSocket() {
active = false;
ESP_LOGD("UDP", "Shutting down socket and restarting...");
shutdown(_listenSockId, 0);
close(_listenSockId);
}
/// @brief Attach to configured socket
/// @note Blocks until successful or failed
/// @return true if successful
bool bindTo() {
active = false;
int err = bind(_listenSockId, Local.asSockAddrPtr(), sizeof(sockaddr_storage));
if (err < 0) {
ESP_LOGE("UDP", "Socket unable to bind: errno %d", errno);
throw CoreException("Socket unable to bind");
}
ESP_LOGI("UDP", "Socket bound, port %d", Local.getPort());
if constexpr (NETBUF_RECVINFO && !USE_IPV6) {
_iov.iov_base = _rxBuffer;
_iov.iov_len = sizeof(_rxBuffer);
_msg.msg_control = _cmsg_buf;
_msg.msg_controllen = sizeof(_cmsg_buf);
_msg.msg_flags = 0;
_msg.msg_iov = &_iov;
_msg.msg_iovlen = 1;
_msg.msg_name = _other.asSockAddrPtr();
_msg.msg_namelen = _other.Size;
}
active = true;
return active;
}
/// @brief Read from socket
/// @note This call blocks until data is received
/// @return read bytes
std::vector<uint8_t> read() override {
char buffer[256];
int len = 0;
if constexpr (NETBUF_RECVINFO && !USE_IPV6) {
len = recvmsg(_listenSockId, &_msg, 0);
} else {
socklen_t socklen = _other.Size;
len = recvfrom(_listenSockId, buffer, sizeof(buffer) - 1, 0, _other.asSockAddrPtr(), &socklen);
}
if (len < 0) {
ESP_LOGE("UDP", "recvfrom failed: errno %d", errno);
return {};
}
std::string addrStr;
// Get the sender's ip address as string
if (Local.ProtocolFamily == PF_INET) {
addrStr = _other.getIp();
if constexpr (NETBUF_RECVINFO && !USE_IPV6) {
struct cmsghdr *cmsgtmp;
for (cmsgtmp = CMSG_FIRSTHDR(&_msg); cmsgtmp != NULL; cmsgtmp = CMSG_NXTHDR(&_msg, cmsgtmp)) {
if (cmsgtmp->cmsg_level == IPPROTO_IP && cmsgtmp->cmsg_type == IP_PKTINFO) {
struct in_pktinfo *pktinfo;
pktinfo = (struct in_pktinfo *)CMSG_DATA(cmsgtmp);
ESP_LOGI("UDP", "dest ip: %s", inet_ntoa(pktinfo->ipi_addr));
}
}
}
} // else if (Local.ProtocolFamily == PF_INET6) {
// inet6_ntoa_r(((struct sockaddr_in6 *)&_other.addrStore)->sin6_addr, addrStr, sizeof(addrStr) - 1);
// }
ESP_LOGD("UDP", "Received %d bytes from %s:", len, addrStr.c_str());
std::vector<uint8_t> ret(buffer, buffer + len);
return ret;
}
/// @brief Write character to socket
/// @param b character to write
void write(uint8_t b) override {}
/// @brief Write bytes to socket
/// @param data payload as series of bytes
void write(std::vector<uint8_t> data) override {
write(&data[0], data.size());
}
/// @brief Write string to specific socket
/// @param message payload
/// @param dest Destination for message
void write(std::string &message, IpAdr *dest = nullptr) {
auto data = message.c_str();
write((uint8_t *)data, message.size() + 1, dest);
}
/// @brief Write string to specific socket or to last source
/// @param data payload
/// @param size size of data
/// @param addr Destination for message (if nullptr it will be send to last source)
void write(uint8_t *data, uint size, IpAdr *dest = nullptr) {
int err;
if (dest == nullptr) {
err = sendto(_listenSockId, data, size, 0, _other.asSockAddrPtr(), sizeof(sockaddr_storage));
} else {
struct sockaddr_in *ipV4 = (struct sockaddr_in *)dest->asSockAddrPtr();
// ESP_LOGI("UDP", "EthIP: %s", inet_ntoa(ipV4->sin_addr.s_addr));
// ESP_LOGI("UDP", "PORT: %i", ntohs(ipV4->sin_port));
err = sendto(_listenSockId, data, size, 0, dest->asSockAddrPtr(), sizeof(sockaddr_storage));
}
if (err < 0) {
ESP_LOGE("UDP", "Error occurred during sending: errno %d", errno);
active = false;
}
}
/// @brief Standard destination address
const IpAdr &Local;
/// @brief query if stream is still active
/// @return true if active
bool isValid() {
return _listenSockId >= 0;
}
IpAdr getOther() {
return _other;
}
private:
int _listenSockId; // os handle for socket
IpAdr _other; // Remote Station
char _rxBuffer[128];
// Only used if NETBUF_RECVINFO is active
iovec _iov;
msghdr _msg;
u8_t _cmsg_buf[CMSG_SPACE(sizeof(struct in_pktinfo))];
};
/**
* @brief
* @details according to RFC-5424
* https://datatracker.ietf.org/doc/html/rfc5424
*/
class Syslog {
public:
static constexpr uint16_t SyslogPort = 514;
static constexpr std::string_view NilValue = "-";
enum class Severity {
Emergency = 0,
Alert = 1,
Critical = 2,
Error = 3,
Warning = 4,
Notice = 5,
Informational = 6,
Debug = 7,
};
static constexpr uint8_t SeverityMask = 0x000F;
enum class Facility {
kern = 0 << 3,
User = 1 << 3,
Mail = 2 << 3,
Daemon = 3 << 3,
Auth = 4 << 3,
Syslog = 5 << 3,
Lpr = 6 << 3,
News = 7 << 3,
Uucp = 8 << 3,
Cron = 9 << 3,
Authpriv = 10 << 3,
Ftp = 11 << 3,
Ntp = 12 << 3,
LogAudit = 13 << 3,
LogAlert = 14 << 3,
Clock = 15 << 3,
Local0 = 16 << 3,
Local1 = 17 << 3,
Local2 = 18 << 3,
Local3 = 19 << 3,
Local4 = 20 << 3,
Local5 = 21 << 3,
Local6 = 22 << 3,
Local7 = 23 << 3,
};
static void log(Severity severity, std::string message) {
if (_Socket == nullptr) {
return;
}
char buffer[512];
// V T H A P M D Msg
// alternatively with BOM flag UTF8 \xEF\xBB\xBF
auto pos = sprintf(buffer, "<%i>1 - %s %s - - - %s",
static_cast<int>(_Facility) | static_cast<int>(severity),
_HostName.c_str(),
_AppName.c_str(),
message.c_str());
_Socket->write((uint8_t *)buffer, pos, &_Target);
}
template <typename...Params>
static void logv(Severity severity, const char *fmt, Params&&... args) {
if (_Socket == nullptr) {
return;
}
char buffer[512];
// V T H A P M D Msg
// alternatively with BOM flag UTF8 \xEF\xBB\xBF
auto pos = sprintf(buffer, "<%i>1 - %s %s - - - ",
static_cast<int>(_Facility) | static_cast<int>(severity),
_HostName.c_str(),
_AppName.c_str());
pos += sprintf(&buffer[pos], fmt, std::forward<Params> (args)...);
_Socket->write((uint8_t *)buffer, pos, &_Target);
}
static void setupTask(IpAdr target) {
if (_Socket) {
return;
}
_Facility = Facility::Syslog;
_HostName = "ESP";
_AppName = "App";
_Target = IpAdr{target.getIp(), SyslogPort};
static fos::Task syslogTask(SyslogTask, "SysLog", 4096);
}
private:
static void SyslogTask();
static Facility _Facility;
static std::string _HostName;
static std::string _AppName;
static UdpSocket *_Socket;
static IpAdr _Target;
};
+68
View File
@@ -0,0 +1,68 @@
/**
* @file etherCommon.cpp
* @author Gustice
* @brief Helper Functions for Ethernet usage
*
* @copyright Copyright (c) 2024
*/
#include "etherCommon.hpp"
#include <cassert>
IpAdr::IpAdr(std::string ipStr, uint16_t port)
: IpAdr(ipaddr_addr(ipStr.c_str()), port) {
}
IpAdr::IpAdr(uint32_t ip, uint16_t port) {
struct sockaddr_in *ipV4 = (struct sockaddr_in *)&addrStore;
ipV4->sin_len = sizeof(sockaddr_in);
ipV4->sin_addr.s_addr = ip;
ipV4->sin_family = AF_INET;
ipV4->sin_port = htons(port);
}
IpAdr::IpAdr(uint16_t port) {
struct sockaddr_in *ipV4 = (struct sockaddr_in *)&addrStore;
ipV4->sin_len = sizeof(sockaddr_in);
ipV4->sin_addr.s_addr = INADDR_ANY;
ipV4->sin_family = AF_INET;
ipV4->sin_port = htons(port);
}
IpAdr::IpAdr(sockaddr_in ipV4data) {
struct sockaddr_storage *store = (struct sockaddr_storage *)&ipV4data;
assert(ipV4data.sin_family == AF_INET);
addrStore = *store;
}
IpAdrV6::IpAdrV6(std::string ipStr, uint16_t port) {
struct sockaddr_in6 *ipV6 = (struct sockaddr_in6 *)&addrStore;
ipV6->sin6_len = sizeof(sockaddr_in6);
inet6_aton(ipStr.c_str(), &ipV6->sin6_addr);
ipV6->sin6_family = AF_INET6;
ipV6->sin6_port = htons(port);
}
IpAdrV6::IpAdrV6(in6_addr ip, uint16_t port) {
struct sockaddr_in6 *ipV6 = (struct sockaddr_in6 *)&addrStore;
ipV6->sin6_len = sizeof(sockaddr_in6);
memcpy(&ip, &ipV6->sin6_addr, sizeof(in6_addr));
ipV6->sin6_family = AF_INET6;
ipV6->sin6_port = htons(port);
}
IpAdrV6::IpAdrV6(uint16_t port) {
struct sockaddr_in6 *ipV6 = (struct sockaddr_in6 *)&addrStore;
ipV6->sin6_len = sizeof(sockaddr_in6);
// ip6_addr_set_any()
bzero(&ipV6->sin6_addr.un, sizeof(ipV6->sin6_addr.un));
ipV6->sin6_family = AF_INET6;
ipV6->sin6_port = htons(port);
}
IpAdrV6::IpAdrV6(sockaddr_in6 ipV6data) {
struct sockaddr_storage *store = (struct sockaddr_storage *)&ipV6data;
assert(ipV6data.sin6_family == AF_INET);
addrStore = *store;
}
+9
View File
@@ -0,0 +1,9 @@
/**
* @file tcpSockets.cpp
* @author Gustice
* @brief Function frames for handling sockets
*
* @copyright Copyright (c) 2024
*/
#include "tcpSockets.hpp"
+35
View File
@@ -0,0 +1,35 @@
/**
* @file udpSockets.cpp
* @author Gustice
* @brief Function frames for handling sockets
*
* @copyright Copyright (c) 2024
*/
#include "udpSockets.hpp"
UdpSocket * Syslog::_Socket;
Syslog::Facility Syslog::_Facility;
std::string Syslog::_HostName;
std::string Syslog::_AppName;
IpAdr Syslog::_Target(INADDR_ANY, 0);
void Syslog::SyslogTask() {
ESP_LOGI("SysLog", "Starting Syslog Task");
try {
while (true) {
IpAdr ip(INADDR_ANY, SyslogPort);
UdpSocket socket(ip);
_Socket = &socket;
socket.bindTo();
while (socket.isActive()) {
(void)socket.read();
// ignore all incomming data
}
}
_Socket = nullptr;
} catch (const std::exception &e) {
ESP_LOGE("SysLog", "%s", e.what());
}
}
+4
View File
@@ -0,0 +1,4 @@
idf_component_register(
SRC_DIRS "extern"
INCLUDE_DIRS "include" "extern"
)
File diff suppressed because it is too large Load Diff
+312
View File
@@ -0,0 +1,312 @@
#ifndef EMBEDDED_CLI_H
#define EMBEDDED_CLI_H
#ifdef __cplusplus
extern "C" {
#else
#include <stdbool.h>
#endif
// cstdint is available only since C++11, so use C header
#include <stdint.h>
// used for proper alignment of cli buffer
#if UINTPTR_MAX == 0xFFFF
#define CLI_UINT uint16_t
#elif UINTPTR_MAX == 0xFFFFFFFF
#define CLI_UINT uint32_t
#elif UINTPTR_MAX == 0xFFFFFFFFFFFFFFFFu
#define CLI_UINT uint64_t
#else
#error unsupported pointer size
#endif
#define CLI_UINT_SIZE (sizeof(CLI_UINT))
// convert size in bytes to size in terms of CLI_UINTs (rounded up
// if bytes is not divisible by size of single CLI_UINT)
#define BYTES_TO_CLI_UINTS(bytes) \
(((bytes) + CLI_UINT_SIZE - 1)/CLI_UINT_SIZE)
typedef struct CliCommand CliCommand;
typedef struct CliCommandBinding CliCommandBinding;
typedef struct EmbeddedCli EmbeddedCli;
typedef struct EmbeddedCliConfig EmbeddedCliConfig;
struct CliCommand {
/**
* Name of the command.
* In command "set led 1 1" "set" is name
*/
const char *name;
/**
* String of arguments of the command.
* In command "set led 1 1" "led 1 1" is string of arguments
* Is ended with double 0x00 char
* Use tokenize functions to easily get individual tokens
*/
char *args;
};
/**
* Struct to describe binding of command to function and
*/
struct CliCommandBinding {
/**
* Name of command to bind. Should not be NULL.
*/
const char *name;
/**
* Help string that will be displayed when "help <cmd>" is executed.
* Can have multiple lines separated with "\r\n"
* Can be NULL if no help is provided.
*/
const char *help;
/**
* Flag to perform tokenization before calling binding function.
*/
bool tokenizeArgs;
/**
* Pointer to any specific app context that is required for this binding.
* It will be provided in binding callback.
*/
void *context;
/**
* Binding function for when command is received.
* If null, default callback (onCommand) will be called.
* @param cli - pointer to cli that is calling this binding
* @param args - string of args (if tokenizeArgs is false) or tokens otherwise
* @param context
*/
void (*binding)(EmbeddedCli *cli, char *args, void *context);
};
struct EmbeddedCli {
/**
* Should write char to connection
* @param cli - pointer to cli that executed this function
* @param c - actual character to write
*/
void (*writeChar)(EmbeddedCli *cli, char c);
/**
* Called when command is received and command not found in list of
* command bindings (or binding function is null).
* @param cli - pointer to cli that executed this function
* @param command - pointer to received command
*/
void (*onCommand)(EmbeddedCli *cli, CliCommand *command);
/**
* Can be used for any application context
*/
void *appContext;
/**
* Pointer to actual implementation, do not use.
*/
void *_impl;
};
/**
* Configuration to create CLI
*/
struct EmbeddedCliConfig {
/**
* Invitation string. Is printed at the beginning of each line with user
* input
*/
const char *invitation;
/**
* Size of buffer that is used to store characters until they're processed
*/
uint16_t rxBufferSize;
/**
* Size of buffer that is used to store current input that is not yet
* sended as command (return not pressed yet)
*/
uint16_t cmdBufferSize;
/**
* Size of buffer that is used to store previously entered commands
* Only unique commands are stored in buffer. If buffer is smaller than
* entered command (including arguments), command is discarded from history
*/
uint16_t historyBufferSize;
/**
* Maximum amount of bindings that can be added via addBinding function.
* Cli increases takes extra bindings for internal commands:
* - help
*/
uint16_t maxBindingCount;
/**
* Buffer to use for cli and all internal structures. If NULL, memory will
* be allocated dynamically. Otherwise this buffer is used and no
* allocations are made
*/
CLI_UINT *cliBuffer;
/**
* Size of buffer for cli and internal structures (in bytes).
*/
uint16_t cliBufferSize;
/**
* Whether autocompletion should be enabled.
* If false, autocompletion is disabled but you still can use 'tab' to
* complete current command manually.
*/
bool enableAutoComplete;
};
/**
* Returns pointer to default configuration for cli creation. It is safe to
* modify it and then send to embeddedCliNew().
* Returned structure is always the same so do not free and try to use it
* immediately.
* Default values:
* <ul>
* <li>rxBufferSize = 64</li>
* <li>cmdBufferSize = 64</li>
* <li>historyBufferSize = 128</li>
* <li>cliBuffer = NULL (use dynamic allocation)</li>
* <li>cliBufferSize = 0</li>
* <li>maxBindingCount = 8</li>
* <li>enableAutoComplete = true</li>
* </ul>
* @return configuration for cli creation
*/
EmbeddedCliConfig *embeddedCliDefaultConfig(void);
/**
* Returns how many space in config buffer is required for cli creation
* If you provide buffer with less space, embeddedCliNew will return NULL
* This amount will always be divisible by CLI_UINT_SIZE so allocated buffer
* and internal structures can be properly aligned
* @param config
* @return
*/
uint16_t embeddedCliRequiredSize(EmbeddedCliConfig *config);
/**
* Create new CLI.
* Memory is allocated dynamically if cliBuffer in config is NULL.
* After CLI is created, override function pointers to start using it
* @param config - config for cli creation
* @return pointer to created CLI
*/
EmbeddedCli *embeddedCliNew(EmbeddedCliConfig *config);
/**
* Same as calling embeddedCliNew with default config.
* @return
*/
EmbeddedCli *embeddedCliNewDefault(void);
/**
* Receive character and put it to internal buffer
* Actual processing is done inside embeddedCliProcess
* You can call this function from something like interrupt service routine,
* just make sure that you call it only from single place. Otherwise input
* might get corrupted
* @param cli
* @param c - received char
*/
void embeddedCliReceiveChar(EmbeddedCli *cli, char c);
/**
* Process rx/tx buffers. Command callbacks are called from here
* @param cli
*/
void embeddedCliProcess(EmbeddedCli *cli);
/**
* Add specified binding to list of bindings. If list is already full, binding
* is not added and false is returned
* @param cli
* @param binding
* @return true if binding was added, false otherwise
*/
bool embeddedCliAddBinding(EmbeddedCli *cli, CliCommandBinding binding);
/**
* Print specified string and account for currently entered but not submitted
* command.
* Current command is deleted, provided string is printed (with new line) after
* that current command is printed again, so user can continue typing it.
* @param cli
* @param string
*/
void embeddedCliPrint(EmbeddedCli *cli, const char *string);
/**
* Free allocated for cli memory
* @param cli
*/
void embeddedCliFree(EmbeddedCli *cli);
/**
* Perform tokenization of arguments string. Original string is modified and
* should not be used directly (only inside other token functions).
* Individual tokens are separated by single 0x00 char, double 0x00 is put at
* the end of token list. After calling this function, you can use other
* token functions to get individual tokens and token count.
*
* Important: Call this function only once. Otherwise information will be lost if
* more than one token existed
* @param args - string to tokenize (must have extra writable char after 0x00)
* @return
*/
void embeddedCliTokenizeArgs(char *args);
/**
* Return specific token from tokenized string
* @param tokenizedStr
* @param pos (counted from 1)
* @return token
*/
const char *embeddedCliGetToken(const char *tokenizedStr, uint16_t pos);
/**
* Same as embeddedCliGetToken but works on non-const buffer
* @param tokenizedStr
* @param pos (counted from 1)
* @return token
*/
char *embeddedCliGetTokenVariable(char *tokenizedStr, uint16_t pos);
/**
* Find token in provided tokens string and return its position (counted from 1)
* If no such token is found - 0 is returned.
* @param tokenizedStr
* @param token - token to find
* @return position (increased by 1) or zero if no such token found
*/
uint16_t embeddedCliFindToken(const char *tokenizedStr, const char *token);
/**
* Return number of tokens in tokenized string
* @param tokenizedStr
* @return number of tokens
*/
uint16_t embeddedCliGetTokenCount(const char *tokenizedStr);
#ifdef __cplusplus
}
#endif
#endif //EMBEDDED_CLI_H
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Sviatoslav Kokurin (funbiscuit)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+7
View File
@@ -0,0 +1,7 @@
# Embedded CLI
Implementation is taken from `funbiscuit` @ [github](https://github.com/funbiscuit/embedded-cli)
- Only source and header where integrated.
- Full project is included in `external/embedded-cli`
+161
View File
@@ -0,0 +1,161 @@
/**
* @file cliWrapper.hpp
* @author Gustice
* @brief Wrapping Command-Line-Interfaces with custom endpoints
* @details Command-Line-Interface is based on the implementation of
* https://github.com/funbiscuit/embedded-cli
*
* @copyright Copyright (c) 2024
*/
#pragma once
#include "embedded_cli.h"
#include <esp_log.h>
#include <functional>
#include <memory>
#include "utils.hpp"
/* Module configuration */
static constexpr int CLI_RX_BUFFER_SIZE = 64;
static constexpr int CLI_CMD_BUFFER_SIZE = 64; // to also accept URLs for Updates
static constexpr int CLI_HISTORY_SIZE = 16;
static constexpr int CLI_BINDING_COUNT = 8;
static constexpr std::string_view CliWelcomeString{
"** Smart-DMX-Bridge **\n"
" send 'help' for help-page\n"
" NOTE: Syslog-messages are sent to client of this session\n"};
/**
* @brief Wrapper for c-implementation of CLI
*/
class Cli {
public:
/// @brief Type for callback arguments
using CArgs = std::vector<std::string> &;
/// @brief Type for bind callback
using BindCb = std::function<void(CArgs)>;
/// @brief Type for command callback
using CommandCb = std::function<void(std::string, CArgs)>;
/// @brief Parameter structure for command-binding
struct Binding {
std::string_view name;
std::string_view help_output;
BindCb callback;
};
/// @brief Constructor
/// @param port Reference to serial port
/// @param cmd command callback
Cli(CharStream &port, CommandCb cmd)
: _port(port), _commandCb(cmd) {
EmbeddedCliConfig *config = embeddedCliDefaultConfig();
// NOTE: Note setting cliBuffer allows Cli to allocate needed buffer in correct size
// config->cliBuffer = cliBuffer;
// config->cliBufferSize = CLI_BUFFER_SIZE;
config->rxBufferSize = CLI_RX_BUFFER_SIZE;
config->cmdBufferSize = CLI_CMD_BUFFER_SIZE;
config->historyBufferSize = CLI_HISTORY_SIZE;
config->maxBindingCount = CLI_BINDING_COUNT;
cli = embeddedCliNew(config);
if (cli == NULL) {
ESP_LOGE("CLI", "Cli was not created, requires %d bytes. Check sizes!",
embeddedCliRequiredSize(config));
return;
}
cli->onCommand = genericCallback;
cli->writeChar = writeToken;
// appContext is a user-end variable and is bound to current instance
cli->appContext = this;
}
/// @brief Destructor
~Cli() {
embeddedCliFree(cli);
}
/// @brief Create command binding
bool addBinding(Binding bind) {
BindCb *cb = new BindCb{bind.callback};
CliCommandBinding cBinding{.name = bind.name.begin(),
.help = bind.help_output.begin(),
.tokenizeArgs = true,
// NOTE: this hard reference only works because we only provide
// an add-function without remove
.context = (void *)(cb),
.binding = boundCallback};
return embeddedCliAddBinding(cli, cBinding);
}
/// @brief Static method to generate c-compatible function-pointer for bound-command-callback
/// @details - receives and wraps arguments in string,
/// - determines object according to appContext
/// - calls appropriate method of appropriate object
static void boundCallback(EmbeddedCli *cli, char *args, void *context) {
if (context == nullptr) {
return;
ESP_LOGE("CLI", "No context in callback available");
}
ESP_LOGD("CLI", "Received command:");
BindCb &cb = *((BindCb *)context);
uint len = embeddedCliGetTokenCount(args);
std::vector<std::string> sArgs;
while (len-- > 0) {
const char *const arg = embeddedCliGetToken(args, 1);
ESP_LOGD("CLI", " arg: %s", arg);
sArgs.emplace_back(arg);
// shift position to end of this element to save time
args += sArgs.back().size() + 1; // needs also to be shifted by null-delimiter
}
cb(sArgs);
}
/// @brief Static method to generate c-compatible function-pointer for command-callback
/// @details - receives and wraps arguments in string,
/// - determines object according to appContext
/// - calls command-callback of appropriate object
static void genericCallback(EmbeddedCli *cli, CliCommand *command) {
std::string name(command->name);
ESP_LOGI("CLI", "Received generic command: %s", name.c_str());
embeddedCliTokenizeArgs(command->args);
std::vector<std::string> sArgs;
for (int i = 1; i <= embeddedCliGetTokenCount(command->args); ++i) {
sArgs.emplace_back(embeddedCliGetToken(command->args, i));
ESP_LOGI("CLI", " arg %d : %s", i, sArgs.back().c_str());
}
auto &commandCb = static_cast<Cli *>(cli->appContext)->_commandCb;
commandCb(name, sArgs);
}
/// @brief Process buffer and trigger commands
void process() {
auto r = _port.read();
for (auto &&c : r) {
embeddedCliReceiveChar(cli, c);
}
embeddedCliProcess(cli);
}
/// @brief publicly exposed write method
void write(std::string output) {
_port.write(output);
}
private:
/// @brief Reference to hardware port
CharStream &_port;
/// @brief Callback for generic command
CommandCb _commandCb;
EmbeddedCli *cli = nullptr;
/// @brief Write callback
static void writeToken(EmbeddedCli *embeddedCli, char c) {
static_cast<Cli *>(embeddedCli->appContext)->_port.write(c);
}
};
+101
View File
@@ -32,6 +32,9 @@
#include "PwmExpanderPca9685.hpp"
#include "StepperDrv8833.hpp"
#include "stepperMotor.hpp"
#include "cliWrapper.hpp"
#include "udpSockets.hpp"
#include "tcpSockets.hpp"
static const char *TAG = "Watch";
static esp_eth_handle_t s_eth_handle = NULL;
@@ -41,6 +44,103 @@ extern "C" { // This switch allows the ROS C-implementation to find this main
void app_main(void);
}
std::unique_ptr<Cli> shellPtr;
/// @brief Callback for update start
/// @param args callback arguments
void startUpdate(Cli::CArgs args) {
ESP_LOGI(TAG, "Invoking Update");
if (args.size() != 1) {
shellPtr->write("Need URL to application'\n");
return;
}
// @ToDo: Start Update here
}
/// @brief Callback for commands that are not directly wired
/// @param name command name
/// @param args callback arguments
void onCommand(std::string name, Cli::CArgs args) {
ESP_LOGI(TAG, "Invoking: %s", name.c_str());
}
void showLogServerConfig(Cli::CArgs args) {
ESP_LOGI(TAG, "on showLogServerConfig:");
// auto &store = NvsApi::getInstance();
// auto logger = store.getValue("syslogDst");
// shellPtr->write(std::string{"syslog destination = '"} + logger + "'\n");
}
void setLogServerConfig(Cli::CArgs args) {
ESP_LOGI(TAG, "on showLogServerConfig:");
if (args.size() != 1) {
shellPtr->write("Need IP address to syslog server'\n");
return;
}
// auto &store = NvsApi::getInstance();
// shellPtr->write(std::string{"setting syslog destination to = '"} + args[0] + "'\n");
// store.setValue("syslogDst", args[0]);
}
/**
* @brief Telnet socket via TCP/IP
*
* @details The ini-file for TeraTerm only changes CRReceive and CRSend values both to "=CR"
* @param arg ignored task argument
*/
static void telnetSocketTask(int addrFamily) {
ESP_LOGI(TAG, "Starting Telnet task");
try {
TelnetServer telnet(addrFamily);
telnet.run(
[](TcpSession &session) {
shellPtr = std::make_unique<Cli>(session, [](std::string n, Cli::CArgs a) { onCommand(n, a); });
shellPtr->addBinding(
{"update", "Start OTA-Update-Process",
[](Cli::CArgs a) { startUpdate(a); }});
shellPtr->addBinding(
{"logserver_show", "Shows log server config ip",
[](Cli::CArgs a) { showLogServerConfig(a); }});
shellPtr->addBinding(
{"logserver_set", "Set log server config ip",
[](Cli::CArgs a) { setLogServerConfig(a); }});
},
[](TcpSession &session) {
shellPtr->process();
Syslog::setupTask(session.getOther());
});
} catch (const std::exception &e) {
ESP_LOGE("TCP", "%s", e.what());
}
vTaskDelete(NULL);
}
void startServerTasks() {
static int ipVersion = AF_INET;
static fos::TaskT<int> telnetSocket(telnetSocketTask, ipVersion, "tcpServer", 4096);
// static TaskT<int> dmxNode(dmxSocketTask, ipVersion, "dmxNode", 4096);
// static Task otaUpdate(SystemFunctions::OtaTaskEntry, "otaTask", 8192);
// initialise_mdns();
// netbiosns_init();
// netbiosns_set_name(CONFIG_EXAMPLE_MDNS_HOST_NAME);
// WebServer::init(FileSystem::getInstance());
// WebServer::getInstance(FileSystem::getInstance()).registerCallback(printSysState);
// auto &store = NvsApi::getInstance();
// auto broker = store.getValue("mqttBroker");
// if (broker != "") {
// MqttClient::init(broker);
// }
// auto logger = store.getValue("syslogDst");
// if (logger != "") {
// Syslog::setupTask(IpAdr{logger, 0});
// }
}
/** Event handler for IP_EVENT_ETH_GOT_IP */
static void got_ip_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
@@ -54,6 +154,7 @@ static void got_ip_event_handler(void *arg, esp_event_base_t event_base,
ESP_LOGI(TAG, "ETHMASK:" IPSTR, IP2STR(&ip_info->netmask));
ESP_LOGI(TAG, "ETHGW:" IPSTR, IP2STR(&ip_info->gw));
ESP_LOGI(TAG, "~~~~~~~~~~~");
startServerTasks();
}
// Event handler for Ethernet
+5 -3
View File
@@ -585,7 +585,8 @@ CONFIG_COMPILER_FLOAT_LIB_FROM_RVFPLIB=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTION_LEVEL=2
# CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT is not set
CONFIG_COMPILER_HIDE_PATHS_MACROS=y
# CONFIG_COMPILER_CXX_EXCEPTIONS is not set
CONFIG_COMPILER_CXX_EXCEPTIONS=y
CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=0
# CONFIG_COMPILER_CXX_RTTI is not set
CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y
# CONFIG_COMPILER_STACK_CHECK_MODE_NORM is not set
@@ -1412,7 +1413,7 @@ CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y
# LWIP
#
CONFIG_LWIP_ENABLE=y
CONFIG_LWIP_LOCAL_HOSTNAME="espressif"
CONFIG_LWIP_LOCAL_HOSTNAME="ChaosWatch"
# CONFIG_LWIP_NETIF_API is not set
CONFIG_LWIP_TCPIP_TASK_PRIO=18
# CONFIG_LWIP_TCPIP_CORE_LOCKING is not set
@@ -2001,7 +2002,8 @@ CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y
# CONFIG_OPTIMIZATION_ASSERTIONS_SILENT is not set
# CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED is not set
CONFIG_OPTIMIZATION_ASSERTION_LEVEL=2
# CONFIG_CXX_EXCEPTIONS is not set
CONFIG_CXX_EXCEPTIONS=y
CONFIG_CXX_EXCEPTIONS_EMG_POOL_SIZE=0
CONFIG_STACK_CHECK_NONE=y
# CONFIG_STACK_CHECK_NORM is not set
# CONFIG_STACK_CHECK_STRONG is not set
+13 -1
View File
@@ -1,3 +1,9 @@
#
# Compiler options
#
CONFIG_COMPILER_CXX_EXCEPTIONS=y
CONFIG_LWIP_LOCAL_HOSTNAME="ChaosWatch"
#
# Example Ethernet Configuration
#
@@ -21,15 +27,21 @@ CONFIG_EXAMPLE_ETH_SPI_PHY_ADDR0=1
#
# Ethernet
#
# CONFIG_ETH_SPI_ETHERNET_W5500 is not set
CONFIG_ETH_SPI_ETHERNET_W5500=y
#
# Wi-Fi
#
# CONFIG_ESP_WIFI_GMAC_SUPPORT is not set
#
# LWIP
#
CONFIG_LWIP_LOCAL_HOSTNAME="ChaosWatch"
#
# Serial flasher config
#
CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y
CONFIG_ESPTOOLPY_FLASHSIZE="8MB"