Introducción

Entre las capacidades del dispositivo Flipper Zero se encuentra la decodificación de protocolos de radio por debajo de la frecuencias de 1 GHz (Sub-GHz). Una de las aplicaciones disponibles es Weather Station que decodifica señales procedentes de sensores de temperatura y humedad. Debido a la arquitectura de la aplicación y de la naturaleza de código del proyecto es posible añadir nuevos protocolos a la aplicación.

En este caso añadiremos el protocolo TWINS basado en el ThermoPRO-TX4 y el Polaroid basado en el inFactory-TH. También observaremos el método de compilar la aplicación.

Instalación de uFBT y clonado del repositorio

En primer lugar instalaremos la herramienta uFBT (micro Flipper Build Tool), que nos permitirá la compilación de las aplicación de forma individual.

$ python3 -m pip install --upgrade ufbt

A continuación clonaremos el repositorio que contiene la aplicación weather_station, flipperzero-good-faps.

$ git clone https://github.com/flipperdevices/flipperzero-good-faps
$ cd flipperzero-good-faps

Modificación de la aplicación

Accediendo a la carpeta de la aplicación, weather_station, encontramos varias carpetas, los archivos a modificar se encuentran en la carpeta protocols.

$ cd weather_station

Para el protocolo TWINS crearemos los archivos twins.c y twins.h. Para el protocolo Polaroid crearemos los archivos polaroid.c y polaroid.h. Finalmente modificaremos el archivo protocol_items.c y protocol_items.h para especificar los nuevos protocolos. En el archivo protocol_items.c añadiremos los protocolos al final del array weather_station_protocol_registry_items de la siguiente manera:

&ws_protocol_twins,
&ws_protocol_polaroid,

En el archivo protocol_items.h, especificaremos la inclusión de los archivos de cabecera de los protocolos de la siguiente forma:

#include "twins.h"
#include "polaroid.h"

A continuación se muestra el contenido de los demás archivos.

twins.c

#include "twins.h"

#define TAG "WSProtocolTwins"

/*
 * Help (similar protocol to ThermoPRO-TX4)
 * https://github.com/merbanan/rtl_433/blob/master/src/devices/thermopro_tx2.c
 *
 * The sensor sends 36 bits 7 times, before the first packet there is a sync pulse.
 * The packets are ppm modulated (distance coding) with a pulse of ~500 us
 * followed by a short gap of ~2000 us for a 0 bit or a long ~4000 us gap for a
 * 1 bit, the sync gap is ~9000 us.
 * The data is grouped in 9 nibbles
 *     [type] [id0] [id1] [flags] [temp0] [temp1] [temp2] [humi0] [humi1]
 * - type: 4 bit fixed 1001 (9) or 0101 (5)
 * - id: 8 bit a random id that is generated when the sensor starts, could include battery status
 *   the same batteries often generate the same id
 * - flags(3): is 0 when the battery is low, otherwise 1 (ok)
 * - flags(2): is 1 when the sensor sends a reading when pressing the button on the sensor
 * - flags(1,0): the channel number that can be set by the sensor (1, 2, 3, X)
 * - temp: 12 bit signed scaled by 10
 * - humi: 8 bit always 11001100 (0xCC) if no humidity sensor is available
 * 
 */

#define TWINS_TYPE_1 0b1001
#define TWINS_TYPE_2 0b0101

static const SubGhzBlockConst ws_protocol_twins_const = {
    .te_short = 500,
    .te_long = 2000,
    .te_delta = 150,
    .min_count_bit_for_found = 36,
};

struct WSProtocolDecoderTwins {
    SubGhzProtocolDecoderBase base;

    SubGhzBlockDecoder decoder;
    WSBlockGeneric generic;
};

struct WSProtocolEncoderTwins {
    SubGhzProtocolEncoderBase base;

    SubGhzProtocolBlockEncoder encoder;
    WSBlockGeneric generic;
};

typedef enum {
    TwinsDecoderStepReset = 0,
    TwinsDecoderStepSaveDuration,
    TwinsDecoderStepCheckDuration,
} TwinsDecoderStep;

