Bluetooth Communication with Palm OS -
Collecting data to display (Part 1/2)

06.06.2020

Since a few years I am collection temperature and humidity data from different rooms e.g. the basement or bedroom. Especially for the bedroom I wanted to make sure, that the air do not get too dry or humid. So I got some ESP8266 / NodeMCU and DHT22, later I did it with BME280 and SHT3x. In this article-series I want to describe how I collect the data, store them on a home-server and display them on Bluetooth-enabled Palm OS device.

The article is splitted in two parts: this first part is about building the „collector-unit“ with an ESP8266 / DHT22 and storing the data on a server. The second part is about the Palm OS Bluetooth application and how I got the data displayed via an ESP32 over Bluetooth on a PalmOS device.

Needed parts

These parts are required for collection the data and store them on a server:

Of course it would work also with other parts. I only listed them, because these are the parts I used.

Setting up an ESP8266

At this point, I will skip the part of explaining how to set up ESP8266 with the Arduino IDE. There are already good tutorials, which explain this very well, like this one: https://randomnerdtutorials.com/how-to-install-esp8266-board-arduino-ide/

In this section I want to explain how to wire up the ESP8266 / NodeMCU with a DHT22 and getting some data out of it.

The wiring

The wiring is straightforward first pin from left of the DHT22 needs to be connected to 3.3V, second pin to D2 and a 4.7kOhm resistor to 3.3V like shown in the picture. The third pin is left out and the last bin (the right one) must be connected to ground. That’s it. If you got an DHT22 already on a PCB with a resistor on it and three pins: Connect “VCC” to 3.3V, “DAT” to D2 and “GND” ground.

The code

A ESP8266 is not able to get data from a DHT22 by default or at least to make things easier it is recommended to install the “DHT”-library. Just click in the Arduino IDE on “Tools” => “Manage Libraries” and search for "DHT sensor library" by Adafruit. It turned out, that Version 1.2.3 works best, with newer versions, it was not possible to get useful values from the DHT22.

// Load needed libraries
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <DHT.h> // Version 1.2.3 works fine


// Tell the ESP, where it can find the DHT22
#define DHTPIN 4     // D2 = GPIO 4
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);

// Wifi credentials
const char* ssid     = "MySSID";
const char* password = "myPassword";

// Default port 80 for our webserver
ESP8266WebServer server(80);

void setup() {

  // For output on the serial monitor
  Serial.begin(9600);
  
  // Connect to Wifi
  startWIFI();

  // Register availables paths on the server and a default one.
  server.on("/", handle_DefaultConnect);
  server.onNotFound(handle_NotFound);

  // Start the server
  server.begin();
}

void loop(){

  // Handle client's requests
  server.handleClient();
  
  //connect to wifi if it is not connected
  if (WiFi.status() != WL_CONNECTED) {
      delay(1);
      startWIFI();
  }
}

// Function to send the current humidity and temperature to the client
void handle_DefaultConnect(){

  float h = dht.readHumidity();
  float t = dht.readTemperature(); // Default: Celsius
  
  // Check if any reads failed and try again
  while(isnan(t)){
    delay(200);
    t = dht.readTemperature();
  };
  
  while(isnan(h)){
    delay(200);
    h = dht.readHumidity();
  }
  
  String output = "";
  output += h;
  output += ";";
  output += t;
  output += ";";

  Serial.print("Sending: ");
  Serial.println(output);

  server.send(200, "text/html",output);   
}

// Function to send the current humidity and temperature to the client
void handle_NotFound(){
  
  server.send(404, "text/plain", "404 - page not found");
  
}

// Function to connect to WiFi
void startWIFI() {
  
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
      Serial.print(".");
      delay(500);
  }
  Serial.println(".");
  
  // Print local IP address
  Serial.println("WiFi connected.");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  delay(300);
}

First we need to include the libraries for Wifi, the web server and for the DHT22. In line 13 and 14, the credentials for the own Wifi are set. After instantiate the WebServer with the default port 80 (line 17), the setup-function is defined, in which the serial connection for debugging-output gets established, the wifi gets connected and the web server registers the paths we want to call later. At the end, the web server gets started (line 32).

In the loop-function, we need to tell the web server, that it must handle incoming client requests (line 38) and reconnection to the Wifi if the ESP8266 gets disconnected.

The handle_DefaultConnect-fuction simply tries to read the temperature and humidity form the DHT22 as long as the result isn’t a float-value (line 53 – 62). In the next step, the output string gets formatted (it is always like “humidity,temperature,”). We send the string to our serial monitor (line 73,74) and finally via the web server to the client (line 76).

If the client sends an unknown request, it just gets the information “404 - page not found” (line 82).

The function “startWIFI” (line 87) is not very complicated. In line 91 we try to connect to the Wifi and in line 93, we wait, until it is connected. In line 102, we are printing the current IP address - that is all.

Setting up the server

The server needs, besides from an OS (I would recommend Raspbian or Ubuntu Server Edition), following software stack:

