Evan Rust
Published © GPL3+

Azure Sphere Weather Station

Use the Azure Sphere Starter Kit and two click boards to monitor current weather conditions and send the data to the Azure Cloud.

IntermediateFull instructions provided4 hours6,875

Things used in this project

Hardware components

Azure Sphere MT3620 Starter Kit
Avnet Azure Sphere MT3620 Starter Kit
×1
MIKROE Environmental Click Board
×1
MIKROE UV-4 Click Board
×1
Water Detect click
MIKROE Water Detect click
×1

Software apps and online services

Visual Studio 2017
Microsoft Visual Studio 2017

Story

Read more

Schematics

Click Board GPIO

Code

main.c

C/C++
Main file
#include <signal.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdio.h>
#include <stdarg.h>
#include <errno.h>

#define I2C_STRUCTS_VERSION 1

#include <applibs/log.h>
#include <applibs/gpio.h>
#include <applibs/i2c.h>
#include <applibs/networking.h>
#include <applibs/storage.h>

#include "epoll_timerfd_utilities.h"

#include "BME680_reg.h"

#include <azureiot/iothub_device_client_ll.h>
#include <azureiot/iothub_client_core_common.h>
#include <azureiot/iothub_client_options.h>
#include <azureiot/iothub.h>
#include <azureiot/iothubtransportmqtt.h>
#include <azureiot/azure_sphere_provisioning.h>
#include "epoll_timerfd_utilities.h"

#define SCOPEID_LENGTH 20
static char scopeId[SCOPEID_LENGTH]; // ScopeId for the Azure IoT Central application, set in
									 // app_manifest.json, CmdArgs

static volatile sig_atomic_t terminationRequired = false;

static I2C_DeviceAddress bme680_addr = 0x77;

static IOTHUB_DEVICE_CLIENT_LL_HANDLE iothubClientHandle = NULL;
static const int keepalivePeriodSeconds = 20;
static bool iothubAuthenticated = false;
static void SendMessageCallback(IOTHUB_CLIENT_CONFIRMATION_RESULT result, void* context);
static void TwinCallback(DEVICE_TWIN_UPDATE_STATE updateState, const unsigned char* payload,
	size_t payloadSize, void* userContextCallback);
static void TwinReportBoolState(const char* propertyName, bool propertyValue);
static void ReportStatusCallback(int result, void* context);
static void readWaterDetectHandler(EventData* eventData);
static const char* GetReasonString(IOTHUB_CLIENT_CONNECTION_STATUS_REASON reason);
static const char* getAzureSphereProvisioningResultString(
	AZURE_SPHERE_PROV_RETURN_VALUE provisioningResult);
static void SendTelemetry(const unsigned char* key, const unsigned char* key1, const unsigned char* value1,
	const unsigned char* key2, const unsigned char* value2,
	const unsigned char* key3, const unsigned char* value3,
	const unsigned char* key4, const unsigned char* value4,
	const unsigned char* key5, const unsigned char* value5);
static void SetupAzureClient(void);

static uint8_t calculate_heat(void);
static double get_temperature(void);
static double get_humidity(void);
static double get_pressure(void);
static double get_gas(void);

static void init_device(void);

static void SendData(void);

static int InitPeripheralsAndHandlers(void);
static void ClosePeripheralsAndHandlers(void);

static int azureTimerFd = -1;
static int epollFd = -1;
static int i2cFd = -1;
static int waterDetectGpioFd = -1;
static int waterDetectTimerFd = -1;

static GPIO_Value_Type waterDetectState = GPIO_Value_Low;

static const int AzureIoTDefaultPollPeriodSeconds = 10;
static const int AzureIoTMinReconnectPeriodSeconds = 60;
static const int AzureIoTMaxReconnectPeriodSeconds = 10 * 60;
static const int waterDetectPollPeriod = 5000000000;

static int azureIoTPollPeriodSeconds = -1;

static void AzureTimerEventHandler(EventData* eventData);

static void TerminationHandler(int signalNumber)
{
	// Don't use Log_Debug here, as it is not guaranteed to be async-signal-safe.
	terminationRequired = true;
}

int main(int argc, char* argv[])
{
	Log_Debug("IoT Hub/Central Application starting.\n");

	if (argc == 2) {
		Log_Debug("Setting Azure Scope ID %s\n", argv[1]);
		strncpy(scopeId, argv[1], SCOPEID_LENGTH);
	}
	else {
		Log_Debug("ScopeId needs to be set in the app_manifest CmdArgs\n");
		return -1;
	}

	if (InitPeripheralsAndHandlers() != 0) {
		terminationRequired = true;
	}

	// Main loop
	while (!terminationRequired) {
		if (WaitForEventAndCallHandler(epollFd) != 0) {
			terminationRequired = true;
		}
	}

	ClosePeripheralsAndHandlers();

	Log_Debug("Application exiting.\n");

	return 0;
}

