In my last entry, I showed how to use OpenHab to control a LED connected to an ESP8266 board. From my limited testing, that seemed to go well, but of course is of limited use by itself.

Now I’m working on using the ESP board as a thermometer demo with the following features:

  • Connect the ESP board to a LM75 temperature sensor IC via i2c
  • Once a minute, measure the temperature and send the value via MQTT
  • Using OpenHab, show and plot the temperature over time

Temperature Sensor

For this project I’m currently using the LM75 ic simply because I had one around the house. However, the accuracy isn’t that great at 2C, so I’ve ordered some TMP112 temperature sensing ICs as they have an accuracy of about 0.3C, which will be much better. But for now the LM75 will do for prototyping.

Of course you could use any i2c chip, or in fact any temperature sensing ic, as long as you are willing to change the code enough.

As mentioned above, the ic has an accuracy of 2C and a resolution of 0.5C. Although the accuracy isn’t great, the resolution is fine as you’re not really going to be controlling the temperature of your house by 0.1C!

I’ve left the settings to default. The ic sends the temperature via two bytes. The first byte contains the non-decimal place number with the MSB containing the sign and the second byte contains the decimal place, with the MSB being 1 for 0.5 and 0 for 0.0. Simple!

MQTT

For MQTT, I’m using the esp_mqtt software available at https://github.com/tuanpmt/esp_mqtt. Make sure you obtain the latest version (newer than 02/02/2015) as the older versions had problems with queues which seems to be fixed now.

Eclipse IDE

For development of the ESP code, I use the Windows Eclipse IDE. Details of installation can be found here http://www.esp8266.com/viewtopic.php?f=9&t=820

Once you have the IDE installed, follow the instructions on how to create new projects and install the extra files required for the MQTT example.

Hardware

As I’m using the ESP-01 module, I only have 2 GPIO pins available: GPIO-0 and GPIO-2. Below is a circuit diagram that I’m currently using. As you can see, it’s all just on breadboard for now, and there’s a switch to choose whether GPIO-0 (not the diagram below shows GPIO-2 instead, which was a mistake) is connected to ground of to SCL (which is used to put the ESP into either programming or functional mode), and a small button used for resetting the module.

I’ve left out most of the pins for the ESP board, but the pin diagram can be easily found elsewhere.

Esp_lm75 circuit_0

ESP Code

Below is the code in my user_main.c file.

#include "ets_sys.h"
#include "driver/uart.h"
#include "driver/i2c.h"
#include "osapi.h"
#include "mqtt.h"
#include "wifi.h"
#include "config.h"
#include "debug.h"
#include "gpio.h"
#include "user_interface.h"
#include "mem.h"

MQTT_Client mqttClient;

LOCAL os_timer_t blink_timer;
#define DELAY 60000
volatile uint8_t temp = 0;
// see eagle_soc.h for these definitions
#define LED_GPIO 2
#define LED_GPIO_MUX PERIPHS_IO_MUX_GPIO2_U
#define LED_GPIO_FUNC FUNC_GPIO2

void initLM75(){
	i2c_init();
}

char* itoa(int value, char* result, int base) {
		// check that the base if valid
		if (base < 2 || base > 36) { *result = ''; return result; }

		char* ptr = result, *ptr1 = result, tmp_char;
		int tmp_value;

		do {
			tmp_value = value;
			value /= base;
			*ptr++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz" [35 + (tmp_value - value * base)];
		} while ( value );

		// Apply negative sign
		if (tmp_value < 0) *ptr++ = '-';
		*ptr-- = '';
		while(ptr1 < ptr) {
			tmp_char = *ptr;
			*ptr--= *ptr1;
			*ptr1++ = tmp_char;
		}
		return result;
	}

