diff --git a/Documentation/bluetooth/ess.rst b/Documentation/bluetooth/ess.rst
new file mode 100644
index 0000000000000000000000000000000000000000..6a84a6aba07cdb58e34a084291562e4e8a89089b
--- /dev/null
+++ b/Documentation/bluetooth/ess.rst
@@ -0,0 +1,65 @@
+Environmental Sensing Service
+========================
+
+.. warning::
+    This service will be available in version v1.17
+
+The Environmental Sensing Service (ESS) implements access to
+the BME680 environmental sensor of the card10.
+
+It provides:
+
+- Temperature
+- Relative humidity
+- Pressure
+
+
+If notifcations are enabled a measurement of all values is performed every 3 seconds. For each measurement a notification is sent for the characteristics which have notifications enabled.
+
+
+A measurement can also be triggered by reading from a characteristic. A measurement takes roughly 200 ms. A notifciation will be sent to all characteristics which have notifications enabled except the one which was used to trigger the measurement.
+
+
+BLE Service
+-----------
+
+- Service
+
+  UUID: ``181A``
+
+- Temperature characteristic:
+
+  UUID: ``2A6E``
+  read and notify
+
+- Humidity characteristic:
+
+  UUID: ``2A6F``
+  read and notify
+
+- Pressure characteristic:
+
+  UUID: ``2A6D``
+  read and notify
+
+Temperature characteristic
+---------------------------------
+
+- 16 bit little endian value representing the measured temperature.
+
+- Unit: 0.01 deg C
+
+
+Humidity characteristic
+---------------------------------
+
+- 16 bit little endian value representing the measured relative humidity.
+
+- Unit: 0.01%
+
+Pressure characteristic
+---------------------------------
+
+- 32 bit little endian value representing the measured pressure.
+
+- Unit: 0.1 Pa (0.001 hPa)
diff --git a/Documentation/index.rst b/Documentation/index.rst
index f83d9ee79b55cae3248cb1529be9cc1f02cda897..1090ad48cb9d2fce0e4cc5ed58512cd4b133c737 100644
--- a/Documentation/index.rst
+++ b/Documentation/index.rst
@@ -69,6 +69,7 @@ Last but not least, if you want to start hacking the lower-level firmware, the
    :maxdepth: 1
    :caption: Bluetooth
 
+   bluetooth/ess
    bluetooth/file-transfer
    bluetooth/card10
    bluetooth/nimble
diff --git a/epicardium/ble/ble.c b/epicardium/ble/ble.c
index b3e175f1dd1c6f5dea54b5d9bf2ae430c53386de..bfb33db010e1b66505a8fdabbfc78f1df269e9fe 100644
--- a/epicardium/ble/ble.c
+++ b/epicardium/ble/ble.c
@@ -69,6 +69,7 @@ extern void StackInit(void);
 extern void bleuart_init(void);
 extern void bleFileTransfer_init(void);
 extern void bleCard10_init(void);
+extern void bleESS_init(void);
 extern void BbBleDrvSetTxPower(int8_t power);
 
 /*************************************************************************************************/
@@ -432,6 +433,7 @@ void vBleTask(void *pvParameters)
 	bleuart_init();
 	bleFileTransfer_init();
 	bleCard10_init();
+	bleESS_init();
 
 	lasttick = xTaskGetTickCount();
 
diff --git a/epicardium/ble/ble_api.h b/epicardium/ble/ble_api.h
index d46b0fe8e63bd7017ea663a07f1d985cd9222be7..93261a58d1b8ac12dbd0091d70ea007d21af933f 100644
--- a/epicardium/ble/ble_api.h
+++ b/epicardium/ble/ble_api.h
@@ -22,7 +22,6 @@
 /*************************************************************************************************/
 void BleStart(void);
 
-
 /* ATT client module interface. Used by main BLE module */
 void bleValueUpdate(attEvt_t *pMsg);
 void bleDiscCback(dmConnId_t connId, uint8_t status);
diff --git a/epicardium/ble/ble_main.c b/epicardium/ble/ble_main.c
index a807a35731e7f16b1603b99bd43dd3fc3b71b468..431b4310a59cd2b811d1ec39670d8a08023423f4 100644
--- a/epicardium/ble/ble_main.c
+++ b/epicardium/ble/ble_main.c
@@ -40,6 +40,7 @@
 #include "rscp/rscp_api.h"
 #include "profiles/gap_api.h"
 #include "cccd.h"
+#include "ess.h"
 
 #include "ble_api.h"
 #include "epicardium.h"
@@ -228,6 +229,9 @@ static const attsCccSet_t bleCccSet[BLE_NUM_CCC_IDX] =
   /* cccd handle          value range               security level */
   {GATT_SC_CH_CCC_HDL,    ATT_CLIENT_CFG_INDICATE,  DM_SEC_LEVEL_NONE},   /* BLE_GATT_SC_CCC_IDX */
   {BATT_LVL_CH_CCC_HDL,   ATT_CLIENT_CFG_NOTIFY,    DM_SEC_LEVEL_NONE},   /* BLE_BATT_LVL_CCC_IDX */