/// <summary>
/// Azure timer event:  Check connection status and send telemetry
/// </summary>
static void AzureTimerEventHandler(EventData* eventData)
{
	if (ConsumeTimerFdEvent(azureTimerFd) != 0) {
		terminationRequired = true;
		return;
	}

	bool isNetworkReady = false;
	if (Networking_IsNetworkingReady(&isNetworkReady) != -1) {
		if (isNetworkReady && !iothubAuthenticated) {
			SetupAzureClient();
		}
	}
	else {
		Log_Debug("Failed to get Network state\n");
	}

	if (iothubAuthenticated) {
		SendData();
		IoTHubDeviceClient_LL_DoWork(iothubClientHandle);
	}
}

static EventData azureEventData = { .eventHandler = &AzureTimerEventHandler };
static EventData waterDetectEventData = { .eventHandler = &readWaterDetectHandler };

static int InitPeripheralsAndHandlers(void)
{
	struct sigaction action;
	memset(&action, 0, sizeof(struct sigaction));
	action.sa_handler = TerminationHandler;
	sigaction(SIGTERM, &action, NULL);

	epollFd = CreateEpollFd();
	if (epollFd < 0) {
		return -1;
	}

	Log_Debug("Opening ISU2 I2C\n");
	i2cFd = I2CMaster_Open(2);
	I2CMaster_SetBusSpeed(i2cFd, I2C_BUS_SPEED_STANDARD);
	I2CMaster_SetTimeout(i2cFd, 100);
	I2CMaster_SetDefaultTargetAddress(i2cFd, bme680_addr);

	init_device();

	azureIoTPollPeriodSeconds = AzureIoTDefaultPollPeriodSeconds;
	struct timespec azureTelemetryPeriod = { azureIoTPollPeriodSeconds, 0 };
	azureTimerFd =
		CreateTimerFdAndAddToEpoll(epollFd, &azureTelemetryPeriod, &azureEventData, EPOLLIN);

	Log_Debug("Opening GPIO2 as input.\n");
	waterDetectGpioFd = GPIO_OpenAsInput(2);
	if (waterDetectGpioFd < 0) {
		Log_Debug("ERROR: Could not open button GPIO: %s (%d).\n", strerror(errno), errno);
		return -1;
	}

	struct timespec waterDetectCheckPeriod = { 0, waterDetectPollPeriod };
	waterDetectTimerFd = CreateTimerFdAndAddToEpoll(epollFd, &waterDetectCheckPeriod, &waterDetectEventData, EPOLLIN);
	if (waterDetectTimerFd < 0) return -1;

	return 0;
}

static void readWaterDetectHandler(EventData *eventData)
{
	GPIO_Value_Type newState;
	int result = GPIO_GetValue(waterDetectGpioFd, &newState);
	if (result != 0) {
		Log_Debug("ERROR: Could not read button GPIO: %s (%d).\n", strerror(errno), errno);
		//terminationRequired = true;
		return;
	}
	waterDetectState = newState;
}

static void init_device(void) {
	I2CMaster_Write(i2cFd, bme680_addr, ctrl_hum_data, sizeof(ctrl_hum_data));
	I2CMaster_Write(i2cFd, bme680_addr, ctrl_meas_data, sizeof(ctrl_meas_data));
	I2CMaster_Write(i2cFd, bme680_addr, ctrl_gas_1_data, sizeof(ctrl_gas_1_data));
	I2CMaster_Write(i2cFd, bme680_addr, gas_wait_0_data, sizeof(gas_wait_0_data));
	res_heat_0_data[1] = calculate_heat();
	I2CMaster_Write(i2cFd, bme680_addr, res_heat_0_data, sizeof(res_heat_0_data));
	I2CMaster_Write(i2cFd, bme680_addr, config_data, sizeof(config_data));
}

static uint8_t calculate_heat(void) 
{
	int par_g1, par_g2, par_g2_temp, par_g3, res_heat_range, res_heat_val;
	uint8_t current_reg = 0xED;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_g1, 1);
	current_reg = 0xEB;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_g2_temp, 1);
	current_reg++;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_g2, 1);
	current_reg = 0xEE;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_g3, 1);
	current_reg = 0x02;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &res_heat_range, 1);
	current_reg = 0x00;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &res_heat_val, 1);

	par_g2 = (par_g2 << 8) + par_g2_temp;
	res_heat_range &= 0b00110000;

	double var1 = ((double)par_g1 / 16.0) + 49.0;
	double var2 = (((double)par_g2 / 32768.0) * 0.0005) + 0.00235;
	double var3 = (double)par_g3 / 1024.0;
	double var4 = var1 * (1.0 + (var2 * (double)300.0));
	double var5 = var4 + (var3 * (double)23.0);
	return (uint8_t)(3.4 * ((var5 * (4.0 / (4.0 + (double)res_heat_range)) *
		(1.0 / (1.0 + ((double)res_heat_val * 0.002)))) - 25));
}