const SubGhzProtocolDecoder ws_protocol_twins_decoder = {
    .alloc = ws_protocol_decoder_twins_alloc,
    .free = ws_protocol_decoder_twins_free,

    .feed = ws_protocol_decoder_twins_feed,
    .reset = ws_protocol_decoder_twins_reset,

    .get_hash_data = ws_protocol_decoder_twins_get_hash_data,
    .serialize = ws_protocol_decoder_twins_serialize,
    .deserialize = ws_protocol_decoder_twins_deserialize,
    .get_string = ws_protocol_decoder_twins_get_string,
};

const SubGhzProtocolEncoder ws_protocol_twins_encoder = {
    .alloc = NULL,
    .free = NULL,

    .deserialize = NULL,
    .stop = NULL,
    .yield = NULL,
};

const SubGhzProtocol ws_protocol_twins = {
    .name = WS_PROTOCOL_TWINS_NAME,
    .type = SubGhzProtocolWeatherStation,
    .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 |
            SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable,

    .decoder = &ws_protocol_twins_decoder,
    .encoder = &ws_protocol_twins_encoder,
};

void* ws_protocol_decoder_twins_alloc(SubGhzEnvironment* environment) {
    UNUSED(environment);
    WSProtocolDecoderTwins* instance = malloc(sizeof(WSProtocolDecoderTwins));
    instance->base.protocol = &ws_protocol_twins;
    instance->generic.protocol_name = instance->base.protocol->name;
    return instance;
}

void ws_protocol_decoder_twins_free(void* context) {
    furi_assert(context);
    WSProtocolDecoderTwins* instance = context;
    free(instance);
}

void ws_protocol_decoder_twins_reset(void* context) {
    furi_assert(context);
    WSProtocolDecoderTwins* instance = context;
    instance->decoder.parser_step = TwinsDecoderStepReset;
}

static bool ws_protocol_twins_check(WSProtocolDecoderTwins* instance) {
    uint8_t type = instance->decoder.decode_data >> 32;

    if((type == TWINS_TYPE_1) || (type == TWINS_TYPE_2)) {
        return true;
    } else {
        return false;
    }
}

/**
 * Analysis of received data
 * @param instance Pointer to a WSBlockGeneric* instance
 */
static void ws_protocol_twins_remote_controller(WSBlockGeneric* instance) {
    instance->id = (instance->data >> 24) & 0xFF;
    instance->battery_low = !((instance->data >> 23) & 1);
    instance->btn = (instance->data >> 22) & 1;
    instance->channel = ((instance->data >> 20) & 0x03) + 1;

    if(!((instance->data >> 19) & 1)) {
        instance->temp = (float)((instance->data >> 8) & 0x07FF) / 10.0f;
    } else {
        instance->temp = (float)((~(instance->data >> 8) & 0x07FF) + 1) / -10.0f;
    }

    instance->humidity = (instance->data) & 0xFF;
}

void ws_protocol_decoder_twins_feed(void* context, bool level, uint32_t duration) {
    furi_assert(context);
    WSProtocolDecoderTwins* instance = context;

    switch(instance->decoder.parser_step) {
    case TwinsDecoderStepReset:
        if((!level) && (DURATION_DIFF(duration, ws_protocol_twins_const.te_short * 18) <
                        ws_protocol_twins_const.te_delta * 10)) {
            //Found sync
            instance->decoder.parser_step = TwinsDecoderStepSaveDuration;
            instance->decoder.decode_data = 0;
            instance->decoder.decode_count_bit = 0;
        }
        break;

    case TwinsDecoderStepSaveDuration:
        if(level) {
            instance->decoder.te_last = duration;
            instance->decoder.parser_step = TwinsDecoderStepCheckDuration;
        } else {
            instance->decoder.parser_step = TwinsDecoderStepReset;
        }
        break;

    case TwinsDecoderStepCheckDuration:
        if(!level) {
            if(DURATION_DIFF(duration, ws_protocol_twins_const.te_short * 18) <
               ws_protocol_twins_const.te_delta * 10) {
                //Found sync
                instance->decoder.parser_step = TwinsDecoderStepReset;
                if((instance->decoder.decode_count_bit ==
                    ws_protocol_twins_const.min_count_bit_for_found) &&
                   ws_protocol_twins_check(instance)) {
                    instance->generic.data = instance->decoder.decode_data;
                    instance->generic.data_count_bit = instance->decoder.decode_count_bit;
                    ws_protocol_twins_remote_controller(&instance->generic);
                    if(instance->base.callback)
                        instance->base.callback(&instance->base, instance->base.context);
                    instance->decoder.parser_step = TwinsDecoderStepCheckDuration;
                }
                instance->decoder.decode_data = 0;
                instance->decoder.decode_count_bit = 0;

                break;
            } else if(
                (DURATION_DIFF(instance->decoder.te_last, ws_protocol_twins_const.te_short) <
                 ws_protocol_twins_const.te_delta) &&
                (DURATION_DIFF(duration, ws_protocol_twins_const.te_long) <
                 ws_protocol_twins_const.te_delta * 2)) {
                subghz_protocol_blocks_add_bit(&instance->decoder, 0);
                instance->decoder.parser_step = TwinsDecoderStepSaveDuration;
            } else if(
                (DURATION_DIFF(instance->decoder.te_last, ws_protocol_twins_const.te_short) <
                 ws_protocol_twins_const.te_delta) &&
                (DURATION_DIFF(duration, ws_protocol_twins_const.te_long * 2) <
                 ws_protocol_twins_const.te_delta * 4)) {
                subghz_protocol_blocks_add_bit(&instance->decoder, 1);
                instance->decoder.parser_step = TwinsDecoderStepSaveDuration;
            } else {
                instance->decoder.parser_step = TwinsDecoderStepReset;
            }
        } else {
            instance->decoder.parser_step = TwinsDecoderStepReset;
        }
        break;
    }
}