+  {ESS_TEMP_CH_CCC_HDL,   ATT_CLIENT_CFG_NOTIFY,    DM_SEC_LEVEL_NONE},   /* BLE_ESS_TEMP_CCC_IDX */
+  {ESS_HUMI_CH_CCC_HDL,   ATT_CLIENT_CFG_NOTIFY,    DM_SEC_LEVEL_NONE},   /* BLE_ESS_HUMI_CCC_IDX */
+  {ESS_PRES_CH_CCC_HDL,   ATT_CLIENT_CFG_NOTIFY,    DM_SEC_LEVEL_NONE},   /* BLE_ESS_PRES_CCC_IDX */
 };
 
 /**************************************************************************************************
@@ -436,18 +440,23 @@ static void bleProcCccState(bleMsg_t *pMsg)
   APP_TRACE_INFO3("ccc state ind value:%d handle:%d idx:%d", pMsg->ccc.value, pMsg->ccc.handle, pMsg->ccc.idx);
 
   /* handle battery level CCC */
-  if (pMsg->ccc.idx == BLE_BATT_LVL_CCC_IDX)
-  {
-    if (pMsg->ccc.value == ATT_CLIENT_CFG_NOTIFY)
-    {
-      BasMeasBattStart((dmConnId_t) pMsg->ccc.hdr.param, BLE_BATT_TIMER_IND, BLE_BATT_LVL_CCC_IDX);
-    }
-    else
-    {
-      BasMeasBattStop((dmConnId_t) pMsg->ccc.hdr.param);
-    }
-    return;
-  }
+  switch(pMsg->ccc.idx) {
+    case BLE_BATT_LVL_CCC_IDX:
+      if (pMsg->ccc.value == ATT_CLIENT_CFG_NOTIFY)
+      {
+        BasMeasBattStart((dmConnId_t) pMsg->ccc.hdr.param, BLE_BATT_TIMER_IND, BLE_BATT_LVL_CCC_IDX);
+      }
+      else
+      {
+        BasMeasBattStop((dmConnId_t) pMsg->ccc.hdr.param);
+      }
+    break;
+    case BLE_ESS_TEMP_CCC_IDX:
+    case BLE_ESS_HUMI_CCC_IDX:
+    case BLE_ESS_PRES_CCC_IDX:
+      bleESS_ccc_update();
+    break;
+  };
 }
 
 /*************************************************************************************************/
@@ -463,6 +472,7 @@ static void bleClose(bleMsg_t *pMsg)
 {
   /* stop battery measurement */
   BasMeasBattStop((dmConnId_t) pMsg->hdr.param);
+  bleESS_ccc_update();
   GapClearDeviceName();
 }
 
@@ -760,6 +770,7 @@ static void bleProcMsg(bleMsg_t *pMsg)
                connOpen->peerAddr[3], connOpen->peerAddr[2],
                connOpen->peerAddr[1], connOpen->peerAddr[0]);
       BasProcMsg(&pMsg->hdr);
+      bleESS_ccc_update();
       break;
 
     case DM_CONN_CLOSE_IND:
diff --git a/epicardium/ble/cccd.h b/epicardium/ble/cccd.h
index 02574bbdc9d0316eeff03ce3a1c19b1b0f09c94c..2715f6ebb6252e4995b73bb7980795b4c0354083 100644
--- a/epicardium/ble/cccd.h
+++ b/epicardium/ble/cccd.h
@@ -4,6 +4,9 @@ enum
 {
   BLE_GATT_SC_CCC_IDX,                    /*! GATT service, service changed characteristic */
   BLE_BATT_LVL_CCC_IDX,                   /*! Battery service, battery level characteristic */
+  BLE_ESS_TEMP_CCC_IDX,                   /*! Environmental sensing service, temperature characteristic */
+  BLE_ESS_HUMI_CCC_IDX,                   /*! Environmental sensing service, humidity characteristic */
+  BLE_ESS_PRES_CCC_IDX,                   /*! Environmental sensing service, pressure characteristic */
   BLE_NUM_CCC_IDX
 };
 
