27. November 2016

Controlling relay connected to ESP8266 via MQTT from Android

After successful implementation of MQTT into Lamp Relay with ESP8266 there was one remaining challenge. Communication of Android Lamp App with ESP8266 via MQTT. It was little bit harder than I expected, because examples were not very clear.

Sending commands to MQTT broker was relatively easy. There was just a catch. It is necessary to declare the MQTT service in AndroidManifest.xml:

<service android:name="org.eclipse.paho.android.service.MqttService">
</service>

Thanks to tutorial from HiveMQ I was able to send messages to MQTT broker.

The problem was the opposite direction. How to receive messages?

Well, there is support in the same MQTT library for topic subscriptions. The issue is that incoming message is being processed on background thread and it’s not possible to update UI. It took me some time to realize the source of problem. I was hunting ghosts. I thought that there is some problem in ImageButton or TobbleButton.

Correct solution was to move communication to Android service class. Then bind the service so UI can send commands to the service and the last part was to add broadcast receiver to pass message from service to UI.

Here is small schema which describes delivery of message from broker to app.

mqtt-broker-to-android

It was necessary to make also small update in ESP8266 in order to tell Mosquitto to persist the state of a lamp (the last bool parameter):

mqttClient.publish(topicName("relay"), "off", true);

The result is nice. I can turn on the lamp via web interface and all connected Android apps are updated and user can see the state of the lamp.

lamp-app-mqtt

The code for Android app is available at Github – LampApp. Previous code with REST API was moved to branch v0.1. The code for ESP8266 module is also at Github – LampESP. Feel free to customize the app. You can find instructions in README.md.

I see the next challenge for LampApp: display lamp control widget at lock screen. Maybe I’ll be more successful than the last time.

22. November 2016

IoT and waiting for the sun

It took me some time to implement all features to ESP8266 controlling relay of my lamp.

smart-home-lidl-salt-lamp

Thanks to examples from PlatformIO I was able to use all neat features in one code:

You can find complete code at github repo LampESP. Just replace HOST_NAME_HERE and ROOM_NAME_HERE with your MQTT server and your room name.

Once you deploy code to ESP8266 you can use Over-the-air update (OTA). Just change the value in platformio.ini and set upload_port to IP address of your device.

The implementation allows control via simple GET API by accessing the web of device or LampAndroidApp.

As I wrote few weeks ago, my goal was to add MQTT. I’ve installed Mosquitto and connected the device to the server.

It is possible to watch states of device is possible via MQTT client:

mosquitto_sub -v -h HOST_NAME_HERE -t '/#'

Here is example how to change states of relay:

mosquitto_pub -h HOST_NAME_HERE -t '/home/ROOM_NAME_HERE/command' -m 'on'
mosquitto_pub -h HOST_NAME_HERE -t '/home/ROOM_NAME_HERE/command' -m 'off'

I had an idea about next cool feature: turn on/off lamp based on sunset and sunrise. I found very simple and elegant solution that I would like to share with you.

Download Sunwait and compile it. The program just starts and waits until the time of sunrise or sunset based on geographic coordinates. You can also specify offset (how long before or after event) should program end.

Now you can put the sunwait into your local cron table and chain it with command that should be executed after sunrise/sunset occurs.

Here is example how to configure cron to turn on the lamp 15 minutes before sunset and turn it off 15 minutes after sunrise.

crontab -e
10 15 * * * /usr/bin/sunwait sun down -0:15:00 49.230738N, 16.576802E; /usr/bin/mosquitto_pub -h HOST_NAME_HERE -t /home/ROOM_NAME_HERE/command -m "on"
4 15 * * * /usr/bin/sunwait sun up +0:15:00 49.230738N, 16.576802E; /usr/bin/mosquitto_pub -h HOST_NAME_HERE -t /home/ROOM_NAME_HERE/command -m "off"

The lamp is now working. The only problem is the Android app. I thought that adding MQTT client to Android app will be as easy as adding it to ESP8266, but it’s far from truth. There are still some challenges ;-)

19. November 2016

Programming ESP8266 in Go language? Not yet. November 2016

I was wondering if it would be possible to use Go language to write code for ESP8266.

The very first search result seemed to be the right choice – Gobot.

I read the instructions and typed:

go get -d -u github.com/hybridgroup/gobot/...

After a while Go asked for password to git.eclipse.org. I had no account there so I registered the account. And again:

go get -d -u github.com/hybridgroup/gobot/...

