diff --git a/ADIS_ESP32_Eclipse/main/.gitignore b/ADIS_ESP32_Eclipse/main/.gitignore new file mode 100644 index 0000000..7427950 --- /dev/null +++ b/ADIS_ESP32_Eclipse/main/.gitignore @@ -0,0 +1,3 @@ +/build/ +# do NOT put the password on git +/pwd.h diff --git a/ADIS_ESP32_Eclipse/main/CMakeLists.txt b/ADIS_ESP32_Eclipse/main/CMakeLists.txt index 8a3ab69..fb42854 100644 --- a/ADIS_ESP32_Eclipse/main/CMakeLists.txt +++ b/ADIS_ESP32_Eclipse/main/CMakeLists.txt @@ -1,2 +1,21 @@ -idf_component_register(SRCS "main.c" - INCLUDE_DIRS "") +idf_component_register( + SRCS + "main.c" + "platform.c" + "led.c" + "wifi.c" + "udp_client.c" + "udp_server.c" + "udp_server_shell.c" + "Shell.c" + "esp32_mac.c" + "rs485.c" + "ping.c" + "ping_shell.c" + "timer.c" + "sntp_time.c" + "robot.c" + + INCLUDE_DIRS + "." +) diff --git a/ADIS_ESP32_Eclipse/main/Identify.h b/ADIS_ESP32_Eclipse/main/Identify.h new file mode 100644 index 0000000..e9932f0 --- /dev/null +++ b/ADIS_ESP32_Eclipse/main/Identify.h @@ -0,0 +1,93 @@ +/** + * \file + * \brief Module to identify different devices based on their unique ID. + * \author Erich Styger, erich.styger@hslu.ch + * \license SPDX-License-Identifier: BSD-3-Clause + * With this module individual devices are identified based on their unique ID. + */ + +#ifndef __IDENTIFY_H_ +#define __IDENTIFY_H_ + +#include "platform.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if PL_CONFIG_USE_IDENTIFY + #if PL_CONFIG_USE_SHELL + #include "McuShell.h" + + /*! + * \brief Parses a command + * \param cmd Command string to be parsed + * \param handled Sets this variable to TRUE if command was handled + * \param io I/O stream to be used for input/output + * \return Error code, ERR_OK if everything was fine + */ + uint8_t ID_ParseCommand(const unsigned char *cmd, bool *handled, const McuShell_StdIOType *io); + #endif /* PL_CONFIG_USE_SHELL */ + + typedef enum { /*! \todo ADIS: Verify and update information */ + ID_ROBOT_E0, + ID_ROBOT_E1, + ID_ROBOT_E2, + ID_ROBOT_E3, + ID_ROBOT_E4, + ID_ROBOT_E5, + ID_ROBOT_E6, + ID_ROBOT_E7, + ID_ROBOT_E8, + ID_ROBOT_E9, + ID_ROBOT_E10, + ID_ROBOT_E11, + ID_ROBOT_E12, + ID_ROBOT_E13, + ID_ROBOT_E14, +// ID_ROBOT_E15, +// ID_ROBOT_E16, + ID_ROBOT_E17, + ID_ROBOT_E18, + ID_ROBOT_E27, + ID_ROBOT_E34, /* no robot? */ + + ID_ROBOT_L0, /* USB port ripped off */ + ID_ROBOT_L1, + ID_ROBOT_L3, + ID_ROBOT_L17, + ID_ROBOT_L20, + + ID_ROBOT_R0, /* USB port ripped off */ + ID_ROBOT_R8, + ID_ROBOT_R9, + ID_ROBOT_R23, + ID_ROBOT_R27, + ID_ROBOT_R28, + ID_ROBOT_R29, + ID_ROBOT_R32, + ID_ROBOT_R33, + ID_ROBOT_R34, + ID_ROBOT_R36, + ID_ROBOT_R37, + ID_ROBOT_R44, + ID_ROBOT_R45, + + ID_ROBOT_UNKNOWN, /* unknown robot, unknown ID */ + ID_ROBOT_NONE /* initialization value, used internally */ + } ID_Robot_e; + + ID_Robot_e ID_WhichDevice(void); + + /*! \brief Module de-initialization */ + void ID_Deinit(void); + + /*! \brief Module initialization */ + void ID_Init(void); +#endif /* PL_CONFIG_USE_IDENTIFY */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __IDENTIFY_H_ */ diff --git a/ADIS_ESP32_Eclipse/main/Shell.c b/ADIS_ESP32_Eclipse/main/Shell.c new file mode 100644 index 0000000..18642e9 --- /dev/null +++ b/ADIS_ESP32_Eclipse/main/Shell.c @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2019-2021, Erich Styger + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "platform.h" +#if PL_CONFIG_USE_SHELL +#include "Shell.h" +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#if PL_CONFIG_USE_BLINKY + #include "led.h" +#endif +#if PL_CONFIG_USE_WIFI + #include "WiFi.h" +#endif +#if PL_CONFIG_USE_PING + #include "ping_shell.h" +#endif +#if PL_CONFIG_USE_UDP_SERVER + #include "udp_server_shell.h" +#endif +#if PL_CONFIG_USE_UDP_CLIENT + #include "udp_client.h" +#endif +#if PL_CONFIG_USE_RS485 + #include "rs485.h" + #include "McuUart485.h" +#endif +#if PL_CONFIG_USE_ROBO_REMOTE + #include "robot.h" +#endif +#include "McuShell.h" +#include "McuUtility.h" +#include "McuRTOS.h" +#include "McuTimeDate.h" +#include "McuLog.h" +#include "driver/uart.h" +#include "driver/gpio.h" + +#define SHELL_ESP32_UART_DEVICE (UART_NUM_0) /* Uart for bootloader and connection to robot */ + +static TaskHandle_t SHELL_taskHandle; +static SemaphoreHandle_t SHELL_stdioMutex; /* mutex to protect access to ESP32 standard I/O */ + +static const McuShell_ParseCommandCallback CmdParserTable[] = +{ + McuShell_ParseCommand, + McuRTOS_ParseCommand, +#if PL_CONFIG_USE_WIFI + WiFi_ParseCommand, +#endif +#if PL_CONFIG_USE_UDP_CLIENT + UDP_Client_ParseCommand, +#endif +#if PL_CONFIG_USE_PING + PING_ParseCommand, +#endif +#if PL_CONFIG_USE_UDP_SERVER + UDP_Server_ParseCommand, +#endif +#if PL_CONFIG_USE_RS485 + RS485_ParseCommand, + McuUart485_ParseCommand, +#endif +#if PL_CONFIG_USE_ROBO_REMOTE + ROBOT_ParseCommand, +#endif + McuLog_ParseCommand, + McuTimeDate_ParseCommand, +#if PL_CONFIG_USE_BLINKY + LED_ParseCommand, +#endif + NULL /* Sentinel */ +}; + +typedef struct { + McuShell_ConstStdIOType *stdio; + unsigned char *buf; + size_t bufSize; +} SHELL_IODesc; + +static void Uart_SendString(const unsigned char *str) { + size_t len; + int written; + + len = strlen((const char*)str); + written = uart_write_bytes(SHELL_ESP32_UART_DEVICE, (const char*)str, len); + if (written!=len) { + McuLog_error("failed sending uart bytes"); + } +} + +static void Uart_SendChar(unsigned char ch) { + uart_write_bytes(SHELL_ESP32_UART_DEVICE, &ch, 1); +} + +static void Uart_ReadChar(uint8_t *c) { + unsigned char ch = '\0'; + int len = 0; + + if (xSemaphoreTakeRecursive(SHELL_stdioMutex, portMAX_DELAY)==pdPASS) { /* take mutex */ + len = uart_read_bytes(SHELL_ESP32_UART_DEVICE, &ch, 1, 0); + (void)xSemaphoreGiveRecursive(SHELL_stdioMutex); /* give back mutex */ + } + if (len==0) { + *c = '\0'; + } else { + *c = ch; + } +} + +static bool Uart_CharPresent(void) { + size_t size=0; + + if (xSemaphoreTakeRecursive(SHELL_stdioMutex, portMAX_DELAY)==pdPASS) { /* take mutex */ + uart_get_buffered_data_len(SHELL_ESP32_UART_DEVICE, &size); + (void)xSemaphoreGiveRecursive(SHELL_stdioMutex); /* give back mutex */ + } + return size!=0; +} + +static McuShell_ConstStdIOType Uart_stdio = { + .stdIn = (McuShell_StdIO_In_FctType)Uart_ReadChar, + .stdOut = (McuShell_StdIO_OutErr_FctType)Uart_SendChar, + .stdErr = (McuShell_StdIO_OutErr_FctType)Uart_SendChar, + .keyPressed = Uart_CharPresent, /* if input is not empty */ + #if McuShell_CONFIG_ECHO_ENABLED + .echoEnabled = true, + #endif + }; + +static uint8_t Uart_DefaultShellBuffer[McuShell_DEFAULT_SHELL_BUFFER_SIZE]; /* default buffer which can be used by the application */ + +static const SHELL_IODesc ios[] = +{ + {&Uart_stdio, Uart_DefaultShellBuffer, sizeof(Uart_DefaultShellBuffer)}, +}; + +void SHELL_SendChar(unsigned char ch) { + for(int i=0;istdOut); + } +} + +uint8_t SHELL_ParseCommand(unsigned char *cmd) { + return McuShell_ParseWithCommandTable(cmd, McuShell_GetStdio(), CmdParserTable); +} + +uint8_t SHELL_ParseCommandIO(const unsigned char *command, McuShell_ConstStdIOType *io, bool silent) { + if (io==NULL) { /* use a default */ + io = McuShell_GetStdio(); + } + return McuShell_ParseWithCommandTableExt(command, io, CmdParserTable, silent); +} + +void SHELL_SendString(const unsigned char *str) { +#if 0 + for(int i=0;istdOut); + } +#else /* need to improve write speed, as writing character by character is too slow */ + Uart_SendString(str); +#endif +} + +void SHELL_SendStringToIO(const unsigned char *str, McuShell_ConstStdIOType *io) { + if (io->stdOut == Uart_SendChar) { /* ESP32 UART? */ + /* if out channel is ESP32 UART: speed it up by sending whole buffer */ + Uart_SendString(str); + } else { /* send it char by char */ + McuShell_SendStr(str, io->stdOut); + } +} + +/* ----------------- buffer handling for shell messages sent to ESP32 */ +static unsigned char *esp_io_buf; /* pointer to buffer */ +static size_t esp_io_buf_size; /* size of buffer */ + +static void esp_io_buf_SendChar(unsigned char ch) { + McuUtility_chcat(esp_io_buf, esp_io_buf_size, ch); +} + +static void esp_io_buf_ReadChar(uint8_t *c) { + *c = '\0'; +} + +static bool esp_io_buf_CharPresent(void) { + return false; +} + +static McuShell_ConstStdIOType esp_stdio = { + .stdIn = (McuShell_StdIO_In_FctType)esp_io_buf_ReadChar, + .stdOut = (McuShell_StdIO_OutErr_FctType)esp_io_buf_SendChar, + .stdErr = (McuShell_StdIO_OutErr_FctType)esp_io_buf_SendChar, + .keyPressed = esp_io_buf_CharPresent, /* if input is not empty */ + #if McuShell_CONFIG_ECHO_ENABLED + .echoEnabled = false, /* echo enabled for idf.py monitor */ + #endif + }; + +void SHELL_SendToESPAndGetResponse(const unsigned char *msg, unsigned char *response, size_t responseSize) { + esp_io_buf = response; + esp_io_buf_size = responseSize; + esp_io_buf[0] = '\0'; /* initialize buffer */ + McuLog_info("Sending to ESP Shell: %s", msg); + McuShell_ParseWithCommandTableExt(msg, &esp_stdio, CmdParserTable, true); /* send to ESP32 shell */ +} +/* ----------------------------------------------------------------------*/ +void SHELL_SendToRobotAndGetResponse(const unsigned char *send, unsigned char *response, size_t responseSize) { + unsigned char buffer[128]; /* buffer for sending command to robot */ + + /* build a frame around the message: that way the robot is able to recognize it */ + McuUtility_strcpy(buffer, sizeof(buffer), (unsigned char*)"@robot:cmd "); + McuUtility_strcat(buffer, sizeof(buffer), send); + McuUtility_strcat(buffer, sizeof(buffer), (unsigned char*)"!\r\n"); + SHELL_SendString(buffer); /* send to UART, which is read by the robot */ + /* get response */ +#if 1 + /* Important: this consumes directly all characters coming from the robot. That way the ESP32 shell does not get it. + * A mutex is used to block the shell from getting the UART stream. + */ + #define TIMEOUT_MS (500) /* stop if we don't get new input after this timeout */ + int timeoutMs = TIMEOUT_MS; + + *response = '\0'; + if (xSemaphoreTakeRecursive(SHELL_stdioMutex, portMAX_DELAY)==pdPASS) { /* take mutex */ + while (true) { /* breaks after timeout */ + if (!Uart_stdio.keyPressed()) { /* no input: wait for timeout */ + timeoutMs -= 50; + if (timeoutMs<=0) { + break; /* timeout */ + } + vTaskDelay(pdMS_TO_TICKS(50)); + } else { /* character available */ + unsigned char ch; + Uart_stdio.stdIn(&ch); + if (ch!='\r') { /* filter out '\r' in "\r\n" */ + McuUtility_chcat(response, responseSize, ch); + } + timeoutMs = TIMEOUT_MS; /* reset timeout */ + } /* if */ + } /* while */ + (void)xSemaphoreGiveRecursive(SHELL_stdioMutex); /* give back mutex */ + } + if (*response=='\0') { /* if response is empty, send back at least an acknowledgment */ + McuUtility_strcpy(response, responseSize, (unsigned char*)"OK"); /* default response */ + } +#else + McuUtility_strcpy(response, responseSize, (unsigned char*)"OK"); /* default response */ +#endif +} +/* ----------------------------------------------------------------------*/ +static void ShellTask(void *pv) { + int i; + + McuLog_info("Shell task started"); + for(i=0;i +#include +#include + +static const ESP32_Device_t ESP32_devices[] = +{ /* registered at https://eeeportal.hslu.ch */ + {.hostName="UnknownRobot", .robotID=ID_ROBOT_UNKNOWN, .macStr="00:00:00:00:00:00", .eee_id="eee-0000", .eee_pwd="deadbeef"}, /* fallback entry */ + {.hostName="ADISRobotE0", .robotID=ID_ROBOT_E0, .macStr="d8:a0:1d:42:e2:08", .eee_id="eee-01367", .eee_pwd="xq4vZdu3eWLWcSGDuYwU"}, + {.hostName="ADISRobotE1", .robotID=ID_ROBOT_E1, .macStr="d8:a0:1d:42:ec:a4", .eee_id="eee-01023", .eee_pwd="i24Z3W5VtDFSkdNtk93f"}, + {.hostName="ADISRobotE2", .robotID=ID_ROBOT_E2, .macStr="d8:a0:1d:42:eb:c4", .eee_id="eee-01381", .eee_pwd="DcUYXx0saffo5ST8TihG"}, + {.hostName="ADISRobotE3", .robotID=ID_ROBOT_E3, .macStr="d8:a0:1d:42:e6:98", .eee_id="eee-01407", .eee_pwd="OjhpZJrXUPWAC3mR0odX"}, + {.hostName="ADISRobotE4", .robotID=ID_ROBOT_E4, .macStr="d8:a0:1d:42:e4:48", .eee_id="eee-01369", .eee_pwd="vDk13U6MzxkwGO879TUy"}, + {.hostName="ADISRobotE5", .robotID=ID_ROBOT_E5, .macStr="d8:a0:1d:42:de:9c", .eee_id="eee-01370", .eee_pwd="jCGeKTeWVrcncJO8u73M"}, + {.hostName="ADISRobotE6", .robotID=ID_ROBOT_E6, .macStr="d8:a0:1d:42:ec:40", .eee_id="eee-01382", .eee_pwd="fJjX12cWv4U0XfaJ46N0"}, + {.hostName="ADISRobotE7", .robotID=ID_ROBOT_E7, .macStr="d8:a0:1d:42:df:14", .eee_id="eee-01387", .eee_pwd="TEkK2FL5Yc8A4dcsf7sk"}, + {.hostName="ADISRobotE8", .robotID=ID_ROBOT_E8, .macStr="d8:a0:1d:42:ec:74", .eee_id="eee-01371", .eee_pwd="aaxQfxvPnCMVLvCZ3ays"}, + {.hostName="ADISRobotE9", .robotID=ID_ROBOT_E9, .macStr="d8:a0:1d:42:e4:64", .eee_id="eee-01446", .eee_pwd="4dZNLrMCZqsQbsNKTQSL"}, + {.hostName="ADISRobotE10", .robotID=ID_ROBOT_E10, .macStr="d8:a0:1d:42:e6:f8", .eee_id="eee-01383", .eee_pwd="KEsv8fendMWx1nHkdQUj"}, + {.hostName="ADISRobotE11", .robotID=ID_ROBOT_E11, .macStr="d8:a0:1d:42:e0:24", .eee_id="eee-01609", .eee_pwd="WeWLiT7eH01Jqrt4DAfi"}, // issue with EEE! + {.hostName="ADISRobotE12", .robotID=ID_ROBOT_E12, .macStr="d8:a0:1d:42:df:d4", .eee_id="eee-01393", .eee_pwd="HtcKHGmaMUwwJgqh7Hao"}, + {.hostName="ADISRobotE13", .robotID=ID_ROBOT_E13, .macStr="d8:a0:1d:42:e5:88", .eee_id="eee-01384", .eee_pwd="q320iZDcc6bFPvXYzRxF"}, + {.hostName="ADISRobotE14", .robotID=ID_ROBOT_E14, .macStr="50:02:91:9c:0d:ac", .eee_id="eee-01635", .eee_pwd="V4V6LdNW7WJUnv4zbRKm"}, + {.hostName="ADISRobotE17", .robotID=ID_ROBOT_E17, .macStr="d8:a0:1d:42:e5:98", .eee_id="eee-00535", .eee_pwd="fXB5kVK6qS1khJ7qybYD"}, + {.hostName="ADISRobotE18", .robotID=ID_ROBOT_E18, .macStr="d8:a0:1d:42:e3:b8", .eee_id="eee-01390", .eee_pwd="W4KuRQERUMwHquUHwVot"}, + + {.hostName="ADISRobotE27", .robotID=ID_ROBOT_E27, .macStr="d8:a0:1d:42:df:e0", .eee_id="eee-01591", .eee_pwd="28o6wxyGLXmQ9DnUHw1P"}, + + {.hostName="ADISRobotL1", .robotID=ID_ROBOT_L1, .macStr="d8:a0:1d:42:e5:d0", .eee_id="eee-01372", .eee_pwd="zwtshA1tnqXxYqrt1mgo"}, + {.hostName="ADISRobotL3", .robotID=ID_ROBOT_L1, .macStr="50:02:91:a0:f8:ac", .eee_id="eee-01388", .eee_pwd="jhw1KSG1piDP7zthNUDt"}, + {.hostName="ADISRobotL17", .robotID=ID_ROBOT_L17, .macStr="d8:a0:1d:42:ed:24", .eee_id="eee-01373", .eee_pwd="XPiSwZ6Vr6Y5Y3ukvq5a"}, + {.hostName="ADISRobotL20", .robotID=ID_ROBOT_L20, .macStr="d8:a0:1d:42:df:94", .eee_id="eee-01374", .eee_pwd="88kY6j6nurJZpqt2gMMF"}, + + {.hostName="ADISRobotR8", .robotID=ID_ROBOT_R8, .macStr="d8:a0:1d:42:ed:50", .eee_id="eee-01368", .eee_pwd="xaA8qck6edMS3h2tsHun"}, + {.hostName="ADISRobotR9", .robotID=ID_ROBOT_R9, .macStr="d8:a0:1d:42:e3:d8", .eee_id="eee-01392", .eee_pwd="NxJZeTDUpGGXC0L1QxX9"}, + {.hostName="ADISRobotR23", .robotID=ID_ROBOT_R23, .macStr="d8:a0:1d:42:e2:b8", .eee_id="eee-01385", .eee_pwd="deOkgVLPBoh6BQXH3Awf"}, + {.hostName="ADISRobotR27", .robotID=ID_ROBOT_R27, .macStr="50:02:91:a1:0f:78", .eee_id="eee-01375", .eee_pwd="7O4HCSqbWVW0zCvfhyA2"}, + {.hostName="ADISRobotR28", .robotID=ID_ROBOT_R28, .macStr="d8:a0:1d:42:e0:44", .eee_id="eee-01386", .eee_pwd="mzyEvEfx0BvgYTOgF4F2"}, + {.hostName="ADISRobotR29", .robotID=ID_ROBOT_R29, .macStr="d8:a0:1d:42:ec:a8", .eee_id="eee-01410", .eee_pwd="c1nbzEkaEmXz9rEeBEfC"}, + {.hostName="ADISRobotR32", .robotID=ID_ROBOT_R32, .macStr="d8:a0:1d:42:e0:24", .eee_id="eee-01376", .eee_pwd="LWWEBe2Au4Hy472W0pbA"}, + {.hostName="ADISRobotR33", .robotID=ID_ROBOT_R33, .macStr="50:02:91:9f:8e:28", .eee_id="eee-01405", .eee_pwd="iWv6FaKso2ARMsPWpdkv"}, + {.hostName="ADISRobotR34", .robotID=ID_ROBOT_R34, .macStr="50:02:91:9f:8d:dc", .eee_id="eee-01592", .eee_pwd="kmCgbmQjuxubnOAKu5Jo"}, + {.hostName="ADISRobotR36", .robotID=ID_ROBOT_R36, .macStr="d8:a0:1d:42:e3:ac", .eee_id="eee-01406", .eee_pwd="gq3rkAs5VOuSgnFOJ3f5"}, + {.hostName="ADISRobotR37", .robotID=ID_ROBOT_R37, .macStr="d8:a0:1d:42:ec:2c", .eee_id="eee-01377", .eee_pwd="d2GVw71vruR9baZA4EdE"}, + {.hostName="ADISRobotR44", .robotID=ID_ROBOT_R44, .macStr="d8:a0:1d:42:e9:98", .eee_id="eee-01394", .eee_pwd="3gG3dQTyjjsteuv18EY9"}, + + {.hostName="ADISRobotR45", .robotID=ID_ROBOT_R45, .macStr="d8:a0:1d:42:e3:bc", .eee_id="eee-01417", .eee_pwd="zD5zCmXSo18qSMq55Lvs"}, + + /* ESP32 modules with no robot! */ + {.hostName="ADISRobotE34", .robotID=ID_ROBOT_NONE, .macStr="50:02:91:a0:f8:18", .eee_id="eee-01389", .eee_pwd="yehFaZWK9kKSULwLyPyR"}, + + /* ESP32 breakout modules */ + {.hostName="ESP32BRKOUT01", .robotID=ID_ROBOT_NONE, .macStr="d8:a0:1d:62:93:40", .eee_id="eee-01408", .eee_pwd="Gu03RtUCsrE99PafZCTo"}, + {.hostName="ESP32DEVKIT02", .robotID=ID_ROBOT_NONE, .macStr="24:0a:c4:13:8e:30", .eee_id="eee-01411", .eee_pwd="6kagmMLOR3tDiRx6GfXm"}, +}; + +void ESP32_MacToString(uint8_t mac[6], uint8_t *buf, size_t bufSize) { + McuXFormat_xsnprintf((char*)buf, bufSize, "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); +} + +void ESP32_MacRead(uint8_t mac[6]) { + ESP_ERROR_CHECK(esp_read_mac(&mac[0], ESP_MAC_WIFI_STA)); +} + +bool ESP32_MacAreSame(const uint8_t macA[6], const uint8_t macB[6]) { + for(int j=0; j<6; j++) { + if(macA[j] != macB[j]) { + return false; /* no match */ + } + } + return true; /* match! */ +} + +const ESP32_Device_t *ESP32_MacStrGetDevice(const char *macStr) { + int i; + + for(i=0; i +#include /* for size_t */ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + const char *hostName; /* name for module, usually matching robot, e.g. "AdisRobotR8" */ + ID_Robot_e robotID; /* ID of the robot */ + const char *macStr; /* ESP32 MAC, e.g. "d8:a0:1d:42:ed:50" */ + const char *eee_id; /* eee network id/name */ + const char *eee_pwd; /* eee network password */ +} ESP32_Device_t; + +/*! + * \brief transforms a binary MAC into a string, e.g. "d8:a0:1d:42:ed:50" + * \param mac binary MAC + * \param buf buffer where to store the string + * \param bufSize size of buffer + */ +void ESP32_MacToString(uint8_t mac[6], uint8_t *buf, size_t bufSize); + +/*! + * \brief return for a given MAC address string (e.g. "d8:a0:1d:42:e2:08") the device or NULL if not found. + */ +const ESP32_Device_t *ESP32_MacStrGetDevice(const char *macStr); + +/*! + * \brief Read the MAC address into buffer + * \param mac buffer where to store the MAC address + */ +void ESP32_MacRead(uint8_t mac[6]); + +/*! + * \brief return a device configuration based on MAC address + * \return pointer to device configuration. Pointing to a dummy configuration if MAC is not found in list. + */ +const ESP32_Device_t *ESP32_GetDeviceConfig(void); + +/*! + * \brief Module initialization + */ +void ESP32_MacInit(void); + +#endif /* PL_CONFIG_USE_IDENTIFY */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* ESP32_MAC_H_ */ diff --git a/ADIS_ESP32_Eclipse/main/led.c b/ADIS_ESP32_Eclipse/main/led.c new file mode 100644 index 0000000..099d8ad --- /dev/null +++ b/ADIS_ESP32_Eclipse/main/led.c @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2020, Erich Styger + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "platform.h" + +#include +#include "driver/gpio.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "led.h" +#include "McuUtility.h" +#include "McuLED.h" +#include "McuLog.h" + +#define TAG "Blinky" + +static McuLED_Handle_t ledHandle; + +static TaskHandle_t taskHandle; +static bool blinkyIsRunning = false; +#define LED_OFF_TIME_MS 1000 +static uint32_t onTimeMs = 10; /* default on time of LED */ + +/* red led on the shield is on IO10, LOW active */ +#define BLINK_GPIO (GPIO_NUM_10) /* IO10 */ +#define LED_LOW_ACTIVE (1) + +void LED_SetOnTime(uint32_t ms) { + onTimeMs = ms; +} + +void LED_On(void) { + McuLED_On(ledHandle); +} +void LED_Off(void) { + McuLED_Off(ledHandle); +} + +void LED_Suspend(void) { + if (taskHandle!=NULL) { + vTaskSuspend(taskHandle); + blinkyIsRunning = false; + LED_Off(); + } +} + +void LED_Resume(void) { + if (taskHandle!=NULL) { + vTaskResume(taskHandle); + blinkyIsRunning = true; + } +} + +void LED_GetStatus(unsigned char *buf, size_t bufSize) { + buf[0] = '\0'; + if (taskHandle!=NULL) { + eTaskState state; + + state = eTaskGetState(taskHandle); + switch(state) { + case eSuspended: + McuUtility_strcpy(buf, bufSize, (unsigned char*)"suspended"); + break; + case eRunning: + case eBlocked: + McuUtility_strcpy(buf, bufSize, (unsigned char*)"running"); + break; + default: + case eDeleted: + McuUtility_strcpy(buf, bufSize, (unsigned char*)"ERROR!"); + break; + } + } else { + McuUtility_strcpy(buf, bufSize, (unsigned char*)"ERROR: no task!"); + } +} + +static void blinkyTask(void *pv) { + (void)pv; + blinkyIsRunning = true; + ESP_LOGI(TAG, "running my blinky task"); + McuLog_info("started blinky task"); + for(;;) { + LED_On(); + vTaskDelay(pdMS_TO_TICKS(onTimeMs)); + LED_Off(); + vTaskDelay(pdMS_TO_TICKS(LED_OFF_TIME_MS)); + } +} + +#if PL_CONFIG_USE_SHELL +static uint8_t PrintStatus(const McuShell_StdIOType *io) { + McuShell_SendStatusStr((unsigned char*)"led", (unsigned char*)"ESP32 LED status\r\n", io->stdOut); + McuShell_SendStatusStr((unsigned char*)" status", blinkyIsRunning?(unsigned char*)"resumed\r\n":(unsigned char*)"suspended\r\n", io->stdOut); + return ERR_OK; +} + +static uint8_t PrintHelp(const McuShell_StdIOType *io) { + McuShell_SendHelpStr((unsigned char*)"led", (unsigned char*)"Group of ESP32 LED commands\r\n", io->stdOut); + McuShell_SendHelpStr((unsigned char*)" help|status", (unsigned char*)"Shows LED help or status\r\n", io->stdOut); + McuShell_SendHelpStr((unsigned char*)" suspend", (unsigned char*)"Suspend the LED task\r\n", io->stdOut); + McuShell_SendHelpStr((unsigned char*)" resume", (unsigned char*)"Resume the LED task\r\n", io->stdOut); + return ERR_OK; +} + +uint8_t LED_ParseCommand(const unsigned char* cmd, bool *handled, const McuShell_StdIOType *io) { + if (McuUtility_strcmp((char*)cmd, (char*)McuShell_CMD_HELP)==0 || McuUtility_strcmp((char*)cmd, (char*)"led help")==0) { + *handled = TRUE; + return PrintHelp(io); + } else if (McuUtility_strcmp((char*)cmd, (char*)McuShell_CMD_STATUS)==0 || McuUtility_strcmp((char*)cmd, (char*)"led status")==0) { + *handled = TRUE; + return PrintStatus(io); + } else if (McuUtility_strcmp((char*)cmd, (char*)"led suspend")==0) { + *handled = TRUE; + LED_Suspend(); + } else if (McuUtility_strcmp((char*)cmd, (char*)"led resume")==0) { + *handled = TRUE; + LED_Resume(); + } + return ERR_OK; +} +#endif /* PL_CONFIG_USE_SHELL */ + +void LED_Init(void) { + BaseType_t res; + McuLED_Config_t config; + + McuLED_GetDefaultConfig(&config); + config.hw.pin = BLINK_GPIO; + config.isLowActive = LED_LOW_ACTIVE; + config.isOnInit = false; + ledHandle = McuLED_InitLed(&config); + if (ledHandle==NULL) { + ESP_LOGE(TAG, "failed creating led handle!"); + return; + } + res = xTaskCreate(blinkyTask, "blinkyTask", 4*1024/sizeof(StackType_t), NULL, tskIDLE_PRIORITY, &taskHandle); + if (res==pdPASS) { + ESP_LOGI(TAG, "created blinky task"); + } else { + ESP_LOGE(TAG, "failed creating blinky!"); + } +} diff --git a/ADIS_ESP32_Eclipse/main/led.h b/ADIS_ESP32_Eclipse/main/led.h new file mode 100644 index 0000000..3a28c63 --- /dev/null +++ b/ADIS_ESP32_Eclipse/main/led.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2020, Erich Styger + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef LED_H_ +#define LED_H_ + +#include "platform.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void LED_SetOnTime(uint32_t ms); +void LED_Suspend(void); +void LED_Resume(void); +void LED_GetStatus(unsigned char *buf, size_t bufSize); + +#if PL_CONFIG_USE_SHELL + #include "McuShell.h" + + /*! + * \brief Command line and shell handler + * \param cmd The command to be parsed + * \param handled If command has been recognized and handled + * \param io I/O handler to be used + * \return error code, otherwise ERR_OK + */ + uint8_t LED_ParseCommand(const unsigned char* cmd, bool *handled, const McuShell_StdIOType *io); +#endif + +/*! \brief Module initialization */ +void LED_Init(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* LED_H_ */ diff --git a/ADIS_ESP32_Eclipse/main/main.c b/ADIS_ESP32_Eclipse/main/main.c index d867234..32892ed 100644 --- a/ADIS_ESP32_Eclipse/main/main.c +++ b/ADIS_ESP32_Eclipse/main/main.c @@ -12,11 +12,12 @@ #include "freertos/task.h" #include "esp_system.h" #include "esp_spi_flash.h" +#include "platform.h" void app_main(void) { printf("Hello world!\n"); - + PL_Init(); /* Print chip information */ esp_chip_info_t chip_info; esp_chip_info(&chip_info); diff --git a/ADIS_ESP32_Eclipse/main/ping.c b/ADIS_ESP32_Eclipse/main/ping.c new file mode 100644 index 0000000..219b366 --- /dev/null +++ b/ADIS_ESP32_Eclipse/main/ping.c @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2021, Erich Styger + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "platform.h" +#if PL_CONFIG_USE_PING +#include "ping.h" +#include "sdkconfig.h" +#include "lwip/inet.h" +#include "lwip/netdb.h" +#include "lwip/sockets.h" +#include "esp_console.h" +#include "esp_event.h" +#include "nvs_flash.h" +#include "argtable3/argtable3.h" +#include "ping/ping_sock.h" + +static void cmd_ping_on_ping_success(esp_ping_handle_t hdl, void *args) { + // optionally, get callback arguments + // const char* str = (const char*) args; + // printf("%s\r\n", str); // "foo" + uint8_t ttl; + uint16_t seqno; + uint32_t elapsed_time, recv_len; + ip_addr_t target_addr; + esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno)); + esp_ping_get_profile(hdl, ESP_PING_PROF_TTL, &ttl, sizeof(ttl)); + esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr)); + esp_ping_get_profile(hdl, ESP_PING_PROF_SIZE, &recv_len, sizeof(recv_len)); + esp_ping_get_profile(hdl, ESP_PING_PROF_TIMEGAP, &elapsed_time, sizeof(elapsed_time)); + printf("%d bytes from %s icmp_seq=%d ttl=%d time=%d ms\n", + recv_len, inet_ntoa(target_addr.u_addr.ip4), seqno, ttl, elapsed_time); +} + +static void cmd_ping_on_ping_timeout(esp_ping_handle_t hdl, void *args) { + uint16_t seqno; + ip_addr_t target_addr; + esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno)); + esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr)); + printf("From %s icmp_seq=%d timeout\n", inet_ntoa(target_addr.u_addr.ip4), seqno); +} + +void cmd_ping_on_ping_end(esp_ping_handle_t hdl, void *args) { + ip_addr_t target_addr; + uint32_t transmitted; + uint32_t received; + uint32_t total_time_ms; + esp_ping_get_profile(hdl, ESP_PING_PROF_REQUEST, &transmitted, sizeof(transmitted)); + esp_ping_get_profile(hdl, ESP_PING_PROF_REPLY, &received, sizeof(received)); + esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr)); + esp_ping_get_profile(hdl, ESP_PING_PROF_DURATION, &total_time_ms, sizeof(total_time_ms)); + uint32_t loss = (uint32_t)((1 - ((float)received) / transmitted) * 100); + if (IP_IS_V4(&target_addr)) { + printf("\n--- %s ping statistics ---\n", inet_ntoa(*ip_2_ip4(&target_addr))); + } else { + printf("\n--- %s ping statistics ---\n", inet6_ntoa(*ip_2_ip6(&target_addr))); + } + printf("%d packets transmitted, %d received, %d%% packet loss, time %dms\n", + transmitted, received, loss, total_time_ms); + // delete the ping sessions, so that we clean up all resources and can create a new ping session + // we don't have to call delete function in the callback, instead we can call delete function from other tasks + esp_ping_delete_session(hdl); +} + +int PING_cmd_do_ping(const char *host) { + esp_ping_config_t config = ESP_PING_DEFAULT_CONFIG(); + + /* parse IP address */ + struct sockaddr_in6 sock_addr6; + ip_addr_t target_addr; + const char *hostp = host; /* extra pointer for getaddrinfo() as it might change it */ + memset(&target_addr, 0, sizeof(target_addr)); + + if (inet_pton(AF_INET6, hostp, &sock_addr6.sin6_addr) == 1) { + /* convert ip6 string to ip6 address */ + ipaddr_aton(host, &target_addr); + } else { + struct addrinfo hint; + struct addrinfo *res = NULL; + memset(&hint, 0, sizeof(hint)); + /* convert ip4 string or hostname to ip4 or ip6 address */ + if (getaddrinfo(hostp, NULL, &hint, &res) != 0) { + printf("ping: unknown host %s\n", host); + return 1; + } + if (res->ai_family == AF_INET) { + struct in_addr addr4 = ((struct sockaddr_in *) (res->ai_addr))->sin_addr; + inet_addr_to_ip4addr(ip_2_ip4(&target_addr), &addr4); + } else { + struct in6_addr addr6 = ((struct sockaddr_in6 *) (res->ai_addr))->sin6_addr; + inet6_addr_to_ip6addr(ip_2_ip6(&target_addr), &addr6); + } + freeaddrinfo(res); + } + config.target_addr = target_addr; + + /* set callback functions */ + esp_ping_callbacks_t cbs = { + .on_ping_success = cmd_ping_on_ping_success, + .on_ping_timeout = cmd_ping_on_ping_timeout, + .on_ping_end = cmd_ping_on_ping_end, + .cb_args = NULL + }; + esp_ping_handle_t ping; + esp_ping_new_session(&config, &cbs, &ping); + esp_ping_start(ping); + return 0; /* ok */ +} + +void PING_Cmd_Init(void) { +} +#endif /* PL_CONFIG_USE_PING */ diff --git a/ADIS_ESP32_Eclipse/main/ping.h b/ADIS_ESP32_Eclipse/main/ping.h new file mode 100644 index 0000000..17e3bd1 --- /dev/null +++ b/ADIS_ESP32_Eclipse/main/ping.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2021, Erich Styger + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MAIN_PING_H_ +#define MAIN_PING_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! + * \brief performs a network ping + * \param host host name or address + * \return 0 for ok, negative values for error + */ +int PING_cmd_do_ping(const char *host); + +/*! + * \brief Module initialization + */ +void PING_cmd_Init(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* MAIN_PING_H_ */ diff --git a/ADIS_ESP32_Eclipse/main/ping_shell.c b/ADIS_ESP32_Eclipse/main/ping_shell.c new file mode 100644 index 0000000..b57496a --- /dev/null +++ b/ADIS_ESP32_Eclipse/main/ping_shell.c @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2021, Erich Styger + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "platform.h" +#if PL_CONFIG_USE_PING +#include "ping_shell.h" +#include "ping.h" +#include "wifi.h" +#include "McuShell.h" +#include "McuUtility.h" + +#if PL_CONFIG_USE_SHELL + +static uint8_t PrintStatus(const McuShell_StdIOType *io) { + McuShell_SendStatusStr((unsigned char*)"ping", (unsigned char*)"ESP32 ping status\r\n", io->stdOut); + return ERR_OK; +} + +static uint8_t PrintHelp(const McuShell_StdIOType *io) { + McuShell_SendHelpStr((unsigned char*)"ping", (unsigned char*)"Group of ESP32 ping commands\r\n", io->stdOut); + McuShell_SendHelpStr((unsigned char*)" help|status", (unsigned char*)"Shows ESP32 ping help or status\r\n", io->stdOut); + McuShell_SendHelpStr((unsigned char*)" ", (unsigned char*)"Ping host\r\n", io->stdOut); + return ERR_OK; +} + +uint8_t PING_ParseCommand(const unsigned char* cmd, bool *handled, const McuShell_StdIOType *io) { + const unsigned char *p; + + if (McuUtility_strcmp((char*)cmd, (char*)McuShell_CMD_HELP)==0 || McuUtility_strcmp((char*)cmd, (char*)"ping help")==0) { + *handled = TRUE; + return PrintHelp(io); + } else if (McuUtility_strcmp((char*)cmd, (char*)McuShell_CMD_STATUS)==0 || McuUtility_strcmp((char*)cmd, (char*)"ping status")==0) { + *handled = TRUE; + return PrintStatus(io); + } else if (McuUtility_strncmp((char*)cmd, (char*)"ping ", sizeof("ping ")-1)==0) { + *handled = TRUE; + if (!WiFi_isConnected()) { + McuShell_SendStr((unsigned char*)"Network not connected\r\n", io->stdErr); + return ERR_FAILED; + } + p = cmd + sizeof("ping ")-1; + if (PING_cmd_do_ping((const char*)p)==0) { + return ERR_OK; + } else { + return ERR_FAILED; + } + } + return ERR_OK; +} +#endif /* PL_CONFIG_USE_SHELL */ + + +void PING_Init(void) { +} + +#endif /* PL_CONFIG_USE_PING */ diff --git a/ADIS_ESP32_Eclipse/main/ping_shell.h b/ADIS_ESP32_Eclipse/main/ping_shell.h new file mode 100644 index 0000000..5242849 --- /dev/null +++ b/ADIS_ESP32_Eclipse/main/ping_shell.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021, Erich Styger + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef PING_SHELL_H_ +#define PING_SHELL_H_ + +#include "platform.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if PL_CONFIG_USE_SHELL + #include "McuShell.h" + + /*! + * \brief Command line and shell handler + * \param cmd The command to be parsed + * \param handled If command has been recognized and handled + * \param io I/O handler to be used + * \return error code, otherwise ERR_OK + */ + uint8_t PING_ParseCommand(const unsigned char* cmd, bool *handled, const McuShell_StdIOType *io); +#endif /* PL_CONFIG_USE_SHELL */ + +/*! + * \brief Module initialization + */ +void PING_Init(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* PING_SHELL_H_ */ diff --git a/ADIS_ESP32_Eclipse/main/platform.c b/ADIS_ESP32_Eclipse/main/platform.c new file mode 100644 index 0000000..660df67 --- /dev/null +++ b/ADIS_ESP32_Eclipse/main/platform.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2021, Erich Styger + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "platform.h" +#include "esp_err.h" +#if PL_CONFIG_USE_BLINKY + #include "led.h" +#endif +#if PL_CONFIG_USE_WIFI + #include "wifi.h" + #include "nvs_flash.h" +#endif +#if PL_CONFIG_USE_UDP_CLIENT + #include "udp_client.h" +#endif +#if PL_CONFIG_USE_UDP_SERVER + #include "udp_server.h" +#endif +#if PL_CONFIG_USE_SHELL + #include "Shell.h" +#endif +#if PL_CONFIG_USE_I2C + #include "i2c.h" +#endif +#if PL_CONFIG_USE_NVMC + #include "nvmc.h" +#endif +#if PL_CONFIG_USE_PING + #include "ping_shell.h" +#endif +#if PL_CONFIG_USE_SNTP_TIME + #include "sntp_time.h" +#endif +#if PL_CONFIG_USE_TIME_DATE + #include "McuTimeDate.h" + #include "timer.h" +#endif +#if PL_CONFIG_USE_ROBO_REMOTE + #include "robot.h" +#endif +#include "McuLib.h" +#include "McuGPIO.h" +#include "McuLED.h" +#include "McuUtility.h" +#include "McuWait.h" +#include "McuShell.h" +#include "McuRTOS.h" +#include "McuXFormat.h" +#include "McuLog.h" +#include "McuCriticalSection.h" +#include "esp32_mac.h" +#include "rs485.h" + +void PL_Init(void) { + McuLib_Init(); + McuLog_Init(); + McuWait_Init(); + McuRTOS_Init(); + McuUtility_Init(); + McuGPIO_Init(); + McuLED_Init(); +#if PL_CONFIG_USE_TIME_DATE + McuTimeDate_Init(); + TMR_Init(); +#endif + +#if PL_CONFIG_USE_BLINKY + LED_Init(); +#endif +#if PL_CONFIG_USE_WIFI + ESP_ERROR_CHECK(nvs_flash_init()); /* need to call this before using any WiFi functions */ + ESP32_MacInit(); + WiFi_Init(); +#endif +#if PL_CONFIG_USE_UDP_SERVER + UDP_Server_Init(); +#endif +#if PL_CONFIG_USE_UDP_CLIENT + UDP_Client_Init(); +#endif +#if PL_CONFIG_USE_SHELL + McuXFormat_Init(); + McuShell_Init(); + SHELL_Init(); +#endif +#if PL_CONFIG_USE_I2C + I2C_Init(); +#endif +#if PL_CONFIG_USE_NVMC + NVMC_Init(); +#endif +#if PL_CONFIG_USE_RS485 + RS485_Init(); +#endif +#if PL_CONFIG_USE_PING + PING_Init(); +#endif +#if PL_CONFIG_USE_SNTP_TIME + SNTP_Init(); +#endif +#if PL_CONFIG_USE_ROBO_REMOTE + ROBOT_Init(); +#endif +} diff --git a/ADIS_ESP32_Eclipse/main/platform.h b/ADIS_ESP32_Eclipse/main/platform.h new file mode 100644 index 0000000..8e0f459 --- /dev/null +++ b/ADIS_ESP32_Eclipse/main/platform.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021, Erich Styger + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef PLATFORM_H_ +#define PLATFORM_H_ + +/* platform configuration macros: turn on to enable functionality */ +#define PL_CONFIG_USE_BLINKY (0) /*!< if using blinky LED */ +#define PL_CONFIG_USE_WIFI (0) /*!< if using WiFi/WLAN */ +#define PL_CONFIG_USE_IDENTIFY (0 && PL_CONFIG_USE_WIFI) /*!< used to identify ESP32 and robot, needed for EEE network */ +#define PL_CONFIG_USE_UDP_SERVER (0 && PL_CONFIG_USE_WIFI) /*!< UDP server, used for communication to robot */ +#define PL_CONFIG_USE_UDP_CLIENT (0 && PL_CONFIG_USE_WIFI) /*!< UDP client, optionally available for tests */ +#define PL_CONFIG_USE_PING (0 && PL_CONFIG_USE_WIFI) /*!< shell command with ping, to test network connection */ + +#define PL_CONFIG_USE_SHELL (0) /*!< implements shell between robot and ESP32 */ +#define PL_CONFIG_USE_RS485 (0) /*!< ESP32 using RS-485 to split-flaps */ +#define PL_CONFIG_USE_SNTP_TIME (0 && PL_CONFIG_USE_WIFI) +#define PL_CONFIG_USE_TIME_DATE (0) /*!< if using Time and Date information */ +#define PL_CONFIG_USE_ROBO_REMOTE (0 && PL_CONFIG_USE_UDP_SERVER) /* UDP Remote controller for robot */ + +/*! \brief Module and platform initialization */ +void PL_Init(void); + +/* the following ones are not implemented yet: */ +#define PL_CONFIG_USE_I2C (0) +#define PL_CONFIG_USE_NVMC (0) +#define PL_CONFIG_USE_WDT (0) /* NYI */ + +#endif /* PLATFORM_H_ */ diff --git a/ADIS_ESP32_Eclipse/main/robot.c b/ADIS_ESP32_Eclipse/main/robot.c new file mode 100644 index 0000000..64e70a9 --- /dev/null +++ b/ADIS_ESP32_Eclipse/main/robot.c @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021, Erich Styger + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "platform.h" +#if PL_CONFIG_USE_ROBO_REMOTE +#include "robot.h" +#include "udp_server.h" +#include "wifi.h" +#include "McuShell.h" +#include "McuUtility.h" +#include "Shell.h" + +#if PL_CONFIG_USE_SHELL + +static uint8_t PrintStatus(const McuShell_StdIOType *io) { + McuShell_SendStatusStr((unsigned char*)"robo", (unsigned char*)"ESP32 robo status\r\n", io->stdOut); + return ERR_OK; +} + +static uint8_t PrintHelp(const McuShell_StdIOType *io) { + McuShell_SendHelpStr((unsigned char*)"robo", (unsigned char*)"Group of robot commands\r\n", io->stdOut); + McuShell_SendHelpStr((unsigned char*)" help|status", (unsigned char*)"Shows robot help or status\r\n", io->stdOut); + McuShell_SendHelpStr((unsigned char*)" send ", (unsigned char*)"Send a text to the robot\r\n", io->stdOut); + McuShell_SendHelpStr((unsigned char*)" sendcmd ", (unsigned char*)"Send a command to the robot, e.g. '#buzzer buz 100 200'\r\n", io->stdOut); + return ERR_OK; +} + +uint8_t ROBOT_ParseCommand(const unsigned char* cmd, bool *handled, const McuShell_StdIOType *io) { + if (McuUtility_strcmp((char*)cmd, (char*)McuShell_CMD_HELP)==0 || McuUtility_strcmp((char*)cmd, (char*)"robo help")==0) { + *handled = TRUE; + return PrintHelp(io); + } else if (McuUtility_strcmp((char*)cmd, (char*)McuShell_CMD_STATUS)==0 || McuUtility_strcmp((char*)cmd, (char*)"robo status")==0) { + *handled = TRUE; + return PrintStatus(io); + } else if (McuUtility_strncmp((char*)cmd, (char*)"robo send ", sizeof("robo send ")-1)==0) { + const unsigned char *p; + + *handled = TRUE; + p = cmd+sizeof("robo send ")-1; + McuShell_SendStr(p, io->stdOut); /* send to standard I/O which is the UART to the robot */ + return ERR_OK; + } else if (McuUtility_strncmp((char*)cmd, (char*)"robo sendcmd ", sizeof("robo sendcmd ")-1)==0) { + static uint8_t response[10*1024]; + const unsigned char *p; + + *handled = TRUE; + p = cmd+sizeof("robo sendcmd ")-1; + SHELL_SendToRobotAndGetResponse(p, response, sizeof(response)); + McuShell_SendStr(response, io->stdOut); /* show result on console */ + return ERR_OK; + } + return ERR_OK; +} +#endif /* PL_CONFIG_USE_SHELL */ + +void ROBOT_Deinit(void) { + /* nothing needed */ +} + +void ROBOT_Init(void) { + /* nothing needed */ +} +#endif /* PL_CONFIG_USE_ROBO_REMOTE */ diff --git a/ADIS_ESP32_Eclipse/main/robot.h b/ADIS_ESP32_Eclipse/main/robot.h new file mode 100644 index 0000000..98db047 --- /dev/null +++ b/ADIS_ESP32_Eclipse/main/robot.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021, Erich Styger + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MAIN_ROBOT_H_ +#define MAIN_ROBOT_H_ + + +#include "platform.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if PL_CONFIG_USE_SHELL + #include "McuShell.h" + + /*! + * \brief Command line and shell handler + * \param cmd The command to be parsed + * \param handled If command has been recognized and handled + * \param io I/O handler to be used + * \return error code, otherwise ERR_OK + */ + uint8_t ROBOT_ParseCommand(const unsigned char* cmd, bool *handled, const McuShell_StdIOType *io); +#endif /* PL_CONFIG_USE_SHELL */ + +/*! \brief Module de-initialization */ +void ROBOT_Deinit(void); + +/*! \brief Module initialization */ +void ROBOT_Init(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + + + +#endif /* MAIN_ROBOT_H_ */ diff --git a/ADIS_ESP32_Eclipse/main/rs485.c b/ADIS_ESP32_Eclipse/main/rs485.c new file mode 100644 index 0000000..5420025 --- /dev/null +++ b/ADIS_ESP32_Eclipse/main/rs485.c @@ -0,0 +1,632 @@ +/* + * Copyright (c) 2019-2022, Erich Styger + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "platform.h" +#if PL_CONFIG_USE_RS485 +#include "rs485.h" +#include "McuGPIO.h" +#include "McuUart485.h" +#include "McuShell.h" +#include "McuRTOS.h" +#include "McuUtility.h" +#include "McuLog.h" +#include "Shell.h" +#if McuLib_CONFIG_CPU_IS_KINETIS || McuLib_CONFIG_CPU_IS_LPC + #include "nvmc.h" +#endif +#if PL_CONFIG_USE_WDT + #include "watchdog.h" +#endif +#if 0 /* \TODO */ +#include "stepper.h" +#endif + +typedef enum RS485_Response_e { + RS485_RESPONSE_CONTINUE, /* continue scanning and parsing */ + RS485_RESPONSE_OK, /* ok response */ + RS485_RESPONSE_NOK, /* not ok response */ + RS485_RESPONSE_TIMEOUT, /* timeout */ +} RS485_Response_e; + +static bool RS485_DoLogging = false; /* if traffic on the bus shall be reported on the shell */ +static SemaphoreHandle_t RS485_stdioMutex; /* mutex to protect access to standard I/O */ + +uint8_t RS485_GetAddress(void) { +#if McuLib_CONFIG_CPU_IS_KINETIS || McuLib_CONFIG_CPU_IS_LPC + uint8_t addr = 0; + + if (NVMC_GetRS485Addr(&addr)==ERR_OK) { + return addr; + } + return 0; /* failed */ +#elif McuLib_CONFIG_CPU_IS_ESP32 + return 1; /* hard coded */ +#endif +} + +static void RS485_SendChar(unsigned char ch) { + McuUart485_stdio.stdOut(ch); +} + +static void RS485_NullSend(unsigned char ch) { + /* do nothing */ +} + +static void RS485_ReadChar(uint8_t *c) { + *c = '\0'; /* only sending on this channel */ +} + +static bool RS485_CharPresent(void) { + return false; /* only sending on this channel */ +} + +McuShell_ConstStdIOType RS485_stdio = { + .stdIn = (McuShell_StdIO_In_FctType)RS485_ReadChar, + .stdOut = (McuShell_StdIO_OutErr_FctType)RS485_SendChar, + .stdErr = (McuShell_StdIO_OutErr_FctType)RS485_SendChar, + .keyPressed = RS485_CharPresent, /* if input is not empty */ + #if McuShell_CONFIG_ECHO_ENABLED + .echoEnabled = false, + #endif +}; + +McuShell_ConstStdIOType RS485_stdioBroadcast = { + .stdIn = (McuShell_StdIO_In_FctType)RS485_ReadChar, + .stdOut = (McuShell_StdIO_OutErr_FctType)RS485_NullSend, + .stdErr = (McuShell_StdIO_OutErr_FctType)RS485_NullSend, + .keyPressed = RS485_CharPresent, /* if input is not empty */ + #if McuShell_CONFIG_ECHO_ENABLED + .echoEnabled = false, + #endif +}; + +/*-----------------------------------------------------------------------*/ +/* parser I/O handler, used to parse the returned data after sending a command to the RS-485 bus */ +static void RS485Parse_SendChar(unsigned char ch) { + if (xSemaphoreTakeRecursive(RS485_stdioMutex, portMAX_DELAY)==pdPASS) { /* take mutex */ + McuUart485_stdio.stdOut(ch); + (void)xSemaphoreGiveRecursive(RS485_stdioMutex); /* give back mutex */ + } +} + +static void RS485Parse_ReadChar(uint8_t *c) { + if (xSemaphoreTakeRecursive(RS485_stdioMutex, portMAX_DELAY)==pdPASS) { /* take mutex */ + McuUart485_stdio.stdIn(c); + (void)xSemaphoreGiveRecursive(RS485_stdioMutex); /* give back mutex */ + } +} + +static bool RS485Parse_CharPresent(void) { + bool inputPresent = false; + + if (xSemaphoreTakeRecursive(RS485_stdioMutex, portMAX_DELAY)==pdPASS) { /* take mutex */ + inputPresent = McuUart485_stdio.keyPressed(); + (void)xSemaphoreGiveRecursive(RS485_stdioMutex); /* give back mutex */ + } + return inputPresent; +} + +static McuShell_ConstStdIOType RS485Parse_stdio = { + .stdIn = (McuShell_StdIO_In_FctType)RS485Parse_ReadChar, + .stdOut = (McuShell_StdIO_OutErr_FctType)RS485Parse_SendChar, + .stdErr = (McuShell_StdIO_OutErr_FctType)RS485Parse_SendChar, + .keyPressed = RS485Parse_CharPresent, /* if input is not empty */ + #if McuShell_CONFIG_ECHO_ENABLED + .echoEnabled = false, + #endif + }; +/*-----------------------------------------------------------------------*/ +static void RS485_SendStr(unsigned char *str) { + while(*str!='\0') { + RS485_stdio.stdOut(*str++); + } +} + +static uint8_t CalcCRC(const uint8_t *data, uint8_t dataSize, uint8_t start) { + uint8_t crc, i, x, y; + + crc = start; + for(x=0;x>= 1; + crc ^= 0x8c; + } else { + crc >>= 1; + } + y >>= 1; + } + } + return crc; +} + +static uint8_t CalcMsgCrc(const unsigned char *msg) { + /* header is "@dd ss cc ...", both dd, ss and cc are 8bit HEX values. The CRC (cc) is not included in the CRC */ + uint8_t crc; + + crc = CalcCRC(msg, sizeof("@dd ss ")-1, 0); + crc = CalcCRC(msg+sizeof("@dd ss cc")-1, strlen((char*)msg+sizeof("@dd ss cc")-1), crc); + return crc; +} + +typedef enum CMD_ParserState_e { + CMD_PARSER_INIT, + CMD_PARSER_START_DETECTED, /* start '@' detected */ + CMD_PARSER_SCAN_DST_ADDR, /* scanning destination address */ + CMD_PARSER_SCAN_SRC_ADDR, /* scan source address */ + CMD_PARSER_SCAN_CRC, /* scan CRC */ + CMD_PARSER_SCAN_OK_NOK, /* reading NOK or OK */ +} CMD_ParserState_e; + +static RS485_Response_e Scan(CMD_ParserState_e *state, unsigned char ch, unsigned char *buf, size_t bufSize, uint8_t fromAddr) { + /* scan for "@ OK" + * or "@ NOK" + */ + const unsigned char *p; + uint8_t addr; + uint8_t exp_crc; + static uint8_t crc = 0; + + if (ch=='@') { /* a marker? start scanning again */ + *state = CMD_PARSER_INIT; + } + for(;;) { /* breaks or returns */ + switch(*state) { + case CMD_PARSER_INIT: + buf[0] = '\0'; /* reset buffer */ + if (ch=='@') { /* a marker? start scanning again */ + McuUtility_chcat(buf, bufSize, ch); + *state = CMD_PARSER_START_DETECTED; + break; /* continue state machine */ + } + return RS485_RESPONSE_CONTINUE; + + case CMD_PARSER_START_DETECTED: + *state = CMD_PARSER_SCAN_DST_ADDR; + return RS485_RESPONSE_CONTINUE; + + case CMD_PARSER_SCAN_DST_ADDR: + McuUtility_chcat(buf, bufSize, ch); + if (ch==' ') { + p = &buf[sizeof("@")-1]; + if (McuUtility_ScanHex8uNumberNoPrefix(&p, &addr)==ERR_OK && addr==RS485_GetAddress()) { + *state = CMD_PARSER_SCAN_SRC_ADDR; + return RS485_RESPONSE_CONTINUE; + } else { + *state = CMD_PARSER_INIT; + } + } else if (ch=='\n') { /* failed */ + *state = CMD_PARSER_INIT; + } + return RS485_RESPONSE_CONTINUE; + + case CMD_PARSER_SCAN_SRC_ADDR: + McuUtility_chcat(buf, bufSize, ch); + if (ch==' ') { + p = &buf[sizeof("@ss ")-1]; + if (McuUtility_ScanHex8uNumberNoPrefix(&p, &addr)==ERR_OK && addr==fromAddr) { + *state = CMD_PARSER_SCAN_CRC; + return RS485_RESPONSE_CONTINUE; + } else { + *state = CMD_PARSER_INIT; + } + } else if (ch=='\n') { /* failed */ + *state = CMD_PARSER_INIT; + } + return RS485_RESPONSE_CONTINUE; + + case CMD_PARSER_SCAN_CRC: + McuUtility_chcat(buf, bufSize, ch); + if (ch==' ') { + p = &buf[sizeof("@ss dd ")-1]; + if (McuUtility_ScanHex8uNumberNoPrefix(&p, &crc)==ERR_OK) { + *state = CMD_PARSER_SCAN_OK_NOK; + return RS485_RESPONSE_CONTINUE; + } else { + *state = CMD_PARSER_INIT; + } + } else if (ch=='\n') { /* failed */ + *state = CMD_PARSER_INIT; + } + return RS485_RESPONSE_CONTINUE; + + case CMD_PARSER_SCAN_OK_NOK: + McuUtility_chcat(buf, bufSize, ch); + if (ch=='\n') { + p = &buf[sizeof("@ss dd cc ")-1]; + if (McuUtility_strcmp((char*)p, (char*)"OK\n")==0) { /* a match! */ + buf[sizeof("@ss dd cc OK")-1] = '\0'; /* overwrite '\n' as not included in CRC */ + exp_crc = CalcMsgCrc(buf); + *state = CMD_PARSER_INIT; + if (crc==exp_crc) { + return RS485_RESPONSE_OK; + } else { + return RS485_RESPONSE_NOK; + } + } else if (McuUtility_strcmp((char*)buf, (char*)"NOK\n")==0) { /* a match! */ + buf[sizeof("@ss dd cc NOK")-1] = '\0'; /* overwrite '\n' as not included in CRC */ + exp_crc = CalcMsgCrc(buf); + *state = CMD_PARSER_INIT; + if (crc==exp_crc) { + return RS485_RESPONSE_NOK; + } else { + return RS485_RESPONSE_NOK; /* CRC is not ok, and message is NOK too */ + } + } else if (ch=='\n') { /* failed */ + *state = CMD_PARSER_INIT; + break; /* continue in state machine */ + } + } + return RS485_RESPONSE_CONTINUE; + + default: + break; + } /* switch */ + /* get here with a break */ + } /* for */ + return RS485_RESPONSE_CONTINUE; +} + +static RS485_Response_e WaitForResponse(int32_t timeoutMs, uint8_t fromAddr, McuShell_ConstStdIOType *shellIO, McuShell_ConstStdIOType *rsIO) { + unsigned char buf[24] = ""; /* enough for "@ OK" or "@ NOK" */ + unsigned char ch; + RS485_Response_e resp; + CMD_ParserState_e state = CMD_PARSER_INIT; + + for(;;) { /* returns */ + /* read response text and write into buffer or to console */ + static unsigned char lineBuffer[512]; /* enough for a line of text coming back from the bus */ + + lineBuffer[0] = '\0'; /* initialize buffer */ + do { + rsIO->stdIn(&ch); + if (ch!='\0') { + McuUtility_chcat(lineBuffer, sizeof(lineBuffer), ch); + if (ch=='\n') { + if (lineBuffer[0]!='@') { /* do not send things like OK or NOK messages from bus */ + SHELL_SendStringToIO(lineBuffer, shellIO); + } + lineBuffer[0] = '\0'; /* reset buffer */ + } + } + } while(ch!='\0'); + + ch = McuUart485_GetResponseQueueChar(); + if (ch!='\0') { + resp = Scan(&state, ch, buf, sizeof(buf), fromAddr); + if (resp==RS485_RESPONSE_OK || resp==RS485_RESPONSE_NOK) { + return resp; + } + } else { /* empty response buffer: check normal incoming characters */ + vTaskDelay(pdMS_TO_TICKS(50)); + #if PL_CONFIG_USE_WDT + WDT_Report(WDT_REPORT_ID_CURR_TASK, 50); + #endif + timeoutMs -= 50; + if (timeoutMs<=0) { + return RS485_RESPONSE_TIMEOUT; + } + } + } /* for */ + return RS485_RESPONSE_CONTINUE; +} + +uint8_t RS485_SendCommand(uint8_t dstAddr, const unsigned char *cmd, int32_t timeoutMs, uint32_t nofRetry, McuShell_ConstStdIOType *shellIO, McuShell_ConstStdIOType *rsIO) { + /* example: send "@16 1 cmd stepper status" */ + unsigned char buf[McuShell_DEFAULT_SHELL_BUFFER_SIZE]; + uint8_t res = ERR_OK; + RS485_Response_e resp; + uint8_t crc, hex; + + if (rsIO==NULL) { /* assign default */ + rsIO = &RS485Parse_stdio; + } + McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)("@")); + McuUtility_strcatNum8Hex(buf, sizeof(buf), dstAddr); /* add destination address */ + McuUtility_chcat(buf, sizeof(buf), ' '); + McuUtility_strcatNum8Hex(buf, sizeof(buf), RS485_GetAddress()); /* add src address */ + McuUtility_chcat(buf, sizeof(buf), ' '); + McuUtility_strcatNum8Hex(buf, sizeof(buf), 0); /* dummy crc, will be replaced with real one */ + McuUtility_strcat(buf, sizeof(buf), (unsigned char*)(" cmd ")); + McuUtility_strcat(buf, sizeof(buf), cmd); + /* update crc */ + crc = CalcMsgCrc(buf); + hex = (char)((crc>>4) & 0x0F); + buf[sizeof("@dd ss ")-1] = (char)(hex + ((hex <= 9) ? '0' : ('A'-10))); + hex = (char)(crc & 0x0F); + buf[sizeof("@dd ss c")-1] = (char)(hex + ((hex <= 9) ? '0' : ('A'-10))); + + if (xSemaphoreTakeRecursive(RS485_stdioMutex, portMAX_DELAY)==pdPASS) { /* take mutex */ + for(;;) { /* breaks */ + McuUart485_ClearResponseQueue(); /* clear up if there is something pending */ + if (RS485_DoLogging) { + McuLog_trace("Tx: %s", buf); + } + RS485_SendStr(buf); + RS485_SendStr((unsigned char*)"\n"); + if (dstAddr==RS485_BROADCAST_ADDRESS) { + /* do not wait for a OK/NOK response for broadcast messages. The caller has to check with 'lastError' */ + res = ERR_OK; + break; /* leave loop */ + } else { + vTaskDelay(pdMS_TO_TICKS(100)); /* give back some time for receiving a response */ + resp = WaitForResponse(timeoutMs, dstAddr, shellIO, rsIO); + if (resp==RS485_RESPONSE_OK) { + res = ERR_OK; + break; /* fine, leave loop */ + } else if (resp==RS485_RESPONSE_TIMEOUT) { /* board did not respond? */ + res = ERR_BUSOFF; /* retry */ + } else if (resp==RS485_RESPONSE_NOK) { /* not ok, crc error? */ + res = ERR_CRC; /* retry */ + } + } + /* NOK or timeout */ + if (nofRetry==0) { /* tried enough */ + res = ERR_FAILED; + break; /* leave loop */ + } + nofRetry--; /* try again */ + } /* for */ + (void)xSemaphoreGiveRecursive(RS485_stdioMutex); /* give back mutex */ + } + return res; +} + +static uint8_t PrintStatus(const McuShell_StdIOType *io) { + uint8_t buf[16]; + + McuShell_SendStatusStr((unsigned char*)"rs", (unsigned char*)"RS-485 settings\r\n", io->stdOut); + McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)"0x"); + McuUtility_strcatNum8Hex(buf, sizeof(buf), RS485_GetAddress()); + McuUtility_strcat(buf, sizeof(buf), (unsigned char*)"\r\n"); + McuShell_SendStatusStr((unsigned char*)" addr", buf, io->stdOut); + McuShell_SendStatusStr((unsigned char*)" log", RS485_DoLogging?(unsigned char*)"on\r\n":(unsigned char*)"off\r\n", io->stdOut); + return ERR_OK; +} + +static uint8_t PrintHelp(const McuShell_StdIOType *io) { + McuShell_SendHelpStr((unsigned char*)"rs", (unsigned char*)"Group of RS-485 commands\r\n", io->stdOut); + McuShell_SendHelpStr((unsigned char*)" help|status", (unsigned char*)"Print help or status information\r\n", io->stdOut); +#if PL_CONFIG_USE_NVMC + McuShell_SendHelpStr((unsigned char*)" addr ", (unsigned char*)"Set RS-485 address\r\n", io->stdOut); +#endif + McuShell_SendHelpStr((unsigned char*)" send ", (unsigned char*)"Send a text to the RS-485 bus\r\n", io->stdOut); + McuShell_SendHelpStr((unsigned char*)" sendcmd ", (unsigned char*)"Send a shell command to the RS-485 address and check response\r\n", io->stdOut); + McuShell_SendHelpStr((unsigned char*)" log on|off", (unsigned char*)"Log RS-485 bus activity to McuLog\r\n", io->stdOut); + return ERR_OK; +} + +uint8_t RS485_ParseCommand(const unsigned char *cmd, bool *handled, const McuShell_StdIOType *io) { + const unsigned char *p; + int32_t val; + + if (McuUtility_strcmp((char*)cmd, McuShell_CMD_HELP)==0 || McuUtility_strcmp((char*)cmd, "rs help")==0) { + *handled = TRUE; + return PrintHelp(io); + } else if ((McuUtility_strcmp((char*)cmd, McuShell_CMD_STATUS)==0) || (McuUtility_strcmp((char*)cmd, "rs status")==0)) { + *handled = TRUE; + return PrintStatus(io); + } else if (McuUtility_strncmp((char*)cmd, "rs log ", sizeof("rs log ")-1)==0) { + *handled = TRUE; + p = cmd + sizeof("rs log ")-1; + if (McuUtility_strcmp((char*)p, "on")==0) { + RS485_DoLogging = true; + return ERR_OK; + } else if (McuUtility_strcmp((char*)p, "off")==0) { + RS485_DoLogging = false; + return ERR_OK; + } + return ERR_FAILED; +#if PL_CONFIG_USE_NVMC + } else if (McuUtility_strncmp((char*)cmd, "rs addr ", sizeof("rs addr ")-1)==0) { + *handled = true; + p = cmd + sizeof("rs addr ")-1; + if (McuUtility_xatoi(&p, &val)==ERR_OK && val>=0 && val<=0xff) { + return NVMC_SetRS485Addr(val); + } + return ERR_FAILED; +#endif + } else if (McuUtility_strncmp((char*)cmd, "rs send ", sizeof("rs send ")-1)==0) { + *handled = true; + RS485_SendStr((unsigned char*)cmd+sizeof("rs send ")-1); + RS485_SendStr((unsigned char*)("\n")); + } else if (McuUtility_strncmp((char*)cmd, "rs sendcmd ", sizeof("rs sendcmd ")-1)==0) { + *handled = true; + p = cmd + sizeof("rs sendcmd ")-1; + if (McuUtility_xatoi(&p, &val)==ERR_OK) { /* parse destination address */ + unsigned char buffer[McuShell_CONFIG_DEFAULT_SHELL_BUFFER_SIZE]; + + while (*p==' ') { /* skip leading spaces */ + p++; + } + if (*p=='"') { /* double-quoted command: it can contain multiple commands */ + if (McuUtility_ScanDoubleQuotedString(&p, buffer, sizeof(buffer))!=ERR_OK) { + return ERR_FAILED; + } + p = buffer; + } + return RS485_SendCommand(val, (unsigned char*)p, 10000, 0, io, &RS485Parse_stdio); /* 10 seconds should be enough */ + } + return ERR_FAILED; + } + return ERR_OK; +} + +static uint8_t CheckHeader(unsigned char *msg, const unsigned char **startCmd, uint8_t *sourceAddr, uint8_t *destinationAddr) { + /* format is in the form "@ cmd help" */ + const unsigned char *p; + uint8_t dstAddr, srcAddr; + uint8_t expected_crc, crc, res; + unsigned char buf[42]; + + /* init with defaults in case of error */ + *sourceAddr = RS485_BROADCAST_ADDRESS; + *destinationAddr = RS485_BROADCAST_ADDRESS; + *startCmd = msg; + if (*msg=='@') { + p = msg+1; /* skip '@' */ + /* check for "@ " */ + if ( McuUtility_ScanHex8uNumberNoPrefix(&p, &dstAddr)==ERR_OK + && (dstAddr==RS485_GetAddress() || dstAddr==RS485_BROADCAST_ADDRESS) /* broadcast or matching destination address */ + && McuUtility_ScanHex8uNumberNoPrefix(&p, &srcAddr)==ERR_OK /* get source address */ + ) + { + *sourceAddr = srcAddr; + *destinationAddr = dstAddr; + /* check CRC */ + res = McuUtility_ScanHex8uNumberNoPrefix(&p, &crc); + if (res!=ERR_OK) { + return ERR_CRC; + } + expected_crc = CalcMsgCrc(msg); + if (crc!=expected_crc) { + if (dstAddr!=RS485_BROADCAST_ADDRESS) { /* only send back error if it was not a broadcast */ + McuUtility_strcpy(buf, sizeof(buf), (uint8_t*)"CRC_ERR 0x"); + McuUtility_strcatNum8Hex(buf, sizeof(buf), RS485_GetAddress()); + McuUtility_strcat(buf, sizeof(buf), (uint8_t*)": 0x"); + McuUtility_strcatNum8Hex(buf, sizeof(buf), crc); + McuUtility_strcat(buf, sizeof(buf), (uint8_t*)" expected 0x"); + McuUtility_strcatNum8Hex(buf, sizeof(buf), expected_crc); + McuUtility_chcat(buf, sizeof(buf), '\n'); + McuShell_SendStr(buf, RS485_stdio.stdErr); + } + return ERR_CRC; + } + *startCmd = p; + return ERR_OK; + } + } + return ERR_FAILED; +} + +static void RS485Task(void *pv) { + static uint8_t cmdBuf[McuShell_DEFAULT_SHELL_BUFFER_SIZE]; /* command line and text from the RS-485 bus */ + const unsigned char *startCmd; + uint8_t srcAddr, dstAddr; + uint8_t res, crc; + uint8_t buf[32]; + unsigned char hex; + bool reply; + static uint8_t lastError = ERR_OK; + + (void)pv; /* not used */ + McuLog_trace("Starting RS485 Task"); + #if PL_CONFIG_USE_WDT + WDT_SetTaskHandle(WDT_REPORT_ID_TASK_RS485, xTaskGetCurrentTaskHandle()); + #endif + cmdBuf[0] = '\0'; + for(;;) { + while (!McuUart485_stdio.keyPressed()) { /* if nothing in input queue, give back some CPU time */ + vTaskDelay(pdMS_TO_TICKS(10)); + #if PL_CONFIG_USE_WDT + WDT_Report(WDT_REPORT_ID_TASK_RS485, 10); + #endif + } + if (McuShell_ReadCommandLine(cmdBuf, sizeof(cmdBuf), &RS485Parse_stdio)==ERR_OK) { + reply = false; + srcAddr = RS485_ILLEGAL_ADDRESS; + dstAddr = RS485_ILLEGAL_ADDRESS; + if (cmdBuf[0]=='@' && strlen((char*)cmdBuf)>sizeof("@dd ss cc ")-1) { /* have a valid message? */ + if (RS485_DoLogging) { + McuLog_trace("Rx: %s", cmdBuf); + } + reply = false; /* default */ + res = CheckHeader(cmdBuf, &startCmd, &srcAddr, &dstAddr); + if (res == ERR_CRC) { /* wrong crc */ + lastError = ERR_CRC; + reply = true; + } else if (res==ERR_OK) { /* header was ok */ + res = ERR_FAILED; /* set default return value */ + if (McuUtility_strcmp((char*)startCmd, (char*)" cmd lastError")==0) { + reply = true; + res = lastError; /* report back last error */ + lastError = ERR_OK; /* clear error */ + } else if (McuUtility_strcmp((char*)startCmd, (char*)" cmd idle")==0) { + reply = true; +#if 0 /* \TODO */ + if (STEPPER_IsIdle()) { + res = ERR_OK; /* ERR_OK if board is idle */ + } else { + res = ERR_FAILED; /* not idle */ + } +#else + res = ERR_FAILED; /* not idle */ +#endif + } else if (McuUtility_strncmp((char*)startCmd, " cmd ", sizeof(" cmd ")-1)==0) { /* shell command? */ + McuUart485_ClearResponseQueue(); /* clear any pending response: we are going to parse a new command */ + startCmd += sizeof(" cmd ")-1; + if (dstAddr==RS485_BROADCAST_ADDRESS) { + res = SHELL_ParseCommandIO(startCmd, &RS485_stdioBroadcast, true); /* do not write anything back if broadcast */ + } else { + res = SHELL_ParseCommandIO(startCmd, &RS485_stdio, true); + } + lastError = res; /* remember error status if we get asked later on */ + reply = true; + } else if (McuUtility_strcmp((char*)startCmd, (char*)" OK")==0) { + reply = false; + } else if (McuUtility_strcmp((char*)startCmd, (char*)" NOK")==0) { + reply = false; + } + } + } else { + /* not starting with '@', print it ... */ + McuUtility_strcat(cmdBuf, sizeof(cmdBuf), (unsigned char*)"\r\n"); /* for the shell parser, the new-line has been removed. Add it again for output */ + SHELL_SendString((unsigned char *)cmdBuf); /* \TODO do not send directly to UART: instead, use a stdio which buffers the output */ + } + cmdBuf[0] = '\0'; /* reset buffer for next iteration */ + /* send response back to sender */ + if (reply && dstAddr!=RS485_BROADCAST_ADDRESS) { /* normal message, send response. For broadcasts it is up to the caller to check the last error */ + McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)"@"); + McuUtility_strcatNum8Hex(buf, sizeof(buf), srcAddr); + McuUtility_chcat(buf, sizeof(buf), ' '); + McuUtility_strcatNum8Hex(buf, sizeof(buf), RS485_GetAddress()); + McuUtility_chcat(buf, sizeof(buf), ' '); + McuUtility_strcatNum8Hex(buf, sizeof(buf), 0); /* dummy crc, will be replaced later */ + if (res==ERR_OK) { + McuUtility_strcat(buf, sizeof(buf), (unsigned char*)" OK"); + } else { + McuUtility_strcat(buf, sizeof(buf), (unsigned char*)" NOK"); + } + crc = CalcMsgCrc(buf); + hex = (char)((crc>>4) & 0x0F); + buf[sizeof("@dd ss ")-1] = (char)(hex + ((hex <= 9) ? '0' : ('A'-10))); + hex = (char)(crc & 0x0F); + buf[sizeof("@dd ss c")-1] = (char)(hex + ((hex <= 9) ? '0' : ('A'-10))); + McuUtility_chcat(buf, sizeof(buf), '\n'); + RS485_SendStr(buf); + } + } + } /* for */ +} + +void RS485_Deinit(void) { + McuUart485_Deinit(); +} + +void RS485_Init(void) { + McuUart485_Init(); + if (xTaskCreate( + RS485Task, /* pointer to the task */ + "RS-485", /* task name for kernel awareness debugging */ + 1300/sizeof(StackType_t), /* task stack size */ + (void*)NULL, /* optional task startup argument */ + tskIDLE_PRIORITY+4, /* initial priority */ + (TaskHandle_t*)NULL /* optional task handle to create */ + ) != pdPASS) + { + McuLog_fatal("Failed creating RS-485 task"); + for(;;){} /* error! probably out of memory */ + } + RS485_stdioMutex = xSemaphoreCreateRecursiveMutex(); + if (RS485_stdioMutex==NULL) { /* creation failed? */ + McuLog_fatal("Failed creating RS-485 Standard I/O mutex"); + for(;;); + } + vQueueAddToRegistry(RS485_stdioMutex, "RS485StdIoMutex"); +} + +#endif /* PL_CONFIG_USE_RS485 */ diff --git a/ADIS_ESP32_Eclipse/main/rs485.h b/ADIS_ESP32_Eclipse/main/rs485.h new file mode 100644 index 0000000..765d3d8 --- /dev/null +++ b/ADIS_ESP32_Eclipse/main/rs485.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019-2022, Erich Styger + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef RS485_H_ +#define RS485_H_ + +#include +#include +#include "McuShell.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/*! special pre-defined node addresses */ +#define RS485_BROADCAST_ADDRESS (0x00) + /*!< special broadcast address */ +#define RS485_ILLEGAL_ADDRESS (0xff) + /*!< illegal/initialization value */ + +/*! + * \brief Send a shell command to the RS-485 bus + * \param dstAddr Destination node address + * \param cmd The shell command string + * \param timeoutMs timeout in milliseconds to wait for a response + * \param nofRetry Number of retries + * \param shellIO I/O handler of the calling shell, used for writing output of the command + * \param rsIO I/O handler for the RS-485 bus, used to read incoming characters + * \return Error code, or ERR_OK + */ +uint8_t RS485_SendCommand(uint8_t dstAddr, const unsigned char *cmd, int32_t timeoutMs, uint32_t nofRetry, McuShell_ConstStdIOType *shellIO, McuShell_ConstStdIOType *rsIO); + +/*! + * \brief Getter for RS-485 node address + * \return address of node + */ +uint8_t RS485_GetAddress(void); + +/*! + * \brief Shell command line parser + * \param cmd command to be parsed + * \param handled if command was recognized + * \param io I/O handler + * \return Error code, or ERR_OK + */ +uint8_t RS485_ParseCommand(const unsigned char *cmd, bool *handled, const McuShell_StdIOType *io); + +/*! \brief Module de-initialization */ +void RS485_Deinit(void); + +/*! \brief Module initialization */ +void RS485_Init(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* RS485_H_ */ diff --git a/ADIS_ESP32_Eclipse/main/sntp_time.c b/ADIS_ESP32_Eclipse/main/sntp_time.c new file mode 100644 index 0000000..c3d49a4 --- /dev/null +++ b/ADIS_ESP32_Eclipse/main/sntp_time.c @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2021, Erich Styger + * + * SPDX-License-Identifier: BSD-3-Clause + */ + + +#include "platform.h" +#if PL_CONFIG_USE_SNTP_TIME +#include "sntp_time.h" +#include "esp_sntp.h" +#include "esp_log.h" +#include "wifi.h" +#include "McuTimeDate.h" +#include "McuLog.h" + +static void SNTP_SetTime(void) { + time_t now = 0; + struct tm timeinfo = { 0 }; + + time(&now); + setenv("TZ", "CET-1CEST,M3.5.0,M10.5.0/3", 1); /* see https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv */ + tzset(); + localtime_r(&now, &timeinfo); + + char strftime_buf[64]; + + strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo); + McuLog_info("The current date/time is: %s", strftime_buf); + +#if PL_CONFIG_USE_TIME_DATE + uint32_t seconds; + TIMEREC timeRec; + DATEREC dateRec; + + seconds = (uint32_t)time(NULL); /* get number of seconds since 1970 */ + McuTimeDate_UnixSecondsToTimeDateCustom(seconds, -1, &timeRec, &dateRec, 1970); + McuTimeDate_SetTimeDate(&timeRec, &dateRec); +#endif +} + +void time_sync_notification_cb(struct timeval *tv) { + McuLog_info("Notification of a time synchronization event"); + SNTP_SetTime(); +} + +static void initialize_sntp(void) { + McuLog_info("Initializing SNTP"); + sntp_setoperatingmode(SNTP_OPMODE_POLL); + sntp_setservername(0, "pool.ntp.org"); + sntp_set_time_sync_notification_cb(time_sync_notification_cb); +#ifdef CONFIG_SNTP_TIME_SYNC_METHOD_SMOOTH + sntp_set_sync_mode(SNTP_SYNC_MODE_SMOOTH); +#endif + sntp_init(); +} + +int SNTP_ObtainTime(void) { + const int retry_count = 10; + int retry = 0; + + if (!WiFi_isConnected()) { + McuLog_error("Cannot get sntp time, WiFi not connected"); + return -1; /* failure */ + } + /** + * NTP server address could be acquired via DHCP, + * see LWIP_DHCP_GET_NTP_SRV menuconfig option + */ +#ifdef LWIP_DHCP_GET_NTP_SRV + sntp_servermode_dhcp(1); +#endif + + initialize_sntp(); + + /* wait for time to be set */ + while (sntp_get_sync_status() == SNTP_SYNC_STATUS_RESET && ++retry < retry_count) { + McuLog_info("Waiting for system time to be set... (%d/%d)", retry, retry_count); + vTaskDelay(pdMS_TO_TICKS(2000)); + } + /* here we have received a valid time/date from the SNTP server */ + return 0; /* ok */ +} + +void SNTP_Init(void) { + /* nothing needed */ +} + +#endif /* PL_CONFIG_USE_SNTP_TIME */ diff --git a/ADIS_ESP32_Eclipse/main/sntp_time.h b/ADIS_ESP32_Eclipse/main/sntp_time.h new file mode 100644 index 0000000..4471c8b --- /dev/null +++ b/ADIS_ESP32_Eclipse/main/sntp_time.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2021, Erich Styger + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MAIN_SNTP_TIME_H_ +#define MAIN_SNTP_TIME_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! + * \brief Get the time from the sntp server + * \return 0 if time has been obtained, negative value in case of error. + */ +int SNTP_ObtainTime(void); + +/*! \brief module initialization */ +void SNTP_Init(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* MAIN_SNTP_TIME_H_ */ diff --git a/ADIS_ESP32_Eclipse/main/timer.c b/ADIS_ESP32_Eclipse/main/timer.c new file mode 100644 index 0000000..6e5479c --- /dev/null +++ b/ADIS_ESP32_Eclipse/main/timer.c @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2021, Erich Styger + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "platform.h" +#if PL_CONFIG_USE_TIME_DATE +#include "timer.h" +#include "McuRTOS.h" +#include "esp_log.h" +#include "McuTimeDate.h" + +static const char *TAG = "Timer"; + +static TimerHandle_t timer; + +static void timerCallback(TimerHandle_t xTimer) { + McuTimeDate_AddTick(); +} + +void TMR_Init(void) { + /* create auto-reload timer to update software RTC */ + timer = xTimerCreate("timer", pdMS_TO_TICKS(McuTimeDate_CONFIG_TICK_TIME_MS), pdTRUE, NULL, timerCallback); + if (timer==NULL) { + ESP_LOGE(TAG, "Failed creating timer"); + return; + } + xTimerStart(timer, 0); +} +#endif /* PL_CONFIG_USE_TIME_DATE */ diff --git a/ADIS_ESP32_Eclipse/main/timer.h b/ADIS_ESP32_Eclipse/main/timer.h new file mode 100644 index 0000000..082f63c --- /dev/null +++ b/ADIS_ESP32_Eclipse/main/timer.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2021, Erich Styger + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MAIN_TIMER_H_ +#define MAIN_TIMER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! \brief Module initialization */ +void TMR_Init(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* MAIN_TIMER_H_ */ diff --git a/ADIS_ESP32_Eclipse/main/udp_client.c b/ADIS_ESP32_Eclipse/main/udp_client.c new file mode 100644 index 0000000..b2945d2 --- /dev/null +++ b/ADIS_ESP32_Eclipse/main/udp_client.c @@ -0,0 +1,303 @@ +/* BSD Socket API Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#include "platform.h" +#if PL_CONFIG_USE_UDP_CLIENT +#include +#include +#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 "esp_netif.h" +#include "lwip/err.h" +#include "lwip/sockets.h" +#include "lwip/sys.h" +#include + +#include "udp_client.h" +#include "McuShell.h" +#include "McuUtility.h" + +#define CONFIG_EXAMPLE_IPV4 + +#ifdef CONFIG_EXAMPLE_IPV4 + #define HOST_IP_ADDR "192.168.0.146" +#else + #define HOST_IP_ADDR "FE80::30AD:E57B:C212:68AD" /*CONFIG_EXAMPLE_IPV6_ADDR*/ +#endif +#define PORT 3333 + +static uint16_t udp_client_destination_port = PORT; +static unsigned char udp_client_destination_host[24] = HOST_IP_ADDR; + +static const char *TAG = "udp_client"; +static TaskHandle_t taskHandle = NULL; /* udp client task handle */ + +#if 0 +static const char *payload = "Message from ESP32 "; + +static void udp_client_task(void *pvParameters) { + char rx_buffer[128]; + char addr_str[128]; + int addr_family; + int ip_protocol; + + vTaskSuspend(NULL); /* UDP_Client_Start() will wake me up */ + for(;;) { +#ifdef CONFIG_EXAMPLE_IPV4 + struct sockaddr_in dest_addr; + dest_addr.sin_addr.s_addr = inet_addr(HOST_IP_ADDR); + dest_addr.sin_family = AF_INET; + dest_addr.sin_port = htons(PORT); + addr_family = AF_INET; + ip_protocol = IPPROTO_IP; + inet_ntoa_r(dest_addr.sin_addr, addr_str, sizeof(addr_str) - 1); +#else /* IPV6 */ + struct sockaddr_in6 dest_addr; + inet6_aton(HOST_IP_ADDR, &dest_addr.sin6_addr); + dest_addr.sin6_family = AF_INET6; + dest_addr.sin6_port = htons(PORT); + addr_family = AF_INET6; + ip_protocol = IPPROTO_IPV6; + inet6_ntoa_r(dest_addr.sin6_addr, addr_str, sizeof(addr_str) - 1); +#endif + + int sock = socket(addr_family, SOCK_DGRAM, ip_protocol); + if (sock < 0) { + ESP_LOGE(TAG, "Unable to create socket: errno %d", errno); + break; + } + ESP_LOGI(TAG, "Socket created, sending to %s:%d", HOST_IP_ADDR, PORT); + while (1) { + int err = sendto(sock, payload, strlen(payload), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr)); + if (err < 0) { + ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno); + break; + } + ESP_LOGI(TAG, "Message sent"); + + struct timeval to; + + to.tv_sec = 1; + to.tv_usec = 0; + if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &to, sizeof(to)) < 0) { + ESP_LOGE(TAG, "setting socket timeout failed"); + } + + ESP_LOGI(TAG, "Waiting for response"); + struct sockaddr_in source_addr; /* Large enough for both IPv4 or IPv6 */ + socklen_t socklen = sizeof(source_addr); + int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&source_addr, &socklen); + + /* Error occurred during receiving */ + if (len < 0) { + ESP_LOGE(TAG, "recvfrom failed: errno %d", errno); + break; + } else { /* Data received */ + rx_buffer[len] = 0; /* Null-terminate whatever we received and treat like a string */ + ESP_LOGI(TAG, "Received %d bytes from %s:", len, addr_str); + ESP_LOGI(TAG, "%s", rx_buffer); + } + vTaskDelay(pdMS_TO_TICKS(2000)); + } + if (sock != -1) { + ESP_LOGE(TAG, "Shutting down socket and restarting..."); + shutdown(sock, 0); + close(sock); + } + } /* for */ + vTaskDelete(NULL); +} +#endif + +static uint8_t udp_client_send(const unsigned char *host, uint16_t port, const unsigned char *msg, unsigned char *rxBuffer, size_t rxBufferSize) { + char addr_str[128]; + int addr_family; + int ip_protocol; + uint8_t res = ERR_OK; + +#ifdef CONFIG_EXAMPLE_IPV4 + struct sockaddr_in dest_addr; + dest_addr.sin_addr.s_addr = inet_addr((const char*)host); + dest_addr.sin_family = AF_INET; + dest_addr.sin_port = htons(port); + addr_family = AF_INET; + ip_protocol = IPPROTO_IP; + inet_ntoa_r(dest_addr.sin_addr, addr_str, sizeof(addr_str) - 1); +#else /* IPV6 */ + struct sockaddr_in6 dest_addr; + inet6_aton(host, &dest_addr.sin6_addr); + dest_addr.sin6_family = AF_INET6; + dest_addr.sin6_port = htons(port); + addr_family = AF_INET6; + ip_protocol = IPPROTO_IPV6; + inet6_ntoa_r(dest_addr.sin6_addr, addr_str, sizeof(addr_str) - 1); +#endif + + int sock = socket(addr_family, SOCK_DGRAM, ip_protocol); + for(;;) { /* breaks in in case of error */ + if (sock < 0) { + ESP_LOGE(TAG, "Unable to create socket: errno %d", errno); + res = ERR_FAILED; + break; + } + ESP_LOGI(TAG, "Socket created, sending to %s:%d", host, port); + + int err = sendto(sock, msg, strlen((char*)msg), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr)); + if (err < 0) { + ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno); + res = ERR_FAILED; + break; + } + ESP_LOGI(TAG, "Message sent"); + + struct timeval to; + + to.tv_sec = 1; + to.tv_usec = 0; + if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &to, sizeof(to)) < 0) { + ESP_LOGE(TAG, "setting socket timeout failed"); + res = ERR_FAILED; + break; + } + + ESP_LOGI(TAG, "Waiting for response"); + struct sockaddr_in source_addr; /* Large enough for both IPv4 or IPv6 */ + socklen_t socklen = sizeof(source_addr); + int len = recvfrom(sock, rxBuffer, rxBufferSize-1, 0, (struct sockaddr *)&source_addr, &socklen); + + /* Error occurred during receiving */ + if (len < 0) { + ESP_LOGE(TAG, "recvfrom failed: errno %d", errno); + res = ERR_FAILED; + break; + } else { /* Data received */ + rxBuffer[len] = '\0'; /* Null-terminate whatever we received and treat like a string */ + ESP_LOGI(TAG, "Received %d bytes from %s:", len, addr_str); + ESP_LOGI(TAG, "%s", rxBuffer); + } + break; + } /* for */ + if (sock != -1) { + ESP_LOGE(TAG, "Shutting down socket"); + shutdown(sock, 0); + close(sock); + } + return res; +} + +void UDP_Client_Start(void) { + if (taskHandle!=NULL) { + vTaskResume(taskHandle); + } +} + +void UDP_Client_Stop(void) { + if (taskHandle!=NULL) { + vTaskSuspend(taskHandle); + } +} + +void UDP_Client_Init(void) { +#if 0 + BaseType_t res; + + res = xTaskCreate(udp_client_task, "udp_client", 16*1024/sizeof(StackType_t), NULL, 5, &taskHandle); + if (res==pdPASS) { + ESP_LOGI(TAG, "created UDP client task"); + } else { + ESP_LOGE(TAG, "failed creating UDP client task!"); + } +#endif +} + +#if PL_CONFIG_USE_SHELL +static uint8_t PrintStatus(const McuShell_StdIOType *io) { + unsigned char buf[64]; + + McuShell_SendStatusStr((unsigned char*)"udpc", (unsigned char*)"UDP client status\r\n", io->stdOut); + + McuUtility_strcpy(buf, sizeof(buf), udp_client_destination_host); + McuUtility_chcat(buf, sizeof(buf), ':'); + McuUtility_strcatNum16u(buf, sizeof(buf), udp_client_destination_port); + McuUtility_strcat(buf, sizeof(buf), (unsigned char*)"\r\n"); + McuShell_SendStatusStr((unsigned char*)" host", buf, io->stdOut); + return ERR_OK; +} + +static uint8_t PrintHelp(const McuShell_StdIOType *io) { + McuShell_SendHelpStr((unsigned char*)"udpc", (unsigned char*)"Group of UDP client commands\r\n", io->stdOut); + McuShell_SendHelpStr((unsigned char*)" help|status", (unsigned char*)"Shows motor help or status\r\n", io->stdOut); + McuShell_SendHelpStr((unsigned char*)" host ", (unsigned char*)"Set default host destination IP address (double quoted)\r\n", io->stdOut); + McuShell_SendHelpStr((unsigned char*)" port ", (unsigned char*)"Set default host destination port number\r\n", io->stdOut); + McuShell_SendHelpStr((unsigned char*)" send default ", (unsigned char*)"Send message (double quoted) to default host and port\r\n", io->stdOut); + McuShell_SendHelpStr((unsigned char*)" send ", (unsigned char*)"Send message (double quoted) to ip (double quoted) and port\r\n", io->stdOut); + return ERR_OK; +} + +uint8_t UDP_Client_ParseCommand(const unsigned char* cmd, bool *handled, const McuShell_StdIOType *io) { + const unsigned char *p; + unsigned char msgBuf[64]; + unsigned char rxBuf[64]; + unsigned char ip[24]; + uint16_t port; + + if (McuUtility_strcmp((char*)cmd, (char*)McuShell_CMD_HELP)==0 || McuUtility_strcmp((char*)cmd, (char*)"udpc help")==0) { + *handled = TRUE; + return PrintHelp(io); + } else if (McuUtility_strcmp((char*)cmd, (char*)McuShell_CMD_STATUS)==0 || McuUtility_strcmp((char*)cmd, (char*)"udpc status")==0) { + *handled = TRUE; + return PrintStatus(io); + } else if (McuUtility_strncmp((char*)cmd, (char*)"udpc host ", sizeof("udpc host ")-1)==0) { + *handled = TRUE; + p = cmd + sizeof("udpc host ")-1; + return McuUtility_ScanDoubleQuotedString(&p, udp_client_destination_host, sizeof(udp_client_destination_host)); + } else if (McuUtility_strncmp((char*)cmd, (char*)"udpc port ", sizeof("udpc port ")-1)==0) { + *handled = TRUE; + p = cmd + sizeof("udpc port ")-1; + return McuUtility_ScanDecimal16uNumber(&p, &udp_client_destination_port); + } else if (McuUtility_strncmp((char*)cmd, (char*)"udpc send default ", sizeof("udpc send default ")-1)==0) { + *handled = TRUE; + p = cmd + sizeof("udpc send default ")-1; + if (McuUtility_ScanDoubleQuotedString(&p, msgBuf, sizeof(msgBuf))!=ERR_OK) { + return ERR_FAILED; + } + if (udp_client_send(udp_client_destination_host, udp_client_destination_port, msgBuf, rxBuf, sizeof(rxBuf))!=ERR_OK) { + return ERR_FAILED; + } + McuShell_SendStr(rxBuf, io->stdOut); + return ERR_OK; + } else if (McuUtility_strncmp((char*)cmd, (char*)"udpc send ", sizeof("udpc send ")-1)==0) { + *handled = TRUE; + p = cmd + sizeof("udpc send ")-1; + if (McuUtility_ScanDoubleQuotedString(&p, ip, sizeof(ip))!=ERR_OK) { + return ERR_FAILED; + } + if (McuUtility_ScanDecimal16uNumber(&p, &port)!=ERR_OK) { + return ERR_FAILED; + } + if (McuUtility_ScanDoubleQuotedString(&p, msgBuf, sizeof(msgBuf))!=ERR_OK) { + return ERR_FAILED; + } + if (udp_client_send(ip, port, msgBuf, rxBuf, sizeof(rxBuf))!=ERR_OK) { + return ERR_FAILED; + } + McuShell_SendStr(rxBuf, io->stdOut); + return ERR_OK; + } + return ERR_OK; +} + +#endif /* PL_CONFIG_USE_SHELL */ + +#endif /* PL_CONFIG_USE_UDP_CLIENT */ diff --git a/ADIS_ESP32_Eclipse/main/udp_client.h b/ADIS_ESP32_Eclipse/main/udp_client.h new file mode 100644 index 0000000..32baca6 --- /dev/null +++ b/ADIS_ESP32_Eclipse/main/udp_client.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019, 2020, Erich Styger + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef UDP_CLIENT_H_ +#define UDP_CLIENT_H_ + +#include "platform.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if PL_CONFIG_USE_SHELL + #include "McuShell.h" + + /*! + * \brief Command line and shell handler + * \param cmd The command to be parsed + * \param handled If command has been recognized and handled + * \param io I/O handler to be used + * \return error code, otherwise ERR_OK + */ + uint8_t UDP_Client_ParseCommand(const unsigned char* cmd, bool *handled, const McuShell_StdIOType *io); +#endif + +void UDP_Client_Start(void); +void UDP_Client_Stop(void); + +/*! \brief Module initialization */ +void UDP_Client_Init(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* UDP_CLIENT_H_ */ diff --git a/ADIS_ESP32_Eclipse/main/udp_server.c b/ADIS_ESP32_Eclipse/main/udp_server.c new file mode 100644 index 0000000..8a6ddcf --- /dev/null +++ b/ADIS_ESP32_Eclipse/main/udp_server.c @@ -0,0 +1,151 @@ +/* BSD Socket API Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include "platform.h" +#if PL_CONFIG_USE_UDP_SERVER +#include "udp_server.h" +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "esp_wifi.h" +#include "esp_event.h" +#include "esp_log.h" +#include "nvs_flash.h" +#include "esp_netif.h" + +#include "lwip/err.h" +#include "lwip/sockets.h" +#include "lwip/sys.h" +#include + +#include "Shell.h" +#include "McuUtility.h" +#include "McuLog.h" + +#define CONFIG_EXAMPLE_IPV4 + +static TaskHandle_t taskHandle = NULL; /* udp server task handle */ + +static int SendToSocket(int sock, const char *msg, const struct sockaddr *to, socklen_t tolen) { + return sendto(sock, msg, McuUtility_strlen((char*)msg), 0, to, tolen); +} + +static void udp_server_task(void *pvParameters) { + char rx_buffer[128]; + char addr_str[128]; + int addr_family; + int ip_protocol; + + vTaskSuspend(NULL); /* UDP_Server_Start() will wake me up */ + for(;;) { +#ifdef CONFIG_EXAMPLE_IPV4 + struct sockaddr_in dest_addr; + + dest_addr.sin_addr.s_addr = htonl(INADDR_ANY); /** 0.0.0.0 */ + dest_addr.sin_family = AF_INET; + dest_addr.sin_port = htons(UDP_SERVER_PORT); + addr_family = AF_INET; + ip_protocol = IPPROTO_IP; + inet_ntoa_r(dest_addr.sin_addr, addr_str, sizeof(addr_str) - 1); +#else // IPV6 + struct sockaddr_in6 dest_addr; + bzero(&dest_addr.sin6_addr.un, sizeof(dest_addr.sin6_addr.un)); + dest_addr.sin6_family = AF_INET6; + dest_addr.sin6_port = htons(UDP_SERVER_PORT);PO + addr_family = AF_INET6; + ip_protocol = IPPROTO_IPV6; + inet6_ntoa_r(dest_addr.sin6_addr, addr_str, sizeof(addr_str) - 1); +#endif + + int sock = socket(addr_family, SOCK_DGRAM, ip_protocol); + if (sock < 0) { + McuLog_error("Unable to create socket: errno %d", errno); + break; + } + McuLog_info("Socket created"); + + int err = bind(sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr)); + if (err < 0) { + McuLog_error("Socket unable to bind: errno %d", errno); + } + McuLog_info("Socket bound, port %d", UDP_SERVER_PORT); + while (1) { + McuLog_info("Waiting for data on port %d", UDP_SERVER_PORT); + struct sockaddr_in6 source_addr; /* Large enough for both IPv4 or IPv6 */ + socklen_t socklen = sizeof(source_addr); + + /* receive data (blocking): */ + int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer)-1, 0, (struct sockaddr *)&source_addr, &socklen); + + /* Error occurred during receiving */ + if (len < 0) { + McuLog_error("recvfrom failed: errno %d", errno); + break; + } else { /* Data received */ + /* Get the sender's ip address as string */ + if (source_addr.sin6_family == PF_INET) { + inet_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr.s_addr, addr_str, sizeof(addr_str) - 1); + } else if (source_addr.sin6_family == PF_INET6) { + inet6_ntoa_r(source_addr.sin6_addr, addr_str, sizeof(addr_str) - 1); + } + rx_buffer[len] = '\0'; /* Null-terminate whatever we received and treat like a string... */ + McuLog_info("Received %d bytes from %s:\n%s", len, addr_str, rx_buffer); + + /* \TODO Need to handle messages and send them to the robot */ + + /* send back response */ + unsigned char test_response[128]; + int err; + + McuLog_info("Sending back response"); + McuUtility_strcpy(test_response, sizeof(test_response), (unsigned char*)"OK"); /* default response */ + if (McuUtility_strncmp(rx_buffer, "test", sizeof("test")-1)==0) { /* hard-coded command */ + McuUtility_strcpy(test_response, sizeof(test_response), (unsigned char*)"test_response"); + } + err = SendToSocket(sock, (const char*)test_response, (struct sockaddr *)&source_addr, sizeof(source_addr)); + if (err < 0) { + McuLog_error("Error occurred during sending: errno %d", errno); + } + } /* if */ + } /* while */ + if (sock != -1) { + McuLog_error("Shutting down socket and restarting..."); + shutdown(sock, 0); + close(sock); + } + } /* for */ + vTaskDelete(NULL); +} + +void UDP_Server_Start(void) { + if (taskHandle!=NULL) { + vTaskResume(taskHandle); + } +} + +void UDP_Server_Stop(void) { + if (taskHandle!=NULL) { + vTaskSuspend(taskHandle); + } +} + +void UDP_Server_Init(void) { + BaseType_t res; + + res = xTaskCreate(udp_server_task, "udp_server", (16*1024)/sizeof(StackType_t), NULL, tskIDLE_PRIORITY+5, &taskHandle); + if (res==pdPASS) { + McuLog_info("created UDP server task"); + } else { + McuLog_error("failed creating UDP server task!"); + } +} + +#endif /* PL_CONFIG_USE_UDP_SERVER */ diff --git a/ADIS_ESP32_Eclipse/main/udp_server.h b/ADIS_ESP32_Eclipse/main/udp_server.h new file mode 100644 index 0000000..1fc07e9 --- /dev/null +++ b/ADIS_ESP32_Eclipse/main/udp_server.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019, 2020, 202ยง, Erich Styger + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef UDP_SERVER_H_ +#define UDP_SERVER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#define UDP_SERVER_PORT (1234) /*!< default UDP server port */ + +/*! \brief start the UDP server */ +void UDP_Server_Start(void); + +/*! \brief stop the UDP server */ +void UDP_Server_Stop(void); + +/*! \brief Module initialization */ +void UDP_Server_Init(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* UDP_SERVER_H_ */ diff --git a/ADIS_ESP32_Eclipse/main/udp_server_shell.c b/ADIS_ESP32_Eclipse/main/udp_server_shell.c new file mode 100644 index 0000000..e99c08a --- /dev/null +++ b/ADIS_ESP32_Eclipse/main/udp_server_shell.c @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021, Erich Styger + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "platform.h" +#if PL_CONFIG_USE_UDP_SERVER +#include "udp_server_shell.h" +#include "udp_server.h" +#include "wifi.h" +#include "McuShell.h" +#include "McuUtility.h" +#include "Shell.h" + +#if PL_CONFIG_USE_SHELL + +static uint8_t PrintStatus(const McuShell_StdIOType *io) { + unsigned char buf[32]; + + McuShell_SendStatusStr((unsigned char*)"udps", (unsigned char*)"ESP32 udp server status\r\n", io->stdOut); + McuUtility_Num32sToStr(buf, sizeof(buf), UDP_SERVER_PORT); + McuUtility_strcat(buf, sizeof(buf), (unsigned char*)"\r\n"); + McuShell_SendStatusStr((unsigned char*)" port", buf, io->stdOut); + return ERR_OK; +} + +static uint8_t PrintHelp(const McuShell_StdIOType *io) { + McuShell_SendHelpStr((unsigned char*)"udps", (unsigned char*)"Group of udp server commands\r\n", io->stdOut); + McuShell_SendHelpStr((unsigned char*)" help|status", (unsigned char*)"Shows udp server help or status\r\n", io->stdOut); + return ERR_OK; +} + +uint8_t UDP_Server_ParseCommand(const unsigned char* cmd, bool *handled, const McuShell_StdIOType *io) { + if (McuUtility_strcmp((char*)cmd, (char*)McuShell_CMD_HELP)==0 || McuUtility_strcmp((char*)cmd, (char*)"udps help")==0) { + *handled = TRUE; + return PrintHelp(io); + } else if (McuUtility_strcmp((char*)cmd, (char*)McuShell_CMD_STATUS)==0 || McuUtility_strcmp((char*)cmd, (char*)"udps status")==0) { + *handled = TRUE; + return PrintStatus(io); + } + return ERR_OK; +} +#endif /* PL_CONFIG_USE_SHELL */ + +#endif /* PL_CONFIG_USE_UDP_SERVER */ diff --git a/ADIS_ESP32_Eclipse/main/udp_server_shell.h b/ADIS_ESP32_Eclipse/main/udp_server_shell.h new file mode 100644 index 0000000..748ace5 --- /dev/null +++ b/ADIS_ESP32_Eclipse/main/udp_server_shell.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2021, Erich Styger + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef UDP_SERVER_SHELL_H_ +#define UDP_SERVER_SHELL_H_ + +#include "platform.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if PL_CONFIG_USE_SHELL + #include "McuShell.h" + + /*! + * \brief Command line and shell handler + * \param cmd The command to be parsed + * \param handled If command has been recognized and handled + * \param io I/O handler to be used + * \return error code, otherwise ERR_OK + */ + uint8_t UDP_Server_ParseCommand(const unsigned char* cmd, bool *handled, const McuShell_StdIOType *io); +#endif /* PL_CONFIG_USE_SHELL */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* UDP_SERVER_SHELL_H_ */ diff --git a/ADIS_ESP32_Eclipse/main/wifi.c b/ADIS_ESP32_Eclipse/main/wifi.c new file mode 100644 index 0000000..8eb4f68 --- /dev/null +++ b/ADIS_ESP32_Eclipse/main/wifi.c @@ -0,0 +1,383 @@ +/* + * Copyright (c) 2019, 2020, 2021, Erich Styger + * + * SPDX-License-Identifier: BSD-3-Clause + * ****************************************************************** + * Dear programmer: + * When I wrote this code, only god and I knew how it worked. + * Now, only god knows it! + * + * Therefore, if you are trying to optimize or change this code + * and it fails (most surely), please increase the counter below + * as a warning for the next person: + * + * total_hours_wasted_here = 257 + * ******************************************************************* + */ + +#include "platform.h" +#if PL_CONFIG_USE_WIFI +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_wifi.h" +#include "esp_wpa2.h" +#include "esp_event.h" +#include "esp_log.h" +#include "esp_system.h" +#include "nvs_flash.h" +#include "esp_netif.h" +#if PL_CONFIG_USE_SNTP_TIME + #include "sntp_time.h" +#endif +#if PL_CONFIG_USE_UDP_CLIENT + #include "udp_client.h" +#endif +#if PL_CONFIG_USE_UDP_SERVER + #include "udp_server.h" +#endif +#if PL_CONFIG_USE_BLINKY + #include "led.h" + #define LED_ON_TIME_MS_CONNECTED 1000 + #define LED_ON_TIME_MS_DISCONNECTED 5 +#endif +#include "McuUtility.h" +#include "McuXFormat.h" +#include "esp32_mac.h" +#include "McuLog.h" + +#define EAP_PEAP 1 /* WPA2 Enterprise with password and no certificate */ +#define EAP_TTLS 2 /* TLS method */ + +#include "pwd.h" /* local file with login information */ + +#ifndef CONFIG_ESP_MAXIMUM_RETRY + #define CONFIG_ESP_MAXIMUM_RETRY (2) /* number of retries to connect to the network */ +#endif + +static int s_retry_num = 0; + +/* FreeRTOS event group to signal when we are connected & ready to make a request */ +static EventGroupHandle_t s_wifi_event_group; +/* The event group allows multiple bits for each event, but we only care about two events: + * - we are connected to the AP with an IP + * - we failed to connect after the maximum amount of retries */ +#define WIFI_EVENT_HANDLER_CONNECTED_BIT (1<<0) +#define WIFI_EVENT_HANDLER_FAIL_BIT (1<<1) +#define WIFI_CONNECTED_BIT (1<<2) + +static esp_netif_t *APP_WiFi_NetIf; +static bool APP_WiFi_isOn = true; + +void APP_WiFi_PrintIP(void) { + tcpip_adapter_ip_info_t ip; + + memset(&ip, 0, sizeof(tcpip_adapter_ip_info_t)); + if (tcpip_adapter_get_ip_info(ESP_IF_WIFI_STA, &ip) == ESP_OK) { + McuLog_info("IP:"IPSTR " MASK:"IPSTR " GW:"IPSTR, IP2STR(&ip.ip), IP2STR(&ip.netmask), IP2STR(&ip.gw)); + } else { + McuLog_error("failed tcpip_adapter_get_ip_info()"); + } +} + +static void 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_STA_START) { + McuLog_info("WIFI_EVENT_STA_START: start event"); + esp_wifi_connect(); + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { + McuLog_info("WIFI_EVENT_STA_DISCONNECTED: disconnected, retry %d", s_retry_num); + if (s_retry_num < CONFIG_ESP_MAXIMUM_RETRY) { + esp_wifi_connect(); + s_retry_num++; + McuLog_info("retry to connect to the AP"); + } else { + xEventGroupSetBits(s_wifi_event_group, WIFI_EVENT_HANDLER_FAIL_BIT); + } + McuLog_info("connect to the AP fail"); + } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { + ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data; + McuLog_info("IP_EVENT_STA_GOT_IP: got ip:" IPSTR, IP2STR(&event->ip_info.ip)); + s_retry_num = 0; + xEventGroupSetBits(s_wifi_event_group, WIFI_EVENT_HANDLER_CONNECTED_BIT); + } +} + +typedef enum { + WIFI_PASSWORD_METHOD_PSK, + WIFI_PASSWORD_METHOD_WPA2, +} WiFi_PasswordMethod_e; + +static void GetNetworkSSID(unsigned char *ssidBuf, size_t ssidBufSize) { + wifi_ap_record_t ap; + esp_err_t err; + + err = esp_wifi_sta_get_ap_info(&ap); + if (err==ESP_OK) { + McuUtility_strcpy(ssidBuf, ssidBufSize, ap.ssid); + } else if (err==ESP_ERR_WIFI_CONN) { + McuUtility_strcpy(ssidBuf, ssidBufSize, (unsigned char*)"not init"); + } else if (err==ESP_ERR_WIFI_NOT_CONNECT) { + McuUtility_strcpy(ssidBuf, ssidBufSize, (unsigned char*)"not connected"); + } else { + McuUtility_strcpy(ssidBuf, ssidBufSize, (unsigned char*)"error?"); + } +} + +static void SetPasswordMode(WiFi_PasswordMethod_e mode) { + wifi_config_t wifi_config; + + McuLog_info("SetPasswordMode(): %d", mode); + ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); + memset(&wifi_config, 0, sizeof(wifi_config_t)); /* initialize all fields */ + if (mode==WIFI_PASSWORD_METHOD_WPA2) { + strncpy((char*)wifi_config.sta.ssid, CONFIG_WIFI_EAP_SSID, sizeof(wifi_config.sta.ssid)); + } else if (mode==WIFI_PASSWORD_METHOD_PSK) { + strncpy((char*)wifi_config.sta.ssid, CONFIG_WIFI_PSK_SSID, sizeof(wifi_config.sta.ssid)); + strncpy((char*)wifi_config.sta.password, CONFIG_WIFI_PSK_PASSWORD, sizeof(wifi_config.sta.password)); + } else { + McuLog_error("Wrong connection mode: %d", mode); + } + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) ); + ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) ); + if (mode==WIFI_PASSWORD_METHOD_WPA2) { + const ESP32_Device_t *device; + + device = ESP32_GetDeviceConfig(); + McuLog_info("EAP_ID: %s", device->eee_id); + ESP_ERROR_CHECK( esp_wifi_sta_wpa2_ent_set_identity((uint8_t *)device->eee_id, strlen(device->eee_id)) ); + if (CONFIG_WIFI_EAP_METHOD == EAP_PEAP || CONFIG_WIFI_EAP_METHOD == EAP_TTLS) { + McuLog_info("EAP_USERNAME: %s", device->eee_id); + ESP_ERROR_CHECK( esp_wifi_sta_wpa2_ent_set_username((uint8_t *)device->eee_id, strlen(device->eee_id)) ); + ESP_ERROR_CHECK( esp_wifi_sta_wpa2_ent_set_password((uint8_t *)device->eee_pwd, strlen(device->eee_pwd)) ); + } + ESP_ERROR_CHECK( esp_wifi_sta_wpa2_ent_enable() ); + } +} + +static void initialise_wifi(void) { + WiFi_PasswordMethod_e mode = CONFIG_WIFI_START_WITH; /* starting mode */ + const ESP32_Device_t *config; + + s_wifi_event_group = xEventGroupCreate(); + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + APP_WiFi_NetIf = esp_netif_create_default_wifi_sta(); + + config = ESP32_GetDeviceConfig(); + McuLog_info("Setting hostname: %s", config->hostName); + ESP_ERROR_CHECK(esp_netif_set_hostname(APP_WiFi_NetIf, config->hostName)); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL)); + ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL)); + + SetPasswordMode(mode); + + McuLog_info("Starting WiFi"); + ESP_ERROR_CHECK(esp_wifi_start()); + + /* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum + * number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */ + EventBits_t bits; + + do { + if (APP_WiFi_isOn) { + bits = xEventGroupWaitBits(s_wifi_event_group, + WIFI_EVENT_HANDLER_CONNECTED_BIT | WIFI_EVENT_HANDLER_FAIL_BIT, /* bits to wait for */ + pdTRUE, /* bits to clear on exit: we do not clear the WIFI_CONNECTED_BIT because used by WiFi task */ + pdFALSE, /* wait for all bits */ + pdMS_TO_TICKS(20000)); /* wait time */ + if (bits&WIFI_EVENT_HANDLER_CONNECTED_BIT) { + break; /* leave loop */ + } + if (bits&WIFI_EVENT_HANDLER_FAIL_BIT) { + McuLog_info("FAILED connecting, restarting WiFi with different mode"); + ESP_ERROR_CHECK(esp_wifi_stop()); + /* toggle mode */ + if (mode==WIFI_PASSWORD_METHOD_PSK) { + mode = WIFI_PASSWORD_METHOD_WPA2; + } else if (mode==WIFI_PASSWORD_METHOD_WPA2) { + ESP_ERROR_CHECK(esp_wifi_sta_wpa2_ent_disable()); + mode = WIFI_PASSWORD_METHOD_PSK; + } + SetPasswordMode(mode); + ESP_ERROR_CHECK(esp_wifi_start()); + } + } else { + vTaskDelay(pdMS_TO_TICKS(500)); + } + } while(true); /* breaks */ + + /* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually happened. */ + if (bits & WIFI_EVENT_HANDLER_CONNECTED_BIT) { + if (mode == WIFI_PASSWORD_METHOD_WPA2) { + McuLog_info("connected to AP SSID: %s ", CONFIG_WIFI_EAP_SSID); + } else if (mode == WIFI_PASSWORD_METHOD_PSK) { + McuLog_info("connected to AP SSID: %s", CONFIG_WIFI_PSK_SSID); + } + xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); + } else if (bits & WIFI_EVENT_HANDLER_FAIL_BIT) { + if (mode == WIFI_PASSWORD_METHOD_WPA2) { + McuLog_info("Failed to connect to SSID: %s", CONFIG_WIFI_EAP_SSID); + } else if (mode == WIFI_PASSWORD_METHOD_PSK) { + McuLog_info("Failed to connect to SSID: %s", CONFIG_WIFI_PSK_SSID); + } + } else { + McuLog_error("UNEXPECTED EVENT"); + } +} + +typedef enum { + WIFI_STATE_INIT, + WIFI_STATE_CONNECTED, + WIFI_STATE_DISCONNECTED, +} WiFi_State_e; + +static void WiFiTask(void *pv) { + bool isConnected = false; + WiFi_State_e state = WIFI_STATE_INIT; + + McuLog_info("Initialize WiFi"); + initialise_wifi(); +#if PL_CONFIG_USE_BLINKY + LED_SetOnTime(LED_ON_TIME_MS_DISCONNECTED); +#endif + for(;;) { + vTaskDelay(pdMS_TO_TICKS(5000)); + isConnected = xEventGroupGetBits(s_wifi_event_group)&WIFI_CONNECTED_BIT; + #if PL_CONFIG_USE_BLINKY + LED_SetOnTime(isConnected?LED_ON_TIME_MS_CONNECTED:LED_ON_TIME_MS_DISCONNECTED); + #endif + if (state == WIFI_STATE_INIT && isConnected) { /* first connection */ + state = WIFI_STATE_CONNECTED; + //ESP_LOGI(TAG, "WiFi is connected."); + if (isConnected) { + APP_WiFi_PrintIP(); + } + #if PL_CONFIG_USE_UDP_SERVER + McuLog_info("starting UDP server."); + UDP_Server_Start(); + #endif + #if PL_CONFIG_USE_UDP_CLIENT + McuLog_info("starting UDP client."); + UDP_Client_Start(); + #endif + #if PL_CONFIG_USE_SNTP_TIME + if (SNTP_ObtainTime()!=0) { + McuLog_error("failed getting SNTP time."); + } + #endif +#if 0 + } else if (isConnected) { + ESP_LOGI(TAG, "still connected."); + APP_WiFi_PrintIP(); + + const char *hostname; + ESP_ERROR_CHECK(esp_netif_get_hostname(APP_WiFi_NetIf, &hostname)); + ESP_LOGI(TAG,"hostname: %s", hostname); + } else { + ESP_LOGI(TAG, "not connected yet."); +#endif + } + } /* for */ +} + +bool WiFi_isConnected(void) { + bool isConnected; + + isConnected = xEventGroupGetBits(s_wifi_event_group)&WIFI_CONNECTED_BIT; + return isConnected; +} + +#if PL_CONFIG_USE_SHELL +static uint8_t PrintStatus(const McuShell_StdIOType *io) { + uint8_t buf[32]; + uint8_t mac[6]; + + McuShell_SendStatusStr((unsigned char*)"wifi", (unsigned char*)"ESP32 WiFi status\r\n", io->stdOut); + McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)IDF_VER); + McuUtility_strcat(buf, sizeof(buf), (unsigned char*)"\r\n"); + McuShell_SendStatusStr((unsigned char*)" IDF", buf, io->stdOut); + McuShell_SendStatusStr((unsigned char*)" connected", WiFi_isConnected()?(unsigned char*)"yes\r\n":(unsigned char*)"no\r\n", io->stdOut); + + ESP32_MacRead(mac); + ESP32_MacToString(mac, buf, sizeof(buf)); + McuUtility_strcat(buf, sizeof(buf), (unsigned char*)"\r\n"); + McuShell_SendStatusStr((unsigned char*)" MAC", buf, io->stdOut); + McuShell_SendStatusStr((unsigned char*)" WiFi", APP_WiFi_isOn?(unsigned char*)"on\r\n":(unsigned char*)"off\r\n", io->stdOut); + + if (WiFi_isConnected()) { + unsigned char buf[64]; + tcpip_adapter_ip_info_t ip; + const char *hostname; + + if (esp_netif_get_hostname(APP_WiFi_NetIf, &hostname)==ERR_OK) { + McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)hostname); + McuUtility_strcat(buf, sizeof(buf), (unsigned char*)"\r\n"); + } else { + McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)"failed\r\n"); + } + McuShell_SendStatusStr((unsigned char*)" hostname", buf, io->stdOut); + + memset(&ip, 0, sizeof(tcpip_adapter_ip_info_t)); + if (tcpip_adapter_get_ip_info(ESP_IF_WIFI_STA, &ip) == ESP_OK) { + McuXFormat_xsnprintf((char*)buf, sizeof(buf), "IP:"IPSTR " MASK:"IPSTR " GW:"IPSTR "\r\n", IP2STR(&ip.ip), IP2STR(&ip.netmask), IP2STR(&ip.gw)); + } else { + McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)"failed\r\n"); + } + McuShell_SendStatusStr((unsigned char*)" IP", buf, io->stdOut); + + GetNetworkSSID(buf, sizeof(buf)); + McuUtility_strcat(buf, sizeof(buf), (unsigned char*)"\r\n"); + McuShell_SendStatusStr((unsigned char*)" SSID", buf, io->stdOut); + } + return ERR_OK; +} + +static uint8_t PrintHelp(const McuShell_StdIOType *io) { + McuShell_SendHelpStr((unsigned char*)"wifi", (unsigned char*)"Group of ESP32 WiFi commands\r\n", io->stdOut); + McuShell_SendHelpStr((unsigned char*)" help|status", (unsigned char*)"Shows WiFi help or status\r\n", io->stdOut); + McuShell_SendHelpStr((unsigned char*)" on|off", (unsigned char*)"Turn WiFi on or off\r\n", io->stdOut); + return ERR_OK; +} + +uint8_t WiFi_ParseCommand(const unsigned char* cmd, bool *handled, const McuShell_StdIOType *io) { + if (McuUtility_strcmp((char*)cmd, (char*)McuShell_CMD_HELP)==0 || McuUtility_strcmp((char*)cmd, (char*)"wifi help")==0) { + *handled = TRUE; + return PrintHelp(io); + } else if (McuUtility_strcmp((char*)cmd, (char*)McuShell_CMD_STATUS)==0 || McuUtility_strcmp((char*)cmd, (char*)"wifi status")==0) { + *handled = TRUE; + return PrintStatus(io); + } else if (McuUtility_strcmp((char*)cmd, (char*)"wifi on")==0) { + *handled = TRUE; + APP_WiFi_isOn = true; + return ERR_OK; + } else if (McuUtility_strcmp((char*)cmd, (char*)"wifi off")==0) { + *handled = TRUE; + APP_WiFi_isOn = false; + return ERR_OK; + } + return ERR_OK; +} +#endif /* PL_CONFIG_USE_SHELL */ + +void WiFi_Init(void) { + BaseType_t res; + + res = xTaskCreate(WiFiTask, "WiFi", 16*1024/sizeof(StackType_t), NULL, tskIDLE_PRIORITY, NULL); + if (res==pdPASS) { + McuLog_info("created WiFi task"); + } else { + McuLog_error("failed creating WiFi task!"); + } + /* set mac address, otherwise will get "system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE" whenever asking for it */ + uint8_t mac[6]; + res = esp_read_mac(&mac[0], ESP_MAC_WIFI_STA); + if (res==ESP_OK) { + ESP_ERROR_CHECK(esp_base_mac_addr_set(&mac[0])); + } +} +#endif /* PL_CONFIG_USE_WIFI */ diff --git a/ADIS_ESP32_Eclipse/main/wifi.h b/ADIS_ESP32_Eclipse/main/wifi.h new file mode 100644 index 0000000..79ae4ce --- /dev/null +++ b/ADIS_ESP32_Eclipse/main/wifi.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020, Erich Styger + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef APPWIFI_H_ +#define APPWIFI_H_ + +#include "platform.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if PL_CONFIG_USE_SHELL + #include "McuShell.h" + + /*! + * \brief Command line and shell handler + * \param cmd The command to be parsed + * \param handled If command has been recognized and handled + * \param io I/O handler to be used + * \return error code, otherwise ERR_OK + */ + uint8_t WiFi_ParseCommand(const unsigned char* cmd, bool *handled, const McuShell_StdIOType *io); +#endif + +/*! + * \brief Decides if connected to a network or not + * \return true if connected, false if not + */ +bool WiFi_isConnected(void); + +/*! + * \brief Module Initialization + */ +void WiFi_Init(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* APPWIFI_H_ */