diff --git a/epicardium/ble/ess.c b/epicardium/ble/ess.c
new file mode 100644
index 0000000000000000000000000000000000000000..a4452e9d5d2a3685515f8bbaba9ad877f0978e64
--- /dev/null
+++ b/epicardium/ble/ess.c
@@ -0,0 +1,355 @@
+#include "ess.h"
+#include "cccd.h"
+
+#include "wsf_types.h"
+#include "util/bstream.h"
+#include "wsf_assert.h"
+#include "att_api.h"
+#include "app_api.h"
+
+#include "epicardium.h"
+#include "modules/log.h"
+#include "modules/modules.h"
+
+#include "FreeRTOS.h"
+#include "timers.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include <machine/endian.h>
+
+/* clang-format off */
+
+/* BLE UUID for ESS service*/
+static const uint8_t UUID_svc[] = { UINT16_TO_BYTES(ATT_UUID_ENVIRONMENTAL_SENSING_SERVICE) };
+static const uint16_t UUID_svc_len = sizeof(UUID_svc);
+
+/* BLE UUID for temperature */
+static const uint8_t UUID_char_temperature[] = {
+	ATT_PROP_READ | ATT_PROP_NOTIFY,
+	UINT16_TO_BYTES(ESS_TEMPERATURE_VAL_HDL),
+	UINT16_TO_BYTES(ATT_UUID_TEMPERATURE)
+};
+
+/* BLE UUID for humidity */
+static const uint8_t UUID_char_humidity[] = {
+	ATT_PROP_READ | ATT_PROP_NOTIFY,
+	UINT16_TO_BYTES(ESS_HUMIDITY_VAL_HDL),
+	UINT16_TO_BYTES(ATT_UUID_HUMIDITY)
+};
+
+/* BLE UUID for pressure */
+static const uint8_t UUID_char_pressure[] = {
+	ATT_PROP_READ | ATT_PROP_NOTIFY,
+	UINT16_TO_BYTES(ESS_PRESSURE_VAL_HDL),
+	UINT16_TO_BYTES(ATT_UUID_PRESSURE)
+};
+
+static const uint16_t UUID_char_len = sizeof(UUID_char_temperature);
+
+static uint8_t initTemperatureValue[] = { UINT16_TO_BYTES(0) };
+static uint16_t initTemperatureLen = sizeof(initTemperatureValue);
+
+static uint8_t initHumidityValue[] = { UINT16_TO_BYTES(0) };
+static uint16_t initHumidityLen = sizeof(initHumidityValue);
+
+static uint8_t initPressureValue[] = { UINT32_TO_BYTES(0) };
+static uint16_t initPressureLen = sizeof(initPressureValue);
+
+/* Temperature client characteristic configuration */
+static uint8_t essValTempChCcc[] = {UINT16_TO_BYTES(0x0000)};
+static const uint16_t essLenTempChCcc = sizeof(essValTempChCcc);
+
+/* Humidity client characteristic configuration */
+static uint8_t essValHumidityChCcc[] = {UINT16_TO_BYTES(0x0000)};
+static const uint16_t essLenHumidityChCcc = sizeof(essValHumidityChCcc);
+
+/* Pressure client characteristic configuration */
+static uint8_t essValPressureChCcc[] = {UINT16_TO_BYTES(0x0000)};
+static const uint16_t essLenPressureChCcc = sizeof(essValPressureChCcc);
+
+/* clang-format on */
+
+/*
+ * BLE service description
+ */
+
+static const attsAttr_t ESSSvcAttrList[] = {
+	{
+		.pUuid       = attPrimSvcUuid,
+		.pValue      = (uint8_t *)UUID_svc,
+		.pLen        = (uint16_t *)&UUID_svc_len,
+		.maxLen      = sizeof(UUID_svc),
+		.permissions = ATTS_PERMIT_READ,
+	},
+
+	// Temperature
+	{
+		.pUuid       = attChUuid,
+		.pValue      = (uint8_t *)UUID_char_temperature,
+		.pLen        = (uint16_t *)&UUID_char_len,
+		.maxLen      = sizeof(UUID_char_temperature),
+		.permissions = ATTS_PERMIT_READ,
+	},
+	{
+		.pUuid       = attTemperatureChUuid,
+		.pValue      = initTemperatureValue,
+		.pLen        = &initTemperatureLen,
+		.maxLen      = sizeof(initTemperatureValue),
+		.settings    = ATTS_SET_READ_CBACK,
+		.permissions = ATTS_PERMIT_READ | ATTS_PERMIT_READ_ENC |
+			       ATTS_PERMIT_READ_AUTH,
+	},
+	/* Characteristic CCC descriptor */
+	{
+		.pUuid    = attCliChCfgUuid,
+		.pValue   = essValTempChCcc,
+		.pLen     = (uint16_t *)&essLenTempChCcc,
+		.maxLen   = sizeof(essValTempChCcc),
+		.settings = ATTS_SET_CCC,
+		.permissions =
+			(ATTS_PERMIT_READ |
+			 ATTS_PERMIT_WRITE) // How about security?
+	},
+
+	// Humidity
+	{
+		.pUuid       = attChUuid,
+		.pValue      = (uint8_t *)UUID_char_humidity,
+		.pLen        = (uint16_t *)&UUID_char_len,
+		.maxLen      = sizeof(UUID_char_humidity),
+		.permissions = ATTS_PERMIT_READ,
+	},
+	{
+		.pUuid       = attHumidityChUuid,
+		.pValue      = initHumidityValue,
+		.pLen        = &initHumidityLen,
+		.maxLen      = sizeof(initHumidityValue),
+		.settings    = ATTS_SET_READ_CBACK,
+		.permissions = ATTS_PERMIT_READ | ATTS_PERMIT_READ_ENC |
+			       ATTS_PERMIT_READ_AUTH,
+	},
+	/* Characteristic CCC descriptor */
+	{
+		.pUuid    = attCliChCfgUuid,
+		.pValue   = essValHumidityChCcc,
+		.pLen     = (uint16_t *)&essLenHumidityChCcc,
+		.maxLen   = sizeof(essValHumidityChCcc),
+		.settings = ATTS_SET_CCC,
+		.permissions =
+			(ATTS_PERMIT_READ |
+			 ATTS_PERMIT_WRITE) // How about security?
+	},
+
+	// Pressure
+	{
+		.pUuid       = attChUuid,
+		.pValue      = (uint8_t *)UUID_char_pressure,
+		.pLen        = (uint16_t *)&UUID_char_len,
+		.maxLen      = sizeof(UUID_char_pressure),
+		.permissions = ATTS_PERMIT_READ,
+	},
+	{
+		.pUuid       = attPressureChUuid,
+		.pValue      = initPressureValue,
+		.pLen        = &initPressureLen,
+		.maxLen      = sizeof(initPressureValue),
+		.settings    = ATTS_SET_READ_CBACK,
+		.permissions = ATTS_PERMIT_READ | ATTS_PERMIT_READ_ENC |
+			       ATTS_PERMIT_READ_AUTH,
+	},
+	/* Characteristic CCC descriptor */
+	{
+		.pUuid    = attCliChCfgUuid,
+		.pValue   = essValPressureChCcc,
+		.pLen     = (uint16_t *)&essLenPressureChCcc,
+		.maxLen   = sizeof(essValPressureChCcc),
+		.settings = ATTS_SET_CCC,
+		.permissions =
+			(ATTS_PERMIT_READ |
+			 ATTS_PERMIT_WRITE) // How about security?
+	},
+
+};
+
+// validating that the service really has all charateristics
+WSF_CT_ASSERT(
+	((sizeof(ESSSvcAttrList) / sizeof(ESSSvcAttrList[0])) ==
+	 ESS_END_HDL - ESS_START_HDL + 1));
+
+static TimerHandle_t poll_timer;
+static StaticTimer_t poll_timer_buffer;
+static void bleESS_update(struct bme680_sensor_data *data);
+
+static void workpoll(void *data)
+{
+	struct bme680_sensor_data sensor_data;
+	if (epic_bme680_read_sensors(&sensor_data) == 0) {
+		bleESS_update(&sensor_data);
+	}
+}
+
+static void poll(TimerHandle_t xTimer)
+{
+	workqueue_schedule(workpoll, NULL);
+}
+
+static void periodic(int period)
+{
+	if (poll_timer == NULL) {
+		poll_timer = xTimerCreateStatic(
+			"bme680_poll",
+			1,
+			pdTRUE,
+			NULL,
+			poll,
+			&poll_timer_buffer
+		);
+	}
+
+	if (period < 1) {
+		xTimerStop(poll_timer, 0);
+	} else {
+		xTimerChangePeriod(poll_timer, pdMS_TO_TICKS(period), 0);
+	}
+}
+
+static void
+sendNotification(dmConnId_t connId, uint16_t handle, uint16_t cccidx)
+{
+	if (connId != DM_CONN_ID_NONE) {
+		uint16_t len;
+		uint8_t *value;
+		uint8_t ret = AttsGetAttr(handle, &len, &value);
+		if (ret == ATT_SUCCESS) {
+			if (AttsCccEnabled(connId, cccidx)) {
+				AttsHandleValueNtf(connId, handle, len, value);
+			}
+		}
+	}
+}
+
+static void setAttrFromBME680(struct bme680_sensor_data *data)
+{
+	int16_t temperature;
+	uint16_t humidity;
+	uint32_t pressure;
+
+	temperature = data->temperature * 100;
+	AttsSetAttr(
+		ESS_TEMPERATURE_VAL_HDL,
+		sizeof(temperature),
+		(uint8_t *)&temperature
+	);
+
+	humidity = data->humidity * 100;
+	AttsSetAttr(
+		ESS_HUMIDITY_VAL_HDL, sizeof(humidity), (uint8_t *)&humidity
+	);
+
+	pressure = data->pressure * 1000;
+	AttsSetAttr(
+		ESS_PRESSURE_VAL_HDL, sizeof(pressure), (uint8_t *)&pressure
+	);
+}
+
+/*
+ * BLE ESS read callback.
+ *
+ */
+static uint8_t readESSCB(
+	dmConnId_t connId,
+	uint16_t handle,
+	uint8_t operation,
+	uint16_t offset,
+	attsAttr_t *pAttr
+) {
+	struct bme680_sensor_data data;
+	int ret = epic_bme680_read_sensors(&data);
+	if (ret != 0) {
+		return ATT_ERR_UNDEFINED;
+	}
+	setAttrFromBME680(&data);
+
+	/* Send notifications (if enabled) for the _other_ characteristics. */
+	switch (handle) {
+	case ESS_TEMPERATURE_VAL_HDL:
+		sendNotification(
+			connId, ESS_HUMIDITY_VAL_HDL, BLE_ESS_HUMI_CCC_IDX
+		);
+		sendNotification(
+			connId, ESS_PRESSURE_VAL_HDL, BLE_ESS_PRES_CCC_IDX
+		);
+		APP_TRACE_INFO1("ble-ess: read temperature: %d\n", temperature);
+		return ATT_SUCCESS;
+	case ESS_HUMIDITY_VAL_HDL:
+		sendNotification(
+			connId, ESS_TEMPERATURE_VAL_HDL, BLE_ESS_TEMP_CCC_IDX
+		);
+		sendNotification(
+			connId, ESS_PRESSURE_VAL_HDL, BLE_ESS_PRES_CCC_IDX
+		);
+		APP_TRACE_INFO1("ble-ess: read humidity: %u\n", humidity);
+		return ATT_SUCCESS;
+	case ESS_PRESSURE_VAL_HDL:
+		sendNotification(
+			connId, ESS_TEMPERATURE_VAL_HDL, BLE_ESS_TEMP_CCC_IDX
+		);
+		sendNotification(
+			connId, ESS_HUMIDITY_VAL_HDL, BLE_ESS_HUMI_CCC_IDX
+		);
+		APP_TRACE_INFO1("ble-ess: read pressure: %u\n", pressure);
+		return ATT_SUCCESS;
+	default:
+		APP_TRACE_INFO0("ble-card10: read no handler found\n");
+		return ATT_ERR_HANDLE;
+	}
+}
+
+static attsGroup_t svcESSGroup = {
+	.pNext       = NULL,
+	.pAttr       = (attsAttr_t *)ESSSvcAttrList,
+	.readCback   = readESSCB,
+	.writeCback  = NULL,
+	.startHandle = ESS_START_HDL,
+	.endHandle   = ESS_END_HDL,
+};
+
+static void bleESS_update(struct bme680_sensor_data *data)
+{
+	setAttrFromBME680(data);
+
+	/* Send notifications (if enabled) for all characteristics. */
+	dmConnId_t connId = AppConnIsOpen();
+	sendNotification(connId, ESS_TEMPERATURE_VAL_HDL, BLE_ESS_TEMP_CCC_IDX);
+	sendNotification(connId, ESS_HUMIDITY_VAL_HDL, BLE_ESS_HUMI_CCC_IDX);
+	sendNotification(connId, ESS_PRESSURE_VAL_HDL, BLE_ESS_PRES_CCC_IDX);
+}
+
+/*
+ * This registers and starts the ESS service.
+ */
+void bleESS_init(void)
+{
+	AttsAddGroup(&svcESSGroup);
+}
+
+/*
+ * Instruct the ESS service to check if any characterstics have
+ * notifications enabled and enable/disable periodic measurements.
+ */
+void bleESS_ccc_update(void)
+{
+	dmConnId_t connId = AppConnIsOpen();
+	if (connId != DM_CONN_ID_NONE &&
+	    (AttsCccEnabled(connId, BLE_ESS_TEMP_CCC_IDX) ||
+	     AttsCccEnabled(connId, BLE_ESS_HUMI_CCC_IDX) ||
+	     AttsCccEnabled(connId, BLE_ESS_PRES_CCC_IDX))) {
+		LOG_INFO("ess", "enable periodic measurement");
+		periodic(3000);
+	} else {
+		LOG_INFO("ess", "disable periodic measurement");
+		periodic(0);
+	}
+}
diff --git a/epicardium/ble/ess.h b/epicardium/ble/ess.h
new file mode 100644
index 0000000000000000000000000000000000000000..3f59edf8841eed21d6d57845c2a9d94f485e409d
--- /dev/null
+++ b/epicardium/ble/ess.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "epicardium.h"
+
+/*!< \brief Service start handle. */
+#define ESS_START_HDL 0x1000
+/*!< \brief Service end handle. */
+#define ESS_END_HDL (ESS_MAX_HDL - 1)
+
+enum {
+	/*!< \brief environmental sensing services service declaration */
+	ESS_SVC_HDL = ESS_START_HDL,
+	/*!< \brief temperature characteristic */
+	ESS_TEMPERATURE_CH_HDL,
+	ESS_TEMPERATURE_VAL_HDL,
+	ESS_TEMP_CH_CCC_HDL,                  /*!< Temperature CCCD */
+	/*!< \brief humidity characteristic */
+	ESS_HUMIDITY_CH_HDL,
+	ESS_HUMIDITY_VAL_HDL,
+	ESS_HUMI_CH_CCC_HDL,                  /*!< Humidity CCCD */
+	/*!< \brief pressure characteristic */
+	ESS_PRESSURE_CH_HDL,
+	ESS_PRESSURE_VAL_HDL,
+	ESS_PRES_CH_CCC_HDL,                  /*!< Pressure CCCD */
+
+
+	/*!< \brief Maximum handle. */
+	ESS_MAX_HDL
+};
+
+void bleESS_ccc_update(void);
diff --git a/epicardium/ble/meson.build b/epicardium/ble/meson.build
index 7da7925fc1a6c9a59d360881838f71a187bbea21..c2f10a12a8976cec3f049d9d8ca4617ef0cb70f9 100644
--- a/epicardium/ble/meson.build
+++ b/epicardium/ble/meson.build
@@ -10,5 +10,6 @@ ble_sources = files(
   'bondings.c',
   'uart.c',
   'card10.c',
+  'ess.c',
   'filetransfer.c',
 )
