How to fix ESP32 not connecting to the Wifi network

If you use a program like our minimal ESP32 wifi example:

#include <Arduino.h>
#include <WiFi.h>

void setup() {
  Serial.begin(115200);
  WiFi.begin("MyWifiNetworkName", "MyWifiPassword");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.println("Wifi connecting...");
    delay(500);
  }
  Serial.println("Wifi connected");
}
void loop() {
  // put your main code here, to run repeatedly:
}

and you just see a loop of

Wifi connecting...

messages, press the RESET button of your board (or unplug and re-plug its power to reset) and and try again. If you still see just Wifi connecting... messages after trying to reset 5 times, most likely your wifi credentials are wrong or the ESP32 can’t see your Wifi network!

Otherwise, if your ESP32 sometimes connects to the Wifi network almost immediately and sometimes it doesn’t seem to connect at all, use this code instead to automatically reset the ESP32 if it doesn’t connect within 5 seconds:

// Wait for wifi to be connected
uint32_t notConnectedCounter = 0;
while (WiFi.status() != WL_CONNECTED) {
    delay(100);
    Serial.println("Wifi connecting...");
    notConnectedCounter++;
    if(notConnectedCounter > 50) { // Reset board if not connected after 5s
        Serial.println("Resetting due to Wifi not connecting...");
        ESP.restart();
    }
}

Full example:

#include <Arduino.h>
#include <WiFi.h>

void setup() {
  Serial.begin(115200);
  WiFi.begin("MyWifiSSID", "MyWifiPassword");

  // Wait for wifi to be connected
  uint32_t notConnectedCounter = 0;
  while (WiFi.status() != WL_CONNECTED) {
      delay(100);
      Serial.println("Wifi connecting...");
      notConnectedCounter++;
      if(notConnectedCounter > 50) { // Reset board if not connected after 5s
          Serial.println("Resetting due to Wifi not connecting...");
          ESP.restart();
      }
  }
  Serial.print("Wifi connected, IP address: ");
  Serial.println(WiFi.localIP());
}

void loop() {
  // put your main code here, to run repeatedly:
}
Posted by Uli Köhler in Arduino, ESP8266/ESP32, PlatformIO

ESP32 minimal WiFi client example

This example shows how to connect your ESP32 to an existing Wifi network using the Arduino Framework:

#include <Arduino.h>
#include <WiFi.h>

void setup() {
  Serial.begin(115200);
  WiFi.begin("MyWifiNetworkName", "MyWifiPassword");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.println("Wifi connecting...");
    delay(500);
  }
  Serial.println("Wifi connected");
}
void loop() {
  // put your main code here, to run repeatedly:
}

 

Posted by Uli Köhler in Arduino, ESP8266/ESP32, PlatformIO

ESPAsyncWebServer JSON response example for ArduinoJson 6

The ESPAsyncWebserver page features an example for generating a basic JSON response using ArduinoJson:

#include "AsyncJson.h"
#include "ArduinoJson.h"

AsyncResponseStream *response = request->beginResponseStream("application/json");
DynamicJsonBuffer jsonBuffer;
JsonObject &root = jsonBuffer.createObject();
root["heap"] = ESP.getFreeHeap();
root["ssid"] = WiFi.SSID();
root.printTo(*response);
request->send(response);

However, that example is made for ArduinoJson 5.x whereas most users want to use the updated ArduinoJson 6.x.

ArduinoJson 6.x minimal ESPAsyncWebserver example:

AsyncResponseStream *response = request->beginResponseStream("application/json");
DynamicJsonDocument json(1024);
json["status"] = "ok";
json["ssid"] = WiFi.SSID();
json["ip"] = WiFi.localIP();
serializeJson(json, *response);
request->send(response);

 

Posted by Uli Köhler in Embedded, ESP8266/ESP32, PlatformIO

How to fix ArduinoJson error: DynamicJsonBuffer is a class from ArduinoJson 5

When you see an error message like

Compiling .pio\build\d1_mini\src\main.cpp.o
src\main.cpp:22:11: error: DynamicJsonBuffer is a class from ArduinoJson 5. Please see arduinojson.org/upgrade to learn how to upgrade your program to ArduinoJson version 6
       DynamicJsonBuffer jsonBuffer;

in your PlatformIO or Arduino project using the ArduinoJson library, your code was written for an old version of ArduinoJson.

According to the official ArduinoJson 5 to ArduinoJson 6 migration guide, you need to use DynamicJsonDocument instead. Note that DynamicJsonDocument uses a slightly different API compared to DynamicJsonDocument, hence you might need to adjust more than just changing the class names. But as a first step, replace e.g.

