diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..723ef36 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..96c21db --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/libcrc"] + path = lib/libcrc + url = https://github.com/lammertb/libcrc diff --git a/checksum.h b/checksum.h new file mode 100644 index 0000000..ad13698 --- /dev/null +++ b/checksum.h @@ -0,0 +1,3 @@ +#pragma once + +#include "lib/libcrc/include/checksum.h" \ No newline at end of file diff --git a/helpers/meshimi_config.c b/helpers/meshimi_config.c new file mode 100644 index 0000000..2530b4d --- /dev/null +++ b/helpers/meshimi_config.c @@ -0,0 +1,165 @@ +#include "meshimi_config.h" +#include "subghz/helpers/subghz_threshold_rssi.h" + +// The order of items below is important +const char* const mode_text[MODE_COUNT] = { + "Simple RX", + "Simple TX", + "Meshtastic", + "LoRaWAN", + "Meshimi", +}; +const MeshimiConfigMode mode_value[MODE_COUNT] = { + ModeSimpleRX, + ModeSimpleTX, + ModeMeshtastic, + ModeLoRaWAN, + ModeMeshimi, +}; + +const char* const spreading_factor_text[SPREADING_FACTOR_COUNT] = { + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12", +}; + +const char* const bandwidth_text[BANDWIDTH_COUNT] = { + "7.8kHz", + "10.4kHz", + "15.6kHz", + "20.8kHz", + "31.2kHz", + "41.7kHz", + "62.5kHz", + "125kHz", + "250kHz", + "500kHz", +}; + +const char* const coding_rate_text[CODING_RATE_COUNT] = { + "4_5", + "4_6", + "4_7", + "4_8", +}; + +const char* const ldro_text[LDRO_COUNT] = { + "OFF", + "ON", +}; + +struct MeshimiConfig { + MeshimiConfigMode mode; + uint32_t lora_frequency; + LoRaSpreadingFactor lora_spreading_factor; + LoRaBandwidth lora_bandwidth; + LoRaCodingRate lora_coding_rate; + LoRaLowDataRateOptimization lora_ldro; +}; + +MeshimiConfig* meshimi_config_alloc(void) { + MeshimiConfig* instance = malloc(sizeof(MeshimiConfig)); + + // TODO: Instantiate it from conf file stored at SD Card + instance->mode = ModeSimpleRX; + instance->lora_frequency = 868000000U; + instance->lora_spreading_factor = LoRaSpreadingFactor_SF7; + instance->lora_bandwidth = LoRaBandwidth_BW_125; + instance->lora_coding_rate = LoRaCodingRate_CR_4_7; + instance->lora_ldro = LoRaLowDataRateOptimization_LDRO_OFF; + return instance; +} + +void meshimi_config_free(MeshimiConfig* instance) { + furi_assert(instance); + free(instance); +} + +void meshimi_mode_set(MeshimiConfig* instance, MeshimiConfigMode mode) { + furi_assert(instance); + instance->mode = mode; +} + +MeshimiConfigMode meshimi_mode_get(MeshimiConfig* instance) { + furi_assert(instance); + return instance->mode; +} + +const char* meshimi_mode_get_text(MeshimiConfigMode mode) { + return mode_text[mode]; +} + +const enum MeshimiConfigMode* meshimi_mode_get_value() { + return mode_value; +} + +void meshimi_spreading_factor_set(MeshimiConfig* instance, LoRaSpreadingFactor spreading_factor) { + furi_assert(instance); + instance->lora_spreading_factor = spreading_factor; +} + +LoRaSpreadingFactor meshimi_spreading_factor_get(MeshimiConfig* instance) { + furi_assert(instance); + return instance->lora_spreading_factor; +} + +const char* meshimi_spreading_factor_get_text(LoRaSpreadingFactor spreading_factor) { + return spreading_factor_text[spreading_factor]; +} + +void meshimi_bandwidth_set(MeshimiConfig* instance, LoRaBandwidth bandwidth) { + furi_assert(instance); + instance->lora_bandwidth = bandwidth; +} + +LoRaBandwidth meshimi_bandwidth_get(MeshimiConfig* instance) { + furi_assert(instance); + return instance->lora_bandwidth; +} + +const char* meshimi_bandwidth_get_text(LoRaBandwidth bandwidth) { + return bandwidth_text[bandwidth]; +} + +void meshimi_coding_rate_set(MeshimiConfig* instance, LoRaCodingRate coding_rate) { + furi_assert(instance); + instance->lora_coding_rate = coding_rate; +} + +LoRaCodingRate meshimi_coding_rate_get(MeshimiConfig* instance) { + furi_assert(instance); + return instance->lora_coding_rate; +} + +const char* meshimi_coding_rate_get_text(LoRaCodingRate coding_rate) { + return coding_rate_text[coding_rate]; +} + +void meshimi_ldro_set(MeshimiConfig* instance, LoRaLowDataRateOptimization ldro) { + furi_assert(instance); + instance->lora_ldro = ldro; +} + +LoRaLowDataRateOptimization meshimi_ldro_get(MeshimiConfig* instance) { + furi_assert(instance); + return instance->lora_ldro; +} + +const char* meshimi_ldro_get_text(LoRaLowDataRateOptimization ldro) { + return ldro_text[ldro]; +} + +void meshimi_frequency_set(MeshimiConfig* instance, uint32_t frequency) { + furi_assert(instance); + instance->lora_frequency = frequency; +} + +uint32_t meshimi_frequency_get(MeshimiConfig* instance) { + furi_assert(instance); + return instance->lora_frequency; +} diff --git a/helpers/meshimi_config.h b/helpers/meshimi_config.h new file mode 100644 index 0000000..5ae0612 --- /dev/null +++ b/helpers/meshimi_config.h @@ -0,0 +1,190 @@ +#pragma once + +#include +#include "proto/spi.pb.h" + +#define MODE_COUNT 5 +typedef enum MeshimiConfigMode { + ModeSimpleRX = 0, + ModeSimpleTX = 1, + ModeMeshtastic = 2, + ModeLoRaWAN = 3, + ModeMeshimi = 4, +} MeshimiConfigMode; + +#define SPREADING_FACTOR_COUNT 8 +#define BANDWIDTH_COUNT 10 +#define CODING_RATE_COUNT 4 +#define LDRO_COUNT 2 +#define FREQUENCY_TEXT_LEN 10 + +typedef struct { + MeshimiConfigMode mode; + uint32_t lora_frequency; + LoRaSpreadingFactor lora_spreading_factor; + LoRaBandwidth lora_bandwidth; + LoRaCodingRate lora_coding_rate; + LoRaLowDataRateOptimization lora_ldro; +} MeshimiConfigData; + +typedef struct MeshimiConfig MeshimiConfig; + +/** + * Allocate MeshimiConfig + * + * @return MeshimiConfig* + */ +MeshimiConfig* meshimi_config_alloc(void); + +/** + * Free MeshimiConfig + * + * @param instance Pointer to a MeshimiConfig + */ +void meshimi_config_free(MeshimiConfig* instance); + +/** + * Set Mode + * + * @param instance Pointer to a MeshimiConfig + * @param MeshimiConfigMode Mode + */ +void meshimi_mode_set(MeshimiConfig* instance, MeshimiConfigMode mode); + +/** + * Get Mode + * + * @param instance Pointer to a MeshimiConfig + * @return MeshimiConfigMode Mode + */ +MeshimiConfigMode meshimi_mode_get(MeshimiConfig* instance); + +/** + * Get text representation of the mode + * + * @param MeshimiConfigMode mode + * @return + */ +const char* meshimi_mode_get_text(MeshimiConfigMode mode); + +const enum MeshimiConfigMode* meshimi_mode_get_value(); + +/** + * Set Spreading Factor + * + * @param instance Pointer to a MeshimiConfig + * @param LoRaSpreadingFactor Spreading Factor + */ +void meshimi_spreading_factor_set(MeshimiConfig* instance, LoRaSpreadingFactor spreading_factor); + +/** + * Get Spreading Factor + * + * @param instance Pointer to a MeshimiConfig + * @return LoRaSpreadingFactor + */ +LoRaSpreadingFactor meshimi_spreading_factor_get(MeshimiConfig* instance); + +/** + * Get text representation of the spreading_factor + * + * @param LoRaSpreadingFactor spreading_factor + * @return + */ +const char* meshimi_spreading_factor_get_text(LoRaSpreadingFactor spreading_factor); + +/** + * Set Bandwidth + * + * @param instance Pointer to a MeshimiConfig + * @param LoRaBandwidth bandwidth + */ +void meshimi_bandwidth_set(MeshimiConfig* instance, LoRaBandwidth bandwidth); + +/** + * Get Bandwidth + * + * @param instance Pointer to a MeshimiConfig + * @return LoRaBandwidth + */ +LoRaBandwidth meshimi_bandwidth_get(MeshimiConfig* instance); + +/** + * Get text representation of the bandwidth + * + * @param LoRaSpreadingFactor bandwidth + * @return + */ +const char* meshimi_bandwidth_get_text(LoRaBandwidth bandwidth); + +/** + * Set Coding Rate + * + * @param instance Pointer to a MeshimiConfig + * @param LoRaCodingRate coding_rate + */ +void meshimi_coding_rate_set(MeshimiConfig* instance, LoRaCodingRate coding_rate); + +/** + * Get Coding Rate + * + * @param instance Pointer to a MeshimiConfig + * @return LoRaCodingRate + */ +LoRaCodingRate meshimi_coding_rate_get(MeshimiConfig* instance); + +/** + * Get text representation of the coding rate + * + * @param LoRaCodingRate coding_rate + * @return + */ +const char* meshimi_coding_rate_get_text(LoRaCodingRate coding_rate); + +/** + * Set Low DataRate Optimization + * + * @param instance Pointer to a MeshimiConfig + * @param LoRaCodingRate ldro + */ +void meshimi_ldro_set(MeshimiConfig* instance, LoRaLowDataRateOptimization ldro); + +/** + * Get Low DataRate Optimization + * + * @param instance Pointer to a MeshimiConfig + * @return LoRaLowDataRateOptimization + */ +LoRaLowDataRateOptimization meshimi_ldro_get(MeshimiConfig* instance); + +/** + * Get text representation of the low dataRate optimization + * + * @param LoRaLowDataRateOptimization ldro + * @return + */ +const char* meshimi_ldro_get_text(LoRaLowDataRateOptimization ldro); + +/** + * Set Frequency + * + * @param instance Pointer to a MeshimiConfig + * @param uint32_t frequency + */ +void meshimi_frequency_set(MeshimiConfig* instance, uint32_t frequency); + +/** + * Get Frequency + * + * @param instance Pointer to a MeshimiConfig + * @return uint32_t + */ +uint32_t meshimi_frequency_get(MeshimiConfig* instance); + +/** + * Get text representation of the frequency + * + * @param uint32_t frequency + * @return + */ +const char* meshimi_frequency_get_text(uint32_t frequency); \ No newline at end of file diff --git a/icons/Meshimi/Meshimi_128x64.png b/icons/Meshimi/Meshimi_128x64.png new file mode 100644 index 0000000..f4eb91c Binary files /dev/null and b/icons/Meshimi/Meshimi_128x64.png differ diff --git a/lib/libcrc b/lib/libcrc new file mode 160000 index 0000000..7719e21 --- /dev/null +++ b/lib/libcrc @@ -0,0 +1 @@ +Subproject commit 7719e2112a9a960b1bba130d02bebdf58e8701f1 diff --git a/meshimi.c b/meshimi.c new file mode 100644 index 0000000..3c5c698 --- /dev/null +++ b/meshimi.c @@ -0,0 +1,187 @@ +#include "meshimi.h" + +bool meshimi_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + Meshimi* meshimi = context; + return scene_manager_handle_custom_event(meshimi->scene_manager, event); +} + +bool meshimi_back_event_callback(void* context) { + furi_assert(context); + Meshimi* meshimi = context; + return scene_manager_handle_back_event(meshimi->scene_manager); +} + +void meshimi_tick_event_callback(void* context) { + furi_assert(context); + Meshimi* meshimi = context; + scene_manager_handle_tick_event(meshimi->scene_manager); +} + +Meshimi* meshimi_alloc() { + Meshimi* meshimi = malloc(sizeof(Meshimi)); + + // Meshimi Configuration + meshimi->config = meshimi_config_alloc(); + + // GUI + meshimi->gui = furi_record_open(RECORD_GUI); + + // Bt + meshimi->bt = furi_record_open(RECORD_BT); + + // View Dispatcher + meshimi->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(meshimi->view_dispatcher); + + meshimi->scene_manager = scene_manager_alloc(&meshimi_scene_handlers, meshimi); + view_dispatcher_set_event_callback_context(meshimi->view_dispatcher, meshimi); + view_dispatcher_set_custom_event_callback( + meshimi->view_dispatcher, meshimi_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + meshimi->view_dispatcher, meshimi_back_event_callback); + view_dispatcher_set_tick_event_callback( + meshimi->view_dispatcher, meshimi_tick_event_callback, 100); + + // Open Notification record + meshimi->notifications = furi_record_open(RECORD_NOTIFICATION); + + // SubMenu + meshimi->submenu = submenu_alloc(); + view_dispatcher_add_view( + meshimi->view_dispatcher, MeshimiViewIdMenu, submenu_get_view(meshimi->submenu)); + + // Popup + meshimi->popup = popup_alloc(); + view_dispatcher_add_view( + meshimi->view_dispatcher, MeshimiViewIdPopup, popup_get_view(meshimi->popup)); + + // Text Input + meshimi->text_input = text_input_alloc(); + view_dispatcher_add_view( + meshimi->view_dispatcher, + MeshimiViewIdTextInput, + text_input_get_view(meshimi->text_input)); + + // Custom Widget + meshimi->widget = widget_alloc(); + view_dispatcher_add_view( + meshimi->view_dispatcher, MeshimiViewIdWidget, widget_get_view(meshimi->widget)); + + // Dialog + meshimi->dialogs = furi_record_open(RECORD_DIALOGS); + + // Views + meshimi->meshimi_view_mode = meshimi_view_mode_alloc(meshimi->config); + view_dispatcher_add_view( + meshimi->view_dispatcher, + MeshimiViewIdMode, + meshimi_view_mode_get_view(meshimi->meshimi_view_mode)); + + // Variable Item List + meshimi->variable_item_list = variable_item_list_alloc(); + view_dispatcher_add_view( + meshimi->view_dispatcher, + MeshimiViewIdVariableItemList, + variable_item_list_get_view(meshimi->variable_item_list)); + + + + furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_external); + + return meshimi; +} + +void meshimi_free(Meshimi* meshimi) { + furi_assert(meshimi); + + // TextInput + view_dispatcher_remove_view(meshimi->view_dispatcher, MeshimiViewIdTextInput); + text_input_free(meshimi->text_input); + + // Custom Widget + view_dispatcher_remove_view(meshimi->view_dispatcher, MeshimiViewIdWidget); + widget_free(meshimi->widget); + + // Dialog + furi_record_close(RECORD_DIALOGS); + + // Bt + furi_record_close(RECORD_BT); + meshimi->bt = NULL; + + // Variable Item List + view_dispatcher_remove_view(meshimi->view_dispatcher, MeshimiViewIdVariableItemList); + variable_item_list_free(meshimi->variable_item_list); + + // Submenu + view_dispatcher_remove_view(meshimi->view_dispatcher, MeshimiViewIdMenu); + submenu_free(meshimi->submenu); + + // Popup + view_dispatcher_remove_view(meshimi->view_dispatcher, MeshimiViewIdPopup); + popup_free(meshimi->popup); + + // Views + view_dispatcher_remove_view(meshimi->view_dispatcher, MeshimiViewIdMode); + meshimi_view_mode_free(meshimi->meshimi_view_mode); + + // Scene manager + scene_manager_free(meshimi->scene_manager); + + // View Dispatcher + view_dispatcher_free(meshimi->view_dispatcher); + + // GUI + furi_record_close(RECORD_GUI); + meshimi->gui = NULL; + + // Notifications + furi_record_close(RECORD_NOTIFICATION); + meshimi->notifications = NULL; + + // Meshimi Configuration + meshimi_config_free(meshimi->config); + + furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_external); + + // The rest + free(meshimi); +} + +/** + * @brief Main function for Meshimi application. + * @details This function is the entry point for the Meshimi application. + * @param p + * @return 0 - Success + */ +int32_t meshimi_app(void* p) { + UNUSED(p); + FURI_LOG_I(LOG_TAG, "Start app"); + + Meshimi* meshimi = meshimi_alloc(); + +#ifdef BACKLIGHT_ALWAYS_ON + notification_message(meshimi->notifications, &sequence_display_backlight_enforce_on); +#endif + + view_dispatcher_attach_to_gui( + meshimi->view_dispatcher, meshimi->gui, ViewDispatcherTypeFullscreen); + scene_manager_next_scene(meshimi->scene_manager, MeshimiSceneStart); + + furi_hal_power_suppress_charge_enter(); + + view_dispatcher_run(meshimi->view_dispatcher); + + notification_message(meshimi->notifications, &sequence_blink_green_100); + + furi_hal_power_suppress_charge_exit(); + +#ifdef BACKLIGHT_ALWAYS_ON + notification_message(meshimi->notifications, &sequence_display_backlight_enforce_auto); +#endif + + meshimi_free(meshimi); + + return 0; +} \ No newline at end of file diff --git a/meshimi.h b/meshimi.h new file mode 100644 index 0000000..0c02dd2 --- /dev/null +++ b/meshimi.h @@ -0,0 +1,54 @@ +#pragma once + +#include "meshimi_types.h" +#include "applications_user/meshimi/scenes/meshimi_scene.h" +#include "helpers/meshimi_config.h" +#include "bt/bt_service/bt.h" +#include "views/meshimi_view_mode.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MESHIMI_VERSION "1.0" +#define MESHIMI_GITHUB "github.com/BegoonLab/meshimi" +#define MESHIMI_GET_MODULE "begoonlab.tech/meshimi" +#define BACKLIGHT_ALWAYS_ON +#define LOG_TAG "Meshimi" +#define TEXT_STORE_SIZE 64U + +typedef struct Meshimi Meshimi; + +struct Meshimi { + MeshimiConfig* config; + + Bt* bt; + Gui* gui; + NotificationApp* notifications; + + SceneManager* scene_manager; + ViewDispatcher* view_dispatcher; + + char text_store[TEXT_STORE_SIZE]; + Submenu* submenu; + Popup* popup; + TextInput* text_input; + Widget* widget; + DialogsApp* dialogs; + VariableItemList* variable_item_list; + MeshimiViewMode* meshimi_view_mode; +}; + +Meshimi* meshimi_alloc(); +void meshimi_free(Meshimi* meshimi); \ No newline at end of file diff --git a/meshimi_custom_event.h b/meshimi_custom_event.h new file mode 100644 index 0000000..96930f8 --- /dev/null +++ b/meshimi_custom_event.h @@ -0,0 +1,10 @@ +#pragma once + +typedef enum { + MeshimiEventMode = 0, + MeshimiEventSpreadingFactor, + MeshimiEventBandwidth, + MeshimiEventCodingRate, + MeshimiEventLDRO, + MeshimiEventFrequency, +} MeshimiCustomEvent; \ No newline at end of file diff --git a/meshimi_types.h b/meshimi_types.h new file mode 100644 index 0000000..149499b --- /dev/null +++ b/meshimi_types.h @@ -0,0 +1,10 @@ +#pragma once + +typedef enum { + MeshimiViewIdMenu, + MeshimiViewIdPopup, + MeshimiViewIdTextInput, + MeshimiViewIdWidget, + MeshimiViewIdVariableItemList, + MeshimiViewIdMode, +} MeshimiViewId; \ No newline at end of file diff --git a/proto/spi.pb.c b/proto/spi.pb.c new file mode 100644 index 0000000..9675c56 --- /dev/null +++ b/proto/spi.pb.c @@ -0,0 +1,49 @@ +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.4.7 */ + +#include "spi.pb.h" +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +PB_BIND(SpiPacket, SpiPacket, 2) + + +PB_BIND(SpiHeader, SpiHeader, AUTO) + + +PB_BIND(Request, Request, AUTO) + + +PB_BIND(Request_ConnectToNetwork, Request_ConnectToNetwork, AUTO) + + +PB_BIND(Response, Response, 2) + + +PB_BIND(Response_ConnectToNetwork, Response_ConnectToNetwork, AUTO) + + +PB_BIND(Response_LoRaMessageReceived, Response_LoRaMessageReceived, 2) + + +PB_BIND(Response_Status, Response_Status, AUTO) + + +PB_BIND(LoRaMessage, LoRaMessage, 2) + + +PB_BIND(LoRaModulationParams, LoRaModulationParams, AUTO) + + +PB_BIND(LoRaPacketParams, LoRaPacketParams, AUTO) + + + + + + + + + + diff --git a/proto/spi.pb.h b/proto/spi.pb.h new file mode 100644 index 0000000..d171a91 --- /dev/null +++ b/proto/spi.pb.h @@ -0,0 +1,400 @@ +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.4.7 */ + +#ifndef PB_SPI_PB_H_INCLUDED +#define PB_SPI_PB_H_INCLUDED +#include + +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +/* Enum definitions */ +/* LoRa Spreading Factor */ +typedef enum _LoRaSpreadingFactor { + LoRaSpreadingFactor_SF5 = 0, + LoRaSpreadingFactor_SF6 = 1, + LoRaSpreadingFactor_SF7 = 2, + LoRaSpreadingFactor_SF8 = 3, + LoRaSpreadingFactor_SF9 = 4, + LoRaSpreadingFactor_SF10 = 5, + LoRaSpreadingFactor_SF11 = 6, + LoRaSpreadingFactor_SF12 = 7 +} LoRaSpreadingFactor; + +/* LoRa Coding Rate */ +typedef enum _LoRaCodingRate { + LoRaCodingRate_CR_4_5 = 0, + LoRaCodingRate_CR_4_6 = 1, + LoRaCodingRate_CR_4_7 = 2, + LoRaCodingRate_CR_4_8 = 3 +} LoRaCodingRate; + +/* LoRa Bandwidth */ +typedef enum _LoRaBandwidth { + LoRaBandwidth_BW_007 = 0, + LoRaBandwidth_BW_010 = 1, + LoRaBandwidth_BW_015 = 2, + LoRaBandwidth_BW_020 = 3, + LoRaBandwidth_BW_031 = 4, + LoRaBandwidth_BW_041 = 5, + LoRaBandwidth_BW_062 = 6, + LoRaBandwidth_BW_125 = 7, + LoRaBandwidth_BW_250 = 8, + LoRaBandwidth_BW_500 = 9 +} LoRaBandwidth; + +/* LoRa Low DataRate Optimization */ +typedef enum _LoRaLowDataRateOptimization { + LoRaLowDataRateOptimization_LDRO_OFF = 0, + LoRaLowDataRateOptimization_LDRO_ON = 1 +} LoRaLowDataRateOptimization; + +/* LoRa packet length enumeration */ +typedef enum _LoRaPacketLengthMode { + LoRaPacketLengthMode_EXPLICIT = 0, /* Header included in the packet */ + LoRaPacketLengthMode_IMPLICIT = 1 /* Header not included in the packet */ +} LoRaPacketLengthMode; + +typedef enum _SpiHeader_Status { + SpiHeader_Status_UNKNOWN = 0, + SpiHeader_Status_STAND_BY = 1, + SpiHeader_Status_TX_READY = 2, + SpiHeader_Status_RX_READY = 3, + SpiHeader_Status_BUSY = 4, + SpiHeader_Status_ERROR = 5 +} SpiHeader_Status; + +typedef enum _Response_Status_Enum { + Response_Status_Enum_OK = 0, + Response_Status_Enum_ERROR = 1 +} Response_Status_Enum; + +/* Struct definitions */ +typedef PB_BYTES_ARRAY_T(512) SpiPacket_buffer_t; +typedef struct _SpiPacket { + SpiPacket_buffer_t buffer; /* Last 4 bytes for CRC */ + uint32_t length; +} SpiPacket; + +typedef struct _SpiHeader { + SpiHeader_Status status; + uint32_t length; /* Buffer length for upcoming TX/RX packet. */ +} SpiHeader; + +typedef struct _Response_Status { + Response_Status_Enum status; + pb_callback_t error; +} Response_Status; + +typedef PB_BYTES_ARRAY_T(255) LoRaMessage_data_t; +typedef struct _LoRaMessage { + LoRaMessage_data_t data; /* Received data */ + uint8_t size; /* The number of bytes from the Rx radio buffer */ + int8_t rssi; /* RSSI of the packet */ + int8_t snr; /* SNR of the packet */ +} LoRaMessage; + +typedef struct _Response_LoRaMessageReceived { + bool has_loraMsg; + LoRaMessage loraMsg; +} Response_LoRaMessageReceived; + +/* LoRa modulation parameters */ +typedef struct _LoRaModulationParams { + LoRaSpreadingFactor sf; /* LoRa Spreading Factor */ + LoRaCodingRate cr; /* LoRa Coding Rate */ + LoRaBandwidth bw; /* LoRa Bandwidth */ + LoRaLowDataRateOptimization ldro; /* Low DataRate Optimization configuration */ +} LoRaModulationParams; + +/* LoRa packet parameters */ +typedef struct _LoRaPacketParams { + uint16_t preambleLenInSymbols; /* Preamble length in symbols */ + LoRaPacketLengthMode headerType; /* Header type */ + uint32_t pldLenInBytes; /* Payload length in bytes */ + bool crcIsOn; /* CRC activation */ + bool invertIqIsOn; /* IQ polarity setup */ +} LoRaPacketParams; + +typedef struct _Request_ConnectToNetwork { + bool private; + uint32_t LoRaFrequency; /* The RF frequency for future radio operations */ + bool has_modParams; + LoRaModulationParams modParams; + bool has_pktParams; + LoRaPacketParams pktParams; +} Request_ConnectToNetwork; + +typedef struct _Request { + uint32_t version; + pb_size_t which_request; + union { + Request_ConnectToNetwork connectToNetworkRequest; + } request; +} Request; + +typedef struct _Response_ConnectToNetwork { + bool private; + uint32_t LoRaFrequency; + bool has_modParams; + LoRaModulationParams modParams; + bool has_pktParams; + LoRaPacketParams pktParams; +} Response_ConnectToNetwork; + +typedef struct _Response { + uint32_t version; + bool has_status; + Response_Status status; + pb_size_t which_response; + union { + Response_ConnectToNetwork connectToNetwork; + Response_LoRaMessageReceived loRaMessageReceived; + } response; +} Response; + + +#ifdef __cplusplus +extern "C" { +#endif + +/* Helper constants for enums */ +#define _LoRaSpreadingFactor_MIN LoRaSpreadingFactor_SF5 +#define _LoRaSpreadingFactor_MAX LoRaSpreadingFactor_SF12 +#define _LoRaSpreadingFactor_ARRAYSIZE ((LoRaSpreadingFactor)(LoRaSpreadingFactor_SF12+1)) + +#define _LoRaCodingRate_MIN LoRaCodingRate_CR_4_5 +#define _LoRaCodingRate_MAX LoRaCodingRate_CR_4_8 +#define _LoRaCodingRate_ARRAYSIZE ((LoRaCodingRate)(LoRaCodingRate_CR_4_8+1)) + +#define _LoRaBandwidth_MIN LoRaBandwidth_BW_007 +#define _LoRaBandwidth_MAX LoRaBandwidth_BW_500 +#define _LoRaBandwidth_ARRAYSIZE ((LoRaBandwidth)(LoRaBandwidth_BW_500+1)) + +#define _LoRaLowDataRateOptimization_MIN LoRaLowDataRateOptimization_LDRO_OFF +#define _LoRaLowDataRateOptimization_MAX LoRaLowDataRateOptimization_LDRO_ON +#define _LoRaLowDataRateOptimization_ARRAYSIZE ((LoRaLowDataRateOptimization)(LoRaLowDataRateOptimization_LDRO_ON+1)) + +#define _LoRaPacketLengthMode_MIN LoRaPacketLengthMode_EXPLICIT +#define _LoRaPacketLengthMode_MAX LoRaPacketLengthMode_IMPLICIT +#define _LoRaPacketLengthMode_ARRAYSIZE ((LoRaPacketLengthMode)(LoRaPacketLengthMode_IMPLICIT+1)) + +#define _SpiHeader_Status_MIN SpiHeader_Status_UNKNOWN +#define _SpiHeader_Status_MAX SpiHeader_Status_ERROR +#define _SpiHeader_Status_ARRAYSIZE ((SpiHeader_Status)(SpiHeader_Status_ERROR+1)) + +#define _Response_Status_Enum_MIN Response_Status_Enum_OK +#define _Response_Status_Enum_MAX Response_Status_Enum_ERROR +#define _Response_Status_Enum_ARRAYSIZE ((Response_Status_Enum)(Response_Status_Enum_ERROR+1)) + + +#define SpiHeader_status_ENUMTYPE SpiHeader_Status + + + + + + +#define Response_Status_status_ENUMTYPE Response_Status_Enum + + +#define LoRaModulationParams_sf_ENUMTYPE LoRaSpreadingFactor +#define LoRaModulationParams_cr_ENUMTYPE LoRaCodingRate +#define LoRaModulationParams_bw_ENUMTYPE LoRaBandwidth +#define LoRaModulationParams_ldro_ENUMTYPE LoRaLowDataRateOptimization + +#define LoRaPacketParams_headerType_ENUMTYPE LoRaPacketLengthMode + + +/* Initializer values for message structs */ +#define SpiPacket_init_default {{0, {0}}, 0} +#define SpiHeader_init_default {_SpiHeader_Status_MIN, 0} +#define Request_init_default {0, 0, {Request_ConnectToNetwork_init_default}} +#define Request_ConnectToNetwork_init_default {0, 0, false, LoRaModulationParams_init_default, false, LoRaPacketParams_init_default} +#define Response_init_default {0, false, Response_Status_init_default, 0, {Response_ConnectToNetwork_init_default}} +#define Response_ConnectToNetwork_init_default {0, 0, false, LoRaModulationParams_init_default, false, LoRaPacketParams_init_default} +#define Response_LoRaMessageReceived_init_default {false, LoRaMessage_init_default} +#define Response_Status_init_default {_Response_Status_Enum_MIN, {{NULL}, NULL}} +#define LoRaMessage_init_default {{0, {0}}, 0, 0, 0} +#define LoRaModulationParams_init_default {_LoRaSpreadingFactor_MIN, _LoRaCodingRate_MIN, _LoRaBandwidth_MIN, _LoRaLowDataRateOptimization_MIN} +#define LoRaPacketParams_init_default {0, _LoRaPacketLengthMode_MIN, 0, 0, 0} +#define SpiPacket_init_zero {{0, {0}}, 0} +#define SpiHeader_init_zero {_SpiHeader_Status_MIN, 0} +#define Request_init_zero {0, 0, {Request_ConnectToNetwork_init_zero}} +#define Request_ConnectToNetwork_init_zero {0, 0, false, LoRaModulationParams_init_zero, false, LoRaPacketParams_init_zero} +#define Response_init_zero {0, false, Response_Status_init_zero, 0, {Response_ConnectToNetwork_init_zero}} +#define Response_ConnectToNetwork_init_zero {0, 0, false, LoRaModulationParams_init_zero, false, LoRaPacketParams_init_zero} +#define Response_LoRaMessageReceived_init_zero {false, LoRaMessage_init_zero} +#define Response_Status_init_zero {_Response_Status_Enum_MIN, {{NULL}, NULL}} +#define LoRaMessage_init_zero {{0, {0}}, 0, 0, 0} +#define LoRaModulationParams_init_zero {_LoRaSpreadingFactor_MIN, _LoRaCodingRate_MIN, _LoRaBandwidth_MIN, _LoRaLowDataRateOptimization_MIN} +#define LoRaPacketParams_init_zero {0, _LoRaPacketLengthMode_MIN, 0, 0, 0} + +/* Field tags (for use in manual encoding/decoding) */ +#define SpiPacket_buffer_tag 1 +#define SpiPacket_length_tag 2 +#define SpiHeader_status_tag 1 +#define SpiHeader_length_tag 2 +#define Response_Status_status_tag 1 +#define Response_Status_error_tag 2 +#define LoRaMessage_data_tag 1 +#define LoRaMessage_size_tag 2 +#define LoRaMessage_rssi_tag 3 +#define LoRaMessage_snr_tag 4 +#define Response_LoRaMessageReceived_loraMsg_tag 1 +#define LoRaModulationParams_sf_tag 1 +#define LoRaModulationParams_cr_tag 2 +#define LoRaModulationParams_bw_tag 3 +#define LoRaModulationParams_ldro_tag 4 +#define LoRaPacketParams_preambleLenInSymbols_tag 1 +#define LoRaPacketParams_headerType_tag 2 +#define LoRaPacketParams_pldLenInBytes_tag 3 +#define LoRaPacketParams_crcIsOn_tag 4 +#define LoRaPacketParams_invertIqIsOn_tag 5 +#define Request_ConnectToNetwork_private_tag 1 +#define Request_ConnectToNetwork_LoRaFrequency_tag 2 +#define Request_ConnectToNetwork_modParams_tag 3 +#define Request_ConnectToNetwork_pktParams_tag 4 +#define Request_version_tag 1 +#define Request_connectToNetworkRequest_tag 2 +#define Response_ConnectToNetwork_private_tag 1 +#define Response_ConnectToNetwork_LoRaFrequency_tag 2 +#define Response_ConnectToNetwork_modParams_tag 3 +#define Response_ConnectToNetwork_pktParams_tag 4 +#define Response_version_tag 1 +#define Response_status_tag 2 +#define Response_connectToNetwork_tag 3 +#define Response_loRaMessageReceived_tag 4 + +/* Struct field encoding specification for nanopb */ +#define SpiPacket_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, BYTES, buffer, 1) \ +X(a, STATIC, SINGULAR, UINT32, length, 2) +#define SpiPacket_CALLBACK NULL +#define SpiPacket_DEFAULT NULL + +#define SpiHeader_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, status, 1) \ +X(a, STATIC, SINGULAR, UINT32, length, 2) +#define SpiHeader_CALLBACK NULL +#define SpiHeader_DEFAULT NULL + +#define Request_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, version, 1) \ +X(a, STATIC, ONEOF, MESSAGE, (request,connectToNetworkRequest,request.connectToNetworkRequest), 2) +#define Request_CALLBACK NULL +#define Request_DEFAULT NULL +#define Request_request_connectToNetworkRequest_MSGTYPE Request_ConnectToNetwork + +#define Request_ConnectToNetwork_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, BOOL, private, 1) \ +X(a, STATIC, SINGULAR, UINT32, LoRaFrequency, 2) \ +X(a, STATIC, OPTIONAL, MESSAGE, modParams, 3) \ +X(a, STATIC, OPTIONAL, MESSAGE, pktParams, 4) +#define Request_ConnectToNetwork_CALLBACK NULL +#define Request_ConnectToNetwork_DEFAULT NULL +#define Request_ConnectToNetwork_modParams_MSGTYPE LoRaModulationParams +#define Request_ConnectToNetwork_pktParams_MSGTYPE LoRaPacketParams + +#define Response_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, version, 1) \ +X(a, STATIC, OPTIONAL, MESSAGE, status, 2) \ +X(a, STATIC, ONEOF, MESSAGE, (response,connectToNetwork,response.connectToNetwork), 3) \ +X(a, STATIC, ONEOF, MESSAGE, (response,loRaMessageReceived,response.loRaMessageReceived), 4) +#define Response_CALLBACK NULL +#define Response_DEFAULT NULL +#define Response_status_MSGTYPE Response_Status +#define Response_response_connectToNetwork_MSGTYPE Response_ConnectToNetwork +#define Response_response_loRaMessageReceived_MSGTYPE Response_LoRaMessageReceived + +#define Response_ConnectToNetwork_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, BOOL, private, 1) \ +X(a, STATIC, SINGULAR, UINT32, LoRaFrequency, 2) \ +X(a, STATIC, OPTIONAL, MESSAGE, modParams, 3) \ +X(a, STATIC, OPTIONAL, MESSAGE, pktParams, 4) +#define Response_ConnectToNetwork_CALLBACK NULL +#define Response_ConnectToNetwork_DEFAULT NULL +#define Response_ConnectToNetwork_modParams_MSGTYPE LoRaModulationParams +#define Response_ConnectToNetwork_pktParams_MSGTYPE LoRaPacketParams + +#define Response_LoRaMessageReceived_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, MESSAGE, loraMsg, 1) +#define Response_LoRaMessageReceived_CALLBACK NULL +#define Response_LoRaMessageReceived_DEFAULT NULL +#define Response_LoRaMessageReceived_loraMsg_MSGTYPE LoRaMessage + +#define Response_Status_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, status, 1) \ +X(a, CALLBACK, OPTIONAL, STRING, error, 2) +#define Response_Status_CALLBACK pb_default_field_callback +#define Response_Status_DEFAULT NULL + +#define LoRaMessage_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, BYTES, data, 1) \ +X(a, STATIC, SINGULAR, UINT32, size, 2) \ +X(a, STATIC, SINGULAR, INT32, rssi, 3) \ +X(a, STATIC, SINGULAR, INT32, snr, 4) +#define LoRaMessage_CALLBACK NULL +#define LoRaMessage_DEFAULT NULL + +#define LoRaModulationParams_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, sf, 1) \ +X(a, STATIC, SINGULAR, UENUM, cr, 2) \ +X(a, STATIC, SINGULAR, UENUM, bw, 3) \ +X(a, STATIC, SINGULAR, UENUM, ldro, 4) +#define LoRaModulationParams_CALLBACK NULL +#define LoRaModulationParams_DEFAULT NULL + +#define LoRaPacketParams_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, preambleLenInSymbols, 1) \ +X(a, STATIC, SINGULAR, UENUM, headerType, 2) \ +X(a, STATIC, SINGULAR, UINT32, pldLenInBytes, 3) \ +X(a, STATIC, SINGULAR, BOOL, crcIsOn, 4) \ +X(a, STATIC, SINGULAR, BOOL, invertIqIsOn, 5) +#define LoRaPacketParams_CALLBACK NULL +#define LoRaPacketParams_DEFAULT NULL + +extern const pb_msgdesc_t SpiPacket_msg; +extern const pb_msgdesc_t SpiHeader_msg; +extern const pb_msgdesc_t Request_msg; +extern const pb_msgdesc_t Request_ConnectToNetwork_msg; +extern const pb_msgdesc_t Response_msg; +extern const pb_msgdesc_t Response_ConnectToNetwork_msg; +extern const pb_msgdesc_t Response_LoRaMessageReceived_msg; +extern const pb_msgdesc_t Response_Status_msg; +extern const pb_msgdesc_t LoRaMessage_msg; +extern const pb_msgdesc_t LoRaModulationParams_msg; +extern const pb_msgdesc_t LoRaPacketParams_msg; + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define SpiPacket_fields &SpiPacket_msg +#define SpiHeader_fields &SpiHeader_msg +#define Request_fields &Request_msg +#define Request_ConnectToNetwork_fields &Request_ConnectToNetwork_msg +#define Response_fields &Response_msg +#define Response_ConnectToNetwork_fields &Response_ConnectToNetwork_msg +#define Response_LoRaMessageReceived_fields &Response_LoRaMessageReceived_msg +#define Response_Status_fields &Response_Status_msg +#define LoRaMessage_fields &LoRaMessage_msg +#define LoRaModulationParams_fields &LoRaModulationParams_msg +#define LoRaPacketParams_fields &LoRaPacketParams_msg + +/* Maximum encoded size of messages (where known) */ +/* Response_size depends on runtime parameters */ +/* Response_Status_size depends on runtime parameters */ +#define LoRaMessage_size 283 +#define LoRaModulationParams_size 8 +#define LoRaPacketParams_size 16 +#define Request_ConnectToNetwork_size 36 +#define Request_size 44 +#define Response_ConnectToNetwork_size 36 +#define Response_LoRaMessageReceived_size 286 +#define SpiHeader_size 8 +#define SpiPacket_size 521 + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/scenes/meshimi_scene.c b/scenes/meshimi_scene.c new file mode 100644 index 0000000..50ad620 --- /dev/null +++ b/scenes/meshimi_scene.c @@ -0,0 +1,31 @@ +#include "meshimi_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const meshimi_on_enter_handlers[])(void*) = { +#include "meshimi_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const meshimi_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "meshimi_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const meshimi_on_exit_handlers[])(void* context) = { +#include "meshimi_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers meshimi_scene_handlers = { + .on_enter_handlers = meshimi_on_enter_handlers, + .on_event_handlers = meshimi_on_event_handlers, + .on_exit_handlers = meshimi_on_exit_handlers, + .scene_num = MeshimiSceneNum, +}; + diff --git a/scenes/meshimi_scene.h b/scenes/meshimi_scene.h new file mode 100644 index 0000000..4b0527a --- /dev/null +++ b/scenes/meshimi_scene.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) MeshimiScene##id, +typedef enum { +#include "meshimi_scene_config.h" + + MeshimiSceneNum, +} MeshimiScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers meshimi_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "meshimi_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "meshimi_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "meshimi_scene_config.h" +#undef ADD_SCENE diff --git a/scenes/meshimi_scene_about.c b/scenes/meshimi_scene_about.c new file mode 100644 index 0000000..1405773 --- /dev/null +++ b/scenes/meshimi_scene_about.c @@ -0,0 +1,57 @@ +#include "../meshimi.h" + +void meshimi_scene_about_on_enter(void* context) { + Meshimi* meshimi = context; + + FuriString* temp_str; + temp_str = furi_string_alloc(); + furi_string_printf(temp_str, "\e#%s\n", "Buy module"); + + furi_string_cat_printf(temp_str, "%s\n\n", MESHIMI_GET_MODULE); + + furi_string_cat_printf(temp_str, "\e#%s\n", "Information"); + + furi_string_cat_printf(temp_str, "Version: %s\n", MESHIMI_VERSION); + furi_string_cat_printf(temp_str, "%s\n\n", MESHIMI_GITHUB); + + furi_string_cat_printf(temp_str, "\e#%s\n", "Description"); + furi_string_cat_printf( + temp_str, + "Meshimi empowers your\nFlipper Zero with advanced\nmesh networking capabilities,\nutilizing Meshtastic,\nLoRaWAN, and a proprietary\nsecure mesh protocol\nto deliver superior\nconnectivity\nand robust security.\n\n"); + + widget_add_text_box_element( + meshimi->widget, + 0, + 0, + 128, + 14, + AlignCenter, + AlignBottom, + "\e#\e! \e!\n", + false); + widget_add_text_box_element( + meshimi->widget, + 0, + 2, + 128, + 14, + AlignCenter, + AlignBottom, + "\e#\e! Meshimi \e!\n", + false); + widget_add_text_scroll_element(meshimi->widget, 0, 16, 128, 50, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(meshimi->view_dispatcher, MeshimiViewIdWidget); +} + +bool meshimi_scene_about_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void meshimi_scene_about_on_exit(void* context) { + Meshimi* meshimi = context; + widget_reset(meshimi->widget); +} \ No newline at end of file diff --git a/scenes/meshimi_scene_config.h b/scenes/meshimi_scene_config.h new file mode 100644 index 0000000..bca0dfd --- /dev/null +++ b/scenes/meshimi_scene_config.h @@ -0,0 +1,7 @@ +ADD_SCENE(meshimi, start, Start) +ADD_SCENE(meshimi, connect, Connect) +ADD_SCENE(meshimi, test, Test) +ADD_SCENE(meshimi, configuration, Configuration) +ADD_SCENE(meshimi, about, About) +ADD_SCENE(meshimi, mode, Mode) +ADD_SCENE(meshimi, frequency, Frequency) diff --git a/scenes/meshimi_scene_configuration.c b/scenes/meshimi_scene_configuration.c new file mode 100644 index 0000000..55161a0 --- /dev/null +++ b/scenes/meshimi_scene_configuration.c @@ -0,0 +1,173 @@ +#include "../meshimi.h" +#include "meshimi_custom_event.h" +#include + +enum MeshimiConfigurationItem { + MeshimiMode, + MeshimiFrequency, +}; + +static void meshimi_scene_configuration_var_list_enter_callback(void* context, uint32_t index) { + furi_assert(context); + Meshimi* meshimi = context; + if(index == MeshimiMode) { + view_dispatcher_send_custom_event(meshimi->view_dispatcher, MeshimiEventMode); + } else if(index == MeshimiFrequency) { + view_dispatcher_send_custom_event(meshimi->view_dispatcher, MeshimiEventFrequency); + } +} + +void meshimi_scene_configuration_spreading_factor_callback(VariableItem* item) { + Meshimi* meshimi = variable_item_get_context(item); + furi_assert(meshimi); + MeshimiConfig* config = meshimi->config; + furi_assert(config); + + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, meshimi_spreading_factor_get_text(index)); + meshimi_spreading_factor_set(config, index); + + view_dispatcher_send_custom_event(meshimi->view_dispatcher, MeshimiEventSpreadingFactor); +} + +void meshimi_scene_configuration_bandwidth_callback(VariableItem* item) { + Meshimi* meshimi = variable_item_get_context(item); + furi_assert(meshimi); + MeshimiConfig* config = meshimi->config; + furi_assert(config); + + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, meshimi_bandwidth_get_text(index)); + meshimi_bandwidth_set(config, index); + + view_dispatcher_send_custom_event(meshimi->view_dispatcher, MeshimiEventBandwidth); +} + +void meshimi_scene_configuration_coding_rate_callback(VariableItem* item) { + Meshimi* meshimi = variable_item_get_context(item); + furi_assert(meshimi); + MeshimiConfig* config = meshimi->config; + furi_assert(config); + + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, meshimi_coding_rate_get_text(index)); + meshimi_coding_rate_set(config, index); + + view_dispatcher_send_custom_event(meshimi->view_dispatcher, MeshimiEventCodingRate); +} + +void meshimi_scene_configuration_ldro_callback(VariableItem* item) { + Meshimi* meshimi = variable_item_get_context(item); + furi_assert(meshimi); + MeshimiConfig* config = meshimi->config; + furi_assert(config); + + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, meshimi_ldro_get_text(index)); + meshimi_ldro_set(config, index); + + view_dispatcher_send_custom_event(meshimi->view_dispatcher, MeshimiEventLDRO); +} + +void meshimi_scene_configuration_on_enter(void* context) { + Meshimi* meshimi = context; + MeshimiConfig* config = meshimi->config; + MeshimiConfigMode mode = meshimi_mode_get(config); + VariableItem* item; + + variable_item_list_set_enter_callback( + meshimi->variable_item_list, meshimi_scene_configuration_var_list_enter_callback, meshimi); + + if(mode == ModeSimpleRX) { + variable_item_list_add( + meshimi->variable_item_list, "Mode: Simple RX", 0, NULL, NULL); + variable_item_list_add( + meshimi->variable_item_list, "Freq: 868,000,000Hz", 0, NULL, NULL); + item = variable_item_list_add( + meshimi->variable_item_list, + "Spread. Factor:", + SPREADING_FACTOR_COUNT, + meshimi_scene_configuration_spreading_factor_callback, + meshimi); + variable_item_set_current_value_index(item, meshimi_spreading_factor_get(config)); + variable_item_set_current_value_text( + item, meshimi_spreading_factor_get_text(meshimi_spreading_factor_get(config))); + + item = variable_item_list_add( + meshimi->variable_item_list, + "Bandwidth:", + BANDWIDTH_COUNT, + meshimi_scene_configuration_bandwidth_callback, + meshimi); + variable_item_set_current_value_index(item, meshimi_bandwidth_get(config)); + variable_item_set_current_value_text( + item, meshimi_bandwidth_get_text(meshimi_bandwidth_get(config))); + + item = variable_item_list_add( + meshimi->variable_item_list, + "Coding Rate:", + CODING_RATE_COUNT, + meshimi_scene_configuration_coding_rate_callback, + meshimi); + variable_item_set_current_value_index(item, meshimi_coding_rate_get(config)); + variable_item_set_current_value_text( + item, meshimi_coding_rate_get_text(meshimi_coding_rate_get(config))); + + item = variable_item_list_add( + meshimi->variable_item_list, + "LDRO:", + LDRO_COUNT, + meshimi_scene_configuration_ldro_callback, + meshimi); + variable_item_set_current_value_index(item, meshimi_ldro_get(config)); + variable_item_set_current_value_text( + item, meshimi_ldro_get_text(meshimi_ldro_get(config))); + + } else if(mode == ModeSimpleTX) { + variable_item_list_add(meshimi->variable_item_list, "Mode: Simple TX", 0, NULL, NULL); + } else if(mode == ModeMeshtastic) { + variable_item_list_add(meshimi->variable_item_list, "Mode: Meshtastic", 0, NULL, NULL); + } else if(mode == ModeMeshimi) { + variable_item_list_add(meshimi->variable_item_list, "Mode: Meshimi", 0, NULL, NULL); + } else if(mode == ModeLoRaWAN) { + variable_item_list_add(meshimi->variable_item_list, "Mode: LoRaWAN", 0, NULL, NULL); + } + + view_dispatcher_switch_to_view(meshimi->view_dispatcher, MeshimiViewIdVariableItemList); +} + +bool meshimi_scene_configuration_on_event(void* context, SceneManagerEvent event) { + Meshimi* meshimi = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == MeshimiEventMode) { + scene_manager_set_scene_state(meshimi->scene_manager, MeshimiSceneStart, MeshimiMode); + scene_manager_next_scene(meshimi->scene_manager, MeshimiSceneMode); + consumed = true; + } else if(event.event == MeshimiEventFrequency) { + scene_manager_set_scene_state( + meshimi->scene_manager, MeshimiSceneStart, MeshimiFrequency); + scene_manager_next_scene(meshimi->scene_manager, MeshimiSceneFrequency); + consumed = true; + } + } + + if(event.type == SceneManagerEventTypeBack) { + scene_manager_next_scene(meshimi->scene_manager, MeshimiSceneStart); + consumed = true; + } + + return consumed; +} + +void meshimi_scene_configuration_on_exit(void* context) { + Meshimi* meshimi = context; + variable_item_list_set_selected_item(meshimi->variable_item_list, 0); + variable_item_list_reset(meshimi->variable_item_list); + widget_reset(meshimi->widget); +} diff --git a/scenes/meshimi_scene_connect.c b/scenes/meshimi_scene_connect.c new file mode 100644 index 0000000..c362136 --- /dev/null +++ b/scenes/meshimi_scene_connect.c @@ -0,0 +1,29 @@ +#include "../meshimi.h" +#include "begoonlab_meshimi_icons.h" +#include + +void meshimi_scene_connect_on_enter(void* context) { + Meshimi* meshimi = context; + + //IconAnimation *icon = icon_animation_alloc(&A_Intro_128x64); +// view_tie_icon_animation(meshimi->view_dispatcher, icon); + //icon_animation_start(icon); + //one_shot_view_start_animation(meshimi->one_shot_view, &A_Levelup1_128x64); + //canvas_draw_icon(meshimi->widget, 0, 0, &A_Intro_128x64); +// widget_add_icon_element(meshimi->widget, 0, 0, &A_Intro_128x64); + //widget_add_icon_element(meshimi->widget, 0, 0, &I_Meshimi_128x64); +// widget_reset(meshimi->widget); + + view_dispatcher_switch_to_view(meshimi->view_dispatcher, MeshimiViewIdWidget); +} + +bool meshimi_scene_connect_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void meshimi_scene_connect_on_exit(void* context) { + Meshimi* meshimi = context; + widget_reset(meshimi->widget); +} \ No newline at end of file diff --git a/scenes/meshimi_scene_frequency.c b/scenes/meshimi_scene_frequency.c new file mode 100644 index 0000000..82542fe --- /dev/null +++ b/scenes/meshimi_scene_frequency.c @@ -0,0 +1,122 @@ +#include "../meshimi.h" +#include "meshimi_custom_event.h" +#include +#include +#include + +void meshimi_scene_frequency_input_callback(void* context) { + furi_assert(context); + Meshimi* meshimi = context; + view_dispatcher_send_custom_event(meshimi->view_dispatcher, MeshimiEventFrequency); +} + +bool validator_lora_frequency_callback(const char* text, FuriString* error, void* context) { + furi_check(context); + + // Check if the text length is exactly 9 digits + if(strlen(text) != 9) { + furi_string_printf(error, "Frequency\nmust be\nexactly 9\ndigits."); + return false; + } + + // Check if all characters are digits + for(int i = 0; i < 9; i++) { + if(!isdigit((unsigned char)text[i])) { + furi_string_printf(error, "Frequency\nmust contain\nonly digits."); + return false; + } + } + + char* endptr; + long frequency = strtol(text, &endptr, 10); + + // Check if the entire string was successfully converted + if(*endptr != '\0') { + furi_string_printf(error, "Frequency\nis invalid."); + return false; + } + + const FuriHalRegion* const region = furi_hal_region_get(); + + // Check regional RF allowance + if(region) { + bool is_within_range = false; + for(uint16_t i = 0; i < region->bands_count; ++i) { + if((unsigned long)frequency >= region->bands[i].start && + (unsigned long)frequency <= region->bands[i].end) { + is_within_range = true; + break; + } + } + if(!is_within_range) { + furi_string_printf(error, "Frequency\nis out of\nallowed\nrange"); + return false; + } + } else { + // Check LoRa modem absolute limits + if(frequency < 150000000 || frequency > 960000000) { + furi_string_printf(error, "Frequency\nis out of\nallowed\nrange"); + return false; + } + } + + return true; +} + +void meshimi_scene_frequency_on_enter(void* context) { + Meshimi* meshimi = context; + MeshimiConfig* config = meshimi->config; + FuriString* frequency_text = furi_string_alloc(); + furi_string_printf(frequency_text, "%lu", meshimi_frequency_get(config)); + + strncpy(meshimi->text_store, furi_string_get_cstr(frequency_text), FREQUENCY_TEXT_LEN); + text_input_set_header_text(meshimi->text_input, "LoRa Frequency:"); + text_input_set_result_callback( + meshimi->text_input, + meshimi_scene_frequency_input_callback, + meshimi, + meshimi->text_store, + FREQUENCY_TEXT_LEN, + NULL); + + text_input_set_validator(meshimi->text_input, validator_lora_frequency_callback, meshimi); + + furi_string_free(frequency_text); + + view_dispatcher_switch_to_view(meshimi->view_dispatcher, MeshimiViewIdTextInput); +} + +bool meshimi_scene_frequency_on_event(void* context, SceneManagerEvent event) { + Meshimi* meshimi = context; + MeshimiConfig* config = meshimi->config; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == MeshimiEventFrequency) { + char* endptr; + long frequency = strtol(meshimi->text_store, &endptr, 10); + + // Check if the entire string was successfully converted + if(*endptr != '\0') { + furi_crash(); + } + + meshimi_frequency_set(config, frequency); + + scene_manager_previous_scene(meshimi->scene_manager); + consumed = true; + } + } + + return consumed; +} + +void meshimi_scene_frequency_on_exit(void* context) { + Meshimi* meshimi = context; + + // Clear validator + text_input_set_validator(meshimi->text_input, NULL, NULL); + + // Clear view + text_input_reset(meshimi->text_input); +} \ No newline at end of file diff --git a/scenes/meshimi_scene_mode.c b/scenes/meshimi_scene_mode.c new file mode 100644 index 0000000..4763575 --- /dev/null +++ b/scenes/meshimi_scene_mode.c @@ -0,0 +1,35 @@ +#include "../meshimi.h" + +void meshimi_view_mode_ok_callback(InputType type, void* context) { + furi_assert(context); + Meshimi* meshimi = context; + + if(type == InputTypePress) { + notification_message(meshimi->notifications, &sequence_set_green_255); + } else if(type == InputTypeRelease) { + notification_message(meshimi->notifications, &sequence_reset_green); + } + + scene_manager_next_scene(meshimi->scene_manager, MeshimiSceneConfiguration); +} + +void meshimi_scene_mode_on_exit(void* context) { + UNUSED(context); +} + +void meshimi_scene_mode_on_enter(void* context) { + furi_assert(context); + Meshimi* meshimi = context; + view_dispatcher_switch_to_view(meshimi->view_dispatcher, MeshimiViewIdMode); +} + +bool meshimi_scene_mode_on_event(void* context, SceneManagerEvent event) { + furi_assert(context); + Meshimi* meshimi = context; + UNUSED(event); + + meshimi_view_mode_set_ok_callback( + meshimi->meshimi_view_mode, meshimi_view_mode_ok_callback, meshimi); + + return false; +} \ No newline at end of file diff --git a/scenes/meshimi_scene_start.c b/scenes/meshimi_scene_start.c new file mode 100644 index 0000000..a307c4e --- /dev/null +++ b/scenes/meshimi_scene_start.c @@ -0,0 +1,66 @@ +#include "../meshimi.h" + +enum SubmenuIndex { + SubmenuIndexConnect, + SubmenuIndexTest, + SubmenuIndexConfiguration, + SubmenuIndexAbout, +}; + +void meshimi_scene_start_submenu_callback(void *context, uint32_t index) { + Meshimi *meshimi = context; + view_dispatcher_send_custom_event(meshimi->view_dispatcher, index); +} + +void meshimi_scene_start_on_enter(void *context) { + Meshimi *meshimi = context; + + submenu_add_item( + meshimi->submenu, "Connect", SubmenuIndexConnect, meshimi_scene_start_submenu_callback, meshimi); + submenu_add_item( + meshimi->submenu, "Config", SubmenuIndexConfiguration, meshimi_scene_start_submenu_callback, meshimi); + submenu_add_item( + meshimi->submenu, "About", SubmenuIndexAbout, meshimi_scene_start_submenu_callback, meshimi); + submenu_set_selected_item( + meshimi->submenu, scene_manager_get_scene_state(meshimi->scene_manager, MeshimiSceneStart)); + + view_dispatcher_switch_to_view(meshimi->view_dispatcher, MeshimiViewIdMenu); +} + +bool meshimi_scene_start_on_event(void *context, SceneManagerEvent event) { + Meshimi *meshimi = context; + if (event.type == SceneManagerEventTypeBack) { + //exit app + scene_manager_stop(meshimi->scene_manager); + view_dispatcher_stop(meshimi->view_dispatcher); + return true; + } else if (event.type == SceneManagerEventTypeCustom) { + if (event.event == SubmenuIndexConnect) { + scene_manager_set_scene_state( + meshimi->scene_manager, MeshimiSceneStart, SubmenuIndexConnect); + scene_manager_next_scene(meshimi->scene_manager, MeshimiSceneConnect); + return true; + } else if (event.event == SubmenuIndexTest) { + scene_manager_set_scene_state( + meshimi->scene_manager, MeshimiSceneStart, SubmenuIndexTest); + scene_manager_next_scene(meshimi->scene_manager, MeshimiSceneTest); + return true; + } else if (event.event == SubmenuIndexConfiguration) { + scene_manager_set_scene_state( + meshimi->scene_manager, MeshimiSceneStart, SubmenuIndexConfiguration); + scene_manager_next_scene(meshimi->scene_manager, MeshimiSceneConfiguration); + return true; + } else if (event.event == SubmenuIndexAbout) { + scene_manager_set_scene_state( + meshimi->scene_manager, MeshimiSceneStart, SubmenuIndexAbout); + scene_manager_next_scene(meshimi->scene_manager, MeshimiSceneAbout); + return true; + } + } + return false; +} + +void meshimi_scene_start_on_exit(void *context) { + Meshimi *meshimi = context; + submenu_reset(meshimi->submenu); +} diff --git a/scenes/meshimi_scene_test.c b/scenes/meshimi_scene_test.c new file mode 100644 index 0000000..9fbbc21 --- /dev/null +++ b/scenes/meshimi_scene_test.c @@ -0,0 +1,158 @@ +#include "../meshimi.h" + +#include +#include +#include +#include +#include "../proto/spi.pb.h" +#include "../checksum.h" +#include "pb_encode.h" + +const NotificationSequence sequence_test_wait = { + &message_green_255, + &message_red_255, + +// &message_vibro_on, + &message_note_c6, + &message_delay_50, + &message_note_d6, + &message_delay_100, + &message_note_c6, + &message_delay_50, + &message_note_e6, + &message_delay_50, + &message_sound_off, +// &message_vibro_off, + + &message_delay_500, + NULL, +}; + +const NotificationSequence sequence_test_error = { + &message_red_255, + &message_note_e6, + &message_delay_50, + &message_sound_off, + + &message_delay_1000, + NULL, +}; + +void meshimi_scene_test_on_enter(void *context) { + Meshimi *meshimi = context; + + +// FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(LoraTestEvent)); +// LoraTestEvent event; + FuriString *buffer = furi_string_alloc(); +// FuriTimer* timer = furi_timer_alloc(lora_scene_test_update, FuriTimerTypePeriodic, event_queue); +// furi_timer_start(timer, furi_kernel_get_tick_frequency()); + furi_string_cat_printf(buffer, "Waiting for Meshimi module...\n"); + + widget_add_string_multiline_element( + meshimi->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(buffer)); + + view_dispatcher_switch_to_view(meshimi->view_dispatcher, MeshimiViewIdWidget); + + notification_message(meshimi->notifications, &sequence_test_wait); + + furi_string_cat_printf(buffer, "SPI receiving the data\n"); + widget_add_string_multiline_element( + meshimi->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(buffer)); + + SpiHeader txHeader = SpiHeader_init_zero; + uint8_t spiBuffer[256] = {0}; + txHeader.status = SpiHeader_Status_STAND_BY; + txHeader.length = 0; + size_t headerSize = 7; // TODO: Make it dynamically + for (int i = 0; i < 100; ++i) { + txHeader.length = 0; + txHeader.status = SpiHeader_Status_STAND_BY; + memset(spiBuffer, 0, sizeof(spiBuffer)); + pb_ostream_t stream = pb_ostream_from_buffer(spiBuffer, headerSize - 4); + + bool status = pb_encode_ex(&stream, SpiHeader_fields, &txHeader, PB_ENCODE_DELIMITED); + size_t msglen = stream.bytes_written; + + if (!status) { + // TODO: Handle error + } + + uint32_t crc = crc_32(spiBuffer, stream.bytes_written); + spiBuffer[stream.bytes_written] = (uint8_t) (crc >> 24); + spiBuffer[stream.bytes_written + 1] = (uint8_t) (crc >> 16); + spiBuffer[stream.bytes_written + 2] = (uint8_t) (crc >> 8); + spiBuffer[stream.bytes_written + 3] = (uint8_t) (crc); + msglen += 4; + + furi_hal_gpio_write(furi_hal_spi_bus_handle_external.cs, false); + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_external); + furi_hal_spi_bus_tx( + &furi_hal_spi_bus_handle_external, spiBuffer, msglen, 200); + furi_hal_spi_release(&furi_hal_spi_bus_handle_external); + furi_hal_gpio_write(furi_hal_spi_bus_handle_external.cs, true); + furi_delay_ms(100); + + furi_hal_gpio_write(furi_hal_spi_bus_handle_external.cs, false); + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_external); + furi_hal_spi_bus_rx( + &furi_hal_spi_bus_handle_external, spiBuffer, msglen, 200); + furi_hal_spi_release(&furi_hal_spi_bus_handle_external); + furi_hal_gpio_write(furi_hal_spi_bus_handle_external.cs, true); + + + furi_delay_ms(100); + } + + // TODO: Implement SPI + // Example: https://github.com/skotopes/flipperzero_max31855/blob/dev/max31855.c + + furi_delay_ms(5000); + widget_reset(meshimi->widget); + + widget_add_string_multiline_element( + meshimi->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, "Meshimi module disconnected\n"); + + notification_message(meshimi->notifications, &sequence_test_error); + + furi_delay_ms(3000); + +// popup_reset(meshimi->popup); +// popup_set_header(meshimi->popup, "New Message!", 63, 3, AlignCenter, AlignTop); +// popup_set_text(meshimi->popup, "Here is some message text. And one more sentence.", 0, 17, AlignLeft, AlignTop); +// +// popup_set_timeout(meshimi->popup, 1500); +// popup_set_context(meshimi->popup, lora); +//// popup_set_callback(meshimi->popup, subghz_test_scene_show_only_rx_popup_callback); +// popup_enable_timeout(meshimi->popup); +// view_dispatcher_switch_to_view(meshimi->view_dispatcher, LoRaViewIdPopup); +// furi_delay_ms(3000); +// view_dispatcher_switch_to_view(meshimi->view_dispatcher, LoRaViewIdWidget); +// popup_reset(meshimi->popup); +// while(1) { +// furi_check(furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk); +// if(event.type == LoraTestEventTypeInput) { +// if((event.input.type == InputTypeShort) && (event.input.key == InputKeyBack)) { +// break; +// } +// } +// if((event.input.type == InputTypeShort) && (event.input.key == InputKeyBack)) { +// break; +// } else { +// +// } +// } + + furi_string_free(buffer); +} + +bool meshimi_scene_test_on_event(void *context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void meshimi_scene_test_on_exit(void *context) { + Meshimi *meshimi = context; + widget_reset(meshimi->widget); +} \ No newline at end of file diff --git a/views/meshimi_view_mode.c b/views/meshimi_view_mode.c new file mode 100644 index 0000000..88c0dae --- /dev/null +++ b/views/meshimi_view_mode.c @@ -0,0 +1,129 @@ +#include "meshimi_view_mode.h" + +struct MeshimiViewMode { + View* view; + MeshimiConfig * meshimi_config; + MeshimiViewModeOkCallback callback; + void* context; +}; + +typedef struct { + MeshimiConfigMode mode; +} MeshimiViewModeModel; + +static void meshimi_view_mode_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + MeshimiViewModeModel* model = context; + + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 35, 3, AlignLeft, AlignTop, "Select Mode"); + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned( + canvas, 64, 16, AlignCenter, AlignTop, "Press < or > to change mode"); + + elements_multiline_text_aligned( + canvas, 64, 32, AlignCenter, AlignTop, meshimi_mode_get_text(model->mode)); +} + +static bool meshimi_view_mode_process_right(MeshimiViewMode* meshimi_view_mode) { + with_view_model( + meshimi_view_mode->view, + MeshimiViewModeModel * model, + { + if(model->mode < MODE_COUNT - 1) { + model->mode = meshimi_mode_get_value()[model->mode + 1]; + } + }, + true); + return true; +} + +static bool meshimi_view_mode_process_left(MeshimiViewMode* meshimi_view_mode) { + with_view_model( + meshimi_view_mode->view, + MeshimiViewModeModel * model, + { + if(model->mode) { + model->mode = meshimi_mode_get_value()[model->mode - 1]; + } + }, + true); + return true; +} + +static bool meshimi_view_mode_process_ok(MeshimiViewMode* meshimi_view_mode, InputEvent* event) { + bool consumed = false; + + with_view_model( + meshimi_view_mode->view, + MeshimiViewModeModel * model, + { + if(event->type == InputTypePress) { + meshimi_mode_set(meshimi_view_mode->meshimi_config, model->mode); + consumed = true; + } else if(event->type == InputTypeRelease) { + meshimi_mode_set(meshimi_view_mode->meshimi_config, model->mode); + consumed = true; + } + meshimi_view_mode->callback(event->type, meshimi_view_mode->context); + }, + true); + + return consumed; +} + +static bool meshimi_view_mode_input_callback(InputEvent* event, void* context) { + furi_assert(context); + MeshimiViewMode* meshimi_view_mode = context; + bool consumed = false; + + if(event->type == InputTypeShort) { + if(event->key == InputKeyRight) { + consumed = meshimi_view_mode_process_right(meshimi_view_mode); + } else if(event->key == InputKeyLeft) { + consumed = meshimi_view_mode_process_left(meshimi_view_mode); + } + } else if(event->key == InputKeyOk) { + consumed = meshimi_view_mode_process_ok(meshimi_view_mode, event); + } + + return consumed; +} + +MeshimiViewMode* meshimi_view_mode_alloc(MeshimiConfig * meshimi_config) { + MeshimiViewMode* meshimi_view_mode = malloc(sizeof(MeshimiViewMode)); + meshimi_view_mode->view = view_alloc(); + meshimi_view_mode->meshimi_config = meshimi_config; + view_set_context(meshimi_view_mode->view, meshimi_view_mode); + view_allocate_model( + meshimi_view_mode->view, ViewModelTypeLocking, sizeof(MeshimiViewModeModel)); + view_set_draw_callback(meshimi_view_mode->view, meshimi_view_mode_draw_callback); + view_set_input_callback(meshimi_view_mode->view, meshimi_view_mode_input_callback); + + return meshimi_view_mode; +} + +void meshimi_view_mode_free(MeshimiViewMode* meshimi_view_mode) { + furi_assert(meshimi_view_mode); + view_free(meshimi_view_mode->view); + free(meshimi_view_mode); +} + +View* meshimi_view_mode_get_view(MeshimiViewMode* meshimi_view_mode) { + furi_assert(meshimi_view_mode); + return meshimi_view_mode->view; +} + +void meshimi_view_mode_set_ok_callback(MeshimiViewMode* meshimi_view_mode, MeshimiViewModeOkCallback callback, void* context) { + furi_assert(meshimi_view_mode); + furi_assert(callback); + with_view_model( + meshimi_view_mode->view, + MeshimiViewModeModel * model, + { + UNUSED(model); + meshimi_view_mode->callback = callback; + meshimi_view_mode->context = context; + }, + false); +} \ No newline at end of file diff --git a/views/meshimi_view_mode.h b/views/meshimi_view_mode.h new file mode 100644 index 0000000..b3ace6a --- /dev/null +++ b/views/meshimi_view_mode.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include "helpers/meshimi_config.h" + +typedef struct Meshimi Meshimi; +typedef struct MeshimiViewMode MeshimiViewMode; +typedef void (*MeshimiViewModeOkCallback)(InputType type, void* context); + +MeshimiViewMode* meshimi_view_mode_alloc(MeshimiConfig * meshimi_config); + +void meshimi_view_mode_free(MeshimiViewMode* meshimi_view_mode); + +View* meshimi_view_mode_get_view(MeshimiViewMode* meshimi_view_mode); + +void meshimi_view_mode_set_ok_callback( + MeshimiViewMode* meshimi_view_mode, + MeshimiViewModeOkCallback callback, + void* context); \ No newline at end of file