diff --git a/epicardium/modules/bme680.c b/epicardium/modules/bme680.c
index 8cda3a6e28d52a3dfa92aa33796ae91cb77f25d3..c92ca1abbb43ffcf04f8e82d18d01dd3d584a981 100644
--- a/epicardium/modules/bme680.c
+++ b/epicardium/modules/bme680.c
@@ -35,6 +35,34 @@ static int convert_error(int8_t error)
 	}
 }
 
+static int8_t
+i2c_write(uint8_t addr, uint8_t reg, uint8_t *p_buf, uint16_t size)
+{
+	int8_t ret;
+	hwlock_acquire(HWLOCK_I2C);
+	ret = card10_bosch_i2c_write(addr, reg, p_buf, size);
+	hwlock_release(HWLOCK_I2C);
+	return ret;
+}
+
+static int8_t i2c_read(uint8_t addr, uint8_t reg, uint8_t *p_buf, uint16_t size)
+{
+	int8_t ret;
+	hwlock_acquire(HWLOCK_I2C);
+	ret = card10_bosch_i2c_read(addr, reg, p_buf, size);
+	hwlock_release(HWLOCK_I2C);
+	return ret;
+}
+
+static void delay(uint32_t msec)
+{
+	if (xTaskGetSchedulerState() == taskSCHEDULER_NOT_STARTED) {
+		card10_bosch_delay(msec);
+	} else {
+		vTaskDelay(pdMS_TO_TICKS(msec));
+	}
+}
+
 int epic_bme680_init()
 {
 	int8_t result = BME680_OK;
@@ -43,13 +71,11 @@ int epic_bme680_init()
 		return 0;
 	}
 