DynamicJsonBuffer json;

by

DynamicJsonDocument json(1024);
Posted by Uli Köhler in Arduino, ESP8266/ESP32, PlatformIO

How to fix ArduinoJson error: ‘ArduinoJson::JsonObject’ has no member named ‘printTo’

Problem:

While trying to build your project using ArduinoJson, you see an error message like

src\main.cpp:26:12: error: 'ArduinoJson::JsonObject' has no member named 'printTo'
       root.printTo(*response);

Solution:

The code you’re using is for an older version of ArduinoJson: It was written for ArduinoJson version 5.x while you’re using ArduinoJson version 6.x. Use serializeJson() instead of  root.printTo(*response):

serializeJson(root, *response);

See the official ArduinoJson guide for Migrating from version 5 to 6 for further information on which calls you need to replace.

 

Posted by Uli Köhler in Arduino, Embedded, ESP8266/ESP32, PlatformIO

ESP8266 WPA EAP minimal example

This example shows how to connect to WPA2-EAP wifi with username and password on the ESP8266 using the Arduino framework:

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include "wpa2_enterprise.h"

char ssid[] = "MyWifiSSID";
char username[] = "MyEAPUsername";
char password[] = "MyEAPPassword";

void setup()
{
  Serial.begin(115200);

  wifi_set_opmode(STATION_MODE);

  // Configure SSID
  struct station_config wifi_config;

  memset(&wifi_config, 0, sizeof(wifi_config));
  strcpy((char *)wifi_config.ssid, ssid);

  wifi_station_set_config(&wifi_config);

  // DO NOT use authentication using certificates
  wifi_station_clear_cert_key();
  wifi_station_clear_enterprise_ca_cert();

  // Authenticate using username/password
  wifi_station_set_wpa2_enterprise_auth(1);
  wifi_station_set_enterprise_identity((uint8 *)username, strlen(username));
  wifi_station_set_enterprise_username((uint8 *)username, strlen(username));
  wifi_station_set_enterprise_password((uint8 *)password, strlen(password));

  // Connect
  wifi_station_connect();

  // Wait for connect
  while (WiFi.status() != WL_CONNECTED)
  {
    Serial.println("Wifi connecting...");
    delay(500);
  }
  // Print wifi IP addess
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void loop()
{
  // put your main code here, to run repeatedly:
}

 

Posted by Uli Köhler in ESP8266/ESP32

Minimal ESP8266 WiFi example

#include <Arduino.h>
#include <ESP8266WiFi.h>

void setup() {
  Serial.begin(115200);
  WiFi.begin("MySSID", "MyPassword");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.println("Wifi connecting...");
    delay(500);
  }
  Serial.println("Wifi connected");
}

void loop() {
  // put your main code here, to run repeatedly:
}

 

Posted by Uli Köhler in C/C++, ESP8266/ESP32

How to fix .NET Core: Could not execute because the application was not found or a compatible .NET SDK is not installed.

Problem:

You are trying to run a .NET core command using e.g.

dotnet new console

but you see this error message:

Could not execute because the application was not found or a compatible .NET SDK is not installed.
Possible reasons for this include:
  * You intended to execute a .NET program:
      The application 'new' does not exist.
  * You intended to execute a .NET SDK command:
      It was not possible to find any installed .NET SDKs.
      Install a .NET SDK from:
        https://aka.ms/dotnet-download

Solution:

You didn’t install a .NET Core SDK, only the .NET Core host.

Install the SDK by using e.g. (this command works on Debian & Ubuntu)

sudo apt -y install dotnet-sdk-5.0

You might need to select the correct version (5.0 in this example).

Posted by Uli Köhler in C#

nginx redirect to output of command

You can use the lua plugin (sudo apt -y install libnginx-mod-http-lua) in order to use this technique to redirect to an URL that is the output of a command line command:

location = /myurl {
    access_by_lua_block  {
        local process = io.popen("curl -fsSL http://mydomain.com/file-which-contains-an-URL.txt", "r")
        local output = process:read('*a')
        process:close()
        ngx.redirect(output)
    }
}

By default, this redirects using the status code 302 (temporary redirect – not cached). In case you want a 301 (moved permanently) instead, use

ngx.redirect(output, 301)

 

Posted by Uli Köhler in nginx

How to fix Angular ng: not found

Problem:

When trying to run a command like ng build, e.g. in a CI setup, you see an error message like

sh: ng: not found

Preferred solution:

Only the machine where you’re running this command, you have not installed the ng command globally.