#define ADDRESS 0b10010001
#define ADDRESS_W 0b10010000
LOCAL void ICACHE_FLASH_ATTR blink_cb(uint32_t *arg)
{
	if (1){
		// Getting temperature
		INFO("\r\nStarting I2C\r\n");
		i2c_start();
		i2c_writeByte(ADDRESS_W); //write address 0x40
		uint8_t ack = 0;
		ack = i2c_check_ack();
		if (!ack){
			INFO("\r\nNot acknowledging\r\n");
			return;
		}
		i2c_writeByte(0x00);
		ack = i2c_check_ack();
		if (!ack){
			INFO("\r\nSecond Ack failed\r\n");
			return;
		}

		i2c_start();
		i2c_writeByte(ADDRESS);
		ack = i2c_check_ack();
		if (!ack){
			INFO("Didn't get temp\r\n");
			return;
		}
		uint8_t T = i2c_readByte();
		i2c_send_ack(1);
		uint8_t T2 = i2c_readByte() & 0bx10000000;
		i2c_stop();

		if (T == 0)
			return;


		char *tempStr = "123.0";
		temp = T;
		itoa(temp, tempStr, 10);
		MQTT_Client* client = (MQTT_Client*)arg;
		if (temp < 10){
			tempStr[1] = '.';
			if (T2)
				tempStr[2] = '5';
			else
				tempStr[2] = '0';
			MQTT_Publish(client, "/LivingRoom/Temp/1", tempStr, 3, 0, 1);
		}
		else if (temp < 100){
			tempStr[2] = '.';
			if (T2)
				tempStr[3] = '5';
			else
				tempStr[3] = '0';
			MQTT_Publish(client, "/LivingRoom/Temp/1", tempStr, 4, 0, 1);
		}
		else{
			tempStr[3] = '.';
			if (T2)
				tempStr[4] = '5';
			else
				tempStr[4] = '0';
			MQTT_Publish(client, "/LivingRoom/Temp/1", tempStr, 5, 0, 1);
		}
		temp++;
		INFO("Temp: %s\r\n", tempStr);
	}
}

void wifiConnectCb(uint8_t status)
{
	if(status == STATION_GOT_IP){
		MQTT_Connect(&mqttClient);
	} else {
		MQTT_Disconnect(&mqttClient);
	}
}
void mqttConnectedCb(uint32_t *args)
{
	MQTT_Client* client = (MQTT_Client*)args;
	INFO("MQTT: Connected\r\n");
}

void mqttDisconnectedCb(uint32_t *args)
{
	MQTT_Client* client = (MQTT_Client*)args;
	INFO("MQTT: Disconnected\r\n");
}

void mqttPublishedCb(uint32_t *args)
{
	MQTT_Client* client = (MQTT_Client*)args;
	INFO("MQTT: Published\r\n");
}

void mqttDataCb(uint32_t *args, const char* topic, uint32_t topic_len, const char *data, uint32_t data_len)
{
	char *topicBuf = (char*)os_zalloc(topic_len+1),
			*dataBuf = (char*)os_zalloc(data_len+1);

	MQTT_Client* client = (MQTT_Client*)args;

	os_memcpy(topicBuf, topic, topic_len);
	topicBuf[topic_len] = 0;

	os_memcpy(dataBuf, data, data_len);
	dataBuf[data_len] = 0;

	INFO("Receive topic: %s, data: %s \r\n", topicBuf, dataBuf);

	/*if (!strcoll(topicBuf, "/LivingRoom/LED/1")){
		if (!strcoll(dataBuf, "1")){
			INFO("LED Switching On\r\n");
			GPIO_OUTPUT_SET(LED_GPIO, 1);
		}
		else{
			INFO("LED Switching Off\r\n");
			GPIO_OUTPUT_SET(LED_GPIO, 0);
		}
	}*/
	os_free(topicBuf);
	os_free(dataBuf);
}


void user_init(void)
{
	uart_init(BIT_RATE_115200, BIT_RATE_115200);
	os_delay_us(1000000);

	CFG_Load();
	//PIN_FUNC_SELECT(LED_GPIO_MUX, LED_GPIO_FUNC);

	MQTT_InitConnection(&mqttClient, sysCfg.mqtt_host, sysCfg.mqtt_port, sysCfg.security);
	//MQTT_InitConnection(&mqttClient, "192.168.11.122", 1880, 0);

	MQTT_InitClient(&mqttClient, sysCfg.device_id, sysCfg.mqtt_user, sysCfg.mqtt_pass, sysCfg.mqtt_keepalive, 1);
	//MQTT_InitClient(&mqttClient, "client_id", "user", "pass", 120, 1);

	MQTT_InitLWT(&mqttClient, "/lwt", "offline", 0, 0);
	MQTT_OnConnected(&mqttClient, mqttConnectedCb);
	MQTT_OnDisconnected(&mqttClient, mqttDisconnectedCb);
	MQTT_OnPublished(&mqttClient, mqttPublishedCb);
	MQTT_OnData(&mqttClient, mqttDataCb);

	WIFI_Connect(sysCfg.sta_ssid, sysCfg.sta_pwd, wifiConnectCb);

	INFO("\r\nSystem started ...\r\n");

	// Setting up the timer
	os_timer_disarm(&blink_timer);
	//// os_timer_setfn(ETSTimer *ptimer, ETSTimerFunc *pfunction, void *parg)
	os_timer_setfn(&blink_timer, (os_timer_func_t *)blink_cb, &mqttClient);
	//// void os_timer_arm(ETSTimer *ptimer,uint32_t milliseconds, bool repeat_flag)
	os_timer_arm(&blink_timer, DELAY, 1);

	initLM75();
}

