MQTT – My simple switch for wardrobe and bed

In the summer 2017 my wife and I furnished our bedroom with new furniture. The new wardrobe has now LED lightning and the bed has also lightning integrated. But the bed has only one switch for the two LED’s. Switching the LED right and left separately was not possible and switch the wardrobe LED’s off while in bed was not possible. I doesn’t want a simple solution. I want something switchable with the smartphones. Because I’ve experience with the ESP8266 from my water level gauge I decide to build a switch for smartphones.

I want make use of the MQTT protocol. It’s lightweight enough to implement into a small device like the ESP8266.

The hardware

The LED’s are supplied via a 12 volt transformer. The ESp8266 needs 3.3 volts. So I decided to use a LF33CV for a stable 3.3 volts supply. For switching the LED’s I use a BD237 transistor.

From this schematics I’ve designed a small double layer PCB.

I’ve packaged the eagle schematics and board into a zip file: MqttSwitch

I’ve ordered three PCB on aisler.net and after a few day I had three professional manufactured PCB’s in hand.

With all the parts soldered on the PCB it looks like this.

The software

Before this project I was not a big fan of the adruino develepment environment. But I’ve found that are many libraries out there for supporting the ESP8266 in general and also the MQTT protocol on the ESP8266. So I used this setup:

The LED’s are fading in and out within two seconds. That’s adjustable by FADING_TIME. The connection to the MQTT server can be established secure or unsecure. You can change the secure / unsecure setting with the defines UNSECURE_MQTT / SECURE_MQTT at the top of the sketch. And here is the complete sketch:

/*
 MqttSwitch
 Matthias Jentsch - April 2018
*/

#define UNSECURE_MQTT
//#define SECURE_MQTT

#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <PubSubClient.h>
#include <time.h>

#include <FadeLed.h>
#define FADE_LED_PWM_BITS 10
#define FADING_TIME 2000

// Update these with values suitable for your network.
#define wifi_ssid1 "...................."
#define wifi_password1 "...................."
#define wifi_ssid2 "...................."
#define wifi_password2 "...................."

// MQTT connection
#define mqtt_server "...................."
#define mqtt_port 1883
// mqtt account
#define mqtt_username "...................."
#define mqtt_password "...................."
#ifdef SECURE_MQTT
// certificate fingerprint
#define mqtt_server_fingerprint ".. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .."
#endif

// Constants for wardrobe switch
/*#define MAX_LAMPS 4
#define LAMP1_PIN 12
#define LAMP2_PIN 13
#define LAMP3_PIN 14
#define LAMP4_PIN 16
const int LAMP_PINS[] = {LAMP1_PIN, LAMP2_PIN, LAMP3_PIN, LAMP4_PIN};
FadeLed lamps[] = {LAMP1_PIN, LAMP2_PIN, LAMP3_PIN, LAMP4_PIN};
#define mqtt_clientName "SwitchWardrobe"
#define mqtt_topic "switchWardrobe"*/

// Constants for bed switch
#define MAX_LAMPS 2
#define LAMP1_PIN 12
#define LAMP2_PIN 13
const int LAMP_PINS[] = {LAMP1_PIN, LAMP2_PIN};
FadeLed lamps[] = {LAMP1_PIN, LAMP2_PIN};
#define mqtt_clientName "SwitchBed"
#define mqtt_topic "switchBed"

// WiFiClient for multiple access points
ESP8266WiFiMulti wifiMulti;
#ifdef UNSECURE_MQTT
// unsecure connection
WiFiClient espClient;
#endif
#ifdef SECURE_MQTT
// secure connection
WiFiClientSecure espClient;
#endif
PubSubClient client(espClient);

void setup() {
  // Initialize the LED pins as outputs
  for (int i = 0; i < MAX_LAMPS; i++) {
    pinMode(LAMP_PINS[i], OUTPUT);
  }
  Serial.begin(115200);
  // switch LEDs off
  delay(10);
  for (int i = 0; i < MAX_LAMPS; i++) {
    digitalWrite(LAMP_PINS[i], LOW);
    lamps[i].setTime(FADING_TIME);
  }
  setup_wifi();
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);
}