Nginx is used later to provide the data, we want to display on the Palm PDA. PHP7 is used to fetch the data via a cron-job periodically and storing them in simple text-files. So we do not need a heavy MySQL-Server at this point. Also moving the project to another sever is a bit easier, because you only need to copy the files to the other server, instead of exporting and importing them in to a database. (I admit it's not the greatest time saver.)

Digital Ocean provides a great tutorial, that explains how to install Nginx, PHP and more on Ubuntu: How To Install Linux, Nginx, MySQL, PHP (LEMP stack) on Ubuntu 20.04

For Raspbian you can follow these instructions on raspberrypi.org: https://www.raspberrypi.org/documentation/remote-access/web-server/nginx.md

Normally the content you want to be delivered by the webserver (in this case: Nginx) is located under “/var/www/html”. So, this is where these need to be:

The config.php

The config.php only contains a static array with IP-addresses as key and the room names as values:

<?php
	
class config
{

    static $config = [
        "192.168.1.50" => "Basement",
        "192.168.1.51" => "Bedroom",
    ];

    CONST WORKINGDIR = "data";

}

The advantage of this config.php is that we can easily integrate it with the other PHP scripts and at the same time have a central place where we can make changes.

The dataGrabber.php

The dataGrabber.php has the task to grab/fetch the data from the ESP8266 and store it in a text-file. It is not very complicated and can look like this:

<?php

require_once ("config.php");

class dataGrabber
{

    /**
     * Main-function to start this program
     */
    public function run(){

        $config = Config::$config;

        foreach ($config as $ip => $name){
            $rawData = $this->fetchData($ip);
	    if(!isset($rawData)){
		throw new Exception("faulty data from temp_hum_sensor!");
   	    }
            $processedData = $this->processData($rawData);
            $this->writeToFile($processedData, $name);
        }

    }

    /**
     * Receive data from HTTP-Request
     *
     * @param String $ip
     * @return array|null
     */
    protected function fetchData(String $ip){

        //$data = "83.50;10.00;";
        $data = file_get_contents("http://".$ip);
        if(preg_match('/^(\d{1,3}\.\d{1,3}\;){2}/', $data)){
            return $data;
        }

        return null;
    }

    /**
     * Appends additional data
     *
     * @param $data
     * @return String|null
     */
    protected function processData(String $data){

        if(isset($data)){
            $items = explode(';',$data);
            array_pop($items);
            $time = date("Y-m-d H:i:s", time());
            return $time.';'.implode(';',$items);
        }
        return null;
    }

    /**
     * Write processed data to a certain file
     *
     * @param String $data
     * @param String $sensorname
     */
    protected function writeToFile(String $data, $sensorname){

        if(isset($data)){

            $fullDirPath = __DIR__."/".Config::WORKINGDIR."/";
            $fullFilePath = $fullDirPath.$sensorname.".txt";

            if(!is_dir($fullDirPath)){
                mkdir($fullDirPath, 0755, true);
            }

            if(!is_file($fullFilePath)){
                touch($fullFilePath);
            }

            file_put_contents($fullFilePath, $data, LOCK_EX);
        }
    }
}

$dataGrabber = new dataGrabber();
$dataGrabber->run();

function run()

This is the entry-point of the script and gets called, when we call the script via “http:///dataGrapper.php”. Because in the last two lines, we instantiate the class and calling the run-function:

	$dataGrabber = new dataGrabber();
	$dataGrabber->run();
	
The run-function itself simply calls the following functions for each configured sensor in the config.php:

function fetchData(String $ip, String $name)

This function simply downloads the data from an ESP8266 by a http-request. The data gets verified and returned by the function. If the data is not valid, it just returns null.

function processData($data)

This function appends the time-String, so we known when the temp/hum-data was recorded and returns the unformatted data, which we want to display on a Palm PDA.

function writeToFile($data)

This function stores the fetched data into a text-file, so we do not have to wait until the ESP8266 read the data from the sensor (DHT22).

The dataProvider.php

<?php
	
require_once ("config.php");

class dataProvider
{

    public function provideData(){

        $data = '-1,-1,'.date("Y-m-d H:i:s", time());
        $sensor = $_GET['sensor'] ?: '';

        if(in_array($sensor, Config::$config)){
            $filename = Config::WORKINGDIR.'/'.$sensor.'.txt';

            if(file_exists($filename)){
                $data = file_get_contents($filename);
            }
        }

        echo $data;
    }
}

$dataProvider = new dataProvider();
$dataProvider->provideData();

The dataProvider only contains one function: “provideData()”. This function expects the sensor-name as GET-Parameter, checks if this name exists in the config and if so, it will read and output the stored value in the text-file, which was written by the dataGrabber.php. If the sensor-name does not exist or the text-file is not present, the function returns a default-value: "-1,-1,<currentDateTime>". If we see this later on the Palm PDA, we know, that something went wrong. Of course, we could implement an error reporting at this point, but I think it is a bit too much for such a simple task. At least we do not deal with highly sensitive data.

Testing the setup

When the files (config.php, dataGrabber.php and dataProvider.php) are saved in the document-root of the web-sever (default path: “/var/www/html”), it should be possible to call them via a web-browser:

The call of “http:///config.php” should provide no output. We could now configure a .htaccess to deny the call of “config.php”. This would make sense for a public-server. (In the case, that you are using a public server, it would be good idea to protect the complete application via a basic-authentication.) Another possibility would be to store the config.php outside of the documemnt-root.

Final thoughts

Now, we have all needed parts for the next step: a ESP8266 (or more) to read the humidity and temperature, a dataGrabber, which collects the wanted data. Also the dataProvider, which simply outputs the collected data via a HTTP-request. In order to collect the data, we need to create a cronjob, which periodically executes the dataGrabber.php. With the command “sudo crontab -e” on the linux server, this line can be added:

	*/30 * * * * php /var/www/html/dataGrabber.php

This executes the php-command very 30 minutes. Of course, it would be possible to fetch the information more often, by changing the 30 to something lower. Save the Cron-file and the system is ready.

The system is easy to extend. Just flash another ESP8266, add the IP to config.php and that’s it.

In part two we will flash an ESP32, which has bluetooth and write a small Palm OS Application, which requests data from the ESP32 and displays it: Bluetooth Communication with Palm OS - The Palm OS Application (Part 2/2)


Misc