uint8_t ws_protocol_decoder_twins_get_hash_data(void* context) {
    furi_assert(context);
    WSProtocolDecoderTwins* instance = context;
    return subghz_protocol_blocks_get_hash_data(
        &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}

SubGhzProtocolStatus ws_protocol_decoder_twins_serialize(
    void* context,
    FlipperFormat* flipper_format,
    SubGhzRadioPreset* preset) {
    furi_assert(context);
    WSProtocolDecoderTwins* instance = context;
    return ws_block_generic_serialize(&instance->generic, flipper_format, preset);
}

SubGhzProtocolStatus
    ws_protocol_decoder_twins_deserialize(void* context, FlipperFormat* flipper_format) {
    furi_assert(context);
    WSProtocolDecoderTwins* instance = context;
    return ws_block_generic_deserialize_check_count_bit(
        &instance->generic, flipper_format, ws_protocol_twins_const.min_count_bit_for_found);
}

void ws_protocol_decoder_twins_get_string(void* context, FuriString* output) {
    furi_assert(context);
    WSProtocolDecoderTwins* instance = context;
    furi_string_printf(
        output,
        "%s %dbit\r\n"
        "Key:0x%lX%08lX\r\n"
        "Sn:0x%lX Ch:%d  Bat:%d\r\n"
        "Temp:%3.1f C Hum:%d%%",
        instance->generic.protocol_name,
        instance->generic.data_count_bit,
        (uint32_t)(instance->generic.data >> 31),
        (uint32_t)(instance->generic.data),
        instance->generic.id,
        instance->generic.channel,
        instance->generic.battery_low,
        (double)instance->generic.temp,
        instance->generic.humidity);
}

twins.h

#pragma once

#include <lib/subghz/protocols/base.h>

#include <lib/subghz/blocks/const.h>
#include <lib/subghz/blocks/decoder.h>
#include <lib/subghz/blocks/encoder.h>
#include "ws_generic.h"
#include <lib/subghz/blocks/math.h>

#define WS_PROTOCOL_TWINS_NAME "Twins"

typedef struct WSProtocolDecoderTwins WSProtocolDecoderTwins;
typedef struct WSProtocolEncoderTwins WSProtocolEncoderTwins;

extern const SubGhzProtocolDecoder ws_protocol_twins_decoder;
extern const SubGhzProtocolEncoder ws_protocol_twins_encoder;
extern const SubGhzProtocol ws_protocol_twins;

/**
 * Allocate WSProtocolDecoderTwins.
 * @param environment Pointer to a SubGhzEnvironment instance
 * @return WSProtocolDecoderTwins* pointer to a WSProtocolDecoderTwins instance
 */
void* ws_protocol_decoder_twins_alloc(SubGhzEnvironment* environment);

/**
 * Free WSProtocolDecoderTwins.
 * @param context Pointer to a WSProtocolDecoderTwins instance
 */
void ws_protocol_decoder_twins_free(void* context);

/**
 * Reset decoder WSProtocolDecoderTwins.
 * @param context Pointer to a WSProtocolDecoderTwins instance
 */
void ws_protocol_decoder_twins_reset(void* context);

/**
 * Parse a raw sequence of levels and durations received from the air.
 * @param context Pointer to a WSProtocolDecoderTwins instance
 * @param level Signal level true-high false-low
 * @param duration Duration of this level in, us
 */
void ws_protocol_decoder_twins_feed(void* context, bool level, uint32_t duration);

/**
 * Getting the hash sum of the last randomly received parcel.
 * @param context Pointer to a WSProtocolDecoderTwins instance
 * @return hash Hash sum
 */
uint8_t ws_protocol_decoder_twins_get_hash_data(void* context);

/**
 * Serialize data WSProtocolDecoderTwins.
 * @param context Pointer to a WSProtocolDecoderTwins instance
 * @param flipper_format Pointer to a FlipperFormat instance
 * @param preset The modulation on which the signal was received, SubGhzRadioPreset
 * @return status
 */
SubGhzProtocolStatus ws_protocol_decoder_twins_serialize(
    void* context,
    FlipperFormat* flipper_format,
    SubGhzRadioPreset* preset);

/**
 * Deserialize data WSProtocolDecoderTwins.
 * @param context Pointer to a WSProtocolDecoderTwins instance
 * @param flipper_format Pointer to a FlipperFormat instance
 * @return status
 */
SubGhzProtocolStatus
    ws_protocol_decoder_twins_deserialize(void* context, FlipperFormat* flipper_format);

/**
 * Getting a textual representation of the received data.
 * @param context Pointer to a WSProtocolDecoderTwins instance
 * @param output Resulting text
 */
void ws_protocol_decoder_twins_get_string(void* context, FuriString* output);

polaroid.c

#include "polaroid.h"

#define TAG "WSProtocolPolaroid"

/*
 * Help
 * https://github.com/merbanan/rtl_433/blob/master/src/devices/polaroid.c
 *
 * Analysis using Genuino (see http://gitlab.com/hp-uno, e.g. uno_log_433):
 * Observed On-Off-Key (OOK) data pattern:
 *     preamble            syncPrefix        data...(40 bit)                        syncPostfix
 *     HHLL HHLL HHLL HHLL HLLLLLLLLLLLLLLLL (HLLLL HLLLLLLLL HLLLL HLLLLLLLL ....) HLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL
 * Breakdown:
 * - four preamble pairs '1'/'0' each with a length of ca. 1000us
 * - syncPre, syncPost, data0, data1 have a '1' start pulse of ca. 500us
 * - syncPre pulse before dataPtr has a '0' pulse length of ca. 8000us
 * - data0 (0-bits) have then a '0' pulse length of ca. 2000us
 * - data1 (1-bits) have then a '0' pulse length of ca. 4000us
 * - syncPost after dataPtr has a '0' pulse length of ca. 16000us
 * This analysis is the reason for the new r_device definitions below.
 * NB: pulse_slicer_ppm does not use .gap_limit if .tolerance is set.
 * 
 * Outdoor sensor, transmits temperature and humidity data
 * - inFactory NC-3982-913/NX-5817-902, Pearl (for FWS-686 station)
 * - nor-tec 73383 (weather station + sensor), Schou Company AS, Denmark
 * - DAY 73365 (weather station + sensor), Schou Company AS, Denmark
 * Known brand names: inFactory, nor-tec, GreenBlue, DAY. Manufacturer in China.
 * Transmissions includes an id. Every 60 seconds the sensor transmits 6 packets:
 *     0000 1111 | 0011 0000 | 0101 1100 | 1110 0111 | 0110 0001
 *     iiii iiii | cccc ub?? | tttt tttt | tttt hhhh | hhhh ??nn
 * - i: identification; changes on battery switch
 * - c: CRC-4; CCITT checksum, see below for computation specifics
 * - u: unknown; (sometimes set at power-on, but not always)
 * - b: battery low; flag to indicate low battery voltage
 * - h: Humidity; BCD-encoded, each nibble is one digit, 'A0' means 100%rH
 * - t: Temperature; in °F as binary number with one decimal place + 90 °F offset
 * - n: Channel; Channel number 1 - 3
 * 
 */

static const SubGhzBlockConst ws_protocol_polaroid_const = {
    .te_short = 500,
    .te_long = 2000,
    .te_delta = 150,
    .min_count_bit_for_found = 40,
};

struct WSProtocolDecoderPolaroid {
    SubGhzProtocolDecoderBase base;

    SubGhzBlockDecoder decoder;
    WSBlockGeneric generic;
};

struct WSProtocolEncoderPolaroid {
    SubGhzProtocolEncoderBase base;

    SubGhzProtocolBlockEncoder encoder;
    WSBlockGeneric generic;
};

typedef enum {
    PolaroidDecoderStepReset = 0,
    PolaroidDecoderStepCheckPreambule,
    PolaroidDecoderStepSaveDuration,
    PolaroidDecoderStepCheckDuration,
} PolaroidDecoderStep;

const SubGhzProtocolDecoder ws_protocol_polaroid_decoder = {
    .alloc = ws_protocol_decoder_polaroid_alloc,
    .free = ws_protocol_decoder_polaroid_free,

    .feed = ws_protocol_decoder_polaroid_feed,
    .reset = ws_protocol_decoder_polaroid_reset,

    .get_hash_data = ws_protocol_decoder_polaroid_get_hash_data,
    .serialize = ws_protocol_decoder_polaroid_serialize,
    .deserialize = ws_protocol_decoder_polaroid_deserialize,
    .get_string = ws_protocol_decoder_polaroid_get_string,
};

const SubGhzProtocolEncoder ws_protocol_polaroid_encoder = {
    .alloc = NULL,
    .free = NULL,

    .deserialize = NULL,
    .stop = NULL,
    .yield = NULL,
};

const SubGhzProtocol ws_protocol_polaroid = {
    .name = WS_PROTOCOL_POLAROID_NAME,
    .type = SubGhzProtocolWeatherStation,
    .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 |
            SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable,

    .decoder = &ws_protocol_polaroid_decoder,
    .encoder = &ws_protocol_polaroid_encoder,
};

void* ws_protocol_decoder_polaroid_alloc(SubGhzEnvironment* environment) {
    UNUSED(environment);
    WSProtocolDecoderPolaroid* instance = malloc(sizeof(WSProtocolDecoderPolaroid));
    instance->base.protocol = &ws_protocol_polaroid;
    instance->generic.protocol_name = instance->base.protocol->name;
    return instance;
}

void ws_protocol_decoder_polaroid_free(void* context) {
    furi_assert(context);
    WSProtocolDecoderPolaroid* instance = context;
    free(instance);
}

void ws_protocol_decoder_polaroid_reset(void* context) {
    furi_assert(context);
    WSProtocolDecoderPolaroid* instance = context;
    instance->decoder.parser_step = PolaroidDecoderStepReset;
}

static bool ws_protocol_polaroid_check_crc(WSProtocolDecoderPolaroid* instance) {
    uint8_t msg[] = {
        instance->decoder.decode_data >> 32,
        (((instance->decoder.decode_data >> 24) & 0x0F) | (instance->decoder.decode_data & 0x0F)
                                                              << 4),
        instance->decoder.decode_data >> 16,
        instance->decoder.decode_data >> 8,
        instance->decoder.decode_data};

    uint8_t crc =
        subghz_protocol_blocks_crc4(msg, 4, 0x13, 0); // Koopmann 0x9, CCITT-4; FP-4; ITU-T G.704
    crc ^= msg[4] >> 4; // last nibble is only XORed
    return (crc == ((instance->decoder.decode_data >> 28) & 0x0F));
}

/**
 * Analysis of received data
 * @param instance Pointer to a WSBlockGeneric* instance
 */
static void ws_protocol_polaroid_remote_controller(WSBlockGeneric* instance) {
    instance->id = instance->data >> 32;
    instance->battery_low = (instance->data >> 26) & 1;
    instance->btn = WS_NO_BTN;
    instance->temp =
        locale_fahrenheit_to_celsius(((float)((instance->data >> 12) & 0x0FFF) - 900.0f) / 10.0f);
    instance->humidity =
        (((instance->data >> 8) & 0x0F) * 10) + ((instance->data >> 4) & 0x0F); // BCD, 'A0'=100%rH
    instance->channel = instance->data & 0x03;
}

void ws_protocol_decoder_polaroid_feed(void* context, bool level, uint32_t duration) {
    furi_assert(context);
    WSProtocolDecoderPolaroid* instance = context;

    switch(instance->decoder.parser_step) {
    case PolaroidDecoderStepReset:
        if((level) && (DURATION_DIFF(duration, ws_protocol_polaroid_const.te_short) <
                       ws_protocol_polaroid_const.te_delta)) {
            instance->decoder.parser_step = PolaroidDecoderStepCheckPreambule;
            instance->decoder.te_last = duration;
        }
        break;

    case PolaroidDecoderStepCheckPreambule:
        if(level) {
            instance->decoder.te_last = duration;
        } else {
            if((DURATION_DIFF(instance->decoder.te_last, ws_protocol_polaroid_const.te_short) <
                ws_protocol_polaroid_const.te_delta) &&
               (DURATION_DIFF(duration, ws_protocol_polaroid_const.te_short * 16) <
                ws_protocol_polaroid_const.te_delta * 8)) {
                //Found syncPrefix
                instance->decoder.parser_step = PolaroidDecoderStepSaveDuration;
                instance->decoder.decode_data = 0;
                instance->decoder.decode_count_bit = 0;
            } else {
                instance->decoder.parser_step = PolaroidDecoderStepReset;
            }
        }
        break;

    case PolaroidDecoderStepSaveDuration:
        if(level) {
            instance->decoder.te_last = duration;
            instance->decoder.parser_step = PolaroidDecoderStepCheckDuration;
        } else {
            instance->decoder.parser_step = PolaroidDecoderStepReset;
        }
        break;

    case PolaroidDecoderStepCheckDuration:
        if(!level) {
            if(duration >= ((uint32_t)ws_protocol_polaroid_const.te_short * 30)) {
                //Found syncPostfix
                if((instance->decoder.decode_count_bit ==
                    ws_protocol_polaroid_const.min_count_bit_for_found) &&
                   ws_protocol_polaroid_check_crc(instance)) {
                    instance->generic.data = instance->decoder.decode_data;
                    instance->generic.data_count_bit = instance->decoder.decode_count_bit;
                    ws_protocol_polaroid_remote_controller(&instance->generic);
                    if(instance->base.callback)
                        instance->base.callback(&instance->base, instance->base.context);
                }
                instance->decoder.decode_data = 0;
                instance->decoder.decode_count_bit = 0;
                instance->decoder.parser_step = PolaroidDecoderStepReset;
                break;
            } else if(
                (DURATION_DIFF(instance->decoder.te_last, ws_protocol_polaroid_const.te_short) <
                 ws_protocol_polaroid_const.te_delta) &&
                (DURATION_DIFF(duration, ws_protocol_polaroid_const.te_long) <
                 ws_protocol_polaroid_const.te_delta * 2)) {
                subghz_protocol_blocks_add_bit(&instance->decoder, 0);
                instance->decoder.parser_step = PolaroidDecoderStepSaveDuration;
            } else if(
                (DURATION_DIFF(instance->decoder.te_last, ws_protocol_polaroid_const.te_short) <
                 ws_protocol_polaroid_const.te_delta) &&
                (DURATION_DIFF(duration, ws_protocol_polaroid_const.te_long * 2) <
                 ws_protocol_polaroid_const.te_delta * 4)) {
                subghz_protocol_blocks_add_bit(&instance->decoder, 1);
                instance->decoder.parser_step = PolaroidDecoderStepSaveDuration;
            } else {
                instance->decoder.parser_step = PolaroidDecoderStepReset;
            }
        } else {
            instance->decoder.parser_step = PolaroidDecoderStepReset;
        }
        break;
    }
}

uint8_t ws_protocol_decoder_polaroid_get_hash_data(void* context) {
    furi_assert(context);
    WSProtocolDecoderPolaroid* instance = context;
    return subghz_protocol_blocks_get_hash_data(
        &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}

SubGhzProtocolStatus ws_protocol_decoder_polaroid_serialize(
    void* context,
    FlipperFormat* flipper_format,
    SubGhzRadioPreset* preset) {
    furi_assert(context);
    WSProtocolDecoderPolaroid* instance = context;
    return ws_block_generic_serialize(&instance->generic, flipper_format, preset);
}

SubGhzProtocolStatus
    ws_protocol_decoder_polaroid_deserialize(void* context, FlipperFormat* flipper_format) {
    furi_assert(context);
    WSProtocolDecoderPolaroid* instance = context;
    return ws_block_generic_deserialize_check_count_bit(
        &instance->generic, flipper_format, ws_protocol_polaroid_const.min_count_bit_for_found);
}

void ws_protocol_decoder_polaroid_get_string(void* context, FuriString* output) {
    furi_assert(context);
    WSProtocolDecoderPolaroid* instance = context;
    furi_string_printf(
        output,
        "%s %dbit\r\n"
        "Key:0x%lX%08lX\r\n"
        "Sn:0x%lX Ch:%d  Bat:%d\r\n"
        "Temp:%3.1f C Hum:%d%%",
        instance->generic.protocol_name,
        instance->generic.data_count_bit,
        (uint32_t)(instance->generic.data >> 32),
        (uint32_t)(instance->generic.data),
        instance->generic.id,
        instance->generic.channel,
        instance->generic.battery_low,
        (double)instance->generic.temp,
        instance->generic.humidity);
}

polaroid.h

#pragma once

#include <lib/subghz/protocols/base.h>

#include <lib/subghz/blocks/const.h>
#include <lib/subghz/blocks/decoder.h>
#include <lib/subghz/blocks/encoder.h>
#include "ws_generic.h"
#include <lib/subghz/blocks/math.h>

#define WS_PROTOCOL_POLAROID_NAME "Polaroid"

typedef struct WSProtocolDecoderPolaroid WSProtocolDecoderPolaroid;
typedef struct WSProtocolEncoderPolaroid WSProtocolEncoderPolaroid;

extern const SubGhzProtocolDecoder ws_protocol_polaroid_decoder;
extern const SubGhzProtocolEncoder ws_protocol_polaroid_encoder;
extern const SubGhzProtocol ws_protocol_polaroid;

/**
 * Allocate WSProtocolDecoderPolaroid.
 * @param environment Pointer to a SubGhzEnvironment instance
 * @return WSProtocolDecoderPolaroid* pointer to a WSProtocolDecoderPolaroid instance
 */
void* ws_protocol_decoder_polaroid_alloc(SubGhzEnvironment* environment);

/**
 * Free WSProtocolDecoderPolaroid.
 * @param context Pointer to a WSProtocolDecoderPolaroid instance
 */
void ws_protocol_decoder_polaroid_free(void* context);

/**
 * Reset decoder WSProtocolDecoderPolaroid.
 * @param context Pointer to a WSProtocolDecoderPolaroid instance
 */
void ws_protocol_decoder_polaroid_reset(void* context);

/**
 * Parse a raw sequence of levels and durations received from the air.
 * @param context Pointer to a WSProtocolDecoderPolaroid instance
 * @param level Signal level true-high false-low
 * @param duration Duration of this level in, us
 */
void ws_protocol_decoder_polaroid_feed(void* context, bool level, uint32_t duration);

/**
 * Getting the hash sum of the last randomly received parcel.
 * @param context Pointer to a WSProtocolDecoderPolaroid instance
 * @return hash Hash sum
 */
uint8_t ws_protocol_decoder_polaroid_get_hash_data(void* context);

/**
 * Serialize data WSProtocolDecoderPolaroid.
 * @param context Pointer to a WSProtocolDecoderPolaroid instance
 * @param flipper_format Pointer to a FlipperFormat instance
 * @param preset The modulation on which the signal was received, SubGhzRadioPreset
 * @return status
 */
SubGhzProtocolStatus ws_protocol_decoder_polaroid_serialize(
    void* context,
    FlipperFormat* flipper_format,
    SubGhzRadioPreset* preset);

/**
 * Deserialize data WSProtocolDecoderPolaroid.
 * @param context Pointer to a WSProtocolDecoderPolaroid instance
 * @param flipper_format Pointer to a FlipperFormat instance
 * @return status
 */
SubGhzProtocolStatus
    ws_protocol_decoder_polaroid_deserialize(void* context, FlipperFormat* flipper_format);

/**
 * Getting a textual representation of the received data.
 * @param context Pointer to a WSProtocolDecoderPolaroid instance
 * @param output Resulting text
 */
void ws_protocol_decoder_polaroid_get_string(void* context, FuriString* output);

Compilación

Finalmente volveremos al directorio principal de la aplicación, weather_station, y ejecutaremos el comando ufbt para compilar la aplicación.

$ cd ..
$ ufbt

El binario resultante, weather_station.fap se habrá creado en la carpeta dist. Con el comando ufbt flash podemos ejecutar la aplicación directamente en el dispositivo conectado por USB.

Conclusión

Con estas instrucciones seremos capaces de modificar otras aplicaciones en el futuro.