-	hwlock_acquire(HWLOCK_I2C);
-
 	bme.dev_id   = BME680_I2C_ADDR_PRIMARY;
 	bme.intf     = BME680_I2C_INTF;
-	bme.read     = card10_bosch_i2c_read;
-	bme.write    = card10_bosch_i2c_write;
-	bme.delay_ms = card10_bosch_delay;
+	bme.read     = i2c_read;
+	bme.write    = i2c_write;
+	bme.delay_ms = delay;
 
 	/*
 	 * amb_temp can be set to 25 prior to configuring the gas sensor
@@ -61,7 +87,7 @@ int epic_bme680_init()
 	result = bme680_init(&bme);
 	if (result != BME680_OK) {
 		LOG_ERR("bme680", "bme680_init error: %d\n", result);
-		goto err;
+		return -convert_error(result);
 	}
 
 	/*
@@ -74,7 +100,7 @@ int epic_bme680_init()
 	bme.tph_sett.os_hum  = BME680_OS_2X;
 	bme.tph_sett.os_pres = BME680_OS_4X;
 	bme.tph_sett.os_temp = BME680_OS_8X;
-	bme.tph_sett.filter  = BME680_FILTER_SIZE_3;
+	bme.tph_sett.filter  = BME680_FILTER_SIZE_0;
 
 	/* Set the remaining gas sensor settings and link the heating profile */
 	bme.gas_sett.run_gas = BME680_ENABLE_GAS_MEAS;