static void ClosePeripheralsAndHandlers(void)
{
	Log_Debug("Closing file descriptors\n");

	CloseFdAndPrintError(i2cFd, "I2C");
	CloseFdAndPrintError(azureTimerFd, "AzureTimer");
	CloseFdAndPrintError(epollFd, "Epoll");
	CloseFdAndPrintError(waterDetectGpioFd, "WaterDetectGpio");
	CloseFdAndPrintError(waterDetectTimerFd, "WaterDetectTimer");
}

static void HubConnectionStatusCallback(IOTHUB_CLIENT_CONNECTION_STATUS result,
	IOTHUB_CLIENT_CONNECTION_STATUS_REASON reason,
	void* userContextCallback)
{
	iothubAuthenticated = (result == IOTHUB_CLIENT_CONNECTION_AUTHENTICATED);
	Log_Debug("IoT Hub Authenticated: %s\n", GetReasonString(reason));
}

static void SetupAzureClient(void)
{
	if (iothubClientHandle != NULL)
		IoTHubDeviceClient_LL_Destroy(iothubClientHandle);

	AZURE_SPHERE_PROV_RETURN_VALUE provResult =
		IoTHubDeviceClient_LL_CreateWithAzureSphereDeviceAuthProvisioning(scopeId, 10000,
			&iothubClientHandle);
	Log_Debug("IoTHubDeviceClient_LL_CreateWithAzureSphereDeviceAuthProvisioning returned '%s'.\n",
		getAzureSphereProvisioningResultString(provResult));

	if (provResult.result != AZURE_SPHERE_PROV_RESULT_OK) {

		// If we fail to connect, reduce the polling frequency, starting at
		// AzureIoTMinReconnectPeriodSeconds and with a backoff up to
		// AzureIoTMaxReconnectPeriodSeconds
		if (azureIoTPollPeriodSeconds == AzureIoTDefaultPollPeriodSeconds) {
			azureIoTPollPeriodSeconds = AzureIoTMinReconnectPeriodSeconds;
		}
		else {
			azureIoTPollPeriodSeconds *= 2;
			if (azureIoTPollPeriodSeconds > AzureIoTMaxReconnectPeriodSeconds) {
				azureIoTPollPeriodSeconds = AzureIoTMaxReconnectPeriodSeconds;
			}
		}

		struct timespec azureTelemetryPeriod = { azureIoTPollPeriodSeconds, 0 };
		SetTimerFdToPeriod(azureTimerFd, &azureTelemetryPeriod);

		Log_Debug("ERROR: failure to create IoTHub Handle - will retry in %i seconds.\n",
			azureIoTPollPeriodSeconds);
		return;
	}

	// Successfully connected, so make sure the polling frequency is back to the default
	azureIoTPollPeriodSeconds = AzureIoTDefaultPollPeriodSeconds;
	struct timespec azureTelemetryPeriod = { azureIoTPollPeriodSeconds, 0 };
	SetTimerFdToPeriod(azureTimerFd, &azureTelemetryPeriod);

	iothubAuthenticated = true;

	if (IoTHubDeviceClient_LL_SetOption(iothubClientHandle, OPTION_KEEP_ALIVE,
		&keepalivePeriodSeconds) != IOTHUB_CLIENT_OK) {
		Log_Debug("ERROR: failure setting option \"%s\"\n", OPTION_KEEP_ALIVE);
		return;
	}

	//IoTHubDeviceClient_LL_SetDeviceTwinCallback(iothubClientHandle, TwinCallback, NULL);
	IoTHubDeviceClient_LL_SetConnectionStatusCallback(iothubClientHandle,
		HubConnectionStatusCallback, NULL);
}

static const char* GetReasonString(IOTHUB_CLIENT_CONNECTION_STATUS_REASON reason)
{
	static char* reasonString = "unknown reason";
	switch (reason) {
	case IOTHUB_CLIENT_CONNECTION_EXPIRED_SAS_TOKEN:
		reasonString = "IOTHUB_CLIENT_CONNECTION_EXPIRED_SAS_TOKEN";
		break;
	case IOTHUB_CLIENT_CONNECTION_DEVICE_DISABLED:
		reasonString = "IOTHUB_CLIENT_CONNECTION_DEVICE_DISABLED";
		break;
	case IOTHUB_CLIENT_CONNECTION_BAD_CREDENTIAL:
		reasonString = "IOTHUB_CLIENT_CONNECTION_BAD_CREDENTIAL";
		break;
	case IOTHUB_CLIENT_CONNECTION_RETRY_EXPIRED:
		reasonString = "IOTHUB_CLIENT_CONNECTION_RETRY_EXPIRED";
		break;
	case IOTHUB_CLIENT_CONNECTION_NO_NETWORK:
		reasonString = "IOTHUB_CLIENT_CONNECTION_NO_NETWORK";
		break;
	case IOTHUB_CLIENT_CONNECTION_COMMUNICATION_ERROR:
		reasonString = "IOTHUB_CLIENT_CONNECTION_COMMUNICATION_ERROR";
		break;
	case IOTHUB_CLIENT_CONNECTION_OK:
		reasonString = "IOTHUB_CLIENT_CONNECTION_OK";
		break;
	}
	return reasonString;
}

