I am fairly new to Arduino and I am trying to construct a generic WebSocket server which send simple ASCII values to a WebSocket client running via a python program connected to the Arduino via an Ethernet cable. I don't want to construct a full WebServer, rather I just want to use WebSocket, in order to avoid using HTTP.
I am unsure of what library I should use for a W5500 shield with an Arduino Uno. Nor can I find any comprehensive and simple examples of how a basic WebSocket Server is constructed in Arduino. Can any one provide some recommendations for appropriate libraries or with good pieces of example code?
Thanks in advance
Edit: Here is a basic attempt at a generic WebSocket server I have taken from the WebSockets_Generic.h library, just to outline what I would like to make:
#include <Ethernet2.h>
#include <WebSockets_Generic.h>
WebSocketsServer webSocket = WebSocketsServer(81);
// Enter a MAC address and IP address for your controller below.
byte mac[] ={0xA8, 0x61, 0x0A, 0xAE, 0x69, 0x13};
// Select the IP address according to your local network
IPAddress ip(198, 162, 1, 177);
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length)
{
switch (type)
{
case WStype_DISCONNECTED:
//Serial.println( "[" + String(num) + "] Disconnected!");
break;
case WStype_CONNECTED:
{
Serial.println( "[" + String(num) + "] Connected!");
//IPAddress ip = webSocket.remoteIP(num);
//Serial.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
// send message to client
webSocket.sendTXT(num, "Connected");
}
break;
case WStype_TEXT:
Serial.println( "[" + String(num) + "] get Text: " + String((char *) payload));
// send message to client
webSocket.sendTXT(num, "message here");
// send data to all connected clients
webSocket.broadcastTXT("message here");
break;
case WStype_BIN:
Serial.println( "[" + String(num) + "] get binary length: " + String(length));
//hexdump(payload, length);
// send message to client
// webSocket.sendBIN(num, payload, length);
break;
default:
break;
}
}
void setup()
{
// Serial.begin(921600);
Serial.begin(115200);
while (!Serial);
// start the ethernet connection and the server:
// Use DHCP dynamic IP and random mac
uint16_t index = millis();
// Use Static IP
//Ethernet.begin(mac[index], clientIP);
Ethernet.begin(mac, ip);
// server address, port and URL
Serial.print("WebSockets Server # IP address: ");
Serial.println(Ethernet.localIP());
webSocket.begin();
webSocket.onEvent(webSocketEvent);
}
void loop()
{
webSocket.loop();
}
This does not compile and instead produces an error:
In file included from /home/user/Data/Code-pieces/Arduino-Flowmeter/test/test.ino:2:0:
/home/user/Arduino/libraries/WebSockets_Generic/src/:69:4: error: #error Version 2.x.x currently does not support Arduino with AVR since there is no support for std namespace of c++.
#error Version 2.x.x currently does not support Arduino with AVR since there is no support for std namespace of c++.
^~~~~
/home/user/Arduino/libraries/WebSockets_Generic/src/WebSockets_Generic.h:70:4: error: #error Use Version 1.x.x. (ATmega branch)
#error Use Version 1.x.x. (ATmega branch)
^~~~~
Multiple libraries were found for "Ethernet.h"
Used: /home/user/Data/Code-pieces/arduino-1.8.14/libraries/Ethernet
Not used: /home/user/Arduino/libraries/UIPEthernet
exit status 1
Error compiling for board Arduino Uno.
WebSockets_Generic.h
This report would have more information with
"Show verbose output during compilation"
option enabled in File -> Preferences.
Related
There are other similar questions on this site, but they either do not related to winsock2 or they are suitable only for use with ipv4 address spaces. The default compiler for Visual Studio 2019 produces an error when the ntoa function is used, hence an ipv4 and ipv6 solution is required.
I did once produce the code to do this for a Linux system however I am currently at work and do not have access to that. It may or may not be "copy and paste"-able into a windows environment with winsock2. (Edit: I will of course add that code later this evening, but of course it might not be useful.)
The following contains an example, however this is an example for client side code, not server side code.
https://www.winsocketdotnetworkprogramming.com/winsock2programming/winsock2advancedInternet3c.html
Here, the getaddrinfo() function is used to obtain a structure containing matching ipv4 and ipv6 addresses. To obtain this information there is some interaction with DNS, which is not required in this case.
I have some server code which calls accept() (after bind and listen) to accept a client connection. I want to be able to print the client ip address and port to stdout.
The most closely related question on this site is here. However the answer uses ntoa and is only ipv4 compatible.
What I have so far:
So far I have something sketched out like this:
SOCKET acceptSocket = INVALID_SOCKET;
SOCKADDR_IN addr; // both of these are NOT like standard unix sockets
// I don't know how they differ and if they can be used with standard
// unix like function calls (eg: inet_ntop)
int addrlen = sizeof addr;
acceptSocket = accept(listenSocket, (SOCKADDR*)&addr, &addrlen);
if(acceptSocket == INVALID_SOCKET)
{
// some stuff
}
else
{
const std::size_t addrbuflen = INET6_ADDRSRTLEN;
char addrbuf[addrbuflen] = '\0'
inet_ntop(AF_INET, (void*)addr.sin_addr, (PSTR)addrbuf, addrbuflen);
// above line does not compile and mixes unix style function calls
// with winsock2 structures
std::cout << addrbuf << ':' << addr.sin_port << std::endl;
}
getpeername()
int ret = getpeername(acceptSocket, addrbuf, &addrbuflen);
// addrbuf cannot convert from char[65] to sockaddr*
if(ret == ???)
{
// TODO
}
You need to access the SOCKADDR. This is effectively a discriminated union. The first field tells you whether its an IPv4 (==AF_INET) or IPv6 (==AF_INET6) address. Depending on that you cast the addr pointer to be either struct sockaddr_in* or struct sockaddr_in6*, and then read off the IP address from the relevant field.
C++ code snippet in vs2019:
char* CPortListener::get_ip_str(struct sockaddr* sa, char* s, size_t maxlen)
{
switch (sa->sa_family) {
case AF_INET:
inet_ntop(AF_INET, &(((struct sockaddr_in*)sa)->sin_addr),
s, maxlen);
break;
case AF_INET6:
inet_ntop(AF_INET6, &(((struct sockaddr_in6*)sa)->sin6_addr),
s, maxlen);
break;
default:
strncpy(s, "Unknown AF", maxlen);
return NULL;
}
return s;
}
Example:
{
...
char s[INET6_ADDRSTRLEN];
sockaddr_storage ca;
socklen_t al = sizeof(ca);
SOCKET recv = accept(sd, (sockaddr*)&ca, &al);
pObj->m_ip = get_ip_str(((sockaddr*)&ca),s,sizeof(s));
}
I want to securely send data to my InfluxDB over the Internet using a NodeMCU MCU and a self signed cert.
I found this library that seems to accomplish exactly this but get compile errors, more below -> https://medium.com/#teebr/iot-with-an-esp32-influxdb-and-grafana-54abc9575fb2
This library seems to only use HTTP -> Am i mistaken?
https://www.arduinolibraries.info/libraries/esp8266-influxdb
Using the example from the 1st library above from TEEBR, i get this error compiling - Any suggestions on how to fix? Will this run on my NodeMCU?
Thanks
C:\Users\Jason\Documents\Arduino\libraries\Influx-Arduino-master\InfluxArduino.cpp:1:24: fatal error: HTTPClient.h: No such file or directory
#include
^
compilation terminated.
exit status 1
Error compiling for board NodeMCU 1.0 (ESP-12E Module).
My code
//https://medium.com/#teebr/iot-with-an-esp32-influxdb-and-grafana-54abc9575fb2
#include <WiFi.h>
#include "InfluxArduino.hpp"
#include "InfluxCert.hpp"
InfluxArduino influx;
//connection/ database stuff that needs configuring
char WIFI_NAME[] = "ssid";
const char WIFI_PASS[] = "password!";
const char INFLUX_DATABASE[] = "db_name";
const char INFLUX_IP[] = "10.10.101.101";
const char INFLUX_USER[] = "db_name"; //username if authorization is enabled.
const char INFLUX_PASS[] = "Password"; //password for if authorization is enabled.
const char INFLUX_MEASUREMENT[] = "FromESP8266"; //measurement name for the database. (in practice, you can use several, this example just uses the one)
unsigned long DELAY_TIME_US = 5 * 1000 * 1000; //how frequently to send data, in microseconds
unsigned long count = 0; //a variable that we gradually increase in the loop
void setup()
{
Serial.begin(115200);
WiFi.begin(WIFI_NAME, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("WiFi connected!");
influx.configure(INFLUX_DATABASE,INFLUX_IP); //third argument (port number) defaults to 8086
influx.authorize(INFLUX_USER,INFLUX_PASS); //if you have set the Influxdb .conf variable auth-enabled to true, uncomment this
influx.addCertificate(ROOT_CERT); //uncomment if you have generated a CA cert and copied it into InfluxCert.hpp
Serial.print("Using HTTPS: ");
Serial.println(influx.isSecure()); //will be true if you've added the InfluxCert.hpp file.
}
void loop()
{
unsigned long startTime = micros(); //used for timing when to send data next.
//update our field variables
float dummy = ((float)random(0, 1000)) / 1000.0;
count++;
//write our variables.
char tags[32];
char fields[32];
sprintf(tags,"new_tag=Yes"); //write a tag called new_tag
sprintf(fields,"count=%d,random_var=%0.3f",count,dummy); //write two fields: count and random_var
bool writeSuccessful = influx.write(INFLUX_MEASUREMENT,tags,fields);
if(!writeSuccessful)
{
Serial.print("error: ");
Serial.println(influx.getResponse());
}
while ((micros() - startTime) < DELAY_TIME_US)
{
//wait until it's time for next reading. Consider using a low power mode if this will be a while.
}
}
Long time reader, first time poster - Thanks for all the help in the past!
I want to get the IP address that just assigned by the DHCP to the new STA that just connected to the ESP32 along with its MAC address.
i was thinking about using the "dhcp_search_ip_on_mac()" function passing to it the parameter "&event->event_info.sta_connected.mac" but the problem is the "SYSTEM_EVENT_AP_STAIPASSIGNED" event id has no event data struction and the sta_connected.mac variable is from the "SYSTEM_EVENT_AP_STACONNECTED" event id. but i was thinking what if i face a multi connections at the same time, what if at the moment the ESP uses sta_connected.mac to get the ip address from DHCP search function, that variable will have the mac address of another STA and not of the one that triggered the event id "SYSTEM_EVENT_AP_STACONNECTED".
esp_err_t eventHandler(void *ctx, system_event_t *event){
switch (event->event_id)
{
case SYSTEM_EVENT_AP_STACONNECTED:
printf("STA just connected.\n");
printf("STA MAC#: %s\n", ip4addr_ntoa(&event->event_info.sta_connected.mac));
break;
case SYSTEM_EVENT_AP_STAIPASSIGNED:
ip4_addr_t staAddr;
dhcp_search_ip_on_mac(&event->event_info.sta_connected.mac, &staAddr);
printf("STA ip address maybe %s\n", ip4addr_ntoa(&staAddr));
break;
default:
break;
}
return ESP_OK;
}
Considering the fact that following code can be found in tcpip_adapter/tcpip_adapter_lwip.c
static void tcpip_adapter_dhcps_cb(u8_t client_ip[4])
{
ESP_LOGI(TAG,"softAP assign IP to station,IP is: %d.%d.%d.%d",
client_ip[0],client_ip[1],client_ip[2],client_ip[3]);
system_event_t evt;
memset(&evt, 0, sizeof(system_event_t));
evt.event_id = SYSTEM_EVENT_AP_STAIPASSIGNED;
memcpy((char *)&evt.event_info.ap_staipassigned.ip.addr, (char *)client_ip, sizeof(evt.event_info.ap_staipassigned.ip.addr));
esp_event_send(&evt);
}
I would assume that handling IP_EVENT_AP_STAIPASSIGNED / SYSTEM_EVENT_AP_STAIPASSIGNED
and reading event->event_info.ap_staipassigned.ip.addr should work in your case.
EDIT:
the following compiles just fine for me. But one need the lastest version of the esp-idf as this functionality seems to be added recently.
case IP_EVENT_AP_STAIPASSIGNED:
{
int addr = event->event_info.ap_staipassigned.ip.addr;
ESP_LOGI(TAG0, "%d",addr);
}break;
tested with version master 6fd535c .
Faced the same issue for the ESP8266_RTOS_SDK, I downloaded recently.
Went to the tcpip_adapter_lwip.c and just added ip to the event_info.got_ip.ip_info.ip.addr field:
static void tcpip_adapter_dhcps_cb(u8_t client_ip[4])
{
ESP_LOGI(TAG,"softAP assign IP to station,IP is: %d.%d.%d.%d", client_ip[0],client_ip[1],client_ip[2],client_ip[3]);
system_event_t evt;
evt.event_id = SYSTEM_EVENT_AP_STAIPASSIGNED;
evt.event_info.got_ip.ip_info.ip.addr = (client_ip[3] << 24) | (client_ip[2] << 16) | (client_ip[1] << 8) | client_ip[0];
esp_event_send(&evt);
}
I have compiled libzmq with openpgm with no changes under windows. Code here is taken from ZeroMQ Guide ("weather publisher" server/client). But if i change "tcp" to "epgm" it doesn't work any more (data is not received, but connection is established).
void test_serv()
{
// Prepare our context and publisher
void *context = zmq_ctx_new();
void *publisher = zmq_socket(context, ZMQ_PUB);
int rc = zmq_bind(publisher, "epgm://127.0.0.1:5556");
assert(rc == 0);
// Initialize random number generator
srandom((unsigned)time(NULL));
while (!stop_server)
{
// Get values that will fool the boss
int zipcode, temperature, relhumidity;
zipcode = randof(1000) + 600;
temperature = randof(215) - 80;
relhumidity = randof(50) + 10;
// Send message to all subscribers
char update[20];
sprintf(update, "%d %d %d", zipcode, temperature, relhumidity);
s_send(publisher, update);
}
LOG("END Server shutdown");
Sleep(500);
zmq_close(publisher);
zmq_ctx_destroy(context);
}
void test_sock()
{
// Socket to talk to server
LOG("Collecting updates from weather server...");
void *context = zmq_ctx_new();
void *subscriber = zmq_socket(context, ZMQ_SUB);
int rc = zmq_connect(subscriber, "epgm://127.0.0.1:5556");
assert(rc == 0);
// Subscribe to zipcode, default is NYC, 10001
char *filter = "1001 ";
rc = zmq_setsockopt(subscriber, ZMQ_SUBSCRIBE,
filter, strlen(filter));
assert(rc == 0);
// Process 100 updates
int update_nbr;
long total_temp = 0;
for (update_nbr = 0; update_nbr < 10; update_nbr++) {
char *string = s_recv(subscriber);
int zipcode, temperature, relhumidity;
sscanf(string, "%d %d %d",
&zipcode, &temperature, &relhumidity);
total_temp += temperature;
LOG(">> " << string);
free(string);
}
LOG("Average temperature for zipcode "<< filter << "was " << (int)(total_temp / update_nbr) << 'F');
zmq_close(subscriber);
zmq_ctx_destroy(context);
}
I run two functions in different threads, with tcp anything works as expected.
I have tried doing "route print 0.0.0.0" with cmd.exe and using interface IP (192.168.137.64) as prefix instead of "eth0" like shown in RFC: epgm://192.168.137.64;127.0.0.1:5556 on connect and/or bind, but this brokes my socket and raises error.
Also "PGM" requires administrator rights and i cannot test it now.
The error IS NOT "protocol not supported" errno is set to B (11) and i don't understand what does it mean (no docs on it).
EPGM is a bit finicky. According to this list post, if you're using EPGM your publisher and subscriber must be on separate hosts. More details here, it looks like this was a deliberate choice by the ZMQ team.
So, try it by spinning up your PUB and SUB on separate machines (changing the network addresses accordingly, of course).
The reason might be that windows does not support loopback capture interface. I tried weather example with protocol changed to epgm on linux and it works fine (well, shows some warnings about loopback, but the messages are transfered correctly)
Test Scenario
I had written a windows program which I simply called it "simpleServer.exe". This program is just a simulation of a very basic server application. It listens on a port, and wait for incoming messages. The listening Socket was defined to be a TCP Stream Socket. that's all that this program is doing.
I had been deploying this exact same program on 2 different machines, both running on windows 7 professional 64bit. This machine will act as a host. and they are stationed in the same network area.
then, using the program "nmap", I used another machine on the same network, to act as a client. using the "-sS" parameter on "nmap", I do a Syn Scan, to the IP and Port of the listening simpleServer on both machine (one attempt at a time).
(note that the 2 hosts already had "wireshark" started, and is monitoring on tcp packets from the client's IP and to the listening port.)
In the "wireshark" entry, on both machine, I saw the expected tcp packet for Syn Scan:
client ----(SYN)----> host
client <--(SYN/ACK)-- host
client ----(RST)----> host
the above packet exchange suggests that the connection was not established.
But on the "simpleServer.exe", only one of it had "new incoming connection" printed in the logs, while the other instance was not alerted of any new incoming connection, hence no logs at all.
Code Snippets
iRetVal = WSAEventSelect (m_Socket, m_hSocketEvent, FD_ACCEPT);
if (SOCKET_ERROR == iRetVal)
{
if (WSAGetLastError()==WSAENOTSOCK)
{
return E_SOCKET_INVALID;
}
CHKLOGGER (m_pLogger->Log (LOGGER_LOG_ERROR,"GHLSocket::OnAccept() Error while WSAEventSelect(). Error code: ", WSAGetLastError() ));
#if defined GHLSOCKET_DEBUG_VERSION
printf ("Error while WSAEventSelect(). Error code: %ld\n", WSAGetLastError() );
#endif
return E_FAILED_RECV_DATA;
}
// Wait for Network Events to occcur
dwRetVal = WSAWaitForMultipleEvents ( 1,
&m_hSocketEvent,
FALSE,
lTimeout,
TRUE);
if ( WSA_WAIT_TIMEOUT == dwRetVal )
{
return E_TIMEOUT;
goto CleanUp;
}
if ( WSA_WAIT_FAILED == dwRetVal)
{
CHKLOGGER (m_pLogger->Log (LOGGER_LOG_ERROR,"GHLSocket::OnAccept() WSAWaitForMultipleEvents() failed. Error code: ", WSAGetLastError() ));
#if defined GHLSOCKET_DEBUG_VERSION
printf ("Error in WSAWaitForMultipleEvents() failed. Error code: %ld\n", WSAGetLastError() );
#endif
dwReturn = E_FAILED_RECV_DATA;
goto CleanUp;
}
// Parse the Results from the Network Events.
iRetVal = WSAEnumNetworkEvents (m_Socket, m_hSocketEvent, &mEvents);
if (SOCKET_ERROR == iRetVal)
{
CHKLOGGER (m_pLogger->Log (LOGGER_LOG_ERROR,"GHLSocket::OnAccept() Error while WSAEnumNetworkEvents(). Error code: ", WSAGetLastError() ));
#if defined GHLSOCKET_DEBUG_VERSION
printf ("Error while WSAEnumNetworkEvents(). Error code: %ld\n", WSAGetLastError() );
#endif
dwReturn = E_FAILED_RECV_DATA;
goto CleanUp;
}
// ACCEPT event Detected.
if (mEvents.lNetworkEvents & FD_ACCEPT)
{
// Perform accept operation.
*p_SOCKET = accept (m_Socket, NULL,NULL);
}
Help That I Needed
why is the different behavior from the 2 same of the same application on a different machine with the same OS?