Talend Open Studio + JRE Error

I’ve just installed TOS 8 on my Windows 10 machine, and after trying to execute it an error appears (I wonder if some day a program running Java not bundled will work at first after being installed):

After installing the latest JDK version, it always throughs the same error.

No matter the times you install/uninstall JDKs, change environment variables, try to launch an inexistent Java Control Panel: always the same error.

The solution
  • Go to Start Button in Windows
  • Locate the TOS Menu
  • Open TOS Menu and right-click on icon launcher
  • Click on More
  • Click on Open File Location
  • Right-Click the Link File and click on Properties
  • Change the Destination folder to the one containing your Java binaries

ESP32 OTA in AP mode: ESP2SOTA

If you are trying to implement OTA functionality for your ESP32 / ESP8266 with AP Wifi Mode enabled, try this simple Arduino Library:

ESP2SOTA Library - ESP32 OTA in AP Mode

Library link: Github Repository

You can also install the library directly from Arduino IDE. Just click on Library Manager and search for ESP2SOTA

By installing the library, two code samples will be available, one for AP Mode and the other one for Client Mode

Sample sketch:

#include <WiFi.h>
#include <WiFiAP.h>
#include <WebServer.h>
#include <ESP2SOTA.h>

const char* ssid = "ESP2SOTA";
const char* password = "123456789abc";

WebServer server(80);