static const char* getAzureSphereProvisioningResultString(
	AZURE_SPHERE_PROV_RETURN_VALUE provisioningResult)
{
	switch (provisioningResult.result) {
	case AZURE_SPHERE_PROV_RESULT_OK:
		return "AZURE_SPHERE_PROV_RESULT_OK";
	case AZURE_SPHERE_PROV_RESULT_INVALID_PARAM:
		return "AZURE_SPHERE_PROV_RESULT_INVALID_PARAM";
	case AZURE_SPHERE_PROV_RESULT_NETWORK_NOT_READY:
		return "AZURE_SPHERE_PROV_RESULT_NETWORK_NOT_READY";
	case AZURE_SPHERE_PROV_RESULT_DEVICEAUTH_NOT_READY:
		return "AZURE_SPHERE_PROV_RESULT_DEVICEAUTH_NOT_READY";
	case AZURE_SPHERE_PROV_RESULT_PROV_DEVICE_ERROR:
		return "AZURE_SPHERE_PROV_RESULT_PROV_DEVICE_ERROR";
	case AZURE_SPHERE_PROV_RESULT_GENERIC_ERROR:
		return "AZURE_SPHERE_PROV_RESULT_GENERIC_ERROR";
	default:
		return "UNKNOWN_RETURN_VALUE";
	}
}

static void SendTelemetry(const unsigned char* key, const unsigned char* key1, const unsigned char* value1,
	const unsigned char* key2, const unsigned char* value2, 
	const unsigned char* key3, const unsigned char* value3,
	const unsigned char* key4, const unsigned char* value4,
	const unsigned char* key5, const unsigned char* value5)
{
	static char eventBuffer[300] = { 0 };
	static const char* EventMsgTemplate = "{ \"%s\": { \
		\"%s\" : \"%s\", \
		\"%s\" : \"%s\", \
		\"%s\" : \"%s\", \
		\"%s\" : \"%s\", \
		\"%s\" : \"%s\"}}";
	int len = snprintf(eventBuffer, sizeof(eventBuffer), EventMsgTemplate, key, 
		key1, value1, key2, value2, key3, value3, key4, value4, key5, value5);
	if (len < 0)
		return;

	Log_Debug("Sending IoT Hub Message: %s\n", eventBuffer);

	IOTHUB_MESSAGE_HANDLE messageHandle = IoTHubMessage_CreateFromString(eventBuffer);

	if (messageHandle == 0) {
		Log_Debug("WARNING: unable to create a new IoTHubMessage\n");
		return;
	}

	if (IoTHubDeviceClient_LL_SendEventAsync(iothubClientHandle, messageHandle, SendMessageCallback,
		/*&callback_param*/ 0) != IOTHUB_CLIENT_OK) {
		Log_Debug("WARNING: failed to hand over the message to IoTHubClient\n");
	}
	else {
		Log_Debug("INFO: IoTHubClient accepted the message for delivery\n");
	}

	IoTHubMessage_Destroy(messageHandle);
}

static void SendMessageCallback(IOTHUB_CLIENT_CONFIRMATION_RESULT result, void* context)
{
	Log_Debug("INFO: Message received by IoT Hub. Result is: %d\n", result);
}

void SendData(void)
{
	double temperature, humidity, pressure, gas_res;

	temperature = get_temperature();
	humidity = get_humidity();
	pressure = get_pressure();
	gas_res = get_gas();

	char tempBuffer[20];
	int len = snprintf(tempBuffer, 20, "%3.2f", temperature);
	char humBuffer[20];
	len = snprintf(humBuffer, 20, "%3.1f", humidity);
	char pressBuffer[20];
	len = snprintf(pressBuffer, 20, "%6.1f", pressure);
	char gasBuffer[20];
	len = snprintf(gasBuffer, 20, "%3.2f", gas_res);

	bool water_present = false;

	if (waterDetectState == GPIO_Value_High) water_present = true;

	char waterPresentBuffer[20];
	len = snprintf(waterPresentBuffer, 20, "%s", water_present ? "true" : "false");

	if (len > 0)
		SendTelemetry("Env_data", "temperature", tempBuffer, "humidity", humBuffer, "pressure",
			pressBuffer, "gas", gasBuffer, "water_present", waterPresentBuffer);

	I2CMaster_Write(i2cFd, bme680_addr, reset_data, sizeof(reset_data));
	init_device();
}