New error:

package git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git: cannot download, git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang uses insecure protocol

Go was trying to get repository from Eclipse using unsecure HTTP. Luckily there was feature request to add switch for accessing insecure repositories by Go get – issue #9637. The solution was just to add “-insecure” parameter:

go get -insecure -d -u github.com/hybridgroup/gobot/...

New error:

# cd .; git clone git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang .../projects/gobot/src/git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git
fatal: repository 'git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang' does not exist
package git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git: exit status 128

Yay, that’s cute. I’ve searched through issue tracking system in hybridgroup/gobot and the answer was issue #339.

The workaround is to change line 4 in github.com/hybridgroup/gobot/platforms/mqtt/mqtt_adaptor.go to:

"github.com/eclipse/paho.mqtt.golang"

Ron Evans recommended just to switch to dev branch.

Thanks to Go integration with Git it was very easy:

cd src/github.com/hybridgroup/gobot
git checkout dev
go get -d -u github.com/hybridgroup/gobot/...

Only after this sequence of steps from magic dance Gobot was ready to build application on my computer.

My next step was attempt to build MQTT ping. Easy? Just grab a code from gobot.io documentation, paste it to file and run it.

go run mqtt-ping.go

Another funny error:

# command-line-arguments
./mqtt-ping.go:11: undefined: gobot.NewGobot
./mqtt-ping.go:13: undefined: "github.com/hybridgroup/gobot/platforms/mqtt".NewMqttAdaptor

The documentation on the web is from master. Ou, but I switched to dev branch. What a simple mistake. I grabbed the code from github repo everything was ok.

cp  ./github.com/hybridgroup/gobot/examples/mqtt_ping.go .
go run mqtt_ping.go

The application was able to connect to Mosquitto Broker and send messages.

The last step would be to deploy it to ESP8266. Unfortunately this is not possible yet. The platform is not supported by Gobot and there is issue with plaftom-request #301.

Ok, never mind. It was interesting experiment and I can see potential of Go for programming IoT.

What will be my next experiment? Developing the code for ESP8266 using PlatformIO.

6. November 2016

Controlling lamp with ESP8266 via WiFi by Android Widget

Two weeks ago I made research about available Smart Home Power Sockets. The result was not very positive.

Week ago I discovered talk about ES8266 which seemed to be the right solution.

And what about this week?

I happy to report success. I am able to control the lamp from mobile phone. :-)

This week I bought WeMos D1 Mini WiFi ESP8266 and relay. Together with friend we soldered few pins and wrote some code. The solution was working immediately after we plugged it into a wall.

Here is the result:

20161105_esp8266-with-relay

Code for Arduino is derrived from esp8266learning.com:

//This example will set up a static IP - in this case 192.168.1.50
 
#include <ESP8266WiFi.h>

const int relayPin = D1;
const int chipSelect = D8;
const char* ssid = "MYSSID";
const char* password = "SECRET";
 
WiFiServer server(80);
IPAddress ip(192, 168, 1, 50); // where xx is the desired IP Address
IPAddress gateway(192, 168, 1, 1); // set gateway to match your network
 
void setup() {
  Serial.begin(115200);
  delay(10);
 
  pinMode(relayPin, OUTPUT);
 
  Serial.print(F("Setting static ip to : "));
  Serial.println(ip);
 
  // Connect to WiFi network
  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  IPAddress subnet(255, 255, 255, 0); // set subnet mask to match your network
  WiFi.config(ip, gateway, subnet); 
  WiFi.begin(ssid, password);
 
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
 
  // Start the server
  server.begin();
  Serial.println("Server started");
 
  // Print the IP address
  Serial.print("Use this URL : ");
  Serial.print("http://");
  Serial.print(WiFi.localIP());
  Serial.println("/");
}
 
void loop() {
  // Check if a client has connected
  WiFiClient client = server.available();
  if (!client) {
    return;
  }
 
  // Wait until the client sends some data
  Serial.println("new client");
  while(!client.available()){
    delay(1);
  }
 
  // Read the first line of the request
  String request = client.readStringUntil('\r');
  Serial.println(request);
  client.flush();
 
  // Match the request
 
  int value = LOW;
  if (request.indexOf("/relay=on") != -1) {
    digitalWrite(relayPin, HIGH);
    value = HIGH;
  } 
  if (request.indexOf("/relay=off") != -1){
    digitalWrite(relayPin, LOW);
    value = LOW;
  }
 
  // Return the response
  client.println("HTTP/1.1 200 OK");
  client.println("Content-Type: text/html");
  client.println(""); //  do not forget this one
  client.println("<!DOCTYPE HTML>");
  client.println("<html>");
 
  client.print("Led pin is now: ");
 
  if(value == HIGH) {
    client.print("On");  
  } else {
    client.print("Off");
  }
  client.println("<br><br>");
  client.println("<a href=\"/relay=on\">Relay ON</a><br>");
  client.println("Click <a href=\"/relay=off\">Relay OFF</a>");
  client.println("</html>");
 
  delay(1);
  Serial.println("Client disconnected");
  Serial.println("");
}