@@ -92,31 +118,33 @@ int epic_bme680_init()
 		LOG_ERR("bme680",
 			"bme680_set_sensor_settings error: %d\n",
 			result);
-		goto err;
+		return -convert_error(result);
 	}
 
 	initialized = true;
-	result      = BME680_OK;
-err:
-	hwlock_release(HWLOCK_I2C);
-	return -convert_error(result);
+	return 0;
 }
 
 int epic_bme680_deinit()
 {
+	/* This is an intentional NO OP to keep the BME680 always initialized.
+	 *
+	 * If it configured to foreced mode, there is no energy consumption
+	 * penalty.
+	 */
+
+#if 0
 	if (!initialized) {
 		return 0;
 	}
 
-	hwlock_acquire(HWLOCK_I2C);
-
 	int8_t result = bme680_soft_reset(&bme);
 	if (result != BME680_OK) {
 		LOG_ERR("bme680", "bme680_soft_reset error: %d\n", result);
 	}
 
-	hwlock_release(HWLOCK_I2C);
 	initialized = false;
+#endif
 	return 0;
 }
 
@@ -129,30 +157,26 @@ int epic_bme680_read_sensors(struct bme680_sensor_data *data)
 		return -EINVAL;
 	}
 
-	hwlock_acquire(HWLOCK_I2C);
-
 	uint16_t profile_dur = 0;
 	bme680_get_profile_dur(&profile_dur, &bme);
 
 	result = bme680_set_sensor_mode(&bme); /* Trigger a measurement */
 	if (result != BME680_OK) {
 		LOG_ERR("bme680", "bme680_set_sensor_mode error: %d\n", result);
-		goto err;
+		return -convert_error(result);
 	}
 
 	/*
 	 * Wait for the measurement to complete.  Release the I2C lock in the
 	 * meantime.
 	 */