static double get_gas(void)
{
	static const const_array1[] = {1, 1, 1, 1, 1, 0.99, 1, 0.992, 1, 1, 0.998, 0.995, 1, 0.99, 1, 1};
	static const const_array2[] = { 8000000, 4000000, 2000000, 1000000, 499500.4995, 248262.1648,
	125000, 63004.03226, 31281.28128, 15625, 7812.5, 3906.25, 1953.125, 976.5625, 488.28125, 244.140625};

	uint8_t gas_adc_lsb, gas_adc_msb, gas_range, range_switching_error;
	uint16_t raw_gas_adc;
	double gas_res;
	uint8_t current_reg = 0x2B;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &gas_adc_lsb, 1);
	current_reg = 0x2A;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &gas_adc_msb, 1);
	current_reg = 0x2B;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &gas_range, 1);
	current_reg = 0x04;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &range_switching_error, 1);

	raw_gas_adc = (gas_adc_msb << 2) + (gas_adc_lsb >> 6);
	gas_range = gas_range & 0b1111;

	double var1 = (1340.0 + 5.0 * range_switching_error) * const_array1[gas_range];
	gas_res = var1 * const_array2[gas_range] / (raw_gas_adc - 512.0 + var1);
	return gas_res;

}

static double get_pressure(void)
{
	uint32_t raw_press_adc;
	uint8_t par_p1_lsb, par_p1_msb, par_p2_lsb, par_p2_msb, par_p3, par_p4_lsb, par_p4_msb,
		par_p5_lsb, par_p5_msb, par_p6, par_p7, par_p8_lsb, par_p8_msb, par_p9_lsb, par_p9_msb,
		par_p10, press_adc_xlsb, press_adc_lsb, press_adc_msb;
	uint16_t par_p1, par_p2, par_p4, par_p5, par_p8, par_p9;
	uint32_t press_comp;
	uint8_t current_reg = 0x8E;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_p1_lsb, 1);
	current_reg++;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_p1_msb, 1);
	current_reg++;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_p2_lsb, 1);
	current_reg++;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_p2_msb, 1);
	current_reg++;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_p3, 1);
	current_reg = 0x94;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_p4_lsb, 1);
	current_reg++;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_p4_msb, 1);
	current_reg++;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_p5_lsb, 1);
	current_reg++;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_p5_msb, 1);
	current_reg = 0x99;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_p6, 1);
	current_reg--;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_p7, 1);
	current_reg = 0x9C;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_p8_lsb, 1);
	current_reg = 0x9D;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_p8_msb, 1);
	current_reg = 0x9E;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_p9_lsb, 1);
	current_reg = 0x9F;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_p9_msb, 1);
	current_reg = 0xA0;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_p10, 1);
	current_reg = 0x21;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &press_adc_xlsb, 1);
	current_reg--;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &press_adc_lsb, 1);
	current_reg--;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &press_adc_msb, 1);
	par_p1 = (par_p1_msb << 8) + par_p1_lsb;
	par_p2 = (par_p2_msb << 8) + par_p2_lsb;
	par_p4 = (par_p4_msb << 8) + par_p4_lsb;
	par_p5 = (par_p5_msb << 8) + par_p5_lsb;
	par_p8 = (par_p8_msb << 8) + par_p8_lsb;
	par_p9 = (par_p9_msb << 8) + par_p9_lsb;
	raw_press_adc = (press_adc_msb << 12) + (press_adc_lsb << 4) + (press_adc_xlsb >> 4);

	double t_fine = get_temperature() * 5120.0;

	int32_t var1 = ((int32_t)t_fine >> 1) - 64000;
	int32_t var2 = ((((var1 >> 2) * (var1 >> 2)) >> 11) * (int32_t)par_p6) >> 2;
	var2 = var2 + ((var1 * (int32_t)par_p5) << 1);
	var2 = (var2 >> 2) + ((int32_t)par_p4 << 16);
	var1 = (((((var1 >> 2) * (var1 >> 2)) >> 13) * ((int32_t)par_p3 << 5)) >> 3) +
		(((int32_t)par_p2 * var1) >> 1);
	var1 = var1 >> 18;
	var1 = ((32768 + var1) * (int32_t)par_p1) >> 15;
	press_comp = 1048576 - raw_press_adc;
	press_comp = (uint32_t)((press_comp - (var2 >> 12)) * ((uint32_t)3125));
	if (press_comp >= (1 << 30)) press_comp = ((press_comp / (uint32_t)var1) << 1);
	else press_comp = ((press_comp << 1) / (uint32_t)var1);
	var1 = ((int32_t)par_p9 * (int32_t)(((press_comp >> 3) *
		(press_comp >> 3)) >> 13)) >> 12;
	var2 = ((int32_t)(press_comp >> 2) * (int32_t)par_p8) >> 13;
	int32_t var3 = ((int32_t)(press_comp >> 8) * (int32_t)(press_comp >> 8) *
		(int32_t)(press_comp >> 8) * (int32_t)par_p10) >> 17;
	press_comp = (int32_t)(press_comp)+((var1 + var2 + var3 + ((int32_t)par_p7 << 7)) >> 4);
	Log_Debug("Pressure integer val is %i\n", press_comp);
	return (double)press_comp;


}