The code is very similar to the example MQTT program, with a few additions:

  • A 1 minute interrupt timer is setup;
  • During the timer interrupt, the temperature is obtained from the LM75 and sent via MQTT to the broker;
  • I’ve added a simple ITOA function call that I found somewhere online (sorry forgot where), as I couldn’t send a number otherwise.

I had some issues sending numbers between MQTT and OpenHab. For OpenHab, I seemed to need to send the number as a string, hence the ITOA function. However, the MQTT publish function needs to know the length of the string it’s sending. Now it would have been easy to simply create the biggest char array, fill it with characters via ITOA, and send the whole thing, with blank characters at the end. However, this caused too many problems with OpenHab not recognising it as a number (despite the fact that other MQTT subscribers could see the number fine!).

So I have to make sure I was sending the right number of characters for the size of the number – including the decimal place – which is why I’ve got the following section in the timer interrupt (which looks very ugly!)

		if (temp < 10){
			tempStr[1] = '.';
			if (T2)
				tempStr[2] = '5';
			else
				tempStr[2] = '0';
			MQTT_Publish(client, "/LivingRoom/Temp/1", tempStr, 3, 0, 1);
		}
		else if (temp < 100){
			tempStr[2] = '.';
			if (T2)
				tempStr[3] = '5';
			else
				tempStr[3] = '0';
			MQTT_Publish(client, "/LivingRoom/Temp/1", tempStr, 4, 0, 1);
		}
		else{
			tempStr[3] = '.';
			if (T2)
				tempStr[4] = '5';
			else
				tempStr[4] = '0';
			MQTT_Publish(client, "/LivingRoom/Temp/1", tempStr, 5, 0, 1);
		}

I’m sure there’s a better way to do this, but it works for now.

OpenHab Code

It has taken me a VERY long time to figure out how to use OpenHab so even the most basic things. There really needs to be a good tutorial online on how to do basic stuff. Most of the tutorials I found went too deep too quick!

Since I want OpenHab to get the temperature via MQTT, and display the temperature along with a graph of the temperature, I need the following files (in the configurations folder): items/temp.itemspersistance/mysql.persit, and sitemaps/default.sitemap

You can name the files whatever you want as long as they are in the correct folders and have the correct suffix.

Below are the contents of the files.

test.items:

Number LivingRoomTemp <temperature> {mqtt="<[localbroker:/LivingRoom/Temp/1:state:default"}
Number TempChartPeriod
mysql.persist:
Strategies {  
	everyMinute: "0 * * * * ?"
    default = everyChange  
}  
  
Items {  
	LivingRoomTemp : strategy = everyMinute
}
default.sitemap:
sitemap TestSiteMap label="Temperature Test"
{
	Frame label="Temperatures"{
		Text item=LivingRoomTemp label="Living Room Temperature: [%.1f]"
		Switch item=TempChartPeriod label="Period" mappings=[0=Hour, 1=Day, 2=Week]
		Chart item=LivingRoomTemp period=h refresh=1000 service="mysql" visibility=[TempChartPeriod==0]
		Chart item=LivingRoomTemp period=d refresh=1000 service="mysql" visibility=[TempChartPeriod==1, TempChartPeriod=="Uninitialized"]
		Chart item=LivingRoomTemp period=W refresh=1000 service="mysql" visibility=[TempChartPeriod==2]
	}
}

This presumes that you have mysql setup as a database for OpenHab. There’s a very good tutorial here on how to setup mysql for OpenHab.

The above should be enough get temperature via MQTT and display is using OpenHab. I did add a little bit extra which allows me to choose the time period that’s being displayed on the chart.

Working

I’ve been running the setup now for about 3 days and I’ve not had any noticeable bugs or problems so far.

The screenshot below shows the OpenHab interface on my phone. As you can see, the temperature is showing well, and the graph shows about 3 days worth of data.

Screenshot_2015-02-05-16-20-18

So far I’m pretty happy with the setup and I’m going to look at making a few PCBs so I can put a few of these around the flat and monitor – and later control – the temperature in every room.

Advertisements