diff --git a/applications/external/unitemp/Sensors.c b/applications/external/unitemp/Sensors.c
index ae90ce4d5..f96401ea2 100644
--- a/applications/external/unitemp/Sensors.c
+++ b/applications/external/unitemp/Sensors.c
@@ -79,7 +79,7 @@ static const SensorType* sensorTypes[] = {&DHT11, &DHT12_SW, &DHT20, &DHT
&Dallas, &AM2320_SW, &AM2320_I2C, &HTU21x, &AHT10,
&SHT30, &GXHT30, &LM75, &HDC1080, &BMP180,
&BMP280, &BME280, &BME680, &MAX31855, &MAX6675,
- &SCD30};
+ &SCD30, &SCD40};
const SensorType* unitemp_sensors_getTypeFromInt(uint8_t index) {
if(index > SENSOR_TYPES_COUNT) return NULL;
diff --git a/applications/external/unitemp/interfaces/endianness.h b/applications/external/unitemp/interfaces/endianness.h
new file mode 100644
index 000000000..c4a3f4b87
--- /dev/null
+++ b/applications/external/unitemp/interfaces/endianness.h
@@ -0,0 +1,60 @@
+//
+// Created by Avilov Vasily on 10.06.2023.
+//
+
+#ifndef FLIPPERZERO_FIRMWARE_ENDIANNESS_H
+#define FLIPPERZERO_FIRMWARE_ENDIANNESS_H
+
+inline static void store16(uint8_t* b, uint16_t i) {
+ memcpy(b, &i, 2);
+}
+
+inline static void store32(uint8_t* b, uint32_t i) {
+ memcpy(b, &i, 4);
+}
+
+inline static uint16_t load16(uint8_t* b) {
+ uint16_t x;
+ memcpy(&x, b, 2);
+ return x;
+}
+
+inline static uint32_t load32(uint8_t* b) {
+ uint32_t x;
+ memcpy(&x, b, 4);
+ return x;
+}
+
+#if BYTE_ORDER == BIG_ENDIAN
+#define htobe16(x) (x)
+#define htobe32(x) (x)
+#define htole16(x) __builtin_bswap16(x)
+#define htole32(x) __builtin_bswap32(x)
+#define be16toh(x) (x)
+#define be32toh(x) (x)
+#define le16toh(x) __builtin_bswap16(x)
+#define le32toh(x) __builtin_bswap32(x)
+#elif BYTE_ORDER == LITTLE_ENDIAN
+#define htobe16(x) __builtin_bswap16(x)
+#define htobe32(x) __builtin_bswap32(x)
+#define htole16(x) (x)
+#define htole32(x) (x)
+#define be16toh(x) __builtin_bswap16(x)
+#define be32toh(x) __builtin_bswap32(x)
+#define le16toh(x) (x)
+#define le32toh(x) (x)
+#else
+#error "What kind of system is this?"
+#endif
+
+#define load16_le(b) (le16toh(load16(b)))
+#define load32_le(b) (le32toh(load32(b)))
+#define store16_le(b, i) (store16(b, htole16(i)))
+#define store32_le(b, i) (store32(b, htole32(i)))
+
+#define load16_be(b) (be16toh(load16(b)))
+#define load32_be(b) (be32toh(load32(b)))
+#define store16_be(b, i) (store16(b, htobe16(i)))
+#define store32_be(b, i) (store32(b, htobe32(i)))
+
+#endif //FLIPPERZERO_FIRMWARE_ENDIANNESS_H
diff --git a/applications/external/unitemp/sensors/SCD30.c b/applications/external/unitemp/sensors/SCD30.c
index 627130da7..d7a10149c 100644
--- a/applications/external/unitemp/sensors/SCD30.c
+++ b/applications/external/unitemp/sensors/SCD30.c
@@ -21,60 +21,9 @@
#include "SCD30.h"
#include "../interfaces/I2CSensor.h"
+#include "../interfaces/endianness.h"
//#include <3rdparty/everest/include/everest/kremlin/c_endianness.h>
-inline static uint16_t load16(uint8_t* b) {
- uint16_t x;
- memcpy(&x, b, 2);
- return x;
-}
-
-inline static uint32_t load32(uint8_t* b) {
- uint32_t x;
- memcpy(&x, b, 4);
- return x;
-}
-
-inline static void store16(uint8_t* b, uint16_t i) {
- memcpy(b, &i, 2);
-}
-
-inline static void store32(uint8_t* b, uint32_t i) {
- memcpy(b, &i, 4);
-}
-
-#if BYTE_ORDER == BIG_ENDIAN
-#define htobe16(x) (x)
-#define htobe32(x) (x)
-#define htole16(x) __builtin_bswap16(x)
-#define htole32(x) __builtin_bswap32(x)
-#define be16toh(x) (x)
-#define be32toh(x) (x)
-#define le16toh(x) __builtin_bswap16(x)
-#define le32toh(x) __builtin_bswap32(x)
-#elif BYTE_ORDER == LITTLE_ENDIAN
-#define htobe16(x) __builtin_bswap16(x)
-#define htobe32(x) __builtin_bswap32(x)
-#define htole16(x) (x)
-#define htole32(x) (x)
-#define be16toh(x) __builtin_bswap16(x)
-#define be32toh(x) __builtin_bswap32(x)
-#define le16toh(x) (x)
-#define le32toh(x) (x)
-#else
-#error "What kind of system is this?"
-#endif
-
-#define load16_le(b) (le16toh(load16(b)))
-#define load32_le(b) (le32toh(load32(b)))
-#define store16_le(b, i) (store16(b, htole16(i)))
-#define store32_le(b, i) (store32(b, htole32(i)))
-
-#define load16_be(b) (be16toh(load16(b)))
-#define load32_be(b) (be32toh(load32(b)))
-#define store16_be(b, i) (store16(b, htobe16(i)))
-#define store32_be(b, i) (store32(b, htobe32(i)))
-
typedef union {
uint16_t array16[2];
uint8_t array8[4];
diff --git a/applications/external/unitemp/sensors/SCD40.c b/applications/external/unitemp/sensors/SCD40.c
new file mode 100644
index 000000000..c88943a00
--- /dev/null
+++ b/applications/external/unitemp/sensors/SCD40.c
@@ -0,0 +1,291 @@
+/*
+ Unitemp - Universal temperature reader
+ Copyright (C) 2022-2023 Victor Nikitchuk (https://github.com/quen0n)
+ Contributed by divinebird (https://github.com/divinebird)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+// Some information may be seen on https://github.com/sparkfun/SparkFun_SCD30_Arduino_Library
+
+#include "SCD30.h"
+#include "../interfaces/I2CSensor.h"
+#include "../interfaces/endianness.h"
+//#include <3rdparty/everest/include/everest/kremlin/c_endianness.h>
+
+bool unitemp_SCD40_alloc(Sensor* sensor, char* args);
+bool unitemp_SCD40_init(Sensor* sensor);
+bool unitemp_SCD40_deinit(Sensor* sensor);
+UnitempStatus unitemp_SCD40_update(Sensor* sensor);
+bool unitemp_SCD40_free(Sensor* sensor);
+
+const SensorType SCD40 = {
+ .typename = "SCD40",
+ .interface = &I2C,
+ .datatype = UT_DATA_TYPE_TEMP_HUM_CO2,
+ .pollingInterval = 5000,
+ .allocator = unitemp_SCD40_alloc,
+ .mem_releaser = unitemp_SCD40_free,
+ .initializer = unitemp_SCD40_init,
+ .deinitializer = unitemp_SCD40_deinit,
+ .updater = unitemp_SCD40_update};
+
+#define SCD40_ID 0x62
+
+#define COMMAND_START_PERIODIC_MEASUREMENT 0X21B1
+#define COMMAND_READ_MEASUREMENT 0XEC05
+#define COMMAND_STOP_PERIODIC_MEASUREMENT 0X3F86
+
+#define COMMAND_PERSIST_SETTINGS 0X3615
+#define COMMAND_GET_SERIAL_NUMBER 0X3682
+#define COMMAND_PERFORM_SELF_TEST 0X3639
+#define COMMAND_PERFORM_FACTORY_RESET 0X3632
+#define COMMAND_REINIT 0X3646
+
+#define COMMAND_SET_TEMPERATURE_OFFSET 0X241D
+#define COMMAND_GET_TEMPERATURE_OFFSET 0X2318
+#define COMMAND_SET_SENSOR_ALTITUDE 0X2427
+#define COMMAND_GET_SENSOR_ALTITUDE 0X2322
+#define COMMAND_SET_AMBIENT_PRESSURE 0XE000
+#define COMMAND_PERFORM_FORCED_RECALIBRATION 0X362F
+#define COMMAND_SET_AUTOMATIC_SELF_CALIBRATION_ENABLED 0X2416
+#define COMMAND_GET_AUTOMATIC_SELF_CALIBRATION_ENABLED 0X2313
+
+static bool readMeasurement(Sensor* sensor) __attribute__((unused));
+static void reset(Sensor* sensor) __attribute__((unused));
+
+static bool setAutoSelfCalibration(Sensor* sensor, bool enable) __attribute__((unused));
+static bool getAutoSelfCalibration(Sensor* sensor) __attribute__((unused));
+
+static bool getFirmwareVersion(Sensor* sensor, uint16_t* val) __attribute__((unused));
+
+static float getTemperatureOffset(Sensor* sensor) __attribute__((unused));
+static bool setTemperatureOffset(Sensor* sensor, float tempOffset) __attribute__((unused));
+
+static bool beginMeasuring(Sensor* sensor) __attribute__((unused));
+static bool stopMeasurement(Sensor* sensor) __attribute__((unused));
+
+bool unitemp_SCD40_alloc(Sensor* sensor, char* args) {
+ UNUSED(args);
+ I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
+
+ i2c_sensor->minI2CAdr = SCD40_ID << 1;
+ i2c_sensor->maxI2CAdr = SCD40_ID << 1;
+ return true;
+}
+
+bool unitemp_SCD40_free(Sensor* sensor) {
+ //Нечего высвобождать, так как ничего не было выделено
+ UNUSED(sensor);
+ return true;
+}
+
+bool unitemp_SCD40_init(Sensor* sensor) {
+ return beginMeasuring(sensor);
+}
+
+bool unitemp_SCD40_deinit(Sensor* sensor) {
+ return stopMeasurement(sensor);
+}
+
+UnitempStatus unitemp_SCD40_update(Sensor* sensor) {
+ readMeasurement(sensor);
+ return UT_SENSORSTATUS_OK;
+}
+
+#define CRC8_POLYNOMIAL 0x31
+#define CRC8_INIT 0xFF
+
+static uint8_t computeCRC8(uint8_t* message, uint8_t len) {
+ uint8_t crc = CRC8_INIT; // Init with 0xFF
+ for(uint8_t x = 0; x < len; x++) {
+ crc ^= message[x]; // XOR-in the next input byte
+ for(uint8_t i = 0; i < 8; i++) {
+ if((crc & 0x80) != 0)
+ crc = (uint8_t)((crc << 1) ^ CRC8_POLYNOMIAL);
+ else
+ crc <<= 1;
+ }
+ }
+ return crc; // No output reflection
+}
+
+// Sends a command along with arguments and CRC
+static bool sendCommandWithCRC(Sensor* sensor, uint16_t command, uint16_t arguments) {
+ static const uint8_t cmdSize = 5;
+
+ uint8_t bytes[cmdSize];
+ uint8_t* pointer = bytes;
+ store16_be(pointer, command);
+ pointer += 2;
+ uint8_t* argPos = pointer;
+ store16_be(pointer, arguments);
+ pointer += 2;
+ *pointer = computeCRC8(argPos, pointer - argPos);
+
+ I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
+ return unitemp_i2c_writeArray(i2c_sensor, cmdSize, bytes);
+}
+
+// Sends just a command, no arguments, no CRC
+static bool sendCommand(Sensor* sensor, uint16_t command) {
+ static const uint8_t cmdSize = 2;
+
+ uint8_t bytes[cmdSize];
+ store16_be(bytes, command);
+
+ I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
+ return unitemp_i2c_writeArray(i2c_sensor, cmdSize, bytes);
+}
+
+static uint16_t readRegister(Sensor* sensor, uint16_t registerAddress) {
+ static const uint8_t regSize = 2;
+
+ if(!sendCommand(sensor, registerAddress)) return 0; // Sensor did not ACK
+
+ furi_delay_ms(3);
+
+ uint8_t bytes[regSize];
+ I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
+ if(!unitemp_i2c_readArray(i2c_sensor, regSize, bytes)) return 0;
+
+ return load16_be(bytes);
+}
+
+static bool loadWord(uint8_t* buff, uint16_t* val) {
+ uint16_t tmp = load16_be(buff);
+ uint8_t expectedCRC = computeCRC8(buff, 2);
+ if(buff[2] != expectedCRC) return false;
+ *val = tmp;
+ return true;
+}
+
+static bool getSettingValue(Sensor* sensor, uint16_t registerAddress, uint16_t* val) {
+ static const uint8_t respSize = 3;
+
+ if(!sendCommand(sensor, registerAddress)) return false; // Sensor did not ACK
+
+ furi_delay_ms(3);
+
+ uint8_t bytes[respSize];
+ I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
+ if(!unitemp_i2c_readArray(i2c_sensor, respSize, bytes)) return false;
+
+ return loadWord(bytes, val);
+}
+
+// Get 18 bytes from SCD30
+// Updates global variables with floats
+// Returns true if success
+static bool readMeasurement(Sensor* sensor) {
+ if(!sendCommand(sensor, COMMAND_READ_MEASUREMENT)) {
+ FURI_LOG_E(APP_NAME, "Sensor did not ACK");
+ return false; // Sensor did not ACK
+ }
+
+ furi_delay_ms(3);
+
+ static const uint8_t respSize = 9;
+ uint8_t buff[respSize];
+ uint8_t* bytes = buff;
+ I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
+ if(!unitemp_i2c_readArray(i2c_sensor, respSize, bytes)) {
+ FURI_LOG_E(APP_NAME, "Error while read measures");
+ return false;
+ }
+
+ uint16_t tmpValue;
+
+ bool error = false;
+ if(loadWord(bytes, &tmpValue)) {
+ sensor->co2 = tmpValue;
+ } else {
+ FURI_LOG_E(APP_NAME, "Error while parsing CO2");
+ error = true;
+ }
+
+ bytes += 3;
+ if(loadWord(bytes, &tmpValue)) {
+ sensor->temp = (float)tmpValue * 175.0f / 65535.0f - 45.0f;
+ } else {
+ FURI_LOG_E(APP_NAME, "Error while parsing temp");
+ error = true;
+ }
+
+ bytes += 3;
+ if(loadWord(bytes, &tmpValue)) {
+ sensor->hum = (float)tmpValue * 100.0f / 65535.0f;
+ } else {
+ FURI_LOG_E(APP_NAME, "Error while parsing humidity");
+ error = true;
+ }
+
+ return !error;
+}
+
+static void reset(Sensor* sensor) {
+ sendCommand(sensor, COMMAND_REINIT);
+}
+
+static bool setAutoSelfCalibration(Sensor* sensor, bool enable) {
+ return sendCommandWithCRC(
+ sensor, COMMAND_SET_AUTOMATIC_SELF_CALIBRATION_ENABLED, enable); // Activate continuous ASC
+}
+
+// Get the current ASC setting
+static bool getAutoSelfCalibration(Sensor* sensor) {
+ return 1 == readRegister(sensor, COMMAND_GET_AUTOMATIC_SELF_CALIBRATION_ENABLED);
+}
+
+// Unfinished
+static bool getFirmwareVersion(Sensor* sensor, uint16_t* val) {
+ if(!sendCommand(sensor, COMMAND_READ_MEASUREMENT)) {
+ FURI_LOG_E(APP_NAME, "Sensor did not ACK");
+ return false; // Sensor did not ACK
+ }
+
+ static const uint8_t respSize = 9;
+ uint8_t buff[respSize];
+ uint8_t* bytes = buff;
+ I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
+ if(!unitemp_i2c_readArray(i2c_sensor, respSize, bytes)) {
+ FURI_LOG_E(APP_NAME, "Error while read measures");
+ return false;
+ }
+
+ *val = 0;
+
+ return true;
+}
+
+static bool beginMeasuring(Sensor* sensor) {
+ return sendCommand(sensor, COMMAND_START_PERIODIC_MEASUREMENT);
+}
+
+// Stop continuous measurement
+static bool stopMeasurement(Sensor* sensor) {
+ return sendCommand(sensor, COMMAND_READ_MEASUREMENT);
+}
+
+static float getTemperatureOffset(Sensor* sensor) {
+ uint16_t curOffset;
+ if(!getSettingValue(sensor, COMMAND_GET_TEMPERATURE_OFFSET, &curOffset)) return 0.0;
+ return (float)curOffset * 175.0f / 65536.0f;
+}
+
+static bool setTemperatureOffset(Sensor* sensor, float tempOffset) {
+ uint16_t newOffset = tempOffset * 65536.0 / 175.0 + 0.5f;
+ return sendCommandWithCRC(
+ sensor, COMMAND_SET_TEMPERATURE_OFFSET, newOffset); // Activate continuous ASC
+}
diff --git a/applications/external/unitemp/sensors/SCD40.h b/applications/external/unitemp/sensors/SCD40.h
new file mode 100644
index 000000000..5cf7a4324
--- /dev/null
+++ b/applications/external/unitemp/sensors/SCD40.h
@@ -0,0 +1,59 @@
+/*
+ Unitemp - Universal temperature reader
+ Copyright (C) 2022-2023 Victor Nikitchuk (https://github.com/quen0n)
+ Contributed by divinebird (https://github.com/divinebird)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+#ifndef UNITEMP_SCD40
+#define UNITEMP_SCD40
+
+#include "../unitemp.h"
+#include "../Sensors.h"
+
+extern const SensorType SCD40;
+/**
+ * @brief Выделение памяти и установка начальных значений датчика SCD40
+ * @param sensor Указатель на создаваемый датчик
+ * @return Истина при успехе
+ */
+bool unitemp_SCD40_alloc(Sensor* sensor, char* args);
+
+/**
+ * @brief Инициализации датчика SCD40
+ * @param sensor Указатель на датчик
+ * @return Истина если инициализация упспешная
+ */
+bool unitemp_SCD40_init(Sensor* sensor);
+
+/**
+ * @brief Деинициализация датчика
+ * @param sensor Указатель на датчик
+ */
+bool unitemp_SCD40_deinit(Sensor* sensor);
+
+/**
+ * @brief Обновление значений из датчика
+ * @param sensor Указатель на датчик
+ * @return Статус опроса датчика
+ */
+UnitempStatus unitemp_SCD40_update(Sensor* sensor);
+
+/**
+ * @brief Высвободить память датчика
+ * @param sensor Указатель на датчик
+ */
+bool unitemp_SCD40_free(Sensor* sensor);
+
+#endif
\ No newline at end of file