static double get_humidity(void)
{
	uint32_t raw_hum_adc;
	uint8_t par_h1_lsb, par_h1_msb, par_h2_lsb, par_h2_msb, par_h3, par_h4, par_h5, par_h6, par_h7,
		hum_adc_lsb, hum_adc_msb;
	uint16_t par_h1, par_h2;
	const double temp_comp = get_temperature();
	uint8_t current_reg = 0xE2;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_h1_lsb, 1);
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_h2_lsb, 1);
	current_reg = 0xE3;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_h1_msb, 1);
	current_reg = 0xE1;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_h2_msb, 1);
	current_reg = 0xE4;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_h3, 1);
	current_reg++;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_h4, 1);
	current_reg++;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_h5, 1);
	current_reg++;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_h6, 1);
	current_reg++; //0xE8
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_h7, 1);
	current_reg = 0x26;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &hum_adc_lsb, 1);
	current_reg = 0x25;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &hum_adc_msb, 1);
	raw_hum_adc = (hum_adc_msb << 8) + hum_adc_lsb;
	par_h1 = (par_h1_msb << 4) + (par_h1_lsb >> 4);
	par_h2 = (par_h2_msb << 4) + (par_h2_lsb >> 4);

	double var1 = raw_hum_adc - (((double)par_h1 * 16.0) + (((double)par_h3 / 2.0) * temp_comp));
	double var2 = var1 * (((double)par_h2 / 262144.0) * (1.0 + (((double)par_h4 / 16384.0) *
		temp_comp) + (((double)par_h5 / 1048576.0) * temp_comp * temp_comp)));
	double var3 = (double)par_h6 / 16384.0;
	double var4 = (double)par_h7 / 2097152.0;

	return var2 + ((var3 + (var4 * temp_comp)) * var2 * var2);

}

static double get_temperature(void)
{
	uint32_t raw_temp_adc;
	uint8_t par_t1_lsb, par_t1_msb, par_t2_lsb, par_t2_msb, par_t3, temp_adc_xlsb,
		temp_adc_lsb, temp_adc_msb;
	uint16_t par_t1, par_t2;
	uint8_t current_reg = 0xE9;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_t1_lsb, 1);
	current_reg = 0xEA;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_t1_msb, 1);
	current_reg = 0x8A;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_t2_lsb, 1);
	current_reg = 0x8B;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_t2_msb, 1);
	current_reg = 0x8C;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &par_t3, 1);
	current_reg = 0x24;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &temp_adc_xlsb, 1);
	current_reg = 0x23;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &temp_adc_lsb, 1);
	current_reg = 0x22;
	I2CMaster_WriteThenRead(i2cFd, bme680_addr, &current_reg, 1, &temp_adc_msb, 1);

	temp_adc_xlsb >>= 4;
	raw_temp_adc = (temp_adc_msb << 12) + (temp_adc_lsb << 4) + temp_adc_xlsb;

	par_t1 = (par_t1_msb << 8) + par_t1_lsb;
	par_t2 = (par_t2_msb << 8) + par_t2_lsb;

	double var1 = (((double)raw_temp_adc / 16384.0) - ((double)par_t1 / 1024.0)) * (double)par_t2;
	double var2 = ((((double)raw_temp_adc / 131072.0) - ((double)par_t1 / 8192.0)) *
		(((double)raw_temp_adc / 131072.0) - ((double)par_t1 / 8192.0))) *
		((double)par_t3 * 16.0);
	double t_fine = var1 + var2;
	double temp_comp = t_fine / 5120.0;
	return temp_comp;
}

Epoll Library .c

C/C++
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/timerfd.h>
#include <applibs/log.h>
#include "epoll_timerfd_utilities.h"

int CreateEpollFd(void)
{
	int epollFd = -1;

	epollFd = epoll_create1(0);
	if (epollFd == -1) {
		Log_Debug("ERROR: Could not create epoll instance: %s (%d).\n", strerror(errno), errno);
		return -1;
	}

	return epollFd;
}

int RegisterEventHandlerToEpoll(int epollFd, int eventFd, EventData* persistentEventData,
	const uint32_t epollEventMask)
{
	persistentEventData->fd = eventFd;
	struct epoll_event eventToAddOrModify = { .data.ptr = persistentEventData,
											 .events = epollEventMask };

	// Register the eventFd on the epoll instance referred by epollFd
	// and register the eventHandler handler for events in epollEventMask.
	if (epoll_ctl(epollFd, EPOLL_CTL_ADD, eventFd, &eventToAddOrModify) == -1) {
		// If the Add fails, retry with the Modify as the file descriptor has already been
		// added to the epoll set after it was removed by the kernel upon its closure.
		if (epoll_ctl(epollFd, EPOLL_CTL_MOD, eventFd, &eventToAddOrModify) == -1) {
			Log_Debug("ERROR: Could not register event to epoll instance: %s (%d).\n",
				strerror(errno), errno);
			return -1;
		}
	}

	return 0;
}