That was the easy part. I was able to control relay directly from mobile phone via web browser which was not very convenient. The widget would serve better.

Writing a widget for Android was real challenge. There is very little documentation about it and even Android Studio does not contain template for writing widget. I spent several hours learning how widget works. It is very different from common application.

The very first gotcha that costed me more than a hour was very common problem with builds:

Error:(3) Error retrieving parent for item: No resource found that matches the given name 'android:TextAppearance.Material.Widget.Button.Inverse'.

Yes, it is very clear where is the problem. Or not? :)

It is necessary to fix build.gradle and set proper version of compile dependency. In my case I was targeting API 22, but appcompat was set to 23:

dependencies {
    compile 'com.android.support:appcompat-v7:22.1.1'
}

The real fun begins with the widget code. I cobbled together several chunks of code. Primary source was Obaro’s SimpleAndroidWidget and android-async-http.

Here is the very crude code for Android:

package com.sample.foo.simplewidget;

import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.widget.RemoteViews;

import com.loopj.android.http.AsyncHttpClient;
import com.loopj.android.http.AsyncHttpResponseHandler;

import cz.msebera.android.httpclient.Header;

public class SimpleWidgetProvider extends AppWidgetProvider {

    private void getHttpRequest(String state) {
        AsyncHttpClient asyncClient = new AsyncHttpClient();
        asyncClient.get("http://192.168.1.50/relay=" + state, new AsyncHttpResponseHandler() {
            @Override
            public void onStart() {
            }

            @Override
            public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {

            }

            @Override
            public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {

            }

        });
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        final int count = appWidgetIds.length;

        for (int i = 0; i < count; i++) {
            int widgetId = appWidgetIds[i];
            String value = "off";
            SharedPreferences prefs = context.getSharedPreferences("LampApp", 0);
            boolean isRelayEnabled = prefs.getBoolean("relayState", false);
            isRelayEnabled = !isRelayEnabled;
            SharedPreferences.Editor editor = prefs.edit();
            editor.putBoolean("relayState", isRelayEnabled);
            editor.commit();

            if (isRelayEnabled) {
                value = "on";
            }
            getHttpRequest(value);


            RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
                    R.layout.simple_widget);
            remoteViews.setTextViewText(R.id.textView, value.toUpperCase());

            Intent intent = new Intent(context, SimpleWidgetProvider.class);
            intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);

            PendingIntent pendingIntent = PendingIntent.getBroadcast(context,
                    0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
            remoteViews.setOnClickPendingIntent(R.id.actionButton, pendingIntent);

            appWidgetManager.updateAppWidget(widgetId, remoteViews);
        }
    }


}

That’s not all. When you want to create widget you need to define also special handling in AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.sample.foo.simplewidget">

    <uses-permission android:name="android.permission.INTERNET" />

    <application android:allowBackup="true" android:label="@string/app_name"
        android:icon="@mipmap/ic_launcher" android:theme="@style/AppTheme">

        <receiver android:name="SimpleWidgetProvider" >
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>
            <meta-data android:name="android.appwidget.provider"
                android:resource="@xml/simple_widget_info" />
        </receiver>

    </application>

</manifest>

Even that is not enough. You’ll need several small files in res directory including graphics. I won’t describe them here. You can download it from GitHub repo georgik/LampAndroidWidget.

The result?

widget-android

It works perfectly on Samsung Galaxy S5 Neo, but for some reason I was not able to display this widget on Lenovo K5. If you have any idea why Lenovo K5 has such issue, let me know.

I also discovered a bug in the code of widget. When you have more than one widget it starts turning on and off the relay several times depending on number of widgets ;-)

The solution is ok for the time being. I’m already thinking about using MQTT and Node-Red which was discussed this weekend at OpenAlt conference in Brno by guys from McGayver Bastlíři SH.