However, your node_modules folder will have a local installation of @angular/cli.

In order to use it, replace ng by ./node_modules/.bin/ng.

For example, a scripts section of your package.json

"scripts": {
  "build": "ng build"
},

would be replaced by

"scripts": {
  "build": "./node_modules/.bin/ng build"
},

Alternative solution:

Install ng globally on the machine running the command by using

sudo npm i -g @angular/cli

 

Posted by Uli Köhler in Angular

How to find out if a certificate has an elliptic curve or an RSA key

You can use openssl to find out if your certificate is using an elliptic curve (e.g. ECDSA) or an RSA key using the following command, replacing cert.pem by the path of your certificate:

openssl x509 -noout -text -in cert.pem | grep -i "ecPublicKey" > /dev/null ; if [ $? -ne 0 ]; then echo "No elliptic curve key" ; else echo "Elliptic curve key"; fi

If the certficate’s key is an elliptic curve key, it will print:

Elliptic curve key

If the certficate’s key another type of key like a RSA key, it will print:

No elliptic curve key

How it works

First we tell OpenSSL to print info about the certificate:

openssl x509 -noout -text -in cert.pem

Then we grep for ecPublicKey. This is because for elliptic curve keys, the output of the aforementioned openssl command contains

Subject Public Key Info:
    Public Key Algorithm: id-ecPublicKey
        Public-Key: (384 bit)
        pub:

whereas for RSA keys it looks like this:

Subject Public Key Info:
    Public Key Algorithm: rsaEncryption
        RSA Public-Key: (2048 bit)
        Modulus:

The grep command is piped to /dev/null since we’re not interested in its output but only in its return code (which is available as $? in the shell). grep returns a return code of 0 if and only if it finds at least one match in the input. Otherwise, it has a return code of 1. In our case, this means that we’ll get a return code of 0 if ecPublicKey appears anywhere in the output. We assume that this string will only ever occur in the Subject Public Key Info: section. While in theory it is possible that ecPublicKey appears in some user-defined fields of the certificate, this is extremely unlikely to happen and could be mitigated by using a regular expression in grep

We can then use this bash snippet:

if [ $? -ne 0 ]
then
    # TODO insert code if grep does NOT find anything
else
    # TODO insert code if grep finds at least one line
fi

which we use like this:

if [ $? -ne 0 ]; then echo "No elliptic curve key" ; else echo "Elliptic curve key"; fi

i.e. depending on the return code of grep, we will print the correct message.

Posted by Uli Köhler in Networking

How to find public key type of SSL/TLS X.509 certificate using OpenSSL

Use the following command to print, replacing cert.pem by the path of your certificate:

openssl x509 -noout -text -in cert.pem | grep -i "Public Key Info" --after 3

Example output:

Subject Public Key Info:
    Public Key Algorithm: rsaEncryption
        RSA Public-Key: (2048 bit)
        Modulus:

 

Posted by Uli Köhler in Networking

How to use apt install correctly in your Dockerfile

This is the correct way to use apt install in your Dockerfile:

