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("*"):