-	hwlock_release(HWLOCK_I2C);
 	vTaskDelay(pdMS_TO_TICKS(profile_dur));
-	hwlock_acquire(HWLOCK_I2C);
 
 	struct bme680_field_data raw_data;
 	result = bme680_get_sensor_data(&raw_data, &bme);
 	if (result != BME680_OK) {
 		LOG_ERR("bme680", "bme680_get_sensor_data error: %d\n", result);
-		goto err;
+		return -convert_error(result);
 	}
 
 	data->temperature    = (float)raw_data.temperature / 100.0f;
@@ -160,8 +184,5 @@ int epic_bme680_read_sensors(struct bme680_sensor_data *data)
 	data->pressure       = raw_data.pressure / 100.0f;
 	data->gas_resistance = raw_data.gas_resistance;
 
-	result = BME680_OK;
-err:
-	hwlock_release(HWLOCK_I2C);
-	return -convert_error(result);
+	return 0;
 }
diff --git a/epicardium/modules/hardware.c b/epicardium/modules/hardware.c
index a05c65f38af808b714de9158a71536c77452d482..fb413958ee0e090251e423e3661426c6fe3dfa85 100644
--- a/epicardium/modules/hardware.c
+++ b/epicardium/modules/hardware.c
@@ -194,6 +194,11 @@ int hardware_early_init(void)
 
 	max86150_shut_down();
 
+	/*
+	 * BME680 Sensor
+	 */
+	epic_bme680_init();
+
 	/* Allow user space to trigger interrupts.
 	 * Used for BLE, not sure if needed. */
 	SCB->CCR |= SCB_CCR_USERSETMPEND_Msk;
@@ -276,11 +281,6 @@ int hardware_reset(void)
 	 */
 	epic_bhi160_disable_all_sensors();
 
-	/*
-	 * BME680 Sensor
-	 */
-	epic_bme680_deinit();
-
 	epic_max30001_disable_sensor();
 
 	epic_max86150_disable_sensor();
diff --git a/epicardium/modules/modules.h b/epicardium/modules/modules.h
index ccc8d0650497133bb193fc162fb112c6f364e82e..687d8db7c5b9408d4ba874cce8acdc3626edc510 100644
--- a/epicardium/modules/modules.h
+++ b/epicardium/modules/modules.h
@@ -120,6 +120,9 @@ void disp_forcelock();
 #define BHI160_MUTEX_WAIT_MS          50
 void vBhi160Task(void *pvParameters);
 
+/* ---------- BME680 ------------------------------------------------------- */
+void bme680_periodic(int period);
+
 /* ---------- MAX86150 ----------------------------------------------------- */
 #define MAX86150_MUTEX_WAIT_MS		50
 void vMAX86150Task(void *pvParameters);