int UnregisterEventHandlerFromEpoll(int epollFd, int eventFd)
{
	int res = 0;
	// Unregister the eventFd on the epoll instance referred by epollFd.
	if ((res = epoll_ctl(epollFd, EPOLL_CTL_DEL, eventFd, NULL)) == -1) {
		if (res == -1 && errno != EBADF) { // Ignore EBADF errors
			Log_Debug("ERROR: Could not remove event from epoll instance: %s (%d).\n",
				strerror(errno), errno);
			return -1;
		}
	}

	return 0;
}

int SetTimerFdToPeriod(int timerFd, const struct timespec* period)
{
	struct itimerspec newValue = { .it_value = *period,.it_interval = *period };

	if (timerfd_settime(timerFd, 0, &newValue, NULL) < 0) {
		Log_Debug("ERROR: Could not set timerfd period: %s (%d).\n", strerror(errno), errno);
		return -1;
	}

	return 0;
}

int SetTimerFdToSingleExpiry(int timerFd, const struct timespec* expiry)
{
	struct itimerspec newValue = { .it_value = *expiry,.it_interval = {} };

	if (timerfd_settime(timerFd, 0, &newValue, NULL) < 0) {
		Log_Debug("ERROR: Could not set timerfd interval: %s (%d).\n", strerror(errno), errno);
		return -1;
	}

	return 0;
}

int ConsumeTimerFdEvent(int timerFd)
{
	uint64_t timerData = 0;

	if (read(timerFd, &timerData, sizeof(timerData)) == -1) {
		Log_Debug("ERROR: Could not read timerfd %s (%d).\n", strerror(errno), errno);
		return -1;
	}

	return 0;
}

int CreateTimerFdAndAddToEpoll(int epollFd, const struct timespec* period,
	EventData* persistentEventData, const uint32_t epollEventMask)
{
	// Create the timerfd and arm it by setting the interval to period
	int timerFd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
	if (timerFd < 0) {
		Log_Debug("ERROR: Could not create timerfd: %s (%d).\n", strerror(errno), errno);
		return -1;
	}
	if (SetTimerFdToPeriod(timerFd, period) != 0) {
		int result = close(timerFd);
		if (result != 0) {
			Log_Debug("ERROR: Could not close timerfd: %s (%d).\n", strerror(errno), errno);
		}
		return -1;
	}

	persistentEventData->fd = timerFd;
	if (RegisterEventHandlerToEpoll(epollFd, timerFd, persistentEventData, epollEventMask) != 0) {
		return -1;
	}

	return timerFd;
}

int WaitForEventAndCallHandler(int epollFd)
{
	struct epoll_event event;
	int numEventsOccurred = epoll_wait(epollFd, &event, 1, -1);

	if (numEventsOccurred == -1) {
		if (errno == EINTR) {
			// interrupted by signal, e.g. due to breakpoint being set; ignore
			return 0;
		}
		Log_Debug("ERROR: Failed waiting on events: %s (%d).\n", strerror(errno), errno);
		return -1;
	}

	if (numEventsOccurred == 1 && event.data.ptr != NULL) {
		EventData* eventData = event.data.ptr;
		eventData->eventHandler(eventData);
	}

	return 0;
}

void CloseFdAndPrintError(int fd, const char* fdName)
{
	if (fd >= 0) {
		int result = close(fd);
		if (result != 0) {
			Log_Debug("ERROR: Could not close fd %s: %s (%d).\n", fdName, strerror(errno), errno);
		}
	}
}

Epoll Library .h

C/C++
#pragma once
#include <time.h>
#include <sys/epoll.h>
#include <unistd.h>

/// Forward declaration of the data type passed to the handlers.
struct EventData;

/// <summary>
///     Function signature for event handlers.
/// </summary>
/// <param name="eventData">The provided event data</param>
typedef void (*EventHandler)(struct EventData* eventData);

/// <summary>
/// <para>Contains context data for epoll events.</para>
/// <para>When an event is registered with RegisterEventHandlerToEpoll, supply
/// a pointer to an instance of this struct.  The pointer must remain valid
/// for as long as the event is active.</para>
/// </summary>
/// <seealso cref="RegisterEventHandlerToEpoll" />
typedef struct EventData {
	/// <summary>
	/// Function which is called when the event occurs.
	/// </summary>
	EventHandler eventHandler;
	/// <summary>
	/// The file descriptor that generated the event.
	/// </summary>
	int fd;
} EventData;

/// <summary>
///    Creates an epoll instance.
/// </summary>
/// <returns>A valid epoll file descriptor on success, or -1 on failure</returns>
int CreateEpollFd(void);