void setup_wifi() {
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to access point");
  WiFi.mode(WIFI_STA);
  wifiMulti.addAP(wifi_ssid1, wifi_password1);
  wifiMulti.addAP(wifi_ssid2, wifi_password2);
  while (wifiMulti.run() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println();
  Serial.printf(" connected to %s\n", WiFi.SSID().c_str());
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

#ifdef SECURE_MQTT
  // Synchronize time useing SNTP. This is necessary to verify that
  // the TLS certificates offered by the server are currently valid.
  Serial.print("Setting time using SNTP");
  configTime(8 * 3600, 0, "pool.ntp.org", "time.nist.gov");
  time_t now = time(nullptr);
  while (now < 1000) {
    delay(500);
    Serial.print(".");
    now = time(nullptr);
  }
  Serial.println("");
#endif
}

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();

  if (String(topic) == mqtt_topic) {
    for (int i = 0; i < MAX_LAMPS; i++) { if (length > i) {
        Serial.print("Lamp");
        Serial.print(i + 1);
        if ((char)payload[i] == '0') {
          lamps[i].off();
          Serial.println(" off");
        }
        else if ((char)payload[i] == '1') {
          lamps[i].on();
          Serial.println(" on");
        }
        else {
          Serial.println(" unchanged");
        }
      }
      else {
        break;
      }
    }
  }
  else {
    Serial.println("Unknown topic!");
  }
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect(mqtt_clientName, mqtt_username, mqtt_password)) {
      Serial.println("connected");
#ifdef SECURE_MQTT
      if (espClient.verify(mqtt_server_fingerprint, mqtt_server)) {
        Serial.print("verified tls!");
      } else {
        Serial.print("unverified tls");
      }
#endif
      // resubscribe
      client.subscribe(mqtt_topic);
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void loop() {
  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  FadeLed::update(); //updates all FadeLed objects
}

 

On chip debugging on ESP32 with Eclipse and OpenOCD

With the help of a JTAG adapter, OpenOCD and Eclipse it’s possible to do on chip debugging on an ESP32. In the following chapters I’ll describe how to do that with the following hard- and software.

Used hardware:

  • ESP-WROOM-32 board
  • FTDI232 USB to Serial adapter
  • Olimex ARM-USB-OCD-H JTAG adapter

In theory it’s possible to program the flash memory also over JTAG but currently that’s not supported by the ESP32 OpenOCD driver. See also So we need an extra USB to Serial adapter. The Olimex ARM-USB-OCD-H JTAG adapter has also an extra RS232 port, but this port doesn’t provide 3.3 volt signals.

Used software:

  • Virtual Box 5.1.14 on Windows 10
  • VirtualBox 5.1.14 Oracle VM VirtualBox Extension Pack
  • Linux OS: Ubuntu 16.10 Desktop 64-bit running as guest on Virtual Box
  • Virtual Box Guest Additions
  • Putty
  • xtensa-esp32-elf toolchain
  • ESP32 IDF
  • OpenOCD; special version for ESP32
  • libftdi
  • Eclipse IDE for C++ Developers
  • GNU ARM Eclipse Plug-ins

I’ve prepare a ready to use Virtual Box appliance for download. If you wouldn’t install all the software by yourself you can download the appliance. But be warned that’s a 3.23 GB download.

You can easily import this appliance into Virtual Box. The user account is esp32 with password esp32.

Installing the software:

If you like installing all software by yourself you can follow these steps

Operating system

sudo usermod -a -G dialout $USER
sudo usermod -a -G plugdev $USER
sudo reboot

Putty

Displaying the output from the ESP32 UART is easy with the Putty tool. So I’ve installed it via

sudo apt-get install putty

ESP32 Toolchain

  • For getting the ESP32  toolchain ready I followed the documentation at https://esp-idf.readthedocs.io/en/latest/linux-setup.html
  • I downloaded the 64-bit binary toolchain and extracted it to ~/esp/xtensa-esp32-elf/
  • Anomalous to the documentation I’ve added the path to the xtensa tools to ~/.profile. Not to ~/.bash_profile
  • The IDF_PATH variable I’d also set in the ~/.profile
  • I added the following lines at the end of the ~./profile and did a source ~/.profile
  • # exports for ESP32
    export PATH=$PATH:$HOME/esp/xtensa-esp32-elf/bin
    export IDF_PATH=~/esp/esp-idf
  • After that I could do the make menuconfig from step 4.
  • I left all settings untouched, saved the configuration, exited menuconfig and executed the make command.
  • That brought me to the point where the ESP32 toolchain was ready for compiling

OpenOCD with libftdi

  • For debugging with OpenOCD and the Olimex ARM-USB-OCD-H JTAG adapter I did the following steps:

cd ~ sudo apt-get install build-essential git-core cmake doxygen libusb-1.0 make libtool pkg-config autoconf automake texinfo
git clone –recursive https://github.com/espressif/openocd-esp32.git

  • Download the sources for libftdi from https://www.intra2net.com/en/developer/libftdi/download/libftdi1-1.3.tar.bz2 and extracted it into the openocd-esp32 directory
  • Then I’ve build the libftdi with the following commands

cd ~/openocd-esp32/libftdi1-1.3
mkdir build
cd build
cmake -DCMAKE_INSTALL_PREFIX=“/usr“ ../
make
sudo make install

  • Now I was ready to build OpenOCD for EPS32 with the following commands

cd ~/openocd-esp32
./bootstrap
./configure
make
sudo make install

source [find interface/ftdi/olimex-arm-usb-ocd-h.cfg]
adapter_khz 2000
esp108 flashbootstrap 3.3
  • The I saved this file to ~/openocd-esp32/esp32.cfg
  • I also copied the file ~/openocd-esp32/contrib/99-openocd.rules into the /etc/udev/rules.d directory.

Eclipse IDE

For running Eclipse I’ve installed the OpenJDK 8 via

sudo apt-get install openjdk-8-jre
  • Then I’ve downloaded Eclipse Neon installer from https://www.eclipse.org/downloads/
  • After unpacking the Eclipse installer package into ~/eclipse-installer I’ve started the eclipse-inst and installed the Eclipse IDE for C++ Developers into the default directory.
  • Then start Eclipse
  • Open a browser goto http://gnuarmeclipse.github.io/plugins/install/ and drag and drop the Install button to the running Eclipse
  • Check the following features of the GNU ARM Eclipse Plug-ins
    • GNU ARM C/C++ Cross Compiler
    • GNU ARM C/C++ OpenOCD Debugging
  • Click Confirm, accept the license agreement and click finish

Building the template app with Eclipse

For getting the template app (an blinky example) compiling in Eclipse I followed the documentation at: https://esp-idf.readthedocs.io/en/latest/eclipse-setup.html Additionally to the steps described I did the following:

  • Added the targets „all“ and „flash“ via right click at the project => Build Targets => Create…
  • After building the target „all“ I rebuild the index via right click at the project => Index => Rebuild

Creating the debugger configuration

For configuring the debugger in Eclipse I followed in general this documentation: http://gnuarmeclipse.github.io/debug/openocd/#create-the-debugger-configuration but with the following changes:

  • Debugger tab:
    • Options: -s /home/esp32/openocd-esp32/tcl -f /home/esp32/openocd-esp32/esp32.cfg
    • Executable: xtensa-esp32-elf-gdb
  • Startup tab:
    • Uncheck „Enable ARM semihosting“
    • Uncheck „Load executable“
    • Set breakpoint at: „app_main“; not „main“

With this configuration you will always break at the first available instruction.

Connecting the hardware

Connecting the FTDI232 USB to Serial adapter to the ESP-WROOM-32

For flashing via this adapter I’ve connected the adapter to the ESP-WROOM-32 board. I found the ESP-WROOM-32 pinout here: https://espressif.com/sites/default/files/documentation/esp-wroom-32_datasheet_en.pdf

  • GND => GND
  • 3.3V => 3.3V
  • ChipPU via 1.5kΩ resistor to 3.3V and via button to GND; that’s the reset button
  • GPIO0 via button to GND; holding this button down during reset will the the UART download mode
  • GPIO4 via 330Ω resistor to the anode of a LED; cathode of the LED to GND
  • U0RXD => TX of the FTDI232 adapter
  • U0TXD => RX of the FTDI232 adapter

Connecting the Olimex ARM-USB-OCD-H to the ESP-WROOM-32

I’ve connected the following 7 pins of the 20 pin JTAG adapter to the ESP-WROOM-32 board:

  • ESP32 3.3V => JTAG Pin 1 – VREF
  • ESP32 ChipPU => JTAG Pin 3 – TTRST_N
  • ESP32 GND => JTAG Pin 4 – GND
  • ESP32 MTDI => JTAG Pin 5 – TTDI
  • ESP32 MTMS => JTAG Pin 7 – TTMS
  • ESP32 MTCK => JTAG Pin 9 – TTCK
  • ESP32 MTDO => JTAG Pin 13 – TTDO

Connect the adapters to the Linux guest and establish a serial connection with putty

Connect both adapters (serial and JTAG) to the USB interfaces of the host computer and then connect both adapters to the Linux guest via Virtual Box menu Devices => USB => FTDI FT232R USB UART and Devices => USB => Olimex OpenOCD JTAG ARM-USB-OCD-H

Then I did a serial connection with putty and the following settings:

  • Connection type: Serial
  • Serial line: /dev/ttyUSB0
  • Speed: 115200

During power on of the ESP-WROOM-32 module I hold down the button on GPIO0 pin. In putty I found the following message:

rst:0x1 (POWERON_RESET),boot:0x3 (DOWNLOAD_BOOT(UART0/UART1/SDIO_REI_REO_V2))
waiting for download

After that test I disconnected putty.

Flashing the template app with Eclipse

If the ESP-WROOM-32 is in download mode you simply build the „flash“ target in Eclipse and wait until the download of the template app into the ESP32 flash is done. After that you can reset the ESP32 and see that you LED is blinking.

Start debugging

In Eclipse you can choose now Run => Debug Configurations…, select under GDB OpenOCD Debugging the app-template configuration and click Debug. If all is working fine you LED stops blinking and you will break in ipc.c:ipc_task. That’s the fist code that’s executed after booting. Now you can press F8 and stop in app_main.

Now you can debug the template app step-by-step, inspect variables, set breakpoints and so on…

Happy debugging!

 

The Wi-Fi water level gauge 2.0 – my own Internet of Things

WaterLevelGaugeAssembledAfter my first steps with the ESP8266 I got far beyond a hello world application. I’ve replaced my old self-made water level gauge with a new self-made one powered by an ESP8266 SoC. The old one have an ultrasonic sensor that sits in my cistern. The sensor was driven by an AT89C4051 micro controller with also controls a big 4-digit 7-segment display.

The new one has also the same HC-SR04 ultrasonic sensor that sits in the cistern. The measurement of the water level is now driven by the ESP8266. But the water level is now send via my router to the internet platform Thingspeak. I’ve a channel in Thingspeak where I can record the measured values.

I’ve started my development with the esp-open-sdk build environment on top of an ubuntu 15.04 server. After I got the blink example running I’ve ported my already existing c code for measuring the water level from the AT89C4051 to the ESP8266. That was the easiest task. Next I integrated the esphttpclient from Martin d’Allens into my project. Thanks Martin for the nice piece of code.

After that I could measure and post the values to Thingspeak. But I’d a big problem. The current consumption. I would power the my Internet-of-Things device by solar energy. So I connected the two pads on the PCB to get the deep sleep feature running.

The device is now powered by a cheap solar accu pack. Most of the time the device is sleeping. I wakes up regular with Wi-Fi modem deactivated to save power. Then it measured the water level and compares the values with the last measurement. If the measured value has not changed too much then the data should not be posted to save power. But after a configurable time span the data will always posted, even if not changed.

That brings me to the next point: the configuration. At the beginning I’d all parameters hard-coded. But I would configure the device via Wi-Fi. I the device is running (not sleeping) I can press the button for at least two seconds to enter the configuration mode. The module signals the configuration mode by blinking slowly with the LED. In configuration mode the ESP8266 is in SoftAP mode with a fixed SSID and a fixed password. It has a fixed IP address and opens a fixed TCP server port. Now a smart phone can send the configuration data as JSON to this port. I use the free of charge Android app TCP client for the configuration. After the ESP8266 has received the configuration it starts with measurement and sending the data to Thingspeak.

I’ve released the source code under the MIT License on github. The binaries for the version 0.1 for the 512KB flash version only can be found also on github.

Assembling the hardware

WaterLevelGaugePcbThe schematics and the board layout are created with EAGLE. I’ve decided to make a single sided board to make it easy to manufacture the PCB at home. For the hardware you need the following parts:

  • ESP8266 – ESP-03
  • Regulator LF33CV
  • LED 3mm red
  • Capacitor 2.2µF
  • Capacitor 100nF
  • Resistor 220Ω
  • Resistor 12kΩ
  • Resistor 22kΩ
  • Micro button
  • 4-pin header
  • 3-pin header
  • 2-pin header
  • USB Solar accu pack

With the board layout file you can create the PCB by your own or order the PCB via internet. After drilling the holes I suggest the assembling in the following order:

  • Solder the two wires on top of the PCB
  • Don’t forget connecting the two pads on the PCB of the ESP-03 for the deep sleep function to work
  • Place the ESP-03 on top of the PCB and solder two opposite pins with a short connection between the bottom layer and the ESP-03 PCB
  • Solder the rest of the pins of the ESP-03
  • Solder the three resistors and the two capacitors; consider the polarity of the 2.2µF capacitor
  • Continue with the LED, the micro button and the regulator
  • Solder the header connectors at last

Place a HC-SR04 ultrasonic sensor in your cistern. Build a connection cable between the ultrasonic sensor and the 4-pin header. Be warned: Don’t plug the HC-SR04 directly into the 4-pin header. The pin assignment doesn’t match!

You need also a cable from your solar accu pack to the 2-pin header.

And for downloading the firmware to the ESP8266 you need a cable for the 3-pin header for the serial port. Most people use a USB to serial converter like the FTDI232 to connect the module to the PC.

See the pictures below to find the correct pin assignments.

WaterLevelGaugePowerHeaderWaterLevelGaugeSerialHeader

Flashing the firmware

FlashToolFor flashing the firmware into the ESP8266 I suggest the orignal tool from Espressif the manufacturer of the ESP8266 chip. You need to flash the two files from github (0x00000.bin and 0x10000.bin) and the files esp_init_data_default.bin and blank.bin from the bin directory of the Espressif SDK 1.5.2. Start the flash download tool, select the four files for download, adjust your com port and set the download speed. I suggest a baud rate of 115200. The SPI FLASH CONFIG group must not be changed. The flash will be auto detected.

Connect the water level module via the 3-pin header to your USB-to-serial converter and the converter to the PC.

For flashing the firmware the module must enter the download mode. To achieve this do the following in this order:

  1. Press the START button in the flash download tool
  2. Hold the micro button on the module down
  3. Connect the 5V power from the solar accu pack to the 2-pin header. Consider the polarity!

The download should start immediately. Maybe you need to practice this a few times because it must be done in a few seconds. After the download starts you can release the micro button.

The download lasts a few seconds. If it’s finished with success disconnect power, close the download tool and open a serial terminal like Putty or CoolTerm. Adjust the COM port, set the baud rate to 74880 and open/connect the COM port. Then power the water level gauge module on.

If all went good the LED of the module blinks slowly and the module enters the configuration mode automatically. if you find the message

Activating access point 'WaterLevelGauge' and listening at port 1253 ...

in the terminal window you can start with the configuration of your module.

Configuring the water level gauge

After flashing the four files the module goes automatically into the configuration mode because it doesn’t find a configuration. You can also enter the configuration mode manually after powering the module on. To enter the configuration mode power the module on, let the LED flash one time, press the micro button for at least to seconds and the release the micro button. If the module has entered the configuration mode the LED will blink slowly.

The configuration is done by sending a JSON data string via TCP/IP connection to the port where the module is listening. You can send the JSON data via smart phone. I use the free of charge Android app TCP client for the configuration. But it’s up to you, what way you choose to send the data. You need only a Wi-Fi enabled device that’s capable to connect via WPA2 to the Wi-Fi access point, connect to the TCP server socket and send the data string.

Simply connect the Wi-Fi of your smart phone or similar device to the access point „WaterLevelGauge“ that the module provide. For the Wi-Fi connection you need the password „IoT-Wlg!4711#“. In the TCP client app or similar tool you need to connect to the IP 192.168.4.1 port 1253.

Here is an example of the configuration to send:

{
  "SSID": "Your router SSID",
  "Password": "Your router password here",
  "CisternType": 1,
  "CisternRadius": 1000,
  "CisternLength": 2320,
  "DistanceEmpty": 1730,
  "LitersFull": 5853,
  "HostName": "WaterLevelGauge1",
  "DeepSleepPeriod": 600,
  "MinDifferenceToPost": 5,
  "MaxDataAgeToPost": 3600,
  "ThingspeakApiKey": "Your API key here",
  "LogType": 0,
  "LogHost": "-",
  "LogPort": 0
}

Adjust all the settings in the example to your needs.

  • SSID: The SSID (the display name) of the router. If the measurement should be posted to Thingspeak the module connects to this router.
  • Password: The password for the router
  • Cisterntype:
    • 1 = the cistern is a horizontal cylinder
    • 2 = the cistern is a vertical cylinder
  • CisternRadius: The radius in millimeters of the cylinder
  • CisternLength: The length of the cylinder in millimeters
  • DistanceEmpty: The distance between the HC-SR04 ultrasonic sensor and the water or the bottom of the cistern if the cistern is empty
  • LitersFull: The amount of liters in the cistern if the cistern is filled to 100%
  • HostName: This name will be set as hostname after the module has connected to the router. Maybe you will distinguish different modules by hostname
  • DeepSleepPeriod: The time span in seconds between two measurements. During this time the module consumes low current.
  • MinDifferenceToPost: If two measurements are not differ more than this amount of millimeters the data will not be posted to Thingspeak to conserve power
  • MaxDataAgeToPost: After this amount of seconds the measurement will always be posted to Thingspeak, even if the MinDifferenceToPost is not reached
  • ThingspeakApiKey: The API key of your Thingspeak channel
  • LogType: Set to zero unless you need the help of the TcpLogger feature for troubleshooting
  • LogHost: Set to „-“ unless you need the help of the TcpLogger feature for troubleshooting
  • LogPort: Set to zero unless you need the help of the TcpLogger feature for troubleshooting

Setup a Thingspeak channel

For setup a Thingspeak channel that will work with the Wi-Fi water level gauge you need first register for a free account at Thingspeak. Then you should setup a new channel with three fields. The module will post the following values into the three fields:

  • Field1: The water level in centimeters
  • Field2: The water level in liters
  • Field3: The water level in percent

Troubleshooting with the TcpLogger

If you have problems during the operation of the Wi-Fi water level gauge and you can’t connect a PC via serial connection you have the option to send all the messages that are output to the serial connection via TCP/IP to a program that logs the messages. I’ve written a short C# console program that receives the log and writes it to a file.

using System;
using System.IO;
using System.Net.Sockets;
using System.Text;

namespace TcpLogger
{
	class Program
	{
		static void Main(string[] args)
		{
			StreamWriter writer = File.CreateText("TcpLogger-" + DateTime.Now.ToString("yyyyMMddHHmmss"));
			try
			{
				TcpListener server = new TcpListener(1254);
				server.Start();
				while (true)
				{
					TcpClient client = server.AcceptTcpClient();
					// Read the data stream from the client. 
					byte[] bytes = new byte[256];
					NetworkStream stream = client.GetStream();
					bool writeTimeStamp = true;
					while (true)
					{
						int length = stream.Read(bytes, 0, bytes.Length);
						if (length == 0)
						{
							break;
						}
						if (writeTimeStamp)
						{
							writer.WriteLine();
							writer.WriteLine(DateTime.Now.ToString("yyyyMMddHHmmss"));
							writeTimeStamp = false;
						}
						writer.Write(Encoding.ASCII.GetString(bytes, 0, length));
					}
					writer.Flush();
				}
			}
			catch (Exception exception)
			{
				writer.WriteLine(exception.Message);
				writer.WriteLine(exception.StackTrace);
			}
		}
	}
}

To configure the module to send the log to the TcpLogger adjust the configuration and set the parameters accordingly:

  • LogType
    • 1 = Send log via unencrypted TCP/IP connection
    • 2 = Send log via encrypted TLS TCP/IP connection. Maybe this feature is not currently working!
  • LogHost: Set to an IP or hostname where the TcpLogger program is listening
  • LogPort: Set to the port where the TcpLogger program is listening

ESP8266 – First steps

During looking for a new solution for measuring the water level in my cistern I stumble about the ESP8266 chip. This chip provides Wi-Fi access at a very low-cost. At eBay I found the ESP-03 model with a price below 4 Euro. But now I go back to the roots of my findings.

The ESP8266 is essentially a chip with 32-bit CPU. The specification left many questions open. It’s more like a adverting flyer. But there are two  communities around this chip: The first forum is maintained by the chip vendor espressif systems. The other forum is driven by the community who  build some cool stuff with the ESP8266.

If you would buy a module you have currently (June 2015) the choise to buy one of the 13 versions. For getting started with the chip I’d chosen the ESP-03 which a bought from a local dealer via eBay. I wouldn’t wait weeks for the first chip if I choose a chinese dealer. I’d chosen the version ESP-03 because I need many GIO pins and would have the Wi-Fi antenna included. I was thinking about version ESP-07 also with many GIO pins and antenna but I was not sure if I can get a module without the DOA issue. So I thought its saver to buy a ESP-03 module.

Regardless the version you choose you get a PCB with the ESP8266 soldered onto the PCB. Also on the PCB is a flash memory chip. But I didn’t find clear information how big the flash memory would be. On a photo found via google I found that for ESP-03 the 25Q40BT chip which has 512KB flash memory is used. Beside these two chips the PCB contains the crystal and some other SMD parts.

For working with the ESP8266 Wi-Fi module I see two general options. You can connect a microcontroller to the UART and communicate via AT commands with the module. But you can also write your own application that runs in the ESP8266 module. There is a SDK available which includes also example code. If you wouldn’t like to set up the build tool chain by your own, you can use a lubuntu virtual machine image where all the stuff is pre-installed.

For now I’m waiting for the arrival of my ordered module and I try to get the example code compiled that’s included in the SDK. After starting the lubuntu VM I found that my german keyboard layout is not supported by default. In the VM are only the english components installed. So I updated the lubuntu to the latest packages and installed the german input language. After that I could compile the example code. But other examples from github are not working. Maybe there is a difference between the espressif IoT SDK and the esp-open-sdk.

The morse code screwdriver

At my birthday I got a „special“ present from my friends. A screwdriver on which are two 1.5 volt batteries are taped on it. That symbolic „electrical screwdriver“ was decorated with some 5 euro banknotes from which I should buy an electrical screwdriver.
After thinking about the screwdriver with the two batteries an idea come into my mind. I can tape a led and a Atmel AVR microcontroller on this present and the led can blink a morse code message. I locked at the morse code wikipage and found how the morse code alphabet is coded. So I soldered an ATTiny85 with a 51 Ohm resistor and a red led together an wrote the following program.

/*
 * MorseCode.c
 *
 * Created: 17.12.2014
 *  Author: Matthias Jentsch
 *
 * The program let a led blink on PB3 to form a morse code. The fixed message will be send.
 */ 

#include <avr/io.h>
#define F_CPU 1000000UL     /* Internal 8MHz clock divided by 8 = 1MHz clock */
#ifndef DEBUG
#include <util/delay.h>
#endif
#include <avr/pgmspace.h> 
#include <avr/eeprom.h>

// The sending speed factor; above 1 means send slower; below 1 means send faster
#define FACTOR 1.5
// The SHORT signal period
#define SHORT 100 * FACTOR
// The LONG signal period
#define LONG 300 * FACTOR
// The period of silence between two SHORT/LONG signals
#define SYMBOL_SPACE 100 * FACTOR
// The period of silence between two characters
#define CHARACTER_SPACE 300 * FACTOR
// The period of silence between two words
#define WORD_SPACE 700 * FACTOR
// The period of silence after the complete sentence/message was sent
#define SENTENCE_SPACE 3000 * FACTOR

// That's the messsage that shoul be send
const char PROGMEM _message[] =
"First line of the message " \
"Next line of the message " \
"Last line of the message";

// The morse code alphabet
// The first byte in the line is the count of symbols that are needed to form a character
// The second byte forms the short/long symbols for each character
// Example: 0x04, 0x09 describes the symbols "long short short long" for the character 'X'
const uint8_t _symbols[40][2] =
{
	{ 0x02, 0x02 },	// A
	{ 0x04, 0x01 },	// B
	{ 0x04, 0x05 },	// C
	{ 0x03, 0x01 },	// D
	{ 0x01, 0x00 },	// E
	{ 0x04, 0x04 },	// F
	{ 0x03, 0x03 },	// G
	{ 0x04, 0x00 },	// H
	{ 0x02, 0x00 },	// I
	{ 0x04, 0x0E },	// J
	{ 0x03, 0x05 },	// K
	{ 0x04, 0x02 },	// L
	{ 0x02, 0x03 },	// M
	{ 0x02, 0x01 },	// N
	{ 0x03, 0x07 },	// O
	{ 0x04, 0x06 },	// P
	{ 0x04, 0x0B },	// Q
	{ 0x03, 0x02 },	// R
	{ 0x03, 0x00 },	// S
	{ 0x01, 0x01 },	// T
	{ 0x03, 0x04 },	// U
	{ 0x04, 0x08 },	// V
	{ 0x03, 0x06 },	// W
	{ 0x04, 0x09 },	// X
	{ 0x04, 0x0D },	// Y
	{ 0x04, 0x03 },	// Z
	{ 0x05, 0x1F },	// 0
	{ 0x05, 0x1E },	// 1
	{ 0x05, 0x1C },	// 2
	{ 0x05, 0x18 },	// 3
	{ 0x05, 0x10 },	// 4
	{ 0x05, 0x00 },	// 5
	{ 0x05, 0x01 },	// 6
	{ 0x05, 0x03 },	// 7
	{ 0x05, 0x07 },	// 8
	{ 0x05, 0x0F }	// 9
};

// Send one SHORT signal
void BlinkShort()
{
	PORTB &= ~(1 << PB3);
	#ifndef DEBUG
	_delay_ms(SHORT);
	#endif
	PORTB |= (1 << PB3);
}

// Send one LONG signal
void BlinkLong()
{
	PORTB &= ~(1 << PB3);
	#ifndef DEBUG
	_delay_ms(LONG);
	#endif
	PORTB |= (1 << PB3);
}

// Wait the SYMBOL_SPACE period
void WaitSymbolSpace()
{
	#ifndef DEBUG
	_delay_ms(SYMBOL_SPACE);
	#endif
}

// Wait the CHARACTER_SPACE period
void WaitCharacterSpace()
{
	#ifndef DEBUG
	_delay_ms(CHARACTER_SPACE);
	#endif
}

// Wait the WORD_SPACE period
void WaitWordSpace()
{
	#ifndef DEBUG
	_delay_ms(WORD_SPACE);
	#endif
}

// Wait the SENTENCE_SPACE period
void WaitSentenceSpace()
{
	#ifndef DEBUG
	_delay_ms(SENTENCE_SPACE);
	#endif
}

// Output one character code; pIndex: Index in _symbols array
void OutputCode(uint8_t pIndex)
{
	uint8_t symbolCount = _symbols[pIndex][0];
	uint8_t symbols = _symbols[pIndex][1];
	for (uint8_t i = 0; i < symbolCount; i++)
	{
		if ((symbols & (1 << i)) > 0)
		{
			BlinkLong();
		}
		else
		{
			BlinkShort();
		}
		if (i != symbolCount - 1)
		{
			WaitSymbolSpace();
		}
	}
	WaitCharacterSpace();
}

// Output one character; pCharacter: The character to send
void OutputCharacter(char pCharacter)
{
	// Upper case characters
	if (pCharacter >= 'A' && pCharacter <= 'Z')
	{
		OutputCode(pCharacter - 'A');
	}
	// Lower case characters
	else if (pCharacter >= 'a' && pCharacter <= 'z')
	{
		OutputCode(pCharacter - 'a');
	}
	// Decimals
	else if (pCharacter >= '0' && pCharacter <= '9')
	{
		OutputCode(26 + pCharacter - '0');
	}
	// The german Ä
	else if (pCharacter == 'Ä' || pCharacter == 'ä')
	{
		// will be splitted into AE
		OutputCharacter('A');
		OutputCharacter('E');
	}
	// The german Ö
	else if (pCharacter == 'Ö' || pCharacter == 'ö')
	{
		// will be splitted into OE
		OutputCharacter('O');
		OutputCharacter('E');
	}
	// The german Ü
	else if (pCharacter == 'Ü' || pCharacter == 'ü')
	{
		// will be splitted into UE
		OutputCharacter('U');
		OutputCharacter('E');
	}
	// The german ß
	else if (pCharacter == 'ß')
	{
		// will be splitted into SS
		OutputCharacter('S');
		OutputCharacter('S');
	}
	// The space between two words
	else if (pCharacter == ' ')
	{
		WaitWordSpace();
	}
}

// The entry point
int main( void )
{
    DDRB = ( 1 << PB3 );        // set PB3 at PORTB as output
	PORTB |= (1 << PB3);

	// read the save point
	uint16_t savePoint = eeprom_read_word((const uint16_t*)0);
	if (savePoint == 0xFFFF)
	{
		savePoint = 0;
	}
	
    while(1)
	{
		// output header (training for the receiving app)
		OutputCharacter('x');
		OutputCharacter('x');
		OutputCharacter('x');
		OutputCharacter(' ');
		// output the message
		int max = sizeof(_message) - savePoint;
		for (uint16_t i = savePoint; i < max; i++)
		{
			uint8_t nextCharacter = pgm_read_byte(_message + i);
			OutputCharacter(nextCharacter);
			// a complete word sent? (current character was a space)
			if (nextCharacter == ' ')
			{
				// write the save point to the last word
				eeprom_write_word((uint16_t*)0, savePoint);
				// the next save point is the start of the next word (current space + 1)
				savePoint = i + 1;
			}
		}
		// reset the save point
		eeprom_write_word((uint16_t*)0, 0);
		WaitSentenceSpace();
    }
    return 0;
}

In the _message variable I programmed a special greetings message for my friend. At christmas I gave this morse code blinkig led pen as a present back to my friend. They were really amazed about the present.

ThreadPool.RegisterWaitForSingleObject with CancellationTokenSource

Some days ago I was inspired by the blog entry Periodic Execution in .NET. I found it great to have a mechanism to periodically do some work but have also the possibility to do some work when my method gets alerted.

I was thinking of a mechanism where a method (like DoSomeWork) get’s called after a period of time. But I don’t want it call after each x seconds. I want to call it once, do the work and when all lights are green I want to schedule the method call again. So in general any sort of timer would not be the best option. I would give the ThreadPool.RegisterWaitForSingleObject a try. In my scenario I’ve multiple worker. So each worker registers it own DoSomeWork method. But I would also shutdown all workers graecefully from an external method. I decided to use a CancellationTokenSource for shutdown all workers with one signal. I use the CancellationTokenSource.Token.WaitHandle as the waitobject for the ThreadPool.RegisterWaitForSingleObject call. And now when the external method decided to shutdown all workers it must only call the Cancel method on the CancellationTokenSource to signal all workers that they should finish there work and should not schedule another call to there DoSomeWork method.