diff --git a/applications/external/protoview/signal_file.c b/applications/external/protoview/signal_file.c
index c60a6a181..886573a06 100644
--- a/applications/external/protoview/signal_file.c
+++ b/applications/external/protoview/signal_file.c
@@ -48,8 +48,9 @@ bool save_signal(ProtoViewApp* app, const char* filename) {
for(int j = 0; regs[j]; j += 2) {
furi_string_cat_printf(custom, "%02X %02X ", (int)regs[j], (int)regs[j + 1]);
}
- size_t len = furi_string_size(file_content);
- furi_string_set_char(custom, len - 1, '\n');
+ //size_t len = furi_string_size(file_content);
+ //furi_string_set_char(custom, len - 1, '\n');
+ furi_string_cat(custom, "\n");
furi_string_cat(file_content, custom);
furi_string_free(custom);
}
diff --git a/applications/external/unitemp/Sensors.c b/applications/external/unitemp/Sensors.c
index 666438bfa..ae90ce4d5 100644
--- a/applications/external/unitemp/Sensors.c
+++ b/applications/external/unitemp/Sensors.c
@@ -78,7 +78,8 @@ const Interface SPI = {
static const SensorType* sensorTypes[] = {&DHT11, &DHT12_SW, &DHT20, &DHT21, &DHT22,
&Dallas, &AM2320_SW, &AM2320_I2C, &HTU21x, &AHT10,
&SHT30, &GXHT30, &LM75, &HDC1080, &BMP180,
- &BMP280, &BME280, &BME680, &MAX31855, &MAX6675};
+ &BMP280, &BME280, &BME680, &MAX31855, &MAX6675,
+ &SCD30};
const SensorType* unitemp_sensors_getTypeFromInt(uint8_t index) {
if(index > SENSOR_TYPES_COUNT) return NULL;
diff --git a/applications/external/unitemp/Sensors.h b/applications/external/unitemp/Sensors.h
index d2b7c07af..25b9cb49e 100644
--- a/applications/external/unitemp/Sensors.h
+++ b/applications/external/unitemp/Sensors.h
@@ -24,6 +24,7 @@
#define UT_TEMPERATURE 0b00000001
#define UT_HUMIDITY 0b00000010
#define UT_PRESSURE 0b00000100
+#define UT_CO2 0b00001000
//Статусы опроса датчика
typedef enum {
@@ -31,6 +32,7 @@ typedef enum {
UT_DATA_TYPE_TEMP_HUM = UT_TEMPERATURE | UT_HUMIDITY,
UT_DATA_TYPE_TEMP_PRESS = UT_TEMPERATURE | UT_PRESSURE,
UT_DATA_TYPE_TEMP_HUM_PRESS = UT_TEMPERATURE | UT_HUMIDITY | UT_PRESSURE,
+ UT_DATA_TYPE_TEMP_HUM_CO2 = UT_TEMPERATURE | UT_HUMIDITY | UT_CO2,
} SensorDataType;
//Типы возвращаемых данных
@@ -121,6 +123,8 @@ typedef struct Sensor {
float hum;
//Атмосферное давление
float pressure;
+ // Концентрация CO2
+ float co2;
//Тип датчика
const SensorType* type;
//Статус последнего опроса датчика
@@ -329,4 +333,5 @@ const GPIO*
#include "./sensors/HDC1080.h"
#include "./sensors/MAX31855.h"
#include "./sensors/MAX6675.h"
+#include "./sensors/SCD30.h"
#endif
diff --git a/applications/external/unitemp/assets/co2_11x14.png b/applications/external/unitemp/assets/co2_11x14.png
new file mode 100644
index 000000000..2a2b5e068
Binary files /dev/null and b/applications/external/unitemp/assets/co2_11x14.png differ
diff --git a/applications/external/unitemp/sensors/SCD30.c b/applications/external/unitemp/sensors/SCD30.c
new file mode 100644
index 000000000..627130da7
--- /dev/null
+++ b/applications/external/unitemp/sensors/SCD30.c
@@ -0,0 +1,438 @@
+/*
+ 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 <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];
+ float value;
+} ByteToFl;
+
+bool unitemp_SCD30_alloc(Sensor* sensor, char* args);
+bool unitemp_SCD30_init(Sensor* sensor);
+bool unitemp_SCD30_deinit(Sensor* sensor);
+UnitempStatus unitemp_SCD30_update(Sensor* sensor);
+bool unitemp_SCD30_free(Sensor* sensor);
+
+const SensorType SCD30 = {
+ .typename = "SCD30",
+ .interface = &I2C,
+ .datatype = UT_DATA_TYPE_TEMP_HUM_CO2,
+ .pollingInterval = 2000,
+ .allocator = unitemp_SCD30_alloc,
+ .mem_releaser = unitemp_SCD30_free,
+ .initializer = unitemp_SCD30_init,
+ .deinitializer = unitemp_SCD30_deinit,
+ .updater = unitemp_SCD30_update};
+
+#define SCD30_ID 0x61
+
+#define COMMAND_CONTINUOUS_MEASUREMENT 0x0010
+#define COMMAND_SET_MEASUREMENT_INTERVAL 0x4600
+#define COMMAND_GET_DATA_READY 0x0202
+#define COMMAND_READ_MEASUREMENT 0x0300
+#define COMMAND_AUTOMATIC_SELF_CALIBRATION 0x5306
+#define COMMAND_SET_FORCED_RECALIBRATION_FACTOR 0x5204
+#define COMMAND_SET_TEMPERATURE_OFFSET 0x5403
+#define COMMAND_SET_ALTITUDE_COMPENSATION 0x5102
+#define COMMAND_RESET 0xD304 // Soft reset
+#define COMMAND_STOP_MEAS 0x0104
+#define COMMAND_READ_FW_VER 0xD100
+
+static bool dataAvailable(Sensor* sensor) __attribute__((unused));
+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 bool setForcedRecalibrationFactor(Sensor* sensor, uint16_t concentration)
+ __attribute__((unused));
+static uint16_t getAltitudeCompensation(Sensor* sensor) __attribute__((unused));
+static bool setAltitudeCompensation(Sensor* sensor, uint16_t altitude) __attribute__((unused));
+static bool setAmbientPressure(Sensor* sensor, uint16_t pressure_mbar) __attribute__((unused));
+
+static float getTemperatureOffset(Sensor* sensor) __attribute__((unused));
+static bool setTemperatureOffset(Sensor* sensor, float tempOffset) __attribute__((unused));
+
+static bool beginMeasuringWithSettings(Sensor* sensor, uint16_t pressureOffset)
+ __attribute__((unused));
+static bool beginMeasuring(Sensor* sensor) __attribute__((unused));
+static bool stopMeasurement(Sensor* sensor) __attribute__((unused));
+
+static bool setMeasurementInterval(Sensor* sensor, uint16_t interval) __attribute__((unused));
+static uint16_t getMeasurementInterval(Sensor* sensor) __attribute__((unused));
+
+bool unitemp_SCD30_alloc(Sensor* sensor, char* args) {
+ UNUSED(args);
+ I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
+
+ i2c_sensor->minI2CAdr = SCD30_ID << 1;
+ i2c_sensor->maxI2CAdr = SCD30_ID << 1;
+ return true;
+}
+
+bool unitemp_SCD30_free(Sensor* sensor) {
+ //Нечего высвобождать, так как ничего не было выделено
+ UNUSED(sensor);
+ return true;
+}
+
+bool unitemp_SCD30_init(Sensor* sensor) {
+ if(beginMeasuring(sensor) == true) { // Start continuous measurements
+ setMeasurementInterval(sensor, SCD30.pollingInterval / 1000);
+ setAutoSelfCalibration(sensor, true);
+ setAmbientPressure(sensor, 0);
+ } else
+ return false;
+
+ return true;
+}
+
+bool unitemp_SCD30_deinit(Sensor* sensor) {
+ return stopMeasurement(sensor);
+}
+
+UnitempStatus unitemp_SCD30_update(Sensor* sensor) {
+ readMeasurement(sensor);
+ return UT_SENSORSTATUS_OK;
+}
+
+static uint8_t computeCRC8(uint8_t* message, uint8_t len) {
+ uint8_t crc = 0xFF; // 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) ^ 0x31);
+ 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);
+}
+
+static bool loadFloat(uint8_t* buff, float* val) {
+ // ByteToFl tmp;
+ size_t cntr = 0;
+ uint8_t floatBuff[4];
+ for(size_t i = 0; i < 2; i++) {
+ floatBuff[cntr++] = buff[0];
+ floatBuff[cntr++] = buff[1];
+ uint8_t expectedCRC = computeCRC8(buff, 2);
+ if(buff[2] != expectedCRC) return false;
+ buff += 3;
+ }
+ uint32_t tmpVal = load32_be(floatBuff);
+ memcpy(val, &tmpVal, sizeof(float));
+ return true;
+}
+
+// Get 18 bytes from SCD30
+// Updates global variables with floats
+// Returns true if success
+static bool readMeasurement(Sensor* sensor) {
+ // Verify we have data from the sensor
+ if(!dataAvailable(sensor)) {
+ return false;
+ }
+
+ if(!sendCommand(sensor, COMMAND_READ_MEASUREMENT)) {
+ FURI_LOG_E(APP_NAME, "Sensor did not ACK");
+ return false; // Sensor did not ACK
+ }
+
+ float tempCO2 = 0;
+ float tempHumidity = 0;
+ float tempTemperature = 0;
+
+ furi_delay_ms(3);
+
+ static const uint8_t respSize = 18;
+ 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;
+ }
+
+ bool error = false;
+ if(loadFloat(bytes, &tempCO2)) {
+ sensor->co2 = tempCO2;
+ } else {
+ FURI_LOG_E(APP_NAME, "Error while parsing CO2");
+ error = true;
+ }
+
+ bytes += 6;
+ if(loadFloat(bytes, &tempTemperature)) {
+ sensor->temp = tempTemperature;
+ } else {
+ FURI_LOG_E(APP_NAME, "Error while parsing temp");
+ error = true;
+ }
+
+ bytes += 6;
+ if(loadFloat(bytes, &tempHumidity)) {
+ sensor->hum = tempHumidity;
+ } else {
+ FURI_LOG_E(APP_NAME, "Error while parsing humidity");
+ error = true;
+ }
+
+ return !error;
+}
+
+static void reset(Sensor* sensor) {
+ sendCommand(sensor, COMMAND_RESET);
+}
+
+static bool setAutoSelfCalibration(Sensor* sensor, bool enable) {
+ return sendCommandWithCRC(
+ sensor, COMMAND_AUTOMATIC_SELF_CALIBRATION, enable); // Activate continuous ASC
+}
+
+// Get the current ASC setting
+static bool getAutoSelfCalibration(Sensor* sensor) {
+ return 1 == readRegister(sensor, COMMAND_AUTOMATIC_SELF_CALIBRATION);
+}
+
+static bool getFirmwareVersion(Sensor* sensor, uint16_t* val) {
+ return getSettingValue(sensor, COMMAND_READ_FW_VER, val);
+}
+
+// Set the forced recalibration factor. See 1.3.7.
+// The reference CO2 concentration has to be within the range 400 ppm ≤ cref(CO2) ≤ 2000 ppm.
+static bool setForcedRecalibrationFactor(Sensor* sensor, uint16_t concentration) {
+ if(concentration < 400 || concentration > 2000) {
+ return false; // Error check.
+ }
+ return sendCommandWithCRC(sensor, COMMAND_SET_FORCED_RECALIBRATION_FACTOR, concentration);
+}
+
+// Get the temperature offset. See 1.3.8.
+static float getTemperatureOffset(Sensor* sensor) {
+ union {
+ int16_t signed16;
+ uint16_t unsigned16;
+ } signedUnsigned; // Avoid any ambiguity casting int16_t to uint16_t
+ signedUnsigned.unsigned16 = readRegister(sensor, COMMAND_SET_TEMPERATURE_OFFSET);
+
+ return ((float)signedUnsigned.signed16) / 100.0;
+}
+
+static bool setTemperatureOffset(Sensor* sensor, float tempOffset) {
+ // Temp offset is only positive. See: https://github.com/sparkfun/SparkFun_SCD30_Arduino_Library/issues/27#issuecomment-971986826
+ //"The SCD30 offset temperature is obtained by subtracting the reference temperature from the SCD30 output temperature"
+ // https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/9.5_CO2/Sensirion_CO2_Sensors_SCD30_Low_Power_Mode.pdf
+
+ if(tempOffset < 0.0) return false;
+
+ uint16_t value = tempOffset * 100;
+
+ return sendCommandWithCRC(sensor, COMMAND_SET_TEMPERATURE_OFFSET, value);
+}
+
+// Get the altitude compenstation. See 1.3.9.
+static uint16_t getAltitudeCompensation(Sensor* sensor) {
+ return readRegister(sensor, COMMAND_SET_ALTITUDE_COMPENSATION);
+}
+
+// Set the altitude compenstation. See 1.3.9.
+static bool setAltitudeCompensation(Sensor* sensor, uint16_t altitude) {
+ return sendCommandWithCRC(sensor, COMMAND_SET_ALTITUDE_COMPENSATION, altitude);
+}
+
+// Set the pressure compenstation. This is passed during measurement startup.
+// mbar can be 700 to 1200
+static bool setAmbientPressure(Sensor* sensor, uint16_t pressure_mbar) {
+ if(pressure_mbar != 0 || pressure_mbar < 700 || pressure_mbar > 1200) {
+ return false;
+ }
+ return sendCommandWithCRC(sensor, COMMAND_CONTINUOUS_MEASUREMENT, pressure_mbar);
+}
+
+// Begins continuous measurements
+// Continuous measurement status is saved in non-volatile memory. When the sensor
+// is powered down while continuous measurement mode is active SCD30 will measure
+// continuously after repowering without sending the measurement command.
+// Returns true if successful
+static bool beginMeasuringWithSettings(Sensor* sensor, uint16_t pressureOffset) {
+ return sendCommandWithCRC(sensor, COMMAND_CONTINUOUS_MEASUREMENT, pressureOffset);
+}
+
+// Overload - no pressureOffset
+static bool beginMeasuring(Sensor* sensor) {
+ return beginMeasuringWithSettings(sensor, 0);
+}
+
+// Stop continuous measurement
+static bool stopMeasurement(Sensor* sensor) {
+ return sendCommand(sensor, COMMAND_STOP_MEAS);
+}
+
+// Sets interval between measurements
+// 2 seconds to 1800 seconds (30 minutes)
+static bool setMeasurementInterval(Sensor* sensor, uint16_t interval) {
+ if(interval < 2 || interval > 1800) return false;
+ if(!sendCommandWithCRC(sensor, COMMAND_SET_MEASUREMENT_INTERVAL, interval)) return false;
+ uint16_t verInterval = readRegister(sensor, COMMAND_SET_MEASUREMENT_INTERVAL);
+ if(verInterval != interval) {
+ FURI_LOG_E(APP_NAME, "Measure interval wrong! Val: %02x", verInterval);
+ return false;
+ }
+ return true;
+}
+
+// Gets interval between measurements
+// 2 seconds to 1800 seconds (30 minutes)
+static uint16_t getMeasurementInterval(Sensor* sensor) {
+ uint16_t interval = 0;
+ getSettingValue(sensor, COMMAND_SET_MEASUREMENT_INTERVAL, &interval);
+ return interval;
+}
+
+// Returns true when data is available
+static bool dataAvailable(Sensor* sensor) {
+ return 1 == readRegister(sensor, COMMAND_GET_DATA_READY);
+}
\ No newline at end of file
diff --git a/applications/external/unitemp/sensors/SCD30.h b/applications/external/unitemp/sensors/SCD30.h
new file mode 100644
index 000000000..1ebfbb411
--- /dev/null
+++ b/applications/external/unitemp/sensors/SCD30.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_SCD30
+#define UNITEMP_SCD30
+
+#include "../unitemp.h"
+#include "../Sensors.h"
+
+extern const SensorType SCD30;
+/**
+ * @brief Выделение памяти и установка начальных значений датчика SCD30
+ * @param sensor Указатель на создаваемый датчик
+ * @return Истина при успехе
+ */
+bool unitemp_SCD30_alloc(Sensor* sensor, char* args);
+
+/**
+ * @brief Инициализации датчика SCD30
+ * @param sensor Указатель на датчик
+ * @return Истина если инициализация упспешная
+ */
+bool unitemp_SCD30_init(Sensor* sensor);
+
+/**
+ * @brief Деинициализация датчика
+ * @param sensor Указатель на датчик
+ */
+bool unitemp_SCD30_deinit(Sensor* sensor);
+
+/**
+ * @brief Обновление значений из датчика
+ * @param sensor Указатель на датчик
+ * @return Статус опроса датчика
+ */
+UnitempStatus unitemp_SCD30_update(Sensor* sensor);
+
+/**
+ * @brief Высвободить память датчика
+ * @param sensor Указатель на датчик
+ */
+bool unitemp_SCD30_free(Sensor* sensor);
+
+#endif
\ No newline at end of file
diff --git a/applications/external/unitemp/unitemp.h b/applications/external/unitemp/unitemp.h
index 4d184b2c3..69cd8cf4f 100644
--- a/applications/external/unitemp/unitemp.h
+++ b/applications/external/unitemp/unitemp.h
@@ -40,7 +40,7 @@
//Имя приложения
#define APP_NAME "Unitemp"
//Версия приложения
-#define UNITEMP_APP_VER "1.2"
+#define UNITEMP_APP_VER "1.3"
//Путь хранения файлов плагина
#define APP_PATH_FOLDER "/ext/unitemp"
//Имя файла с настройками
diff --git a/applications/external/unitemp/views/General_view.c b/applications/external/unitemp/views/General_view.c
index 2c8d389bf..22d724935 100644
--- a/applications/external/unitemp/views/General_view.c
+++ b/applications/external/unitemp/views/General_view.c
@@ -162,6 +162,35 @@ static void _draw_pressure(Canvas* canvas, Sensor* sensor) {
}
}
+static void _draw_co2(Canvas* canvas, Sensor* sensor, Color color) {
+ const uint8_t x = 29, y = 39;
+ //Рисование рамки
+ canvas_draw_rframe(canvas, x, y, 75, 20, 3);
+ if(color == ColorBlack) {
+ canvas_draw_rbox(canvas, x, y, 75, 19, 3);
+ canvas_invert_color(canvas);
+ } else {
+ canvas_draw_rframe(canvas, x, y, 75, 19, 3);
+ }
+
+ //Рисование иконки
+ canvas_draw_icon(canvas, x + 3, y + 3, &I_co2_11x14);
+
+ int16_t concentration_int = sensor->co2;
+ // int8_t concentration_dec = (int16_t)(sensor->co2 * 10) % 10;
+
+ //Целая часть
+ if(concentration_int > 9999) {
+ snprintf(app->buff, BUFF_SIZE, "MAX ");
+ canvas_set_font(canvas, FontPrimary);
+ } else {
+ snprintf(app->buff, BUFF_SIZE, "%d", concentration_int);
+ canvas_set_font(canvas, FontBigNumbers);
+ }
+
+ canvas_draw_str_aligned(canvas, x + 70, y + 10, AlignRight, AlignCenter, app->buff);
+}
+
static void _draw_singleSensor(Canvas* canvas, Sensor* sensor, const uint8_t pos[2], Color color) {
canvas_set_font(canvas, FontPrimary);
@@ -320,6 +349,17 @@ static void _draw_carousel_values(Canvas* canvas) {
canvas, unitemp_sensor_getActive(generalview_sensor_index), hum_positions[1]);
_draw_pressure(canvas, unitemp_sensor_getActive(generalview_sensor_index));
break;
+ case UT_DATA_TYPE_TEMP_HUM_CO2:
+ _draw_temperature(
+ canvas,
+ unitemp_sensor_getActive(generalview_sensor_index),
+ temp_positions[2][0],
+ temp_positions[2][1],
+ ColorWhite);
+ _draw_humidity(
+ canvas, unitemp_sensor_getActive(generalview_sensor_index), hum_positions[1]);
+ _draw_co2(canvas, unitemp_sensor_getActive(generalview_sensor_index), ColorWhite);
+ break;
}
}
diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c
index d30bd79f2..97e2e8c34 100644
--- a/applications/main/subghz/subghz.c
+++ b/applications/main/subghz/subghz.c
@@ -211,7 +211,7 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
flipper_format_free(temp_fm_preset3);
- // # HND - FM presets
+ // # HND - FM preset
FlipperFormat* temp_fm_preset4 = flipper_format_string_alloc();
flipper_format_write_string_cstr(
temp_fm_preset4,
@@ -221,16 +221,6 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
subghz_setting_load_custom_preset(setting, (const char*)"HND_1", temp_fm_preset4);
flipper_format_free(temp_fm_preset4);
-
- FlipperFormat* temp_fm_preset5 = flipper_format_string_alloc();
- flipper_format_write_string_cstr(
- temp_fm_preset5,
- (const char*)"Custom_preset_data",
- (const char*)"02 0D 0B 06 08 32 07 04 14 00 13 02 12 07 11 36 10 E9 15 32 18 18 19 16 1D 92 1C 40 1B 03 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00");
- flipper_format_rewind(temp_fm_preset5);
- subghz_setting_load_custom_preset(setting, (const char*)"HND_2", temp_fm_preset5);
-
- flipper_format_free(temp_fm_preset5);
}
// custom presets loading - end
diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py
index eb265cee8..820f5a8c5 100644
--- a/scripts/fbt/appmanifest.py
+++ b/scripts/fbt/appmanifest.py
@@ -1,7 +1,8 @@
import os
+import re
from dataclasses import dataclass, field
from enum import Enum
-from typing import Callable, List, Optional, Tuple, Union
+from typing import Callable, ClassVar, List, Optional, Tuple, Union
class FlipperManifestException(Exception):
@@ -23,6 +24,8 @@ class FlipperAppType(Enum):
@dataclass
class FlipperApplication:
+ APP_ID_REGEX: ClassVar[re.Pattern] = re.compile(r"^[a-z0-9_]+$")
+
@dataclass
class ExternallyBuiltFile:
path: str
@@ -84,6 +87,10 @@ class FlipperApplication:
def __post_init__(self):
if self.apptype == FlipperAppType.PLUGIN:
self.stack_size = 0
+ if not self.APP_ID_REGEX.match(self.appid):
+ raise FlipperManifestException(
+ f"Invalid appid '{self.appid}'. Must match regex '{self.APP_ID_REGEX}'"
+ )
if isinstance(self.fap_version, str):
try:
self.fap_version = tuple(int(v) for v in self.fap_version.split("."))
diff --git a/scripts/map_parser.py b/scripts/map_parser.py
index c0c34e3d1..1efc4fe82 100755
--- a/scripts/map_parser.py
+++ b/scripts/map_parser.py
@@ -3,6 +3,29 @@
# Requiremets:
# cxxfilt==0.3.0
+# Most part of this code written by Lars-Dominik Braun https://github.com/PromyLOPh/linkermapviz
+# and distributes under MIT licence
+
+# Copyright (c) 2017 Lars-Dominik Braun
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
import sys
import re
import os
diff --git a/scripts/ufbt/SConstruct b/scripts/ufbt/SConstruct
index d72de380c..a1acd270a 100644
--- a/scripts/ufbt/SConstruct
+++ b/scripts/ufbt/SConstruct
@@ -75,7 +75,7 @@ from fbt.util import (
wrap_tempfile,
path_as_posix,
)
-from fbt.appmanifest import FlipperAppType
+from fbt.appmanifest import FlipperAppType, FlipperApplication
from fbt.sdk.cache import SdkCache
# Base environment with all tools loaded from SDK
@@ -410,6 +410,12 @@ dist_env.Alias("vscode_dist", vscode_dist)
# Creating app from base template
dist_env.SetDefault(FBT_APPID=appenv.subst("$APPID") or "template")
+if fbt_appid := dist_env.subst("$FBT_APPID"):
+ if not FlipperApplication.APP_ID_REGEX.match(fbt_appid):
+ raise UserError(
+ f"Invalid app id '{fbt_appid}'. App id must match {FlipperApplication.APP_ID_REGEX.pattern}"
+ )
+
app_template_dir = project_template_dir.Dir("app_template")
app_template_dist = []
for template_file in app_template_dir.glob("*"):