diff --git a/epicardium/modules/mutex.c b/epicardium/modules/mutex.c
index 9d58eec080977d93f69f275350a5101a15d4a64b..4fa3eacf93e42a4ba1da5aaada9f660fb0f33b9c 100644
--- a/epicardium/modules/mutex.c
+++ b/epicardium/modules/mutex.c
@@ -23,6 +23,9 @@ void _mutex_create(struct mutex *m, const char *name)
 
 void mutex_lock(struct mutex *m)
 {
+	if (xTaskGetSchedulerState() == taskSCHEDULER_NOT_STARTED) {
+		return;
+	}
 	int ret = xSemaphoreTake(m->_rtos_mutex, portMAX_DELAY);
 
 	/* Ensure locking was actually successful */
@@ -31,12 +34,18 @@ void mutex_lock(struct mutex *m)
 
 bool mutex_trylock(struct mutex *m)
 {
+	if (xTaskGetSchedulerState() == taskSCHEDULER_NOT_STARTED) {
+		return true;
+	}
 	int ret = xSemaphoreTake(m->_rtos_mutex, 0);
 	return ret == pdTRUE;
 }
 
 void mutex_unlock(struct mutex *m)
 {
+	if (xTaskGetSchedulerState() == taskSCHEDULER_NOT_STARTED) {
+		return;
+	}
 	/* Ensure that only the owner can unlock a mutex */
 	assert(mutex_get_owner(m) == xTaskGetCurrentTaskHandle());
 
diff --git a/lib/sdk/Libraries/BTLE/stack/ble-host/include/att_uuid.h b/lib/sdk/Libraries/BTLE/stack/ble-host/include/att_uuid.h
index b51cabdf90253bb654ac4bdb88ae4e6384a75752..a630a14911a880c6690187fe91ecb2dedefdbbaf 100644
--- a/lib/sdk/Libraries/BTLE/stack/ble-host/include/att_uuid.h
+++ b/lib/sdk/Libraries/BTLE/stack/ble-host/include/att_uuid.h
@@ -62,6 +62,7 @@ extern "C" {
 #define ATT_UUID_RUNNING_SPEED_SERVICE      0x1814    /*!< \brief Running Speed Service */
 #define ATT_UUID_CYCLING_SPEED_SERVICE      0x1816    /*!< \brief Cycling Speed Service */
 #define ATT_UUID_CYCLING_POWER_SERVICE      0x1818    /*!< \brief Cycling Power Service */
+#define ATT_UUID_ENVIRONMENTAL_SENSING_SERVICE 0x181A
 #define ATT_UUID_USER_DATA_SERVICE          0x181C    /*!< \brief User Data Service */
 #define ATT_UUID_WEIGHT_SCALE_SERVICE       0x181D    /*!< \brief Weight Scale Service */
 #define ATT_UUID_IP_SUPPORT_SERVICE         0x1820    /*!< \brief IP Support Service */
@@ -213,6 +214,9 @@ extern "C" {
 #define ATT_UUID_CTE_TX_DURATION            0x7F83    /*!< \brief Constant Tone Extension transmit duration */
 #define ATT_UUID_CTE_INTERVAL               0x7F84    /*!< \brief Constant Tone Extension interval */
 #define ATT_UUID_CTE_PHY                    0x7F85    /*!< \brief Constant Tone Extension PHY */
+#define ATT_UUID_PRESSURE                   0x2A6D
+#define ATT_UUID_TEMPERATURE                0x2A6E
+#define ATT_UUID_HUMIDITY                   0x2A6F
 /**@}*/
 
 /** \name GATT Unit UUIDs
@@ -531,6 +535,9 @@ extern const uint8_t attCteTxCntChUuid[ATT_16_UUID_LEN]; /*!< \brief Constant To
 extern const uint8_t attCteTxDurChUuid[ATT_16_UUID_LEN]; /*!< \brief Constant Tone Extension transmit duration */
 extern const uint8_t attCteIntChUuid[ATT_16_UUID_LEN];   /*!< \brief Constant Tone Extension interval */
 extern const uint8_t attCtePhyChUuid[ATT_16_UUID_LEN];   /*!< \brief Constant Tone Extension PHY */
+extern const uint8_t attTemperatureChUuid[ATT_16_UUID_LEN];
+extern const uint8_t attHumidityChUuid[ATT_16_UUID_LEN];
+extern const uint8_t attPressureChUuid[ATT_16_UUID_LEN];
 /**@}*/
 
 /*! \} */    /* STACK_ATT_API */
diff --git a/lib/sdk/Libraries/BTLE/stack/ble-host/sources/stack/att/att_uuid.c b/lib/sdk/Libraries/BTLE/stack/ble-host/sources/stack/att/att_uuid.c
index d34e556fd143446b2441590135eb2a2fd239ecee..53296d0b5c5a8be9a69449b0a1730bfd4f0a3734 100644
--- a/lib/sdk/Libraries/BTLE/stack/ble-host/sources/stack/att/att_uuid.c
+++ b/lib/sdk/Libraries/BTLE/stack/ble-host/sources/stack/att/att_uuid.c
@@ -186,3 +186,6 @@ const uint8_t attCteTxCntChUuid[ATT_16_UUID_LEN] = {UINT16_TO_BYTES(ATT_UUID_CTE
 const uint8_t attCteTxDurChUuid[ATT_16_UUID_LEN] = {UINT16_TO_BYTES(ATT_UUID_CTE_TX_DURATION)};
 const uint8_t attCteIntChUuid[ATT_16_UUID_LEN] =   {UINT16_TO_BYTES(ATT_UUID_CTE_INTERVAL)};
 const uint8_t attCtePhyChUuid[ATT_16_UUID_LEN] =   {UINT16_TO_BYTES(ATT_UUID_CTE_PHY)};
+const uint8_t attTemperatureChUuid[ATT_16_UUID_LEN] = {UINT16_TO_BYTES(ATT_UUID_TEMPERATURE)};
+const uint8_t attHumidityChUuid[ATT_16_UUID_LEN] = {UINT16_TO_BYTES(ATT_UUID_HUMIDITY)};
+const uint8_t attPressureChUuid[ATT_16_UUID_LEN] = {UINT16_TO_BYTES(ATT_UUID_PRESSURE)};