I am currently trying to implement a save method for someone to change the Mode of an ESP-32 from Accesspoint Mode to Station mode via a Webpage on an asynchronous Webserver. The data gets put into a JSON string and sent to the ESP-32 where it gets written in a JSON file, and later read out of again and used to connect to a network with.
else {
IP = "NULL";
Netmask = "NULL";
Gateway = "NULL";
var data = {ssid:ssid, password:password, IP:IP, Netmask:Netmask, Gateway:Gateway};
console.log(data);
var xhr = new XMLHttpRequest();
var url ="/settings";
xhr.onreadystatechange = function() {
if(this.readyState == 4 && this.status ==200) {
console.log(xhr.response);
}
};
xhr.open("POST", url, true);
xhr.send(JSON.stringify(data));
}
The problem with this method is that someone who is spying on the network could just grab the JSON string as it is and gain access to the network the ESP-32 is trying to connect itself with. Thats why I want to implement a lightweight way to encrypt this string before it gets sent, and to locally encrypt it, using Javascript on the webpage. The Problem is that, the moment I execute this operation, I am not online, so grabbing a script from the outside, e.g. CryptoJS is not possible. I'm stuck on this for weeks now and have no idea how to do this.
Does anyone have an idea?
The de-facto way of protecting against eavesdropping is to implement HTTPS.
ESP32 has built-in TLSv1.3 support thanks to wolfssl.
Below is an example HTTPS server taken from the official ESP-IDF examples.
You will need a server certificate to run it. You can use generate a self-signed certificate with openssl, or if you have a valid DNS name, get a CA-signed one e.g. from LetsEncrypt. Even a self-signed cert with TLS 1.2+ will protect against sniffing.
#include <esp_wifi.h>
#include <esp_event.h>
#include <esp_log.h>
#include <esp_system.h>
#include <nvs_flash.h>
#include <sys/param.h>
#include "esp_netif.h"
#include "esp_eth.h"
#include "protocol_examples_common.h"
#include <esp_https_server.h>
/* A simple example that demonstrates how to create GET and POST
* handlers and start an HTTPS server.
*/
static const char *TAG = "example";
/* An HTTP GET handler */
static esp_err_t root_get_handler(httpd_req_t *req)
{
httpd_resp_set_type(req, "text/html");
httpd_resp_send(req, "<h1>Hello Secure World!</h1>", HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
static const httpd_uri_t root = {
.uri = "/",
.method = HTTP_GET,
.handler = root_get_handler
};
static httpd_handle_t start_webserver(void)
{
httpd_handle_t server = NULL;
// Start the httpd server
ESP_LOGI(TAG, "Starting server");
httpd_ssl_config_t conf = HTTPD_SSL_CONFIG_DEFAULT();
extern const unsigned char cacert_pem_start[] asm("_binary_cacert_pem_start");
extern const unsigned char cacert_pem_end[] asm("_binary_cacert_pem_end");
conf.cacert_pem = cacert_pem_start;
conf.cacert_len = cacert_pem_end - cacert_pem_start;
extern const unsigned char prvtkey_pem_start[] asm("_binary_prvtkey_pem_start");
extern const unsigned char prvtkey_pem_end[] asm("_binary_prvtkey_pem_end");
conf.prvtkey_pem = prvtkey_pem_start;
conf.prvtkey_len = prvtkey_pem_end - prvtkey_pem_start;
esp_err_t ret = httpd_ssl_start(&server, &conf);
if (ESP_OK != ret) {
ESP_LOGI(TAG, "Error starting server!");
return NULL;
}
// Set URI handlers
ESP_LOGI(TAG, "Registering URI handlers");
httpd_register_uri_handler(server, &root);
return server;
}
static void stop_webserver(httpd_handle_t server)
{
// Stop the httpd server
httpd_ssl_stop(server);
}
static void disconnect_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
httpd_handle_t* server = (httpd_handle_t*) arg;
if (*server) {
stop_webserver(*server);
*server = NULL;
}
}
static void connect_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
httpd_handle_t* server = (httpd_handle_t*) arg;
if (*server == NULL) {
*server = start_webserver();
}
}
void app_main(void)
{
static httpd_handle_t server = NULL;
ESP_ERROR_CHECK(nvs_flash_init());
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
/* Register event handlers to start server when Wi-Fi or Ethernet is connected,
* and stop server when disconnection happens.
*/
#ifdef CONFIG_EXAMPLE_CONNECT_WIFI
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &connect_handler, &server));
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &disconnect_handler, &server));
#endif // CONFIG_EXAMPLE_CONNECT_WIFI
#ifdef CONFIG_EXAMPLE_CONNECT_ETHERNET
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &connect_handler, &server));
ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ETHERNET_EVENT_DISCONNECTED, &disconnect_handler, &server));
#endif // CONFIG_EXAMPLE_CONNECT_ETHERNET
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
* Read "Establishing Wi-Fi or Ethernet Connection" section in
* examples/protocols/README.md for more information about this function.
*/
ESP_ERROR_CHECK(example_connect());
}
ESP32 can even do Websocket over SSL - see wss_server example.
Related
I would like to ask about sending data from arduino/esp to .net App service running on Azure.
I am sending JSON to API with "list" of measurements (simple JSON, Id and Value).
Over PostMan I don't have any issue, but over Arduino I am facing issue that I receive result code 415.
Data are sent in JSON.
Arduino Code:
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClient.h>
const char* ssid = "*******";
const char* password = "*******";
//Your Domain name with URL path or IP address with path
const char* serverName = "https://********.azurewebsites.net/api/Measurements/";
unsigned long lastTime = 0;
// Set timer to 5 seconds (5000)
unsigned long timerDelay = 5000;
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
Serial.println("Connecting");
while(WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected to WiFi network with IP Address: ");
Serial.println(WiFi.localIP());
Serial.println("Timer set to 5 seconds (timerDelay variable), it will take 5 seconds before publishing the first reading.");
}
void loop() {
//Send an HTTP POST request every 10 minutes
if ((millis() - lastTime) > timerDelay) {
//Check WiFi connection status
if(WiFi.status()== WL_CONNECTED)
{
std::unique_ptr<BearSSL::WiFiClientSecure>client(new BearSSL::WiFiClientSecure);
client->setInsecure();
HTTPClient https;
https.addHeader("Content-Type", "application/json"); // tried even "text/json"
//https.addHeader("Content-Length", "32");
https.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
https.addHeader("Host","******.azurewebsites.net");
https.addHeader("Cache-Control","no-cache");
String httpRequestData = "[{\"SensorId\": 1, \"Value\": 77.7}]";
if (https.begin(*client, serverName))
{
Serial.print("[HTTPS] POST...\n");
int httpCode = https.POST(httpRequestData);
if (httpCode > 0)
{
// HTTP header has been send and Server response header has been handled
Serial.printf("[HTTPS] POST... code: %d\n", httpCode);
// file found at server
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY)
{
String payload = https.getString();
Serial.println(payload);
}
else
{
Serial.printf("[HTTPS] POST... failed, error: %s\n", https.errorToString(httpCode).c_str());
}
}
else
{
Serial.printf("[HTTPS] POST... failed, error: %s\n", https.errorToString(httpCode).c_str());
}
https.end();
}
else
{
Serial.printf("[HTTPS] Unable to connect\n");
}
}
lastTime = millis();
}
}
Not receiving any error, just response code 415.
The HTTP 415 Unsupported Media Type client error response code indicates that the server refuses to accept the request because the payload
JSON text:
[{"SensorId": 1, "Value": 77.7}]
AppInsight:
PostMan successful request:
body of request in Postman:
And report from AppInsight:
App service:
Over PostMan I am able to reach azure and send data, but over Arduino I am not able to send data (I am able to get data, but not post)
Probably some beginner mistake.
Can someone give me hint?
Do I have wrong format of JSON?
I'm using Arduino's ethernet shield with Arduino mega to parse a JSON document in a HTTP response to read some values in the JSON file.
The JSON address is: https://api.coinbase.com/v2/prices/BTC-USD/spot.
I'm getting this error message:
Connecting...
Connected!
Unexpected response: HTTP/1.1 301 Moved Permanently
Is is that the the Arduino library doesn't have SSL capability, so it can't connect to a secure server? If it is, then I would need your help to resolve this issue.
Or perhaps I need to use another port that's related to https?
Here is my code:
#include <ArduinoJson.h>
#include <Ethernet.h>
#include <SPI.h>
void setup() {
// Initialize Serial port
Serial.begin(9600);
while (!Serial) continue;
// Initialize Ethernet library
byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
if (!Ethernet.begin(mac)) {
Serial.println(F("Failed to configure Ethernet"));
return;
}
delay(1000);
Serial.println(F("Connecting..."));
// Connect to HTTP server
EthernetClient client;
client.setTimeout(10000);
if (!client.connect("api.coinbase.com", 80)) {
Serial.println(F("Connection failed"));
return;
}
Serial.println(F("Connected!"));
// Send HTTP request
client.println(F("GET /v2/prices/BTC-USD/spot/response.json HTTP/1.0"));
client.println(F("Host: api.coinbase.com"));
client.println(F("Connection: close"));
if (client.println() == 0) {
Serial.println(F("Failed to send request"));
return;
}
// Check HTTP status
char status[32] = {0};
client.readBytesUntil('\r', status, sizeof(status));
// It should be "HTTP/1.0 200 OK" or "HTTP/1.1 200 OK"
if (strcmp(status + 9, "200 OK") != 0) {
Serial.print(F("Unexpected response: "));
Serial.println(status);
return;
}
// Skip HTTP headers
char endOfHeaders[] = "\r\n\r\n";
if (!client.find(endOfHeaders)) {
Serial.println(F("Invalid response"));
return;
// the rest of the code related to parsing that I didn't paste for the sake the argument.
Thanks everybody.
I am trying to publish some data I get from a embedded platform using UART transmission on to a web server using ESP32. This is the code I am using, I am using Arduino IDE. I am not quite familiar with HTML and have seen couple of examples where HTML is being used to update the server values by sending requests.
#include <WiFi.h>
#include "ESPAsyncWebServer.h"
AsyncWebServer server1(81);
const char* ssid ="mywifi";
const char* password ="mywifipass";
char c;
String readuart() {
if (Serial.available() > 0) {
c = Serial.read();}
return String(c);
}
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi..");
}
Serial.println("Connected to the WiFi network");
Serial.println(WiFi.localIP());
server1.on("/test_data", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plain", readuart().c_str());
});
server1.begin();
delay(100);
}
void loop() {
}
Following part of the code is publishing the first string I get but it stops there.
server1.on("/test_data", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plain", readuart().c_str());
});
I want to update this one string periodically. How do I achieve that? Any help will be much appreciated.
It sounds like you want the page to reload itself every x seconds. You can change your response string to actual html and use the meta refresh tag. This happens on the client side (in the web browser).
server1.on("/test_data", HTTP_GET, [](AsyncWebServerRequest *request){
String html = "<html><head><meta http-equiv=\"refresh\" content=\"30\"></head><body>"
+ readuart() + "</body></html>";
request->send(200, "text/html", html.c_str());
});
Change the 30 to whatever timeout you want.
Another option would be to use client side JS.
An example code:
#include <FS.h> //this needs to be first, or it all crashes and burns...
#include <ESP8266WiFi.h> //https://github.com/esp8266/Arduino
//needed for library
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h> //https://github.com/tzapu/WiFiManager
#include <ArduinoJson.h> //https://github.com/bblanchon/ArduinoJson
//define your default values here, if there are different values in config.json, they are overwritten.
char mqtt_server[40];
char mqtt_port[6] = "8080";
char blynk_token[34] = "YOUR_BLYNK_TOKEN";
//flag for saving data
bool shouldSaveConfig = false;
//callback notifying us of the need to save config
void saveConfigCallback () {
Serial.println("Should save config");
shouldSaveConfig = true;
}
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
Serial.println();
//clean FS, for testing
//SPIFFS.format();
//read configuration from FS json
Serial.println("mounting FS...");
if (SPIFFS.begin()) {
Serial.println("mounted file system");
if (SPIFFS.exists("/config.json")) {
//file exists, reading and loading
Serial.println("reading config file");
File configFile = SPIFFS.open("/config.json", "r");
if (configFile) {
Serial.println("opened config file");
size_t size = configFile.size();
// Allocate a buffer to store contents of the file.
std::unique_ptr<char[]> buf(new char[size]);
configFile.readBytes(buf.get(), size);
DynamicJsonBuffer jsonBuffer;
JsonObject& json = jsonBuffer.parseObject(buf.get());
json.printTo(Serial);
if (json.success()) {
Serial.println("\nparsed json");
strcpy(mqtt_server, json["mqtt_server"]);
strcpy(mqtt_port, json["mqtt_port"]);
strcpy(blynk_token, json["blynk_token"]);
} else {
Serial.println("failed to load json config");
}
}
}
} else {
Serial.println("failed to mount FS");
}
//end read
// The extra parameters to be configured (can be either global or just in the setup)
// After connecting, parameter.getValue() will get you the configured value
// id/name placeholder/prompt default length
WiFiManagerParameter custom_mqtt_server("server", "mqtt server", mqtt_server, 40);
WiFiManagerParameter custom_mqtt_port("port", "mqtt port", mqtt_port, 5);
WiFiManagerParameter custom_blynk_token("blynk", "blynk token", blynk_token, 32);
//WiFiManager
//Local intialization. Once its business is done, there is no need to keep it around
WiFiManager wifiManager;
//set config save notify callback
wifiManager.setSaveConfigCallback(saveConfigCallback);
//set static ip
wifiManager.setSTAStaticIPConfig(IPAddress(10,0,1,99), IPAddress(10,0,1,1), IPAddress(255,255,255,0));
//add all your parameters here
wifiManager.addParameter(&custom_mqtt_server);
wifiManager.addParameter(&custom_mqtt_port);
wifiManager.addParameter(&custom_blynk_token);
//reset settings - for testing
//wifiManager.resetSettings();
//set minimu quality of signal so it ignores AP's under that quality
//defaults to 8%
//wifiManager.setMinimumSignalQuality();
//sets timeout until configuration portal gets turned off
//useful to make it all retry or go to sleep
//in seconds
//wifiManager.setTimeout(120);
//fetches ssid and pass and tries to connect
//if it does not connect it starts an access point with the specified name
//here "AutoConnectAP"
//and goes into a blocking loop awaiting configuration
if (!wifiManager.autoConnect("AutoConnectAP", "password")) {
Serial.println("failed to connect and hit timeout");
delay(3000);
//reset and try again, or maybe put it to deep sleep
ESP.reset();
delay(5000);
}
//if you get here you have connected to the WiFi
Serial.println("connected...yeey :)");
//read updated parameters
strcpy(mqtt_server, custom_mqtt_server.getValue());
strcpy(mqtt_port, custom_mqtt_port.getValue());
strcpy(blynk_token, custom_blynk_token.getValue());
//save the custom parameters to FS
if (shouldSaveConfig) {
Serial.println("saving config");
DynamicJsonBuffer jsonBuffer;
JsonObject& json = jsonBuffer.createObject();
json["mqtt_server"] = mqtt_server;
json["mqtt_port"] = mqtt_port;
json["blynk_token"] = blynk_token;
File configFile = SPIFFS.open("/config.json", "w");
if (!configFile) {
Serial.println("failed to open config file for writing");
}
json.printTo(Serial);
json.printTo(configFile);
configFile.close();
//end save
}
Serial.println("local ip");
Serial.println(WiFi.localIP());
}
void loop() {
// put your main code here, to run repeatedly:
}
I got this code from here (https://techtutorialsx.com/2017/01/08/esp8266-posting-json-data-to-a-flask-server-on-the-cloud/):
HTTPClient http; //Declare object of class HTTPClient
http.begin("http://anteph.pythonanywhere.com/postjson"); //Specify request destination
http.addHeader("Content-Type", "application/json"); //Specify content-type header
int httpCode = http.POST(JSONmessageBuffer); //Send the request
String payload = http.getString(); //Get the response payload
Serial.println(httpCode); //Print HTTP return code
Serial.println(payload); //Print request response payload
http.end(); //Close connection
But If I use:
int httpCode = http.POST(JSONmessageBuffer); //Send the request
This will send all the json data (i.e mqtt_server, mqtt_port, blynk_token). But I want to send only "blynk_token" and not the rest json data to server, So how can I achieve this?
Please suggest.
So to answer your question regarding "how to read and write limited Json data from SPIFF using DynamicJsonBuffer" you can take a look at your code right here where you read in the data.
if (configFile) {
Serial.println("opened config file");
size_t size = configFile.size();
// Allocate a buffer to store contents of the file.
std::unique_ptr<char[]> buf(new char[size]);
configFile.readBytes(buf.get(), size);
DynamicJsonBuffer jsonBuffer;
JsonObject& json = jsonBuffer.parseObject(buf.get());
json.printTo(Serial);
if (json.success()) {
Serial.println("\nparsed json");
strcpy(mqtt_server, json["mqtt_server"]); // copy mqtt_server value
strcpy(mqtt_port, json["mqtt_port"]); // copy mqtt_port value
strcpy(blynk_token, json["blynk_token"]); // copy blynk_token value
} else {
Serial.println("failed to load json config");
}
}
after this piece of code you have your 3 variables filled up with values.
These are mqtt_server, mqtt_port and blynk_token, which now contain the values read from the json which was saved in SPIFF.
If you then want to send the blynk_token to your local server you will have to put only that varialbe in your JSONmessageBuffer.
like so:
StaticJsonBuffer<300> JSONbuffer; //Declaring static JSON buffer
JsonObject& JSONencoder = JSONbuffer.createObject();
JSONencoder["blynk_token"] = blynk_token; // add blynk_token to the values sent to the server
char JSONmessageBuffer[300];
JSONencoder.prettyPrintTo(JSONmessageBuffer, sizeof(JSONmessageBuffer));
Serial.println(JSONmessageBuffer);
HTTPClient http; //Declare object of class HTTPClient
http.begin("http://anteph.pythonanywhere.com/postjson"); //Specify request destination
http.addHeader("Content-Type", "application/json"); //Specify content-type header
int httpCode = http.POST(JSONmessageBuffer); //Send the request
String payload = http.getString(); //Get the response payload
Serial.println(httpCode); //Print HTTP return code
Serial.println(payload); //Print request response payload
http.end(); //Close connection
this line JSONencoder["blynk_token"] = blynk_token; is your way to go. Here you say which values you want to sent to your server.
This tutorial might also help you :)
I am trying to get a reference to a response stream before its complete in windows phone 8.
In other .Net platforms you can do
HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(myUri);
WebResponse subscribeWebResponse = null;
Stream subscribeStream = null;
subscribeWebResponse = httpRequest.GetResponse();
subscribeStream = subscribeWebResponse.GetResponseStream();
For the purpose of creating Portable class libraries I've used the HttpClientLibrary from nuget.
This Adds ref to extensions assembly Microsoft.Net.Http
this allows me to return the async request at the time the headers have been read instead of waiting for the content transfer to be complete with
var clientResponse = await httpClient.SendAsync(requestmessage, HttpCompletionOption.ResponseHeadersRead);
The problem I'm having is that in windows phone 8 it doesn't work correctly, and still awaits the completion of the content stream to return.
Additionally
await httpWebRequest.BeginGetResponse(callback, request)
has the same behavior as these async methods are actually waiting for the completion of the web's response to continue execution.
So, is there any way to achieve the returning the response/stream at the point that i have received the response headers without Microsoft.Http.Net package?
Even if it has to be a Windows Phone 8 Platform Specific Solution?
Possibly an extension of HttpWebRequest?
From what I can tell, ResponseHeadersRead works on the WP8 emulator as it does on the desktop.
I installed the Win8 SDK. Created a windows phone app. I added this code to the MainPage ctor. This demonstrates a very rudimentary long polling example.
var client = new HttpClient();
var request = new HttpRequestMessage()
{
RequestUri = new Uri("http://oak:1001/longpolling")
};
client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, new CancellationToken())
.ContinueWith((t) =>
{
var response = t.Result;
response.Content.ReadAsStreamAsync()
.ContinueWith(s =>
{
var st = s.Result;
while (true)
{
var message= ReadNextMessage(st);
}
});
});
}
private static string ReadNextMessage(Stream stream)
{
int chr = 0;
string output = "";
while (chr != 10)
{
chr = stream.ReadByte();
output += Convert.ToChar(chr);
}
return output;
}
On my host dev machine I have a web api with a controller that looks like this...
public class LongPollingController : ApiController
{
public HttpResponseMessage Get()
{
Thread.Sleep(2000);
var content = new PushStreamContent( (s,c,t) =>
{
int i = 0;
while (true)
{
try
{
var message = String.Format("The current count is {0} " + Environment.NewLine, i++);
var buffer = Encoding.UTF8.GetBytes(message);
s.Write(buffer, 0, buffer.Length);
}
catch (IOException exception)
{
s.Close();
return;
}
Thread.Sleep(1000);
}
});
return new HttpResponseMessage(HttpStatusCode.OK)
{
RequestMessage = Request,
Content = content
};
}
}
So here's the deal. I would say that what you want to do is not possible, due to platform limitations... But SignalR has a WP client and is able to manage it. So it seems to me you have two options:
1) Dig into the SignalR source code to see how they do it (I'm on my phone right now so I can't provide a link).
UPDATE: Here is the link. They do some pretty neat tricks, like setting the Timeout to -1 for long-running clients. I think you should definitely use the techniques here.
OR
2) You can move whatever you're doing over to SignalR, which would gain the benefit of having a robust infrastructure and being cross-platform compatible.
HTH