void setup(void) {
  WiFi.softAP(ssid, password);
  IPAddress IP = IPAddress (10, 10, 10, 1);
  IPAddress NMask = IPAddress (255, 255, 255, 0);
  WiFi.softAPConfig(IP, IP, NMask);
  IPAddress myIP = WiFi.softAPIP();
  Serial.print("AP IP address: ");

  server.on("/myurl", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/plain", "Hello there!");


void loop(void) {




No matter how simple you define your function, its creation will fail when using HeidiSQL interface.

CREATE FUNCTION `fn_my_function`(
    `parameter_a` INT
   return 0; 

HeidiSQL Create Function Error

After reviewing your code, you can’t find any syntax error. You try to fix it by removing the COMMENT ” line, but nothing seems to work.

The solution is quite simple:

  • Tell the SQL engine a new DELIMITER:
    • DELIMITER //
  • After the END; line, add another one to restore the DELIMITER to ;
  • That’s all

CREATE FUNCTION `fn_my_function`(
    `parameter_a` INT
   return 0; 


Please note that there is a space separator between DELIMITER and ;

Laravel 8 + Socialite + Google Login = Error with no message

After struggling for three hours, and making a useless bunch of changes in my code and server configuration, I finally found the solution to this:

  • When you finish your coding, and test it in your localhost deployment, you can login using Google and there’s no problem in getting all the user profile information.
  • Then you deploy the application in your cloud server environment and a “no message” error appears and doesn’t allow you to log into your application.
  • Laravel Socialite Google Login Error with No Message
    Laravel Socialite Google Login Error with No Message


MigBuilder: a tool to generate Laravel’s Eloquent files

MigBuilder is a tool to reverse engineer a MySQL database into Models, Factories, Seeders & Migrations files for Laravel Eloquent ORM.


MigBuilder is a tool to reverse engineer a MySQL database into Models, Factories, Seeders & Migrations files for Laravel Eloquent ORM.

It reads the database structure and completes as much code as possible to ease the model generation in Laravel.

Unless the model is structured exactly as expected, the code files will contain non accurate lines or declarations. So it is ALWAYS recommended to check the code once is generated.

The ‘perfect’ tables definition is:

  • Tables should contain a single column primary key, better if its name is ‘id’
  • Relationships have to be declared and always be composed of one single column
  • Not frequent datatypes (binary, longtext, …) have not been tested yet

composer require pangodream/migbuilder


php artisan migbuilder originschema

Migbuilder will read originschema database (originschema is the connection name in Laravel config/app.php file) and will generate the migration, model, seeder and factory files for each of the tables A good practice is two have two different connections declared in Laravel config/database.php file:

  • One alternative connection pointing to the existing database to be reverse engineered
  • The main connection that will be used to deploy migrate files and use models in

CAUTION: If you use the command above and any of the generated files already exists, the generation will not begin unless you specify the overwrite (–overwrite) parameter. Notice that using overwrite will destroy the information you have in any of your files having the same name.

NOTE: Migbuilder is not an error free code generator, but a help to save hand code lines, so you should review the generated code in every case.

I hope it to help you and if you have any comment, please let me know.

Sample generated files:

Source DDL

CREATE TABLE product (
id INT(11) NOT NULL,
description VARCHAR(45) NOT NULL DEFAULT '',
subcategory_id INT(11) NOT NULL DEFAULT '0',
brand_id INT(11) NOT NULL DEFAULT '0',
quantity VARCHAR(45) NOT NULL DEFAULT '0',
INDEX FK_products_subcategories (subcategory_id),
INDEX FK_products_brands (brand_id),
CONSTRAINT FK_products_brands FOREIGN KEY (brand_id) REFERENCES brand (id),
CONSTRAINT FK_products_subcategories FOREIGN KEY (subcategory_id) REFERENCES subcategory (id)

Migration file

/* Generated automatically using MigBuilder by Pangodream */

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateProductTable extends Migration
     * Run the migrations.
     * @return void
    public function up()
        Schema::create('product', function (Blueprint $table) {
            $table->string('name', 45)->default('');
            $table->string('slug', 45)->default('');
            $table->string('description', 45)->default('');
            $table->string('price', 45)->default('');
            $table->string('quantity', 45)->default('0');

            // Indexes

            // Constraints & Foreign Keys

     * Reverse the migrations.
     * @return void
    public function down()


Model file

/* Generated automatically using MigBuilder by Pangodream */

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Product extends Model
    use HasFactory;
    protected $table = 'product';

    // Fillables (remove the columns you don't need)
    protected $fillable = ['id', 'name', 'slug', 'description', 'price', 'subcategory_id', 'brand_id', 'quantity', ];

    // Parent relationships (change belongsTo to belongsToMany or similar if needed)
    public function Subcategory(){
        return $this->belongsTo(Subcategory::class);
    public function Brand(){
        return $this->belongsTo(Brand::class);
    // Child relationships (change hasMany to hasOne or similar if needed)
    public function ColorProduct(){
        return $this->hasMany(ColorProduct::class);
    public function Size(){
        return $this->hasMany(Size::class);


Seeder file

/* Generated automatically using MigBuilder by Pangodream */

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use App\Models\Product;

class ProductSeeder extends Seeder
     * Run the database seeds.
     * @return void
    public function run()
    // Record sample structure
    $product = [
        //'id' =>  ,
        //'name' => '',
        //'slug' => '',
        //'description' => '',
        //'price' => '',
        //'subcategory_id' =>  ,
        //'brand_id' =>  ,
        //'quantity' => '',


Factory file

/* Generated automatically using MigBuilder by Pangodream */

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;
use App\Models\Product;

class ProductFactory extends Factory
    protected $model = Product::class;
     * Define the model's default state.
     * @return array
    public function definition()
        return [

Callejero español 2020 con códigos postales (gratis)

En determinadas ocasiones necesitamos utilizar información geográfica en nuestras aplicaciones. No me refiero tanto a coordenadas geográficas sino simplemente a disponer de una relación de provincias, municipios, distritos, calles y códigos postales.

Desafortunadamente en España parece que los datos de dominio público se encuentran en un estado de disponibilidad muy precario y que cualquier oportunidad es buena para arañar unos cuantos euros a quien esté dispuesto a pagar por ellos.

Ayer tuve la necesidad de incluir información de este tipo en una de mis aplicaciones y cuál fue mi sorpresa cuando al ir a buscarla a internet para adaptarla me encontré con que aparentemente era información ‘de pago’.

En una búsqueda de Google como esta ‘base de datos codigos postales españa‘ aparece como primer resultado un enlace a la página de la empresa Correos en la que nos da instrucciones de cómo acceder a la base de datos de códigos postales previo pago de 687€ (en su versión más básica).

Indagando algo más encontré alguna referencia a reclamaciones realizadas en la página datos.gob.es cuya resolución dice textualmente:

La base de delimitaciones de códigos postales actualmente está sujeta a comercialización. Para adquirir el listado hay que seguir las instrucciones indicadas en la página de CORREOS

Personalmente me parece vergonzoso que este tipo de información esté comercializada por una empresa privada que nació con presupuesto público, pero España es así.

Dejando polémicas a un lado, seguí buscando y encontré una alternativa muy válida en la página del INE. El Callejero del Censo Electoral sirve perfectamente para este propósito ya que contiene información de todos los municipios de España con sus tramos de vías y pseudovías identificados con su correspondiente código postal. En la página de referencia existe un documento con la especificación de formato de cada uno de los ficheros que contienen la información:

  • Tramos de vía
  • Unidades poblacionales
  • Vías
  • Pseudovías

He realizado una importación y tratamiento de los ficheros para MySQL dejando únicamente la información esencial para poder montar un callejero con información de códigos postales.

La base de datos está disponible aquí. Una vez descomprimida ocupa 164MB.

Tras ejecutar el script SQL, encontraremos una base de datos llamada codpos que contendrá las siguientes tablas:

  • provincia
  • unidad_poblacional
  • via
  • pseudovia
  • tramo

Tramo es la tabla que relaciona el resto y corresponde a los tramos de vía correspondientes a cada una de las secciones y subsecciones censales (información que he omitido de la base de datos).

Un ejemplo de utilización sería la siguiente SQL:

select t.cpro as `codigo_provincia`,
       pr.dpro as `provincia`,
       up.dmun as `municipio`,
       ifnull(pv.dpsvia, concat(v.tvia, ' ', v.nvia)) as `via`,
		 t.cpos as `codigo_postal`
from tramo t left outer join via v 
on(t.cpro = v.cpro and t.cmum = v.cmum and t.cvia = v.cvia)
left outer join pseudovia pv 
on(t.cpro = pv.cpro and t.cmum = pv.cmum and t.cpsvia = pv.cpsvia)
left outer join unidad_poblacional up 
on(t.cpro = up.cpro and t.cmum = up.cmum and t.cun = up.cun)
left outer join provincia pr
on(t.cpro = pr.cpro)
where v.nvia like 'velazquez%'
  and 58 between t.ein and t.esn
  and pr.cpro = 28
  and t.tinum = 2

que nos devolverá todos aquellos tramos de vía con denominación que empiece por ‘velazquez’, que contengan un número 58 entre sus pares (tinum = 2) y que estén en la provincia de Madrid (28).

Read a Xiaomi Mi Smart scale using an ESP32

Xiaomi Mi Smart is a digital scale with BLE interface. This allows it to broadcast your weight to any device like your smart phone and incorporate the measurements to an application like Samsung Health.

Xiaomi Mi Smart Scale

 Two years ago I was trying to read BLE messages from an ESP32, but Arduino BLE libraries didn’t work very well at that moment and all I got were some headaches. I wasn’t even able to match the Weight Measurement remote characteristic, so once the ESP32 connected to the scale it wasn’t able to implement the callback functions.

Today I’ve tried again, and I made it work!!!

Calculate the UUIDs

I have used the BLE_Client example (under ESP32 dev board samples).  In order to connect and read values from any device you need two unique identifiers (16 bit UUID alias):

Now, with this two short uuids, we will calculate the long (128 bits) uuid by using the next formula:

128bit_base_uuid = 16bit_uuid_alias * 2^96 + Bluetooth_Base_UUID

where the Bluetooth_Base_UUID is 1000800000805F9B34FB (00000000-0000-1000-8000-00805F9B34FB)

In our case we will be using the string version of the 128bit uuid and that means all we have to do to calculate the long uuid for the service and the characteristic is concatenating the uuids (aliases and Bluetooth Base one) in the following way:

  • Service UUID: 0000181D-0000-1000-8000-00805F9B34FB
  • Characteristic UUID: 00002A9D-0000-1000-8000-00805F9B34FB
Finding our weight scale

Bring your Xiaomi scale next to your ESP32 MCU and load the next sketch using Arduino IDE:

 * An ESP32 BLE client to find Xiaomi Mi Smart weight scale
 * Author Pangodream
 * Date 2020.05.31

#include "BLEDevice.h"
//Base UUIDs
//Weight Scale service
static BLEUUID srvUUID("0000181d-0000-1000-8000-00805f9b34fb");

 * Callback class for each advertised device during scan
class deviceCB: public BLEAdvertisedDeviceCallbacks {
 //Called on each advertised device
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    // We have found a device, let us now see if it contains the service we are looking for.
    if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(srvUUID)) {
      if(advertisedDevice.getName() != "MI_SCALE"){
        Serial.print("Weight scale (no MI_SCALE) device found with address ");
        Serial.print(" and name ");
      } else {
        Serial.print("Xiaomi Mi Smart weight scale found with address ");
        Serial.println("End of scan");
    } else {
      Serial.print("Non weight scale device found with address ");

void setup() {
  Serial.println("Devices scan");
  BLEScan* pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new deviceCB());
  //Set active scan
  //Scan during 5 seconds
  pBLEScan->start(5, false);

void loop() {


The sketch starts an active scan to find all the BLE servers (devices) that are advertising themselves at that moment.

The scan results are shown at the Serial Monitor (115200 bauds) and if a Xiaomi Mi Smart scale is found (and its name is MI_SCALE) something like the next should appear in your monitor:

If your weight scale was present in the scan and you can read the message “Xiaomi Mi Smart weight scale found with address xx:xx:xx:xx:xx:xx” then you can continue with our next goal, which is to retrieve data from weight measurement.

Retrieving data

When the ESP32 is scanning for devices, it only reads the service beacons sent by each BLE server to advertise itself, but when we use the scale and it broadcasts the calculated weight, it doesn’t send the same type of message (it is not a service message, but a characteristic one).

Note that we haven’t used yet the characteristic UUID we calculated before and we’ve only used the service one.

Let’s load a new sketch into the ESP32 MCU to retrieve some data from the characteristic:

 * An ESP32 BLE client to retrieve data from the Weight Measurement characteristic  
 * of a Xiaomi Mi Smart weight scale
 * Author Pangodream
 * Date 2020.05.31

#include "BLEDevice.h"
//Base UUIDs
//Weight Scale service
static BLEUUID srvUUID("0000181d-0000-1000-8000-00805f9b34fb");
//Weight Measurement characteristic
static BLEUUID chrUUID("00002a9d-0000-1000-8000-00805f9b34fb");

static BLEAdvertisedDevice* scale;
static BLERemoteCharacteristic* remoteChr;
static boolean doConnect = false;
static boolean connected = false;
static int year = 0;

 * Callback function for characteristic notify / indication
static void chrCB(BLERemoteCharacteristic* remoteChr, uint8_t* pData, size_t length, bool isNotify) {
    //Console debugging
    Serial.print("Received data. Length = ");
    Serial.print(". - Data bytes: ");
    for(int i =0; i< length; i++){
      Serial.print("  ");
    Serial.println(" ");
    //End of console debugging
    //Parsing the received data and calculate weight
    boolean temporary = true;
    int rcvdYear = pData[3];
    //If we received a year for the first time, store it in the year variable
    //The first year we receive indicates a temporary measurement
    if(year == 0){
      year = rcvdYear;
      //If year has been previously defined and the year we have received is
      //greater than it, then the measurement is not temporary, is the final one
      if(rcvdYear > year){
        temporary = false;
    double weight = 0;
    weight = (pData[1] + pData[2] * 256) * 0.005;
    Serial.print("Weight: ");
    Serial.print(" Kg - ");
      Serial.println(" (Provisional)");
      Serial.println(" (Definitive)");

 * Callback class for each advertised device during scan
class deviceCB: public BLEAdvertisedDeviceCallbacks {
 //Called on each advertised device
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    // We have found a device, let us now see if it contains the service we are looking for.
    if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(srvUUID)) {
      if(advertisedDevice.getName() != "MI_SCALE"){
      } else {
        Serial.println("  Found!");
        Serial.println("Stopping scan and connecting to scale");
        scale = new BLEAdvertisedDevice(advertisedDevice);
        doConnect = true;
    } else {
 * Callback class for device events
class ClientCB : public BLEClientCallbacks {
  void onConnect(BLEClient* pclient) {


  void onDisconnect(BLEClient* pclient) {
    Serial.println("Disconnected. Reconnecting...");
    connected = false;

bool connectToScale() {
    Serial.println("Stablishing communications with scale:");
    BLEClient*  pClient  = BLEDevice::createClient();
    Serial.println("    BLE client created");

    pClient->setClientCallbacks(new ClientCB());

    // Connect to the remove BLE Server.
    Serial.println("    Connected to scale");

    // Obtain a reference to the service we are after in the remote BLE server.
    BLERemoteService* pRemoteService = pClient->getService(srvUUID);
    if (pRemoteService == nullptr) {
      Serial.println("    Error: Failed to find service");
      return false;
    Serial.println("    Service found");

    remoteChr = pRemoteService->getCharacteristic(chrUUID);
    if (remoteChr == nullptr) {
      Serial.print("    Failed to find characteristic");
      return false;
    Serial.println("    Characteristic found");
    Serial.println("    Setting callback for notify / indicate");
    return true;

void setup() {
  Serial.println("Searching for MI_SCALE device");
  BLEScan* pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new deviceCB());
  //Set active scan
  //Scan during 5 seconds
  pBLEScan->start(5, false);

void loop() {
  if(doConnect && !connected){
    connected = connectToScale();

Reboot the MCU and take a look to the serial monitor. If everything goes right, something similar to this should be shown:

The sketch performs these tasks:

  1. Scan devices to find the Xiomi scale
  2. Connects to the scale
  3. Search for the Weight Scale service
  4. Instantiates the service
  5. Search for the remote characteristic
  6. Instantiates the remote characteristic
  7. Sets the callBack function for the characteristic

In this way, when the scale begins to measure weight, our callback function will be invoked receiving the data.

At this point, the ESP32 is waiting to receive something in the callBack function. Put some weight over the scale and you should see the provisional and definitive measurements at the serial monitor.

The callback function always receive 10 bytes of information. These bytes can be parsed to get the weight and timestamp values in the following way:

Parsing the XIOMI Scale BLE data

The way we can differentiate between the provisional and the definitive measurements is looking at the year in the timestamp. While measurements are provisional, the scale sends a fake year and not the current one. So, all we hve to do is taking note of the value for the year in the first data packet we receive and then compare to the rest of the years we receive.

When the received year is greater than the first year we received that is the definitive measurement and we can get the calculated weight.

Why BLE_client sketch doesn’t work

If yoy try the sketch located at File / Examples / ESP32 BLE Arduino / BLE Client it won’t work and will not receive any chararteristic message even when you change the UUIDs.

The reason is this piece of code


To set the callback function for the remote characteristic, first it checks the characteristic can be notifiable (method canNotify()) and our characteristic is not.

If you study the BLE connection using any app for that purpose (I use nRF Connect from Nordic Semiconductor) you will notice that the characteristic we are using is not notifiable but ‘indicate’ type:

So, if you comment out the condition to avoid checking if the characteristic is notifiable or not, and set the callback function anyway, you will received calls in the callback function with the parameter isNotify set to false.

ESP32 MCU + MAX30100 not working

First of all, please read the post title. This doesn’t work on an ESP32 MCU.

I only made it work on an ESP8266 MCU.

This afternoon I received a couple of MAX30100 sensors that I ordered some days before (44 days, China, COVID19, …)

MAX30100 Sensor Board
MAX30100 Sensor Board

The board is quite easy to connect: it has an I2C interface and you even don’t need the INT pin, so all you have to do is wiring the SDA and SCL pins.

I used D22 and D21 pins in my ESP32 MCU, as they are also used for I2C_SDA and I2C_SCL interfacing (checkout pinout diagram here)

Summarizing, the pins you need to connect are:





Once you have all the pins connected it’s software time. 

In Arduino IDE, select Manage Libraries and install the Sparkfun MAX3010X Library

Sparkfun MAX3010X Library

After that, you are ready to open any of the examples provided with the library and test the module.

Probably you will notice that doesn’t work correctly. It seems to initialize the module correctly and even it starts to make some measurements. You even  would think that is working for a few seconds, but it isn’t.

The behaviour, at least in my case, is the next: 

  • The module initialize with a SUCESS status
  • It lights on the red led
  • You put your finger over the sensor
  • It starts measuring, but after a second the red led goes off
  • It seems to measure intermittently

If this is your case, don’t waste your time. I was searching for troubleshootings and all I found was replacing the inbuilt 4K7 resistors or bypassing the voltage limiter in the sensor board. Don’t do it, it is not a problem of pull-up resistors.

I decided then to try with an ESP8266 MCU and this is the result:

The sensor board works fine, so probably it is a problem of compatibility between the library and ESP32.

The sensor measures heart rate and SpO2. I only used the heartBeat event to light the led to show that it works.

Google Earth Studio: a filming drone in your hands

I can’t stop amazing about what Google can do with data.  If you like switching to terrain view in Google Maps to look to the actual appearance of places, then you have to visit Google Earth Studio

A short promotional video is shown at the home page and the first time I saw it I thought: This can’t be real. I immediately pressed the “Try Earth Studio” button and… and nothing: you need to register and explain your reasons to try the product. I did it, but honestly I thought I would never receive their approval.

I was entirely wrong. Today I received a link to try the product, and I have to say it is amazing. 

It was a matter of minutes to make a video showing “La Puerta de Alcalá” in Madrid (where I live) from the air like being filmed by a camera drone.

The video is very simple, but what I needed to produce it was even simpler.

The only thing I didn’t like was that the platform doesn’t produce a video ready to be downloaded, but all the video frames (jpeg format) compressed in a zip file and you have to compose the video with them.

Well, if you use default options (30 fps) and you want to produce an H264 video all you have to do is:

  • Unzip the file containing the frames
  • Access the footage folder
  • Execute ffmpeg specifying these options
ffmpeg -i "videoname_%03d.jpeg" -c:v libx264 -vf fps=30 -pix_fmt yuv420p out.mp4

Of course, you’ll have to replace videoname with the prefix name of your jpeg files.

Make your WOL configuration TRULY sticky


The typical steps to make your Ubuntu server wake on LAN are:

  • Find your network card interface name
  • Check your network card capabilities
  • Use ethtool to set “Wake-on” option to “g” value

And that’s all, then you put your server in suspend or hibernate mode and wake it up remotely. It works like a charm, but then you try a second time, you hibernate the server again and… it doesn’t wake remotely.

What happened, is that you didn’t repeat the third step to set again the “Wake-on” option to “g” value. The value you set for the network interface is volatile and you have to repeat the third step on each boot… unless you make it sticky.

Setup the network interface to work just once

1.- Find your network card interface name

sudo ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: enp3s0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc fq_codel state DOWN group default qlen 1000
    link/ether e8:94:f6:08:5a:60 brd ff:ff:ff:ff:ff:ff
3: eno1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether c8:9c:dc:2b:aa:48 brd ff:ff:ff:ff:ff:ff
    inet brd scope global noprefixroute eno1
       valid_lft forever preferred_lft forever
    inet6 fe80::ca9c:dcff:fe2b:aa48/64 scope link
       valid_lft forever preferred_lft forever

In my case, the server has three interfaces:

1: lo (the local loopback)

2: enp3s0: one 100Mbps ethernet card (not being used)

3: eno1: one 1Gbs ethernet card (this is the one I want to use to wake the system remotely, as it is the one configured to connect to my LAN). I will copy two values:

Interface name: eno1 (be aware of one (1) and lowercase L (l)). Usually interface name ends with a number, not a letter.

MAC address: e8:94:f6:08:5a:60

Now we know the interface name, we will check the Wake-on capabilities:

sudo ethtool eno1
Settings for eno1:
        Supported ports: [ TP ]
        Supported link modes:   10baseT/Half 10baseT/Full
                                100baseT/Half 100baseT/Full
        Supported pause frame use: No
        Supports auto-negotiation: Yes
        Supported FEC modes: Not reported
        Advertised link modes:  10baseT/Half 10baseT/Full
                                100baseT/Half 100baseT/Full
        Advertised pause frame use: No
        Advertised auto-negotiation: Yes
        Advertised FEC modes: Not reported
        Speed: 1000Mb/s
        Duplex: Full
        Port: Twisted Pair
        PHYAD: 1
        Transceiver: internal
        Auto-negotiation: on
        MDI-X: off (auto)
        Supports Wake-on: pumbg
        Wake-on: d
        Current message level: 0x00000007 (7)
                               drv probe link
        Link detected: yes

Take a look at the last lines. We are looking for two different lines:

Supports Wake-on: pumbg


Wake-on: d

The “Wake-on” mode configured by default is “d”, which means that the network card will not switch on the server when it receives a magic packet but, as the network interface supports “g” mode (it is one the letters in pumbg) we can set the value of “Wake-on” to “g”.

We will use ethtool for this. If it is not already installed on your system, do it:

sudo ethtool -s eno1 wol g

Now, if you repeat the step to check your network card capabilities (ethtool eno1) you shoud see the “Wake-on” option set to “g” value.

That means your server is ready to sleep and wake remotely.

Put the server into hibernation mode:

sudo systemctl hibernate

And now wake it remotely using one of the many available tools. Depending on the platform you will use an Android, Windows, Linux, … tool for this purpose and the only thing you will need is the MAC address you copied some steps above.

If everything went right, your server has woken, but what if you repeat the previous steps? (hibernate – remotely wake) It doesn’t work.

As I mentioned in the introduction, the value you configure in the “Wake-on” option of your network card is volatile. Each time you reboot your server it resets its value (usually to “d”).

Make your configuration sticky

We will create a system service to set the “Wake-on” value to “g” each time the server boots or restart.

There are a lot of recipes for these, but most of them didn’t work in my case. I’ll tell you one configuration line that did the trick for me.

1.- Create the .service file using your favourite editor

sudo nano /etc/systemd/system/wol.service

Now, copy the next content inside the file (change the name of the interface card and set the description you prefer):

Description=Activate WOL on eno1 network card

ExecStart=/sbin/ethtool -s eno1 wol g


Save the file (^O + ENTER + ^X)

Now we will start the service for the first time

sudo service wol start

And check its status

sudo service wol status

● wol.service - Activate Wake On LAN
   Loaded: loaded (/etc/systemd/system/wol.service; enabled; vendor preset: enabled)
   Active: inactive (dead) since Sat 2020-05-09 12:55:26 CEST; 2min 8s ago
  Process: 1706 ExecStart=/sbin/ethtool -s eno1 wol g (code=exited, status=0/SUCCESS)
 Main PID: 1706 (code=exited, status=0/SUCCESS)

may 09 12:55:26 estudios-srv systemd[1]: Starting Activate Wake On LAN...
may 09 12:55:26 estudios-srv systemd[1]: wol.service: Succeeded.
may 09 12:55:26 estudios-srv systemd[1]: Started Activate Wake On LAN.

You will notice the service is dead or inactive. This is normal because it is not actually a service and it is not running like a daemon, it starts,  do whatever it has to do and finishes.

If we restart the server now, our service entry will not run at startup because we haven’t enabled it. To do so:

sudo systemctl enable wol.service

Now, you can restart the server and it will wake remotely because “Wake-on: g” should be already set when it boots.

The explanation to “TRULY sticky”

But, why did I titled my post with a “TRULY sticky”?. Well, the reason is that all the recipes I’ve found to do this didn’t work. After rebooting, always the “d” value was set for the “Wake-on” option.

In fact it is not a problem of executing the configuration or not. Although the service entry run on every reboot, it was doing it before the network card was available to be configured.

So, the problem is when to run the network card configuration.

That’s the reason you should put this line in you .service file:


To make sure it configures the network card when it’s really available.

I hope it to work for you too.