ENV DEBIAN_FRONTEND=noninteractive
RUN apt update && apt install -y PACKAGE && rm -rf /var/lib/apt/lists/*

Key takeaways:

  • Set DEBIAN_FRONTEND=noninteractive to prevent some packages from prompting interactive input (tzdata for example), which leads to indefinite waiting for an user input
  • Run apt update before the install command to fetch the current package lists
  • apt install with -y to prevent apt from asking you if you really want to install the packages
  • rm -rf /var/lib/apt/lists/* after the install command in order ot prevent the cached apt lists (which are fetched by apt update) from ending up in the container image
  • All of that in one command joined by && in order to prevent docker from building separate layers for each part of the command (and to prevent it from first storing /var/lib/apt/lists in one layer and then delete it in another layer)

Also see the official guide on Dockerfile best practices

Posted by Uli Köhler in Container, Docker

How to fix Gitlab CI error during connect: Post http://docker:2375/v1.40/auth: dial tcp: lookup docker on … no such host

Problem:

In your GitLab CI jobs, you see this error message while trying to run some docker command:

error during connect: Post http://docker:2375/v1.40/auth: dial tcp: lookup docker on 192.168.178.1:53: no such host

Solution:

This error occurs with docker-based gitlab runners such as the one we’re that are configured using a docker executor. The error message means that the inner docker container doesn’t have a connection to the host docker daemon.

In order to fix this, set these options in [runners.docker] in your gitlab-runner config.toml:

privileged = true
volumes = ["/cache", "/var/run/docker.sock:/var/run/docker.sock"]

A valid config.toml looks like this:

concurrent = 1
check_interval = 0

[session_server]
  session_timeout = 1800

[[runners]]
  name = "CoreOS-Haar-Runner"
  url = "https://gitlab.techoverflow.net/"
  token = "bemaiBie8usahMoo9ish"
  executor = "docker"
  [runners.custom_build_dir]
  [runners.cache]
    [runners.cache.s3]
    [runners.cache.gcs]
    [runners.cache.azure]
  [runners.docker]
    tls_verify = false
    image = "docker:stable"
    privileged = true
    disable_entrypoint_overwrite = false
    oom_kill_disable = false
    disable_cache = false
    volumes = ["/cache", "/var/run/docker.sock:/var/run/docker.sock"]
    shm_size = 0

Why this works

Adding "/var/run/docker.sock:/var/run/docker.sock" allows the inner docker container running inside the gitlab-runner to access the outer (host) docker daemon. Without this, it tries to connect to the docker default URL, i.e. http://docker:2375. This URL can’t be resolved via DNS (the DNS server in my case is 192.168.178.1, the DNS standard port being 53), hence docker prints the error message listed above.

Posted by Uli Köhler in Docker

Mini systemd command cheat-sheet

These are the most common commands I use if my systemd service file is placed in /etc/systemd/system/myservice.service.

Enable (i.e. start at boot) and also start the service right now (--now):

sudo systemctl enable --now myservice

Start by

sudo systemctl start myservice

Restart by

sudo systemctl restart myservice

Stop by

sudo systemctl stop myservice

View status:

sudo systemctl status myservice

View & follow logs:

sudo journalctl -xfu myservice

View logs in less:

sudo journalctl -xu myservice

 

Posted by Uli Köhler in Linux

How to install gitlab-runner using docker-compose

First, choose a directory where the service will reside in. I recommend /opt/gitlab-runner.  Then create docker-compose.yml in said directory with this content:

version: '3'
services:
  gitlab-runner:
    image: 'gitlab/gitlab-runner:latest'
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./config:/etc/gitlab-runner
    restart: unless-stopped

then run this command to configure the runner:

docker-compose up -d
docker-compose exec -T gitlab-runner gitlab-runner register

It will ask you for details about the GitLab instance you want to attach to. You will find this information at https://<your-gitlab-domain>/admin/runners. This example is for my GitLab instance:

Runtime platform                                    arch=amd64 os=linux pid=38 revision=943fc252 version=13.7.0
Running in system-mode.

Enter the GitLab instance URL (for example, https://gitlab.com/):
https://gitlab.techoverflow.net/
Enter the registration token:
Loo2lahf9Shoogheiyae
Enter a description for the runner:
[148a53203df8]: My-Runner
Enter tags for the runner (comma-separated):

Registering runner... succeeded                     runner=oc-oKWMH
Enter an executor: custom, docker-ssh, shell, virtualbox, docker-ssh+machine, docker, parallels, ssh, docker+machine, kubernetes:
shell
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!

Now, restart the runner that is running with the old config (i.e. with no gitlab instance being attached):

docker-compose down

After that’s finished, you can run the script from our previous post Create a systemd service for your docker-compose project in 10 seconds in the directory where docker-compose.yml is located.

curl -fsSL https://techoverflow.net/scripts/create-docker-compose-service.sh | sudo bash /dev/stdin

This will automatically generate a systemd service and start the runner (also on boot). For more details, see the corresponding blogpost. If your directory is named gitlab-runner, the service file will be stored in /etc/systemd/systemd/gitlab-runner.service, hence these are commands you can use to control the service:

Note that the script that creates the systemd service will automatically start the runner, so you don’t need to start it manually. !

Start by

sudo systemctl start gitlab-runner

Restart by

sudo systemctl restart gitlab-runner

Stop by

sudo systemctl stop gitlab-runner

View status:

sudo systemctl status gitlab-runner

View & follow logs:

sudo journalctl -xfu gitlab-runner

View logs in less:

sudo journalctl -xu gitlab-runner

Also see Mini systemd cheat-sheet

Also see How to register gitlab runner for multiple GitLab instances.

Note that you can also use

docker-compose logs -f

to view the logs (run this from the directory where docker-compose.yml) is located.

In case you see an error message like

error during connect: Post http://docker:2375/v1.40/auth: dial tcp: lookup docker on 192.168.178.1:53: no such host

in your jobs, see How to fix Gitlab CI error during connect: Post http://docker:2375/v1.40/auth: dial tcp: lookup docker on &#8230; no such host

Posted by Uli Köhler in GitLab