/// <summary>
///     Registers an event with the epoll instance. If the event was previously added, that
///     registration will be modified to match the new mask.
/// </summary>
/// <param name="epollFd">Epoll file descriptor</param>
/// <param name="eventFd">File descriptor generating events for the epoll</param>
/// <param name="persistentEventData">Persistent event data structure. This must stay in memory
/// until the handler is removed from the epoll.</param>
/// <param name="epollEventMask">Bit mask for the epoll event type</param>
/// <returns>0 on success, or -1 on failure</returns>
int RegisterEventHandlerToEpoll(int epollFd, int eventFd, EventData* persistentEventData,
	const uint32_t epollEventMask);

/// <summary>
///     Unregisters an event with the epoll instance.
/// </summary>
/// <param name="epollFd">Epoll file descriptor</param>
/// <param name="eventFd">File descriptor generating events for the epoll</param>
/// <returns>0 on success, or -1 on failure</returns>
int UnregisterEventHandlerFromEpoll(int epollFd, int eventFd);

/// <summary>
///     Sets the period of a timer.
/// </summary>
/// <param name="timerFd">Timer file descriptor</param>
/// <param name="period">The new period</param>
/// <returns>0 on success, or -1 on failure</returns>
int SetTimerFdToPeriod(int timerFd, const struct timespec* period);

/// <summary>
///     Sets a timer to fire once only, after a duration specified in milliseconds.
/// </summary>
/// <param name="timerFd">Timer file descriptor</param>
/// <param name="expiry">The time elapsed before it expires once</param>
/// <returns>0 on success, or -1 on failure</returns>
int SetTimerFdToSingleExpiry(int timerFd, const struct timespec* expiry);

/// <summary>
///     Consumes an event by reading from the timer file descriptor.
///     If the event is not consumed, then it will immediately recur.
/// </summary>
/// <param name="timerFd">Timer file descriptor</param>
/// <returns>0 on success, or -1 on failure</returns>
int ConsumeTimerFdEvent(int timerFd);

/// <summary>
///     Creates a timerfd and adds it to an epoll instance.
/// </summary>
/// <param name="epollFd">Epoll file descriptor</param>
/// <param name="period">The timer period</param>
/// <param name="persistentEventData">Persistent event data structure. This must stay in memory
/// until the handler is removed from the epoll.</param>
/// <param name="epollEventMask">Bit mask for the epoll event type</param>
/// <returns>A valid timerfd file descriptor on success, or -1 on failure</returns>
int CreateTimerFdAndAddToEpoll(int epollFd, const struct timespec* period,
	EventData* persistentEventData, const uint32_t epollEventMask);

/// <summary>
///     Waits for an event on an epoll instance and triggers the handler.
/// </summary>
/// <param name="epollFd">
///     Epoll file descriptor which was created with <see cref="CreateEpollFd" />.
/// </param>
/// <returns>0 on success, or -1 on failure</returns>
int WaitForEventAndCallHandler(int epollFd);

/// <summary>
///     Closes a file descriptor and prints an error on failure.
/// </summary>
/// <param name="fd">File descriptor to close</param>
/// <param name="name">File descriptor name to use in error message</param>
void CloseFdAndPrintError(int fd, const char* name);

BME680 Header

C/C++
C header that contains register addresses and some initial data for BME680 sensor
#pragma once
#define Ctrl_meas 0x74
#define Ctrl_hum 0x72
#define Ctrl_gas_1 0x71
#define Ctrl_gas_0 0x70
#define Config 0x75
#define Gas_wait_(x) (0x64 + x)
#define Idac_heat_(x) (0x50 + x)
#define Res_heat_(x) (0x5A + x)

#define Gas_r_lsb 0x2B
#define Gas_r_msb 0x2A

#define Hum_lsb 0x26
#define Hum_msb 0x25

#define Temp_xlsb 0x24
#define Temp_lsb 0x23
#define Temp_msb 0x22

#define Press_xlsb 0x21
#define Press_lsb 0x20
#define Press_msb 0x1F

#define BME680_ID 0x61
#define Id 0xD0
#define Reset 0xE0

#define BME680_ADDR 0x77

const uint8_t ctrl_hum_data[] = { Ctrl_hum, 0x01 };
const uint8_t ctrl_meas_data[] = { Ctrl_meas, 0b01010101 };
const uint8_t ctrl_gas_1_data[] = { Ctrl_gas_1, 0b0001000 };
const uint8_t gas_wait_0_data[] = { Gas_wait_(0), 0x59 };
uint8_t res_heat_0_data[] = { Res_heat_(0), 0x00 };
const uint8_t config_data[] = { Config, 0b00001000 };
const uint8_t reset_data[] = { Reset, 0xB6 };

Credits

Evan Rust

Evan Rust

122 projects • 1091 followers
IoT, web, and embedded systems enthusiast. Contact me for product reviews or custom project requests.

Comments