diff --git a/applications/main/nfc/nfc_i.h b/applications/main/nfc/nfc_i.h index 339db66c7..a3a62972c 100644 --- a/applications/main/nfc/nfc_i.h +++ b/applications/main/nfc/nfc_i.h @@ -45,6 +45,7 @@ ARRAY_DEF(MfClassicUserKeys, char*, M_PTR_OPLIST); #define NFC_TEXT_STORE_SIZE 128 +#define NFC_APP_FOLDER ANY_PATH("nfc") typedef enum { NfcRpcStateIdle, diff --git a/applications/main/nfc/scenes/nfc_scene_delete.c b/applications/main/nfc/scenes/nfc_scene_delete.c index cbb52bfd0..32bd92a07 100644 --- a/applications/main/nfc/scenes/nfc_scene_delete.c +++ b/applications/main/nfc/scenes/nfc_scene_delete.c @@ -52,6 +52,7 @@ void nfc_scene_delete_on_enter(void* context) { bool nfc_scene_delete_on_event(void* context, SceneManagerEvent event) { Nfc* nfc = context; + furi_string_printf(nfc->dev->folder, "%s", NFC_APP_FOLDER); bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { diff --git a/applications/main/nfc/scenes/nfc_scene_file_select.c b/applications/main/nfc/scenes/nfc_scene_file_select.c index 374a933d1..00ccf9012 100644 --- a/applications/main/nfc/scenes/nfc_scene_file_select.c +++ b/applications/main/nfc/scenes/nfc_scene_file_select.c @@ -3,6 +3,7 @@ void nfc_scene_file_select_on_enter(void* context) { Nfc* nfc = context; + furi_string_printf(nfc->dev->folder, "%s", NFC_APP_FOLDER); // Process file_select return nfc_device_set_loading_callback(nfc->dev, nfc_show_loading_popup, nfc); if(!furi_string_size(nfc->dev->load_path)) { diff --git a/applications/main/nfc/scenes/nfc_scene_restore_original_confirm.c b/applications/main/nfc/scenes/nfc_scene_restore_original_confirm.c index 730dd41e8..0836c4183 100644 --- a/applications/main/nfc/scenes/nfc_scene_restore_original_confirm.c +++ b/applications/main/nfc/scenes/nfc_scene_restore_original_confirm.c @@ -24,6 +24,7 @@ void nfc_scene_restore_original_confirm_on_enter(void* context) { bool nfc_scene_restore_original_confirm_on_event(void* context, SceneManagerEvent event) { Nfc* nfc = context; + furi_string_printf(nfc->dev->folder, "%s", NFC_APP_FOLDER); bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { diff --git a/applications/main/nfc/scenes/nfc_scene_save_name.c b/applications/main/nfc/scenes/nfc_scene_save_name.c index 43d476ea7..b297b2d23 100644 --- a/applications/main/nfc/scenes/nfc_scene_save_name.c +++ b/applications/main/nfc/scenes/nfc_scene_save_name.c @@ -51,6 +51,7 @@ void nfc_scene_save_name_on_enter(void* context) { bool nfc_scene_save_name_on_event(void* context, SceneManagerEvent event) { Nfc* nfc = context; + furi_string_printf(nfc->dev->folder, "%s", NFC_APP_FOLDER); bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { diff --git a/applications/plugins/am2320_temp_sensor/application.fam b/applications/plugins/am2320_temp_sensor/application.fam new file mode 100644 index 000000000..f7b6dfbf2 --- /dev/null +++ b/applications/plugins/am2320_temp_sensor/application.fam @@ -0,0 +1,14 @@ +App( + appid="AM2320_temp_sensor", + name="[AM2320] Temp. Sensor", + apptype=FlipperAppType.EXTERNAL, + entry_point="am_temperature_sensor_app", + cdefines=["APP_AM_TEMPERATURE_SENSOR"], + requires=[ + "gui", + ], + stack_size=2 * 1024, + order=90, + fap_icon="temperature_sensor.png", + fap_category="GPIO", +) diff --git a/applications/plugins/am2320_temp_sensor/temperature_sensor.c b/applications/plugins/am2320_temp_sensor/temperature_sensor.c new file mode 100644 index 000000000..212014e22 --- /dev/null +++ b/applications/plugins/am2320_temp_sensor/temperature_sensor.c @@ -0,0 +1,336 @@ +/* Flipper Plugin to read the values from a AM2320/AM2321 Sensor */ +/* Created by @xMasterX, original app (was used as template) by Mywk - https://github.com/Mywk */ +/* Lib used as reference: https://github.com/Gozem/am2320/blob/master/am2321.c*/ +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#define TS_DEFAULT_VALUE 0xFFFF + +#define AM2320_ADDRESS (0x5C << 1) + +#define DATA_BUFFER_SIZE 8 + +// External I2C BUS +#define I2C_BUS &furi_hal_i2c_handle_external + +typedef enum { + TSSInitializing, + TSSNoSensor, + TSSPendingUpdate, +} TSStatus; + +typedef enum { + TSEventTypeTick, + TSEventTypeInput, +} TSEventType; + +typedef struct { + TSEventType type; + InputEvent input; +} TSEvent; + +extern const NotificationSequence sequence_blink_red_100; +extern const NotificationSequence sequence_blink_blue_100; + +static TSStatus temperature_sensor_current_status = TSSInitializing; + +// Temperature and Humidity data buffers, ready to print +char ts_data_buffer_temperature_c[DATA_BUFFER_SIZE]; +char ts_data_buffer_temperature_f[DATA_BUFFER_SIZE]; +char ts_data_buffer_relative_humidity[DATA_BUFFER_SIZE]; +char ts_data_buffer_absolute_humidity[DATA_BUFFER_SIZE]; + +// CRC16 calculation +static uint16_t get_crc16(const uint8_t* buf, size_t len) { + uint16_t crc = 0xFFFF; + + while(len--) { + crc ^= (uint16_t)*buf++; + for(unsigned i = 0; i < 8; i++) { + if(crc & 0x0001) { + crc >>= 1; + crc ^= 0xA001; + } else { + crc >>= 1; + } + } + } + + return crc; +} +// Combine bytes +static uint16_t combine_bytes(uint8_t msb, uint8_t lsb) { + return ((uint16_t)msb << 8) | (uint16_t)lsb; +} + +// Executes an I2C wake up, sends command and reads result +// true if fetch was successful, false otherwise +static bool temperature_sensor_get_data(uint8_t* buffer, uint8_t size) { + uint32_t timeout = furi_ms_to_ticks(100); + uint8_t cmdbuffer[3] = {0, 0, 0}; + bool ret = false; + + // Aquire I2C bus + furi_hal_i2c_acquire(I2C_BUS); + + // Wake UP AM2320 (sensor goes to sleep to not warm up and affect the humidity sensor) + furi_hal_i2c_is_device_ready(I2C_BUS, (uint8_t)AM2320_ADDRESS, timeout); + // Check if device woken up then we do next stuff + if(furi_hal_i2c_is_device_ready(I2C_BUS, (uint8_t)AM2320_ADDRESS, timeout)) { + // Wait a bit + furi_delay_us(1000); + + // Prepare command: Addr 0x03, start register = 0x00, number of registers to read = 0x04 + cmdbuffer[0] = 0x03; + cmdbuffer[1] = 0x00; + cmdbuffer[2] = 0x04; + + // Transmit command to read registers + ret = furi_hal_i2c_tx(I2C_BUS, (uint8_t)AM2320_ADDRESS, cmdbuffer, 3, timeout); + + // Wait a bit + furi_delay_us(1600); + if(ret) { + /* + * Read out 8 bytes of data + * Byte 0: Should be Modbus function code 0x03 + * Byte 1: Should be number of registers to read (0x04) + * Byte 2: Humidity msb + * Byte 3: Humidity lsb + * Byte 4: Temperature msb + * Byte 5: Temperature lsb + * Byte 6: CRC lsb byte + * Byte 7: CRC msb byte + */ + ret = furi_hal_i2c_rx(I2C_BUS, (uint8_t)AM2320_ADDRESS, buffer, size, timeout); + } + } + // Release i2c bus + furi_hal_i2c_release(I2C_BUS); + + return ret; +} + +// Fetches temperature and humidity from sensor +// Temperature and humidity must be preallocated +// true if fetch was successful, false otherwise +static bool temperature_sensor_fetch_info(double* temperature, double* humidity) { + *humidity = (float)0; + bool ret = false; + + uint8_t buffer[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + + // Fetch data from sensor + ret = temperature_sensor_get_data(buffer, 8); + + // If we got no result + if(!ret) return false; + + if(buffer[0] != 0x03) return false; // must be 0x03 modbus reply + if(buffer[1] != 0x04) return false; // must be 0x04 number of registers reply + + // Check CRC16 sum, if not correct - return false + uint16_t crcdata = get_crc16(buffer, 6); + uint16_t crcread = combine_bytes(buffer[7], buffer[6]); + if(crcdata != crcread) return false; + + // Combine bytes for temp and humidity + uint16_t temp16 = combine_bytes(buffer[4], buffer[5]); + uint16_t humi16 = combine_bytes(buffer[2], buffer[3]); + + /* Temperature resolution is 16Bit, + * temperature highest bit (Bit15) is equal to 1 indicates a + * negative temperature, the temperature highest bit (Bit15) + * is equal to 0 indicates a positive temperature; + * temperature in addition to the most significant bit (Bit14 ~ Bit0) + * indicates the temperature sensor string value. + * Temperature sensor value is a string of 10 times the + * actual temperature value. + */ + if(temp16 & 0x8000) { + temp16 = -(temp16 & 0x7FFF); + } + + // Prepare output data + *temperature = (float)temp16 / 10.0; + *humidity = (float)humi16 / 10.0; + + return true; +} + +// Draw callback + +static void temperature_sensor_draw_callback(Canvas* canvas, void* ctx) { + UNUSED(ctx); + + canvas_clear(canvas); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 2, 10, "AM2320/AM2321 Sensor"); + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 2, 62, "Press back to exit."); + + switch(temperature_sensor_current_status) { + case TSSInitializing: + canvas_draw_str(canvas, 2, 30, "Initializing.."); + break; + case TSSNoSensor: + canvas_draw_str(canvas, 2, 30, "No sensor found!"); + break; + case TSSPendingUpdate: { + canvas_draw_str(canvas, 3, 24, "Temperature"); + canvas_draw_str(canvas, 68, 24, "Humidity"); + + // Draw vertical lines + canvas_draw_line(canvas, 61, 16, 61, 50); + canvas_draw_line(canvas, 62, 16, 62, 50); + + // Draw horizontal line + canvas_draw_line(canvas, 2, 27, 122, 27); + + // Draw temperature and humidity values + canvas_draw_str(canvas, 8, 38, ts_data_buffer_temperature_c); + canvas_draw_str(canvas, 42, 38, "C"); + canvas_draw_str(canvas, 8, 48, ts_data_buffer_temperature_f); + canvas_draw_str(canvas, 42, 48, "F"); + canvas_draw_str(canvas, 68, 38, ts_data_buffer_relative_humidity); + canvas_draw_str(canvas, 100, 38, "%"); + canvas_draw_str(canvas, 68, 48, ts_data_buffer_absolute_humidity); + canvas_draw_str(canvas, 100, 48, "g/m3"); + + } break; + default: + break; + } +} + +// Input callback + +static void temperature_sensor_input_callback(InputEvent* input_event, void* ctx) { + furi_assert(ctx); + FuriMessageQueue* event_queue = ctx; + + TSEvent event = {.type = TSEventTypeInput, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +// Timer callback + +static void temperature_sensor_timer_callback(FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + TSEvent event = {.type = TSEventTypeTick}; + furi_message_queue_put(event_queue, &event, 0); +} + +// App entry point + +int32_t am_temperature_sensor_app(void* p) { + UNUSED(p); + + furi_hal_power_suppress_charge_enter(); + // Declare our variables and assign variables a default value + TSEvent tsEvent; + bool sensorFound = false; + double celsius, fahrenheit, rel_humidity, abs_humidity = TS_DEFAULT_VALUE; + + // Used for absolute humidity calculation + double vapour_pressure = 0; + + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(TSEvent)); + + // Register callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, temperature_sensor_draw_callback, NULL); + view_port_input_callback_set(view_port, temperature_sensor_input_callback, event_queue); + + // Create timer and register its callback + FuriTimer* timer = + furi_timer_alloc(temperature_sensor_timer_callback, FuriTimerTypePeriodic, event_queue); + furi_timer_start(timer, furi_kernel_get_tick_frequency()); + + // Register viewport + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + // Used to notify the user by blinking red (error) or blue (fetch successful) + NotificationApp* notifications = furi_record_open(RECORD_NOTIFICATION); + + while(1) { + furi_check(furi_message_queue_get(event_queue, &tsEvent, FuriWaitForever) == FuriStatusOk); + + // Handle events + if(tsEvent.type == TSEventTypeInput) { + // Exit on back key + if(tsEvent.input.key == + InputKeyBack) // We dont check for type here, we can check the type of keypress like: (event.input.type == InputTypeShort) + break; + + } else if(tsEvent.type == TSEventTypeTick) { + // Update sensor data + // Fetch data and set the sensor current status accordingly + sensorFound = temperature_sensor_fetch_info(&celsius, &rel_humidity); + temperature_sensor_current_status = (sensorFound ? TSSPendingUpdate : TSSNoSensor); + + if(sensorFound) { + // Blink blue + notification_message(notifications, &sequence_blink_blue_100); + + if(celsius != TS_DEFAULT_VALUE && rel_humidity != TS_DEFAULT_VALUE) { + // Convert celsius to fahrenheit + fahrenheit = (celsius * 9 / 5) + 32; + + // Calculate absolute humidity - For more info refer to https://github.com/Mywk/FlipperTemperatureSensor/issues/1 + // Calculate saturation vapour pressure first + vapour_pressure = + (double)6.11 * + pow(10, (double)(((double)7.5 * celsius) / ((double)237.3 + celsius))); + // Then the vapour pressure in Pa + vapour_pressure = vapour_pressure * rel_humidity; + // Calculate absolute humidity + abs_humidity = + (double)2.16679 * (double)(vapour_pressure / ((double)273.15 + celsius)); + + // Fill our buffers here, not on the canvas draw callback + snprintf(ts_data_buffer_temperature_c, DATA_BUFFER_SIZE, "%.2f", celsius); + snprintf(ts_data_buffer_temperature_f, DATA_BUFFER_SIZE, "%.2f", fahrenheit); + snprintf( + ts_data_buffer_relative_humidity, DATA_BUFFER_SIZE, "%.2f", rel_humidity); + snprintf( + ts_data_buffer_absolute_humidity, DATA_BUFFER_SIZE, "%.2f", abs_humidity); + } + + } else { + // Reset our variables to their default values + celsius = fahrenheit = rel_humidity = abs_humidity = TS_DEFAULT_VALUE; + + // Blink red + notification_message(notifications, &sequence_blink_red_100); + } + } + + uint32_t wait_ticks = furi_ms_to_ticks(!sensorFound ? 100 : 500); + furi_delay_tick(wait_ticks); + } + + furi_hal_power_suppress_charge_exit(); + // Dobby is freee (free our variables, Flipper will crash if we don't do this!) + furi_timer_free(timer); + gui_remove_view_port(gui, view_port); + view_port_free(view_port); + furi_message_queue_free(event_queue); + + furi_record_close(RECORD_NOTIFICATION); + furi_record_close(RECORD_GUI); + + return 0; +} diff --git a/applications/plugins/am2320_temp_sensor/temperature_sensor.png b/applications/plugins/am2320_temp_sensor/temperature_sensor.png new file mode 100644 index 000000000..b6fe6d7fe Binary files /dev/null and b/applications/plugins/am2320_temp_sensor/temperature_sensor.png differ diff --git a/applications/plugins/bpmtapper/LICENSE b/applications/plugins/bpmtapper/LICENSE new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/applications/plugins/bpmtapper/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/applications/plugins/bpmtapper/README.md b/applications/plugins/bpmtapper/README.md new file mode 100644 index 000000000..8e88863ee --- /dev/null +++ b/applications/plugins/bpmtapper/README.md @@ -0,0 +1,14 @@ +# BPM Tapper + +A BPM Tapper for the Flipper Zero. + +![screenshot](img/screenshot.png) + +Hit any button other than back repeatedly. Calculates based on the average of the last 8 inputs. + +## Compiling + +``` +./fbt firmware_bpm_tapper +``` + diff --git a/applications/plugins/bpmtapper/application.fam b/applications/plugins/bpmtapper/application.fam new file mode 100644 index 000000000..93c4179c5 --- /dev/null +++ b/applications/plugins/bpmtapper/application.fam @@ -0,0 +1,13 @@ +App( + appid="BPM_Tapper", + name="BPM Tapper", + apptype=FlipperAppType.EXTERNAL, + entry_point="bpm_tapper_app", + cdefines=["APP_BPM_TAPPER"], + requires=["gui"], + stack_size=2 * 1024, + fap_icon="bpm_10px.png", + fap_category="Music", + fap_icon_assets="icons", + order=15, +) diff --git a/applications/plugins/bpmtapper/bpm.c b/applications/plugins/bpmtapper/bpm.c new file mode 100644 index 000000000..cee83a6a4 --- /dev/null +++ b/applications/plugins/bpmtapper/bpm.c @@ -0,0 +1,262 @@ +#include +#include +#include +#include +#include +#include +#include "BPM_Tapper_icons.h" + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} PluginEvent; + +//QUEUE + +struct node { + int interval; + struct node* next; +}; +typedef struct node node; + +typedef struct { + int size; + int max_size; + node* front; + node* rear; +} queue; + +static void init_queue(queue* q) { + q->size = 0; + q->max_size = 8; + q->front = NULL; + q->rear = NULL; +} + +static void queue_remove(queue* q) { + node* tmp; + tmp = q->front; + q->front = q->front->next; + q->size--; + free(tmp); +} + +static void queue_add(queue* q, int value) { + node* tmp = malloc(sizeof(node)); + tmp->interval = value; + tmp->next = NULL; + if(q->size == q->max_size) { + queue_remove(q); + } + // check if empty + if(q->rear == NULL) { + q->front = tmp; + q->rear = tmp; + } else { + q->rear->next = tmp; + q->rear = tmp; + } + q->size++; +} + +static float queue_avg(queue* q) { + float avg = 0.0; + if(q->size == 0) { + return avg; + } else { + node* tmp; + float sum = 0.0; + tmp = q->front; + while(tmp != NULL) { + sum = sum + tmp->interval; + tmp = tmp->next; + } + avg = sum / q->size; + FURI_LOG_D("BPM-Tapper", "Sum: %.2f Avg: %.2f", (double)sum, (double)avg); + return avg; + } +} + +// TOO SLOW! +//uint64_t dolphin_state_timestamp() { +// FuriHalRtcDateTime datetime; +// furi_hal_rtc_get_datetime(&datetime); +// return furi_hal_rtc_datetime_to_timestamp(&datetime); +//} +// +typedef struct { + int taps; + double bpm; + uint32_t last_stamp; + uint32_t interval; + queue* tap_queue; +} BPMTapper; + +static void show_hello() { + // BEGIN HELLO DIALOG + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + DialogMessage* message = dialog_message_alloc(); + + const char* header_text = "BPM Tapper"; + const char* message_text = "Tap center to start"; + + dialog_message_set_header(message, header_text, 63, 3, AlignCenter, AlignTop); + dialog_message_set_text(message, message_text, 0, 17, AlignLeft, AlignTop); + dialog_message_set_buttons(message, NULL, "Tap", NULL); + + dialog_message_set_icon(message, &I_DolphinCommon_56x48, 72, 17); + + dialog_message_show(dialogs, message); + + dialog_message_free(message); + furi_record_close(RECORD_DIALOGS); + // END HELLO DIALOG +} + +static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + PluginEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void render_callback(Canvas* const canvas, void* ctx) { + FuriString* tempStr; + + const BPMTapper* bpm_state = acquire_mutex((ValueMutex*)ctx, 25); + if(bpm_state == NULL) { + return; + } + // border + //canvas_draw_frame(canvas, 0, 0, 128, 64); + canvas_set_font(canvas, FontPrimary); + + tempStr = furi_string_alloc(); + + furi_string_printf(tempStr, "Taps: %d", bpm_state->taps); + canvas_draw_str_aligned(canvas, 5, 10, AlignLeft, AlignBottom, furi_string_get_cstr(tempStr)); + furi_string_reset(tempStr); + + furi_string_printf(tempStr, "Queue: %d", bpm_state->tap_queue->size); + canvas_draw_str_aligned(canvas, 70, 10, AlignLeft, AlignBottom, furi_string_get_cstr(tempStr)); + furi_string_reset(tempStr); + + furi_string_printf(tempStr, "Interval: %ldms", bpm_state->interval); + canvas_draw_str_aligned(canvas, 5, 20, AlignLeft, AlignBottom, furi_string_get_cstr(tempStr)); + furi_string_reset(tempStr); + + furi_string_printf(tempStr, "x2 %.2f /2 %.2f", bpm_state->bpm * 2, bpm_state->bpm / 2); + canvas_draw_str_aligned( + canvas, 64, 60, AlignCenter, AlignCenter, furi_string_get_cstr(tempStr)); + furi_string_reset(tempStr); + + furi_string_printf(tempStr, "%.2f", bpm_state->bpm); + canvas_set_font(canvas, FontBigNumbers); + canvas_draw_str_aligned( + canvas, 64, 40, AlignCenter, AlignCenter, furi_string_get_cstr(tempStr)); + furi_string_reset(tempStr); + + furi_string_free(tempStr); + + release_mutex((ValueMutex*)ctx, bpm_state); +} + +static void bpm_state_init(BPMTapper* const plugin_state) { + plugin_state->taps = 0; + plugin_state->bpm = 120.0; + plugin_state->last_stamp = 0; // furi_get_tick(); + plugin_state->interval = 0; + queue* q; + q = malloc(sizeof(queue)); + init_queue(q); + plugin_state->tap_queue = q; +} + +int32_t bpm_tapper_app(void* p) { + UNUSED(p); + + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); + + BPMTapper* bpm_state = malloc(sizeof(BPMTapper)); + // setup + bpm_state_init(bpm_state); + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, bpm_state, sizeof(bpm_state))) { + FURI_LOG_E("BPM-Tapper", "cannot create mutex\r\n"); + free(bpm_state); + return 255; + } + show_hello(); + + // BEGIN IMPLEMENTATION + + // Set system callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, render_callback, &state_mutex); + view_port_input_callback_set(view_port, input_callback, event_queue); + + // Open GUI and register view_port + Gui* gui = furi_record_open("gui"); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + PluginEvent event; + for(bool processing = true; processing;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + BPMTapper* bpm_state = (BPMTapper*)acquire_mutex_block(&state_mutex); + if(event_status == FuriStatusOk) { + // press events + if(event.type == EventTypeKey) { + if(event.input.type == InputTypePress) { + switch(event.input.key) { + case InputKeyUp: + case InputKeyDown: + case InputKeyRight: + case InputKeyLeft: + case InputKeyOk: + bpm_state->taps++; + uint32_t new_stamp = furi_get_tick(); + if(bpm_state->last_stamp == 0) { + bpm_state->last_stamp = new_stamp; + break; + } + bpm_state->interval = new_stamp - bpm_state->last_stamp; + bpm_state->last_stamp = new_stamp; + queue_add(bpm_state->tap_queue, bpm_state->interval); + float avg = queue_avg(bpm_state->tap_queue); + float bps = 1.0 / (avg / 1000.0); + bpm_state->bpm = bps * 60.0; + break; + case InputKeyBack: + // Exit the plugin + processing = false; + break; + default: + break; + } + } + } + } else { + FURI_LOG_D("BPM-Tapper", "FuriMessageQueue: event timeout"); + // event timeout + } + view_port_update(view_port); + release_mutex(&state_mutex, bpm_state); + } + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close("gui"); + view_port_free(view_port); + furi_message_queue_free(event_queue); + delete_mutex(&state_mutex); + queue* q = bpm_state->tap_queue; + free(q); + free(bpm_state); + + return 0; +} diff --git a/applications/plugins/bpmtapper/bpm_10px.png b/applications/plugins/bpmtapper/bpm_10px.png new file mode 100644 index 000000000..ebf27486c Binary files /dev/null and b/applications/plugins/bpmtapper/bpm_10px.png differ diff --git a/applications/plugins/bpmtapper/icons/DolphinCommon_56x48.png b/applications/plugins/bpmtapper/icons/DolphinCommon_56x48.png new file mode 100644 index 000000000..089aaed83 Binary files /dev/null and b/applications/plugins/bpmtapper/icons/DolphinCommon_56x48.png differ diff --git a/applications/plugins/bpmtapper/img/screenshot.png b/applications/plugins/bpmtapper/img/screenshot.png new file mode 100644 index 000000000..fbba2aad9 Binary files /dev/null and b/applications/plugins/bpmtapper/img/screenshot.png differ diff --git a/applications/plugins/dht_temp_sensor/DHT.c b/applications/plugins/dht_temp_sensor/DHT.c new file mode 100644 index 000000000..63a189ce1 --- /dev/null +++ b/applications/plugins/dht_temp_sensor/DHT.c @@ -0,0 +1,169 @@ +#include "DHT.h" + +#define lineDown() furi_hal_gpio_write(sensor->GPIO, false) +#define lineUp() furi_hal_gpio_write(sensor->GPIO, true) +#define getLine() furi_hal_gpio_read(sensor->GPIO) +#define Delay(d) furi_delay_ms(d) + +DHT_data DHT_getData(DHT_sensor* sensor) { + DHT_data data = {-128.0f, -128.0f}; + +#if DHT_POLLING_CONTROL == 1 + /* Ограничение по частоте опроса датчика */ + //Определение интервала опроса в зависимости от датчика + uint16_t pollingInterval; + if(sensor->type == DHT11) { + pollingInterval = DHT_POLLING_INTERVAL_DHT11; + } else { + pollingInterval = DHT_POLLING_INTERVAL_DHT22; + } + + //Если интервал маленький, то возврат последнего удачного значения + if((furi_get_tick() - sensor->lastPollingTime < pollingInterval) && + sensor->lastPollingTime != 0) { + data.hum = sensor->lastHum; + data.temp = sensor->lastTemp; + return data; + } + sensor->lastPollingTime = furi_get_tick() + 1; +#endif + + //Опускание линии данных на 18 мс + lineDown(); +#ifdef DHT_IRQ_CONTROL + //Выключение прерываний, чтобы ничто не мешало обработке данных + __disable_irq(); +#endif + Delay(18); + + //Подъём линии + lineUp(); + + /* Ожидание ответа от датчика */ + uint16_t timeout = 0; + while(!getLine()) { + timeout++; + if(timeout > DHT_TIMEOUT) { +#ifdef DHT_IRQ_CONTROL + __enable_irq(); +#endif + //Если датчик не отозвался, значит его точно нет + //Обнуление последнего удачного значения, чтобы + //не получать фантомные значения + sensor->lastHum = -128.0f; + sensor->lastTemp = -128.0f; + + return data; + } + } + //Ожидание спада + while(getLine()) { + timeout++; + if(timeout > DHT_TIMEOUT) { +#ifdef DHT_IRQ_CONTROL + __enable_irq(); +#endif + //Если датчик не отозвался, значит его точно нет + //Обнуление последнего удачного значения, чтобы + //не получать фантомные значения + sensor->lastHum = -128.0f; + sensor->lastTemp = -128.0f; + + return data; + } + } + timeout = 0; + //Ожидание подъёма + while(!getLine()) { + timeout++; + if(timeout > DHT_TIMEOUT) { + if(timeout > DHT_TIMEOUT) { +#ifdef DHT_IRQ_CONTROL + __enable_irq(); +#endif + //Если датчик не отозвался, значит его точно нет + //Обнуление последнего удачного значения, чтобы + //не получать фантомные значения + sensor->lastHum = -128.0f; + sensor->lastTemp = -128.0f; + + return data; + } + } + } + timeout = 0; + //Ожидание спада + while(getLine()) { + timeout++; + if(timeout > DHT_TIMEOUT) { +#ifdef DHT_IRQ_CONTROL + __enable_irq(); +#endif + //Если датчик не отозвался, значит его точно нет + //Обнуление последнего удачного значения, чтобы + //не получать фантомные значения + sensor->lastHum = -128.0f; + sensor->lastTemp = -128.0f; + return data; + } + } + + /* Чтение ответа от датчика */ + uint8_t rawData[5] = {0, 0, 0, 0, 0}; + for(uint8_t a = 0; a < 5; a++) { + for(uint8_t b = 7; b != 255; b--) { + uint16_t hT = 0, lT = 0; + //Пока линия в низком уровне, инкремент переменной lT + while(!getLine() && lT != 65535) lT++; + //Пока линия в высоком уровне, инкремент переменной hT + timeout = 0; + while(getLine() && hT != 65535) hT++; + //Если hT больше lT, то пришла единица + if(hT > lT) rawData[a] |= (1 << b); + } + } +#ifdef DHT_IRQ_CONTROL + //Включение прерываний после приёма данных + __enable_irq(); +#endif + /* Проверка целостности данных */ + if((uint8_t)(rawData[0] + rawData[1] + rawData[2] + rawData[3]) == rawData[4]) { + //Если контрольная сумма совпадает, то конвертация и возврат полученных значений + if(sensor->type == DHT22) { + data.hum = (float)(((uint16_t)rawData[0] << 8) | rawData[1]) * 0.1f; + //Проверка на отрицательность температуры + if(!(rawData[2] & (1 << 7))) { + data.temp = (float)(((uint16_t)rawData[2] << 8) | rawData[3]) * 0.1f; + } else { + rawData[2] &= ~(1 << 7); + data.temp = (float)(((uint16_t)rawData[2] << 8) | rawData[3]) * -0.1f; + } + } + if(sensor->type == DHT11) { + data.hum = (float)rawData[0]; + data.temp = (float)rawData[2]; + //DHT11 производства ASAIR имеют дробную часть в температуре + //А ещё температуру измеряет от -20 до +60 *С + //Вот прикол, да? + if(rawData[3] != 0) { + //Проверка знака + if(!(rawData[3] & (1 << 7))) { + //Добавление положительной дробной части + data.temp += rawData[3] * 0.1f; + } else { + //А тут делаем отрицательное значение + rawData[3] &= ~(1 << 7); + data.temp += rawData[3] * 0.1f; + data.temp *= -1; + } + } + } + } + +#if DHT_POLLING_CONTROL == 1 + sensor->lastHum = data.hum; + sensor->lastTemp = data.temp; +#endif + + return data; +} \ No newline at end of file diff --git a/applications/plugins/dht_temp_sensor/DHT.h b/applications/plugins/dht_temp_sensor/DHT.h new file mode 100644 index 000000000..409847d8b --- /dev/null +++ b/applications/plugins/dht_temp_sensor/DHT.h @@ -0,0 +1,40 @@ +#ifndef DHT_H_ +#define DHT_H_ + +#include + +/* Настройки */ +#define DHT_TIMEOUT 65534 //Количество итераций, после которых функция вернёт пустые значения +#define DHT_POLLING_CONTROL 1 //Включение проверки частоты опроса датчика +#define DHT_POLLING_INTERVAL_DHT11 \ + 2000 //Интервал опроса DHT11 (0.5 Гц по даташиту). Можно поставить 1500, будет работать +//Костыль, временно 2 секунды для датчика AM2302 +#define DHT_POLLING_INTERVAL_DHT22 2000 //Интервал опроса DHT22 (1 Гц по даташиту) +#define DHT_IRQ_CONTROL //Выключать прерывания во время обмена данных с датчиком +/* Структура возвращаемых датчиком данных */ +typedef struct { + float hum; + float temp; +} DHT_data; + +/* Тип используемого датчика */ +typedef enum { DHT11, DHT22 } DHT_type; + +/* Структура объекта датчика */ +typedef struct { + char name[11]; + const GpioPin* GPIO; //Пин датчика + DHT_type type; //Тип датчика (DHT11 или DHT22) + +//Контроль частоты опроса датчика. Значения не заполнять! +#if DHT_POLLING_CONTROL == 1 + uint32_t lastPollingTime; //Время последнего опроса датчика + float lastTemp; //Последнее значение температуры + float lastHum; //Последнее значение влажности +#endif +} DHT_sensor; + +/* Прототипы функций */ +DHT_data DHT_getData(DHT_sensor* sensor); //Получить данные с датчика + +#endif diff --git a/applications/plugins/dht_temp_sensor/application.fam b/applications/plugins/dht_temp_sensor/application.fam new file mode 100644 index 000000000..fe91415b3 --- /dev/null +++ b/applications/plugins/dht_temp_sensor/application.fam @@ -0,0 +1,13 @@ +App( + appid="DHT_Monitor", + name="[DHT] Temp. Monitor", + apptype=FlipperAppType.EXTERNAL, + entry_point="quenon_dht_mon_app", + cdefines=["QUENON_DHT_MON"], + requires=[ + "gui", + ], + fap_category="GPIO", + fap_icon="icon.png", + stack_size=2 * 1024, +) \ No newline at end of file diff --git a/applications/plugins/dht_temp_sensor/icon.png b/applications/plugins/dht_temp_sensor/icon.png new file mode 100644 index 000000000..0e87c26c2 Binary files /dev/null and b/applications/plugins/dht_temp_sensor/icon.png differ diff --git a/applications/plugins/dht_temp_sensor/quenon_dht_mon.c b/applications/plugins/dht_temp_sensor/quenon_dht_mon.c new file mode 100644 index 000000000..e2a1aba8b --- /dev/null +++ b/applications/plugins/dht_temp_sensor/quenon_dht_mon.c @@ -0,0 +1,469 @@ +#include "quenon_dht_mon.h" +#include + +//Порты ввода/вывода, которые не были обозначены в общем списке +const GpioPin SWC_10 = {.pin = LL_GPIO_PIN_14, .port = GPIOA}; +const GpioPin SIO_12 = {.pin = LL_GPIO_PIN_13, .port = GPIOA}; +const GpioPin TX_13 = {.pin = LL_GPIO_PIN_6, .port = GPIOB}; +const GpioPin RX_14 = {.pin = LL_GPIO_PIN_7, .port = GPIOB}; + +//Количество доступных портов ввода/вывода +#define GPIO_ITEMS (sizeof(gpio_item) / sizeof(GpioItem)) + +//Перечень достуных портов ввода/вывода +static const GpioItem gpio_item[] = { + {2, "2 (A7)", &gpio_ext_pa7}, + {3, "3 (A6)", &gpio_ext_pa6}, + {4, "4 (A4)", &gpio_ext_pa4}, + {5, "5 (B3)", &gpio_ext_pb3}, + {6, "6 (B2)", &gpio_ext_pb2}, + {7, "7 (C3)", &gpio_ext_pc3}, + {10, " 10(SWC) ", &SWC_10}, + {12, "12 (SIO)", &SIO_12}, + {13, "13 (TX)", &TX_13}, + {14, "14 (RX)", &RX_14}, + {15, "15 (C1)", &gpio_ext_pc1}, + {16, "16 (C0)", &gpio_ext_pc0}, + {17, "17 (1W)", &ibutton_gpio}}; + +//Данные плагина +static PluginData* app; + +uint8_t DHTMon_GPIO_to_int(const GpioPin* gpio) { + if(gpio == NULL) return 255; + for(uint8_t i = 0; i < GPIO_ITEMS; i++) { + if(gpio_item[i].pin->pin == gpio->pin && gpio_item[i].pin->port == gpio->port) { + return gpio_item[i].num; + } + } + return 255; +} + +const GpioPin* DHTMon_GPIO_form_int(uint8_t name) { + for(uint8_t i = 0; i < GPIO_ITEMS; i++) { + if(gpio_item[i].num == name) { + return gpio_item[i].pin; + } + } + return NULL; +} + +const GpioPin* DHTMon_GPIO_from_index(uint8_t index) { + if(index > GPIO_ITEMS) return NULL; + return gpio_item[index].pin; +} + +uint8_t DHTMon_GPIO_to_index(const GpioPin* gpio) { + if(gpio == NULL) return 255; + for(uint8_t i = 0; i < GPIO_ITEMS; i++) { + if(gpio_item[i].pin->pin == gpio->pin && gpio_item[i].pin->port == gpio->port) { + return i; + } + } + return 255; +} + +const char* DHTMon_GPIO_getName(const GpioPin* gpio) { + if(gpio == NULL) return NULL; + for(uint8_t i = 0; i < GPIO_ITEMS; i++) { + if(gpio_item[i].pin->pin == gpio->pin && gpio_item[i].pin->port == gpio->port) { + return gpio_item[i].name; + } + } + return NULL; +} + +void DHTMon_sensors_init(void) { + //Включение 5V если на порту 1 FZ его нет + if(furi_hal_power_is_otg_enabled() != true) { + furi_hal_power_enable_otg(); + } + + //Настройка GPIO загруженных датчиков + for(uint8_t i = 0; i < app->sensors_count; i++) { + //Высокий уровень по умолчанию + furi_hal_gpio_write(app->sensors[i].GPIO, true); + //Режим работы - OpenDrain, подтяжка включается на всякий случай + furi_hal_gpio_init( + app->sensors[i].GPIO, //Порт FZ + GpioModeOutputOpenDrain, //Режим работы - открытый сток + GpioPullUp, //Принудительная подтяжка линии данных к питанию + GpioSpeedVeryHigh); //Скорость работы - максимальная + } +} + +void DHTMon_sensors_deinit(void) { + //Возврат исходного состояния 5V + if(app->last_OTG_State != true) { + furi_hal_power_disable_otg(); + } + + //Перевод портов GPIO в состояние по умолчанию + for(uint8_t i = 0; i < app->sensors_count; i++) { + furi_hal_gpio_init( + app->sensors[i].GPIO, //Порт FZ + GpioModeAnalog, //Режим работы - аналог + GpioPullNo, //Отключение подтяжки + GpioSpeedLow); //Скорость работы - низкая + //Установка низкого уровня + furi_hal_gpio_write(app->sensors[i].GPIO, false); + } +} + +bool DHTMon_sensor_check(DHT_sensor* sensor) { + /* Проверка имени */ + //1) Строка должна быть длиной от 1 до 10 символов + //2) Первый символ строки должен быть только 0-9, A-Z, a-z и _ + if(strlen(sensor->name) == 0 || strlen(sensor->name) > 10 || + (!(sensor->name[0] >= '0' && sensor->name[0] <= '9') && + !(sensor->name[0] >= 'A' && sensor->name[0] <= 'Z') && + !(sensor->name[0] >= 'a' && sensor->name[0] <= 'z') && !(sensor->name[0] == '_'))) { + FURI_LOG_D(APP_NAME, "Sensor [%s] name check failed\r\n", sensor->name); + return false; + } + //Проверка GPIO + if(DHTMon_GPIO_to_int(sensor->GPIO) == 255) { + FURI_LOG_D( + APP_NAME, + "Sensor [%s] GPIO check failed: %d\r\n", + sensor->name, + DHTMon_GPIO_to_int(sensor->GPIO)); + return false; + } + //Проверка типа датчика + if(sensor->type != DHT11 && sensor->type != DHT22) { + FURI_LOG_D(APP_NAME, "Sensor [%s] type check failed: %d\r\n", sensor->name, sensor->type); + return false; + } + + //Возврат истины если всё ок + FURI_LOG_D(APP_NAME, "Sensor [%s] all checks passed\r\n", sensor->name); + return true; +} + +void DHTMon_sensor_delete(DHT_sensor* sensor) { + if(sensor == NULL) return; + //Делаем параметры датчика неверными + sensor->name[0] = '\0'; + sensor->type = 255; + //Теперь сохраняем текущие датчики. Сохранятор не сохранит неисправный датчик + DHTMon_sensors_save(); + //Перезагружаемся с SD-карты + DHTMon_sensors_reload(); +} + +uint8_t DHTMon_sensors_save(void) { + //Выделение памяти для потока + app->file_stream = file_stream_alloc(app->storage); + uint8_t savedSensorsCount = 0; + //Переменная пути к файлу + FuriString* filepath = furi_string_alloc(); + //Составление пути к файлу + furi_string_printf(filepath, "%s/%s", APP_PATH_FOLDER, APP_FILENAME); + + //Открытие потока. Если поток открылся, то выполнение сохранения датчиков + if(file_stream_open( + app->file_stream, furi_string_get_cstr(filepath), FSAM_READ_WRITE, FSOM_CREATE_ALWAYS)) { + const char template[] = + "#DHT monitor sensors file\n#Name - name of sensor. Up to 10 sumbols\n#Type - type of sensor. DHT11 - 0, DHT22 - 1\n#GPIO - connection port. May being 2-7, 10, 12-17\n#Name Type GPIO\n"; + stream_write(app->file_stream, (uint8_t*)template, strlen(template)); + //Сохранение датчиков + for(uint8_t i = 0; i < app->sensors_count; i++) { + //Если параметры датчика верны, то сохраняемся + if(DHTMon_sensor_check(&app->sensors[i])) { + stream_write_format( + app->file_stream, + "%s %d %d\n", + app->sensors[i].name, + app->sensors[i].type, + DHTMon_GPIO_to_int(app->sensors[i].GPIO)); + savedSensorsCount++; + } + } + } else { + //TODO: печать ошибки на экран + FURI_LOG_E(APP_NAME, "cannot create sensors file\r\n"); + } + stream_free(app->file_stream); + + return savedSensorsCount; +} + +bool DHTMon_sensors_load(void) { + //Обнуление количества датчиков + app->sensors_count = -1; + //Очистка предыдущих датчиков + memset(app->sensors, 0, sizeof(app->sensors)); + + //Открытие файла на SD-карте + //Выделение памяти для потока + app->file_stream = file_stream_alloc(app->storage); + //Переменная пути к файлу + FuriString* filepath = furi_string_alloc(); + //Составление пути к файлу + furi_string_printf(filepath, "%s/%s", APP_PATH_FOLDER, APP_FILENAME); + //Открытие потока к файлу + if(!file_stream_open( + app->file_stream, furi_string_get_cstr(filepath), FSAM_READ_WRITE, FSOM_OPEN_EXISTING)) { + //Если файл отсутствует, то создание болванки + FURI_LOG_W(APP_NAME, "Missing sensors file. Creating new file\r\n"); + app->sensors_count = 0; + stream_free(app->file_stream); + DHTMon_sensors_save(); + return false; + } + //Вычисление размера файла + size_t file_size = stream_size(app->file_stream); + if(file_size == (size_t)0) { + //Выход если файл пустой + FURI_LOG_W(APP_NAME, "Sensors file is empty\r\n"); + app->sensors_count = 0; + stream_free(app->file_stream); + return false; + } + + //Выделение памяти под загрузку файла + uint8_t* file_buf = malloc(file_size); + //Опустошение буфера файла + memset(file_buf, 0, file_size); + //Загрузка файла + if(stream_read(app->file_stream, file_buf, file_size) != file_size) { + //Выход при ошибке чтения + FURI_LOG_E(APP_NAME, "Error reading sensor file\r\n"); + app->sensors_count = 0; + stream_free(app->file_stream); + return false; + } + //Построчное чтение файла + //Указатель на начало строки + FuriString* file = furi_string_alloc_set_str((char*)file_buf); + //Сколько байт до конца строки + size_t line_end = 0; + while(line_end != STRING_FAILURE && app->sensors_count < MAX_SENSORS) { + if(((char*)(file_buf + line_end))[1] != '#') { + DHT_sensor s = {0}; + int type, port; + char name[11] = {0}; + sscanf(((char*)(file_buf + line_end)), "%s %d %d", name, &type, &port); + s.type = type; + s.GPIO = DHTMon_GPIO_form_int(port); + + name[10] = '\0'; + strcpy(s.name, name); + //Если данные корректны, то + if(DHTMon_sensor_check(&s) == true) { + //Установка нуля при первом датчике + if(app->sensors_count == -1) app->sensors_count = 0; + //Добавление датчика в общий список + app->sensors[app->sensors_count] = s; + //Увеличение количества загруженных датчиков + app->sensors_count++; + } + } + line_end = furi_string_search_char(file, '\n', line_end + 1); + } + stream_free(app->file_stream); + free(file_buf); + + //Обнуление количества датчиков если ни один из них не был загружен + if(app->sensors_count == -1) app->sensors_count = 0; + + //Инициализация портов датчиков если таковые есть + if(app->sensors_count > 0) { + DHTMon_sensors_init(); + return true; + } else { + return false; + } + return false; +} + +bool DHTMon_sensors_reload(void) { + DHTMon_sensors_deinit(); + return DHTMon_sensors_load(); +} + +/** + * @brief Обработчик отрисовки экрана + * + * @param canvas Указатель на холст + * @param ctx Данные плагина + */ +static void render_callback(Canvas* const canvas, void* ctx) { + PluginData* app = acquire_mutex((ValueMutex*)ctx, 25); + if(app == NULL) { + return; + } + //Вызов отрисовки главного экрана + scene_main(canvas, app); + + release_mutex((ValueMutex*)ctx, app); +} + +/** + * @brief Обработчик нажатия кнопок главного экрана + * + * @param input_event Указатель на событие + * @param event_queue Указатель на очередь событий + */ +static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + PluginEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +/** + * @brief Выделение места под переменные плагина + * + * @return true Если всё прошло успешно + * @return false Если в процессе загрузки произошла ошибка + */ +static bool DHTMon_alloc(void) { + //Выделение места под данные плагина + app = malloc(sizeof(PluginData)); + //Выделение места под очередь событий + app->event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); + + //Обнуление количества датчиков + app->sensors_count = -1; + + //Инициализация мутекса + if(!init_mutex(&app->state_mutex, app, sizeof(PluginData))) { + FURI_LOG_E(APP_NAME, "cannot create mutex\r\n"); + return false; + } + + // Set system callbacks + app->view_port = view_port_alloc(); + view_port_draw_callback_set(app->view_port, render_callback, &app->state_mutex); + view_port_input_callback_set(app->view_port, input_callback, app->event_queue); + + // Open GUI and register view_port + app->gui = furi_record_open(RECORD_GUI); + gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen); + + app->view_dispatcher = view_dispatcher_alloc(); + + sensorActions_sceneCreate(app); + sensorEdit_sceneCreate(app); + + app->widget = widget_alloc(); + view_dispatcher_add_view(app->view_dispatcher, WIDGET_VIEW, widget_get_view(app->widget)); + + app->text_input = text_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, TEXTINPUT_VIEW, text_input_get_view(app->text_input)); + + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + //Уведомления + app->notifications = furi_record_open(RECORD_NOTIFICATION); + + //Подготовка хранилища + app->storage = furi_record_open(RECORD_STORAGE); + storage_common_mkdir(app->storage, APP_PATH_FOLDER); + app->file_stream = file_stream_alloc(app->storage); + + return true; +} + +/** + * @brief Освыбождение памяти после работы приложения + */ +static void DHTMon_free(void) { + //Автоматическое управление подсветкой + notification_message(app->notifications, &sequence_display_backlight_enforce_auto); + + furi_record_close(RECORD_STORAGE); + furi_record_close(RECORD_NOTIFICATION); + + text_input_free(app->text_input); + widget_free(app->widget); + sensorEdit_sceneRemove(); + sensorActions_screneRemove(); + view_dispatcher_free(app->view_dispatcher); + + furi_record_close(RECORD_GUI); + + view_port_enabled_set(app->view_port, false); + gui_remove_view_port(app->gui, app->view_port); + + view_port_free(app->view_port); + furi_message_queue_free(app->event_queue); + delete_mutex(&app->state_mutex); + + free(app); +} + +/** + * @brief Точка входа в приложение + * + * @return Код ошибки + */ +int32_t quenon_dht_mon_app() { + if(!DHTMon_alloc()) { + DHTMon_free(); + return 255; + } + //Постоянное свечение подсветки + notification_message(app->notifications, &sequence_display_backlight_enforce_on); + //Сохранение состояния наличия 5V на порту 1 FZ + app->last_OTG_State = furi_hal_power_is_otg_enabled(); + + //Загрузка датчиков с SD-карты + DHTMon_sensors_load(); + + app->currentSensorEdit = &app->sensors[0]; + + PluginEvent event; + for(bool processing = true; processing;) { + FuriStatus event_status = furi_message_queue_get(app->event_queue, &event, 100); + + acquire_mutex_block(&app->state_mutex); + + if(event_status == FuriStatusOk) { + // press events + if(event.type == EventTypeKey) { + if(event.input.type == InputTypePress) { + switch(event.input.key) { + case InputKeyUp: + break; + case InputKeyDown: + break; + case InputKeyRight: + break; + case InputKeyLeft: + break; + case InputKeyMAX: + break; + case InputKeyOk: + view_port_update(app->view_port); + release_mutex(&app->state_mutex, app); + mainMenu_scene(app); + break; + case InputKeyBack: + processing = false; + break; + default: + break; + } + } + } + } else { + FURI_LOG_D(APP_NAME, "FuriMessageQueue: event timeout"); + // event timeout + } + + view_port_update(app->view_port); + release_mutex(&app->state_mutex, app); + } + //Освобождение памяти и деинициализация + DHTMon_sensors_deinit(); + DHTMon_free(); + + return 0; +} +//TODO: Обработка ошибок +//TODO: Пропуск использованных портов в меню добавления датчиков \ No newline at end of file diff --git a/applications/plugins/dht_temp_sensor/quenon_dht_mon.h b/applications/plugins/dht_temp_sensor/quenon_dht_mon.h new file mode 100644 index 000000000..4e888f6c1 --- /dev/null +++ b/applications/plugins/dht_temp_sensor/quenon_dht_mon.h @@ -0,0 +1,176 @@ +#ifndef QUENON_DHT_MON +#define QUENON_DHT_MON + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "DHT.h" + +#define APP_NAME "DHT_monitor" +#define APP_PATH_FOLDER "/ext/dht_monitor" +#define APP_FILENAME "sensors.txt" +#define MAX_SENSORS 5 + +// //Виды менюшек +typedef enum { + MAIN_MENU_VIEW, + ADDSENSOR_MENU_VIEW, + TEXTINPUT_VIEW, + SENSOR_ACTIONS_VIEW, + WIDGET_VIEW, +} MENU_VIEWS; + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} PluginEvent; + +typedef struct { + const uint8_t num; + const char* name; + const GpioPin* pin; +} GpioItem; + +//Структура с данными плагина +typedef struct { + //Очередь сообщений + FuriMessageQueue* event_queue; + //Мутекс + ValueMutex state_mutex; + //Вьюпорт + ViewPort* view_port; + //GUI + Gui* gui; + NotificationApp* notifications; + ViewDispatcher* view_dispatcher; + View* view; + TextInput* text_input; + VariableItem* item; + Widget* widget; + + char txtbuff[30]; //Буффер для печати строк на экране + bool last_OTG_State; //Состояние OTG до запуска приложения + Storage* storage; //Хранилище датчиков + Stream* file_stream; //Поток файла с датчиками + int8_t sensors_count; // Количество загруженных датчиков + DHT_sensor sensors[MAX_SENSORS]; //Сохранённые датчики + DHT_data data; //Инфа из датчика + DHT_sensor* currentSensorEdit; //Указатель на редактируемый датчик + +} PluginData; + +/* ================== Работа с GPIO ================== */ +/** + * @brief Конвертация GPIO в его номер на корпусе FZ + * + * @param gpio Указатель на преобразовываемый GPIO + * @return Номер порта на корпусе FZ + */ +uint8_t DHTMon_GPIO_to_int(const GpioPin* gpio); +/** + * @brief Конвертация номера порта на корпусе FZ в GPIO + * + * @param name Номер порта на корпусе FZ + * @return Указатель на GPIO при успехе, NULL при ошибке + */ +const GpioPin* DHTMon_GPIO_form_int(uint8_t name); +/** + * @brief Преобразование порядкового номера порта в GPIO + * + * @param index Индекс порта от 0 до GPIO_ITEMS-1 + * @return Указатель на GPIO при успехе, NULL при ошибке + */ +const GpioPin* DHTMon_GPIO_from_index(uint8_t index); +/** + * @brief Преобразование GPIO в порядковый номер порта + * + * @param gpio Указатель на GPIO + * @return index при успехе, 255 при ошибке + */ +uint8_t DHTMon_GPIO_to_index(const GpioPin* gpio); + +/** + * @brief Получить имя GPIO в виде строки + * + * @param gpio Искомый порт + * @return char* Указатель на строку с именем порта + */ +const char* DHTMon_GPIO_getName(const GpioPin* gpio); + +/* ================== Работа с датчиками ================== */ +/** + * @brief Инициализация портов ввода/вывода датчиков + */ +void DHTMon_sensors_init(void); +/** + * @brief Функция деинициализации портов ввода/вывода датчиков + */ +void DHTMon_sensors_deinit(void); +/** + * @brief Проверка корректности параметров датчика + * + * @param sensor Указатель на проверяемый датчик + * @return true Параметры датчика корректные + * @return false Параметры датчика некорректные + */ +bool DHTMon_sensor_check(DHT_sensor* sensor); +/** + * @brief Удаление датчика из списка и перезагрузка + * + * @param sensor Указатель на удаляемый датчик + */ +void DHTMon_sensor_delete(DHT_sensor* sensor); +/** + * @brief Сохранение датчиков на SD-карту + * + * @return Количество сохранённых датчиков + */ +uint8_t DHTMon_sensors_save(void); +/** + * @brief Загрузка датчиков с SD-карты + * + * @return true Был загружен хотя бы 1 датчик + * @return false Датчики отсутствуют + */ +bool DHTMon_sensors_load(void); +/** + * @brief Перезагрузка датчиков с SD-карты + * + * @return true Когда был загружен хотя бы 1 датчик + * @return false Ни один из датчиков не был загружен + */ +bool DHTMon_sensors_reload(void); + +void scene_main(Canvas* const canvas, PluginData* app); +void mainMenu_scene(PluginData* app); + +void sensorEdit_sceneCreate(PluginData* app); +void sensorEdit_scene(PluginData* app); +void sensorEdit_sceneRemove(void); + +void sensorActions_sceneCreate(PluginData* app); +void sensorActions_scene(PluginData* app); +void sensorActions_screneRemove(void); +#endif \ No newline at end of file diff --git a/applications/plugins/dht_temp_sensor/scenes/DHTMon_mainMenu_scene.c b/applications/plugins/dht_temp_sensor/scenes/DHTMon_mainMenu_scene.c new file mode 100644 index 000000000..26ac9ca89 --- /dev/null +++ b/applications/plugins/dht_temp_sensor/scenes/DHTMon_mainMenu_scene.c @@ -0,0 +1,157 @@ +#include "../quenon_dht_mon.h" +//Текущий вид +static View* view; +//Список +static VariableItemList* variable_item_list; + +/** + * @brief Функция обработки нажатия кнопки "Назад" + * + * @param context Указатель на данные приложения + * @return ID вида в который нужно переключиться + */ +static uint32_t actions_exitCallback(void* context) { + PluginData* app = context; + UNUSED(app); + //Возвращаем ID вида, в который нужно вернуться + return VIEW_NONE; +} +/** + * @brief Функция обработки нажатия средней кнопки + * + * @param context Указатель на данные приложения + * @param index На каком элементе списка была нажата кнопка + */ +static void enterCallback(void* context, uint32_t index) { + PluginData* app = context; + if((uint8_t)index < (uint8_t)app->sensors_count) { + app->currentSensorEdit = &app->sensors[index]; + sensorActions_scene(app); + } + if((uint8_t)index == (uint8_t)app->sensors_count) { + app->currentSensorEdit = &app->sensors[app->sensors_count++]; + strcpy(app->currentSensorEdit->name, "NewSensor"); + app->currentSensorEdit->GPIO = DHTMon_GPIO_from_index(0); + app->currentSensorEdit->type = DHT11; + sensorEdit_scene(app); + } +} + +/** + * @brief Создание списка действий с указанным датчиком + * + * @param app Указатель на данные плагина + */ +void mainMenu_scene(PluginData* app) { + variable_item_list = variable_item_list_alloc(); + //Сброс всех элементов меню + variable_item_list_reset(variable_item_list); + //Добавление названий датчиков в качестве элементов списка + for(uint8_t i = 0; i < app->sensors_count; i++) { + variable_item_list_add(variable_item_list, app->sensors[i].name, 1, NULL, NULL); + } + if(app->sensors_count < (uint8_t)MAX_SENSORS) { + variable_item_list_add(variable_item_list, " + Add new sensor +", 1, NULL, NULL); + } + + //Добавление колбека на нажатие средней кнопки + variable_item_list_set_enter_callback(variable_item_list, enterCallback, app); + + //Создание вида из списка + view = variable_item_list_get_view(variable_item_list); + //Добавление колбека на нажатие кнопки "Назад" + view_set_previous_callback(view, actions_exitCallback); + //Добавление вида в диспетчер + view_dispatcher_add_view(app->view_dispatcher, MAIN_MENU_VIEW, view); + + view_dispatcher_enable_queue(app->view_dispatcher); + + //Переключение на наш вид + view_dispatcher_switch_to_view(app->view_dispatcher, MAIN_MENU_VIEW); + + //Запуск диспетчера + view_dispatcher_run(app->view_dispatcher); + + //Очистка списка элементов + variable_item_list_free(variable_item_list); + //Удаление вида после обработки + view_dispatcher_remove_view(app->view_dispatcher, MAIN_MENU_VIEW); +} + +/* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +*/ + +// static VariableItemList* variable_item_list; +// /* ============== Главное меню ============== */ +// static uint32_t mainMenu_exitCallback(void* context) { +// UNUSED(context); +// variable_item_list_free(variable_item_list); +// DHT_sensors_reload(); +// return VIEW_NONE; +// } +// static void mainMenu_enterCallback(void* context, uint32_t index) { +// PluginData* app = context; +// if((uint8_t)index == (uint8_t)app->sensors_count) { +// addSensor_scene(app); +// view_dispatcher_run(app->view_dispatcher); +// } +// } +// void mainMenu_scene(PluginData* app) { +// variable_item_list = variable_item_list_alloc(); +// variable_item_list_reset(variable_item_list); +// for(uint8_t i = 0; i < app->sensors_count; i++) { +// variable_item_list_add(variable_item_list, app->sensors[i].name, 1, NULL, NULL); +// } +// variable_item_list_add(variable_item_list, "+ Add new sensor +", 1, NULL, NULL); + +// app->view = variable_item_list_get_view(variable_item_list); +// app->view_dispatcher = view_dispatcher_alloc(); + +// view_dispatcher_enable_queue(app->view_dispatcher); +// view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); +// view_dispatcher_add_view(app->view_dispatcher, MAIN_MENU_VIEW, app->view); +// view_dispatcher_switch_to_view(app->view_dispatcher, MAIN_MENU_VIEW); + +// variable_item_list_set_enter_callback(variable_item_list, mainMenu_enterCallback, app); +// view_set_previous_callback(app->view, mainMenu_exitCallback); +// } \ No newline at end of file diff --git a/applications/plugins/dht_temp_sensor/scenes/DHTMon_main_scene.c b/applications/plugins/dht_temp_sensor/scenes/DHTMon_main_scene.c new file mode 100644 index 000000000..aab343752 --- /dev/null +++ b/applications/plugins/dht_temp_sensor/scenes/DHTMon_main_scene.c @@ -0,0 +1,40 @@ +#include "../quenon_dht_mon.h" + +/* ============== Главный экран ============== */ +void scene_main(Canvas* const canvas, PluginData* app) { + //Рисование бара + canvas_draw_box(canvas, 0, 0, 128, 14); + canvas_set_color(canvas, ColorWhite); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 32, 11, "DHT Monitor"); + + canvas_set_color(canvas, ColorBlack); + if(app->sensors_count > 0) { + if(!furi_hal_power_is_otg_enabled()) { + furi_hal_power_enable_otg(); + } + for(uint8_t i = 0; i < app->sensors_count; i++) { + app->data = DHT_getData(&app->sensors[i]); + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 0, 24 + 10 * i, app->sensors[i].name); + + canvas_set_font(canvas, FontSecondary); + if(app->data.hum == -128.0f && app->data.temp == -128.0f) { + canvas_draw_str(canvas, 96, 24 + 10 * i, "timeout"); + } else { + snprintf( + app->txtbuff, + sizeof(app->txtbuff), + "%2.1f*C/%d%%", + (double)app->data.temp, + (int8_t)app->data.hum); + canvas_draw_str(canvas, 64, 24 + 10 * i, app->txtbuff); + } + } + } else { + canvas_set_font(canvas, FontSecondary); + if(app->sensors_count == 0) canvas_draw_str(canvas, 0, 24, "Sensors not found"); + if(app->sensors_count == -1) canvas_draw_str(canvas, 0, 24, "Loading..."); + } +} diff --git a/applications/plugins/dht_temp_sensor/scenes/DHTMon_sensorActions_scene.c b/applications/plugins/dht_temp_sensor/scenes/DHTMon_sensorActions_scene.c new file mode 100644 index 000000000..ae7674f70 --- /dev/null +++ b/applications/plugins/dht_temp_sensor/scenes/DHTMon_sensorActions_scene.c @@ -0,0 +1,194 @@ +#include "../quenon_dht_mon.h" + +//Текущий вид +static View* view; +//Список +static VariableItemList* variable_item_list; + +/* ================== Информация о датчике ================== */ +/** + * @brief Функция обработки нажатия кнопки "Назад" + * + * @param context Указатель на данные приложения + * @return ID вида в который нужно переключиться + */ +static uint32_t infoWidget_exitCallback(void* context) { + PluginData* app = context; + UNUSED(app); + //Возвращаем ID вида, в который нужно вернуться + return SENSOR_ACTIONS_VIEW; +} +/** + * @brief Обработчик нажатий на кнопку в виджете + * + * @param result Какая из кнопок была нажата + * @param type Тип нажатия + * @param context Указатель на данные плагина + */ +static void infoWidget_callback(GuiButtonType result, InputType type, void* context) { + PluginData* app = context; + //Коротко нажата левая кнопка (Back) + if(result == GuiButtonTypeLeft && type == InputTypeShort) { + view_dispatcher_switch_to_view(app->view_dispatcher, SENSOR_ACTIONS_VIEW); + } +} +/** + * @brief Создание виджета информации о датчике + * + * @param app Указатель на данные плагина + */ +static void sensorInfo_widget(PluginData* app) { + //Очистка виджета + widget_reset(app->widget); + //Добавление кнопок + widget_add_button_element(app->widget, GuiButtonTypeLeft, "Back", infoWidget_callback, app); + + char str[32]; + snprintf(str, sizeof(str), "\e#%s\e#", app->currentSensorEdit->name); + widget_add_text_box_element(app->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, str, false); + snprintf(str, sizeof(str), "\e#Type:\e# %s", app->currentSensorEdit->type ? "DHT22" : "DHT11"); + widget_add_text_box_element(app->widget, 0, 0, 128, 47, AlignLeft, AlignCenter, str, false); + snprintf( + str, sizeof(str), "\e#GPIO:\e# %s", DHTMon_GPIO_getName(app->currentSensorEdit->GPIO)); + widget_add_text_box_element(app->widget, 0, 0, 128, 72, AlignLeft, AlignCenter, str, false); + view_set_previous_callback(widget_get_view(app->widget), infoWidget_exitCallback); + view_dispatcher_switch_to_view(app->view_dispatcher, WIDGET_VIEW); +} + +/* ================== Подтверждение удаления ================== */ +/** + * @brief Функция обработки нажатия кнопки "Назад" + * + * @param context Указатель на данные приложения + * @return ID вида в который нужно переключиться + */ +static uint32_t deleteWidget_exitCallback(void* context) { + PluginData* app = context; + UNUSED(app); + //Возвращаем ID вида, в который нужно вернуться + return SENSOR_ACTIONS_VIEW; +} +/** + * @brief Обработчик нажатий на кнопку в виджете + * + * @param result Какая из кнопок была нажата + * @param type Тип нажатия + * @param context Указатель на данные плагина + */ +static void deleteWidget_callback(GuiButtonType result, InputType type, void* context) { + PluginData* app = context; + //Коротко нажата левая кнопка (Cancel) + if(result == GuiButtonTypeLeft && type == InputTypeShort) { + view_dispatcher_switch_to_view(app->view_dispatcher, SENSOR_ACTIONS_VIEW); + } + //Коротко нажата правая кнопка (Delete) + if(result == GuiButtonTypeRight && type == InputTypeShort) { + //Удаление датчика + DHTMon_sensor_delete(app->currentSensorEdit); + //Выход из меню + view_dispatcher_switch_to_view(app->view_dispatcher, VIEW_NONE); + } +} +/** + * @brief Создание виджета удаления датчика + * + * @param app Указатель на данные плагина + */ +static void sensorDelete_widget(PluginData* app) { + //Очистка виджета + widget_reset(app->widget); + //Добавление кнопок + widget_add_button_element( + app->widget, GuiButtonTypeLeft, "Cancel", deleteWidget_callback, app); + widget_add_button_element( + app->widget, GuiButtonTypeRight, "Delete", deleteWidget_callback, app); + + char delete_str[32]; + snprintf(delete_str, sizeof(delete_str), "\e#Delete %s?\e#", app->currentSensorEdit->name); + widget_add_text_box_element( + app->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, delete_str, false); + snprintf( + delete_str, + sizeof(delete_str), + "\e#Type:\e# %s", + app->currentSensorEdit->type ? "DHT22" : "DHT11"); + widget_add_text_box_element( + app->widget, 0, 0, 128, 47, AlignLeft, AlignCenter, delete_str, false); + snprintf( + delete_str, + sizeof(delete_str), + "\e#GPIO:\e# %s", + DHTMon_GPIO_getName(app->currentSensorEdit->GPIO)); + widget_add_text_box_element( + app->widget, 0, 0, 128, 72, AlignLeft, AlignCenter, delete_str, false); + view_set_previous_callback(widget_get_view(app->widget), deleteWidget_exitCallback); + view_dispatcher_switch_to_view(app->view_dispatcher, WIDGET_VIEW); +} + +/* ================== Меню действий ================== */ +/** + * @brief Функция обработки нажатия средней кнопки + * + * @param context Указатель на данные приложения + * @param index На каком элементе списка была нажата кнопка + */ +static void enterCallback(void* context, uint32_t index) { + PluginData* app = context; + if(index == 0) { + sensorInfo_widget(app); + } + if(index == 1) { + sensorEdit_scene(app); + } + if(index == 2) { + sensorDelete_widget(app); + } +} + +/** + * @brief Функция обработки нажатия кнопки "Назад" + * + * @param context Указатель на данные приложения + * @return ID вида в который нужно переключиться + */ +static uint32_t actions_exitCallback(void* context) { + PluginData* app = context; + UNUSED(app); + //Возвращаем ID вида, в который нужно вернуться + return MAIN_MENU_VIEW; +} + +/** + * @brief Создание списка действий с указанным датчиком + * + * @param app Указатель на данные плагина + */ +void sensorActions_sceneCreate(PluginData* app) { + variable_item_list = variable_item_list_alloc(); + //Сброс всех элементов меню + variable_item_list_reset(variable_item_list); + //Добавление элементов в список + variable_item_list_add(variable_item_list, "Info", 0, NULL, NULL); + variable_item_list_add(variable_item_list, "Edit", 0, NULL, NULL); + variable_item_list_add(variable_item_list, "Delete", 0, NULL, NULL); + + //Добавление колбека на нажатие средней кнопки + variable_item_list_set_enter_callback(variable_item_list, enterCallback, app); + + //Создание вида из списка + view = variable_item_list_get_view(variable_item_list); + //Добавление колбека на нажатие кнопки "Назад" + view_set_previous_callback(view, actions_exitCallback); + //Добавление вида в диспетчер + view_dispatcher_add_view(app->view_dispatcher, SENSOR_ACTIONS_VIEW, view); +} +void sensorActions_scene(PluginData* app) { + //Сброс выбранного пункта в ноль + variable_item_list_set_selected_item(variable_item_list, 0); + //Переключение на наш вид + view_dispatcher_switch_to_view(app->view_dispatcher, SENSOR_ACTIONS_VIEW); +} + +void sensorActions_screneRemove(void) { + variable_item_list_free(variable_item_list); +} diff --git a/applications/plugins/dht_temp_sensor/scenes/DHTMon_sensorEdit_scene.c b/applications/plugins/dht_temp_sensor/scenes/DHTMon_sensorEdit_scene.c new file mode 100644 index 000000000..5decac3d1 --- /dev/null +++ b/applications/plugins/dht_temp_sensor/scenes/DHTMon_sensorEdit_scene.c @@ -0,0 +1,103 @@ +#include "../quenon_dht_mon.h" + +static VariableItem* nameItem; +static VariableItemList* variable_item_list; + +static const char* const sensorsTypes[2] = { + "DHT11", + "DHT22", +}; + +// /* ============== Добавление датчика ============== */ +static uint32_t addSensor_exitCallback(void* context) { + UNUSED(context); + DHTMon_sensors_reload(); + return VIEW_NONE; +} + +static void addSensor_sensorTypeChanged(VariableItem* item) { + uint8_t index = variable_item_get_current_value_index(item); + PluginData* app = variable_item_get_context(item); + variable_item_set_current_value_text(item, sensorsTypes[index]); + app->currentSensorEdit->type = index; +} + +static void addSensor_GPIOChanged(VariableItem* item) { + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, DHTMon_GPIO_getName(DHTMon_GPIO_from_index(index))); + PluginData* app = variable_item_get_context(item); + app->currentSensorEdit->GPIO = DHTMon_GPIO_from_index(index); +} + +static void addSensor_sensorNameChanged(void* context) { + PluginData* app = context; + variable_item_set_current_value_text(nameItem, app->currentSensorEdit->name); + view_dispatcher_switch_to_view(app->view_dispatcher, ADDSENSOR_MENU_VIEW); +} +static void addSensor_sensorNameChange(PluginData* app) { + text_input_set_header_text(app->text_input, "Sensor name"); + //По неясной мне причине в длину строки входит терминатор. Поэтому при длине 10 приходится указывать 11 + text_input_set_result_callback( + app->text_input, addSensor_sensorNameChanged, app, app->currentSensorEdit->name, 11, true); + view_dispatcher_switch_to_view(app->view_dispatcher, TEXTINPUT_VIEW); +} + +static void addSensor_enterCallback(void* context, uint32_t index) { + PluginData* app = context; + if(index == 0) { + addSensor_sensorNameChange(app); + } + if(index == 3) { + //Сохранение датчика + DHTMon_sensors_save(); + DHTMon_sensors_reload(); + view_dispatcher_switch_to_view(app->view_dispatcher, VIEW_NONE); + } +} + +void sensorEdit_sceneCreate(PluginData* app) { + variable_item_list = variable_item_list_alloc(); + + variable_item_list_reset(variable_item_list); + + variable_item_list_set_enter_callback(variable_item_list, addSensor_enterCallback, app); + + app->view = variable_item_list_get_view(variable_item_list); + + view_set_previous_callback(app->view, addSensor_exitCallback); + + view_dispatcher_add_view(app->view_dispatcher, ADDSENSOR_MENU_VIEW, app->view); +} +void sensorEdit_scene(PluginData* app) { + //Очистка списка + variable_item_list_reset(variable_item_list); + + //Имя редактируемого датчика + nameItem = variable_item_list_add(variable_item_list, "Name: ", 1, NULL, NULL); + variable_item_set_current_value_index(nameItem, 0); + variable_item_set_current_value_text(nameItem, app->currentSensorEdit->name); + + //Тип датчика + app->item = + variable_item_list_add(variable_item_list, "Type:", 2, addSensor_sensorTypeChanged, app); + + variable_item_set_current_value_index(app->item, app->currentSensorEdit->type); + variable_item_set_current_value_text(app->item, sensorsTypes[app->currentSensorEdit->type]); + + //GPIO + app->item = + variable_item_list_add(variable_item_list, "GPIO:", 13, addSensor_GPIOChanged, app); + variable_item_set_current_value_index( + app->item, DHTMon_GPIO_to_index(app->currentSensorEdit->GPIO)); + variable_item_set_current_value_text( + app->item, DHTMon_GPIO_getName(app->currentSensorEdit->GPIO)); + variable_item_list_add(variable_item_list, "Save", 1, NULL, app); + + //Сброс выбранного пункта в ноль + variable_item_list_set_selected_item(variable_item_list, 0); + + view_dispatcher_switch_to_view(app->view_dispatcher, ADDSENSOR_MENU_VIEW); +} +void sensorEdit_sceneRemove(void) { + variable_item_list_free(variable_item_list); +} \ No newline at end of file diff --git a/applications/plugins/dice2/LICENSE.md b/applications/plugins/dice2/LICENSE.md new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/applications/plugins/dice2/LICENSE.md @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/applications/plugins/dice2/README.md b/applications/plugins/dice2/README.md new file mode 100644 index 000000000..43ac42ee8 --- /dev/null +++ b/applications/plugins/dice2/README.md @@ -0,0 +1,21 @@ +# Flipper Zero DnD Dice + +
+
+ +**DnD Dice** is a dice rolling application for your **Flipper Zero**. + +Dice types: Coin, d4, d6, d8, d10, d12, d20, d100 + +## Screenshots + +
+
+
+ +## Compiling + +1. Clone the [flipperzero-firmware](https://github.com/flipperdevices/flipperzero-firmware) repository or another firmware that you use (for example [unleashed-firmware](https://github.com/DarkFlippers/unleashed-firmware)). +2. Create a symbolic link in `applications_user` named **dice**, pointing to this repository. +3. Compile by command `./fbt fap_dice_dnd_app` +4. Copy `build/f7-firmware-D/.extapps/dice_dnd_app.fap` to **apps/Games** on the SD card or by [qFlipper](https://flipperzero.one/update) app. diff --git a/applications/plugins/dice2/application.fam b/applications/plugins/dice2/application.fam new file mode 100644 index 000000000..e8ec7ccd2 --- /dev/null +++ b/applications/plugins/dice2/application.fam @@ -0,0 +1,13 @@ +App( + appid="DND_Dice_app", + name="DnD Dice [Ka3u6y6a]", + apptype=FlipperAppType.EXTERNAL, + entry_point="dice_dnd_app", + cdefines=["APP_DICE"], + requires=["gui"], + stack_size=1 * 1024, + order=90, + fap_icon="icon.png", + fap_category="Games", + fap_icon_assets="assets", +) \ No newline at end of file diff --git a/applications/plugins/dice2/assets/coin_1.png b/applications/plugins/dice2/assets/coin_1.png new file mode 100644 index 000000000..6f56f9644 Binary files /dev/null and b/applications/plugins/dice2/assets/coin_1.png differ diff --git a/applications/plugins/dice2/assets/coin_2.png b/applications/plugins/dice2/assets/coin_2.png new file mode 100644 index 000000000..08c5872d9 Binary files /dev/null and b/applications/plugins/dice2/assets/coin_2.png differ diff --git a/applications/plugins/dice2/assets/coin_3.png b/applications/plugins/dice2/assets/coin_3.png new file mode 100644 index 000000000..c757caa66 Binary files /dev/null and b/applications/plugins/dice2/assets/coin_3.png differ diff --git a/applications/plugins/dice2/assets/coin_4.png b/applications/plugins/dice2/assets/coin_4.png new file mode 100644 index 000000000..508184d14 Binary files /dev/null and b/applications/plugins/dice2/assets/coin_4.png differ diff --git a/applications/plugins/dice2/assets/coin_5.png b/applications/plugins/dice2/assets/coin_5.png new file mode 100644 index 000000000..85831d239 Binary files /dev/null and b/applications/plugins/dice2/assets/coin_5.png differ diff --git a/applications/plugins/dice2/assets/coin_6.png b/applications/plugins/dice2/assets/coin_6.png new file mode 100644 index 000000000..17cdbf105 Binary files /dev/null and b/applications/plugins/dice2/assets/coin_6.png differ diff --git a/applications/plugins/dice2/assets/coin_7.png b/applications/plugins/dice2/assets/coin_7.png new file mode 100644 index 000000000..82f828a94 Binary files /dev/null and b/applications/plugins/dice2/assets/coin_7.png differ diff --git a/applications/plugins/dice2/assets/d100_1.png b/applications/plugins/dice2/assets/d100_1.png new file mode 100644 index 000000000..a7f2c18b0 Binary files /dev/null and b/applications/plugins/dice2/assets/d100_1.png differ diff --git a/applications/plugins/dice2/assets/d100_2.png b/applications/plugins/dice2/assets/d100_2.png new file mode 100644 index 000000000..783486976 Binary files /dev/null and b/applications/plugins/dice2/assets/d100_2.png differ diff --git a/applications/plugins/dice2/assets/d100_3.png b/applications/plugins/dice2/assets/d100_3.png new file mode 100644 index 000000000..92d5a5c0c Binary files /dev/null and b/applications/plugins/dice2/assets/d100_3.png differ diff --git a/applications/plugins/dice2/assets/d100_4.png b/applications/plugins/dice2/assets/d100_4.png new file mode 100644 index 000000000..324b7f633 Binary files /dev/null and b/applications/plugins/dice2/assets/d100_4.png differ diff --git a/applications/plugins/dice2/assets/d10_1.png b/applications/plugins/dice2/assets/d10_1.png new file mode 100644 index 000000000..d742026c9 Binary files /dev/null and b/applications/plugins/dice2/assets/d10_1.png differ diff --git a/applications/plugins/dice2/assets/d10_2.png b/applications/plugins/dice2/assets/d10_2.png new file mode 100644 index 000000000..0d9ca60ef Binary files /dev/null and b/applications/plugins/dice2/assets/d10_2.png differ diff --git a/applications/plugins/dice2/assets/d10_3.png b/applications/plugins/dice2/assets/d10_3.png new file mode 100644 index 000000000..ab67316c6 Binary files /dev/null and b/applications/plugins/dice2/assets/d10_3.png differ diff --git a/applications/plugins/dice2/assets/d10_4.png b/applications/plugins/dice2/assets/d10_4.png new file mode 100644 index 000000000..e89b03f3a Binary files /dev/null and b/applications/plugins/dice2/assets/d10_4.png differ diff --git a/applications/plugins/dice2/assets/d12_1.png b/applications/plugins/dice2/assets/d12_1.png new file mode 100644 index 000000000..053ead3cd Binary files /dev/null and b/applications/plugins/dice2/assets/d12_1.png differ diff --git a/applications/plugins/dice2/assets/d12_2.png b/applications/plugins/dice2/assets/d12_2.png new file mode 100644 index 000000000..752abaf33 Binary files /dev/null and b/applications/plugins/dice2/assets/d12_2.png differ diff --git a/applications/plugins/dice2/assets/d12_3.png b/applications/plugins/dice2/assets/d12_3.png new file mode 100644 index 000000000..711d18514 Binary files /dev/null and b/applications/plugins/dice2/assets/d12_3.png differ diff --git a/applications/plugins/dice2/assets/d12_4.png b/applications/plugins/dice2/assets/d12_4.png new file mode 100644 index 000000000..dff920c42 Binary files /dev/null and b/applications/plugins/dice2/assets/d12_4.png differ diff --git a/applications/plugins/dice2/assets/d20_1.png b/applications/plugins/dice2/assets/d20_1.png new file mode 100644 index 000000000..593adbad9 Binary files /dev/null and b/applications/plugins/dice2/assets/d20_1.png differ diff --git a/applications/plugins/dice2/assets/d20_2.png b/applications/plugins/dice2/assets/d20_2.png new file mode 100644 index 000000000..b8d11952d Binary files /dev/null and b/applications/plugins/dice2/assets/d20_2.png differ diff --git a/applications/plugins/dice2/assets/d20_3.png b/applications/plugins/dice2/assets/d20_3.png new file mode 100644 index 000000000..bea6e8ffc Binary files /dev/null and b/applications/plugins/dice2/assets/d20_3.png differ diff --git a/applications/plugins/dice2/assets/d20_4.png b/applications/plugins/dice2/assets/d20_4.png new file mode 100644 index 000000000..1fa19dc4c Binary files /dev/null and b/applications/plugins/dice2/assets/d20_4.png differ diff --git a/applications/plugins/dice2/assets/d4_1.png b/applications/plugins/dice2/assets/d4_1.png new file mode 100644 index 000000000..8007cdca5 Binary files /dev/null and b/applications/plugins/dice2/assets/d4_1.png differ diff --git a/applications/plugins/dice2/assets/d4_2.png b/applications/plugins/dice2/assets/d4_2.png new file mode 100644 index 000000000..8757c56e3 Binary files /dev/null and b/applications/plugins/dice2/assets/d4_2.png differ diff --git a/applications/plugins/dice2/assets/d4_3.png b/applications/plugins/dice2/assets/d4_3.png new file mode 100644 index 000000000..8d1687ac8 Binary files /dev/null and b/applications/plugins/dice2/assets/d4_3.png differ diff --git a/applications/plugins/dice2/assets/d6_1.png b/applications/plugins/dice2/assets/d6_1.png new file mode 100644 index 000000000..f3b2ba293 Binary files /dev/null and b/applications/plugins/dice2/assets/d6_1.png differ diff --git a/applications/plugins/dice2/assets/d6_2.png b/applications/plugins/dice2/assets/d6_2.png new file mode 100644 index 000000000..257c87a0d Binary files /dev/null and b/applications/plugins/dice2/assets/d6_2.png differ diff --git a/applications/plugins/dice2/assets/d6_3.png b/applications/plugins/dice2/assets/d6_3.png new file mode 100644 index 000000000..882be3a67 Binary files /dev/null and b/applications/plugins/dice2/assets/d6_3.png differ diff --git a/applications/plugins/dice2/assets/d6_4.png b/applications/plugins/dice2/assets/d6_4.png new file mode 100644 index 000000000..ac7928e2c Binary files /dev/null and b/applications/plugins/dice2/assets/d6_4.png differ diff --git a/applications/plugins/dice2/assets/d8_1.png b/applications/plugins/dice2/assets/d8_1.png new file mode 100644 index 000000000..b4c3b692e Binary files /dev/null and b/applications/plugins/dice2/assets/d8_1.png differ diff --git a/applications/plugins/dice2/assets/d8_2.png b/applications/plugins/dice2/assets/d8_2.png new file mode 100644 index 000000000..705416e49 Binary files /dev/null and b/applications/plugins/dice2/assets/d8_2.png differ diff --git a/applications/plugins/dice2/assets/d8_3.png b/applications/plugins/dice2/assets/d8_3.png new file mode 100644 index 000000000..4c95fdcbf Binary files /dev/null and b/applications/plugins/dice2/assets/d8_3.png differ diff --git a/applications/plugins/dice2/assets/d8_4.png b/applications/plugins/dice2/assets/d8_4.png new file mode 100644 index 000000000..a5f7fe838 Binary files /dev/null and b/applications/plugins/dice2/assets/d8_4.png differ diff --git a/applications/plugins/dice2/assets/ui_button_back.png b/applications/plugins/dice2/assets/ui_button_back.png new file mode 100644 index 000000000..2c22d19c6 Binary files /dev/null and b/applications/plugins/dice2/assets/ui_button_back.png differ diff --git a/applications/plugins/dice2/assets/ui_button_down.png b/applications/plugins/dice2/assets/ui_button_down.png new file mode 100644 index 000000000..2954bb6a6 Binary files /dev/null and b/applications/plugins/dice2/assets/ui_button_down.png differ diff --git a/applications/plugins/dice2/assets/ui_button_exit.png b/applications/plugins/dice2/assets/ui_button_exit.png new file mode 100644 index 000000000..22f357913 Binary files /dev/null and b/applications/plugins/dice2/assets/ui_button_exit.png differ diff --git a/applications/plugins/dice2/assets/ui_button_left.png b/applications/plugins/dice2/assets/ui_button_left.png new file mode 100644 index 000000000..0b4655d43 Binary files /dev/null and b/applications/plugins/dice2/assets/ui_button_left.png differ diff --git a/applications/plugins/dice2/assets/ui_button_right.png b/applications/plugins/dice2/assets/ui_button_right.png new file mode 100644 index 000000000..8e1c74c1c Binary files /dev/null and b/applications/plugins/dice2/assets/ui_button_right.png differ diff --git a/applications/plugins/dice2/assets/ui_button_roll.png b/applications/plugins/dice2/assets/ui_button_roll.png new file mode 100644 index 000000000..f20d7f565 Binary files /dev/null and b/applications/plugins/dice2/assets/ui_button_roll.png differ diff --git a/applications/plugins/dice2/assets/ui_button_up.png b/applications/plugins/dice2/assets/ui_button_up.png new file mode 100644 index 000000000..1be79328b Binary files /dev/null and b/applications/plugins/dice2/assets/ui_button_up.png differ diff --git a/applications/plugins/dice2/assets/ui_count.png b/applications/plugins/dice2/assets/ui_count.png new file mode 100644 index 000000000..a408de025 Binary files /dev/null and b/applications/plugins/dice2/assets/ui_count.png differ diff --git a/applications/plugins/dice2/assets/ui_count_1.png b/applications/plugins/dice2/assets/ui_count_1.png new file mode 100644 index 000000000..ec61bde96 Binary files /dev/null and b/applications/plugins/dice2/assets/ui_count_1.png differ diff --git a/applications/plugins/dice2/assets/ui_result_border.png b/applications/plugins/dice2/assets/ui_result_border.png new file mode 100644 index 000000000..576402050 Binary files /dev/null and b/applications/plugins/dice2/assets/ui_result_border.png differ diff --git a/applications/plugins/dice2/constants.h b/applications/plugins/dice2/constants.h new file mode 100644 index 000000000..3f263433d --- /dev/null +++ b/applications/plugins/dice2/constants.h @@ -0,0 +1,159 @@ +#include +#include "DND_Dice_app_icons.h" + +#define TAG "DiceApp" + +#define DICE_TYPES 8 + +#define MAX_DICE_COUNT 10 +#define MAX_COIN_FRAMES 9 +#define MAX_DICE_FRAMES 4 + +#define DICE_X 45 +#define DICE_Y 6 +#define DICE_Y_T 0 + +#define DICE_GAP 44 + +#define RESULT_BORDER_X 44 +#define RESULT_OFFSET 20 + +#define SWIPE_DIST 11 + +const Icon* coin_heads_start[] = {&I_coin_1, &I_coin_2}; +const Icon* coin_heads_end[] = {&I_coin_7, &I_coin_1}; +const Icon* coin_tails_start[] = {&I_coin_5, &I_coin_6}; +const Icon* coin_tails_end[] = {&I_coin_4, &I_coin_5}; +const Icon* coin_frames[] = { + &I_coin_1, + &I_coin_2, + &I_coin_3, + &I_coin_4, + &I_coin_5, + &I_coin_6, + &I_coin_3, + &I_coin_7, + &I_coin_1, +}; + +const int8_t result_frame_pos_y[] = {-30, -20, -10, 0}; +const Icon* dice_frames[] = { + &I_d4_1, &I_d4_2, &I_d4_3, &I_d4_1, // d4 + &I_d6_1, &I_d6_2, &I_d6_3, &I_d6_4, // d6 + &I_d8_1, &I_d8_2, &I_d8_3, &I_d8_4, // d8 + &I_d10_1, &I_d10_2, &I_d10_3, &I_d10_4, // d10 + &I_d12_1, &I_d12_2, &I_d12_3, &I_d12_4, // d12 + &I_d20_1, &I_d20_2, &I_d20_3, &I_d20_4, // d20 + &I_d100_1, &I_d100_2, &I_d100_3, &I_d100_4, // d100 +}; + +typedef struct { + uint8_t type; + int x; + int y; + char* name; +} Dice; + +const uint8_t screen_pos[] = {}; + +static const Dice dice_types[] = { + {2, 0, 0, "Coin"}, + {4, 0, 0, "d4"}, + {6, 0, 0, "d6"}, + {8, 0, 0, "d8"}, + {10, 0, 0, "d10"}, + {12, 0, 0, "d12"}, + {20, 0, 0, "d20"}, + {100, 0, 0, "d100"}, +}; + +typedef enum { EventTypeTick, EventTypeKey } EventType; +typedef enum { + SelectState, + SwipeLeftState, + SwipeRightState, + AnimState, + AnimResultState, + ResultState +} AppState; + +typedef struct { + EventType type; + InputEvent input; +} AppEvent; + +typedef struct { + AppState app_state; + uint16_t roll_result; + uint8_t rolled_dices[MAX_DICE_COUNT]; + uint8_t anim_frame; + uint8_t dice_index; + uint8_t dice_count; + int8_t result_pos; + Dice dices[DICE_TYPES]; +} State; + +void init(State* const state) { + state->app_state = SelectState; + state->roll_result = 0; + state->dice_index = 0; + state->anim_frame = 0; + state->dice_count = 1; + + for(uint8_t i = 0; i < DICE_TYPES; i++) { + state->dices[i] = dice_types[i]; + state->dices[i].x = DICE_X + (i * DICE_GAP); + state->dices[i].y = i == 0 ? DICE_Y_T : DICE_Y; + } +} + +void coin_set_start(uint16_t type) { + if(type == 1) { + coin_frames[0] = coin_heads_start[0]; + coin_frames[1] = coin_heads_start[1]; + } else { + coin_frames[0] = coin_tails_start[0]; + coin_frames[1] = coin_tails_start[1]; + } +} + +void coin_set_end(uint16_t type) { + if(type == 1) { + coin_frames[MAX_COIN_FRAMES - 2] = coin_heads_end[0]; + coin_frames[MAX_COIN_FRAMES - 1] = coin_heads_end[1]; + } else { + coin_frames[MAX_COIN_FRAMES - 2] = coin_tails_end[0]; + coin_frames[MAX_COIN_FRAMES - 1] = coin_tails_end[1]; + } +} + +bool isResultVisible(AppState state, uint8_t dice_index) { + return (state == ResultState || state == AnimResultState) && dice_index != 0; +} + +bool isDiceNameVisible(AppState state) { + return state != SwipeLeftState && state != SwipeRightState; +} + +bool isDiceButtonsVisible(AppState state) { + return isDiceNameVisible(state) && state != AnimResultState && state != ResultState && + state != AnimState; +} + +bool isOneDice(uint8_t dice_index) { + return dice_index == 0 || dice_index == 7; +} + +bool isDiceSettingsDisabled(AppState state, uint8_t dice_index) { + return isOneDice(dice_index) || state == ResultState || state == AnimResultState || + state == AnimState; +} + +bool isAnimState(AppState state) { + return state == SwipeLeftState || state == SwipeRightState || state == AnimResultState || + state == AnimState; +} + +bool isMenuState(AppState state) { + return state == SwipeLeftState || state == SwipeRightState || state == SelectState; +} \ No newline at end of file diff --git a/applications/plugins/dice2/dice_app.c b/applications/plugins/dice2/dice_app.c new file mode 100644 index 000000000..5e06c4aad --- /dev/null +++ b/applications/plugins/dice2/dice_app.c @@ -0,0 +1,333 @@ +#include +#include +#include +#include "constants.h" + +const Icon* draw_dice_frame; + +static void update(State* const state) { + if(state->app_state == SwipeLeftState) { + for(uint8_t i = 0; i < DICE_TYPES; i++) { + state->dices[i].x -= SWIPE_DIST; + state->dices[i].y = DICE_Y; + } + + if(state->dices[state->dice_index].x == DICE_X) { + state->app_state = SelectState; + state->dices[state->dice_index].y = DICE_Y_T; + } + + } else if(state->app_state == SwipeRightState) { + for(uint8_t i = 0; i < DICE_TYPES; i++) { + state->dices[i].x += SWIPE_DIST; + state->dices[i].y = DICE_Y; + } + + if(state->dices[state->dice_index].x == DICE_X) { + state->app_state = SelectState; + state->dices[state->dice_index].y = DICE_Y_T; + } + } else if(state->app_state == AnimState) { + state->anim_frame += 1; + + if(state->dice_index == 0) { + if(state->anim_frame == 3) coin_set_start(state->roll_result); // change coin anim + + if(state->anim_frame >= MAX_COIN_FRAMES) { + state->anim_frame = 0; + state->app_state = AnimResultState; + } + } else { + if(state->anim_frame >= MAX_DICE_FRAMES) { + state->anim_frame = 0; + state->app_state = AnimResultState; + } + } + } else if(state->app_state == AnimResultState) { + if(state->dice_index == 0) { // no extra animations for coin + state->anim_frame = 0; + state->app_state = ResultState; + return; + } + + state->result_pos = result_frame_pos_y[state->anim_frame]; + state->anim_frame += 1; + + // end animation + if(state->result_pos == 0) { + state->anim_frame = 0; + state->app_state = ResultState; + } + } +} + +static void roll(State* const state) { + state->roll_result = 0; + state->result_pos = result_frame_pos_y[0]; + + for(uint8_t i = 0; i < MAX_DICE_COUNT; i++) { + if(i < state->dice_count) { + state->rolled_dices[i] = (rand() % dice_types[state->dice_index].type) + 1; + state->roll_result += state->rolled_dices[i]; + } else { + state->rolled_dices[i] = 0; + } + } + + if(state->dice_index == 0) coin_set_end(state->roll_result); // change coin anim + + state->app_state = AnimState; +} + +static void draw_ui(const State* state, Canvas* canvas) { + canvas_set_font(canvas, FontSecondary); + + FuriString* count = furi_string_alloc(); + furi_string_printf(count, "%01d", state->dice_count); + + // dice name + if(isDiceNameVisible(state->app_state)) { + canvas_draw_str_aligned( + canvas, 63, 50, AlignCenter, AlignBottom, dice_types[state->dice_index].name); + } + // dice arrow buttons + if(isDiceButtonsVisible(state->app_state)) { + if(state->dice_index > 0) canvas_draw_icon(canvas, 45, 44, &I_ui_button_left); + if(state->dice_index < DICE_TYPES - 1) + canvas_draw_icon(canvas, 78, 44, &I_ui_button_right); + } + + // dice count settings + if(isDiceSettingsDisabled(state->app_state, state->dice_index)) + canvas_draw_icon(canvas, 48, 51, &I_ui_count_1); + else + canvas_draw_icon(canvas, 48, 51, &I_ui_count); + canvas_draw_str_aligned(canvas, 58, 61, AlignCenter, AlignBottom, furi_string_get_cstr(count)); + + // buttons + if(isAnimState(state->app_state) == false) canvas_draw_icon(canvas, 92, 54, &I_ui_button_roll); + + if(state->app_state != AnimResultState && state->app_state != ResultState) { + canvas_draw_icon(canvas, 0, 54, &I_ui_button_exit); + } else { + canvas_draw_icon(canvas, 0, 54, &I_ui_button_back); + } + + furi_string_free(count); +} + +static void draw_dice(const State* state, Canvas* canvas) { + if(isMenuState(state->app_state) == false) { // draw only selected dice + if(state->dice_index == 0) { // coin + draw_dice_frame = coin_frames[state->anim_frame]; + } else { // dices + draw_dice_frame = + dice_frames[(state->dice_index - 1) * MAX_DICE_FRAMES + state->anim_frame]; + } + + canvas_draw_icon( + canvas, + state->dices[state->dice_index].x, + state->dices[state->dice_index].y, + draw_dice_frame); + return; + } + + for(uint8_t i = 0; i < DICE_TYPES; i++) { + if(state->app_state == ResultState && state->dice_index == i && state->dice_index != 0) + continue; // draw results except coin + if(state->dices[i].x > 128 || state->dices[i].x < -35) continue; // outside the screen + + if(i == 0) { // coin + draw_dice_frame = coin_frames[0]; + } else { // dices + draw_dice_frame = dice_frames[(i - 1) * MAX_DICE_FRAMES]; + } + + canvas_draw_icon(canvas, state->dices[i].x, state->dices[i].y, draw_dice_frame); + } +} + +static void draw_results(const State* state, Canvas* canvas) { + canvas_set_font(canvas, FontPrimary); + + FuriString* sum = furi_string_alloc(); + furi_string_printf(sum, "%01d", state->roll_result); + + // ui frame + if(state->app_state == AnimResultState) + canvas_draw_icon(canvas, RESULT_BORDER_X, state->result_pos, &I_ui_result_border); + else + canvas_draw_icon( + canvas, RESULT_BORDER_X, result_frame_pos_y[MAX_DICE_FRAMES - 1], &I_ui_result_border); + + // result text + canvas_draw_str_aligned( + canvas, + 64, + state->result_pos + RESULT_OFFSET, + AlignCenter, + AlignCenter, + furi_string_get_cstr(sum)); + + if(state->app_state == ResultState && isOneDice(state->dice_index) == false) { + canvas_set_font(canvas, FontSecondary); + + FuriString* dices = furi_string_alloc(); + for(uint8_t i = 0; i < state->dice_count; i++) { + furi_string_cat_printf(dices, "%01d", state->rolled_dices[i]); + + if(i != state->dice_count - 1) furi_string_cat_printf(dices, "%s", ", "); + } + + canvas_draw_str_aligned( + canvas, 63, 37, AlignCenter, AlignCenter, furi_string_get_cstr(dices)); + furi_string_free(dices); + } + + furi_string_free(sum); +} + +static void draw_callback(Canvas* canvas, void* ctx) { + const State* state = acquire_mutex((ValueMutex*)ctx, 25); + if(state == NULL) { + return; + } + + canvas_clear(canvas); + + draw_ui(state, canvas); + + if(isResultVisible(state->app_state, state->dice_index)) { + draw_results(state, canvas); + } else { + draw_dice(state, canvas); + } + + release_mutex((ValueMutex*)ctx, state); +} + +static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + AppEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void timer_callback(FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + AppEvent event = {.type = EventTypeTick}; + furi_message_queue_put(event_queue, &event, 0); +} + +int32_t dice_dnd_app(void* p) { + UNUSED(p); + + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(AppEvent)); + + FURI_LOG_E(TAG, ">>> Started...\r\n"); + State* state = malloc(sizeof(State)); + init(state); + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, state, sizeof(State))) { + FURI_LOG_E(TAG, "cannot create mutex\r\n"); + free(state); + return 255; + } + + // Set callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, draw_callback, &state_mutex); + view_port_input_callback_set(view_port, input_callback, event_queue); + + FuriTimer* timer = furi_timer_alloc(timer_callback, FuriTimerTypePeriodic, event_queue); + furi_timer_start(timer, furi_kernel_get_tick_frequency() * 0.2); + + // Create GUI, register view port + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + AppEvent event; + for(bool processing = true; processing;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + State* state = (State*)acquire_mutex_block(&state_mutex); + + if(event_status == FuriStatusOk) { + // timer evetn + if(event.type == EventTypeTick) { + update(state); + } + // button events + if(event.type == EventTypeKey) { + if(event.input.type == InputTypePress) { + // dice type + if(isDiceButtonsVisible(state->app_state)) { + if(event.input.key == InputKeyRight) { + if(state->dice_index < DICE_TYPES - 1) { + state->dice_index += 1; + state->app_state = SwipeLeftState; + } + } else if(event.input.key == InputKeyLeft) { + if(state->dice_index > 0) { + state->dice_index -= 1; + state->app_state = SwipeRightState; + } + } + + if(isOneDice(state->dice_index)) state->dice_count = 1; + } + // dice count + if(isDiceSettingsDisabled(state->app_state, state->dice_index) == false && + isAnimState(state->app_state) == false) { + if(event.input.key == InputKeyUp) { + if(state->dice_index != 0) { + state->dice_count += 1; + if(state->dice_count > MAX_DICE_COUNT) { + state->dice_count = MAX_DICE_COUNT; + } + } + } else if(event.input.key == InputKeyDown) { + state->dice_count -= 1; + if(state->dice_count < 1) { + state->dice_count = 1; + } + } + } + // roll + if(event.input.key == InputKeyOk && isAnimState(state->app_state) == false) { + roll(state); + } + // back to dice select state or quit from app + if(event.input.key == InputKeyBack) { + if(state->app_state == ResultState || + state->app_state == AnimResultState) { + state->anim_frame = 0; + state->app_state = SelectState; + } else { + processing = false; + } + } + } + } + } else { + FURI_LOG_D(TAG, "osMessageQueue: event timeout"); + } + + view_port_update(view_port); + release_mutex(&state_mutex, state); + } + + // Clear + free(state); + furi_timer_free(timer); + furi_message_queue_free(event_queue); + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close(RECORD_GUI); + view_port_free(view_port); + delete_mutex(&state_mutex); + + return 0; +} \ No newline at end of file diff --git a/applications/plugins/dice2/icon.png b/applications/plugins/dice2/icon.png new file mode 100644 index 000000000..840088565 Binary files /dev/null and b/applications/plugins/dice2/icon.png differ diff --git a/applications/plugins/dice2/sources/coin.pixil b/applications/plugins/dice2/sources/coin.pixil new file mode 100644 index 000000000..838aeafc0 --- /dev/null +++ b/applications/plugins/dice2/sources/coin.pixil @@ -0,0 +1 @@ +{"application":"pixil","version":"2.6.1","website":"pixilart.com","author":"https://www.pixilart.com","contact":"support@pixilart.com","width":"35","height":"35","colors":{"default":["000000","ffffff","f44336","E91E63","9C27B0","673AB7","3F51B5","2196F3","03A9F4","00BCD4","009688","4CAF50","8BC34A","CDDC39","FFEB3B","FFC107","FF9800","FF5722","795548","9E9E9E","607D8B","ffebee","ffcdd2","ef9a9a","e57373","ef5350","e53935","d32f2f","c62828","b71c1c","ff8a80","ff5252","ff1744","d50000","fce4ec","f8bbd0","f48fb1","f06292","ec407a","e91e63","d81b60","c2185b","ad1457","880e4f","ff80ab","ff4081","f50057","c51162","f3e5f5","e1bee7","ce93d8","ba68c8","ab47bc","9c27b0","8e24aa","7b1fa2","6a1b9a","4a148c","ea80fc","e040fb","d500f9","aa00ff","ede7f6","d1c4e9","b39ddb","9575cd","7e57c2","673ab7","5e35b1","512da8","4527a0","311b92","b388ff","7c4dff","651fff","6200ea","e8eaf6","c5cae9","9fa8da","7986cb","5c6bc0","3f51b5","3949ab","303f9f","283593","1a237e","8c9eff","536dfe","3d5afe","304ffe","e3f2fd","bbdefb","90caf9","64b5f6","42a5f5","2196f3","1e88e5","1976d2","1565c0","0d47a1","82b1ff","448aff","2979ff","2962ff","e1f5fe","b3e5fc","81d4fa","4fc3f7","29b6f6","03a9f4","039be5","0288d1","0277bd","01579b","80d8ff","40c4ff","00b0ff","0091ea","e0f7fa","b2ebf2","80deea","4dd0e1","26c6da","00bcd4","00acc1","0097a7","00838f","006064","84ffff","18ffff","00e5ff","00b8d4","e0f2f1","b2dfdb","80cbc4","4db6ac","26a69a","009688","00897b","00796b","00695c","004d40","a7ffeb","64ffda","1de9b6","00bfa5","e8f5e9","c8e6c9","a5d6a7","81c784","66bb6a","4caf50","43a047","388e3c","2e7d32","1b5e20","b9f6ca","69f0ae","00e676","00c853","f1f8e9","dcedc8","c5e1a5","aed581","9ccc65","8bc34a","7cb342","689f38","558b2f","33691e","ccff90","b2ff59","76ff03","64dd17","f9fbe7","f0f4c3","e6ee9c","dce775","d4e157","cddc39","c0ca33","afb42b","9e9d24","827717","f4ff81","eeff41","c6ff00","aeea00","fffde7","fff9c4","fff59d","fff176","ffee58","ffeb3b","fdd835","fbc02d","f9a825","f57f17","ffff8d","ffff00","ffea00","ffd600","fff8e1","ffecb3","ffe082","ffd54f","ffca28","ffc107","ffb300","ffa000","ff8f00","ff6f00","ffe57f","ffd740","ffc400","ffab00","fff3e0","ffe0b2","ffcc80","ffb74d","ffa726","ff9800","fb8c00","f57c00","ef6c00","e65100","ffd180","ffab40","ff9100","ff6d00","fbe9e7","ffccbc","ffab91","ff8a65","ff7043","ff5722","f4511e","e64a19","d84315","bf360c","ff9e80","ff6e40","ff3d00","dd2c00","efebe9","d7ccc8","bcaaa4","a1887f","8d6e63","795548","6d4c41","5d4037","4e342e","3e2723","fafafa","f5f5f5","eeeeee","e0e0e0","bdbdbd","9e9e9e","757575","616161","424242","212121","eceff1","cfd8dc","b0bec5","90a4ae","78909c","607d8b","546e7a","455a64","37474f","263238"],"simple":["ffffff","d4d4d4","a1a1a1","787878","545454","303030","000000","edc5c5","e68383","ff0000","de2424","ad3636","823737","592b2b","f5d2ee","eb8dd7","f700b9","bf1f97","9c277f","732761","4f2445","e2bcf7","bf79e8","9d00ff","8330ba","6d3096","502c69","351b47","c5c3f0","736feb","0905f7","2e2eb0","2d2d80","252554","090936","c7e2ed","6ac3e6","00bbff","279ac4","347c96","2d5b6b","103947","bbf0d9","6febb3","00ff88","2eb878","349166","2b694c","0c3d25","c2edc0","76ed70","0dff00","36c72c","408c3b","315c2e","144511","d6edbb","b5eb73","8cff00","89c93a","6f8f44","4b632a","2a400c","f1f2bf","eef069","ffff00","baba30","91913f","5e5e2b","3b3b09","ffdeb8","f2ae61","ff8400","c48037","85623d","573e25","3d2309","fcbbae","ff8066","ff2b00","cc553d","9c5b4e","61372e","36130b"],"common":["000000","FFFFFF","7F7F7F","a1a1a1","C3C3C3","c40424","880015","B97A57","dba88c","ED1C24","f75b63","f26f9b","FF7F27","f7ab79","FFC90E","FFF200","cfc532","EFE4B0","1ee656","0c6624","22B14C","B5E61D","5487ff","00A2E8","99D9EA","3F48CC","7f86e3","7092BE","720899","cd55cf","A349A4","C8BFE7","ffffff"],"skin tones":["ffe0bd","ffdbac","ffcd94","eac086","e0ac69","f1c27d","ffad60","c68642","8d5524","896347","765339","613D24","4C2D17","391E0B","351606","2D1304","180A01","090300"]},"frames":[{"name":"","speed":100,"layers":[{"id":0,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAFlJREFUWEft1MEJACAMBEGv/6IvFeRxhoDC+hYMm0HZ9nnkiGGaTVCmI0oZyqTfF2Ywg5m0AGbSYvwzYzOS1iuuP5C4YZixmST37V3WxJpSO5jBDGbSAl+YKXbCj5ghLqGvAAAAAElFTkSuQmCC","edit":false,"name":"Layer 1","opacity":"1","active":true,"unqid":"zdzpbp","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":1,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAf5JREFUWEftVkFywjAMVAZooP//apukScYdq7ariJXsAAcO4UICtrxerVbq6I0+3RthoQOMlY1nmAkNKd4Vf9fidLgEkffH36znjDn+/0lE369gJoPYHBpCCF3XUQiBuvhA8fH/WYGMOEwCWpiRIO4Y2AmGwVqAamDyRvTdIJmyRIJ4CAxipOhlnucqmMvlwiBOpxOt65pTpNO9QYyChmEY6Ha76QD8Po5juF6v+baterRYdsGgkpXptKrJY0qKXq8rsZFm+LBYEcuyUKKaf0pR9B5LA1D4XGqRXi48/phgSoBcJeIaFvA9RcCHW7HRLbNnuJ5glChKYWYV+pNkx6K8UJ9u8SgrmdicHdcYkTBdYxItAbKq1IlaRAGY9FKk8UowUuQ6xUjkstT/NC1uYtm+vIlZll5dO/qSXtUExhKlpruC564nucxI1VsjgU5FreIkQJ0q7ch37XyTqth/zuczNKgaDeB/dMGNkdZKlgNM0xT6vvd6Uc34aJ7nkBunMYjBQScC+CGivrGM9UDlkSbZ+UqTn9ubpC48n/D0oAGVdKzryiOFdF74oiIsRBR36Y7L744zIyCWhWzW1nKNOnWt1LUvofEBnlsDA11VsIJmHwYjhnKUdqirFjCWA7fs1Sx+pOJ4GowWbKvVtIDmWM0LW09+Zt0BxmLvYMZi5hdntuIk2wS1xwAAAABJRU5ErkJggg==","edit":false,"name":"Layer 2 Copy","opacity":"1","active":true,"unqid":"","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":2,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAmlJREFUWEelWOt6qDAIs+//0O5rV1hIA9Rz9mdOsYQQLm48//bzXr42Lu2W2RfjAOB9azxjHEe3vlqD53ncKwMQDleEjV3qswOzgODhCoA9r56tNPyxJf1WYAIQdETgjjPebaDeqQBlYCSQTieZWA2AYDD4V2CugIxMMBuRsWMAbwBJMFUE00kHxAAIWxWoY2AwboyRoPMEjKrzwbb7b6841s8BBlmBa7cjB6razNYAhnetqoj9ZSPBcH73AYrFdQ/1IVK4Hs/7XGX7vuNAByFFXfnuZhjAFOmcZzsYZAdTFcB8SNE6HIVKTc2jBZ0wcG+mG1AYIEZnaOlJtAGMpUpVWQXGunsLxmg1B5TvtD9R47OKYqbm+1y5mhkUXAYm6SHmxH+DnevGxDwZQREfmgGD0CfUvGHdUFN2jeyADp1dgdlUc9PCxtZNe88WMwNpvEsTCMtzyxpCbVQjgsBgb8O0/RYjIk1KW4nPq/rrtlj5uAGD7PCWkI2JbJvwZm16gf6UM2OpglFw9BZuesnYWOPCRAzV0zc9c0DIefh5Y8SZA++mS5uBEatsZIbYCA6RBSjVLB1c1nbWZIjP9eme7jOQSw84afu8y1Q6yvbqeoXgbU98fvDED4HdrJ3VCrEChx7DAuNPFtTR/wDx1iEHXqF4zzeWJDdALALQ2rokzTiQcAEHhi9IrCqhIwUuiJqrBgPdhk5INWPCfoOVpiIndgIg/piz/YVLsQSDFPNnUvdBp+zVVEdAN9PXGRIUZ33G72OTyxgJvaM5UWqIZ0s2ACuNfEkT26bCroDBIW0WWgPB2u1/rb6wv2x/ACDUeEL6Qq7OAAAAAElFTkSuQmCC","edit":false,"name":"Background","opacity":"1","active":false,"unqid":"u6oba5","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":3,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAfZJREFUWEftVl1vgzAMNCoM2P//qxtQiDw5JZkxdj5YH6qpeULCdu7OziUNvNBqXggL/AswWKFoMeHiQACQAEpyeQ7FfwDA3SJypSAgIjZNQ7mmQogIewztHeKS++XAaEUkW4uozM3mpcBsAHADiEN+KM7U4cw5sFCb8vg+pkoWGI1VKlb7x2vEb+cc3m7E0a9DnlWEhqzfEyQzzt7/I5V85cccycXz+fcXAHxyQEXJy7Jg3/cyVgNpzQWu6wpd14WhV1uobRDk8wXati1hnLMdbW74afQ4LLap1oSBtYiUDDGvEfeypvwKGHmqZO0U8cfIMRpy+muY17ZJKlMMJmdWORUD0OzclChjmZbqFQmJqsCoQ7U7be3GZ7M5epG3Jna/maeJLkIKtE4bB5ZroSZUNErmkcnNvKsKQ9VaGj1JsQnNiX28Vdu8U0idbduCa/Jjm3NtUw1f5Lddp9anLjjNvFJeYinB2629fyIG8yaepgnGceRtiEzmecZhGLida2rk3P1kCRYY2ZaQGJnRvZVb4WKkJ4NzThI77Z0Cox31E6gcoP0/v61Nk8yBsRSKQMPJ4L6hecj+DpZPiAOXEjAhQX39VYLRvCsCqgEjQclToj2YpCnSy+7bau0VMBqop9T/C5jC2S0Pe4N5Sk/LBb8W+W6TpdsPAVbkJHCu0MYAAAAASUVORK5CYII=","edit":false,"name":"Layer 2","opacity":"1","active":false,"unqid":"","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":4,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAKFJREFUWEftVVsKwCAMa+9/6I4Oiw/cap2Cg+zHQWuaJXEyHfTwQVwIZJ7cgDJQJnpSkRlkBpmJKjCSGVkFOonD5dEWkcyHuaq5+Lo5sqcclkb1yViRiJSdEq7WNJiMvPa/vVu9Xe0Lt5BJytzES8KfyCjjiOSuh50G16YZ0IV7YiFdOLgLhYty5Ke32wUXHzbBJjckTQMyg8wgM1EFfpGZCziRWCSDveGOAAAAAElFTkSuQmCC","edit":false,"name":"Layer 4","opacity":"1","active":false,"unqid":"","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":5,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAfhJREFUWEftl91uwjAMhR21HbC9/6sOOkCZ0tWVe+q/VprEBVyh4iZfju3jUOiFPuWFWOgNY2XjiDI1kdoj64Zp+iSibyKSALxRe2Z9Z95dUFEwQ6w2rbXWUqZHhb/XWqn8PUTIKS6hphvEi24U2AnDgCGUR4wwmVqRAkiVtINtxLJgZHpq13X0fD6nk93v9xBqGAYPylRIg9EUwTgPqNxut3o+n2V6GKBer1e6XC4qUGYTWbyySxDIiuN3tAOs9ldhWmdM6HPLQIdkGgMBpn1aivu+b+uqrS9hFnJoU5RUdpd2atd7vLU3MIZfyE2t1KLHtHdU8HmPjToIs5gYG5pIl6XIZg3Io/fe6jcsOu10Vo1EIwLfW9nF7Morh49gcBxoYNaM8mKlfSw1mYGJ5koW5uWUwUZQ0yQrXys4z1v2KINtrxYwwyy2DWPfAsTnZlxr58fjQfPcUie55hkr+uaawzBYXRZafPOacRzpdDrhGhtwDabd7L7mvIQnb3cbGB2eFfB+IxF94KXLmtrTlaHrOlXOLKigwrpKTW11wDmbowrs4JplcF22i1Gvyed5iOWw6qlABW0qhwYaGRp2GYn7r1UbljrRIcJbeyuyH+OvSuQ98vfMoUMYa9h5IFqKMvG7YVKLHg1KyXd08b3vvWHMNtwr5X/G/wLO/uskz2jgOQAAAABJRU5ErkJggg==","edit":false,"name":"Layer 2 Copy Copy","opacity":"1","active":false,"unqid":"","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":6,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAkBJREFUWEetl1t2wyAMRO39L9o9pkgZDSMJ2vQrTQRcRk/u629/z+aye9NumJ0YB4DnqXnue9m6Pas1uK7LT2UAceC4YWOXntnBDBDcXAHY79Vvww0fteS5FUwAwYMIbtnjmQZqTQWUwUiQBKKNJQMQCobzFUwLMiW3ta/98tnUsWzaAZIwzQ3w8J3MZViPwQnoDAzjquBN7o+jx4Xh/xLmNZ62A2j+7xnH8bPAoCrw+bXDDXGdKjit4qT+sJcwiX+34ysrppxlU2XnWG5oBl36QjEc8kNQZwq+seIqvhcGmN/l4HS3NUPKmrEZxg8GDMRSCHBa40B0hoahwPXMnJUYYQKLSm+xJkuQGmZu7rckV3AWyVqTXMBsGWx1E/jSM8g2RbcJl6kiyK4dW2GaYxAvMVPBFPXlWBmrOS3M9EFQpim1xzCWjVswnHbfDGCMR6jKZ6mdqGO1Q8bMEukwXnAVPqozGzAMtizBKrwNgxNe0xjb3oSZx80SJkDtJjNQjTILZCr1WR/zzi1G2QhDamCr93j48ghhGbtUYCtIPPyElmBFq0n1qlWo/esRgqc98fz499hZjRBBGdFV+cmCMKiEjxQu6YwGHty6Sc/bUDFAo8zpMwWyxGMPv2NVxm/C9+EFiQrxAbb25BFHlT0wZO+mRSHMNJZfVNnwFT/m+FXAGaKSY1EopMjhw79SZAcGvDCeJzyzbmU3ztSZIkcwGBsIlTxrQh3ZUeQExlkUlAV1BgbyVfE5zFqDKtu2/HRwxg9BIkRCZQ/enQAAAABJRU5ErkJggg==","edit":false,"name":"Background Copy","opacity":"1","active":false,"unqid":"u6oba5","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":7,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAfpJREFUWEftV9tugzAMdQQM2P7/VwcUUKakJHKMHZuqk3hon6piHx8f36iDG33cjbjAh4xUjavK+AtlvYqtlukLAB4AgElYglDSFh+VTBAiAUdA7713rortDptkZE6khsqBFMSEkknBVV+JDOcYfsP2XP9IqmCVdgBouUQ4MjHIvu/QNE14rmZkaGqciIhHyQTDXwD4OQJwahQ+Qg9hJbjvCwCE4SiwODKF87quvuu6pJCoJAI+JbAsC/R9TzGoHdsDXOCTI1IufKW9dErSew/btkHXdcn+VC4ORApMW0PaJRb/ZFPY0l3ASUkz5/qVNqhWTkwm41vIWLanhUxaoDjholQWMrdRxrBCoolVmYRXLVMGRHsjbtMoy/MWWbc118DSvmIbOB/EEN89o6sLT9jOml/MER3bnCR7awghrIrlPnEq5jVQwT5JH52O6iSFaE9Y++j0+hG2edu2rCrVDFBEOnFUqWRas8PEcDLV2yTtAu6GSQq5eZ79MAz44ucTME0TjOPIDoRlQnzTNPGVIkxUkFqr03F/sGLsKFOc2nal90MlQcDppq1d/uh6hYy4i8KYCivBpAhtOkl5euYjOF2MChkt6RxbO4Lfx5sfLlHx8nUg0XNgTbYQQSPDKWbpnVdwTf+btOF52/OXMnhbdGb8/gv7Mu5HGUmyP5KE6CQNV8vwAAAAAElFTkSuQmCC","edit":false,"name":"Layer 2 Copy","opacity":"1","active":false,"unqid":"","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}}],"active":true,"selectedLayer":1,"unqid":"rs6rne","preview":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAdFJREFUWEftV9uqg0AMXKko6v//quL9ECESY25r+1AO7UuL7mYnM5NkW+z7vqcv+RQ/MIoS/4OZoihcp+XaMZsZCgIPg2fab0QM7/u+T23bqkmEwSCICAALmMWWC4aCsBiIgqQxOEUmGNwofbuGIQsAKI8h7VfBSIxQv8zz7OIpy/IA8Xq90rquh6+43DSICAY2DMOQmqa5BICNEHAcx1TX9RlYQsW9obFsgpFKlgbWqsmiifqJr7vE5uOA0rgsSwKq4aPRqxlSMz6CkeJdZJLKl/aJm/tJf9GYofKgzDS5yzPKjJfNRV8BiCQhHhzpPTdmaBlaPrD6hceg2q+QmU+ywqWVgEvyncy8C0bzAZeJGpj3nTAYr+RzJX3EjGZKnqHXjrlUJhhKp2YwLgUty3fAnJVmlTbMH5wvOQdLwKQEb31N6sC8J0zTlKqqUmdR5EaH3dxkXQIDB8MwtCqBlm+059B1XdcdNz9zNklzyDss8h5l3rbtuFJIsqv3GfAK3kGsQfnEuJr/3Jse3+iVOi15bzDexob3j9I6XGqEfBTksOpeyHmm0mGaVDwRrEp1vceM1jM8r+SAPhN+AiYKJHddWKbcwE/W/8B81MBPJIjs+QPyMEG2lHD1VgAAAABJRU5ErkJggg==","width":"35","height":"35"}],"currentFrame":0,"name":"Untitled","preview":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMA/sfR5H8Fkddasdmnacvx//8745jkhasdASD945kjknhj/AAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAdFJREFUWEftV9uqg0AMXKko6v//quL9ECESY25r+1AO7UuL7mYnM5NkW+z7vqcv+RQ/MIoS/4OZoihcp+XaMZsZCgIPg2fab0QM7/u+T23bqkmEwSCICAALmMWWC4aCsBiIgqQxOEUmGNwofbuGIQsAKI8h7VfBSIxQv8zz7OIpy/IA8Xq90rquh6+43DSICAY2DMOQmqa5BICNEHAcx1TX9RlYQsW9obFsgpFKlgbWqsmiifqJr7vE5uOA0rgsSwKq4aPRqxlSMz6CkeJdZJLKl/aJm/tJf9GYofKgzDS5yzPKjJfNRV8BiCQhHhzpPTdmaBlaPrD6hceg2q+QmU+ywqWVgEvyncy8C0bzAZeJGpj3nTAYr+RzJX3EjGZKnqHXjrlUJhhKp2YwLgUty3fAnJVmlTbMH5wvOQdLwKQEb31N6sC8J0zTlKqqUmdR5EaH3dxkXQIDB8MwtCqBlm+059B1XdcdNz9zNklzyDss8h5l3rbtuFJIsqv3GfAK3kGsQfnEuJr/3Jse3+iVOi15bzDexob3j9I6XGqEfBTksOpeyHmm0mGaVDwRrEp1vceM1jM8r+SAPhN+AiYKJHddWKbcwE/W/8B81MBPJIjs+QPyMEG2lHD1VgAAAABJRU5ErkJggg==","palette_id":false} \ No newline at end of file diff --git a/applications/plugins/dice2/sources/d10.pixil b/applications/plugins/dice2/sources/d10.pixil new file mode 100644 index 000000000..3356c453c --- /dev/null +++ b/applications/plugins/dice2/sources/d10.pixil @@ -0,0 +1 @@ +{"application":"pixil","version":"2.6.1","website":"pixilart.com","author":"https://www.pixilart.com","contact":"support@pixilart.com","width":"35","height":"35","colors":{"default":["000000","ffffff","f44336","E91E63","9C27B0","673AB7","3F51B5","2196F3","03A9F4","00BCD4","009688","4CAF50","8BC34A","CDDC39","FFEB3B","FFC107","FF9800","FF5722","795548","9E9E9E","607D8B","ffebee","ffcdd2","ef9a9a","e57373","ef5350","e53935","d32f2f","c62828","b71c1c","ff8a80","ff5252","ff1744","d50000","fce4ec","f8bbd0","f48fb1","f06292","ec407a","e91e63","d81b60","c2185b","ad1457","880e4f","ff80ab","ff4081","f50057","c51162","f3e5f5","e1bee7","ce93d8","ba68c8","ab47bc","9c27b0","8e24aa","7b1fa2","6a1b9a","4a148c","ea80fc","e040fb","d500f9","aa00ff","ede7f6","d1c4e9","b39ddb","9575cd","7e57c2","673ab7","5e35b1","512da8","4527a0","311b92","b388ff","7c4dff","651fff","6200ea","e8eaf6","c5cae9","9fa8da","7986cb","5c6bc0","3f51b5","3949ab","303f9f","283593","1a237e","8c9eff","536dfe","3d5afe","304ffe","e3f2fd","bbdefb","90caf9","64b5f6","42a5f5","2196f3","1e88e5","1976d2","1565c0","0d47a1","82b1ff","448aff","2979ff","2962ff","e1f5fe","b3e5fc","81d4fa","4fc3f7","29b6f6","03a9f4","039be5","0288d1","0277bd","01579b","80d8ff","40c4ff","00b0ff","0091ea","e0f7fa","b2ebf2","80deea","4dd0e1","26c6da","00bcd4","00acc1","0097a7","00838f","006064","84ffff","18ffff","00e5ff","00b8d4","e0f2f1","b2dfdb","80cbc4","4db6ac","26a69a","009688","00897b","00796b","00695c","004d40","a7ffeb","64ffda","1de9b6","00bfa5","e8f5e9","c8e6c9","a5d6a7","81c784","66bb6a","4caf50","43a047","388e3c","2e7d32","1b5e20","b9f6ca","69f0ae","00e676","00c853","f1f8e9","dcedc8","c5e1a5","aed581","9ccc65","8bc34a","7cb342","689f38","558b2f","33691e","ccff90","b2ff59","76ff03","64dd17","f9fbe7","f0f4c3","e6ee9c","dce775","d4e157","cddc39","c0ca33","afb42b","9e9d24","827717","f4ff81","eeff41","c6ff00","aeea00","fffde7","fff9c4","fff59d","fff176","ffee58","ffeb3b","fdd835","fbc02d","f9a825","f57f17","ffff8d","ffff00","ffea00","ffd600","fff8e1","ffecb3","ffe082","ffd54f","ffca28","ffc107","ffb300","ffa000","ff8f00","ff6f00","ffe57f","ffd740","ffc400","ffab00","fff3e0","ffe0b2","ffcc80","ffb74d","ffa726","ff9800","fb8c00","f57c00","ef6c00","e65100","ffd180","ffab40","ff9100","ff6d00","fbe9e7","ffccbc","ffab91","ff8a65","ff7043","ff5722","f4511e","e64a19","d84315","bf360c","ff9e80","ff6e40","ff3d00","dd2c00","efebe9","d7ccc8","bcaaa4","a1887f","8d6e63","795548","6d4c41","5d4037","4e342e","3e2723","fafafa","f5f5f5","eeeeee","e0e0e0","bdbdbd","9e9e9e","757575","616161","424242","212121","eceff1","cfd8dc","b0bec5","90a4ae","78909c","607d8b","546e7a","455a64","37474f","263238"],"simple":["ffffff","d4d4d4","a1a1a1","787878","545454","303030","000000","edc5c5","e68383","ff0000","de2424","ad3636","823737","592b2b","f5d2ee","eb8dd7","f700b9","bf1f97","9c277f","732761","4f2445","e2bcf7","bf79e8","9d00ff","8330ba","6d3096","502c69","351b47","c5c3f0","736feb","0905f7","2e2eb0","2d2d80","252554","090936","c7e2ed","6ac3e6","00bbff","279ac4","347c96","2d5b6b","103947","bbf0d9","6febb3","00ff88","2eb878","349166","2b694c","0c3d25","c2edc0","76ed70","0dff00","36c72c","408c3b","315c2e","144511","d6edbb","b5eb73","8cff00","89c93a","6f8f44","4b632a","2a400c","f1f2bf","eef069","ffff00","baba30","91913f","5e5e2b","3b3b09","ffdeb8","f2ae61","ff8400","c48037","85623d","573e25","3d2309","fcbbae","ff8066","ff2b00","cc553d","9c5b4e","61372e","36130b"],"common":["000000","FFFFFF","7F7F7F","a1a1a1","C3C3C3","c40424","880015","B97A57","dba88c","ED1C24","f75b63","f26f9b","FF7F27","f7ab79","FFC90E","FFF200","cfc532","EFE4B0","1ee656","0c6624","22B14C","B5E61D","5487ff","00A2E8","99D9EA","3F48CC","7f86e3","7092BE","720899","cd55cf","A349A4","C8BFE7","ffffff"],"skin tones":["ffe0bd","ffdbac","ffcd94","eac086","e0ac69","f1c27d","ffad60","c68642","8d5524","896347","765339","613D24","4C2D17","391E0B","351606","2D1304","180A01","090300"]},"frames":[{"name":"","speed":100,"layers":[{"id":0,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAEpJREFUWEft1MENACAIADHZf2kn8HE/TOoE5KjMWfRm0SzHMK9tKKNM/anMMMNMLcBMLebOMMNMLcBMLebOMMNMLcBMLebOfGHmAm5UACTjh/FnAAAAAElFTkSuQmCC","edit":false,"name":"Background","opacity":"1","active":true,"unqid":"vbd6q","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":1,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAhVJREFUWEftV9GOwjAMW7f//+R2p+Sa4npJ2wEPCIF04hgjcRzHzdJ5nuf2Ia/0AxN04ruZSSlNFRjJNGRmJWiU1UuG8W6BWfmhAZF7Jbi9y/X0H0Bng4pq171CXGY8Viiw9UJsQRPQuyKxOMbErMgLGPtBznk7jqMrYJV+LylcC9lxwQgQezEgRDcCZ9+VUqQoZTLnfO773rUU44VgBISBGgHi3iNA0w7co/lQX1Mw9Qalk9sVTkIdadCHaUk1bbq6DQYB4P+SyP4kAzDWKpbrVezeRD3HjAUspWifvYnw9EMj3m7B8V9uU/WJ5h0iQANDE+HI5eErkhzF+1SbDIy1xwGDHtO8r7ZH1xH2FwKFt3YFhabH4w2m18A4Rtg2EgRUwWhi/A0PwxCMeQIFYWbQgbuqPQdG7S2D8aqowdn+sVUXZiKT9EQ8PLWZUgCjWKt/UL5exA0prBZ8sLZ7orUTT2NkSbQk7atC7VoWgG9aQXFbzKEDYyVecAODVs8epJ9L2VI9k0xI7EFLmuEKIgGHq8EDCD55XKZwGYxH6Wg18E5p2nM0JC9d3cE6elTh5NGy5DDUnVW8gEXjPXw6GG1mEbDq3O6J/1ZmeEw9Rmjk2/hjB6IibzGzopluVFPq4ke79dRnPLPyvIGSdwef9yFazlTds2ftSMRR1lGyGdJbYCzYKwlHgKZgZtW88/sfmIjNj2LmD0cIcLZmv4arAAAAAElFTkSuQmCC","edit":false,"name":"Layer 1","opacity":"1","active":false,"unqid":"syuab","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":2,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAjJJREFUWEfVl9u2gyAMRKX+/yerXaEMaxgSQHseevpSeyHsJDNB0/ZDr/RDLNv/gbmu64oql1L680SGAQ3G23PAmNmfgt6CMYhZQWagI9gQBi3SzUebzUANxFuPSg5hOLhWBUHpPznWRGedBEvcvPYWDCIBwAHizZrYDOkk+Rym9D1vrEDHcWz7vgPE3GjXcGU6juN6vV5Ve48rE1VEgCoItRZATSst3hTGE68G1org83meTWVEaxmKv1uC8VxUvmtaoLMoQ53nlj6t4qHZVYwSijWDDZwMNtNE6XktvVZSgKCtPEC5olyV0E0rMDS8cvZe29Q1LHys4WndWZuz9CpD46C6tQTkz43TAohGvG5lWAMDGLUsYnVAPHh4+qqThjCkjZqlI+AKEVSsm0W3NIPKGAxe+76jpJxAN9DK/zvrFrtrUuttKpN0s0DmHpkzWX/UAm/QsairNtkceoK75wf1cwSjh15UkeY4EKc2+3cwPPLtmisDV8j5U1z6KZTMkgqCysr7HIbFi3ZhLlgwa5vnEk2EDskIag3GRGvB7JQt193sYKDgEE1yXCjUOgwcxTAqFEc4tV3lN96w6kqPgm7ORLaOAIJbyG628E3cIwGzVgAz2hyagoh1DYwguhq3SYNp0OCmu7ldiO51HF3FMPCod9qOtMJ25mTkxqqG8PTSaYYHxspjxxMtoZ3eg577dDB63Ji5aSWJ5crMNhv9fieJ5cp8A/TN2vAh7pugT9f+FMwbzj9NQl0XLG4AAAAASUVORK5CYII=","edit":false,"name":"Layer 2","opacity":"1","active":false,"unqid":"n3umw","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":3,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAj5JREFUWEfNVwGOwyAMK93/n8yYwhHOMQnQ3k7qpGnayoLjOCak40Gv9CAsx/PBlFLKLmMppa8l5AYSMDt7XMA85OYlsQXG23QHbMSuxmNAKzDyvJdMgjQQSUsp3/X3FVOYQPuP2X8AwyWCLGqiFESAKmD8rEth/UCSx84MTGeFAekm8snZ8q5czpzz8Xq9vMTG1lZmBMD7/ZY/VsA553KeZw2iG1zVkgDRlwDiUoXMwEJs864VZmVlBQpEQCg7d8GgkI14sWSzEkF5TPmxo7aZ4U7CUrWAlUFlQJ7rW39XrdxlpretZo1i9oSN7OBz0d95npVVJ5EfLTK1KGDI2IiYgrlxuLUbmMGTwjKhkSHQWSdBMCP0lmQNKeAVTFSiISOHwhpTAkhbc0urGzsGp4x3MKu2DsHMdEE66ZuCE2PcDka7rnnX4DFLMJGXOIC4RP2YgC40zbA8m0i8XdvotBviVSCNjH64TsU71QyamXPa4tmioGvJtAmoHGa9x8qyTDAedAsoOZcjFnONKYD+BMabT6C97flEgBwWh25iM5xOekFbmzOptfDyFI/KSqAHw/2lv9l00Cne4daHJ+y6YBgzzRAN8QMYRc+GRoddElfGIckD1JUNF4hIvEbA0bjZNmFzq5jR6DxGvgZGA5HHmOGKN4vG09UgbrwB/QFVv3Mdmd0I+P/eEG72wy/Ufu6tUsRHp3sUwp2FZjfQ21fT1RXYY3Um3sGBwxQvPpgB/RdmLuLbWn67TFvRLy56FJgPoS1QQmVa3RoAAAAASUVORK5CYII=","edit":false,"name":"Layer 3","opacity":"1","active":false,"unqid":"syuab","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":4,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAiVJREFUWEfdl9uOwyAMRHP5/09OUxkxaBhsQ6OtVO0+dUuCj8djQ/fth/72H2LZ/j/Mfd93pPi+76EAj5XJAhqIF9MYH8HMgkUBWZEavH21DOMFTxTtAiQl6ZZQvUidViaDiaRdqH/xCAVTNZbUCWHYg04mg0EVRN5/DmMbvV6v7TxPg0VgfC4JXNd1n+dZgmQg6q3MN4MyTl0NpoCQIfFesxpK7IFx+T+CQaZ1g6aMgpgyx3EUZQIQTUBVHMZKpww29hQgE+/3dd3bcXSzhDKGkjWvMluGrvI6qnsKHcVSq6wKImUtILZP9VzxEytnn6NSuTBIY9aqznoBsff/DMabnACM/AEI8tpQJuwbDb+0TNkZQ2vc/uXrOg4GAzsl6+IveWbSMUUBggCQq4yUbw4j7W0DbrM2ViNWAMCoKh2M7YE/G5aeiQdlpL3d1ndmEQO5ypgilpBBLcEgFWm/dhxg0CUKdfOEpzZgMhMPU5BnDUG1ZuKukdZue+Hc4nWFIXXbe1OYSpF2iNd1FtySgYpcnsjEIYxAsOHCw1FrxMdAhSlJRb5ZUYafCS9RCqL/o5vMvE9hOpDZvSUakgCbtXdaJlyy9KoA82lwvt01x8uJDVWWDZxMXPeeK9O3Vcg7aLHonU+DMjxrnKExXKR4SM58M7vxLcGsyD8D4fWlU5skdH+2eEp9AsHPTm96DLMaJPu5urpHM/unL3zzedcz3wyY7f1TMG9hxkxC7RN4kQAAAABJRU5ErkJggg==","edit":false,"name":"Layer 4","opacity":"1","active":true,"unqid":"n3umw","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}}],"active":true,"selectedLayer":4,"unqid":"rbcl7d","preview":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAiVJREFUWEfdl9uOwyAMRHP5/09OUxkxaBhsQ6OtVO0+dUuCj8djQ/fth/72H2LZ/j/Mfd93pPi+76EAj5XJAhqIF9MYH8HMgkUBWZEavH21DOMFTxTtAiQl6ZZQvUidViaDiaRdqH/xCAVTNZbUCWHYg04mg0EVRN5/DmMbvV6v7TxPg0VgfC4JXNd1n+dZgmQg6q3MN4MyTl0NpoCQIfFesxpK7IFx+T+CQaZ1g6aMgpgyx3EUZQIQTUBVHMZKpww29hQgE+/3dd3bcXSzhDKGkjWvMluGrvI6qnsKHcVSq6wKImUtILZP9VzxEytnn6NSuTBIY9aqznoBsff/DMabnACM/AEI8tpQJuwbDb+0TNkZQ2vc/uXrOg4GAzsl6+IveWbSMUUBggCQq4yUbw4j7W0DbrM2ViNWAMCoKh2M7YE/G5aeiQdlpL3d1ndmEQO5ypgilpBBLcEgFWm/dhxg0CUKdfOEpzZgMhMPU5BnDUG1ZuKukdZue+Hc4nWFIXXbe1OYSpF2iNd1FtySgYpcnsjEIYxAsOHCw1FrxMdAhSlJRb5ZUYafCS9RCqL/o5vMvE9hOpDZvSUakgCbtXdaJlyy9KoA82lwvt01x8uJDVWWDZxMXPeeK9O3Vcg7aLHonU+DMjxrnKExXKR4SM58M7vxLcGsyD8D4fWlU5skdH+2eEp9AsHPTm96DLMaJPu5urpHM/unL3zzedcz3wyY7f1TMG9hxkxC7RN4kQAAAABJRU5ErkJggg==","width":"35","height":"35","old_width":"35","old_height":"35"}],"currentFrame":0,"name":"Untitled","preview":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMA/sfR5H8Fkddasdmnacvx//8745jkhasdASD945kjknhj/AAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAn9JREFUWEfNmFGOwyAMRCG5y+b+x0nvQlmZYjQMNmSjrpT+VGkbPDyPbdIYQvgJIbye8B7D5/UIQSLmEUJEx7+QyTnnSnx4izG6AG6TmQUUBZ+Y/Us0xhgPz6MumZzz6e1OP7cC4j01ePuoXq/JWDtdBZMok4wMdPS3XqoaGSHhoZ3kX5EXj0AwpsHXZqoaMiGDYnDHxk6GFLIQuv9SqkwystD7/Q77vssONLD8VgiUDaSUzn3fS5CZEDbzzMQDGVgYU1CEgCH1vtMTopT/YmKXTF2skWEhQmbbtkIGA8M1b4ApDlXVkdGFLAJg4iOndIZt66oFyralUlLMZDStVr/p+oyaGNFzjlkIlWsRIutUzxU/ccq8ftOh4vJelarxfREiu78gZihvl8ykzK0KOrRjg9fkWIKmb5SMIinD2iTj9Qj1DRHB8pef6HV5t9IEvuniX/LMpGKG8q+C2+cshtLXpcr1DC6SUgpSxkbvaP2miugaI5KRNfQlzdIy8UCGyrspJ09glbAgTVXnGSEiGxJRIMYnIybC8q4zSQ9Khza6CSE+SzfPqBglbpl4OM9oeeNNUFld1ZCRW8pTSll3r6RZjGXioSUTGU0zemEoVx6G2mdkE7oRTI9nYpcMVEWZ2tCJ3eEII6ObWXJRxZRyJ980IFfIoMmaMIsGi8FrrSZJn2filWc6Iatzy0rgqrxdMpoaNR4H8ubWLFWQrjZScHqbZCYd1zznwgjotFiCjZHie0aOlLPDOR+ksEnOPGOR5bP1QAYbH+8Cg115jPHEXZra+qTnkeHFcbqvqBhemp9n9E+A1aMrERqK4O6fCV9b6K4AvM/0zDcWvvMH1KPI/AIAOS/9S+9jJwAAAABJRU5ErkJggg==","palette_id":false} \ No newline at end of file diff --git a/applications/plugins/dice2/sources/d100.pixil b/applications/plugins/dice2/sources/d100.pixil new file mode 100644 index 000000000..c4fd81342 --- /dev/null +++ b/applications/plugins/dice2/sources/d100.pixil @@ -0,0 +1 @@ +{"application":"pixil","version":"2.6.1","website":"pixilart.com","author":"https://www.pixilart.com","contact":"support@pixilart.com","width":"35","height":"35","colors":{"default":["000000","ffffff","f44336","E91E63","9C27B0","673AB7","3F51B5","2196F3","03A9F4","00BCD4","009688","4CAF50","8BC34A","CDDC39","FFEB3B","FFC107","FF9800","FF5722","795548","9E9E9E","607D8B","ffebee","ffcdd2","ef9a9a","e57373","ef5350","e53935","d32f2f","c62828","b71c1c","ff8a80","ff5252","ff1744","d50000","fce4ec","f8bbd0","f48fb1","f06292","ec407a","e91e63","d81b60","c2185b","ad1457","880e4f","ff80ab","ff4081","f50057","c51162","f3e5f5","e1bee7","ce93d8","ba68c8","ab47bc","9c27b0","8e24aa","7b1fa2","6a1b9a","4a148c","ea80fc","e040fb","d500f9","aa00ff","ede7f6","d1c4e9","b39ddb","9575cd","7e57c2","673ab7","5e35b1","512da8","4527a0","311b92","b388ff","7c4dff","651fff","6200ea","e8eaf6","c5cae9","9fa8da","7986cb","5c6bc0","3f51b5","3949ab","303f9f","283593","1a237e","8c9eff","536dfe","3d5afe","304ffe","e3f2fd","bbdefb","90caf9","64b5f6","42a5f5","2196f3","1e88e5","1976d2","1565c0","0d47a1","82b1ff","448aff","2979ff","2962ff","e1f5fe","b3e5fc","81d4fa","4fc3f7","29b6f6","03a9f4","039be5","0288d1","0277bd","01579b","80d8ff","40c4ff","00b0ff","0091ea","e0f7fa","b2ebf2","80deea","4dd0e1","26c6da","00bcd4","00acc1","0097a7","00838f","006064","84ffff","18ffff","00e5ff","00b8d4","e0f2f1","b2dfdb","80cbc4","4db6ac","26a69a","009688","00897b","00796b","00695c","004d40","a7ffeb","64ffda","1de9b6","00bfa5","e8f5e9","c8e6c9","a5d6a7","81c784","66bb6a","4caf50","43a047","388e3c","2e7d32","1b5e20","b9f6ca","69f0ae","00e676","00c853","f1f8e9","dcedc8","c5e1a5","aed581","9ccc65","8bc34a","7cb342","689f38","558b2f","33691e","ccff90","b2ff59","76ff03","64dd17","f9fbe7","f0f4c3","e6ee9c","dce775","d4e157","cddc39","c0ca33","afb42b","9e9d24","827717","f4ff81","eeff41","c6ff00","aeea00","fffde7","fff9c4","fff59d","fff176","ffee58","ffeb3b","fdd835","fbc02d","f9a825","f57f17","ffff8d","ffff00","ffea00","ffd600","fff8e1","ffecb3","ffe082","ffd54f","ffca28","ffc107","ffb300","ffa000","ff8f00","ff6f00","ffe57f","ffd740","ffc400","ffab00","fff3e0","ffe0b2","ffcc80","ffb74d","ffa726","ff9800","fb8c00","f57c00","ef6c00","e65100","ffd180","ffab40","ff9100","ff6d00","fbe9e7","ffccbc","ffab91","ff8a65","ff7043","ff5722","f4511e","e64a19","d84315","bf360c","ff9e80","ff6e40","ff3d00","dd2c00","efebe9","d7ccc8","bcaaa4","a1887f","8d6e63","795548","6d4c41","5d4037","4e342e","3e2723","fafafa","f5f5f5","eeeeee","e0e0e0","bdbdbd","9e9e9e","757575","616161","424242","212121","eceff1","cfd8dc","b0bec5","90a4ae","78909c","607d8b","546e7a","455a64","37474f","263238"],"simple":["ffffff","d4d4d4","a1a1a1","787878","545454","303030","000000","edc5c5","e68383","ff0000","de2424","ad3636","823737","592b2b","f5d2ee","eb8dd7","f700b9","bf1f97","9c277f","732761","4f2445","e2bcf7","bf79e8","9d00ff","8330ba","6d3096","502c69","351b47","c5c3f0","736feb","0905f7","2e2eb0","2d2d80","252554","090936","c7e2ed","6ac3e6","00bbff","279ac4","347c96","2d5b6b","103947","bbf0d9","6febb3","00ff88","2eb878","349166","2b694c","0c3d25","c2edc0","76ed70","0dff00","36c72c","408c3b","315c2e","144511","d6edbb","b5eb73","8cff00","89c93a","6f8f44","4b632a","2a400c","f1f2bf","eef069","ffff00","baba30","91913f","5e5e2b","3b3b09","ffdeb8","f2ae61","ff8400","c48037","85623d","573e25","3d2309","fcbbae","ff8066","ff2b00","cc553d","9c5b4e","61372e","36130b"],"common":["000000","FFFFFF","7F7F7F","a1a1a1","C3C3C3","c40424","880015","B97A57","dba88c","ED1C24","f75b63","f26f9b","FF7F27","f7ab79","FFC90E","FFF200","cfc532","EFE4B0","1ee656","0c6624","22B14C","B5E61D","5487ff","00A2E8","99D9EA","3F48CC","7f86e3","7092BE","720899","cd55cf","A349A4","C8BFE7","ffffff"],"skin tones":["ffe0bd","ffdbac","ffcd94","eac086","e0ac69","f1c27d","ffad60","c68642","8d5524","896347","765339","613D24","4C2D17","391E0B","351606","2D1304","180A01","090300"]},"frames":[{"name":"","speed":100,"layers":[{"id":0,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAExJREFUWEft1LERACAMAzGy/9DOBCncUYgJuI/OkyTvkzc+c1xCmYuoMsq088UMM8y0BZhpi9kZZphpCzDTFrMzzDDTFmCmLWZnrmIL7qWLmCXUhAcAAAAASUVORK5CYII=","edit":false,"name":"Background","opacity":"1","active":true,"unqid":"1exvb","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":1,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAArBJREFUWEfNWMF2wjAMa+n/fzJ0zyEyiiKn5W2H9bIBia3IsmLYt3/07L/AclZ7z/Pc9n3/Ova3G0oAfwHsFpjzPE86aOwJUNXfCVcwFc8VW5dgCEhL3kuwPZ/P7TgOAAKAnYFTudq+K0BLMMJICwYwCA4Ur9crGTmOA+sGsFeASjDmhAMYrUUwFU8AYdDu/4ohC6YCIgmx14oaDAI0v64YmsCoRhBMT87MxGePxwOaSGY6A7bRHKABjDLCUUITKEG8zwA0GzqPEvKS1JECUmYa5UoxRWqBHBMCaEro6AnQbJAJRsoTJ2/AiA13omzpfgjnRxOOSj/MzCREMMDObvylectQh89NwK2dgu8xcgvYUTCDsVHJMqgEGkyuR9+DVTDqEqv+BjBVBzkwRPGqtRNQrOfkWiLopnVePw3fNYOA2dJF3G4v31dtuTM9B67lITAAl/V3rfktM647XbdOYLjNIMrFbZ3gsQ9akWSsq9Sk1Y0wY93SeAhKkA0AK8DaLuAGRBOrbyV7WiZ2YXVZYSk1geDuPlo5d3wW10iaHwvYjQycKB3u7SOtY4SJ9hL3WPzPV0gkD8D83nCQThGcc3BZN8aKqBsqLZEBmNdIBYRbmzWghopxseoynfZUd5VhDoNajKTTdVC1c0NOy7Vj4CmrDmA3Nub3iV4MVGXZKq+owMRB3NzMY4Sb9PTCTG9gdpTBYnZJQaNryPUh1/zWMA1XSOjatCqVXhkFO9b8esx3e+pGdl62/qu273py44QttU55Fkxy1wVr6J8GMoCGQJkp3o91DkgJRgERe0MbayItY99X7Zmq4gScuauS8TdLPm23CvfVt8WsGEHCJZgFQww475aCmVtAlmViYbMl6xWh5uU6Lk9+8TPJJTMVqDss3AVxu0yFZ7x/jlg8Vz9/uK0/fGymIjAHw4EAAAAASUVORK5CYII=","edit":false,"name":"Layer 1","opacity":"1","active":false,"unqid":"oo532f","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":2,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAArJJREFUWEe1mFt2wjAMRDHsf8khPVItdTwaOaGn5QuSWL4ePcN4/PJznue5WzrGGJ+a/nhBbGAw3X7G+e8wAsAOwwot1z4Bu6VMA2EiGciA+/E97AYoX5cevIQhEKWEGfbrU4XcCH9HiO3ct4VBEJA7N4XNCiTGd8TWFVALI6R/HMfxeL1eRWKliMsFCRXP7IAkDMdIGDIY+yAQGF9glWpXQAVGuMaCIU+pygu6QX2PmPKIn7aUQguMAkED4B9cl6lN7sIsi6XLNQZiZTxVsXaQay6zCWPljopYh+QJ4xg3QZaA5mAOZSmg81CoDsO4jGH9/X6X7AnA5/OZEFA7cvmmNQXIAmQ2tsrgkQ3CAOYa1SSz6mKGiVLb1qRSto/jOOemWVkDREidfXN+KUAZuT/nLtU64efDeNKl10SZ34Dg4c9ZGEuzjPWdah5rBHPZY6gxIoivBZhopLJiLwvH+K4/QpmlyEGs+OFU48S+1WWg6lWLpJ0yaJBkVYXMIS3Wok1wD0NYTHWuSUoZN2wPmnGEEf5O19jzEfgIw4eBBMkyYkkt3WQXub5wb1LpjQAM0/Qr9xL2KldmN8+CXzNDKEh9fRyAO7tqshPuXtHjngK1sQBhrBj4FYxSNWKqVOAu2BQQ5azHwA0XZx2MhlxgcLLjiZ8Dl9I9U55AVM3CwdyTpGuUS+xw9+Xf2CLMqAKJzfBeY9c9VIarkvuzp4CRbqYplTYusOvx+tyvwkRmIRAZjAO0L26mAicA1quwje7BIConiqEEg5ZH0jQAU4ga2EH9ZTxFRbYwSqE4iZ08qjOPFhwPVKPQrHwX5xl4UQkV2myUryiboM/MUopcKgOx4vLyGCnglmapwHYgJZu6dMC5WL0liiBPUzQ6bD2xvclw/AfR7v8ZXHv3v5qPYNpC8kc3vgArjLsiNA/itgAAAABJRU5ErkJggg==","edit":false,"name":"Layer 2","opacity":"1","active":false,"unqid":"oo532f","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":3,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAqNJREFUWEell9uSgzAMQ6H9/0+m3bEbZ2RFduiWl90Bkhxk+dLz+Of1fr/f3dLzPM9vt/5qAQNU5zHnXbBbMAjBAHZw3AsIfAfBdlBbmADpVLBnCgTDBM/LM1sYBAEFbI0/Cgj468+O4+C/zrUDKmEKReIQ3rxTplqznC1hWBE7GcI019h7qM51Xcfz+XSw4Y9pN1Zx7JnOX2Cq0MTBKl0xhCLjpjKF2SdDgilAlpAEEPkmhUNAn/BB03eokAoTFzNeKOG6bLLwPR6PCDWeGYng95b4h+uHR6TE9M4UoSvKtt/wVGnoREnptxzCxYxM3bYH2ywMzh8ThmeYFJKgicxAr0CGbb0yak+CQaAEw8aqwiCU84+5rkuqYmk+LgdGZWR1Hje9au7SF5RyYRACDnYF4sL7r9fLaxDeS5kJMH4AAkUWEAQaPymCdSRUNAAGtcyyy55FlvlagkH1qv9xzfQch2oA+McxEGycsxVhoIx/OuHaBrAJhkKzVhgQtIMpqCp2DKiUKRthiu1niOtUdT9FeFT45FyEBo4U5B6CqU2jhFtDjAwJlPdDVVJLGUbz7gubzrBiBw5TwrsBw/6ayth6dTjuNVsJ7DK/sFOGaoUqBylEXFsYDqDSBL8ULmpwzi36iwOhNEWbmKFTgGYF1ShTVeUqGl8iMs0z8E6/quYaHiH8C9n9XBewGBaDeqofZcEag3yIsgxX/LMjNuoq6eLeIXgx4yygEqbLrOg3qq8opbphi0I9BVGTXoo9hoyA2lbSFbrqJ4uEIYWShyo/sXKiPHg0KxAs6dJjaqTAiomLzFNx8c+VuN+BbGGUhwJGzbM8gmD13oHcguGQFX2Iu3lS+g7IbRhqGVXZqEIdvy6360oDb1d+uvXPALjBLzA3eL975Q81aKYi/zu6FwAAAABJRU5ErkJggg==","edit":false,"name":"Layer 3","opacity":"1","active":false,"unqid":"oo532f","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":4,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAp5JREFUWEe9l8uWgzAMQ0v7/59M6bEbe2RFDtDFdDPTB/hGVuSwPX58HcdxrC7dtm27e+vLF3DxrhYz3oE6hUEIBrDC+Jl6H+pcgVrCBEhXcN/3x+v1sntkyxjIYEKtM6AWhkC8YKfEgJIWiWuuAEkYAeIL5Gpc6P1+m1IPg7PX8/n0v6HsGdAEo1pj9xsgCRQFrXi80LwIMNrj166ACgyC8I2jOBgyVz5gWTnZ2hVQwjSKuMr23Y+xUXyGnlMKFZjVrrH+w/e5g0yx8IaRh2+wdd19B1wy+D9ClVAji8KqJhD2BAKBAux/bKNzJIxaNcuKKwxFEISqlYXgrhKtb2HSo+gVBFM7KQJw3/cDdtgEhBsA4NfKsLwdDCnk9++AusGZtSI/uhinvIhiHm6jcP6PK+1UjWtoQ3j+ZJjhVrMbDROmybDnEf92jf3OXgNO+gRVhhbPMw2TVSWoyhmcRWpYRrCFuRuY2ECZzFPMr6I9kpYMjKHIUZDK8naXxxH0DBpskbi+ADBpkZtVgfd5e5VZxTMIgrI2obURjNooPBizLZC89Rw0yHP2dNRCNW4xDt0CAi3ittW2MgwFkTzDwM5qPRfG51nVZM3pOJi8wOYevirzzX5D2z3r81CFUVJhMEfwfNINOhWSi1UfaoRQFzL0fHLbStWZQ+0yguxU9M8bkLimHiHCN6hO6/hvb/6Cqj6rTSbmrS1OAjMMA1EbJMAdFZXq/OiCK/Fu8DmYzzk8vW0mKV9ha9hffMILG0wwJ0CycGdmHKg4p7qHOQnTeShXMD/TZ5hRO8phvlNkqQzkiRtVnWnImMvdBL9tFx+zoomH78djy2OxDDkhvcFPqXymyCVlkFI8Oyk1ymdXIW7DnIDl13cBSqAue/TPX34AQUDBIiqR4IkAAAAASUVORK5CYII=","edit":false,"name":"Layer 4","opacity":"1","active":true,"unqid":"oo532f","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}}],"active":true,"selectedLayer":1,"unqid":"vr50fd","preview":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAjVJREFUWEfNWEFywkAMa+D/T4Z0nEEdRZHshR5aTiQhu7JsyV62fd/3r3/y2T4Fs22bDeE3sb0FRgHUxnzPXb9D+hKYtOHj8fi63W4toAKzytYIpoAg4sREgbrf7zFtWGNiqQXDi7gaYZC10fP5PEAVuPoUa/XBuxNDEYymhhet79iQGeHNFMAKIAtGaa1rbA6q6x4i1/ShTjjFnKLE0AWMU4yyMuU+gWHQDtAJzKpqeFEoCgBRN7ju5K+ALJiUe6WdgXBNMCCslTzqtBccmBebvqOAVS1aF1MRX54rGKZXZYmXnZLAkrKSVGTVmphRerVOCig8BQy5unFKUwH87FVgOgm6lLHjOvftAuEUXwTDYBhx0Y2+o4sDQN2v34ElJ111cXbnCEYbWio+RAbndc2S10pg6n0Ff0g79R12WzYyLmBmrWMmFfbJj1KaHECOGIunVpCU2LqzY0aj1XaAWgHVrj24NGuXtzXjouj6iNbBVG/KIoLhwezYrzO9rpYcINeZtVc5Fk8+MzETTeo1Bepzljs/06aKfS9gUsFNjc5F6nqUayGX1sNHFXViO3O8jihJ0qtAtJgPMvTc1JndZPNu9GApu+8Mvh07p1GiU9xUh5b1dKJ0KXPDFXsNq09d2o0MWm9LRxU7e9DxNo0J3FBVOdYop7O2G466EZLZczXWqW88Ub7bY5ya1KEToCUwCuikADK+5MquWD9Kk27MRpXSpWY2GSOeLzNzqfy//n9mNcJPf/cNbqwfxYQm5T8AAAAASUVORK5CYII=","width":"35","height":"35"}],"currentFrame":0,"name":"Untitled","preview":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMA/sfR5H8Fkddasdmnacvx//8745jkhasdASD945kjknhj/AAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAjVJREFUWEfNWEFywkAMa+D/T4Z0nEEdRZHshR5aTiQhu7JsyV62fd/3r3/y2T4Fs22bDeE3sb0FRgHUxnzPXb9D+hKYtOHj8fi63W4toAKzytYIpoAg4sREgbrf7zFtWGNiqQXDi7gaYZC10fP5PEAVuPoUa/XBuxNDEYymhhet79iQGeHNFMAKIAtGaa1rbA6q6x4i1/ShTjjFnKLE0AWMU4yyMuU+gWHQDtAJzKpqeFEoCgBRN7ju5K+ALJiUe6WdgXBNMCCslTzqtBccmBebvqOAVS1aF1MRX54rGKZXZYmXnZLAkrKSVGTVmphRerVOCig8BQy5unFKUwH87FVgOgm6lLHjOvftAuEUXwTDYBhx0Y2+o4sDQN2v34ElJ111cXbnCEYbWio+RAbndc2S10pg6n0Ff0g79R12WzYyLmBmrWMmFfbJj1KaHECOGIunVpCU2LqzY0aj1XaAWgHVrj24NGuXtzXjouj6iNbBVG/KIoLhwezYrzO9rpYcINeZtVc5Fk8+MzETTeo1Bepzljs/06aKfS9gUsFNjc5F6nqUayGX1sNHFXViO3O8jihJ0qtAtJgPMvTc1JndZPNu9GApu+8Mvh07p1GiU9xUh5b1dKJ0KXPDFXsNq09d2o0MWm9LRxU7e9DxNo0J3FBVOdYop7O2G466EZLZczXWqW88Ub7bY5ya1KEToCUwCuikADK+5MquWD9Kk27MRpXSpWY2GSOeLzNzqfy//n9mNcJPf/cNbqwfxYQm5T8AAAAASUVORK5CYII=","palette_id":false} \ No newline at end of file diff --git a/applications/plugins/dice2/sources/d12.pixil b/applications/plugins/dice2/sources/d12.pixil new file mode 100644 index 000000000..70e25c5ae --- /dev/null +++ b/applications/plugins/dice2/sources/d12.pixil @@ -0,0 +1 @@ +{"application":"pixil","version":"2.6.1","website":"pixilart.com","author":"https://www.pixilart.com","contact":"support@pixilart.com","width":"35","height":"35","colors":{"default":["000000","ffffff","f44336","E91E63","9C27B0","673AB7","3F51B5","2196F3","03A9F4","00BCD4","009688","4CAF50","8BC34A","CDDC39","FFEB3B","FFC107","FF9800","FF5722","795548","9E9E9E","607D8B","ffebee","ffcdd2","ef9a9a","e57373","ef5350","e53935","d32f2f","c62828","b71c1c","ff8a80","ff5252","ff1744","d50000","fce4ec","f8bbd0","f48fb1","f06292","ec407a","e91e63","d81b60","c2185b","ad1457","880e4f","ff80ab","ff4081","f50057","c51162","f3e5f5","e1bee7","ce93d8","ba68c8","ab47bc","9c27b0","8e24aa","7b1fa2","6a1b9a","4a148c","ea80fc","e040fb","d500f9","aa00ff","ede7f6","d1c4e9","b39ddb","9575cd","7e57c2","673ab7","5e35b1","512da8","4527a0","311b92","b388ff","7c4dff","651fff","6200ea","e8eaf6","c5cae9","9fa8da","7986cb","5c6bc0","3f51b5","3949ab","303f9f","283593","1a237e","8c9eff","536dfe","3d5afe","304ffe","e3f2fd","bbdefb","90caf9","64b5f6","42a5f5","2196f3","1e88e5","1976d2","1565c0","0d47a1","82b1ff","448aff","2979ff","2962ff","e1f5fe","b3e5fc","81d4fa","4fc3f7","29b6f6","03a9f4","039be5","0288d1","0277bd","01579b","80d8ff","40c4ff","00b0ff","0091ea","e0f7fa","b2ebf2","80deea","4dd0e1","26c6da","00bcd4","00acc1","0097a7","00838f","006064","84ffff","18ffff","00e5ff","00b8d4","e0f2f1","b2dfdb","80cbc4","4db6ac","26a69a","009688","00897b","00796b","00695c","004d40","a7ffeb","64ffda","1de9b6","00bfa5","e8f5e9","c8e6c9","a5d6a7","81c784","66bb6a","4caf50","43a047","388e3c","2e7d32","1b5e20","b9f6ca","69f0ae","00e676","00c853","f1f8e9","dcedc8","c5e1a5","aed581","9ccc65","8bc34a","7cb342","689f38","558b2f","33691e","ccff90","b2ff59","76ff03","64dd17","f9fbe7","f0f4c3","e6ee9c","dce775","d4e157","cddc39","c0ca33","afb42b","9e9d24","827717","f4ff81","eeff41","c6ff00","aeea00","fffde7","fff9c4","fff59d","fff176","ffee58","ffeb3b","fdd835","fbc02d","f9a825","f57f17","ffff8d","ffff00","ffea00","ffd600","fff8e1","ffecb3","ffe082","ffd54f","ffca28","ffc107","ffb300","ffa000","ff8f00","ff6f00","ffe57f","ffd740","ffc400","ffab00","fff3e0","ffe0b2","ffcc80","ffb74d","ffa726","ff9800","fb8c00","f57c00","ef6c00","e65100","ffd180","ffab40","ff9100","ff6d00","fbe9e7","ffccbc","ffab91","ff8a65","ff7043","ff5722","f4511e","e64a19","d84315","bf360c","ff9e80","ff6e40","ff3d00","dd2c00","efebe9","d7ccc8","bcaaa4","a1887f","8d6e63","795548","6d4c41","5d4037","4e342e","3e2723","fafafa","f5f5f5","eeeeee","e0e0e0","bdbdbd","9e9e9e","757575","616161","424242","212121","eceff1","cfd8dc","b0bec5","90a4ae","78909c","607d8b","546e7a","455a64","37474f","263238"],"simple":["ffffff","d4d4d4","a1a1a1","787878","545454","303030","000000","edc5c5","e68383","ff0000","de2424","ad3636","823737","592b2b","f5d2ee","eb8dd7","f700b9","bf1f97","9c277f","732761","4f2445","e2bcf7","bf79e8","9d00ff","8330ba","6d3096","502c69","351b47","c5c3f0","736feb","0905f7","2e2eb0","2d2d80","252554","090936","c7e2ed","6ac3e6","00bbff","279ac4","347c96","2d5b6b","103947","bbf0d9","6febb3","00ff88","2eb878","349166","2b694c","0c3d25","c2edc0","76ed70","0dff00","36c72c","408c3b","315c2e","144511","d6edbb","b5eb73","8cff00","89c93a","6f8f44","4b632a","2a400c","f1f2bf","eef069","ffff00","baba30","91913f","5e5e2b","3b3b09","ffdeb8","f2ae61","ff8400","c48037","85623d","573e25","3d2309","fcbbae","ff8066","ff2b00","cc553d","9c5b4e","61372e","36130b"],"common":["000000","FFFFFF","7F7F7F","a1a1a1","C3C3C3","c40424","880015","B97A57","dba88c","ED1C24","f75b63","f26f9b","FF7F27","f7ab79","FFC90E","FFF200","cfc532","EFE4B0","1ee656","0c6624","22B14C","B5E61D","5487ff","00A2E8","99D9EA","3F48CC","7f86e3","7092BE","720899","cd55cf","A349A4","C8BFE7","ffffff"],"skin tones":["ffe0bd","ffdbac","ffcd94","eac086","e0ac69","f1c27d","ffad60","c68642","8d5524","896347","765339","613D24","4C2D17","391E0B","351606","2D1304","180A01","090300"]},"frames":[{"name":"","speed":100,"layers":[{"id":0,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAFpJREFUWEftlDEKAEAIw67/f3TvBQ4tCA5xVzSGyrbfkVK7jCRNN9Qz28YNmDUZltkgMM3kTZBJfcMZnMGZlADOpMTIGZzBmZQAzqTEyBmcwZmUAM6kxE7lzAeBa4+YB9sR7AAAAABJRU5ErkJggg==","edit":false,"name":"Background","opacity":"1","active":true,"unqid":"4anbk4","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":1,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAjZJREFUWEfNmEuSwzAIRMf3P7SnrBFU0zQIezXZJLEt8fi1SK6ff/S6vrLc931Xa6/r+rTveJEyrmwqxincCMZATsa7+xOgIwyDoOfbwJOuZ5/1jhE0OFtzAmphKpBtxAC4dNZ1AFj3J0AlzABk2cCoGIRF7C2QhKlqhDa3tdhVfo1BLHxdhBKMiggWZtPRqdNFzbQpCzAVyAPwUTrcOBTviqSKkMOI1KTOeBMVC9PjBDgTUstAAQa8907B3NPiUoGxxQEm1BOm0KKmYLBlQ/tyAReqXLY2SoJFa78vjgRDD3mIJyCQmgTEdfcGxhV1GwgiRirsXaTUmerFdektzJJRyu2K5uSsQuFTe6CTZc0g8bK8u8E+CxiMIkZQpgq7zFq8hVELGpggdtxxlRLbMdIWMAmSixPApFRhXRxOedScJIiVzkhvGYj1vwMpUm5wfWsLQ35JzSkTENxz1E18umI0VEtjzvdIEVJL0UyH6VH0uIDZI3E/HR/FHmnsGBWwGhs4PcJrOXaC0tt5FuBla2ORKcOHlJ1AUhmWOrNvuOJyi3ff2Qqd/mspPKOmwdhNBqO859O2ilDR+pwamaK1VmkF10t1tuDZldrk70I4KuB7P+lBSy9PKqD9nJ87NMmhEYQJn6uhfPzrQE15PPuAQyFQRWcm2xJG1Q8WMGtIBYVEYtZJmS1hEIjTpoyIUWM9NoFw54rCC5fV72c0NPhDoHX6FQwX9yRS+5kRxCcYBaUie/q3ocrGL7FUikLdy+7AAAAAAElFTkSuQmCC","edit":false,"name":"Layer 1","opacity":"1","active":false,"unqid":"a2sj1t","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":2,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAi9JREFUWEfVV9FuAzEI6/3/R98UFpBxDEmr09Ttpes1CY4xhrteX/R3fRGW1/8Dc9/37Qxe10f4jzZtFzkQBDGwje/+OYAC3sj89bvJL7KNtV0Ah6WADG4gmM/8TARxBKgEMxipUuIs+O/IirOBrA2cJwx1zIROOLinhcHCOg9un2K/jFuBWYCwgFkvBOxyZhGI2JPiL2AwPUR1aEbppao2SmGytXl+YEhgGiCSblVBGK3THOpt6iybXkftRrTLjdnZC7HbMmeI02RaAfriO5RuVeJ4Vtrne1k/+Hyw04GxtRNclHmhARMspSgqChkHL7ItYJ4JTKogwBkecQoExBxaw6D+O4HMYCp794OK8o4yhiDYEqJtcPrZDFOelblx32Ehiv6z6Ez0ssS2p64VnRLevHJpCcwONlT2rcEM2InWDJelsHnDOQ/ivvM8M5XLYloKkEvpc9VQ04yOHnSL8mvnFWViwuhkNTmjmKLwEThk26mV3W8Gq9Q0lc+4n73Vm7iy0JX9QLwY+1LlwBLMPIhbApKx2DwBirXMVtebSjBKO1TOEhDrpQIJvS88dPmHDsM+o0bGElA1/bF7K7+Skx6pfATGMdIup8wNLhSNE6yA28YS+2QGVqaGzxad8AgqqvWtGdimaOrcQQY2uGLGTQP5E28HO0AGrhiyu3FEad2edWnyYO37k5h70qBVsCsBbcHM29tNP3zPxjG2ZOWImXb3wz8eMfNwzM8181dARpwfC5kXQnaygo4AAAAASUVORK5CYII=","edit":false,"name":"Layer 2","opacity":"1","active":false,"unqid":"a2sj1t","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":3,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAjxJREFUWEfNV0GSwzAIa/7/aO/YaxghC3A7PXRP3YSAIiRwntcP/T0/hOX1MZgxxnie9PGP8r79UAZijLFIRoBw7arOVdAswiCwuAKC7Z/3n4JGi23BVO3YRSZQZ8SAeYHYyrJeejNjQhVRoGbHZg7VuowlCYaAzJh/QbyW4K1GYGN2ARjCZwJzkOcw8gEmAWLJQxHORtpYoE0q+Htr8NBRANMAMWZWLkuIgLAwtCdlc8c4hgyMXbf2tEBYS+ywzPqoH24TakMN54ORncx1hILNnEWC18ywUElsKRBDjcWTlq1Q0k8L5mhLpRFzGceoOScAX4EJbSqG2UqG4q8Y4rmzX8TnBhYNdlT0K01M3fCAU4CokNm+ZkbtncItanAqfSlDoByOI4RbGRXfAHE2aUtLQMLiJTPHlKUEcudwS9ny7C4ailozyhENmLAqmJ3sqEF7LAXj0zfZLW8zQ/uJ3Ze3SWxe3y07qYNhm7KDmNHdyqmllcPyuSZJ4oe1WcgKENtE6YXPPFdtYma4VSi8sJBsd/yjPc48sFrC2agcerQ7Qn+VdZPhEXSV2PlYyt15xib9mpa8ErIphtf5OWshMOXh2Rk4oG6+kVpMrBcFJIxikTFMUDW0OhT8DQX/SxLSrwOwodcsrMruzARcEtCBWZrBqYzWhjmxZseODUOTZlFZ7wZMCghOhkGzxefMV8AcgPYbh72EH27IyBZw++JtAE9oFu3lIeqqzlVQ4Rr8lFlh4nh6XeM6sLPxN+7/AbQmAUIHBi45AAAAAElFTkSuQmCC","edit":false,"name":"Layer 3","opacity":"1","active":false,"unqid":"a2sj1t","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":4,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAjJJREFUWEftV9FuxDAIu/z/R2dqLlDjGJKe9nCTtpdNbQaOMYa21xf9tC/C8vqbYHrvvbXPsPfeRwHaJsCT6COiBYbg1/Mrjr/Pcl7/WwE6BZMlasjYTIYyxPgYQ+Y9AfPm+L69sTB+GwAohYHBc/asBLQDg0BCQCxZUpYAmi5klwvNXIHhWwxtDCpuMRpY1xIBs/ioK2Q05M/ALEmce+ooFLQ4g2Cw1FhKZ0eBOeqKnVkCQwxosHi95+5iMKp9R15mQOmkOBPykGf5uwUMdoX6e+pFCXC5CJwNJUILQHYkmHnrpXWnYQUhB1OZeqpYFBbgF8vAmJm5qcFtvKOUbqx8GSDBimtncUi0dmtlEBuPg0AMtL13iz2jkikrCH3qbooGRcqXQWwuAXifVeRNVRNKMBiI2zAzt6VNZ9YgamNY6Wz4KLz4NWawJDTlAzMk5ghGzQ/zBB6ISgNXt/EUR1dWwLAxnBlKmj0fGFCURHnYa5LxkK4jZVJeESqfgXey48j6lSTuHRiZsRJsHJhdeDtKcF6pkbAww3MoMzHWzO7/lFfxsEwHmFghXR5qbajGAo4XA42XtL14WSHUV8BJewqxSk/KhqSavCOmWrJZPyjY5LYWK3hLsgmOM0c7MJvTtH9cQRczw6TVpA7lrYYFWnrybRQ8h8X+9Ptpx4xh5c8VZNU9ULEhui7NeQpmyMLGhXJg6g4fsH6bg0/cJ2AQ0EF130eoE8t8T8Ecg/jk4D+YjLUfNgMXQvU7QCMAAAAASUVORK5CYII=","edit":false,"name":"Layer 4","opacity":"1","active":true,"unqid":"a2sj1t","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}}],"active":true,"selectedLayer":4,"unqid":"d602vs","preview":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAadJREFUWEftV9ESwjAIs///0fPmyY5lSWC9TX3QF7XtSggQ2FiWZXn8yGfMghljDOXD9J2zD95B5ilmDBkWWzcT2mACCF68rq9reV+BrkC1wChDcXm1v9KmnMmUlmDQULDAvtFosBYGK0AWDHs4hwCZyV4i2HUPw4mJJsEgEPQysxCG3JpisgwT8x6pjv8sKZkjyEx2YLsbdeaKqmAJiw5SpjMYlyOspDHm6kyHvZcDCCYnJfsdXncNM6ZVIlMwWchy4ikgVelWFbg5rZjZZXlS2VJF3/3TMce0axcmRWdmQ4kYVoZrHViFO7vBjOoxXaXtAHKN9mUHwTDhwhzCM0wzVHnb8H2TGazWHTOqfzBmlPQzRlgV0gb8iWqqtGYjgYG5UmeUqjPhm1bgM9XDhjCa4LO9qaooNYJkrTro1h1d2wFRRXJolJ2ZBYXLdXM2CbpprzXpKWrZjOI8d3IgmVFduKM3CCYnupsgSzCY8arbOvC5+2OTxL3yVcUBQmVVnleMbM5037XVWMBYwQlANdIpZlSVsRDgWjWM5fOtMHWMXnHmD0ax+AT6o7S2HI0/tgAAAABJRU5ErkJggg==","width":"35","height":"35"}],"currentFrame":0,"name":"Untitled","preview":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMA/sfR5H8Fkddasdmnacvx//8745jkhasdASD945kjknhj/AAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAbFJREFUWEftV1sSgzAITC7T+5+nl7GjLRlcdwEz9vFRf2wTDcsCC/ZlWZb2I1efBdN77621W2vtjvfpM2dffAeZp5h5knH+qmZCGYwBwYPX9XXN7yvQGagSGGXIDs/2Vy6VM57nFAwaMhbYHY0aa2YwAxSCYS/7ECAz3ksEu+5hODH7JBgEgl56FsxQtKaYTMPEvEeq7T9LSuYIMuMdGGejzlxRFSxh0UHKtAcT5QgraYy5eqbC3uYAgvFJyX6b11XDjGmVyBSMFzKfeApIVrpZBQ6nFTO7LHcqm6roq2VEzDHt2oVJ0enZUCKGlRG1DqzCnV1jRvWYqtJWAEWNdrODYJhwYQ7hM0wzVHmH4fsmM1itO2ZU/2DMKOlnjLAqpA34E9WUac0ggYG5UmeUqjPhm1bgM9XDhjCa4LO9KasoNYJ4rTro1ju6dgREFcmhUVZmFhSuqJuzSTCa9kqTnqKWzSiR55EcSGZUF67oDYLxiR5NkCkYzHjVbSPwvvtjk8S99FMlAoTKqjzPGBnOVL+11VjAWMEJQDXSKWZUlbEQ4Fo2jPnnS2GqGL3imT8YxeIDSbO3tqGpQPgAAAAASUVORK5CYII=","palette_id":false} \ No newline at end of file diff --git a/applications/plugins/dice2/sources/d20.pixil b/applications/plugins/dice2/sources/d20.pixil new file mode 100644 index 000000000..4e2baeb61 --- /dev/null +++ b/applications/plugins/dice2/sources/d20.pixil @@ -0,0 +1 @@ +{"application":"pixil","version":"2.6.1","website":"pixilart.com","author":"https://www.pixilart.com","contact":"support@pixilart.com","width":"35","height":"35","colors":{"default":["000000","ffffff","f44336","E91E63","9C27B0","673AB7","3F51B5","2196F3","03A9F4","00BCD4","009688","4CAF50","8BC34A","CDDC39","FFEB3B","FFC107","FF9800","FF5722","795548","9E9E9E","607D8B","ffebee","ffcdd2","ef9a9a","e57373","ef5350","e53935","d32f2f","c62828","b71c1c","ff8a80","ff5252","ff1744","d50000","fce4ec","f8bbd0","f48fb1","f06292","ec407a","e91e63","d81b60","c2185b","ad1457","880e4f","ff80ab","ff4081","f50057","c51162","f3e5f5","e1bee7","ce93d8","ba68c8","ab47bc","9c27b0","8e24aa","7b1fa2","6a1b9a","4a148c","ea80fc","e040fb","d500f9","aa00ff","ede7f6","d1c4e9","b39ddb","9575cd","7e57c2","673ab7","5e35b1","512da8","4527a0","311b92","b388ff","7c4dff","651fff","6200ea","e8eaf6","c5cae9","9fa8da","7986cb","5c6bc0","3f51b5","3949ab","303f9f","283593","1a237e","8c9eff","536dfe","3d5afe","304ffe","e3f2fd","bbdefb","90caf9","64b5f6","42a5f5","2196f3","1e88e5","1976d2","1565c0","0d47a1","82b1ff","448aff","2979ff","2962ff","e1f5fe","b3e5fc","81d4fa","4fc3f7","29b6f6","03a9f4","039be5","0288d1","0277bd","01579b","80d8ff","40c4ff","00b0ff","0091ea","e0f7fa","b2ebf2","80deea","4dd0e1","26c6da","00bcd4","00acc1","0097a7","00838f","006064","84ffff","18ffff","00e5ff","00b8d4","e0f2f1","b2dfdb","80cbc4","4db6ac","26a69a","009688","00897b","00796b","00695c","004d40","a7ffeb","64ffda","1de9b6","00bfa5","e8f5e9","c8e6c9","a5d6a7","81c784","66bb6a","4caf50","43a047","388e3c","2e7d32","1b5e20","b9f6ca","69f0ae","00e676","00c853","f1f8e9","dcedc8","c5e1a5","aed581","9ccc65","8bc34a","7cb342","689f38","558b2f","33691e","ccff90","b2ff59","76ff03","64dd17","f9fbe7","f0f4c3","e6ee9c","dce775","d4e157","cddc39","c0ca33","afb42b","9e9d24","827717","f4ff81","eeff41","c6ff00","aeea00","fffde7","fff9c4","fff59d","fff176","ffee58","ffeb3b","fdd835","fbc02d","f9a825","f57f17","ffff8d","ffff00","ffea00","ffd600","fff8e1","ffecb3","ffe082","ffd54f","ffca28","ffc107","ffb300","ffa000","ff8f00","ff6f00","ffe57f","ffd740","ffc400","ffab00","fff3e0","ffe0b2","ffcc80","ffb74d","ffa726","ff9800","fb8c00","f57c00","ef6c00","e65100","ffd180","ffab40","ff9100","ff6d00","fbe9e7","ffccbc","ffab91","ff8a65","ff7043","ff5722","f4511e","e64a19","d84315","bf360c","ff9e80","ff6e40","ff3d00","dd2c00","efebe9","d7ccc8","bcaaa4","a1887f","8d6e63","795548","6d4c41","5d4037","4e342e","3e2723","fafafa","f5f5f5","eeeeee","e0e0e0","bdbdbd","9e9e9e","757575","616161","424242","212121","eceff1","cfd8dc","b0bec5","90a4ae","78909c","607d8b","546e7a","455a64","37474f","263238"],"simple":["ffffff","d4d4d4","a1a1a1","787878","545454","303030","000000","edc5c5","e68383","ff0000","de2424","ad3636","823737","592b2b","f5d2ee","eb8dd7","f700b9","bf1f97","9c277f","732761","4f2445","e2bcf7","bf79e8","9d00ff","8330ba","6d3096","502c69","351b47","c5c3f0","736feb","0905f7","2e2eb0","2d2d80","252554","090936","c7e2ed","6ac3e6","00bbff","279ac4","347c96","2d5b6b","103947","bbf0d9","6febb3","00ff88","2eb878","349166","2b694c","0c3d25","c2edc0","76ed70","0dff00","36c72c","408c3b","315c2e","144511","d6edbb","b5eb73","8cff00","89c93a","6f8f44","4b632a","2a400c","f1f2bf","eef069","ffff00","baba30","91913f","5e5e2b","3b3b09","ffdeb8","f2ae61","ff8400","c48037","85623d","573e25","3d2309","fcbbae","ff8066","ff2b00","cc553d","9c5b4e","61372e","36130b"],"common":["000000","FFFFFF","7F7F7F","a1a1a1","C3C3C3","c40424","880015","B97A57","dba88c","ED1C24","f75b63","f26f9b","FF7F27","f7ab79","FFC90E","FFF200","cfc532","EFE4B0","1ee656","0c6624","22B14C","B5E61D","5487ff","00A2E8","99D9EA","3F48CC","7f86e3","7092BE","720899","cd55cf","A349A4","C8BFE7","ffffff"],"skin tones":["ffe0bd","ffdbac","ffcd94","eac086","e0ac69","f1c27d","ffad60","c68642","8d5524","896347","765339","613D24","4C2D17","391E0B","351606","2D1304","180A01","090300"]},"frames":[{"name":"","speed":100,"layers":[{"id":0,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAExJREFUWEft1LERACAMAzGy/9DOBCncUYgJuI/OkyTvkzc+c1xCmYuoMsq088UMM8y0BZhpi9kZZphpCzDTFrMzzDDTFmCmLWZnrmIL7qWLmCXUhAcAAAAASUVORK5CYII=","edit":false,"name":"Background","opacity":"1","active":true,"unqid":"epa8u","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":1,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAjNJREFUWEfNWNFy5CAMK///0WlhYo8QkoHcy+1LO9mAZVmyYdvPf/Rp/4LleZ6H17fWPu/5aSGCwNiI7QuoKzAOBLPzFdQRmB2ICM4VugVVgtmB6IywbJRkTkFJMDtNRMAe5P2/75PLnIZ3oCYwOxCdCQFkkowo2QCKLzlQCSaAKHf0Z8BClgZZ6TiRHQUc98ASh/MmMJh1ZKKAwLMpa8p4SYD3CkAlGBamKo3od1O5qjXMvgXDVEYEztp1blVOLBnuRwyPOo9P10x8qRar4Ce2rtYdgwlAkSm4pKmZVM04Z4ojzaA9mXY3i1DwXFqhkcT+7j8qJMuEqlfAkCXqIcmac+GVZoC60NL4y+5wQu+vov6UDgXbNTPORagh1fSw+VECS0/aChiZqUpUOG4plTLD21uGF8o+w4tflib6UbA0MLNNqJIbLfkyUdZyZKD40dY440jo2NJSh1tmXK2N5XHyB+0p/ihHsBvuI+3trS0YymCQkX0G61O8bIAjZjBYOERphw5SHBQdlEcM7mPbQYmIidpFyNxLWNDQGKOkis21A4tWPjagw5d1FjsohK4Ob0ezSTU87is8QLEdkFZCzONUwB+rmaKRTaMBN6z6RoyFBcH7gK0vD+TudE/9hHsFn40dhikxvHkuvN3cEMpo5ks6MU7x7SWuElzEOWHQsCnjfr5Rnp6JwzGQgI1ZggFX5SWM2cARoZxyAiLfuan7yd37hgmOfcSMyHj5kWjZ+FZQfxv8AsTOxkLIDDsGAAAAAElFTkSuQmCC","edit":false,"name":"Layer 1","opacity":"1","active":false,"unqid":"c2f8it","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":2,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAiFJREFUWEfVV0FuwzAMa/7/6Aw2LIGmSMUbcuh6aZHFNk2RlHZ9vuhzfRGWz/8Bc9/3PZi7rncwr+3GfnJDe4oCEptxacfe42/8He/h2eu934N5YgTBPQFBsIvtAkgiPGHFAWWA42AozyTLsVPAdECi1vgOH6TcqRhU7EgwfGu6yRT10PX6DmwFR+zDenLsbGCcewDMADLWIKD5W5UH0TmBo7MKGL6NqjkAmutdaVHUJ9pJMLxhc5MUobMuPlfijezi3NnAICu4ADQkmTAlmuVs9FKcZcGIcl0PTsPNMwSVbvhZOAs1swkx6o0LBXMJkN8LRljcwbgAufWIBKPKRQIMR00wFAUbQBBxuhCBos03ZlyANZqYe0HuSJvD4RbQ7MdAV4otnnFjJB2lmFVIsqMce2iOopkSoz7QMoH/ygzpp2qmU38kMeiEAc39VWbhwa5xtsw0EY8HyhTnkYEZVzOOzBlaWNyhNIVR4NpAl9ijRx2BAceU2YQsn/dg+4KFt7viRGB7E95+gY6OnY2RA0y5T7UDZjZIsV2bS4UREO7BQYY1EEBFdnHWJIajeQYHKbJxNkOlBy6NahF2nlmLOd5ZJ3K4UpY+iQl8R46drAW4IQORgxXb+GTKm2eaxHX/vMkxQh1+EnIlexQYVy53KIrVdXznoLZMsKiww9aF2+coYUTK+SMrIh92gByTJ8955j0u08nmb7/TMvP2YU/7fRWYH/oqWEIZZe9qAAAAAElFTkSuQmCC","edit":false,"name":"Layer 2","opacity":"1","active":false,"unqid":"c2f8it","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":3,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAhpJREFUWEfNV9uuwyAMW///ozuBSGSMDeE8HG0v2yraGF8S+nx+6PP8EJbPFZj3fd8A/zz+Vlj2eXYLiYkSmBOIKM51b0FtwZxA4Mao8LTnKigLJoBcsNwBVEA56RYwN2zszO9A7ViawNywgT5pvxuDzjsKNKxNDAuYU0qC4gY81jIYLF59XruHZcroxgNHIVyXSiIb/WGDHQSAssQal+gtGEFlBxtMKMMCeATd6xgbaJli/dhZLAq28r/yiPDQg1KGCnENNnQGQ4sTkCiQ0iLI4S3cSPudAP8KJrw1ecU92Ow85RXeOjMzTNYWSlbUzlBmEfNJtjIz4PpEjeZTPYISgkafkoimJ2/qaJP22eapr1hDkxTJrmmOXiblBU7KKIYSJjE8BlzyoBeVPdPrqm47otoTMnzVv5nV3Yy6kon13UR3YoY7MZsZmyZOgaUD08Kl29JAZKk6ixxfN1RLzHDLFwlYzi5BDc8lN0xLnrkpvDvPtA68Y0m1DymTma6ydmEqL/eRh67SJI+S7C2u6FJER5MYNf2yPULsEsBsMBD2iTpqwD3bpjd1XD67qAMVPlgMykykYat27HRjgZKzND30nBkBuGEPRkV0XJtmjDM5M+kiD9etTIH4+BorEpGjoNIe1LsTG3jy4ukdqhpr8oqtuQUDMbRMiUNUbqgKIiXbdVHRO47yiRiXNqz6TAmbk++WiaU/lar/06IvOzpSQih7Kx4AAAAASUVORK5CYII=","edit":false,"name":"Layer 3","opacity":"1","active":false,"unqid":"c2f8it","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":4,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAidJREFUWEfVV9GOwzAMWv//o3uKVWeYgNNKfdjt5XTr4hDAOD0+P/Q5fgjL5/+AOc/zHMwdxzuYr3KjnizY7jLA4DooFuqO/8fz6++olfiL+qbGsrcF41jpACWCHZMAvoJ2BkZWFICdfCRJyJ0fJ5dkBlkhKWzziQ0SwMFyp8TXgSYGC4Z1VtSb04d3LtTFR1yD5VrAsFeSGTSsohxkSzAMKkp0ckkwcIJZEH3DHkLagXpkZ9qFdUZ2Cpiug6CFo5Xxo1qX/DAWbL2zgDG6ls1ROjKj6piCm6UC+b9JqFhxDGCHpVfwtxumKq3f8KxgXBGWRCQvMpIjBA1cpHXTYMr0JOSaQMt6QfSYQWqkbMGklqJTZlakiZMG1XXDqNfzYMjJJ9KzTM+y+JqsU1/2CeRKMW1OZMW0OMQctnE5AITLxpyibzNDKvQGRhnypET7YlJgNPyCDLqJn4eWBp5xacJNSQYSWBAok/JmAdP4QIYezy1ez13n5lx2nk1gtRC+W2aWkWPpLPRd2sCC4UTFBQ/TNtjkAYszjp6vF+Nd+HExLggZIyc+zKIJNuOgvc9Q7C85ZQJtuVzdmdjBukjCuOKTnhhOyxWCwqz4JPdwV098bbl1B1bJyYcwl6+o390ItmCygDIvekRFwS6jjMdi2a33JpUn2FnNmFAuwJe+8tyCYXYUI2xwvowpeSkIn4HppJDHbr7sgLQyPd3ojd+3Mr2xwZMaPwXmDyLsWEINgQlHAAAAAElFTkSuQmCC","edit":false,"name":"Layer 4","opacity":"1","active":true,"unqid":"c2f8it","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}}],"active":true,"selectedLayer":"0","unqid":"unboeg","preview":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAbJJREFUWEftV8tyg0AM6/7/R9OhE2eMVg9TesihuSRhwCvLsmzWcRzH14d81j8YUYkRM2utPylkUkQE04FUsLp2/j9/9+vsQBaDZXcLzBkgATrvSUwqhiyYzkA/BAGxLPuBCO7XYHopXMZ4gEqkgNNyqtbGOjMgLPsCXyVFTXUWtwQSGBZcBayD+zfTmWKHaga7BQMyzaQyKWFf2GXMMMFh1p2d1LrTUm3MKCBYGuc/qjS95KzUFoyj3pmdY8o1wgVMorvqjs6rZoWKpwBRMMxlmemhKNVYmNrEG4xzWyXe1HVdO2lM/LBd3YRgWNZdJyz4pL1d3A1Md050UQUGjXFSUuZdVsCsdCp7ZYxuwG7u3k0vTVfUSFyWXksZS4CuIujAd0zPlYeJV825NzAFRk1fNQSVTvA6Gw0STDc2VW+1OKmVgpkl7V43KGldXztvsvzUYWMwLhO3yzhbGPnSdLlimeJMcnp4BAa1M7Fz9kwHnAbxo7cDB3Bijhuz6V07tTJbJ9iKoLR2YW4Cph7A1u1AcG7dKU/dG98ok1aQavc/jo/EzJ3Dnt47YubpIdPnPwrMN5+zjLYb9TyVAAAAAElFTkSuQmCC","width":"35","height":"35"}],"currentFrame":0,"name":"Untitled","preview":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMA/sfR5H8Fkddasdmnacvx//8745jkhasdASD945kjknhj/AAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAbJJREFUWEftV8tyg0AM6/7/R9OhE2eMVg9TesihuSRhwCvLsmzWcRzH14d81j8YUYkRM2utPylkUkQE04FUsLp2/j9/9+vsQBaDZXcLzBkgATrvSUwqhiyYzkA/BAGxLPuBCO7XYHopXMZ4gEqkgNNyqtbGOjMgLPsCXyVFTXUWtwQSGBZcBayD+zfTmWKHaga7BQMyzaQyKWFf2GXMMMFh1p2d1LrTUm3MKCBYGuc/qjS95KzUFoyj3pmdY8o1wgVMorvqjs6rZoWKpwBRMMxlmemhKNVYmNrEG4xzWyXe1HVdO2lM/LBd3YRgWNZdJyz4pL1d3A1Md050UQUGjXFSUuZdVsCsdCp7ZYxuwG7u3k0vTVfUSFyWXksZS4CuIujAd0zPlYeJV825NzAFRk1fNQSVTvA6Gw0STDc2VW+1OKmVgpkl7V43KGldXztvsvzUYWMwLhO3yzhbGPnSdLlimeJMcnp4BAa1M7Fz9kwHnAbxo7cDB3Bijhuz6V07tTJbJ9iKoLR2YW4Cph7A1u1AcG7dKU/dG98ok1aQavc/jo/EzJ3Dnt47YubpIdPnPwrMN5+zjLYb9TyVAAAAAElFTkSuQmCC","palette_id":false} \ No newline at end of file diff --git a/applications/plugins/dice2/sources/d6.pixil b/applications/plugins/dice2/sources/d6.pixil new file mode 100644 index 000000000..4d9d44d7b --- /dev/null +++ b/applications/plugins/dice2/sources/d6.pixil @@ -0,0 +1 @@ +{"application":"pixil","version":"2.6.1","website":"pixilart.com","author":"https://www.pixilart.com","contact":"support@pixilart.com","width":"35","height":"35","colors":{"default":["000000","ffffff","f44336","E91E63","9C27B0","673AB7","3F51B5","2196F3","03A9F4","00BCD4","009688","4CAF50","8BC34A","CDDC39","FFEB3B","FFC107","FF9800","FF5722","795548","9E9E9E","607D8B","ffebee","ffcdd2","ef9a9a","e57373","ef5350","e53935","d32f2f","c62828","b71c1c","ff8a80","ff5252","ff1744","d50000","fce4ec","f8bbd0","f48fb1","f06292","ec407a","e91e63","d81b60","c2185b","ad1457","880e4f","ff80ab","ff4081","f50057","c51162","f3e5f5","e1bee7","ce93d8","ba68c8","ab47bc","9c27b0","8e24aa","7b1fa2","6a1b9a","4a148c","ea80fc","e040fb","d500f9","aa00ff","ede7f6","d1c4e9","b39ddb","9575cd","7e57c2","673ab7","5e35b1","512da8","4527a0","311b92","b388ff","7c4dff","651fff","6200ea","e8eaf6","c5cae9","9fa8da","7986cb","5c6bc0","3f51b5","3949ab","303f9f","283593","1a237e","8c9eff","536dfe","3d5afe","304ffe","e3f2fd","bbdefb","90caf9","64b5f6","42a5f5","2196f3","1e88e5","1976d2","1565c0","0d47a1","82b1ff","448aff","2979ff","2962ff","e1f5fe","b3e5fc","81d4fa","4fc3f7","29b6f6","03a9f4","039be5","0288d1","0277bd","01579b","80d8ff","40c4ff","00b0ff","0091ea","e0f7fa","b2ebf2","80deea","4dd0e1","26c6da","00bcd4","00acc1","0097a7","00838f","006064","84ffff","18ffff","00e5ff","00b8d4","e0f2f1","b2dfdb","80cbc4","4db6ac","26a69a","009688","00897b","00796b","00695c","004d40","a7ffeb","64ffda","1de9b6","00bfa5","e8f5e9","c8e6c9","a5d6a7","81c784","66bb6a","4caf50","43a047","388e3c","2e7d32","1b5e20","b9f6ca","69f0ae","00e676","00c853","f1f8e9","dcedc8","c5e1a5","aed581","9ccc65","8bc34a","7cb342","689f38","558b2f","33691e","ccff90","b2ff59","76ff03","64dd17","f9fbe7","f0f4c3","e6ee9c","dce775","d4e157","cddc39","c0ca33","afb42b","9e9d24","827717","f4ff81","eeff41","c6ff00","aeea00","fffde7","fff9c4","fff59d","fff176","ffee58","ffeb3b","fdd835","fbc02d","f9a825","f57f17","ffff8d","ffff00","ffea00","ffd600","fff8e1","ffecb3","ffe082","ffd54f","ffca28","ffc107","ffb300","ffa000","ff8f00","ff6f00","ffe57f","ffd740","ffc400","ffab00","fff3e0","ffe0b2","ffcc80","ffb74d","ffa726","ff9800","fb8c00","f57c00","ef6c00","e65100","ffd180","ffab40","ff9100","ff6d00","fbe9e7","ffccbc","ffab91","ff8a65","ff7043","ff5722","f4511e","e64a19","d84315","bf360c","ff9e80","ff6e40","ff3d00","dd2c00","efebe9","d7ccc8","bcaaa4","a1887f","8d6e63","795548","6d4c41","5d4037","4e342e","3e2723","fafafa","f5f5f5","eeeeee","e0e0e0","bdbdbd","9e9e9e","757575","616161","424242","212121","eceff1","cfd8dc","b0bec5","90a4ae","78909c","607d8b","546e7a","455a64","37474f","263238"],"simple":["ffffff","d4d4d4","a1a1a1","787878","545454","303030","000000","edc5c5","e68383","ff0000","de2424","ad3636","823737","592b2b","f5d2ee","eb8dd7","f700b9","bf1f97","9c277f","732761","4f2445","e2bcf7","bf79e8","9d00ff","8330ba","6d3096","502c69","351b47","c5c3f0","736feb","0905f7","2e2eb0","2d2d80","252554","090936","c7e2ed","6ac3e6","00bbff","279ac4","347c96","2d5b6b","103947","bbf0d9","6febb3","00ff88","2eb878","349166","2b694c","0c3d25","c2edc0","76ed70","0dff00","36c72c","408c3b","315c2e","144511","d6edbb","b5eb73","8cff00","89c93a","6f8f44","4b632a","2a400c","f1f2bf","eef069","ffff00","baba30","91913f","5e5e2b","3b3b09","ffdeb8","f2ae61","ff8400","c48037","85623d","573e25","3d2309","fcbbae","ff8066","ff2b00","cc553d","9c5b4e","61372e","36130b"],"common":["000000","FFFFFF","7F7F7F","a1a1a1","C3C3C3","c40424","880015","B97A57","dba88c","ED1C24","f75b63","f26f9b","FF7F27","f7ab79","FFC90E","FFF200","cfc532","EFE4B0","1ee656","0c6624","22B14C","B5E61D","5487ff","00A2E8","99D9EA","3F48CC","7f86e3","7092BE","720899","cd55cf","A349A4","C8BFE7","ffffff"],"skin tones":["ffe0bd","ffdbac","ffcd94","eac086","e0ac69","f1c27d","ffad60","c68642","8d5524","896347","765339","613D24","4C2D17","391E0B","351606","2D1304","180A01","090300"]},"frames":[{"name":"","speed":100,"layers":[{"id":0,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAExJREFUWEft1LERACAMAzGy/9DOBCncUYgJuI/OkyTvkzc+c1xCmYuoMsq088UMM8y0BZhpi9kZZphpCzDTFrMzzDDTFmCmLWZnrmIL7qWLmCXUhAcAAAAASUVORK5CYII=","edit":false,"name":"Background","opacity":1,"active":true,"unqid":"ynvcrq","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":1,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAgxJREFUWEfNmNGOQyEIROv/f/RtrhEyjoOgfek+bVLF4wAjbfv80V/7heV5nkftb61dxb3ahBB8LvKdQh3BRBAGgGA3UCWYDIJTdQu1hckgVGmQIs5ZUUrCHEJgDC/oG6gFxkA2UvfbBo3UP8v24n4s8gmGQbLbjUBTe7974ID+GYMbLBR+51hgeCHdtAHwu1f6jMV91yqV8BIIz2lack63VzAIZf/juiWtDGhKKhgPrtJE0kpl2HeiOC9UpowB7jqj3xxU8z0VECiPnvWtMllrn75nu7pJYawws27iNubO2bX4gCilaWlXUyM6ANWq+ky1Zl6YSu1MnULPg3TmEddqpaxMD6bqJ0ofqLNchDztHAYNTpld1DnWZeTGmMk7GLZ8OMCt3nKPBc3rBolZgqWnniayc3ZazyIUYlRDk5Hi+mpr+y1I325SBkot6jD8WFoMXv8TDD+Cu+ADQL3qx2nyDZZveKVDZdQIYe1spniUJkyBcFlveRF0KkhWBou67MBYuDwAWdXi3BPdlHtZzDFpN/lkBo7q8wk/C6pmhpqVWSmGUYqwd6gZd9PakxNv5ho9dqrD1CMZtaoo9mkG3owT8wwcvUU8GginValNITid4Ze4kwELnwMs2kTl5ez0620FSsHcfMVNYaA23EnFdM+CTG9U0F3LnjJMBgWRJxs4+VnkGEZBKVlOIGz/F/AshkJzdMK+AAAAAElFTkSuQmCC","edit":false,"name":"Layer 1","opacity":1,"active":false,"unqid":"eiymk","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":2,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAi5JREFUWEfdWMF2wzAIS/7/o9NnD5iQBU7THva2YxZjIYQgPY8/9Hd+guW6rkudP8/zUdxHhz5JoDvbgvHMHyYq73UyFXslGAVkBPIgVYnazK16GAffb8EAI+M918c5gLzLFjByVOxIMMgKatQBYODjOCKGYsuYnIkYI5MMxU4Jhi92OvE5MeevpA6DSwPrOKfYWcAoVvDwuJGCzUeGRLa6YCTYsXjzvARTlAOzQaqDNNNV0hc8s+r84FXsJDAVK6h4FiJoAkFwU+E98xoq98oMdgkf8GxYiMauX+CAsGzMfqmdpRMqPTSsxcWdoXmNVAO4yBOYXadw64puwqwrCUz2VLkXMIU5hQZ4NhYJhNiTw8L8VF6VhFUYE3dLmFenq24sKCOdDQaHEnXjn4Xtx3PKTrKxm/BY6q+C2V28+X9mhsrEzISPeHlUmZ4AMnZ+wXhJCkNCm58gcVZxue4AEmdWMMiO8g0GokywsYClK93XhpOXptdM7WCmGZiJHF7UlPATmJ1DclmEv6Quo5kVLscJIPt3B6Wy/DTFUWtCMwub20HZseMXqN1EuGmsqL5CYIPcWiFw4RB6WJjAoLheMjO87Yl9SS9Xih27NPlOMQqWnaaadTQSejBMZeMrM1D36dKtDVa+GkzDzsREYpVLvYEPcywYcrb3YJwd0sBb301qQoOGUuguq+QNdqp9fzMG4tvJNcjvb4N/83t7t5ZuwdwZet9653+A2f0K8eQHoxeuA2tCWhaNlwAAAABJRU5ErkJggg==","edit":false,"name":"Layer 2","opacity":1,"active":false,"unqid":"eiymk","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":3,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAh9JREFUWEfNmEuSwyAQQ8f3P7SnIIgSQg0NmcVkk8SB9EP9tZ+ff/R6vmF53/d1+5/nufrfo01svBmcYJjvFCoFIxBWzALBgtxALWEiCPVOgRDjHfgEysJkIWBRYfg6PmegBpgMhHMFYPCbUy4DNcEs/M6xUvbV4EWsaMwExif3cZCrm7oBI3VdC/VYBVUGkOXdqaXXADTBQGIpFdadDMHKGDcNSirkEoZAnqKEqxftunVTA+sHCNSsgLzWKtNgAKKxUr9HMALSiyIrTmtyMBKQXeYSBkkYgEzB3k6XVybIjkF6FzORMpR5gPsbGLjIpTZgeA2nOfW0a5hdNvRAhhtRh9p3rknXynCs1IxUVZCmGmNGGc7MApdXhk7IVbOkeYdyRU3LQltTQfC5qXYNw3MLgng3y6AE9MNQvbpSxkHUuNXec9DTsP9IGXeyqdFxRWS3cX/S61d1xjTLcIhaGQ8Gr70yyJjVKVWN1ffNKLpuB1on3Kmks3NAD+kbjaJsI9sorUuCEWNopq5LG5en3BQGLoEMVZQDUtw8NFiFlH716cD06p12t5HTW/7DFbidyp9KOpB8BqlJGQnmYYRYgNgyEIyhHiZbK4I72wqwmnu5BJSKzJOkuqkP3FkoUfekMk+2pwvQ9vQeivYN6phiGNoMfziBGvKaHkBk7iKHvdlqulPq25v+KZsyYKsnEqdKqL2tmyLA6EER1p8+myn7fgE8c4RCu8Kc0wAAAABJRU5ErkJggg==","edit":false,"name":"Layer 3","opacity":1,"active":false,"unqid":"eiymk","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":4,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAiZJREFUWEfNWFEWwiAMc/c/9Hwg4YWQdgx+9EcdUEJI2ur1+aPXdYLlvu/brb+uayvu1qKTA2RrUzA4+eZBh31BYsZaCMYAOWGxXucToBQMGGFprLJU1jQWBl1lgCwYZoUWM+0ZS50FPQx/d9cVgpGFFUhgnjpGwbvJSgxdw88U0AQmYiVgqOJQfbUr6gcomwZz6nOAsmAMK1gUObOPM2g9gBtjVgcwGSskxrIGoqyfdRN3xTiFspaC0UCifptxsUHCCg5t9TRd04JWekDoBGIEa0Yrg4AjU1gwmRXFMaqd8MRavzLmu2aaqjlLTmI1Nu36cVrQAyj7mndYwJqyl+vhg0tYZ4MrWWvlqgYwWVJ7Qubop/DKYHchz5mYedo0GzcW5zQwJEFNCTWJU/Dhmt6AcsLPCiW7r+nq95aBWanQLsuS5RE+FHp0Ta0W/vTGQJyWdNy1G1EcKZbVwYOAA2tzgQtLR8HeKLBXrUZBBxBaG2CiiU5DUomXUr6rZchHQ9IDraIDrezl9LY0tOerzZW6qwfFQOkteFLFpyI3LEGgtpXg+UExrXsstRDKhLYQbxhhUWs/vNpcQfFR+kn14q5fmvyZGfa22o/bBkFkc9WT1WHntO0EoKBb61ZnQFGNi37q8PUsgQmctVwlTDlwxXKIN2kGo5p3llG0iWCL4yCHIa9ozBSMpvO3gHR+9mtysvbpZqfrQ2ZOA++s3wYT/VEEEDt/GH0BxdBvQrBEJaEAAAAASUVORK5CYII=","edit":false,"name":"Layer 4","opacity":1,"active":true,"unqid":"eiymk","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}}],"active":true,"selectedLayer":4,"unqid":"m1fr0o","preview":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAY1JREFUWEftWNEOwkAI8/7/o2dmhmFcW3rTGB/mk0bkSoF259i2bXv8yWvcYEgnLGbGGB830pmGFkwAcZIxxG4OCQYlcVkK8DVeFdWCQUllwqOle0wtpmOIgsk/7JLk9uyxuYAKPH9f2yrBoKSKdqcAVRgEU5Oy/kdlDHT9nWJsz0XBqAMU2DoriEnGzgSGHfRCTvQmADgMyXmqdrAygCjWKQZt2tSm1VWMmekKqNu2f65sQjBqWJG+OBs0rfCx/lPx0SbGimNKna6grUPsvAdYiZEDCM2KYi3OO8VUZpyDWcyKSMLhZ21aAYVa3BnlMhimKxkoUlnYAmKgsE0haq5LZ6BMN6pQtqKYRW9FL5jGxJY4D1p0tXMVqlJ0CCsCbagqGHpTpxsrJpqZ6gTyJ0bJBn568EL3po72KwLHGDr5FgOT5bq+r3PTbQnaVOhz7Eap2EG0uwvAHqwm11ZW362su+rKAy9dVRyrYMqsimrBuIwogFWtqSB2/0I4/uQw5RTV3rXdg74Rd4NhLD4BqUiGtstfWkUAAAAASUVORK5CYII=","width":"35","height":"35"}],"currentFrame":0,"name":"Untitled","preview":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMA/sfR5H8Fkddasdmnacvx//8745jkhasdASD945kjknhj/AAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAY1JREFUWEftWNEOwkAI8/7/o2dmhmFcW3rTGB/mk0bkSoF259i2bXv8yWvcYEgnLGbGGB830pmGFkwAcZIxxG4OCQYlcVkK8DVeFdWCQUllwqOle0wtpmOIgsk/7JLk9uyxuYAKPH9f2yrBoKSKdqcAVRgEU5Oy/kdlDHT9nWJsz0XBqAMU2DoriEnGzgSGHfRCTvQmADgMyXmqdrAygCjWKQZt2tSm1VWMmekKqNu2f65sQjBqWJG+OBs0rfCx/lPx0SbGimNKna6grUPsvAdYiZEDCM2KYi3OO8VUZpyDWcyKSMLhZ21aAYVa3BnlMhimKxkoUlnYAmKgsE0haq5LZ6BMN6pQtqKYRW9FL5jGxJY4D1p0tXMVqlJ0CCsCbagqGHpTpxsrJpqZ6gTyJ0bJBn568EL3po72KwLHGDr5FgOT5bq+r3PTbQnaVOhz7Eap2EG0uwvAHqwm11ZW362su+rKAy9dVRyrYMqsimrBuIwogFWtqSB2/0I4/uQw5RTV3rXdg74Rd4NhLD4BqUiGtstfWkUAAAAASUVORK5CYII=","palette_id":false} \ No newline at end of file diff --git a/applications/plugins/dice2/sources/d8.pixil b/applications/plugins/dice2/sources/d8.pixil new file mode 100644 index 000000000..4f7ff440e --- /dev/null +++ b/applications/plugins/dice2/sources/d8.pixil @@ -0,0 +1 @@ +{"application":"pixil","version":"2.6.1","website":"pixilart.com","author":"https://www.pixilart.com","contact":"support@pixilart.com","width":"35","height":"35","colors":{"default":["000000","ffffff","f44336","E91E63","9C27B0","673AB7","3F51B5","2196F3","03A9F4","00BCD4","009688","4CAF50","8BC34A","CDDC39","FFEB3B","FFC107","FF9800","FF5722","795548","9E9E9E","607D8B","ffebee","ffcdd2","ef9a9a","e57373","ef5350","e53935","d32f2f","c62828","b71c1c","ff8a80","ff5252","ff1744","d50000","fce4ec","f8bbd0","f48fb1","f06292","ec407a","e91e63","d81b60","c2185b","ad1457","880e4f","ff80ab","ff4081","f50057","c51162","f3e5f5","e1bee7","ce93d8","ba68c8","ab47bc","9c27b0","8e24aa","7b1fa2","6a1b9a","4a148c","ea80fc","e040fb","d500f9","aa00ff","ede7f6","d1c4e9","b39ddb","9575cd","7e57c2","673ab7","5e35b1","512da8","4527a0","311b92","b388ff","7c4dff","651fff","6200ea","e8eaf6","c5cae9","9fa8da","7986cb","5c6bc0","3f51b5","3949ab","303f9f","283593","1a237e","8c9eff","536dfe","3d5afe","304ffe","e3f2fd","bbdefb","90caf9","64b5f6","42a5f5","2196f3","1e88e5","1976d2","1565c0","0d47a1","82b1ff","448aff","2979ff","2962ff","e1f5fe","b3e5fc","81d4fa","4fc3f7","29b6f6","03a9f4","039be5","0288d1","0277bd","01579b","80d8ff","40c4ff","00b0ff","0091ea","e0f7fa","b2ebf2","80deea","4dd0e1","26c6da","00bcd4","00acc1","0097a7","00838f","006064","84ffff","18ffff","00e5ff","00b8d4","e0f2f1","b2dfdb","80cbc4","4db6ac","26a69a","009688","00897b","00796b","00695c","004d40","a7ffeb","64ffda","1de9b6","00bfa5","e8f5e9","c8e6c9","a5d6a7","81c784","66bb6a","4caf50","43a047","388e3c","2e7d32","1b5e20","b9f6ca","69f0ae","00e676","00c853","f1f8e9","dcedc8","c5e1a5","aed581","9ccc65","8bc34a","7cb342","689f38","558b2f","33691e","ccff90","b2ff59","76ff03","64dd17","f9fbe7","f0f4c3","e6ee9c","dce775","d4e157","cddc39","c0ca33","afb42b","9e9d24","827717","f4ff81","eeff41","c6ff00","aeea00","fffde7","fff9c4","fff59d","fff176","ffee58","ffeb3b","fdd835","fbc02d","f9a825","f57f17","ffff8d","ffff00","ffea00","ffd600","fff8e1","ffecb3","ffe082","ffd54f","ffca28","ffc107","ffb300","ffa000","ff8f00","ff6f00","ffe57f","ffd740","ffc400","ffab00","fff3e0","ffe0b2","ffcc80","ffb74d","ffa726","ff9800","fb8c00","f57c00","ef6c00","e65100","ffd180","ffab40","ff9100","ff6d00","fbe9e7","ffccbc","ffab91","ff8a65","ff7043","ff5722","f4511e","e64a19","d84315","bf360c","ff9e80","ff6e40","ff3d00","dd2c00","efebe9","d7ccc8","bcaaa4","a1887f","8d6e63","795548","6d4c41","5d4037","4e342e","3e2723","fafafa","f5f5f5","eeeeee","e0e0e0","bdbdbd","9e9e9e","757575","616161","424242","212121","eceff1","cfd8dc","b0bec5","90a4ae","78909c","607d8b","546e7a","455a64","37474f","263238"],"simple":["ffffff","d4d4d4","a1a1a1","787878","545454","303030","000000","edc5c5","e68383","ff0000","de2424","ad3636","823737","592b2b","f5d2ee","eb8dd7","f700b9","bf1f97","9c277f","732761","4f2445","e2bcf7","bf79e8","9d00ff","8330ba","6d3096","502c69","351b47","c5c3f0","736feb","0905f7","2e2eb0","2d2d80","252554","090936","c7e2ed","6ac3e6","00bbff","279ac4","347c96","2d5b6b","103947","bbf0d9","6febb3","00ff88","2eb878","349166","2b694c","0c3d25","c2edc0","76ed70","0dff00","36c72c","408c3b","315c2e","144511","d6edbb","b5eb73","8cff00","89c93a","6f8f44","4b632a","2a400c","f1f2bf","eef069","ffff00","baba30","91913f","5e5e2b","3b3b09","ffdeb8","f2ae61","ff8400","c48037","85623d","573e25","3d2309","fcbbae","ff8066","ff2b00","cc553d","9c5b4e","61372e","36130b"],"common":["000000","FFFFFF","7F7F7F","a1a1a1","C3C3C3","c40424","880015","B97A57","dba88c","ED1C24","f75b63","f26f9b","FF7F27","f7ab79","FFC90E","FFF200","cfc532","EFE4B0","1ee656","0c6624","22B14C","B5E61D","5487ff","00A2E8","99D9EA","3F48CC","7f86e3","7092BE","720899","cd55cf","A349A4","C8BFE7","ffffff"],"skin tones":["ffe0bd","ffdbac","ffcd94","eac086","e0ac69","f1c27d","ffad60","c68642","8d5524","896347","765339","613D24","4C2D17","391E0B","351606","2D1304","180A01","090300"]},"frames":[{"name":"","speed":100,"layers":[{"id":0,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAFtJREFUWEftlEEOACAIw9z/Hz0foBwWTdCknomQUibbHo88MUyxCchUikIGMml84QzO4ExKAGdSYkvOSGrLnrbGO2oMwzUdX1P6wc16BEbg1CecwRmcSQl84cwEPw2PmDu06wcAAAAASUVORK5CYII=","edit":false,"name":"Background","opacity":"1","active":true,"unqid":"f9mfzy5","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":1,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAhRJREFUWEfNmFtu7DAMQ5v9LzqFDUugGephX1yg/ZlM09jHFPVIn59//Hnf9+Ulnud5bpa9esg2MhDc29hugK5hFAhAzstToCuYQpEJMhT67zCRImidEbabcB0pI0DG8x8Pm4dOgdowDAJhcBhSxMPV9U8Jg8cWJ0Z/eDaDIkdAKUzgDw8NK7HUYoC2f0KYBGQmy8hc84tS7EadFIY3sTislJ2V19TgT7u3/NIKl4QZqmT+sA3o9LyWwzJQVH8+MAgyQgHfZ1iUGqgC9qQifJ+9t18QyDzQWtyaoQMxFJ4+ClFVfxyGFUGTmlkjAMwiVCaqRQTrDBIGFkdlMGRu3LW5KaY+LfswxWX/CmFAdgylzy4ILMK7bRYlBLeL0DNcJxKPzDVsQ0xxaANbXYqyK80mBOJrGuY2haEeoYX8Wqw1n5cwqlCJ09jzn7GTTayIMLOs7pRFj9NUhc/qkZTBHB6MxTiEhTBk4NmqsDHebqxUT5VBQ1LqenpWvmALQOPdBjJsDe2uHRU2gN1GvuBthVvKtn8IY+qYrCpMa0MvjFwsaQzZQCDtPeIpjAgXFjPMRpVRYZNVIDK12ZjRa0mhSjV8SRFKZThcBkshqZRpjZ4tGAWUVeSbkbMVJqqmUwFRPfF1ZZuBTt6d2spAeDagbAA7ATlWpgE0I0otpH3g9h92sgzNHaVv2kaym9W9P/P/mQr05v4vDlEtQnsbdYsAAAAASUVORK5CYII=","edit":false,"name":"Layer 1","opacity":"1","active":false,"unqid":"rly08e","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":2,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAchJREFUWEfNmNtygzAQQ8P/fzQdezAjhGSv3DwkL02L8R6kvZgenx/6HDss53me7b7jeN9+XWrX4r3jGxpEgxmxRnCE2wWKYYwqbZ+u1vjsAG3BKFUGBLqTAkUwqAoEUmpsKRTDoCqr76llZRinCiewUuxK+s42q7IIxinB+dKAZmsdUAlmpUoLjIq47yuFyjDqSZvqomkO9oc6lT60hJlU0HFHBSIu+6EaN+TLykf8EswqV1gdo2K3kq7VYVJVEIrzCMsc1MpgXK6osbAq821lZqrgCGKLuM+o32NleDJDAvbEvXpFG45jSPafNI9kZZVhsEKE793j4DzTJzn3nTKMOq+oCmAbCRwT86XOAORO/CrtVQVdYA+FqIrGnn0r1XcimEpfmQ08tpqtimHc9BXtv/QnVfYlm9D/UqRwUTmB3WE7jGeXR+NgVrbfAnJnYzkoK60ek9JBugnuTnx2as+AOKdUN1Z9yFXRfVKcSa9KFKcvHTfdWLhDrF5dlucZbP94WlO9Ax8MGx6ttTFLMKDG6x3b9KPHG+ZKkZJNykJnnXrnriqyDcMqsXVslasc9aCRTTtKJf8a+TeMUgqqLNo/WvytDuz2+QMl8/wzbBarJwAAAABJRU5ErkJggg==","edit":false,"name":"Layer 2","opacity":"1","active":true,"unqid":"tqny2","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":3,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAgdJREFUWEfNmEF2AyEMQ8v9Dz15UOwnhAyGySLdpZ2BH1kWpuXv8ud5nodfLaWUy+Xaa1cvGwjubWxvgI5hFIip8RboCsYUgc29Om+AjmBQFbQMW+UWKA3D5WH/dqC6XjP2DVAKJgKpAFSqUp8VZUzts31oBYLGjXxE5VzudwTDhq2fWR37XY8Nz6JM2ZYwB+Vp5SLf2NppoBBmVx5UxdSBrmrewTTGMkbBuIRZ5Am/53sLU7PJTcVpbwmj8oTBFt8aS2ZHTluyK+KtzwpNMKo8ycRlCAfp4NJDCDTAbHwyZAgqs+kqhLJQHMLRgCaYhE+qzLwoL95YqTSuDHTe4B+H2fmk0mO6CmUaEG5E5el8/02mumuACVRxCAw9au3hW9tmDBaEpjNMMGosYBXsM3XDEG4qmSNFfD04X6YDToyVu6lyKpUaNaKjQZaJdxTj7vAI5geND1PgkbHHdVAZlhE/nwIK37hq/W/rBKac8fNFnCXTzQAVAU+hHcLknTyjvMNjADzjQgFoCJgdtrbHgQEJ02GmTK3NZbqeZ8R1BLvE05Z8JBMW1dzdqY7mGTE8hercXPCOJz1uY1BHHgeZ8oQGFi3cjLkbIzZD1fJLp2F6yw5AdkdXUZDtHBXlKeIFkLXzMDidlAah0jArhfqC1zfJozIh/U/9FyJQ6Opuzb45KpMy3Td/9wFxxy9CEK7yBQAAAABJRU5ErkJggg==","edit":false,"name":"Layer 3","opacity":"1","active":false,"unqid":"rly08e","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":4,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAcZJREFUWEfNWFsOwzAIW+5/6E6JAmLU5pF10vYzqXk5xmDa8fqj3zjBcl3XNdeNcV++h+ZYe+/2Ag9EDrfg5rOfgwkYmZdabM3fKTtlZhAQy4oAkeicsFMCEwGx0jCMHLGTgmGh2TfXsDhG7PP0DGU1yqYIiAjWC/gbdihqlDXs9kQvbXYgGJa+Ewy5eeV5Gq7bhAiIS9sVKRDmleJAU2dgfPGUjS0rFpgFtIud3GlV6WrdgcwgbaDbMvGfZhYFY2n24UHF7gl2yswg4XqzdOFoa6fEDNMKMWa7Z0s7J2CGyzgxSf2f4yLkTmalYDwrM52b/UyZnVAzUV2xgEDqr303Q8y/bmenAgZNkxY7vbJJJdNUreFO3WmDMT6U+lrXs0LN+M0ih6+M+fLgW9MSmMpBnTliEWUwzHs6h6K5UVuauva3h6P1zDjTfqbi1M6XNJWR5iIHDzPCuy/wno+qaw8HxTJtJdL0ZN3/bqymGS5A6GVOwLkxembYfbFXlOS1VqNWBaGMZgIlPiTh+dAHYTFtN8tgxGNY75I8LwNRj8nYYYDsum5IYA2qADFCvH0KeQJEK0yOAf3aYFK5FQ5GwCObdNiN5r4BawL8M515sSoAAAAASUVORK5CYII=","edit":false,"name":"Layer 4","opacity":"1","active":false,"unqid":"tqny2","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}}],"active":true,"selectedLayer":2,"unqid":"jv591w","preview":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAYNJREFUWEfNmEsSwjAMQ8n9D12mzIQxqmXJbhew4VvyoihSYB3Hcbz+5LbuwKy1LtO4M7cxzAaJg2evdUS/DXMOdgKhShOFRjCVAhGqCzSG2QNlg0+B2jBRFfV4+8VVaAQTVUEDZ4ptXykzt2CYEnsQpZRSqA2TqXJCOK8rhWwYpUpUB5eKPcdla8Ewr8QvxW2/VXNyyIJxVam8g+GY+ceGcVRBhVgWRY/9XKOKUqnCAg5N/YgybKecX161duadCNReJkeVXZLxXu2s0TJVqihl4vvotzaMUgVnTweAQKz6iu4mJ1WrrsF8cYIvhXFVUcXHlorVAoVh7esAZJ9h7S5zhq3/FOTSQcFHFoxq2ClYeWRlCZwF2hQAr2PnmrKbqqh3lGMNziYlizIDyjwVt3Jm1mp5vpmlihKPBVloIUhWkpaSLkwGVW1//GGnzr+fTOrCYCdVZ5Yq+jPfjGAqlRissxNvwTA/dRVpG9iaWfiLxPHIJZknnnHAJp95ZJkmA2fXvAFTp/+nAJYHZgAAAABJRU5ErkJggg==","width":"35","height":"35","old_width":"35","old_height":"35"}],"currentFrame":0,"name":"Untitled","preview":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACMA/sfR5H8Fkddasdmnacvx//8745jkhasdASD945kjknhj/AAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAYNJREFUWEfNmEsSwjAMQ8n9D12mzIQxqmXJbhew4VvyoihSYB3Hcbz+5LbuwKy1LtO4M7cxzAaJg2evdUS/DXMOdgKhShOFRjCVAhGqCzSG2QNlg0+B2jBRFfV4+8VVaAQTVUEDZ4ptXykzt2CYEnsQpZRSqA2TqXJCOK8rhWwYpUpUB5eKPcdla8Ewr8QvxW2/VXNyyIJxVam8g+GY+ceGcVRBhVgWRY/9XKOKUqnCAg5N/YgybKecX161duadCNReJkeVXZLxXu2s0TJVqihl4vvotzaMUgVnTweAQKz6iu4mJ1WrrsF8cYIvhXFVUcXHlorVAoVh7esAZJ9h7S5zhq3/FOTSQcFHFoxq2ClYeWRlCZwF2hQAr2PnmrKbqqh3lGMNziYlizIDyjwVt3Jm1mp5vpmlihKPBVloIUhWkpaSLkwGVW1//GGnzr+fTOrCYCdVZ5Yq+jPfjGAqlRissxNvwTA/dRVpG9iaWfiLxPHIJZknnnHAJp95ZJkmA2fXvAFTp/+nAJYHZgAAAABJRU5ErkJggg==","palette_id":false} \ No newline at end of file diff --git a/applications/plugins/dice2/sources/flipper-screen.png b/applications/plugins/dice2/sources/flipper-screen.png new file mode 100644 index 000000000..af759a20f Binary files /dev/null and b/applications/plugins/dice2/sources/flipper-screen.png differ diff --git a/applications/plugins/dice2/sources/main-screen.png b/applications/plugins/dice2/sources/main-screen.png new file mode 100644 index 000000000..20a4e9c2c Binary files /dev/null and b/applications/plugins/dice2/sources/main-screen.png differ diff --git a/applications/plugins/dice2/sources/result_border.pixil b/applications/plugins/dice2/sources/result_border.pixil new file mode 100644 index 000000000..afe25bf07 --- /dev/null +++ b/applications/plugins/dice2/sources/result_border.pixil @@ -0,0 +1 @@ +{"application":"pixil","version":"2.6.1","website":"pixilart.com","author":"https://www.pixilart.com","contact":"support@pixilart.com","width":"40","height":"30","colors":{"default":["000000","ffffff","f44336","E91E63","9C27B0","673AB7","3F51B5","2196F3","03A9F4","00BCD4","009688","4CAF50","8BC34A","CDDC39","FFEB3B","FFC107","FF9800","FF5722","795548","9E9E9E","607D8B","ffebee","ffcdd2","ef9a9a","e57373","ef5350","e53935","d32f2f","c62828","b71c1c","ff8a80","ff5252","ff1744","d50000","fce4ec","f8bbd0","f48fb1","f06292","ec407a","e91e63","d81b60","c2185b","ad1457","880e4f","ff80ab","ff4081","f50057","c51162","f3e5f5","e1bee7","ce93d8","ba68c8","ab47bc","9c27b0","8e24aa","7b1fa2","6a1b9a","4a148c","ea80fc","e040fb","d500f9","aa00ff","ede7f6","d1c4e9","b39ddb","9575cd","7e57c2","673ab7","5e35b1","512da8","4527a0","311b92","b388ff","7c4dff","651fff","6200ea","e8eaf6","c5cae9","9fa8da","7986cb","5c6bc0","3f51b5","3949ab","303f9f","283593","1a237e","8c9eff","536dfe","3d5afe","304ffe","e3f2fd","bbdefb","90caf9","64b5f6","42a5f5","2196f3","1e88e5","1976d2","1565c0","0d47a1","82b1ff","448aff","2979ff","2962ff","e1f5fe","b3e5fc","81d4fa","4fc3f7","29b6f6","03a9f4","039be5","0288d1","0277bd","01579b","80d8ff","40c4ff","00b0ff","0091ea","e0f7fa","b2ebf2","80deea","4dd0e1","26c6da","00bcd4","00acc1","0097a7","00838f","006064","84ffff","18ffff","00e5ff","00b8d4","e0f2f1","b2dfdb","80cbc4","4db6ac","26a69a","009688","00897b","00796b","00695c","004d40","a7ffeb","64ffda","1de9b6","00bfa5","e8f5e9","c8e6c9","a5d6a7","81c784","66bb6a","4caf50","43a047","388e3c","2e7d32","1b5e20","b9f6ca","69f0ae","00e676","00c853","f1f8e9","dcedc8","c5e1a5","aed581","9ccc65","8bc34a","7cb342","689f38","558b2f","33691e","ccff90","b2ff59","76ff03","64dd17","f9fbe7","f0f4c3","e6ee9c","dce775","d4e157","cddc39","c0ca33","afb42b","9e9d24","827717","f4ff81","eeff41","c6ff00","aeea00","fffde7","fff9c4","fff59d","fff176","ffee58","ffeb3b","fdd835","fbc02d","f9a825","f57f17","ffff8d","ffff00","ffea00","ffd600","fff8e1","ffecb3","ffe082","ffd54f","ffca28","ffc107","ffb300","ffa000","ff8f00","ff6f00","ffe57f","ffd740","ffc400","ffab00","fff3e0","ffe0b2","ffcc80","ffb74d","ffa726","ff9800","fb8c00","f57c00","ef6c00","e65100","ffd180","ffab40","ff9100","ff6d00","fbe9e7","ffccbc","ffab91","ff8a65","ff7043","ff5722","f4511e","e64a19","d84315","bf360c","ff9e80","ff6e40","ff3d00","dd2c00","efebe9","d7ccc8","bcaaa4","a1887f","8d6e63","795548","6d4c41","5d4037","4e342e","3e2723","fafafa","f5f5f5","eeeeee","e0e0e0","bdbdbd","9e9e9e","757575","616161","424242","212121","eceff1","cfd8dc","b0bec5","90a4ae","78909c","607d8b","546e7a","455a64","37474f","263238"],"simple":["ffffff","d4d4d4","a1a1a1","787878","545454","303030","000000","edc5c5","e68383","ff0000","de2424","ad3636","823737","592b2b","f5d2ee","eb8dd7","f700b9","bf1f97","9c277f","732761","4f2445","e2bcf7","bf79e8","9d00ff","8330ba","6d3096","502c69","351b47","c5c3f0","736feb","0905f7","2e2eb0","2d2d80","252554","090936","c7e2ed","6ac3e6","00bbff","279ac4","347c96","2d5b6b","103947","bbf0d9","6febb3","00ff88","2eb878","349166","2b694c","0c3d25","c2edc0","76ed70","0dff00","36c72c","408c3b","315c2e","144511","d6edbb","b5eb73","8cff00","89c93a","6f8f44","4b632a","2a400c","f1f2bf","eef069","ffff00","baba30","91913f","5e5e2b","3b3b09","ffdeb8","f2ae61","ff8400","c48037","85623d","573e25","3d2309","fcbbae","ff8066","ff2b00","cc553d","9c5b4e","61372e","36130b"],"common":["000000","FFFFFF","7F7F7F","a1a1a1","C3C3C3","c40424","880015","B97A57","dba88c","ED1C24","f75b63","f26f9b","FF7F27","f7ab79","FFC90E","FFF200","cfc532","EFE4B0","1ee656","0c6624","22B14C","B5E61D","5487ff","00A2E8","99D9EA","3F48CC","7f86e3","7092BE","720899","cd55cf","A349A4","C8BFE7","ffffff"],"skin tones":["ffe0bd","ffdbac","ffcd94","eac086","e0ac69","f1c27d","ffad60","c68642","8d5524","896347","765339","613D24","4C2D17","391E0B","351606","2D1304","180A01","090300"]},"frames":[{"name":"","speed":100,"layers":[{"id":0,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAYAAABe3VzdAAAAAXNSR0IArs4c6QAAAExJREFUWEft0rENADAIBDHYf2gyxDUpnP6kyPze3c3Hb30wXodgBByCBKtA7W2QYBWovQ0SrAK1t0GCVaD2NkiwCtTeBglWgdp/v8EHgvp3pxVCCEMAAAAASUVORK5CYII=","edit":false,"name":"Background","opacity":"1","active":true,"unqid":"q5hq79","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":1,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAYAAABe3VzdAAAAAXNSR0IArs4c6QAAAMxJREFUWEftl9EOgCAIRfH/P7omC4clOsQcLXxpK4UjKNwStMcBAAkA6ClMW/K66ytDSGMHHPkWfX0W0H2K3QPmsxFnkN1O9SVxn2KXgBnK08ASSHWwByfVSu2GZuwkamc8cr3iPYowQa+wgb7ugBbDI3jN95IdDugFjvfnKoIBqMnx1cm2aT4lW5l+7BKlATgbAeu6SPF/IsiFg3XXq9Zju4tWZwjnI4Ke0txUM7RZi2h4RQ/SL6aUjRkl3LI1YwcVdaW/DGdm9VJkOwHIXkgOkN1G8QAAAABJRU5ErkJggg==","edit":false,"name":"Layer 1","opacity":"1","active":true,"unqid":"d3rvnh","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}}],"active":true,"selectedLayer":1,"unqid":"h9fxa","preview":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAYAAABe3VzdAAAAAXNSR0IArs4c6QAAANFJREFUWEftllkOgDAIROH+h66BSNMqaheXMcEfo1Z8zGCBU0qJNgczk9y28/b5nddX32IPUADegLNEz771T8Ar2T+3GB4wavBg1/BKx/1J4C2GBBQopMP6h1p8Buc0Gs2jN6GRONrNiKhqdUeBWtQ16Dti5C5TAs4EbkmgdU3pTlYQBa7sz1pKpmAAtvq7rjObVUE09UqbA7DT2Wq52BwKhoIzCsy+m2tQAqFtNdU+GIADXu8URFLRnWYsyZlafGQehJ+obVLoHeEHyqrrFXNyAfXA6dp8XWGDAAAAAElFTkSuQmCC","width":"40","height":"30"}],"currentFrame":0,"name":"Untitled","preview":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAACgA/sfR5H8Fkddasdmnacvx/AAAeCAYAAABe3VzdAAAAAXNSR0IArs4c6QAAANFJREFUWEftllkOgDAIROH+h66BSNMqaheXMcEfo1Z8zGCBU0qJNgczk9y28/b5nddX32IPUADegLNEz771T8Ar2T+3GB4wavBg1/BKx/1J4C2GBBQopMP6h1p8Buc0Gs2jN6GRONrNiKhqdUeBWtQ16Dti5C5TAs4EbkmgdU3pTlYQBa7sz1pKpmAAtvq7rjObVUE09UqbA7DT2Wq52BwKhoIzCsy+m2tQAqFtNdU+GIADXu8URFLRnWYsyZlafGQehJ+obVLoHeEHyqrrFXNyAfXA6dp8XWGDAAAAAElFTkSuQmCC","palette_id":false} \ No newline at end of file diff --git a/applications/plugins/dice2/sources/roll-screen.png b/applications/plugins/dice2/sources/roll-screen.png new file mode 100644 index 000000000..a1f13cec1 Binary files /dev/null and b/applications/plugins/dice2/sources/roll-screen.png differ diff --git a/applications/plugins/dtmf_dolphin/LICENSE b/applications/plugins/dtmf_dolphin/LICENSE new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/applications/plugins/dtmf_dolphin/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/applications/plugins/dtmf_dolphin/README.md b/applications/plugins/dtmf_dolphin/README.md new file mode 100644 index 000000000..5c9561f4b --- /dev/null +++ b/applications/plugins/dtmf_dolphin/README.md @@ -0,0 +1,16 @@ +![Image](assets/dialer.jpg) + +## DTMF Dolphin + +DTMF (Dual-Tone Multi-Frequency) dialer, Bluebox, and Redbox. + +Now in a release-ready state for both Dialer, Bluebox, and Redbox (US/UK) functionality! + +Please note that using the current tone output method, the 2600 tone is scaled about 33 Hz higher than it should be. This is a limitation of the current sample rate. + +### Educational Links: + +* http://www.phrack.org/issues/25/7.html#article +* https://en.wikipedia.org/wiki/Dual-tone_multi-frequency_signaling +* https://en.wikipedia.org/wiki/Blue_box +* https://en.wikipedia.org/wiki/Red_box_(phreaking) diff --git a/applications/plugins/dtmf_dolphin/application.fam b/applications/plugins/dtmf_dolphin/application.fam new file mode 100644 index 000000000..98fbe7363 --- /dev/null +++ b/applications/plugins/dtmf_dolphin/application.fam @@ -0,0 +1,16 @@ +App( + appid="DTMF_Dolphin", + name="DTMF Dolphin", + apptype=FlipperAppType.EXTERNAL, + entry_point="dtmf_dolphin_app", + cdefines=["DTMF_DOLPHIN"], + requires=[ + "storage", + "gui", + "dialogs", + ], + fap_icon="phone.png", + stack_size=8 * 1024, + order=20, + fap_category="Tools", +) diff --git a/applications/plugins/dtmf_dolphin/assets/dialer.jpg b/applications/plugins/dtmf_dolphin/assets/dialer.jpg new file mode 100644 index 000000000..ff6fad7a8 Binary files /dev/null and b/applications/plugins/dtmf_dolphin/assets/dialer.jpg differ diff --git a/applications/plugins/dtmf_dolphin/dtmf_dolphin.c b/applications/plugins/dtmf_dolphin/dtmf_dolphin.c new file mode 100644 index 000000000..c1b10defa --- /dev/null +++ b/applications/plugins/dtmf_dolphin/dtmf_dolphin.c @@ -0,0 +1,89 @@ +#include "dtmf_dolphin_i.h" + +#include +#include + +static bool dtmf_dolphin_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + DTMFDolphinApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool dtmf_dolphin_app_back_event_callback(void* context) { + furi_assert(context); + DTMFDolphinApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void dtmf_dolphin_app_tick_event_callback(void* context) { + furi_assert(context); + DTMFDolphinApp* app = context; + + scene_manager_handle_tick_event(app->scene_manager); +} + +static DTMFDolphinApp* app_alloc() { + DTMFDolphinApp* app = malloc(sizeof(DTMFDolphinApp)); + + app->gui = furi_record_open(RECORD_GUI); + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&dtmf_dolphin_scene_handlers, app); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, dtmf_dolphin_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, dtmf_dolphin_app_back_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, dtmf_dolphin_app_tick_event_callback, 100); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + app->main_menu_list = variable_item_list_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + DTMFDolphinViewMainMenu, + variable_item_list_get_view(app->main_menu_list)); + + app->dtmf_dolphin_dialer = dtmf_dolphin_dialer_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + DTMFDolphinViewDialer, + dtmf_dolphin_dialer_get_view(app->dtmf_dolphin_dialer)); + + app->notification = furi_record_open(RECORD_NOTIFICATION); + notification_message(app->notification, &sequence_display_backlight_enforce_on); + + scene_manager_next_scene(app->scene_manager, DTMFDolphinSceneStart); + + return app; +} + +static void app_free(DTMFDolphinApp* app) { + furi_assert(app); + view_dispatcher_remove_view(app->view_dispatcher, DTMFDolphinViewMainMenu); + view_dispatcher_remove_view(app->view_dispatcher, DTMFDolphinViewDialer); + variable_item_list_free(app->main_menu_list); + + dtmf_dolphin_dialer_free(app->dtmf_dolphin_dialer); + + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + notification_message(app->notification, &sequence_display_backlight_enforce_auto); + + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_NOTIFICATION); + free(app); +} + +int32_t dtmf_dolphin_app(void* p) { + UNUSED(p); + DTMFDolphinApp* app = app_alloc(); + + view_dispatcher_run(app->view_dispatcher); + + app_free(app); + return 0; +} \ No newline at end of file diff --git a/applications/plugins/dtmf_dolphin/dtmf_dolphin_audio.c b/applications/plugins/dtmf_dolphin/dtmf_dolphin_audio.c new file mode 100644 index 000000000..4b84ceb97 --- /dev/null +++ b/applications/plugins/dtmf_dolphin/dtmf_dolphin_audio.c @@ -0,0 +1,265 @@ +#include "dtmf_dolphin_audio.h" + +DTMFDolphinAudio* current_player; + +static void dtmf_dolphin_audio_dma_isr(void* ctx) { + FuriMessageQueue* event_queue = ctx; + + if(LL_DMA_IsActiveFlag_HT1(DMA1)) { + LL_DMA_ClearFlag_HT1(DMA1); + + DTMFDolphinCustomEvent event = {.type = DTMFDolphinEventDMAHalfTransfer}; + furi_message_queue_put(event_queue, &event, 0); + } + + if(LL_DMA_IsActiveFlag_TC1(DMA1)) { + LL_DMA_ClearFlag_TC1(DMA1); + + DTMFDolphinCustomEvent event = {.type = DTMFDolphinEventDMAFullTransfer}; + furi_message_queue_put(event_queue, &event, 0); + } +} + +void dtmf_dolphin_audio_clear_samples(DTMFDolphinAudio* player) { + for(size_t i = 0; i < player->buffer_length; i++) { + player->sample_buffer[i] = 0; + } +} + +DTMFDolphinOsc* dtmf_dolphin_osc_alloc() { + DTMFDolphinOsc* osc = malloc(sizeof(DTMFDolphinOsc)); + osc->cached_freq = 0; + osc->offset = 0; + osc->period = 0; + osc->lookup_table = NULL; + return osc; +} + +DTMFDolphinPulseFilter* dtmf_dolphin_pulse_filter_alloc() { + DTMFDolphinPulseFilter* pf = malloc(sizeof(DTMFDolphinPulseFilter)); + pf->duration = 0; + pf->period = 0; + pf->offset = 0; + pf->lookup_table = NULL; + return pf; +} + +DTMFDolphinAudio* dtmf_dolphin_audio_alloc() { + DTMFDolphinAudio* player = malloc(sizeof(DTMFDolphinAudio)); + player->buffer_length = SAMPLE_BUFFER_LENGTH; + player->half_buffer_length = SAMPLE_BUFFER_LENGTH / 2; + player->sample_buffer = malloc(sizeof(uint16_t) * player->buffer_length); + player->osc1 = dtmf_dolphin_osc_alloc(); + player->osc2 = dtmf_dolphin_osc_alloc(); + player->volume = 1.0f; + player->queue = furi_message_queue_alloc(10, sizeof(DTMFDolphinCustomEvent)); + player->filter = dtmf_dolphin_pulse_filter_alloc(); + player->playing = false; + dtmf_dolphin_audio_clear_samples(player); + + return player; +} + +size_t calc_waveform_period(float freq) { + if(!freq) { + return 0; + } + // DMA Rate calculation, thanks to Dr_Zlo + float dma_rate = CPU_CLOCK_FREQ / 2 / DTMF_DOLPHIN_HAL_DMA_PRESCALER / + (DTMF_DOLPHIN_HAL_DMA_AUTORELOAD + 1); + + // Using a constant scaling modifier, which likely represents + // the combined system overhead and isr latency. + return (uint16_t)dma_rate * 2 / freq * 0.801923; +} + +void osc_generate_lookup_table(DTMFDolphinOsc* osc, float freq) { + if(osc->lookup_table != NULL) { + free(osc->lookup_table); + } + osc->offset = 0; + osc->cached_freq = freq; + osc->period = calc_waveform_period(freq); + if(!osc->period) { + osc->lookup_table = NULL; + return; + } + osc->lookup_table = malloc(sizeof(float) * osc->period); + + for(size_t i = 0; i < osc->period; i++) { + osc->lookup_table[i] = sin(i * PERIOD_2_PI / osc->period) + 1; + } +} + +void filter_generate_lookup_table( + DTMFDolphinPulseFilter* pf, + uint16_t pulses, + uint16_t pulse_ms, + uint16_t gap_ms) { + if(pf->lookup_table != NULL) { + free(pf->lookup_table); + } + pf->offset = 0; + + uint16_t gap_period = calc_waveform_period(1000 / (float)gap_ms); + uint16_t pulse_period = calc_waveform_period(1000 / (float)pulse_ms); + pf->period = pulse_period + gap_period; + + if(!pf->period) { + pf->lookup_table = NULL; + return; + } + pf->duration = pf->period * pulses; + pf->lookup_table = malloc(sizeof(bool) * pf->duration); + + for(size_t i = 0; i < pf->duration; i++) { + pf->lookup_table[i] = i % pf->period < pulse_period; + } +} + +float sample_frame(DTMFDolphinOsc* osc) { + float frame = 0.0; + + if(osc->period) { + frame = osc->lookup_table[osc->offset]; + osc->offset = (osc->offset + 1) % osc->period; + } + + return frame; +} + +bool sample_filter(DTMFDolphinPulseFilter* pf) { + bool frame = true; + + if(pf->duration) { + if(pf->offset < pf->duration) { + frame = pf->lookup_table[pf->offset]; + pf->offset = pf->offset + 1; + } else { + frame = false; + } + } + + return frame; +} + +void dtmf_dolphin_osc_free(DTMFDolphinOsc* osc) { + if(osc->lookup_table != NULL) { + free(osc->lookup_table); + } + free(osc); +} + +void dtmf_dolphin_filter_free(DTMFDolphinPulseFilter* pf) { + if(pf->lookup_table != NULL) { + free(pf->lookup_table); + } + free(pf); +} + +void dtmf_dolphin_audio_free(DTMFDolphinAudio* player) { + furi_message_queue_free(player->queue); + dtmf_dolphin_osc_free(player->osc1); + dtmf_dolphin_osc_free(player->osc2); + dtmf_dolphin_filter_free(player->filter); + free(player->sample_buffer); + free(player); + current_player = NULL; +} + +bool generate_waveform(DTMFDolphinAudio* player, uint16_t buffer_index) { + uint16_t* sample_buffer_start = &player->sample_buffer[buffer_index]; + + for(size_t i = 0; i < player->half_buffer_length; i++) { + float data = 0; + if(player->osc2->period) { + data = (sample_frame(player->osc1) / 2) + (sample_frame(player->osc2) / 2); + } else { + data = (sample_frame(player->osc1)); + } + data *= sample_filter(player->filter) ? player->volume : 0.0; + data *= UINT8_MAX / 2; // scale -128..127 + data += UINT8_MAX / 2; // to unsigned + + if(data < 0) { + data = 0; + } + + if(data > 255) { + data = 255; + } + + sample_buffer_start[i] = data; + } + + return true; +} + +bool dtmf_dolphin_audio_play_tones( + float freq1, + float freq2, + uint16_t pulses, + uint16_t pulse_ms, + uint16_t gap_ms) { + if(current_player != NULL && current_player->playing) { + // Cannot start playing while still playing something else + return false; + } + current_player = dtmf_dolphin_audio_alloc(); + + osc_generate_lookup_table(current_player->osc1, freq1); + osc_generate_lookup_table(current_player->osc2, freq2); + filter_generate_lookup_table(current_player->filter, pulses, pulse_ms, gap_ms); + + generate_waveform(current_player, 0); + generate_waveform(current_player, current_player->half_buffer_length); + + dtmf_dolphin_speaker_init(); + dtmf_dolphin_dma_init((uint32_t)current_player->sample_buffer, current_player->buffer_length); + + furi_hal_interrupt_set_isr( + FuriHalInterruptIdDma1Ch1, dtmf_dolphin_audio_dma_isr, current_player->queue); + + dtmf_dolphin_dma_start(); + dtmf_dolphin_speaker_start(); + current_player->playing = true; + return true; +} + +bool dtmf_dolphin_audio_stop_tones() { + if(current_player != NULL && !current_player->playing) { + // Can't stop a player that isn't playing. + return false; + } + while(current_player->filter->offset > 0 && + current_player->filter->offset < current_player->filter->duration) { + // run remaining ticks if needed to complete filter sequence + dtmf_dolphin_audio_handle_tick(); + } + dtmf_dolphin_speaker_stop(); + dtmf_dolphin_dma_stop(); + + furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, NULL, NULL); + + dtmf_dolphin_audio_free(current_player); + + return true; +} + +bool dtmf_dolphin_audio_handle_tick() { + bool handled = false; + + if(current_player) { + DTMFDolphinCustomEvent event; + if(furi_message_queue_get(current_player->queue, &event, 250) == FuriStatusOk) { + if(event.type == DTMFDolphinEventDMAHalfTransfer) { + generate_waveform(current_player, 0); + handled = true; + } else if(event.type == DTMFDolphinEventDMAFullTransfer) { + generate_waveform(current_player, current_player->half_buffer_length); + handled = true; + } + } + } + return handled; +} \ No newline at end of file diff --git a/applications/plugins/dtmf_dolphin/dtmf_dolphin_audio.h b/applications/plugins/dtmf_dolphin/dtmf_dolphin_audio.h new file mode 100644 index 000000000..2dd1d6eb6 --- /dev/null +++ b/applications/plugins/dtmf_dolphin/dtmf_dolphin_audio.h @@ -0,0 +1,54 @@ +#pragma once +// #include "dtmf_dolphin_i.h" +#include "dtmf_dolphin_event.h" +#include "dtmf_dolphin_hal.h" + +#define SAMPLE_BUFFER_LENGTH 8192 +#define PERIOD_2_PI 6.2832 +#define CPU_CLOCK_FREQ 64000000 + +typedef struct { + float cached_freq; + size_t period; + float* lookup_table; + uint16_t offset; +} DTMFDolphinOsc; + +typedef struct { + float duration; + size_t period; + bool* lookup_table; + uint16_t offset; +} DTMFDolphinPulseFilter; + +typedef struct { + size_t buffer_length; + size_t half_buffer_length; + uint8_t* buffer_buffer; + uint16_t* sample_buffer; + float volume; + FuriMessageQueue* queue; + DTMFDolphinOsc* osc1; + DTMFDolphinOsc* osc2; + DTMFDolphinPulseFilter* filter; + bool playing; +} DTMFDolphinAudio; + +DTMFDolphinOsc* dtmf_dolphin_osc_alloc(); + +DTMFDolphinAudio* dtmf_dolphin_audio_alloc(); + +void dtmf_dolphin_audio_free(DTMFDolphinAudio* player); + +void dtmf_dolphin_osc_free(DTMFDolphinOsc* osc); + +bool dtmf_dolphin_audio_play_tones( + float freq1, + float freq2, + uint16_t pulses, + uint16_t pulse_ms, + uint16_t gap_ms); + +bool dtmf_dolphin_audio_stop_tones(); + +bool dtmf_dolphin_audio_handle_tick(); diff --git a/applications/plugins/dtmf_dolphin/dtmf_dolphin_data.c b/applications/plugins/dtmf_dolphin/dtmf_dolphin_data.c new file mode 100644 index 000000000..72386b83d --- /dev/null +++ b/applications/plugins/dtmf_dolphin/dtmf_dolphin_data.c @@ -0,0 +1,220 @@ +#include "dtmf_dolphin_data.h" + +typedef struct { + const uint8_t row; + const uint8_t col; + const uint8_t span; +} DTMFDolphinTonePos; + +typedef struct { + const char* name; + const float frequency_1; + const float frequency_2; + const DTMFDolphinTonePos pos; + const uint16_t pulses; // for Redbox + const uint16_t pulse_ms; // for Redbox + const uint16_t gap_duration; // for Redbox +} DTMFDolphinTones; + +typedef struct { + const char* name; + DTMFDolphinToneSection block; + uint8_t tone_count; + DTMFDolphinTones tones[DTMF_DOLPHIN_MAX_TONE_COUNT]; +} DTMFDolphinSceneData; + +DTMFDolphinSceneData DTMFDolphinSceneDataDialer = { + .name = "Dialer", + .block = DTMF_DOLPHIN_TONE_BLOCK_DIALER, + .tone_count = 16, + .tones = { + {"1", 697.0, 1209.0, {0, 0, 1}, 0, 0, 0}, + {"2", 697.0, 1336.0, {0, 1, 1}, 0, 0, 0}, + {"3", 697.0, 1477.0, {0, 2, 1}, 0, 0, 0}, + {"A", 697.0, 1633.0, {0, 3, 1}, 0, 0, 0}, + {"4", 770.0, 1209.0, {1, 0, 1}, 0, 0, 0}, + {"5", 770.0, 1336.0, {1, 1, 1}, 0, 0, 0}, + {"6", 770.0, 1477.0, {1, 2, 1}, 0, 0, 0}, + {"B", 770.0, 1633.0, {1, 3, 1}, 0, 0, 0}, + {"7", 852.0, 1209.0, {2, 0, 1}, 0, 0, 0}, + {"8", 852.0, 1336.0, {2, 1, 1}, 0, 0, 0}, + {"9", 852.0, 1477.0, {2, 2, 1}, 0, 0, 0}, + {"C", 852.0, 1633.0, {2, 3, 1}, 0, 0, 0}, + {"*", 941.0, 1209.0, {3, 0, 1}, 0, 0, 0}, + {"0", 941.0, 1336.0, {3, 1, 1}, 0, 0, 0}, + {"#", 941.0, 1477.0, {3, 2, 1}, 0, 0, 0}, + {"D", 941.0, 1633.0, {3, 3, 1}, 0, 0, 0}, + }}; + +DTMFDolphinSceneData DTMFDolphinSceneDataBluebox = { + .name = "Bluebox", + .block = DTMF_DOLPHIN_TONE_BLOCK_BLUEBOX, + .tone_count = 13, + .tones = { + {"1", 700.0, 900.0, {0, 0, 1}, 0, 0, 0}, + {"2", 700.0, 1100.0, {0, 1, 1}, 0, 0, 0}, + {"3", 900.0, 1100.0, {0, 2, 1}, 0, 0, 0}, + {"4", 700.0, 1300.0, {1, 0, 1}, 0, 0, 0}, + {"5", 900.0, 1300.0, {1, 1, 1}, 0, 0, 0}, + {"6", 1100.0, 1300.0, {1, 2, 1}, 0, 0, 0}, + {"7", 700.0, 1500.0, {2, 0, 1}, 0, 0, 0}, + {"8", 900.0, 1500.0, {2, 1, 1}, 0, 0, 0}, + {"9", 1100.0, 1500.0, {2, 2, 1}, 0, 0, 0}, + {"0", 1300.0, 1500.0, {3, 1, 1}, 0, 0, 0}, + {"KP", 1100.0, 1700.0, {0, 3, 2}, 0, 0, 0}, + {"ST", 1500.0, 1700.0, {1, 3, 2}, 0, 0, 0}, + {"2600", 2600.0, 0.0, {3, 2, 3}, 0, 0, 0}, + }}; + +DTMFDolphinSceneData DTMFDolphinSceneDataRedboxUS = { + .name = "Redbox (US)", + .block = DTMF_DOLPHIN_TONE_BLOCK_REDBOX_US, + .tone_count = 4, + .tones = { + {"Nickel", 1700.0, 2200.0, {0, 0, 5}, 1, 66, 0}, + {"Dime", 1700.0, 2200.0, {1, 0, 5}, 2, 66, 66}, + {"Quarter", 1700.0, 2200.0, {2, 0, 5}, 5, 33, 33}, + {"Dollar", 1700.0, 2200.0, {3, 0, 5}, 1, 650, 0}, + }}; + +DTMFDolphinSceneData DTMFDolphinSceneDataRedboxCA = { + .name = "Redbox (CA)", + .block = DTMF_DOLPHIN_TONE_BLOCK_REDBOX_CA, + .tone_count = 3, + .tones = { + {"Nickel", 2200.0, 0.0, {0, 0, 5}, 1, 66, 0}, + {"Dime", 2200.0, 0.0, {1, 0, 5}, 2, 66, 66}, + {"Quarter", 2200.0, 0.0, {2, 0, 5}, 5, 33, 33}, + }}; + +DTMFDolphinSceneData DTMFDolphinSceneDataRedboxUK = { + .name = "Redbox (UK)", + .block = DTMF_DOLPHIN_TONE_BLOCK_REDBOX_UK, + .tone_count = 2, + .tones = { + {"10p", 1000.0, 0.0, {0, 0, 5}, 1, 200, 0}, + {"50p", 1000.0, 0.0, {1, 0, 5}, 1, 350, 0}, + }}; + +DTMFDolphinSceneData DTMFDolphinSceneDataMisc = { + .name = "Misc", + .block = DTMF_DOLPHIN_TONE_BLOCK_MISC, + .tone_count = 3, + .tones = { + {"CCITT 11", 700.0, 1700.0, {0, 0, 5}, 0, 0, 0}, + {"CCITT 12", 900.0, 1700.0, {1, 0, 5}, 0, 0, 0}, + {"CCITT KP2", 1300.0, 1700.0, {2, 0, 5}, 0, 0, 0}, + }}; + +DTMFDolphinToneSection current_section; +DTMFDolphinSceneData* current_scene_data; + +void dtmf_dolphin_data_set_current_section(DTMFDolphinToneSection section) { + current_section = section; + + switch(current_section) { + case DTMF_DOLPHIN_TONE_BLOCK_BLUEBOX: + current_scene_data = &DTMFDolphinSceneDataBluebox; + break; + case DTMF_DOLPHIN_TONE_BLOCK_REDBOX_US: + current_scene_data = &DTMFDolphinSceneDataRedboxUS; + break; + case DTMF_DOLPHIN_TONE_BLOCK_REDBOX_CA: + current_scene_data = &DTMFDolphinSceneDataRedboxCA; + break; + case DTMF_DOLPHIN_TONE_BLOCK_REDBOX_UK: + current_scene_data = &DTMFDolphinSceneDataRedboxUK; + break; + case DTMF_DOLPHIN_TONE_BLOCK_MISC: + current_scene_data = &DTMFDolphinSceneDataMisc; + break; + default: // DTMF_DOLPHIN_TONE_BLOCK_DIALER: + current_scene_data = &DTMFDolphinSceneDataDialer; + break; + } +} + +DTMFDolphinToneSection dtmf_dolphin_data_get_current_section() { + return current_section; +} + +DTMFDolphinSceneData* dtmf_dolphin_data_get_current_scene_data() { + return current_scene_data; +} + +bool dtmf_dolphin_data_get_tone_frequencies(float* freq1, float* freq2, uint8_t row, uint8_t col) { + for(size_t i = 0; i < current_scene_data->tone_count; i++) { + DTMFDolphinTones tones = current_scene_data->tones[i]; + if(tones.pos.row == row && tones.pos.col == col) { + freq1[0] = tones.frequency_1; + freq2[0] = tones.frequency_2; + return true; + } + } + return false; +} + +bool dtmf_dolphin_data_get_filter_data( + uint16_t* pulses, + uint16_t* pulse_ms, + uint16_t* gap_ms, + uint8_t row, + uint8_t col) { + for(size_t i = 0; i < current_scene_data->tone_count; i++) { + DTMFDolphinTones tones = current_scene_data->tones[i]; + if(tones.pos.row == row && tones.pos.col == col) { + pulses[0] = tones.pulses; + pulse_ms[0] = tones.pulse_ms; + gap_ms[0] = tones.gap_duration; + return true; + } + } + return false; +} + +const char* dtmf_dolphin_data_get_tone_name(uint8_t row, uint8_t col) { + for(size_t i = 0; i < current_scene_data->tone_count; i++) { + DTMFDolphinTones tones = current_scene_data->tones[i]; + if(tones.pos.row == row && tones.pos.col == col) { + return tones.name; + } + } + return NULL; +} + +const char* dtmf_dolphin_data_get_current_section_name() { + if(current_scene_data) { + return current_scene_data->name; + } + return NULL; +} + +void dtmf_dolphin_tone_get_max_pos(uint8_t* max_rows, uint8_t* max_cols, uint8_t* max_span) { + max_rows[0] = 0; + max_cols[0] = 0; + max_span[0] = 0; + uint8_t tmp_rowspan[5] = {0, 0, 0, 0, 0}; + for(size_t i = 0; i < current_scene_data->tone_count; i++) { + DTMFDolphinTones tones = current_scene_data->tones[i]; + if(tones.pos.row > max_rows[0]) { + max_rows[0] = tones.pos.row; + } + if(tones.pos.col > max_cols[0]) { + max_cols[0] = tones.pos.col; + } + tmp_rowspan[tones.pos.row] += tones.pos.span; + if(tmp_rowspan[tones.pos.row] > max_span[0]) max_span[0] = tmp_rowspan[tones.pos.row]; + } + max_rows[0]++; + max_cols[0]++; +} + +uint8_t dtmf_dolphin_get_tone_span(uint8_t row, uint8_t col) { + for(size_t i = 0; i < current_scene_data->tone_count; i++) { + DTMFDolphinTones tones = current_scene_data->tones[i]; + if(tones.pos.row == row && tones.pos.col == col) { + return tones.pos.span; + } + } + return 0; +} diff --git a/applications/plugins/dtmf_dolphin/dtmf_dolphin_data.h b/applications/plugins/dtmf_dolphin/dtmf_dolphin_data.h new file mode 100644 index 000000000..56ceaf03d --- /dev/null +++ b/applications/plugins/dtmf_dolphin/dtmf_dolphin_data.h @@ -0,0 +1,36 @@ +#pragma once +#include +#include +#include + +#define DTMF_DOLPHIN_MAX_TONE_COUNT 16 + +typedef enum { + DTMF_DOLPHIN_TONE_BLOCK_DIALER, + DTMF_DOLPHIN_TONE_BLOCK_BLUEBOX, + DTMF_DOLPHIN_TONE_BLOCK_REDBOX_US, + DTMF_DOLPHIN_TONE_BLOCK_REDBOX_UK, + DTMF_DOLPHIN_TONE_BLOCK_REDBOX_CA, + DTMF_DOLPHIN_TONE_BLOCK_MISC, +} DTMFDolphinToneSection; + +void dtmf_dolphin_data_set_current_section(DTMFDolphinToneSection section); + +DTMFDolphinToneSection dtmf_dolphin_data_get_current_section(); + +bool dtmf_dolphin_data_get_tone_frequencies(float* freq1, float* freq2, uint8_t row, uint8_t col); + +bool dtmf_dolphin_data_get_filter_data( + uint16_t* pulses, + uint16_t* pulse_ms, + uint16_t* gap_ms, + uint8_t row, + uint8_t col); + +const char* dtmf_dolphin_data_get_tone_name(uint8_t row, uint8_t col); + +const char* dtmf_dolphin_data_get_current_section_name(); + +void dtmf_dolphin_tone_get_max_pos(uint8_t* max_rows, uint8_t* max_cols, uint8_t* max_span); + +uint8_t dtmf_dolphin_get_tone_span(uint8_t row, uint8_t col); \ No newline at end of file diff --git a/applications/plugins/dtmf_dolphin/dtmf_dolphin_event.h b/applications/plugins/dtmf_dolphin/dtmf_dolphin_event.h new file mode 100644 index 000000000..525d0eb04 --- /dev/null +++ b/applications/plugins/dtmf_dolphin/dtmf_dolphin_event.h @@ -0,0 +1,21 @@ +#pragma once + +typedef enum { + DTMFDolphinEventVolumeUp = 0, + DTMFDolphinEventVolumeDown, + DTMFDolphinDialerOkCB, + DTMFDolphinEventStartDialer, + DTMFDolphinEventStartBluebox, + DTMFDolphinEventStartRedboxUS, + DTMFDolphinEventStartRedboxUK, + DTMFDolphinEventStartRedboxCA, + DTMFDolphinEventStartMisc, + DTMFDolphinEventPlayTones, + DTMFDolphinEventStopTones, + DTMFDolphinEventDMAHalfTransfer, + DTMFDolphinEventDMAFullTransfer, +} DTMFDolphinEvent; + +typedef struct { + DTMFDolphinEvent type; +} DTMFDolphinCustomEvent; \ No newline at end of file diff --git a/applications/plugins/dtmf_dolphin/dtmf_dolphin_hal.c b/applications/plugins/dtmf_dolphin/dtmf_dolphin_hal.c new file mode 100644 index 000000000..b556c7145 --- /dev/null +++ b/applications/plugins/dtmf_dolphin/dtmf_dolphin_hal.c @@ -0,0 +1,52 @@ +#include "dtmf_dolphin_hal.h" + +void dtmf_dolphin_speaker_init() { + LL_TIM_InitTypeDef TIM_InitStruct = {0}; + TIM_InitStruct.Prescaler = DTMF_DOLPHIN_HAL_DMA_PRESCALER; + TIM_InitStruct.Autoreload = DTMF_DOLPHIN_HAL_DMA_AUTORELOAD; + LL_TIM_Init(FURI_HAL_SPEAKER_TIMER, &TIM_InitStruct); + + LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0}; + TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1; + TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_ENABLE; + TIM_OC_InitStruct.CompareValue = 127; + LL_TIM_OC_Init(FURI_HAL_SPEAKER_TIMER, FURI_HAL_SPEAKER_CHANNEL, &TIM_OC_InitStruct); +} + +void dtmf_dolphin_speaker_start() { + LL_TIM_EnableAllOutputs(FURI_HAL_SPEAKER_TIMER); + LL_TIM_EnableCounter(FURI_HAL_SPEAKER_TIMER); +} + +void dtmf_dolphin_speaker_stop() { + LL_TIM_DisableAllOutputs(FURI_HAL_SPEAKER_TIMER); + LL_TIM_DisableCounter(FURI_HAL_SPEAKER_TIMER); +} + +void dtmf_dolphin_dma_init(uint32_t address, size_t size) { + uint32_t dma_dst = (uint32_t) & (FURI_HAL_SPEAKER_TIMER->CCR1); + + LL_DMA_ConfigAddresses(DMA_INSTANCE, address, dma_dst, LL_DMA_DIRECTION_MEMORY_TO_PERIPH); + LL_DMA_SetDataLength(DMA_INSTANCE, size); + + LL_DMA_SetPeriphRequest(DMA_INSTANCE, LL_DMAMUX_REQ_TIM16_UP); + LL_DMA_SetDataTransferDirection(DMA_INSTANCE, LL_DMA_DIRECTION_MEMORY_TO_PERIPH); + LL_DMA_SetChannelPriorityLevel(DMA_INSTANCE, LL_DMA_PRIORITY_VERYHIGH); + LL_DMA_SetMode(DMA_INSTANCE, LL_DMA_MODE_CIRCULAR); + LL_DMA_SetPeriphIncMode(DMA_INSTANCE, LL_DMA_PERIPH_NOINCREMENT); + LL_DMA_SetMemoryIncMode(DMA_INSTANCE, LL_DMA_MEMORY_INCREMENT); + LL_DMA_SetPeriphSize(DMA_INSTANCE, LL_DMA_PDATAALIGN_HALFWORD); + LL_DMA_SetMemorySize(DMA_INSTANCE, LL_DMA_MDATAALIGN_HALFWORD); + + LL_DMA_EnableIT_TC(DMA_INSTANCE); + LL_DMA_EnableIT_HT(DMA_INSTANCE); +} + +void dtmf_dolphin_dma_start() { + LL_DMA_EnableChannel(DMA_INSTANCE); + LL_TIM_EnableDMAReq_UPDATE(FURI_HAL_SPEAKER_TIMER); +} + +void dtmf_dolphin_dma_stop() { + LL_DMA_DisableChannel(DMA_INSTANCE); +} diff --git a/applications/plugins/dtmf_dolphin/dtmf_dolphin_hal.h b/applications/plugins/dtmf_dolphin/dtmf_dolphin_hal.h new file mode 100644 index 000000000..5b426f6a0 --- /dev/null +++ b/applications/plugins/dtmf_dolphin/dtmf_dolphin_hal.h @@ -0,0 +1,33 @@ +#pragma once +#include +#include +#include +#include +#include + +#define FURI_HAL_SPEAKER_TIMER TIM16 +#define FURI_HAL_SPEAKER_CHANNEL LL_TIM_CHANNEL_CH1 +#define DMA_INSTANCE DMA1, LL_DMA_CHANNEL_1 + +#define DTMF_DOLPHIN_HAL_DMA_PRESCALER 4 +#define DTMF_DOLPHIN_HAL_DMA_AUTORELOAD 255 + +#ifdef __cplusplus +extern "C" { +#endif + +void dtmf_dolphin_speaker_init(); + +void dtmf_dolphin_speaker_start(); + +void dtmf_dolphin_speaker_stop(); + +void dtmf_dolphin_dma_init(uint32_t address, size_t size); + +void dtmf_dolphin_dma_start(); + +void dtmf_dolphin_dma_stop(); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/plugins/dtmf_dolphin/dtmf_dolphin_i.h b/applications/plugins/dtmf_dolphin/dtmf_dolphin_i.h new file mode 100644 index 000000000..f8ae1530f --- /dev/null +++ b/applications/plugins/dtmf_dolphin/dtmf_dolphin_i.h @@ -0,0 +1,42 @@ +#pragma once + +#include "scenes/dtmf_dolphin_scene.h" + +#include +#include +#include +// #include +// #include +#include +#include +#include + +#include "dtmf_dolphin_event.h" + +#include "views/dtmf_dolphin_dialer.h" + +#define TAG "DTMFDolphin" + +enum DTMFDolphinSceneState { + DTMFDolphinSceneStateDialer, + DTMFDolphinSceneStateBluebox, + DTMFDolphinSceneStateRedboxUS, + DTMFDolphinSceneStateRedboxUK, + DTMFDolphinSceneStateRedboxCA, + DTMFDolphinSceneStateMisc, +}; + +typedef struct { + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + VariableItemList* main_menu_list; + DTMFDolphinDialer* dtmf_dolphin_dialer; + + Gui* gui; + // ButtonPanel* dialer_button_panel; + // ButtonPanel* bluebox_button_panel; + // ButtonPanel* redbox_button_panel; + NotificationApp* notification; +} DTMFDolphinApp; + +typedef enum { DTMFDolphinViewMainMenu, DTMFDolphinViewDialer } DTMFDolphinView; diff --git a/applications/plugins/dtmf_dolphin/phone.png b/applications/plugins/dtmf_dolphin/phone.png new file mode 100644 index 000000000..443f847c3 Binary files /dev/null and b/applications/plugins/dtmf_dolphin/phone.png differ diff --git a/applications/plugins/dtmf_dolphin/scenes/dtmf_dolphin_scene.c b/applications/plugins/dtmf_dolphin/scenes/dtmf_dolphin_scene.c new file mode 100644 index 000000000..bfb8da1da --- /dev/null +++ b/applications/plugins/dtmf_dolphin/scenes/dtmf_dolphin_scene.c @@ -0,0 +1,30 @@ +#include "dtmf_dolphin_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const dtmf_dolphin_scene_on_enter_handlers[])(void*) = { +#include "dtmf_dolphin_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 dtmf_dolphin_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "dtmf_dolphin_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 dtmf_dolphin_scene_on_exit_handlers[])(void* context) = { +#include "dtmf_dolphin_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers dtmf_dolphin_scene_handlers = { + .on_enter_handlers = dtmf_dolphin_scene_on_enter_handlers, + .on_event_handlers = dtmf_dolphin_scene_on_event_handlers, + .on_exit_handlers = dtmf_dolphin_scene_on_exit_handlers, + .scene_num = DTMFDolphinSceneNum, +}; diff --git a/applications/plugins/dtmf_dolphin/scenes/dtmf_dolphin_scene.h b/applications/plugins/dtmf_dolphin/scenes/dtmf_dolphin_scene.h new file mode 100644 index 000000000..e45dc68ae --- /dev/null +++ b/applications/plugins/dtmf_dolphin/scenes/dtmf_dolphin_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) DTMFDolphinScene##id, +typedef enum { +#include "dtmf_dolphin_scene_config.h" + DTMFDolphinSceneNum, +} DTMFDolphinScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers dtmf_dolphin_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "dtmf_dolphin_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 "dtmf_dolphin_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 "dtmf_dolphin_scene_config.h" +#undef ADD_SCENE diff --git a/applications/plugins/dtmf_dolphin/scenes/dtmf_dolphin_scene_config.h b/applications/plugins/dtmf_dolphin/scenes/dtmf_dolphin_scene_config.h new file mode 100644 index 000000000..b6dab07dc --- /dev/null +++ b/applications/plugins/dtmf_dolphin/scenes/dtmf_dolphin_scene_config.h @@ -0,0 +1,2 @@ +ADD_SCENE(dtmf_dolphin, start, Start) +ADD_SCENE(dtmf_dolphin, dialer, Dialer) \ No newline at end of file diff --git a/applications/plugins/dtmf_dolphin/scenes/dtmf_dolphin_scene_dialer.c b/applications/plugins/dtmf_dolphin/scenes/dtmf_dolphin_scene_dialer.c new file mode 100644 index 000000000..06da595e0 --- /dev/null +++ b/applications/plugins/dtmf_dolphin/scenes/dtmf_dolphin_scene_dialer.c @@ -0,0 +1,49 @@ +#include "../dtmf_dolphin_i.h" +// #include "../dtmf_dolphin_data.h" +// #include "../dtmf_dolphin_audio.h" + +void dtmf_dolphin_scene_dialer_on_enter(void* context) { + DTMFDolphinApp* app = context; + DTMFDolphinScene scene_id = DTMFDolphinSceneDialer; + enum DTMFDolphinSceneState state = scene_manager_get_scene_state(app->scene_manager, scene_id); + + switch(state) { + case DTMFDolphinSceneStateBluebox: + dtmf_dolphin_data_set_current_section(DTMF_DOLPHIN_TONE_BLOCK_BLUEBOX); + break; + case DTMFDolphinSceneStateRedboxUS: + dtmf_dolphin_data_set_current_section(DTMF_DOLPHIN_TONE_BLOCK_REDBOX_US); + break; + case DTMFDolphinSceneStateRedboxUK: + dtmf_dolphin_data_set_current_section(DTMF_DOLPHIN_TONE_BLOCK_REDBOX_UK); + break; + case DTMFDolphinSceneStateRedboxCA: + dtmf_dolphin_data_set_current_section(DTMF_DOLPHIN_TONE_BLOCK_REDBOX_CA); + break; + case DTMFDolphinSceneStateMisc: + dtmf_dolphin_data_set_current_section(DTMF_DOLPHIN_TONE_BLOCK_MISC); + break; + default: + dtmf_dolphin_data_set_current_section(DTMF_DOLPHIN_TONE_BLOCK_DIALER); + break; + } + + view_dispatcher_switch_to_view(app->view_dispatcher, DTMFDolphinViewDialer); +} + +bool dtmf_dolphin_scene_dialer_on_event(void* context, SceneManagerEvent event) { + DTMFDolphinApp* app = context; + UNUSED(app); + UNUSED(event); + bool consumed = false; + + // if(event.type == SceneManagerEventTypeTick) { + // consumed = true; + // } + + return consumed; +} + +void dtmf_dolphin_scene_dialer_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/plugins/dtmf_dolphin/scenes/dtmf_dolphin_scene_start.c b/applications/plugins/dtmf_dolphin/scenes/dtmf_dolphin_scene_start.c new file mode 100644 index 000000000..484e9e8eb --- /dev/null +++ b/applications/plugins/dtmf_dolphin/scenes/dtmf_dolphin_scene_start.c @@ -0,0 +1,94 @@ +#include "../dtmf_dolphin_i.h" + +static void dtmf_dolphin_scene_start_main_menu_enter_callback(void* context, uint32_t index) { + DTMFDolphinApp* app = context; + uint8_t cust_event = 255; + switch(index) { + case 0: + cust_event = DTMFDolphinEventStartDialer; + break; + case 1: + cust_event = DTMFDolphinEventStartBluebox; + break; + case 2: + cust_event = DTMFDolphinEventStartRedboxUS; + break; + case 3: + cust_event = DTMFDolphinEventStartRedboxUK; + break; + case 4: + cust_event = DTMFDolphinEventStartRedboxCA; + break; + case 5: + cust_event = DTMFDolphinEventStartMisc; + break; + default: + return; + } + + view_dispatcher_send_custom_event(app->view_dispatcher, cust_event); +} + +void dtmf_dolphin_scene_start_on_enter(void* context) { + DTMFDolphinApp* app = context; + VariableItemList* var_item_list = app->main_menu_list; + + // VariableItem* item; + variable_item_list_set_enter_callback( + var_item_list, dtmf_dolphin_scene_start_main_menu_enter_callback, app); + + variable_item_list_add(var_item_list, "Dialer", 0, NULL, context); + variable_item_list_add(var_item_list, "Bluebox", 0, NULL, context); + variable_item_list_add(var_item_list, "Redbox (US)", 0, NULL, context); + variable_item_list_add(var_item_list, "Redbox (UK)", 0, NULL, context); + variable_item_list_add(var_item_list, "Redbox (CA)", 0, NULL, context); + variable_item_list_add(var_item_list, "Misc", 0, NULL, context); + + variable_item_list_set_selected_item( + var_item_list, scene_manager_get_scene_state(app->scene_manager, DTMFDolphinSceneStart)); + + view_dispatcher_switch_to_view(app->view_dispatcher, DTMFDolphinViewMainMenu); +} + +bool dtmf_dolphin_scene_start_on_event(void* context, SceneManagerEvent event) { + DTMFDolphinApp* app = context; + UNUSED(app); + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + uint8_t sc_state; + + switch(event.event) { + case DTMFDolphinEventStartDialer: + sc_state = DTMFDolphinSceneStateDialer; + break; + case DTMFDolphinEventStartBluebox: + sc_state = DTMFDolphinSceneStateBluebox; + break; + case DTMFDolphinEventStartRedboxUS: + sc_state = DTMFDolphinSceneStateRedboxUS; + break; + case DTMFDolphinEventStartRedboxUK: + sc_state = DTMFDolphinSceneStateRedboxUK; + break; + case DTMFDolphinEventStartRedboxCA: + sc_state = DTMFDolphinSceneStateRedboxCA; + break; + case DTMFDolphinEventStartMisc: + sc_state = DTMFDolphinSceneStateMisc; + break; + default: + return consumed; + } + scene_manager_set_scene_state(app->scene_manager, DTMFDolphinSceneDialer, sc_state); + scene_manager_next_scene(app->scene_manager, DTMFDolphinSceneDialer); + + consumed = true; + } + return consumed; +} + +void dtmf_dolphin_scene_start_on_exit(void* context) { + DTMFDolphinApp* app = context; + variable_item_list_reset(app->main_menu_list); +} diff --git a/applications/plugins/dtmf_dolphin/views/dtmf_dolphin_common.h b/applications/plugins/dtmf_dolphin/views/dtmf_dolphin_common.h new file mode 100644 index 000000000..f2f4838d6 --- /dev/null +++ b/applications/plugins/dtmf_dolphin/views/dtmf_dolphin_common.h @@ -0,0 +1,10 @@ +#pragma once +#include "../dtmf_dolphin_event.h" +#include "../dtmf_dolphin_data.h" +#include "../dtmf_dolphin_audio.h" + +#define DTMF_DOLPHIN_NUMPAD_X 1 +#define DTMF_DOLPHIN_NUMPAD_Y 14 +#define DTMF_DOLPHIN_BUTTON_WIDTH 13 +#define DTMF_DOLPHIN_BUTTON_HEIGHT 13 +#define DTMF_DOLPHIN_BUTTON_PADDING 1 // all sides diff --git a/applications/plugins/dtmf_dolphin/views/dtmf_dolphin_dialer.c b/applications/plugins/dtmf_dolphin/views/dtmf_dolphin_dialer.c new file mode 100644 index 000000000..bdffa2313 --- /dev/null +++ b/applications/plugins/dtmf_dolphin/views/dtmf_dolphin_dialer.c @@ -0,0 +1,350 @@ +#include "dtmf_dolphin_dialer.h" + +#include + +typedef struct DTMFDolphinDialer { + View* view; + DTMFDolphinDialerOkCallback callback; + void* context; +} DTMFDolphinDialer; + +typedef struct { + DTMFDolphinToneSection section; + uint8_t row; + uint8_t col; + float freq1; + float freq2; + bool playing; + uint16_t pulses; + uint16_t pulse_ms; + uint16_t gap_ms; +} DTMFDolphinDialerModel; + +static bool dtmf_dolphin_dialer_process_up(DTMFDolphinDialer* dtmf_dolphin_dialer); +static bool dtmf_dolphin_dialer_process_down(DTMFDolphinDialer* dtmf_dolphin_dialer); +static bool dtmf_dolphin_dialer_process_left(DTMFDolphinDialer* dtmf_dolphin_dialer); +static bool dtmf_dolphin_dialer_process_right(DTMFDolphinDialer* dtmf_dolphin_dialer); +static bool + dtmf_dolphin_dialer_process_ok(DTMFDolphinDialer* dtmf_dolphin_dialer, InputEvent* event); + +void draw_button(Canvas* canvas, uint8_t row, uint8_t col, bool invert) { + uint8_t left = DTMF_DOLPHIN_NUMPAD_X + // ((col + 1) * DTMF_DOLPHIN_BUTTON_PADDING) + + (col * DTMF_DOLPHIN_BUTTON_WIDTH); + // (col * DTMF_DOLPHIN_BUTTON_PADDING); + uint8_t top = DTMF_DOLPHIN_NUMPAD_Y + // ((row + 1) * DTMF_DOLPHIN_BUTTON_PADDING) + + (row * DTMF_DOLPHIN_BUTTON_HEIGHT); + // (row * DTMF_DOLPHIN_BUTTON_PADDING); + + uint8_t span = dtmf_dolphin_get_tone_span(row, col); + + if(span == 0) { + return; + } + + canvas_set_color(canvas, ColorBlack); + + if(invert) + canvas_draw_rbox( + canvas, + left, + top, + (DTMF_DOLPHIN_BUTTON_WIDTH * span) - (DTMF_DOLPHIN_BUTTON_PADDING * 2), + DTMF_DOLPHIN_BUTTON_HEIGHT - (DTMF_DOLPHIN_BUTTON_PADDING * 2), + 2); + else + canvas_draw_rframe( + canvas, + left, + top, + (DTMF_DOLPHIN_BUTTON_WIDTH * span) - (DTMF_DOLPHIN_BUTTON_PADDING * 2), + DTMF_DOLPHIN_BUTTON_HEIGHT - (DTMF_DOLPHIN_BUTTON_PADDING * 2), + 2); + + if(invert) canvas_invert_color(canvas); + + canvas_set_font(canvas, FontSecondary); + // canvas_set_color(canvas, invert ? ColorWhite : ColorBlack); + canvas_draw_str_aligned( + canvas, + left - 1 + (int)((DTMF_DOLPHIN_BUTTON_WIDTH * span) / 2), + top + (int)(DTMF_DOLPHIN_BUTTON_HEIGHT / 2), + AlignCenter, + AlignCenter, + dtmf_dolphin_data_get_tone_name(row, col)); + + if(invert) canvas_invert_color(canvas); +} + +void draw_dialer(Canvas* canvas, void* _model) { + DTMFDolphinDialerModel* model = _model; + uint8_t max_rows; + uint8_t max_cols; + uint8_t max_span; + dtmf_dolphin_tone_get_max_pos(&max_rows, &max_cols, &max_span); + + canvas_set_font(canvas, FontSecondary); + + for(int r = 0; r < max_rows; r++) { + for(int c = 0; c < max_cols; c++) { + if(model->row == r && model->col == c) + draw_button(canvas, r, c, true); + else + draw_button(canvas, r, c, false); + } + } +} + +void update_frequencies(DTMFDolphinDialerModel* model) { + dtmf_dolphin_data_get_tone_frequencies(&model->freq1, &model->freq2, model->row, model->col); + dtmf_dolphin_data_get_filter_data( + &model->pulses, &model->pulse_ms, &model->gap_ms, model->row, model->col); +} + +static void dtmf_dolphin_dialer_draw_callback(Canvas* canvas, void* _model) { + DTMFDolphinDialerModel* model = _model; + if(model->playing) { + // Leverage the prioritized draw callback to handle + // the DMA so that it doesn't skip. + dtmf_dolphin_audio_handle_tick(); + // Don't do any drawing if audio is playing. + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned( + canvas, + canvas_width(canvas) / 2, + canvas_height(canvas) / 2, + AlignCenter, + AlignCenter, + "Playing Tones"); + return; + } + update_frequencies(model); + uint8_t max_rows = 0; + uint8_t max_cols = 0; + uint8_t max_span = 0; + dtmf_dolphin_tone_get_max_pos(&max_rows, &max_cols, &max_span); + + canvas_set_font(canvas, FontPrimary); + elements_multiline_text(canvas, 2, 10, dtmf_dolphin_data_get_current_section_name()); + canvas_draw_line( + canvas, + (max_span * DTMF_DOLPHIN_BUTTON_WIDTH) + 1, + 0, + (max_span * DTMF_DOLPHIN_BUTTON_WIDTH) + 1, + canvas_height(canvas)); + elements_multiline_text(canvas, (max_span * DTMF_DOLPHIN_BUTTON_WIDTH) + 4, 10, "Detail"); + canvas_draw_line( + canvas, 0, DTMF_DOLPHIN_NUMPAD_Y - 3, canvas_width(canvas), DTMF_DOLPHIN_NUMPAD_Y - 3); + // elements_multiline_text_aligned(canvas, 64, 2, AlignCenter, AlignTop, "Dialer Mode"); + + draw_dialer(canvas, model); + + FuriString* output = furi_string_alloc(); + + if(model->freq1 && model->freq2) { + furi_string_cat_printf( + output, + "Dual Tone\nF1: %u Hz\nF2: %u Hz\n", + (unsigned int)model->freq1, + (unsigned int)model->freq2); + } else if(model->freq1) { + furi_string_cat_printf(output, "Single Tone\nF: %u Hz\n", (unsigned int)model->freq1); + } + + canvas_set_font(canvas, FontSecondary); + canvas_set_color(canvas, ColorBlack); + if(model->pulse_ms) { + furi_string_cat_printf(output, "P: %u * %u ms\n", model->pulses, model->pulse_ms); + } + if(model->gap_ms) { + furi_string_cat_printf(output, "Gaps: %u ms\n", model->gap_ms); + } + elements_multiline_text( + canvas, (max_span * DTMF_DOLPHIN_BUTTON_WIDTH) + 4, 21, furi_string_get_cstr(output)); + + furi_string_free(output); +} + +static bool dtmf_dolphin_dialer_input_callback(InputEvent* event, void* context) { + furi_assert(context); + DTMFDolphinDialer* dtmf_dolphin_dialer = context; + bool consumed = false; + + if(event->type == InputTypeShort) { + if(event->key == InputKeyRight) { + consumed = dtmf_dolphin_dialer_process_right(dtmf_dolphin_dialer); + } else if(event->key == InputKeyLeft) { + consumed = dtmf_dolphin_dialer_process_left(dtmf_dolphin_dialer); + } else if(event->key == InputKeyUp) { + consumed = dtmf_dolphin_dialer_process_up(dtmf_dolphin_dialer); + } else if(event->key == InputKeyDown) { + consumed = dtmf_dolphin_dialer_process_down(dtmf_dolphin_dialer); + } + + } else if(event->key == InputKeyOk) { + consumed = dtmf_dolphin_dialer_process_ok(dtmf_dolphin_dialer, event); + } + + return consumed; +} + +static bool dtmf_dolphin_dialer_process_up(DTMFDolphinDialer* dtmf_dolphin_dialer) { + with_view_model( + dtmf_dolphin_dialer->view, + DTMFDolphinDialerModel * model, + { + uint8_t span = 0; + uint8_t cursor = model->row; + while(span == 0 && cursor > 0) { + cursor--; + span = dtmf_dolphin_get_tone_span(cursor, model->col); + } + if(span != 0) { + model->row = cursor; + } + }, + true); + return true; +} + +static bool dtmf_dolphin_dialer_process_down(DTMFDolphinDialer* dtmf_dolphin_dialer) { + uint8_t max_rows = 0; + uint8_t max_cols = 0; + uint8_t max_span = 0; + dtmf_dolphin_tone_get_max_pos(&max_rows, &max_cols, &max_span); + + with_view_model( + dtmf_dolphin_dialer->view, + DTMFDolphinDialerModel * model, + { + uint8_t span = 0; + uint8_t cursor = model->row; + while(span == 0 && cursor < max_rows - 1) { + cursor++; + span = dtmf_dolphin_get_tone_span(cursor, model->col); + } + if(span != 0) { + model->row = cursor; + } + }, + true); + return true; +} + +static bool dtmf_dolphin_dialer_process_left(DTMFDolphinDialer* dtmf_dolphin_dialer) { + with_view_model( + dtmf_dolphin_dialer->view, + DTMFDolphinDialerModel * model, + { + uint8_t span = 0; + uint8_t cursor = model->col; + while(span == 0 && cursor > 0) { + cursor--; + span = dtmf_dolphin_get_tone_span(model->row, cursor); + } + if(span != 0) { + model->col = cursor; + } + }, + true); + return true; +} + +static bool dtmf_dolphin_dialer_process_right(DTMFDolphinDialer* dtmf_dolphin_dialer) { + uint8_t max_rows = 0; + uint8_t max_cols = 0; + uint8_t max_span = 0; + dtmf_dolphin_tone_get_max_pos(&max_rows, &max_cols, &max_span); + + with_view_model( + dtmf_dolphin_dialer->view, + DTMFDolphinDialerModel * model, + { + uint8_t span = 0; + uint8_t cursor = model->col; + while(span == 0 && cursor < max_cols - 1) { + cursor++; + span = dtmf_dolphin_get_tone_span(model->row, cursor); + } + if(span != 0) { + model->col = cursor; + } + }, + true); + return true; +} + +static bool + dtmf_dolphin_dialer_process_ok(DTMFDolphinDialer* dtmf_dolphin_dialer, InputEvent* event) { + bool consumed = false; + + with_view_model( + dtmf_dolphin_dialer->view, + DTMFDolphinDialerModel * model, + { + if(event->type == InputTypePress) { + model->playing = dtmf_dolphin_audio_play_tones( + model->freq1, model->freq2, model->pulses, model->pulse_ms, model->gap_ms); + } else if(event->type == InputTypeRelease) { + model->playing = !dtmf_dolphin_audio_stop_tones(); + } + }, + true); + + return consumed; +} + +static void dtmf_dolphin_dialer_enter_callback(void* context) { + furi_assert(context); + DTMFDolphinDialer* dtmf_dolphin_dialer = context; + + with_view_model( + dtmf_dolphin_dialer->view, + DTMFDolphinDialerModel * model, + { + model->col = 0; + model->row = 0; + model->section = 0; + model->freq1 = 0.0; + model->freq2 = 0.0; + model->playing = false; + }, + true); +} + +DTMFDolphinDialer* dtmf_dolphin_dialer_alloc() { + DTMFDolphinDialer* dtmf_dolphin_dialer = malloc(sizeof(DTMFDolphinDialer)); + + dtmf_dolphin_dialer->view = view_alloc(); + view_allocate_model( + dtmf_dolphin_dialer->view, ViewModelTypeLocking, sizeof(DTMFDolphinDialerModel)); + + with_view_model( + dtmf_dolphin_dialer->view, + DTMFDolphinDialerModel * model, + { + model->col = 0; + model->row = 0; + model->section = 0; + model->freq1 = 0.0; + model->freq2 = 0.0; + model->playing = false; + }, + true); + + view_set_context(dtmf_dolphin_dialer->view, dtmf_dolphin_dialer); + view_set_draw_callback(dtmf_dolphin_dialer->view, dtmf_dolphin_dialer_draw_callback); + view_set_input_callback(dtmf_dolphin_dialer->view, dtmf_dolphin_dialer_input_callback); + view_set_enter_callback(dtmf_dolphin_dialer->view, dtmf_dolphin_dialer_enter_callback); + return dtmf_dolphin_dialer; +} + +void dtmf_dolphin_dialer_free(DTMFDolphinDialer* dtmf_dolphin_dialer) { + furi_assert(dtmf_dolphin_dialer); + view_free(dtmf_dolphin_dialer->view); + free(dtmf_dolphin_dialer); +} + +View* dtmf_dolphin_dialer_get_view(DTMFDolphinDialer* dtmf_dolphin_dialer) { + furi_assert(dtmf_dolphin_dialer); + return dtmf_dolphin_dialer->view; +} diff --git a/applications/plugins/dtmf_dolphin/views/dtmf_dolphin_dialer.h b/applications/plugins/dtmf_dolphin/views/dtmf_dolphin_dialer.h new file mode 100644 index 000000000..1929afbc5 --- /dev/null +++ b/applications/plugins/dtmf_dolphin/views/dtmf_dolphin_dialer.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include "dtmf_dolphin_common.h" + +typedef struct DTMFDolphinDialer DTMFDolphinDialer; +typedef void (*DTMFDolphinDialerOkCallback)(InputType type, void* context); + +DTMFDolphinDialer* dtmf_dolphin_dialer_alloc(); + +void dtmf_dolphin_dialer_free(DTMFDolphinDialer* dtmf_dolphin_dialer); + +View* dtmf_dolphin_dialer_get_view(DTMFDolphinDialer* dtmf_dolphin_dialer); + +void dtmf_dolphin_dialer_set_ok_callback( + DTMFDolphinDialer* dtmf_dolphin_dialer, + DTMFDolphinDialerOkCallback callback, + void* context); diff --git a/applications/plugins/flashlight/LICENSE b/applications/plugins/flashlight/LICENSE new file mode 100644 index 000000000..28d693a7c --- /dev/null +++ b/applications/plugins/flashlight/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 MX + +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. diff --git a/applications/plugins/flashlight/README.md b/applications/plugins/flashlight/README.md new file mode 100644 index 000000000..a40cb2d5a --- /dev/null +++ b/applications/plugins/flashlight/README.md @@ -0,0 +1,7 @@ +# Flashlight Plugin for Flipper Zero + +Simple Flashlight special for @Svaarich by @xMasterX + +Enables 3.3v on pin 7/C3 and leaves it on when you exit app + +**Connect LED to (+ -> 7/C3) | (GND -> GND)** diff --git a/applications/plugins/flashlight/application.fam b/applications/plugins/flashlight/application.fam new file mode 100644 index 000000000..d6d5aa791 --- /dev/null +++ b/applications/plugins/flashlight/application.fam @@ -0,0 +1,14 @@ +App( + appid="Flashlight", + name="Flashlight", + apptype=FlipperAppType.EXTERNAL, + entry_point="flashlight_app", + cdefines=["APP_FLASHLIGHT"], + requires=[ + "gui", + ], + stack_size=2 * 1024, + order=20, + fap_icon="flash10px.png", + fap_category="GPIO", +) \ No newline at end of file diff --git a/applications/plugins/flashlight/flash10px.png b/applications/plugins/flashlight/flash10px.png new file mode 100644 index 000000000..963a9ab5f Binary files /dev/null and b/applications/plugins/flashlight/flash10px.png differ diff --git a/applications/plugins/flashlight/flashlight.c b/applications/plugins/flashlight/flashlight.c new file mode 100644 index 000000000..9c5f600f7 --- /dev/null +++ b/applications/plugins/flashlight/flashlight.c @@ -0,0 +1,130 @@ +// by @xMasterX + +#include +#include +#include +#include +#include +#include + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} PluginEvent; + +typedef struct { + bool is_on; +} PluginState; + +static void render_callback(Canvas* const canvas, void* ctx) { + const PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25); + if(plugin_state == NULL) { + return; + } + + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 64, 2, AlignCenter, AlignTop, "Flashlight"); + + canvas_set_font(canvas, FontSecondary); + + if(!plugin_state->is_on) { + elements_multiline_text_aligned( + canvas, 64, 28, AlignCenter, AlignTop, "Press OK button turn on"); + } else { + elements_multiline_text_aligned(canvas, 64, 28, AlignCenter, AlignTop, "Light is on!"); + elements_multiline_text_aligned( + canvas, 64, 40, AlignCenter, AlignTop, "Press OK button to off"); + } + + release_mutex((ValueMutex*)ctx, plugin_state); +} + +static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + PluginEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void flash_toggle(PluginState* const plugin_state) { + furi_hal_gpio_write(&gpio_ext_pc3, false); + furi_hal_gpio_init(&gpio_ext_pc3, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + + if(plugin_state->is_on) { + furi_hal_gpio_write(&gpio_ext_pc3, false); + plugin_state->is_on = false; + } else { + furi_hal_gpio_write(&gpio_ext_pc3, true); + plugin_state->is_on = true; + } +} + +int32_t flashlight_app() { + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); + + PluginState* plugin_state = malloc(sizeof(PluginState)); + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, plugin_state, sizeof(PluginState))) { + FURI_LOG_E("flashlight", "cannot create mutex\r\n"); + furi_message_queue_free(event_queue); + free(plugin_state); + return 255; + } + + // Set system callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, render_callback, &state_mutex); + view_port_input_callback_set(view_port, input_callback, event_queue); + + // Open GUI and register view_port + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + PluginEvent event; + for(bool processing = true; processing;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + + PluginState* plugin_state = (PluginState*)acquire_mutex_block(&state_mutex); + + if(event_status == FuriStatusOk) { + // press events + if(event.type == EventTypeKey) { + if(event.input.type == InputTypePress) { + switch(event.input.key) { + case InputKeyUp: + case InputKeyDown: + case InputKeyRight: + case InputKeyLeft: + break; + case InputKeyOk: + flash_toggle(plugin_state); + break; + case InputKeyBack: + processing = false; + break; + default: + break; + } + } + } + } + + view_port_update(view_port); + release_mutex(&state_mutex, plugin_state); + } + + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close(RECORD_GUI); + view_port_free(view_port); + furi_message_queue_free(event_queue); + delete_mutex(&state_mutex); + + return 0; +} \ No newline at end of file diff --git a/applications/plugins/gps_nmea_uart/LICENSE b/applications/plugins/gps_nmea_uart/LICENSE new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/applications/plugins/gps_nmea_uart/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/applications/plugins/gps_nmea_uart/README.md b/applications/plugins/gps_nmea_uart/README.md new file mode 100644 index 000000000..3f7a5ef3d --- /dev/null +++ b/applications/plugins/gps_nmea_uart/README.md @@ -0,0 +1,38 @@ +# GPS for Flipper Zero + +A simple Flipper Zero application for NMEA 0183 serial GPS modules, such as the + +[Original link](https://github.com/ezod/flipperzero-gps) +[Adafruit Ultimate GPS Breakout]. + +[Original link](https://github.com/ezod/flipperzero-gps) + +![ui](ui.png) + +Heavy lifting (NMEA parsing) provided by [minmea], which is included in this +repository. + +## Hardware Setup + +Connect the GPS module to power and the USART using GPIO pins 9 (3.3V), 11 +(GND), 13 (TX), and 14 (RX), as appropriate. + +![wiring](wiring.png) + + +## Contributing + +This project was a learning exercise and is more or less "complete" from my +perspective, but I will happily accept pull requests that improve and enhance +the functionality for others. + +Currently, the app only parses RMC and GGA sentences, and displays a subset of +the data that fits on the screen. The UART is also hard-coded to 9600 baud. +These limitations are largely driven by the GPS module I have to work with. A +more elaborate UI with scrolling or multiple screens, as well as a configurable +baud rate, may be useful for other GPS modules. + +[Adafruit Ultimate GPS Breakout]: https://www.adafruit.com/product/746 +[minmea]: https://github.com/kosma/minmea +[flipperzero-firmware]: https://github.com/flipperdevices/flipperzero-firmware +[qFlipper]: https://flipperzero.one/update diff --git a/applications/plugins/gps_nmea_uart/application.fam b/applications/plugins/gps_nmea_uart/application.fam new file mode 100644 index 000000000..138fb3f29 --- /dev/null +++ b/applications/plugins/gps_nmea_uart/application.fam @@ -0,0 +1,12 @@ +App( + appid="NMEA_GPS", + name="[NMEA] GPS", + apptype=FlipperAppType.EXTERNAL, + entry_point="gps_app", + cdefines=["APP_GPS"], + requires=["gui"], + stack_size=1 * 1024, + order=35, + fap_icon="gps_10px.png", + fap_category="GPIO", +) diff --git a/applications/plugins/gps_nmea_uart/gps.c b/applications/plugins/gps_nmea_uart/gps.c new file mode 100644 index 000000000..62053cede --- /dev/null +++ b/applications/plugins/gps_nmea_uart/gps.c @@ -0,0 +1,133 @@ +#include "gps_uart.h" + +#include +#include +#include + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} PluginEvent; + +static void render_callback(Canvas* const canvas, void* context) { + const GpsUart* gps_uart = acquire_mutex((ValueMutex*)context, 25); + if(gps_uart == NULL) { + return; + } + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 32, 8, AlignCenter, AlignBottom, "Latitude"); + canvas_draw_str_aligned(canvas, 96, 8, AlignCenter, AlignBottom, "Longitude"); + canvas_draw_str_aligned(canvas, 21, 30, AlignCenter, AlignBottom, "Course"); + canvas_draw_str_aligned(canvas, 64, 30, AlignCenter, AlignBottom, "Speed"); + canvas_draw_str_aligned(canvas, 107, 30, AlignCenter, AlignBottom, "Altitude"); + canvas_draw_str_aligned(canvas, 32, 52, AlignCenter, AlignBottom, "Satellites"); + canvas_draw_str_aligned(canvas, 96, 52, AlignCenter, AlignBottom, "Last Fix"); + + canvas_set_font(canvas, FontSecondary); + char buffer[64]; + snprintf(buffer, 64, "%f", (double)gps_uart->status.latitude); + canvas_draw_str_aligned(canvas, 32, 18, AlignCenter, AlignBottom, buffer); + snprintf(buffer, 64, "%f", (double)gps_uart->status.longitude); + canvas_draw_str_aligned(canvas, 96, 18, AlignCenter, AlignBottom, buffer); + snprintf(buffer, 64, "%.1f", (double)gps_uart->status.course); + canvas_draw_str_aligned(canvas, 21, 40, AlignCenter, AlignBottom, buffer); + snprintf(buffer, 64, "%.2f kn", (double)gps_uart->status.speed); + canvas_draw_str_aligned(canvas, 64, 40, AlignCenter, AlignBottom, buffer); + snprintf( + buffer, + 64, + "%.1f %c", + (double)gps_uart->status.altitude, + tolower(gps_uart->status.altitude_units)); + canvas_draw_str_aligned(canvas, 107, 40, AlignCenter, AlignBottom, buffer); + snprintf(buffer, 64, "%d", gps_uart->status.satellites_tracked); + canvas_draw_str_aligned(canvas, 32, 62, AlignCenter, AlignBottom, buffer); + snprintf( + buffer, + 64, + "%02d:%02d:%02d UTC", + gps_uart->status.time_hours, + gps_uart->status.time_minutes, + gps_uart->status.time_seconds); + canvas_draw_str_aligned(canvas, 96, 62, AlignCenter, AlignBottom, buffer); + + release_mutex((ValueMutex*)context, gps_uart); +} + +static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + PluginEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +int32_t gps_app(void* p) { + UNUSED(p); + + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); + + GpsUart* gps_uart = gps_uart_enable(); + + ValueMutex gps_uart_mutex; + if(!init_mutex(&gps_uart_mutex, gps_uart, sizeof(GpsUart))) { + FURI_LOG_E("GPS", "cannot create mutex\r\n"); + free(gps_uart); + return 255; + } + + // set system callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, render_callback, &gps_uart_mutex); + view_port_input_callback_set(view_port, input_callback, event_queue); + + // open GUI and register view_port + Gui* gui = furi_record_open("gui"); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + PluginEvent event; + for(bool processing = true; processing;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + + GpsUart* gps_uart = (GpsUart*)acquire_mutex_block(&gps_uart_mutex); + + if(event_status == FuriStatusOk) { + // press events + if(event.type == EventTypeKey) { + if(event.input.type == InputTypePress) { + switch(event.input.key) { + case InputKeyUp: + case InputKeyDown: + case InputKeyRight: + case InputKeyLeft: + case InputKeyOk: + break; + case InputKeyBack: + processing = false; + break; + default: + break; + } + } + } + } + + view_port_update(view_port); + release_mutex(&gps_uart_mutex, gps_uart); + } + + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close("gui"); + view_port_free(view_port); + furi_message_queue_free(event_queue); + delete_mutex(&gps_uart_mutex); + gps_uart_disable(gps_uart); + + return 0; +} diff --git a/applications/plugins/gps_nmea_uart/gps_10px.png b/applications/plugins/gps_nmea_uart/gps_10px.png new file mode 100644 index 000000000..841787a2a Binary files /dev/null and b/applications/plugins/gps_nmea_uart/gps_10px.png differ diff --git a/applications/plugins/gps_nmea_uart/gps_uart.c b/applications/plugins/gps_nmea_uart/gps_uart.c new file mode 100644 index 000000000..52ba660bc --- /dev/null +++ b/applications/plugins/gps_nmea_uart/gps_uart.c @@ -0,0 +1,173 @@ +#include + +#include "minmea.h" +#include "gps_uart.h" + +typedef enum { + WorkerEvtStop = (1 << 0), + WorkerEvtRxDone = (1 << 1), +} WorkerEvtFlags; + +#define WORKER_ALL_RX_EVENTS (WorkerEvtStop | WorkerEvtRxDone) + +static void gps_uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) { + GpsUart* gps_uart = (GpsUart*)context; + + if(ev == UartIrqEventRXNE) { + furi_stream_buffer_send(gps_uart->rx_stream, &data, 1, 0); + furi_thread_flags_set(furi_thread_get_id(gps_uart->thread), WorkerEvtRxDone); + } +} + +static void gps_uart_serial_init(GpsUart* gps_uart) { + furi_hal_console_disable(); + furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, gps_uart_on_irq_cb, gps_uart); + furi_hal_uart_set_br(FuriHalUartIdUSART1, GPS_BAUDRATE); +} + +static void gps_uart_serial_deinit(GpsUart* gps_uart) { + UNUSED(gps_uart); + furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, NULL, NULL); + furi_hal_console_enable(); +} + +static void gps_uart_parse_nmea(GpsUart* gps_uart, char* line) { + switch(minmea_sentence_id(line, false)) { + case MINMEA_SENTENCE_RMC: { + struct minmea_sentence_rmc frame; + if(minmea_parse_rmc(&frame, line)) { + gps_uart->status.valid = frame.valid; + gps_uart->status.latitude = minmea_tocoord(&frame.latitude); + gps_uart->status.longitude = minmea_tocoord(&frame.longitude); + gps_uart->status.speed = minmea_tofloat(&frame.speed); + gps_uart->status.course = minmea_tofloat(&frame.course); + gps_uart->status.time_hours = frame.time.hours; + gps_uart->status.time_minutes = frame.time.minutes; + gps_uart->status.time_seconds = frame.time.seconds; + + notification_message_block(gps_uart->notifications, &sequence_blink_green_10); + } + } break; + + case MINMEA_SENTENCE_GGA: { + struct minmea_sentence_gga frame; + if(minmea_parse_gga(&frame, line)) { + gps_uart->status.latitude = minmea_tocoord(&frame.latitude); + gps_uart->status.longitude = minmea_tocoord(&frame.longitude); + gps_uart->status.altitude = minmea_tofloat(&frame.altitude); + gps_uart->status.altitude_units = frame.altitude_units; + gps_uart->status.fix_quality = frame.fix_quality; + gps_uart->status.satellites_tracked = frame.satellites_tracked; + gps_uart->status.time_hours = frame.time.hours; + gps_uart->status.time_minutes = frame.time.minutes; + gps_uart->status.time_seconds = frame.time.seconds; + + notification_message_block(gps_uart->notifications, &sequence_blink_magenta_10); + } + } break; + + default: + break; + } +} + +static int32_t gps_uart_worker(void* context) { + GpsUart* gps_uart = (GpsUart*)context; + + gps_uart->rx_stream = furi_stream_buffer_alloc(RX_BUF_SIZE * 5, 1); + size_t rx_offset = 0; + + gps_uart_serial_init(gps_uart); + + while(1) { + uint32_t events = + furi_thread_flags_wait(WORKER_ALL_RX_EVENTS, FuriFlagWaitAny, FuriWaitForever); + furi_check((events & FuriFlagError) == 0); + + if(events & WorkerEvtStop) { + break; + } + + if(events & WorkerEvtRxDone) { + size_t len = 0; + do { + len = furi_stream_buffer_receive( + gps_uart->rx_stream, + gps_uart->rx_buf + rx_offset, + RX_BUF_SIZE - 1 - rx_offset, + 0); + if(len > 0) { + rx_offset += len; + gps_uart->rx_buf[rx_offset] = '\0'; + + char* line_current = (char*)gps_uart->rx_buf; + while(1) { + while(*line_current == '\0' && + line_current < (char*)gps_uart->rx_buf + rx_offset - 1) { + line_current++; + } + + char* newline = strchr(line_current, '\n'); + if(newline) { + *newline = '\0'; + gps_uart_parse_nmea(gps_uart, line_current); + line_current = newline + 1; + } else { + if(line_current > (char*)gps_uart->rx_buf) { + rx_offset = 0; + while(*line_current) { + gps_uart->rx_buf[rx_offset++] = *(line_current++); + } + } + break; + } + } + } + } while(len > 0); + } + } + + gps_uart_serial_deinit(gps_uart); + furi_stream_buffer_free(gps_uart->rx_stream); + + return 0; +} + +GpsUart* gps_uart_enable() { + GpsUart* gps_uart = malloc(sizeof(GpsUart)); + + gps_uart->status.valid = false; + gps_uart->status.latitude = 0.0; + gps_uart->status.longitude = 0.0; + gps_uart->status.speed = 0.0; + gps_uart->status.course = 0.0; + gps_uart->status.altitude = 0.0; + gps_uart->status.altitude_units = ' '; + gps_uart->status.fix_quality = 0; + gps_uart->status.satellites_tracked = 0; + gps_uart->status.time_hours = 0; + gps_uart->status.time_minutes = 0; + gps_uart->status.time_seconds = 0; + + gps_uart->notifications = furi_record_open(RECORD_NOTIFICATION); + + gps_uart->thread = furi_thread_alloc(); + furi_thread_set_name(gps_uart->thread, "GpsUartWorker"); + furi_thread_set_stack_size(gps_uart->thread, 1024); + furi_thread_set_context(gps_uart->thread, gps_uart); + furi_thread_set_callback(gps_uart->thread, gps_uart_worker); + + furi_thread_start(gps_uart->thread); + return gps_uart; +} + +void gps_uart_disable(GpsUart* gps_uart) { + furi_assert(gps_uart); + furi_thread_flags_set(furi_thread_get_id(gps_uart->thread), WorkerEvtStop); + furi_thread_join(gps_uart->thread); + furi_thread_free(gps_uart->thread); + + furi_record_close(RECORD_NOTIFICATION); + + free(gps_uart); +} diff --git a/applications/plugins/gps_nmea_uart/gps_uart.h b/applications/plugins/gps_nmea_uart/gps_uart.h new file mode 100644 index 000000000..d6aafae9f --- /dev/null +++ b/applications/plugins/gps_nmea_uart/gps_uart.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +#define GPS_BAUDRATE 9600 +#define RX_BUF_SIZE 1024 + +typedef struct { + bool valid; + float latitude; + float longitude; + float speed; + float course; + float altitude; + char altitude_units; + int fix_quality; + int satellites_tracked; + int time_hours; + int time_minutes; + int time_seconds; +} GpsStatus; + +typedef struct { + FuriThread* thread; + FuriStreamBuffer* rx_stream; + uint8_t rx_buf[RX_BUF_SIZE]; + + NotificationApp* notifications; + + GpsStatus status; +} GpsUart; + +GpsUart* gps_uart_enable(); + +void gps_uart_disable(GpsUart* gps_uart); diff --git a/applications/plugins/gps_nmea_uart/minmea.c b/applications/plugins/gps_nmea_uart/minmea.c new file mode 100644 index 000000000..1b7a84b1c --- /dev/null +++ b/applications/plugins/gps_nmea_uart/minmea.c @@ -0,0 +1,640 @@ +/* + * Copyright © 2014 Kosma Moczek + * This program is free software. It comes without any warranty, to the extent + * permitted by applicable law. You can redistribute it and/or modify it under + * the terms of the Do What The Fuck You Want To Public License, Version 2, as + * published by Sam Hocevar. See the COPYING file for more details. + */ + +#include "minmea.h" + +#include +#include +#include + +#define boolstr(s) ((s) ? "true" : "false") + +static int hex2int(char c) { + if(c >= '0' && c <= '9') return c - '0'; + if(c >= 'A' && c <= 'F') return c - 'A' + 10; + if(c >= 'a' && c <= 'f') return c - 'a' + 10; + return -1; +} + +uint8_t minmea_checksum(const char* sentence) { + // Support senteces with or without the starting dollar sign. + if(*sentence == '$') sentence++; + + uint8_t checksum = 0x00; + + // The optional checksum is an XOR of all bytes between "$" and "*". + while(*sentence && *sentence != '*') checksum ^= *sentence++; + + return checksum; +} + +bool minmea_check(const char* sentence, bool strict) { + uint8_t checksum = 0x00; + + // A valid sentence starts with "$". + if(*sentence++ != '$') return false; + + // The optional checksum is an XOR of all bytes between "$" and "*". + while(*sentence && *sentence != '*' && isprint((unsigned char)*sentence)) + checksum ^= *sentence++; + + // If checksum is present... + if(*sentence == '*') { + // Extract checksum. + sentence++; + int upper = hex2int(*sentence++); + if(upper == -1) return false; + int lower = hex2int(*sentence++); + if(lower == -1) return false; + int expected = upper << 4 | lower; + + // Check for checksum mismatch. + if(checksum != expected) return false; + } else if(strict) { + // Discard non-checksummed frames in strict mode. + return false; + } + + // The only stuff allowed at this point is a newline. + while(*sentence == '\r' || *sentence == '\n') { + sentence++; + } + + if(*sentence) { + return false; + } + + return true; +} + +bool minmea_scan(const char* sentence, const char* format, ...) { + bool result = false; + bool optional = false; + + if(sentence == NULL) return false; + + va_list ap; + va_start(ap, format); + + const char* field = sentence; +#define next_field() \ + do { \ + /* Progress to the next field. */ \ + while(minmea_isfield(*sentence)) sentence++; \ + /* Make sure there is a field there. */ \ + if(*sentence == ',') { \ + sentence++; \ + field = sentence; \ + } else { \ + field = NULL; \ + } \ + } while(0) + + while(*format) { + char type = *format++; + + if(type == ';') { + // All further fields are optional. + optional = true; + continue; + } + + if(!field && !optional) { + // Field requested but we ran out if input. Bail out. + goto parse_error; + } + + switch(type) { + case 'c': { // Single character field (char). + char value = '\0'; + + if(field && minmea_isfield(*field)) value = *field; + + *va_arg(ap, char*) = value; + } break; + + case 'd': { // Single character direction field (int). + int value = 0; + + if(field && minmea_isfield(*field)) { + switch(*field) { + case 'N': + case 'E': + value = 1; + break; + case 'S': + case 'W': + value = -1; + break; + default: + goto parse_error; + } + } + + *va_arg(ap, int*) = value; + } break; + + case 'f': { // Fractional value with scale (struct minmea_float). + int sign = 0; + int_least32_t value = -1; + int_least32_t scale = 0; + + if(field) { + while(minmea_isfield(*field)) { + if(*field == '+' && !sign && value == -1) { + sign = 1; + } else if(*field == '-' && !sign && value == -1) { + sign = -1; + } else if(isdigit((unsigned char)*field)) { + int digit = *field - '0'; + if(value == -1) value = 0; + if(value > (INT_LEAST32_MAX - digit) / 10) { + /* we ran out of bits, what do we do? */ + if(scale) { + /* truncate extra precision */ + break; + } else { + /* integer overflow. bail out. */ + goto parse_error; + } + } + value = (10 * value) + digit; + if(scale) scale *= 10; + } else if(*field == '.' && scale == 0) { + scale = 1; + } else if(*field == ' ') { + /* Allow spaces at the start of the field. Not NMEA + * conformant, but some modules do this. */ + if(sign != 0 || value != -1 || scale != 0) goto parse_error; + } else { + goto parse_error; + } + field++; + } + } + + if((sign || scale) && value == -1) goto parse_error; + + if(value == -1) { + /* No digits were scanned. */ + value = 0; + scale = 0; + } else if(scale == 0) { + /* No decimal point. */ + scale = 1; + } + if(sign) value *= sign; + + *va_arg(ap, struct minmea_float*) = (struct minmea_float){value, scale}; + } break; + + case 'i': { // Integer value, default 0 (int). + int value = 0; + + if(field) { + char* endptr; + value = strtol(field, &endptr, 10); + if(minmea_isfield(*endptr)) goto parse_error; + } + + *va_arg(ap, int*) = value; + } break; + + case 's': { // String value (char *). + char* buf = va_arg(ap, char*); + + if(field) { + while(minmea_isfield(*field)) *buf++ = *field++; + } + + *buf = '\0'; + } break; + + case 't': { // NMEA talker+sentence identifier (char *). + // This field is always mandatory. + if(!field) goto parse_error; + + if(field[0] != '$') goto parse_error; + for(int f = 0; f < 5; f++) + if(!minmea_isfield(field[1 + f])) goto parse_error; + + char* buf = va_arg(ap, char*); + memcpy(buf, field + 1, 5); + buf[5] = '\0'; + } break; + + case 'D': { // Date (int, int, int), -1 if empty. + struct minmea_date* date = va_arg(ap, struct minmea_date*); + + int d = -1, m = -1, y = -1; + + if(field && minmea_isfield(*field)) { + // Always six digits. + for(int f = 0; f < 6; f++) + if(!isdigit((unsigned char)field[f])) goto parse_error; + + char dArr[] = {field[0], field[1], '\0'}; + char mArr[] = {field[2], field[3], '\0'}; + char yArr[] = {field[4], field[5], '\0'}; + d = strtol(dArr, NULL, 10); + m = strtol(mArr, NULL, 10); + y = strtol(yArr, NULL, 10); + } + + date->day = d; + date->month = m; + date->year = y; + } break; + + case 'T': { // Time (int, int, int, int), -1 if empty. + struct minmea_time* time_ = va_arg(ap, struct minmea_time*); + + int h = -1, i = -1, s = -1, u = -1; + + if(field && minmea_isfield(*field)) { + // Minimum required: integer time. + for(int f = 0; f < 6; f++) + if(!isdigit((unsigned char)field[f])) goto parse_error; + + char hArr[] = {field[0], field[1], '\0'}; + char iArr[] = {field[2], field[3], '\0'}; + char sArr[] = {field[4], field[5], '\0'}; + h = strtol(hArr, NULL, 10); + i = strtol(iArr, NULL, 10); + s = strtol(sArr, NULL, 10); + field += 6; + + // Extra: fractional time. Saved as microseconds. + if(*field++ == '.') { + uint32_t value = 0; + uint32_t scale = 1000000LU; + while(isdigit((unsigned char)*field) && scale > 1) { + value = (value * 10) + (*field++ - '0'); + scale /= 10; + } + u = value * scale; + } else { + u = 0; + } + } + + time_->hours = h; + time_->minutes = i; + time_->seconds = s; + time_->microseconds = u; + } break; + + case '_': { // Ignore the field. + } break; + + default: { // Unknown. + goto parse_error; + } + } + + next_field(); + } + + result = true; + +parse_error: + va_end(ap); + return result; +} + +bool minmea_talker_id(char talker[3], const char* sentence) { + char type[6]; + if(!minmea_scan(sentence, "t", type)) return false; + + talker[0] = type[0]; + talker[1] = type[1]; + talker[2] = '\0'; + + return true; +} + +enum minmea_sentence_id minmea_sentence_id(const char* sentence, bool strict) { + if(!minmea_check(sentence, strict)) return MINMEA_INVALID; + + char type[6]; + if(!minmea_scan(sentence, "t", type)) return MINMEA_INVALID; + + if(!strcmp(type + 2, "GBS")) return MINMEA_SENTENCE_GBS; + if(!strcmp(type + 2, "GGA")) return MINMEA_SENTENCE_GGA; + if(!strcmp(type + 2, "GLL")) return MINMEA_SENTENCE_GLL; + if(!strcmp(type + 2, "GSA")) return MINMEA_SENTENCE_GSA; + if(!strcmp(type + 2, "GST")) return MINMEA_SENTENCE_GST; + if(!strcmp(type + 2, "GSV")) return MINMEA_SENTENCE_GSV; + if(!strcmp(type + 2, "RMC")) return MINMEA_SENTENCE_RMC; + if(!strcmp(type + 2, "VTG")) return MINMEA_SENTENCE_VTG; + if(!strcmp(type + 2, "ZDA")) return MINMEA_SENTENCE_ZDA; + + return MINMEA_UNKNOWN; +} + +bool minmea_parse_gbs(struct minmea_sentence_gbs* frame, const char* sentence) { + // $GNGBS,170556.00,3.0,2.9,8.3,,,,*5C + char type[6]; + if(!minmea_scan( + sentence, + "tTfffifff", + type, + &frame->time, + &frame->err_latitude, + &frame->err_longitude, + &frame->err_altitude, + &frame->svid, + &frame->prob, + &frame->bias, + &frame->stddev)) + return false; + if(strcmp(type + 2, "GBS")) return false; + + return true; +} + +bool minmea_parse_rmc(struct minmea_sentence_rmc* frame, const char* sentence) { + // $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62 + char type[6]; + char validity; + int latitude_direction; + int longitude_direction; + int variation_direction; + if(!minmea_scan( + sentence, + "tTcfdfdffDfd", + type, + &frame->time, + &validity, + &frame->latitude, + &latitude_direction, + &frame->longitude, + &longitude_direction, + &frame->speed, + &frame->course, + &frame->date, + &frame->variation, + &variation_direction)) + return false; + if(strcmp(type + 2, "RMC")) return false; + + frame->valid = (validity == 'A'); + frame->latitude.value *= latitude_direction; + frame->longitude.value *= longitude_direction; + frame->variation.value *= variation_direction; + + return true; +} + +bool minmea_parse_gga(struct minmea_sentence_gga* frame, const char* sentence) { + // $GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47 + char type[6]; + int latitude_direction; + int longitude_direction; + + if(!minmea_scan( + sentence, + "tTfdfdiiffcfcf_", + type, + &frame->time, + &frame->latitude, + &latitude_direction, + &frame->longitude, + &longitude_direction, + &frame->fix_quality, + &frame->satellites_tracked, + &frame->hdop, + &frame->altitude, + &frame->altitude_units, + &frame->height, + &frame->height_units, + &frame->dgps_age)) + return false; + if(strcmp(type + 2, "GGA")) return false; + + frame->latitude.value *= latitude_direction; + frame->longitude.value *= longitude_direction; + + return true; +} + +bool minmea_parse_gsa(struct minmea_sentence_gsa* frame, const char* sentence) { + // $GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39 + char type[6]; + + if(!minmea_scan( + sentence, + "tciiiiiiiiiiiiifff", + type, + &frame->mode, + &frame->fix_type, + &frame->sats[0], + &frame->sats[1], + &frame->sats[2], + &frame->sats[3], + &frame->sats[4], + &frame->sats[5], + &frame->sats[6], + &frame->sats[7], + &frame->sats[8], + &frame->sats[9], + &frame->sats[10], + &frame->sats[11], + &frame->pdop, + &frame->hdop, + &frame->vdop)) + return false; + if(strcmp(type + 2, "GSA")) return false; + + return true; +} + +bool minmea_parse_gll(struct minmea_sentence_gll* frame, const char* sentence) { + // $GPGLL,3723.2475,N,12158.3416,W,161229.487,A,A*41$; + char type[6]; + int latitude_direction; + int longitude_direction; + + if(!minmea_scan( + sentence, + "tfdfdTc;c", + type, + &frame->latitude, + &latitude_direction, + &frame->longitude, + &longitude_direction, + &frame->time, + &frame->status, + &frame->mode)) + return false; + if(strcmp(type + 2, "GLL")) return false; + + frame->latitude.value *= latitude_direction; + frame->longitude.value *= longitude_direction; + + return true; +} + +bool minmea_parse_gst(struct minmea_sentence_gst* frame, const char* sentence) { + // $GPGST,024603.00,3.2,6.6,4.7,47.3,5.8,5.6,22.0*58 + char type[6]; + + if(!minmea_scan( + sentence, + "tTfffffff", + type, + &frame->time, + &frame->rms_deviation, + &frame->semi_major_deviation, + &frame->semi_minor_deviation, + &frame->semi_major_orientation, + &frame->latitude_error_deviation, + &frame->longitude_error_deviation, + &frame->altitude_error_deviation)) + return false; + if(strcmp(type + 2, "GST")) return false; + + return true; +} + +bool minmea_parse_gsv(struct minmea_sentence_gsv* frame, const char* sentence) { + // $GPGSV,3,1,11,03,03,111,00,04,15,270,00,06,01,010,00,13,06,292,00*74 + // $GPGSV,3,3,11,22,42,067,42,24,14,311,43,27,05,244,00,,,,*4D + // $GPGSV,4,2,11,08,51,203,30,09,45,215,28*75 + // $GPGSV,4,4,13,39,31,170,27*40 + // $GPGSV,4,4,13*7B + char type[6]; + + if(!minmea_scan( + sentence, + "tiii;iiiiiiiiiiiiiiii", + type, + &frame->total_msgs, + &frame->msg_nr, + &frame->total_sats, + &frame->sats[0].nr, + &frame->sats[0].elevation, + &frame->sats[0].azimuth, + &frame->sats[0].snr, + &frame->sats[1].nr, + &frame->sats[1].elevation, + &frame->sats[1].azimuth, + &frame->sats[1].snr, + &frame->sats[2].nr, + &frame->sats[2].elevation, + &frame->sats[2].azimuth, + &frame->sats[2].snr, + &frame->sats[3].nr, + &frame->sats[3].elevation, + &frame->sats[3].azimuth, + &frame->sats[3].snr)) { + return false; + } + if(strcmp(type + 2, "GSV")) return false; + + return true; +} + +bool minmea_parse_vtg(struct minmea_sentence_vtg* frame, const char* sentence) { + // $GPVTG,054.7,T,034.4,M,005.5,N,010.2,K*48 + // $GPVTG,156.1,T,140.9,M,0.0,N,0.0,K*41 + // $GPVTG,096.5,T,083.5,M,0.0,N,0.0,K,D*22 + // $GPVTG,188.36,T,,M,0.820,N,1.519,K,A*3F + char type[6]; + char c_true, c_magnetic, c_knots, c_kph, c_faa_mode; + + if(!minmea_scan( + sentence, + "t;fcfcfcfcc", + type, + &frame->true_track_degrees, + &c_true, + &frame->magnetic_track_degrees, + &c_magnetic, + &frame->speed_knots, + &c_knots, + &frame->speed_kph, + &c_kph, + &c_faa_mode)) + return false; + if(strcmp(type + 2, "VTG")) return false; + // values are only valid with the accompanying characters + if(c_true != 'T') frame->true_track_degrees.scale = 0; + if(c_magnetic != 'M') frame->magnetic_track_degrees.scale = 0; + if(c_knots != 'N') frame->speed_knots.scale = 0; + if(c_kph != 'K') frame->speed_kph.scale = 0; + frame->faa_mode = (enum minmea_faa_mode)c_faa_mode; + + return true; +} + +bool minmea_parse_zda(struct minmea_sentence_zda* frame, const char* sentence) { + // $GPZDA,201530.00,04,07,2002,00,00*60 + char type[6]; + + if(!minmea_scan( + sentence, + "tTiiiii", + type, + &frame->time, + &frame->date.day, + &frame->date.month, + &frame->date.year, + &frame->hour_offset, + &frame->minute_offset)) + return false; + if(strcmp(type + 2, "ZDA")) return false; + + // check offsets + if(abs(frame->hour_offset) > 13 || frame->minute_offset > 59 || frame->minute_offset < 0) + return false; + + return true; +} + +int minmea_getdatetime( + struct tm* tm, + const struct minmea_date* date, + const struct minmea_time* time_) { + if(date->year == -1 || time_->hours == -1) return -1; + + memset(tm, 0, sizeof(*tm)); + if(date->year < 80) { + tm->tm_year = 2000 + date->year - 1900; // 2000-2079 + } else if(date->year >= 1900) { + tm->tm_year = date->year - 1900; // 4 digit year, use directly + } else { + tm->tm_year = date->year; // 1980-1999 + } + tm->tm_mon = date->month - 1; + tm->tm_mday = date->day; + tm->tm_hour = time_->hours; + tm->tm_min = time_->minutes; + tm->tm_sec = time_->seconds; + + return 0; +} + +int minmea_gettime( + struct timespec* ts, + const struct minmea_date* date, + const struct minmea_time* time_) { + struct tm tm; + if(minmea_getdatetime(&tm, date, time_)) return -1; + + time_t timestamp = mktime(&tm); /* See README.md if your system lacks timegm(). */ + if(timestamp != (time_t)-1) { + ts->tv_sec = timestamp; + ts->tv_nsec = time_->microseconds * 1000; + return 0; + } else { + return -1; + } +} + +/* vim: set ts=4 sw=4 et: */ diff --git a/applications/plugins/gps_nmea_uart/minmea.h b/applications/plugins/gps_nmea_uart/minmea.h new file mode 100644 index 000000000..88eec4ae9 --- /dev/null +++ b/applications/plugins/gps_nmea_uart/minmea.h @@ -0,0 +1,295 @@ +/* + * Copyright © 2014 Kosma Moczek + * This program is free software. It comes without any warranty, to the extent + * permitted by applicable law. You can redistribute it and/or modify it under + * the terms of the Do What The Fuck You Want To Public License, Version 2, as + * published by Sam Hocevar. See the COPYING file for more details. + */ + +#ifndef MINMEA_H +#define MINMEA_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include +#include +#ifdef MINMEA_INCLUDE_COMPAT +#include +#endif + +#ifndef MINMEA_MAX_SENTENCE_LENGTH +#define MINMEA_MAX_SENTENCE_LENGTH 80 +#endif + +enum minmea_sentence_id { + MINMEA_INVALID = -1, + MINMEA_UNKNOWN = 0, + MINMEA_SENTENCE_GBS, + MINMEA_SENTENCE_GGA, + MINMEA_SENTENCE_GLL, + MINMEA_SENTENCE_GSA, + MINMEA_SENTENCE_GST, + MINMEA_SENTENCE_GSV, + MINMEA_SENTENCE_RMC, + MINMEA_SENTENCE_VTG, + MINMEA_SENTENCE_ZDA, +}; + +struct minmea_float { + int_least32_t value; + int_least32_t scale; +}; + +struct minmea_date { + int day; + int month; + int year; +}; + +struct minmea_time { + int hours; + int minutes; + int seconds; + int microseconds; +}; + +struct minmea_sentence_gbs { + struct minmea_time time; + struct minmea_float err_latitude; + struct minmea_float err_longitude; + struct minmea_float err_altitude; + int svid; + struct minmea_float prob; + struct minmea_float bias; + struct minmea_float stddev; +}; + +struct minmea_sentence_rmc { + struct minmea_time time; + bool valid; + struct minmea_float latitude; + struct minmea_float longitude; + struct minmea_float speed; + struct minmea_float course; + struct minmea_date date; + struct minmea_float variation; +}; + +struct minmea_sentence_gga { + struct minmea_time time; + struct minmea_float latitude; + struct minmea_float longitude; + int fix_quality; + int satellites_tracked; + struct minmea_float hdop; + struct minmea_float altitude; + char altitude_units; + struct minmea_float height; + char height_units; + struct minmea_float dgps_age; +}; + +enum minmea_gll_status { + MINMEA_GLL_STATUS_DATA_VALID = 'A', + MINMEA_GLL_STATUS_DATA_NOT_VALID = 'V', +}; + +// FAA mode added to some fields in NMEA 2.3. +enum minmea_faa_mode { + MINMEA_FAA_MODE_AUTONOMOUS = 'A', + MINMEA_FAA_MODE_DIFFERENTIAL = 'D', + MINMEA_FAA_MODE_ESTIMATED = 'E', + MINMEA_FAA_MODE_MANUAL = 'M', + MINMEA_FAA_MODE_SIMULATED = 'S', + MINMEA_FAA_MODE_NOT_VALID = 'N', + MINMEA_FAA_MODE_PRECISE = 'P', +}; + +struct minmea_sentence_gll { + struct minmea_float latitude; + struct minmea_float longitude; + struct minmea_time time; + char status; + char mode; +}; + +struct minmea_sentence_gst { + struct minmea_time time; + struct minmea_float rms_deviation; + struct minmea_float semi_major_deviation; + struct minmea_float semi_minor_deviation; + struct minmea_float semi_major_orientation; + struct minmea_float latitude_error_deviation; + struct minmea_float longitude_error_deviation; + struct minmea_float altitude_error_deviation; +}; + +enum minmea_gsa_mode { + MINMEA_GPGSA_MODE_AUTO = 'A', + MINMEA_GPGSA_MODE_FORCED = 'M', +}; + +enum minmea_gsa_fix_type { + MINMEA_GPGSA_FIX_NONE = 1, + MINMEA_GPGSA_FIX_2D = 2, + MINMEA_GPGSA_FIX_3D = 3, +}; + +struct minmea_sentence_gsa { + char mode; + int fix_type; + int sats[12]; + struct minmea_float pdop; + struct minmea_float hdop; + struct minmea_float vdop; +}; + +struct minmea_sat_info { + int nr; + int elevation; + int azimuth; + int snr; +}; + +struct minmea_sentence_gsv { + int total_msgs; + int msg_nr; + int total_sats; + struct minmea_sat_info sats[4]; +}; + +struct minmea_sentence_vtg { + struct minmea_float true_track_degrees; + struct minmea_float magnetic_track_degrees; + struct minmea_float speed_knots; + struct minmea_float speed_kph; + enum minmea_faa_mode faa_mode; +}; + +struct minmea_sentence_zda { + struct minmea_time time; + struct minmea_date date; + int hour_offset; + int minute_offset; +}; + +/** + * Calculate raw sentence checksum. Does not check sentence integrity. + */ +uint8_t minmea_checksum(const char* sentence); + +/** + * Check sentence validity and checksum. Returns true for valid sentences. + */ +bool minmea_check(const char* sentence, bool strict); + +/** + * Determine talker identifier. + */ +bool minmea_talker_id(char talker[3], const char* sentence); + +/** + * Determine sentence identifier. + */ +enum minmea_sentence_id minmea_sentence_id(const char* sentence, bool strict); + +/** + * Scanf-like processor for NMEA sentences. Supports the following formats: + * c - single character (char *) + * d - direction, returned as 1/-1, default 0 (int *) + * f - fractional, returned as value + scale (struct minmea_float *) + * i - decimal, default zero (int *) + * s - string (char *) + * t - talker identifier and type (char *) + * D - date (struct minmea_date *) + * T - time stamp (struct minmea_time *) + * _ - ignore this field + * ; - following fields are optional + * Returns true on success. See library source code for details. + */ +bool minmea_scan(const char* sentence, const char* format, ...); + +/* + * Parse a specific type of sentence. Return true on success. + */ +bool minmea_parse_gbs(struct minmea_sentence_gbs* frame, const char* sentence); +bool minmea_parse_rmc(struct minmea_sentence_rmc* frame, const char* sentence); +bool minmea_parse_gga(struct minmea_sentence_gga* frame, const char* sentence); +bool minmea_parse_gsa(struct minmea_sentence_gsa* frame, const char* sentence); +bool minmea_parse_gll(struct minmea_sentence_gll* frame, const char* sentence); +bool minmea_parse_gst(struct minmea_sentence_gst* frame, const char* sentence); +bool minmea_parse_gsv(struct minmea_sentence_gsv* frame, const char* sentence); +bool minmea_parse_vtg(struct minmea_sentence_vtg* frame, const char* sentence); +bool minmea_parse_zda(struct minmea_sentence_zda* frame, const char* sentence); + +/** + * Convert GPS UTC date/time representation to a UNIX calendar time. + */ +int minmea_getdatetime( + struct tm* tm, + const struct minmea_date* date, + const struct minmea_time* time_); + +/** + * Convert GPS UTC date/time representation to a UNIX timestamp. + */ +int minmea_gettime( + struct timespec* ts, + const struct minmea_date* date, + const struct minmea_time* time_); + +/** + * Rescale a fixed-point value to a different scale. Rounds towards zero. + */ +static inline int_least32_t minmea_rescale(const struct minmea_float* f, int_least32_t new_scale) { + if(f->scale == 0) return 0; + if(f->scale == new_scale) return f->value; + if(f->scale > new_scale) + return (f->value + ((f->value > 0) - (f->value < 0)) * f->scale / new_scale / 2) / + (f->scale / new_scale); + else + return f->value * (new_scale / f->scale); +} + +/** + * Convert a fixed-point value to a floating-point value. + * Returns NaN for "unknown" values. + */ +static inline float minmea_tofloat(const struct minmea_float* f) { + if(f->scale == 0) return NAN; + return (float)f->value / (float)f->scale; +} + +/** + * Convert a raw coordinate to a floating point DD.DDD... value. + * Returns NaN for "unknown" values. + */ +static inline float minmea_tocoord(const struct minmea_float* f) { + if(f->scale == 0) return NAN; + if(f->scale > (INT_LEAST32_MAX / 100)) return NAN; + if(f->scale < (INT_LEAST32_MIN / 100)) return NAN; + int_least32_t degrees = f->value / (f->scale * 100); + int_least32_t minutes = f->value % (f->scale * 100); + return (float)degrees + (float)minutes / (60 * f->scale); +} + +/** + * Check whether a character belongs to the set of characters allowed in a + * sentence data field. + */ +static inline bool minmea_isfield(char c) { + return isprint((unsigned char)c) && c != ',' && c != '*'; +} + +#ifdef __cplusplus +} +#endif + +#endif /* MINMEA_H */ + +/* vim: set ts=4 sw=4 et: */ diff --git a/applications/plugins/gps_nmea_uart/ui.png b/applications/plugins/gps_nmea_uart/ui.png new file mode 100644 index 000000000..8e5214574 Binary files /dev/null and b/applications/plugins/gps_nmea_uart/ui.png differ diff --git a/applications/plugins/gps_nmea_uart/wiring.png b/applications/plugins/gps_nmea_uart/wiring.png new file mode 100644 index 000000000..74b4a4401 Binary files /dev/null and b/applications/plugins/gps_nmea_uart/wiring.png differ diff --git a/applications/plugins/hc_sr04/README.md b/applications/plugins/hc_sr04/README.md new file mode 100644 index 000000000..2fb3b8ce4 --- /dev/null +++ b/applications/plugins/hc_sr04/README.md @@ -0,0 +1 @@ +## (5V -> VCC) / (GND -> GND) / (13|TX -> Trig) / (14|RX -> Echo) \ No newline at end of file diff --git a/applications/plugins/hc_sr04/application.fam b/applications/plugins/hc_sr04/application.fam new file mode 100644 index 000000000..da91a0864 --- /dev/null +++ b/applications/plugins/hc_sr04/application.fam @@ -0,0 +1,14 @@ +App( + appid="HC_SR04_Dist_Sensor", + name="[HC-SR] Dist. Sensor", + apptype=FlipperAppType.EXTERNAL, + entry_point="hc_sr04_app", + cdefines=["APP_HC_SR04"], + requires=[ + "gui", + ], + stack_size=2 * 1024, + order=20, + fap_icon="dist_sensor10px.png", + fap_category="GPIO", +) \ No newline at end of file diff --git a/applications/plugins/hc_sr04/dist_sensor10px.png b/applications/plugins/hc_sr04/dist_sensor10px.png new file mode 100644 index 000000000..af9aa7358 Binary files /dev/null and b/applications/plugins/hc_sr04/dist_sensor10px.png differ diff --git a/applications/plugins/hc_sr04/hc_sr04.c b/applications/plugins/hc_sr04/hc_sr04.c new file mode 100644 index 000000000..dbbf4f3ec --- /dev/null +++ b/applications/plugins/hc_sr04/hc_sr04.c @@ -0,0 +1,273 @@ +// insired by +// https://github.com/esphome/esphome/blob/ac0d921413c3884752193fe568fa82853f0f99e9/esphome/components/ultrasonic/ultrasonic_sensor.cpp +// Ported and modified by @xMasterX + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} PluginEvent; + +typedef struct { + NotificationApp* notification; + bool have_5v; + bool measurement_made; + uint32_t echo; // ms + float distance; // meters +} PluginState; + +const NotificationSequence sequence_done = { + &message_display_backlight_on, + &message_green_255, + &message_note_c5, + &message_delay_50, + &message_sound_off, + NULL, +}; + +static void render_callback(Canvas* const canvas, void* ctx) { + const PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25); + if(plugin_state == NULL) { + return; + } + // border around the edge of the screen + // canvas_draw_frame(canvas, 0, 0, 128, 64); + + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned( + canvas, 64, 2, AlignCenter, AlignTop, "HC-SR04 Ultrasonic\nDistance Sensor"); + + canvas_set_font(canvas, FontSecondary); + + if(!plugin_state->have_5v) { + elements_multiline_text_aligned( + canvas, + 4, + 28, + AlignLeft, + AlignTop, + "5V on GPIO must be\nenabled, or USB must\nbe connected."); + } else { + if(!plugin_state->measurement_made) { + elements_multiline_text_aligned( + canvas, 64, 28, AlignCenter, AlignTop, "Press OK button to measure"); + elements_multiline_text_aligned( + canvas, 64, 40, AlignCenter, AlignTop, "13/TX -> Trig\n14/RX -> Echo"); + } else { + elements_multiline_text_aligned(canvas, 4, 28, AlignLeft, AlignTop, "Readout:"); + + FuriString* str_buf; + str_buf = furi_string_alloc(); + furi_string_printf(str_buf, "Echo: %ld ms", plugin_state->echo); + + canvas_draw_str_aligned( + canvas, 8, 38, AlignLeft, AlignTop, furi_string_get_cstr(str_buf)); + furi_string_printf(str_buf, "Distance: %02f m", (double)plugin_state->distance); + canvas_draw_str_aligned( + canvas, 8, 48, AlignLeft, AlignTop, furi_string_get_cstr(str_buf)); + + furi_string_free(str_buf); + } + } + + release_mutex((ValueMutex*)ctx, plugin_state); +} + +static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + PluginEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void hc_sr04_state_init(PluginState* const plugin_state) { + plugin_state->echo = -1; + plugin_state->distance = -1; + plugin_state->measurement_made = false; + + furi_hal_power_suppress_charge_enter(); + + plugin_state->have_5v = false; + if(furi_hal_power_is_otg_enabled() || furi_hal_power_is_charging()) { + plugin_state->have_5v = true; + } else { + furi_hal_power_enable_otg(); + plugin_state->have_5v = true; + } +} + +float hc_sr04_ms_to_m(uint32_t ms) { + const float speed_sound_m_per_s = 343.0f; + const float time_s = ms / 1e3f; + const float total_dist = time_s * speed_sound_m_per_s; + return total_dist / 2.0f; +} + +static void hc_sr04_measure(PluginState* const plugin_state) { + //plugin_state->echo = 1; + //return; + + if(!plugin_state->have_5v) { + if(furi_hal_power_is_otg_enabled() || furi_hal_power_is_charging()) { + plugin_state->have_5v = true; + } else { + return; + } + } + + //furi_hal_light_set(LightRed, 0xFF); + notification_message(plugin_state->notification, &sequence_blink_start_yellow); + + const uint32_t timeout_ms = 2000; + // Pin 13 / TX -> Trig + furi_hal_gpio_write(&gpio_usart_tx, false); + furi_hal_gpio_init(&gpio_usart_tx, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + + // Pin 14 / RX -> Echo + furi_hal_gpio_write(&gpio_usart_rx, false); + furi_hal_gpio_init(&gpio_usart_rx, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); + + //FURI_CRITICAL_ENTER(); + // 10 ms pulse on TX + furi_hal_gpio_write(&gpio_usart_tx, true); + furi_delay_ms(10); + furi_hal_gpio_write(&gpio_usart_tx, false); + + // TODO change from furi_get_tick(), which returns ms, + // to DWT->CYCCNT, which is a more precise counter with + // us precision (see furi_hal_cortex_delay_us) + + const uint32_t start = furi_get_tick(); + + while(furi_get_tick() - start < timeout_ms && furi_hal_gpio_read(&gpio_usart_rx)) + ; + while(furi_get_tick() - start < timeout_ms && !furi_hal_gpio_read(&gpio_usart_rx)) + ; + + const uint32_t pulse_start = furi_get_tick(); + + while(furi_get_tick() - start < timeout_ms && furi_hal_gpio_read(&gpio_usart_rx)) + ; + + const uint32_t pulse_end = furi_get_tick(); + //FURI_CRITICAL_EXIT(); + + plugin_state->echo = pulse_end - pulse_start; + plugin_state->distance = hc_sr04_ms_to_m(pulse_end - pulse_start); + plugin_state->measurement_made = true; + + //furi_hal_light_set(LightRed, 0x00); + notification_message(plugin_state->notification, &sequence_blink_stop); + notification_message(plugin_state->notification, &sequence_done); +} + +int32_t hc_sr04_app() { + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); + + PluginState* plugin_state = malloc(sizeof(PluginState)); + + hc_sr04_state_init(plugin_state); + + furi_hal_console_disable(); + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, plugin_state, sizeof(PluginState))) { + FURI_LOG_E("hc_sr04", "cannot create mutex\r\n"); + if(furi_hal_power_is_otg_enabled()) { + furi_hal_power_disable_otg(); + } + furi_hal_console_enable(); + furi_hal_power_suppress_charge_exit(); + furi_message_queue_free(event_queue); + free(plugin_state); + return 255; + } + + plugin_state->notification = furi_record_open(RECORD_NOTIFICATION); + + // Set system callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, render_callback, &state_mutex); + view_port_input_callback_set(view_port, input_callback, event_queue); + + // Open GUI and register view_port + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + PluginEvent event; + for(bool processing = true; processing;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + + PluginState* plugin_state = (PluginState*)acquire_mutex_block(&state_mutex); + + if(event_status == FuriStatusOk) { + // press events + if(event.type == EventTypeKey) { + if(event.input.type == InputTypePress) { + switch(event.input.key) { + case InputKeyUp: + case InputKeyDown: + case InputKeyRight: + case InputKeyLeft: + break; + case InputKeyOk: + hc_sr04_measure(plugin_state); + break; + case InputKeyBack: + processing = false; + break; + default: + break; + } + } + } + } + + view_port_update(view_port); + release_mutex(&state_mutex, plugin_state); + } + + if(furi_hal_power_is_otg_enabled()) { + furi_hal_power_disable_otg(); + } + furi_hal_power_suppress_charge_exit(); + + // Return TX / RX back to usart mode + furi_hal_gpio_init_ex( + &gpio_usart_tx, + GpioModeAltFunctionPushPull, + GpioPullUp, + GpioSpeedVeryHigh, + GpioAltFn7USART1); + furi_hal_gpio_init_ex( + &gpio_usart_rx, + GpioModeAltFunctionPushPull, + GpioPullUp, + GpioSpeedVeryHigh, + GpioAltFn7USART1); + furi_hal_console_enable(); + + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_NOTIFICATION); + view_port_free(view_port); + furi_message_queue_free(event_queue); + delete_mutex(&state_mutex); + + return 0; +} \ No newline at end of file diff --git a/applications/plugins/htu21d_temp_sensor/Readme.md b/applications/plugins/htu21d_temp_sensor/Readme.md new file mode 100644 index 000000000..45c332306 --- /dev/null +++ b/applications/plugins/htu21d_temp_sensor/Readme.md @@ -0,0 +1,65 @@ +[Original link](https://github.com/Mywk/FlipperTemperatureSensor) + +# Flipper Temperature Sensor + +## Supported sensors + +> HTU2xD, SHT2x, SI702x, SI700x, SI701x, AM2320 + +## What is this? + +A small app for the [Flipper Zero](https://flipperzero.one) that reads the [I2C](https://en.wikipedia.org/wiki/I%C2%B2C) signal from a few temperature sensors and displays the current temperature and humidity. + +I'm using a [Sparkfun HTU21D sensor](https://learn.sparkfun.com/tutorials/htu21d-humidity-sensor-hookup-guide), also tested with a clone and with the Si7021 variant. + +![Flipper Temperature Sensor](images/Flipper.png) + +![App](images/App.png) + +
+ +# How to Connect the HTU21D sensor +![Connection](images/Connection.png) + + +# How to install + +If you have the FAP loader, just copy the fap file from the Releases into your Flipper apps folder and you should be able to launch it from the menu. + +If you don't have the FAP loader you will have to bake this application together with your firmware (aka compile it all together). + +# FAQ + +## The app says the sensor is not found! + +1- Are the four connectors correctly soldered? + +2- Are the SCL and SDA connections correct? Re-check the "How to Connect the sensor" above. + +3- For the HTU21D, on the sensor board, there should be three contacts in the center, for it to work correctly they must be soldered together (basically drop a blob of solder to connect the three of them). Without the solder it looks like this: + +![Sensor](images/Sensor.png) + +## Which Flipper versions was this app tested on? + +Version 1.2 +- RM11221439-0.71.2 +- unlshd-015 +- 0.71.1 + +Version 1.1 +- RM10302252-0.70.1 +- unlshd-012 +- 0.68.2-1007-RM +- 0.68.1 +- 0.67.2 + +## I can't build the app together with the firmware? + +In the *application.fam*, don't forget to change the apptype, it should not be EXTERNAL but APP. + +# How to compile + +Place the temperature_sensor folder in the applications_user folder and compile using FBT. + +Please refer to the [Flipper Build Tool documentation](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/documentation/fbt.md). \ No newline at end of file diff --git a/applications/plugins/htu21d_temp_sensor/application.fam b/applications/plugins/htu21d_temp_sensor/application.fam new file mode 100644 index 000000000..5807d7f51 --- /dev/null +++ b/applications/plugins/htu21d_temp_sensor/application.fam @@ -0,0 +1,14 @@ +App( + appid="HTU_Temperature_Sensor", + name="[HTU/+] Temperature Sensor", + apptype=FlipperAppType.EXTERNAL, + entry_point="temperature_sensor_app", + cdefines=["APP_TEMPERATURE_SENSOR"], + requires=[ + "gui", + ], + stack_size=2 * 1024, + order=90, + fap_icon="temperature_sensor.png", + fap_category="GPIO", +) diff --git a/applications/plugins/htu21d_temp_sensor/docs/App.png b/applications/plugins/htu21d_temp_sensor/docs/App.png new file mode 100644 index 000000000..a0373bdbd Binary files /dev/null and b/applications/plugins/htu21d_temp_sensor/docs/App.png differ diff --git a/applications/plugins/htu21d_temp_sensor/docs/Connection.png b/applications/plugins/htu21d_temp_sensor/docs/Connection.png new file mode 100644 index 000000000..b38f5c250 Binary files /dev/null and b/applications/plugins/htu21d_temp_sensor/docs/Connection.png differ diff --git a/applications/plugins/htu21d_temp_sensor/docs/Flipper.png b/applications/plugins/htu21d_temp_sensor/docs/Flipper.png new file mode 100644 index 000000000..c85319593 Binary files /dev/null and b/applications/plugins/htu21d_temp_sensor/docs/Flipper.png differ diff --git a/applications/plugins/htu21d_temp_sensor/temperature_sensor.c b/applications/plugins/htu21d_temp_sensor/temperature_sensor.c new file mode 100644 index 000000000..b04e40a7d --- /dev/null +++ b/applications/plugins/htu21d_temp_sensor/temperature_sensor.c @@ -0,0 +1,387 @@ +/* Flipper App to read the values from a HTU2XD, SHT2X, SI702X, SI700X, SI701X or AM2320 Sensor */ +/* Created by Mywk - https://github.com/Mywk - https://mywk.net */ +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#define TS_DEFAULT_VALUE 0xFFFF + +#define TS_AVAILABLE_SENSORS 2 + +// HTU2XD, SHT2X, SI702X, SI700X address +#define HTU2XD_SHT2X_SI702X_SI700X_ADDRESS (0x40 << 1) +// SI701X ADDRESS +#define SI701X_ADDRESS (0x41 << 1) + +// HTU2XD, SHT2X, SI702X, SI700X commands +#define HTU21D_CMD_TEMPERATURE 0xE3 +#define HTU21D_CMD_HUMIDITY 0xE5 + +// AM2320 address +#define AM2320_ADDRESS (0x5C << 1) + +// Used for the temperature and humidity buffers +#define DATA_BUFFER_SIZE 8 + +// External I2C BUS +#define I2C_BUS &furi_hal_i2c_handle_external + +// Typedef enums to make everything easier to read + +typedef enum { TSSCmdNone, TSSCmdTemperature, TSSCmdHumidity } TSSCmdType; + +typedef enum { + TSSInitializing, + TSSNoSensor, + TSSPendingUpdate, +} TSStatus; + +typedef enum { + TSEventTypeTick, + TSEventTypeInput, +} TSEventType; + +typedef struct { + TSEventType type; + InputEvent input; +} TSEvent; + +// Possible return values for sensor_cmd +typedef enum { + TSCmdRet_Error, + TSCmdRet_HTU2XD_SHT2X_SI702X_SI700X, + TSCmdRet_SI701X, + TSCmdRet_AM2320, +} TSCmdRet; + +// External NotificationSequence RGB +extern const NotificationSequence sequence_blink_red_100; +extern const NotificationSequence sequence_blink_green_100; +extern const NotificationSequence sequence_blink_blue_100; + +// Current status of the temperature sensor app +static TSStatus temperature_sensor_current_status = TSSInitializing; + +// We keep track of the last cmd return +static TSCmdRet temperature_sensor_last_cmd_ret = TSCmdRet_Error; + +// Temperature and Humidity data buffers, ready to print +char ts_data_buffer_temperature_c[DATA_BUFFER_SIZE]; +char ts_data_buffer_temperature_f[DATA_BUFFER_SIZE]; +char ts_data_buffer_relative_humidity[DATA_BUFFER_SIZE]; +char ts_data_buffer_absolute_humidity[DATA_BUFFER_SIZE]; + +// +// Executes an I2C cmd (trx) +// +// +// CRC +// +// +// true if fetch was successful, false otherwise +// +static TSCmdRet temperature_sensor_cmd(TSSCmdType cmd, uint8_t* buffer) { + uint32_t timeout = furi_ms_to_ticks(100); + TSCmdRet ret = TSCmdRet_Error; + + // Aquire I2C and check if device is ready, then release + furi_hal_i2c_acquire(I2C_BUS); + + // Check if HTU2XD, SHT2X, SI702X, SI700X sensor is available + uint8_t isAddress40 = + furi_hal_i2c_is_device_ready(I2C_BUS, HTU2XD_SHT2X_SI702X_SI700X_ADDRESS, timeout); + uint8_t isAddress41 = 0; + + // Check if SI701X sensor is available if necessary + if(!isAddress40) isAddress41 = furi_hal_i2c_is_device_ready(I2C_BUS, SI701X_ADDRESS, timeout); + + if(isAddress40 || isAddress41) { + uint8_t address = isAddress40 ? HTU2XD_SHT2X_SI702X_SI700X_ADDRESS : SI701X_ADDRESS; + + // Better safe than sorry delay + furi_delay_ms(15); + + // Extra delay for the SI70XX + if(isAddress41) furi_delay_ms(50); + + // Transmit either the temperature or the humidity command depending on TSSCmdType + uint8_t c = (cmd == TSSCmdTemperature) ? HTU21D_CMD_TEMPERATURE : HTU21D_CMD_HUMIDITY; + if(furi_hal_i2c_tx(I2C_BUS, address, &c, 1, timeout)) { + // Receive data (2 bytes) + if(furi_hal_i2c_rx(I2C_BUS, address, buffer, 2, timeout + 50)) + ret = isAddress40 ? TSCmdRet_HTU2XD_SHT2X_SI702X_SI700X : TSCmdRet_SI701X; + } + } else { + // The AM2320 goes to sleep after a period of inactivity, wake it up (check AM2320 datasheet for more info) + furi_hal_i2c_is_device_ready(I2C_BUS, AM2320_ADDRESS, timeout); + furi_delay_ms(30); + + // Check if it's really available + if(furi_hal_i2c_is_device_ready(I2C_BUS, AM2320_ADDRESS, timeout)) { + // {Address, Register, Len} + const uint8_t request[3] = {0x03, 0x00, 0x04}; + + if(furi_hal_i2c_tx(I2C_BUS, AM2320_ADDRESS, request, 3, timeout)) { + // 6 bytes - usually 8 but we currently don't check the CRC + if(furi_hal_i2c_rx(I2C_BUS, (uint8_t)AM2320_ADDRESS, buffer, 6, timeout)) + ret = TSCmdRet_AM2320; + } + } + } + + furi_hal_i2c_release(I2C_BUS); + + temperature_sensor_last_cmd_ret = ret; + return ret; +} + +// +// Fetches temperature and humidity from sensor +// +// +// temperature in C +// humidity in relative humidity +// +// +// Temperature and humidity must be preallocated +// +// +// CRC +// +// +// true if fetch was successful, false otherwise +// +static bool temperature_sensor_fetch_data(double* temperature, double* humidity) { + bool ret = false; + + uint16_t adc_raw; + + uint8_t buffer[DATA_BUFFER_SIZE] = {0x00}; + + // Check if the sensor is the HTU21D by attempting to fetch the temperature + TSCmdRet cmdRet = temperature_sensor_cmd(TSSCmdTemperature, buffer); + if(cmdRet == TSCmdRet_HTU2XD_SHT2X_SI702X_SI700X || cmdRet == TSCmdRet_SI701X) { + // Calculate temperature + adc_raw = ((uint16_t)(buffer[0] << 8) | (buffer[1])); + *temperature = (float)(adc_raw * 175.72 / 65536.00) - 46.85; + + // Fetch humidity + if(temperature_sensor_cmd(TSSCmdHumidity, buffer)) { + // Calculate humidity + adc_raw = ((uint16_t)(buffer[0] << 8) | (buffer[1])); + *humidity = (float)(adc_raw * 125.0 / 65536.00) - 6.0; + + ret = true; + } + } else if(cmdRet == TSCmdRet_AM2320) { + // The AM2320 returns all the data immediately so we just process it all + // Note: CRC isn't currently present in the buffer + + // Temperature + float temp = (((buffer[4] & 0x7F) << 8) + buffer[5]) / 10; + *temperature = ((buffer[4] & 0x80) >> 7) == 1 ? temp * (-1) : temp; + + // Humidity + temp = ((buffer[2] << 8) + buffer[3]) / 10; + *humidity = temp; + + ret = true; + } + + return ret; +} + +// +// Draw callback +// +static void temperature_sensor_draw_callback(Canvas* canvas, void* ctx) { + UNUSED(ctx); + + canvas_clear(canvas); + canvas_set_font(canvas, FontPrimary); + + // Update title accordingly (this could be improved by checking the hardware id) + switch(temperature_sensor_last_cmd_ret) { + case TSCmdRet_Error: + canvas_draw_str(canvas, 2, 10, "Temperature Sensor"); + break; + + case TSCmdRet_HTU2XD_SHT2X_SI702X_SI700X: + canvas_draw_str(canvas, 2, 10, "HTU/SHT/SI70 Sensor"); + break; + + case TSCmdRet_SI701X: + canvas_draw_str(canvas, 2, 10, "SI701X Sensor"); + break; + + case TSCmdRet_AM2320: + canvas_draw_str(canvas, 2, 10, "AM2320 Sensor"); + break; + + default: + break; + } + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 2, 62, "Press back to exit."); + + switch(temperature_sensor_current_status) { + case TSSInitializing: + canvas_draw_str(canvas, 2, 30, "Initializing.."); + break; + case TSSNoSensor: + canvas_draw_str(canvas, 2, 30, "No sensor found!"); + break; + case TSSPendingUpdate: { + canvas_draw_str(canvas, 3, 24, "Temperature"); + canvas_draw_str(canvas, 68, 24, "Humidity"); + + // Draw vertical lines + canvas_draw_line(canvas, 61, 16, 61, 50); + canvas_draw_line(canvas, 62, 16, 62, 50); + + // Draw horizontal line + canvas_draw_line(canvas, 2, 27, 122, 27); + + // Draw temperature and humidity values + canvas_draw_str(canvas, 8, 38, ts_data_buffer_temperature_c); + canvas_draw_str(canvas, 42, 38, "C"); + canvas_draw_str(canvas, 8, 48, ts_data_buffer_temperature_f); + canvas_draw_str(canvas, 42, 48, "F"); + canvas_draw_str(canvas, 68, 38, ts_data_buffer_relative_humidity); + canvas_draw_str(canvas, 100, 38, "%"); + canvas_draw_str(canvas, 68, 48, ts_data_buffer_absolute_humidity); + canvas_draw_str(canvas, 100, 48, "g/m3"); + } break; + default: + break; + } +} + +// +// Input callback +// +static void temperature_sensor_input_callback(InputEvent* input_event, void* ctx) { + furi_assert(ctx); + FuriMessageQueue* event_queue = ctx; + + TSEvent event = {.type = TSEventTypeInput, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +// +// Timer callback +// +static void temperature_sensor_timer_callback(FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + TSEvent event = {.type = TSEventTypeTick}; + furi_message_queue_put(event_queue, &event, 0); +} + +// +// App entry point +// +int32_t temperature_sensor_app(void* p) { + UNUSED(p); + + // Declare our variables and assign variables a default value + TSEvent tsEvent; + bool sensorFound = false; + double celsius, fahrenheit, rel_humidity, abs_humidity = TS_DEFAULT_VALUE; + + // Used for absolute humidity calculation + double vapour_pressure = 0; + + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(TSEvent)); + + // Register callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, temperature_sensor_draw_callback, NULL); + view_port_input_callback_set(view_port, temperature_sensor_input_callback, event_queue); + + // Create timer and register its callback + FuriTimer* timer = + furi_timer_alloc(temperature_sensor_timer_callback, FuriTimerTypePeriodic, event_queue); + furi_timer_start(timer, furi_kernel_get_tick_frequency()); + + // Register viewport + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + // Used to notify the user by blinking red (error) or blue (fetch successful) + NotificationApp* notifications = furi_record_open(RECORD_NOTIFICATION); + + while(1) { + furi_check(furi_message_queue_get(event_queue, &tsEvent, FuriWaitForever) == FuriStatusOk); + + // Handle events + if(tsEvent.type == TSEventTypeInput) { + // Exit on back key + if(tsEvent.input.key == + InputKeyBack) // We dont check for type here, we can check the type of keypress like: (event.input.type == InputTypeShort) + break; + } else if(tsEvent.type == TSEventTypeTick) { + // Update sensor data + // Fetch data and set the sensor current status accordingly + sensorFound = temperature_sensor_fetch_data(&celsius, &rel_humidity); + temperature_sensor_current_status = (sensorFound ? TSSPendingUpdate : TSSNoSensor); + + if(sensorFound) { + // Blink blue + notification_message(notifications, &sequence_blink_blue_100); + + if(celsius != TS_DEFAULT_VALUE && rel_humidity != TS_DEFAULT_VALUE) { + // Convert celsius to fahrenheit + fahrenheit = (celsius * 9 / 5) + 32; + + // Calculate absolute humidity - For more info refer to https://github.com/Mywk/FlipperTemperatureSensor/issues/1 + // Calculate saturation vapour pressure first + vapour_pressure = + (double)6.11 * + pow(10, (double)(((double)7.5 * celsius) / ((double)237.3 + celsius))); + // Then the vapour pressure in Pa + vapour_pressure = vapour_pressure * rel_humidity; + // Calculate absolute humidity + abs_humidity = + (double)2.16679 * (double)(vapour_pressure / ((double)273.15 + celsius)); + + // Fill our buffers here, not on the canvas draw callback + snprintf(ts_data_buffer_temperature_c, DATA_BUFFER_SIZE, "%.2f", celsius); + snprintf(ts_data_buffer_temperature_f, DATA_BUFFER_SIZE, "%.2f", fahrenheit); + snprintf( + ts_data_buffer_relative_humidity, DATA_BUFFER_SIZE, "%.2f", rel_humidity); + snprintf( + ts_data_buffer_absolute_humidity, DATA_BUFFER_SIZE, "%.2f", abs_humidity); + } + } else { + // Reset our variables to their default values + celsius = fahrenheit = rel_humidity = abs_humidity = TS_DEFAULT_VALUE; + + // Blink red + notification_message(notifications, &sequence_blink_red_100); + } + } + + furi_delay_ms(!sensorFound ? 100 : 500); + } + + // Dobby is freee (free our variables, Flipper will crash if we don't do this!) + furi_timer_free(timer); + gui_remove_view_port(gui, view_port); + view_port_free(view_port); + furi_message_queue_free(event_queue); + + furi_record_close(RECORD_NOTIFICATION); + furi_record_close(RECORD_GUI); + + return 0; +} \ No newline at end of file diff --git a/applications/plugins/htu21d_temp_sensor/temperature_sensor.png b/applications/plugins/htu21d_temp_sensor/temperature_sensor.png new file mode 100644 index 000000000..b6fe6d7fe Binary files /dev/null and b/applications/plugins/htu21d_temp_sensor/temperature_sensor.png differ diff --git a/applications/plugins/ifttt/application.fam b/applications/plugins/ifttt/application.fam new file mode 100644 index 000000000..495627429 --- /dev/null +++ b/applications/plugins/ifttt/application.fam @@ -0,0 +1,14 @@ +App( + appid="ESP8266_IFTTT_Virtual_Button", + name="[ESP8266] IFTTT Virtual Button", + apptype=FlipperAppType.EXTERNAL, + entry_point="ifttt_virtual_button_app", + cdefines=["APP_IFTTT_VIRTUAL_BUTTON"], + requires=[ + "gui", + ], + stack_size=2 * 1024, + order=20, + fap_icon="icon.png", + fap_category="GPIO", +) \ No newline at end of file diff --git a/applications/plugins/ifttt/icon.png b/applications/plugins/ifttt/icon.png new file mode 100644 index 000000000..f6d586b38 Binary files /dev/null and b/applications/plugins/ifttt/icon.png differ diff --git a/applications/plugins/ifttt/ifttt_virtual_button.c b/applications/plugins/ifttt/ifttt_virtual_button.c new file mode 100644 index 000000000..e23b8715d --- /dev/null +++ b/applications/plugins/ifttt/ifttt_virtual_button.c @@ -0,0 +1,251 @@ +#include "ifttt_virtual_button.h" + +#define IFTTT_FOLDER "/ext/ifttt" +#define IFTTT_CONFIG_FOLDER "/ext/ifttt/config" +const char* CONFIG_FILE_PATH = "/ext/ifttt/config/config.settings"; + +#define FLIPPERZERO_SERIAL_BAUD 115200 +typedef enum ESerialCommand { ESerialCommand_Config } ESerialCommand; + +Settings save_settings(Settings settings) { + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* file = flipper_format_file_alloc(storage); + if(flipper_format_file_open_existing(file, CONFIG_FILE_PATH)) { + flipper_format_update_string_cstr(file, CONF_SSID, settings.save_ssid); + flipper_format_update_string_cstr(file, CONF_PASSWORD, settings.save_password); + flipper_format_update_string_cstr(file, CONF_KEY, settings.save_key); + flipper_format_update_string_cstr(file, CONF_EVENT, settings.save_event); + } else { + } + flipper_format_file_close(file); + flipper_format_free(file); + furi_record_close(RECORD_STORAGE); + return settings; +} + +void save_settings_file(FlipperFormat* file, Settings* settings) { + flipper_format_write_header_cstr(file, CONFIG_FILE_HEADER, CONFIG_FILE_VERSION); + flipper_format_write_comment_cstr(file, "Enter here the SSID of the wifi network"); + flipper_format_write_string_cstr(file, CONF_SSID, settings->save_ssid); + flipper_format_write_comment_cstr(file, "Enter here the PASSWORD of the wifi network"); + flipper_format_write_string_cstr(file, CONF_PASSWORD, settings->save_password); + flipper_format_write_comment_cstr(file, "Enter here the WEBHOOKS of your IFTTT account"); + flipper_format_write_string_cstr(file, CONF_KEY, settings->save_key); + flipper_format_write_comment_cstr(file, "Enter here the EVENT name of your trigger"); + flipper_format_write_string_cstr(file, CONF_EVENT, settings->save_event); +} + +Settings* load_settings() { + Settings* settings = malloc(sizeof(Settings)); + + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* file = flipper_format_file_alloc(storage); + + FuriString* string_value; + string_value = furi_string_alloc(); + FuriString* text_ssid_value; + text_ssid_value = furi_string_alloc(); + FuriString* text_password_value; + text_password_value = furi_string_alloc(); + FuriString* text_key_value; + text_key_value = furi_string_alloc(); + FuriString* text_event_value; + text_event_value = furi_string_alloc(); + + if(storage_common_stat(storage, CONFIG_FILE_PATH, NULL) != FSE_OK) { + if(!flipper_format_file_open_new(file, CONFIG_FILE_PATH)) { + flipper_format_file_close(file); + } else { + settings->save_ssid = malloc(1); + settings->save_password = malloc(1); + settings->save_key = malloc(1); + settings->save_event = malloc(1); + + settings->save_ssid[0] = '\0'; + settings->save_password[0] = '\0'; + settings->save_key[0] = '\0'; + settings->save_event[0] = '\0'; + + save_settings_file(file, settings); + flipper_format_file_close(file); + } + } else { + if(!flipper_format_file_open_existing(file, CONFIG_FILE_PATH)) { + flipper_format_file_close(file); + } else { + uint32_t value; + if(!flipper_format_read_header(file, string_value, &value)) { + } else { + if(flipper_format_read_string(file, CONF_SSID, text_ssid_value)) { + settings->save_ssid = malloc(furi_string_size(text_ssid_value) + 1); + strcpy(settings->save_ssid, furi_string_get_cstr(text_ssid_value)); + } + if(flipper_format_read_string(file, CONF_PASSWORD, text_password_value)) { + settings->save_password = malloc(furi_string_size(text_password_value) + 1); + strcpy(settings->save_password, furi_string_get_cstr(text_password_value)); + } + if(flipper_format_read_string(file, CONF_KEY, text_key_value)) { + settings->save_key = malloc(furi_string_size(text_key_value) + 1); + strcpy(settings->save_key, furi_string_get_cstr(text_key_value)); + } + if(flipper_format_read_string(file, CONF_EVENT, text_event_value)) { + settings->save_event = malloc(furi_string_size(text_event_value) + 1); + strcpy(settings->save_event, furi_string_get_cstr(text_event_value)); + } + } + flipper_format_file_close(file); + } + } + + furi_string_free(text_ssid_value); + furi_string_free(text_password_value); + furi_string_free(text_key_value); + furi_string_free(text_event_value); + flipper_format_free(file); + furi_record_close(RECORD_STORAGE); + return settings; +} + +void send_serial_command_config(ESerialCommand command, Settings* settings) { + uint8_t data[1] = {0}; + + char config_tmp[100]; + strcpy(config_tmp, "config,"); + strcat(config_tmp, settings->save_key); + char config_tmp2[5]; + strcpy(config_tmp2, config_tmp); + strcat(config_tmp2, ","); + char config_tmp3[100]; + strcpy(config_tmp3, config_tmp2); + strcat(config_tmp3, settings->save_ssid); + char config_tmp4[5]; + strcpy(config_tmp4, config_tmp3); + strcat(config_tmp4, ","); + char config_tmp5[100]; + strcpy(config_tmp5, config_tmp4); + strcat(config_tmp5, settings->save_password); + char config_tmp6[5]; + strcpy(config_tmp6, config_tmp5); + strcat(config_tmp6, ","); + char config[350]; + strcpy(config, config_tmp6); + strcat(config, settings->save_event); + + int length = strlen(config); + for(int i = 0; i < length; i++) { + switch(command) { + case ESerialCommand_Config: + data[0] = config[i]; + break; + default: + return; + }; + + furi_hal_uart_tx(FuriHalUartIdUSART1, data, 1); + } +} + +static bool ifttt_virtual_button_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + VirtualButtonApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool ifttt_virtual_button_back_event_callback(void* context) { + furi_assert(context); + VirtualButtonApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void ifttt_virtual_button_tick_event_callback(void* context) { + furi_assert(context); + VirtualButtonApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +VirtualButtonApp* ifttt_virtual_button_app_alloc(uint32_t first_scene) { + VirtualButtonApp* app = malloc(sizeof(VirtualButtonApp)); + + // Records + app->gui = furi_record_open(RECORD_GUI); + app->power = furi_record_open(RECORD_POWER); + + // View dispatcher + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&virtual_button_scene_handlers, app); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, ifttt_virtual_button_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, ifttt_virtual_button_back_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, ifttt_virtual_button_tick_event_callback, 2000); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + // Views + app->sen_view = send_view_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, VirtualButtonAppViewSendView, send_view_get_view(app->sen_view)); + + app->abou_view = about_view_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, VirtualButtonAppViewAboutView, about_view_get_view(app->abou_view)); + + app->submenu = submenu_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, VirtualButtonAppViewSubmenu, submenu_get_view(app->submenu)); + app->dialog = dialog_ex_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, VirtualButtonAppViewDialog, dialog_ex_get_view(app->dialog)); + + // Set first scene + scene_manager_next_scene(app->scene_manager, first_scene); + return app; +} + +void ifttt_virtual_button_app_free(VirtualButtonApp* app) { + furi_assert(app); + + free(app->settings.save_ssid); + free(app->settings.save_password); + free(app->settings.save_key); + + // Views + view_dispatcher_remove_view(app->view_dispatcher, VirtualButtonAppViewSendView); + send_view_free(app->sen_view); + view_dispatcher_remove_view(app->view_dispatcher, VirtualButtonAppViewAboutView); + about_view_free(app->abou_view); + view_dispatcher_remove_view(app->view_dispatcher, VirtualButtonAppViewSubmenu); + submenu_free(app->submenu); + view_dispatcher_remove_view(app->view_dispatcher, VirtualButtonAppViewDialog); + dialog_ex_free(app->dialog); + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + // Records + furi_record_close(RECORD_POWER); + furi_record_close(RECORD_GUI); + + free(app); +} + +int32_t ifttt_virtual_button_app(void* p) { + UNUSED(p); + + Storage* storage = furi_record_open(RECORD_STORAGE); + if(!storage_simply_mkdir(storage, IFTTT_FOLDER)) { + } + if(!storage_simply_mkdir(storage, IFTTT_CONFIG_FOLDER)) { + } + furi_record_close(RECORD_STORAGE); + + uint32_t first_scene = VirtualButtonAppSceneStart; + VirtualButtonApp* app = ifttt_virtual_button_app_alloc(first_scene); + memcpy(&app->settings, load_settings(), sizeof(Settings)); + send_serial_command_config(ESerialCommand_Config, &(app->settings)); + + view_dispatcher_run(app->view_dispatcher); + ifttt_virtual_button_app_free(app); + return 0; +} \ No newline at end of file diff --git a/applications/plugins/ifttt/ifttt_virtual_button.h b/applications/plugins/ifttt/ifttt_virtual_button.h new file mode 100644 index 000000000..563f5cd95 --- /dev/null +++ b/applications/plugins/ifttt/ifttt_virtual_button.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "views/send_view.h" +#include "views/about_view.h" +#include +#include +#include +#include +#include +#include +#include "scenes/virtual_button_scene.h" + +#define APP_NAME "[ESP8266] IFTTT Virtual Button" + +#define CONF_SSID "wifi_ssid" +#define CONF_PASSWORD "wifi_password" +#define CONF_KEY "webhooks_key" +#define CONF_EVENT "event" +#define CONFIG_FILE_HEADER "IFTTT Virtual Button Config File" +#define CONFIG_FILE_VERSION 1 + +typedef struct { + char* save_ssid; + char* save_password; + char* save_key; + char* save_event; +} Settings; + +typedef struct { + Power* power; + Gui* gui; + SceneManager* scene_manager; + ViewDispatcher* view_dispatcher; + SendView* sen_view; + AboutView* abou_view; + Submenu* submenu; + DialogEx* dialog; + PowerInfo info; + Settings settings; +} VirtualButtonApp; + +typedef enum { + VirtualButtonAppViewSendView, + VirtualButtonAppViewAboutView, + VirtualButtonAppViewSubmenu, + VirtualButtonAppViewDialog, +} VirtualButtonAppView; + +Settings save_settings(Settings settings); +Settings* load_settings(); \ No newline at end of file diff --git a/applications/plugins/ifttt/scenes/virtual_button_scene.c b/applications/plugins/ifttt/scenes/virtual_button_scene.c new file mode 100644 index 000000000..a75d822fc --- /dev/null +++ b/applications/plugins/ifttt/scenes/virtual_button_scene.c @@ -0,0 +1,30 @@ +#include "virtual_button_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const virtual_button_on_enter_handlers[])(void*) = { +#include "virtual_button_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 virtual_button_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "virtual_button_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 virtual_button_on_exit_handlers[])(void* context) = { +#include "virtual_button_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers virtual_button_scene_handlers = { + .on_enter_handlers = virtual_button_on_enter_handlers, + .on_event_handlers = virtual_button_on_event_handlers, + .on_exit_handlers = virtual_button_on_exit_handlers, + .scene_num = VirtualButtonAppSceneNum, +}; diff --git a/applications/plugins/ifttt/scenes/virtual_button_scene.h b/applications/plugins/ifttt/scenes/virtual_button_scene.h new file mode 100644 index 000000000..870807dee --- /dev/null +++ b/applications/plugins/ifttt/scenes/virtual_button_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) VirtualButtonAppScene##id, +typedef enum { +#include "virtual_button_scene_config.h" + VirtualButtonAppSceneNum, +} VirtualButtonAppScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers virtual_button_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "virtual_button_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 "virtual_button_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 "virtual_button_scene_config.h" +#undef ADD_SCENE diff --git a/applications/plugins/ifttt/scenes/virtual_button_scene_about.c b/applications/plugins/ifttt/scenes/virtual_button_scene_about.c new file mode 100644 index 000000000..86fe1a9d0 --- /dev/null +++ b/applications/plugins/ifttt/scenes/virtual_button_scene_about.c @@ -0,0 +1,26 @@ +#include "../ifttt_virtual_button.h" + +static void virtual_button_scene_about_view_update_model(VirtualButtonApp* app) { + power_get_info(app->power, &app->info); +} + +void virtual_button_scene_about_view_on_enter(void* context) { + VirtualButtonApp* app = context; + virtual_button_scene_about_view_update_model(app); + view_dispatcher_switch_to_view(app->view_dispatcher, VirtualButtonAppViewAboutView); +} + +bool virtual_button_scene_about_view_on_event(void* context, SceneManagerEvent event) { + VirtualButtonApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeTick) { + virtual_button_scene_about_view_update_model(app); + consumed = true; + } + return consumed; +} + +void virtual_button_scene_about_view_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/plugins/ifttt/scenes/virtual_button_scene_config.h b/applications/plugins/ifttt/scenes/virtual_button_scene_config.h new file mode 100644 index 000000000..70af5ccf7 --- /dev/null +++ b/applications/plugins/ifttt/scenes/virtual_button_scene_config.h @@ -0,0 +1,3 @@ +ADD_SCENE(virtual_button, start, Start) +ADD_SCENE(virtual_button, send_view, SendView) +ADD_SCENE(virtual_button, about_view, AboutView) diff --git a/applications/plugins/ifttt/scenes/virtual_button_scene_send.c b/applications/plugins/ifttt/scenes/virtual_button_scene_send.c new file mode 100644 index 000000000..caa23fadf --- /dev/null +++ b/applications/plugins/ifttt/scenes/virtual_button_scene_send.c @@ -0,0 +1,26 @@ +#include "../ifttt_virtual_button.h" + +static void virtual_button_scene_send_view_update_model(VirtualButtonApp* app) { + power_get_info(app->power, &app->info); +} + +void virtual_button_scene_send_view_on_enter(void* context) { + VirtualButtonApp* app = context; + virtual_button_scene_send_view_update_model(app); + view_dispatcher_switch_to_view(app->view_dispatcher, VirtualButtonAppViewSendView); +} + +bool virtual_button_scene_send_view_on_event(void* context, SceneManagerEvent event) { + VirtualButtonApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeTick) { + virtual_button_scene_send_view_update_model(app); + consumed = true; + } + return consumed; +} + +void virtual_button_scene_send_view_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/plugins/ifttt/scenes/virtual_button_scene_start.c b/applications/plugins/ifttt/scenes/virtual_button_scene_start.c new file mode 100644 index 000000000..6b03a35f0 --- /dev/null +++ b/applications/plugins/ifttt/scenes/virtual_button_scene_start.c @@ -0,0 +1,55 @@ +#include "../ifttt_virtual_button.h" + +enum VirtualButtonSubmenuIndex { + VirtualButtonSubmenuIndexSendView, + VirtualButtonSubmenuIndexAboutView, +}; + +static void virtual_button_scene_start_submenu_callback(void* context, uint32_t index) { + furi_assert(context); + VirtualButtonApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void virtual_button_scene_start_on_enter(void* context) { + VirtualButtonApp* app = context; + Submenu* submenu = app->submenu; + + submenu_add_item( + submenu, + "Send IFTTT command", + VirtualButtonSubmenuIndexSendView, + virtual_button_scene_start_submenu_callback, + app); + submenu_add_item( + submenu, + "About", + VirtualButtonSubmenuIndexAboutView, + virtual_button_scene_start_submenu_callback, + app); + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(app->scene_manager, VirtualButtonAppSceneStart)); + + view_dispatcher_switch_to_view(app->view_dispatcher, VirtualButtonAppViewSubmenu); +} + +bool virtual_button_scene_start_on_event(void* context, SceneManagerEvent event) { + VirtualButtonApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == VirtualButtonSubmenuIndexSendView) { + scene_manager_next_scene(app->scene_manager, VirtualButtonAppSceneSendView); + } else if(event.event == VirtualButtonSubmenuIndexAboutView) { + scene_manager_next_scene(app->scene_manager, VirtualButtonAppSceneAboutView); + } + scene_manager_set_scene_state(app->scene_manager, VirtualButtonAppSceneStart, event.event); + consumed = true; + } + return consumed; +} + +void virtual_button_scene_start_on_exit(void* context) { + VirtualButtonApp* app = context; + submenu_reset(app->submenu); +} diff --git a/applications/plugins/ifttt/views/about_view.c b/applications/plugins/ifttt/views/about_view.c new file mode 100644 index 000000000..80c00883a --- /dev/null +++ b/applications/plugins/ifttt/views/about_view.c @@ -0,0 +1,48 @@ +#include "about_view.h" +#include +#include +#include +#include + +struct AboutView { + View* view; +}; + +typedef struct { + bool connected; +} AboutViewModel; + +static void about_view_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + canvas_draw_str_aligned(canvas, 0, 0, AlignLeft, AlignTop, "IFTTT Virtual button"); + canvas_draw_str_aligned(canvas, 0, 15, AlignLeft, AlignTop, "Version 0.2"); + canvas_draw_str_aligned(canvas, 0, 50, AlignLeft, AlignTop, "press back"); +} + +AboutView* about_view_alloc() { + AboutView* about_view = malloc(sizeof(AboutView)); + about_view->view = view_alloc(); + view_set_context(about_view->view, about_view); + view_allocate_model(about_view->view, ViewModelTypeLocking, sizeof(AboutViewModel)); + view_set_draw_callback(about_view->view, about_view_draw_callback); + return about_view; +} + +void about_view_free(AboutView* about_view) { + furi_assert(about_view); + view_free(about_view->view); + free(about_view); +} + +View* about_view_get_view(AboutView* about_view) { + furi_assert(about_view); + return about_view->view; +} + +void about_view_set_data(AboutView* about_view, bool connected) { + furi_assert(about_view); + with_view_model( + about_view->view, AboutViewModel * model, { model->connected = connected; }, true); +} \ No newline at end of file diff --git a/applications/plugins/ifttt/views/about_view.h b/applications/plugins/ifttt/views/about_view.h new file mode 100644 index 000000000..d1ac287e3 --- /dev/null +++ b/applications/plugins/ifttt/views/about_view.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +typedef struct AboutView AboutView; + +AboutView* about_view_alloc(); + +void about_view_free(AboutView* about_view); + +View* about_view_get_view(AboutView* about_view); \ No newline at end of file diff --git a/applications/plugins/ifttt/views/send_view.c b/applications/plugins/ifttt/views/send_view.c new file mode 100644 index 000000000..6046c39e3 --- /dev/null +++ b/applications/plugins/ifttt/views/send_view.c @@ -0,0 +1,137 @@ +#include "send_view.h" +#include +#include +#include +#include +#include +#include +#include + +#define FLIPPERZERO_SERIAL_BAUD 115200 + +typedef enum ESerialCommand { ESerialCommand_Send } ESerialCommand; + +struct SendView { + View* view; +}; + +typedef struct { + bool right_pressed; + bool connected; +} SendViewModel; + +static void Shake(void) { + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + notification_message(notification, &sequence_single_vibro); + furi_record_close(RECORD_NOTIFICATION); +} + +void send_serial_command_send(ESerialCommand command) { + uint8_t data[1] = {0}; + + char name[10] = "send"; + int length = strlen(name); + for(int i = 0; i < length; i++) { + switch(command) { + case ESerialCommand_Send: + data[0] = name[i]; + break; + default: + return; + }; + + furi_hal_uart_tx(FuriHalUartIdUSART1, data, 1); + } +} + +static void send_view_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + SendViewModel* model = context; + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + canvas_draw_str_aligned(canvas, 64, 0, AlignCenter, AlignTop, "SEND MODULE"); + canvas_draw_line(canvas, 0, 10, 128, 10); + canvas_draw_str_aligned(canvas, 64, 15, AlignCenter, AlignTop, "Press right to send IFTTT"); + canvas_draw_str_aligned(canvas, 64, 25, AlignCenter, AlignTop, "command or press and hold"); + canvas_draw_str_aligned(canvas, 64, 35, AlignCenter, AlignTop, "back to return to the menu"); + + // Right + if(model->right_pressed) { + } +} + +static void send_view_process(SendView* send_view, InputEvent* event) { + with_view_model( + send_view->view, + SendViewModel * model, + { + if(event->type == InputTypePress) { + if(event->key == InputKeyUp) { + } else if(event->key == InputKeyDown) { + } else if(event->key == InputKeyLeft) { + } else if(event->key == InputKeyRight) { + model->right_pressed = true; + Shake(); + send_serial_command_send(ESerialCommand_Send); + } else if(event->key == InputKeyOk) { + } else if(event->key == InputKeyBack) { + } + } else if(event->type == InputTypeRelease) { + if(event->key == InputKeyUp) { + } else if(event->key == InputKeyDown) { + } else if(event->key == InputKeyLeft) { + } else if(event->key == InputKeyRight) { + model->right_pressed = false; + } else if(event->key == InputKeyOk) { + } else if(event->key == InputKeyBack) { + } + } else if(event->type == InputTypeShort) { + if(event->key == InputKeyBack) { + } + } + }, + true); +} + +static bool send_view_input_callback(InputEvent* event, void* context) { + furi_assert(context); + SendView* send_view = context; + bool consumed = false; + + if(event->type == InputTypeLong && event->key == InputKeyBack) { + } else { + send_view_process(send_view, event); + consumed = true; + } + + return consumed; +} + +SendView* send_view_alloc() { + SendView* send_view = malloc(sizeof(SendView)); + send_view->view = view_alloc(); + view_set_context(send_view->view, send_view); + view_allocate_model(send_view->view, ViewModelTypeLocking, sizeof(SendViewModel)); + view_set_draw_callback(send_view->view, send_view_draw_callback); + view_set_input_callback(send_view->view, send_view_input_callback); + furi_hal_uart_set_br(FuriHalUartIdUSART1, FLIPPERZERO_SERIAL_BAUD); + + return send_view; +} + +void send_view_free(SendView* send_view) { + furi_assert(send_view); + view_free(send_view->view); + free(send_view); +} + +View* send_view_get_view(SendView* send_view) { + furi_assert(send_view); + return send_view->view; +} + +void send_view_set_data(SendView* send_view, bool connected) { + furi_assert(send_view); + with_view_model( + send_view->view, SendViewModel * model, { model->connected = connected; }, true); +} \ No newline at end of file diff --git a/applications/plugins/ifttt/views/send_view.h b/applications/plugins/ifttt/views/send_view.h new file mode 100644 index 000000000..4b1944dd4 --- /dev/null +++ b/applications/plugins/ifttt/views/send_view.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +typedef struct SendView SendView; + +SendView* send_view_alloc(); + +void send_view_free(SendView* send_view); + +View* send_view_get_view(SendView* send_view); \ No newline at end of file diff --git a/applications/plugins/lightmeter/.clang-format b/applications/plugins/lightmeter/.clang-format new file mode 100644 index 000000000..4b76f7fa4 --- /dev/null +++ b/applications/plugins/lightmeter/.clang-format @@ -0,0 +1,191 @@ +--- +Language: Cpp +AccessModifierOffset: -4 +AlignAfterOpenBracket: AlwaysBreak +AlignArrayOfStructures: None +AlignConsecutiveMacros: None +AlignConsecutiveAssignments: None +AlignConsecutiveBitFields: None +AlignConsecutiveDeclarations: None +AlignEscapedNewlines: Left +AlignOperands: Align +AlignTrailingComments: false +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortEnumsOnASingleLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: WithoutElse +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +AttributeMacros: + - __capability +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: true +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: false +ColumnLimit: 99 +CommentPragmas: '^ IWYU pragma:' +QualifierAlignment: Leave +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +PackConstructorInitializers: BinPack +BasedOnStyle: '' +ConstructorInitializerAllOnOneLineOrOnePerLine: false +AllowAllConstructorInitializersOnNextLine: true +FixNamespaceComments: false +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseLabels: false +IndentCaseBlocks: false +IndentGotoLabels: true +IndentPPDirectives: None +IndentExternBlock: AfterExternBlock +IndentRequires: false +IndentWidth: 4 +IndentWrappedFunctionNames: true +InsertTrailingCommas: None +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +LambdaBodyIndentation: Signature +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 4 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 10 +PenaltyBreakBeforeFirstCallParameter: 30 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 10 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 100 +PenaltyReturnTypeOnItsOwnLine: 60 +PenaltyIndentedWhitespace: 0 +PointerAlignment: Left +PPIndentWidth: -1 +ReferenceAlignment: Pointer +ReflowComments: false +RemoveBracesLLVM: false +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: Never +SortJavaStaticImport: Before +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: Never +SpaceBeforeParensOptions: + AfterControlStatements: false + AfterForeachMacros: false + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: false + AfterOverloadedOperator: false + BeforeNonEmptyParentheses: false +SpaceAroundPointerQualifiers: Default +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInConditionalStatement: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +BitFieldColonSpacing: Both +Standard: c++03 +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 4 +UseCRLF: false +UseTab: Never +WhitespaceSensitiveMacros: + - STRINGIZE + - PP_STRINGIZE + - BOOST_PP_STRINGIZE + - NS_SWIFT_NAME + - CF_SWIFT_NAME +... + diff --git a/applications/plugins/lightmeter/LICENSE b/applications/plugins/lightmeter/LICENSE new file mode 100644 index 000000000..cb2f65db5 --- /dev/null +++ b/applications/plugins/lightmeter/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Oleksii Kutuzov + +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. diff --git a/applications/plugins/lightmeter/README.md b/applications/plugins/lightmeter/README.md new file mode 100644 index 000000000..dc6c6ffd5 --- /dev/null +++ b/applications/plugins/lightmeter/README.md @@ -0,0 +1,35 @@ +# flipperzero-lightmeter + +[![Build FAP](https://github.com/oleksiikutuzov/flipperzero-lightmeter/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/oleksiikutuzov/flipperzero-lightmeter/actions/workflows/build.yml) + + + + + +## Wiring + +``` +VCC -> 3.3V +GND -> GND +SCL -> C0 +SDA -> C1 +``` + +## Sensor module + + + +### If you want to build this module, you'll need (it's quite over-engineered, sorry :D) +1. [Module PCB](https://github.com/oleksiikutuzov/flipperzero-lightmeter/blob/main/module/module_v2_gerber.zip) +2. [Enclosure](https://github.com/oleksiikutuzov/flipperzero-lightmeter/blob/main/module/module_v2_enclosure.stl) +3. 4-pin female header +4. 10-pin male header +5. 2x M3 threaded inserts (max diameter 5.3 mm, max height 4 mm) +6. 2x M3x5 screws + + +## TODO +- [ ] Save settings to sd card + +## References +App inspired by [lightmeter](https://github.com/vpominchuk/lightmeter) project for Arduino by [vpominchuk](https://github.com/vpominchuk). diff --git a/applications/plugins/lightmeter/application.fam b/applications/plugins/lightmeter/application.fam new file mode 100644 index 000000000..b1de62641 --- /dev/null +++ b/applications/plugins/lightmeter/application.fam @@ -0,0 +1,28 @@ +App( + appid="BH1750_Lightmeter", + name="[BH1750] Lightmeter", + apptype=FlipperAppType.EXTERNAL, + entry_point="lightmeter_app", + cdefines=["APP_LIGHTMETER"], + requires=[ + "gui", + ], + stack_size=1 * 1024, + order=90, + fap_version=(0, 7), + fap_icon="lightmeter.png", + fap_category="GPIO", + fap_private_libs=[ + Lib( + name="BH1750", + cincludes=["."], + sources=[ + "BH1750.c", + ], + ), + ], + fap_icon_assets="icons", + fap_description="Lightmeter app for photography based on BH1750 sensor", + fap_author="Oleksii Kutuzov", + fap_weburl="https://github.com/oleksiikutuzov/flipperzero-lightmeter", +) diff --git a/applications/plugins/lightmeter/gui/scenes/config/lightmeter_scene.c b/applications/plugins/lightmeter/gui/scenes/config/lightmeter_scene.c new file mode 100644 index 000000000..2487d5817 --- /dev/null +++ b/applications/plugins/lightmeter/gui/scenes/config/lightmeter_scene.c @@ -0,0 +1,30 @@ +#include "lightmeter_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const lightmeter_on_enter_handlers[])(void*) = { +#include "lightmeter_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 lightmeter_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "lightmeter_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 lightmeter_on_exit_handlers[])(void* context) = { +#include "lightmeter_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers lightmeter_scene_handlers = { + .on_enter_handlers = lightmeter_on_enter_handlers, + .on_event_handlers = lightmeter_on_event_handlers, + .on_exit_handlers = lightmeter_on_exit_handlers, + .scene_num = LightMeterAppSceneNum, +}; diff --git a/applications/plugins/lightmeter/gui/scenes/config/lightmeter_scene.h b/applications/plugins/lightmeter/gui/scenes/config/lightmeter_scene.h new file mode 100644 index 000000000..9d5931384 --- /dev/null +++ b/applications/plugins/lightmeter/gui/scenes/config/lightmeter_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) LightMeterAppScene##id, +typedef enum { +#include "lightmeter_scene_config.h" + LightMeterAppSceneNum, +} LightMeterAppScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers lightmeter_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "lightmeter_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 "lightmeter_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 "lightmeter_scene_config.h" +#undef ADD_SCENE diff --git a/applications/plugins/lightmeter/gui/scenes/config/lightmeter_scene_config.h b/applications/plugins/lightmeter/gui/scenes/config/lightmeter_scene_config.h new file mode 100644 index 000000000..c72a7713e --- /dev/null +++ b/applications/plugins/lightmeter/gui/scenes/config/lightmeter_scene_config.h @@ -0,0 +1,4 @@ +ADD_SCENE(lightmeter, main, Main) +ADD_SCENE(lightmeter, config, Config) +ADD_SCENE(lightmeter, help, Help) +ADD_SCENE(lightmeter, about, About) diff --git a/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_about.c b/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_about.c new file mode 100644 index 000000000..1508b4c00 --- /dev/null +++ b/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_about.c @@ -0,0 +1,71 @@ +#include "../../lightmeter.h" + +void lightmeter_scene_about_widget_callback(GuiButtonType result, InputType type, void* context) { + LightMeterApp* app = context; + + UNUSED(app); + UNUSED(result); + UNUSED(type); + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(app->view_dispatcher, result); + } +} + +void lightmeter_scene_about_on_enter(void* context) { + LightMeterApp* app = context; + + FuriString* temp_str; + temp_str = furi_string_alloc(); + furi_string_printf(temp_str, "\e#%s\n", "Information"); + + furi_string_cat_printf(temp_str, "Version: %s\n", LM_VERSION_APP); + furi_string_cat_printf(temp_str, "Developed by: %s\n", LM_DEVELOPED); + furi_string_cat_printf(temp_str, "Github: %s\n\n", LM_GITHUB); + + furi_string_cat_printf(temp_str, "\e#%s\n", "Description"); + furi_string_cat_printf( + temp_str, + "Showing suggested camera\nsettings based on ambient\nlight or flash.\n\nInspired by a lightmeter\nproject by vpominchuk\n"); + + widget_add_text_box_element( + app->widget, + 0, + 0, + 128, + 14, + AlignCenter, + AlignBottom, + "\e#\e! \e!\n", + false); + widget_add_text_box_element( + app->widget, + 0, + 2, + 128, + 14, + AlignCenter, + AlignBottom, + "\e#\e! Lightmeter \e!\n", + false); + widget_add_text_scroll_element(app->widget, 0, 16, 128, 50, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(app->view_dispatcher, LightMeterAppViewAbout); +} + +bool lightmeter_scene_about_on_event(void* context, SceneManagerEvent event) { + LightMeterApp* app = context; + + bool consumed = false; + UNUSED(app); + UNUSED(event); + + return consumed; +} + +void lightmeter_scene_about_on_exit(void* context) { + LightMeterApp* app = context; + + // Clear views + widget_reset(app->widget); +} diff --git a/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_config.c b/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_config.c new file mode 100644 index 000000000..3d6bd5803 --- /dev/null +++ b/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_config.c @@ -0,0 +1,216 @@ +#include "../../lightmeter.h" + +#define TAG "Scene Config" + +static const char* iso_numbers[] = { + [ISO_6] = "6", + [ISO_12] = "12", + [ISO_25] = "25", + [ISO_50] = "50", + [ISO_100] = "100", + [ISO_200] = "200", + [ISO_400] = "400", + [ISO_800] = "800", + [ISO_1600] = "1600", + [ISO_3200] = "3200", + [ISO_6400] = "6400", + [ISO_12800] = "12800", + [ISO_25600] = "25600", + [ISO_51200] = "51200", + [ISO_102400] = "102400", +}; + +static const char* nd_numbers[] = { + [ND_0] = "0", + [ND_2] = "2", + [ND_4] = "4", + [ND_8] = "8", + [ND_16] = "16", + [ND_32] = "32", + [ND_64] = "64", + [ND_128] = "128", + [ND_256] = "256", + [ND_512] = "512", + [ND_1024] = "1024", + [ND_2048] = "2048", + [ND_4096] = "4096", +}; + +static const char* diffusion_dome[] = { + [WITHOUT_DOME] = "No", + [WITH_DOME] = "Yes", +}; + +static const char* backlight[] = { + [BACKLIGHT_AUTO] = "Auto", + [BACKLIGHT_ON] = "On", +}; + +static const char* lux_only[] = { + [LUX_ONLY_OFF] = "Off", + [LUX_ONLY_ON] = "On", +}; + +enum LightMeterSubmenuIndex { + LightMeterSubmenuIndexISO, + LightMeterSubmenuIndexND, + LightMeterSubmenuIndexDome, + LightMeterSubmenuIndexBacklight, + LightMeterSubmenuIndexLuxMeter, + LightMeterSubmenuIndexHelp, + LightMeterSubmenuIndexAbout, +}; + +static void iso_numbers_cb(VariableItem* item) { + LightMeterApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, iso_numbers[index]); + + LightMeterConfig* config = app->config; + config->iso = index; + lightmeter_app_set_config(app, config); +} + +static void nd_numbers_cb(VariableItem* item) { + LightMeterApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, nd_numbers[index]); + + LightMeterConfig* config = app->config; + config->nd = index; + lightmeter_app_set_config(app, config); +} + +static void dome_presence_cb(VariableItem* item) { + LightMeterApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, diffusion_dome[index]); + + LightMeterConfig* config = app->config; + config->dome = index; + lightmeter_app_set_config(app, config); +} + +static void backlight_cb(VariableItem* item) { + LightMeterApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, backlight[index]); + + LightMeterConfig* config = app->config; + if(index != config->backlight) { + if(index == BACKLIGHT_ON) { + notification_message( + app->notifications, + &sequence_display_backlight_enforce_on); // force on backlight + } else { + notification_message( + app->notifications, + &sequence_display_backlight_enforce_auto); // force auto backlight + } + } + config->backlight = index; + lightmeter_app_set_config(app, config); +} + +static void lux_only_cb(VariableItem* item) { + LightMeterApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, lux_only[index]); + + LightMeterConfig* config = app->config; + config->lux_only = index; + lightmeter_app_set_config(app, config); +} + +static void ok_cb(void* context, uint32_t index) { + LightMeterApp* app = context; + UNUSED(app); + switch(index) { + case LightMeterSubmenuIndexHelp: + view_dispatcher_send_custom_event(app->view_dispatcher, LightMeterAppCustomEventHelp); + break; + case LightMeterSubmenuIndexAbout: + view_dispatcher_send_custom_event(app->view_dispatcher, LightMeterAppCustomEventAbout); + break; + default: + break; + } +} + +void lightmeter_scene_config_on_enter(void* context) { + LightMeterApp* app = context; + VariableItemList* var_item_list = app->var_item_list; + VariableItem* item; + LightMeterConfig* config = app->config; + + item = + variable_item_list_add(var_item_list, "ISO", COUNT_OF(iso_numbers), iso_numbers_cb, app); + variable_item_set_current_value_index(item, config->iso); + variable_item_set_current_value_text(item, iso_numbers[config->iso]); + + item = variable_item_list_add( + var_item_list, "ND factor", COUNT_OF(nd_numbers), nd_numbers_cb, app); + variable_item_set_current_value_index(item, config->nd); + variable_item_set_current_value_text(item, nd_numbers[config->nd]); + + item = variable_item_list_add( + var_item_list, "Diffusion dome", COUNT_OF(diffusion_dome), dome_presence_cb, app); + variable_item_set_current_value_index(item, config->dome); + variable_item_set_current_value_text(item, diffusion_dome[config->dome]); + + item = + variable_item_list_add(var_item_list, "Backlight", COUNT_OF(backlight), backlight_cb, app); + variable_item_set_current_value_index(item, config->backlight); + variable_item_set_current_value_text(item, backlight[config->backlight]); + + item = variable_item_list_add( + var_item_list, "Lux meter only", COUNT_OF(lux_only), lux_only_cb, app); + variable_item_set_current_value_index(item, config->lux_only); + variable_item_set_current_value_text(item, lux_only[config->lux_only]); + + item = variable_item_list_add(var_item_list, "Help and Pinout", 0, NULL, NULL); + item = variable_item_list_add(var_item_list, "About", 0, NULL, NULL); + + variable_item_list_set_selected_item( + var_item_list, + scene_manager_get_scene_state(app->scene_manager, LightMeterAppSceneConfig)); + + variable_item_list_set_enter_callback(var_item_list, ok_cb, app); + + view_dispatcher_switch_to_view(app->view_dispatcher, LightMeterAppViewVarItemList); +} + +bool lightmeter_scene_config_on_event(void* context, SceneManagerEvent event) { + LightMeterApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeTick) { + consumed = true; + } else if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case LightMeterAppCustomEventHelp: + scene_manager_next_scene(app->scene_manager, LightMeterAppSceneHelp); + consumed = true; + break; + case LightMeterAppCustomEventAbout: + scene_manager_next_scene(app->scene_manager, LightMeterAppSceneAbout); + consumed = true; + break; + } + } + return consumed; +} + +void lightmeter_scene_config_on_exit(void* context) { + LightMeterApp* app = context; + variable_item_list_reset(app->var_item_list); + main_view_set_iso(app->main_view, app->config->iso); + main_view_set_nd(app->main_view, app->config->nd); + main_view_set_dome(app->main_view, app->config->dome); + main_view_set_lux_only(app->main_view, app->config->lux_only); +} diff --git a/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_help.c b/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_help.c new file mode 100644 index 000000000..0441f0925 --- /dev/null +++ b/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_help.c @@ -0,0 +1,34 @@ +#include "../../lightmeter.h" + +void lightmeter_scene_help_on_enter(void* context) { + LightMeterApp* app = context; + + FuriString* temp_str; + temp_str = furi_string_alloc(); + furi_string_printf( + temp_str, "App works with BH1750\nambient light sensor\nconnected via I2C interface\n\n"); + furi_string_cat(temp_str, "\e#Pinout:\r\n"); + furi_string_cat( + temp_str, + " VCC: 3.3V\r\n" + " GND: GND\r\n" + " SDA: 15 [C1]\r\n" + " SCL: 16 [C0]\r\n"); + + widget_add_text_scroll_element(app->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(app->view_dispatcher, LightMeterAppViewHelp); +} + +bool lightmeter_scene_help_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void lightmeter_scene_help_on_exit(void* context) { + LightMeterApp* app = context; + + widget_reset(app->widget); +} diff --git a/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_main.c b/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_main.c new file mode 100644 index 000000000..8ba474461 --- /dev/null +++ b/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_main.c @@ -0,0 +1,43 @@ +#include "../../lightmeter.h" + +static void lightmeter_scene_main_on_left(void* context) { + LightMeterApp* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, LightMeterAppCustomEventConfig); +} + +void lightmeter_scene_main_on_enter(void* context) { + LightMeterApp* app = context; + + lightmeter_main_view_set_left_callback(app->main_view, lightmeter_scene_main_on_left, app); + view_dispatcher_switch_to_view(app->view_dispatcher, LightMeterAppViewMainView); +} + +bool lightmeter_scene_main_on_event(void* context, SceneManagerEvent event) { + LightMeterApp* app = context; + + bool response = false; + + switch(event.type) { + case SceneManagerEventTypeCustom: + if(event.event == LightMeterAppCustomEventConfig) { + scene_manager_next_scene(app->scene_manager, LightMeterAppSceneConfig); + response = true; + } + break; + + case SceneManagerEventTypeTick: + lightmeter_app_i2c_callback(app); + response = true; + break; + + default: + break; + } + + return response; +} + +void lightmeter_scene_main_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/plugins/lightmeter/gui/views/main_view.c b/applications/plugins/lightmeter/gui/views/main_view.c new file mode 100644 index 000000000..fcbafbff4 --- /dev/null +++ b/applications/plugins/lightmeter/gui/views/main_view.c @@ -0,0 +1,470 @@ +#include "main_view.h" +#include +#include +#include +#include "../../lightmeter.h" +#include "../../lightmeter_helper.h" + +#define WORKER_TAG "Main View" + +static const int iso_numbers[] = { + [ISO_6] = 6, + [ISO_12] = 12, + [ISO_25] = 25, + [ISO_50] = 50, + [ISO_100] = 100, + [ISO_200] = 200, + [ISO_400] = 400, + [ISO_800] = 800, + [ISO_1600] = 1600, + [ISO_3200] = 3200, + [ISO_6400] = 6400, + [ISO_12800] = 12800, + [ISO_25600] = 25600, + [ISO_51200] = 51200, + [ISO_102400] = 102400, +}; + +static const int nd_numbers[] = { + [ND_0] = 0, + [ND_2] = 2, + [ND_4] = 4, + [ND_8] = 8, + [ND_16] = 16, + [ND_32] = 32, + [ND_64] = 64, + [ND_128] = 128, + [ND_256] = 256, + [ND_512] = 512, + [ND_1024] = 1024, + [ND_2048] = 2048, + [ND_4096] = 4096, +}; + +const float aperture_numbers[] = { + [AP_1] = 1.0, + [AP_1_4] = 1.4, + [AP_2] = 2.0, + [AP_2_8] = 2.8, + [AP_4] = 4.0, + [AP_5_6] = 5.6, + [AP_8] = 8, + [AP_11] = 11, + [AP_16] = 16, + [AP_22] = 22, + [AP_32] = 32, + [AP_45] = 45, + [AP_64] = 64, + [AP_90] = 90, + [AP_128] = 128, +}; + +const float speed_numbers[] = { + [SPEED_8000] = 1.0 / 8000, [SPEED_4000] = 1.0 / 4000, [SPEED_2000] = 1.0 / 2000, + [SPEED_1000] = 1.0 / 1000, [SPEED_500] = 1.0 / 500, [SPEED_250] = 1.0 / 250, + [SPEED_125] = 1.0 / 125, [SPEED_60] = 1.0 / 60, [SPEED_30] = 1.0 / 30, + [SPEED_15] = 1.0 / 15, [SPEED_8] = 1.0 / 8, [SPEED_4] = 1.0 / 4, + [SPEED_2] = 1.0 / 2, [SPEED_1S] = 1.0, [SPEED_2S] = 2.0, + [SPEED_4S] = 4.0, [SPEED_8S] = 8.0, [SPEED_15S] = 15.0, + [SPEED_30S] = 30.0, +}; + +struct MainView { + View* view; + LightMeterMainViewButtonCallback cb_left; + void* cb_context; +}; + +void lightmeter_main_view_set_left_callback( + MainView* lightmeter_main_view, + LightMeterMainViewButtonCallback callback, + void* context) { + with_view_model( + lightmeter_main_view->view, + MainViewModel * model, + { + UNUSED(model); + lightmeter_main_view->cb_left = callback; + lightmeter_main_view->cb_context = context; + }, + true); +} + +static void main_view_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + MainViewModel* model = context; + + canvas_clear(canvas); + + // draw button + canvas_set_font(canvas, FontSecondary); + elements_button_left(canvas, "Config"); + + if(!model->lux_only) { + // top row + draw_top_row(canvas, model); + + // add f, T values + canvas_set_font(canvas, FontBigNumbers); + + // draw f icon and number + canvas_draw_icon(canvas, 15, 17, &I_f_10x14); + draw_aperture(canvas, model); + + // draw T icon and number + canvas_draw_icon(canvas, 15, 34, &I_T_10x14); + draw_speed(canvas, model); + + // draw ND number + draw_nd_number(canvas, model); + + // draw EV number + canvas_set_font(canvas, FontSecondary); + draw_EV_number(canvas, model); + + // draw mode indicator + draw_mode_indicator(canvas, model); + } else { + draw_lux_only_mode(canvas, model); + } +} + +static void main_view_process(MainView* main_view, InputEvent* event) { + with_view_model( + main_view->view, + MainViewModel * model, + { + if(event->type == InputTypePress) { + if(event->key == InputKeyUp) { + switch(model->current_mode) { + case FIXED_APERTURE: + if(model->aperture < AP_NUM - 1) model->aperture++; + break; + + case FIXED_SPEED: + if(model->speed < SPEED_NUM - 1) model->speed++; + break; + + default: + break; + } + } else if(event->key == InputKeyDown) { + switch(model->current_mode) { + case FIXED_APERTURE: + if(model->aperture > 0) model->aperture--; + break; + + case FIXED_SPEED: + if(model->speed > 0) model->speed--; + break; + + default: + break; + } + } else if(event->key == InputKeyOk) { + switch(model->current_mode) { + case FIXED_SPEED: + model->current_mode = FIXED_APERTURE; + break; + + case FIXED_APERTURE: + model->current_mode = FIXED_SPEED; + break; + + default: + break; + } + } + } + }, + true); +} + +static bool main_view_input_callback(InputEvent* event, void* context) { + furi_assert(context); + MainView* main_view = context; + bool consumed = false; + + if(event->type == InputTypeShort && event->key == InputKeyLeft) { + if(main_view->cb_left) { + main_view->cb_left(main_view->cb_context); + } + consumed = true; + } else if(event->type == InputTypeShort && event->key == InputKeyBack) { + } else { + main_view_process(main_view, event); + consumed = true; + } + + return consumed; +} + +MainView* main_view_alloc() { + MainView* main_view = malloc(sizeof(MainView)); + main_view->view = view_alloc(); + view_set_context(main_view->view, main_view); + view_allocate_model(main_view->view, ViewModelTypeLocking, sizeof(MainViewModel)); + view_set_draw_callback(main_view->view, main_view_draw_callback); + view_set_input_callback(main_view->view, main_view_input_callback); + + return main_view; +} + +void main_view_free(MainView* main_view) { + furi_assert(main_view); + view_free(main_view->view); + free(main_view); +} + +View* main_view_get_view(MainView* main_view) { + furi_assert(main_view); + return main_view->view; +} + +void main_view_set_lux(MainView* main_view, float val) { + furi_assert(main_view); + with_view_model( + main_view->view, MainViewModel * model, { model->lux = val; }, true); +} + +void main_view_set_EV(MainView* main_view, float val) { + furi_assert(main_view); + with_view_model( + main_view->view, MainViewModel * model, { model->EV = val; }, true); +} + +void main_view_set_response(MainView* main_view, bool val) { + furi_assert(main_view); + with_view_model( + main_view->view, MainViewModel * model, { model->response = val; }, true); +} + +void main_view_set_iso(MainView* main_view, int iso) { + furi_assert(main_view); + with_view_model( + main_view->view, MainViewModel * model, { model->iso = iso; }, true); +} + +void main_view_set_nd(MainView* main_view, int nd) { + furi_assert(main_view); + with_view_model( + main_view->view, MainViewModel * model, { model->nd = nd; }, true); +} + +void main_view_set_aperture(MainView* main_view, int aperture) { + furi_assert(main_view); + with_view_model( + main_view->view, MainViewModel * model, { model->aperture = aperture; }, true); +} + +void main_view_set_speed(MainView* main_view, int speed) { + furi_assert(main_view); + with_view_model( + main_view->view, MainViewModel * model, { model->speed = speed; }, true); +} + +void main_view_set_dome(MainView* main_view, bool dome) { + furi_assert(main_view); + with_view_model( + main_view->view, MainViewModel * model, { model->dome = dome; }, true); +} + +void main_view_set_lux_only(MainView* main_view, bool lux_only) { + furi_assert(main_view); + with_view_model( + main_view->view, MainViewModel * model, { model->lux_only = lux_only; }, true); +} + +bool main_view_get_dome(MainView* main_view) { + furi_assert(main_view); + bool val = false; + with_view_model( + main_view->view, MainViewModel * model, { val = model->dome; }, true); + return val; +} + +void draw_top_row(Canvas* canvas, MainViewModel* context) { + MainViewModel* model = context; + + char str[12]; + + if(!model->response) { + canvas_draw_box(canvas, 0, 0, 128, 12); + canvas_set_color(canvas, ColorWhite); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 24, 10, "No sensor found"); + canvas_set_color(canvas, ColorBlack); + } else { + model->iso_val = iso_numbers[model->iso]; + if(model->nd > 0) model->iso_val /= nd_numbers[model->nd]; + + if(model->lux > 0) { + if(model->current_mode == FIXED_APERTURE) { + model->speed_val = 100 * pow(aperture_numbers[model->aperture], 2) / + (double)model->iso_val / pow(2, model->EV); + } else { + model->aperture_val = sqrt( + pow(2, model->EV) * (double)model->iso_val * + (double)speed_numbers[model->speed] / 100); + } + } + + // TODO when T:30, f/0 instead of f/128 + + canvas_draw_line(canvas, 0, 10, 128, 10); + + canvas_set_font(canvas, FontPrimary); + // metering mode A – ambient, F – flash + // canvas_draw_str_aligned(canvas, 1, 1, AlignLeft, AlignTop, "A"); + + snprintf(str, sizeof(str), "ISO: %d", iso_numbers[model->iso]); + canvas_draw_str_aligned(canvas, 19, 1, AlignLeft, AlignTop, str); + + canvas_set_font(canvas, FontSecondary); + snprintf(str, sizeof(str), "lx: %.0f", (double)model->lux); + canvas_draw_str_aligned(canvas, 87, 2, AlignLeft, AlignTop, str); + } +} + +void draw_aperture(Canvas* canvas, MainViewModel* context) { + MainViewModel* model = context; + + char str[12]; + + switch(model->current_mode) { + case FIXED_APERTURE: + if(model->response) { + if(model->aperture < AP_8) { + snprintf(str, sizeof(str), "/%.1f", (double)aperture_numbers[model->aperture]); + } else { + snprintf(str, sizeof(str), "/%.0f", (double)aperture_numbers[model->aperture]); + } + } else { + snprintf(str, sizeof(str), " ---"); + } + canvas_draw_str_aligned(canvas, 27, 15, AlignLeft, AlignTop, str); + break; + case FIXED_SPEED: + if(model->aperture_val < aperture_numbers[0] || !model->response) { + snprintf(str, sizeof(str), " ---"); + } else if(model->aperture_val < aperture_numbers[AP_8]) { + snprintf(str, sizeof(str), "/%.1f", (double)normalizeAperture(model->aperture_val)); + } else { + snprintf(str, sizeof(str), "/%.0f", (double)normalizeAperture(model->aperture_val)); + } + canvas_draw_str_aligned(canvas, 27, 15, AlignLeft, AlignTop, str); + break; + default: + break; + } +} + +void draw_speed(Canvas* canvas, MainViewModel* context) { + MainViewModel* model = context; + + char str[12]; + + switch(model->current_mode) { + case FIXED_APERTURE: + if(model->lux > 0 && model->response) { + if(model->speed_val < 1 && model->speed_val > 0) { + snprintf(str, sizeof(str), ":1/%.0f", 1 / (double)normalizeTime(model->speed_val)); + } else { + snprintf(str, sizeof(str), ":%.0f", (double)normalizeTime(model->speed_val)); + } + } else { + snprintf(str, sizeof(str), " ---"); + } + canvas_draw_str_aligned(canvas, 27, 34, AlignLeft, AlignTop, str); + break; + + case FIXED_SPEED: + if(model->response) { + if(model->speed < SPEED_1S) { + snprintf(str, sizeof(str), ":1/%.0f", 1 / (double)speed_numbers[model->speed]); + } else { + snprintf(str, sizeof(str), ":%.0f", (double)speed_numbers[model->speed]); + } + } else { + snprintf(str, sizeof(str), " ---"); + } + canvas_draw_str_aligned(canvas, 27, 34, AlignLeft, AlignTop, str); + break; + + default: + break; + } +} + +void draw_mode_indicator(Canvas* canvas, MainViewModel* context) { + MainViewModel* model = context; + + switch(model->current_mode) { + case FIXED_SPEED: + canvas_set_font(canvas, FontBigNumbers); + canvas_draw_str_aligned(canvas, 3, 36, AlignLeft, AlignTop, "*"); + break; + + case FIXED_APERTURE: + canvas_set_font(canvas, FontBigNumbers); + canvas_draw_str_aligned(canvas, 3, 17, AlignLeft, AlignTop, "*"); + break; + + default: + break; + } +} + +void draw_nd_number(Canvas* canvas, MainViewModel* context) { + MainViewModel* model = context; + + char str[9]; + + canvas_set_font(canvas, FontSecondary); + + if(model->response) { + snprintf(str, sizeof(str), "ND: %d", nd_numbers[model->nd]); + } else { + snprintf(str, sizeof(str), "ND: ---"); + } + canvas_draw_str_aligned(canvas, 87, 20, AlignLeft, AlignBottom, str); +} + +void draw_EV_number(Canvas* canvas, MainViewModel* context) { + MainViewModel* model = context; + + char str[7]; + + if(model->lux > 0 && model->response) { + snprintf(str, sizeof(str), "EV: %1.0f", (double)model->EV); + canvas_draw_str_aligned(canvas, 87, 29, AlignLeft, AlignBottom, str); + } else { + canvas_draw_str_aligned(canvas, 87, 29, AlignLeft, AlignBottom, "EV: --"); + } +} + +void draw_lux_only_mode(Canvas* canvas, MainViewModel* context) { + MainViewModel* model = context; + + if(!model->response) { + canvas_draw_box(canvas, 0, 0, 128, 12); + canvas_set_color(canvas, ColorWhite); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 24, 10, "No sensor found"); + canvas_set_color(canvas, ColorBlack); + } else { + char str[12]; + + canvas_set_font(canvas, FontPrimary); + + canvas_draw_line(canvas, 0, 10, 128, 10); + canvas_draw_str_aligned(canvas, 64, 1, AlignCenter, AlignTop, "Lux meter mode"); + + canvas_set_font(canvas, FontBigNumbers); + snprintf(str, sizeof(str), "%.0f", (double)model->lux); + canvas_draw_str_aligned(canvas, 80, 32, AlignRight, AlignCenter, str); + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 85, 39, AlignLeft, AlignBottom, "Lux"); + } +} diff --git a/applications/plugins/lightmeter/gui/views/main_view.h b/applications/plugins/lightmeter/gui/views/main_view.h new file mode 100644 index 000000000..42d2d1877 --- /dev/null +++ b/applications/plugins/lightmeter/gui/views/main_view.h @@ -0,0 +1,78 @@ +#pragma once + +#include +#include "BH1750_Lightmeter_icons.h" +#include "../../lightmeter_config.h" + +typedef struct MainView MainView; + +typedef enum { + FIXED_APERTURE, + FIXED_SPEED, + + MODES_SIZE +} MainViewMode; + +typedef struct { + uint8_t recv[2]; + MainViewMode current_mode; + float lux; + float EV; + float aperture_val; + float speed_val; + int iso_val; + bool response; + int iso; + int nd; + int aperture; + int speed; + bool dome; + bool lux_only; +} MainViewModel; + +typedef void (*LightMeterMainViewButtonCallback)(void* context); + +void lightmeter_main_view_set_left_callback( + MainView* lightmeter_main_view, + LightMeterMainViewButtonCallback callback, + void* context); + +MainView* main_view_alloc(); + +void main_view_free(MainView* main_view); + +View* main_view_get_view(MainView* main_view); + +void main_view_set_lux(MainView* main_view, float val); + +void main_view_set_EV(MainView* main_view_, float val); + +void main_view_set_response(MainView* main_view_, bool val); + +void main_view_set_iso(MainView* main_view, int val); + +void main_view_set_nd(MainView* main_view, int val); + +void main_view_set_aperture(MainView* main_view, int val); + +void main_view_set_speed(MainView* main_view, int val); + +void main_view_set_dome(MainView* main_view, bool val); + +void main_view_set_lux_only(MainView* main_view, bool val); + +bool main_view_get_dome(MainView* main_view); + +void draw_top_row(Canvas* canvas, MainViewModel* context); + +void draw_aperture(Canvas* canvas, MainViewModel* context); + +void draw_speed(Canvas* canvas, MainViewModel* context); + +void draw_mode_indicator(Canvas* canvas, MainViewModel* context); + +void draw_nd_number(Canvas* canvas, MainViewModel* context); + +void draw_EV_number(Canvas* canvas, MainViewModel* context); + +void draw_lux_only_mode(Canvas* canvas, MainViewModel* context); diff --git a/applications/plugins/lightmeter/icons/T_10x14.png b/applications/plugins/lightmeter/icons/T_10x14.png new file mode 100644 index 000000000..d81c2c424 Binary files /dev/null and b/applications/plugins/lightmeter/icons/T_10x14.png differ diff --git a/applications/plugins/lightmeter/icons/f_10x14.png b/applications/plugins/lightmeter/icons/f_10x14.png new file mode 100644 index 000000000..c3e85c0ec Binary files /dev/null and b/applications/plugins/lightmeter/icons/f_10x14.png differ diff --git a/applications/plugins/lightmeter/images/framed_gui_config.png b/applications/plugins/lightmeter/images/framed_gui_config.png new file mode 100644 index 000000000..b87c3bd5c Binary files /dev/null and b/applications/plugins/lightmeter/images/framed_gui_config.png differ diff --git a/applications/plugins/lightmeter/images/framed_gui_lux_meter.png b/applications/plugins/lightmeter/images/framed_gui_lux_meter.png new file mode 100644 index 000000000..6ab0cf191 Binary files /dev/null and b/applications/plugins/lightmeter/images/framed_gui_lux_meter.png differ diff --git a/applications/plugins/lightmeter/images/framed_gui_main.png b/applications/plugins/lightmeter/images/framed_gui_main.png new file mode 100644 index 000000000..23dc4a2cc Binary files /dev/null and b/applications/plugins/lightmeter/images/framed_gui_main.png differ diff --git a/applications/plugins/lightmeter/images/gui_config.png b/applications/plugins/lightmeter/images/gui_config.png new file mode 100644 index 000000000..95fdcb167 Binary files /dev/null and b/applications/plugins/lightmeter/images/gui_config.png differ diff --git a/applications/plugins/lightmeter/images/gui_lux_meter.png b/applications/plugins/lightmeter/images/gui_lux_meter.png new file mode 100644 index 000000000..f7159e671 Binary files /dev/null and b/applications/plugins/lightmeter/images/gui_lux_meter.png differ diff --git a/applications/plugins/lightmeter/images/gui_main.png b/applications/plugins/lightmeter/images/gui_main.png new file mode 100644 index 000000000..ba4c3f75f Binary files /dev/null and b/applications/plugins/lightmeter/images/gui_main.png differ diff --git a/applications/plugins/lightmeter/lib/BH1750/BH1750.c b/applications/plugins/lightmeter/lib/BH1750/BH1750.c new file mode 100644 index 000000000..28616e040 --- /dev/null +++ b/applications/plugins/lightmeter/lib/BH1750/BH1750.c @@ -0,0 +1,144 @@ +/** + * @file BH1750.h + * @author Oleksii Kutuzov (oleksii.kutuzov@icloud.com) + * @brief + * @version 0.1 + * @date 2022-11-06 + * + * @copyright Copyright (c) 2022 + * + * Ported from: + * https://github.com/lamik/Light_Sensors_STM32 + */ + +#include "BH1750.h" + +BH1750_mode bh1750_mode = BH1750_DEFAULT_MODE; // Current sensor mode +uint8_t bh1750_mt_reg = BH1750_DEFAULT_MTREG; // Current MT register value + +BH1750_STATUS bh1750_init() { + if(BH1750_OK == bh1750_reset()) { + if(BH1750_OK == bh1750_set_mt_reg(BH1750_DEFAULT_MTREG)) { + return BH1750_OK; + } + } + return BH1750_ERROR; +} + +BH1750_STATUS bh1750_reset() { + uint8_t command = 0x07; + bool status; + + furi_hal_i2c_acquire(I2C_BUS); + status = furi_hal_i2c_tx(I2C_BUS, BH1750_ADDRESS, &command, 1, I2C_TIMEOUT); + furi_hal_i2c_release(I2C_BUS); + + if(status) { + return BH1750_OK; + } + + return BH1750_ERROR; +} + +BH1750_STATUS bh1750_set_power_state(uint8_t PowerOn) { + PowerOn = (PowerOn ? 1 : 0); + bool status; + + furi_hal_i2c_acquire(I2C_BUS); + status = furi_hal_i2c_tx(I2C_BUS, BH1750_ADDRESS, &PowerOn, 1, I2C_TIMEOUT); + furi_hal_i2c_release(I2C_BUS); + + if(status) { + return BH1750_OK; + } + + return BH1750_ERROR; +} + +BH1750_STATUS bh1750_set_mode(BH1750_mode mode) { + if(!((mode >> 4) || (mode >> 5))) { + return BH1750_ERROR; + } + + if((mode & 0x0F) > 3) { + return BH1750_ERROR; + } + + bool status; + + bh1750_mode = mode; + + furi_hal_i2c_acquire(I2C_BUS); + status = furi_hal_i2c_tx(I2C_BUS, BH1750_ADDRESS, &mode, 1, I2C_TIMEOUT); + furi_hal_i2c_release(I2C_BUS); + + if(status) { + return BH1750_OK; + } + + return BH1750_ERROR; +} + +BH1750_STATUS bh1750_set_mt_reg(uint8_t mt_reg) { + if(mt_reg < 31 || mt_reg > 254) { + return BH1750_ERROR; + } + + bh1750_mt_reg = mt_reg; + + uint8_t tmp[2]; + bool status; + + tmp[0] = (0x40 | (mt_reg >> 5)); + tmp[1] = (0x60 | (mt_reg & 0x1F)); + + furi_hal_i2c_acquire(I2C_BUS); + status = furi_hal_i2c_tx(I2C_BUS, BH1750_ADDRESS, &tmp[0], 1, I2C_TIMEOUT); + furi_hal_i2c_release(I2C_BUS); + if(!status) { + return BH1750_ERROR; + } + + furi_hal_i2c_acquire(I2C_BUS); + status = furi_hal_i2c_tx(I2C_BUS, BH1750_ADDRESS, &tmp[1], 1, I2C_TIMEOUT); + furi_hal_i2c_release(I2C_BUS); + if(status) { + return BH1750_OK; + } + + return BH1750_ERROR; +} + +BH1750_STATUS bh1750_trigger_manual_conversion() { + if(BH1750_OK == bh1750_set_mode(bh1750_mode)) { + return BH1750_OK; + } + return BH1750_ERROR; +} + +BH1750_STATUS bh1750_read_light(float* result) { + float result_tmp; + uint8_t rcv[2]; + bool status; + + furi_hal_i2c_acquire(I2C_BUS); + status = furi_hal_i2c_rx(I2C_BUS, BH1750_ADDRESS, rcv, 2, I2C_TIMEOUT); + furi_hal_i2c_release(I2C_BUS); + + if(status) { + result_tmp = (rcv[0] << 8) | (rcv[1]); + + if(bh1750_mt_reg != BH1750_DEFAULT_MTREG) { + result_tmp *= (float)((uint8_t)BH1750_DEFAULT_MTREG / (float)bh1750_mt_reg); + } + + if(bh1750_mode == ONETIME_HIGH_RES_MODE_2 || bh1750_mode == CONTINUOUS_HIGH_RES_MODE_2) { + result_tmp /= 2.0; + } + + *result = result_tmp / BH1750_CONVERSION_FACTOR; + + return BH1750_OK; + } + return BH1750_ERROR; +} diff --git a/applications/plugins/lightmeter/lib/BH1750/BH1750.h b/applications/plugins/lightmeter/lib/BH1750/BH1750.h new file mode 100644 index 000000000..991350f7f --- /dev/null +++ b/applications/plugins/lightmeter/lib/BH1750/BH1750.h @@ -0,0 +1,103 @@ +/** + * @file BH1750.h + * @author Oleksii Kutuzov (oleksii.kutuzov@icloud.com) + * @brief + * @version 0.1 + * @date 2022-11-06 + * + * @copyright Copyright (c) 2022 + * + * Ported from: + * https://github.com/lamik/Light_Sensors_STM32 + */ + +#include +#include + +#ifndef BH1750_H_ +#define BH1750_H_ + +// I2C BUS +#define I2C_BUS &furi_hal_i2c_handle_external +#define I2C_TIMEOUT 10 + +#define BH1750_ADDRESS (0x23 << 1) + +#define BH1750_POWER_DOWN 0x00 +#define BH1750_POWER_ON 0x01 +#define BH1750_RESET 0x07 +#define BH1750_DEFAULT_MTREG 69 +#define BH1750_DEFAULT_MODE ONETIME_HIGH_RES_MODE + +#define BH1750_CONVERSION_FACTOR 1.2 + +typedef enum { BH1750_OK = 0, BH1750_ERROR = 1 } BH1750_STATUS; + +typedef enum { + CONTINUOUS_HIGH_RES_MODE = 0x10, + CONTINUOUS_HIGH_RES_MODE_2 = 0x11, + CONTINUOUS_LOW_RES_MODE = 0x13, + ONETIME_HIGH_RES_MODE = 0x20, + ONETIME_HIGH_RES_MODE_2 = 0x21, + ONETIME_LOW_RES_MODE = 0x23 +} BH1750_mode; + +/** + * @brief Initialize the sensor. Sends the reset command and sets the measurement register to the default value. + * + * @return BH1750_STATUS + */ +BH1750_STATUS bh1750_init(); + +/** + * @brief Reset all registers to the default value. + * + * @return BH1750_STATUS + */ +BH1750_STATUS bh1750_reset(); + +/** + * @brief Sets the power state. 1 - running; 0 - sleep, low power. + * + * @param PowerOn sensor state. + * @return BH1750_STATUS + */ +BH1750_STATUS bh1750_set_power_state(uint8_t PowerOn); + +/** + * @brief Set the Measurement Time register. It allows to increase or decrease the sensitivity. + * + * @param MTreg value from 31 to 254, defaults to 69. + * + * @return BH1750_STATUS + */ +BH1750_STATUS bh1750_set_mt_reg(uint8_t MTreg); + +/** + * @brief Set the mode of converting. Look into the bh1750_mode enum. + * + * @param Mode mode enumerator + * @return BH1750_STATUS + */ +BH1750_STATUS bh1750_set_mode(BH1750_mode Mode); + +/** + * @brief Trigger the conversion in manual modes. + * + * @details a low-resolution mode, the conversion time is typically 16 ms, and for a high-resolution + * mode is 120 ms. You need to wait until reading the measurement value. There is no need + * to exit low-power mode for manual conversion. It makes automatically. + * + * @return BH1750_STATUS + */ +BH1750_STATUS bh1750_trigger_manual_conversion(); + +/** + * @brief Read the converted value and calculate the result. + * + * @param Result stores received value to this variable. + * @return BH1750_STATUS + */ +BH1750_STATUS bh1750_read_light(float* Result); + +#endif /* BH1750_H_ */ diff --git a/applications/plugins/lightmeter/lib/BH1750/LICENSE b/applications/plugins/lightmeter/lib/BH1750/LICENSE new file mode 100644 index 000000000..cb2f65db5 --- /dev/null +++ b/applications/plugins/lightmeter/lib/BH1750/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Oleksii Kutuzov + +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. diff --git a/applications/plugins/lightmeter/lib/BH1750/README.md b/applications/plugins/lightmeter/lib/BH1750/README.md new file mode 100644 index 000000000..b1338d4ab --- /dev/null +++ b/applications/plugins/lightmeter/lib/BH1750/README.md @@ -0,0 +1,2 @@ +# flipperzero-BH1750 +BH1750 light sensor library for Flipper Zero diff --git a/applications/plugins/lightmeter/lib/BH1750/docs/BH1750.pdf b/applications/plugins/lightmeter/lib/BH1750/docs/BH1750.pdf new file mode 100644 index 000000000..267efddc6 Binary files /dev/null and b/applications/plugins/lightmeter/lib/BH1750/docs/BH1750.pdf differ diff --git a/applications/plugins/lightmeter/lightmeter.c b/applications/plugins/lightmeter/lightmeter.c new file mode 100644 index 000000000..07661d2d4 --- /dev/null +++ b/applications/plugins/lightmeter/lightmeter.c @@ -0,0 +1,161 @@ +#include "lightmeter.h" +#include "lightmeter_helper.h" + +#define TAG "MAIN APP" + +static bool lightmeter_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + LightMeterApp* app = context; + + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool lightmeter_back_event_callback(void* context) { + furi_assert(context); + LightMeterApp* app = context; + + return scene_manager_handle_back_event(app->scene_manager); +} + +static void lightmeter_tick_event_callback(void* context) { + furi_assert(context); + LightMeterApp* app = context; + + scene_manager_handle_tick_event(app->scene_manager); +} + +LightMeterApp* lightmeter_app_alloc(uint32_t first_scene) { + LightMeterApp* app = malloc(sizeof(LightMeterApp)); + + // Sensor + bh1750_set_power_state(1); + bh1750_init(); + bh1750_set_mode(ONETIME_HIGH_RES_MODE); + + // Set default values to config + app->config = malloc(sizeof(LightMeterConfig)); + app->config->iso = DEFAULT_ISO; + app->config->nd = DEFAULT_ND; + app->config->aperture = DEFAULT_APERTURE; + app->config->dome = DEFAULT_DOME; + app->config->backlight = DEFAULT_BACKLIGHT; + + // Records + app->gui = furi_record_open(RECORD_GUI); + app->notifications = furi_record_open(RECORD_NOTIFICATION); + + // View dispatcher + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&lightmeter_scene_handlers, app); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, lightmeter_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, lightmeter_back_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, lightmeter_tick_event_callback, furi_ms_to_ticks(200)); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + // Views + app->main_view = main_view_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, LightMeterAppViewMainView, main_view_get_view(app->main_view)); + + // Set default values to main view from config + main_view_set_iso(app->main_view, app->config->iso); + main_view_set_nd(app->main_view, app->config->nd); + main_view_set_aperture(app->main_view, app->config->aperture); + main_view_set_speed(app->main_view, DEFAULT_SPEED); + main_view_set_dome(app->main_view, app->config->dome); + + // Variable item list + app->var_item_list = variable_item_list_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + LightMeterAppViewVarItemList, + variable_item_list_get_view(app->var_item_list)); + + // Widget + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, LightMeterAppViewAbout, widget_get_view(app->widget)); + view_dispatcher_add_view( + app->view_dispatcher, LightMeterAppViewHelp, widget_get_view(app->widget)); + + // Set first scene + scene_manager_next_scene(app->scene_manager, first_scene); + return app; +} + +void lightmeter_app_free(LightMeterApp* app) { + furi_assert(app); + + // Views + view_dispatcher_remove_view(app->view_dispatcher, LightMeterAppViewMainView); + main_view_free(app->main_view); + + // Variable item list + view_dispatcher_remove_view(app->view_dispatcher, LightMeterAppViewVarItemList); + variable_item_list_free(app->var_item_list); + + // Widget + view_dispatcher_remove_view(app->view_dispatcher, LightMeterAppViewAbout); + view_dispatcher_remove_view(app->view_dispatcher, LightMeterAppViewHelp); + widget_free(app->widget); + + // View dispatcher + scene_manager_free(app->scene_manager); + view_dispatcher_free(app->view_dispatcher); + + // Records + furi_record_close(RECORD_GUI); + if(app->config->backlight != BACKLIGHT_AUTO) { + notification_message( + app->notifications, + &sequence_display_backlight_enforce_auto); // set backlight back to auto + } + furi_record_close(RECORD_NOTIFICATION); + + bh1750_set_power_state(0); + + free(app->config); + free(app); +} + +int32_t lightmeter_app(void* p) { + UNUSED(p); + uint32_t first_scene = LightMeterAppSceneMain; + LightMeterApp* app = lightmeter_app_alloc(first_scene); + view_dispatcher_run(app->view_dispatcher); + lightmeter_app_free(app); + return 0; +} + +void lightmeter_app_set_config(LightMeterApp* context, LightMeterConfig* config) { + LightMeterApp* app = context; + + app->config = config; +} + +void lightmeter_app_i2c_callback(LightMeterApp* context) { + LightMeterApp* app = context; + + float EV = 0; + float lux = 0; + bool response = 0; + + if(bh1750_trigger_manual_conversion() == BH1750_OK) response = 1; + + if(response) { + bh1750_read_light(&lux); + + if(main_view_get_dome(app->main_view)) lux *= DOME_COEFFICIENT; + + EV = lux2ev(lux); + } + + main_view_set_lux(app->main_view, lux); + main_view_set_EV(app->main_view, EV); + main_view_set_response(app->main_view, response); +} diff --git a/applications/plugins/lightmeter/lightmeter.h b/applications/plugins/lightmeter/lightmeter.h new file mode 100644 index 000000000..2558be3c5 --- /dev/null +++ b/applications/plugins/lightmeter/lightmeter.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include + +#include "gui/views/main_view.h" + +#include +#include + +#include "gui/scenes/config/lightmeter_scene.h" +#include + +#include "lightmeter_config.h" +#include + +typedef struct { + int iso; + int nd; + int aperture; + int dome; + int backlight; + int lux_only; +} LightMeterConfig; + +typedef struct { + Gui* gui; + SceneManager* scene_manager; + ViewDispatcher* view_dispatcher; + MainView* main_view; + VariableItemList* var_item_list; + LightMeterConfig* config; + NotificationApp* notifications; + Widget* widget; +} LightMeterApp; + +typedef enum { + LightMeterAppViewMainView, + LightMeterAppViewConfigView, + LightMeterAppViewVarItemList, + LightMeterAppViewAbout, + LightMeterAppViewHelp, +} LightMeterAppView; + +typedef enum { + LightMeterAppCustomEventConfig, + LightMeterAppCustomEventHelp, + LightMeterAppCustomEventAbout, +} LightMeterAppCustomEvent; + +void lightmeter_app_set_config(LightMeterApp* context, LightMeterConfig* config); + +void lightmeter_app_i2c_callback(LightMeterApp* context); diff --git a/applications/plugins/lightmeter/lightmeter.png b/applications/plugins/lightmeter/lightmeter.png new file mode 100644 index 000000000..cacd2276f Binary files /dev/null and b/applications/plugins/lightmeter/lightmeter.png differ diff --git a/applications/plugins/lightmeter/lightmeter_config.h b/applications/plugins/lightmeter/lightmeter_config.h new file mode 100644 index 000000000..4f7e11e0d --- /dev/null +++ b/applications/plugins/lightmeter/lightmeter_config.h @@ -0,0 +1,107 @@ +#pragma once + +#define LM_VERSION_APP "0.7" +#define LM_DEVELOPED "Oleksii Kutuzov" +#define LM_GITHUB "https://github.com/oleksiikutuzov/flipperzero-lightmeter" + +#define DOME_COEFFICIENT 2.3 +#define DEFAULT_ISO ISO_100 +#define DEFAULT_ND ND_0 +#define DEFAULT_APERTURE AP_2_8 +#define DEFAULT_SPEED SPEED_125 +#define DEFAULT_DOME WITHOUT_DOME +#define DEFAULT_BACKLIGHT BACKLIGHT_AUTO + +typedef enum { + ISO_6, + ISO_12, + ISO_25, + ISO_50, + ISO_100, + ISO_200, + ISO_400, + ISO_800, + ISO_1600, + ISO_3200, + ISO_6400, + ISO_12800, + ISO_25600, + ISO_51200, + ISO_102400, + + ISO_NUM, +} LightMeterISONumbers; + +typedef enum { + ND_0, + ND_2, + ND_4, + ND_8, + ND_16, + ND_32, + ND_64, + ND_128, + ND_256, + ND_512, + ND_1024, + ND_2048, + ND_4096, + + ND_NUM, +} LightMeterNDNumbers; + +typedef enum { + AP_1, + AP_1_4, + AP_2, + AP_2_8, + AP_4, + AP_5_6, + AP_8, + AP_11, + AP_16, + AP_22, + AP_32, + AP_45, + AP_64, + AP_90, + AP_128, + + AP_NUM, +} LightMeterApertureNumbers; + +typedef enum { + SPEED_8000, + SPEED_4000, + SPEED_2000, + SPEED_1000, + SPEED_500, + SPEED_250, + SPEED_125, + SPEED_60, + SPEED_30, + SPEED_15, + SPEED_8, + SPEED_4, + SPEED_2, + SPEED_1S, + SPEED_2S, + SPEED_4S, + SPEED_8S, + SPEED_15S, + SPEED_30S, + + SPEED_NUM, +} LightMeterSpeedNumbers; + +typedef enum { + WITHOUT_DOME, + WITH_DOME, +} LightMeterDomePresence; + +typedef enum { + LUX_ONLY_OFF, + LUX_ONLY_ON, +} LightMeterLuxOnlyMode; + +typedef enum { BACKLIGHT_AUTO, BACKLIGHT_ON } LightMeterBacklight; diff --git a/applications/plugins/lightmeter/lightmeter_helper.c b/applications/plugins/lightmeter/lightmeter_helper.c new file mode 100644 index 000000000..465ccbce1 --- /dev/null +++ b/applications/plugins/lightmeter/lightmeter_helper.c @@ -0,0 +1,43 @@ +#include "lightmeter_helper.h" +#include "lightmeter_config.h" + +extern const float aperture_numbers[]; +extern const float speed_numbers[]; + +float lux2ev(float lux) { + return log2(lux / 2.5); +} + +float getMinDistance(float x, float v1, float v2) { + if(x - v1 > v2 - x) { + return v2; + } + + return v1; +} + +float normalizeAperture(float a) { + for(int i = 0; i < AP_NUM; i++) { + float a1 = aperture_numbers[i]; + float a2 = aperture_numbers[i + 1]; + + if(a1 < a && a2 >= a) { + return getMinDistance(a, a1, a2); + } + } + + return 0; +} + +float normalizeTime(float a) { + for(int i = 0; i < SPEED_NUM; i++) { + float a1 = speed_numbers[i]; + float a2 = speed_numbers[i + 1]; + + if(a1 < a && a2 >= a) { + return getMinDistance(a, a1, a2); + } + } + + return 0; +} diff --git a/applications/plugins/lightmeter/lightmeter_helper.h b/applications/plugins/lightmeter/lightmeter_helper.h new file mode 100644 index 000000000..78ea6a8d8 --- /dev/null +++ b/applications/plugins/lightmeter/lightmeter_helper.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +float lux2ev(float lux); + +float getMinDistance(float x, float v1, float v2); + +float normalizeAperture(float a); + +float normalizeTime(float a); diff --git a/applications/plugins/lightmeter/module/back.jpg b/applications/plugins/lightmeter/module/back.jpg new file mode 100644 index 000000000..366d7a852 Binary files /dev/null and b/applications/plugins/lightmeter/module/back.jpg differ diff --git a/applications/plugins/lightmeter/module/front.jpg b/applications/plugins/lightmeter/module/front.jpg new file mode 100644 index 000000000..b38fd4150 Binary files /dev/null and b/applications/plugins/lightmeter/module/front.jpg differ diff --git a/applications/plugins/lightmeter/module/module.jpg b/applications/plugins/lightmeter/module/module.jpg new file mode 100644 index 000000000..53f481338 Binary files /dev/null and b/applications/plugins/lightmeter/module/module.jpg differ diff --git a/applications/plugins/lightmeter/module/module_v2_enclosure.stl b/applications/plugins/lightmeter/module/module_v2_enclosure.stl new file mode 100644 index 000000000..ee2be93fd Binary files /dev/null and b/applications/plugins/lightmeter/module/module_v2_enclosure.stl differ diff --git a/applications/plugins/lightmeter/module/module_v2_gerber.zip b/applications/plugins/lightmeter/module/module_v2_gerber.zip new file mode 100644 index 000000000..02887f4bf Binary files /dev/null and b/applications/plugins/lightmeter/module/module_v2_gerber.zip differ diff --git a/applications/plugins/metronome/README.md b/applications/plugins/metronome/README.md new file mode 100644 index 000000000..4b6cd3122 --- /dev/null +++ b/applications/plugins/metronome/README.md @@ -0,0 +1,23 @@ +# Metronome + +[Original link](https://github.com/panki27/Metronome) + +A metronome for the [Flipper Zero](https://flipperzero.one/) device. Goes along perfectly with my [BPM tapper](https://github.com/panki27/bpm-tapper). + +![screenshot](img/screenshot.png) + +## Features + +- BPM adjustable, fine and coarse (hold pressed) +- Selectable amount of beats per bar +- Selectable note length +- First beat is pronounced +- Progress indicator +- LED flashes accordingly +- 3 different settings: Beep, Vibrate, Silent (push Down to change) + +## Compiling + +``` +./fbt firmware_metronome +``` diff --git a/applications/plugins/metronome/application.fam b/applications/plugins/metronome/application.fam new file mode 100644 index 000000000..32588d06e --- /dev/null +++ b/applications/plugins/metronome/application.fam @@ -0,0 +1,15 @@ +App( + appid="Metronome", + name="Metronome", + apptype=FlipperAppType.EXTERNAL, + entry_point="metronome_app", + cdefines=["APP_METRONOME"], + requires=[ + "gui", + ], + fap_icon="metronome_icon.png", + fap_category="Music", + fap_icon_assets="images", + stack_size=2 * 1024, + order=20, +) diff --git a/applications/plugins/metronome/gui_extensions.c b/applications/plugins/metronome/gui_extensions.c new file mode 100644 index 000000000..458eb137b --- /dev/null +++ b/applications/plugins/metronome/gui_extensions.c @@ -0,0 +1,56 @@ +#include +#include +#include + +//lib can only do bottom left/right +void elements_button_top_left(Canvas* canvas, const char* str) { + const uint8_t button_height = 12; + const uint8_t vertical_offset = 3; + const uint8_t horizontal_offset = 3; + const uint8_t string_width = canvas_string_width(canvas, str); + const Icon* icon = &I_ButtonUp_7x4; + const uint8_t icon_h_offset = 3; + const uint8_t icon_width_with_offset = icon->width + icon_h_offset; + const uint8_t icon_v_offset = icon->height + vertical_offset; + const uint8_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset; + + const uint8_t x = 0; + const uint8_t y = 0 + button_height; + + canvas_draw_box(canvas, x, y - button_height, button_width, button_height); + canvas_draw_line(canvas, x + button_width + 0, y - button_height, x + button_width + 0, y - 1); + canvas_draw_line(canvas, x + button_width + 1, y - button_height, x + button_width + 1, y - 2); + canvas_draw_line(canvas, x + button_width + 2, y - button_height, x + button_width + 2, y - 3); + + canvas_invert_color(canvas); + canvas_draw_icon(canvas, x + horizontal_offset, y - icon_v_offset, &I_ButtonUp_7x4); + canvas_draw_str( + canvas, x + horizontal_offset + icon_width_with_offset, y - vertical_offset, str); + canvas_invert_color(canvas); +} + +void elements_button_top_right(Canvas* canvas, const char* str) { + const uint8_t button_height = 12; + const uint8_t vertical_offset = 3; + const uint8_t horizontal_offset = 3; + const uint8_t string_width = canvas_string_width(canvas, str); + const Icon* icon = &I_ButtonUp_7x4; + const uint8_t icon_h_offset = 3; + const uint8_t icon_width_with_offset = icon->width + icon_h_offset; + const uint8_t icon_v_offset = icon->height + vertical_offset; + const uint8_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset; + + const uint8_t x = canvas_width(canvas); + const uint8_t y = 0 + button_height; + + canvas_draw_box(canvas, x - button_width, y - button_height, button_width, button_height); + canvas_draw_line(canvas, x - button_width - 1, y - button_height, x - button_width - 1, y - 1); + canvas_draw_line(canvas, x - button_width - 2, y - button_height, x - button_width - 2, y - 2); + canvas_draw_line(canvas, x - button_width - 3, y - button_height, x - button_width - 3, y - 3); + + canvas_invert_color(canvas); + canvas_draw_str(canvas, x - button_width + horizontal_offset, y - vertical_offset, str); + canvas_draw_icon( + canvas, x - horizontal_offset - icon->width, y - icon_v_offset, &I_ButtonUp_7x4); + canvas_invert_color(canvas); +} diff --git a/applications/plugins/metronome/gui_extensions.h b/applications/plugins/metronome/gui_extensions.h new file mode 100644 index 000000000..97df1952c --- /dev/null +++ b/applications/plugins/metronome/gui_extensions.h @@ -0,0 +1,3 @@ +void elements_button_top_right(Canvas* canvas, const char* str); + +void elements_button_top_left(Canvas* canvas, const char* str); diff --git a/applications/plugins/metronome/icons/ButtonUp_7x4.png b/applications/plugins/metronome/icons/ButtonUp_7x4.png new file mode 100644 index 000000000..1be79328b Binary files /dev/null and b/applications/plugins/metronome/icons/ButtonUp_7x4.png differ diff --git a/applications/plugins/metronome/images/ButtonUp_7x4.png b/applications/plugins/metronome/images/ButtonUp_7x4.png new file mode 100644 index 000000000..1be79328b Binary files /dev/null and b/applications/plugins/metronome/images/ButtonUp_7x4.png differ diff --git a/applications/plugins/metronome/img/screenshot.png b/applications/plugins/metronome/img/screenshot.png new file mode 100644 index 000000000..7b6916e81 Binary files /dev/null and b/applications/plugins/metronome/img/screenshot.png differ diff --git a/applications/plugins/metronome/img/wave_left_4x14.png b/applications/plugins/metronome/img/wave_left_4x14.png new file mode 100644 index 000000000..beb2a611d Binary files /dev/null and b/applications/plugins/metronome/img/wave_left_4x14.png differ diff --git a/applications/plugins/metronome/img/wave_right_4x14.png b/applications/plugins/metronome/img/wave_right_4x14.png new file mode 100644 index 000000000..af249ee5b Binary files /dev/null and b/applications/plugins/metronome/img/wave_right_4x14.png differ diff --git a/applications/plugins/metronome/metronome.c b/applications/plugins/metronome/metronome.c new file mode 100644 index 000000000..18f68ea42 --- /dev/null +++ b/applications/plugins/metronome/metronome.c @@ -0,0 +1,397 @@ +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "gui_extensions.h" + +#define BPM_STEP_SIZE_FINE 0.5d +#define BPM_STEP_SIZE_COARSE 10.0d +#define BPM_BOUNDARY_LOW 10.0d +#define BPM_BOUNDARY_HIGH 300.0d +#define BEEP_DELAY_MS 50 + +#define wave_bitmap_left_width 4 +#define wave_bitmap_left_height 14 +static uint8_t wave_bitmap_left_bits[] = + {0x08, 0x0C, 0x06, 0x06, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x06, 0x06, 0x0C, 0x08}; + +#define wave_bitmap_right_width 4 +#define wave_bitmap_right_height 14 +static uint8_t wave_bitmap_right_bits[] = + {0x01, 0x03, 0x06, 0x06, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x06, 0x06, 0x03, 0x01}; + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} PluginEvent; + +enum OutputMode { Loud, Vibro, Silent }; + +typedef struct { + double bpm; + bool playing; + int beats_per_bar; + int note_length; + int current_beat; + enum OutputMode output_mode; + FuriTimer* timer; + NotificationApp* notifications; +} MetronomeState; + +static void render_callback(Canvas* const canvas, void* ctx) { + const MetronomeState* metronome_state = acquire_mutex((ValueMutex*)ctx, 25); + if(metronome_state == NULL) { + return; + } + + FuriString* tempStr; + tempStr = furi_string_alloc(); + + canvas_draw_frame(canvas, 0, 0, 128, 64); + + canvas_set_font(canvas, FontPrimary); + + // draw bars/beat + furi_string_printf( + tempStr, "%d/%d", metronome_state->beats_per_bar, metronome_state->note_length); + canvas_draw_str_aligned( + canvas, 64, 8, AlignCenter, AlignCenter, furi_string_get_cstr(tempStr)); + furi_string_reset(tempStr); + + // draw BPM value + furi_string_printf(tempStr, "%.2f", metronome_state->bpm); + canvas_set_font(canvas, FontBigNumbers); + canvas_draw_str_aligned( + canvas, 64, 24, AlignCenter, AlignCenter, furi_string_get_cstr(tempStr)); + furi_string_reset(tempStr); + + // draw volume indicator + // always draw first waves + canvas_draw_xbm( + canvas, 20, 17, wave_bitmap_left_width, wave_bitmap_left_height, wave_bitmap_left_bits); + canvas_draw_xbm( + canvas, + canvas_width(canvas) - 20 - wave_bitmap_right_width, + 17, + wave_bitmap_right_width, + wave_bitmap_right_height, + wave_bitmap_right_bits); + if(metronome_state->output_mode < Silent) { + canvas_draw_xbm( + canvas, 16, 17, wave_bitmap_left_width, wave_bitmap_left_height, wave_bitmap_left_bits); + canvas_draw_xbm( + canvas, + canvas_width(canvas) - 16 - wave_bitmap_right_width, + 17, + wave_bitmap_right_width, + wave_bitmap_right_height, + wave_bitmap_right_bits); + } + if(metronome_state->output_mode < Vibro) { + canvas_draw_xbm( + canvas, 12, 17, wave_bitmap_left_width, wave_bitmap_left_height, wave_bitmap_left_bits); + canvas_draw_xbm( + canvas, + canvas_width(canvas) - 12 - wave_bitmap_right_width, + 17, + wave_bitmap_right_width, + wave_bitmap_right_height, + wave_bitmap_right_bits); + } + // draw button prompts + canvas_set_font(canvas, FontSecondary); + elements_button_left(canvas, "Slow"); + elements_button_right(canvas, "Fast"); + if(metronome_state->playing) { + elements_button_center(canvas, "Stop "); + } else { + elements_button_center(canvas, "Start"); + } + elements_button_top_left(canvas, "Push"); + elements_button_top_right(canvas, "Hold"); + + // draw progress bar + elements_progress_bar( + canvas, 8, 36, 112, (float)metronome_state->current_beat / metronome_state->beats_per_bar); + + // cleanup + furi_string_free(tempStr); + release_mutex((ValueMutex*)ctx, metronome_state); +} + +static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + PluginEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void timer_callback(void* ctx) { + // this is where we go BEEP! + MetronomeState* metronome_state = acquire_mutex((ValueMutex*)ctx, 25); + metronome_state->current_beat++; + if(metronome_state->current_beat > metronome_state->beats_per_bar) { + metronome_state->current_beat = 1; + } + if(metronome_state->current_beat == 1) { + // pronounced beat + notification_message(metronome_state->notifications, &sequence_set_only_red_255); + switch(metronome_state->output_mode) { + case Loud: + furi_hal_speaker_start(440.0f, 1.0f); + break; + case Vibro: + notification_message(metronome_state->notifications, &sequence_set_vibro_on); + break; + case Silent: + break; + default: + break; + } + } else { + // unpronounced beat + notification_message(metronome_state->notifications, &sequence_set_only_green_255); + switch(metronome_state->output_mode) { + case Loud: + furi_hal_speaker_start(220.0f, 1.0f); + break; + case Vibro: + notification_message(metronome_state->notifications, &sequence_set_vibro_on); + break; + case Silent: + break; + default: + break; + } + }; + + // this is a bit of a kludge... if we are on vibro and unpronounced, stop vibro after half the usual duration + switch(metronome_state->output_mode) { + case Loud: + furi_delay_ms(BEEP_DELAY_MS); + furi_hal_speaker_stop(); + break; + case Vibro: + if(metronome_state->current_beat == 1) { + furi_delay_ms(BEEP_DELAY_MS); + notification_message(metronome_state->notifications, &sequence_reset_vibro); + } else { + furi_delay_ms((int)BEEP_DELAY_MS / 2); + notification_message(metronome_state->notifications, &sequence_reset_vibro); + furi_delay_ms((int)BEEP_DELAY_MS / 2); + } + break; + case Silent: + break; + default: + break; + } + notification_message(metronome_state->notifications, &sequence_reset_rgb); + + release_mutex((ValueMutex*)ctx, metronome_state); +} + +static uint32_t state_to_sleep_ticks(MetronomeState* metronome_state) { + // calculate time between beeps + uint32_t tps = furi_kernel_get_tick_frequency(); + double multiplier = 4.0d / metronome_state->note_length; + double bps = (double)metronome_state->bpm / 60; + return (uint32_t)(round(tps / bps) - ((BEEP_DELAY_MS / 1000) * tps)) * multiplier; +} + +static void update_timer(MetronomeState* metronome_state) { + if(furi_timer_is_running(metronome_state->timer)) { + furi_timer_stop(metronome_state->timer); + furi_timer_start(metronome_state->timer, state_to_sleep_ticks(metronome_state)); + } +} + +static void increase_bpm(MetronomeState* metronome_state, double amount) { + metronome_state->bpm += amount; + if(metronome_state->bpm > (double)BPM_BOUNDARY_HIGH) { + metronome_state->bpm = BPM_BOUNDARY_HIGH; + } + update_timer(metronome_state); +} + +static void decrease_bpm(MetronomeState* metronome_state, double amount) { + metronome_state->bpm -= amount; + if(metronome_state->bpm < (double)BPM_BOUNDARY_LOW) { + metronome_state->bpm = BPM_BOUNDARY_LOW; + } + update_timer(metronome_state); +} + +static void cycle_beats_per_bar(MetronomeState* metronome_state) { + metronome_state->beats_per_bar++; + if(metronome_state->beats_per_bar > metronome_state->note_length) { + metronome_state->beats_per_bar = 1; + } +} + +static void cycle_note_length(MetronomeState* metronome_state) { + metronome_state->note_length *= 2; + if(metronome_state->note_length > 16) { + metronome_state->note_length = 2; + metronome_state->beats_per_bar = 1; + } + update_timer(metronome_state); +} + +static void cycle_output_mode(MetronomeState* metronome_state) { + metronome_state->output_mode++; + if(metronome_state->output_mode > Silent) { + metronome_state->output_mode = Loud; + } +} + +static void metronome_state_init(MetronomeState* const metronome_state) { + metronome_state->bpm = 120.0; + metronome_state->playing = false; + metronome_state->beats_per_bar = 4; + metronome_state->note_length = 4; + metronome_state->current_beat = 0; + metronome_state->output_mode = Loud; + metronome_state->notifications = furi_record_open(RECORD_NOTIFICATION); +} + +int32_t metronome_app() { + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); + + MetronomeState* metronome_state = malloc(sizeof(MetronomeState)); + metronome_state_init(metronome_state); + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, metronome_state, sizeof(MetronomeState))) { + FURI_LOG_E("Metronome", "cannot create mutex\r\n"); + free(metronome_state); + return 255; + } + + // Set system callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, render_callback, &state_mutex); + view_port_input_callback_set(view_port, input_callback, event_queue); + metronome_state->timer = furi_timer_alloc(timer_callback, FuriTimerTypePeriodic, &state_mutex); + + // Open GUI and register view_port + Gui* gui = furi_record_open("gui"); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + PluginEvent event; + for(bool processing = true; processing;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + + MetronomeState* metronome_state = (MetronomeState*)acquire_mutex_block(&state_mutex); + + if(event_status == FuriStatusOk) { + if(event.type == EventTypeKey) { + if(event.input.type == InputTypeShort) { + // push events + switch(event.input.key) { + case InputKeyUp: + cycle_beats_per_bar(metronome_state); + break; + case InputKeyDown: + cycle_output_mode(metronome_state); + break; + case InputKeyRight: + increase_bpm(metronome_state, BPM_STEP_SIZE_FINE); + break; + case InputKeyLeft: + decrease_bpm(metronome_state, BPM_STEP_SIZE_FINE); + break; + case InputKeyOk: + metronome_state->playing = !metronome_state->playing; + if(metronome_state->playing) { + furi_timer_start( + metronome_state->timer, state_to_sleep_ticks(metronome_state)); + } else { + furi_timer_stop(metronome_state->timer); + } + break; + case InputKeyBack: + processing = false; + break; + default: + break; + } + } else if(event.input.type == InputTypeLong) { + // hold events + switch(event.input.key) { + case InputKeyUp: + cycle_note_length(metronome_state); + break; + case InputKeyDown: + break; + case InputKeyRight: + increase_bpm(metronome_state, BPM_STEP_SIZE_COARSE); + break; + case InputKeyLeft: + decrease_bpm(metronome_state, BPM_STEP_SIZE_COARSE); + break; + case InputKeyOk: + break; + case InputKeyBack: + processing = false; + break; + default: + break; + } + } else if(event.input.type == InputTypeRepeat) { + // repeat events + switch(event.input.key) { + case InputKeyUp: + break; + case InputKeyDown: + break; + case InputKeyRight: + increase_bpm(metronome_state, BPM_STEP_SIZE_COARSE); + break; + case InputKeyLeft: + decrease_bpm(metronome_state, BPM_STEP_SIZE_COARSE); + break; + case InputKeyOk: + break; + case InputKeyBack: + processing = false; + break; + default: + break; + } + } + } + } else { + FURI_LOG_D("Metronome", "FuriMessageQueue: event timeout"); + // event timeout + } + + view_port_update(view_port); + release_mutex(&state_mutex, metronome_state); + } + + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close("gui"); + view_port_free(view_port); + furi_message_queue_free(event_queue); + delete_mutex(&state_mutex); + furi_timer_free(metronome_state->timer); + furi_record_close(RECORD_NOTIFICATION); + free(metronome_state); + + return 0; +} diff --git a/applications/plugins/metronome/metronome_icon.png b/applications/plugins/metronome/metronome_icon.png new file mode 100644 index 000000000..64d0ddbe9 Binary files /dev/null and b/applications/plugins/metronome/metronome_icon.png differ diff --git a/applications/plugins/nfc_magic/nfc_magic_i.h b/applications/plugins/nfc_magic/nfc_magic_i.h index 78f7256ac..a6f9352b4 100644 --- a/applications/plugins/nfc_magic/nfc_magic_i.h +++ b/applications/plugins/nfc_magic/nfc_magic_i.h @@ -27,6 +27,8 @@ #include #include "NFC_Magic_icons.h" +#define NFC_APP_FOLDER ANY_PATH("nfc") + enum NfcMagicCustomEvent { // Reserve first 100 events for button types and indexes, starting from 0 NfcMagicCustomEventReserved = 100, diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_file_select.c b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_file_select.c index a19237ed4..825ceacea 100644 --- a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_file_select.c +++ b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_file_select.c @@ -8,6 +8,7 @@ static bool nfc_magic_scene_file_select_is_file_suitable(NfcDevice* nfc_dev) { void nfc_magic_scene_file_select_on_enter(void* context) { NfcMagic* nfc_magic = context; + furi_string_printf(nfc_magic->nfc_dev->folder, "%s", NFC_APP_FOLDER); // Process file_select return nfc_device_set_loading_callback(nfc_magic->nfc_dev, nfc_magic_show_loading_popup, nfc_magic); diff --git a/applications/plugins/nrf24scan/LICENSE b/applications/plugins/nrf24scan/LICENSE new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/applications/plugins/nrf24scan/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/applications/plugins/nrf24scan/README.md b/applications/plugins/nrf24scan/README.md new file mode 100644 index 000000000..bc4838174 --- /dev/null +++ b/applications/plugins/nrf24scan/README.md @@ -0,0 +1,104 @@ +# NRF24 scanner with logging and resend ability for Flipper Zero + +An [NRF24](https://www.sparkfun.com/datasheets/Components/SMD/nRF24L01Pluss_Preliminary_Product_Specification_v1_0.pdf) driver for the [Flipper Zero](https://flipperzero.one/) device. The NRF24 is a popular line of 2.4GHz radio transceivers from Nordic Semiconductors.
+NRF24L01+ Enhanced ShockBurst packet decoder example using Python: [nrf24_packet_decoder.py](https://raw.githubusercontent.com/vad7/nrf24scan/master/nrf24_packet_decoder.py)
+
+Flipper Zero FAP file: [Nrf24_Scanner.fap](https://raw.githubusercontent.com/vad7/nrf24scan/master/Nrf24_Scanner.fap) +

+___________________________________________________________________________ +
+Приложение для Flipper Zero, читающее эфир на радиомодулях nRF24L01.
+Выбор пукта меню - стрелки вверх/вниз, стрелки влево/вправо либо изменют настройки либо управляют видом списка, кнопка ОК либо выбирает режим (короткое нажатие), либо выполняет дополнительное действие (длительное нажатие).

+
+По умолчанию при запуске включается режим поиска (sniff) - ищутся все валидные пакеты с корректным заголовком и CRC.
+Размер CRC и тип пакета (Enhanced ShockBurst или нет) задается. CRC может быть или 1 или 2 байта.
+Так как пакеты читаются в RAW формате, то длина полезной нагрузки не может быть больше 23 байт, пакеты с большей длинной не будут пойманы.
+В настройках задается минимальный размер нагрузки (payload)
+После принятия, пакет сдвигается побитно и валидируется. Побитный сдвиг сильно увеличивает вероятность отлова пакета, но так же увеличивается количество мусорных пакетов.
+Количество уникальных адресов запоминается (просмотр - стрелка вниз в режиме просмотра адресов)
+После поиска можно переключиться в режим сканирования по найденным адресам или сканировать адрес конкретного пакета - нажать ОК в режиме просмотра адресов
+Изменение режима sniff/scan - стрелками на пункте Scan.

+Режим сканирования (scan) - просто чтение пакетов по заданным в настройках мак адресам и виду пакета - ESB/DPL.
+На начальном экране в режиме чтения можно загрузить файл настроек (по умолчанию загружается settings.txt из папки nrf24_scanner на SD карте).
+В файле настройке задаются адреса (максимум 6) в шестнадцатеричном виде (старший байт - первый), длина адреса вычисляется по P0.
+Остальные настройки можно поменять интерактивно
+Настройки сохраняются длительным нажатием на ОК.

+Описание настроек:
+Ch - номер канала.
+Rate - скорость передачи данных
+Next Ch time - через сколько секунд будет увеличен номер канала
+Log - выбор режима авто сохраннения в файлы log-xx.txt. Yes - сохранять в новый файл при заполнении буфера в 99 записей, Append - добавлять в последний файл, Clear - только очистка буфера

+В пунктах Ch, Rate, Next при нажатии OK меняются параметры связи:
+ESB - Enhanced ShockBurst (включена автоотправка подтверждения получения пакета, работающий приемник тоже попытается это сделать, возможны коллизии)
+DPL - Динамический пакет
+CRC1/2 - Размер CRC в байтах
+Payload - размер пакета в байтах
+
+Просмотр принятых пакетов

+ +
+В пункте "Start scan/sniff" можно выбрать стрелками сканировать и смотреть или просто смотреть (view).
+Если в файле настройки было несколько адресов, то первая цифра - номер канала (pipe) от 0 до 5.
+Стрелки - перемещение по списку и горизонтальное скролирование
+Долгий OK - отправка пакета
+OK - вход в режим просмотра адресов и включения декодирования заголовка ESB пакета и CRC.
+При декодировании заголовка (PCF) - первые 2 цифры - длина пакета в hex или 33, если длина пакета фиксирована
+3-я цифра - PID (2bit) << 1 + флаг NO_ACK
+Если включен режим декодирования CRC, то по всему пакету ищется подходящая CRC и подчеркивается в списке, а так же вместо ":" выводится "=" после номера записи в буфере
+
+ +
+ +
+ +
+
+_________________________________________________________________________________ +
+
+Settings file (default addr.txt) format:
+ +Rate: 0/1/2 - rate in Mbps (=0.25/1/2)
+Ch: 0..125 - default channel
+ESB: 0/1 (1 - Enhanced ShockBurst)
+DPL: 0/1 (1 - Dynamic Payload Length)
+CRC: 0/1/2 (CRC length)
+Payload: 1..32 (bytes)
+P0: address pipe #0 in hex (max 5 bytes, LSB last)
+P1: address pipe #1 in hex (max 5 bytes, LSB last)
+P2: address pipe #2, LSB in hex (1 byte)
+P3: address pipe #3, LSB in hex (1 byte)
+P4: address pipe #4, LSB in hex (1 byte)
+P5: address pipe #5, LSB in hex (1 byte)
+captured data in raw format, first byte = address # 0..5, Payload len if DPL
+... up to MAX_LOG_RECORDS-1
+
+In the list of the received:
+Press OK - send the packet,
+Long press OK - view addresses.
+
+Decode the Packet Control Field and check CRC (long press OK in the list and then press '<' / '>').
+ESB (Enhanced Shockburst) option must be turned off. +Press '>' to decode CRC.
+1 - pipe #
+2 - Payload length (for valide packet must be 1..20 or 33 hex)
+3 - PID (2 bit) + NO_ACK (1 bit)
+
+
+
+## PinOut from from NoComp/Frog + + +# NRF24 pinout by UberGuidoZ +2/A7 on FZ goes to MOSI/6 on nrf24l01
+3/A6 on FZ goes to MISO/7 on nrf24l01
+4/A4 on FZ goes to CSN/4 on nrf24l01
+5/B3 on FZ goes to SCK/5 on nrf24l01
+6/B2 on FZ goes to CE/3 on nrf24l01
+8/GND on FZ goes to GND/1 on nrf24l01
+9/3V3 on FZ goes to VCC/2 on nrf24l01
+IRQ/8 is left disconnected on nrf24l01 +![NRF_Pins](https://user-images.githubusercontent.com/57457139/178093717-39effd5c-ebe2-4253-b13c-70517d7902f9.png) +If the nRF module is acting a bit flakey, try adding a capacitor to the vcc/gnd lines! I've not tried the Plus model so it may have a bigger need for a cap. Otherwise, I haven't had any major issues. Anything from a 3.3 uF to 10 uF should do. (Watch your positive/negative placement! Negative to ground.) I learned if you wanna get fancy, include a 0.1 uF cap in parallel. The 3.3 uF to 10 uF will respond to slow freq changes while the 0.1 uF will respond to the high freq switching spikes that the larger one cannot. That said, a single 10 uF will likely suffice for the Mousejack attack. ¯\\\_(ツ)_/¯ +![NRF_Capacitor](https://user-images.githubusercontent.com/57457139/178169959-d030f9a6-d2ac-46af-af8b-470ff092c8a7.jpg) + diff --git a/applications/plugins/nrf24scan/Screenshot-1.png b/applications/plugins/nrf24scan/Screenshot-1.png new file mode 100644 index 000000000..61ca892d8 Binary files /dev/null and b/applications/plugins/nrf24scan/Screenshot-1.png differ diff --git a/applications/plugins/nrf24scan/Screenshot-2.png b/applications/plugins/nrf24scan/Screenshot-2.png new file mode 100644 index 000000000..0e3ebf5b2 Binary files /dev/null and b/applications/plugins/nrf24scan/Screenshot-2.png differ diff --git a/applications/plugins/nrf24scan/Screenshot-3.png b/applications/plugins/nrf24scan/Screenshot-3.png new file mode 100644 index 000000000..651052350 Binary files /dev/null and b/applications/plugins/nrf24scan/Screenshot-3.png differ diff --git a/applications/plugins/nrf24scan/Screenshot-4.png b/applications/plugins/nrf24scan/Screenshot-4.png new file mode 100644 index 000000000..6351cdd3f Binary files /dev/null and b/applications/plugins/nrf24scan/Screenshot-4.png differ diff --git a/applications/plugins/nrf24scan/Screenshot-5.png b/applications/plugins/nrf24scan/Screenshot-5.png new file mode 100644 index 000000000..d9e3173a2 Binary files /dev/null and b/applications/plugins/nrf24scan/Screenshot-5.png differ diff --git a/applications/plugins/nrf24scan/application.fam b/applications/plugins/nrf24scan/application.fam new file mode 100644 index 000000000..c346112e2 --- /dev/null +++ b/applications/plugins/nrf24scan/application.fam @@ -0,0 +1,20 @@ +App( + appid="Nrf24_Scanner", + name="[NRF24] Scanner", + apptype=FlipperAppType.EXTERNAL, + entry_point="nrf24scan_app", + cdefines=["APP_NRF24SCAN"], + requires=["gui"], + stack_size=2 * 1024, + order=60, + fap_icon="nrf24scan_10px.png", + fap_category="GPIO", + fap_private_libs=[ + Lib( + name="nrf24", + sources=[ + "nrf24.c", + ], + ), + ], +) diff --git a/applications/plugins/nrf24scan/lib/nrf24/nrf24.c b/applications/plugins/nrf24scan/lib/nrf24/nrf24.c new file mode 100644 index 000000000..83f0613a1 --- /dev/null +++ b/applications/plugins/nrf24scan/lib/nrf24/nrf24.c @@ -0,0 +1,533 @@ +// Modified by vad7, 25.11.2022 +// +#include "nrf24.h" +#include +#include +#include +#include +#include + +void nrf24_init() { + furi_hal_spi_bus_handle_init(nrf24_HANDLE); + furi_hal_spi_acquire(nrf24_HANDLE); + furi_hal_gpio_init(nrf24_CE_PIN, GpioModeOutputPushPull, GpioPullUp, GpioSpeedVeryHigh); + furi_hal_gpio_write(nrf24_CE_PIN, false); +} + +void nrf24_deinit() { + furi_hal_spi_release(nrf24_HANDLE); + furi_hal_spi_bus_handle_deinit(nrf24_HANDLE); + furi_hal_gpio_write(nrf24_CE_PIN, false); + furi_hal_gpio_init(nrf24_CE_PIN, GpioModeAnalog, GpioPullNo, GpioSpeedLow); +} + +void nrf24_spi_trx( + FuriHalSpiBusHandle* handle, + uint8_t* tx, + uint8_t* rx, + uint8_t size, + uint32_t timeout) { + UNUSED(timeout); + furi_hal_gpio_write(handle->cs, false); + furi_hal_spi_bus_trx(handle, tx, rx, size, nrf24_TIMEOUT); + furi_hal_gpio_write(handle->cs, true); +} + +uint8_t nrf24_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data) { + uint8_t tx[2] = {W_REGISTER | (REGISTER_MASK & reg), data}; + uint8_t rx[2] = {0}; + nrf24_spi_trx(handle, tx, rx, 2, nrf24_TIMEOUT); + //FURI_LOG_D("NRF_WR", " #%02X=%02X", reg, data); + return rx[0]; +} + +uint8_t nrf24_write_buf_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size) { + uint8_t tx[size + 1]; + uint8_t rx[size + 1]; + memset(rx, 0, size + 1); + tx[0] = W_REGISTER | (REGISTER_MASK & reg); + memcpy(&tx[1], data, size); + nrf24_spi_trx(handle, tx, rx, size + 1, nrf24_TIMEOUT); + //FURI_LOG_D("NRF_WR", " #%02X(%02X)=0x%02X%02X%02X%02X%02X", reg, size, data[0], data[1], data[2], data[3], data[4] ); + return rx[0]; +} + +uint8_t nrf24_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size) { + uint8_t tx[size + 1]; + uint8_t rx[size + 1]; + memset(rx, 0, size + 1); + tx[0] = R_REGISTER | (REGISTER_MASK & reg); + memset(&tx[1], 0, size); + nrf24_spi_trx(handle, tx, rx, size + 1, nrf24_TIMEOUT); + memcpy(data, &rx[1], size); + return rx[0]; +} + +uint8_t nrf24_flush_rx(FuriHalSpiBusHandle* handle) { + uint8_t tx[] = {FLUSH_RX}; + uint8_t rx[] = {0}; + nrf24_spi_trx(handle, tx, rx, 1, nrf24_TIMEOUT); + return rx[0]; +} + +uint8_t nrf24_flush_tx(FuriHalSpiBusHandle* handle) { + uint8_t tx[] = {FLUSH_TX}; + uint8_t rx[] = {0}; + nrf24_spi_trx(handle, tx, rx, 1, nrf24_TIMEOUT); + return rx[0]; +} + +uint8_t nrf24_get_maclen(FuriHalSpiBusHandle* handle) { + uint8_t maclen; + nrf24_read_reg(handle, REG_SETUP_AW, &maclen, 1); + maclen &= 3; + return maclen + 2; +} + +uint8_t nrf24_set_maclen(FuriHalSpiBusHandle* handle, uint8_t maclen) { + assert(maclen > 1 && maclen < 6); + uint8_t status = 0; + status = nrf24_write_reg(handle, REG_SETUP_AW, maclen - 2); + return status; +} + +uint8_t nrf24_status(FuriHalSpiBusHandle* handle) { + uint8_t status; + uint8_t tx[] = {R_REGISTER | (REGISTER_MASK & REG_STATUS)}; + nrf24_spi_trx(handle, tx, &status, 1, nrf24_TIMEOUT); + return status; +} + +uint32_t nrf24_get_rate(FuriHalSpiBusHandle* handle) { + uint8_t setup = 0; + uint32_t rate = 0; + nrf24_read_reg(handle, REG_RF_SETUP, &setup, 1); + setup &= 0x28; + if(setup == 0x20) + rate = 250000; // 250kbps + else if(setup == 0x08) + rate = 2000000; // 2Mbps + else if(setup == 0x00) + rate = 1000000; // 1Mbps + + return rate; +} + +uint8_t nrf24_set_rate(FuriHalSpiBusHandle* handle, uint32_t rate) { + uint8_t r6 = 0; + uint8_t status = 0; + if(!rate) rate = 2000000; + + nrf24_read_reg(handle, REG_RF_SETUP, &r6, 1); // RF_SETUP register + r6 = r6 & (~0x28); // Clear rate fields. + if(rate == 2000000) + r6 = r6 | 0x08; + else if(rate == 1000000) + r6 = r6; + else if(rate == 250000) + r6 = r6 | 0x20; + + status = nrf24_write_reg(handle, REG_RF_SETUP, r6); // Write new rate. + return status; +} + +uint8_t nrf24_get_chan(FuriHalSpiBusHandle* handle) { + uint8_t channel = 0; + nrf24_read_reg(handle, REG_RF_CH, &channel, 1); + return channel; +} + +uint8_t nrf24_set_chan(FuriHalSpiBusHandle* handle, uint8_t chan) { + uint8_t status; + status = nrf24_write_reg(handle, REG_RF_CH, chan); + return status; +} + +uint8_t nrf24_get_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac) { + uint8_t size = 0; + uint8_t status = 0; + size = nrf24_get_maclen(handle); + status = nrf24_read_reg(handle, REG_RX_ADDR_P0, mac, size); + return status; +} + +uint8_t nrf24_set_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size) { + uint8_t status = 0; + uint8_t clearmac[] = {0, 0, 0, 0, 0}; + nrf24_set_maclen(handle, size); + nrf24_write_buf_reg(handle, REG_RX_ADDR_P0, clearmac, 5); + status = nrf24_write_buf_reg(handle, REG_RX_ADDR_P0, mac, size); + return status; +} + +uint8_t nrf24_get_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac) { + uint8_t size = 0; + uint8_t status = 0; + size = nrf24_get_maclen(handle); + status = nrf24_read_reg(handle, REG_TX_ADDR, mac, size); + return status; +} + +uint8_t nrf24_set_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size) { + uint8_t status = 0; + uint8_t clearmac[] = {0, 0, 0, 0, 0}; + nrf24_set_maclen(handle, size); + nrf24_write_buf_reg(handle, REG_TX_ADDR, clearmac, 5); + status = nrf24_write_buf_reg(handle, REG_TX_ADDR, mac, size); + return status; +} + +uint8_t nrf24_get_packetlen(FuriHalSpiBusHandle* handle, uint8_t pipe) { + uint8_t len = 0; + if(pipe > 5) pipe = 0; + nrf24_read_reg(handle, RX_PW_P0 + pipe, &len, 1); + return len; +} + +uint8_t nrf24_set_packetlen(FuriHalSpiBusHandle* handle, uint8_t len) { + uint8_t status = 0; + status = nrf24_write_reg(handle, RX_PW_P0, len); + return status; +} + +uint8_t nrf24_rxpacket(FuriHalSpiBusHandle* handle, uint8_t* packet, uint8_t* ret_packetsize, uint8_t packet_size) { + uint8_t status = 0; + uint8_t tx_cmd[33] = {0}; // 32 max payload size + 1 for command + uint8_t tmp_packet[33] = {0}; + + status = nrf24_status(handle); + if(!(status & RX_DR)) { + tx_cmd[0] = R_REGISTER | (REGISTER_MASK & REG_FIFO_STATUS); + nrf24_spi_trx(handle, tx_cmd, tmp_packet, 2, nrf24_TIMEOUT); + if((tmp_packet[1] & 1) == 0) status |= RX_DR; // packet in FIFO buffer + } + if(status & RX_DR) { + if(packet_size == 1) + packet_size = nrf24_get_packetlen(handle, (status >> 1) & 7); + else if(packet_size == 0){ + tx_cmd[0] = R_RX_PL_WID; tx_cmd[1] = 0; + nrf24_spi_trx(handle, tx_cmd, tmp_packet, 2, nrf24_TIMEOUT); + packet_size = tmp_packet[1]; + } + if(packet_size > 32 || packet_size == 0) packet_size = 32; + tx_cmd[0] = R_RX_PAYLOAD; tx_cmd[1] = 0; + nrf24_spi_trx(handle, tx_cmd, tmp_packet, packet_size + 1, nrf24_TIMEOUT); + memcpy(packet, &tmp_packet[1], packet_size); + nrf24_write_reg(handle, REG_STATUS, RX_DR); // clear RX_DR + } else if(status & (TX_DS | MAX_RT)) { // MAX_RT, TX_DS + nrf24_write_reg(handle, REG_STATUS, (TX_DS | MAX_RT)); // clear RX_DR, MAX_RT. + } + + *ret_packetsize = packet_size; + return status; +} + +// Return 0 when error +uint8_t nrf24_txpacket(FuriHalSpiBusHandle* handle, uint8_t* payload, uint8_t size, bool ack) { + uint8_t status = 0; + uint8_t tx[size + 1]; + uint8_t rx[size + 1]; + memset(tx, 0, size + 1); + memset(rx, 0, size + 1); + + if(!ack) + tx[0] = W_TX_PAYLOAD_NOACK; + else + tx[0] = W_TX_PAYLOAD; + + memcpy(&tx[1], payload, size); + nrf24_spi_trx(handle, tx, rx, size + 1, nrf24_TIMEOUT); + nrf24_set_tx_mode(handle); + + uint32_t start_time = furi_get_tick(); + while(!(status & (TX_DS | MAX_RT)) && furi_get_tick() - start_time < 2000UL) status = nrf24_status(handle); + + if(status & MAX_RT) nrf24_flush_tx(handle); + + nrf24_set_idle(handle); + nrf24_write_reg(handle, REG_STATUS, TX_DS | MAX_RT); + return status & TX_DS; +} + +uint8_t nrf24_power_up(FuriHalSpiBusHandle* handle) { + uint8_t status = 0; + uint8_t cfg = 0; + nrf24_read_reg(handle, REG_CONFIG, &cfg, 1); + cfg = cfg | 2; + status = nrf24_write_reg(handle, REG_CONFIG, cfg); + furi_delay_ms(1000); + return status; +} + +uint8_t nrf24_set_idle(FuriHalSpiBusHandle* handle) { + uint8_t status = 0; + uint8_t cfg = 0; + nrf24_read_reg(handle, REG_CONFIG, &cfg, 1); + cfg &= 0xfc; // clear bottom two bits to power down the radio + status = nrf24_write_reg(handle, REG_CONFIG, cfg); + //nr204_write_reg(handle, REG_EN_RXADDR, 0x0); + furi_hal_gpio_write(nrf24_CE_PIN, false); + return status; +} + +uint8_t nrf24_set_rx_mode(FuriHalSpiBusHandle* handle) { + uint8_t status = 0; + uint8_t cfg = 0; + //status = nrf24_write_reg(handle, REG_CONFIG, 0x0F); // enable 2-byte CRC, PWR_UP, and PRIM_RX + nrf24_read_reg(handle, REG_CONFIG, &cfg, 1); + cfg |= 0x03; // PWR_UP, and PRIM_RX + status = nrf24_write_reg(handle, REG_CONFIG, cfg); + //nr204_write_reg(REG_EN_RXADDR, 0x03) // Set RX Pipe 0 and 1 + furi_hal_gpio_write(nrf24_CE_PIN, true); + furi_delay_ms(2); + return status; +} + +uint8_t nrf24_set_tx_mode(FuriHalSpiBusHandle* handle) { + uint8_t status = 0; + uint8_t cfg = 0; + furi_hal_gpio_write(nrf24_CE_PIN, false); + nrf24_write_reg(handle, REG_STATUS, 0x30); + //status = nrf24_write_reg(handle, REG_CONFIG, 0x0E); // enable 2-byte CRC, PWR_UP + nrf24_read_reg(handle, REG_CONFIG, &cfg, 1); + cfg &= 0xfe; // disable PRIM_RX + cfg |= 0x02; // PWR_UP + status = nrf24_write_reg(handle, REG_CONFIG, cfg); + furi_hal_gpio_write(nrf24_CE_PIN, true); + furi_delay_ms(2); + return status; +} + +void nrf24_configure( + FuriHalSpiBusHandle* handle, + uint8_t rate, + uint8_t* srcmac, + uint8_t* dstmac, + uint8_t maclen, + uint8_t channel, + bool noack, + bool disable_aa) { + assert(channel <= 125); + assert(rate == 1 || rate == 2); + if(rate == 2) + rate = 8; // 2Mbps + else + rate = 0; // 1Mbps + + nrf24_write_reg(handle, REG_CONFIG, 0x00); // Stop nRF + nrf24_set_idle(handle); + nrf24_write_reg(handle, REG_STATUS, 0x70); // clear interrupts + if(disable_aa) + nrf24_write_reg(handle, REG_EN_AA, 0x00); // Disable Shockburst + else + nrf24_write_reg(handle, REG_EN_AA, 0x1F); // Enable Shockburst + + nrf24_write_reg(handle, REG_DYNPD, 0x3F); // enable dynamic payload length on all pipes + if(noack) + nrf24_write_reg(handle, REG_FEATURE, 0x05); // disable payload-with-ack, enable noack + else { + nrf24_write_reg(handle, REG_CONFIG, 0x0C); // 2 byte CRC + nrf24_write_reg(handle, REG_FEATURE, 0x07); // enable dyn payload and ack + nrf24_write_reg( + handle, REG_SETUP_RETR, 0x1f); // 15 retries for AA, 500us auto retransmit delay + } + + nrf24_set_idle(handle); + nrf24_flush_rx(handle); + nrf24_flush_tx(handle); + + if(maclen) nrf24_set_maclen(handle, maclen); + if(srcmac) nrf24_set_src_mac(handle, srcmac, maclen); + if(dstmac) nrf24_set_dst_mac(handle, dstmac, maclen); + + nrf24_write_reg(handle, REG_RF_CH, channel); + nrf24_write_reg(handle, REG_RF_SETUP, rate); + furi_delay_ms(200); +} + +void nrf24_init_promisc_mode(FuriHalSpiBusHandle* handle, uint8_t channel, uint8_t rate) { + //uint8_t preamble[] = {0x55, 0x00}; // little endian + uint8_t preamble[] = {0xAA, 0x00}; // little endian + //uint8_t preamble[] = {0x00, 0x55}; // little endian + //uint8_t preamble[] = {0x00, 0xAA}; // little endian + nrf24_write_reg(handle, REG_CONFIG, 0x00); // Stop nRF + nrf24_write_reg(handle, REG_STATUS, 0x70); // clear interrupts + nrf24_write_reg(handle, REG_DYNPD, 0x0); // disable shockburst + nrf24_write_reg(handle, REG_EN_AA, 0x00); // Disable Shockburst + nrf24_write_reg(handle, REG_FEATURE, 0x05); // disable payload-with-ack, enable noack + nrf24_set_maclen(handle, 2); // shortest address + nrf24_set_src_mac(handle, preamble, 2); // set src mac to preamble bits to catch everything + nrf24_set_packetlen(handle, 32); // set max packet length + nrf24_set_idle(handle); + nrf24_flush_rx(handle); + nrf24_flush_tx(handle); + nrf24_write_reg(handle, REG_RF_CH, channel); + nrf24_write_reg(handle, REG_RF_SETUP, rate); + + // prime for RX, no checksum + nrf24_write_reg(handle, REG_CONFIG, 0x03); // PWR_UP and PRIM_RX, disable AA and CRC + furi_hal_gpio_write(nrf24_CE_PIN, true); + furi_delay_ms(100); +} + +void hexlify(uint8_t* in, uint8_t size, char* out) { + memset(out, 0, size * 2); + for(int i = 0; i < size; i++) + snprintf(out + strlen(out), sizeof(out + strlen(out)), "%02X", in[i]); +} + +uint64_t bytes_to_int64(uint8_t* bytes, uint8_t size, bool bigendian) { + uint64_t ret = 0; + for(int i = 0; i < size; i++) + if(bigendian) + ret |= bytes[i] << ((size - 1 - i) * 8); + else + ret |= bytes[i] << (i * 8); + + return ret; +} + +void int64_to_bytes(uint64_t val, uint8_t* out, bool bigendian) { + for(int i = 0; i < 8; i++) { + if(bigendian) + out[i] = (val >> ((7 - i) * 8)) & 0xff; + else + out[i] = (val >> (i * 8)) & 0xff; + } +} + +uint32_t bytes_to_int32(uint8_t* bytes, bool bigendian) { + uint32_t ret = 0; + for(int i = 0; i < 4; i++) + if(bigendian) + ret |= bytes[i] << ((3 - i) * 8); + else + ret |= bytes[i] << (i * 8); + + return ret; +} + +void int32_to_bytes(uint32_t val, uint8_t* out, bool bigendian) { + for(int i = 0; i < 4; i++) { + if(bigendian) + out[i] = (val >> ((3 - i) * 8)) & 0xff; + else + out[i] = (val >> (i * 8)) & 0xff; + } +} + +uint64_t bytes_to_int16(uint8_t* bytes, bool bigendian) { + uint16_t ret = 0; + for(int i = 0; i < 2; i++) + if(bigendian) + ret |= bytes[i] << ((1 - i) * 8); + else + ret |= bytes[i] << (i * 8); + + return ret; +} + +void int16_to_bytes(uint16_t val, uint8_t* out, bool bigendian) { + for(int i = 0; i < 2; i++) { + if(bigendian) + out[i] = (val >> ((1 - i) * 8)) & 0xff; + else + out[i] = (val >> (i * 8)) & 0xff; + } +} + +// handle iffyness with preamble processing sometimes being a bit (literally) off +void alt_address_old(uint8_t* packet, uint8_t* altaddr) { + uint8_t macmess_hi_b[4]; + uint8_t macmess_lo_b[2]; + uint32_t macmess_hi; + uint16_t macmess_lo; + uint8_t preserved; + + // get first 6 bytes into 32-bit and 16-bit variables + memcpy(macmess_hi_b, packet, 4); + memcpy(macmess_lo_b, packet + 4, 2); + + macmess_hi = bytes_to_int32(macmess_hi_b, true); + + //preserve least 7 bits from hi that will be shifted down to lo + preserved = macmess_hi & 0x7f; + macmess_hi >>= 7; + + macmess_lo = bytes_to_int16(macmess_lo_b, true); + macmess_lo >>= 7; + macmess_lo = (preserved << 9) | macmess_lo; + int32_to_bytes(macmess_hi, macmess_hi_b, true); + int16_to_bytes(macmess_lo, macmess_lo_b, true); + memcpy(altaddr, &macmess_hi_b[1], 3); + memcpy(altaddr + 3, macmess_lo_b, 2); +} + +bool validate_address(uint8_t* addr) { + uint8_t bad[][3] = {{0x55, 0x55}, {0xAA, 0xAA}, {0x00, 0x00}, {0xFF, 0xFF}}; + for(int i = 0; i < 4; i++) + for(int j = 0; j < 2; j++) + if(!memcmp(addr + j * 2, bad[i], 2)) return false; + + return true; +} + +bool nrf24_sniff_address(FuriHalSpiBusHandle* handle, uint8_t maclen, uint8_t* address) { + bool found = false; + uint8_t packet[32] = {0}; + uint8_t packetsize; + //char printit[65]; + uint8_t status = 0; + status = nrf24_rxpacket(handle, packet, &packetsize, true); + if(status & 0x40) { + if(validate_address(packet)) { + for(int i = 0; i < maclen; i++) address[i] = packet[maclen - 1 - i]; + + /* + alt_address(packet, packet); + + for(i = 0; i < maclen; i++) + address[i + 5] = packet[maclen - 1 - i]; + */ + + //memcpy(address, packet, maclen); + //hexlify(packet, packetsize, printit); + found = true; + } + } + + return found; +} + +uint8_t nrf24_find_channel( + FuriHalSpiBusHandle* handle, + uint8_t* srcmac, + uint8_t* dstmac, + uint8_t maclen, + uint8_t rate, + uint8_t min_channel, + uint8_t max_channel, + bool autoinit) { + uint8_t ping_packet[] = {0x0f, 0x0f, 0x0f, 0x0f}; // this can be anything, we just need an ack + uint8_t ch = max_channel + 1; // means fail + nrf24_configure(handle, rate, srcmac, dstmac, maclen, 2, false, false); + for(ch = min_channel; ch <= max_channel + 1; ch++) { + nrf24_write_reg(handle, REG_RF_CH, ch); + if(nrf24_txpacket(handle, ping_packet, 4, true)) break; + } + + if(autoinit) { + FURI_LOG_D("nrf24", "initializing radio for channel %d", ch); + nrf24_configure(handle, rate, srcmac, dstmac, maclen, ch, false, false); + return ch; + } + + return ch; +} + +uint8_t nrf24_set_mac(uint8_t mac_addr, uint8_t *mac, uint8_t mlen) +{ + uint8_t addr[5]; + for(int i = 0; i < mlen; i++) addr[i] = mac[mlen - i - 1]; + return nrf24_write_buf_reg(nrf24_HANDLE, mac_addr, addr, mlen); +} \ No newline at end of file diff --git a/applications/plugins/nrf24scan/lib/nrf24/nrf24.h b/applications/plugins/nrf24scan/lib/nrf24/nrf24.h new file mode 100644 index 000000000..cd994dc40 --- /dev/null +++ b/applications/plugins/nrf24scan/lib/nrf24/nrf24.h @@ -0,0 +1,381 @@ +#pragma once +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define R_REGISTER 0x00 +#define W_REGISTER 0x20 +#define REGISTER_MASK 0x1F +#define ACTIVATE 0x50 +#define R_RX_PL_WID 0x60 +#define R_RX_PAYLOAD 0x61 +#define W_TX_PAYLOAD 0xA0 +#define W_TX_PAYLOAD_NOACK 0xB0 +#define W_ACK_PAYLOAD 0xA8 +#define FLUSH_TX 0xE1 +#define FLUSH_RX 0xE2 +#define REUSE_TX_PL 0xE3 +#define RF24_NOP 0xFF + +#define REG_CONFIG 0x00 +#define REG_EN_AA 0x01 +#define REG_EN_RXADDR 0x02 +#define REG_SETUP_AW 0x03 +#define REG_SETUP_RETR 0x04 +#define REG_DYNPD 0x1C +#define REG_FEATURE 0x1D +#define REG_RF_SETUP 0x06 +#define REG_STATUS 0x07 +#define REG_RX_ADDR_P0 0x0A +#define REG_RX_ADDR_P1 0x0B +#define REG_RX_ADDR_P2 0x0C +#define REG_RX_ADDR_P3 0x0D +#define REG_RX_ADDR_P4 0x0E +#define REG_RX_ADDR_P5 0x0F +#define REG_RF_CH 0x05 +#define REG_TX_ADDR 0x10 +#define REG_FIFO_STATUS 0x17 + +#define RX_PW_P0 0x11 +#define RX_PW_P1 0x12 +#define RX_PW_P2 0x13 +#define RX_PW_P3 0x14 +#define RX_PW_P4 0x15 +#define RX_PW_P5 0x16 +#define RX_DR 0x40 +#define TX_DS 0x20 +#define MAX_RT 0x10 + +#define nrf24_TIMEOUT 500 +#define nrf24_CE_PIN &gpio_ext_pb2 +#define nrf24_HANDLE &furi_hal_spi_bus_handle_external + +/* Low level API */ + +/** Write device register + * + * @param handle - pointer to FuriHalSpiHandle + * @param reg - register + * @param data - data to write + * + * @return device status + */ +uint8_t nrf24_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data); + +/** Write buffer to device register + * + * @param handle - pointer to FuriHalSpiHandle + * @param reg - register + * @param data - data to write + * @param size - size of data to write + * + * @return device status + */ +uint8_t nrf24_write_buf_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size); + +/** Read device register + * + * @param handle - pointer to FuriHalSpiHandle + * @param reg - register + * @param[out] data - pointer to data + * + * @return device status + */ +uint8_t nrf24_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size); + +/** Power up the radio for operation + * + * @param handle - pointer to FuriHalSpiHandle + * + * @return device status + */ +uint8_t nrf24_power_up(FuriHalSpiBusHandle* handle); + +/** Power down the radio + * + * @param handle - pointer to FuriHalSpiHandle + * + * @return device status + */ +uint8_t nrf24_set_idle(FuriHalSpiBusHandle* handle); + +/** Sets the radio to RX mode + * + * @param handle - pointer to FuriHalSpiHandle + * + * @return device status + */ +uint8_t nrf24_set_rx_mode(FuriHalSpiBusHandle* handle); + +/** Sets the radio to TX mode + * + * @param handle - pointer to FuriHalSpiHandle + * + * @return device status + */ +uint8_t nrf24_set_tx_mode(FuriHalSpiBusHandle* handle); + +/*=============================================================================================================*/ + +/* High level API */ + +/** Must call this before using any other nrf24 API + * + */ +void nrf24_init(); + +/** Must call this when we end using nrf24 device + * + */ +void nrf24_deinit(); + +/** Send flush rx command + * + * @param handle - pointer to FuriHalSpiHandle + * + * @return device status + */ +uint8_t nrf24_flush_rx(FuriHalSpiBusHandle* handle); + +/** Send flush tx command + * + * @param handle - pointer to FuriHalSpiHandle + * + * @return device status + */ +uint8_t nrf24_flush_tx(FuriHalSpiBusHandle* handle); + +/** Gets the RX packet length in data pipe 0 + * + * @param handle - pointer to FuriHalSpiHandle + * pipe - pipe index (0..5) + * @return packet length in data pipe 0 + */ +uint8_t nrf24_get_packetlen(FuriHalSpiBusHandle* handle, uint8_t pipe); + +/** Sets the RX packet length in data pipe 0 + * + * @param handle - pointer to FuriHalSpiHandle + * @param len - length to set + * + * @return device status + */ +uint8_t nrf24_set_packetlen(FuriHalSpiBusHandle* handle, uint8_t len); + +/** Gets configured length of MAC address + * + * @param handle - pointer to FuriHalSpiHandle + * + * @return MAC address length + */ +uint8_t nrf24_get_maclen(FuriHalSpiBusHandle* handle); + +/** Sets configured length of MAC address + * + * @param handle - pointer to FuriHalSpiHandle + * @param maclen - length to set MAC address to, must be greater than 1 and less than 6 + * + * @return MAC address length + */ +uint8_t nrf24_set_maclen(FuriHalSpiBusHandle* handle, uint8_t maclen); + +/** Gets the current status flags from the STATUS register + * + * @param handle - pointer to FuriHalSpiHandle + * + * @return status flags + */ +uint8_t nrf24_status(FuriHalSpiBusHandle* handle); + +/** Gets the current transfer rate + * + * @param handle - pointer to FuriHalSpiHandle + * + * @return transfer rate in bps + */ +uint32_t nrf24_get_rate(FuriHalSpiBusHandle* handle); + +/** Sets the transfer rate + * + * @param handle - pointer to FuriHalSpiHandle + * @param rate - the transfer rate in bps + * + * @return device status + */ +uint8_t nrf24_set_rate(FuriHalSpiBusHandle* handle, uint32_t rate); + +/** Gets the current channel + * In nrf24, the channel number is multiplied times 1MHz and added to 2400MHz to get the frequency + * + * @param handle - pointer to FuriHalSpiHandle + * + * @return channel + */ +uint8_t nrf24_get_chan(FuriHalSpiBusHandle* handle); + +/** Sets the channel + * + * @param handle - pointer to FuriHalSpiHandle + * @param frequency - the frequency in hertz + * + * @return device status + */ +uint8_t nrf24_set_chan(FuriHalSpiBusHandle* handle, uint8_t chan); + +/** Gets the source mac address + * + * @param handle - pointer to FuriHalSpiHandle + * @param[out] mac - the source mac address + * + * @return device status + */ +uint8_t nrf24_get_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac); + +/** Sets the source mac address + * + * @param handle - pointer to FuriHalSpiHandle + * @param mac - the mac address to set + * @param size - the size of the mac address (2 to 5) + * + * @return device status + */ +uint8_t nrf24_set_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size); + +/** Gets the dest mac address + * + * @param handle - pointer to FuriHalSpiHandle + * @param[out] mac - the source mac address + * + * @return device status + */ +uint8_t nrf24_get_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac); + +/** Sets the dest mac address + * + * @param handle - pointer to FuriHalSpiHandle + * @param mac - the mac address to set + * @param size - the size of the mac address (2 to 5) + * + * @return device status + */ +uint8_t nrf24_set_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size); + +/** Reads RX packet + * + * @param handle - pointer to FuriHalSpiHandle + * @param[out] packet - the packet contents + * @param[out] ret_packetsize - size of the received packet + * @param packet_size: >1 - size, 1 - packet length is determined by RX_PW_P0 register, 0 - it is determined by dynamic payload length command + * + * @return device status + */ +uint8_t + nrf24_rxpacket(FuriHalSpiBusHandle* handle, uint8_t* packet, uint8_t* ret_packetsize, uint8_t packet_size_flag); + +/** Sends TX packet + * + * @param handle - pointer to FuriHalSpiHandle + * @param packet - the packet contents + * @param size - packet size + * @param ack - boolean to determine whether an ACK is required for the packet or not + * + * @return device status + */ +uint8_t nrf24_txpacket(FuriHalSpiBusHandle* handle, uint8_t* payload, uint8_t size, bool ack); + +/** Configure the radio + * This is not comprehensive, but covers a lot of the common configuration options that may be changed + * @param handle - pointer to FuriHalSpiHandle + * @param rate - transfer rate in Mbps (1 or 2) + * @param srcmac - source mac address + * @param dstmac - destination mac address + * @param maclen - length of mac address + * @param channel - channel to tune to + * @param noack - if true, disable auto-acknowledge + * @param disable_aa - if true, disable ShockBurst + * + */ +void nrf24_configure( + FuriHalSpiBusHandle* handle, + uint8_t rate, + uint8_t* srcmac, + uint8_t* dstmac, + uint8_t maclen, + uint8_t channel, + bool noack, + bool disable_aa); + +// Set mac address (MSB first), Return: Status +uint8_t nrf24_set_mac(uint8_t mac_addr, uint8_t *mac, uint8_t mlen); + +/** Configures the radio for "promiscuous mode" and primes it for rx + * This is not an actual mode of the nrf24, but this function exploits a few bugs in the chip that allows it to act as if it were. + * See http://travisgoodspeed.blogspot.com/2011/02/promiscuity-is-nrf24l01s-duty.html for details. + * @param handle - pointer to FuriHalSpiHandle + * @param channel - channel to tune to + * @param rate - transfer rate in Mbps (1 or 2) + */ +void nrf24_init_promisc_mode(FuriHalSpiBusHandle* handle, uint8_t channel, uint8_t rate); + +/** Listens for a packet and returns first possible address sniffed + * Call this only after calling nrf24_init_promisc_mode + * @param handle - pointer to FuriHalSpiHandle + * @param maclen - length of target mac address + * @param[out] addresses - sniffed address + * + * @return success + */ +bool nrf24_sniff_address(FuriHalSpiBusHandle* handle, uint8_t maclen, uint8_t* address); + +/** Sends ping packet on each channel for designated tx mac looking for ack + * + * @param handle - pointer to FuriHalSpiHandle + * @param srcmac - source address + * @param dstmac - destination address + * @param maclen - length of address + * @param rate - transfer rate in Mbps (1 or 2) + * @param min_channel - channel to start with + * @param max_channel - channel to end at + * @param autoinit - if true, automatically configure radio for this channel + * + * @return channel that the address is listening on, if this value is above the max_channel param, it failed + */ +uint8_t nrf24_find_channel( + FuriHalSpiBusHandle* handle, + uint8_t* srcmac, + uint8_t* dstmac, + uint8_t maclen, + uint8_t rate, + uint8_t min_channel, + uint8_t max_channel, + bool autoinit); + +/** Converts 64 bit value into uint8_t array + * @param val - 64-bit integer + * @param[out] out - bytes out + * @param bigendian - if true, convert as big endian, otherwise little endian + */ +void int64_to_bytes(uint64_t val, uint8_t* out, bool bigendian); + +/** Converts 32 bit value into uint8_t array + * @param val - 32-bit integer + * @param[out] out - bytes out + * @param bigendian - if true, convert as big endian, otherwise little endian + */ +void int32_to_bytes(uint32_t val, uint8_t* out, bool bigendian); + +/** Converts uint8_t array into 32 bit value + * @param bytes - uint8_t array + * @param bigendian - if true, convert as big endian, otherwise little endian + * + * @return 32-bit value + */ +uint32_t bytes_to_int32(uint8_t* bytes, bool bigendian); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/plugins/nrf24scan/nrf24_packet_decoder.py b/applications/plugins/nrf24scan/nrf24_packet_decoder.py new file mode 100644 index 000000000..d0ebc9c24 --- /dev/null +++ b/applications/plugins/nrf24scan/nrf24_packet_decoder.py @@ -0,0 +1,131 @@ +# +# NRF24L01+ Enhanced ShockBurst packets decoder +# +payload_len_default = 4 +packets = \ +( + '10101010 11101110 00000011 00001000 00001011 01000111 000100 10 0 10101010 10101010 10101010 10101010 00011101', + '10101010 11001000 11001000 11000011 110011 10 0 00001011 00000011 00000101 00000000 0010001100100000', + '10101010 11001000 11001000 11000100 000100 11 1 00001011 00000011 00000101 00000000 0010010011100010', + '10101010 11001000 11001000 11000100 00001011 00000011 00000101 00000010 1000010101000010', + '10101010 11001000 11001000 11000000 110011 10 0 11110101 00000010 00000011 00000000 0000111001000000', + '01010101 01000000 01101000 00010101 000000 00 0 0100100000100000', +# '01010101 01000010 11100100 10100110 01010101 01000100 110011 00 0 10010101 10110011 01100100 10101100 10101011 01010010 01111100 01001010 1100110100110001', + +) + +def bin2hex(x): + def bin2hex_helper(r): + while r: + yield r[0:2].upper() + r = r[2:] + if len(x) == 0: return + fmt = "{0:0" + str(int(len(x) / 8 * 2)) + "X}" + hex_data = fmt.format(int(x, 2)) + return list(bin2hex_helper(hex_data)) + +def bin2hexlong(b): + b = b.replace(" ", "") + out = ""; + n = 8 + for i in range(0, len(b), n): + b2 = b[i:i+n] + out = out + "{0:02X}".format(int(b2.ljust(8, '0'),2)) + return out + + +def split_packet(packet, parts): + """Split a string of 1s and 0s into multiple substrings as specified by parts. + Example: "111000011000", (3, 4, 2) -> ["111", "0000", "11", "000"] + :param packet: String of 1s and 0s + :param parts: Tuple of length of substrings + :return: list of substrings + """ + out = [] + packet = packet.replace(' ', '') + for part_length in parts: + out.append(packet[0:part_length]) + packet = packet[part_length:] + out.append(packet) + return out + + +def parse_packet(packet, address_length, ESB): + """Split a packet into its fields and return them as tuple.""" + if ESB: + preamble, address, payload_length, pid, no_ack, rest = split_packet(packet=packet, parts=(8, 8 * address_length, 6, 2, 1)) + payload, crc = split_packet(packet=rest, parts=((payload_len_default if int(payload_length, 2) > 32 else int(payload_length, 2)) * 8,)) + else: + preamble, address, rest = split_packet(packet=packet, parts=(8, 8 * address_length)) + crc = packet.rsplit(' ', 1)[1] + payload = rest[0:len(rest) - len(crc)] + payload_length = pid = no_ack = '' + + assert preamble in ('10101010', '01010101') + assert len(crc) in (8, 16) + + return preamble, address, payload_length, pid, no_ack, payload, crc + + +def crc(bits, size=8): + """Calculate the crc value for the polynomial initialized with 0xFF/0xFFFF) + :param size: 8 or 16 bit crc + :param bits: String of 1s and 0s + :return: + :polynomial: 1 byte CRC - standard is 0x107 = 0b100000111 = x^8+x^2+x^1+1, result the same for 0x07 + :polynomial: 2 byte CRC - standard is 0x11021 = X^16+X^12+X^5+1, result the same for 0x1021 + """ + if size == 8: + polynomial = 0x107 + crc = 0xFF + else: + polynomial = 0x11021 + crc = 0xFFFF + max_crc_value = (1 << size) - 1 # e.g. 0xFF for mode 8bit-crc + for bit in bits: + bit = int(bit, 2) + crc <<= 1 + if (crc >> size) ^ bit: # top most lfsr bit xor current data bit + crc ^= polynomial + crc &= max_crc_value # trim the crc to reject carry over bits +# print('{:X}'.format(crc)) + return crc + + +if __name__ == '__main__': + for packet in packets: + fld = packet.split(' '); + address_length = -1 + ESB = True + for f in fld: + if len(f) == 6 : break + if len(f) == 0 : + ESB = False + break + address_length += 1 + preamble, address, payload_length, pid, no_ack, payload, crc_received = \ + parse_packet(packet=packet, address_length=address_length, ESB=ESB) + crc_size = len(crc_received) + crc_received = '0x' + '{:X}'.format(int(crc_received, 2)) + print(f"Packet: {packet}") + print('\n'.join(( + f'Hex: {bin2hexlong(packet)}', + 'Preamble: 0x%X' % int(preamble,2), + f'Address: {address_length} bytes - {bin2hex(address)}'))) + if ESB: + print('\n'.join(( + f'Payload length in packet: {int(payload_length, 2)}, used: {(payload_len_default if int(payload_length, 2) > 32 else int(payload_length, 2))}', + f'Payload: {bin2hex(payload)}', + f'Pid: {int(pid, 2)}', + f'No_ack: {int(no_ack, 2) == 1}'))) + else: + print(f'Not Enhanced ShockBurst packet, payload length: {int(len(payload) / 8)}') + print(f'Payload: {bin2hex(payload)}') + print(f'CRC{crc_size}: {crc_received}') + crc_calculated = '0x' + '{:X}'.format(crc(address + payload_length + pid + no_ack + payload, size=crc_size)) + if crc_received == crc_calculated: + print('CRC is valid!') + else: + print(f'CRC mismatch! Calculated CRC is {crc_calculated}.') + print('-------------') + diff --git a/applications/plugins/nrf24scan/nrf24scan.c b/applications/plugins/nrf24scan/nrf24scan.c new file mode 100644 index 000000000..f2bdfa096 --- /dev/null +++ b/applications/plugins/nrf24scan/nrf24scan.c @@ -0,0 +1,1601 @@ +// +// Written by vad7, 20.11.2022. +// +#include "nrf24scan.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TAG "nrf24scan" +#define VERSION "2.0b" +#define MAX_CHANNEL 125 +#define MAX_ADDR 6 + +#define SCAN_APP_PATH_FOLDER "/ext/apps_data/nrf24scan" +#define SETTINGS_FILENAME "addresses.txt" // Settings file format (1 parameter per line): +// SNIFF - if present then sniff mode +// Rate: 0/1/2 - rate in Mbps (=0.25/1/2) +// Ch: 0..125 - default channel +// ESB: 0/1 (1 - Enhanced ShockBurst) +// DPL: 0/1 (1 - Dynamic Payload Length) +// CRC: 0/1/2 (CRC length) +// Payload: 1..32 (bytes) +// P0: address P0 in hex (5 byte, LSB last) +// P1: address P1 in hex (5 byte, LSB last) +// P2: address P2, LSB in hex (1 byte) +// P3: address P3, LSB in hex (1 byte) +// P4: address P4, LSB in hex (1 byte) +// P5: address P5, LSB in hex (1 byte) +// captured data: +// first byte = { RAW packet flag (0x80/0x00) } + { channel number } +// second byte = { Payload len 5 bits, 0 = 32 } + {{ RAW packet: ESB flag 0x04/0x00 + address size-2 if RAW packet } or { pipe #(0..5) }}, +// ... up to MAX_LOG_RECORDS-1 +#define SNIFF_FILENAME "sniff.txt" // settings for sniff mode +#define LOG_FILENAME "log" +#define LOG_FILEEXT ".txt" +#define MAX_LOG_RECORDS 100 +#define LOG_REC_SIZE 34 // max packet size +#define VIEW_LOG_MAX_X 22 +#define VIEW_LOG_WIDTH_B 10 // bytes + +const char SettingsFld_Rate[] = "Rate:"; +const char SettingsFld_Ch[] = "Ch:"; +const char SettingsFld_ESB[] = "ESB:"; +const char SettingsFld_DPL[] = "DPL:"; +const char SettingsFld_CRC[] = "CRC:"; +const char SettingsFld_Payload[] = "Payload:"; +const char SettingsFld_Sniff[] = "SNIFF"; +char SettingsFld_Addr = 'P'; + +Nrf24Scan* APP; +uint8_t what_doing = 0; // 0 - setup, 1 - view log, 2 - view addresses +uint8_t what_to_do = + 1; // 0 - view, 1 - view & sniff, 2 - view & read, 3 - view & read selected addr +uint32_t key_press_seq_ok = 0; +uint8_t save_settings = 0; +char screen_buf[64]; +char addr_file_name[32]; +uint8_t NRF_rate = 2; // 0 - 250Kbps, 1 - 1Mbps, 2 - 2Mbps +uint8_t NRF_channel = 0; // 0..125 +uint8_t NRF_ESB = 1; // 0 - ShockBurst, 1 - Enhanced ShockBurst +uint8_t NRF_DPL = 0; // 1 - Dynamic Payload Length +uint8_t NRF_CRC = 2; // 1 - No, 1 - CRC 1byte, 2 - CRC 2byte +uint8_t NRF_Payload = 32; // Payload len in bytes or Minimum payload in sniff mode, 0..32 +uint8_t NRF_Payload_sniff_min = 0; +uint8_t NRF_AA_OFF = 0; // Disable Auto Acknowledgement +bool NRF_ERROR = 0; + +struct ADDRS { + uint8_t addr_P0[5]; // MSB first + uint8_t addr_P1[5]; // MSB first + uint8_t addr_P2; // LSB only, MSB bytes equal addr_P1 + uint8_t addr_P3; // LSB only, MSB bytes equal addr_P1 + uint8_t addr_P4; // LSB only, MSB bytes equal addr_P1 + uint8_t addr_P5; // LSB only, MSB bytes equal addr_P1 + uint8_t addr_len; // 2..5 + uint8_t addr_count; +}; + +struct ADDRS addrs; +struct ADDRS addrs_sniff; +struct ADDRS addrs_found; +uint16_t found_total[6]; +bool sniff_loaded = 0; + +int8_t log_to_file = 0; // 0 - no, 1 - yes(new), 2 - append, -1 - only clear +uint16_t log_arr_idx; +uint16_t view_log_arr_idx = 0; +uint16_t view_log_arr_x = 0; +bool save_to_new_log = true; +uint16_t last_packet_send = -1; +uint8_t last_packet_send_st = 0; +int16_t find_channel_period = 0; // sec +uint8_t menu_selected = 0; +uint8_t view_details_type = 1; // 0 - sniff addrs, 1 - found addrs +uint32_t start_time; +uint8_t view_log_decode_PCF = + 0; // view log: 1 - decode packet control field (9b) when ESB off. After pipe # (hex): +uint8_t view_log_decode_CRC = 0; // CRC bytes - 1/2, 0 - none + +#define menu_selected_max 5 +enum { + Menu_open_file = 0, + Menu_enter_channel, + Menu_enter_rate, + Menu_enter_scan_period, + Menu_log, + Menu_ok +}; + +//#define MIN(a, b) ((a> 7)); + arr++; + out += 2; + } while(--bytes); +} + +void clear_log() { + log_arr_idx = 0; + view_log_arr_idx = 0; + last_packet_send = -1; + memset(&addrs_found, 0, sizeof(addrs_found)); + view_details_type = 0; + memset(&found_total, 0, sizeof(found_total)); +} + +void allocate_log_array() { + APP->log_arr = malloc(LOG_REC_SIZE * MAX_LOG_RECORDS); + if(APP->log_arr == NULL) { + FURI_LOG_E(TAG, "Not enouch memory: %d", LOG_REC_SIZE * MAX_LOG_RECORDS); + strcpy(addr_file_name, "MEMORY LOW!"); + } + clear_log(); +} + +void write_to_log_file(Storage* storage, bool f_settings) { + if(log_arr_idx == 0 && !f_settings) return; + Stream* file_stream = file_stream_alloc(storage); + FuriString* str = furi_string_alloc(); + furi_string_set(str, SCAN_APP_PATH_FOLDER); + furi_string_cat(str, "/"); + bool fl; + if(f_settings) { + furi_string_cat(str, SETTINGS_FILENAME); + fl = file_stream_open( + file_stream, furi_string_get_cstr(str), FSAM_READ_WRITE, FSOM_CREATE_ALWAYS); + if(!fl) file_stream_close(file_stream); + } else { + furi_string_cat(str, LOG_FILENAME); + furi_string_cat(str, LOG_FILEEXT); + if(save_to_new_log) { + int cnt = 1; + do { + fl = file_stream_open( + file_stream, furi_string_get_cstr(str), FSAM_READ_WRITE, FSOM_CREATE_NEW); + if(fl) break; + file_stream_close(file_stream); + furi_string_set(str, SCAN_APP_PATH_FOLDER); + furi_string_cat(str, "/"); + furi_string_cat(str, LOG_FILENAME); + furi_string_cat_printf(str, "-%02d", cnt); + furi_string_cat(str, LOG_FILEEXT); + } while(++cnt < 100); + if(!fl) { + FURI_LOG_E(TAG, "Failed to create new log file"); + notification_message(APP->notification, &sequence_blink_red_100); + } + } else { + fl = file_stream_open( + file_stream, furi_string_get_cstr(str), FSAM_READ_WRITE, FSOM_OPEN_APPEND); + if(fl) { + if(stream_size(file_stream) == 0) save_to_new_log = true; + } else + file_stream_close(file_stream); + } + } + if(fl) { + FURI_LOG_D(TAG, "Save to %s", furi_string_get_cstr(str)); + if(save_to_new_log || f_settings) { + //if(what_to_do == 1) furi_string_printf(str, "%s\n", SettingsFld_Sniff); else furi_string_reset(str); + furi_string_printf( + str, + "%s %d\n%s %d\n%s %d\n", + SettingsFld_Rate, + NRF_rate, + SettingsFld_Ch, + NRF_channel, + SettingsFld_ESB, + NRF_ESB); + furi_string_cat_printf( + str, + "%s %d\n%s %d\n%s %d\n", + SettingsFld_DPL, + NRF_DPL, + SettingsFld_CRC, + NRF_CRC, + SettingsFld_Payload, + what_to_do == 1 ? NRF_Payload_sniff_min : NRF_Payload); + furi_string_cat_printf(str, "P0: "); + add_to_furi_str_hex_bytes(str, (char*)addrs.addr_P0, addrs.addr_len); + furi_string_cat(str, "\n"); + if(addrs.addr_count > 1) { + furi_string_cat_printf(str, "P1: "); + add_to_furi_str_hex_bytes(str, (char*)addrs.addr_P1, addrs.addr_len); + furi_string_cat(str, "\n"); + } + if(addrs.addr_count > 2) { + furi_string_cat_printf(str, "P2: "); + furi_string_cat_printf(str, "%02X\n", addrs.addr_P2); + } + if(addrs.addr_count > 3) { + furi_string_cat_printf(str, "P3: "); + furi_string_cat_printf(str, "%02X\n", addrs.addr_P3); + } + if(addrs.addr_count > 4) { + furi_string_cat_printf(str, "P4: "); + furi_string_cat_printf(str, "%02X\n", addrs.addr_P4); + } + if(addrs.addr_count > 5) { + furi_string_cat_printf(str, "P5: "); + furi_string_cat_printf(str, "%02X\n", addrs.addr_P5); + } + if(!(fl = stream_write_string(file_stream, str) == furi_string_size(str))) { + FURI_LOG_E(TAG, "Failed to write header to file!"); + notification_message(APP->notification, &sequence_blink_red_100); + } + } + if(fl) { + if(f_settings) { + save_settings = 0; + if(strcmp(addr_file_name, "NONE") == 0) strcpy(addr_file_name, SETTINGS_FILENAME); + } else { + int i = 0; + for(; i < log_arr_idx; i++) { + furi_string_reset(str); + uint8_t* ptr = APP->log_arr + i * LOG_REC_SIZE; + int len; + if(ptr[0] & 0x80) { // RAW + len = (ptr[1] & 0b11) + 2 + ((ptr[1] & 0b100) ? 2 : 0) + (ptr[1] >> 3) + + 2; // addr + PCF? + payload + crcmax + } else { + len = (ptr[1] >> 3); + if(len == 0) len = 32; + } + //if(len < NRF_Payload) len = NRF_Payload; + add_to_furi_str_hex_bytes(str, (char*)ptr, len + 2); + furi_string_cat(str, "\n"); + if(stream_write_string(file_stream, str) != furi_string_size(str)) { + FURI_LOG_E(TAG, "Failed to write to file!"); + break; + } + } + if(i == log_arr_idx) { + notification_message(APP->notification, &sequence_blink_yellow_100); + FURI_LOG_D(TAG, "File saved"); + } + save_to_new_log = false; + } + } + file_stream_close(file_stream); + } else { + FURI_LOG_E(TAG, "Failed to open file %s", furi_string_get_cstr(str)); + notification_message(APP->notification, &sequence_blink_red_100); + } + stream_free(file_stream); + furi_string_free(str); +} + +static bool select_settings_file(Stream* stream) { + DialogsApp* dialogs = furi_record_open("dialogs"); + bool result = false; + FuriString* path; + path = furi_string_alloc(); + furi_string_set(path, SCAN_APP_PATH_FOLDER); + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options(&browser_options, ".txt", NULL); + browser_options.hide_ext = false; + + bool ret = dialog_file_browser_show(dialogs, path, path, &browser_options); + + furi_record_close("dialogs"); + if(ret) { + if(!file_stream_open(stream, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) { + FURI_LOG_D(TAG, "Cannot open file \"%s\"", furi_string_get_cstr(path)); + file_stream_close(stream); + } else { + FURI_LOG_D(TAG, "Open file \"%s\"", furi_string_get_cstr(path)); + strncpy( + addr_file_name, + furi_string_get_cstr(path) + sizeof(SCAN_APP_PATH_FOLDER), + sizeof(addr_file_name)); + result = true; + } + } + furi_string_free(path); + return result; +} + +// 0 - success, otherwise an error +static uint8_t load_settings_file(Stream* file_stream) { + size_t file_size = 0; + char* file_buf; + uint8_t err = 5; + file_size = stream_size(file_stream); + if(file_size == (size_t)0) { + FURI_LOG_D(TAG, "load failed. file_size: %d", file_size); + return 1; + } + file_size = MIN(file_size, (size_t)LOG_REC_SIZE * MAX_LOG_RECORDS * 2 + 100); + file_buf = malloc(file_size + 1); + if(file_buf == NULL) { + FURI_LOG_D(TAG, "Memory low, need: %d", file_size); + return 2; + } + memset(file_buf, 0, file_size + 1); + if(stream_read(file_stream, (uint8_t*)file_buf, file_size) == file_size) { + FURI_LOG_D(TAG, "Loading settings file"); + char* line_ptr = file_buf; + int16_t line_num = 0; + what_to_do = 2; + sniff_loaded = 0; + bool log_loaded = false; + while(line_ptr && (size_t)(line_ptr - file_buf) < file_size) { + char* end_ptr = strstr((char*)line_ptr, "\n"); + if(end_ptr == NULL) + end_ptr = file_buf + file_size; + else + *end_ptr = '\0'; + int line_len = end_ptr - line_ptr; + if(*line_ptr == '\r' || line_len == 0) { + line_ptr = end_ptr + 1; + continue; + } + if(*(end_ptr - 1) < '0') { + *(end_ptr - 1) = '\0'; + line_len--; + } + //FURI_LOG_D(TAG, " L#%d: [%d]%s", line_num, line_len, line_ptr); + if(strncmp(line_ptr, SettingsFld_Rate, sizeof(SettingsFld_Rate) - 1) == 0) { + NRF_rate = atoi(line_ptr + sizeof(SettingsFld_Rate)); + } else if(strncmp(line_ptr, SettingsFld_Ch, sizeof(SettingsFld_Ch) - 1) == 0) { + NRF_channel = atoi(line_ptr + sizeof(SettingsFld_Ch)); + } else if(strncmp(line_ptr, SettingsFld_ESB, sizeof(SettingsFld_ESB) - 1) == 0) { + NRF_ESB = atoi(line_ptr + sizeof(SettingsFld_ESB)); + } else if(strncmp(line_ptr, SettingsFld_DPL, sizeof(SettingsFld_DPL) - 1) == 0) { + NRF_DPL = atoi(line_ptr + sizeof(SettingsFld_DPL)); + } else if(strncmp(line_ptr, SettingsFld_CRC, sizeof(SettingsFld_CRC) - 1) == 0) { + NRF_CRC = atoi(line_ptr + sizeof(SettingsFld_CRC)); + if(what_to_do == 1) view_log_decode_CRC = NRF_CRC; + } else if(strncmp(line_ptr, SettingsFld_Payload, sizeof(SettingsFld_Payload) - 1) == 0) { + uint8_t pld = atoi(line_ptr + sizeof(SettingsFld_Payload)); + if(pld > 32) pld = 32; + if(sniff_loaded) { + NRF_Payload_sniff_min = pld; + } else { + if(pld == 0) pld = 32; + NRF_Payload = pld; + } + } else if(strncmp(line_ptr, SettingsFld_Sniff, sizeof(SettingsFld_Sniff) - 1) == 0) { + what_to_do = 1; + sniff_loaded = 1; + } else if(*line_ptr == SettingsFld_Addr) { + char a = *(++line_ptr); + struct ADDRS* adr = sniff_loaded ? &addrs_sniff : &addrs; + line_ptr += 3; + switch(a) { + case '0': + memset(adr, 0, sizeof(addrs)); + adr->addr_len = + ConvertHexToArray(line_ptr, adr->addr_P0, sniff_loaded ? 3 : 5); + if(adr->addr_len >= 2) err = 0; + break; + case '1': + ConvertHexToArray(line_ptr, adr->addr_P1, what_to_do == 1 ? 3 : 5); + break; + case '2': + ConvertHexToArray(line_ptr, &adr->addr_P2, 1); + break; + case '3': + ConvertHexToArray(line_ptr, &adr->addr_P3, 1); + break; + case '4': + ConvertHexToArray(line_ptr, &adr->addr_P4, 1); + break; + case '5': + ConvertHexToArray(line_ptr, &adr->addr_P5, 1); + break; + default: + a = 0; + break; + } + if(err == 0 && a) adr->addr_count = a - '0' + 1; + } else if(line_len >= 3 * 2) { // data + if(!log_loaded) { + clear_log(); + what_to_do = 0; + log_loaded = true; + } + if(log_arr_idx < MAX_LOG_RECORDS - 1) { + ConvertHexToArray( + line_ptr, APP->log_arr + log_arr_idx * LOG_REC_SIZE, LOG_REC_SIZE); + log_arr_idx++; + } + } + line_ptr = end_ptr + 1; + line_num++; + } + } else { + FURI_LOG_D(TAG, "load failed. file size: %d", file_size); + err = 4; + } + free(file_buf); + return err; +} + +static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + PluginEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void prepare_nrf24(bool fsend_packet) { + nrf24_write_reg(nrf24_HANDLE, REG_STATUS, 0x70); // clear interrupts + nrf24_write_reg(nrf24_HANDLE, REG_RF_SETUP, NRF_rate); + uint8_t erx_addr = (1 << 0); // Enable RX_P0 + struct ADDRS* adr = what_to_do == 1 ? &addrs_sniff : &addrs; + if(!fsend_packet) { + if(adr->addr_count == 0) return; + uint8_t payload = NRF_Payload; + if(what_to_do == 1) { // SNIFF + //payload += 5 + NRF_CRC; // + addr_max + CRC + //if(NRF_ESB) payload += 2; + //if(payload > 32) payload = 32; + payload = 32; + nrf24_write_reg(nrf24_HANDLE, REG_CONFIG, 0x70); // Mask all interrupts + nrf24_write_reg(nrf24_HANDLE, REG_SETUP_RETR, 0); // Automatic Retransmission + nrf24_write_reg(nrf24_HANDLE, REG_EN_AA, 0); // Auto acknowledgement + nrf24_write_reg( + nrf24_HANDLE, + REG_FEATURE, + 0); // Enables the W_TX_PAYLOAD_NOACK command, Disable Payload with ACK, set Dynamic Payload + } else { + nrf24_write_reg( + nrf24_HANDLE, + REG_CONFIG, + 0x70 | ((NRF_CRC == 1 ? 0b1000 : + NRF_CRC == 2 ? 0b1100 : + 0))); // Mask all interrupts + nrf24_write_reg( + nrf24_HANDLE, REG_SETUP_RETR, NRF_ESB ? 0x11 : 0); // Automatic Retransmission + nrf24_write_reg( + nrf24_HANDLE, + REG_EN_AA, + NRF_AA_OFF || !NRF_ESB ? 0 : 0x3F); // Auto acknowledgement + nrf24_write_reg( + nrf24_HANDLE, + REG_FEATURE, + NRF_DPL ? + 4 + 1 : + 1); // Enables the W_TX_PAYLOAD_NOACK command, Disable Payload with ACK, set Dynamic Payload + } + nrf24_set_maclen(nrf24_HANDLE, adr->addr_len); + nrf24_set_mac(REG_RX_ADDR_P0, adr->addr_P0, adr->addr_len); + uint8_t tmp[5] = {0}; + nrf24_read_reg(nrf24_HANDLE, REG_RX_ADDR_P0, tmp, adr->addr_len); + for(uint8_t i = 0; i < adr->addr_len / 2; i++) { + uint8_t tb = tmp[i]; + tmp[i] = tmp[adr->addr_len - i - 1]; + tmp[adr->addr_len - i - 1] = tb; + } + NRF_ERROR = memcmp(adr->addr_P0, tmp, adr->addr_len) != 0; + FURI_LOG_D(TAG, "Payload: %d", payload); + nrf24_write_reg(nrf24_HANDLE, RX_PW_P0, payload); + if(adr->addr_count > 1) { + nrf24_set_mac(REG_RX_ADDR_P1, adr->addr_P1, adr->addr_len); + nrf24_write_reg(nrf24_HANDLE, RX_PW_P1, payload); + erx_addr |= (1 << 1); // Enable RX_P1 + } else + nrf24_write_reg(nrf24_HANDLE, RX_PW_P1, 0); + if(adr->addr_count > 2) { + nrf24_write_buf_reg(nrf24_HANDLE, REG_RX_ADDR_P2, &adr->addr_P2, 1); + nrf24_write_reg(nrf24_HANDLE, RX_PW_P2, payload); + erx_addr |= (1 << 2); // Enable RX_P2 + } else + nrf24_write_reg(nrf24_HANDLE, RX_PW_P2, 0); + if(adr->addr_count > 3) { + nrf24_write_buf_reg(nrf24_HANDLE, REG_RX_ADDR_P3, &adr->addr_P3, 1); + nrf24_write_reg(nrf24_HANDLE, RX_PW_P3, payload); + erx_addr |= (1 << 3); // Enable RX_P3 + } else + nrf24_write_reg(nrf24_HANDLE, RX_PW_P3, 0); + if(adr->addr_count > 4) { + nrf24_write_buf_reg(nrf24_HANDLE, REG_RX_ADDR_P4, &adr->addr_P4, 1); + nrf24_write_reg(nrf24_HANDLE, RX_PW_P4, payload); + erx_addr |= (1 << 4); // Enable RX_P4 + } else + nrf24_write_reg(nrf24_HANDLE, RX_PW_P4, 0); + if(adr->addr_count > 5) { + nrf24_write_buf_reg(nrf24_HANDLE, REG_RX_ADDR_P5, &adr->addr_P5, 1); + nrf24_write_reg(nrf24_HANDLE, RX_PW_P5, payload); + erx_addr |= (1 << 5); // Enable RX_P5 + } else + nrf24_write_reg(nrf24_HANDLE, RX_PW_P5, 0); + nrf24_write_reg(nrf24_HANDLE, REG_DYNPD, NRF_DPL ? 0x3F : 0); // Enable dynamic payload reg + nrf24_write_reg(nrf24_HANDLE, REG_EN_RXADDR, erx_addr); + nrf24_write_reg(nrf24_HANDLE, REG_RF_CH, NRF_channel); + } + nrf24_flush_rx(nrf24_HANDLE); + nrf24_flush_tx(nrf24_HANDLE); + nrf24_set_idle(nrf24_HANDLE); +} + +void correct_NRF_Payload_sniff_min() { + uint8_t pld = 32 - 3 - (NRF_ESB ? 2 : 0) - NRF_CRC + (addrs_sniff.addr_len - 2); + if(NRF_Payload_sniff_min > pld) NRF_Payload_sniff_min = pld; +} + +static void start_scanning() { + FURI_LOG_D(TAG, "Start proc-%d: Ch=%d Rate=%d", what_to_do, NRF_channel, NRF_rate); + if(what_to_do == 1) { // SNIFF + correct_NRF_Payload_sniff_min(); + view_log_decode_CRC = NRF_CRC; + } else if(sniff_loaded) { // Switch from sniff to scan/view + } + prepare_nrf24(false); + if(NRF_ERROR) { + FURI_LOG_E(TAG, "NRF R/W ERROR!"); + return; + } + nrf24_set_rx_mode(nrf24_HANDLE); + start_time = furi_get_tick(); +} + +bool check_addr_found(uint8_t* pkt) { + uint8_t idx = 255; + if(addrs_found.addr_count > 0 && memcmp(addrs_found.addr_P0, pkt, addrs_found.addr_len) == 0) { + idx = 0; + goto x_end; + } + if(addrs_found.addr_count > 1 && + memcmp(addrs_found.addr_P1, pkt, addrs_found.addr_len - 1) == 0) { + if(addrs_found.addr_P1[addrs_found.addr_len - 1] == pkt[addrs_found.addr_len - 1]) { + idx = 1; + goto x_end; + } + if(addrs_found.addr_count > 2 && addrs_found.addr_P2 == pkt[addrs_found.addr_len - 1]) { + idx = 2; + goto x_end; + } + if(addrs_found.addr_count > 3 && addrs_found.addr_P3 == pkt[addrs_found.addr_len - 1]) { + idx = 3; + goto x_end; + } + if(addrs_found.addr_count > 4 && addrs_found.addr_P4 == pkt[addrs_found.addr_len - 1]) { + idx = 4; + goto x_end; + } + if(addrs_found.addr_count > 5 && addrs_found.addr_P5 == pkt[addrs_found.addr_len - 1]) { + idx = 5; + goto x_end; + } + } +x_end: + if(idx < sizeof(found_total) / sizeof(found_total[0])) { + found_total[idx]++; + return true; + } else + return false; +} + +// start bitnum = 7 +uint32_t calc_crc(uint32_t crc, uint8_t* ptr, uint8_t bitnum, uint16_t bits) { + //uint8_t bitnum = 7; + uint32_t crc_high, polynom; + if(view_log_decode_CRC == 2) { + crc_high = (1 << 16); + polynom = 0x1021; // X^16+X^12+X^5+1 => 0x11021 & 0xFFFF = 0x1021 + } else { + crc_high = (1 << 8); + polynom = 0x07; // x^8+x^2+x^1+1 => 0x107 & 0xFF = 0x07 + } + while(bits--) { + crc <<= 1; + if(((crc & crc_high) != 0) ^ ((*ptr >> bitnum) & 1)) crc ^= polynom; + if(bitnum == 0) { + ptr++; + bitnum = 7; + } else + bitnum--; + } + return crc & (view_log_decode_CRC == 2 ? 0xFFFF : 0xFF); +} + +// shifted 1 bit right +uint32_t get_shifted_crc(uint8_t* pcrc) { + uint32_t crc = ((*pcrc << 1) & 0xFF) | (*(pcrc + 1) >> 7); + if(view_log_decode_CRC == 2) { + crc = (crc << 8) | ((*(pcrc + 1) << 1) & 0xFF) | (*(pcrc + 2) >> 7); + } + return crc; +} + +bool check_packet(uint8_t* pkt, uint16_t size) { + if(size < 3 || size > 32) return false; + uint8_t b = *pkt; + if(b == 0x55 || b == 0xAA || b == 0x00 || b == 0xFF) + return false; // skip pkt when address begin with + uint32_t prevcrc; + bool found = false; + uint8_t addr_size = 3; + for(; addr_size <= 5; addr_size++) { + if(NRF_ESB) { + uint8_t _payload = *(pkt + addr_size) >> 2; + if((_payload > size - addr_size - 2 - view_log_decode_CRC && _payload != 0x33)) + continue; + uint8_t* p = pkt + addr_size; + if(addr_size == 3) { + prevcrc = calc_crc( + view_log_decode_CRC == 2 ? 0xFFFF : 0xFF, + pkt, + 7, + 3 * 8); // crc for smallest addr + } else { + prevcrc = calc_crc(prevcrc, p - 1, 7, 8); + } + uint32_t crc = prevcrc; + if(_payload != 0x33) { // DPL + crc = calc_crc(crc, p, 7, 9 + _payload * 8); + if(crc == get_shifted_crc(p + _payload + 1)) { + *(pkt - 1) = ((_payload & 0x1F) << 3) + 0b100 + (addr_size - 2); + FURI_LOG_D( + TAG, "VALID CRC %X: dpl: %d, addr: %d", (uint16_t)crc, _payload, addr_size); + found = true; + break; + } + } else { + crc = calc_crc(crc, p++, 7, 9); // PCF + if(crc == get_shifted_crc(p)) { + _payload = 0; + found = true; + } else { + for(uint8_t i = 1; i < size - addr_size - view_log_decode_CRC; i++) { + crc = calc_crc(crc, p++, 6, 8); + if(crc == get_shifted_crc(p)) { + _payload = i; + found = true; + break; + } + } + } + if(found) { + *(pkt - 1) = ((_payload & 0x1F) << 3) + 0b100 + (addr_size - 2); + FURI_LOG_D( + TAG, "VALID CRC %X: pl: %d, addr: %d", (uint16_t)crc, _payload, addr_size); + break; + } + } + } else { + uint8_t* p; + if(addr_size == 3) { + prevcrc = calc_crc( + view_log_decode_CRC == 2 ? 0xFFFF : 0xFF, + pkt, + 7, + 3 * 8); // crc for smallest addr + p = pkt + addr_size; + } else { + p = pkt + addr_size - 1; + prevcrc = calc_crc(prevcrc, p++, 7, 8); + } + uint32_t crc = prevcrc; + if((view_log_decode_CRC == 1 && crc == *p) || + (view_log_decode_CRC == 2 && crc == (uint32_t)((*p << 8) | *(p + 1)))) { + *(pkt - 1) = ((0 & 0x1F) << 3) + 0b000 + (addr_size - 2); + FURI_LOG_D(TAG, "VALID CRC %X: pl: %d, addr: %d", (uint16_t)crc, 0, addr_size); + found = true; + break; + } + for(uint8_t i = 1; i <= size - addr_size - view_log_decode_CRC; i++) { + crc = calc_crc(crc, p++, 7, 8); + if((view_log_decode_CRC == 1 && crc == *p) || + (view_log_decode_CRC == 2 && crc == (uint32_t)((*p << 8) | *(p + 1)))) { + *(pkt - 1) = ((i & 0x1F) << 3) + 0b000 + (addr_size - 2); + FURI_LOG_D(TAG, "VALID CRC %X: pl: %d, addr: %d", (uint16_t)crc, i, addr_size); + found = true; + break; + } + } + if(found) break; + } + } + if(found && furi_log_get_level() == FuriLogLevelDebug) { + char dbuf[65]; + dbuf[0] = 0; + add_to_str_hex_bytes(dbuf, (char*)pkt, size); + FURI_LOG_D(TAG, "PKT%02X: %s (%d)", *(pkt - 1), dbuf, size); + } + if(found && addrs_found.addr_count < 6) { + if(addrs_found.addr_count == 0) { + memcpy(addrs_found.addr_P0, pkt, addr_size); + addrs_found.addr_len = addr_size; + found_total[0]++; + addrs_found.addr_count++; + } else if(addr_size == addrs_found.addr_len) { + if(!check_addr_found(pkt)) { + if(addrs_found.addr_count == 1) { + memcpy(addrs_found.addr_P1, pkt, addr_size); + found_total[1]++; + addrs_found.addr_count++; + } else if(addrs_found.addr_count == 2) { + if(memcmp(addrs_found.addr_P1, pkt, addr_size - 1) == 0) { + addrs_found.addr_P2 = pkt[addr_size - 1]; + found_total[2]++; + addrs_found.addr_count++; + } else if(memcmp(addrs_found.addr_P0, pkt, addr_size - 1) == 0) { + uint8_t tmp[5]; + memcpy(tmp, addrs_found.addr_P1, addr_size); // swap P0-P1 + memcpy(addrs_found.addr_P1, addrs_found.addr_P0, addr_size); + memcpy(addrs_found.addr_P0, tmp, addr_size); + uint32_t n = found_total[0]; + found_total[0] = found_total[1]; + found_total[1] = n; + addrs_found.addr_P2 = pkt[addr_size - 1]; + found_total[2]++; + addrs_found.addr_count++; + } + } else if(addrs_found.addr_count >= 3) { + if(memcmp(addrs_found.addr_P1, pkt, addr_size - 1) == 0) { + if(addrs_found.addr_count == 3) { + addrs_found.addr_P3 = pkt[addr_size - 1]; + found_total[3]++; + addrs_found.addr_count++; + } else if(addrs_found.addr_count == 4) { + addrs_found.addr_P4 = pkt[addr_size - 1]; + found_total[4]++; + addrs_found.addr_count++; + } else if(addrs_found.addr_count == 5) { + addrs_found.addr_P5 = pkt[addr_size - 1]; + found_total[5]++; + addrs_found.addr_count++; + } + } + } + } + } + } + return found; +} + +bool nrf24_read_newpacket() { + if(APP->log_arr == NULL) return false; + bool found = false; + uint8_t packetsize; + uint8_t* ptr = APP->log_arr + log_arr_idx * LOG_REC_SIZE; + uint8_t st; + /* test pkts + static int iii = 0; + char ppp[][65] = { "42E4A65544CC4AD9B25655A93E25669895572162DDA295524660D2", + "C8C8C0CE7A81018007202FFFFC", + "EAEC8C8C2CE3C0101006FB737A", + "BEBFFFEC8C8C1CC00542AF7CFF7DBEAFE3397FEAFEF1DDFA4AEF7FDBB7CDEABC", + "FEAAAABEAAFEAAC8C8C28E1C810080490ABAF7FEEB76B7FDFEF7DFFB47FB97FE", + "A8AAC8C8C1CE20163DF7DFFD00", + "AFFEEFEC8C8C2CE4001010062F037F9BFFDF1DAD5EDBEF55DD9AB535FCB67F55", + "AC8C8C1CE5F8102000D503D7ABF", + "EE03080B4712555555550E80", + "C8C8C41385818280127100", + "AAC8C8C3CE05818280119000" + "AC8C8C413858182801271000", + "AAC8C8C40B0305028542" + }; + if(iii < 13) { + ConvertHexToArray(ppp[iii], ptr + 2, 32); + st = RX_DR; + packetsize = 32; + iii++; + } else +//*/ + st = nrf24_rxpacket( + nrf24_HANDLE, + ptr + 2 + (what_to_do == 1 ? addrs_sniff.addr_len - 2 : 0), + &packetsize, + what_to_do == 1 ? 32 : !NRF_DPL); + if(st & RX_DR) { + st = (st >> 1) & 7; // pipe # + if(what_to_do == 1) { // SNIFF + *ptr++ = NRF_channel | 0x80; + *ptr++ = st; // pipe # + if(addrs_sniff.addr_len > 2) { + *ptr = st == 0 ? addrs_sniff.addr_P0[2] : + st == 1 ? addrs_sniff.addr_P1[2] : + st == 2 ? addrs_sniff.addr_P2 : + st == 3 ? addrs_sniff.addr_P3 : + st == 4 ? addrs_sniff.addr_P4 : + addrs_sniff.addr_P5; + } + if(!check_packet(ptr, packetsize)) { + if(addrs_sniff.addr_len > 2) return false; // skip if mac MSB added to preamble + uint8_t shifted = 0; + uint8_t shift_max = (32 - 3 - NRF_Payload_sniff_min - NRF_CRC) * 8 - 1; + while(shifted++ < + shift_max) { // Shift packet left by one bit if minimum payload fits + uint8_t i = 0; + for(; i < packetsize - 1; i++) ptr[i] = (ptr[i] << 1) | (ptr[i + 1] >> 7); + ptr[i] <<= 1; + if(check_packet(ptr, packetsize - (shifted >> 3) - 1)) goto x_valid; + } + return false; + } + } else { + *ptr++ = NRF_channel; + *ptr++ = ((packetsize & 0x1F) << 3) | st; // payload size + pipe # + } + x_valid: + if(packetsize < 32) memset(ptr + packetsize, 0, 32 - packetsize); + if(log_arr_idx < MAX_LOG_RECORDS - 1) { + log_arr_idx++; + } else { + if(log_to_file == 1 || log_to_file == 2) { + write_to_log_file(APP->storage, false); + clear_log(); + } else { + memmove(APP->log_arr, APP->log_arr + LOG_REC_SIZE, log_arr_idx * LOG_REC_SIZE); + } + } + FURI_LOG_D(TAG, "Found packet #%d pipe %d", log_arr_idx, st); + notification_message(APP->notification, &sequence_blink_white_100); + found = true; + } + return found; +} + +bool nrf24_send_packet() { + if(log_arr_idx == 0) return false; + prepare_nrf24(!what_to_do); + uint8_t* ptr = APP->log_arr + view_log_arr_idx * LOG_REC_SIZE; + nrf24_write_reg(nrf24_HANDLE, REG_RF_CH, *ptr & 0x7F); + if(*ptr & 0x80) { // RAW packet + //uint8_t pktinfo = *(ptr + 1); + //nrf24_set_maclen(nrf24_HANDLE, (pktinfo & 0b11) + 2); + //if(pktinfo & 0b100) { // ESB + nrf24_write_reg(nrf24_HANDLE, REG_SETUP_RETR, 0); // No Automatic Retransmission + nrf24_write_reg(nrf24_HANDLE, REG_EN_AA, 0); // No Auto acknowledgement + //} + //uint8_t alen = (*(ptr + 2) & 0b11) + 2; + uint8_t adr[2]; + adr[0] = ptr[2]; + adr[1] = ptr[3]; + nrf24_set_maclen(nrf24_HANDLE, 2); + nrf24_set_mac(REG_RX_ADDR_P0, adr, 2); + nrf24_set_mac(REG_TX_ADDR, adr, 2); + last_packet_send_st = nrf24_txpacket(nrf24_HANDLE, ptr + 2 + 2, 32 - 2, false); + } else { + nrf24_write_reg( + nrf24_HANDLE, REG_SETUP_RETR, NRF_ESB ? 0x11 : 0); // Automatic Retransmission + nrf24_write_reg( + nrf24_HANDLE, REG_EN_AA, NRF_AA_OFF || !NRF_ESB ? 0 : 0x3F); // Auto acknowledgement + uint8_t* adr; + uint8_t a = *(ptr + 1) & 0b111; + if(a < 2) { + if(a == 0) + adr = addrs.addr_P0; + else + adr = addrs.addr_P1; + nrf24_set_mac(REG_RX_ADDR_P0, adr, addrs.addr_len); + nrf24_set_mac(REG_TX_ADDR, adr, addrs.addr_len); + } else { + uint8_t buf[5]; + memcpy(buf, addrs.addr_P1, addrs.addr_len - 1); + buf[addrs.addr_len - 1] = a == 2 ? addrs.addr_P2 : + a == 3 ? addrs.addr_P3 : + a == 4 ? addrs.addr_P4 : + addrs.addr_P5; + nrf24_set_mac(REG_RX_ADDR_P0, buf, addrs.addr_len); + nrf24_set_mac(REG_TX_ADDR, buf, addrs.addr_len); + } + a = *(ptr + 1) >> 3; + if(a == 0) a = 32; + nrf24_write_reg( + nrf24_HANDLE, + REG_CONFIG, + 0x70 | ((NRF_CRC == 1 ? 0b1000 : + NRF_CRC == 2 ? 0b1100 : + 0))); // Mask all interrupts + nrf24_write_reg(nrf24_HANDLE, REG_DYNPD, NRF_DPL ? 0x3F : 0); // Enable dynamic payload reg + last_packet_send_st = nrf24_txpacket(nrf24_HANDLE, ptr + 2, a, false); + } + last_packet_send = view_log_arr_idx; + notification_message( + APP->notification, + last_packet_send_st ? &sequence_blink_blue_100 : &sequence_blink_red_100); + if(what_to_do) start_scanning(); + return last_packet_send_st; +} + +static void render_callback(Canvas* const canvas, void* ctx) { + const PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25); + if(plugin_state == NULL) return; + //canvas_draw_frame(canvas, 0, 0, 128, 64); // border around the edge of the screen + if(what_doing == 0) { + canvas_set_font(canvas, FontSecondary); // 8x10 font, 6 lines + if(save_settings) + snprintf( + screen_buf, sizeof(screen_buf), "Save: %s", SETTINGS_FILENAME); // menu_selected = 0 + else + snprintf(screen_buf, sizeof(screen_buf), "Load: %s", addr_file_name); + canvas_draw_str(canvas, 10, 10, screen_buf); + snprintf(screen_buf, sizeof(screen_buf), "Ch: %d", NRF_channel); // menu_selected = 1 + canvas_draw_str(canvas, 10, 20, screen_buf); + if(NRF_ESB) { + strcpy(screen_buf, "ESB"); + if(NRF_DPL) strcat(screen_buf, " DPL"); + canvas_draw_str(canvas, 78, 20, screen_buf); + } + if(NRF_AA_OFF) { + canvas_draw_str(canvas, 61, 20, "AA"); + canvas_draw_line(canvas, 60, 21, 72, 12); + } + snprintf( + screen_buf, + sizeof(screen_buf), + "Rate: %sbps", + NRF_rate == 2 ? "2M" : + NRF_rate == 1 ? "1M" : + "250K"); // menu_selected = 2 + canvas_draw_str(canvas, 10, 30, screen_buf); + if(what_to_do == 1) + snprintf(screen_buf, sizeof(screen_buf), "Min Payl: %d", NRF_Payload_sniff_min); + else + snprintf(screen_buf, sizeof(screen_buf), "Payload: %d", NRF_Payload); + canvas_draw_str(canvas, 78, 30, screen_buf); + strcpy(screen_buf, "Next Ch time: "); // menu_selected = 3 + if(find_channel_period == 0) + strcat(screen_buf, "off"); + else + snprintf( + screen_buf + strlen(screen_buf), sizeof(screen_buf), "%d s", find_channel_period); + canvas_draw_str(canvas, 10, 40, screen_buf); + if(NRF_CRC == 1) + canvas_draw_str(canvas, 99, 40, "CRC1"); + else if(NRF_CRC == 2) + canvas_draw_str(canvas, 99, 40, "CRC2"); + snprintf( + screen_buf, + sizeof(screen_buf), + "Log: %s", + log_to_file == 0 ? "No" : + log_to_file == 1 ? "Yes" : + log_to_file == 2 ? "Append" : + "Clear"); // menu_selected = 4 + canvas_draw_str(canvas, 10, 50, screen_buf); + if(what_to_do) { // menu_selected = 5 + if(NRF_ERROR) + snprintf(screen_buf, sizeof(screen_buf), "nRF24L01+ R/W ERROR!"); + else { + if(what_to_do == 1) + snprintf(screen_buf, sizeof(screen_buf), "Start sniff"); + else { + uint8_t* p; + if(what_to_do == 3 && log_arr_idx && + *(p = APP->log_arr + view_log_arr_idx * LOG_REC_SIZE) & 0x80) { // +RAW + snprintf(screen_buf, sizeof(screen_buf), "Start read: "); + add_to_str_hex_bytes(screen_buf, (char*)p + 2, (*(p + 1) & 0b11) + 2); + } else + snprintf( + screen_buf, + sizeof(screen_buf), + "Start scan (pipes: %d)", + addrs.addr_count); + } + } + } else + snprintf(screen_buf, sizeof(screen_buf), "View log (pipes: %d)", addrs.addr_count); + canvas_draw_str(canvas, 10, 60, screen_buf); + canvas_draw_str(canvas, 0, menu_selected * 10 + 10, ">"); + } else if(what_doing == 1) { + canvas_set_font(canvas, FontBatteryPercent); // 5x7 font, 9 lines + bool ch2 = false; + screen_buf[0] = '\0'; + if(view_log_arr_x == 0) { + strcat(screen_buf, " "); + ch2 = true; + } else { + snprintf(screen_buf, sizeof(screen_buf), "<%d", view_log_arr_x); + if(view_log_arr_x < VIEW_LOG_MAX_X) ch2 = true; + } + snprintf( + screen_buf + strlen(screen_buf), + sizeof(screen_buf), + " %s ch: %d - %d.", + what_to_do == 1 ? "Sniff" : + what_to_do == 0 ? "View" : + "Read", + NRF_channel, + log_arr_idx); + canvas_draw_str(canvas, 0, 7, screen_buf); + if(ch2) canvas_draw_str(canvas, 121, 7, ">"); + if(log_arr_idx) { + if(view_log_arr_idx >= log_arr_idx) view_log_arr_idx = log_arr_idx - 1; + uint16_t page = view_log_arr_idx & ~7; + for(uint8_t i = 0; i < 8 && page + i < log_arr_idx; i++) { + screen_buf[0] = (view_log_arr_idx & 7) != i ? ' ' : + last_packet_send != view_log_arr_idx ? '>' : + last_packet_send_st ? '*' : + '!'; + screen_buf[1] = '\0'; + uint8_t* ptr = APP->log_arr + (page + i) * LOG_REC_SIZE; + uint8_t channel = *ptr++; + uint8_t* crcptr = NULL; + uint8_t pre = 0; + int count = 0; + if(channel & 0x80) { // RAW packet: nn:>{.address..}-xxxxxxxx + uint8_t pktinfo = *ptr++; + bool _PCF = pktinfo & 0b100; + uint8_t plen = count = (pktinfo >> 3); + uint8_t adrsize = (pktinfo & 0b11) + 2; + plen += adrsize; + count += view_log_decode_CRC; + if(view_log_arr_x > 0) count -= view_log_arr_x - 1; + uint8_t max_width = VIEW_LOG_WIDTH_B; + if(view_log_arr_x == 0) max_width -= 5; + if(count > max_width) count = max_width; + if(count > 0) { + uint8_t* pcrc = ptr; + uint32_t crc; + crc = view_log_decode_CRC == 2 ? 0xFFFF : 0xFF; + crc = calc_crc(crc, pcrc, 7, (_PCF ? 9 : 0) + plen * 8); + pcrc += plen; + if(_PCF) { //ESB + pcrc++; + if(crc == get_shifted_crc(pcrc)) crcptr = pcrc; + } else { + if((view_log_decode_CRC == 1 && crc == *pcrc) || + (view_log_decode_CRC == 2 && + crc == (uint32_t)((*pcrc << 8) | *(pcrc + 1)))) { + crcptr = pcrc; + } + } + if(view_log_arr_x == 0) { + add_to_str_hex_bytes(screen_buf, (char*)ptr, adrsize); + for(int8_t j = 5 - adrsize; j > 0; j--) strcat(screen_buf, " "); + strcat(screen_buf, "-"); + pre += 5 * 2 + 1; + } else { + ptr += view_log_arr_x - 1; + } + ptr += adrsize; + if(_PCF) + add_to_str_hex_bytes_shift_9b(screen_buf, (char*)ptr++, count); + else + add_to_str_hex_bytes(screen_buf, (char*)ptr, count); + } + } else { + uint8_t dpl = *ptr++; + uint8_t pipe = dpl & 0b111; + dpl >>= 3; + if(dpl == 0) dpl = 32; + count = dpl - view_log_arr_x; + if(view_log_decode_PCF) count--; + uint8_t max_width = VIEW_LOG_WIDTH_B; + if(view_log_arr_x == 0) { + if(addrs.addr_count > 1) max_width--; + if(view_log_decode_PCF) max_width -= 2; + } + if(count > max_width) count = max_width; + if(count > 0) { + if(view_log_decode_CRC) { + static uint16_t prev_addr_CRC; + static int8_t prev_pipe = -1; + uint8_t* pcrc = ptr; + uint32_t crc; + if(prev_pipe == pipe) { + crc = prev_addr_CRC; + } else { + crc = view_log_decode_CRC == 2 ? 0xFFFF : 0xFF; + if(pipe <= 1) { + crc = calc_crc( + crc, + pipe == 0 ? addrs.addr_P0 : addrs.addr_P1, + 7, + addrs.addr_len * 8); + } else { + crc = + calc_crc(crc, addrs.addr_P1, 7, (addrs.addr_len - 1) * 8); + crc = calc_crc( + crc, + pipe == 2 ? &addrs.addr_P2 : + pipe == 3 ? &addrs.addr_P3 : + pipe == 4 ? &addrs.addr_P4 : + &addrs.addr_P5, + 7, + 8); + } + prev_addr_CRC = crc; + prev_pipe = pipe; + } + if(view_log_decode_PCF) { + crc = calc_crc(crc, pcrc++, 7, 9); + if(crc == get_shifted_crc(pcrc)) crcptr = pcrc; + if(crcptr == NULL) { + for(int8_t j = 0; j < (int8_t)dpl - view_log_decode_CRC - 1; + j++) { + crc = calc_crc(crc, pcrc++, 6, 8); + if(crc == get_shifted_crc(pcrc)) { + crcptr = pcrc; + break; + } + } + } + } else { + for(int8_t j = 0; j < (int8_t)dpl - view_log_decode_CRC; j++) { + crc = calc_crc(crc, pcrc++, 7, 8); + if((view_log_decode_CRC == 1 && crc == *pcrc) || + (view_log_decode_CRC == 2 && + crc == (uint32_t)((*pcrc << 8) | *(pcrc + 1)))) { + crcptr = pcrc; + break; + } + } + } + } + } + ptr += view_log_arr_x; + if(max_width < VIEW_LOG_WIDTH_B) { + pre += snprintf(screen_buf + 1, 10, "%X-", pipe); + if(view_log_decode_PCF) { + pre += snprintf( + screen_buf + strlen(screen_buf), + 10, + "%02X%01X-", + *ptr >> 2, + ((*ptr & 3) << 1) | (*(ptr + 1) >> 7)); + } + } + if(view_log_decode_PCF) + add_to_str_hex_bytes_shift_9b(screen_buf, (char*)ptr++, count); + else + add_to_str_hex_bytes(screen_buf, (char*)ptr, count); + } + uint16_t y = 14 + i * 7; + canvas_draw_str(canvas, 3 * 5, y, screen_buf); + uint16_t x = snprintf(screen_buf, sizeof(screen_buf), "%d", page + i + 1); + canvas_draw_str(canvas, 0, y, screen_buf); + if(crcptr) { // 5x7 font, 9 lines + canvas_draw_str(canvas, x * 5, y, "="); + int n = crcptr - (uint8_t*)ptr; + if(n > -view_log_decode_CRC && n < count) { + int len; + x = (4 + pre) * 5; + if(n < 0) { + len = view_log_decode_CRC + n; + n = 0; + } else { + len = MIN(view_log_decode_CRC, count - n); + x += n * 2 * 5; + canvas_draw_line(canvas, x - 1, y, x - 1, y - 1); + } + canvas_draw_line(canvas, x - 1, y, n = x + len * 2 * 5 - 1, y); + canvas_draw_line(canvas, n, y, n, y - 1); + } + } else + canvas_draw_str(canvas, x * 5, y, ":"); + } + } + } else { + canvas_set_font(canvas, FontBatteryPercent); // 5x7 font, 9 lines + struct ADDRS* a; + if(what_to_do == 1) { + if(view_details_type && addrs_found.addr_count) { + a = &addrs_found; + canvas_draw_str(canvas, 0, 1 * 7, "Found addr:"); + } else { + a = &addrs_sniff; + canvas_draw_str(canvas, 0, 1 * 7, "Sniff prefix:"); + } + } else { + a = &addrs; + canvas_draw_str(canvas, 0, 1 * 7, "Addresses:"); + } + if(a->addr_count > 0) { + snprintf(screen_buf, sizeof(screen_buf), "P0: "); + add_to_str_hex_bytes(screen_buf, (char*)a->addr_P0, a->addr_len); + snprintf(screen_buf + strlen(screen_buf), 16, " - %d", found_total[0]); + canvas_draw_str(canvas, 0, 2 * 7, screen_buf); + } + if(a->addr_count > 1) { + snprintf(screen_buf, sizeof(screen_buf), "P1: "); + add_to_str_hex_bytes(screen_buf, (char*)a->addr_P1, a->addr_len); + snprintf(screen_buf + strlen(screen_buf), 16, " - %d", found_total[1]); + canvas_draw_str(canvas, 0, 3 * 7, screen_buf); + } + if(a->addr_count > 2) { + canvas_draw_str(canvas, 0, 4 * 7, "P2: "); + snprintf(screen_buf, sizeof(screen_buf), "%02X", a->addr_P2); + snprintf(screen_buf + strlen(screen_buf), 16, " - %d", found_total[2]); + canvas_draw_str(canvas, (4 + (a->addr_len - 1) * 2) * 5, 4 * 7, screen_buf); + } + if(a->addr_count > 3) { + canvas_draw_str(canvas, 0, 5 * 7, "P3: "); + snprintf(screen_buf, sizeof(screen_buf), "%02X", a->addr_P3); + snprintf(screen_buf + strlen(screen_buf), 16, " - %d", found_total[3]); + canvas_draw_str(canvas, (4 + (a->addr_len - 1) * 2) * 5, 5 * 7, screen_buf); + } + if(a->addr_count > 4) { + canvas_draw_str(canvas, 0, 6 * 7, "P4: "); + snprintf(screen_buf, sizeof(screen_buf), "%02X", a->addr_P4); + snprintf(screen_buf + strlen(screen_buf), 16, " - %d", found_total[4]); + canvas_draw_str(canvas, (4 + (a->addr_len - 1) * 2) * 5, 6 * 7, screen_buf); + } + if(a->addr_count > 5) { + canvas_draw_str(canvas, 0, 7 * 7, "P5: "); + snprintf(screen_buf, sizeof(screen_buf), "%02X", a->addr_P5); + snprintf(screen_buf + strlen(screen_buf), 16, " - %d", found_total[5]); + canvas_draw_str(canvas, (4 + (a->addr_len - 1) * 2) * 5, 7 * 7, screen_buf); + } + if(log_arr_idx) { + uint8_t* ptr = APP->log_arr + view_log_arr_idx * LOG_REC_SIZE; + uint8_t pktinfo = *(ptr + 1); + snprintf(screen_buf, 32, ">Ch: %d L: %d", *ptr & 0x7F, pktinfo >> 3); + if(*ptr & 0x80) { + strcat(screen_buf, " RAW"); + if(pktinfo & 0b100) { + snprintf( + screen_buf + strlen(screen_buf), + 16, + " ESB %s", + *(ptr + 2 + (pktinfo & 0b11) + 2) >> 2 != 0x33 ? "DPL" : ""); + } + } + canvas_draw_str(canvas, 0, 8 * 7, screen_buf); + } + screen_buf[0] = 'v'; + strcpy(screen_buf + 1, VERSION); + canvas_draw_str(canvas, 104, 7, screen_buf); + if(view_log_decode_PCF || view_log_decode_CRC) { + strcpy(screen_buf, "Decode: "); + if(view_log_decode_PCF) strcat(screen_buf, "ESB "); + if(view_log_decode_CRC == 1) + strcat(screen_buf, "CRC1"); + else if(view_log_decode_CRC == 2) + strcat(screen_buf, "CRC2"); + canvas_draw_str(canvas, 0, 64, screen_buf); + } + } + release_mutex((ValueMutex*)ctx, plugin_state); +} + +int32_t nrf24scan_app(void* p) { + UNUSED(p); + APP = malloc(sizeof(Nrf24Scan)); + APP->event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); + APP->plugin_state = malloc(sizeof(PluginState)); + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, APP->plugin_state, sizeof(PluginState))) { + furi_message_queue_free(APP->event_queue); + FURI_LOG_E(TAG, "cannot create mutex"); + free(APP->plugin_state); + return 255; + } + memset((uint8_t*)&addrs, 0, sizeof(addrs)); + memset((uint8_t*)&addrs_sniff, 0, sizeof(addrs_sniff)); + memset((uint8_t*)&addrs_found, 0, sizeof(addrs_found)); + nrf24_init(); + + // Set system callbacks + APP->view_port = view_port_alloc(); + view_port_draw_callback_set(APP->view_port, render_callback, &state_mutex); + view_port_input_callback_set(APP->view_port, input_callback, APP->event_queue); + + // Open GUI and register view_port + APP->gui = furi_record_open(RECORD_GUI); + gui_add_view_port(APP->gui, APP->view_port, GuiLayerFullscreen); + APP->notification = furi_record_open(RECORD_NOTIFICATION); + APP->storage = furi_record_open(RECORD_STORAGE); + storage_common_mkdir(APP->storage, SCAN_APP_PATH_FOLDER); + Stream* file_stream = file_stream_alloc(APP->storage); + FuriString* path = furi_string_alloc(); + furi_string_set(path, SCAN_APP_PATH_FOLDER); + furi_string_cat(path, "/"); + furi_string_cat(path, SNIFF_FILENAME); + if(file_stream_open(file_stream, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) { + uint8_t err = load_settings_file(file_stream); + if(!err) + strncpy( + addr_file_name, + furi_string_get_cstr(path) + sizeof(SCAN_APP_PATH_FOLDER), + sizeof(addr_file_name)); + else + snprintf(addr_file_name, sizeof(addr_file_name), "LOAD ERROR#%d", err); + } else { + strcpy(addr_file_name, "NONE"); + if(what_to_do == 1) { + addrs.addr_P0[0] = 0; + addrs.addr_P0[1] = 0x55; + addrs.addr_len = 2; + addrs.addr_count = 1; + view_log_decode_CRC = NRF_CRC = 2; + NRF_Payload_sniff_min = 0; // Min + } + } + file_stream_close(file_stream); + stream_free(file_stream); + furi_string_free(path); + allocate_log_array(); + + PluginEvent event; + for(bool processing = true; processing;) { + FuriStatus event_status = furi_message_queue_get(APP->event_queue, &event, 100); + PluginState* plugin_state = (PluginState*)acquire_mutex_block(&state_mutex); + + if(event_status == FuriStatusOk) { + // press events + if(event.type == EventTypeKey) { + //FURI_LOG_D(TAG, "Key: %d Type: %d Sec: %u", event.input.key, event.input.type, event.input.sequence); + switch(event.input.key) { + case InputKeyUp: + if(event.input.type == InputTypeShort || event.input.type == InputTypeRepeat) { + if(what_doing == 0) { + if(menu_selected > 0) + menu_selected--; + else + menu_selected = menu_selected_max; + } else if(what_doing == 1) { + view_log_arr_idx -= event.input.type == InputTypeRepeat ? 10 : 1; + if(view_log_arr_idx >= log_arr_idx) view_log_arr_idx = 0; + } else if(what_doing == 2) { + view_details_type = 0; + } + } + break; + case InputKeyDown: + if(event.input.type == InputTypeShort || event.input.type == InputTypeRepeat) { + if(what_doing == 0) { + if(menu_selected < menu_selected_max) + menu_selected++; + else + menu_selected = 0; + } else if(what_doing == 1) { + view_log_arr_idx += event.input.type == InputTypeRepeat ? 10 : 1; + if(view_log_arr_idx >= log_arr_idx) view_log_arr_idx = log_arr_idx - 1; + } else if(what_doing == 2) { + view_details_type = 1; + } + } + break; + case InputKeyLeft: + if(event.input.type == InputTypeShort || event.input.type == InputTypeRepeat) { + if(what_doing == 0) { + switch(menu_selected) { + case Menu_enter_channel: + NRF_channel -= event.input.type == InputTypeRepeat ? 10 : 1; + if(NRF_channel > MAX_CHANNEL) NRF_channel = MAX_CHANNEL; + break; + case Menu_enter_rate: + if(what_to_do == 1) { // SNIFF + NRF_Payload_sniff_min -= + event.input.type == InputTypeRepeat ? 10 : 1; + correct_NRF_Payload_sniff_min(); + } else { + NRF_Payload -= event.input.type == InputTypeRepeat ? 10 : 1; + if(NRF_Payload > 32) NRF_Payload = 0; + } + break; + case Menu_enter_scan_period: + find_channel_period -= event.input.type == InputTypeRepeat ? 10 : + 1; + if(find_channel_period < 0) find_channel_period = 0; + break; + case Menu_log: + if(--log_to_file < -1) log_to_file = 2; + break; + case Menu_ok: + if(--what_to_do > 3) what_to_do = 3; + break; + } + } else if(what_doing == 1) { + if(view_log_arr_x > 0) view_log_arr_x--; + } else if(what_doing == 2) { + //if(NRF_ESB == 0) + view_log_decode_PCF ^= 1; + } + } + break; + case InputKeyRight: + if(event.input.type == InputTypeShort || event.input.type == InputTypeRepeat) { + if(what_doing == 0) { + switch(menu_selected) { + case Menu_open_file: + save_settings ^= 1; + break; + case Menu_enter_channel: + NRF_channel += event.input.type == InputTypeRepeat ? 10 : 1; + if(NRF_channel > MAX_CHANNEL) NRF_channel = 0; + break; + case Menu_enter_rate: + if(what_to_do == 1) { // SNIFF + NRF_Payload_sniff_min += + event.input.type == InputTypeRepeat ? 10 : 1; + correct_NRF_Payload_sniff_min(); + } else { + NRF_Payload += event.input.type == InputTypeRepeat ? 10 : 1; + if(NRF_Payload > 32) NRF_Payload = 32; + } + break; + case Menu_enter_scan_period: + find_channel_period += event.input.type == InputTypeRepeat ? 10 : + 1; + break; + case Menu_log: + if(++log_to_file > 2) log_to_file = -1; + break; + case Menu_ok: + if(++what_to_do > 3) what_to_do = 0; + break; + } + } else if(what_doing == 1) { + if(view_log_arr_x < VIEW_LOG_MAX_X) view_log_arr_x++; + } else if(what_doing == 2) { + if(++view_log_decode_CRC > 2) view_log_decode_CRC = 0; + } + } + break; + case InputKeyOk: + if(event.input.type == InputTypeShort) { + if(what_doing == 0) { + switch(menu_selected) { + case Menu_open_file: + if(save_settings) { + write_to_log_file(APP->storage, true); + } else { + file_stream = file_stream_alloc(APP->storage); + if(select_settings_file(file_stream)) { + uint8_t err = load_settings_file(file_stream); + if(!err) + save_to_new_log = true; + else + snprintf( + addr_file_name, + sizeof(addr_file_name), + "LOAD ERROR#%d", + err); + file_stream_close(file_stream); + menu_selected = Menu_ok; + } + stream_free(file_stream); + } + break; + case Menu_enter_channel: + if(what_to_do == 1) { + if(NRF_ESB) + NRF_DPL = NRF_ESB = 0; + else + NRF_ESB = 1; + } else { + if(NRF_ESB) { + if(NRF_DPL) + NRF_DPL = NRF_ESB = 0; + else + NRF_DPL = 1; + } else + NRF_ESB = 1; + } + break; + case Menu_enter_rate: + NRF_rate++; + if(NRF_rate > 2) NRF_rate = 0; + break; + case Menu_enter_scan_period: + if(++NRF_CRC > 2) NRF_CRC = what_to_do == 1 ? 1 : 0; + break; + case Menu_ok: + if(what_to_do) { + if(addrs.addr_count || what_to_do == 1) { + if(log_to_file == -1) { + log_to_file = 0; + clear_log(); + save_to_new_log = true; + } else if(log_to_file == 1) + save_to_new_log = true; + start_scanning(); + if(!NRF_ERROR) what_doing = 1; + } + } else + what_doing = 1; + key_press_seq_ok = event.input.sequence; + break; + } + } else if(what_doing == 1) { + what_doing = 2; + } else if(what_doing == 2) { + what_doing = 1; + } + } else if(event.input.type == InputTypeLong) { + if(what_doing == 0) { + if(menu_selected == Menu_enter_channel) { + NRF_AA_OFF ^= 1; + key_press_seq_ok = event.input.sequence; + } else if(menu_selected == Menu_log) { // Log + if(log_arr_idx && (log_to_file == 1 || log_to_file == 2)) { + write_to_log_file(APP->storage, false); + clear_log(); + } + } + } else if(what_doing == 1 || what_doing == 2) { + nrf24_send_packet(); + } + } + break; + case InputKeyBack: + if(event.input.type == InputTypeLong) + processing = false; + else if(event.input.type == InputTypeShort) { + if(what_doing) what_doing--; + if(what_doing == 0) { + memcpy(&addrs, &addrs_found, sizeof(addrs)); + nrf24_set_idle(nrf24_HANDLE); + } + } + break; + default: + break; + } + } + } + if(what_doing && what_to_do) { + nrf24_read_newpacket(); + if(find_channel_period && + furi_get_tick() - start_time >= (uint32_t)find_channel_period * 1000UL) { + if(++NRF_channel > MAX_CHANNEL) NRF_channel = 0; + start_scanning(); + } + } + + view_port_update(APP->view_port); + release_mutex(&state_mutex, plugin_state); + } + nrf24_set_idle(nrf24_HANDLE); + if(log_arr_idx && (log_to_file == 1 || log_to_file == 2)) { + write_to_log_file(APP->storage, false); + } + nrf24_deinit(); + + view_port_enabled_set(APP->view_port, false); + gui_remove_view_port(APP->gui, APP->view_port); + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_NOTIFICATION); + furi_record_close(RECORD_STORAGE); + view_port_free(APP->view_port); + furi_message_queue_free(APP->event_queue); + free(APP->plugin_state); + if(APP->log_arr) free(APP->log_arr); + free(APP); + return 0; +} diff --git a/applications/plugins/nrf24scan/nrf24scan.h b/applications/plugins/nrf24scan/nrf24scan.h new file mode 100644 index 000000000..c3fa2891a --- /dev/null +++ b/applications/plugins/nrf24scan/nrf24scan.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} PluginEvent; + +typedef struct { + int x; + int y; +} PluginState; + +typedef struct { + Gui* gui; + FuriMessageQueue* event_queue; + PluginState* plugin_state; + ViewPort* view_port; + Storage* storage; + NotificationApp* notification; + uint8_t* log_arr; +} Nrf24Scan; diff --git a/applications/plugins/nrf24scan/nrf24scan_10px.png b/applications/plugins/nrf24scan/nrf24scan_10px.png new file mode 100644 index 000000000..348b35eca Binary files /dev/null and b/applications/plugins/nrf24scan/nrf24scan_10px.png differ diff --git a/applications/plugins/nrfsniff/README.md b/applications/plugins/nrfsniff/README.md new file mode 100644 index 000000000..89cc401c3 --- /dev/null +++ b/applications/plugins/nrfsniff/README.md @@ -0,0 +1,31 @@ +# flipperzero-nrf24 + +An [NRF24](https://www.sparkfun.com/datasheets/Components/SMD/nRF24L01Pluss_Preliminary_Product_Specification_v1_0.pdf) driver for the [Flipper Zero](https://flipperzero.one/) device. The NRF24 is a popular line of 2.4GHz radio transceivers from Nordic Semiconductors. This library is not currently complete, but functional. + +## Warning +This repo contains two Flipper Zero apps that utilize the NRF24 driver to sniff for NRF24 addresses and perform mousejack attacks. These apps are for **educational purposes** only. Please use this code responsibly and only use these apps on your own equipment. + +## Acknowledgments +The NRF24 sniffing technique was discovered and shared by Travis Goodspeed in [his blog](http://travisgoodspeed.blogspot.com/2011/02/promiscuity-is-nrf24l01s-duty.html). + +The mousejack vulnerabilities were discovered and reported by Marc Newlin, see [the blog](https://www.bastille.net/research/vulnerabilities/mousejack/technical-details) for technical details. + +Much of the driver code was inspired by [RadioHead's Arduino library](https://www.airspayce.com/mikem/arduino/RadioHead/classRH__NRF24.html). +Much of the mousejack code was inspired by the [Jackit project](https://github.com/insecurityofthings/jackit). +# +## PinOut from from NoComp/Frog + + +# Mousejack / NRF24 pinout by UberGuidoZ +2/A7 on FZ goes to MOSI/6 on nrf24l01
+3/A6 on FZ goes to MISO/7 on nrf24l01
+4/A4 on FZ goes to CSN/4 on nrf24l01
+5/B3 on FZ goes to SCK/5 on nrf24l01
+6/B2 on FZ goes to CE/3 on nrf24l01
+8/GND on FZ goes to GND/1 on nrf24l01
+9/3V3 on FZ goes to VCC/2 on nrf24l01
+IRQ/8 is left disconnected on nrf24l01 +![NRF_Pins](https://user-images.githubusercontent.com/57457139/178093717-39effd5c-ebe2-4253-b13c-70517d7902f9.png) +If the nRF module is acting a bit flakey, try adding a capacitor to the vcc/gnd lines! I've not tried the Plus model so it may have a bigger need for a cap. Otherwise, I haven't had any major issues. Anything from a 3.3 uF to 10 uF should do. (Watch your positive/negative placement! Negative to ground.) I learned if you wanna get fancy, include a 0.1 uF cap in parallel. The 3.3 uF to 10 uF will respond to slow freq changes while the 0.1 uF will respond to the high freq switching spikes that the larger one cannot. That said, a single 10 uF will likely suffice for the Mousejack attack. ¯\\\_(ツ)_/¯ +![NRF_Capacitor](https://user-images.githubusercontent.com/57457139/178169959-d030f9a6-d2ac-46af-af8b-470ff092c8a7.jpg) + diff --git a/applications/plugins/nrfsniff/application.fam b/applications/plugins/nrfsniff/application.fam new file mode 100644 index 000000000..e55ab3e9c --- /dev/null +++ b/applications/plugins/nrfsniff/application.fam @@ -0,0 +1,20 @@ +App( + appid="NRF24_Sniffer", + name="[NRF24] Sniffer", + apptype=FlipperAppType.EXTERNAL, + entry_point="nrfsniff_app", + cdefines=["APP_NRFSNIFF"], + requires=["gui"], + stack_size=2 * 1024, + order=60, + fap_icon="nrfsniff_10px.png", + fap_category="GPIO", + fap_private_libs=[ + Lib( + name="nrf24", + sources=[ + "nrf24.c", + ], + ), + ], +) diff --git a/applications/plugins/nrfsniff/lib/nrf24/nrf24.c b/applications/plugins/nrfsniff/lib/nrf24/nrf24.c new file mode 100644 index 000000000..8b3776445 --- /dev/null +++ b/applications/plugins/nrfsniff/lib/nrf24/nrf24.c @@ -0,0 +1,520 @@ +#include "nrf24.h" +#include +#include +#include +#include +#include + +void nrf24_init() { + furi_hal_spi_bus_handle_init(nrf24_HANDLE); + furi_hal_spi_acquire(nrf24_HANDLE); + furi_hal_gpio_init(nrf24_CE_PIN, GpioModeOutputPushPull, GpioPullUp, GpioSpeedVeryHigh); + furi_hal_gpio_write(nrf24_CE_PIN, false); +} + +void nrf24_deinit() { + furi_hal_spi_release(nrf24_HANDLE); + furi_hal_spi_bus_handle_deinit(nrf24_HANDLE); + furi_hal_gpio_write(nrf24_CE_PIN, false); + furi_hal_gpio_init(nrf24_CE_PIN, GpioModeAnalog, GpioPullNo, GpioSpeedLow); +} + +void nrf24_spi_trx( + FuriHalSpiBusHandle* handle, + uint8_t* tx, + uint8_t* rx, + uint8_t size, + uint32_t timeout) { + UNUSED(timeout); + furi_hal_gpio_write(handle->cs, false); + furi_hal_spi_bus_trx(handle, tx, rx, size, nrf24_TIMEOUT); + furi_hal_gpio_write(handle->cs, true); +} + +uint8_t nrf24_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data) { + uint8_t tx[2] = {W_REGISTER | (REGISTER_MASK & reg), data}; + uint8_t rx[2] = {0}; + nrf24_spi_trx(handle, tx, rx, 2, nrf24_TIMEOUT); + return rx[0]; +} + +uint8_t + nrf24_write_buf_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size) { + uint8_t tx[size + 1]; + uint8_t rx[size + 1]; + memset(rx, 0, size + 1); + tx[0] = W_REGISTER | (REGISTER_MASK & reg); + memcpy(&tx[1], data, size); + nrf24_spi_trx(handle, tx, rx, size + 1, nrf24_TIMEOUT); + return rx[0]; +} + +uint8_t nrf24_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size) { + uint8_t tx[size + 1]; + uint8_t rx[size + 1]; + memset(rx, 0, size + 1); + tx[0] = R_REGISTER | (REGISTER_MASK & reg); + memset(&tx[1], 0, size); + nrf24_spi_trx(handle, tx, rx, size + 1, nrf24_TIMEOUT); + memcpy(data, &rx[1], size); + return rx[0]; +} + +uint8_t nrf24_flush_rx(FuriHalSpiBusHandle* handle) { + uint8_t tx[] = {FLUSH_RX}; + uint8_t rx[] = {0}; + nrf24_spi_trx(handle, tx, rx, 1, nrf24_TIMEOUT); + return rx[0]; +} + +uint8_t nrf24_flush_tx(FuriHalSpiBusHandle* handle) { + uint8_t tx[] = {FLUSH_TX}; + uint8_t rx[] = {0}; + nrf24_spi_trx(handle, tx, rx, 1, nrf24_TIMEOUT); + return rx[0]; +} + +uint8_t nrf24_get_maclen(FuriHalSpiBusHandle* handle) { + uint8_t maclen; + nrf24_read_reg(handle, REG_SETUP_AW, &maclen, 1); + maclen &= 3; + return maclen + 2; +} + +uint8_t nrf24_set_maclen(FuriHalSpiBusHandle* handle, uint8_t maclen) { + assert(maclen > 1 && maclen < 6); + uint8_t status = 0; + status = nrf24_write_reg(handle, REG_SETUP_AW, maclen - 2); + return status; +} + +uint8_t nrf24_status(FuriHalSpiBusHandle* handle) { + uint8_t status; + uint8_t tx[] = {R_REGISTER | (REGISTER_MASK & REG_STATUS)}; + nrf24_spi_trx(handle, tx, &status, 1, nrf24_TIMEOUT); + return status; +} + +uint32_t nrf24_get_rate(FuriHalSpiBusHandle* handle) { + uint8_t setup = 0; + uint32_t rate = 0; + nrf24_read_reg(handle, REG_RF_SETUP, &setup, 1); + setup &= 0x28; + if(setup == 0x20) + rate = 250000; // 250kbps + else if(setup == 0x08) + rate = 2000000; // 2Mbps + else if(setup == 0x00) + rate = 1000000; // 1Mbps + + return rate; +} + +uint8_t nrf24_set_rate(FuriHalSpiBusHandle* handle, uint32_t rate) { + uint8_t r6 = 0; + uint8_t status = 0; + if(!rate) rate = 2000000; + + nrf24_read_reg(handle, REG_RF_SETUP, &r6, 1); // RF_SETUP register + r6 = r6 & (~0x28); // Clear rate fields. + if(rate == 2000000) + r6 = r6 | 0x08; + else if(rate == 1000000) + r6 = r6; + else if(rate == 250000) + r6 = r6 | 0x20; + + status = nrf24_write_reg(handle, REG_RF_SETUP, r6); // Write new rate. + return status; +} + +uint8_t nrf24_get_chan(FuriHalSpiBusHandle* handle) { + uint8_t channel = 0; + nrf24_read_reg(handle, REG_RF_CH, &channel, 1); + return channel; +} + +uint8_t nrf24_set_chan(FuriHalSpiBusHandle* handle, uint8_t chan) { + uint8_t status; + status = nrf24_write_reg(handle, REG_RF_CH, chan); + return status; +} + +uint8_t nrf24_get_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac) { + uint8_t size = 0; + uint8_t status = 0; + size = nrf24_get_maclen(handle); + status = nrf24_read_reg(handle, REG_RX_ADDR_P0, mac, size); + return status; +} + +uint8_t nrf24_set_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size) { + uint8_t status = 0; + uint8_t clearmac[] = {0, 0, 0, 0, 0}; + nrf24_set_maclen(handle, size); + nrf24_write_buf_reg(handle, REG_RX_ADDR_P0, clearmac, 5); + status = nrf24_write_buf_reg(handle, REG_RX_ADDR_P0, mac, size); + return status; +} + +uint8_t nrf24_get_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac) { + uint8_t size = 0; + uint8_t status = 0; + size = nrf24_get_maclen(handle); + status = nrf24_read_reg(handle, REG_TX_ADDR, mac, size); + return status; +} + +uint8_t nrf24_set_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size) { + uint8_t status = 0; + uint8_t clearmac[] = {0, 0, 0, 0, 0}; + nrf24_set_maclen(handle, size); + nrf24_write_buf_reg(handle, REG_TX_ADDR, clearmac, 5); + status = nrf24_write_buf_reg(handle, REG_TX_ADDR, mac, size); + return status; +} + +uint8_t nrf24_get_packetlen(FuriHalSpiBusHandle* handle) { + uint8_t len = 0; + nrf24_read_reg(handle, RX_PW_P0, &len, 1); + return len; +} + +uint8_t nrf24_set_packetlen(FuriHalSpiBusHandle* handle, uint8_t len) { + uint8_t status = 0; + status = nrf24_write_reg(handle, RX_PW_P0, len); + return status; +} + +uint8_t + nrf24_rxpacket(FuriHalSpiBusHandle* handle, uint8_t* packet, uint8_t* packetsize, bool full) { + uint8_t status = 0; + uint8_t size = 0; + uint8_t tx_pl_wid[] = {R_RX_PL_WID, 0}; + uint8_t rx_pl_wid[] = {0, 0}; + uint8_t tx_cmd[33] = {0}; // 32 max payload size + 1 for command + uint8_t tmp_packet[33] = {0}; + + status = nrf24_status(handle); + + if(status & 0x40) { + if(full) + size = nrf24_get_packetlen(handle); + else { + nrf24_spi_trx(handle, tx_pl_wid, rx_pl_wid, 2, nrf24_TIMEOUT); + size = rx_pl_wid[1]; + } + + tx_cmd[0] = R_RX_PAYLOAD; + nrf24_spi_trx(handle, tx_cmd, tmp_packet, size + 1, nrf24_TIMEOUT); + nrf24_write_reg(handle, REG_STATUS, 0x40); // clear bit. + memcpy(packet, &tmp_packet[1], size); + } else if(status == 0) { + nrf24_flush_rx(handle); + nrf24_write_reg(handle, REG_STATUS, 0x40); // clear bit. + } + + *packetsize = size; + return status; +} + +uint8_t nrf24_txpacket(FuriHalSpiBusHandle* handle, uint8_t* payload, uint8_t size, bool ack) { + uint8_t status = 0; + uint8_t tx[size + 1]; + uint8_t rx[size + 1]; + memset(tx, 0, size + 1); + memset(rx, 0, size + 1); + + if(!ack) + tx[0] = W_TX_PAYLOAD_NOACK; + else + tx[0] = W_TX_PAYLOAD; + + memcpy(&tx[1], payload, size); + nrf24_spi_trx(handle, tx, rx, size + 1, nrf24_TIMEOUT); + nrf24_set_tx_mode(handle); + + while(!(status & (TX_DS | MAX_RT))) status = nrf24_status(handle); + + if(status & MAX_RT) nrf24_flush_tx(handle); + + nrf24_set_idle(handle); + nrf24_write_reg(handle, REG_STATUS, TX_DS | MAX_RT); + return status & TX_DS; +} + +uint8_t nrf24_power_up(FuriHalSpiBusHandle* handle) { + uint8_t status = 0; + uint8_t cfg = 0; + nrf24_read_reg(handle, REG_CONFIG, &cfg, 1); + cfg = cfg | 2; + status = nrf24_write_reg(handle, REG_CONFIG, cfg); + furi_delay_ms(5000); + return status; +} + +uint8_t nrf24_set_idle(FuriHalSpiBusHandle* handle) { + uint8_t status = 0; + uint8_t cfg = 0; + nrf24_read_reg(handle, REG_CONFIG, &cfg, 1); + cfg &= 0xfc; // clear bottom two bits to power down the radio + status = nrf24_write_reg(handle, REG_CONFIG, cfg); + //nr204_write_reg(handle, REG_EN_RXADDR, 0x0); + furi_hal_gpio_write(nrf24_CE_PIN, false); + return status; +} + +uint8_t nrf24_set_rx_mode(FuriHalSpiBusHandle* handle) { + uint8_t status = 0; + uint8_t cfg = 0; + //status = nrf24_write_reg(handle, REG_CONFIG, 0x0F); // enable 2-byte CRC, PWR_UP, and PRIM_RX + nrf24_read_reg(handle, REG_CONFIG, &cfg, 1); + cfg |= 0x03; // PWR_UP, and PRIM_RX + status = nrf24_write_reg(handle, REG_CONFIG, cfg); + //nr204_write_reg(REG_EN_RXADDR, 0x03) // Set RX Pipe 0 and 1 + furi_hal_gpio_write(nrf24_CE_PIN, true); + furi_delay_ms(2000); + return status; +} + +uint8_t nrf24_set_tx_mode(FuriHalSpiBusHandle* handle) { + uint8_t status = 0; + uint8_t cfg = 0; + furi_hal_gpio_write(nrf24_CE_PIN, false); + nrf24_write_reg(handle, REG_STATUS, 0x30); + //status = nrf24_write_reg(handle, REG_CONFIG, 0x0E); // enable 2-byte CRC, PWR_UP + nrf24_read_reg(handle, REG_CONFIG, &cfg, 1); + cfg &= 0xfe; // disable PRIM_RX + cfg |= 0x02; // PWR_UP + status = nrf24_write_reg(handle, REG_CONFIG, cfg); + furi_hal_gpio_write(nrf24_CE_PIN, true); + furi_delay_ms(2); + return status; +} + +void nrf24_configure( + FuriHalSpiBusHandle* handle, + uint8_t rate, + uint8_t* srcmac, + uint8_t* dstmac, + uint8_t maclen, + uint8_t channel, + bool noack, + bool disable_aa) { + assert(channel <= 125); + assert(rate == 1 || rate == 2); + if(rate == 2) + rate = 8; // 2Mbps + else + rate = 0; // 1Mbps + + nrf24_write_reg(handle, REG_CONFIG, 0x00); // Stop nRF + nrf24_set_idle(handle); + nrf24_write_reg(handle, REG_STATUS, 0x1c); // clear interrupts + if(disable_aa) + nrf24_write_reg(handle, REG_EN_AA, 0x00); // Disable Shockburst + else + nrf24_write_reg(handle, REG_EN_AA, 0x1F); // Enable Shockburst + + nrf24_write_reg(handle, REG_DYNPD, 0x3F); // enable dynamic payload length on all pipes + if(noack) + nrf24_write_reg(handle, REG_FEATURE, 0x05); // disable payload-with-ack, enable noack + else { + nrf24_write_reg(handle, REG_CONFIG, 0x0C); // 2 byte CRC + nrf24_write_reg(handle, REG_FEATURE, 0x07); // enable dyn payload and ack + nrf24_write_reg( + handle, REG_SETUP_RETR, 0x1f); // 15 retries for AA, 500us auto retransmit delay + } + + nrf24_set_idle(handle); + nrf24_flush_rx(handle); + nrf24_flush_tx(handle); + + if(maclen) nrf24_set_maclen(handle, maclen); + if(srcmac) nrf24_set_src_mac(handle, srcmac, maclen); + if(dstmac) nrf24_set_dst_mac(handle, dstmac, maclen); + + nrf24_write_reg(handle, REG_RF_CH, channel); + nrf24_write_reg(handle, REG_RF_SETUP, rate); + furi_delay_ms(200); +} + +void nrf24_init_promisc_mode(FuriHalSpiBusHandle* handle, uint8_t channel, uint8_t rate) { + //uint8_t preamble[] = {0x55, 0x00}; // little endian + uint8_t preamble[] = {0xAA, 0x00}; // little endian + //uint8_t preamble[] = {0x00, 0x55}; // little endian + //uint8_t preamble[] = {0x00, 0xAA}; // little endian + nrf24_write_reg(handle, REG_CONFIG, 0x00); // Stop nRF + nrf24_write_reg(handle, REG_STATUS, 0x1c); // clear interrupts + nrf24_write_reg(handle, REG_DYNPD, 0x0); // disable shockburst + nrf24_write_reg(handle, REG_EN_AA, 0x00); // Disable Shockburst + nrf24_write_reg(handle, REG_FEATURE, 0x05); // disable payload-with-ack, enable noack + nrf24_set_maclen(handle, 2); // shortest address + nrf24_set_src_mac(handle, preamble, 2); // set src mac to preamble bits to catch everything + nrf24_set_packetlen(handle, 32); // set max packet length + nrf24_set_idle(handle); + nrf24_flush_rx(handle); + nrf24_flush_tx(handle); + nrf24_write_reg(handle, REG_RF_CH, channel); + nrf24_write_reg(handle, REG_RF_SETUP, rate); + + // prime for RX, no checksum + nrf24_write_reg(handle, REG_CONFIG, 0x03); // PWR_UP and PRIM_RX, disable AA and CRC + furi_hal_gpio_write(nrf24_CE_PIN, true); + furi_delay_ms(100); +} + +void hexlify(uint8_t* in, uint8_t size, char* out) { + memset(out, 0, size * 2); + for(int i = 0; i < size; i++) + snprintf(out + strlen(out), sizeof(out + strlen(out)), "%02X", in[i]); +} + +uint64_t bytes_to_int64(uint8_t* bytes, uint8_t size, bool bigendian) { + uint64_t ret = 0; + for(int i = 0; i < size; i++) + if(bigendian) + ret |= bytes[i] << ((size - 1 - i) * 8); + else + ret |= bytes[i] << (i * 8); + + return ret; +} + +void int64_to_bytes(uint64_t val, uint8_t* out, bool bigendian) { + for(int i = 0; i < 8; i++) { + if(bigendian) + out[i] = (val >> ((7 - i) * 8)) & 0xff; + else + out[i] = (val >> (i * 8)) & 0xff; + } +} + +uint32_t bytes_to_int32(uint8_t* bytes, bool bigendian) { + uint32_t ret = 0; + for(int i = 0; i < 4; i++) + if(bigendian) + ret |= bytes[i] << ((3 - i) * 8); + else + ret |= bytes[i] << (i * 8); + + return ret; +} + +void int32_to_bytes(uint32_t val, uint8_t* out, bool bigendian) { + for(int i = 0; i < 4; i++) { + if(bigendian) + out[i] = (val >> ((3 - i) * 8)) & 0xff; + else + out[i] = (val >> (i * 8)) & 0xff; + } +} + +uint64_t bytes_to_int16(uint8_t* bytes, bool bigendian) { + uint16_t ret = 0; + for(int i = 0; i < 2; i++) + if(bigendian) + ret |= bytes[i] << ((1 - i) * 8); + else + ret |= bytes[i] << (i * 8); + + return ret; +} + +void int16_to_bytes(uint16_t val, uint8_t* out, bool bigendian) { + for(int i = 0; i < 2; i++) { + if(bigendian) + out[i] = (val >> ((1 - i) * 8)) & 0xff; + else + out[i] = (val >> (i * 8)) & 0xff; + } +} + +// handle iffyness with preamble processing sometimes being a bit (literally) off +void alt_address_old(uint8_t* packet, uint8_t* altaddr) { + uint8_t macmess_hi_b[4]; + uint8_t macmess_lo_b[2]; + uint32_t macmess_hi; + uint16_t macmess_lo; + uint8_t preserved; + + // get first 6 bytes into 32-bit and 16-bit variables + memcpy(macmess_hi_b, packet, 4); + memcpy(macmess_lo_b, packet + 4, 2); + + macmess_hi = bytes_to_int32(macmess_hi_b, true); + + //preserve least 7 bits from hi that will be shifted down to lo + preserved = macmess_hi & 0x7f; + macmess_hi >>= 7; + + macmess_lo = bytes_to_int16(macmess_lo_b, true); + macmess_lo >>= 7; + macmess_lo = (preserved << 9) | macmess_lo; + int32_to_bytes(macmess_hi, macmess_hi_b, true); + int16_to_bytes(macmess_lo, macmess_lo_b, true); + memcpy(altaddr, &macmess_hi_b[1], 3); + memcpy(altaddr + 3, macmess_lo_b, 2); +} + +bool validate_address(uint8_t* addr) { + uint8_t bad[][3] = {{0x55, 0x55}, {0xAA, 0xAA}, {0x00, 0x00}, {0xFF, 0xFF}}; + for(int i = 0; i < 4; i++) + for(int j = 0; j < 2; j++) + if(!memcmp(addr + j * 2, bad[i], 2)) return false; + + return true; +} + +bool nrf24_sniff_address(FuriHalSpiBusHandle* handle, uint8_t maclen, uint8_t* address) { + bool found = false; + uint8_t packet[32] = {0}; + uint8_t packetsize; + //char printit[65]; + uint8_t status = 0; + status = nrf24_rxpacket(handle, packet, &packetsize, true); + if(status & 0x40) { + if(validate_address(packet)) { + for(int i = 0; i < maclen; i++) address[i] = packet[maclen - 1 - i]; + + /* + alt_address(packet, packet); + + for(i = 0; i < maclen; i++) + address[i + 5] = packet[maclen - 1 - i]; + */ + + //memcpy(address, packet, maclen); + //hexlify(packet, packetsize, printit); + found = true; + } + } + + return found; +} + +uint8_t nrf24_find_channel( + FuriHalSpiBusHandle* handle, + uint8_t* srcmac, + uint8_t* dstmac, + uint8_t maclen, + uint8_t rate, + uint8_t min_channel, + uint8_t max_channel, + bool autoinit) { + uint8_t ping_packet[] = {0x0f, 0x0f, 0x0f, 0x0f}; // this can be anything, we just need an ack + uint8_t ch = max_channel + 1; // means fail + nrf24_configure(handle, rate, srcmac, dstmac, maclen, 2, false, false); + for(ch = min_channel; ch <= max_channel + 1; ch++) { + nrf24_write_reg(handle, REG_RF_CH, ch); + if(nrf24_txpacket(handle, ping_packet, 4, true)) break; + } + + if(autoinit) { + FURI_LOG_D("nrf24", "initializing radio for channel %d", ch); + nrf24_configure(handle, rate, srcmac, dstmac, maclen, ch, false, false); + return ch; + } + + return ch; +} \ No newline at end of file diff --git a/applications/plugins/nrfsniff/lib/nrf24/nrf24.h b/applications/plugins/nrfsniff/lib/nrf24/nrf24.h new file mode 100644 index 000000000..3cfcfe089 --- /dev/null +++ b/applications/plugins/nrfsniff/lib/nrf24/nrf24.h @@ -0,0 +1,366 @@ +#pragma once +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define R_REGISTER 0x00 +#define W_REGISTER 0x20 +#define REGISTER_MASK 0x1F +#define ACTIVATE 0x50 +#define R_RX_PL_WID 0x60 +#define R_RX_PAYLOAD 0x61 +#define W_TX_PAYLOAD 0xA0 +#define W_TX_PAYLOAD_NOACK 0xB0 +#define W_ACK_PAYLOAD 0xA8 +#define FLUSH_TX 0xE1 +#define FLUSH_RX 0xE2 +#define REUSE_TX_PL 0xE3 +#define RF24_NOP 0xFF + +#define REG_CONFIG 0x00 +#define REG_EN_AA 0x01 +#define REG_EN_RXADDR 0x02 +#define REG_SETUP_AW 0x03 +#define REG_SETUP_RETR 0x04 +#define REG_DYNPD 0x1C +#define REG_FEATURE 0x1D +#define REG_RF_SETUP 0x06 +#define REG_STATUS 0x07 +#define REG_RX_ADDR_P0 0x0A +#define REG_RF_CH 0x05 +#define REG_TX_ADDR 0x10 + +#define RX_PW_P0 0x11 +#define TX_DS 0x20 +#define MAX_RT 0x10 + +#define nrf24_TIMEOUT 500 +#define nrf24_CE_PIN &gpio_ext_pb2 +#define nrf24_HANDLE &furi_hal_spi_bus_handle_external + +/* Low level API */ + +/** Write device register + * + * @param handle - pointer to FuriHalSpiHandle + * @param reg - register + * @param data - data to write + * + * @return device status + */ +uint8_t nrf24_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data); + +/** Write buffer to device register + * + * @param handle - pointer to FuriHalSpiHandle + * @param reg - register + * @param data - data to write + * @param size - size of data to write + * + * @return device status + */ +uint8_t nrf24_write_buf_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size); + +/** Read device register + * + * @param handle - pointer to FuriHalSpiHandle + * @param reg - register + * @param[out] data - pointer to data + * + * @return device status + */ +uint8_t nrf24_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size); + +/** Power up the radio for operation + * + * @param handle - pointer to FuriHalSpiHandle + * + * @return device status + */ +uint8_t nrf24_power_up(FuriHalSpiBusHandle* handle); + +/** Power down the radio + * + * @param handle - pointer to FuriHalSpiHandle + * + * @return device status + */ +uint8_t nrf24_set_idle(FuriHalSpiBusHandle* handle); + +/** Sets the radio to RX mode + * + * @param handle - pointer to FuriHalSpiHandle + * + * @return device status + */ +uint8_t nrf24_set_rx_mode(FuriHalSpiBusHandle* handle); + +/** Sets the radio to TX mode + * + * @param handle - pointer to FuriHalSpiHandle + * + * @return device status + */ +uint8_t nrf24_set_tx_mode(FuriHalSpiBusHandle* handle); + +/*=============================================================================================================*/ + +/* High level API */ + +/** Must call this before using any other nrf24 API + * + */ +void nrf24_init(); + +/** Must call this when we end using nrf24 device + * + */ +void nrf24_deinit(); + +/** Send flush rx command + * + * @param handle - pointer to FuriHalSpiHandle + * + * @return device status + */ +uint8_t nrf24_flush_rx(FuriHalSpiBusHandle* handle); + +/** Send flush tx command + * + * @param handle - pointer to FuriHalSpiHandle + * + * @return device status + */ +uint8_t nrf24_flush_tx(FuriHalSpiBusHandle* handle); + +/** Gets the RX packet length in data pipe 0 + * + * @param handle - pointer to FuriHalSpiHandle + * + * @return packet length in data pipe 0 + */ +uint8_t nrf24_get_packetlen(FuriHalSpiBusHandle* handle); + +/** Sets the RX packet length in data pipe 0 + * + * @param handle - pointer to FuriHalSpiHandle + * @param len - length to set + * + * @return device status + */ +uint8_t nrf24_set_packetlen(FuriHalSpiBusHandle* handle, uint8_t len); + +/** Gets configured length of MAC address + * + * @param handle - pointer to FuriHalSpiHandle + * + * @return MAC address length + */ +uint8_t nrf24_get_maclen(FuriHalSpiBusHandle* handle); + +/** Sets configured length of MAC address + * + * @param handle - pointer to FuriHalSpiHandle + * @param maclen - length to set MAC address to, must be greater than 1 and less than 6 + * + * @return MAC address length + */ +uint8_t nrf24_set_maclen(FuriHalSpiBusHandle* handle, uint8_t maclen); + +/** Gets the current status flags from the STATUS register + * + * @param handle - pointer to FuriHalSpiHandle + * + * @return status flags + */ +uint8_t nrf24_status(FuriHalSpiBusHandle* handle); + +/** Gets the current transfer rate + * + * @param handle - pointer to FuriHalSpiHandle + * + * @return transfer rate in bps + */ +uint32_t nrf24_get_rate(FuriHalSpiBusHandle* handle); + +/** Sets the transfer rate + * + * @param handle - pointer to FuriHalSpiHandle + * @param rate - the transfer rate in bps + * + * @return device status + */ +uint8_t nrf24_set_rate(FuriHalSpiBusHandle* handle, uint32_t rate); + +/** Gets the current channel + * In nrf24, the channel number is multiplied times 1MHz and added to 2400MHz to get the frequency + * + * @param handle - pointer to FuriHalSpiHandle + * + * @return channel + */ +uint8_t nrf24_get_chan(FuriHalSpiBusHandle* handle); + +/** Sets the channel + * + * @param handle - pointer to FuriHalSpiHandle + * @param frequency - the frequency in hertz + * + * @return device status + */ +uint8_t nrf24_set_chan(FuriHalSpiBusHandle* handle, uint8_t chan); + +/** Gets the source mac address + * + * @param handle - pointer to FuriHalSpiHandle + * @param[out] mac - the source mac address + * + * @return device status + */ +uint8_t nrf24_get_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac); + +/** Sets the source mac address + * + * @param handle - pointer to FuriHalSpiHandle + * @param mac - the mac address to set + * @param size - the size of the mac address (2 to 5) + * + * @return device status + */ +uint8_t nrf24_set_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size); + +/** Gets the dest mac address + * + * @param handle - pointer to FuriHalSpiHandle + * @param[out] mac - the source mac address + * + * @return device status + */ +uint8_t nrf24_get_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac); + +/** Sets the dest mac address + * + * @param handle - pointer to FuriHalSpiHandle + * @param mac - the mac address to set + * @param size - the size of the mac address (2 to 5) + * + * @return device status + */ +uint8_t nrf24_set_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size); + +/** Reads RX packet + * + * @param handle - pointer to FuriHalSpiHandle + * @param[out] packet - the packet contents + * @param[out] packetsize - size of the received packet + * @param full - boolean set to true, packet length is determined by RX_PW_P0 register, false it is determined by dynamic payload length command + * + * @return device status + */ +uint8_t + nrf24_rxpacket(FuriHalSpiBusHandle* handle, uint8_t* packet, uint8_t* packetsize, bool full); + +/** Sends TX packet + * + * @param handle - pointer to FuriHalSpiHandle + * @param packet - the packet contents + * @param size - packet size + * @param ack - boolean to determine whether an ACK is required for the packet or not + * + * @return device status + */ +uint8_t nrf24_txpacket(FuriHalSpiBusHandle* handle, uint8_t* payload, uint8_t size, bool ack); + +/** Configure the radio + * This is not comprehensive, but covers a lot of the common configuration options that may be changed + * @param handle - pointer to FuriHalSpiHandle + * @param rate - transfer rate in Mbps (1 or 2) + * @param srcmac - source mac address + * @param dstmac - destination mac address + * @param maclen - length of mac address + * @param channel - channel to tune to + * @param noack - if true, disable auto-acknowledge + * @param disable_aa - if true, disable ShockBurst + * + */ +void nrf24_configure( + FuriHalSpiBusHandle* handle, + uint8_t rate, + uint8_t* srcmac, + uint8_t* dstmac, + uint8_t maclen, + uint8_t channel, + bool noack, + bool disable_aa); + +/** Configures the radio for "promiscuous mode" and primes it for rx + * This is not an actual mode of the nrf24, but this function exploits a few bugs in the chip that allows it to act as if it were. + * See http://travisgoodspeed.blogspot.com/2011/02/promiscuity-is-nrf24l01s-duty.html for details. + * @param handle - pointer to FuriHalSpiHandle + * @param channel - channel to tune to + * @param rate - transfer rate in Mbps (1 or 2) + */ +void nrf24_init_promisc_mode(FuriHalSpiBusHandle* handle, uint8_t channel, uint8_t rate); + +/** Listens for a packet and returns first possible address sniffed + * Call this only after calling nrf24_init_promisc_mode + * @param handle - pointer to FuriHalSpiHandle + * @param maclen - length of target mac address + * @param[out] addresses - sniffed address + * + * @return success + */ +bool nrf24_sniff_address(FuriHalSpiBusHandle* handle, uint8_t maclen, uint8_t* address); + +/** Sends ping packet on each channel for designated tx mac looking for ack + * + * @param handle - pointer to FuriHalSpiHandle + * @param srcmac - source address + * @param dstmac - destination address + * @param maclen - length of address + * @param rate - transfer rate in Mbps (1 or 2) + * @param min_channel - channel to start with + * @param max_channel - channel to end at + * @param autoinit - if true, automatically configure radio for this channel + * + * @return channel that the address is listening on, if this value is above the max_channel param, it failed + */ +uint8_t nrf24_find_channel( + FuriHalSpiBusHandle* handle, + uint8_t* srcmac, + uint8_t* dstmac, + uint8_t maclen, + uint8_t rate, + uint8_t min_channel, + uint8_t max_channel, + bool autoinit); + +/** Converts 64 bit value into uint8_t array + * @param val - 64-bit integer + * @param[out] out - bytes out + * @param bigendian - if true, convert as big endian, otherwise little endian + */ +void int64_to_bytes(uint64_t val, uint8_t* out, bool bigendian); + +/** Converts 32 bit value into uint8_t array + * @param val - 32-bit integer + * @param[out] out - bytes out + * @param bigendian - if true, convert as big endian, otherwise little endian + */ +void int32_to_bytes(uint32_t val, uint8_t* out, bool bigendian); + +/** Converts uint8_t array into 32 bit value + * @param bytes - uint8_t array + * @param bigendian - if true, convert as big endian, otherwise little endian + * + * @return 32-bit value + */ +uint32_t bytes_to_int32(uint8_t* bytes, bool bigendian); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/plugins/nrfsniff/nrfsniff.c b/applications/plugins/nrfsniff/nrfsniff.c new file mode 100644 index 000000000..ce2836152 --- /dev/null +++ b/applications/plugins/nrfsniff/nrfsniff.c @@ -0,0 +1,459 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOGITECH_MAX_CHANNEL 85 +#define COUNT_THRESHOLD 2 +#define DEFAULT_SAMPLE_TIME 8000 +#define MAX_ADDRS 100 +#define MAX_CONFIRMED 32 + +#define NRFSNIFF_APP_PATH_FOLDER "/ext/nrfsniff" +#define NRFSNIFF_APP_FILENAME "addresses.txt" +#define TAG "nrfsniff" + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} PluginEvent; + +typedef struct { + int x; + int y; +} PluginState; + +char rate_text_fmt[] = "Transfer rate: %dMbps"; +char sample_text_fmt[] = "Sample Time: %d ms"; +char channel_text_fmt[] = "Channel: %d Sniffing: %s"; +char preamble_text_fmt[] = "Preamble: %02X"; +char sniff_text_fmt[] = "Found: %d Unique: %u"; +char addresses_header_text[] = "Address,rate"; +char sniffed_address_fmt[] = "%s,%d"; +char rate_text[46]; +char channel_text[38]; +char sample_text[32]; +char preamble_text[14]; +char sniff_text[38]; +char sniffed_address[14]; + +uint8_t target_channel = 0; +uint32_t found_count = 0; +uint32_t unique_saved_count = 0; +uint32_t sample_time = DEFAULT_SAMPLE_TIME; +uint8_t target_rate = 8; // rate can be either 8 (2Mbps) or 0 (1Mbps) +uint8_t target_preamble[] = {0xAA, 0x00}; +uint8_t sniffing_state = false; +char top_address[12]; + +uint8_t candidates[MAX_ADDRS][5] = {0}; // last 100 sniffed addresses +uint32_t counts[MAX_ADDRS]; +uint8_t confirmed[MAX_CONFIRMED][5] = {0}; // first 32 confirmed addresses +uint8_t confirmed_idx = 0; +uint32_t total_candidates = 0; +uint32_t candidate_idx = 0; + +static int get_addr_index(uint8_t* addr, uint8_t addr_size) { + for(uint32_t i = 0; i < total_candidates; i++) { + uint8_t* arr_item = candidates[i]; + if(!memcmp(arr_item, addr, addr_size)) return i; + } + + return -1; +} + +static int get_highest_idx() { + uint32_t highest = 0; + int highest_idx = 0; + for(uint32_t i = 0; i < total_candidates; i++) { + if(counts[i] > highest) { + highest = counts[i]; + highest_idx = i; + } + } + + return highest_idx; +} + +// if array is full, start over from beginning +static void insert_addr(uint8_t* addr, uint8_t addr_size) { + if(candidate_idx >= MAX_ADDRS) candidate_idx = 0; + + memcpy(candidates[candidate_idx], addr, addr_size); + counts[candidate_idx] = 1; + if(total_candidates < MAX_ADDRS) total_candidates++; + candidate_idx++; +} + +static void render_callback(Canvas* const canvas, void* ctx) { + uint8_t rate = 2; + char sniffing[] = "Yes"; + + const PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25); + if(plugin_state == NULL) { + return; + } + // border around the edge of the screen + canvas_draw_frame(canvas, 0, 0, 128, 64); + canvas_set_font(canvas, FontSecondary); + + if(target_rate == 0) rate = 1; + + if(!sniffing_state) strcpy(sniffing, "No"); + + snprintf(rate_text, sizeof(rate_text), rate_text_fmt, (int)rate); + snprintf(channel_text, sizeof(channel_text), channel_text_fmt, (int)target_channel, sniffing); + snprintf(sample_text, sizeof(sample_text), sample_text_fmt, (int)sample_time); + //snprintf(preamble_text, sizeof(preamble_text), preamble_text_fmt, target_preamble[0]); + snprintf(sniff_text, sizeof(sniff_text), sniff_text_fmt, found_count, unique_saved_count); + snprintf( + sniffed_address, sizeof(sniffed_address), sniffed_address_fmt, top_address, (int)rate); + canvas_draw_str_aligned(canvas, 10, 10, AlignLeft, AlignBottom, rate_text); + canvas_draw_str_aligned(canvas, 10, 20, AlignLeft, AlignBottom, sample_text); + canvas_draw_str_aligned(canvas, 10, 30, AlignLeft, AlignBottom, channel_text); + //canvas_draw_str_aligned(canvas, 10, 30, AlignLeft, AlignBottom, preamble_text); + canvas_draw_str_aligned(canvas, 10, 40, AlignLeft, AlignBottom, sniff_text); + canvas_draw_str_aligned(canvas, 30, 50, AlignLeft, AlignBottom, addresses_header_text); + canvas_draw_str_aligned(canvas, 30, 60, AlignLeft, AlignBottom, sniffed_address); + + release_mutex((ValueMutex*)ctx, plugin_state); +} + +static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + PluginEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void hexlify(uint8_t* in, uint8_t size, char* out) { + memset(out, 0, size * 2); + for(int i = 0; i < size; i++) + snprintf(out + strlen(out), sizeof(out + strlen(out)), "%02X", in[i]); +} + +static bool save_addr_to_file( + Storage* storage, + uint8_t* data, + uint8_t size, + NotificationApp* notification) { + size_t file_size = 0; + uint8_t linesize = 0; + char filepath[42] = {0}; + char addrline[14] = {0}; + char ending[4]; + uint8_t* file_contents; + uint8_t rate = 1; + Stream* stream = file_stream_alloc(storage); + + if(target_rate == 8) rate = 2; + snprintf(ending, sizeof(ending), ",%d\n", rate); + hexlify(data, size, addrline); + strcat(addrline, ending); + linesize = strlen(addrline); + strcpy(filepath, NRFSNIFF_APP_PATH_FOLDER); + strcat(filepath, "/"); + strcat(filepath, NRFSNIFF_APP_FILENAME); + stream_seek(stream, 0, StreamOffsetFromStart); + + // check if address already exists in file + if(file_stream_open(stream, filepath, FSAM_READ_WRITE, FSOM_OPEN_APPEND)) { + bool found = false; + file_size = stream_size(stream); + stream_seek(stream, 0, StreamOffsetFromStart); + if(file_size > 0) { + file_contents = malloc(file_size + 1); + memset(file_contents, 0, file_size + 1); + if(stream_read(stream, file_contents, file_size) > 0) { + char* line = strtok((char*)file_contents, "\n"); + + while(line != NULL) { + if(!memcmp(line, addrline, 12)) { + found = true; + break; + } + line = strtok(NULL, "\n"); + } + } + free(file_contents); + } + + if(found) { + FURI_LOG_I(TAG, "Address exists in file. Ending save process."); + stream_free(stream); + return false; + } else { + if(stream_write(stream, (uint8_t*)addrline, linesize) != linesize) { + FURI_LOG_I(TAG, "Failed to write bytes to file stream."); + stream_free(stream); + return false; + } else { + FURI_LOG_I(TAG, "Found a new address: %s", addrline); + FURI_LOG_I(TAG, "Save successful!"); + + notification_message(notification, &sequence_success); + + stream_free(stream); + unique_saved_count++; + return true; + } + } + } else { + FURI_LOG_I(TAG, "Cannot open file \"%s\"", filepath); + stream_free(stream); + return false; + } +} + +void alt_address(uint8_t* addr, uint8_t* altaddr) { + uint8_t macmess_hi_b[4]; + uint32_t macmess_hi; + uint8_t macmess_lo; + uint8_t preserved; + uint8_t tmpaddr[5]; + + // swap bytes + for(int i = 0; i < 5; i++) tmpaddr[i] = addr[4 - i]; + + // get address into 32-bit and 8-bit variables + memcpy(macmess_hi_b, tmpaddr, 4); + macmess_lo = tmpaddr[4]; + + macmess_hi = bytes_to_int32(macmess_hi_b, true); + + //preserve lowest bit from hi to shift to low + preserved = macmess_hi & 1; + macmess_hi >>= 1; + macmess_lo >>= 1; + macmess_lo = (preserved << 7) | macmess_lo; + int32_to_bytes(macmess_hi, macmess_hi_b, true); + memcpy(tmpaddr, macmess_hi_b, 4); + tmpaddr[4] = macmess_lo; + + // swap bytes back + for(int i = 0; i < 5; i++) altaddr[i] = tmpaddr[4 - i]; +} + +static bool previously_confirmed(uint8_t* addr) { + bool found = false; + for(int i = 0; i < MAX_CONFIRMED; i++) { + if(!memcmp(confirmed[i], addr, 5)) { + found = true; + break; + } + } + + return found; +} + +static void wrap_up(Storage* storage, NotificationApp* notification) { + uint8_t ch; + uint8_t addr[5]; + uint8_t altaddr[5]; + char trying[12]; + int idx; + uint8_t rate = 0; + if(target_rate == 8) rate = 2; + + nrf24_set_idle(nrf24_HANDLE); + + while(true) { + idx = get_highest_idx(); + if(counts[idx] < COUNT_THRESHOLD) break; + + counts[idx] = 0; + memcpy(addr, candidates[idx], 5); + hexlify(addr, 5, trying); + FURI_LOG_I(TAG, "trying address %s", trying); + ch = nrf24_find_channel(nrf24_HANDLE, addr, addr, 5, rate, 2, LOGITECH_MAX_CHANNEL, false); + FURI_LOG_I(TAG, "find_channel returned %d", (int)ch); + if(ch > LOGITECH_MAX_CHANNEL) { + alt_address(addr, altaddr); + hexlify(altaddr, 5, trying); + FURI_LOG_I(TAG, "trying alternate address %s", trying); + ch = nrf24_find_channel( + nrf24_HANDLE, altaddr, altaddr, 5, rate, 2, LOGITECH_MAX_CHANNEL, false); + FURI_LOG_I(TAG, "find_channel returned %d", (int)ch); + memcpy(addr, altaddr, 5); + } + + if(ch <= LOGITECH_MAX_CHANNEL) { + hexlify(addr, 5, top_address); + found_count++; + save_addr_to_file(storage, addr, 5, notification); + DOLPHIN_DEED(getRandomDeed()); + if(confirmed_idx < MAX_CONFIRMED) memcpy(confirmed[confirmed_idx++], addr, 5); + break; + } + } +} + +static void clear_cache() { + found_count = 0; + unique_saved_count = 0; + confirmed_idx = 0; + candidate_idx = 0; + target_channel = 2; + total_candidates = 0; + memset(candidates, 0, sizeof(candidates)); + memset(counts, 0, sizeof(counts)); + memset(confirmed, 0, sizeof(confirmed)); +} + +static void start_sniffing() { + nrf24_init_promisc_mode(nrf24_HANDLE, target_channel, target_rate); +} + +int32_t nrfsniff_app(void* p) { + UNUSED(p); + uint8_t address[5] = {0}; + uint32_t start = 0; + hexlify(address, 5, top_address); + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); + PluginState* plugin_state = malloc(sizeof(PluginState)); + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, plugin_state, sizeof(PluginState))) { + furi_message_queue_free(event_queue); + FURI_LOG_E(TAG, "cannot create mutex\r\n"); + free(plugin_state); + return 255; + } + + nrf24_init(); + + // Set system callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, render_callback, &state_mutex); + view_port_input_callback_set(view_port, input_callback, event_queue); + + // Open GUI and register view_port + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + + Storage* storage = furi_record_open(RECORD_STORAGE); + storage_common_mkdir(storage, NRFSNIFF_APP_PATH_FOLDER); + + PluginEvent event; + for(bool processing = true; processing;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + PluginState* plugin_state = (PluginState*)acquire_mutex_block(&state_mutex); + + if(event_status == FuriStatusOk) { + // press events + if(event.type == EventTypeKey) { + if(event.input.type == InputTypePress || + (event.input.type == InputTypeLong && event.input.key == InputKeyBack)) { + switch(event.input.key) { + case InputKeyUp: + // toggle rate 1/2Mbps + if(!sniffing_state) { + if(target_rate == 0) + target_rate = 8; + else + target_rate = 0; + } + break; + case InputKeyDown: + // toggle preamble + if(!sniffing_state) { + if(target_preamble[0] == 0x55) + target_preamble[0] = 0xAA; + else + target_preamble[0] = 0x55; + + nrf24_set_src_mac(nrf24_HANDLE, target_preamble, 2); + } + break; + case InputKeyRight: + // increment channel + //if(!sniffing_state && target_channel <= LOGITECH_MAX_CHANNEL) + // target_channel++; + sample_time += 500; + break; + case InputKeyLeft: + // decrement channel + //if(!sniffing_state && target_channel > 0) target_channel--; + if(sample_time > 500) sample_time -= 500; + break; + case InputKeyOk: + // toggle sniffing + sniffing_state = !sniffing_state; + if(sniffing_state) { + clear_cache(); + start_sniffing(); + start = furi_get_tick(); + } else + wrap_up(storage, notification); + break; + case InputKeyBack: + if(event.input.type == InputTypeLong) processing = false; + break; + default: + break; + } + } + } + } else { + // FURI_LOG_D(TAG, "osMessageQueue: event timeout"); + // event timeout + } + + if(sniffing_state) { + if(nrf24_sniff_address(nrf24_HANDLE, 5, address)) { + int idx; + uint8_t* top_addr; + if(!previously_confirmed(address)) { + idx = get_addr_index(address, 5); + if(idx == -1) + insert_addr(address, 5); + else + counts[idx]++; + + top_addr = candidates[get_highest_idx()]; + hexlify(top_addr, 5, top_address); + } + } + + if(furi_get_tick() - start >= sample_time) { + target_channel++; + if(target_channel > LOGITECH_MAX_CHANNEL) target_channel = 2; + { + wrap_up(storage, notification); + start_sniffing(); + } + + start = furi_get_tick(); + } + } + + view_port_update(view_port); + release_mutex(&state_mutex, plugin_state); + } + + clear_cache(); + sample_time = DEFAULT_SAMPLE_TIME; + target_rate = 8; // rate can be either 8 (2Mbps) or 0 (1Mbps) + sniffing_state = false; + nrf24_deinit(); + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_NOTIFICATION); + furi_record_close(RECORD_STORAGE); + view_port_free(view_port); + furi_message_queue_free(event_queue); + + return 0; +} diff --git a/applications/plugins/nrfsniff/nrfsniff_10px.png b/applications/plugins/nrfsniff/nrfsniff_10px.png new file mode 100644 index 000000000..348b35eca Binary files /dev/null and b/applications/plugins/nrfsniff/nrfsniff_10px.png differ diff --git a/applications/plugins/rc2014_coleco/LICENSE b/applications/plugins/rc2014_coleco/LICENSE new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/applications/plugins/rc2014_coleco/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/applications/plugins/rc2014_coleco/README.md b/applications/plugins/rc2014_coleco/README.md new file mode 100644 index 000000000..0667860e7 --- /dev/null +++ b/applications/plugins/rc2014_coleco/README.md @@ -0,0 +1,38 @@ +# RC2014 ColecoVision Controller for Flipper Zero + +A Flipper Zero application and [RC2014] module allowing the Flipper to be used as a controller for ColecoVision games on +the [RC2014]. + +![ui](ui.png) + +## Running ColecoVision Games on the RC2014 + +A full tutorial is out of scope here, but briefly, you will need a [RC2014] with J. B. Langston's [TMS9918A Video Card] +and [SN76489 Sound Card], as well as some way to launch ColecoVision ROMs. + +Note that if you're using the standard pageable ROM module (e.g. if you're using the stock Pro kit), you will need to +[modify it](https://github.com/jblang/TMS9918A/issues/12) in order for the TMS9918A module to work on the ColecoVision +port addresses. + +## Hardware Setup + +The [interface](interface) directory contains Eagle schematics for a RC2014 module that handles the controller port +addressing for two players, breaking out the 8 data line inputs as well as the mode select line. This can actually be +used for different controller implementations and is slightly more flexible than the actual [ColecoVision] spec. + +To use this with the Flipper Zero and this application, a GPIO board is needed to provide hardware multiplexing for the +data lines. A schematic for the GPIO board will be added to this repository soon. + +## Building the FAP + +1. Clone the [flipperzero-firmware] repository. +2. Create a symbolic link in `applications_user` named `coleco`, pointing to this repository. +3. Compile with `./fbt fap_coleco`. +4. Copy `build/f7-firmware-D/.extapps/coleco.fap` to `apps/Misc` on the SD card (directly or using [qFlipper]). + +[RC2014]: https://rc2014.co.uk/ +[TMS9918A Video Card]: https://github.com/jblang/TMS9918A +[SN76489 Sound Card]: https://github.com/jblang/SN76489 +[ColecoVision]: http://www.atarihq.com/danb/files/CV-Tech.txt +[flipperzero-firmware]: https://github.com/flipperdevices/flipperzero-firmware +[qFlipper]: https://flipperzero.one/update diff --git a/applications/plugins/rc2014_coleco/application.fam b/applications/plugins/rc2014_coleco/application.fam new file mode 100644 index 000000000..2a3900e19 --- /dev/null +++ b/applications/plugins/rc2014_coleco/application.fam @@ -0,0 +1,13 @@ +App( + appid="RC2014_Coleco", + name="RC2014 ColecoVision", + apptype=FlipperAppType.EXTERNAL, + entry_point="coleco_app", + cdefines=["APP_COLECO"], + requires=["gui"], + stack_size=1 * 1024, + order=35, + fap_icon="coleco_10px.png", + fap_icon_assets="icons", + fap_category="GPIO", +) diff --git a/applications/plugins/rc2014_coleco/coleco.c b/applications/plugins/rc2014_coleco/coleco.c new file mode 100644 index 000000000..6ae050633 --- /dev/null +++ b/applications/plugins/rc2014_coleco/coleco.c @@ -0,0 +1,366 @@ +#include +#include +#include +#include +#include "RC2014_Coleco_icons.h" + +#define CODE_0 0x0A +#define CODE_1 0x0D +#define CODE_2 0x07 +#define CODE_3 0x0C +#define CODE_4 0x02 +#define CODE_5 0x03 +#define CODE_6 0x0E +#define CODE_7 0x05 +#define CODE_8 0x01 +#define CODE_9 0x0B +#define CODE_H 0x06 +#define CODE_S 0x09 +#define CODE_N 0x0F + +const GpioPin* const pin_up = &gpio_ext_pa6; +const GpioPin* const pin_down = &gpio_ext_pc0; +const GpioPin* const pin_right = &gpio_ext_pb2; +const GpioPin* const pin_left = &gpio_ext_pc3; +const GpioPin* const pin_code0 = &gpio_ext_pa7; +const GpioPin* const pin_code1 = &gpio_ext_pa4; +const GpioPin* const pin_code2 = &ibutton_gpio; +const GpioPin* const pin_code3 = &gpio_ext_pc1; +const GpioPin* const pin_fire = &gpio_ext_pb3; +const GpioPin* const pin_alt = &gpio_usart_tx; + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} PluginEvent; + +typedef struct { + bool dpad; + int row; + int column; +} Coleco; + +static void render_callback(Canvas* const canvas, void* context) { + Coleco* coleco = acquire_mutex((ValueMutex*)context, 25); + if(coleco == NULL) { + return; + } + + if(coleco->dpad) { + canvas_draw_icon(canvas, 4, 16, &I_ColecoJoystick_sel_33x33); + canvas_draw_icon(canvas, 27, 52, &I_ColecoFire_sel_18x9); + } else { + const bool hvr = coleco->row == 0 && coleco->column < 2; + canvas_draw_icon( + canvas, 4, 16, hvr ? &I_ColecoJoystick_hvr_33x33 : &I_ColecoJoystick_33x33); + canvas_draw_icon(canvas, 27, 52, hvr ? &I_ColecoFire_hvr_18x9 : &I_ColecoFire_18x9); + } + + canvas_draw_icon( + canvas, + 27, + 4, + (coleco->row == 0 && coleco->column == 2) ? &I_ColecoAlt_hvr_18x9 : &I_ColecoAlt_18x9); + canvas_draw_icon( + canvas, + 49, + 44, + (coleco->row == 1 && coleco->column == 0) ? &I_Coleco1_hvr_17x17 : &I_Coleco1_17x17); + canvas_draw_icon( + canvas, + 49, + 24, + (coleco->row == 1 && coleco->column == 1) ? &I_Coleco2_hvr_17x17 : &I_Coleco2_17x17); + canvas_draw_icon( + canvas, + 49, + 4, + (coleco->row == 1 && coleco->column == 2) ? &I_Coleco3_hvr_17x17 : &I_Coleco3_17x17); + canvas_draw_icon( + canvas, + 69, + 44, + (coleco->row == 2 && coleco->column == 0) ? &I_Coleco4_hvr_17x17 : &I_Coleco4_17x17); + canvas_draw_icon( + canvas, + 69, + 24, + (coleco->row == 2 && coleco->column == 1) ? &I_Coleco5_hvr_17x17 : &I_Coleco5_17x17); + canvas_draw_icon( + canvas, + 69, + 4, + (coleco->row == 2 && coleco->column == 2) ? &I_Coleco6_hvr_17x17 : &I_Coleco6_17x17); + canvas_draw_icon( + canvas, + 89, + 44, + (coleco->row == 3 && coleco->column == 0) ? &I_Coleco7_hvr_17x17 : &I_Coleco7_17x17); + canvas_draw_icon( + canvas, + 89, + 24, + (coleco->row == 3 && coleco->column == 1) ? &I_Coleco8_hvr_17x17 : &I_Coleco8_17x17); + canvas_draw_icon( + canvas, + 89, + 4, + (coleco->row == 3 && coleco->column == 2) ? &I_Coleco9_hvr_17x17 : &I_Coleco9_17x17); + canvas_draw_icon( + canvas, + 109, + 44, + (coleco->row == 4 && coleco->column == 0) ? &I_ColecoStar_hvr_17x17 : &I_ColecoStar_17x17); + canvas_draw_icon( + canvas, + 109, + 24, + (coleco->row == 4 && coleco->column == 1) ? &I_Coleco0_hvr_17x17 : &I_Coleco0_17x17); + canvas_draw_icon( + canvas, + 109, + 4, + (coleco->row == 4 && coleco->column == 2) ? &I_ColecoPound_hvr_17x17 : + &I_ColecoPound_17x17); + + release_mutex((ValueMutex*)context, coleco); +} + +static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + PluginEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void coleco_write_code(uint8_t code) { + furi_hal_gpio_write(pin_code0, (code & 1)); + furi_hal_gpio_write(pin_code1, (code & 2)); + furi_hal_gpio_write(pin_code2, (code & 4)); + furi_hal_gpio_write(pin_code3, (code & 8)); +} + +static void coleco_gpio_init() { + // configure output pins + furi_hal_gpio_init(pin_up, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_init(pin_down, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_init(pin_right, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_init(pin_left, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_init(pin_code0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_init(pin_code1, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_init(pin_code2, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_init(pin_code3, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_init(pin_fire, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_init(pin_alt, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + + furi_hal_gpio_write(pin_up, true); + furi_hal_gpio_write(pin_down, true); + furi_hal_gpio_write(pin_right, true); + furi_hal_gpio_write(pin_left, true); + furi_hal_gpio_write(pin_fire, true); + furi_hal_gpio_write(pin_alt, true); + + coleco_write_code(CODE_N); +} + +static Coleco* coleco_alloc() { + Coleco* coleco = malloc(sizeof(Coleco)); + + coleco->dpad = false; + coleco->row = 0; + coleco->column = 1; + + return coleco; +} + +static void coleco_free(Coleco* coleco) { + furi_assert(coleco); + + free(coleco); +} + +int32_t coleco_app(void* p) { + UNUSED(p); + + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); + + Coleco* coleco = coleco_alloc(); + + ValueMutex coleco_mutex; + if(!init_mutex(&coleco_mutex, coleco, sizeof(Coleco))) { + FURI_LOG_E("Coleco", "cannot create mutex\r\n"); + coleco_free(coleco); + return 255; + } + + // set system callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, render_callback, &coleco_mutex); + view_port_input_callback_set(view_port, input_callback, event_queue); + + // open GUI and register view_port + Gui* gui = furi_record_open("gui"); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + coleco_gpio_init(); + furi_hal_power_enable_otg(); + + PluginEvent event; + for(bool processing = true; processing;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + + Coleco* coleco = (Coleco*)acquire_mutex_block(&coleco_mutex); + + if(event_status == FuriStatusOk) { + // press events + if(event.type == EventTypeKey) { + switch(event.input.key) { + case InputKeyUp: + if(coleco->dpad) { + if(event.input.type == InputTypePress) { + furi_hal_gpio_write(pin_up, false); + } else if(event.input.type == InputTypeRelease) { + furi_hal_gpio_write(pin_up, true); + } + } else { + if(event.input.type == InputTypePress && coleco->column < 2) { + coleco->column++; + coleco_write_code(CODE_N); + } + } + break; + case InputKeyDown: + if(coleco->dpad) { + if(event.input.type == InputTypePress) { + furi_hal_gpio_write(pin_down, false); + } else if(event.input.type == InputTypeRelease) { + furi_hal_gpio_write(pin_down, true); + } + } else { + if(event.input.type == InputTypePress && coleco->column > 0) { + coleco->column--; + coleco_write_code(CODE_N); + } + } + break; + case InputKeyRight: + if(coleco->dpad) { + if(event.input.type == InputTypePress) { + furi_hal_gpio_write(pin_right, false); + } else if(event.input.type == InputTypeRelease) { + furi_hal_gpio_write(pin_right, true); + } + } else { + if(event.input.type == InputTypePress && coleco->row < 4) { + coleco->row++; + coleco_write_code(CODE_N); + } + } + break; + case InputKeyLeft: + if(coleco->dpad) { + if(event.input.type == InputTypePress) { + furi_hal_gpio_write(pin_left, false); + } else if(event.input.type == InputTypeRelease) { + furi_hal_gpio_write(pin_left, true); + } + } else { + if(event.input.type == InputTypePress && coleco->row > 0) { + coleco->row--; + coleco_write_code(CODE_N); + } + } + break; + case InputKeyOk: + if(coleco->dpad) { + if(event.input.type == InputTypePress) { + furi_hal_gpio_write(pin_fire, false); + } else if(event.input.type == InputTypeRelease) { + furi_hal_gpio_write(pin_fire, true); + } + } else { + if(event.input.type == InputTypePress) { + if(coleco->row == 0) { + if(coleco->column == 2) { + furi_hal_gpio_write(pin_alt, false); + } else { + coleco->dpad = true; + } + } else if(coleco->row == 1) { + if(coleco->column == 0) { + coleco_write_code(CODE_1); + } else if(coleco->column == 1) { + coleco_write_code(CODE_2); + } else { + coleco_write_code(CODE_3); + } + } else if(coleco->row == 2) { + if(coleco->column == 0) { + coleco_write_code(CODE_4); + } else if(coleco->column == 1) { + coleco_write_code(CODE_5); + } else { + coleco_write_code(CODE_6); + } + } else if(coleco->row == 3) { + if(coleco->column == 0) { + coleco_write_code(CODE_7); + } else if(coleco->column == 1) { + coleco_write_code(CODE_8); + } else { + coleco_write_code(CODE_9); + } + } else if(coleco->row == 4) { + if(coleco->column == 0) { + coleco_write_code(CODE_S); + } else if(coleco->column == 1) { + coleco_write_code(CODE_0); + } else { + coleco_write_code(CODE_H); + } + } + } + if(event.input.type == InputTypeRelease) { + furi_hal_gpio_write(pin_alt, true); + coleco_write_code(CODE_N); + } + } + break; + case InputKeyBack: + if(event.input.type == InputTypePress) { + if(coleco->dpad) { + coleco->dpad = false; + } else { + processing = false; + } + } + break; + default: + break; + } + + view_port_update(view_port); + } + } else { + FURI_LOG_D("Coleco", "FuriMessageQueue: event timeout"); + } + + release_mutex(&coleco_mutex, coleco); + } + + furi_hal_power_disable_otg(); + + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close("gui"); + view_port_free(view_port); + furi_message_queue_free(event_queue); + delete_mutex(&coleco_mutex); + coleco_free(coleco); + return 0; +} diff --git a/applications/plugins/rc2014_coleco/coleco_10px.png b/applications/plugins/rc2014_coleco/coleco_10px.png new file mode 100644 index 000000000..d51652adc Binary files /dev/null and b/applications/plugins/rc2014_coleco/coleco_10px.png differ diff --git a/applications/plugins/rc2014_coleco/icons/Coleco0_17x17.png b/applications/plugins/rc2014_coleco/icons/Coleco0_17x17.png new file mode 100644 index 000000000..b53bc3b5f Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/Coleco0_17x17.png differ diff --git a/applications/plugins/rc2014_coleco/icons/Coleco0_hvr_17x17.png b/applications/plugins/rc2014_coleco/icons/Coleco0_hvr_17x17.png new file mode 100644 index 000000000..19627388e Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/Coleco0_hvr_17x17.png differ diff --git a/applications/plugins/rc2014_coleco/icons/Coleco1_17x17.png b/applications/plugins/rc2014_coleco/icons/Coleco1_17x17.png new file mode 100644 index 000000000..2c3977967 Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/Coleco1_17x17.png differ diff --git a/applications/plugins/rc2014_coleco/icons/Coleco1_hvr_17x17.png b/applications/plugins/rc2014_coleco/icons/Coleco1_hvr_17x17.png new file mode 100644 index 000000000..562c7e8db Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/Coleco1_hvr_17x17.png differ diff --git a/applications/plugins/rc2014_coleco/icons/Coleco2_17x17.png b/applications/plugins/rc2014_coleco/icons/Coleco2_17x17.png new file mode 100644 index 000000000..f8f18405f Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/Coleco2_17x17.png differ diff --git a/applications/plugins/rc2014_coleco/icons/Coleco2_hvr_17x17.png b/applications/plugins/rc2014_coleco/icons/Coleco2_hvr_17x17.png new file mode 100644 index 000000000..cac468981 Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/Coleco2_hvr_17x17.png differ diff --git a/applications/plugins/rc2014_coleco/icons/Coleco3_17x17.png b/applications/plugins/rc2014_coleco/icons/Coleco3_17x17.png new file mode 100644 index 000000000..3f2288392 Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/Coleco3_17x17.png differ diff --git a/applications/plugins/rc2014_coleco/icons/Coleco3_hvr_17x17.png b/applications/plugins/rc2014_coleco/icons/Coleco3_hvr_17x17.png new file mode 100644 index 000000000..c0015312a Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/Coleco3_hvr_17x17.png differ diff --git a/applications/plugins/rc2014_coleco/icons/Coleco4_17x17.png b/applications/plugins/rc2014_coleco/icons/Coleco4_17x17.png new file mode 100644 index 000000000..b3888910c Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/Coleco4_17x17.png differ diff --git a/applications/plugins/rc2014_coleco/icons/Coleco4_hvr_17x17.png b/applications/plugins/rc2014_coleco/icons/Coleco4_hvr_17x17.png new file mode 100644 index 000000000..63e086275 Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/Coleco4_hvr_17x17.png differ diff --git a/applications/plugins/rc2014_coleco/icons/Coleco5_17x17.png b/applications/plugins/rc2014_coleco/icons/Coleco5_17x17.png new file mode 100644 index 000000000..42eb3f59d Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/Coleco5_17x17.png differ diff --git a/applications/plugins/rc2014_coleco/icons/Coleco5_hvr_17x17.png b/applications/plugins/rc2014_coleco/icons/Coleco5_hvr_17x17.png new file mode 100644 index 000000000..b06fdfaf9 Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/Coleco5_hvr_17x17.png differ diff --git a/applications/plugins/rc2014_coleco/icons/Coleco6_17x17.png b/applications/plugins/rc2014_coleco/icons/Coleco6_17x17.png new file mode 100644 index 000000000..6ed3e239c Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/Coleco6_17x17.png differ diff --git a/applications/plugins/rc2014_coleco/icons/Coleco6_hvr_17x17.png b/applications/plugins/rc2014_coleco/icons/Coleco6_hvr_17x17.png new file mode 100644 index 000000000..4be93b365 Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/Coleco6_hvr_17x17.png differ diff --git a/applications/plugins/rc2014_coleco/icons/Coleco7_17x17.png b/applications/plugins/rc2014_coleco/icons/Coleco7_17x17.png new file mode 100644 index 000000000..2d200d71b Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/Coleco7_17x17.png differ diff --git a/applications/plugins/rc2014_coleco/icons/Coleco7_hvr_17x17.png b/applications/plugins/rc2014_coleco/icons/Coleco7_hvr_17x17.png new file mode 100644 index 000000000..8886dffef Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/Coleco7_hvr_17x17.png differ diff --git a/applications/plugins/rc2014_coleco/icons/Coleco8_17x17.png b/applications/plugins/rc2014_coleco/icons/Coleco8_17x17.png new file mode 100644 index 000000000..3905ef80d Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/Coleco8_17x17.png differ diff --git a/applications/plugins/rc2014_coleco/icons/Coleco8_hvr_17x17.png b/applications/plugins/rc2014_coleco/icons/Coleco8_hvr_17x17.png new file mode 100644 index 000000000..519ac1e97 Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/Coleco8_hvr_17x17.png differ diff --git a/applications/plugins/rc2014_coleco/icons/Coleco9_17x17.png b/applications/plugins/rc2014_coleco/icons/Coleco9_17x17.png new file mode 100644 index 000000000..a51739a1b Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/Coleco9_17x17.png differ diff --git a/applications/plugins/rc2014_coleco/icons/Coleco9_hvr_17x17.png b/applications/plugins/rc2014_coleco/icons/Coleco9_hvr_17x17.png new file mode 100644 index 000000000..206e0acd9 Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/Coleco9_hvr_17x17.png differ diff --git a/applications/plugins/rc2014_coleco/icons/ColecoAlt_18x9.png b/applications/plugins/rc2014_coleco/icons/ColecoAlt_18x9.png new file mode 100644 index 000000000..7e6853e52 Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/ColecoAlt_18x9.png differ diff --git a/applications/plugins/rc2014_coleco/icons/ColecoAlt_hvr_18x9.png b/applications/plugins/rc2014_coleco/icons/ColecoAlt_hvr_18x9.png new file mode 100644 index 000000000..6b15dcf7b Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/ColecoAlt_hvr_18x9.png differ diff --git a/applications/plugins/rc2014_coleco/icons/ColecoFire_18x9.png b/applications/plugins/rc2014_coleco/icons/ColecoFire_18x9.png new file mode 100644 index 000000000..8be499c21 Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/ColecoFire_18x9.png differ diff --git a/applications/plugins/rc2014_coleco/icons/ColecoFire_hvr_18x9.png b/applications/plugins/rc2014_coleco/icons/ColecoFire_hvr_18x9.png new file mode 100644 index 000000000..2b0d1d72c Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/ColecoFire_hvr_18x9.png differ diff --git a/applications/plugins/rc2014_coleco/icons/ColecoFire_sel_18x9.png b/applications/plugins/rc2014_coleco/icons/ColecoFire_sel_18x9.png new file mode 100644 index 000000000..383cd3a2d Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/ColecoFire_sel_18x9.png differ diff --git a/applications/plugins/rc2014_coleco/icons/ColecoJoystick_33x33.png b/applications/plugins/rc2014_coleco/icons/ColecoJoystick_33x33.png new file mode 100644 index 000000000..de4c574bc Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/ColecoJoystick_33x33.png differ diff --git a/applications/plugins/rc2014_coleco/icons/ColecoJoystick_hvr_33x33.png b/applications/plugins/rc2014_coleco/icons/ColecoJoystick_hvr_33x33.png new file mode 100644 index 000000000..fd653bfaf Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/ColecoJoystick_hvr_33x33.png differ diff --git a/applications/plugins/rc2014_coleco/icons/ColecoJoystick_sel_33x33.png b/applications/plugins/rc2014_coleco/icons/ColecoJoystick_sel_33x33.png new file mode 100644 index 000000000..ea01af395 Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/ColecoJoystick_sel_33x33.png differ diff --git a/applications/plugins/rc2014_coleco/icons/ColecoPound_17x17.png b/applications/plugins/rc2014_coleco/icons/ColecoPound_17x17.png new file mode 100644 index 000000000..10b46e0ca Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/ColecoPound_17x17.png differ diff --git a/applications/plugins/rc2014_coleco/icons/ColecoPound_hvr_17x17.png b/applications/plugins/rc2014_coleco/icons/ColecoPound_hvr_17x17.png new file mode 100644 index 000000000..784f3687c Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/ColecoPound_hvr_17x17.png differ diff --git a/applications/plugins/rc2014_coleco/icons/ColecoStar_17x17.png b/applications/plugins/rc2014_coleco/icons/ColecoStar_17x17.png new file mode 100644 index 000000000..3031c0baf Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/ColecoStar_17x17.png differ diff --git a/applications/plugins/rc2014_coleco/icons/ColecoStar_hvr_17x17.png b/applications/plugins/rc2014_coleco/icons/ColecoStar_hvr_17x17.png new file mode 100644 index 000000000..546922971 Binary files /dev/null and b/applications/plugins/rc2014_coleco/icons/ColecoStar_hvr_17x17.png differ diff --git a/applications/plugins/rc2014_coleco/interface/flipper-coleco.brd b/applications/plugins/rc2014_coleco/interface/flipper-coleco.brd new file mode 100644 index 000000000..47ed27322 --- /dev/null +++ b/applications/plugins/rc2014_coleco/interface/flipper-coleco.brd @@ -0,0 +1,2554 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +COLECOVISION +INTERFACE +PLAYER 1 +PLAYER 2 +74HCT138 +74HCT00 +74HCT541 +74HCT541 + + + +<b>TTL Devices, 74xx Series with US Symbols</b><p> +Based on the following sources: +<ul> +<li>Texas Instruments <i>TTL Data Book</i>&nbsp;&nbsp;&nbsp;Volume 1, 1996. +<li>TTL Data Book, Volume 2 , 1993 +<li>National Seminconductor Databook 1990, ALS/LS Logic +<li>ttl 74er digital data dictionary, ECA Electronic + Acustic GmbH, ISBN 3-88109-032-0 +<li>http://icmaster.com/ViewCompare.asp +</ul> +<author>Created by librarian@cadsoft.de</author> + + +<b>Dual In Line Package</b> + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>Dual In Line Package</b> + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>Dual In Line Package</b> + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +<b>Resistors, Capacitors, Inductors</b><p> +Based on the previous libraries: +<ul> +<li>r.lbr +<li>cap.lbr +<li>cap-fe.lbr +<li>captant.lbr +<li>polcap.lbr +<li>ipc-smd.lbr +</ul> +All SMD packages are defined according to the IPC specifications and CECC<p> +<author>Created by librarian@cadsoft.de</author><p> +<p> +for Electrolyt Capacitors see also :<p> +www.bccomponents.com <p> +www.panasonic.com<p> +www.kemet.com<p> +http://www.secc.co.jp/pdf/os_e/2004/e_os_all.pdf <b>(SANYO)</b> +<p> +for trimmer refence see : <u>www.electrospec-inc.com/cross_references/trimpotcrossref.asp</u><p> + +<table border=0 cellspacing=0 cellpadding=0 width="100%" cellpaddding=0> +<tr valign="top"> + +<! <td width="10">&nbsp;</td> +<td width="90%"> + +<b><font color="#0000FF" size="4">TRIM-POT CROSS REFERENCE</font></b> +<P> +<TABLE BORDER=0 CELLSPACING=1 CELLPADDING=2> + <TR> + <TD COLSPAN=8> + <FONT SIZE=3 FACE=ARIAL><B>RECTANGULAR MULTI-TURN</B></FONT> + </TD> + </TR> + <TR> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">BOURNS</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">BI&nbsp;TECH</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">DALE-VISHAY</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">PHILIPS/MEPCO</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">MURATA</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">PANASONIC</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">SPECTROL</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">MILSPEC</FONT> + </B> + </TD><TD>&nbsp;</TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3 > + 3005P<BR> + 3006P<BR> + 3006W<BR> + 3006Y<BR> + 3009P<BR> + 3009W<BR> + 3009Y<BR> + 3057J<BR> + 3057L<BR> + 3057P<BR> + 3057Y<BR> + 3059J<BR> + 3059L<BR> + 3059P<BR> + 3059Y<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 89P<BR> + 89W<BR> + 89X<BR> + 89PH<BR> + 76P<BR> + 89XH<BR> + 78SLT<BR> + 78L&nbsp;ALT<BR> + 56P&nbsp;ALT<BR> + 78P&nbsp;ALT<BR> + T8S<BR> + 78L<BR> + 56P<BR> + 78P<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + T18/784<BR> + 783<BR> + 781<BR> + -<BR> + -<BR> + -<BR> + 2199<BR> + 1697/1897<BR> + 1680/1880<BR> + 2187<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 8035EKP/CT20/RJ-20P<BR> + -<BR> + RJ-20X<BR> + -<BR> + -<BR> + -<BR> + 1211L<BR> + 8012EKQ&nbsp;ALT<BR> + 8012EKR&nbsp;ALT<BR> + 1211P<BR> + 8012EKJ<BR> + 8012EKL<BR> + 8012EKQ<BR> + 8012EKR<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 2101P<BR> + 2101W<BR> + 2101Y<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 2102L<BR> + 2102S<BR> + 2102Y<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + EVMCOG<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 43P<BR> + 43W<BR> + 43Y<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 40L<BR> + 40P<BR> + 40Y<BR> + 70Y-T602<BR> + 70L<BR> + 70P<BR> + 70Y<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + RT/RTR12<BR> + RT/RTR12<BR> + RT/RTR12<BR> + -<BR> + RJ/RJR12<BR> + RJ/RJR12<BR> + RJ/RJR12<BR></FONT> + </TD> + </TR> + <TR> + <TD COLSPAN=8>&nbsp; + </TD> + </TR> + <TR> + <TD COLSPAN=8> + <FONT SIZE=4 FACE=ARIAL><B>SQUARE MULTI-TURN</B></FONT> + </TD> + </TR> + <TR> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BOURN</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MURATA</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>SPECTROL</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MILSPEC</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3250L<BR> + 3250P<BR> + 3250W<BR> + 3250X<BR> + 3252P<BR> + 3252W<BR> + 3252X<BR> + 3260P<BR> + 3260W<BR> + 3260X<BR> + 3262P<BR> + 3262W<BR> + 3262X<BR> + 3266P<BR> + 3266W<BR> + 3266X<BR> + 3290H<BR> + 3290P<BR> + 3290W<BR> + 3292P<BR> + 3292W<BR> + 3292X<BR> + 3296P<BR> + 3296W<BR> + 3296X<BR> + 3296Y<BR> + 3296Z<BR> + 3299P<BR> + 3299W<BR> + 3299X<BR> + 3299Y<BR> + 3299Z<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 66P&nbsp;ALT<BR> + 66W&nbsp;ALT<BR> + 66X&nbsp;ALT<BR> + 66P&nbsp;ALT<BR> + 66W&nbsp;ALT<BR> + 66X&nbsp;ALT<BR> + -<BR> + 64W&nbsp;ALT<BR> + -<BR> + 64P&nbsp;ALT<BR> + 64W&nbsp;ALT<BR> + 64X&nbsp;ALT<BR> + 64P<BR> + 64W<BR> + 64X<BR> + 66X&nbsp;ALT<BR> + 66P&nbsp;ALT<BR> + 66W&nbsp;ALT<BR> + 66P<BR> + 66W<BR> + 66X<BR> + 67P<BR> + 67W<BR> + 67X<BR> + 67Y<BR> + 67Z<BR> + 68P<BR> + 68W<BR> + 68X<BR> + 67Y&nbsp;ALT<BR> + 67Z&nbsp;ALT<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 5050<BR> + 5091<BR> + 5080<BR> + 5087<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + T63YB<BR> + T63XB<BR> + -<BR> + -<BR> + -<BR> + 5887<BR> + 5891<BR> + 5880<BR> + -<BR> + -<BR> + -<BR> + T93Z<BR> + T93YA<BR> + T93XA<BR> + T93YB<BR> + T93XB<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 8026EKP<BR> + 8026EKW<BR> + 8026EKM<BR> + 8026EKP<BR> + 8026EKB<BR> + 8026EKM<BR> + 1309X<BR> + 1309P<BR> + 1309W<BR> + 8024EKP<BR> + 8024EKW<BR> + 8024EKN<BR> + RJ-9P/CT9P<BR> + RJ-9W<BR> + RJ-9X<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 3103P<BR> + 3103Y<BR> + 3103Z<BR> + 3103P<BR> + 3103Y<BR> + 3103Z<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 3105P/3106P<BR> + 3105W/3106W<BR> + 3105X/3106X<BR> + 3105Y/3106Y<BR> + 3105Z/3105Z<BR> + 3102P<BR> + 3102W<BR> + 3102X<BR> + 3102Y<BR> + 3102Z<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + EVMCBG<BR> + EVMCCG<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 55-1-X<BR> + 55-4-X<BR> + 55-3-X<BR> + 55-2-X<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 50-2-X<BR> + 50-4-X<BR> + 50-3-X<BR> + -<BR> + -<BR> + -<BR> + 64P<BR> + 64W<BR> + 64X<BR> + 64Y<BR> + 64Z<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + RT/RTR22<BR> + RT/RTR22<BR> + RT/RTR22<BR> + RT/RTR22<BR> + RJ/RJR22<BR> + RJ/RJR22<BR> + RJ/RJR22<BR> + RT/RTR26<BR> + RT/RTR26<BR> + RT/RTR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RT/RTR24<BR> + RT/RTR24<BR> + RT/RTR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> + <TR> + <TD COLSPAN=8>&nbsp; + </TD> + </TR> + <TR> + <TD COLSPAN=8> + <FONT SIZE=4 FACE=ARIAL><B>SINGLE TURN</B></FONT> + </TD> + </TR> + <TR> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BOURN</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MURATA</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>SPECTROL</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MILSPEC</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3323P<BR> + 3323S<BR> + 3323W<BR> + 3329H<BR> + 3329P<BR> + 3329W<BR> + 3339H<BR> + 3339P<BR> + 3339W<BR> + 3352E<BR> + 3352H<BR> + 3352K<BR> + 3352P<BR> + 3352T<BR> + 3352V<BR> + 3352W<BR> + 3362H<BR> + 3362M<BR> + 3362P<BR> + 3362R<BR> + 3362S<BR> + 3362U<BR> + 3362W<BR> + 3362X<BR> + 3386B<BR> + 3386C<BR> + 3386F<BR> + 3386H<BR> + 3386K<BR> + 3386M<BR> + 3386P<BR> + 3386S<BR> + 3386W<BR> + 3386X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 25P<BR> + 25S<BR> + 25RX<BR> + 82P<BR> + 82M<BR> + 82PA<BR> + -<BR> + -<BR> + -<BR> + 91E<BR> + 91X<BR> + 91T<BR> + 91B<BR> + 91A<BR> + 91V<BR> + 91W<BR> + 25W<BR> + 25V<BR> + 25P<BR> + -<BR> + 25S<BR> + 25U<BR> + 25RX<BR> + 25X<BR> + 72XW<BR> + 72XL<BR> + 72PM<BR> + 72RX<BR> + -<BR> + 72PX<BR> + 72P<BR> + 72RXW<BR> + 72RXL<BR> + 72X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + T7YB<BR> + T7YA<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + TXD<BR> + TYA<BR> + TYP<BR> + -<BR> + TYD<BR> + TX<BR> + -<BR> + 150SX<BR> + 100SX<BR> + 102T<BR> + 101S<BR> + 190T<BR> + 150TX<BR> + 101<BR> + -<BR> + -<BR> + 101SX<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + ET6P<BR> + ET6S<BR> + ET6X<BR> + RJ-6W/8014EMW<BR> + RJ-6P/8014EMP<BR> + RJ-6X/8014EMX<BR> + TM7W<BR> + TM7P<BR> + TM7X<BR> + -<BR> + 8017SMS<BR> + -<BR> + 8017SMB<BR> + 8017SMA<BR> + -<BR> + -<BR> + CT-6W<BR> + CT-6H<BR> + CT-6P<BR> + CT-6R<BR> + -<BR> + CT-6V<BR> + CT-6X<BR> + -<BR> + -<BR> + 8038EKV<BR> + -<BR> + 8038EKX<BR> + -<BR> + -<BR> + 8038EKP<BR> + 8038EKZ<BR> + 8038EKW<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + 3321H<BR> + 3321P<BR> + 3321N<BR> + 1102H<BR> + 1102P<BR> + 1102T<BR> + RVA0911V304A<BR> + -<BR> + RVA0911H413A<BR> + RVG0707V100A<BR> + RVA0607V(H)306A<BR> + RVA1214H213A<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 3104B<BR> + 3104C<BR> + 3104F<BR> + 3104H<BR> + -<BR> + 3104M<BR> + 3104P<BR> + 3104S<BR> + 3104W<BR> + 3104X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + EVMQ0G<BR> + EVMQIG<BR> + EVMQ3G<BR> + EVMS0G<BR> + EVMQ0G<BR> + EVMG0G<BR> + -<BR> + -<BR> + -<BR> + EVMK4GA00B<BR> + EVM30GA00B<BR> + EVMK0GA00B<BR> + EVM38GA00B<BR> + EVMB6<BR> + EVLQ0<BR> + -<BR> + EVMMSG<BR> + EVMMBG<BR> + EVMMAG<BR> + -<BR> + -<BR> + EVMMCS<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + EVMM1<BR> + -<BR> + -<BR> + EVMM0<BR> + -<BR> + -<BR> + EVMM3<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + 62-3-1<BR> + 62-1-2<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 67R<BR> + -<BR> + 67P<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 67X<BR> + 63V<BR> + 63S<BR> + 63M<BR> + -<BR> + -<BR> + 63H<BR> + 63P<BR> + -<BR> + -<BR> + 63X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + RJ/RJR50<BR> + RJ/RJR50<BR> + RJ/RJR50<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> +</TABLE> +<P>&nbsp;<P> +<TABLE BORDER=0 CELLSPACING=1 CELLPADDING=3> + <TR> + <TD COLSPAN=7> + <FONT color="#0000FF" SIZE=4 FACE=ARIAL><B>SMD TRIM-POT CROSS REFERENCE</B></FONT> + <P> + <FONT SIZE=4 FACE=ARIAL><B>MULTI-TURN</B></FONT> + </TD> + </TR> + <TR> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BOURNS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>TOCOS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>AUX/KYOCERA</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3224G<BR> + 3224J<BR> + 3224W<BR> + 3269P<BR> + 3269W<BR> + 3269X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 44G<BR> + 44J<BR> + 44W<BR> + 84P<BR> + 84W<BR> + 84X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + ST63Z<BR> + ST63Y<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + ST5P<BR> + ST5W<BR> + ST5X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> + <TR> + <TD COLSPAN=7>&nbsp; + </TD> + </TR> + <TR> + <TD COLSPAN=7> + <FONT SIZE=4 FACE=ARIAL><B>SINGLE TURN</B></FONT> + </TD> + </TR> + <TR> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BOURNS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>TOCOS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>AUX/KYOCERA</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3314G<BR> + 3314J<BR> + 3364A/B<BR> + 3364C/D<BR> + 3364W/X<BR> + 3313G<BR> + 3313J<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 23B<BR> + 23A<BR> + 21X<BR> + 21W<BR> + -<BR> + 22B<BR> + 22A<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + ST5YL/ST53YL<BR> + ST5YJ/5T53YJ<BR> + ST-23A<BR> + ST-22B<BR> + ST-22<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + ST-4B<BR> + ST-4A<BR> + -<BR> + -<BR> + -<BR> + ST-3B<BR> + ST-3A<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + EVM-6YS<BR> + EVM-1E<BR> + EVM-1G<BR> + EVM-1D<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + G4B<BR> + G4A<BR> + TR04-3S1<BR> + TRG04-2S1<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + DVR-43A<BR> + CVR-42C<BR> + CVR-42A/C<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> +</TABLE> +<P> +<FONT SIZE=4 FACE=ARIAL><B>ALT =&nbsp;ALTERNATE</B></FONT> +<P> + +&nbsp; +<P> +</td> +</tr> +</table> + + +<b>CAPACITOR</b><p> +grid 5 mm, outline 2.4 x 4.4 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + +<b>Pin Header Connectors</b><p> +<author>Created by librarian@cadsoft.de</author> + + +<b>PIN HEADER</b> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + + + + + + + + +<b>EAGLE Design Rules</b> +<p> +Die Standard-Design-Rules sind so gewählt, dass sie für +die meisten Anwendungen passen. Sollte ihre Platine +besondere Anforderungen haben, treffen Sie die erforderlichen +Einstellungen hier und speichern die Design Rules unter +einem neuen Namen ab. +<b>Laen's PCB Order Design Rules</b> +<p> +Please make sure your boards conform to these design rules. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Since Version 6.2.2 text objects can contain more than one line, +which will not be processed correctly with this version. + + + diff --git a/applications/plugins/rc2014_coleco/interface/flipper-coleco.sch b/applications/plugins/rc2014_coleco/interface/flipper-coleco.sch new file mode 100644 index 000000000..c29acd315 --- /dev/null +++ b/applications/plugins/rc2014_coleco/interface/flipper-coleco.sch @@ -0,0 +1,5482 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +<b>TTL Devices, 74xx Series with US Symbols</b><p> +Based on the following sources: +<ul> +<li>Texas Instruments <i>TTL Data Book</i>&nbsp;&nbsp;&nbsp;Volume 1, 1996. +<li>TTL Data Book, Volume 2 , 1993 +<li>National Seminconductor Databook 1990, ALS/LS Logic +<li>ttl 74er digital data dictionary, ECA Electronic + Acustic GmbH, ISBN 3-88109-032-0 +<li>http://icmaster.com/ViewCompare.asp +</ul> +<author>Created by librarian@cadsoft.de</author> + + +<b>Dual In Line Package</b> + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>Wide Small Outline package</b> 300 mil + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>VALUE +>NAME + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +<b>Leadless Chip Carrier</b><p> Ceramic Package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>Dual In Line Package</b> + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>Small Outline package</b> 150 mil + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>VALUE +>NAME + + + + + + + + + + + + + + + + + + +<b>Dual In Line Package</b> + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>Small Outline package</b> 150 mil + + + + + + + + + + + + + + + + + + + + + + + + + + +>VALUE +>NAME + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + + + + + +>NAME +GND +VCC + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + +Octal <b>BUFFER</b> and <b>LINE DRIVER</b>, 3-state + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +3-line to 8-line <b>DECODER/DEMULTIPLEXER</b> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Quad 2-input <b>NAND</b> gate + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +A15 +A0 +M1 +RST +CLK +INT +MREQ +WR +RO +IORQ +D0 +D7 +TX +RX + + +>NAME +GND +VCC + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +<b>Pin Header Connectors</b><p> +<author>Created by librarian@cadsoft.de</author> + + +<b>PIN HEADER</b> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + +<b>PIN HEADER</b> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + +<b>PIN HEADER</b> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +<b>Supply Symbols</b><p> + GND, VCC, 0V, +5V, -5V, etc.<p> + Please keep in mind, that these devices are necessary for the + automatic wiring of the supply signals.<p> + The pin name defined in the symbol is identical to the net which is to be wired automatically.<p> + In this library the device names are the same as the pin names of the symbols, therefore the correct signal names appear next to the supply symbols in the schematic.<p> + <author>Created by librarian@cadsoft.de</author> + + + + + +>VALUE + + + + + +>VALUE + + + + + +<b>SUPPLY SYMBOL</b> + + + + + + + + + + + + +<b>SUPPLY SYMBOL</b> + + + + + + + + + + + + + + +<b>Resistors, Capacitors, Inductors</b><p> +Based on the previous libraries: +<ul> +<li>r.lbr +<li>cap.lbr +<li>cap-fe.lbr +<li>captant.lbr +<li>polcap.lbr +<li>ipc-smd.lbr +</ul> +All SMD packages are defined according to the IPC specifications and CECC<p> +<author>Created by librarian@cadsoft.de</author><p> +<p> +for Electrolyt Capacitors see also :<p> +www.bccomponents.com <p> +www.panasonic.com<p> +www.kemet.com<p> +http://www.secc.co.jp/pdf/os_e/2004/e_os_all.pdf <b>(SANYO)</b> +<p> +for trimmer refence see : <u>www.electrospec-inc.com/cross_references/trimpotcrossref.asp</u><p> + +<table border=0 cellspacing=0 cellpadding=0 width="100%" cellpaddding=0> +<tr valign="top"> + +<! <td width="10">&nbsp;</td> +<td width="90%"> + +<b><font color="#0000FF" size="4">TRIM-POT CROSS REFERENCE</font></b> +<P> +<TABLE BORDER=0 CELLSPACING=1 CELLPADDING=2> + <TR> + <TD COLSPAN=8> + <FONT SIZE=3 FACE=ARIAL><B>RECTANGULAR MULTI-TURN</B></FONT> + </TD> + </TR> + <TR> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">BOURNS</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">BI&nbsp;TECH</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">DALE-VISHAY</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">PHILIPS/MEPCO</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">MURATA</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">PANASONIC</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">SPECTROL</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">MILSPEC</FONT> + </B> + </TD><TD>&nbsp;</TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3 > + 3005P<BR> + 3006P<BR> + 3006W<BR> + 3006Y<BR> + 3009P<BR> + 3009W<BR> + 3009Y<BR> + 3057J<BR> + 3057L<BR> + 3057P<BR> + 3057Y<BR> + 3059J<BR> + 3059L<BR> + 3059P<BR> + 3059Y<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 89P<BR> + 89W<BR> + 89X<BR> + 89PH<BR> + 76P<BR> + 89XH<BR> + 78SLT<BR> + 78L&nbsp;ALT<BR> + 56P&nbsp;ALT<BR> + 78P&nbsp;ALT<BR> + T8S<BR> + 78L<BR> + 56P<BR> + 78P<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + T18/784<BR> + 783<BR> + 781<BR> + -<BR> + -<BR> + -<BR> + 2199<BR> + 1697/1897<BR> + 1680/1880<BR> + 2187<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 8035EKP/CT20/RJ-20P<BR> + -<BR> + RJ-20X<BR> + -<BR> + -<BR> + -<BR> + 1211L<BR> + 8012EKQ&nbsp;ALT<BR> + 8012EKR&nbsp;ALT<BR> + 1211P<BR> + 8012EKJ<BR> + 8012EKL<BR> + 8012EKQ<BR> + 8012EKR<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 2101P<BR> + 2101W<BR> + 2101Y<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 2102L<BR> + 2102S<BR> + 2102Y<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + EVMCOG<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 43P<BR> + 43W<BR> + 43Y<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 40L<BR> + 40P<BR> + 40Y<BR> + 70Y-T602<BR> + 70L<BR> + 70P<BR> + 70Y<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + RT/RTR12<BR> + RT/RTR12<BR> + RT/RTR12<BR> + -<BR> + RJ/RJR12<BR> + RJ/RJR12<BR> + RJ/RJR12<BR></FONT> + </TD> + </TR> + <TR> + <TD COLSPAN=8>&nbsp; + </TD> + </TR> + <TR> + <TD COLSPAN=8> + <FONT SIZE=4 FACE=ARIAL><B>SQUARE MULTI-TURN</B></FONT> + </TD> + </TR> + <TR> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BOURN</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MURATA</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>SPECTROL</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MILSPEC</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3250L<BR> + 3250P<BR> + 3250W<BR> + 3250X<BR> + 3252P<BR> + 3252W<BR> + 3252X<BR> + 3260P<BR> + 3260W<BR> + 3260X<BR> + 3262P<BR> + 3262W<BR> + 3262X<BR> + 3266P<BR> + 3266W<BR> + 3266X<BR> + 3290H<BR> + 3290P<BR> + 3290W<BR> + 3292P<BR> + 3292W<BR> + 3292X<BR> + 3296P<BR> + 3296W<BR> + 3296X<BR> + 3296Y<BR> + 3296Z<BR> + 3299P<BR> + 3299W<BR> + 3299X<BR> + 3299Y<BR> + 3299Z<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 66P&nbsp;ALT<BR> + 66W&nbsp;ALT<BR> + 66X&nbsp;ALT<BR> + 66P&nbsp;ALT<BR> + 66W&nbsp;ALT<BR> + 66X&nbsp;ALT<BR> + -<BR> + 64W&nbsp;ALT<BR> + -<BR> + 64P&nbsp;ALT<BR> + 64W&nbsp;ALT<BR> + 64X&nbsp;ALT<BR> + 64P<BR> + 64W<BR> + 64X<BR> + 66X&nbsp;ALT<BR> + 66P&nbsp;ALT<BR> + 66W&nbsp;ALT<BR> + 66P<BR> + 66W<BR> + 66X<BR> + 67P<BR> + 67W<BR> + 67X<BR> + 67Y<BR> + 67Z<BR> + 68P<BR> + 68W<BR> + 68X<BR> + 67Y&nbsp;ALT<BR> + 67Z&nbsp;ALT<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 5050<BR> + 5091<BR> + 5080<BR> + 5087<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + T63YB<BR> + T63XB<BR> + -<BR> + -<BR> + -<BR> + 5887<BR> + 5891<BR> + 5880<BR> + -<BR> + -<BR> + -<BR> + T93Z<BR> + T93YA<BR> + T93XA<BR> + T93YB<BR> + T93XB<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 8026EKP<BR> + 8026EKW<BR> + 8026EKM<BR> + 8026EKP<BR> + 8026EKB<BR> + 8026EKM<BR> + 1309X<BR> + 1309P<BR> + 1309W<BR> + 8024EKP<BR> + 8024EKW<BR> + 8024EKN<BR> + RJ-9P/CT9P<BR> + RJ-9W<BR> + RJ-9X<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 3103P<BR> + 3103Y<BR> + 3103Z<BR> + 3103P<BR> + 3103Y<BR> + 3103Z<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 3105P/3106P<BR> + 3105W/3106W<BR> + 3105X/3106X<BR> + 3105Y/3106Y<BR> + 3105Z/3105Z<BR> + 3102P<BR> + 3102W<BR> + 3102X<BR> + 3102Y<BR> + 3102Z<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + EVMCBG<BR> + EVMCCG<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 55-1-X<BR> + 55-4-X<BR> + 55-3-X<BR> + 55-2-X<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 50-2-X<BR> + 50-4-X<BR> + 50-3-X<BR> + -<BR> + -<BR> + -<BR> + 64P<BR> + 64W<BR> + 64X<BR> + 64Y<BR> + 64Z<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + RT/RTR22<BR> + RT/RTR22<BR> + RT/RTR22<BR> + RT/RTR22<BR> + RJ/RJR22<BR> + RJ/RJR22<BR> + RJ/RJR22<BR> + RT/RTR26<BR> + RT/RTR26<BR> + RT/RTR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RT/RTR24<BR> + RT/RTR24<BR> + RT/RTR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> + <TR> + <TD COLSPAN=8>&nbsp; + </TD> + </TR> + <TR> + <TD COLSPAN=8> + <FONT SIZE=4 FACE=ARIAL><B>SINGLE TURN</B></FONT> + </TD> + </TR> + <TR> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BOURN</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MURATA</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>SPECTROL</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MILSPEC</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3323P<BR> + 3323S<BR> + 3323W<BR> + 3329H<BR> + 3329P<BR> + 3329W<BR> + 3339H<BR> + 3339P<BR> + 3339W<BR> + 3352E<BR> + 3352H<BR> + 3352K<BR> + 3352P<BR> + 3352T<BR> + 3352V<BR> + 3352W<BR> + 3362H<BR> + 3362M<BR> + 3362P<BR> + 3362R<BR> + 3362S<BR> + 3362U<BR> + 3362W<BR> + 3362X<BR> + 3386B<BR> + 3386C<BR> + 3386F<BR> + 3386H<BR> + 3386K<BR> + 3386M<BR> + 3386P<BR> + 3386S<BR> + 3386W<BR> + 3386X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 25P<BR> + 25S<BR> + 25RX<BR> + 82P<BR> + 82M<BR> + 82PA<BR> + -<BR> + -<BR> + -<BR> + 91E<BR> + 91X<BR> + 91T<BR> + 91B<BR> + 91A<BR> + 91V<BR> + 91W<BR> + 25W<BR> + 25V<BR> + 25P<BR> + -<BR> + 25S<BR> + 25U<BR> + 25RX<BR> + 25X<BR> + 72XW<BR> + 72XL<BR> + 72PM<BR> + 72RX<BR> + -<BR> + 72PX<BR> + 72P<BR> + 72RXW<BR> + 72RXL<BR> + 72X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + T7YB<BR> + T7YA<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + TXD<BR> + TYA<BR> + TYP<BR> + -<BR> + TYD<BR> + TX<BR> + -<BR> + 150SX<BR> + 100SX<BR> + 102T<BR> + 101S<BR> + 190T<BR> + 150TX<BR> + 101<BR> + -<BR> + -<BR> + 101SX<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + ET6P<BR> + ET6S<BR> + ET6X<BR> + RJ-6W/8014EMW<BR> + RJ-6P/8014EMP<BR> + RJ-6X/8014EMX<BR> + TM7W<BR> + TM7P<BR> + TM7X<BR> + -<BR> + 8017SMS<BR> + -<BR> + 8017SMB<BR> + 8017SMA<BR> + -<BR> + -<BR> + CT-6W<BR> + CT-6H<BR> + CT-6P<BR> + CT-6R<BR> + -<BR> + CT-6V<BR> + CT-6X<BR> + -<BR> + -<BR> + 8038EKV<BR> + -<BR> + 8038EKX<BR> + -<BR> + -<BR> + 8038EKP<BR> + 8038EKZ<BR> + 8038EKW<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + 3321H<BR> + 3321P<BR> + 3321N<BR> + 1102H<BR> + 1102P<BR> + 1102T<BR> + RVA0911V304A<BR> + -<BR> + RVA0911H413A<BR> + RVG0707V100A<BR> + RVA0607V(H)306A<BR> + RVA1214H213A<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 3104B<BR> + 3104C<BR> + 3104F<BR> + 3104H<BR> + -<BR> + 3104M<BR> + 3104P<BR> + 3104S<BR> + 3104W<BR> + 3104X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + EVMQ0G<BR> + EVMQIG<BR> + EVMQ3G<BR> + EVMS0G<BR> + EVMQ0G<BR> + EVMG0G<BR> + -<BR> + -<BR> + -<BR> + EVMK4GA00B<BR> + EVM30GA00B<BR> + EVMK0GA00B<BR> + EVM38GA00B<BR> + EVMB6<BR> + EVLQ0<BR> + -<BR> + EVMMSG<BR> + EVMMBG<BR> + EVMMAG<BR> + -<BR> + -<BR> + EVMMCS<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + EVMM1<BR> + -<BR> + -<BR> + EVMM0<BR> + -<BR> + -<BR> + EVMM3<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + 62-3-1<BR> + 62-1-2<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 67R<BR> + -<BR> + 67P<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 67X<BR> + 63V<BR> + 63S<BR> + 63M<BR> + -<BR> + -<BR> + 63H<BR> + 63P<BR> + -<BR> + -<BR> + 63X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + RJ/RJR50<BR> + RJ/RJR50<BR> + RJ/RJR50<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> +</TABLE> +<P>&nbsp;<P> +<TABLE BORDER=0 CELLSPACING=1 CELLPADDING=3> + <TR> + <TD COLSPAN=7> + <FONT color="#0000FF" SIZE=4 FACE=ARIAL><B>SMD TRIM-POT CROSS REFERENCE</B></FONT> + <P> + <FONT SIZE=4 FACE=ARIAL><B>MULTI-TURN</B></FONT> + </TD> + </TR> + <TR> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BOURNS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>TOCOS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>AUX/KYOCERA</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3224G<BR> + 3224J<BR> + 3224W<BR> + 3269P<BR> + 3269W<BR> + 3269X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 44G<BR> + 44J<BR> + 44W<BR> + 84P<BR> + 84W<BR> + 84X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + ST63Z<BR> + ST63Y<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + ST5P<BR> + ST5W<BR> + ST5X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> + <TR> + <TD COLSPAN=7>&nbsp; + </TD> + </TR> + <TR> + <TD COLSPAN=7> + <FONT SIZE=4 FACE=ARIAL><B>SINGLE TURN</B></FONT> + </TD> + </TR> + <TR> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BOURNS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>TOCOS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>AUX/KYOCERA</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3314G<BR> + 3314J<BR> + 3364A/B<BR> + 3364C/D<BR> + 3364W/X<BR> + 3313G<BR> + 3313J<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 23B<BR> + 23A<BR> + 21X<BR> + 21W<BR> + -<BR> + 22B<BR> + 22A<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + ST5YL/ST53YL<BR> + ST5YJ/5T53YJ<BR> + ST-23A<BR> + ST-22B<BR> + ST-22<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + ST-4B<BR> + ST-4A<BR> + -<BR> + -<BR> + -<BR> + ST-3B<BR> + ST-3A<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + EVM-6YS<BR> + EVM-1E<BR> + EVM-1G<BR> + EVM-1D<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + G4B<BR> + G4A<BR> + TR04-3S1<BR> + TRG04-2S1<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + DVR-43A<BR> + CVR-42C<BR> + CVR-42A/C<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> +</TABLE> +<P> +<FONT SIZE=4 FACE=ARIAL><B>ALT =&nbsp;ALTERNATE</B></FONT> +<P> + +&nbsp; +<P> +</td> +</tr> +</table> + + +<b>CAPACITOR</b> + + + + + + + + +>NAME +>VALUE + + + + + +<b>CAPACITOR</b> + + + + + + + + +>NAME +>VALUE + + + + + +<b>CAPACITOR</b> + + + + + + + + +>NAME +>VALUE + + + + + +<b>CAPACITOR</b><p> + + + + + + + + +>NAME +>VALUE + + + + + +<b>CAPACITOR</b> + + + + + + + + +>NAME +>VALUE + + + + + +<b>CAPACITOR</b> + + + + + + + + +>NAME +>VALUE + + + + + +<b>CAPACITOR</b> + + + + + + + + +>NAME +>VALUE + + + + + +<b>CAPACITOR</b> + + + + + + + + +>NAME +>VALUE + + + + + +<b>CAPACITOR</b> + + + + + + + + +>NAME +>VALUE + + + + + +<b>CAPACITOR</b> + + + + + + + + +>NAME +>VALUE + + + + + +<b>CAPACITOR</b> + + + + + + + + +>NAME +>VALUE + + + + + +<b>CAPACITOR</b> + + + + + + + + +>NAME +>VALUE + + + + + +<b>CAPACITOR</b> + + + + + + + + +>NAME +>VALUE + + + + + +<b>CAPACITOR</b> + + + + + + + + +>NAME +>VALUE + + + + + +<b>CAPACITOR</b> + + + + + + + + +>NAME +>VALUE + + + + + +<b>CAPACITOR</b><p> +grid 2.5 mm, outline 2.4 x 4.4 mm + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 2.5 mm, outline 2.5 x 5 mm + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 2.5 mm, outline 3 x 5 mm + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 2.5 mm, outline 4 x 5 mm + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 2.5 mm, outline 5 x 5 mm + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 2.5 mm, outline 6 x 5 mm + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 2.5 mm + 5 mm, outline 2.4 x 7 mm + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 2.5 + 5 mm, outline 2.5 x 7.5 mm + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 2.5 + 5 mm, outline 3.5 x 7.5 mm + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 2.5 + 5 mm, outline 4.5 x 7.5 mm + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 2.5 + 5 mm, outline 5.5 x 7.5 mm + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 5 mm, outline 2.4 x 4.4 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + +<b>CAPACITOR</b><p> +grid 5 mm, outline 2.5 x 7.5 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 5 mm, outline 4.5 x 7.5 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 5 mm, outline 3 x 7.5 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 5 mm, outline 5 x 7.5 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 5 mm, outline 5.5 x 7.5 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 5 mm, outline 7.5 x 7.5 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +Horizontal, grid 5 mm, outline 7.5 x 7.5 mm + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + +<b>CAPACITOR</b><p> +grid 7.5 mm, outline 3.2 x 10.3 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 7.5 mm, outline 4.2 x 10.3 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 7.5 mm, outline 5.2 x 10.6 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 10.2 mm, outline 4.3 x 13.3 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 10.2 mm, outline 5.4 x 13.3 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 10.2 mm, outline 6.4 x 13.3 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 10.2 mm + 15.2 mm, outline 6.2 x 18.4 mm + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 15 mm, outline 5.4 x 18.3 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 15 mm, outline 6.4 x 18.3 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 15 mm, outline 7.2 x 18.3 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 15 mm, outline 8.4 x 18.3 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 15 mm, outline 9.1 x 18.2 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 22.5 mm, outline 6.2 x 26.8 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 22.5 mm, outline 7.4 x 26.8 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 22.5 mm, outline 8.7 x 26.8 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 22.5 mm, outline 10.8 x 26.8 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 22.5 mm, outline 11.3 x 26.8 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 27.5 mm, outline 9.3 x 31.6 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 27.5 mm, outline 11.3 x 31.6 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 27.5 mm, outline 13.4 x 31.6 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 27.5 mm, outline 20.5 x 31.6 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 32.5 mm, outline 13.7 x 37.4 mm + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 32.5 mm, outline 16.2 x 37.4 mm + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 32.5 mm, outline 18.2 x 37.4 mm + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 37.5 mm, outline 19.2 x 41.8 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 37.5 mm, outline 20.3 x 41.8 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 5 mm, outline 3.5 x 7.5 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 37.5 mm, outline 15.5 x 41.8 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 7.5 mm, outline 6.3 x 10.6 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 27.5 mm, outline 15.4 x 31.6 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>CAPACITOR</b><p> +grid 27.5 mm, outline 17.3 x 31.6 mm + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>Ceramic Chip Capacitor KEMET 0204 reflow solder</b><p> +Metric Code Size 1005 + + + + +>NAME +>VALUE + + + + +<b>Ceramic Chip Capacitor KEMET 0603 reflow solder</b><p> +Metric Code Size 1608 + + + + +>NAME +>VALUE + + + + +<b>Ceramic Chip Capacitor KEMET 0805 reflow solder</b><p> +Metric Code Size 2012 + + + + +>NAME +>VALUE + + + + +<b>Ceramic Chip Capacitor KEMET 1206 reflow solder</b><p> +Metric Code Size 3216 + + + + +>NAME +>VALUE + + + + +<b>Ceramic Chip Capacitor KEMET 1210 reflow solder</b><p> +Metric Code Size 3225 + + + + +>NAME +>VALUE + + + + +<b>Ceramic Chip Capacitor KEMET 1812 reflow solder</b><p> +Metric Code Size 4532 + + + + +>NAME +>VALUE + + + + +<b>Ceramic Chip Capacitor KEMET 1825 reflow solder</b><p> +Metric Code Size 4564 + + + + +>NAME +>VALUE + + + + +<b>Ceramic Chip Capacitor KEMET 2220 reflow solder</b><p>Metric Code Size 5650 + + + + +>NAME +>VALUE + + + + +<b>Ceramic Chip Capacitor KEMET 2225 reflow solder</b><p>Metric Code Size 5664 + + + + +>NAME +>VALUE + + + + +<b> </b><p> +Source: http://www.vishay.com/docs/10129/hpc0201a.pdf + + +>NAME +>VALUE + + + +Source: http://www.avxcorp.com/docs/catalogs/cx5r.pdf + + +>NAME +>VALUE + + + + + + +<b>CAPACITOR</b><p> +Source: AVX .. aphvc.pdf + + + + +>NAME +>VALUE + + + + +<b>CAPACITOR</b><p> +Source: AVX .. aphvc.pdf + + + + +>NAME +>VALUE + + + + +<b>CAPACITOR</b> + + + +>NAME +>VALUE + + + + + + + + + + +>NAME +>VALUE + + + + + + + + +<B>CAPACITOR</B>, European symbol + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applications/plugins/rc2014_coleco/ui.png b/applications/plugins/rc2014_coleco/ui.png new file mode 100644 index 000000000..97c0ddc21 Binary files /dev/null and b/applications/plugins/rc2014_coleco/ui.png differ diff --git a/applications/plugins/tuning_fork/LICENSE b/applications/plugins/tuning_fork/LICENSE new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/applications/plugins/tuning_fork/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/applications/plugins/tuning_fork/README.md b/applications/plugins/tuning_fork/README.md new file mode 100644 index 000000000..5524eba3e --- /dev/null +++ b/applications/plugins/tuning_fork/README.md @@ -0,0 +1,30 @@ +# Tuning Fork + +Inspired by [Metronome](https://github.com/panki27/Metronome) + +A tuning fork for the [Flipper Zero](https://flipperzero.one/) device. +Allows to play different notes in different pitches. + +![screenshot](img/tuning_fork.gif) + +## Features +- Tuning forks (440Hz, 432Hz, etc.) +- Scientific pitch (..., 256Hz, 512Hz, 1024Hz, ...) +- Guitar Standard (6 strings) +- Guitar Drop D (6 strings) +- Guitar D (6 strings) +- Guitar Drop C (6 strings) +- Guitar Standard (7 strings) +- Bass Standard (4 strings) +- Bass Standard Tenor (4 strings) +- Bass Standard (5 strings) +- Bass Standard Tenor (5 strings) +- Bass Drop D (4 strings) +- Bass D (4 strings) +- Bass Drop A (5 strings) + +## Compiling + +``` +./fbt firmware_tuning_fork +``` diff --git a/applications/plugins/tuning_fork/application.fam b/applications/plugins/tuning_fork/application.fam new file mode 100644 index 000000000..47cef5364 --- /dev/null +++ b/applications/plugins/tuning_fork/application.fam @@ -0,0 +1,14 @@ +App( + appid="Tuning_Fork", + name="Tuning Fork", + apptype=FlipperAppType.EXTERNAL, + entry_point="tuning_fork_app", + cdefines=["APP_TUNING_FORM"], + requires=[ + "gui", + ], + fap_icon="tuning_fork_icon.png", + fap_category="Music", + stack_size=2 * 1024, + order=20, +) diff --git a/applications/plugins/tuning_fork/img/screenshot_1.png b/applications/plugins/tuning_fork/img/screenshot_1.png new file mode 100644 index 000000000..047279889 Binary files /dev/null and b/applications/plugins/tuning_fork/img/screenshot_1.png differ diff --git a/applications/plugins/tuning_fork/img/screenshot_2.png b/applications/plugins/tuning_fork/img/screenshot_2.png new file mode 100644 index 000000000..c31f37744 Binary files /dev/null and b/applications/plugins/tuning_fork/img/screenshot_2.png differ diff --git a/applications/plugins/tuning_fork/img/tuning_fork.gif b/applications/plugins/tuning_fork/img/tuning_fork.gif new file mode 100644 index 000000000..27bfe8cbe Binary files /dev/null and b/applications/plugins/tuning_fork/img/tuning_fork.gif differ diff --git a/applications/plugins/tuning_fork/notes.h b/applications/plugins/tuning_fork/notes.h new file mode 100644 index 000000000..c00b4f8ed --- /dev/null +++ b/applications/plugins/tuning_fork/notes.h @@ -0,0 +1,158 @@ +#ifndef NOTES +#define NOTES + +#define C0 16.35f +#define Cs0 17.32f +#define Db0 17.32f +#define D0 18.35f +#define Ds0 19.45f +#define Eb0 19.45f +#define E0 20.60f +#define F0 21.83f +#define Fs0 23.12f +#define Gb0 23.12f +#define G0 24.50f +#define Gs0 25.96f +#define Ab0 25.96f +#define A0 27.50f +#define As0 29.14f +#define Bb0 29.14f +#define B0 30.868f +#define C1 32.70f +#define Cs1 34.65f +#define Db1 34.65f +#define D1 36.71f +#define Ds1 38.89f +#define Eb1 38.89f +#define E1 41.203f +#define F1 43.65f +#define Fs1 46.25f +#define Gb1 46.25f +#define G1 49.00f +#define Gs1 51.91f +#define Ab1 51.91f +#define A1 55.00f +#define As1 58.27f +#define Bb1 58.27f +#define B1 61.74f +#define C2 65.41f +#define Cs2 69.30f +#define Db2 69.30f +#define D2 73.416f +#define Ds2 77.78f +#define Eb2 77.78f +#define E2 82.41f +#define F2 87.31f +#define Fs2 92.50f +#define Gb2 92.50f +#define G2 97.999f +#define Gs2 103.83f +#define Ab2 103.83f +#define A2 110.00f +#define As2 116.54f +#define Bb2 116.54f +#define B2 123.47f +#define C3 130.813f +#define Cs3 138.59f +#define Db3 138.59f +#define D3 146.83f +#define Ds3 155.56f +#define Eb3 155.56f +#define E3 164.81f +#define F3 174.61f +#define Fs3 185.00f +#define Gb3 185.00f +#define G3 196.00f +#define Gs3 207.65f +#define Ab3 207.65f +#define A3 220.00f +#define As3 233.08f +#define Bb3 233.08f +#define B3 246.94f +#define C4 261.63f +#define Cs4 277.18f +#define Db4 277.18f +#define D4 293.66f +#define Ds4 311.13f +#define Eb4 311.13f +#define E4 329.63f +#define F4 349.23f +#define Fs4 369.99f +#define Gb4 369.99f +#define G4 392.00f +#define Gs4 415.30f +#define Ab4 415.30f +#define A4 440.00f +#define As4 466.16f +#define Bb4 466.16f +#define B4 493.88f +#define C5 523.25f +#define Cs5 554.37f +#define Db5 554.37f +#define D5 587.33f +#define Ds5 622.25f +#define Eb5 622.25f +#define E5 659.25f +#define F5 698.46f +#define Fs5 739.99f +#define Gb5 739.99f +#define G5 783.99f +#define Gs5 830.61f +#define Ab5 830.61f +#define A5 880.00f +#define As5 932.33f +#define Bb5 932.33f +#define B5 987.77f +#define C6 1046.50f +#define Cs6 1108.73f +#define Db6 1108.73f +#define D6 1174.66f +#define Ds6 1244.51f +#define Eb6 1244.51f +#define E6 1318.51f +#define F6 1396.91f +#define Fs6 1479.98f +#define Gb6 1479.98f +#define G6 1567.98f +#define Gs6 1661.22f +#define Ab6 1661.22f +#define A6 1760.00f +#define As6 1864.66f +#define Bb6 1864.66f +#define B6 1975.53f +#define C7 2093.00f +#define Cs7 2217.46f +#define Db7 2217.46f +#define D7 2349.32f +#define Ds7 2489.02f +#define Eb7 2489.02f +#define E7 2637.02f +#define F7 2793.83f +#define Fs7 2959.96f +#define Gb7 2959.96f +#define G7 3135.96f +#define Gs7 3322.44f +#define Ab7 3322.44f +#define A7 3520.00f +#define As7 3729.31f +#define Bb7 3729.31f +#define B7 3951.07f +#define C8 4186.01f +#define Cs8 4434.92f +#define Db8 4434.92f +#define D8 4698.63f +#define Ds8 4978.03f +#define Eb8 4978.03f +#define E8 5274.04f +#define F8 5587.65f +#define Fs8 5919.91f +#define Gb8 5919.91f +#define G8 6271.93f +#define Gs8 6644.88f +#define Ab8 6644.88f +#define A8 7040.00f +#define As8 7458.62f +#define Bb8 7458.62f +#define B8 7902.13f + +#endif //NOTES diff --git a/applications/plugins/tuning_fork/tuning_fork.c b/applications/plugins/tuning_fork/tuning_fork.c new file mode 100644 index 000000000..ab39e1f14 --- /dev/null +++ b/applications/plugins/tuning_fork/tuning_fork.c @@ -0,0 +1,402 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "notes.h" +#include "tunings.h" + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} PluginEvent; + +enum Page { Tunings, Notes }; + +typedef struct { + bool playing; + enum Page page; + int current_tuning_note_index; + int current_tuning_index; + float volume; + TUNING tuning; +} TuningForkState; + +static TUNING current_tuning(TuningForkState* tuningForkState) { + return tuningForkState->tuning; +} + +static NOTE current_tuning_note(TuningForkState* tuningForkState) { + return current_tuning(tuningForkState).notes[tuningForkState->current_tuning_note_index]; +} + +static float current_tuning_note_freq(TuningForkState* tuningForkState) { + return current_tuning_note(tuningForkState).frequency; +} + +static void current_tuning_note_label(TuningForkState* tuningForkState, char* outNoteLabel) { + for(int i = 0; i < 20; ++i) { + outNoteLabel[i] = current_tuning_note(tuningForkState).label[i]; + } +} + +static void current_tuning_label(TuningForkState* tuningForkState, char* outTuningLabel) { + for(int i = 0; i < 20; ++i) { + outTuningLabel[i] = current_tuning(tuningForkState).label[i]; + } +} + +static void updateTuning(TuningForkState* tuning_fork_state) { + tuning_fork_state->tuning = TuningList[tuning_fork_state->current_tuning_index]; + tuning_fork_state->current_tuning_note_index = 0; +} + +static void next_tuning(TuningForkState* tuning_fork_state) { + if(tuning_fork_state->current_tuning_index == TUNINGS_COUNT - 1) { + tuning_fork_state->current_tuning_index = 0; + } else { + tuning_fork_state->current_tuning_index += 1; + } + updateTuning(tuning_fork_state); +} + +static void prev_tuning(TuningForkState* tuning_fork_state) { + if(tuning_fork_state->current_tuning_index - 1 < 0) { + tuning_fork_state->current_tuning_index = TUNINGS_COUNT - 1; + } else { + tuning_fork_state->current_tuning_index -= 1; + } + updateTuning(tuning_fork_state); +} + +static void next_note(TuningForkState* tuning_fork_state) { + if(tuning_fork_state->current_tuning_note_index == + current_tuning(tuning_fork_state).notes_length - 1) { + tuning_fork_state->current_tuning_note_index = 0; + } else { + tuning_fork_state->current_tuning_note_index += 1; + } +} + +static void prev_note(TuningForkState* tuning_fork_state) { + if(tuning_fork_state->current_tuning_note_index == 0) { + tuning_fork_state->current_tuning_note_index = + current_tuning(tuning_fork_state).notes_length - 1; + } else { + tuning_fork_state->current_tuning_note_index -= 1; + } +} + +static void increase_volume(TuningForkState* tuning_fork_state) { + if(tuning_fork_state->volume < 1.0f) { + tuning_fork_state->volume += 0.1f; + } +} + +static void decrease_volume(TuningForkState* tuning_fork_state) { + if(tuning_fork_state->volume > 0.0f) { + tuning_fork_state->volume -= 0.1f; + } +} + +static void play(TuningForkState* tuning_fork_state) { + furi_hal_speaker_start(current_tuning_note_freq(tuning_fork_state), tuning_fork_state->volume); +} + +static void stop() { + furi_hal_speaker_stop(); +} + +static void replay(TuningForkState* tuning_fork_state) { + stop(); + play(tuning_fork_state); +} + +static void render_callback(Canvas* const canvas, void* ctx) { + TuningForkState* tuning_fork_state = acquire_mutex((ValueMutex*)ctx, 25); + if(tuning_fork_state == NULL) { + return; + } + + string_t tempStr; + string_init(tempStr); + + canvas_draw_frame(canvas, 0, 0, 128, 64); + + canvas_set_font(canvas, FontPrimary); + + if(tuning_fork_state->page == Tunings) { + char tuningLabel[20]; + current_tuning_label(tuning_fork_state, tuningLabel); + string_printf(tempStr, "< %s >", tuningLabel); + canvas_draw_str_aligned( + canvas, 64, 28, AlignCenter, AlignCenter, string_get_cstr(tempStr)); + string_reset(tempStr); + } else { + char tuningLabel[20]; + current_tuning_label(tuning_fork_state, tuningLabel); + string_printf(tempStr, "%s", tuningLabel); + canvas_draw_str_aligned(canvas, 64, 8, AlignCenter, AlignCenter, string_get_cstr(tempStr)); + string_reset(tempStr); + + char tuningNoteLabel[20]; + current_tuning_note_label(tuning_fork_state, tuningNoteLabel); + string_printf(tempStr, "< %s >", tuningNoteLabel); + canvas_draw_str_aligned( + canvas, 64, 24, AlignCenter, AlignCenter, string_get_cstr(tempStr)); + string_reset(tempStr); + } + + canvas_set_font(canvas, FontSecondary); + elements_button_left(canvas, "Prev"); + elements_button_right(canvas, "Next"); + + if(tuning_fork_state->page == Notes) { + if(tuning_fork_state->playing) { + elements_button_center(canvas, "Stop "); + } else { + elements_button_center(canvas, "Play"); + } + } else { + elements_button_center(canvas, "Select"); + } + if(tuning_fork_state->page == Notes) { + elements_progress_bar(canvas, 8, 36, 112, tuning_fork_state->volume); + } + + string_clear(tempStr); + release_mutex((ValueMutex*)ctx, tuning_fork_state); +} + +static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + PluginEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void tuning_fork_state_init(TuningForkState* const tuning_fork_state) { + tuning_fork_state->playing = false; + tuning_fork_state->page = Tunings; + tuning_fork_state->volume = 1.0f; + tuning_fork_state->tuning = GuitarStandard6; + tuning_fork_state->current_tuning_index = 2; + tuning_fork_state->current_tuning_note_index = 0; +} + +int32_t tuning_fork_app() { + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); + + TuningForkState* tuning_fork_state = malloc(sizeof(TuningForkState)); + tuning_fork_state_init(tuning_fork_state); + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, tuning_fork_state, sizeof(TuningForkState))) { + FURI_LOG_E("TuningFork", "cannot create mutex\r\n"); + free(tuning_fork_state); + return 255; + } + + // Set system callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, render_callback, &state_mutex); + view_port_input_callback_set(view_port, input_callback, event_queue); + + Gui* gui = furi_record_open("gui"); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + PluginEvent event; + for(bool processing = true; processing;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + + TuningForkState* tuning_fork_state = (TuningForkState*)acquire_mutex_block(&state_mutex); + + if(event_status == FuriStatusOk) { + if(event.type == EventTypeKey) { + if(event.input.type == InputTypeShort) { + // push events + switch(event.input.key) { + case InputKeyUp: + if(tuning_fork_state->page == Notes) { + increase_volume(tuning_fork_state); + if(tuning_fork_state->playing) { + replay(tuning_fork_state); + } + } + break; + case InputKeyDown: + if(tuning_fork_state->page == Notes) { + decrease_volume(tuning_fork_state); + if(tuning_fork_state->playing) { + replay(tuning_fork_state); + } + } + break; + case InputKeyRight: + if(tuning_fork_state->page == Tunings) { + next_tuning(tuning_fork_state); + } else { + next_note(tuning_fork_state); + if(tuning_fork_state->playing) { + replay(tuning_fork_state); + } + } + break; + case InputKeyLeft: + if(tuning_fork_state->page == Tunings) { + prev_tuning(tuning_fork_state); + } else { + prev_note(tuning_fork_state); + if(tuning_fork_state->playing) { + replay(tuning_fork_state); + } + } + break; + case InputKeyOk: + if(tuning_fork_state->page == Tunings) { + tuning_fork_state->page = Notes; + } else { + tuning_fork_state->playing = !tuning_fork_state->playing; + if(tuning_fork_state->playing) { + play(tuning_fork_state); + } else { + stop(); + } + } + break; + case InputKeyBack: + if(tuning_fork_state->page == Tunings) { + processing = false; + } else { + tuning_fork_state->playing = false; + tuning_fork_state->current_tuning_note_index = 0; + stop(); + tuning_fork_state->page = Tunings; + } + break; + default: + break; + } + } else if(event.input.type == InputTypeLong) { + // hold events + switch(event.input.key) { + case InputKeyUp: + break; + case InputKeyDown: + break; + case InputKeyRight: + if(tuning_fork_state->page == Tunings) { + next_tuning(tuning_fork_state); + } else { + next_note(tuning_fork_state); + if(tuning_fork_state->playing) { + replay(tuning_fork_state); + } + } + + break; + case InputKeyLeft: + if(tuning_fork_state->page == Tunings) { + prev_tuning(tuning_fork_state); + } else { + prev_note(tuning_fork_state); + if(tuning_fork_state->playing) { + replay(tuning_fork_state); + } + } + + break; + case InputKeyOk: + break; + case InputKeyBack: + if(tuning_fork_state->page == Tunings) { + processing = false; + } else { + tuning_fork_state->playing = false; + stop(); + tuning_fork_state->page = Tunings; + tuning_fork_state->current_tuning_note_index = 0; + } + break; + default: + break; + } + } else if(event.input.type == InputTypeRepeat) { + // repeat events + switch(event.input.key) { + case InputKeyUp: + break; + case InputKeyDown: + break; + case InputKeyRight: + if(tuning_fork_state->page == Tunings) { + next_tuning(tuning_fork_state); + } else { + next_note(tuning_fork_state); + if(tuning_fork_state->playing) { + replay(tuning_fork_state); + } + } + + break; + case InputKeyLeft: + if(tuning_fork_state->page == Tunings) { + prev_tuning(tuning_fork_state); + } else { + prev_note(tuning_fork_state); + if(tuning_fork_state->playing) { + replay(tuning_fork_state); + } + } + + break; + case InputKeyOk: + break; + case InputKeyBack: + if(tuning_fork_state->page == Tunings) { + processing = false; + } else { + tuning_fork_state->playing = false; + stop(); + tuning_fork_state->page = Tunings; + tuning_fork_state->current_tuning_note_index = 0; + } + break; + default: + break; + } + } + } + } else { + FURI_LOG_D("TuningFork", "FuriMessageQueue: event timeout"); + } + + view_port_update(view_port); + release_mutex(&state_mutex, tuning_fork_state); + } + + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close("gui"); + view_port_free(view_port); + furi_message_queue_free(event_queue); + delete_mutex(&state_mutex); + furi_record_close(RECORD_NOTIFICATION); + free(tuning_fork_state); + + return 0; +} diff --git a/applications/plugins/tuning_fork/tuning_fork_icon.png b/applications/plugins/tuning_fork/tuning_fork_icon.png new file mode 100644 index 000000000..074d9d590 Binary files /dev/null and b/applications/plugins/tuning_fork/tuning_fork_icon.png differ diff --git a/applications/plugins/tuning_fork/tunings.h b/applications/plugins/tuning_fork/tunings.h new file mode 100644 index 000000000..14bf469fe --- /dev/null +++ b/applications/plugins/tuning_fork/tunings.h @@ -0,0 +1,151 @@ +#include "notes.h" + +#ifndef TUNINGS +#define TUNINGS + +typedef struct { + char label[20]; + float frequency; +} NOTE; + +typedef struct { + char label[20]; + int notes_length; + NOTE notes[20]; +} TUNING; + +const TUNING TuningForks = { + "Tuning forks", + 6, + { + {"Common A4 (440)", 440.00f}, + {"Sarti's A4 (436)", 436.00f}, + {"1858 A4 (435)", 435.00f}, + {"Verdi's A4 (432)", 432.00f}, + {"1750-1820 A4 (423.5)", 423.50f}, + {"Verdi's C4 (256.00)", 256.00f}, + }}; + +const TUNING ScientificPitch = { + "Scientific pitch", + 12, + {{"C0 (16Hz)", 16.0f}, + {"C1 (32Hz)", 32.0f}, + {"C2 (64Hz)", 64.0f}, + {"C3 (128Hz)", 128.0f}, + {"C4 (256Hz)", 256.0f}, + {"C5 (512Hz)", 512.0f}, + {"C6 (1024Hz)", 1024.0f}, + {"C7 (2048Hz)", 2048.0f}, + {"C8 (4096Hz)", 4096.0f}, + {"C9 (8192Hz)", 8192.0f}, + {"C10 (16384Hz)", 16384.0f}, + {"C11 (32768Hz)", 32768.0f}}}; + +const TUNING GuitarStandard6 = { + "Guitar Standard 6", + 6, + {{"String 1", E4}, + {"String 2", B3}, + {"String 3", G3}, + {"String 4", D3}, + {"String 5", A2}, + {"String 6", E2}}}; + +const TUNING GuitarDropD6 = { + "Guitar Drop D 6", + 6, + {{"String 1", E4}, + {"String 2", B3}, + {"String 3", G3}, + {"String 4", D3}, + {"String 5", A2}, + {"String 6", D2}}}; + +const TUNING GuitarD6 = { + "Guitar D 6", + 6, + {{"String 1", D4}, + {"String 2", A3}, + {"String 3", F3}, + {"String 4", C3}, + {"String 5", G2}, + {"String 6", D2}}}; + +const TUNING GuitarDropC6 = { + "Guitar Drop C 6", + 6, + {{"String 1", D4}, + {"String 2", A3}, + {"String 3", F3}, + {"String 4", C3}, + {"String 5", G2}, + {"String 6", C2}}}; + +const TUNING GuitarStandard7 = { + "Guitar Standard 7", + 7, + {{"String 1", E4}, + {"String 2", B3}, + {"String 3", G3}, + {"String 4", D3}, + {"String 5", A2}, + {"String 6", E2}, + {"String 7", B1}}}; + +const TUNING BassStandard4 = { + "Bass Standard 4", + 4, + {{"String 1", G2}, {"String 2", D2}, {"String 3", A1}, {"String 4", E1}}}; + +const TUNING BassStandardTenor4 = { + "Bass Stand Tenor 4", + 4, + {{"String 1", C3}, {"String 2", G2}, {"String 3", D2}, {"String 4", A1}}}; + +const TUNING BassStandard5 = { + "Bass Standard 5", + 5, + {{"String 1", G2}, {"String 2", D2}, {"String 3", A1}, {"String 4", E1}, {"String 5", B0}}}; + +const TUNING BassStandardTenor5 = { + "Bass Stand Tenor 5", + 5, + {{"String 1", C3}, {"String 2", G2}, {"String 3", D2}, {"String 4", A1}, {"String 5", E1}}}; + +const TUNING BassDropD4 = { + "Bass Drop D 4", + 4, + {{"String 1", G2}, {"String 2", D2}, {"String 3", A1}, {"String 4", D1}}}; + +const TUNING BassD4 = { + "Bass D 4", + 4, + {{"String 1", F2}, {"String 2", C2}, {"String 3", G1}, {"String 4", D1}}}; + +const TUNING BassDropA5 = { + "Bass Drop A 5", + 5, + {{"String 1", G2}, {"String 2", D2}, {"String 3", A1}, {"String 4", E1}, {"String 5", A0}}}; + +#define TUNINGS_COUNT 14 + +TUNING TuningList[TUNINGS_COUNT] = { + ScientificPitch, + TuningForks, + + GuitarStandard6, + GuitarDropD6, + GuitarD6, + GuitarDropC6, + GuitarStandard7, + + BassStandard4, + BassStandardTenor4, + BassStandard5, + BassStandardTenor5, + BassDropD4, + BassD4, + BassDropA5}; + +#endif //TUNINGS diff --git a/applications/plugins/weather_station/application.fam b/applications/plugins/weather_station/application.fam new file mode 100644 index 000000000..7505900e5 --- /dev/null +++ b/applications/plugins/weather_station/application.fam @@ -0,0 +1,13 @@ +App( + appid="Weather_Station", + name="Weather Station", + apptype=FlipperAppType.EXTERNAL, + entry_point="weather_station_app", + cdefines=["APP_WEATHER_STATION"], + requires=["gui"], + stack_size=4 * 1024, + order=50, + fap_icon="weather_station_10px.png", + fap_category="Tools", + fap_icon_assets="images", +) diff --git a/applications/plugins/weather_station/helpers/weather_station_event.h b/applications/plugins/weather_station/helpers/weather_station_event.h new file mode 100644 index 000000000..b0486183d --- /dev/null +++ b/applications/plugins/weather_station/helpers/weather_station_event.h @@ -0,0 +1,14 @@ +#pragma once + +typedef enum { + //WSCustomEvent + WSCustomEventStartId = 100, + + WSCustomEventSceneSettingLock, + + WSCustomEventViewReceiverOK, + WSCustomEventViewReceiverConfig, + WSCustomEventViewReceiverBack, + WSCustomEventViewReceiverOffDisplay, + WSCustomEventViewReceiverUnlock, +} WSCustomEvent; diff --git a/applications/plugins/weather_station/helpers/weather_station_types.h b/applications/plugins/weather_station/helpers/weather_station_types.h new file mode 100644 index 000000000..16c195fa1 --- /dev/null +++ b/applications/plugins/weather_station/helpers/weather_station_types.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +#define WS_VERSION_APP "0.6" +#define WS_DEVELOPED "SkorP" +#define WS_GITHUB "https://github.com/flipperdevices/flipperzero-firmware" + +#define WS_KEY_FILE_VERSION 1 +#define WS_KEY_FILE_TYPE "Flipper Weather Station Key File" + +/** WSRxKeyState state */ +typedef enum { + WSRxKeyStateIDLE, + WSRxKeyStateBack, + WSRxKeyStateStart, + WSRxKeyStateAddKey, +} WSRxKeyState; + +/** WSHopperState state */ +typedef enum { + WSHopperStateOFF, + WSHopperStateRunnig, + WSHopperStatePause, + WSHopperStateRSSITimeOut, +} WSHopperState; + +/** WSLock */ +typedef enum { + WSLockOff, + WSLockOn, +} WSLock; + +typedef enum { + WeatherStationViewVariableItemList, + WeatherStationViewSubmenu, + WeatherStationViewReceiver, + WeatherStationViewReceiverInfo, + WeatherStationViewWidget, +} WeatherStationView; + +/** WeatherStationTxRx state */ +typedef enum { + WSTxRxStateIDLE, + WSTxRxStateRx, + WSTxRxStateTx, + WSTxRxStateSleep, +} WSTxRxState; diff --git a/applications/plugins/weather_station/images/Humid_10x15.png b/applications/plugins/weather_station/images/Humid_10x15.png new file mode 100644 index 000000000..34b074e5f Binary files /dev/null and b/applications/plugins/weather_station/images/Humid_10x15.png differ diff --git a/applications/plugins/weather_station/images/Humid_8x13.png b/applications/plugins/weather_station/images/Humid_8x13.png new file mode 100644 index 000000000..6d8c71b00 Binary files /dev/null and b/applications/plugins/weather_station/images/Humid_8x13.png differ diff --git a/applications/plugins/weather_station/images/Lock_7x8.png b/applications/plugins/weather_station/images/Lock_7x8.png new file mode 100644 index 000000000..f7c9ca2c7 Binary files /dev/null and b/applications/plugins/weather_station/images/Lock_7x8.png differ diff --git a/applications/plugins/weather_station/images/Pin_back_arrow_10x8.png b/applications/plugins/weather_station/images/Pin_back_arrow_10x8.png new file mode 100644 index 000000000..3bafabd14 Binary files /dev/null and b/applications/plugins/weather_station/images/Pin_back_arrow_10x8.png differ diff --git a/applications/plugins/weather_station/images/Quest_7x8.png b/applications/plugins/weather_station/images/Quest_7x8.png new file mode 100644 index 000000000..6825247fb Binary files /dev/null and b/applications/plugins/weather_station/images/Quest_7x8.png differ diff --git a/applications/plugins/weather_station/images/Scanning_123x52.png b/applications/plugins/weather_station/images/Scanning_123x52.png new file mode 100644 index 000000000..ec785948d Binary files /dev/null and b/applications/plugins/weather_station/images/Scanning_123x52.png differ diff --git a/applications/plugins/weather_station/images/Therm_7x16.png b/applications/plugins/weather_station/images/Therm_7x16.png new file mode 100644 index 000000000..7c55500b7 Binary files /dev/null and b/applications/plugins/weather_station/images/Therm_7x16.png differ diff --git a/applications/plugins/weather_station/images/Timer_11x11.png b/applications/plugins/weather_station/images/Timer_11x11.png new file mode 100644 index 000000000..21ad47f4b Binary files /dev/null and b/applications/plugins/weather_station/images/Timer_11x11.png differ diff --git a/applications/plugins/weather_station/images/Unlock_7x8.png b/applications/plugins/weather_station/images/Unlock_7x8.png new file mode 100644 index 000000000..9d82b4daf Binary files /dev/null and b/applications/plugins/weather_station/images/Unlock_7x8.png differ diff --git a/applications/plugins/weather_station/images/WarningDolphin_45x42.png b/applications/plugins/weather_station/images/WarningDolphin_45x42.png new file mode 100644 index 000000000..d766ffbb4 Binary files /dev/null and b/applications/plugins/weather_station/images/WarningDolphin_45x42.png differ diff --git a/applications/plugins/weather_station/images/station_icon.png b/applications/plugins/weather_station/images/station_icon.png new file mode 100644 index 000000000..b839eeb7a Binary files /dev/null and b/applications/plugins/weather_station/images/station_icon.png differ diff --git a/applications/plugins/weather_station/protocols/acurite_592txr.c b/applications/plugins/weather_station/protocols/acurite_592txr.c new file mode 100644 index 000000000..5384a3c91 --- /dev/null +++ b/applications/plugins/weather_station/protocols/acurite_592txr.c @@ -0,0 +1,306 @@ +#include "acurite_592txr.h" + +#define TAG "WSProtocolAcurite_592TXR" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/master/src/devices/acurite.c + * + * Acurite 592TXR Temperature Humidity sensor decoder + * Message Type 0x04, 7 bytes + * | Byte 0 | Byte 1 | Byte 2 | Byte 3 | Byte 4 | Byte 5 | Byte 6 | + * | --------- | --------- | --------- | --------- | --------- | --------- | --------- | + * | CCII IIII | IIII IIII | pB00 0100 | pHHH HHHH | p??T TTTT | pTTT TTTT | KKKK KKKK | + * - C: Channel 00: C, 10: B, 11: A, (01 is invalid) + * - I: Device ID (14 bits) + * - B: Battery, 1 is battery OK, 0 is battery low + * - M: Message type (6 bits), 0x04 + * - T: Temperature Celsius (11 - 14 bits?), + 1000 * 10 + * - H: Relative Humidity (%) (7 bits) + * - K: Checksum (8 bits) + * - p: Parity bit + * Notes: + * - Temperature + * - Encoded as Celsius + 1000 * 10 + * - only 11 bits needed for specified range -40 C to 70 C (-40 F - 158 F) + * - However 14 bits available for temperature, giving possible range of -100 C to 1538.4 C + * - @todo - check if high 3 bits ever used for anything else + * + */ + +static const SubGhzBlockConst ws_protocol_acurite_592txr_const = { + .te_short = 200, + .te_long = 400, + .te_delta = 90, + .min_count_bit_for_found = 56, +}; + +struct WSProtocolDecoderAcurite_592TXR { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + + uint16_t header_count; +}; + +struct WSProtocolEncoderAcurite_592TXR { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + Acurite_592TXRDecoderStepReset = 0, + Acurite_592TXRDecoderStepCheckPreambule, + Acurite_592TXRDecoderStepSaveDuration, + Acurite_592TXRDecoderStepCheckDuration, +} Acurite_592TXRDecoderStep; + +const SubGhzProtocolDecoder ws_protocol_acurite_592txr_decoder = { + .alloc = ws_protocol_decoder_acurite_592txr_alloc, + .free = ws_protocol_decoder_acurite_592txr_free, + + .feed = ws_protocol_decoder_acurite_592txr_feed, + .reset = ws_protocol_decoder_acurite_592txr_reset, + + .get_hash_data = ws_protocol_decoder_acurite_592txr_get_hash_data, + .serialize = ws_protocol_decoder_acurite_592txr_serialize, + .deserialize = ws_protocol_decoder_acurite_592txr_deserialize, + .get_string = ws_protocol_decoder_acurite_592txr_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_acurite_592txr_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_acurite_592txr = { + .name = WS_PROTOCOL_ACURITE_592TXR_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_acurite_592txr_decoder, + .encoder = &ws_protocol_acurite_592txr_encoder, +}; + +void* ws_protocol_decoder_acurite_592txr_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderAcurite_592TXR* instance = malloc(sizeof(WSProtocolDecoderAcurite_592TXR)); + instance->base.protocol = &ws_protocol_acurite_592txr; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_acurite_592txr_free(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_592TXR* instance = context; + free(instance); +} + +void ws_protocol_decoder_acurite_592txr_reset(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_592TXR* instance = context; + instance->decoder.parser_step = Acurite_592TXRDecoderStepReset; +} + +static bool ws_protocol_acurite_592txr_check_crc(WSProtocolDecoderAcurite_592TXR* instance) { + uint8_t msg[] = { + instance->decoder.decode_data >> 48, + instance->decoder.decode_data >> 40, + instance->decoder.decode_data >> 32, + instance->decoder.decode_data >> 24, + instance->decoder.decode_data >> 16, + instance->decoder.decode_data >> 8}; + + if((subghz_protocol_blocks_add_bytes(msg, 6) == + (uint8_t)(instance->decoder.decode_data & 0xFF)) && + (!subghz_protocol_blocks_parity_bytes(&msg[2], 4))) { + return true; + } else { + return false; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_acurite_592txr_remote_controller(WSBlockGeneric* instance) { + uint8_t channel[] = {3, 0, 2, 1}; + uint8_t channel_raw = ((instance->data >> 54) & 0x03); + instance->channel = channel[channel_raw]; + instance->id = (instance->data >> 40) & 0x3FFF; + instance->battery_low = !((instance->data >> 38) & 1); + instance->humidity = (instance->data >> 24) & 0x7F; + + uint16_t temp_raw = ((instance->data >> 9) & 0xF80) | ((instance->data >> 8) & 0x7F); + instance->temp = ((float)(temp_raw)-1000) / 10.0f; + + instance->btn = WS_NO_BTN; +} + +void ws_protocol_decoder_acurite_592txr_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderAcurite_592TXR* instance = context; + + switch(instance->decoder.parser_step) { + case Acurite_592TXRDecoderStepReset: + if((level) && (DURATION_DIFF(duration, ws_protocol_acurite_592txr_const.te_short * 3) < + ws_protocol_acurite_592txr_const.te_delta * 2)) { + instance->decoder.parser_step = Acurite_592TXRDecoderStepCheckPreambule; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + + case Acurite_592TXRDecoderStepCheckPreambule: + if(level) { + instance->decoder.te_last = duration; + } else { + if((DURATION_DIFF( + instance->decoder.te_last, ws_protocol_acurite_592txr_const.te_short * 3) < + ws_protocol_acurite_592txr_const.te_delta * 2) && + (DURATION_DIFF(duration, ws_protocol_acurite_592txr_const.te_short * 3) < + ws_protocol_acurite_592txr_const.te_delta * 2)) { + //Found preambule + instance->header_count++; + } else if((instance->header_count > 2) && (instance->header_count < 5)) { + if((DURATION_DIFF( + instance->decoder.te_last, ws_protocol_acurite_592txr_const.te_short) < + ws_protocol_acurite_592txr_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_acurite_592txr_const.te_long) < + ws_protocol_acurite_592txr_const.te_delta)) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = Acurite_592TXRDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, ws_protocol_acurite_592txr_const.te_long) < + ws_protocol_acurite_592txr_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_acurite_592txr_const.te_short) < + ws_protocol_acurite_592txr_const.te_delta)) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = Acurite_592TXRDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = Acurite_592TXRDecoderStepReset; + } + } else { + instance->decoder.parser_step = Acurite_592TXRDecoderStepReset; + } + } + break; + + case Acurite_592TXRDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = Acurite_592TXRDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = Acurite_592TXRDecoderStepReset; + } + break; + + case Acurite_592TXRDecoderStepCheckDuration: + if(!level) { + if(duration >= ((uint32_t)ws_protocol_acurite_592txr_const.te_short * 5)) { + if((instance->decoder.decode_count_bit == + ws_protocol_acurite_592txr_const.min_count_bit_for_found) && + ws_protocol_acurite_592txr_check_crc(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_acurite_592txr_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 = Acurite_592TXRDecoderStepReset; + break; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, ws_protocol_acurite_592txr_const.te_short) < + ws_protocol_acurite_592txr_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_acurite_592txr_const.te_long) < + ws_protocol_acurite_592txr_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = Acurite_592TXRDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, ws_protocol_acurite_592txr_const.te_long) < + ws_protocol_acurite_592txr_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_acurite_592txr_const.te_short) < + ws_protocol_acurite_592txr_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = Acurite_592TXRDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = Acurite_592TXRDecoderStepReset; + } + } else { + instance->decoder.parser_step = Acurite_592TXRDecoderStepReset; + } + break; + } +} + +uint8_t ws_protocol_decoder_acurite_592txr_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_592TXR* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool ws_protocol_decoder_acurite_592txr_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderAcurite_592TXR* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool ws_protocol_decoder_acurite_592txr_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderAcurite_592TXR* instance = context; + bool ret = false; + do { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + ws_protocol_acurite_592txr_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void ws_protocol_decoder_acurite_592txr_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderAcurite_592TXR* 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); +} diff --git a/applications/plugins/weather_station/protocols/acurite_592txr.h b/applications/plugins/weather_station/protocols/acurite_592txr.h new file mode 100644 index 000000000..ac0371d89 --- /dev/null +++ b/applications/plugins/weather_station/protocols/acurite_592txr.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_ACURITE_592TXR_NAME "Acurite 592TXR" + +typedef struct WSProtocolDecoderAcurite_592TXR WSProtocolDecoderAcurite_592TXR; +typedef struct WSProtocolEncoderAcurite_592TXR WSProtocolEncoderAcurite_592TXR; + +extern const SubGhzProtocolDecoder ws_protocol_acurite_592txr_decoder; +extern const SubGhzProtocolEncoder ws_protocol_acurite_592txr_encoder; +extern const SubGhzProtocol ws_protocol_acurite_592txr; + +/** + * Allocate WSProtocolDecoderAcurite_592TXR. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderAcurite_592TXR* pointer to a WSProtocolDecoderAcurite_592TXR instance + */ +void* ws_protocol_decoder_acurite_592txr_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderAcurite_592TXR. + * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance + */ +void ws_protocol_decoder_acurite_592txr_free(void* context); + +/** + * Reset decoder WSProtocolDecoderAcurite_592TXR. + * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance + */ +void ws_protocol_decoder_acurite_592txr_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_acurite_592txr_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_acurite_592txr_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderAcurite_592TXR. + * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_protocol_decoder_acurite_592txr_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderAcurite_592TXR. + * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_protocol_decoder_acurite_592txr_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance + * @param output Resulting text + */ +void ws_protocol_decoder_acurite_592txr_get_string(void* context, FuriString* output); diff --git a/applications/plugins/weather_station/protocols/acurite_606tx.c b/applications/plugins/weather_station/protocols/acurite_606tx.c new file mode 100644 index 000000000..4cb5d18b8 --- /dev/null +++ b/applications/plugins/weather_station/protocols/acurite_606tx.c @@ -0,0 +1,247 @@ +#include "acurite_606tx.h" + +#define TAG "WSProtocolAcurite_606TX" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/5bef4e43133ac4c0e2d18d36f87c52b4f9458453/src/devices/acurite.c#L1644 + * + * 0000 1111 | 0011 0000 | 0101 1100 | 1110 0111 + * iiii iiii | buuu tttt | tttt tttt | cccc cccc + * - i: identification; changes on battery switch + * - c: lfsr_digest8; + * - u: unknown; + * - b: battery low; flag to indicate low battery voltage + * - t: Temperature; in °C + * + */ + +static const SubGhzBlockConst ws_protocol_acurite_606tx_const = { + .te_short = 500, + .te_long = 2000, + .te_delta = 150, + .min_count_bit_for_found = 32, +}; + +struct WSProtocolDecoderAcurite_606TX { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; +}; + +struct WSProtocolEncoderAcurite_606TX { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + Acurite_606TXDecoderStepReset = 0, + Acurite_606TXDecoderStepSaveDuration, + Acurite_606TXDecoderStepCheckDuration, +} Acurite_606TXDecoderStep; + +const SubGhzProtocolDecoder ws_protocol_acurite_606tx_decoder = { + .alloc = ws_protocol_decoder_acurite_606tx_alloc, + .free = ws_protocol_decoder_acurite_606tx_free, + + .feed = ws_protocol_decoder_acurite_606tx_feed, + .reset = ws_protocol_decoder_acurite_606tx_reset, + + .get_hash_data = ws_protocol_decoder_acurite_606tx_get_hash_data, + .serialize = ws_protocol_decoder_acurite_606tx_serialize, + .deserialize = ws_protocol_decoder_acurite_606tx_deserialize, + .get_string = ws_protocol_decoder_acurite_606tx_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_acurite_606tx_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_acurite_606tx = { + .name = WS_PROTOCOL_ACURITE_606TX_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_acurite_606tx_decoder, + .encoder = &ws_protocol_acurite_606tx_encoder, +}; + +void* ws_protocol_decoder_acurite_606tx_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderAcurite_606TX* instance = malloc(sizeof(WSProtocolDecoderAcurite_606TX)); + instance->base.protocol = &ws_protocol_acurite_606tx; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_acurite_606tx_free(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_606TX* instance = context; + free(instance); +} + +void ws_protocol_decoder_acurite_606tx_reset(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_606TX* instance = context; + instance->decoder.parser_step = Acurite_606TXDecoderStepReset; +} + +static bool ws_protocol_acurite_606tx_check(WSProtocolDecoderAcurite_606TX* instance) { + if(!instance->decoder.decode_data) return false; + uint8_t msg[] = { + instance->decoder.decode_data >> 24, + instance->decoder.decode_data >> 16, + instance->decoder.decode_data >> 8}; + + uint8_t crc = subghz_protocol_blocks_lfsr_digest8(msg, 3, 0x98, 0xF1); + return (crc == (instance->decoder.decode_data & 0xFF)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_acurite_606tx_remote_controller(WSBlockGeneric* instance) { + instance->id = (instance->data >> 24) & 0xFF; + instance->battery_low = (instance->data >> 23) & 1; + + instance->channel = WS_NO_CHANNEL; + + 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->btn = WS_NO_BTN; + instance->humidity = WS_NO_HUMIDITY; +} + +void ws_protocol_decoder_acurite_606tx_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderAcurite_606TX* instance = context; + + switch(instance->decoder.parser_step) { + case Acurite_606TXDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, ws_protocol_acurite_606tx_const.te_short * 17) < + ws_protocol_acurite_606tx_const.te_delta * 8)) { + //Found syncPrefix + instance->decoder.parser_step = Acurite_606TXDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + break; + + case Acurite_606TXDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = Acurite_606TXDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = Acurite_606TXDecoderStepReset; + } + break; + + case Acurite_606TXDecoderStepCheckDuration: + if(!level) { + if(DURATION_DIFF(instance->decoder.te_last, ws_protocol_acurite_606tx_const.te_short) < + ws_protocol_acurite_606tx_const.te_delta) { + if((DURATION_DIFF(duration, ws_protocol_acurite_606tx_const.te_short) < + ws_protocol_acurite_606tx_const.te_delta) || + (duration > ws_protocol_acurite_606tx_const.te_long * 3)) { + //Found syncPostfix + instance->decoder.parser_step = Acurite_606TXDecoderStepReset; + if((instance->decoder.decode_count_bit == + ws_protocol_acurite_606tx_const.min_count_bit_for_found) && + ws_protocol_acurite_606tx_check(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_acurite_606tx_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; + } else if( + DURATION_DIFF(duration, ws_protocol_acurite_606tx_const.te_long) < + ws_protocol_acurite_606tx_const.te_delta * 2) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = Acurite_606TXDecoderStepSaveDuration; + } else if( + DURATION_DIFF(duration, ws_protocol_acurite_606tx_const.te_long * 2) < + ws_protocol_acurite_606tx_const.te_delta * 4) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = Acurite_606TXDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = Acurite_606TXDecoderStepReset; + } + } else { + instance->decoder.parser_step = Acurite_606TXDecoderStepReset; + } + } else { + instance->decoder.parser_step = Acurite_606TXDecoderStepReset; + } + break; + } +} + +uint8_t ws_protocol_decoder_acurite_606tx_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_606TX* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool ws_protocol_decoder_acurite_606tx_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderAcurite_606TX* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool ws_protocol_decoder_acurite_606tx_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderAcurite_606TX* instance = context; + bool ret = false; + do { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + ws_protocol_acurite_606tx_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void ws_protocol_decoder_acurite_606tx_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderAcurite_606TX* 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); +} diff --git a/applications/plugins/weather_station/protocols/acurite_606tx.h b/applications/plugins/weather_station/protocols/acurite_606tx.h new file mode 100644 index 000000000..5bab3bcb7 --- /dev/null +++ b/applications/plugins/weather_station/protocols/acurite_606tx.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_ACURITE_606TX_NAME "Acurite-606TX" + +typedef struct WSProtocolDecoderAcurite_606TX WSProtocolDecoderAcurite_606TX; +typedef struct WSProtocolEncoderAcurite_606TX WSProtocolEncoderAcurite_606TX; + +extern const SubGhzProtocolDecoder ws_protocol_acurite_606tx_decoder; +extern const SubGhzProtocolEncoder ws_protocol_acurite_606tx_encoder; +extern const SubGhzProtocol ws_protocol_acurite_606tx; + +/** + * Allocate WSProtocolDecoderAcurite_606TX. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderAcurite_606TX* pointer to a WSProtocolDecoderAcurite_606TX instance + */ +void* ws_protocol_decoder_acurite_606tx_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderAcurite_606TX. + * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance + */ +void ws_protocol_decoder_acurite_606tx_free(void* context); + +/** + * Reset decoder WSProtocolDecoderAcurite_606TX. + * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance + */ +void ws_protocol_decoder_acurite_606tx_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_acurite_606tx_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_acurite_606tx_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderAcurite_606TX. + * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_protocol_decoder_acurite_606tx_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderAcurite_606TX. + * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_protocol_decoder_acurite_606tx_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance + * @param output Resulting text + */ +void ws_protocol_decoder_acurite_606tx_get_string(void* context, FuriString* output); diff --git a/applications/plugins/weather_station/protocols/acurite_609txc.c b/applications/plugins/weather_station/protocols/acurite_609txc.c new file mode 100644 index 000000000..aeb0785eb --- /dev/null +++ b/applications/plugins/weather_station/protocols/acurite_609txc.c @@ -0,0 +1,247 @@ +#include "acurite_609txc.h" + +#define TAG "WSProtocolAcurite_609TXC" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/5bef4e43133ac4c0e2d18d36f87c52b4f9458453/src/devices/acurite.c#L216 + * + * 0000 1111 | 0011 0000 | 0101 1100 | 0000 0000 | 1110 0111 + * iiii iiii | buuu tttt | tttt tttt | hhhh hhhh | cccc cccc + * - i: identification; changes on battery switch + * - c: checksum (sum of previous by bytes) + * - u: unknown + * - b: battery low; flag to indicate low battery voltage + * - t: temperature; in °C * 10, 12 bit with complement + * - h: humidity + * + */ + +static const SubGhzBlockConst ws_protocol_acurite_609txc_const = { + .te_short = 500, + .te_long = 1000, + .te_delta = 150, + .min_count_bit_for_found = 40, +}; + +struct WSProtocolDecoderAcurite_609TXC { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; +}; + +struct WSProtocolEncoderAcurite_609TXC { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + Acurite_609TXCDecoderStepReset = 0, + Acurite_609TXCDecoderStepSaveDuration, + Acurite_609TXCDecoderStepCheckDuration, +} Acurite_609TXCDecoderStep; + +const SubGhzProtocolDecoder ws_protocol_acurite_609txc_decoder = { + .alloc = ws_protocol_decoder_acurite_609txc_alloc, + .free = ws_protocol_decoder_acurite_609txc_free, + + .feed = ws_protocol_decoder_acurite_609txc_feed, + .reset = ws_protocol_decoder_acurite_609txc_reset, + + .get_hash_data = ws_protocol_decoder_acurite_609txc_get_hash_data, + .serialize = ws_protocol_decoder_acurite_609txc_serialize, + .deserialize = ws_protocol_decoder_acurite_609txc_deserialize, + .get_string = ws_protocol_decoder_acurite_609txc_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_acurite_609txc_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_acurite_609txc = { + .name = WS_PROTOCOL_ACURITE_609TXC_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_acurite_609txc_decoder, + .encoder = &ws_protocol_acurite_609txc_encoder, +}; + +void* ws_protocol_decoder_acurite_609txc_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderAcurite_609TXC* instance = malloc(sizeof(WSProtocolDecoderAcurite_609TXC)); + instance->base.protocol = &ws_protocol_acurite_609txc; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_acurite_609txc_free(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_609TXC* instance = context; + free(instance); +} + +void ws_protocol_decoder_acurite_609txc_reset(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_609TXC* instance = context; + instance->decoder.parser_step = Acurite_609TXCDecoderStepReset; +} + +static bool ws_protocol_acurite_609txc_check(WSProtocolDecoderAcurite_609TXC* instance) { + if(!instance->decoder.decode_data) return false; + uint8_t crc = (uint8_t)(instance->decoder.decode_data >> 32) + + (uint8_t)(instance->decoder.decode_data >> 24) + + (uint8_t)(instance->decoder.decode_data >> 16) + + (uint8_t)(instance->decoder.decode_data >> 8); + return (crc == (instance->decoder.decode_data & 0xFF)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_acurite_609txc_remote_controller(WSBlockGeneric* instance) { + instance->id = (instance->data >> 32) & 0xFF; + instance->battery_low = (instance->data >> 31) & 1; + + instance->channel = WS_NO_CHANNEL; + + // Temperature in Celsius is encoded as a 12 bit integer value + // multiplied by 10 using the 4th - 6th nybbles (bytes 1 & 2) + // negative values are recovered by sign extend from int16_t. + int16_t temp_raw = + (int16_t)(((instance->data >> 12) & 0xf000) | ((instance->data >> 16) << 4)); + instance->temp = (temp_raw >> 4) * 0.1f; + instance->humidity = (instance->data >> 8) & 0xff; + instance->btn = WS_NO_BTN; +} + +void ws_protocol_decoder_acurite_609txc_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderAcurite_609TXC* instance = context; + + switch(instance->decoder.parser_step) { + case Acurite_609TXCDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, ws_protocol_acurite_609txc_const.te_short * 17) < + ws_protocol_acurite_609txc_const.te_delta * 8)) { + //Found syncPrefix + instance->decoder.parser_step = Acurite_609TXCDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + break; + + case Acurite_609TXCDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = Acurite_609TXCDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = Acurite_609TXCDecoderStepReset; + } + break; + + case Acurite_609TXCDecoderStepCheckDuration: + if(!level) { + if(DURATION_DIFF(instance->decoder.te_last, ws_protocol_acurite_609txc_const.te_short) < + ws_protocol_acurite_609txc_const.te_delta) { + if((DURATION_DIFF(duration, ws_protocol_acurite_609txc_const.te_short) < + ws_protocol_acurite_609txc_const.te_delta) || + (duration > ws_protocol_acurite_609txc_const.te_long * 3)) { + //Found syncPostfix + instance->decoder.parser_step = Acurite_609TXCDecoderStepReset; + if((instance->decoder.decode_count_bit == + ws_protocol_acurite_609txc_const.min_count_bit_for_found) && + ws_protocol_acurite_609txc_check(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_acurite_609txc_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; + } else if( + DURATION_DIFF(duration, ws_protocol_acurite_609txc_const.te_long) < + ws_protocol_acurite_609txc_const.te_delta * 2) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = Acurite_609TXCDecoderStepSaveDuration; + } else if( + DURATION_DIFF(duration, ws_protocol_acurite_609txc_const.te_long * 2) < + ws_protocol_acurite_609txc_const.te_delta * 4) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = Acurite_609TXCDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = Acurite_609TXCDecoderStepReset; + } + } else { + instance->decoder.parser_step = Acurite_609TXCDecoderStepReset; + } + } else { + instance->decoder.parser_step = Acurite_609TXCDecoderStepReset; + } + break; + } +} + +uint8_t ws_protocol_decoder_acurite_609txc_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_609TXC* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool ws_protocol_decoder_acurite_609txc_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderAcurite_609TXC* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool ws_protocol_decoder_acurite_609txc_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderAcurite_609TXC* instance = context; + bool ret = false; + do { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + ws_protocol_acurite_609txc_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void ws_protocol_decoder_acurite_609txc_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderAcurite_609TXC* 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 >> 40), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (double)instance->generic.temp, + instance->generic.humidity); +} diff --git a/applications/plugins/weather_station/protocols/acurite_609txc.h b/applications/plugins/weather_station/protocols/acurite_609txc.h new file mode 100644 index 000000000..f87c20e9b --- /dev/null +++ b/applications/plugins/weather_station/protocols/acurite_609txc.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_ACURITE_609TXC_NAME "Acurite-609TXC" + +typedef struct WSProtocolDecoderAcurite_609TXC WSProtocolDecoderAcurite_609TXC; +typedef struct WSProtocolEncoderAcurite_609TXC WSProtocolEncoderAcurite_609TXC; + +extern const SubGhzProtocolDecoder ws_protocol_acurite_609txc_decoder; +extern const SubGhzProtocolEncoder ws_protocol_acurite_609txc_encoder; +extern const SubGhzProtocol ws_protocol_acurite_609txc; + +/** + * Allocate WSProtocolDecoderAcurite_609TXC. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderAcurite_609TXC* pointer to a WSProtocolDecoderAcurite_609TXC instance + */ +void* ws_protocol_decoder_acurite_609txc_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderAcurite_609TXC. + * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance + */ +void ws_protocol_decoder_acurite_609txc_free(void* context); + +/** + * Reset decoder WSProtocolDecoderAcurite_609TXC. + * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance + */ +void ws_protocol_decoder_acurite_609txc_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_acurite_609txc_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_acurite_609txc_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderAcurite_609TXC. + * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_protocol_decoder_acurite_609txc_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderAcurite_609TXC. + * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_protocol_decoder_acurite_609txc_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance + * @param output Resulting text + */ +void ws_protocol_decoder_acurite_609txc_get_string(void* context, FuriString* output); diff --git a/applications/plugins/weather_station/protocols/ambient_weather.c b/applications/plugins/weather_station/protocols/ambient_weather.c new file mode 100644 index 000000000..5ae22b790 --- /dev/null +++ b/applications/plugins/weather_station/protocols/ambient_weather.c @@ -0,0 +1,276 @@ +#include "ambient_weather.h" +#include + +#define TAG "WSProtocolAmbient_Weather" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/master/src/devices/ambient_weather.c + * + * Decode Ambient Weather F007TH, F012TH, TF 30.3208.02, SwitchDoc F016TH. + * Devices supported: + * - Ambient Weather F007TH Thermo-Hygrometer. + * - Ambient Weather F012TH Indoor/Display Thermo-Hygrometer. + * - TFA senders 30.3208.02 from the TFA "Klima-Monitor" 30.3054, + * - SwitchDoc Labs F016TH. + * This decoder handles the 433mhz/868mhz thermo-hygrometers. + * The 915mhz (WH*) family of devices use different modulation/encoding. + * Byte 0 Byte 1 Byte 2 Byte 3 Byte 4 Byte 5 + * xxxxMMMM IIIIIIII BCCCTTTT TTTTTTTT HHHHHHHH MMMMMMMM + * - x: Unknown 0x04 on F007TH/F012TH + * - M: Model Number?, 0x05 on F007TH/F012TH/SwitchDocLabs F016TH + * - I: ID byte (8 bits), volatie, changes at power up, + * - B: Battery Low + * - C: Channel (3 bits 1-8) - F007TH set by Dip switch, F012TH soft setting + * - T: Temperature 12 bits - Fahrenheit * 10 + 400 + * - H: Humidity (8 bits) + * - M: Message integrity check LFSR Digest-8, gen 0x98, key 0x3e, init 0x64 + * + * three repeats without gap + * full preamble is 0x00145 (the last bits might not be fixed, e.g. 0x00146) + * and on decoding also 0xffd45 + */ + +#define AMBIENT_WEATHER_PACKET_HEADER_1 0xFFD440000000000 //0xffd45 .. 0xffd46 +#define AMBIENT_WEATHER_PACKET_HEADER_2 0x001440000000000 //0x00145 .. 0x00146 +#define AMBIENT_WEATHER_PACKET_HEADER_MASK 0xFFFFC0000000000 + +static const SubGhzBlockConst ws_protocol_ambient_weather_const = { + .te_short = 500, + .te_long = 1000, + .te_delta = 120, + .min_count_bit_for_found = 48, +}; + +struct WSProtocolDecoderAmbient_Weather { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + ManchesterState manchester_saved_state; + uint16_t header_count; +}; + +struct WSProtocolEncoderAmbient_Weather { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +const SubGhzProtocolDecoder ws_protocol_ambient_weather_decoder = { + .alloc = ws_protocol_decoder_ambient_weather_alloc, + .free = ws_protocol_decoder_ambient_weather_free, + + .feed = ws_protocol_decoder_ambient_weather_feed, + .reset = ws_protocol_decoder_ambient_weather_reset, + + .get_hash_data = ws_protocol_decoder_ambient_weather_get_hash_data, + .serialize = ws_protocol_decoder_ambient_weather_serialize, + .deserialize = ws_protocol_decoder_ambient_weather_deserialize, + .get_string = ws_protocol_decoder_ambient_weather_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_ambient_weather_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_ambient_weather = { + .name = WS_PROTOCOL_AMBIENT_WEATHER_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_ambient_weather_decoder, + .encoder = &ws_protocol_ambient_weather_encoder, +}; + +void* ws_protocol_decoder_ambient_weather_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderAmbient_Weather* instance = malloc(sizeof(WSProtocolDecoderAmbient_Weather)); + instance->base.protocol = &ws_protocol_ambient_weather; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_ambient_weather_free(void* context) { + furi_assert(context); + WSProtocolDecoderAmbient_Weather* instance = context; + free(instance); +} + +void ws_protocol_decoder_ambient_weather_reset(void* context) { + furi_assert(context); + WSProtocolDecoderAmbient_Weather* instance = context; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); +} + +static bool ws_protocol_ambient_weather_check_crc(WSProtocolDecoderAmbient_Weather* instance) { + uint8_t msg[] = { + instance->decoder.decode_data >> 40, + instance->decoder.decode_data >> 32, + instance->decoder.decode_data >> 24, + instance->decoder.decode_data >> 16, + instance->decoder.decode_data >> 8}; + + uint8_t crc = subghz_protocol_blocks_lfsr_digest8(msg, 5, 0x98, 0x3e) ^ 0x64; + return (crc == (uint8_t)(instance->decoder.decode_data & 0xFF)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_ambient_weather_remote_controller(WSBlockGeneric* instance) { + instance->id = (instance->data >> 32) & 0xFF; + instance->battery_low = (instance->data >> 31) & 1; + instance->channel = ((instance->data >> 28) & 0x07) + 1; + instance->temp = ws_block_generic_fahrenheit_to_celsius( + ((float)((instance->data >> 16) & 0x0FFF) - 400.0f) / 10.0f); + instance->humidity = (instance->data >> 8) & 0xFF; + instance->btn = WS_NO_BTN; + + // ToDo maybe it won't be needed + /* + Sanity checks to reduce false positives and other bad data + Packets with Bad data often pass the MIC check. + - humidity > 100 (such as 255) and + - temperatures > 140 F (such as 369.5 F and 348.8 F + Specs in the F007TH and F012TH manuals state the range is: + - Temperature: -40 to 140 F + - Humidity: 10 to 99% + @todo - sanity check b[0] "model number" + - 0x45 - F007TH and F012TH + - 0x?5 - SwitchDocLabs F016TH temperature sensor (based on comment b[0] & 0x0f == 5) + - ? - TFA 30.3208.02 + if (instance->humidity < 0 || instance->humidity > 100) { + ERROR; + } + + if (instance->temp < -40.0 || instance->temp > 140.0) { + ERROR; + } + */ +} + +void ws_protocol_decoder_ambient_weather_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderAmbient_Weather* instance = context; + + ManchesterEvent event = ManchesterEventReset; + if(!level) { + if(DURATION_DIFF(duration, ws_protocol_ambient_weather_const.te_short) < + ws_protocol_ambient_weather_const.te_delta) { + event = ManchesterEventShortLow; + } else if( + DURATION_DIFF(duration, ws_protocol_ambient_weather_const.te_long) < + ws_protocol_ambient_weather_const.te_delta * 2) { + event = ManchesterEventLongLow; + } + } else { + if(DURATION_DIFF(duration, ws_protocol_ambient_weather_const.te_short) < + ws_protocol_ambient_weather_const.te_delta) { + event = ManchesterEventShortHigh; + } else if( + DURATION_DIFF(duration, ws_protocol_ambient_weather_const.te_long) < + ws_protocol_ambient_weather_const.te_delta * 2) { + event = ManchesterEventLongHigh; + } + } + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + instance->manchester_saved_state, event, &instance->manchester_saved_state, &data); + + if(data_ok) { + instance->decoder.decode_data = (instance->decoder.decode_data << 1) | !data; + } + + if(((instance->decoder.decode_data & AMBIENT_WEATHER_PACKET_HEADER_MASK) == + AMBIENT_WEATHER_PACKET_HEADER_1) || + ((instance->decoder.decode_data & AMBIENT_WEATHER_PACKET_HEADER_MASK) == + AMBIENT_WEATHER_PACKET_HEADER_2)) { + if(ws_protocol_ambient_weather_check_crc(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = + ws_protocol_ambient_weather_const.min_count_bit_for_found; + ws_protocol_ambient_weather_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; + } + } + } else { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); + } +} + +uint8_t ws_protocol_decoder_ambient_weather_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderAmbient_Weather* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool ws_protocol_decoder_ambient_weather_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderAmbient_Weather* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool ws_protocol_decoder_ambient_weather_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderAmbient_Weather* instance = context; + bool ret = false; + do { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + ws_protocol_ambient_weather_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void ws_protocol_decoder_ambient_weather_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderAmbient_Weather* 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); +} diff --git a/applications/plugins/weather_station/protocols/ambient_weather.h b/applications/plugins/weather_station/protocols/ambient_weather.h new file mode 100644 index 000000000..04cc5819c --- /dev/null +++ b/applications/plugins/weather_station/protocols/ambient_weather.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_AMBIENT_WEATHER_NAME "Ambient_Weather" + +typedef struct WSProtocolDecoderAmbient_Weather WSProtocolDecoderAmbient_Weather; +typedef struct WSProtocolEncoderAmbient_Weather WSProtocolEncoderAmbient_Weather; + +extern const SubGhzProtocolDecoder ws_protocol_ambient_weather_decoder; +extern const SubGhzProtocolEncoder ws_protocol_ambient_weather_encoder; +extern const SubGhzProtocol ws_protocol_ambient_weather; + +/** + * Allocate WSProtocolDecoderAmbient_Weather. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderAmbient_Weather* pointer to a WSProtocolDecoderAmbient_Weather instance + */ +void* ws_protocol_decoder_ambient_weather_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderAmbient_Weather. + * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance + */ +void ws_protocol_decoder_ambient_weather_free(void* context); + +/** + * Reset decoder WSProtocolDecoderAmbient_Weather. + * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance + */ +void ws_protocol_decoder_ambient_weather_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_ambient_weather_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_ambient_weather_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderAmbient_Weather. + * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_protocol_decoder_ambient_weather_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderAmbient_Weather. + * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_protocol_decoder_ambient_weather_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance + * @param output Resulting text + */ +void ws_protocol_decoder_ambient_weather_get_string(void* context, FuriString* output); diff --git a/applications/plugins/weather_station/protocols/auriol_hg0601a.c b/applications/plugins/weather_station/protocols/auriol_hg0601a.c new file mode 100644 index 000000000..d5f89fc8b --- /dev/null +++ b/applications/plugins/weather_station/protocols/auriol_hg0601a.c @@ -0,0 +1,258 @@ +#include "auriol_hg0601a.h" + +#define TAG "WSProtocolAuriol_TH" + +/* + * +Auriol HG06061A-DCF-TX sensor. + +Data layout: + DDDDDDDD-B0-NN-TT-TTTTTTTTTT-CCCC-HHHHHHHH +Exmpl.: 11110100-10-01-00-0001001100-1111-01011101 + +- D: id, 8 bit +- B: where B is the battery status: 1=OK, 0=LOW, 1 bit +- 0: just zero :) +- N: NN is the channel: 00=CH1, 01=CH2, 11=CH3, 2bit +- T: temperature, 12 bit: 2's complement, scaled by 10 +- C: 4 bit: seems to be 0xf constantly, a separator between temp and humidity +- H: humidity sensor, humidity is 8 bits + + * The sensor sends 37 bits 10 times, + * the packets are ppm modulated (distance coding) with a pulse of ~500 us + * followed by a short gap of ~1000 us for a 0 bit or a long ~2000 us gap for a + * 1 bit, the sync gap is ~4000 us. + * + */ + +#define AURIOL_TH_CONST_DATA 0b1110 + +static const SubGhzBlockConst ws_protocol_auriol_th_const = { + .te_short = 500, + .te_long = 2000, + .te_delta = 150, + .min_count_bit_for_found = 37, +}; + +struct WSProtocolDecoderAuriol_TH { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; +}; + +struct WSProtocolEncoderAuriol_TH { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + auriol_THDecoderStepReset = 0, + auriol_THDecoderStepSaveDuration, + auriol_THDecoderStepCheckDuration, +} auriol_THDecoderStep; + +const SubGhzProtocolDecoder ws_protocol_auriol_th_decoder = { + .alloc = ws_protocol_decoder_auriol_th_alloc, + .free = ws_protocol_decoder_auriol_th_free, + + .feed = ws_protocol_decoder_auriol_th_feed, + .reset = ws_protocol_decoder_auriol_th_reset, + + .get_hash_data = ws_protocol_decoder_auriol_th_get_hash_data, + .serialize = ws_protocol_decoder_auriol_th_serialize, + .deserialize = ws_protocol_decoder_auriol_th_deserialize, + .get_string = ws_protocol_decoder_auriol_th_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_auriol_th_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_auriol_th = { + .name = WS_PROTOCOL_AURIOL_TH_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_auriol_th_decoder, + .encoder = &ws_protocol_auriol_th_encoder, +}; + +void* ws_protocol_decoder_auriol_th_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderAuriol_TH* instance = malloc(sizeof(WSProtocolDecoderAuriol_TH)); + instance->base.protocol = &ws_protocol_auriol_th; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_auriol_th_free(void* context) { + furi_assert(context); + WSProtocolDecoderAuriol_TH* instance = context; + free(instance); +} + +void ws_protocol_decoder_auriol_th_reset(void* context) { + furi_assert(context); + WSProtocolDecoderAuriol_TH* instance = context; + instance->decoder.parser_step = auriol_THDecoderStepReset; +} + +static bool ws_protocol_auriol_th_check(WSProtocolDecoderAuriol_TH* instance) { + uint8_t type = (instance->decoder.decode_data >> 8) & 0x0F; + + if((type == AURIOL_TH_CONST_DATA) && ((instance->decoder.decode_data >> 4) != 0xffffffff)) { + return true; + } else { + return false; + } + return true; +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_auriol_th_remote_controller(WSBlockGeneric* instance) { + instance->id = (instance->data >> 31) & 0xFF; + instance->battery_low = ((instance->data >> 30) & 1); + instance->channel = ((instance->data >> 25) & 0x03) + 1; + instance->btn = WS_NO_BTN; + if(!((instance->data >> 23) & 1)) { + instance->temp = (float)((instance->data >> 13) & 0x07FF) / 10.0f; + } else { + instance->temp = (float)((~(instance->data >> 13) & 0x07FF) + 1) / -10.0f; + } + + instance->humidity = (instance->data >> 1) & 0x7F; +} + +void ws_protocol_decoder_auriol_th_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderAuriol_TH* instance = context; + + switch(instance->decoder.parser_step) { + case auriol_THDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, ws_protocol_auriol_th_const.te_short * 8) < + ws_protocol_auriol_th_const.te_delta)) { + //Found sync + instance->decoder.parser_step = auriol_THDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + break; + + case auriol_THDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = auriol_THDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = auriol_THDecoderStepReset; + } + break; + + case auriol_THDecoderStepCheckDuration: + if(!level) { + if(DURATION_DIFF(duration, ws_protocol_auriol_th_const.te_short * 8) < + ws_protocol_auriol_th_const.te_delta) { + //Found sync + instance->decoder.parser_step = auriol_THDecoderStepReset; + if((instance->decoder.decode_count_bit == + ws_protocol_auriol_th_const.min_count_bit_for_found) && + ws_protocol_auriol_th_check(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_auriol_th_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + instance->decoder.parser_step = auriol_THDecoderStepCheckDuration; + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + + break; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_auriol_th_const.te_short) < + ws_protocol_auriol_th_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_auriol_th_const.te_short * 2) < + ws_protocol_auriol_th_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = auriol_THDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_auriol_th_const.te_short) < + ws_protocol_auriol_th_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_auriol_th_const.te_short * 4) < + ws_protocol_auriol_th_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = auriol_THDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = auriol_THDecoderStepReset; + } + } else { + instance->decoder.parser_step = auriol_THDecoderStepReset; + } + break; + } +} + +uint8_t ws_protocol_decoder_auriol_th_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderAuriol_TH* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool ws_protocol_decoder_auriol_th_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderAuriol_TH* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool ws_protocol_decoder_auriol_th_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderAuriol_TH* instance = context; + bool ret = false; + do { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + ws_protocol_auriol_th_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void ws_protocol_decoder_auriol_th_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderAuriol_TH* 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); +} diff --git a/applications/plugins/weather_station/protocols/auriol_hg0601a.h b/applications/plugins/weather_station/protocols/auriol_hg0601a.h new file mode 100644 index 000000000..c23007c1a --- /dev/null +++ b/applications/plugins/weather_station/protocols/auriol_hg0601a.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_AURIOL_TH_NAME "Auriol HG06061" //HG06061A-DCF-TX + +typedef struct WSProtocolDecoderAuriol_TH WSProtocolDecoderAuriol_TH; +typedef struct WSProtocolEncoderAuriol_TH WSProtocolEncoderAuriol_TH; + +extern const SubGhzProtocolDecoder ws_protocol_auriol_th_decoder; +extern const SubGhzProtocolEncoder ws_protocol_auriol_th_encoder; +extern const SubGhzProtocol ws_protocol_auriol_th; + +/** + * Allocate WSProtocolDecoderAuriol_TH. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderAuriol_TH* pointer to a WSProtocolDecoderAuriol_TH instance + */ +void* ws_protocol_decoder_auriol_th_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderAuriol_TH. + * @param context Pointer to a WSProtocolDecoderAuriol_TH instance + */ +void ws_protocol_decoder_auriol_th_free(void* context); + +/** + * Reset decoder WSProtocolDecoderAuriol_TH. + * @param context Pointer to a WSProtocolDecoderAuriol_TH instance + */ +void ws_protocol_decoder_auriol_th_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderAuriol_TH instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_auriol_th_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderAuriol_TH instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_auriol_th_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderAuriol_TH. + * @param context Pointer to a WSProtocolDecoderAuriol_TH instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_protocol_decoder_auriol_th_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderAuriol_TH. + * @param context Pointer to a WSProtocolDecoderAuriol_TH instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_protocol_decoder_auriol_th_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderAuriol_TH instance + * @param output Resulting text + */ +void ws_protocol_decoder_auriol_th_get_string(void* context, FuriString* output); diff --git a/applications/plugins/weather_station/protocols/gt_wt_02.c b/applications/plugins/weather_station/protocols/gt_wt_02.c new file mode 100644 index 000000000..cbe119192 --- /dev/null +++ b/applications/plugins/weather_station/protocols/gt_wt_02.c @@ -0,0 +1,265 @@ +#include "gt_wt_02.h" + +#define TAG "WSProtocolGT_WT02" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/master/src/devices/gt_wt_02.c + * + * GT-WT-02 sensor on 433.92MHz. + * Example and frame description provided by https://github.com/ludwich66 + * [01] {37} 34 00 ed 47 60 : 00110100 00000000 11101101 01000111 01100000 + * code, BatOK,not-man-send, Channel1, +23,7C, 35% + * [01] {37} 34 8f 87 15 90 : 00110100 10001111 10000111 00010101 10010000 + * code, BatOK,not-man-send, Channel1,-12,1C, 10% + * Humidity: + * - the working range is 20-90 % + * - if "LL" in display view it sends 10 % + * - if "HH" in display view it sends 110% + * SENSOR: GT-WT-02 (ALDI Globaltronics..) + * TYP IIIIIIII BMCCTTTT TTTTTTTT HHHHHHHX XXXXX + * TYPE Description: + * - I = Random Device Code, changes with battery reset + * - B = Battery 0=OK 1=LOW + * - M = Manual Send Button Pressed 0=not pressed 1=pressed + * - C = Channel 00=CH1, 01=CH2, 10=CH3 + * - T = Temperature, 12 Bit 2's complement, scaled by 10 + * - H = Humidity = 7 Bit bin2dez 00-99, Display LL=10%, Display HH=110% (Range 20-90%) + * - X = Checksum, sum modulo 64 + * A Lidl AURIO (from 12/2018) with PCB marking YJ-T12 V02 has two extra bits in front. + * +*/ + +static const SubGhzBlockConst ws_protocol_gt_wt_02_const = { + .te_short = 500, + .te_long = 2000, + .te_delta = 150, + .min_count_bit_for_found = 37, +}; + +struct WSProtocolDecoderGT_WT02 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; +}; + +struct WSProtocolEncoderGT_WT02 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + GT_WT02DecoderStepReset = 0, + GT_WT02DecoderStepSaveDuration, + GT_WT02DecoderStepCheckDuration, +} GT_WT02DecoderStep; + +const SubGhzProtocolDecoder ws_protocol_gt_wt_02_decoder = { + .alloc = ws_protocol_decoder_gt_wt_02_alloc, + .free = ws_protocol_decoder_gt_wt_02_free, + + .feed = ws_protocol_decoder_gt_wt_02_feed, + .reset = ws_protocol_decoder_gt_wt_02_reset, + + .get_hash_data = ws_protocol_decoder_gt_wt_02_get_hash_data, + .serialize = ws_protocol_decoder_gt_wt_02_serialize, + .deserialize = ws_protocol_decoder_gt_wt_02_deserialize, + .get_string = ws_protocol_decoder_gt_wt_02_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_gt_wt_02_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_gt_wt_02 = { + .name = WS_PROTOCOL_GT_WT_02_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_gt_wt_02_decoder, + .encoder = &ws_protocol_gt_wt_02_encoder, +}; + +void* ws_protocol_decoder_gt_wt_02_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderGT_WT02* instance = malloc(sizeof(WSProtocolDecoderGT_WT02)); + instance->base.protocol = &ws_protocol_gt_wt_02; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_gt_wt_02_free(void* context) { + furi_assert(context); + WSProtocolDecoderGT_WT02* instance = context; + free(instance); +} + +void ws_protocol_decoder_gt_wt_02_reset(void* context) { + furi_assert(context); + WSProtocolDecoderGT_WT02* instance = context; + instance->decoder.parser_step = GT_WT02DecoderStepReset; +} + +static bool ws_protocol_gt_wt_02_check(WSProtocolDecoderGT_WT02* instance) { + if(!instance->decoder.decode_data) return false; + uint8_t sum = (instance->decoder.decode_data >> 5) & 0xe; + uint64_t temp_data = instance->decoder.decode_data >> 9; + for(uint8_t i = 0; i < 7; i++) { + sum += (temp_data >> (i * 4)) & 0xF; + } + return ((uint8_t)(instance->decoder.decode_data & 0x3F) == (sum & 0x3F)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_gt_wt_02_remote_controller(WSBlockGeneric* instance) { + instance->id = (instance->data >> 29) & 0xFF; + instance->battery_low = (instance->data >> 28) & 1; + instance->btn = (instance->data >> 27) & 1; + instance->channel = ((instance->data >> 25) & 0x3) + 1; + + if(!((instance->data >> 24) & 1)) { + instance->temp = (float)((instance->data >> 13) & 0x07FF) / 10.0f; + } else { + instance->temp = (float)((~(instance->data >> 13) & 0x07FF) + 1) / -10.0f; + } + + instance->humidity = (instance->data >> 6) & 0x7F; + if(instance->humidity <= 10) // actually the sensors sends 10 below working range of 20% + instance->humidity = 0; + else if(instance->humidity > 90) // actually the sensors sends 110 above working range of 90% + instance->humidity = 100; +} + +void ws_protocol_decoder_gt_wt_02_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderGT_WT02* instance = context; + + switch(instance->decoder.parser_step) { + case GT_WT02DecoderStepReset: + if((!level) && (DURATION_DIFF(duration, ws_protocol_gt_wt_02_const.te_short * 18) < + ws_protocol_gt_wt_02_const.te_delta * 8)) { + //Found syncPrefix + instance->decoder.parser_step = GT_WT02DecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + break; + + case GT_WT02DecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = GT_WT02DecoderStepCheckDuration; + } else { + instance->decoder.parser_step = GT_WT02DecoderStepReset; + } + break; + + case GT_WT02DecoderStepCheckDuration: + if(!level) { + if(DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_02_const.te_short) < + ws_protocol_gt_wt_02_const.te_delta) { + if(DURATION_DIFF(duration, ws_protocol_gt_wt_02_const.te_short * 18) < + ws_protocol_gt_wt_02_const.te_delta * 8) { + //Found syncPostfix + instance->decoder.parser_step = GT_WT02DecoderStepReset; + if((instance->decoder.decode_count_bit == + ws_protocol_gt_wt_02_const.min_count_bit_for_found) && + ws_protocol_gt_wt_02_check(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_gt_wt_02_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } else if(instance->decoder.decode_count_bit == 1) { + instance->decoder.parser_step = GT_WT02DecoderStepSaveDuration; + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else if( + DURATION_DIFF(duration, ws_protocol_gt_wt_02_const.te_long) < + ws_protocol_gt_wt_02_const.te_delta * 2) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = GT_WT02DecoderStepSaveDuration; + } else if( + DURATION_DIFF(duration, ws_protocol_gt_wt_02_const.te_long * 2) < + ws_protocol_gt_wt_02_const.te_delta * 4) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = GT_WT02DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = GT_WT02DecoderStepReset; + } + } else { + instance->decoder.parser_step = GT_WT02DecoderStepReset; + } + } else { + instance->decoder.parser_step = GT_WT02DecoderStepReset; + } + break; + } +} + +uint8_t ws_protocol_decoder_gt_wt_02_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderGT_WT02* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool ws_protocol_decoder_gt_wt_02_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderGT_WT02* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool ws_protocol_decoder_gt_wt_02_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderGT_WT02* instance = context; + bool ret = false; + do { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + ws_protocol_gt_wt_02_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void ws_protocol_decoder_gt_wt_02_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderGT_WT02* 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); +} diff --git a/applications/plugins/weather_station/protocols/gt_wt_02.h b/applications/plugins/weather_station/protocols/gt_wt_02.h new file mode 100644 index 000000000..f17d5baa0 --- /dev/null +++ b/applications/plugins/weather_station/protocols/gt_wt_02.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_GT_WT_02_NAME "GT-WT02" + +typedef struct WSProtocolDecoderGT_WT02 WSProtocolDecoderGT_WT02; +typedef struct WSProtocolEncoderGT_WT02 WSProtocolEncoderGT_WT02; + +extern const SubGhzProtocolDecoder ws_protocol_gt_wt_02_decoder; +extern const SubGhzProtocolEncoder ws_protocol_gt_wt_02_encoder; +extern const SubGhzProtocol ws_protocol_gt_wt_02; + +/** + * Allocate WSProtocolDecoderGT_WT02. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderGT_WT02* pointer to a WSProtocolDecoderGT_WT02 instance + */ +void* ws_protocol_decoder_gt_wt_02_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderGT_WT02. + * @param context Pointer to a WSProtocolDecoderGT_WT02 instance + */ +void ws_protocol_decoder_gt_wt_02_free(void* context); + +/** + * Reset decoder WSProtocolDecoderGT_WT02. + * @param context Pointer to a WSProtocolDecoderGT_WT02 instance + */ +void ws_protocol_decoder_gt_wt_02_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderGT_WT02 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_gt_wt_02_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderGT_WT02 instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_gt_wt_02_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderGT_WT02. + * @param context Pointer to a WSProtocolDecoderGT_WT02 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_protocol_decoder_gt_wt_02_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderGT_WT02. + * @param context Pointer to a WSProtocolDecoderGT_WT02 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_protocol_decoder_gt_wt_02_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderGT_WT02 instance + * @param output Resulting text + */ +void ws_protocol_decoder_gt_wt_02_get_string(void* context, FuriString* output); diff --git a/applications/plugins/weather_station/protocols/gt_wt_03.c b/applications/plugins/weather_station/protocols/gt_wt_03.c new file mode 100644 index 000000000..7831cf069 --- /dev/null +++ b/applications/plugins/weather_station/protocols/gt_wt_03.c @@ -0,0 +1,340 @@ +#include "gt_wt_03.h" + +#define TAG "WSProtocolGT_WT03" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/master/src/devices/gt_wt_03.c + * + * + * Globaltronics GT-WT-03 sensor on 433.92MHz. + * The 01-set sensor has 60 ms packet gap with 10 repeats. + * The 02-set sensor has no packet gap with 23 repeats. + * Example: + * {41} 17 cf be fa 6a 80 [ S1 C1 26,1 C 78.9 F 48% Bat-Good Manual-Yes ] + * {41} 17 cf be fa 6a 80 [ S1 C1 26,1 C 78.9 F 48% Bat-Good Manual-Yes Batt-Changed ] + * {41} 17 cf fe fa ea 80 [ S1 C1 26,1 C 78.9 F 48% Bat-Good Manual-No Batt-Changed ] + * {41} 01 cf 6f 11 b2 80 [ S2 C2 23,8 C 74.8 F 48% Bat-LOW Manual-No ] + * {41} 01 c8 d0 2b 76 80 [ S2 C3 -4,4 C 24.1 F 55% Bat-Good Manual-No Batt-Changed ] + * Format string: + * ID:8h HUM:8d B:b M:b C:2d TEMP:12d CHK:8h 1x + * Data layout: + * TYP IIIIIIII HHHHHHHH BMCCTTTT TTTTTTTT XXXXXXXX + * - I: Random Device Code: changes with battery reset + * - H: Humidity: 8 Bit 00-99, Display LL=10%, Display HH=110% (Range 20-95%) + * - B: Battery: 0=OK 1=LOW + * - M: Manual Send Button Pressed: 0=not pressed, 1=pressed + * - C: Channel: 00=CH1, 01=CH2, 10=CH3 + * - T: Temperature: 12 Bit 2's complement, scaled by 10, range-50.0 C (-50.1 shown as Lo) to +70.0 C (+70.1 C is shown as Hi) + * - X: Checksum, xor shifting key per byte + * Humidity: + * - the working range is 20-95 % + * - if "LL" in display view it sends 10 % + * - if "HH" in display view it sends 110% + * Checksum: + * Per byte xor the key for each 1-bit, shift per bit. Key list per bit, starting at MSB: + * - 0x00 [07] + * - 0x80 [06] + * - 0x40 [05] + * - 0x20 [04] + * - 0x10 [03] + * - 0x88 [02] + * - 0xc4 [01] + * - 0x62 [00] + * Note: this can also be seen as lower byte of a Galois/Fibonacci LFSR-16, gen 0x00, init 0x3100 (or 0x62 if reversed) resetting at every byte. + * Battery voltages: + * - U=<2,65V +- ~5% Battery indicator + * - U=>2.10C +- 5% plausible readings + * - U=2,00V +- ~5% Temperature offset -5°C Humidity offset unknown + * - U=<1,95V +- ~5% does not initialize anymore + * - U=1,90V +- 5% temperature offset -15°C + * - U=1,80V +- 5% Display is showing refresh pattern + * - U=1.75V +- ~5% TX causes cut out + * + */ + +static const SubGhzBlockConst ws_protocol_gt_wt_03_const = { + .te_short = 285, + .te_long = 570, + .te_delta = 120, + .min_count_bit_for_found = 41, +}; + +struct WSProtocolDecoderGT_WT03 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + + uint16_t header_count; +}; + +struct WSProtocolEncoderGT_WT03 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + GT_WT03DecoderStepReset = 0, + GT_WT03DecoderStepCheckPreambule, + GT_WT03DecoderStepSaveDuration, + GT_WT03DecoderStepCheckDuration, +} GT_WT03DecoderStep; + +const SubGhzProtocolDecoder ws_protocol_gt_wt_03_decoder = { + .alloc = ws_protocol_decoder_gt_wt_03_alloc, + .free = ws_protocol_decoder_gt_wt_03_free, + + .feed = ws_protocol_decoder_gt_wt_03_feed, + .reset = ws_protocol_decoder_gt_wt_03_reset, + + .get_hash_data = ws_protocol_decoder_gt_wt_03_get_hash_data, + .serialize = ws_protocol_decoder_gt_wt_03_serialize, + .deserialize = ws_protocol_decoder_gt_wt_03_deserialize, + .get_string = ws_protocol_decoder_gt_wt_03_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_gt_wt_03_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_gt_wt_03 = { + .name = WS_PROTOCOL_GT_WT_03_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_gt_wt_03_decoder, + .encoder = &ws_protocol_gt_wt_03_encoder, +}; + +void* ws_protocol_decoder_gt_wt_03_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderGT_WT03* instance = malloc(sizeof(WSProtocolDecoderGT_WT03)); + instance->base.protocol = &ws_protocol_gt_wt_03; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_gt_wt_03_free(void* context) { + furi_assert(context); + WSProtocolDecoderGT_WT03* instance = context; + free(instance); +} + +void ws_protocol_decoder_gt_wt_03_reset(void* context) { + furi_assert(context); + WSProtocolDecoderGT_WT03* instance = context; + instance->decoder.parser_step = GT_WT03DecoderStepReset; +} + +static bool ws_protocol_gt_wt_03_check_crc(WSProtocolDecoderGT_WT03* instance) { + uint8_t msg[] = { + instance->decoder.decode_data >> 33, + instance->decoder.decode_data >> 25, + instance->decoder.decode_data >> 17, + instance->decoder.decode_data >> 9}; + + uint8_t sum = 0; + for(unsigned k = 0; k < sizeof(msg); ++k) { + uint8_t data = msg[k]; + uint16_t key = 0x3100; + for(int i = 7; i >= 0; --i) { + // XOR key into sum if data bit is set + if((data >> i) & 1) sum ^= key & 0xff; + // roll the key right + key = (key >> 1); + } + } + return ((sum ^ (uint8_t)((instance->decoder.decode_data >> 1) & 0xFF)) == 0x2D); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_gt_wt_03_remote_controller(WSBlockGeneric* instance) { + instance->id = instance->data >> 33; + instance->humidity = (instance->data >> 25) & 0xFF; + + if(instance->humidity <= 10) { // actually the sensors sends 10 below working range of 20% + instance->humidity = 0; + } else if(instance->humidity > 95) { // actually the sensors sends 110 above working range of 90% + instance->humidity = 100; + } + + instance->battery_low = (instance->data >> 24) & 1; + instance->btn = (instance->data >> 23) & 1; + instance->channel = ((instance->data >> 21) & 0x03) + 1; + + if(!((instance->data >> 20) & 1)) { + instance->temp = (float)((instance->data >> 9) & 0x07FF) / 10.0f; + } else { + instance->temp = (float)((~(instance->data >> 9) & 0x07FF) + 1) / -10.0f; + } +} + +void ws_protocol_decoder_gt_wt_03_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderGT_WT03* instance = context; + + switch(instance->decoder.parser_step) { + case GT_WT03DecoderStepReset: + if((level) && (DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_short * 3) < + ws_protocol_gt_wt_03_const.te_delta * 2)) { + instance->decoder.parser_step = GT_WT03DecoderStepCheckPreambule; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + + case GT_WT03DecoderStepCheckPreambule: + if(level) { + instance->decoder.te_last = duration; + } else { + if((DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_03_const.te_short * 3) < + ws_protocol_gt_wt_03_const.te_delta * 2) && + (DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_short * 3) < + ws_protocol_gt_wt_03_const.te_delta * 2)) { + //Found preambule + instance->header_count++; + } else if(instance->header_count == 4) { + if((DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_03_const.te_short) < + ws_protocol_gt_wt_03_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_long) < + ws_protocol_gt_wt_03_const.te_delta)) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = GT_WT03DecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_03_const.te_long) < + ws_protocol_gt_wt_03_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_short) < + ws_protocol_gt_wt_03_const.te_delta)) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = GT_WT03DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = GT_WT03DecoderStepReset; + } + } else { + instance->decoder.parser_step = GT_WT03DecoderStepReset; + } + } + break; + + case GT_WT03DecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = GT_WT03DecoderStepCheckDuration; + } else { + instance->decoder.parser_step = GT_WT03DecoderStepReset; + } + break; + + case GT_WT03DecoderStepCheckDuration: + if(!level) { + if(((DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_03_const.te_short * 3) < + ws_protocol_gt_wt_03_const.te_delta * 2) && + (DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_short * 3) < + ws_protocol_gt_wt_03_const.te_delta * 2))) { + if((instance->decoder.decode_count_bit == + ws_protocol_gt_wt_03_const.min_count_bit_for_found) && + ws_protocol_gt_wt_03_check_crc(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_gt_wt_03_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->header_count = 1; + instance->decoder.parser_step = GT_WT03DecoderStepCheckPreambule; + break; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_03_const.te_short) < + ws_protocol_gt_wt_03_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_long) < + ws_protocol_gt_wt_03_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = GT_WT03DecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_03_const.te_long) < + ws_protocol_gt_wt_03_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_short) < + ws_protocol_gt_wt_03_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = GT_WT03DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = GT_WT03DecoderStepReset; + } + } else { + instance->decoder.parser_step = GT_WT03DecoderStepReset; + } + break; + } +} + +uint8_t ws_protocol_decoder_gt_wt_03_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderGT_WT03* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool ws_protocol_decoder_gt_wt_03_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderGT_WT03* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool ws_protocol_decoder_gt_wt_03_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderGT_WT03* instance = context; + bool ret = false; + do { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + ws_protocol_gt_wt_03_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void ws_protocol_decoder_gt_wt_03_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderGT_WT03* 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); +} diff --git a/applications/plugins/weather_station/protocols/gt_wt_03.h b/applications/plugins/weather_station/protocols/gt_wt_03.h new file mode 100644 index 000000000..fd9536e34 --- /dev/null +++ b/applications/plugins/weather_station/protocols/gt_wt_03.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_GT_WT_03_NAME "GT-WT03" + +typedef struct WSProtocolDecoderGT_WT03 WSProtocolDecoderGT_WT03; +typedef struct WSProtocolEncoderGT_WT03 WSProtocolEncoderGT_WT03; + +extern const SubGhzProtocolDecoder ws_protocol_gt_wt_03_decoder; +extern const SubGhzProtocolEncoder ws_protocol_gt_wt_03_encoder; +extern const SubGhzProtocol ws_protocol_gt_wt_03; + +/** + * Allocate WSProtocolDecoderGT_WT03. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderGT_WT03* pointer to a WSProtocolDecoderGT_WT03 instance + */ +void* ws_protocol_decoder_gt_wt_03_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderGT_WT03. + * @param context Pointer to a WSProtocolDecoderGT_WT03 instance + */ +void ws_protocol_decoder_gt_wt_03_free(void* context); + +/** + * Reset decoder WSProtocolDecoderGT_WT03. + * @param context Pointer to a WSProtocolDecoderGT_WT03 instance + */ +void ws_protocol_decoder_gt_wt_03_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderGT_WT03 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_gt_wt_03_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderGT_WT03 instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_gt_wt_03_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderGT_WT03. + * @param context Pointer to a WSProtocolDecoderGT_WT03 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_protocol_decoder_gt_wt_03_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderGT_WT03. + * @param context Pointer to a WSProtocolDecoderGT_WT03 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_protocol_decoder_gt_wt_03_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderGT_WT03 instance + * @param output Resulting text + */ +void ws_protocol_decoder_gt_wt_03_get_string(void* context, FuriString* output); diff --git a/applications/plugins/weather_station/protocols/infactory.c b/applications/plugins/weather_station/protocols/infactory.c new file mode 100644 index 000000000..2d444d981 --- /dev/null +++ b/applications/plugins/weather_station/protocols/infactory.c @@ -0,0 +1,296 @@ +#include "infactory.h" + +#define TAG "WSProtocolInfactory" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/master/src/devices/infactory.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_infactory_const = { + .te_short = 500, + .te_long = 2000, + .te_delta = 150, + .min_count_bit_for_found = 40, +}; + +struct WSProtocolDecoderInfactory { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + + uint16_t header_count; +}; + +struct WSProtocolEncoderInfactory { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + InfactoryDecoderStepReset = 0, + InfactoryDecoderStepCheckPreambule, + InfactoryDecoderStepSaveDuration, + InfactoryDecoderStepCheckDuration, +} InfactoryDecoderStep; + +const SubGhzProtocolDecoder ws_protocol_infactory_decoder = { + .alloc = ws_protocol_decoder_infactory_alloc, + .free = ws_protocol_decoder_infactory_free, + + .feed = ws_protocol_decoder_infactory_feed, + .reset = ws_protocol_decoder_infactory_reset, + + .get_hash_data = ws_protocol_decoder_infactory_get_hash_data, + .serialize = ws_protocol_decoder_infactory_serialize, + .deserialize = ws_protocol_decoder_infactory_deserialize, + .get_string = ws_protocol_decoder_infactory_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_infactory_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_infactory = { + .name = WS_PROTOCOL_INFACTORY_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_infactory_decoder, + .encoder = &ws_protocol_infactory_encoder, +}; + +void* ws_protocol_decoder_infactory_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderInfactory* instance = malloc(sizeof(WSProtocolDecoderInfactory)); + instance->base.protocol = &ws_protocol_infactory; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_infactory_free(void* context) { + furi_assert(context); + WSProtocolDecoderInfactory* instance = context; + free(instance); +} + +void ws_protocol_decoder_infactory_reset(void* context) { + furi_assert(context); + WSProtocolDecoderInfactory* instance = context; + instance->decoder.parser_step = InfactoryDecoderStepReset; +} + +static bool ws_protocol_infactory_check_crc(WSProtocolDecoderInfactory* 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_infactory_remote_controller(WSBlockGeneric* instance) { + instance->id = instance->data >> 32; + instance->battery_low = (instance->data >> 26) & 1; + instance->btn = WS_NO_BTN; + instance->temp = ws_block_generic_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_infactory_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderInfactory* instance = context; + + switch(instance->decoder.parser_step) { + case InfactoryDecoderStepReset: + if((level) && (DURATION_DIFF(duration, ws_protocol_infactory_const.te_short * 2) < + ws_protocol_infactory_const.te_delta * 2)) { + instance->decoder.parser_step = InfactoryDecoderStepCheckPreambule; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + + case InfactoryDecoderStepCheckPreambule: + if(level) { + instance->decoder.te_last = duration; + } else { + if((DURATION_DIFF(instance->decoder.te_last, ws_protocol_infactory_const.te_short * 2) < + ws_protocol_infactory_const.te_delta * 2) && + (DURATION_DIFF(duration, ws_protocol_infactory_const.te_short * 2) < + ws_protocol_infactory_const.te_delta * 2)) { + //Found preambule + instance->header_count++; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_infactory_const.te_short) < + ws_protocol_infactory_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_infactory_const.te_short * 16) < + ws_protocol_infactory_const.te_delta * 8)) { + //Found syncPrefix + if(instance->header_count > 3) { + instance->decoder.parser_step = InfactoryDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + } else { + instance->decoder.parser_step = InfactoryDecoderStepReset; + } + } + break; + + case InfactoryDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = InfactoryDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = InfactoryDecoderStepReset; + } + break; + + case InfactoryDecoderStepCheckDuration: + if(!level) { + if(duration >= ((uint32_t)ws_protocol_infactory_const.te_short * 30)) { + //Found syncPostfix + if((instance->decoder.decode_count_bit == + ws_protocol_infactory_const.min_count_bit_for_found) && + ws_protocol_infactory_check_crc(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_infactory_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 = InfactoryDecoderStepReset; + break; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_infactory_const.te_short) < + ws_protocol_infactory_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_infactory_const.te_long) < + ws_protocol_infactory_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = InfactoryDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_infactory_const.te_short) < + ws_protocol_infactory_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_infactory_const.te_long * 2) < + ws_protocol_infactory_const.te_delta * 4)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = InfactoryDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = InfactoryDecoderStepReset; + } + } else { + instance->decoder.parser_step = InfactoryDecoderStepReset; + } + break; + } +} + +uint8_t ws_protocol_decoder_infactory_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderInfactory* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool ws_protocol_decoder_infactory_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderInfactory* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool ws_protocol_decoder_infactory_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderInfactory* instance = context; + bool ret = false; + do { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + ws_protocol_infactory_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void ws_protocol_decoder_infactory_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderInfactory* 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); +} diff --git a/applications/plugins/weather_station/protocols/infactory.h b/applications/plugins/weather_station/protocols/infactory.h new file mode 100644 index 000000000..2792ab987 --- /dev/null +++ b/applications/plugins/weather_station/protocols/infactory.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_INFACTORY_NAME "inFactory-TH" + +typedef struct WSProtocolDecoderInfactory WSProtocolDecoderInfactory; +typedef struct WSProtocolEncoderInfactory WSProtocolEncoderInfactory; + +extern const SubGhzProtocolDecoder ws_protocol_infactory_decoder; +extern const SubGhzProtocolEncoder ws_protocol_infactory_encoder; +extern const SubGhzProtocol ws_protocol_infactory; + +/** + * Allocate WSProtocolDecoderInfactory. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderInfactory* pointer to a WSProtocolDecoderInfactory instance + */ +void* ws_protocol_decoder_infactory_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderInfactory. + * @param context Pointer to a WSProtocolDecoderInfactory instance + */ +void ws_protocol_decoder_infactory_free(void* context); + +/** + * Reset decoder WSProtocolDecoderInfactory. + * @param context Pointer to a WSProtocolDecoderInfactory instance + */ +void ws_protocol_decoder_infactory_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderInfactory instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_infactory_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderInfactory instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_infactory_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderInfactory. + * @param context Pointer to a WSProtocolDecoderInfactory instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_protocol_decoder_infactory_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderInfactory. + * @param context Pointer to a WSProtocolDecoderInfactory instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_protocol_decoder_infactory_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderInfactory instance + * @param output Resulting text + */ +void ws_protocol_decoder_infactory_get_string(void* context, FuriString* output); diff --git a/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.c b/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.c new file mode 100644 index 000000000..e4b612250 --- /dev/null +++ b/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.c @@ -0,0 +1,297 @@ +#include "lacrosse_tx141thbv2.h" + +#define TAG "WSProtocolLaCrosse_TX141THBv2" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/master/src/devices/lacrosse_tx141x.c + * + * iiii iiii | bkcc tttt | tttt tttt | hhhh hhhh | cccc cccc | u + * - i: identification; changes on battery switch + * - c: lfsr_digest8_reflect; + * - u: unknown; + * - b: battery low; flag to indicate low battery voltage + * - h: Humidity; + * - t: Temperature; in °F as binary number with one decimal place + 50 °F offset + * - n: Channel; Channel number 1 - 3 + */ + +static const SubGhzBlockConst ws_protocol_lacrosse_tx141thbv2_const = { + .te_short = 250, + .te_long = 500, + .te_delta = 120, + .min_count_bit_for_found = 41, +}; + +struct WSProtocolDecoderLaCrosse_TX141THBv2 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + + uint16_t header_count; +}; + +struct WSProtocolEncoderLaCrosse_TX141THBv2 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + LaCrosse_TX141THBv2DecoderStepReset = 0, + LaCrosse_TX141THBv2DecoderStepCheckPreambule, + LaCrosse_TX141THBv2DecoderStepSaveDuration, + LaCrosse_TX141THBv2DecoderStepCheckDuration, +} LaCrosse_TX141THBv2DecoderStep; + +const SubGhzProtocolDecoder ws_protocol_lacrosse_tx141thbv2_decoder = { + .alloc = ws_protocol_decoder_lacrosse_tx141thbv2_alloc, + .free = ws_protocol_decoder_lacrosse_tx141thbv2_free, + + .feed = ws_protocol_decoder_lacrosse_tx141thbv2_feed, + .reset = ws_protocol_decoder_lacrosse_tx141thbv2_reset, + + .get_hash_data = ws_protocol_decoder_lacrosse_tx141thbv2_get_hash_data, + .serialize = ws_protocol_decoder_lacrosse_tx141thbv2_serialize, + .deserialize = ws_protocol_decoder_lacrosse_tx141thbv2_deserialize, + .get_string = ws_protocol_decoder_lacrosse_tx141thbv2_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_lacrosse_tx141thbv2_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_lacrosse_tx141thbv2 = { + .name = WS_PROTOCOL_LACROSSE_TX141THBV2_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_lacrosse_tx141thbv2_decoder, + .encoder = &ws_protocol_lacrosse_tx141thbv2_encoder, +}; + +void* ws_protocol_decoder_lacrosse_tx141thbv2_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderLaCrosse_TX141THBv2* instance = + malloc(sizeof(WSProtocolDecoderLaCrosse_TX141THBv2)); + instance->base.protocol = &ws_protocol_lacrosse_tx141thbv2; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_lacrosse_tx141thbv2_free(void* context) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; + free(instance); +} + +void ws_protocol_decoder_lacrosse_tx141thbv2_reset(void* context) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset; +} + +static bool + ws_protocol_lacrosse_tx141thbv2_check_crc(WSProtocolDecoderLaCrosse_TX141THBv2* instance) { + if(!instance->decoder.decode_data) return false; + uint8_t msg[] = { + instance->decoder.decode_data >> 33, + instance->decoder.decode_data >> 25, + instance->decoder.decode_data >> 17, + instance->decoder.decode_data >> 9}; + + uint8_t crc = subghz_protocol_blocks_lfsr_digest8_reflect(msg, 4, 0x31, 0xF4); + return (crc == ((instance->decoder.decode_data >> 1) & 0xFF)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_lacrosse_tx141thbv2_remote_controller(WSBlockGeneric* instance) { + instance->id = instance->data >> 33; + instance->battery_low = (instance->data >> 32) & 1; + instance->btn = (instance->data >> 31) & 1; + instance->channel = ((instance->data >> 29) & 0x03) + 1; + instance->temp = ((float)((instance->data >> 17) & 0x0FFF) - 500.0f) / 10.0f; + instance->humidity = (instance->data >> 9) & 0xFF; +} + +void ws_protocol_decoder_lacrosse_tx141thbv2_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; + + switch(instance->decoder.parser_step) { + case LaCrosse_TX141THBv2DecoderStepReset: + if((level) && + (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2)) { + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepCheckPreambule; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + + case LaCrosse_TX141THBv2DecoderStepCheckPreambule: + if(level) { + instance->decoder.te_last = duration; + } else { + if((DURATION_DIFF( + instance->decoder.te_last, + ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2) && + (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2)) { + //Found preambule + instance->header_count++; + } else if(instance->header_count == 4) { + if((DURATION_DIFF( + instance->decoder.te_last, + ws_protocol_lacrosse_tx141thbv2_const.te_short) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_long) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta)) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, + ws_protocol_lacrosse_tx141thbv2_const.te_long) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta)) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset; + } + } else { + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset; + } + } + break; + + case LaCrosse_TX141THBv2DecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepCheckDuration; + } else { + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset; + } + break; + + case LaCrosse_TX141THBv2DecoderStepCheckDuration: + if(!level) { + if(((DURATION_DIFF( + instance->decoder.te_last, + ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2) && + (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2))) { + if((instance->decoder.decode_count_bit == + ws_protocol_lacrosse_tx141thbv2_const.min_count_bit_for_found) && + ws_protocol_lacrosse_tx141thbv2_check_crc(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_lacrosse_tx141thbv2_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->header_count = 1; + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepCheckPreambule; + break; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, ws_protocol_lacrosse_tx141thbv2_const.te_short) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_long) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, ws_protocol_lacrosse_tx141thbv2_const.te_long) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset; + } + } else { + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset; + } + break; + } +} + +uint8_t ws_protocol_decoder_lacrosse_tx141thbv2_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool ws_protocol_decoder_lacrosse_tx141thbv2_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool ws_protocol_decoder_lacrosse_tx141thbv2_deserialize( + void* context, + FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; + bool ret = false; + do { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + ws_protocol_lacrosse_tx141thbv2_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void ws_protocol_decoder_lacrosse_tx141thbv2_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX141THBv2* 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); +} diff --git a/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.h b/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.h new file mode 100644 index 000000000..941b01058 --- /dev/null +++ b/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.h @@ -0,0 +1,81 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_LACROSSE_TX141THBV2_NAME "TX141THBv2" + +typedef struct WSProtocolDecoderLaCrosse_TX141THBv2 WSProtocolDecoderLaCrosse_TX141THBv2; +typedef struct WSProtocolEncoderLaCrosse_TX141THBv2 WSProtocolEncoderLaCrosse_TX141THBv2; + +extern const SubGhzProtocolDecoder ws_protocol_lacrosse_tx141thbv2_decoder; +extern const SubGhzProtocolEncoder ws_protocol_lacrosse_tx141thbv2_encoder; +extern const SubGhzProtocol ws_protocol_lacrosse_tx141thbv2; + +/** + * Allocate WSProtocolDecoderLaCrosse_TX141THBv2. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderLaCrosse_TX141THBv2* pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance + */ +void* ws_protocol_decoder_lacrosse_tx141thbv2_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderLaCrosse_TX141THBv2. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance + */ +void ws_protocol_decoder_lacrosse_tx141thbv2_free(void* context); + +/** + * Reset decoder WSProtocolDecoderLaCrosse_TX141THBv2. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance + */ +void ws_protocol_decoder_lacrosse_tx141thbv2_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_lacrosse_tx141thbv2_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_lacrosse_tx141thbv2_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderLaCrosse_TX141THBv2. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_protocol_decoder_lacrosse_tx141thbv2_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderLaCrosse_TX141THBv2. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_protocol_decoder_lacrosse_tx141thbv2_deserialize( + void* context, + FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance + * @param output Resulting text + */ +void ws_protocol_decoder_lacrosse_tx141thbv2_get_string(void* context, FuriString* output); diff --git a/applications/plugins/weather_station/protocols/nexus_th.c b/applications/plugins/weather_station/protocols/nexus_th.c new file mode 100644 index 000000000..38f2fe895 --- /dev/null +++ b/applications/plugins/weather_station/protocols/nexus_th.c @@ -0,0 +1,264 @@ +#include "nexus_th.h" + +#define TAG "WSProtocolNexus_TH" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/master/src/devices/nexus.c + * + * Nexus sensor protocol with ID, temperature and optional humidity + * also FreeTec (Pearl) NC-7345 sensors for FreeTec Weatherstation NC-7344, + * also infactory/FreeTec (Pearl) NX-3980 sensors for infactory/FreeTec NX-3974 station, + * also Solight TE82S sensors for Solight TE76/TE82/TE83/TE84 stations, + * also TFA 30.3209.02 temperature/humidity sensor. + * The sensor sends 36 bits 12 times, + * the packets are ppm modulated (distance coding) with a pulse of ~500 us + * followed by a short gap of ~1000 us for a 0 bit or a long ~2000 us gap for a + * 1 bit, the sync gap is ~4000 us. + * The data is grouped in 9 nibbles: + * [id0] [id1] [flags] [temp0] [temp1] [temp2] [const] [humi0] [humi1] + * - The 8-bit id changes when the battery is changed in the sensor. + * - flags are 4 bits B 0 C C, where B is the battery status: 1=OK, 0=LOW + * - and CC is the channel: 0=CH1, 1=CH2, 2=CH3 + * - temp is 12 bit signed scaled by 10 + * - const is always 1111 (0x0F) + * - humidity is 8 bits + * The sensors can be bought at Clas Ohlsen (Nexus) and Pearl (infactory/FreeTec). + * + */ + +#define NEXUS_TH_CONST_DATA 0b1111 + +static const SubGhzBlockConst ws_protocol_nexus_th_const = { + .te_short = 500, + .te_long = 2000, + .te_delta = 150, + .min_count_bit_for_found = 36, +}; + +struct WSProtocolDecoderNexus_TH { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; +}; + +struct WSProtocolEncoderNexus_TH { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + Nexus_THDecoderStepReset = 0, + Nexus_THDecoderStepSaveDuration, + Nexus_THDecoderStepCheckDuration, +} Nexus_THDecoderStep; + +const SubGhzProtocolDecoder ws_protocol_nexus_th_decoder = { + .alloc = ws_protocol_decoder_nexus_th_alloc, + .free = ws_protocol_decoder_nexus_th_free, + + .feed = ws_protocol_decoder_nexus_th_feed, + .reset = ws_protocol_decoder_nexus_th_reset, + + .get_hash_data = ws_protocol_decoder_nexus_th_get_hash_data, + .serialize = ws_protocol_decoder_nexus_th_serialize, + .deserialize = ws_protocol_decoder_nexus_th_deserialize, + .get_string = ws_protocol_decoder_nexus_th_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_nexus_th_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_nexus_th = { + .name = WS_PROTOCOL_NEXUS_TH_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_nexus_th_decoder, + .encoder = &ws_protocol_nexus_th_encoder, +}; + +void* ws_protocol_decoder_nexus_th_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderNexus_TH* instance = malloc(sizeof(WSProtocolDecoderNexus_TH)); + instance->base.protocol = &ws_protocol_nexus_th; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_nexus_th_free(void* context) { + furi_assert(context); + WSProtocolDecoderNexus_TH* instance = context; + free(instance); +} + +void ws_protocol_decoder_nexus_th_reset(void* context) { + furi_assert(context); + WSProtocolDecoderNexus_TH* instance = context; + instance->decoder.parser_step = Nexus_THDecoderStepReset; +} + +static bool ws_protocol_nexus_th_check(WSProtocolDecoderNexus_TH* instance) { + uint8_t type = (instance->decoder.decode_data >> 8) & 0x0F; + + if((type == NEXUS_TH_CONST_DATA) && ((instance->decoder.decode_data >> 4) != 0xffffffff)) { + return true; + } else { + return false; + } + return true; +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_nexus_th_remote_controller(WSBlockGeneric* instance) { + instance->id = (instance->data >> 28) & 0xFF; + instance->battery_low = !((instance->data >> 27) & 1); + instance->channel = ((instance->data >> 24) & 0x03) + 1; + instance->btn = WS_NO_BTN; + if(!((instance->data >> 23) & 1)) { + instance->temp = (float)((instance->data >> 12) & 0x07FF) / 10.0f; + } else { + instance->temp = (float)((~(instance->data >> 12) & 0x07FF) + 1) / -10.0f; + } + + instance->humidity = instance->data & 0xFF; + if(instance->humidity > 95) + instance->humidity = 95; + else if(instance->humidity < 20) + instance->humidity = 20; +} + +void ws_protocol_decoder_nexus_th_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderNexus_TH* instance = context; + + switch(instance->decoder.parser_step) { + case Nexus_THDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, ws_protocol_nexus_th_const.te_short * 8) < + ws_protocol_nexus_th_const.te_delta * 4)) { + //Found sync + instance->decoder.parser_step = Nexus_THDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + break; + + case Nexus_THDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = Nexus_THDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = Nexus_THDecoderStepReset; + } + break; + + case Nexus_THDecoderStepCheckDuration: + if(!level) { + if(DURATION_DIFF(duration, ws_protocol_nexus_th_const.te_short * 8) < + ws_protocol_nexus_th_const.te_delta * 4) { + //Found sync + instance->decoder.parser_step = Nexus_THDecoderStepReset; + if((instance->decoder.decode_count_bit == + ws_protocol_nexus_th_const.min_count_bit_for_found) && + ws_protocol_nexus_th_check(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_nexus_th_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + instance->decoder.parser_step = Nexus_THDecoderStepCheckDuration; + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + + break; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_nexus_th_const.te_short) < + ws_protocol_nexus_th_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_nexus_th_const.te_short * 2) < + ws_protocol_nexus_th_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = Nexus_THDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_nexus_th_const.te_short) < + ws_protocol_nexus_th_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_nexus_th_const.te_short * 4) < + ws_protocol_nexus_th_const.te_delta * 4)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = Nexus_THDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = Nexus_THDecoderStepReset; + } + } else { + instance->decoder.parser_step = Nexus_THDecoderStepReset; + } + break; + } +} + +uint8_t ws_protocol_decoder_nexus_th_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderNexus_TH* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool ws_protocol_decoder_nexus_th_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderNexus_TH* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool ws_protocol_decoder_nexus_th_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderNexus_TH* instance = context; + bool ret = false; + do { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + ws_protocol_nexus_th_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void ws_protocol_decoder_nexus_th_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderNexus_TH* 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); +} diff --git a/applications/plugins/weather_station/protocols/nexus_th.h b/applications/plugins/weather_station/protocols/nexus_th.h new file mode 100644 index 000000000..5450f0040 --- /dev/null +++ b/applications/plugins/weather_station/protocols/nexus_th.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_NEXUS_TH_NAME "Nexus-TH" + +typedef struct WSProtocolDecoderNexus_TH WSProtocolDecoderNexus_TH; +typedef struct WSProtocolEncoderNexus_TH WSProtocolEncoderNexus_TH; + +extern const SubGhzProtocolDecoder ws_protocol_nexus_th_decoder; +extern const SubGhzProtocolEncoder ws_protocol_nexus_th_encoder; +extern const SubGhzProtocol ws_protocol_nexus_th; + +/** + * Allocate WSProtocolDecoderNexus_TH. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderNexus_TH* pointer to a WSProtocolDecoderNexus_TH instance + */ +void* ws_protocol_decoder_nexus_th_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderNexus_TH. + * @param context Pointer to a WSProtocolDecoderNexus_TH instance + */ +void ws_protocol_decoder_nexus_th_free(void* context); + +/** + * Reset decoder WSProtocolDecoderNexus_TH. + * @param context Pointer to a WSProtocolDecoderNexus_TH instance + */ +void ws_protocol_decoder_nexus_th_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderNexus_TH instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_nexus_th_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderNexus_TH instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_nexus_th_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderNexus_TH. + * @param context Pointer to a WSProtocolDecoderNexus_TH instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_protocol_decoder_nexus_th_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderNexus_TH. + * @param context Pointer to a WSProtocolDecoderNexus_TH instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_protocol_decoder_nexus_th_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderNexus_TH instance + * @param output Resulting text + */ +void ws_protocol_decoder_nexus_th_get_string(void* context, FuriString* output); diff --git a/applications/plugins/weather_station/protocols/oregon2.c b/applications/plugins/weather_station/protocols/oregon2.c new file mode 100644 index 000000000..8779e9596 --- /dev/null +++ b/applications/plugins/weather_station/protocols/oregon2.c @@ -0,0 +1,423 @@ +#include "oregon2.h" + +#include +#include +#include +#include +#include "ws_generic.h" + +#include +#include + +#define TAG "WSProtocolOregon2" + +static const SubGhzBlockConst ws_oregon2_const = { + .te_long = 1000, + .te_short = 500, + .te_delta = 200, + .min_count_bit_for_found = 32, +}; + +#define OREGON2_PREAMBLE_BITS 19 +#define OREGON2_PREAMBLE_MASK 0b1111111111111111111 +#define OREGON2_SENSOR_ID(d) (((d) >> 16) & 0xFFFF) +#define OREGON2_CHECKSUM_BITS 8 + +// 15 ones + 0101 (inverted A) +#define OREGON2_PREAMBLE 0b1111111111111110101 + +// bit indicating the low battery +#define OREGON2_FLAG_BAT_LOW 0x4 + +/// Documentation for Oregon Scientific protocols can be found here: +/// http://wmrx00.sourceforge.net/Arduino/OregonScientific-RF-Protocols.pdf +// Sensors ID +#define ID_THGR122N 0x1d20 +#define ID_THGR968 0x1d30 +#define ID_BTHR918 0x5d50 +#define ID_BHTR968 0x5d60 +#define ID_RGR968 0x2d10 +#define ID_THR228N 0xec40 +#define ID_THN132N 0xec40 // same as THR228N but different packet size +#define ID_RTGN318 0x0cc3 // warning: id is from 0x0cc3 and 0xfcc3 +#define ID_RTGN129 0x0cc3 // same as RTGN318 but different packet size +#define ID_THGR810 0xf824 // This might be ID_THGR81, but what's true is lost in (git) history +#define ID_THGR810a 0xf8b4 // unconfirmed version +#define ID_THN802 0xc844 +#define ID_PCR800 0x2914 +#define ID_PCR800a 0x2d14 // Different PCR800 ID - AU version I think +#define ID_WGR800 0x1984 +#define ID_WGR800a 0x1994 // unconfirmed version +#define ID_WGR968 0x3d00 +#define ID_UV800 0xd874 +#define ID_THN129 0xcc43 // THN129 Temp only +#define ID_RTHN129 0x0cd3 // RTHN129 Temp, clock sensors +#define ID_RTHN129_1 0x9cd3 +#define ID_RTHN129_2 0xacd3 +#define ID_RTHN129_3 0xbcd3 +#define ID_RTHN129_4 0xccd3 +#define ID_RTHN129_5 0xdcd3 +#define ID_BTHGN129 0x5d53 // Baro, Temp, Hygro sensor +#define ID_UVR128 0xec70 +#define ID_THGR328N 0xcc23 // Temp & Hygro sensor similar to THR228N with 5 channel instead of 3 +#define ID_RTGR328N_1 0xdcc3 // RTGR328N_[1-5] RFclock(date &time)&Temp&Hygro sensor +#define ID_RTGR328N_2 0xccc3 +#define ID_RTGR328N_3 0xbcc3 +#define ID_RTGR328N_4 0xacc3 +#define ID_RTGR328N_5 0x9cc3 +#define ID_RTGR328N_6 0x8ce3 // RTGR328N_6&7 RFclock(date &time)&Temp&Hygro sensor like THGR328N +#define ID_RTGR328N_7 0x8ae3 + +struct WSProtocolDecoderOregon2 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + ManchesterState manchester_state; + bool prev_bit; + bool have_bit; + + uint8_t var_bits; + uint32_t var_data; +}; + +typedef struct WSProtocolDecoderOregon2 WSProtocolDecoderOregon2; + +typedef enum { + Oregon2DecoderStepReset = 0, + Oregon2DecoderStepFoundPreamble, + Oregon2DecoderStepVarData, +} Oregon2DecoderStep; + +void* ws_protocol_decoder_oregon2_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderOregon2* instance = malloc(sizeof(WSProtocolDecoderOregon2)); + instance->base.protocol = &ws_protocol_oregon2; + instance->generic.protocol_name = instance->base.protocol->name; + instance->generic.humidity = WS_NO_HUMIDITY; + instance->generic.temp = WS_NO_TEMPERATURE; + instance->generic.btn = WS_NO_BTN; + instance->generic.channel = WS_NO_CHANNEL; + instance->generic.battery_low = WS_NO_BATT; + instance->generic.id = WS_NO_ID; + return instance; +} + +void ws_protocol_decoder_oregon2_free(void* context) { + furi_assert(context); + WSProtocolDecoderOregon2* instance = context; + free(instance); +} + +void ws_protocol_decoder_oregon2_reset(void* context) { + furi_assert(context); + WSProtocolDecoderOregon2* instance = context; + instance->decoder.parser_step = Oregon2DecoderStepReset; + instance->decoder.decode_data = 0UL; + instance->decoder.decode_count_bit = 0; + manchester_advance( + instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL); + instance->have_bit = false; + instance->var_data = 0; + instance->var_bits = 0; +} + +static ManchesterEvent level_and_duration_to_event(bool level, uint32_t duration) { + bool is_long = false; + + if(DURATION_DIFF(duration, ws_oregon2_const.te_long) < ws_oregon2_const.te_delta) { + is_long = true; + } else if(DURATION_DIFF(duration, ws_oregon2_const.te_short) < ws_oregon2_const.te_delta) { + is_long = false; + } else { + return ManchesterEventReset; + } + + if(level) + return is_long ? ManchesterEventLongHigh : ManchesterEventShortHigh; + else + return is_long ? ManchesterEventLongLow : ManchesterEventShortLow; +} + +// From sensor id code return amount of bits in variable section +// https://temofeev.ru/info/articles/o-dekodirovanii-protokola-pogodnykh-datchikov-oregon-scientific +static uint8_t oregon2_sensor_id_var_bits(uint16_t sensor_id) { + switch(sensor_id) { + case ID_THR228N: + case ID_RTHN129_1: + case ID_RTHN129_2: + case ID_RTHN129_3: + case ID_RTHN129_4: + case ID_RTHN129_5: + return 16; + case ID_THGR122N: + return 24; + default: + return 0; + } +} + +static void ws_oregon2_decode_const_data(WSBlockGeneric* ws_block) { + ws_block->id = OREGON2_SENSOR_ID(ws_block->data); + + uint8_t ch_bits = (ws_block->data >> 12) & 0xF; + ws_block->channel = 1; + while(ch_bits > 1) { + ws_block->channel++; + ch_bits >>= 1; + } + + ws_block->battery_low = (ws_block->data & OREGON2_FLAG_BAT_LOW) ? 1 : 0; +} + +uint16_t bcd_decode_short(uint32_t data) { + return (data & 0xF) * 10 + ((data >> 4) & 0xF); +} + +static float ws_oregon2_decode_temp(uint32_t data) { + int32_t temp_val; + temp_val = bcd_decode_short(data >> 4); + temp_val *= 10; + temp_val += (data >> 12) & 0xF; + if(data & 0xF) temp_val = -temp_val; + return (float)temp_val / 10.0; +} + +static void ws_oregon2_decode_var_data(WSBlockGeneric* ws_b, uint16_t sensor_id, uint32_t data) { + switch(sensor_id) { + case ID_THR228N: + case ID_RTHN129_1: + case ID_RTHN129_2: + case ID_RTHN129_3: + case ID_RTHN129_4: + case ID_RTHN129_5: + ws_b->temp = ws_oregon2_decode_temp(data); + ws_b->humidity = WS_NO_HUMIDITY; + return; + case ID_THGR122N: + ws_b->humidity = bcd_decode_short(data); + ws_b->temp = ws_oregon2_decode_temp(data >> 8); + return; + default: + break; + } +} + +void ws_protocol_decoder_oregon2_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderOregon2* instance = context; + // oregon v2.1 signal is inverted + ManchesterEvent event = level_and_duration_to_event(!level, duration); + bool data; + + // low-level bit sequence decoding + if(event == ManchesterEventReset) { + instance->decoder.parser_step = Oregon2DecoderStepReset; + instance->have_bit = false; + instance->decoder.decode_data = 0UL; + instance->decoder.decode_count_bit = 0; + } + if(manchester_advance(instance->manchester_state, event, &instance->manchester_state, &data)) { + if(instance->have_bit) { + if(!instance->prev_bit && data) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } else if(instance->prev_bit && !data) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } else { + ws_protocol_decoder_oregon2_reset(context); + } + instance->have_bit = false; + } else { + instance->prev_bit = data; + instance->have_bit = true; + } + } + + switch(instance->decoder.parser_step) { + case Oregon2DecoderStepReset: + // waiting for fixed oregon2 preamble + if(instance->decoder.decode_count_bit >= OREGON2_PREAMBLE_BITS && + ((instance->decoder.decode_data & OREGON2_PREAMBLE_MASK) == OREGON2_PREAMBLE)) { + instance->decoder.parser_step = Oregon2DecoderStepFoundPreamble; + instance->decoder.decode_count_bit = 0; + instance->decoder.decode_data = 0UL; + } + break; + case Oregon2DecoderStepFoundPreamble: + // waiting for fixed oregon2 data + if(instance->decoder.decode_count_bit == 32) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + instance->decoder.decode_data = 0UL; + instance->decoder.decode_count_bit = 0; + + // reverse nibbles in decoded data + instance->generic.data = (instance->generic.data & 0x55555555) << 1 | + (instance->generic.data & 0xAAAAAAAA) >> 1; + instance->generic.data = (instance->generic.data & 0x33333333) << 2 | + (instance->generic.data & 0xCCCCCCCC) >> 2; + + ws_oregon2_decode_const_data(&instance->generic); + instance->var_bits = + oregon2_sensor_id_var_bits(OREGON2_SENSOR_ID(instance->generic.data)); + + if(!instance->var_bits) { + // sensor is not supported, stop decoding, but showing the decoded fixed part + instance->decoder.parser_step = Oregon2DecoderStepReset; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } else { + instance->decoder.parser_step = Oregon2DecoderStepVarData; + } + } + break; + case Oregon2DecoderStepVarData: + // waiting for variable (sensor-specific data) + if(instance->decoder.decode_count_bit == instance->var_bits + OREGON2_CHECKSUM_BITS) { + instance->var_data = instance->decoder.decode_data & 0xFFFFFFFF; + + // reverse nibbles in var data + instance->var_data = (instance->var_data & 0x55555555) << 1 | + (instance->var_data & 0xAAAAAAAA) >> 1; + instance->var_data = (instance->var_data & 0x33333333) << 2 | + (instance->var_data & 0xCCCCCCCC) >> 2; + + ws_oregon2_decode_var_data( + &instance->generic, + OREGON2_SENSOR_ID(instance->generic.data), + instance->var_data >> OREGON2_CHECKSUM_BITS); + + instance->decoder.parser_step = Oregon2DecoderStepReset; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + break; + } +} + +uint8_t ws_protocol_decoder_oregon2_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderOregon2* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool ws_protocol_decoder_oregon2_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderOregon2* instance = context; + if(!ws_block_generic_serialize(&instance->generic, flipper_format, preset)) return false; + uint32_t temp = instance->var_bits; + if(!flipper_format_write_uint32(flipper_format, "VarBits", &temp, 1)) { + FURI_LOG_E(TAG, "Error adding VarBits"); + return false; + } + if(!flipper_format_write_hex( + flipper_format, + "VarData", + (const uint8_t*)&instance->var_data, + sizeof(instance->var_data))) { + FURI_LOG_E(TAG, "Error adding VarData"); + return false; + } + return true; +} + +bool ws_protocol_decoder_oregon2_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderOregon2* instance = context; + bool ret = false; + uint32_t temp_data; + do { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(!flipper_format_read_uint32(flipper_format, "VarBits", &temp_data, 1)) { + FURI_LOG_E(TAG, "Missing VarLen"); + break; + } + instance->var_bits = (uint8_t)temp_data; + if(!flipper_format_read_hex( + flipper_format, + "VarData", + (uint8_t*)&instance->var_data, + sizeof(instance->var_data))) { + FURI_LOG_E(TAG, "Missing VarData"); + break; + } + if(instance->generic.data_count_bit != ws_oregon2_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key: %d", instance->generic.data_count_bit); + break; + } + ret = true; + } while(false); + return ret; +} + +static void oregon2_append_check_sum(uint32_t fix_data, uint32_t var_data, FuriString* output) { + uint8_t sum = fix_data & 0xF; + uint8_t ref_sum = var_data & 0xFF; + var_data >>= 8; + + for(uint8_t i = 1; i < 8; i++) { + fix_data >>= 4; + var_data >>= 4; + sum += (fix_data & 0xF) + (var_data & 0xF); + } + + // swap calculated sum nibbles + sum = (((sum >> 4) & 0xF) | (sum << 4)) & 0xFF; + if(sum == ref_sum) + furi_string_cat_printf(output, "Sum ok: 0x%hhX", ref_sum); + else + furi_string_cat_printf(output, "Sum err: 0x%hhX vs 0x%hhX", ref_sum, sum); +} + +void ws_protocol_decoder_oregon2_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderOregon2* instance = context; + furi_string_cat_printf( + output, + "%s\r\n" + "ID: 0x%04lX, ch: %d, bat: %d, rc: 0x%02lX\r\n", + instance->generic.protocol_name, + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (uint32_t)(instance->generic.data >> 4) & 0xFF); + + if(instance->var_bits > 0) { + furi_string_cat_printf( + output, + "Temp:%d.%d C Hum:%d%%", + (int16_t)instance->generic.temp, + abs( + ((int16_t)(instance->generic.temp * 10) - + (((int16_t)instance->generic.temp) * 10))), + instance->generic.humidity); + oregon2_append_check_sum((uint32_t)instance->generic.data, instance->var_data, output); + } +} + +const SubGhzProtocolDecoder ws_protocol_oregon2_decoder = { + .alloc = ws_protocol_decoder_oregon2_alloc, + .free = ws_protocol_decoder_oregon2_free, + + .feed = ws_protocol_decoder_oregon2_feed, + .reset = ws_protocol_decoder_oregon2_reset, + + .get_hash_data = ws_protocol_decoder_oregon2_get_hash_data, + .serialize = ws_protocol_decoder_oregon2_serialize, + .deserialize = ws_protocol_decoder_oregon2_deserialize, + .get_string = ws_protocol_decoder_oregon2_get_string, +}; + +const SubGhzProtocol ws_protocol_oregon2 = { + .name = WS_PROTOCOL_OREGON2_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_oregon2_decoder, +}; diff --git a/applications/plugins/weather_station/protocols/oregon2.h b/applications/plugins/weather_station/protocols/oregon2.h new file mode 100644 index 000000000..cfe938e6d --- /dev/null +++ b/applications/plugins/weather_station/protocols/oregon2.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +#define WS_PROTOCOL_OREGON2_NAME "Oregon2" +extern const SubGhzProtocol ws_protocol_oregon2; diff --git a/applications/plugins/weather_station/protocols/oregon_v1.c b/applications/plugins/weather_station/protocols/oregon_v1.c new file mode 100644 index 000000000..d1cc4c7a7 --- /dev/null +++ b/applications/plugins/weather_station/protocols/oregon_v1.c @@ -0,0 +1,331 @@ +#include "oregon_v1.h" +#include + +#define TAG "WSProtocolOregon_V1" + +/* + * Help + * https://github.dev/merbanan/rtl_433/blob/bb1be7f186ac0fdb7dc5d77693847d96fb95281e/src/devices/oregon_scientific_v1.c + * + * OSv1 protocol. + * + * MC with nominal bit width of 2930 us. + * Pulses are somewhat longer than nominal half-bit width, 1748 us / 3216 us, + * Gaps are somewhat shorter than nominal half-bit width, 1176 us / 2640 us. + * After 12 preamble bits there is 4200 us gap, 5780 us pulse, 5200 us gap. + * And next 32 bit data + * + * Care must be taken with the gap after the sync pulse since it + * is outside of the normal clocking. Because of this a data stream + * beginning with a 0 will have data in this gap. + * + * + * Data is in reverse order of bits + * RevBit(data32bit)=> tib23atad + * + * tib23atad => xxxxxxxx | busuTTTT | ttttzzzz | ccuuiiii + * + * - i: ID + * - x: CRC; + * - u: unknown; + * - b: battery low; flag to indicate low battery voltage + * - s: temperature sign + * - T: BCD, Temperature; in C * 10 + * - t: BCD, Temperature; in C * 1 + * - z: BCD, Temperature; in C * 0.1 + * - c: Channel 00=CH1, 01=CH2, 10=CH3 + * + */ + +#define OREGON_V1_HEADER_OK 0xFF + +static const SubGhzBlockConst ws_protocol_oregon_v1_const = { + .te_short = 1465, + .te_long = 2930, + .te_delta = 350, + .min_count_bit_for_found = 32, +}; + +struct WSProtocolDecoderOregon_V1 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + ManchesterState manchester_state; + uint16_t header_count; + uint8_t first_bit; +}; + +struct WSProtocolEncoderOregon_V1 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + Oregon_V1DecoderStepReset = 0, + Oregon_V1DecoderStepFoundPreamble, + Oregon_V1DecoderStepParse, +} Oregon_V1DecoderStep; + +const SubGhzProtocolDecoder ws_protocol_oregon_v1_decoder = { + .alloc = ws_protocol_decoder_oregon_v1_alloc, + .free = ws_protocol_decoder_oregon_v1_free, + + .feed = ws_protocol_decoder_oregon_v1_feed, + .reset = ws_protocol_decoder_oregon_v1_reset, + + .get_hash_data = ws_protocol_decoder_oregon_v1_get_hash_data, + .serialize = ws_protocol_decoder_oregon_v1_serialize, + .deserialize = ws_protocol_decoder_oregon_v1_deserialize, + .get_string = ws_protocol_decoder_oregon_v1_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_oregon_v1_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_oregon_v1 = { + .name = WS_PROTOCOL_OREGON_V1_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_oregon_v1_decoder, + .encoder = &ws_protocol_oregon_v1_encoder, +}; + +void* ws_protocol_decoder_oregon_v1_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderOregon_V1* instance = malloc(sizeof(WSProtocolDecoderOregon_V1)); + instance->base.protocol = &ws_protocol_oregon_v1; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_oregon_v1_free(void* context) { + furi_assert(context); + WSProtocolDecoderOregon_V1* instance = context; + free(instance); +} + +void ws_protocol_decoder_oregon_v1_reset(void* context) { + furi_assert(context); + WSProtocolDecoderOregon_V1* instance = context; + instance->decoder.parser_step = Oregon_V1DecoderStepReset; +} + +static bool ws_protocol_oregon_v1_check(WSProtocolDecoderOregon_V1* instance) { + if(!instance->decoder.decode_data) return false; + uint64_t data = subghz_protocol_blocks_reverse_key(instance->decoder.decode_data, 32); + uint16_t crc = (data & 0xff) + ((data >> 8) & 0xff) + ((data >> 16) & 0xff); + crc = (crc & 0xff) + ((crc >> 8) & 0xff); + return (crc == ((data >> 24) & 0xFF)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_oregon_v1_remote_controller(WSBlockGeneric* instance) { + uint64_t data = subghz_protocol_blocks_reverse_key(instance->data, 32); + + instance->id = data & 0xFF; + instance->channel = ((data >> 6) & 0x03) + 1; + + float temp_raw = + ((data >> 8) & 0x0F) * 0.1f + ((data >> 12) & 0x0F) + ((data >> 16) & 0x0F) * 10.0f; + if(!((data >> 21) & 1)) { + instance->temp = temp_raw; + } else { + instance->temp = -temp_raw; + } + + instance->battery_low = !(instance->data >> 23) & 1; + + instance->btn = WS_NO_BTN; + instance->humidity = WS_NO_HUMIDITY; +} + +void ws_protocol_decoder_oregon_v1_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderOregon_V1* instance = context; + ManchesterEvent event = ManchesterEventReset; + switch(instance->decoder.parser_step) { + case Oregon_V1DecoderStepReset: + if((level) && (DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short) < + ws_protocol_oregon_v1_const.te_delta)) { + instance->decoder.parser_step = Oregon_V1DecoderStepFoundPreamble; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + case Oregon_V1DecoderStepFoundPreamble: + if(level) { + //keep high levels, if they suit our durations + if((DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short) < + ws_protocol_oregon_v1_const.te_delta) || + (DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short * 4) < + ws_protocol_oregon_v1_const.te_delta)) { + instance->decoder.te_last = duration; + } else { + instance->decoder.parser_step = Oregon_V1DecoderStepReset; + } + } else if( + //checking low levels + (DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short) < + ws_protocol_oregon_v1_const.te_delta) && + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_oregon_v1_const.te_short) < + ws_protocol_oregon_v1_const.te_delta)) { + // Found header + instance->header_count++; + } else if( + (DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short * 3) < + ws_protocol_oregon_v1_const.te_delta) && + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_oregon_v1_const.te_short) < + ws_protocol_oregon_v1_const.te_delta)) { + // check header + if(instance->header_count > 7) { + instance->header_count = OREGON_V1_HEADER_OK; + } + } else if( + (instance->header_count == OREGON_V1_HEADER_OK) && + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_oregon_v1_const.te_short * 4) < + ws_protocol_oregon_v1_const.te_delta)) { + //found all the necessary patterns + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 1; + manchester_advance( + instance->manchester_state, + ManchesterEventReset, + &instance->manchester_state, + NULL); + instance->decoder.parser_step = Oregon_V1DecoderStepParse; + if(duration < ws_protocol_oregon_v1_const.te_short * 4) { + instance->first_bit = 1; + } else { + instance->first_bit = 0; + } + } else { + instance->decoder.parser_step = Oregon_V1DecoderStepReset; + } + break; + case Oregon_V1DecoderStepParse: + if(level) { + if(DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short) < + ws_protocol_oregon_v1_const.te_delta) { + event = ManchesterEventShortHigh; + } else if( + DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_long) < + ws_protocol_oregon_v1_const.te_delta) { + event = ManchesterEventLongHigh; + } else { + instance->decoder.parser_step = Oregon_V1DecoderStepReset; + } + } else { + if(DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short) < + ws_protocol_oregon_v1_const.te_delta) { + event = ManchesterEventShortLow; + } else if( + DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_long) < + ws_protocol_oregon_v1_const.te_delta) { + event = ManchesterEventLongLow; + } else if(duration >= ((uint32_t)ws_protocol_oregon_v1_const.te_long * 2)) { + if(instance->decoder.decode_count_bit == + ws_protocol_oregon_v1_const.min_count_bit_for_found) { + if(instance->first_bit) { + instance->decoder.decode_data = ~instance->decoder.decode_data | (1 << 31); + } + if(ws_protocol_oregon_v1_check(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_oregon_v1_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; + manchester_advance( + instance->manchester_state, + ManchesterEventReset, + &instance->manchester_state, + NULL); + } else { + instance->decoder.parser_step = Oregon_V1DecoderStepReset; + } + } + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + instance->manchester_state, event, &instance->manchester_state, &data); + + if(data_ok) { + instance->decoder.decode_data = (instance->decoder.decode_data << 1) | !data; + instance->decoder.decode_count_bit++; + } + } + + break; + } +} + +uint8_t ws_protocol_decoder_oregon_v1_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderOregon_V1* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool ws_protocol_decoder_oregon_v1_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderOregon_V1* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool ws_protocol_decoder_oregon_v1_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderOregon_V1* instance = context; + bool ret = false; + do { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + ws_protocol_oregon_v1_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void ws_protocol_decoder_oregon_v1_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderOregon_V1* 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); +} diff --git a/applications/plugins/weather_station/protocols/oregon_v1.h b/applications/plugins/weather_station/protocols/oregon_v1.h new file mode 100644 index 000000000..c9aa5af48 --- /dev/null +++ b/applications/plugins/weather_station/protocols/oregon_v1.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_OREGON_V1_NAME "Oregon-v1" + +typedef struct WSProtocolDecoderOregon_V1 WSProtocolDecoderOregon_V1; +typedef struct WSProtocolEncoderOregon_V1 WSProtocolEncoderOregon_V1; + +extern const SubGhzProtocolDecoder ws_protocol_oregon_v1_decoder; +extern const SubGhzProtocolEncoder ws_protocol_oregon_v1_encoder; +extern const SubGhzProtocol ws_protocol_oregon_v1; + +/** + * Allocate WSProtocolDecoderOregon_V1. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderOregon_V1* pointer to a WSProtocolDecoderOregon_V1 instance + */ +void* ws_protocol_decoder_oregon_v1_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderOregon_V1. + * @param context Pointer to a WSProtocolDecoderOregon_V1 instance + */ +void ws_protocol_decoder_oregon_v1_free(void* context); + +/** + * Reset decoder WSProtocolDecoderOregon_V1. + * @param context Pointer to a WSProtocolDecoderOregon_V1 instance + */ +void ws_protocol_decoder_oregon_v1_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderOregon_V1 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_oregon_v1_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderOregon_V1 instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_oregon_v1_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderOregon_V1. + * @param context Pointer to a WSProtocolDecoderOregon_V1 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_protocol_decoder_oregon_v1_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderOregon_V1. + * @param context Pointer to a WSProtocolDecoderOregon_V1 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_protocol_decoder_oregon_v1_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderOregon_V1 instance + * @param output Resulting text + */ +void ws_protocol_decoder_oregon_v1_get_string(void* context, FuriString* output); diff --git a/applications/plugins/weather_station/protocols/protocol_items.c b/applications/plugins/weather_station/protocols/protocol_items.c new file mode 100644 index 000000000..99c8344f4 --- /dev/null +++ b/applications/plugins/weather_station/protocols/protocol_items.c @@ -0,0 +1,22 @@ +#include "protocol_items.h" + +const SubGhzProtocol* weather_station_protocol_registry_items[] = { + &ws_protocol_infactory, + &ws_protocol_thermopro_tx4, + &ws_protocol_nexus_th, + &ws_protocol_gt_wt_02, + &ws_protocol_gt_wt_03, + &ws_protocol_acurite_606tx, + &ws_protocol_acurite_609txc, + &ws_protocol_lacrosse_tx141thbv2, + &ws_protocol_oregon2, + &ws_protocol_acurite_592txr, + &ws_protocol_ambient_weather, + &ws_protocol_auriol_th, + &ws_protocol_oregon_v1, + &ws_protocol_tx_8300, +}; + +const SubGhzProtocolRegistry weather_station_protocol_registry = { + .items = weather_station_protocol_registry_items, + .size = COUNT_OF(weather_station_protocol_registry_items)}; \ No newline at end of file diff --git a/applications/plugins/weather_station/protocols/protocol_items.h b/applications/plugins/weather_station/protocols/protocol_items.h new file mode 100644 index 000000000..9d5d096f8 --- /dev/null +++ b/applications/plugins/weather_station/protocols/protocol_items.h @@ -0,0 +1,19 @@ +#pragma once +#include "../weather_station_app_i.h" + +#include "infactory.h" +#include "thermopro_tx4.h" +#include "nexus_th.h" +#include "gt_wt_02.h" +#include "gt_wt_03.h" +#include "acurite_606tx.h" +#include "acurite_609txc.h" +#include "lacrosse_tx141thbv2.h" +#include "oregon2.h" +#include "acurite_592txr.h" +#include "ambient_weather.h" +#include "auriol_hg0601a.h" +#include "oregon_v1.h" +#include "tx_8300.h" + +extern const SubGhzProtocolRegistry weather_station_protocol_registry; diff --git a/applications/plugins/weather_station/protocols/thermopro_tx4.c b/applications/plugins/weather_station/protocols/thermopro_tx4.c new file mode 100644 index 000000000..0882bc33d --- /dev/null +++ b/applications/plugins/weather_station/protocols/thermopro_tx4.c @@ -0,0 +1,259 @@ +#include "thermopro_tx4.h" + +#define TAG "WSProtocolThermoPRO_TX4" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/master/src/devices/thermopro_tx2.c + * + * The sensor sends 37 bits 6 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 0110 (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 1 when the battery is low, otherwise 0 (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 THERMO_PRO_TX4_TYPE_1 0b1001 +#define THERMO_PRO_TX4_TYPE_2 0b0110 + +static const SubGhzBlockConst ws_protocol_thermopro_tx4_const = { + .te_short = 500, + .te_long = 2000, + .te_delta = 150, + .min_count_bit_for_found = 37, +}; + +struct WSProtocolDecoderThermoPRO_TX4 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; +}; + +struct WSProtocolEncoderThermoPRO_TX4 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + ThermoPRO_TX4DecoderStepReset = 0, + ThermoPRO_TX4DecoderStepSaveDuration, + ThermoPRO_TX4DecoderStepCheckDuration, +} ThermoPRO_TX4DecoderStep; + +const SubGhzProtocolDecoder ws_protocol_thermopro_tx4_decoder = { + .alloc = ws_protocol_decoder_thermopro_tx4_alloc, + .free = ws_protocol_decoder_thermopro_tx4_free, + + .feed = ws_protocol_decoder_thermopro_tx4_feed, + .reset = ws_protocol_decoder_thermopro_tx4_reset, + + .get_hash_data = ws_protocol_decoder_thermopro_tx4_get_hash_data, + .serialize = ws_protocol_decoder_thermopro_tx4_serialize, + .deserialize = ws_protocol_decoder_thermopro_tx4_deserialize, + .get_string = ws_protocol_decoder_thermopro_tx4_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_thermopro_tx4_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_thermopro_tx4 = { + .name = WS_PROTOCOL_THERMOPRO_TX4_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_thermopro_tx4_decoder, + .encoder = &ws_protocol_thermopro_tx4_encoder, +}; + +void* ws_protocol_decoder_thermopro_tx4_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderThermoPRO_TX4* instance = malloc(sizeof(WSProtocolDecoderThermoPRO_TX4)); + instance->base.protocol = &ws_protocol_thermopro_tx4; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_thermopro_tx4_free(void* context) { + furi_assert(context); + WSProtocolDecoderThermoPRO_TX4* instance = context; + free(instance); +} + +void ws_protocol_decoder_thermopro_tx4_reset(void* context) { + furi_assert(context); + WSProtocolDecoderThermoPRO_TX4* instance = context; + instance->decoder.parser_step = ThermoPRO_TX4DecoderStepReset; +} + +static bool ws_protocol_thermopro_tx4_check(WSProtocolDecoderThermoPRO_TX4* instance) { + uint8_t type = instance->decoder.decode_data >> 33; + + if((type == THERMO_PRO_TX4_TYPE_1) || (type == THERMO_PRO_TX4_TYPE_2)) { + return true; + } else { + return false; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_thermopro_tx4_remote_controller(WSBlockGeneric* instance) { + instance->id = (instance->data >> 25) & 0xFF; + instance->battery_low = (instance->data >> 24) & 1; + instance->btn = (instance->data >> 23) & 1; + instance->channel = ((instance->data >> 21) & 0x03) + 1; + + if(!((instance->data >> 20) & 1)) { + instance->temp = (float)((instance->data >> 9) & 0x07FF) / 10.0f; + } else { + instance->temp = (float)((~(instance->data >> 9) & 0x07FF) + 1) / -10.0f; + } + + instance->humidity = (instance->data >> 1) & 0xFF; +} + +void ws_protocol_decoder_thermopro_tx4_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderThermoPRO_TX4* instance = context; + + switch(instance->decoder.parser_step) { + case ThermoPRO_TX4DecoderStepReset: + if((!level) && (DURATION_DIFF(duration, ws_protocol_thermopro_tx4_const.te_short * 18) < + ws_protocol_thermopro_tx4_const.te_delta * 10)) { + //Found sync + instance->decoder.parser_step = ThermoPRO_TX4DecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + break; + + case ThermoPRO_TX4DecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = ThermoPRO_TX4DecoderStepCheckDuration; + } else { + instance->decoder.parser_step = ThermoPRO_TX4DecoderStepReset; + } + break; + + case ThermoPRO_TX4DecoderStepCheckDuration: + if(!level) { + if(DURATION_DIFF(duration, ws_protocol_thermopro_tx4_const.te_short * 18) < + ws_protocol_thermopro_tx4_const.te_delta * 10) { + //Found sync + instance->decoder.parser_step = ThermoPRO_TX4DecoderStepReset; + if((instance->decoder.decode_count_bit == + ws_protocol_thermopro_tx4_const.min_count_bit_for_found) && + ws_protocol_thermopro_tx4_check(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_thermopro_tx4_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + instance->decoder.parser_step = ThermoPRO_TX4DecoderStepCheckDuration; + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + + break; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, ws_protocol_thermopro_tx4_const.te_short) < + ws_protocol_thermopro_tx4_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_thermopro_tx4_const.te_long) < + ws_protocol_thermopro_tx4_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = ThermoPRO_TX4DecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, ws_protocol_thermopro_tx4_const.te_short) < + ws_protocol_thermopro_tx4_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_thermopro_tx4_const.te_long * 2) < + ws_protocol_thermopro_tx4_const.te_delta * 4)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = ThermoPRO_TX4DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = ThermoPRO_TX4DecoderStepReset; + } + } else { + instance->decoder.parser_step = ThermoPRO_TX4DecoderStepReset; + } + break; + } +} + +uint8_t ws_protocol_decoder_thermopro_tx4_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderThermoPRO_TX4* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool ws_protocol_decoder_thermopro_tx4_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderThermoPRO_TX4* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool ws_protocol_decoder_thermopro_tx4_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderThermoPRO_TX4* instance = context; + bool ret = false; + do { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + ws_protocol_thermopro_tx4_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void ws_protocol_decoder_thermopro_tx4_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderThermoPRO_TX4* 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); +} diff --git a/applications/plugins/weather_station/protocols/thermopro_tx4.h b/applications/plugins/weather_station/protocols/thermopro_tx4.h new file mode 100644 index 000000000..1feae58d3 --- /dev/null +++ b/applications/plugins/weather_station/protocols/thermopro_tx4.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_THERMOPRO_TX4_NAME "ThermoPRO-TX4" + +typedef struct WSProtocolDecoderThermoPRO_TX4 WSProtocolDecoderThermoPRO_TX4; +typedef struct WSProtocolEncoderThermoPRO_TX4 WSProtocolEncoderThermoPRO_TX4; + +extern const SubGhzProtocolDecoder ws_protocol_thermopro_tx4_decoder; +extern const SubGhzProtocolEncoder ws_protocol_thermopro_tx4_encoder; +extern const SubGhzProtocol ws_protocol_thermopro_tx4; + +/** + * Allocate WSProtocolDecoderThermoPRO_TX4. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderThermoPRO_TX4* pointer to a WSProtocolDecoderThermoPRO_TX4 instance + */ +void* ws_protocol_decoder_thermopro_tx4_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderThermoPRO_TX4. + * @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance + */ +void ws_protocol_decoder_thermopro_tx4_free(void* context); + +/** + * Reset decoder WSProtocolDecoderThermoPRO_TX4. + * @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance + */ +void ws_protocol_decoder_thermopro_tx4_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_thermopro_tx4_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_thermopro_tx4_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderThermoPRO_TX4. + * @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_protocol_decoder_thermopro_tx4_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderThermoPRO_TX4. + * @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_protocol_decoder_thermopro_tx4_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance + * @param output Resulting text + */ +void ws_protocol_decoder_thermopro_tx4_get_string(void* context, FuriString* output); diff --git a/applications/plugins/weather_station/protocols/tx_8300.c b/applications/plugins/weather_station/protocols/tx_8300.c new file mode 100644 index 000000000..ee0412ba9 --- /dev/null +++ b/applications/plugins/weather_station/protocols/tx_8300.c @@ -0,0 +1,293 @@ +#include "tx_8300.h" + +#define TAG "WSProtocolTX_8300" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/master/src/devices/ambientweather_tx8300.c + * + * Ambient Weather TX-8300 (also sold as TFA 30.3211.02). + * 1970us pulse with variable gap (third pulse 3920 us). + * Above 79% humidity, gap after third pulse is 5848 us. + * - Bit 1 : 1970us pulse with 3888 us gap + * - Bit 0 : 1970us pulse with 1936 us gap + * 74 bit (2 bit preamble and 72 bit data => 9 bytes => 18 nibbles) + * The preamble seems to be a repeat counter (00, and 01 seen), + * the first 4 bytes are data, + * the second 4 bytes the same data inverted, + * the last byte is a checksum. + * Preamble format (2 bits): + * [1 bit (0)] [1 bit rolling count] + * Payload format (32 bits): + * HHHHhhhh ??CCNIII IIIITTTT ttttuuuu + * - H = First BCD digit humidity (the MSB might be distorted by the demod) + * - h = Second BCD digit humidity, invalid humidity seems to be 0x0e + * - ? = Likely battery flag, 2 bits + * - C = Channel, 2 bits + * - N = Negative temperature sign bit + * - I = ID, 7-bit + * - T = First BCD digit temperature + * - t = Second BCD digit temperature + * - u = Third BCD digit temperature + * The Checksum seems to covers the 4 data bytes and is something like Fletcher-8. + **/ + +#define TX_8300_PACKAGE_SIZE 32 + +static const SubGhzBlockConst ws_protocol_tx_8300_const = { + .te_short = 1940, + .te_long = 3880, + .te_delta = 250, + .min_count_bit_for_found = 72, +}; + +struct WSProtocolDecoderTX_8300 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + uint32_t package_1; + uint32_t package_2; +}; + +struct WSProtocolEncoderTX_8300 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + TX_8300DecoderStepReset = 0, + TX_8300DecoderStepCheckPreambule, + TX_8300DecoderStepSaveDuration, + TX_8300DecoderStepCheckDuration, +} TX_8300DecoderStep; + +const SubGhzProtocolDecoder ws_protocol_tx_8300_decoder = { + .alloc = ws_protocol_decoder_tx_8300_alloc, + .free = ws_protocol_decoder_tx_8300_free, + + .feed = ws_protocol_decoder_tx_8300_feed, + .reset = ws_protocol_decoder_tx_8300_reset, + + .get_hash_data = ws_protocol_decoder_tx_8300_get_hash_data, + .serialize = ws_protocol_decoder_tx_8300_serialize, + .deserialize = ws_protocol_decoder_tx_8300_deserialize, + .get_string = ws_protocol_decoder_tx_8300_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_tx_8300_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_tx_8300 = { + .name = WS_PROTOCOL_TX_8300_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_tx_8300_decoder, + .encoder = &ws_protocol_tx_8300_encoder, +}; + +void* ws_protocol_decoder_tx_8300_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderTX_8300* instance = malloc(sizeof(WSProtocolDecoderTX_8300)); + instance->base.protocol = &ws_protocol_tx_8300; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_tx_8300_free(void* context) { + furi_assert(context); + WSProtocolDecoderTX_8300* instance = context; + free(instance); +} + +void ws_protocol_decoder_tx_8300_reset(void* context) { + furi_assert(context); + WSProtocolDecoderTX_8300* instance = context; + instance->decoder.parser_step = TX_8300DecoderStepReset; +} + +static bool ws_protocol_tx_8300_check_crc(WSProtocolDecoderTX_8300* instance) { + if(!instance->package_2) return false; + if(instance->package_1 != ~instance->package_2) return false; + + uint16_t x = 0; + uint16_t y = 0; + for(int i = 0; i < 32; i += 4) { + x += (instance->package_1 >> i) & 0x0F; + y += (instance->package_1 >> i) & 0x05; + } + uint8_t crc = (~x & 0xF) << 4 | (~y & 0xF); + return (crc == ((instance->decoder.decode_data) & 0xFF)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_tx_8300_remote_controller(WSBlockGeneric* instance) { + instance->humidity = (((instance->data >> 28) & 0x0F) * 10) + ((instance->data >> 24) & 0x0F); + instance->btn = WS_NO_BTN; + if(!((instance->data >> 22) & 0x03)) + instance->battery_low = 0; + else + instance->battery_low = 1; + instance->channel = (instance->data >> 20) & 0x03; + instance->id = (instance->data >> 12) & 0x7F; + + float temp_raw = ((instance->data >> 8) & 0x0F) * 10.0f + ((instance->data >> 4) & 0x0F) + + (instance->data & 0x0F) * 0.1f; + if(!((instance->data >> 19) & 1)) { + instance->temp = temp_raw; + } else { + instance->temp = -temp_raw; + } +} + +void ws_protocol_decoder_tx_8300_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderTX_8300* instance = context; + + switch(instance->decoder.parser_step) { + case TX_8300DecoderStepReset: + if((level) && (DURATION_DIFF(duration, ws_protocol_tx_8300_const.te_short * 2) < + ws_protocol_tx_8300_const.te_delta)) { + instance->decoder.parser_step = TX_8300DecoderStepCheckPreambule; + } + break; + + case TX_8300DecoderStepCheckPreambule: + if((!level) && ((DURATION_DIFF(duration, ws_protocol_tx_8300_const.te_short * 2) < + ws_protocol_tx_8300_const.te_delta) || + (DURATION_DIFF(duration, ws_protocol_tx_8300_const.te_short * 3) < + ws_protocol_tx_8300_const.te_delta))) { + instance->decoder.parser_step = TX_8300DecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 1; + instance->package_1 = 0; + instance->package_2 = 0; + } else { + instance->decoder.parser_step = TX_8300DecoderStepReset; + } + break; + + case TX_8300DecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = TX_8300DecoderStepCheckDuration; + } else { + instance->decoder.parser_step = TX_8300DecoderStepReset; + } + break; + + case TX_8300DecoderStepCheckDuration: + if(!level) { + if(duration >= ((uint32_t)ws_protocol_tx_8300_const.te_short * 5)) { + //Found syncPostfix + if((instance->decoder.decode_count_bit == + ws_protocol_tx_8300_const.min_count_bit_for_found) && + ws_protocol_tx_8300_check_crc(instance)) { + instance->generic.data = instance->package_1; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_tx_8300_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 = 1; + instance->decoder.parser_step = TX_8300DecoderStepReset; + break; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_tx_8300_const.te_short) < + ws_protocol_tx_8300_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_tx_8300_const.te_long) < + ws_protocol_tx_8300_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = TX_8300DecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_tx_8300_const.te_short) < + ws_protocol_tx_8300_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_tx_8300_const.te_short) < + ws_protocol_tx_8300_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = TX_8300DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = TX_8300DecoderStepReset; + } + + if(instance->decoder.decode_count_bit == TX_8300_PACKAGE_SIZE) { + instance->package_1 = instance->decoder.decode_data; + instance->decoder.decode_data = 0; + } else if(instance->decoder.decode_count_bit == TX_8300_PACKAGE_SIZE * 2) { + instance->package_2 = instance->decoder.decode_data; + instance->decoder.decode_data = 0; + } + + } else { + instance->decoder.parser_step = TX_8300DecoderStepReset; + } + break; + } +} + +uint8_t ws_protocol_decoder_tx_8300_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderTX_8300* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool ws_protocol_decoder_tx_8300_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderTX_8300* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool ws_protocol_decoder_tx_8300_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderTX_8300* instance = context; + bool ret = false; + do { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != ws_protocol_tx_8300_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void ws_protocol_decoder_tx_8300_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderTX_8300* 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); +} diff --git a/applications/plugins/weather_station/protocols/tx_8300.h b/applications/plugins/weather_station/protocols/tx_8300.h new file mode 100644 index 000000000..ec198e80f --- /dev/null +++ b/applications/plugins/weather_station/protocols/tx_8300.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_TX_8300_NAME "TX8300" + +typedef struct WSProtocolDecoderTX_8300 WSProtocolDecoderTX_8300; +typedef struct WSProtocolEncoderTX_8300 WSProtocolEncoderTX_8300; + +extern const SubGhzProtocolDecoder ws_protocol_tx_8300_decoder; +extern const SubGhzProtocolEncoder ws_protocol_tx_8300_encoder; +extern const SubGhzProtocol ws_protocol_tx_8300; + +/** + * Allocate WSProtocolDecoderTX_8300. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderTX_8300* pointer to a WSProtocolDecoderTX_8300 instance + */ +void* ws_protocol_decoder_tx_8300_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderTX_8300. + * @param context Pointer to a WSProtocolDecoderTX_8300 instance + */ +void ws_protocol_decoder_tx_8300_free(void* context); + +/** + * Reset decoder WSProtocolDecoderTX_8300. + * @param context Pointer to a WSProtocolDecoderTX_8300 instance + */ +void ws_protocol_decoder_tx_8300_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderTX_8300 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_tx_8300_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderTX_8300 instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_tx_8300_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderTX_8300. + * @param context Pointer to a WSProtocolDecoderTX_8300 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_protocol_decoder_tx_8300_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderTX_8300. + * @param context Pointer to a WSProtocolDecoderTX_8300 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_protocol_decoder_tx_8300_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderTX_8300 instance + * @param output Resulting text + */ +void ws_protocol_decoder_tx_8300_get_string(void* context, FuriString* output); diff --git a/applications/plugins/weather_station/protocols/ws_generic.c b/applications/plugins/weather_station/protocols/ws_generic.c new file mode 100644 index 000000000..cd5bf6557 --- /dev/null +++ b/applications/plugins/weather_station/protocols/ws_generic.c @@ -0,0 +1,215 @@ +#include "ws_generic.h" +#include +#include +#include "../helpers/weather_station_types.h" + +#define TAG "WSBlockGeneric" + +void ws_block_generic_get_preset_name(const char* preset_name, FuriString* preset_str) { + const char* preset_name_temp; + if(!strcmp(preset_name, "AM270")) { + preset_name_temp = "FuriHalSubGhzPresetOok270Async"; + } else if(!strcmp(preset_name, "AM650")) { + preset_name_temp = "FuriHalSubGhzPresetOok650Async"; + } else if(!strcmp(preset_name, "FM238")) { + preset_name_temp = "FuriHalSubGhzPreset2FSKDev238Async"; + } else if(!strcmp(preset_name, "FM476")) { + preset_name_temp = "FuriHalSubGhzPreset2FSKDev476Async"; + } else { + preset_name_temp = "FuriHalSubGhzPresetCustom"; + } + furi_string_set(preset_str, preset_name_temp); +} + +bool ws_block_generic_serialize( + WSBlockGeneric* instance, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(instance); + bool res = false; + FuriString* temp_str; + temp_str = furi_string_alloc(); + do { + stream_clean(flipper_format_get_raw_stream(flipper_format)); + if(!flipper_format_write_header_cstr( + flipper_format, WS_KEY_FILE_TYPE, WS_KEY_FILE_VERSION)) { + FURI_LOG_E(TAG, "Unable to add header"); + break; + } + + if(!flipper_format_write_uint32(flipper_format, "Frequency", &preset->frequency, 1)) { + FURI_LOG_E(TAG, "Unable to add Frequency"); + break; + } + + ws_block_generic_get_preset_name(furi_string_get_cstr(preset->name), temp_str); + if(!flipper_format_write_string_cstr( + flipper_format, "Preset", furi_string_get_cstr(temp_str))) { + FURI_LOG_E(TAG, "Unable to add Preset"); + break; + } + if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) { + if(!flipper_format_write_string_cstr( + flipper_format, "Custom_preset_module", "CC1101")) { + FURI_LOG_E(TAG, "Unable to add Custom_preset_module"); + break; + } + if(!flipper_format_write_hex( + flipper_format, "Custom_preset_data", preset->data, preset->data_size)) { + FURI_LOG_E(TAG, "Unable to add Custom_preset_data"); + break; + } + } + if(!flipper_format_write_string_cstr(flipper_format, "Protocol", instance->protocol_name)) { + FURI_LOG_E(TAG, "Unable to add Protocol"); + break; + } + + uint32_t temp_data = instance->id; + if(!flipper_format_write_uint32(flipper_format, "Id", &temp_data, 1)) { + FURI_LOG_E(TAG, "Unable to add Id"); + break; + } + + temp_data = instance->data_count_bit; + if(!flipper_format_write_uint32(flipper_format, "Bit", &temp_data, 1)) { + FURI_LOG_E(TAG, "Unable to add Bit"); + break; + } + + uint8_t key_data[sizeof(uint64_t)] = {0}; + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (instance->data >> i * 8) & 0xFF; + } + + if(!flipper_format_write_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Data"); + break; + } + + temp_data = instance->battery_low; + if(!flipper_format_write_uint32(flipper_format, "Batt", &temp_data, 1)) { + FURI_LOG_E(TAG, "Unable to add Battery_low"); + break; + } + + temp_data = instance->humidity; + if(!flipper_format_write_uint32(flipper_format, "Hum", &temp_data, 1)) { + FURI_LOG_E(TAG, "Unable to add Humidity"); + break; + } + + //DATE AGE set + FuriHalRtcDateTime curr_dt; + furi_hal_rtc_get_datetime(&curr_dt); + uint32_t curr_ts = furi_hal_rtc_datetime_to_timestamp(&curr_dt); + + temp_data = curr_ts; + if(!flipper_format_write_uint32(flipper_format, "Ts", &temp_data, 1)) { + FURI_LOG_E(TAG, "Unable to add timestamp"); + break; + } + + temp_data = instance->channel; + if(!flipper_format_write_uint32(flipper_format, "Ch", &temp_data, 1)) { + FURI_LOG_E(TAG, "Unable to add Channel"); + break; + } + + temp_data = instance->btn; + if(!flipper_format_write_uint32(flipper_format, "Btn", &temp_data, 1)) { + FURI_LOG_E(TAG, "Unable to add Btn"); + break; + } + + float temp = instance->temp; + if(!flipper_format_write_float(flipper_format, "Temp", &temp, 1)) { + FURI_LOG_E(TAG, "Unable to add Temperature"); + break; + } + + res = true; + } while(false); + furi_string_free(temp_str); + return res; +} + +bool ws_block_generic_deserialize(WSBlockGeneric* instance, FlipperFormat* flipper_format) { + furi_assert(instance); + bool res = false; + uint32_t temp_data = 0; + + do { + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + if(!flipper_format_read_uint32(flipper_format, "Id", (uint32_t*)&temp_data, 1)) { + FURI_LOG_E(TAG, "Missing Id"); + break; + } + instance->id = (uint32_t)temp_data; + + if(!flipper_format_read_uint32(flipper_format, "Bit", (uint32_t*)&temp_data, 1)) { + FURI_LOG_E(TAG, "Missing Bit"); + break; + } + instance->data_count_bit = (uint8_t)temp_data; + + uint8_t key_data[sizeof(uint64_t)] = {0}; + if(!flipper_format_read_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Missing Data"); + break; + } + + for(uint8_t i = 0; i < sizeof(uint64_t); i++) { + instance->data = instance->data << 8 | key_data[i]; + } + + if(!flipper_format_read_uint32(flipper_format, "Batt", (uint32_t*)&temp_data, 1)) { + FURI_LOG_E(TAG, "Missing Battery_low"); + break; + } + instance->battery_low = (uint8_t)temp_data; + + if(!flipper_format_read_uint32(flipper_format, "Hum", (uint32_t*)&temp_data, 1)) { + FURI_LOG_E(TAG, "Missing Humidity"); + break; + } + instance->humidity = (uint8_t)temp_data; + + if(!flipper_format_read_uint32(flipper_format, "Ts", (uint32_t*)&temp_data, 1)) { + FURI_LOG_E(TAG, "Missing timestamp"); + break; + } + instance->timestamp = (uint32_t)temp_data; + + if(!flipper_format_read_uint32(flipper_format, "Ch", (uint32_t*)&temp_data, 1)) { + FURI_LOG_E(TAG, "Missing Channel"); + break; + } + instance->channel = (uint8_t)temp_data; + + if(!flipper_format_read_uint32(flipper_format, "Btn", (uint32_t*)&temp_data, 1)) { + FURI_LOG_E(TAG, "Missing Btn"); + break; + } + instance->btn = (uint8_t)temp_data; + + float temp; + if(!flipper_format_read_float(flipper_format, "Temp", (float*)&temp, 1)) { + FURI_LOG_E(TAG, "Missing Temperature"); + break; + } + instance->temp = temp; + + res = true; + } while(0); + + return res; +} + +float ws_block_generic_fahrenheit_to_celsius(float fahrenheit) { + return (fahrenheit - 32.0f) / 1.8f; +} \ No newline at end of file diff --git a/applications/plugins/weather_station/protocols/ws_generic.h b/applications/plugins/weather_station/protocols/ws_generic.h new file mode 100644 index 000000000..657f8a1fc --- /dev/null +++ b/applications/plugins/weather_station/protocols/ws_generic.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +#include + +#include +#include "furi.h" +#include "furi_hal.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define WS_NO_ID 0xFFFFFFFF +#define WS_NO_BATT 0xFF +#define WS_NO_HUMIDITY 0xFF +#define WS_NO_CHANNEL 0xFF +#define WS_NO_BTN 0xFF +#define WS_NO_TEMPERATURE -273.0f + +typedef struct WSBlockGeneric WSBlockGeneric; + +struct WSBlockGeneric { + const char* protocol_name; + uint64_t data; + uint32_t id; + uint8_t data_count_bit; + uint8_t battery_low; + uint8_t humidity; + uint32_t timestamp; + uint8_t channel; + uint8_t btn; + float temp; +}; + +/** + * Get name preset. + * @param preset_name name preset + * @param preset_str Output name preset + */ +void ws_block_generic_get_preset_name(const char* preset_name, FuriString* preset_str); + +/** + * Serialize data WSBlockGeneric. + * @param instance Pointer to a WSBlockGeneric instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_block_generic_serialize( + WSBlockGeneric* instance, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSBlockGeneric. + * @param instance Pointer to a WSBlockGeneric instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_block_generic_deserialize(WSBlockGeneric* instance, FlipperFormat* flipper_format); + +float ws_block_generic_fahrenheit_to_celsius(float fahrenheit); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/plugins/weather_station/scenes/weather_station_receiver.c b/applications/plugins/weather_station/scenes/weather_station_receiver.c new file mode 100644 index 000000000..670c8c386 --- /dev/null +++ b/applications/plugins/weather_station/scenes/weather_station_receiver.c @@ -0,0 +1,207 @@ +#include "../weather_station_app_i.h" +#include "../views/weather_station_receiver.h" + +static const NotificationSequence subghs_sequence_rx = { + &message_green_255, + + &message_vibro_on, + &message_note_c6, + &message_delay_50, + &message_sound_off, + &message_vibro_off, + + &message_delay_50, + NULL, +}; + +static const NotificationSequence subghs_sequence_rx_locked = { + &message_green_255, + + &message_display_backlight_on, + + &message_vibro_on, + &message_note_c6, + &message_delay_50, + &message_sound_off, + &message_vibro_off, + + &message_delay_500, + + &message_display_backlight_off, + NULL, +}; + +static void weather_station_scene_receiver_update_statusbar(void* context) { + WeatherStationApp* app = context; + FuriString* history_stat_str; + history_stat_str = furi_string_alloc(); + if(!ws_history_get_text_space_left(app->txrx->history, history_stat_str)) { + FuriString* frequency_str; + FuriString* modulation_str; + + frequency_str = furi_string_alloc(); + modulation_str = furi_string_alloc(); + + ws_get_frequency_modulation(app, frequency_str, modulation_str); + + ws_view_receiver_add_data_statusbar( + app->ws_receiver, + furi_string_get_cstr(frequency_str), + furi_string_get_cstr(modulation_str), + furi_string_get_cstr(history_stat_str)); + + furi_string_free(frequency_str); + furi_string_free(modulation_str); + } else { + ws_view_receiver_add_data_statusbar( + app->ws_receiver, furi_string_get_cstr(history_stat_str), "", ""); + } + furi_string_free(history_stat_str); +} + +void weather_station_scene_receiver_callback(WSCustomEvent event, void* context) { + furi_assert(context); + WeatherStationApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +static void weather_station_scene_receiver_add_to_history_callback( + SubGhzReceiver* receiver, + SubGhzProtocolDecoderBase* decoder_base, + void* context) { + furi_assert(context); + WeatherStationApp* app = context; + FuriString* str_buff; + str_buff = furi_string_alloc(); + + if(ws_history_add_to_history(app->txrx->history, decoder_base, app->txrx->preset) == + WSHistoryStateAddKeyNewDada) { + furi_string_reset(str_buff); + + ws_history_get_text_item_menu( + app->txrx->history, str_buff, ws_history_get_item(app->txrx->history) - 1); + ws_view_receiver_add_item_to_menu( + app->ws_receiver, + furi_string_get_cstr(str_buff), + ws_history_get_type_protocol( + app->txrx->history, ws_history_get_item(app->txrx->history) - 1)); + + weather_station_scene_receiver_update_statusbar(app); + notification_message(app->notifications, &sequence_blink_green_10); + if(app->lock != WSLockOn) { + notification_message(app->notifications, &subghs_sequence_rx); + } else { + notification_message(app->notifications, &subghs_sequence_rx_locked); + } + } + subghz_receiver_reset(receiver); + furi_string_free(str_buff); + app->txrx->rx_key_state = WSRxKeyStateAddKey; +} + +void weather_station_scene_receiver_on_enter(void* context) { + WeatherStationApp* app = context; + + FuriString* str_buff; + str_buff = furi_string_alloc(); + + if(app->txrx->rx_key_state == WSRxKeyStateIDLE) { + ws_preset_init(app, "AM650", subghz_setting_get_default_frequency(app->setting), NULL, 0); + ws_history_reset(app->txrx->history); + app->txrx->rx_key_state = WSRxKeyStateStart; + } + + ws_view_receiver_set_lock(app->ws_receiver, app->lock); + + //Load history to receiver + ws_view_receiver_exit(app->ws_receiver); + for(uint8_t i = 0; i < ws_history_get_item(app->txrx->history); i++) { + furi_string_reset(str_buff); + ws_history_get_text_item_menu(app->txrx->history, str_buff, i); + ws_view_receiver_add_item_to_menu( + app->ws_receiver, + furi_string_get_cstr(str_buff), + ws_history_get_type_protocol(app->txrx->history, i)); + app->txrx->rx_key_state = WSRxKeyStateAddKey; + } + furi_string_free(str_buff); + weather_station_scene_receiver_update_statusbar(app); + + ws_view_receiver_set_callback(app->ws_receiver, weather_station_scene_receiver_callback, app); + subghz_receiver_set_rx_callback( + app->txrx->receiver, weather_station_scene_receiver_add_to_history_callback, app); + + if(app->txrx->txrx_state == WSTxRxStateRx) { + ws_rx_end(app); + }; + if((app->txrx->txrx_state == WSTxRxStateIDLE) || (app->txrx->txrx_state == WSTxRxStateSleep)) { + ws_begin( + app, + subghz_setting_get_preset_data_by_name( + app->setting, furi_string_get_cstr(app->txrx->preset->name))); + + ws_rx(app, app->txrx->preset->frequency); + } + + ws_view_receiver_set_idx_menu(app->ws_receiver, app->txrx->idx_menu_chosen); + view_dispatcher_switch_to_view(app->view_dispatcher, WeatherStationViewReceiver); +} + +bool weather_station_scene_receiver_on_event(void* context, SceneManagerEvent event) { + WeatherStationApp* app = context; + bool consumed = false; + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case WSCustomEventViewReceiverBack: + // Stop CC1101 Rx + if(app->txrx->txrx_state == WSTxRxStateRx) { + ws_rx_end(app); + ws_sleep(app); + }; + app->txrx->hopper_state = WSHopperStateOFF; + app->txrx->idx_menu_chosen = 0; + subghz_receiver_set_rx_callback(app->txrx->receiver, NULL, app); + + app->txrx->rx_key_state = WSRxKeyStateIDLE; + ws_preset_init( + app, "AM650", subghz_setting_get_default_frequency(app->setting), NULL, 0); + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, WeatherStationSceneStart); + consumed = true; + break; + case WSCustomEventViewReceiverOK: + app->txrx->idx_menu_chosen = ws_view_receiver_get_idx_menu(app->ws_receiver); + scene_manager_next_scene(app->scene_manager, WeatherStationSceneReceiverInfo); + consumed = true; + break; + case WSCustomEventViewReceiverConfig: + app->txrx->idx_menu_chosen = ws_view_receiver_get_idx_menu(app->ws_receiver); + scene_manager_next_scene(app->scene_manager, WeatherStationSceneReceiverConfig); + consumed = true; + break; + case WSCustomEventViewReceiverOffDisplay: + notification_message(app->notifications, &sequence_display_backlight_off); + consumed = true; + break; + case WSCustomEventViewReceiverUnlock: + app->lock = WSLockOff; + consumed = true; + break; + default: + break; + } + } else if(event.type == SceneManagerEventTypeTick) { + if(app->txrx->hopper_state != WSHopperStateOFF) { + ws_hopper_update(app); + weather_station_scene_receiver_update_statusbar(app); + } + if(app->txrx->txrx_state == WSTxRxStateRx) { + notification_message(app->notifications, &sequence_blink_cyan_10); + } + } + return consumed; +} + +void weather_station_scene_receiver_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/plugins/weather_station/scenes/weather_station_scene.c b/applications/plugins/weather_station/scenes/weather_station_scene.c new file mode 100644 index 000000000..f9306e5f4 --- /dev/null +++ b/applications/plugins/weather_station/scenes/weather_station_scene.c @@ -0,0 +1,30 @@ +#include "../weather_station_app_i.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const weather_station_scene_on_enter_handlers[])(void*) = { +#include "weather_station_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 weather_station_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "weather_station_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 weather_station_scene_on_exit_handlers[])(void* context) = { +#include "weather_station_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers weather_station_scene_handlers = { + .on_enter_handlers = weather_station_scene_on_enter_handlers, + .on_event_handlers = weather_station_scene_on_event_handlers, + .on_exit_handlers = weather_station_scene_on_exit_handlers, + .scene_num = WeatherStationSceneNum, +}; diff --git a/applications/plugins/weather_station/scenes/weather_station_scene.h b/applications/plugins/weather_station/scenes/weather_station_scene.h new file mode 100644 index 000000000..8cee4ee60 --- /dev/null +++ b/applications/plugins/weather_station/scenes/weather_station_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) WeatherStationScene##id, +typedef enum { +#include "weather_station_scene_config.h" + WeatherStationSceneNum, +} WeatherStationScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers weather_station_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "weather_station_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 "weather_station_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 "weather_station_scene_config.h" +#undef ADD_SCENE diff --git a/applications/plugins/weather_station/scenes/weather_station_scene_about.c b/applications/plugins/weather_station/scenes/weather_station_scene_about.c new file mode 100644 index 000000000..d916dc76f --- /dev/null +++ b/applications/plugins/weather_station/scenes/weather_station_scene_about.c @@ -0,0 +1,78 @@ +#include "../weather_station_app_i.h" +#include "../helpers/weather_station_types.h" + +void weather_station_scene_about_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + WeatherStationApp* app = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(app->view_dispatcher, result); + } +} + +void weather_station_scene_about_on_enter(void* context) { + WeatherStationApp* app = context; + + FuriString* temp_str; + temp_str = furi_string_alloc(); + furi_string_printf(temp_str, "\e#%s\n", "Information"); + + furi_string_cat_printf(temp_str, "Version: %s\n", WS_VERSION_APP); + furi_string_cat_printf(temp_str, "Developed by: %s\n", WS_DEVELOPED); + furi_string_cat_printf(temp_str, "Github: %s\n\n", WS_GITHUB); + + furi_string_cat_printf(temp_str, "\e#%s\n", "Description"); + furi_string_cat_printf( + temp_str, "Reading messages from\nweather stations that work\nwith SubGhz sensors\n\n"); + + furi_string_cat_printf(temp_str, "Supported protocols:\n"); + size_t i = 0; + const char* protocol_name = + subghz_environment_get_protocol_name_registry(app->txrx->environment, i++); + do { + furi_string_cat_printf(temp_str, "%s\n", protocol_name); + protocol_name = subghz_environment_get_protocol_name_registry(app->txrx->environment, i++); + } while(protocol_name != NULL); + + widget_add_text_box_element( + app->widget, + 0, + 0, + 128, + 14, + AlignCenter, + AlignBottom, + "\e#\e! \e!\n", + false); + widget_add_text_box_element( + app->widget, + 0, + 2, + 128, + 14, + AlignCenter, + AlignBottom, + "\e#\e! Weather station \e!\n", + false); + widget_add_text_scroll_element(app->widget, 0, 16, 128, 50, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(app->view_dispatcher, WeatherStationViewWidget); +} + +bool weather_station_scene_about_on_event(void* context, SceneManagerEvent event) { + WeatherStationApp* app = context; + bool consumed = false; + UNUSED(app); + UNUSED(event); + + return consumed; +} + +void weather_station_scene_about_on_exit(void* context) { + WeatherStationApp* app = context; + + // Clear views + widget_reset(app->widget); +} diff --git a/applications/plugins/weather_station/scenes/weather_station_scene_config.h b/applications/plugins/weather_station/scenes/weather_station_scene_config.h new file mode 100644 index 000000000..0ba8ec013 --- /dev/null +++ b/applications/plugins/weather_station/scenes/weather_station_scene_config.h @@ -0,0 +1,5 @@ +ADD_SCENE(weather_station, start, Start) +ADD_SCENE(weather_station, about, About) +ADD_SCENE(weather_station, receiver, Receiver) +ADD_SCENE(weather_station, receiver_config, ReceiverConfig) +ADD_SCENE(weather_station, receiver_info, ReceiverInfo) diff --git a/applications/plugins/weather_station/scenes/weather_station_scene_receiver_config.c b/applications/plugins/weather_station/scenes/weather_station_scene_receiver_config.c new file mode 100644 index 000000000..fcd8f6d3e --- /dev/null +++ b/applications/plugins/weather_station/scenes/weather_station_scene_receiver_config.c @@ -0,0 +1,223 @@ +#include "../weather_station_app_i.h" + +enum WSSettingIndex { + WSSettingIndexFrequency, + WSSettingIndexHopping, + WSSettingIndexModulation, + WSSettingIndexLock, +}; + +#define HOPPING_COUNT 2 +const char* const hopping_text[HOPPING_COUNT] = { + "OFF", + "ON", +}; +const uint32_t hopping_value[HOPPING_COUNT] = { + WSHopperStateOFF, + WSHopperStateRunnig, +}; + +uint8_t weather_station_scene_receiver_config_next_frequency(const uint32_t value, void* context) { + furi_assert(context); + WeatherStationApp* app = context; + uint8_t index = 0; + for(uint8_t i = 0; i < subghz_setting_get_frequency_count(app->setting); i++) { + if(value == subghz_setting_get_frequency(app->setting, i)) { + index = i; + break; + } else { + index = subghz_setting_get_frequency_default_index(app->setting); + } + } + return index; +} + +uint8_t weather_station_scene_receiver_config_next_preset(const char* preset_name, void* context) { + furi_assert(context); + WeatherStationApp* app = context; + uint8_t index = 0; + for(uint8_t i = 0; i < subghz_setting_get_preset_count(app->setting); i++) { + if(!strcmp(subghz_setting_get_preset_name(app->setting, i), preset_name)) { + index = i; + break; + } else { + // index = subghz_setting_get_frequency_default_index(app ->setting); + } + } + return index; +} + +uint8_t weather_station_scene_receiver_config_hopper_value_index( + const uint32_t value, + const uint32_t values[], + uint8_t values_count, + void* context) { + furi_assert(context); + UNUSED(values_count); + WeatherStationApp* app = context; + + if(value == values[0]) { + return 0; + } else { + variable_item_set_current_value_text( + (VariableItem*)scene_manager_get_scene_state( + app->scene_manager, WeatherStationSceneReceiverConfig), + " -----"); + return 1; + } +} + +static void weather_station_scene_receiver_config_set_frequency(VariableItem* item) { + WeatherStationApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + if(app->txrx->hopper_state == WSHopperStateOFF) { + char text_buf[10] = {0}; + snprintf( + text_buf, + sizeof(text_buf), + "%lu.%02lu", + subghz_setting_get_frequency(app->setting, index) / 1000000, + (subghz_setting_get_frequency(app->setting, index) % 1000000) / 10000); + variable_item_set_current_value_text(item, text_buf); + app->txrx->preset->frequency = subghz_setting_get_frequency(app->setting, index); + } else { + variable_item_set_current_value_index( + item, subghz_setting_get_frequency_default_index(app->setting)); + } +} + +static void weather_station_scene_receiver_config_set_preset(VariableItem* item) { + WeatherStationApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text( + item, subghz_setting_get_preset_name(app->setting, index)); + ws_preset_init( + app, + subghz_setting_get_preset_name(app->setting, index), + app->txrx->preset->frequency, + subghz_setting_get_preset_data(app->setting, index), + subghz_setting_get_preset_data_size(app->setting, index)); +} + +static void weather_station_scene_receiver_config_set_hopping_running(VariableItem* item) { + WeatherStationApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, hopping_text[index]); + if(hopping_value[index] == WSHopperStateOFF) { + char text_buf[10] = {0}; + snprintf( + text_buf, + sizeof(text_buf), + "%lu.%02lu", + subghz_setting_get_default_frequency(app->setting) / 1000000, + (subghz_setting_get_default_frequency(app->setting) % 1000000) / 10000); + variable_item_set_current_value_text( + (VariableItem*)scene_manager_get_scene_state( + app->scene_manager, WeatherStationSceneReceiverConfig), + text_buf); + app->txrx->preset->frequency = subghz_setting_get_default_frequency(app->setting); + variable_item_set_current_value_index( + (VariableItem*)scene_manager_get_scene_state( + app->scene_manager, WeatherStationSceneReceiverConfig), + subghz_setting_get_frequency_default_index(app->setting)); + } else { + variable_item_set_current_value_text( + (VariableItem*)scene_manager_get_scene_state( + app->scene_manager, WeatherStationSceneReceiverConfig), + " -----"); + variable_item_set_current_value_index( + (VariableItem*)scene_manager_get_scene_state( + app->scene_manager, WeatherStationSceneReceiverConfig), + subghz_setting_get_frequency_default_index(app->setting)); + } + + app->txrx->hopper_state = hopping_value[index]; +} + +static void + weather_station_scene_receiver_config_var_list_enter_callback(void* context, uint32_t index) { + furi_assert(context); + WeatherStationApp* app = context; + if(index == WSSettingIndexLock) { + view_dispatcher_send_custom_event(app->view_dispatcher, WSCustomEventSceneSettingLock); + } +} + +void weather_station_scene_receiver_config_on_enter(void* context) { + WeatherStationApp* app = context; + VariableItem* item; + uint8_t value_index; + + item = variable_item_list_add( + app->variable_item_list, + "Frequency:", + subghz_setting_get_frequency_count(app->setting), + weather_station_scene_receiver_config_set_frequency, + app); + value_index = + weather_station_scene_receiver_config_next_frequency(app->txrx->preset->frequency, app); + scene_manager_set_scene_state( + app->scene_manager, WeatherStationSceneReceiverConfig, (uint32_t)item); + variable_item_set_current_value_index(item, value_index); + char text_buf[10] = {0}; + snprintf( + text_buf, + sizeof(text_buf), + "%lu.%02lu", + subghz_setting_get_frequency(app->setting, value_index) / 1000000, + (subghz_setting_get_frequency(app->setting, value_index) % 1000000) / 10000); + variable_item_set_current_value_text(item, text_buf); + + item = variable_item_list_add( + app->variable_item_list, + "Hopping:", + HOPPING_COUNT, + weather_station_scene_receiver_config_set_hopping_running, + app); + value_index = weather_station_scene_receiver_config_hopper_value_index( + app->txrx->hopper_state, hopping_value, HOPPING_COUNT, app); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, hopping_text[value_index]); + + item = variable_item_list_add( + app->variable_item_list, + "Modulation:", + subghz_setting_get_preset_count(app->setting), + weather_station_scene_receiver_config_set_preset, + app); + value_index = weather_station_scene_receiver_config_next_preset( + furi_string_get_cstr(app->txrx->preset->name), app); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text( + item, subghz_setting_get_preset_name(app->setting, value_index)); + + variable_item_list_add(app->variable_item_list, "Lock Keyboard", 1, NULL, NULL); + variable_item_list_set_enter_callback( + app->variable_item_list, + weather_station_scene_receiver_config_var_list_enter_callback, + app); + + view_dispatcher_switch_to_view(app->view_dispatcher, WeatherStationViewVariableItemList); +} + +bool weather_station_scene_receiver_config_on_event(void* context, SceneManagerEvent event) { + WeatherStationApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == WSCustomEventSceneSettingLock) { + app->lock = WSLockOn; + scene_manager_previous_scene(app->scene_manager); + consumed = true; + } + } + return consumed; +} + +void weather_station_scene_receiver_config_on_exit(void* context) { + WeatherStationApp* app = context; + variable_item_list_set_selected_item(app->variable_item_list, 0); + variable_item_list_reset(app->variable_item_list); +} diff --git a/applications/plugins/weather_station/scenes/weather_station_scene_receiver_info.c b/applications/plugins/weather_station/scenes/weather_station_scene_receiver_info.c new file mode 100644 index 000000000..b26661be3 --- /dev/null +++ b/applications/plugins/weather_station/scenes/weather_station_scene_receiver_info.c @@ -0,0 +1,50 @@ +#include "../weather_station_app_i.h" +#include "../views/weather_station_receiver.h" + +void weather_station_scene_receiver_info_callback(WSCustomEvent event, void* context) { + furi_assert(context); + WeatherStationApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +static void weather_station_scene_receiver_info_add_to_history_callback( + SubGhzReceiver* receiver, + SubGhzProtocolDecoderBase* decoder_base, + void* context) { + furi_assert(context); + WeatherStationApp* app = context; + + if(ws_history_add_to_history(app->txrx->history, decoder_base, app->txrx->preset) == + WSHistoryStateAddKeyUpdateData) { + ws_view_receiver_info_update( + app->ws_receiver_info, + ws_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen)); + subghz_receiver_reset(receiver); + + notification_message(app->notifications, &sequence_blink_green_10); + app->txrx->rx_key_state = WSRxKeyStateAddKey; + } +} + +void weather_station_scene_receiver_info_on_enter(void* context) { + WeatherStationApp* app = context; + + subghz_receiver_set_rx_callback( + app->txrx->receiver, weather_station_scene_receiver_info_add_to_history_callback, app); + ws_view_receiver_info_update( + app->ws_receiver_info, + ws_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen)); + view_dispatcher_switch_to_view(app->view_dispatcher, WeatherStationViewReceiverInfo); +} + +bool weather_station_scene_receiver_info_on_event(void* context, SceneManagerEvent event) { + WeatherStationApp* app = context; + bool consumed = false; + UNUSED(app); + UNUSED(event); + return consumed; +} + +void weather_station_scene_receiver_info_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/plugins/weather_station/scenes/weather_station_scene_start.c b/applications/plugins/weather_station/scenes/weather_station_scene_start.c new file mode 100644 index 000000000..56dd6fa86 --- /dev/null +++ b/applications/plugins/weather_station/scenes/weather_station_scene_start.c @@ -0,0 +1,58 @@ +#include "../weather_station_app_i.h" + +typedef enum { + SubmenuIndexWeatherStationReceiver, + SubmenuIndexWeatherStationAbout, +} SubmenuIndex; + +void weather_station_scene_start_submenu_callback(void* context, uint32_t index) { + WeatherStationApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void weather_station_scene_start_on_enter(void* context) { + UNUSED(context); + WeatherStationApp* app = context; + Submenu* submenu = app->submenu; + + submenu_add_item( + submenu, + "Read Weather Station", + SubmenuIndexWeatherStationReceiver, + weather_station_scene_start_submenu_callback, + app); + submenu_add_item( + submenu, + "About", + SubmenuIndexWeatherStationAbout, + weather_station_scene_start_submenu_callback, + app); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(app->scene_manager, WeatherStationSceneStart)); + + view_dispatcher_switch_to_view(app->view_dispatcher, WeatherStationViewSubmenu); +} + +bool weather_station_scene_start_on_event(void* context, SceneManagerEvent event) { + WeatherStationApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexWeatherStationAbout) { + scene_manager_next_scene(app->scene_manager, WeatherStationSceneAbout); + consumed = true; + } else if(event.event == SubmenuIndexWeatherStationReceiver) { + scene_manager_next_scene(app->scene_manager, WeatherStationSceneReceiver); + consumed = true; + } + scene_manager_set_scene_state(app->scene_manager, WeatherStationSceneStart, event.event); + } + + return consumed; +} + +void weather_station_scene_start_on_exit(void* context) { + WeatherStationApp* app = context; + submenu_reset(app->submenu); +} diff --git a/applications/plugins/weather_station/views/weather_station_receiver.c b/applications/plugins/weather_station/views/weather_station_receiver.c new file mode 100644 index 000000000..654fb5a3b --- /dev/null +++ b/applications/plugins/weather_station/views/weather_station_receiver.c @@ -0,0 +1,436 @@ +#include "weather_station_receiver.h" +#include "../weather_station_app_i.h" +#include +#include + +#include +#include +#include + +#define FRAME_HEIGHT 12 +#define MAX_LEN_PX 112 +#define MENU_ITEMS 4u +#define UNLOCK_CNT 3 + +typedef struct { + FuriString* item_str; + uint8_t type; +} WSReceiverMenuItem; + +ARRAY_DEF(WSReceiverMenuItemArray, WSReceiverMenuItem, M_POD_OPLIST) + +#define M_OPL_WSReceiverMenuItemArray_t() ARRAY_OPLIST(WSReceiverMenuItemArray, M_POD_OPLIST) + +struct WSReceiverHistory { + WSReceiverMenuItemArray_t data; +}; + +typedef struct WSReceiverHistory WSReceiverHistory; + +static const Icon* ReceiverItemIcons[] = { + [SubGhzProtocolTypeUnknown] = &I_Quest_7x8, + [SubGhzProtocolTypeStatic] = &I_Unlock_7x8, + [SubGhzProtocolTypeDynamic] = &I_Lock_7x8, + [SubGhzProtocolWeatherStation] = &I_station_icon, +}; + +typedef enum { + WSReceiverBarShowDefault, + WSReceiverBarShowLock, + WSReceiverBarShowToUnlockPress, + WSReceiverBarShowUnlock, +} WSReceiverBarShow; + +struct WSReceiver { + WSLock lock; + uint8_t lock_count; + FuriTimer* timer; + View* view; + WSReceiverCallback callback; + void* context; +}; + +typedef struct { + FuriString* frequency_str; + FuriString* preset_str; + FuriString* history_stat_str; + WSReceiverHistory* history; + uint16_t idx; + uint16_t list_offset; + uint16_t history_item; + WSReceiverBarShow bar_show; +} WSReceiverModel; + +void ws_view_receiver_set_lock(WSReceiver* ws_receiver, WSLock lock) { + furi_assert(ws_receiver); + ws_receiver->lock_count = 0; + if(lock == WSLockOn) { + ws_receiver->lock = lock; + with_view_model( + ws_receiver->view, + WSReceiverModel * model, + { model->bar_show = WSReceiverBarShowLock; }, + true); + furi_timer_start(ws_receiver->timer, pdMS_TO_TICKS(1000)); + } else { + with_view_model( + ws_receiver->view, + WSReceiverModel * model, + { model->bar_show = WSReceiverBarShowDefault; }, + true); + } +} + +void ws_view_receiver_set_callback( + WSReceiver* ws_receiver, + WSReceiverCallback callback, + void* context) { + furi_assert(ws_receiver); + furi_assert(callback); + ws_receiver->callback = callback; + ws_receiver->context = context; +} + +static void ws_view_receiver_update_offset(WSReceiver* ws_receiver) { + furi_assert(ws_receiver); + + with_view_model( + ws_receiver->view, + WSReceiverModel * model, + { + size_t history_item = model->history_item; + uint16_t bounds = history_item > 3 ? 2 : history_item; + + if(history_item > 3 && model->idx >= (int16_t)(history_item - 1)) { + model->list_offset = model->idx - 3; + } else if(model->list_offset < model->idx - bounds) { + model->list_offset = + CLAMP(model->list_offset + 1, (int16_t)(history_item - bounds), 0); + } else if(model->list_offset > model->idx - bounds) { + model->list_offset = CLAMP(model->idx - 1, (int16_t)(history_item - bounds), 0); + } + }, + true); +} + +void ws_view_receiver_add_item_to_menu(WSReceiver* ws_receiver, const char* name, uint8_t type) { + furi_assert(ws_receiver); + with_view_model( + ws_receiver->view, + WSReceiverModel * model, + { + WSReceiverMenuItem* item_menu = WSReceiverMenuItemArray_push_raw(model->history->data); + item_menu->item_str = furi_string_alloc_set(name); + item_menu->type = type; + if((model->idx == model->history_item - 1)) { + model->history_item++; + model->idx++; + } else { + model->history_item++; + } + }, + true); + ws_view_receiver_update_offset(ws_receiver); +} + +void ws_view_receiver_add_data_statusbar( + WSReceiver* ws_receiver, + const char* frequency_str, + const char* preset_str, + const char* history_stat_str) { + furi_assert(ws_receiver); + with_view_model( + ws_receiver->view, + WSReceiverModel * model, + { + furi_string_set_str(model->frequency_str, frequency_str); + furi_string_set_str(model->preset_str, preset_str); + furi_string_set_str(model->history_stat_str, history_stat_str); + }, + true); +} + +static void ws_view_receiver_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_box(canvas, 0, 0 + idx * FRAME_HEIGHT, scrollbar ? 122 : 127, FRAME_HEIGHT); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_dot(canvas, 0, 0 + idx * FRAME_HEIGHT); + canvas_draw_dot(canvas, 1, 0 + idx * FRAME_HEIGHT); + canvas_draw_dot(canvas, 0, (0 + idx * FRAME_HEIGHT) + 1); + + canvas_draw_dot(canvas, 0, (0 + idx * FRAME_HEIGHT) + 11); + canvas_draw_dot(canvas, scrollbar ? 121 : 126, 0 + idx * FRAME_HEIGHT); + canvas_draw_dot(canvas, scrollbar ? 121 : 126, (0 + idx * FRAME_HEIGHT) + 11); +} + +void ws_view_receiver_draw(Canvas* canvas, WSReceiverModel* model) { + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontSecondary); + + elements_button_left(canvas, "Config"); + canvas_draw_line(canvas, 46, 51, 125, 51); + + bool scrollbar = model->history_item > 4; + FuriString* str_buff; + str_buff = furi_string_alloc(); + + WSReceiverMenuItem* item_menu; + + for(size_t i = 0; i < MIN(model->history_item, MENU_ITEMS); ++i) { + size_t idx = CLAMP((uint16_t)(i + model->list_offset), model->history_item, 0); + item_menu = WSReceiverMenuItemArray_get(model->history->data, idx); + furi_string_set(str_buff, item_menu->item_str); + elements_string_fit_width(canvas, str_buff, scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX); + if(model->idx == idx) { + ws_view_receiver_draw_frame(canvas, i, scrollbar); + } else { + canvas_set_color(canvas, ColorBlack); + } + canvas_draw_icon(canvas, 4, 2 + i * FRAME_HEIGHT, ReceiverItemIcons[item_menu->type]); + canvas_draw_str(canvas, 14, 9 + i * FRAME_HEIGHT, furi_string_get_cstr(str_buff)); + furi_string_reset(str_buff); + } + if(scrollbar) { + elements_scrollbar_pos(canvas, 128, 0, 49, model->idx, model->history_item); + } + furi_string_free(str_buff); + + canvas_set_color(canvas, ColorBlack); + + if(model->history_item == 0) { + canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 63, 46, "Scanning..."); + canvas_draw_line(canvas, 46, 51, 125, 51); + canvas_set_font(canvas, FontSecondary); + } + + switch(model->bar_show) { + case WSReceiverBarShowLock: + canvas_draw_icon(canvas, 64, 55, &I_Lock_7x8); + canvas_draw_str(canvas, 74, 62, "Locked"); + break; + case WSReceiverBarShowToUnlockPress: + canvas_draw_str(canvas, 44, 62, furi_string_get_cstr(model->frequency_str)); + canvas_draw_str(canvas, 79, 62, furi_string_get_cstr(model->preset_str)); + canvas_draw_str(canvas, 96, 62, furi_string_get_cstr(model->history_stat_str)); + canvas_set_font(canvas, FontSecondary); + elements_bold_rounded_frame(canvas, 14, 8, 99, 48); + elements_multiline_text(canvas, 65, 26, "To unlock\npress:"); + canvas_draw_icon(canvas, 65, 42, &I_Pin_back_arrow_10x8); + canvas_draw_icon(canvas, 80, 42, &I_Pin_back_arrow_10x8); + canvas_draw_icon(canvas, 95, 42, &I_Pin_back_arrow_10x8); + canvas_draw_icon(canvas, 16, 13, &I_WarningDolphin_45x42); + canvas_draw_dot(canvas, 17, 61); + break; + case WSReceiverBarShowUnlock: + canvas_draw_icon(canvas, 64, 55, &I_Unlock_7x8); + canvas_draw_str(canvas, 74, 62, "Unlocked"); + break; + default: + canvas_draw_str(canvas, 44, 62, furi_string_get_cstr(model->frequency_str)); + canvas_draw_str(canvas, 79, 62, furi_string_get_cstr(model->preset_str)); + canvas_draw_str(canvas, 96, 62, furi_string_get_cstr(model->history_stat_str)); + break; + } +} + +static void ws_view_receiver_timer_callback(void* context) { + furi_assert(context); + WSReceiver* ws_receiver = context; + with_view_model( + ws_receiver->view, + WSReceiverModel * model, + { model->bar_show = WSReceiverBarShowDefault; }, + true); + if(ws_receiver->lock_count < UNLOCK_CNT) { + ws_receiver->callback(WSCustomEventViewReceiverOffDisplay, ws_receiver->context); + } else { + ws_receiver->lock = WSLockOff; + ws_receiver->callback(WSCustomEventViewReceiverUnlock, ws_receiver->context); + } + ws_receiver->lock_count = 0; +} + +bool ws_view_receiver_input(InputEvent* event, void* context) { + furi_assert(context); + WSReceiver* ws_receiver = context; + + if(ws_receiver->lock == WSLockOn) { + with_view_model( + ws_receiver->view, + WSReceiverModel * model, + { model->bar_show = WSReceiverBarShowToUnlockPress; }, + true); + if(ws_receiver->lock_count == 0) { + furi_timer_start(ws_receiver->timer, pdMS_TO_TICKS(1000)); + } + if(event->key == InputKeyBack && event->type == InputTypeShort) { + ws_receiver->lock_count++; + } + if(ws_receiver->lock_count >= UNLOCK_CNT) { + ws_receiver->callback(WSCustomEventViewReceiverUnlock, ws_receiver->context); + with_view_model( + ws_receiver->view, + WSReceiverModel * model, + { model->bar_show = WSReceiverBarShowUnlock; }, + true); + ws_receiver->lock = WSLockOff; + furi_timer_start(ws_receiver->timer, pdMS_TO_TICKS(650)); + } + + return true; + } + + if(event->key == InputKeyBack && event->type == InputTypeShort) { + ws_receiver->callback(WSCustomEventViewReceiverBack, ws_receiver->context); + } else if( + event->key == InputKeyUp && + (event->type == InputTypeShort || event->type == InputTypeRepeat)) { + with_view_model( + ws_receiver->view, + WSReceiverModel * model, + { + if(model->idx != 0) model->idx--; + }, + true); + } else if( + event->key == InputKeyDown && + (event->type == InputTypeShort || event->type == InputTypeRepeat)) { + with_view_model( + ws_receiver->view, + WSReceiverModel * model, + { + if(model->idx != model->history_item - 1) model->idx++; + }, + true); + } else if(event->key == InputKeyLeft && event->type == InputTypeShort) { + ws_receiver->callback(WSCustomEventViewReceiverConfig, ws_receiver->context); + } else if(event->key == InputKeyOk && event->type == InputTypeShort) { + with_view_model( + ws_receiver->view, + WSReceiverModel * model, + { + if(model->history_item != 0) { + ws_receiver->callback(WSCustomEventViewReceiverOK, ws_receiver->context); + } + }, + false); + } + + ws_view_receiver_update_offset(ws_receiver); + + return true; +} + +void ws_view_receiver_enter(void* context) { + furi_assert(context); +} + +void ws_view_receiver_exit(void* context) { + furi_assert(context); + WSReceiver* ws_receiver = context; + with_view_model( + ws_receiver->view, + WSReceiverModel * model, + { + furi_string_reset(model->frequency_str); + furi_string_reset(model->preset_str); + furi_string_reset(model->history_stat_str); + for + M_EACH(item_menu, model->history->data, WSReceiverMenuItemArray_t) { + furi_string_free(item_menu->item_str); + item_menu->type = 0; + } + WSReceiverMenuItemArray_reset(model->history->data); + model->idx = 0; + model->list_offset = 0; + model->history_item = 0; + }, + false); + furi_timer_stop(ws_receiver->timer); +} + +WSReceiver* ws_view_receiver_alloc() { + WSReceiver* ws_receiver = malloc(sizeof(WSReceiver)); + + // View allocation and configuration + ws_receiver->view = view_alloc(); + + ws_receiver->lock = WSLockOff; + ws_receiver->lock_count = 0; + view_allocate_model(ws_receiver->view, ViewModelTypeLocking, sizeof(WSReceiverModel)); + view_set_context(ws_receiver->view, ws_receiver); + view_set_draw_callback(ws_receiver->view, (ViewDrawCallback)ws_view_receiver_draw); + view_set_input_callback(ws_receiver->view, ws_view_receiver_input); + view_set_enter_callback(ws_receiver->view, ws_view_receiver_enter); + view_set_exit_callback(ws_receiver->view, ws_view_receiver_exit); + + with_view_model( + ws_receiver->view, + WSReceiverModel * model, + { + model->frequency_str = furi_string_alloc(); + model->preset_str = furi_string_alloc(); + model->history_stat_str = furi_string_alloc(); + model->bar_show = WSReceiverBarShowDefault; + model->history = malloc(sizeof(WSReceiverHistory)); + WSReceiverMenuItemArray_init(model->history->data); + }, + true); + ws_receiver->timer = + furi_timer_alloc(ws_view_receiver_timer_callback, FuriTimerTypeOnce, ws_receiver); + return ws_receiver; +} + +void ws_view_receiver_free(WSReceiver* ws_receiver) { + furi_assert(ws_receiver); + + with_view_model( + ws_receiver->view, + WSReceiverModel * model, + { + furi_string_free(model->frequency_str); + furi_string_free(model->preset_str); + furi_string_free(model->history_stat_str); + for + M_EACH(item_menu, model->history->data, WSReceiverMenuItemArray_t) { + furi_string_free(item_menu->item_str); + item_menu->type = 0; + } + WSReceiverMenuItemArray_clear(model->history->data); + free(model->history); + }, + false); + furi_timer_free(ws_receiver->timer); + view_free(ws_receiver->view); + free(ws_receiver); +} + +View* ws_view_receiver_get_view(WSReceiver* ws_receiver) { + furi_assert(ws_receiver); + return ws_receiver->view; +} + +uint16_t ws_view_receiver_get_idx_menu(WSReceiver* ws_receiver) { + furi_assert(ws_receiver); + uint32_t idx = 0; + with_view_model( + ws_receiver->view, WSReceiverModel * model, { idx = model->idx; }, false); + return idx; +} + +void ws_view_receiver_set_idx_menu(WSReceiver* ws_receiver, uint16_t idx) { + furi_assert(ws_receiver); + with_view_model( + ws_receiver->view, + WSReceiverModel * model, + { + model->idx = idx; + if(model->idx > 2) model->list_offset = idx - 2; + }, + true); + ws_view_receiver_update_offset(ws_receiver); +} diff --git a/applications/plugins/weather_station/views/weather_station_receiver.h b/applications/plugins/weather_station/views/weather_station_receiver.h new file mode 100644 index 000000000..30c6516d5 --- /dev/null +++ b/applications/plugins/weather_station/views/weather_station_receiver.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include "../helpers/weather_station_types.h" +#include "../helpers/weather_station_event.h" + +typedef struct WSReceiver WSReceiver; + +typedef void (*WSReceiverCallback)(WSCustomEvent event, void* context); + +void ws_view_receiver_set_lock(WSReceiver* ws_receiver, WSLock keyboard); + +void ws_view_receiver_set_callback( + WSReceiver* ws_receiver, + WSReceiverCallback callback, + void* context); + +WSReceiver* ws_view_receiver_alloc(); + +void ws_view_receiver_free(WSReceiver* ws_receiver); + +View* ws_view_receiver_get_view(WSReceiver* ws_receiver); + +void ws_view_receiver_add_data_statusbar( + WSReceiver* ws_receiver, + const char* frequency_str, + const char* preset_str, + const char* history_stat_str); + +void ws_view_receiver_add_item_to_menu(WSReceiver* ws_receiver, const char* name, uint8_t type); + +uint16_t ws_view_receiver_get_idx_menu(WSReceiver* ws_receiver); + +void ws_view_receiver_set_idx_menu(WSReceiver* ws_receiver, uint16_t idx); + +void ws_view_receiver_exit(void* context); diff --git a/applications/plugins/weather_station/views/weather_station_receiver_info.c b/applications/plugins/weather_station/views/weather_station_receiver_info.c new file mode 100644 index 000000000..a7a92f3b4 --- /dev/null +++ b/applications/plugins/weather_station/views/weather_station_receiver_info.c @@ -0,0 +1,226 @@ +#include "weather_station_receiver.h" +#include "../weather_station_app_i.h" +#include "Weather_Station_icons.h" +#include "../protocols/ws_generic.h" +#include +#include + +#define abs(x) ((x) > 0 ? (x) : -(x)) + +struct WSReceiverInfo { + View* view; + FuriTimer* timer; +}; + +typedef struct { + uint32_t curr_ts; + FuriString* protocol_name; + WSBlockGeneric* generic; +} WSReceiverInfoModel; + +void ws_view_receiver_info_update(WSReceiverInfo* ws_receiver_info, FlipperFormat* fff) { + furi_assert(ws_receiver_info); + furi_assert(fff); + + with_view_model( + ws_receiver_info->view, + WSReceiverInfoModel * model, + { + flipper_format_rewind(fff); + flipper_format_read_string(fff, "Protocol", model->protocol_name); + + ws_block_generic_deserialize(model->generic, fff); + + FuriHalRtcDateTime curr_dt; + furi_hal_rtc_get_datetime(&curr_dt); + model->curr_ts = furi_hal_rtc_datetime_to_timestamp(&curr_dt); + }, + true); +} + +void ws_view_receiver_info_draw(Canvas* canvas, WSReceiverInfoModel* model) { + char buffer[64]; + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontSecondary); + + snprintf( + buffer, + sizeof(buffer), + "%s %db", + furi_string_get_cstr(model->protocol_name), + model->generic->data_count_bit); + canvas_draw_str(canvas, 0, 8, buffer); + + if(model->generic->channel != WS_NO_CHANNEL) { + snprintf(buffer, sizeof(buffer), "Ch: %01d", model->generic->channel); + canvas_draw_str(canvas, 106, 8, buffer); + } + + if(model->generic->id != WS_NO_ID) { + snprintf(buffer, sizeof(buffer), "Sn: 0x%02lX", model->generic->id); + canvas_draw_str(canvas, 0, 20, buffer); + } + + if(model->generic->btn != WS_NO_BTN) { + snprintf(buffer, sizeof(buffer), "Btn: %01d", model->generic->btn); + canvas_draw_str(canvas, 57, 20, buffer); + } + + if(model->generic->battery_low != WS_NO_BATT) { + snprintf( + buffer, sizeof(buffer), "Batt: %s", (!model->generic->battery_low ? "ok" : "low")); + canvas_draw_str_aligned(canvas, 126, 17, AlignRight, AlignCenter, buffer); + } + + snprintf(buffer, sizeof(buffer), "Data: 0x%llX", model->generic->data); + canvas_draw_str(canvas, 0, 32, buffer); + + elements_bold_rounded_frame(canvas, 0, 38, 127, 25); + canvas_set_font(canvas, FontPrimary); + + if(model->generic->temp != WS_NO_TEMPERATURE) { + canvas_draw_icon(canvas, 6, 43, &I_Therm_7x16); + snprintf(buffer, sizeof(buffer), "%3.1f C", (double)model->generic->temp); + uint8_t temp_x1 = 47; + uint8_t temp_x2 = 38; + if(model->generic->temp < -9.0) { + temp_x1 = 49; + temp_x2 = 40; + } + canvas_draw_str_aligned(canvas, temp_x1, 47, AlignRight, AlignTop, buffer); + canvas_draw_circle(canvas, temp_x2, 46, 1); + } + + if(model->generic->humidity != WS_NO_HUMIDITY) { + canvas_draw_icon(canvas, 53, 44, &I_Humid_8x13); + snprintf(buffer, sizeof(buffer), "%d%%", model->generic->humidity); + canvas_draw_str(canvas, 64, 55, buffer); + } + + if((int)model->generic->timestamp > 0 && model->curr_ts) { + int ts_diff = (int)model->curr_ts - (int)model->generic->timestamp; + + canvas_draw_icon(canvas, 91, 46, &I_Timer_11x11); + + if(ts_diff > 60) { + int tmp_sec = ts_diff; + int cnt_min = 1; + for(int i = 1; tmp_sec > 60; i++) { + tmp_sec = tmp_sec - 60; + cnt_min = i; + } + + if(model->curr_ts % 2 == 0) { + canvas_draw_str_aligned(canvas, 105, 51, AlignLeft, AlignCenter, "Old"); + } else { + if(cnt_min >= 59) { + canvas_draw_str_aligned(canvas, 105, 51, AlignLeft, AlignCenter, "Old"); + } else { + snprintf(buffer, sizeof(buffer), "%dm", cnt_min); + canvas_draw_str_aligned(canvas, 114, 51, AlignCenter, AlignCenter, buffer); + } + } + + } else { + snprintf(buffer, sizeof(buffer), "%d", ts_diff); + canvas_draw_str_aligned(canvas, 112, 51, AlignCenter, AlignCenter, buffer); + } + } +} + +bool ws_view_receiver_info_input(InputEvent* event, void* context) { + furi_assert(context); + //WSReceiverInfo* ws_receiver_info = context; + + if(event->key == InputKeyBack) { + return false; + } + + return true; +} + +static void ws_view_receiver_info_enter(void* context) { + furi_assert(context); + WSReceiverInfo* ws_receiver_info = context; + + furi_timer_start(ws_receiver_info->timer, 1000); +} + +static void ws_view_receiver_info_exit(void* context) { + furi_assert(context); + WSReceiverInfo* ws_receiver_info = context; + + furi_timer_stop(ws_receiver_info->timer); + + with_view_model( + ws_receiver_info->view, + WSReceiverInfoModel * model, + { furi_string_reset(model->protocol_name); }, + false); +} + +static void ws_view_receiver_info_timer(void* context) { + WSReceiverInfo* ws_receiver_info = context; + // Force redraw + with_view_model( + ws_receiver_info->view, + WSReceiverInfoModel * model, + { + FuriHalRtcDateTime curr_dt; + furi_hal_rtc_get_datetime(&curr_dt); + model->curr_ts = furi_hal_rtc_datetime_to_timestamp(&curr_dt); + }, + true); +} + +WSReceiverInfo* ws_view_receiver_info_alloc() { + WSReceiverInfo* ws_receiver_info = malloc(sizeof(WSReceiverInfo)); + + // View allocation and configuration + ws_receiver_info->view = view_alloc(); + + view_allocate_model(ws_receiver_info->view, ViewModelTypeLocking, sizeof(WSReceiverInfoModel)); + view_set_context(ws_receiver_info->view, ws_receiver_info); + view_set_draw_callback(ws_receiver_info->view, (ViewDrawCallback)ws_view_receiver_info_draw); + view_set_input_callback(ws_receiver_info->view, ws_view_receiver_info_input); + view_set_enter_callback(ws_receiver_info->view, ws_view_receiver_info_enter); + view_set_exit_callback(ws_receiver_info->view, ws_view_receiver_info_exit); + + with_view_model( + ws_receiver_info->view, + WSReceiverInfoModel * model, + { + model->generic = malloc(sizeof(WSBlockGeneric)); + model->protocol_name = furi_string_alloc(); + }, + true); + + ws_receiver_info->timer = + furi_timer_alloc(ws_view_receiver_info_timer, FuriTimerTypePeriodic, ws_receiver_info); + + return ws_receiver_info; +} + +void ws_view_receiver_info_free(WSReceiverInfo* ws_receiver_info) { + furi_assert(ws_receiver_info); + + furi_timer_free(ws_receiver_info->timer); + + with_view_model( + ws_receiver_info->view, + WSReceiverInfoModel * model, + { + furi_string_free(model->protocol_name); + free(model->generic); + }, + false); + + view_free(ws_receiver_info->view); + free(ws_receiver_info); +} + +View* ws_view_receiver_info_get_view(WSReceiverInfo* ws_receiver_info) { + furi_assert(ws_receiver_info); + return ws_receiver_info->view; +} diff --git a/applications/plugins/weather_station/views/weather_station_receiver_info.h b/applications/plugins/weather_station/views/weather_station_receiver_info.h new file mode 100644 index 000000000..705434a23 --- /dev/null +++ b/applications/plugins/weather_station/views/weather_station_receiver_info.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include "../helpers/weather_station_types.h" +#include "../helpers/weather_station_event.h" +#include + +typedef struct WSReceiverInfo WSReceiverInfo; + +void ws_view_receiver_info_update(WSReceiverInfo* ws_receiver_info, FlipperFormat* fff); + +WSReceiverInfo* ws_view_receiver_info_alloc(); + +void ws_view_receiver_info_free(WSReceiverInfo* ws_receiver_info); + +View* ws_view_receiver_info_get_view(WSReceiverInfo* ws_receiver_info); diff --git a/applications/plugins/weather_station/weather_station_10px.png b/applications/plugins/weather_station/weather_station_10px.png new file mode 100644 index 000000000..7d5cc318c Binary files /dev/null and b/applications/plugins/weather_station/weather_station_10px.png differ diff --git a/applications/plugins/weather_station/weather_station_app.c b/applications/plugins/weather_station/weather_station_app.c new file mode 100644 index 000000000..b17f2acfc --- /dev/null +++ b/applications/plugins/weather_station/weather_station_app.c @@ -0,0 +1,178 @@ +#include "weather_station_app_i.h" + +#include +#include +#include "protocols/protocol_items.h" + +static bool weather_station_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + WeatherStationApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool weather_station_app_back_event_callback(void* context) { + furi_assert(context); + WeatherStationApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void weather_station_app_tick_event_callback(void* context) { + furi_assert(context); + WeatherStationApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +WeatherStationApp* weather_station_app_alloc() { + WeatherStationApp* app = malloc(sizeof(WeatherStationApp)); + + // GUI + app->gui = furi_record_open(RECORD_GUI); + + // View Dispatcher + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&weather_station_scene_handlers, app); + view_dispatcher_enable_queue(app->view_dispatcher); + + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, weather_station_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, weather_station_app_back_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, weather_station_app_tick_event_callback, 100); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + // Open Notification record + app->notifications = furi_record_open(RECORD_NOTIFICATION); + + // Variable Item List + app->variable_item_list = variable_item_list_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + WeatherStationViewVariableItemList, + variable_item_list_get_view(app->variable_item_list)); + + // SubMenu + app->submenu = submenu_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, WeatherStationViewSubmenu, submenu_get_view(app->submenu)); + + // Widget + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, WeatherStationViewWidget, widget_get_view(app->widget)); + + // Receiver + app->ws_receiver = ws_view_receiver_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + WeatherStationViewReceiver, + ws_view_receiver_get_view(app->ws_receiver)); + + // Receiver Info + app->ws_receiver_info = ws_view_receiver_info_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + WeatherStationViewReceiverInfo, + ws_view_receiver_info_get_view(app->ws_receiver_info)); + + //init setting + app->setting = subghz_setting_alloc(); + + //ToDo FIX file name setting + subghz_setting_load(app->setting, EXT_PATH("subghz/assets/setting_user")); + + //init Worker & Protocol & History + app->lock = WSLockOff; + app->txrx = malloc(sizeof(WeatherStationTxRx)); + app->txrx->preset = malloc(sizeof(SubGhzRadioPreset)); + app->txrx->preset->name = furi_string_alloc(); + ws_preset_init(app, "AM650", subghz_setting_get_default_frequency(app->setting), NULL, 0); + + app->txrx->hopper_state = WSHopperStateOFF; + app->txrx->history = ws_history_alloc(); + app->txrx->worker = subghz_worker_alloc(); + app->txrx->environment = subghz_environment_alloc(); + subghz_environment_set_protocol_registry( + app->txrx->environment, (void*)&weather_station_protocol_registry); + app->txrx->receiver = subghz_receiver_alloc_init(app->txrx->environment); + + subghz_receiver_set_filter(app->txrx->receiver, SubGhzProtocolFlag_Decodable); + subghz_worker_set_overrun_callback( + app->txrx->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset); + subghz_worker_set_pair_callback( + app->txrx->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode); + subghz_worker_set_context(app->txrx->worker, app->txrx->receiver); + + furi_hal_power_suppress_charge_enter(); + + scene_manager_next_scene(app->scene_manager, WeatherStationSceneStart); + + return app; +} + +void weather_station_app_free(WeatherStationApp* app) { + furi_assert(app); + + //CC1101 off + ws_sleep(app); + + // Submenu + view_dispatcher_remove_view(app->view_dispatcher, WeatherStationViewSubmenu); + submenu_free(app->submenu); + + // Variable Item List + view_dispatcher_remove_view(app->view_dispatcher, WeatherStationViewVariableItemList); + variable_item_list_free(app->variable_item_list); + + // Widget + view_dispatcher_remove_view(app->view_dispatcher, WeatherStationViewWidget); + widget_free(app->widget); + + // Receiver + view_dispatcher_remove_view(app->view_dispatcher, WeatherStationViewReceiver); + ws_view_receiver_free(app->ws_receiver); + + // Receiver Info + view_dispatcher_remove_view(app->view_dispatcher, WeatherStationViewReceiverInfo); + ws_view_receiver_info_free(app->ws_receiver_info); + + //setting + subghz_setting_free(app->setting); + + //Worker & Protocol & History + subghz_receiver_free(app->txrx->receiver); + subghz_environment_free(app->txrx->environment); + ws_history_free(app->txrx->history); + subghz_worker_free(app->txrx->worker); + furi_string_free(app->txrx->preset->name); + free(app->txrx->preset); + free(app->txrx); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + // Notifications + furi_record_close(RECORD_NOTIFICATION); + app->notifications = NULL; + + // Close records + furi_record_close(RECORD_GUI); + + furi_hal_power_suppress_charge_exit(); + + free(app); +} + +int32_t weather_station_app(void* p) { + UNUSED(p); + WeatherStationApp* weather_station_app = weather_station_app_alloc(); + + view_dispatcher_run(weather_station_app->view_dispatcher); + + weather_station_app_free(weather_station_app); + + return 0; +} diff --git a/applications/plugins/weather_station/weather_station_app_i.c b/applications/plugins/weather_station/weather_station_app_i.c new file mode 100644 index 000000000..052bb8533 --- /dev/null +++ b/applications/plugins/weather_station/weather_station_app_i.c @@ -0,0 +1,159 @@ +#include "weather_station_app_i.h" + +#define TAG "WeatherStation" +#include + +void ws_preset_init( + void* context, + const char* preset_name, + uint32_t frequency, + uint8_t* preset_data, + size_t preset_data_size) { + furi_assert(context); + WeatherStationApp* app = context; + furi_string_set(app->txrx->preset->name, preset_name); + app->txrx->preset->frequency = frequency; + app->txrx->preset->data = preset_data; + app->txrx->preset->data_size = preset_data_size; +} + +bool ws_set_preset(WeatherStationApp* app, const char* preset) { + if(!strcmp(preset, "FuriHalSubGhzPresetOok270Async")) { + furi_string_set(app->txrx->preset->name, "AM270"); + } else if(!strcmp(preset, "FuriHalSubGhzPresetOok650Async")) { + furi_string_set(app->txrx->preset->name, "AM650"); + } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev238Async")) { + furi_string_set(app->txrx->preset->name, "FM238"); + } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev476Async")) { + furi_string_set(app->txrx->preset->name, "FM476"); + } else if(!strcmp(preset, "FuriHalSubGhzPresetCustom")) { + furi_string_set(app->txrx->preset->name, "CUSTOM"); + } else { + FURI_LOG_E(TAG, "Unknown preset"); + return false; + } + return true; +} + +void ws_get_frequency_modulation( + WeatherStationApp* app, + FuriString* frequency, + FuriString* modulation) { + furi_assert(app); + if(frequency != NULL) { + furi_string_printf( + frequency, + "%03ld.%02ld", + app->txrx->preset->frequency / 1000000 % 1000, + app->txrx->preset->frequency / 10000 % 100); + } + if(modulation != NULL) { + furi_string_printf(modulation, "%.2s", furi_string_get_cstr(app->txrx->preset->name)); + } +} + +void ws_begin(WeatherStationApp* app, uint8_t* preset_data) { + furi_assert(app); + UNUSED(preset_data); + furi_hal_subghz_reset(); + furi_hal_subghz_idle(); + furi_hal_subghz_load_custom_preset(preset_data); + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); + app->txrx->txrx_state = WSTxRxStateIDLE; +} + +uint32_t ws_rx(WeatherStationApp* app, uint32_t frequency) { + furi_assert(app); + if(!furi_hal_subghz_is_frequency_valid(frequency)) { + furi_crash("WeatherStation: Incorrect RX frequency."); + } + furi_assert( + app->txrx->txrx_state != WSTxRxStateRx && app->txrx->txrx_state != WSTxRxStateSleep); + + furi_hal_subghz_idle(); + uint32_t value = furi_hal_subghz_set_frequency_and_path(frequency); + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); + furi_hal_subghz_flush_rx(); + furi_hal_subghz_rx(); + + furi_hal_subghz_start_async_rx(subghz_worker_rx_callback, app->txrx->worker); + subghz_worker_start(app->txrx->worker); + app->txrx->txrx_state = WSTxRxStateRx; + return value; +} + +void ws_idle(WeatherStationApp* app) { + furi_assert(app); + furi_assert(app->txrx->txrx_state != WSTxRxStateSleep); + furi_hal_subghz_idle(); + app->txrx->txrx_state = WSTxRxStateIDLE; +} + +void ws_rx_end(WeatherStationApp* app) { + furi_assert(app); + furi_assert(app->txrx->txrx_state == WSTxRxStateRx); + if(subghz_worker_is_running(app->txrx->worker)) { + subghz_worker_stop(app->txrx->worker); + furi_hal_subghz_stop_async_rx(); + } + furi_hal_subghz_idle(); + app->txrx->txrx_state = WSTxRxStateIDLE; +} + +void ws_sleep(WeatherStationApp* app) { + furi_assert(app); + furi_hal_subghz_sleep(); + app->txrx->txrx_state = WSTxRxStateSleep; +} + +void ws_hopper_update(WeatherStationApp* app) { + furi_assert(app); + + switch(app->txrx->hopper_state) { + case WSHopperStateOFF: + return; + break; + case WSHopperStatePause: + return; + break; + case WSHopperStateRSSITimeOut: + if(app->txrx->hopper_timeout != 0) { + app->txrx->hopper_timeout--; + return; + } + break; + default: + break; + } + float rssi = -127.0f; + if(app->txrx->hopper_state != WSHopperStateRSSITimeOut) { + // See RSSI Calculation timings in CC1101 17.3 RSSI + rssi = furi_hal_subghz_get_rssi(); + + // Stay if RSSI is high enough + if(rssi > -90.0f) { + app->txrx->hopper_timeout = 10; + app->txrx->hopper_state = WSHopperStateRSSITimeOut; + return; + } + } else { + app->txrx->hopper_state = WSHopperStateRunnig; + } + // Select next frequency + if(app->txrx->hopper_idx_frequency < + subghz_setting_get_hopper_frequency_count(app->setting) - 1) { + app->txrx->hopper_idx_frequency++; + } else { + app->txrx->hopper_idx_frequency = 0; + } + + if(app->txrx->txrx_state == WSTxRxStateRx) { + ws_rx_end(app); + }; + if(app->txrx->txrx_state == WSTxRxStateIDLE) { + subghz_receiver_reset(app->txrx->receiver); + app->txrx->preset->frequency = + subghz_setting_get_hopper_frequency(app->setting, app->txrx->hopper_idx_frequency); + ws_rx(app, app->txrx->preset->frequency); + } +} diff --git a/applications/plugins/weather_station/weather_station_app_i.h b/applications/plugins/weather_station/weather_station_app_i.h new file mode 100644 index 000000000..41e248112 --- /dev/null +++ b/applications/plugins/weather_station/weather_station_app_i.h @@ -0,0 +1,73 @@ +#pragma once + +#include "helpers/weather_station_types.h" + +#include "scenes/weather_station_scene.h" +#include +#include +#include +#include +#include +#include +#include +#include "views/weather_station_receiver.h" +#include "views/weather_station_receiver_info.h" +#include "weather_station_history.h" + +#include +#include +#include +#include +#include + +typedef struct WeatherStationApp WeatherStationApp; + +struct WeatherStationTxRx { + SubGhzWorker* worker; + + SubGhzEnvironment* environment; + SubGhzReceiver* receiver; + SubGhzRadioPreset* preset; + WSHistory* history; + uint16_t idx_menu_chosen; + WSTxRxState txrx_state; + WSHopperState hopper_state; + uint8_t hopper_timeout; + uint8_t hopper_idx_frequency; + WSRxKeyState rx_key_state; +}; + +typedef struct WeatherStationTxRx WeatherStationTxRx; + +struct WeatherStationApp { + Gui* gui; + ViewDispatcher* view_dispatcher; + WeatherStationTxRx* txrx; + SceneManager* scene_manager; + NotificationApp* notifications; + VariableItemList* variable_item_list; + Submenu* submenu; + Widget* widget; + WSReceiver* ws_receiver; + WSReceiverInfo* ws_receiver_info; + WSLock lock; + SubGhzSetting* setting; +}; + +void ws_preset_init( + void* context, + const char* preset_name, + uint32_t frequency, + uint8_t* preset_data, + size_t preset_data_size); +bool ws_set_preset(WeatherStationApp* app, const char* preset); +void ws_get_frequency_modulation( + WeatherStationApp* app, + FuriString* frequency, + FuriString* modulation); +void ws_begin(WeatherStationApp* app, uint8_t* preset_data); +uint32_t ws_rx(WeatherStationApp* app, uint32_t frequency); +void ws_idle(WeatherStationApp* app); +void ws_rx_end(WeatherStationApp* app); +void ws_sleep(WeatherStationApp* app); +void ws_hopper_update(WeatherStationApp* app); diff --git a/applications/plugins/weather_station/weather_station_history.c b/applications/plugins/weather_station/weather_station_history.c new file mode 100644 index 000000000..b37009c46 --- /dev/null +++ b/applications/plugins/weather_station/weather_station_history.c @@ -0,0 +1,245 @@ +#include "weather_station_history.h" +#include +#include +#include +#include "protocols/ws_generic.h" + +#include + +#define WS_HISTORY_MAX 50 +#define TAG "WSHistory" + +typedef struct { + FuriString* item_str; + FlipperFormat* flipper_string; + uint8_t type; + uint32_t id; + SubGhzRadioPreset* preset; +} WSHistoryItem; + +ARRAY_DEF(WSHistoryItemArray, WSHistoryItem, M_POD_OPLIST) + +#define M_OPL_WSHistoryItemArray_t() ARRAY_OPLIST(WSHistoryItemArray, M_POD_OPLIST) + +typedef struct { + WSHistoryItemArray_t data; +} WSHistoryStruct; + +struct WSHistory { + uint32_t last_update_timestamp; + uint16_t last_index_write; + uint8_t code_last_hash_data; + FuriString* tmp_string; + WSHistoryStruct* history; +}; + +WSHistory* ws_history_alloc(void) { + WSHistory* instance = malloc(sizeof(WSHistory)); + instance->tmp_string = furi_string_alloc(); + instance->history = malloc(sizeof(WSHistoryStruct)); + WSHistoryItemArray_init(instance->history->data); + return instance; +} + +void ws_history_free(WSHistory* instance) { + furi_assert(instance); + furi_string_free(instance->tmp_string); + for + M_EACH(item, instance->history->data, WSHistoryItemArray_t) { + furi_string_free(item->item_str); + furi_string_free(item->preset->name); + free(item->preset); + flipper_format_free(item->flipper_string); + item->type = 0; + } + WSHistoryItemArray_clear(instance->history->data); + free(instance->history); + free(instance); +} + +uint32_t ws_history_get_frequency(WSHistory* instance, uint16_t idx) { + furi_assert(instance); + WSHistoryItem* item = WSHistoryItemArray_get(instance->history->data, idx); + return item->preset->frequency; +} + +SubGhzRadioPreset* ws_history_get_radio_preset(WSHistory* instance, uint16_t idx) { + furi_assert(instance); + WSHistoryItem* item = WSHistoryItemArray_get(instance->history->data, idx); + return item->preset; +} + +const char* ws_history_get_preset(WSHistory* instance, uint16_t idx) { + furi_assert(instance); + WSHistoryItem* item = WSHistoryItemArray_get(instance->history->data, idx); + return furi_string_get_cstr(item->preset->name); +} + +void ws_history_reset(WSHistory* instance) { + furi_assert(instance); + furi_string_reset(instance->tmp_string); + for + M_EACH(item, instance->history->data, WSHistoryItemArray_t) { + furi_string_free(item->item_str); + furi_string_free(item->preset->name); + free(item->preset); + flipper_format_free(item->flipper_string); + item->type = 0; + } + WSHistoryItemArray_reset(instance->history->data); + instance->last_index_write = 0; + instance->code_last_hash_data = 0; +} + +uint16_t ws_history_get_item(WSHistory* instance) { + furi_assert(instance); + return instance->last_index_write; +} + +uint8_t ws_history_get_type_protocol(WSHistory* instance, uint16_t idx) { + furi_assert(instance); + WSHistoryItem* item = WSHistoryItemArray_get(instance->history->data, idx); + return item->type; +} + +const char* ws_history_get_protocol_name(WSHistory* instance, uint16_t idx) { + furi_assert(instance); + WSHistoryItem* item = WSHistoryItemArray_get(instance->history->data, idx); + flipper_format_rewind(item->flipper_string); + if(!flipper_format_read_string(item->flipper_string, "Protocol", instance->tmp_string)) { + FURI_LOG_E(TAG, "Missing Protocol"); + furi_string_reset(instance->tmp_string); + } + return furi_string_get_cstr(instance->tmp_string); +} + +FlipperFormat* ws_history_get_raw_data(WSHistory* instance, uint16_t idx) { + furi_assert(instance); + WSHistoryItem* item = WSHistoryItemArray_get(instance->history->data, idx); + if(item->flipper_string) { + return item->flipper_string; + } else { + return NULL; + } +} +bool ws_history_get_text_space_left(WSHistory* instance, FuriString* output) { + furi_assert(instance); + if(instance->last_index_write == WS_HISTORY_MAX) { + if(output != NULL) furi_string_printf(output, "Memory is FULL"); + return true; + } + if(output != NULL) + furi_string_printf(output, "%02u/%02u", instance->last_index_write, WS_HISTORY_MAX); + return false; +} + +void ws_history_get_text_item_menu(WSHistory* instance, FuriString* output, uint16_t idx) { + WSHistoryItem* item = WSHistoryItemArray_get(instance->history->data, idx); + furi_string_set(output, item->item_str); +} + +WSHistoryStateAddKey + ws_history_add_to_history(WSHistory* instance, void* context, SubGhzRadioPreset* preset) { + furi_assert(instance); + furi_assert(context); + + if(instance->last_index_write >= WS_HISTORY_MAX) return WSHistoryStateAddKeyOverflow; + + SubGhzProtocolDecoderBase* decoder_base = context; + if((instance->code_last_hash_data == + subghz_protocol_decoder_base_get_hash_data(decoder_base)) && + ((furi_get_tick() - instance->last_update_timestamp) < 500)) { + instance->last_update_timestamp = furi_get_tick(); + return WSHistoryStateAddKeyTimeOut; + } + + instance->code_last_hash_data = subghz_protocol_decoder_base_get_hash_data(decoder_base); + instance->last_update_timestamp = furi_get_tick(); + + FlipperFormat* fff = flipper_format_string_alloc(); + uint32_t id = 0; + subghz_protocol_decoder_base_serialize(decoder_base, fff, preset); + + do { + if(!flipper_format_rewind(fff)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_uint32(fff, "Id", (uint32_t*)&id, 1)) { + FURI_LOG_E(TAG, "Missing Id"); + break; + } + } while(false); + flipper_format_free(fff); + + //Update record if found + bool sensor_found = false; + for(size_t i = 0; i < WSHistoryItemArray_size(instance->history->data); i++) { + WSHistoryItem* item = WSHistoryItemArray_get(instance->history->data, i); + if(item->id == id) { + sensor_found = true; + Stream* flipper_string_stream = flipper_format_get_raw_stream(item->flipper_string); + stream_clean(flipper_string_stream); + subghz_protocol_decoder_base_serialize(decoder_base, item->flipper_string, preset); + return WSHistoryStateAddKeyUpdateData; + } + } + + // or add new record + if(!sensor_found) { + WSHistoryItem* item = WSHistoryItemArray_push_raw(instance->history->data); + item->preset = malloc(sizeof(SubGhzRadioPreset)); + item->type = decoder_base->protocol->type; + item->preset->frequency = preset->frequency; + item->preset->name = furi_string_alloc(); + furi_string_set(item->preset->name, preset->name); + item->preset->data = preset->data; + item->preset->data_size = preset->data_size; + item->id = id; + + item->item_str = furi_string_alloc(); + item->flipper_string = flipper_format_string_alloc(); + subghz_protocol_decoder_base_serialize(decoder_base, item->flipper_string, preset); + + do { + if(!flipper_format_rewind(item->flipper_string)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_string( + item->flipper_string, "Protocol", instance->tmp_string)) { + FURI_LOG_E(TAG, "Missing Protocol"); + break; + } + + if(!flipper_format_rewind(item->flipper_string)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + uint8_t key_data[sizeof(uint64_t)] = {0}; + if(!flipper_format_read_hex(item->flipper_string, "Data", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Missing Data"); + break; + } + uint64_t data = 0; + for(uint8_t i = 0; i < sizeof(uint64_t); i++) { + data = (data << 8) | key_data[i]; + } + uint32_t temp_data = 0; + if(!flipper_format_read_uint32(item->flipper_string, "Ch", (uint32_t*)&temp_data, 1)) { + FURI_LOG_E(TAG, "Missing Channel"); + break; + } + if(temp_data != WS_NO_CHANNEL) { + furi_string_cat_printf(instance->tmp_string, " Ch:%X", (uint8_t)temp_data); + } + + furi_string_printf( + item->item_str, "%s %llX", furi_string_get_cstr(instance->tmp_string), data); + + } while(false); + instance->last_index_write++; + return WSHistoryStateAddKeyNewDada; + } + return WSHistoryStateAddKeyUnknown; +} diff --git a/applications/plugins/weather_station/weather_station_history.h b/applications/plugins/weather_station/weather_station_history.h new file mode 100644 index 000000000..11601fe79 --- /dev/null +++ b/applications/plugins/weather_station/weather_station_history.h @@ -0,0 +1,112 @@ + +#pragma once + +#include +#include +#include +#include +#include + +typedef struct WSHistory WSHistory; + +/** History state add key */ +typedef enum { + WSHistoryStateAddKeyUnknown, + WSHistoryStateAddKeyTimeOut, + WSHistoryStateAddKeyNewDada, + WSHistoryStateAddKeyUpdateData, + WSHistoryStateAddKeyOverflow, +} WSHistoryStateAddKey; + +/** Allocate WSHistory + * + * @return WSHistory* + */ +WSHistory* ws_history_alloc(void); + +/** Free WSHistory + * + * @param instance - WSHistory instance + */ +void ws_history_free(WSHistory* instance); + +/** Clear history + * + * @param instance - WSHistory instance + */ +void ws_history_reset(WSHistory* instance); + +/** Get frequency to history[idx] + * + * @param instance - WSHistory instance + * @param idx - record index + * @return frequency - frequency Hz + */ +uint32_t ws_history_get_frequency(WSHistory* instance, uint16_t idx); + +SubGhzRadioPreset* ws_history_get_radio_preset(WSHistory* instance, uint16_t idx); + +/** Get preset to history[idx] + * + * @param instance - WSHistory instance + * @param idx - record index + * @return preset - preset name + */ +const char* ws_history_get_preset(WSHistory* instance, uint16_t idx); + +/** Get history index write + * + * @param instance - WSHistory instance + * @return idx - current record index + */ +uint16_t ws_history_get_item(WSHistory* instance); + +/** Get type protocol to history[idx] + * + * @param instance - WSHistory instance + * @param idx - record index + * @return type - type protocol + */ +uint8_t ws_history_get_type_protocol(WSHistory* instance, uint16_t idx); + +/** Get name protocol to history[idx] + * + * @param instance - WSHistory instance + * @param idx - record index + * @return name - const char* name protocol + */ +const char* ws_history_get_protocol_name(WSHistory* instance, uint16_t idx); + +/** Get string item menu to history[idx] + * + * @param instance - WSHistory instance + * @param output - FuriString* output + * @param idx - record index + */ +void ws_history_get_text_item_menu(WSHistory* instance, FuriString* output, uint16_t idx); + +/** Get string the remaining number of records to history + * + * @param instance - WSHistory instance + * @param output - FuriString* output + * @return bool - is FUUL + */ +bool ws_history_get_text_space_left(WSHistory* instance, FuriString* output); + +/** Add protocol to history + * + * @param instance - WSHistory instance + * @param context - SubGhzProtocolCommon context + * @param preset - SubGhzRadioPreset preset + * @return WSHistoryStateAddKey; + */ +WSHistoryStateAddKey + ws_history_add_to_history(WSHistory* instance, void* context, SubGhzRadioPreset* preset); + +/** Get SubGhzProtocolCommonLoad to load into the protocol decoder bin data + * + * @param instance - WSHistory instance + * @param idx - record index + * @return SubGhzProtocolCommonLoad* + */ +FlipperFormat* ws_history_get_raw_data(WSHistory* instance, uint16_t idx); diff --git a/applications/plugins/wifi_deauther_v1/FlipperZeroWiFiDeauthModuleDefines.h b/applications/plugins/wifi_deauther_v1/FlipperZeroWiFiDeauthModuleDefines.h new file mode 100644 index 000000000..a8e0e8d61 --- /dev/null +++ b/applications/plugins/wifi_deauther_v1/FlipperZeroWiFiDeauthModuleDefines.h @@ -0,0 +1,7 @@ +#define WIFI_MODULE_INIT_VERSION "WFDM_0.1" + +#define MODULE_CONTEXT_INITIALIZATION WIFI_MODULE_INIT_VERSION + +#define FLIPPERZERO_SERIAL_BAUD 230400 + +#define NA 0 diff --git a/applications/plugins/wifi_deauther_v1/application.fam b/applications/plugins/wifi_deauther_v1/application.fam new file mode 100644 index 000000000..6ce0f4435 --- /dev/null +++ b/applications/plugins/wifi_deauther_v1/application.fam @@ -0,0 +1,12 @@ +App( + appid="ESP8266_Deauther", + name="[ESP8266] Deauther", + apptype=FlipperAppType.EXTERNAL, + entry_point="esp8266_deauth_app", + cdefines=["APP_ESP8266_deauth"], + requires=["gui"], + stack_size=2 * 1024, + order=20, + fap_icon="wifi_10px.png", + fap_category="GPIO", +) diff --git a/applications/plugins/wifi_deauther_v1/esp8266_deauth.c b/applications/plugins/wifi_deauther_v1/esp8266_deauth.c new file mode 100644 index 000000000..3cc61a588 --- /dev/null +++ b/applications/plugins/wifi_deauther_v1/esp8266_deauth.c @@ -0,0 +1,538 @@ +#include +#include +#include +#include +#include +#include +#include +#include +//#include +//#include +//#include +//#include + +#include + +#include "FlipperZeroWiFiDeauthModuleDefines.h" + +#define DEAUTH_APP_DEBUG 0 + +#if DEAUTH_APP_DEBUG +#define APP_NAME_TAG "WiFi_Deauther" +#define DEAUTH_APP_LOG_I(format, ...) FURI_LOG_I(APP_NAME_TAG, format, ##__VA_ARGS__) +#define DEAUTH_APP_LOG_D(format, ...) FURI_LOG_D(APP_NAME_TAG, format, ##__VA_ARGS__) +#define DEAUTH_APP_LOG_E(format, ...) FURI_LOG_E(APP_NAME_TAG, format, ##__VA_ARGS__) +#else +#define DEAUTH_APP_LOG_I(format, ...) +#define DEAUTH_APP_LOG_D(format, ...) +#define DEAUTH_APP_LOG_E(format, ...) +#endif // WIFI_APP_DEBUG + +#define DISABLE_CONSOLE !DEAUTH_APP_DEBUG +#define ENABLE_MODULE_POWER 1 +#define ENABLE_MODULE_DETECTION 1 + +typedef enum EEventType // app internally defined event types +{ EventTypeKey // flipper input.h type +} EEventType; + +typedef struct SPluginEvent { + EEventType m_type; + InputEvent m_input; +} SPluginEvent; + +typedef enum EAppContext { + Undefined, + WaitingForModule, + Initializing, + ModuleActive, +} EAppContext; + +typedef enum EWorkerEventFlags { + WorkerEventReserved = (1 << 0), // Reserved for StreamBuffer internal event + WorkerEventStop = (1 << 1), + WorkerEventRx = (1 << 2), +} EWorkerEventFlags; + +typedef struct SGpioButtons { + GpioPin const* pinButtonUp; + GpioPin const* pinButtonDown; + GpioPin const* pinButtonOK; + GpioPin const* pinButtonBack; +} SGpioButtons; + +typedef struct SWiFiDeauthApp { + Gui* m_gui; + FuriThread* m_worker_thread; + //NotificationApp* m_notification; + FuriStreamBuffer* m_rx_stream; + SGpioButtons m_GpioButtons; + + bool m_wifiDeauthModuleInitialized; + bool m_wifiDeauthModuleAttached; + + EAppContext m_context; + + uint8_t m_backBuffer[128 * 8 * 8]; + //uint8_t m_renderBuffer[128 * 8 * 8]; + + uint8_t* m_backBufferPtr; + //uint8_t* m_m_renderBufferPtr; + + //uint8_t* m_originalBuffer; + //uint8_t** m_originalBufferLocation; + size_t m_canvasSize; + + bool m_needUpdateGUI; +} SWiFiDeauthApp; + +/////// INIT STATE /////// +static void esp8266_deauth_app_init(SWiFiDeauthApp* const app) { + app->m_context = Undefined; + + app->m_canvasSize = 128 * 8 * 8; + memset(app->m_backBuffer, DEAUTH_APP_DEBUG ? 0xFF : 0x00, app->m_canvasSize); + //memset(app->m_renderBuffer, DEAUTH_APP_DEBUG ? 0xFF : 0x00, app->m_canvasSize); + + //app->m_originalBuffer = NULL; + //app->m_originalBufferLocation = NULL; + + //app->m_m_renderBufferPtr = app->m_renderBuffer; + app->m_backBufferPtr = app->m_backBuffer; + + app->m_GpioButtons.pinButtonUp = &gpio_ext_pc3; + app->m_GpioButtons.pinButtonDown = &gpio_ext_pb2; + app->m_GpioButtons.pinButtonOK = &gpio_ext_pb3; + app->m_GpioButtons.pinButtonBack = &gpio_ext_pa4; + + app->m_needUpdateGUI = false; + +#if ENABLE_MODULE_POWER + app->m_wifiDeauthModuleInitialized = false; +#else + app->m_wifiDeauthModuleInitialized = true; +#endif // ENABLE_MODULE_POWER + +#if ENABLE_MODULE_DETECTION + app->m_wifiDeauthModuleAttached = false; +#else + app->m_wifiDeauthModuleAttached = true; +#endif +} + +static void esp8266_deauth_module_render_callback(Canvas* const canvas, void* ctx) { + SWiFiDeauthApp* app = acquire_mutex((ValueMutex*)ctx, 25); + if(app == NULL) { + return; + } + + //if(app->m_needUpdateGUI) + //{ + // app->m_needUpdateGUI = false; + + // //app->m_canvasSize = canvas_get_buffer_size(canvas); + // //app->m_originalBuffer = canvas_get_buffer(canvas); + // //app->m_originalBufferLocation = &u8g2_GetBufferPtr(&canvas->fb); + // //u8g2_GetBufferPtr(&canvas->fb) = app->m_m_renderBufferPtr; + //} + + //uint8_t* exchangeBuffers = app->m_m_renderBufferPtr; + //app->m_m_renderBufferPtr = app->m_backBufferPtr; + //app->m_backBufferPtr = exchangeBuffers; + + //if(app->m_needUpdateGUI) + //{ + // //memcpy(app->m_renderBuffer, app->m_backBuffer, app->m_canvasSize); + // app->m_needUpdateGUI = false; + //} + + switch(app->m_context) { + case Undefined: { + canvas_clear(canvas); + canvas_set_font(canvas, FontPrimary); + + const char* strInitializing = "Something wrong"; + canvas_draw_str( + canvas, + (u8g2_GetDisplayWidth(&canvas->fb) / 2) - + (canvas_string_width(canvas, strInitializing) / 2), + (u8g2_GetDisplayHeight(&canvas->fb) / + 2) /* - (u8g2_GetMaxCharHeight(&canvas->fb) / 2)*/, + strInitializing); + } break; + case WaitingForModule: +#if ENABLE_MODULE_DETECTION + furi_assert(!app->m_wifiDeauthModuleAttached); + if(!app->m_wifiDeauthModuleAttached) { + canvas_clear(canvas); + canvas_set_font(canvas, FontSecondary); + + const char* strInitializing = "Attach WiFi Deauther module"; + canvas_draw_str( + canvas, + (u8g2_GetDisplayWidth(&canvas->fb) / 2) - + (canvas_string_width(canvas, strInitializing) / 2), + (u8g2_GetDisplayHeight(&canvas->fb) / + 2) /* - (u8g2_GetMaxCharHeight(&canvas->fb) / 2)*/, + strInitializing); + } +#endif + break; + case Initializing: +#if ENABLE_MODULE_POWER + { + furi_assert(!app->m_wifiDeauthModuleInitialized); + if(!app->m_wifiDeauthModuleInitialized) { + canvas_set_font(canvas, FontPrimary); + + const char* strInitializing = "Initializing..."; + canvas_draw_str( + canvas, + (u8g2_GetDisplayWidth(&canvas->fb) / 2) - + (canvas_string_width(canvas, strInitializing) / 2), + (u8g2_GetDisplayHeight(&canvas->fb) / 2) - + (u8g2_GetMaxCharHeight(&canvas->fb) / 2), + strInitializing); + } + } +#endif // ENABLE_MODULE_POWER + break; + case ModuleActive: { + uint8_t* buffer = canvas_get_buffer(canvas); + app->m_canvasSize = canvas_get_buffer_size(canvas); + memcpy(buffer, app->m_backBuffer, app->m_canvasSize); + } break; + default: + break; + } + + release_mutex((ValueMutex*)ctx, app); +} + +static void + esp8266_deauth_module_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + SPluginEvent event = {.m_type = EventTypeKey, .m_input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) { + furi_assert(context); + + SWiFiDeauthApp* app = context; + + DEAUTH_APP_LOG_I("uart_echo_on_irq_cb"); + + if(ev == UartIrqEventRXNE) { + DEAUTH_APP_LOG_I("ev == UartIrqEventRXNE"); + furi_stream_buffer_send(app->m_rx_stream, &data, 1, 0); + furi_thread_flags_set(furi_thread_get_id(app->m_worker_thread), WorkerEventRx); + } +} + +static int32_t uart_worker(void* context) { + furi_assert(context); + DEAUTH_APP_LOG_I("[UART] Worker thread init"); + + SWiFiDeauthApp* app = acquire_mutex((ValueMutex*)context, 25); + if(app == NULL) { + return 1; + } + + FuriStreamBuffer* rx_stream = app->m_rx_stream; + + release_mutex((ValueMutex*)context, app); + +#if ENABLE_MODULE_POWER + bool initialized = false; + + FuriString* receivedString; + receivedString = furi_string_alloc(); +#endif // ENABLE_MODULE_POWER + + while(true) { + uint32_t events = furi_thread_flags_wait( + WorkerEventStop | WorkerEventRx, FuriFlagWaitAny, FuriWaitForever); + furi_check((events & FuriFlagError) == 0); + + if(events & WorkerEventStop) break; + if(events & WorkerEventRx) { + DEAUTH_APP_LOG_I("[UART] Received data"); + SWiFiDeauthApp* app = acquire_mutex((ValueMutex*)context, 25); + if(app == NULL) { + return 1; + } + + size_t dataReceivedLength = 0; + int index = 0; + do { + const uint8_t dataBufferSize = 64; + uint8_t dataBuffer[dataBufferSize]; + dataReceivedLength = + furi_stream_buffer_receive(rx_stream, dataBuffer, dataBufferSize, 25); + if(dataReceivedLength > 0) { +#if ENABLE_MODULE_POWER + if(!initialized) { + if(!(dataReceivedLength > strlen(MODULE_CONTEXT_INITIALIZATION))) { + DEAUTH_APP_LOG_I("[UART] Found possible init candidate"); + for(uint16_t i = 0; i < dataReceivedLength; i++) { + furi_string_push_back(receivedString, dataBuffer[i]); + } + } + } else +#endif // ENABLE_MODULE_POWER + { + DEAUTH_APP_LOG_I("[UART] Data copied to backbuffer"); + memcpy(app->m_backBuffer + index, dataBuffer, dataReceivedLength); + index += dataReceivedLength; + app->m_needUpdateGUI = true; + } + } + + } while(dataReceivedLength > 0); + +#if ENABLE_MODULE_POWER + if(!app->m_wifiDeauthModuleInitialized) { + if(furi_string_cmp_str(receivedString, MODULE_CONTEXT_INITIALIZATION) == 0) { + DEAUTH_APP_LOG_I("[UART] Initialized"); + initialized = true; + app->m_wifiDeauthModuleInitialized = true; + app->m_context = ModuleActive; + furi_string_free(receivedString); + } else { + DEAUTH_APP_LOG_I("[UART] Not an initialization command"); + furi_string_reset(receivedString); + } + } +#endif // ENABLE_MODULE_POWER + + release_mutex((ValueMutex*)context, app); + } + } + + return 0; +} + +int32_t esp8266_deauth_app(void* p) { + UNUSED(p); + + DEAUTH_APP_LOG_I("Init"); + + // FuriTimer* timer = furi_timer_alloc(blink_test_update, FuriTimerTypePeriodic, event_queue); + // furi_timer_start(timer, furi_kernel_get_tick_frequency()); + + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(SPluginEvent)); + + SWiFiDeauthApp* app = malloc(sizeof(SWiFiDeauthApp)); + + esp8266_deauth_app_init(app); + + furi_hal_gpio_init_simple(app->m_GpioButtons.pinButtonUp, GpioModeOutputPushPull); + furi_hal_gpio_init_simple(app->m_GpioButtons.pinButtonDown, GpioModeOutputPushPull); + furi_hal_gpio_init_simple(app->m_GpioButtons.pinButtonOK, GpioModeOutputPushPull); + furi_hal_gpio_init_simple(app->m_GpioButtons.pinButtonBack, GpioModeOutputPushPull); + + furi_hal_gpio_write(app->m_GpioButtons.pinButtonUp, true); + furi_hal_gpio_write(app->m_GpioButtons.pinButtonDown, true); + furi_hal_gpio_write(app->m_GpioButtons.pinButtonOK, true); + furi_hal_gpio_write( + app->m_GpioButtons.pinButtonBack, false); // GPIO15 - Boot fails if pulled HIGH + +#if ENABLE_MODULE_DETECTION + furi_hal_gpio_init( + &gpio_ext_pc0, + GpioModeInput, + GpioPullUp, + GpioSpeedLow); // Connect to the Flipper's ground just to be sure + //furi_hal_gpio_add_int_callback(pinD0, input_isr_d0, this); + app->m_context = WaitingForModule; +#else +#if ENABLE_MODULE_POWER + app->m_context = Initializing; + furi_hal_power_enable_otg(); +#else + app->m_context = ModuleActive; +#endif +#endif // ENABLE_MODULE_DETECTION + + ValueMutex app_data_mutex; + if(!init_mutex(&app_data_mutex, app, sizeof(SWiFiDeauthApp))) { + DEAUTH_APP_LOG_E("cannot create mutex\r\n"); + free(app); + return 255; + } + + DEAUTH_APP_LOG_I("Mutex created"); + + //app->m_notification = furi_record_open("notification"); + + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, esp8266_deauth_module_render_callback, &app_data_mutex); + view_port_input_callback_set(view_port, esp8266_deauth_module_input_callback, event_queue); + + // Open GUI and register view_port + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + //notification_message(app->notification, &sequence_set_only_blue_255); + + app->m_rx_stream = furi_stream_buffer_alloc(1 * 1024, 1); + + app->m_worker_thread = furi_thread_alloc(); + furi_thread_set_name(app->m_worker_thread, "WiFiDeauthModuleUARTWorker"); + furi_thread_set_stack_size(app->m_worker_thread, 1 * 1024); + furi_thread_set_context(app->m_worker_thread, &app_data_mutex); + furi_thread_set_callback(app->m_worker_thread, uart_worker); + furi_thread_start(app->m_worker_thread); + DEAUTH_APP_LOG_I("UART thread allocated"); + + // Enable uart listener +#if DISABLE_CONSOLE + furi_hal_console_disable(); +#endif + furi_hal_uart_set_br(FuriHalUartIdUSART1, FLIPPERZERO_SERIAL_BAUD); + furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, uart_on_irq_cb, app); + DEAUTH_APP_LOG_I("UART Listener created"); + + SPluginEvent event; + for(bool processing = true; processing;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + SWiFiDeauthApp* app = (SWiFiDeauthApp*)acquire_mutex_block(&app_data_mutex); + +#if ENABLE_MODULE_DETECTION + if(!app->m_wifiDeauthModuleAttached) { + if(furi_hal_gpio_read(&gpio_ext_pc0) == false) { + DEAUTH_APP_LOG_I("Module Attached"); + app->m_wifiDeauthModuleAttached = true; +#if ENABLE_MODULE_POWER + app->m_context = Initializing; + furi_hal_power_enable_otg(); +#else + app->m_context = ModuleActive; +#endif + } + } +#endif // ENABLE_MODULE_DETECTION + + if(event_status == FuriStatusOk) { + if(event.m_type == EventTypeKey) { + if(app->m_wifiDeauthModuleInitialized) { + if(app->m_context == ModuleActive) { + switch(event.m_input.key) { + case InputKeyUp: + if(event.m_input.type == InputTypePress) { + DEAUTH_APP_LOG_I("Up Press"); + furi_hal_gpio_write(app->m_GpioButtons.pinButtonUp, false); + } else if(event.m_input.type == InputTypeRelease) { + DEAUTH_APP_LOG_I("Up Release"); + furi_hal_gpio_write(app->m_GpioButtons.pinButtonUp, true); + } + break; + case InputKeyDown: + if(event.m_input.type == InputTypePress) { + DEAUTH_APP_LOG_I("Down Press"); + furi_hal_gpio_write(app->m_GpioButtons.pinButtonDown, false); + } else if(event.m_input.type == InputTypeRelease) { + DEAUTH_APP_LOG_I("Down Release"); + furi_hal_gpio_write(app->m_GpioButtons.pinButtonDown, true); + } + break; + case InputKeyOk: + if(event.m_input.type == InputTypePress) { + DEAUTH_APP_LOG_I("OK Press"); + furi_hal_gpio_write(app->m_GpioButtons.pinButtonOK, false); + } else if(event.m_input.type == InputTypeRelease) { + DEAUTH_APP_LOG_I("OK Release"); + furi_hal_gpio_write(app->m_GpioButtons.pinButtonOK, true); + } + break; + case InputKeyBack: + if(event.m_input.type == InputTypePress) { + DEAUTH_APP_LOG_I("Back Press"); + furi_hal_gpio_write(app->m_GpioButtons.pinButtonBack, false); + } else if(event.m_input.type == InputTypeRelease) { + DEAUTH_APP_LOG_I("Back Release"); + furi_hal_gpio_write(app->m_GpioButtons.pinButtonBack, true); + } else if(event.m_input.type == InputTypeLong) { + DEAUTH_APP_LOG_I("Back Long"); + processing = false; + } + break; + default: + break; + } + } + } else { + if(event.m_input.key == InputKeyBack) { + if(event.m_input.type == InputTypeShort || + event.m_input.type == InputTypeLong) { + processing = false; + } + } + } + } + } + +#if ENABLE_MODULE_DETECTION + if(app->m_wifiDeauthModuleAttached && furi_hal_gpio_read(&gpio_ext_pc0) == true) { + DEAUTH_APP_LOG_D("Module Disconnected - Exit"); + processing = false; + app->m_wifiDeauthModuleAttached = false; + app->m_wifiDeauthModuleInitialized = false; + } +#endif + + view_port_update(view_port); + release_mutex(&app_data_mutex, app); + } + + DEAUTH_APP_LOG_I("Start exit app"); + + furi_thread_flags_set(furi_thread_get_id(app->m_worker_thread), WorkerEventStop); + furi_thread_join(app->m_worker_thread); + furi_thread_free(app->m_worker_thread); + + DEAUTH_APP_LOG_I("Thread Deleted"); + + // Reset GPIO pins to default state + furi_hal_gpio_init(&gpio_ext_pc0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(&gpio_ext_pc3, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(&gpio_ext_pb2, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(&gpio_ext_pb3, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(&gpio_ext_pa4, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + +#if DISABLE_CONSOLE + furi_hal_console_enable(); +#endif + + //*app->m_originalBufferLocation = app->m_originalBuffer; + + view_port_enabled_set(view_port, false); + + gui_remove_view_port(gui, view_port); + + // Close gui record + furi_record_close(RECORD_GUI); + furi_record_close("notification"); + app->m_gui = NULL; + + view_port_free(view_port); + + furi_message_queue_free(event_queue); + + furi_stream_buffer_free(app->m_rx_stream); + + delete_mutex(&app_data_mutex); + + // Free rest + free(app); + + DEAUTH_APP_LOG_I("App freed"); + +#if ENABLE_MODULE_POWER + furi_hal_power_disable_otg(); +#endif + + return 0; +} diff --git a/applications/plugins/wifi_deauther_v1/wifi_10px.png b/applications/plugins/wifi_deauther_v1/wifi_10px.png new file mode 100644 index 000000000..c13534660 Binary files /dev/null and b/applications/plugins/wifi_deauther_v1/wifi_10px.png differ diff --git a/applications/plugins/wifi_deauther_v2/LICENSE b/applications/plugins/wifi_deauther_v2/LICENSE new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/applications/plugins/wifi_deauther_v2/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/applications/plugins/wifi_deauther_v2/README.md b/applications/plugins/wifi_deauther_v2/README.md new file mode 100644 index 000000000..40fff1324 --- /dev/null +++ b/applications/plugins/wifi_deauther_v2/README.md @@ -0,0 +1,57 @@ +# flipperzero_esp8266_deautherv2 +Flipper Zero esp8266 deauther app. + + +Based off the WiFi Marauder App from 0xchocolate. + +Thanks to Roguemaster for fixing some issues I had with the code and didnt get a chance to get to. I have now uploaded these changes into the source. + +I have also successfully built this with unleashed souce as well and included the FAP file here. unleashed version unlshd-020. + +https://github.com/0xchocolate/flipperzero-firmware-with-wifi-marauder-companion + +https://github.com/RogueMaster/flipperzero-firmware-wPlugins/tree/unleashed/applications/wifi_marauder_companion + +uses the Version 2 of the ESP8266 Deauther code. +https://github.com/SpacehuhnTech/esp8266_deauther/tree/v2/esp8266_deauther + +This is done so you can use the original deauther v2 firmware on the esp8266. +you can just flash the latest binary. + +also a shout out to https://github.com/SequoiaSan/FlipperZero-Wifi-ESP8266-Deauther-Module +This is already in the Roguemaster firmware and just needs to be enabled and compiled. unfortunatly I could not get this past the menu when I compiled his deauther source for the nodemcu. Nice menu though. + +I used a nodeMCU board. Wiring is simple. follow the wiring guide on https://github.com/SequoiaSan/FlipperZero-WiFi-Scanner_Module +On mine I connected one G to ground, VIN to 5V, RX to U_TX, TX to U_RX. + +NodeMCU---FlipperZero + +G---------GND + +VIN-------5V + +RX--------U_TX + +TX--------U_RX + + + +Video in action (old version). +https://youtu.be/_RFzZyPkeR0 + +New video and install instructions. +https://youtu.be/CKK7t0TaRVQ + +If you want to disable the built in WiFi access and web interface (only use flipper to send serial commands) then select "set webinterface false", "save settings" and "reboot". When it starts back up you wont see the pwned AP any more. + +I installed this into Roguemaster to test. + +git clone --recursive https://github.com/RogueMaster/flipperzero-firmware-wPlugins.git +cd flipperzero-firmware-wPlugins/ + +copy folder into applications. +add "APPS_wifi_deauther", to the meta/application.fam file. + +compile +./fbt resources icons +./fbt updater_package diff --git a/applications/plugins/wifi_deauther_v2/application.fam b/applications/plugins/wifi_deauther_v2/application.fam new file mode 100644 index 000000000..8fd4602c4 --- /dev/null +++ b/applications/plugins/wifi_deauther_v2/application.fam @@ -0,0 +1,12 @@ +App( + appid="ESP8266_Wifi_Deauther", + name="[ESP8266] WiFi (Deauther) v2", + apptype=FlipperAppType.EXTERNAL, + entry_point="wifi_deauther_app", + cdefines=["APP_WIFI_deauther"], + requires=["gui"], + stack_size=1 * 1024, + order=30, + fap_icon="wifi_10px.png", + fap_category="GPIO", +) diff --git a/applications/plugins/wifi_deauther_v2/scenes/wifi_deauther_scene.c b/applications/plugins/wifi_deauther_v2/scenes/wifi_deauther_scene.c new file mode 100644 index 000000000..a974beea9 --- /dev/null +++ b/applications/plugins/wifi_deauther_v2/scenes/wifi_deauther_scene.c @@ -0,0 +1,30 @@ +#include "wifi_deauther_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const wifi_deauther_scene_on_enter_handlers[])(void*) = { +#include "wifi_deauther_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 wifi_deauther_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "wifi_deauther_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 wifi_deauther_scene_on_exit_handlers[])(void* context) = { +#include "wifi_deauther_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers wifi_deauther_scene_handlers = { + .on_enter_handlers = wifi_deauther_scene_on_enter_handlers, + .on_event_handlers = wifi_deauther_scene_on_event_handlers, + .on_exit_handlers = wifi_deauther_scene_on_exit_handlers, + .scene_num = WifideautherSceneNum, +}; diff --git a/applications/plugins/wifi_deauther_v2/scenes/wifi_deauther_scene.h b/applications/plugins/wifi_deauther_v2/scenes/wifi_deauther_scene.h new file mode 100644 index 000000000..a6ef08553 --- /dev/null +++ b/applications/plugins/wifi_deauther_v2/scenes/wifi_deauther_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) WifideautherScene##id, +typedef enum { +#include "wifi_deauther_scene_config.h" + WifideautherSceneNum, +} WifideautherScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers wifi_deauther_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "wifi_deauther_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 "wifi_deauther_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 "wifi_deauther_scene_config.h" +#undef ADD_SCENE diff --git a/applications/plugins/wifi_deauther_v2/scenes/wifi_deauther_scene_config.h b/applications/plugins/wifi_deauther_v2/scenes/wifi_deauther_scene_config.h new file mode 100644 index 000000000..5f21cdc50 --- /dev/null +++ b/applications/plugins/wifi_deauther_v2/scenes/wifi_deauther_scene_config.h @@ -0,0 +1,3 @@ +ADD_SCENE(wifi_deauther, start, Start) +ADD_SCENE(wifi_deauther, console_output, ConsoleOutput) +ADD_SCENE(wifi_deauther, text_input, TextInput) diff --git a/applications/plugins/wifi_deauther_v2/scenes/wifi_deauther_scene_console_output.c b/applications/plugins/wifi_deauther_v2/scenes/wifi_deauther_scene_console_output.c new file mode 100644 index 000000000..a9b738b06 --- /dev/null +++ b/applications/plugins/wifi_deauther_v2/scenes/wifi_deauther_scene_console_output.c @@ -0,0 +1,83 @@ +#include "../wifi_deauther_app_i.h" + +void wifi_deauther_console_output_handle_rx_data_cb(uint8_t* buf, size_t len, void* context) { + furi_assert(context); + WifideautherApp* app = context; + + // If text box store gets too big, then truncate it + app->text_box_store_strlen += len; + if(app->text_box_store_strlen >= WIFI_deauther_TEXT_BOX_STORE_SIZE - 1) { + furi_string_right(app->text_box_store, app->text_box_store_strlen / 2); + app->text_box_store_strlen = furi_string_size(app->text_box_store); + } + + // Null-terminate buf and append to text box store + buf[len] = '\0'; + furi_string_cat_printf(app->text_box_store, "%s", buf); + + view_dispatcher_send_custom_event(app->view_dispatcher, WifideautherEventRefreshConsoleOutput); +} + +void wifi_deauther_scene_console_output_on_enter(void* context) { + WifideautherApp* app = context; + + // Register callback to receive data + wifi_deauther_uart_set_handle_rx_data_cb( + app->uart, wifi_deauther_console_output_handle_rx_data_cb); // setup callback for rx thread + + // Give a small delay to allow UART to settle. + furi_delay_ms(600); + + TextBox* text_box = app->text_box; + text_box_reset(app->text_box); + text_box_set_font(text_box, TextBoxFontText); + if(app->focus_console_start) { + text_box_set_focus(text_box, TextBoxFocusStart); + } else { + text_box_set_focus(text_box, TextBoxFocusEnd); + } + if(app->is_command) { + furi_string_reset(app->text_box_store); + app->text_box_store_strlen = 0; + + } else { // "View Log" menu action + text_box_set_text(app->text_box, furi_string_get_cstr(app->text_box_store)); + } + + scene_manager_set_scene_state(app->scene_manager, WifideautherSceneConsoleOutput, 0); + view_dispatcher_switch_to_view(app->view_dispatcher, WifideautherAppViewConsoleOutput); + + // Send command with newline '\n' + if(app->is_command && app->selected_tx_string) { + wifi_deauther_uart_tx( + (uint8_t*)(app->selected_tx_string), strlen(app->selected_tx_string)); + wifi_deauther_uart_tx((uint8_t*)("\n"), 1); + } +} + +bool wifi_deauther_scene_console_output_on_event(void* context, SceneManagerEvent event) { + WifideautherApp* app = context; + + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + text_box_set_text(app->text_box, furi_string_get_cstr(app->text_box_store)); + consumed = true; + } else if(event.type == SceneManagerEventTypeTick) { + consumed = true; + } + + return consumed; +} + +void wifi_deauther_scene_console_output_on_exit(void* context) { + WifideautherApp* app = context; + + // Unregister rx callback + wifi_deauther_uart_set_handle_rx_data_cb(app->uart, NULL); + + // Automatically stop the scan when exiting view + if(app->is_command) { + wifi_deauther_uart_tx((uint8_t*)("stopscan\n"), strlen("stopscan\n")); + } +} \ No newline at end of file diff --git a/applications/plugins/wifi_deauther_v2/scenes/wifi_deauther_scene_start.c b/applications/plugins/wifi_deauther_v2/scenes/wifi_deauther_scene_start.c new file mode 100644 index 000000000..2519c9a07 --- /dev/null +++ b/applications/plugins/wifi_deauther_v2/scenes/wifi_deauther_scene_start.c @@ -0,0 +1,172 @@ +#include "../wifi_deauther_app_i.h" + +// For each command, define whether additional arguments are needed +// (enabling text input to fill them out), and whether the console +// text box should focus at the start of the output or the end +typedef enum { NO_ARGS = 0, INPUT_ARGS, TOGGLE_ARGS } InputArgs; + +typedef enum { FOCUS_CONSOLE_END = 0, FOCUS_CONSOLE_START, FOCUS_CONSOLE_TOGGLE } FocusConsole; + +#define SHOW_STOPSCAN_TIP (true) +#define NO_TIP (false) + +#define MAX_OPTIONS (6) +typedef struct { + const char* item_string; + const char* options_menu[MAX_OPTIONS]; + int num_options_menu; + const char* actual_commands[MAX_OPTIONS]; + InputArgs needs_keyboard; + FocusConsole focus_console; + bool show_stopscan_tip; +} WifideautherItem; + +// NUM_MENU_ITEMS defined in wifi_deauther_app_i.h - if you add an entry here, increment it! +const WifideautherItem MenuItems[NUM_MENU_ITEMS] = { + {"View Log from", {"start", "end"}, 2, {}, NO_ARGS, FOCUS_CONSOLE_TOGGLE, NO_TIP}, + {"Help", {""}, 1, {"help"}, NO_ARGS, FOCUS_CONSOLE_END, NO_TIP}, + {"Stop", {""}, 1, {"stop all"}, NO_ARGS, FOCUS_CONSOLE_END, NO_TIP}, + {"Scan", + {"All", "SSIDs", "Stations"}, + 3, + {"scan", "scan aps", "scan stations"}, + NO_ARGS, + FOCUS_CONSOLE_END, + NO_TIP}, + {"Show", + {"SSIDs", "Stations", "All", "Selected"}, + 4, + {"show ap", "show station", "show all", "show selected"}, + NO_ARGS, + FOCUS_CONSOLE_END, + NO_TIP}, + {"Select", + {"All", "SSIDs", "Stations"}, + 3, + {"select all", "select aps", "select stations"}, + INPUT_ARGS, + FOCUS_CONSOLE_END, + NO_TIP}, + {"Deselect", + {"All", "SSIDs", "Stations"}, + 3, + {"deselect all", "deselect aps", "deselect stations"}, + INPUT_ARGS, + FOCUS_CONSOLE_END, + NO_TIP}, + {"Attack", + {"deauth", "deauthall", "beacon", "probe"}, + 4, + {"attack deauth", "attack deauthall", "attack beacon", "attack probe"}, + NO_ARGS, + FOCUS_CONSOLE_END, + SHOW_STOPSCAN_TIP}, + {"Settings", + {"Get", "Remove AP", "Set SSID", "Set Pass", "Save"}, + 5, + {"get settings", + "set webinterface false", + "set ssid: pwned", + "set password: deauther", + "save settings"}, + INPUT_ARGS, + FOCUS_CONSOLE_END, + NO_TIP}, + {"Sysinfo", {""}, 1, {"sysinfo"}, NO_ARGS, FOCUS_CONSOLE_END, NO_TIP}, + {"Reboot", {""}, 1, {"reboot"}, NO_ARGS, FOCUS_CONSOLE_END, NO_TIP}, +}; + +static void wifi_deauther_scene_start_var_list_enter_callback(void* context, uint32_t index) { + furi_assert(context); + WifideautherApp* app = context; + if(app->selected_option_index[index] < MenuItems[index].num_options_menu) { + app->selected_tx_string = + MenuItems[index].actual_commands[app->selected_option_index[index]]; + } + app->is_command = (1 <= index); + app->is_custom_tx_string = false; + app->selected_menu_index = index; + app->focus_console_start = (MenuItems[index].focus_console == FOCUS_CONSOLE_TOGGLE) ? + (app->selected_option_index[index] == 0) : + MenuItems[index].focus_console; + app->show_stopscan_tip = MenuItems[index].show_stopscan_tip; + + bool needs_keyboard = (MenuItems[index].needs_keyboard == TOGGLE_ARGS) ? + (app->selected_option_index[index] != 0) : + MenuItems[index].needs_keyboard; + if(needs_keyboard) { + view_dispatcher_send_custom_event(app->view_dispatcher, WifideautherEventStartKeyboard); + } else { + view_dispatcher_send_custom_event(app->view_dispatcher, WifideautherEventStartConsole); + } +} + +static void wifi_deauther_scene_start_var_list_change_callback(VariableItem* item) { + furi_assert(item); + + WifideautherApp* app = variable_item_get_context(item); + furi_assert(app); + + const WifideautherItem* menu_item = &MenuItems[app->selected_menu_index]; + uint8_t item_index = variable_item_get_current_value_index(item); + furi_assert(item_index < menu_item->num_options_menu); + variable_item_set_current_value_text(item, menu_item->options_menu[item_index]); + app->selected_option_index[app->selected_menu_index] = item_index; +} + +void wifi_deauther_scene_start_on_enter(void* context) { + WifideautherApp* app = context; + VariableItemList* var_item_list = app->var_item_list; + + variable_item_list_set_enter_callback( + var_item_list, wifi_deauther_scene_start_var_list_enter_callback, app); + + VariableItem* item; + for(int i = 0; i < NUM_MENU_ITEMS; ++i) { + item = variable_item_list_add( + var_item_list, + MenuItems[i].item_string, + MenuItems[i].num_options_menu, + wifi_deauther_scene_start_var_list_change_callback, + app); + if(MenuItems[i].num_options_menu) { + variable_item_set_current_value_index(item, app->selected_option_index[i]); + variable_item_set_current_value_text( + item, MenuItems[i].options_menu[app->selected_option_index[i]]); + } + } + + variable_item_list_set_selected_item( + var_item_list, scene_manager_get_scene_state(app->scene_manager, WifideautherSceneStart)); + + view_dispatcher_switch_to_view(app->view_dispatcher, WifideautherAppViewVarItemList); +} + +bool wifi_deauther_scene_start_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + WifideautherApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == WifideautherEventStartKeyboard) { + scene_manager_set_scene_state( + app->scene_manager, WifideautherSceneStart, app->selected_menu_index); + scene_manager_next_scene(app->scene_manager, WifideautherAppViewTextInput); + } else if(event.event == WifideautherEventStartConsole) { + scene_manager_set_scene_state( + app->scene_manager, WifideautherSceneStart, app->selected_menu_index); + scene_manager_next_scene(app->scene_manager, WifideautherAppViewConsoleOutput); + } + consumed = true; + } else if(event.type == SceneManagerEventTypeTick) { + app->selected_menu_index = variable_item_list_get_selected_item_index(app->var_item_list); + consumed = true; + } + + return consumed; +} + +void wifi_deauther_scene_start_on_exit(void* context) { + WifideautherApp* app = context; + variable_item_list_reset(app->var_item_list); +} diff --git a/applications/plugins/wifi_deauther_v2/scenes/wifi_deauther_scene_text_input.c b/applications/plugins/wifi_deauther_v2/scenes/wifi_deauther_scene_text_input.c new file mode 100644 index 000000000..2968f4de1 --- /dev/null +++ b/applications/plugins/wifi_deauther_v2/scenes/wifi_deauther_scene_text_input.c @@ -0,0 +1,90 @@ +#include "../wifi_deauther_app_i.h" + +void wifi_deauther_scene_text_input_callback(void* context) { + WifideautherApp* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, WifideautherEventStartConsole); +} + +void wifi_deauther_scene_text_input_on_enter(void* context) { + WifideautherApp* app = context; + + if(false == app->is_custom_tx_string) { + // Fill text input with selected string so that user can add to it + size_t length = strlen(app->selected_tx_string); + furi_assert(length < WIFI_deauther_TEXT_INPUT_STORE_SIZE); + bzero(app->text_input_store, WIFI_deauther_TEXT_INPUT_STORE_SIZE); + strncpy(app->text_input_store, app->selected_tx_string, length); + + // Add space - because flipper keyboard currently doesn't have a space + app->text_input_store[length] = ' '; + app->text_input_store[length + 1] = '\0'; + app->is_custom_tx_string = true; + } + + // Setup view + TextInput* text_input = app->text_input; + // Add help message to header + if(0 == strncmp("select aps", app->selected_tx_string, strlen("select aps"))) { + text_input_set_header_text(text_input, "Enter SSID ID to attack"); + } else if(0 == strncmp("select stations", app->selected_tx_string, strlen("select stations"))) { + text_input_set_header_text(text_input, "Enter Station ID to attack"); + } + if(0 == strncmp("deselect aps", app->selected_tx_string, strlen("deselect aps"))) { + text_input_set_header_text(text_input, "Enter SSID ID to remove"); + } else if( + 0 == strncmp("deselect stations", app->selected_tx_string, strlen("deselect stations"))) { + text_input_set_header_text(text_input, "Enter Station ID to remove"); + } else if(0 == strncmp("get settings", app->selected_tx_string, strlen("get settings"))) { + text_input_set_header_text(text_input, "Get setting. Enter for all"); + } else if( + 0 == + strncmp( + "set webinterface false", app->selected_tx_string, strlen("set webinterface false"))) { + text_input_set_header_text(text_input, "Disable PWNED management AP"); + } else if(0 == strncmp("set ssid: pwned", app->selected_tx_string, strlen("set ssid: pwned"))) { + text_input_set_header_text(text_input, "Change management SSID"); + } else if( + 0 == + strncmp( + "set password: deauther", app->selected_tx_string, strlen("set password: deauther"))) { + text_input_set_header_text(text_input, "Change management PWD"); + } else if(0 == strncmp("save settings", app->selected_tx_string, strlen("save settings"))) { + text_input_set_header_text(text_input, "Save Settings"); + } else { + text_input_set_header_text(text_input, "Add command arguments"); + } + text_input_set_result_callback( + text_input, + wifi_deauther_scene_text_input_callback, + app, + app->text_input_store, + WIFI_deauther_TEXT_INPUT_STORE_SIZE, + false); + + view_dispatcher_switch_to_view(app->view_dispatcher, WifideautherAppViewTextInput); +} + +///* +bool wifi_deauther_scene_text_input_on_event(void* context, SceneManagerEvent event) { + WifideautherApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == WifideautherEventStartConsole) { + // Point to custom string to send + app->selected_tx_string = app->text_input_store; + scene_manager_next_scene(app->scene_manager, WifideautherAppViewConsoleOutput); + consumed = true; + } + } + + return consumed; +} + +void wifi_deauther_scene_text_input_on_exit(void* context) { + WifideautherApp* app = context; + + text_input_reset(app->text_input); +} +//*/ \ No newline at end of file diff --git a/applications/plugins/wifi_deauther_v2/wifi_10px.png b/applications/plugins/wifi_deauther_v2/wifi_10px.png new file mode 100644 index 000000000..c13534660 Binary files /dev/null and b/applications/plugins/wifi_deauther_v2/wifi_10px.png differ diff --git a/applications/plugins/wifi_deauther_v2/wifi_deauther_app.c b/applications/plugins/wifi_deauther_v2/wifi_deauther_app.c new file mode 100644 index 000000000..28fb28d88 --- /dev/null +++ b/applications/plugins/wifi_deauther_v2/wifi_deauther_app.c @@ -0,0 +1,106 @@ +#include "wifi_deauther_app_i.h" + +#include +#include +#include + +static bool wifi_deauther_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + WifideautherApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool wifi_deauther_app_back_event_callback(void* context) { + furi_assert(context); + WifideautherApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void wifi_deauther_app_tick_event_callback(void* context) { + furi_assert(context); + WifideautherApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +WifideautherApp* wifi_deauther_app_alloc() { + WifideautherApp* app = malloc(sizeof(WifideautherApp)); + + app->gui = furi_record_open(RECORD_GUI); + + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&wifi_deauther_scene_handlers, app); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, wifi_deauther_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, wifi_deauther_app_back_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, wifi_deauther_app_tick_event_callback, 100); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + app->var_item_list = variable_item_list_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + WifideautherAppViewVarItemList, + variable_item_list_get_view(app->var_item_list)); + + for(int i = 0; i < NUM_MENU_ITEMS; ++i) { + app->selected_option_index[i] = 0; + } + + app->text_box = text_box_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, WifideautherAppViewConsoleOutput, text_box_get_view(app->text_box)); + app->text_box_store = furi_string_alloc(); + furi_string_reserve(app->text_box_store, WIFI_deauther_TEXT_BOX_STORE_SIZE); + + app->text_input = text_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, WifideautherAppViewTextInput, text_input_get_view(app->text_input)); + + scene_manager_next_scene(app->scene_manager, WifideautherSceneStart); + + return app; +} + +void wifi_deauther_app_free(WifideautherApp* app) { + furi_assert(app); + + // Views + view_dispatcher_remove_view(app->view_dispatcher, WifideautherAppViewVarItemList); + view_dispatcher_remove_view(app->view_dispatcher, WifideautherAppViewConsoleOutput); + view_dispatcher_remove_view(app->view_dispatcher, WifideautherAppViewTextInput); + text_box_free(app->text_box); + furi_string_free(app->text_box_store); + text_input_free(app->text_input); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + wifi_deauther_uart_free(app->uart); + + // Close records + furi_record_close(RECORD_GUI); + + free(app); +} + +int32_t wifi_deauther_app(void* p) { + furi_hal_power_enable_otg(); + furi_delay_ms(600); + UNUSED(p); + WifideautherApp* wifi_deauther_app = wifi_deauther_app_alloc(); + + wifi_deauther_app->uart = wifi_deauther_uart_init(wifi_deauther_app); + + view_dispatcher_run(wifi_deauther_app->view_dispatcher); + + wifi_deauther_app_free(wifi_deauther_app); + furi_hal_power_disable_otg(); + + return 0; +} diff --git a/applications/plugins/wifi_deauther_v2/wifi_deauther_app.h b/applications/plugins/wifi_deauther_v2/wifi_deauther_app.h new file mode 100644 index 000000000..bb2f6fbfb --- /dev/null +++ b/applications/plugins/wifi_deauther_v2/wifi_deauther_app.h @@ -0,0 +1,11 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct WifideautherApp WifideautherApp; + +#ifdef __cplusplus +} +#endif diff --git a/applications/plugins/wifi_deauther_v2/wifi_deauther_app_i.h b/applications/plugins/wifi_deauther_v2/wifi_deauther_app_i.h new file mode 100644 index 000000000..bab52f385 --- /dev/null +++ b/applications/plugins/wifi_deauther_v2/wifi_deauther_app_i.h @@ -0,0 +1,69 @@ +#pragma once + +#include "wifi_deauther_app.h" +#include "scenes/wifi_deauther_scene.h" +#include "wifi_deauther_custom_event.h" +#include "wifi_deauther_uart.h" + +#include +#include +#include +#include +#include +#include + +#define NUM_MENU_ITEMS (11) + +#define WIFI_deauther_TEXT_BOX_STORE_SIZE (4096) +#define WIFI_deauther_TEXT_INPUT_STORE_SIZE (512) + +struct WifideautherApp { + Gui* gui; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + + char text_input_store[WIFI_deauther_TEXT_INPUT_STORE_SIZE + 1]; + FuriString* text_box_store; + size_t text_box_store_strlen; + TextBox* text_box; + TextInput* text_input; + //Widget* widget; + + VariableItemList* var_item_list; + + WifideautherUart* uart; + int selected_menu_index; + int selected_option_index[NUM_MENU_ITEMS]; + const char* selected_tx_string; + bool is_command; + bool is_custom_tx_string; + bool focus_console_start; + bool show_stopscan_tip; +}; + +// Supported commands: +// https://github.com/justcallmekoko/ESP32deauther/wiki/cli +// Scan +// -> If list is empty, then start a new scanap. (Tap any button to stop.) +// -> If there's a list, provide option to rescan and dump list of targets to select. +// -> Press BACK to go back to top-level. +// Attack +// -> Beacon +// -> Deauth +// -> Probe +// -> Rickroll +// Sniff +// -> Beacon +// -> Deauth +// -> ESP +// -> PMKID +// -> Pwnagotchi +// Channel +// Update +// Reboot + +typedef enum { + WifideautherAppViewVarItemList, + WifideautherAppViewConsoleOutput, + WifideautherAppViewTextInput, +} WifideautherAppView; diff --git a/applications/plugins/wifi_deauther_v2/wifi_deauther_custom_event.h b/applications/plugins/wifi_deauther_v2/wifi_deauther_custom_event.h new file mode 100644 index 000000000..142961b1d --- /dev/null +++ b/applications/plugins/wifi_deauther_v2/wifi_deauther_custom_event.h @@ -0,0 +1,7 @@ +#pragma once + +typedef enum { + WifideautherEventRefreshConsoleOutput = 0, + WifideautherEventStartConsole, + WifideautherEventStartKeyboard, +} WifideautherCustomEvent; diff --git a/applications/plugins/wifi_deauther_v2/wifi_deauther_uart.c b/applications/plugins/wifi_deauther_v2/wifi_deauther_uart.c new file mode 100644 index 000000000..10e73c323 --- /dev/null +++ b/applications/plugins/wifi_deauther_v2/wifi_deauther_uart.c @@ -0,0 +1,98 @@ +#include "wifi_deauther_app_i.h" +#include "wifi_deauther_uart.h" + +#include + +#define UART_CH (FuriHalUartIdUSART1) +#define BAUDRATE (115200) + +struct WifideautherUart { + WifideautherApp* app; + FuriThread* rx_thread; + StreamBufferHandle_t rx_stream; + uint8_t rx_buf[RX_BUF_SIZE + 1]; + void (*handle_rx_data_cb)(uint8_t* buf, size_t len, void* context); +}; + +typedef enum { + WorkerEvtStop = (1 << 0), + WorkerEvtRxDone = (1 << 1), +} WorkerEvtFlags; + +void wifi_deauther_uart_set_handle_rx_data_cb( + WifideautherUart* uart, + void (*handle_rx_data_cb)(uint8_t* buf, size_t len, void* context)) { + furi_assert(uart); + uart->handle_rx_data_cb = handle_rx_data_cb; +} + +#define WORKER_ALL_RX_EVENTS (WorkerEvtStop | WorkerEvtRxDone) + +void wifi_deauther_uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) { + WifideautherUart* uart = (WifideautherUart*)context; + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + + if(ev == UartIrqEventRXNE) { + xStreamBufferSendFromISR(uart->rx_stream, &data, 1, &xHigherPriorityTaskWoken); + furi_thread_flags_set(furi_thread_get_id(uart->rx_thread), WorkerEvtRxDone); + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); + } +} + +static int32_t uart_worker(void* context) { + WifideautherUart* uart = (void*)context; + + uart->rx_stream = xStreamBufferCreate(RX_BUF_SIZE, 1); + + while(1) { + uint32_t events = + furi_thread_flags_wait(WORKER_ALL_RX_EVENTS, FuriFlagWaitAny, FuriWaitForever); + furi_check((events & FuriFlagError) == 0); + if(events & WorkerEvtStop) break; + if(events & WorkerEvtRxDone) { + size_t len = xStreamBufferReceive(uart->rx_stream, uart->rx_buf, RX_BUF_SIZE, 0); + if(len > 0) { + if(uart->handle_rx_data_cb) uart->handle_rx_data_cb(uart->rx_buf, len, uart->app); + } + } + } + + vStreamBufferDelete(uart->rx_stream); + + return 0; +} + +void wifi_deauther_uart_tx(uint8_t* data, size_t len) { + furi_hal_uart_tx(UART_CH, data, len); +} + +WifideautherUart* wifi_deauther_uart_init(WifideautherApp* app) { + WifideautherUart* uart = malloc(sizeof(WifideautherUart)); + + furi_hal_console_disable(); + furi_hal_uart_set_br(UART_CH, BAUDRATE); + furi_hal_uart_set_irq_cb(UART_CH, wifi_deauther_uart_on_irq_cb, uart); + + uart->app = app; + uart->rx_thread = furi_thread_alloc(); + furi_thread_set_name(uart->rx_thread, "WifideautherUartRxThread"); + furi_thread_set_stack_size(uart->rx_thread, 1024); + furi_thread_set_context(uart->rx_thread, uart); + furi_thread_set_callback(uart->rx_thread, uart_worker); + + furi_thread_start(uart->rx_thread); + return uart; +} + +void wifi_deauther_uart_free(WifideautherUart* uart) { + furi_assert(uart); + + furi_thread_flags_set(furi_thread_get_id(uart->rx_thread), WorkerEvtStop); + furi_thread_join(uart->rx_thread); + furi_thread_free(uart->rx_thread); + + furi_hal_uart_set_irq_cb(UART_CH, NULL, NULL); + furi_hal_console_enable(); + + free(uart); +} \ No newline at end of file diff --git a/applications/plugins/wifi_deauther_v2/wifi_deauther_uart.h b/applications/plugins/wifi_deauther_v2/wifi_deauther_uart.h new file mode 100644 index 000000000..534c2fdf7 --- /dev/null +++ b/applications/plugins/wifi_deauther_v2/wifi_deauther_uart.h @@ -0,0 +1,14 @@ +#pragma once + +#include "furi_hal.h" + +#define RX_BUF_SIZE (320) + +typedef struct WifideautherUart WifideautherUart; + +void wifi_deauther_uart_set_handle_rx_data_cb( + WifideautherUart* uart, + void (*handle_rx_data_cb)(uint8_t* buf, size_t len, void* context)); +void wifi_deauther_uart_tx(uint8_t* data, size_t len); +WifideautherUart* wifi_deauther_uart_init(WifideautherApp* app); +void wifi_deauther_uart_free(WifideautherUart* uart); diff --git a/applications/plugins/wifi_scanner/FlipperZeroWiFiModuleDefines.h b/applications/plugins/wifi_scanner/FlipperZeroWiFiModuleDefines.h new file mode 100644 index 000000000..7b28ebd64 --- /dev/null +++ b/applications/plugins/wifi_scanner/FlipperZeroWiFiModuleDefines.h @@ -0,0 +1,17 @@ +#define WIFI_MODULE_INIT_VERSION "WFSM_0.1" + +#define MODULE_CONTEXT_INITIALIZATION WIFI_MODULE_INIT_VERSION +#define MODULE_CONTEXT_MONITOR "monitor" +#define MODULE_CONTEXT_SCAN "scan" +#define MODULE_CONTEXT_SCAN_ANIMATION "scan_anim" +#define MODULE_CONTEXT_MONITOR_ANIMATION "monitor_anim" + +#define MODULE_CONTROL_COMMAND_NEXT 'n' +#define MODULE_CONTROL_COMMAND_PREVIOUS 'p' +#define MODULE_CONTROL_COMMAND_SCAN 's' +#define MODULE_CONTROL_COMMAND_MONITOR 'm' +#define MODULE_CONTROL_COMMAND_RESTART 'r' + +#define FLIPPERZERO_SERIAL_BAUD 115200 + +#define NA 0 diff --git a/applications/plugins/wifi_scanner/application.fam b/applications/plugins/wifi_scanner/application.fam new file mode 100644 index 000000000..bc7d352eb --- /dev/null +++ b/applications/plugins/wifi_scanner/application.fam @@ -0,0 +1,12 @@ +App( + appid="WiFi_Scanner", + name="[WiFi] Scanner", + apptype=FlipperAppType.EXTERNAL, + entry_point="wifi_scanner_app", + cdefines=["APP_WIFI_SCANNER"], + requires=["gui"], + stack_size=2 * 1024, + order=70, + fap_icon="wifi_10px.png", + fap_category="GPIO", +) diff --git a/applications/plugins/wifi_scanner/wifi_10px.png b/applications/plugins/wifi_scanner/wifi_10px.png new file mode 100644 index 000000000..c13534660 Binary files /dev/null and b/applications/plugins/wifi_scanner/wifi_10px.png differ diff --git a/applications/plugins/wifi_scanner/wifi_scanner.c b/applications/plugins/wifi_scanner/wifi_scanner.c new file mode 100644 index 000000000..826048e26 --- /dev/null +++ b/applications/plugins/wifi_scanner/wifi_scanner.c @@ -0,0 +1,855 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "FlipperZeroWiFiModuleDefines.h" + +#define WIFI_APP_DEBUG 0 + +#if WIFI_APP_DEBUG +#define APP_NAME_TAG "WiFi_Scanner" +#define WIFI_APP_LOG_I(format, ...) FURI_LOG_I(APP_NAME_TAG, format, ##__VA_ARGS__) +#define WIFI_APP_LOG_D(format, ...) FURI_LOG_D(APP_NAME_TAG, format, ##__VA_ARGS__) +#define WIFI_APP_LOG_E(format, ...) FURI_LOG_E(APP_NAME_TAG, format, ##__VA_ARGS__) +#else +#define WIFI_APP_LOG_I(format, ...) +#define WIFI_APP_LOG_D(format, ...) +#define WIFI_APP_LOG_E(format, ...) +#endif // WIFI_APP_DEBUG + +#define DISABLE_CONSOLE !WIFI_APP_DEBUG + +#define ENABLE_MODULE_POWER 1 +#define ENABLE_MODULE_DETECTION 1 + +#define ANIMATION_TIME 350 + +typedef enum EChunkArrayData { + EChunkArrayData_Context = 0, + EChunkArrayData_SSID, + EChunkArrayData_EncryptionType, + EChunkArrayData_RSSI, + EChunkArrayData_BSSID, + EChunkArrayData_Channel, + EChunkArrayData_IsHidden, + EChunkArrayData_CurrentAPIndex, + EChunkArrayData_TotalAps, + EChunkArrayData_ENUM_MAX +} EChunkArrayData; + +typedef enum EEventType // app internally defined event types +{ EventTypeKey // flipper input.h type +} EEventType; + +typedef struct SPluginEvent { + EEventType m_type; + InputEvent m_input; +} SPluginEvent; + +typedef struct EAccessPointDesc { + FuriString* m_accessPointName; + int16_t m_rssi; + FuriString* m_secType; + FuriString* m_bssid; + unsigned short m_channel; + bool m_isHidden; +} EAccessPointDesc; + +typedef enum EAppContext { + Undefined, + WaitingForModule, + Initializing, + ScanMode, + MonitorMode, + ScanAnimation, + MonitorAnimation +} EAppContext; + +typedef enum EWorkerEventFlags { + WorkerEventReserved = (1 << 0), // Reserved for StreamBuffer internal event + WorkerEventStop = (1 << 1), + WorkerEventRx = (1 << 2), +} EWorkerEventFlags; + +typedef struct SWiFiScannerApp { + Gui* m_gui; + FuriThread* m_worker_thread; + NotificationApp* m_notification; + FuriStreamBuffer* m_rx_stream; + + bool m_wifiModuleInitialized; + bool m_wifiModuleAttached; + + EAppContext m_context; + + EAccessPointDesc m_currentAccesspointDescription; + + unsigned short m_totalAccessPoints; + unsigned short m_currentIndexAccessPoint; + + uint32_t m_prevAnimationTime; + uint32_t m_animationTime; + uint8_t m_animtaionCounter; +} SWiFiScannerApp; + +/////// INIT STATE /////// +static void wifi_scanner_app_init(SWiFiScannerApp* const app) { + app->m_context = Undefined; + + app->m_totalAccessPoints = 0; + app->m_currentIndexAccessPoint = 0; + + app->m_currentAccesspointDescription.m_accessPointName = furi_string_alloc(); + furi_string_set(app->m_currentAccesspointDescription.m_accessPointName, "N/A\n"); + app->m_currentAccesspointDescription.m_channel = 0; + app->m_currentAccesspointDescription.m_bssid = furi_string_alloc(); + furi_string_set(app->m_currentAccesspointDescription.m_bssid, "N/A\n"); + app->m_currentAccesspointDescription.m_secType = furi_string_alloc(); + furi_string_set(app->m_currentAccesspointDescription.m_secType, "N/A\n"); + app->m_currentAccesspointDescription.m_rssi = 0; + app->m_currentAccesspointDescription.m_isHidden = false; + + app->m_prevAnimationTime = 0; + app->m_animationTime = ANIMATION_TIME; + app->m_animtaionCounter = 0; + + app->m_wifiModuleInitialized = false; + +#if ENABLE_MODULE_DETECTION + app->m_wifiModuleAttached = false; +#else + app->m_wifiModuleAttached = true; +#endif +} + +int16_t dBmtoPercentage(int16_t dBm) { + const int16_t RSSI_MAX = -50; // define maximum strength of signal in dBm + const int16_t RSSI_MIN = -100; // define minimum strength of signal in dBm + + int16_t quality; + if(dBm <= RSSI_MIN) { + quality = 0; + } else if(dBm >= RSSI_MAX) { + quality = 100; + } else { + quality = 2 * (dBm + 100); + } + + return quality; +} + +void DrawSignalStrengthBar(Canvas* canvas, int rssi, int x, int y, int width, int height) { + int16_t percents = dBmtoPercentage(rssi); + + u8g2_DrawHLine(&canvas->fb, x, y, width); + u8g2_DrawHLine(&canvas->fb, x, y + height, width); + + if(rssi != NA && height > 0) { + uint8_t barHeight = floor((height / 100.f) * percents); + canvas_draw_box(canvas, x, y + height - barHeight, width, barHeight); + } +} + +static void wifi_module_render_callback(Canvas* const canvas, void* ctx) { + SWiFiScannerApp* app = acquire_mutex((ValueMutex*)ctx, 25); + if(app == NULL) { + return; + } + + canvas_clear(canvas); + + { + switch(app->m_context) { + case Undefined: { + canvas_set_font(canvas, FontPrimary); + + const char* strError = "Something wrong"; + canvas_draw_str( + canvas, + (u8g2_GetDisplayWidth(&canvas->fb) / 2) - + (canvas_string_width(canvas, strError) / 2), + (u8g2_GetDisplayHeight(&canvas->fb) / + 2) /* - (u8g2_GetMaxCharHeight(&canvas->fb) / 2)*/, + strError); + } break; + case WaitingForModule: +#if ENABLE_MODULE_DETECTION + furi_assert(!app->m_wifiModuleAttached); + if(!app->m_wifiModuleAttached) { + canvas_set_font(canvas, FontSecondary); + + const char* strConnectModule = "Attach WiFi scanner module"; + canvas_draw_str( + canvas, + (u8g2_GetDisplayWidth(&canvas->fb) / 2) - + (canvas_string_width(canvas, strConnectModule) / 2), + (u8g2_GetDisplayHeight(&canvas->fb) / + 2) /* - (u8g2_GetMaxCharHeight(&canvas->fb) / 2)*/, + strConnectModule); + } +#endif + break; + case Initializing: { + furi_assert(!app->m_wifiModuleInitialized); + if(!app->m_wifiModuleInitialized) { + canvas_set_font(canvas, FontPrimary); + + const char* strInitializing = "Initializing..."; + canvas_draw_str( + canvas, + (u8g2_GetDisplayWidth(&canvas->fb) / 2) - + (canvas_string_width(canvas, strInitializing) / 2), + (u8g2_GetDisplayHeight(&canvas->fb) / 2) - + (u8g2_GetMaxCharHeight(&canvas->fb) / 2), + strInitializing); + } + } break; + case ScanMode: { + uint8_t offsetY = 0; + uint8_t offsetX = 0; + canvas_draw_frame( + canvas, + 0, + 0, + u8g2_GetDisplayWidth(&canvas->fb), + u8g2_GetDisplayHeight(&canvas->fb)); + + //canvas_set_font(canvas, FontPrimary); + u8g2_SetFont(&canvas->fb, u8g2_font_7x13B_tr); + uint8_t fontHeight = u8g2_GetMaxCharHeight(&canvas->fb); + + offsetX += 5; + offsetY += fontHeight; + canvas_draw_str( + canvas, + offsetX, + offsetY, + app->m_currentAccesspointDescription.m_isHidden ? + "(Hidden SSID)" : + furi_string_get_cstr(app->m_currentAccesspointDescription.m_accessPointName)); + + offsetY += fontHeight; + + canvas_draw_str( + canvas, + offsetX, + offsetY, + furi_string_get_cstr(app->m_currentAccesspointDescription.m_bssid)); + + canvas_set_font(canvas, FontSecondary); + //u8g2_SetFont(&canvas->fb, u8g2_font_tinytim_tf); + fontHeight = u8g2_GetMaxCharHeight(&canvas->fb); + + offsetY += fontHeight + 1; + + char string[15]; + snprintf( + string, sizeof(string), "RSSI: %d", app->m_currentAccesspointDescription.m_rssi); + canvas_draw_str(canvas, offsetX, offsetY, string); + + offsetY += fontHeight + 1; + + snprintf( + string, sizeof(string), "CHNL: %d", app->m_currentAccesspointDescription.m_channel); + canvas_draw_str(canvas, offsetX, offsetY, string); + + offsetY += fontHeight + 1; + + snprintf( + string, + sizeof(string), + "ENCR: %s", + furi_string_get_cstr(app->m_currentAccesspointDescription.m_secType)); + canvas_draw_str(canvas, offsetX, offsetY, string); + + offsetY += fontHeight; + offsetY -= fontHeight; + + u8g2_SetFont(&canvas->fb, u8g2_font_courB08_tn); + snprintf( + string, + sizeof(string), + "%d/%d", + app->m_currentIndexAccessPoint, + app->m_totalAccessPoints); + offsetX = u8g2_GetDisplayWidth(&canvas->fb) - canvas_string_width(canvas, string) - 5; + canvas_draw_str(canvas, offsetX, offsetY, string); + + canvas_draw_frame( + canvas, + offsetX - 6, + offsetY - u8g2_GetMaxCharHeight(&canvas->fb) - 3, + u8g2_GetDisplayWidth(&canvas->fb), + u8g2_GetDisplayHeight(&canvas->fb)); + + u8g2_SetFont(&canvas->fb, u8g2_font_open_iconic_arrow_2x_t); + if(app->m_currentIndexAccessPoint != app->m_totalAccessPoints) { + //canvas_draw_triangle(canvas, offsetX - 5 - 20, offsetY + 5, 4, 4, CanvasDirectionBottomToTop); + canvas_draw_str(canvas, offsetX - 0 - 35, offsetY + 5, "\x4C"); + } + + if(app->m_currentIndexAccessPoint != 1) { + //canvas_draw_triangle(canvas, offsetX - 6 - 35, offsetY + 5, 4, 4, CanvasDirectionTopToBottom); + canvas_draw_str(canvas, offsetX - 4 - 20, offsetY + 5, "\x4F"); + } + } break; + case MonitorMode: { + uint8_t offsetY = 0; + uint8_t offsetX = 0; + + canvas_draw_frame( + canvas, + 0, + 0, + u8g2_GetDisplayWidth(&canvas->fb), + u8g2_GetDisplayHeight(&canvas->fb)); + + //canvas_set_font(canvas, FontBigNumbers); + u8g2_SetFont(&canvas->fb, u8g2_font_inb27_mr); + uint8_t fontHeight = u8g2_GetMaxCharHeight(&canvas->fb); + uint8_t fontWidth = u8g2_GetMaxCharWidth(&canvas->fb); + + if(app->m_currentAccesspointDescription.m_rssi == NA) { + offsetX += floor(u8g2_GetDisplayWidth(&canvas->fb) / 2) - fontWidth - 10; + offsetY += fontHeight - 5; + + canvas_draw_str(canvas, offsetX, offsetY, "N/A"); + } else { + offsetX += floor(u8g2_GetDisplayWidth(&canvas->fb) / 2) - 2 * fontWidth; + offsetY += fontHeight - 5; + + char rssi[8]; + snprintf(rssi, sizeof(rssi), "%d", app->m_currentAccesspointDescription.m_rssi); + canvas_draw_str(canvas, offsetX, offsetY, rssi); + } + + //canvas_set_font(canvas, FontPrimary); + u8g2_SetFont(&canvas->fb, u8g2_font_7x13B_tr); + fontHeight = u8g2_GetMaxCharHeight(&canvas->fb); + fontWidth = u8g2_GetMaxCharWidth(&canvas->fb); + + offsetX = 5; + offsetY = u8g2_GetDisplayHeight(&canvas->fb) - 7 - fontHeight; + canvas_draw_str( + canvas, + offsetX, + offsetY, + furi_string_get_cstr(app->m_currentAccesspointDescription.m_accessPointName)); + + offsetY += fontHeight + 2; + + canvas_draw_str( + canvas, + offsetX, + offsetY, + furi_string_get_cstr(app->m_currentAccesspointDescription.m_bssid)); + + DrawSignalStrengthBar( + canvas, app->m_currentAccesspointDescription.m_rssi, 5, 5, 12, 25); + DrawSignalStrengthBar( + canvas, + app->m_currentAccesspointDescription.m_rssi, + u8g2_GetDisplayWidth(&canvas->fb) - 5 - 12, + 5, + 12, + 25); + } break; + case ScanAnimation: { + uint32_t currentTime = furi_get_tick(); + if(currentTime - app->m_prevAnimationTime > app->m_animationTime) { + app->m_prevAnimationTime = currentTime; + app->m_animtaionCounter += 1; + app->m_animtaionCounter = app->m_animtaionCounter % 3; + } + + uint8_t offsetX = 10; + uint8_t mutliplier = 2; + + for(uint8_t i = 0; i < 3; ++i) { + canvas_draw_disc( + canvas, + offsetX + 30 + 25 * i, + u8g2_GetDisplayHeight(&canvas->fb) / 2 - 7, + 5 * (app->m_animtaionCounter == i ? mutliplier : 1)); + } + + u8g2_SetFont(&canvas->fb, u8g2_font_7x13B_tr); + //canvas_set_font(canvas, FontPrimary); + const char* message = "Scanning"; + canvas_draw_str( + canvas, + u8g2_GetDisplayWidth(&canvas->fb) / 2 - canvas_string_width(canvas, message) / 2, + 55, + message); + } break; + case MonitorAnimation: { + uint32_t currentTime = furi_get_tick(); + if(currentTime - app->m_prevAnimationTime > app->m_animationTime) { + app->m_prevAnimationTime = currentTime; + app->m_animtaionCounter += 1; + app->m_animtaionCounter = app->m_animtaionCounter % 2; + } + + uint8_t offsetX = 10; + uint8_t mutliplier = 2; + + canvas_draw_disc( + canvas, + offsetX + 30, + u8g2_GetDisplayHeight(&canvas->fb) / 2 - 7, + 5 * (app->m_animtaionCounter == 0 ? mutliplier : 1)); + canvas_draw_disc( + canvas, + offsetX + 55, + u8g2_GetDisplayHeight(&canvas->fb) / 2 - 7, + 5 * (app->m_animtaionCounter == 1 ? mutliplier : 1)); + canvas_draw_disc( + canvas, + offsetX + 80, + u8g2_GetDisplayHeight(&canvas->fb) / 2 - 7, + 5 * (app->m_animtaionCounter == 0 ? mutliplier : 1)); + + u8g2_SetFont(&canvas->fb, u8g2_font_7x13B_tr); + //canvas_set_font(canvas, FontPrimary); + const char* message = "Monitor Mode"; + canvas_draw_str( + canvas, + u8g2_GetDisplayWidth(&canvas->fb) / 2 - canvas_string_width(canvas, message) / 2, + 55, + message); + } break; + default: + break; + } + } + release_mutex((ValueMutex*)ctx, app); +} + +static void wifi_module_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + SPluginEvent event = {.m_type = EventTypeKey, .m_input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) { + furi_assert(context); + + SWiFiScannerApp* app = context; + + WIFI_APP_LOG_I("uart_echo_on_irq_cb"); + + if(ev == UartIrqEventRXNE) { + WIFI_APP_LOG_I("ev == UartIrqEventRXNE"); + furi_stream_buffer_send(app->m_rx_stream, &data, 1, 0); + furi_thread_flags_set(furi_thread_get_id(app->m_worker_thread), WorkerEventRx); + } +} + +static int32_t uart_worker(void* context) { + furi_assert(context); + + SWiFiScannerApp* app = acquire_mutex((ValueMutex*)context, 25); + if(app == NULL) { + return 1; + } + + FuriStreamBuffer* rx_stream = app->m_rx_stream; + + release_mutex((ValueMutex*)context, app); + + while(true) { + uint32_t events = furi_thread_flags_wait( + WorkerEventStop | WorkerEventRx, FuriFlagWaitAny, FuriWaitForever); + furi_check((events & FuriFlagError) == 0); + + if(events & WorkerEventStop) break; + if(events & WorkerEventRx) { + size_t length = 0; + FuriString* receivedString; + receivedString = furi_string_alloc(); + do { + uint8_t data[64]; + length = furi_stream_buffer_receive(rx_stream, data, 64, 25); + if(length > 0) { + WIFI_APP_LOG_I("Received Data - length: %i", length); + + for(uint16_t i = 0; i < length; i++) { + furi_string_push_back(receivedString, data[i]); + } + + //notification_message(app->notification, &sequence_set_only_red_255); + } + } while(length > 0); + if(furi_string_size(receivedString) > 0) { + FuriString* chunk; + chunk = furi_string_alloc(); + size_t begin = 0; + size_t end = 0; + size_t stringSize = furi_string_size(receivedString); + + WIFI_APP_LOG_I("Received string: %s", furi_string_get_cstr(receivedString)); + + FuriString* chunksArray[EChunkArrayData_ENUM_MAX]; + for(uint8_t i = 0; i < EChunkArrayData_ENUM_MAX; ++i) { + chunksArray[i] = furi_string_alloc(); + } + + uint8_t index = 0; + do { + end = furi_string_search_char(receivedString, '+', begin); + + if(end == FURI_STRING_FAILURE) { + end = stringSize; + } + + WIFI_APP_LOG_I("size: %i, begin: %i, end: %i", stringSize, begin, end); + + furi_string_set_strn( + chunk, &furi_string_get_cstr(receivedString)[begin], end - begin); + + WIFI_APP_LOG_I("String chunk: %s", furi_string_get_cstr(chunk)); + + furi_string_set(chunksArray[index++], chunk); + + begin = end + 1; + } while(end < stringSize); + furi_string_free(chunk); + + app = acquire_mutex((ValueMutex*)context, 25); + if(app == NULL) { + return 1; + } + + if(!app->m_wifiModuleInitialized) { + if(furi_string_cmp_str( + chunksArray[EChunkArrayData_Context], MODULE_CONTEXT_INITIALIZATION) == + 0) { + app->m_wifiModuleInitialized = true; + app->m_context = ScanAnimation; + } + + } else { + if(furi_string_cmp_str( + chunksArray[EChunkArrayData_Context], MODULE_CONTEXT_MONITOR) == 0) { + app->m_context = MonitorMode; + } else if( + furi_string_cmp_str( + chunksArray[EChunkArrayData_Context], MODULE_CONTEXT_SCAN) == 0) { + app->m_context = ScanMode; + } else if( + furi_string_cmp_str( + chunksArray[EChunkArrayData_Context], MODULE_CONTEXT_SCAN_ANIMATION) == + 0) { + app->m_context = ScanAnimation; + } else if( + furi_string_cmp_str( + chunksArray[EChunkArrayData_Context], + MODULE_CONTEXT_MONITOR_ANIMATION) == 0) { + app->m_context = MonitorAnimation; + } + + if(app->m_context == MonitorMode || app->m_context == ScanMode) { + furi_string_set( + app->m_currentAccesspointDescription.m_accessPointName, + chunksArray[EChunkArrayData_SSID]); + furi_string_set( + app->m_currentAccesspointDescription.m_secType, + chunksArray[EChunkArrayData_EncryptionType]); + app->m_currentAccesspointDescription.m_rssi = + atoi(furi_string_get_cstr(chunksArray[EChunkArrayData_RSSI])); + furi_string_set( + app->m_currentAccesspointDescription.m_bssid, + chunksArray[EChunkArrayData_BSSID]); + app->m_currentAccesspointDescription.m_channel = + atoi(furi_string_get_cstr(chunksArray[EChunkArrayData_Channel])); + app->m_currentAccesspointDescription.m_isHidden = + atoi(furi_string_get_cstr(chunksArray[EChunkArrayData_IsHidden])); + + app->m_currentIndexAccessPoint = atoi( + furi_string_get_cstr(chunksArray[EChunkArrayData_CurrentAPIndex])); + app->m_totalAccessPoints = + atoi(furi_string_get_cstr(chunksArray[EChunkArrayData_TotalAps])); + } + } + + release_mutex((ValueMutex*)context, app); + + // Clear string array + for(index = 0; index < EChunkArrayData_ENUM_MAX; ++index) { + furi_string_free(chunksArray[index]); + } + } + furi_string_free(receivedString); + } + } + + return 0; +} + +typedef enum ESerialCommand { + ESerialCommand_Next, + ESerialCommand_Previous, + ESerialCommand_Scan, + ESerialCommand_MonitorMode, + ESerialCommand_Restart +} ESerialCommand; + +void send_serial_command(ESerialCommand command) { +#if !DISABLE_CONSOLE + return; +#endif + + uint8_t data[1] = {0}; + + switch(command) { + case ESerialCommand_Next: + data[0] = MODULE_CONTROL_COMMAND_NEXT; + break; + case ESerialCommand_Previous: + data[0] = MODULE_CONTROL_COMMAND_PREVIOUS; + break; + case ESerialCommand_Scan: + data[0] = MODULE_CONTROL_COMMAND_SCAN; + break; + case ESerialCommand_MonitorMode: + data[0] = MODULE_CONTROL_COMMAND_MONITOR; + break; + case ESerialCommand_Restart: + data[0] = MODULE_CONTROL_COMMAND_RESTART; + break; + default: + return; + }; + + furi_hal_uart_tx(FuriHalUartIdUSART1, data, 1); +} + +int32_t wifi_scanner_app(void* p) { + UNUSED(p); + + WIFI_APP_LOG_I("Init"); + + // FuriTimer* timer = furi_timer_alloc(blink_test_update, FuriTimerTypePeriodic, event_queue); + // furi_timer_start(timer, furi_kernel_get_tick_frequency()); + + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(SPluginEvent)); + + SWiFiScannerApp* app = malloc(sizeof(SWiFiScannerApp)); + + wifi_scanner_app_init(app); + +#if ENABLE_MODULE_DETECTION + furi_hal_gpio_init( + &gpio_ext_pc0, + GpioModeInput, + GpioPullUp, + GpioSpeedLow); // Connect to the Flipper's ground just to be sure + //furi_hal_gpio_add_int_callback(pinD0, input_isr_d0, this); + app->m_context = WaitingForModule; +#else + app->m_context = Initializing; +#if ENABLE_MODULE_POWER + furi_hal_power_enable_otg(); +#endif // ENABLE_MODULE_POWER +#endif // ENABLE_MODULE_DETECTION + + ValueMutex app_data_mutex; + if(!init_mutex(&app_data_mutex, app, sizeof(SWiFiScannerApp))) { + WIFI_APP_LOG_E("cannot create mutex\r\n"); + free(app); + return 255; + } + + WIFI_APP_LOG_I("Mutex created"); + + app->m_notification = furi_record_open("notification"); + + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, wifi_module_render_callback, &app_data_mutex); + view_port_input_callback_set(view_port, wifi_module_input_callback, event_queue); + + // Open GUI and register view_port + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + //notification_message(app->notification, &sequence_set_only_blue_255); + + app->m_rx_stream = furi_stream_buffer_alloc(1 * 1024, 1); + + app->m_worker_thread = furi_thread_alloc(); + furi_thread_set_name(app->m_worker_thread, "WiFiModuleUARTWorker"); + furi_thread_set_stack_size(app->m_worker_thread, 1024); + furi_thread_set_context(app->m_worker_thread, &app_data_mutex); + furi_thread_set_callback(app->m_worker_thread, uart_worker); + furi_thread_start(app->m_worker_thread); + WIFI_APP_LOG_I("UART thread allocated"); + + // Enable uart listener +#if DISABLE_CONSOLE + furi_hal_console_disable(); +#endif + furi_hal_uart_set_br(FuriHalUartIdUSART1, FLIPPERZERO_SERIAL_BAUD); + furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, uart_on_irq_cb, app); + WIFI_APP_LOG_I("UART Listener created"); + + // Because we assume that module was on before we launched the app. We need to ensure that module will be in initial state on app start + send_serial_command(ESerialCommand_Restart); + + SPluginEvent event; + for(bool processing = true; processing;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + SWiFiScannerApp* app = (SWiFiScannerApp*)acquire_mutex_block(&app_data_mutex); + +#if ENABLE_MODULE_DETECTION + if(!app->m_wifiModuleAttached) { + if(furi_hal_gpio_read(&gpio_ext_pc0) == false) { + WIFI_APP_LOG_I("Module Attached"); + app->m_wifiModuleAttached = true; + app->m_context = Initializing; +#if ENABLE_MODULE_POWER + furi_hal_power_enable_otg(); +#endif + } + } +#endif // ENABLE_MODULE_DETECTION + + if(event_status == FuriStatusOk) { + if(event.m_type == EventTypeKey) { + if(app->m_wifiModuleInitialized) { + if(app->m_context == ScanMode) { + switch(event.m_input.key) { + case InputKeyUp: + case InputKeyLeft: + if(event.m_input.type == InputTypeShort) { + WIFI_APP_LOG_I("Previous"); + send_serial_command(ESerialCommand_Previous); + } else if(event.m_input.type == InputTypeRepeat) { + WIFI_APP_LOG_I("Previous Repeat"); + send_serial_command(ESerialCommand_Previous); + } + break; + case InputKeyDown: + case InputKeyRight: + if(event.m_input.type == InputTypeShort) { + WIFI_APP_LOG_I("Next"); + send_serial_command(ESerialCommand_Next); + } else if(event.m_input.type == InputTypeRepeat) { + WIFI_APP_LOG_I("Next Repeat"); + send_serial_command(ESerialCommand_Next); + } + break; + default: + break; + } + } + + switch(event.m_input.key) { + case InputKeyOk: + if(event.m_input.type == InputTypeShort) { + if(app->m_context == ScanMode) { + WIFI_APP_LOG_I("Monitor Mode"); + send_serial_command(ESerialCommand_MonitorMode); + } + } else if(event.m_input.type == InputTypeLong) { + WIFI_APP_LOG_I("Scan"); + send_serial_command(ESerialCommand_Scan); + } + break; + case InputKeyBack: + if(event.m_input.type == InputTypeShort) { + switch(app->m_context) { + case MonitorMode: + send_serial_command(ESerialCommand_Scan); + break; + case ScanMode: + processing = false; + break; + default: + break; + } + } else if(event.m_input.type == InputTypeLong) { + processing = false; + } + break; + default: + break; + } + } else { + if(event.m_input.key == InputKeyBack) { + if(event.m_input.type == InputTypeShort || + event.m_input.type == InputTypeLong) { + processing = false; + } + } + } + } + } + +#if ENABLE_MODULE_DETECTION + if(app->m_wifiModuleAttached && furi_hal_gpio_read(&gpio_ext_pc0) == true) { + WIFI_APP_LOG_D("Module Disconnected - Exit"); + processing = false; + app->m_wifiModuleAttached = false; + app->m_wifiModuleInitialized = false; + } +#endif + + view_port_update(view_port); + release_mutex(&app_data_mutex, app); + } + + WIFI_APP_LOG_I("Start exit app"); + + furi_thread_flags_set(furi_thread_get_id(app->m_worker_thread), WorkerEventStop); + furi_thread_join(app->m_worker_thread); + furi_thread_free(app->m_worker_thread); + + WIFI_APP_LOG_I("Thread Deleted"); + + // Reset GPIO pins to default state + furi_hal_gpio_init(&gpio_ext_pc0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + +#if DISABLE_CONSOLE + furi_hal_console_enable(); +#endif + + view_port_enabled_set(view_port, false); + + gui_remove_view_port(gui, view_port); + + // Close gui record + furi_record_close(RECORD_GUI); + furi_record_close("notification"); + app->m_gui = NULL; + + view_port_free(view_port); + + furi_message_queue_free(event_queue); + + furi_stream_buffer_free(app->m_rx_stream); + + delete_mutex(&app_data_mutex); + + // Free rest + free(app); + + WIFI_APP_LOG_I("App freed"); + +#if ENABLE_MODULE_POWER + furi_hal_power_disable_otg(); +#endif + + return 0; +} \ No newline at end of file diff --git a/applications/plugins/wii_ec_anal/LICENSE b/applications/plugins/wii_ec_anal/LICENSE new file mode 100644 index 000000000..95e544a06 --- /dev/null +++ b/applications/plugins/wii_ec_anal/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 BlueChip + +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. diff --git a/applications/plugins/wii_ec_anal/README.md b/applications/plugins/wii_ec_anal/README.md new file mode 100644 index 000000000..8d439c7e0 --- /dev/null +++ b/applications/plugins/wii_ec_anal/README.md @@ -0,0 +1,234 @@ +# [FlipperZero] Wii Extension Controller Protocol Analyser +This Protocol Analyser offers a full Test and Calibrate system for Wii Extension Controllers. + +__Disclaimer:__ *Use of this plugin, and notably connecting an Extension Controller to the FlipperZero is performed entirely at your own risk.* + +# Notes +This plugin has (todate) only been tested with official Nintendo Nunchucks and Classic Controllers - namely Nunchucks and Classic Controllers. + +# Encryption +This plugin has SOME code to handle encryption, but it it unused, untested, and some of it is known to un-work. + +This plugin (currently) only works with Extension Controllers which implement the encryption-bypass strategy. IE. `i2c_write(0xf0, 0x55) ; i2c_write(0xfb, 0x00)` + +If you need this functionality, either raise an Issue or, better still, a Pull Request. + +# Screen: SPLASH +
+The SPLASH Screen is displayed when the Plugin starts. It can be cleared by pressing any key, else it will auto-clear after 3.5 seconds. + +# Screen: WAIT +   

+The WAIT screen will display which pins you need to connect between the flipper and the Wii Extension Controller. + +__Disclaimer:__ Use of this plugin, and notably connecting the Controller to the FlipperZero is performed entirely at your own risk. + +Looking in to the exposed side of the Extension Controller plug, with the notch on the bottom + +| EC Pin # | EC Position | EC Pin ID | Pin Function | FZ GPIO Pin Name | FZ GPIO Pin # | +| :---: | :---: | :---: | :---: | :---: | :---: | +| 1 | top-left | +3v3 | Power | 3v3 | 9 | +| 2 | bottom-left | SCL | i2c clock | C0 | 16 | +| 3 | top-centre | EN | ¿detect? | | | +| 4 | bottom-centre | -x- | -none- | | | +| 5 | top-right | SDA | i2c data | C1 | 15 | +| 6 | bottom-right | Gnd | Power | Gnd | 18 | + +Keys: +* Left - Show splash screen +* Back - exit plugin + +The easiest way to connect a Wii Extension Controller to a FlipperZero is arguably with a ["WiiChuck"](https://www.ebay.co.uk/sch/?_nkw=wiichuck) or a ["Nunchucky"](https://www.solarbotics.com/product/31040)

+ + + + + +
WiiChuckNunchucky
+ +### ** WARNING ** +Neither the WiiChuck, nor the Nunchucky have a pin polarisation mechanism.
+If you plug the adaptor in the wrong way around you WILL apply voltage to the Controller the wrong way round!!
+I have no idea if THIS WILL PERMANENTLY KILL THE CONTROLLER ...Who wants to try it? + +On all the WiiChucks I have seen: +* The WiiChuck has THREE connectors on one side, and TWO connectors on the other. +* The side with TWO connectors should go against the side of the Controller plug with the big indent. +``` ++-------------+ +| _________ | +| | = = = | | +| |_=_____=_| | <-- notice missing pin +| ___ | +| | | | <-- notice indent ++----+ +----+ +``` +
+ +...BUT I *highly* recommend you check the pins on your adaptor to make sure everything goes well. + +I believe the unconnected pin on the top is a "presence detect" function, but I have not (yet) verified this.
+This feature is NOT required by this plugin, as the detection is performed by means of an i2c handshake. + +When a device is connected it will be immediately recognised. If it is not, either: +* The Controller is not correctly connected
+...This may be as simple as a broken wire. +* The controller board in the Controller is faulty.
+...Repair of which is beyond the scope of this document. + +To get the list of "known" Controllers, run `./info.sh`
+As of writing this, that returns: +```c +[PID_UNKNOWN ] = { {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, "Unknown Perhipheral", SCENE_DUMP, +[PID_NUNCHUCK ] = { {0x00, 0x00, 0xA4, 0x20, 0x00, 0x00}, "Nunchuck", SCENE_NUNCHUCK, +[PID_CLASSIC ] = { {0x00, 0x00, 0xA4, 0x20, 0x01, 0x01}, "Classic Controller", SCENE_CLASSIC, +[PID_BALANCE ] = { {0x00, 0x00, 0xA4, 0x20, 0x04, 0x02}, "Balance Board", SCENE_DUMP, +[PID_GH_GUITAR ] = { {0x00, 0x00, 0xA4, 0x20, 0x01, 0x03}, "Guitar Hero Guitar", SCENE_DUMP, +[PID_GH_DRUMS ] = { {0x01, 0x00, 0xA4, 0x20, 0x01, 0x03}, "Guitar Hero World Tour Drums", SCENE_DUMP, +[PID_TURNTABLE ] = { {0x03, 0x00, 0xA4, 0x20, 0x01, 0x03}, "DJ Hero Turntable", SCENE_DUMP, +[PID_TAIKO_DRUMS] = { {0x00, 0x00, 0xA4, 0x20, 0x01, 0x11}, "Taiko Drum Controller)", SCENE_DUMP, + +``` + +You can see that there are EIGHT known devices. One is the default for an unknown controller; SEVEN devices are known by name; and TWO (of those seven) have bespoke "scenes" (ie. SCENE_NUNCHUCK & SCENE_CLASSIC). + +# Screen: NUNCHUCK - MAIN +
+When you connect a Nunchuck, you will see a screen displaying: +* Accelerometer{X,Y,Z} values +* Joystick{X,Y} values +* Joystick graphic +* Button{C,Z} + +Keys: +* Left - Go to the DUMP screen +* Right - Go to the NUNCHUCK_ACC accelerometers screen +* Up/Down/OK - [qv. Peak Meters] +* Short-Back - Reset controller +* Long-Back - Exit plugin + +# Screen: NUNCHUCK - ACCELEROMETERS + +   
+ +| Axis | Movement | Lower | Higher | +| :---: | :---: | :---: | :---: | +| X | Left / Right | Left | Right | +| Y | Fwd / Bkwd | Fwd | Bkwd | +| Z | Down / Up | Down | Up | + +* Movement in the direction of an axis changes that axis reading +* Twisting/tilting around an axis changes the other two readings +* EG. + * Move left (along the X axis) will effect X + * Turn left (a rotation around the Y axis) will effect X and Z + +Keys: +* Left - go to the main NUNCHUCK screen +* Up + * Auto-Pause Disabled --> Enable Auto-Pause + * Paused at the end of a page --> Restart scanner + * Running with Auto-Pause Enabled --> Disable Auto-Pause +* Nunchuck-Z - Toggle pause +* Nunchuck-C - Toggle auto-pause +* Long-OK - Enter Software Calibration mode [qv. Calibration] + * Calibration mode on the Accelerometer screen will ONLY calibrate the accelerometer +* Short-OK - Leave Software Calibration mode *and* Calibrate CENTRE position(s) +* Short-Back - Reset controller +* Long-Back - Exit plugin + +NB. Code DOES exist to scroll the display, but the LCD refresh rate is too low, and it looks awful + +# Screen: CLASSIC +
+When you connect a Classic Controller [Pro], you will see a screen displaying a Classic Controller +* The Classic Controller will animate in line with controller events +* The scan rate is set to 30fps, but in reality there is a bit of lag with the LCD screen, so YMMV. + +Keys: +* Left - go to the DUMP screen +* Right - show analogue readings (Left to hide them again) +* Up/Down/OK - [qv. Peak Meters] +* Short-Back - Reset controller +* Long-Back - Exit plugin + +# Screen: DUMP +
+The Dump screen will show you the raw readings from the device.
+If you connect a device which does not have a bespoke `_decode()` function (etc.), you will see (only) this screen. +* SID - String ID - human-readable name (from the `info` table) +* PID - Peripheral ID - The 6 bytes which identify the device. +* Cal - Calibration data - 16 bytes +* The bottom row of hex shows the SIX bytes of Controller data + * Below each hex digit is the binary representation of that digit + * By example. With a Nunchuck connected, click the Z button, and watch the bit on the far right + +Keys: +* Right - return to controller-specific screen (if there is one) +* Short-Back - Reset controller +* Long-Back - Exit plugin + +# Peak Meters (Calibration values) + +On any Controller-specific screen with a Peak/Trough menu displayed: +* Up - [toggle] only show peak values +* Down - [toggle] only show trough values +* Long-OK - Enter Software Calibration mode [qv. Calibration] +* Short-OK - Leave Software Calibration mode / Calibrate CENTRE position(s) + +# Calibration +
+ +* __This project handles Calibration of Analogue Controls, but has NO understanding of Accelerometer values (yet).__ + +Digital buttons do NOT require Calibration. + +Some Calibration data is calculated at the factory, and stored in memory (¿OTP?) on the Controller. + +Each device has a different way to interpret the Calibration Data.
+EG. A Nunchuck has one joystick, and an accelerometer ...whereas a Classic Controller has 2 joysticks and 2 analogue buttons. + +I have personally found the calibration data to be inaccurate (when compared to actual readings), I guess Controllers drift over the years‽ +If the factory-values LIMIT movement, this is easily resolved - by expanding them on-the-fly.
+BUT, I have seen Controllers with factory calibration data that suggests the limits are FURTHER than the joystick can reach ...and this requires a full re-calibration of the Controller! + +Probably the best way to calibrate is to: +* Take a/some reading(s) while the Controller is 'at rest', IE. perfectly still and level. +* Move the Controller to all extremes and store the extreme {peak/trough} values. + +Nintendo (allegedly) take the 'at rest' reading immediately after the Controller is connected, and a 're-calibration' can be performed at any time by pressing {`A`, `B`, `+`, `-`} at the same time, for at least 3 seconds. Although I have no details on what this actually does. + +### This tool calibrates as such: +* When the Controller is first recognised + * The factory Calibration data is used to decide the Centre/Middle position and extreme values (eg. far-left & far-right) for each analogue Control +* Long-OK button press (on the FlipperZero) ...Do NOT touch ANY of the analogue controllers while you are pressing Long-OK + * Start the calibrate button flashing + * Take the current reading as the Centre position + * Set the range limits to "no range" + * You must now move the Control between its extremes, so the code can work out the new Calibration/range/peak+trough values + * When done, press Short-OK to end Software Calibration mode +* Short-OK button press (on the FlipperZero) ...Do NOT touch ANY of the analogue controllers while you are pressing Short-OK + * Stop the calibrate button flashing + * Calibrate the centre position of all analogue controls (accelerometers not supported (yet)) + +# Screen: DEBUG +
+On any screen (except SPLASH) you may press Long-Down to enter Debug mode. + +You can (at any time) attach to the FlipperZero (via USB) with a serial console {`minicom`, `putty`, whatever} and start the `log` function to see the debug messages. + +When you enter the DEBUG screen, the real-time scanner will be stopped. And the following keys made available: +* Up - Attempt to initialise the attached Controller +* OK - Take a reading from the attached Controller +* Long-Down - Restart the real-time scanner and return to the WAIT screen + +You can limit the messages at compile-time [see `./info.sh`], or at runtime [FZ->Settings->System->LogLevel]
+ +[This is probably irrelevant since the introduction of FAP support]
+If you have memory issues, limiting the messages at compile-time will make the plugin smaller.
+But (¿obviously?) the more you limit the messsages, the less debug information will be sent to the logger. + +# TODO + +* FZ Bug: At the time of writing this, there are problems with the i2c FZ functions [qv `i2c_workaround.c`] + diff --git a/applications/plugins/wii_ec_anal/README.txt b/applications/plugins/wii_ec_anal/README.txt new file mode 100644 index 000000000..e7ebe7a4c --- /dev/null +++ b/applications/plugins/wii_ec_anal/README.txt @@ -0,0 +1,67 @@ + ,-------. +---( Files )--- + `-------' + + README.md - User Manual : Body [github markdown] + _images/ - User Manual : Images + _images/GIMP/ - User Manual : GIMP image masters + + LICENSE - Tech Docs : MIT Licence file + README.txt - Tech Docs : Dev notes + notes.txt - Tech Docs : Random dev notes + info.sh - Tech Docs : Retrieve info from source code + + application.fam - FAP : Header file + WiiEC.png - FAP : Icon {10x10} + + gfx/ - Analyser : Images [generated by bc_image_tool] + wii_anal.c|h - Analyser : Main application + wii_anal_ec.c|h - Analyser : Extension controller actions + wii_anal_keys.c|h - Analyser : Keyboard handling + wii_anal_lcd.c|h - Analyser : LCD handling + + i2c_workaround.h - Temporary workaround for i2c bug in FZ code + err.h - Errors + bc_logging.h - Logging macros - especially LOG_LEVEL + + wii_i2c.c|h - i2c functionality + + wii_ec.c|h - Extension Controller basic functions + wii_ec_macros.h - Bespoke Extension Controller handy-dandy MACROs + wii_ec_classic.c|h - EC: Classic Controller Pro scene + wii_ec_nunchuck.c|h - EC: Nunchuck scene + wii_ec_udraw.c|h - EC: UDraw scene - not written + + ,----------------------------------. +---( Adding a new Extension Controller )--- + `----------------------------------' + +//! I'll finish this when I write the UDraw code + +Create a new Extension Controller called "mydev" + +Create wii_ec_mydev.c and wii_ec_mydev.h + +In wii_ec_mydev.c|h + Create the functions [& prototypes] + bool mydev_init (wiiEC_t* const) ; // Additional initialisation code + void mydev_decode (wiiEC_t* const) ; // Decode controller input data + void mydev_msg (wiiEC_t* const, FuriMessageQueue* const) ; // Put event messages in the event queue + void mydev_calib (wiiEC_t* const, ecCalib_t) ; // Controller calibration function + void mydev_show (Canvas* const, state_t* const) ; // Scene LCD display + bool mydev_key (const eventMsg_t* const, state_t* const) ; // Scene key controls + +In wii_ec.h + Include the new header + #include "wii_ec_mydev.h" + Add a perhipheral id to enum ecPid + PID_MYDEV + +In wii_anal.h + As a scene name to enum scene + SCENE_MYDEV + +In wii_ec.c + Add the device definition to the ecId[] array + [PID_MYDEV] = { {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, "My Device", SCENE_MYDEV, + mydev_init, mydev_decode, mydev_msg, mydev_calib, mydev_show, mydev_key }, diff --git a/applications/plugins/wii_ec_anal/WiiEC.png b/applications/plugins/wii_ec_anal/WiiEC.png new file mode 100644 index 000000000..6e1afcb0c Binary files /dev/null and b/applications/plugins/wii_ec_anal/WiiEC.png differ diff --git a/applications/plugins/wii_ec_anal/_image_tool/LICENSE b/applications/plugins/wii_ec_anal/_image_tool/LICENSE new file mode 100644 index 000000000..95e544a06 --- /dev/null +++ b/applications/plugins/wii_ec_anal/_image_tool/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 BlueChip + +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. diff --git a/applications/plugins/wii_ec_anal/_image_tool/README b/applications/plugins/wii_ec_anal/_image_tool/README new file mode 100644 index 000000000..979605a08 --- /dev/null +++ b/applications/plugins/wii_ec_anal/_image_tool/README @@ -0,0 +1,30 @@ +1. Prepare the image + a. Open your *black and white* image in GIMP + b. File -> Export As + filename: EXAMPLE.c + Type : "C source code" + [Export] + prefixed name: gimp_image + Comment : + [x] Use GLib types + [ ] <> + Opacity : 100% + [Export] + +2. Prepare conversion tool [stored in (eg.) /path/] + a. cp _convert*.* /path/ + b. cp EXAMPLE.c /path/ + +3. Run the conversion tool + a. cd /path/ + b. ./_convert.sh EXAMPLE.c + +4. All being well, you will see an ascii version of your image. + If not, then you're gonna have to submit a bug report + +5. You should now have a directory called img_/ + In that directory should be + img_EXAMPLE.c - The data for your new image + img_*.c - The data for other images + images.h - A header for ALL images that have been created in this directory + images.c - A sample FlipperZero show() function [not optimised] diff --git a/applications/plugins/wii_ec_anal/_image_tool/_convert.c b/applications/plugins/wii_ec_anal/_image_tool/_convert.c new file mode 100644 index 000000000..57deeb083 --- /dev/null +++ b/applications/plugins/wii_ec_anal/_image_tool/_convert.c @@ -0,0 +1,148 @@ +#include +#include +#include +#include + +int main(int argc, char* argv[]) { + const unsigned char* pp = NULL; + uint32_t pix = 0; + int bit = 0; + + uint8_t b = 0; + uint8_t bcnt = 0; + + unsigned int lcnt = 0; + static const int lmax = 16; // max hex values per line + + uint8_t* buf = NULL; + uint8_t* bp = NULL; + unsigned int blen = 0; + + uint8_t* cmp = NULL; + uint8_t* cp = NULL; + unsigned int clen = 0; + uint8_t ctag = 0xFF; + uint32_t tag[256] = {0}; + uint32_t tmax = UINT32_MAX; + + unsigned int x, y, z; + + const char* name = argv[1]; + FILE* fh = fopen(argv[2], "wb"); + + uint32_t white = 0xFF; + + int rv = 0; // assume success + + // allocate buffers + blen = ((img.w * img.h) + 0x7) >> 3; + bp = (buf = calloc(blen + 1, 1)); + cp = (cmp = calloc(blen + 4, 1)); + + // sanity check + if(!fh || !buf || !cmp) { + printf("! fopen() or malloc() fail.\n"); + rv = 255; + goto bail; + } + + // Find white value + for(x = 1; x < img.bpp; x++) white = (white << 8) | 0xFF; + + // build bit pattern + // create the comment as we go + for(pp = img.b, y = 0; y < img.h; y++) { + fprintf(fh, "// "); + for(x = 0; x < img.w; x++) { + // read pixel + for(pix = 0, z = 0; z < img.bpp; pix = (pix << 8) | *pp++, z++) + ; + // get bit and draw + if(pix < white) { + b = (b << 1) | 1; + fprintf(fh, "##"); + } else { + b <<= 1; + fprintf(fh, ".."); + } + // got byte + if((++bcnt) == 8) { + *bp++ = b; + tag[b]++; + bcnt = (b = 0); + } + } + fprintf(fh, "\n"); + } + fprintf(fh, "\n"); + // padding + if(bcnt) { + b <<= (bcnt = 8 - bcnt); + *bp++ = b; + tag[b]++; + } + // Kill the compression + *bp = ~bp[-1]; // https://youtube.com/clip/Ugkx-JZIr16hETy7hz_H6yIdKPtxVe8C5w_V + + // Byte run length compression + // Find a good tag + for(x = 0; tmax && (x < 256); x++) { + if(tag[x] < tmax) { + tmax = tag[x]; + ctag = x; + } + } + + // compress the data + for(bp = buf, x = 0; (clen < blen) && (x < blen); x++) { + // need at least 4 the same to be worth it + // must compress tag (if it occurs) + if((bp[x] == bp[x + 1]) && (bp[x] == bp[x + 2]) && (bp[x] == bp[x + 3]) || + (bp[x] == ctag)) { + for(y = 1; (y < 255) && (bp[x] == bp[x + y]); y++) + ; + *cp++ = ctag; // tag + *cp++ = y; // length + *cp++ = bp[x]; // byte + x += y - 1; + clen += 3; + } else { + *cp++ = bp[x]; + clen++; + } + } + + // create struct + fprintf(fh, "#include \"images.h\"\n\n"); + fprintf(fh, "const image_t img_%s = { %d, %d, ", name, img.w, img.h); + + if(clen < blen) { // dump compressed? + fprintf( + fh, + "true, %d, 0x%02X, { // orig:%d, comp:%.2f%%\n\t", + clen, + ctag, + blen, + 100.0 - ((clen * 100.0) / blen)); + for(x = 0; x < clen; x++) + if(x == clen - 1) + fprintf(fh, "0x%02X\n}};\n", cmp[x]); + else + fprintf(fh, "0x%02X%s", cmp[x], (!((x + 1) % 16)) ? ",\n\t" : ", "); + + } else { // dump UNcompressed + fprintf(fh, "false, %d, 0, {\n\t", blen); + for(x = 0; x < blen; x++) + if(x == blen - 1) + fprintf(fh, "0x%02X\n}};\n", buf[x]); + else + fprintf(fh, "0x%02X%s", buf[x], (!((x + 1) % 16)) ? ",\n\t" : ", "); + } + +bail: + if(fh) fclose(fh); + if(buf) free(buf); + if(cmp) free(cmp); + + return rv; +} diff --git a/applications/plugins/wii_ec_anal/_image_tool/_convert.sh b/applications/plugins/wii_ec_anal/_image_tool/_convert.sh new file mode 100644 index 000000000..aaa7977b5 --- /dev/null +++ b/applications/plugins/wii_ec_anal/_image_tool/_convert.sh @@ -0,0 +1,79 @@ +#!/bin/bash + +[ -z $1 ] && { + echo "Specify an image" + echo "gimp -> export -> c source file -> [x] gunit names" + exit 2 +} + +echo $* + +for N in $* ; do + + [ ! -f $N ] && { + echo "!! File missing $N" + continue + } + + # filename (sans extension) + FN=$(basename -- "$N") + EXT="${FN##*.}" + NAME="${FN%.*}" + + OUTDIR=img_/ + mkdir -p ${OUTDIR} + + HDR=${OUTDIR}/images.h + SRC=${OUTDIR}/images.c + + OUT=${OUTDIR}/img_${NAME}.c + + echo -e "\n¦${N}¦ == ¦${NAME}¦ -> ¦${OUT}¦" + + TESTX=test_${NAME} + TESTC=test_${NAME}.c + + # compile name + CONV=${NAME}_ + + # clean up gimp output + sed -e "s/gimp_image/img/g" \ + -e 's/guint8/unsigned char/g' \ + -e 's/width/w/g' \ + -e 's/height/h/g' \ + -e 's/bytes_per_pixel/bpp/g' \ + -e 's/pixel_data/b/g' \ + -e 's/guint/unsigned int/g' \ + $N \ + | grep -v ^/ \ + | grep -v ^$ \ + > ${CONV}.c + + # append conversion code + cat _convert.c >> ${CONV}.c + + # compile & run converter + rm -f ${CONV} + gcc ${CONV}.c -DIMGTEST -o ${CONV} + ./${CONV} ${NAME} ${OUT} + rm -f ${CONV} ${CONV}.c + + # (create &) update header + [[ ! -f ${HDR} ]] && cp _convert_images.h ${HDR} + sed -i "/ img_${NAME};/d" ${HDR} + sed -i "s#//\[TAG\]#//\[TAG\]\nextern const image_t img_${NAME};#" ${HDR} + + # sample FZ code + [[ ! -f images.c ]] && cp _convert_images.c ${SRC} + + # test + ROOT=${PWD} + pushd ${OUTDIR} >/dev/null + sed "s/zzz/${NAME}/" ${ROOT}/_convert_test.c > ${TESTC} + rm -f ${TESTX} + gcc ${TESTC} ${OUT##*/} -DIMGTEST -o ${TESTX} + ./${TESTX} + rm -f ${TESTX} ${TESTC} + popd >/dev/null + +done diff --git a/applications/plugins/wii_ec_anal/_image_tool/_convert_images.c b/applications/plugins/wii_ec_anal/_image_tool/_convert_images.c new file mode 100644 index 000000000..e8ab899f7 --- /dev/null +++ b/applications/plugins/wii_ec_anal/_image_tool/_convert_images.c @@ -0,0 +1,137 @@ +#include // GUI (screen/keyboard) API + +#include "images.h" + +//----------------------------------------------------------------------------- ---------------------------------------- +static Canvas* _canvas; +static uint8_t _tlx; +static uint8_t _tly; + +static uint8_t _x; +static uint8_t _y; + +static const image_t* _img; + +static bool _blk; +static Color _set; +static Color _clr; + +//+============================================================================ +static void _showByteSet(const uint8_t b) { + for(uint8_t m = 0x80; m; m >>= 1) { + if(b & m) // plot only SET bits + canvas_draw_dot(_canvas, (_tlx + _x), (_tly + _y)); + if(((++_x) == _img->w) && !(_x = 0) && ((++_y) == _img->h)) break; + } +} + +//+============================================================================ +static void _showByteClr(const uint8_t b) { + for(uint8_t m = 0x80; m; m >>= 1) { + if(!(b & m)) // plot only CLR bits + canvas_draw_dot(_canvas, (_tlx + _x), (_tly + _y)); + if(((++_x) == _img->w) && !(_x = 0) && ((++_y) == _img->h)) break; + } +} + +//+============================================================================ +static void _showByteAll(const uint8_t b) { + for(uint8_t m = 0x80; m; m >>= 1) { + if((!!(b & m)) ^ _blk) { // Change colour only when required + canvas_set_color(_canvas, ((b & m) ? _set : _clr)); + _blk = !_blk; + } + canvas_draw_dot(_canvas, (_tlx + _x), (_tly + _y)); + if(((++_x) == _img->w) && !(_x = 0) && ((++_y) == _img->h)) break; + } +} + +//+============================================================================ +// available modes are SHOW_SET_BLK - plot image pixels that are SET in BLACK +// SHOW_XOR - same as SET_BLACK +// SHOW_SET_WHT - plot image pixels that are SET in WHITE +// SHOW_CLR_BLK - plot image pixels that are CLEAR in BLACK +// SHOW_CLR_WHT - plot image pixels that are CLEAR in WHITE +// SHOW_ALL - plot all images pixels as they are +// SHOW_ALL_INV - plot all images pixels inverted +// +void show( + Canvas* const canvas, + const uint8_t tlx, + const uint8_t tly, + const image_t* img, + const showMode_t mode) { + void (*fnShow)(const uint8_t) = NULL; + + const uint8_t* bp = img->data; + + // code size optimisation + switch(mode & SHOW_INV_) { + case SHOW_NRM_: + _set = ColorBlack; + _clr = ColorWhite; + break; + + case SHOW_INV_: + _set = ColorWhite; + _clr = ColorBlack; + break; + + case SHOW_BLK_: + canvas_set_color(canvas, ColorBlack); + break; + + case SHOW_WHT_: + canvas_set_color(canvas, ColorWhite); + break; + } + switch(mode & SHOW_INV_) { + case SHOW_NRM_: + case SHOW_INV_: + fnShow = _showByteAll; + canvas_set_color(canvas, ColorWhite); + _blk = 0; + break; + + case SHOW_BLK_: + case SHOW_WHT_: + switch(mode & SHOW_ALL_) { + case SHOW_SET_: + fnShow = _showByteSet; + break; + case SHOW_CLR_: + fnShow = _showByteClr; + break; + } + break; + } + furi_check(fnShow); + + // I want nested functions! + _canvas = canvas; + _img = img; + _tlx = tlx; + _tly = tly; + _x = 0; + _y = 0; + + // Compressed + if(img->c) { + for(unsigned int i = 0; i < img->len; i++, bp++) { + // Compressed data? {tag, length, value} + if(*bp == img->tag) { + for(uint16_t c = 0; c < bp[1]; c++) fnShow(bp[2]); + bp += 3 - 1; + i += 3 - 1; + + // Uncompressed byte + } else { + fnShow(*bp); + } + } + + // Not compressed + } else { + for(unsigned int i = 0; i < img->len; i++, bp++) fnShow(*bp); + } +} diff --git a/applications/plugins/wii_ec_anal/_image_tool/_convert_images.h b/applications/plugins/wii_ec_anal/_image_tool/_convert_images.h new file mode 100644 index 000000000..1743cb409 --- /dev/null +++ b/applications/plugins/wii_ec_anal/_image_tool/_convert_images.h @@ -0,0 +1,53 @@ +#ifndef IMAGES_H_ +#define IMAGES_H_ + +#include +#include + +//----------------------------------------------------------------------------- ---------------------------------------- +typedef enum showMode { + // {INV:--:WHT:BLK::--:--:CLR:SET} + SHOW_SET_ = 0x01, + SHOW_CLR_ = 0x02, + SHOW_ALL_ = SHOW_SET_ | SHOW_CLR_, + + SHOW_BLK_ = 0x10, + SHOW_WHT_ = 0x20, + SHOW_NRM_ = 0x00, + SHOW_INV_ = SHOW_BLK_ | SHOW_WHT_, + + SHOW_SET_BLK = SHOW_SET_ | SHOW_BLK_, + SHOW_SET_WHT = SHOW_SET_ | SHOW_WHT_, + + SHOW_CLR_BLK = SHOW_CLR_ | SHOW_BLK_, + SHOW_CLR_WHT = SHOW_CLR_ | SHOW_WHT_, + + SHOW_ALL = SHOW_ALL_ | SHOW_NRM_, + SHOW_ALL_INV = SHOW_ALL_ | SHOW_INV_, +} showMode_t; + +//----------------------------------------------------------------------------- ---------------------------------------- +typedef struct image { + uint8_t w; // width + uint8_t h; // height + bool c; // compressed? + uint16_t len; // image data length + uint8_t tag; // rle tag + uint8_t data[]; // image data +} image_t; + +//----------------------------------------------------------------------------- ---------------------------------------- +//[TAG] + +//----------------------------------------------------------------------------- ---------------------------------------- +#ifndef IMGTEST +#include +void show( + Canvas* const canvas, + const uint8_t tlx, + const uint8_t tly, + const image_t* img, + const showMode_t mode); +#endif + +#endif //IMAGES_H_ diff --git a/applications/plugins/wii_ec_anal/_image_tool/_convert_test.c b/applications/plugins/wii_ec_anal/_image_tool/_convert_test.c new file mode 100644 index 000000000..fdc2ee946 --- /dev/null +++ b/applications/plugins/wii_ec_anal/_image_tool/_convert_test.c @@ -0,0 +1,59 @@ +#include +#include + +#include "images.h" + +//----------------------------------------------------------------------------- +// This will be the plot function out of your graphics library +// +#define PLOT(x, y, c) \ + do { \ + printf("%s", (c ? "#" : ".")); \ + if(x == img->w - 1) printf("\n"); \ + } while(0) + +//+============================================================================ +// The pain we endure to avoid code duplication cleanly +// +#define PLOTBYTE(b) \ + do { \ + for(uint8_t m = 0x80; m; m >>= 1) { \ + PLOT(x, y, (b & m)); \ + if(((++x) == img->w) && !(x = 0) && ((++y) == img->h)) break; \ + } \ + } while(0) + +void show(const image_t* img) { + // Some variables + const uint8_t* bp = img->data; + unsigned int x = 0; + unsigned int y = 0; + + // Compressed + if(img->c) { + for(unsigned int i = 0; i < img->len; i++, bp++) { + // Compressed data? {tag, length, value} + if(*bp == img->tag) { + for(uint16_t c = 0; c < bp[1]; c++) PLOTBYTE(bp[2]); + bp += 3 - 1; + i += 3 - 1; + + // Uncompressed byte + } else { + PLOTBYTE(*bp); + } + } + + // Not compressed + } else { + for(unsigned int i = 0; i < img->len; i++, bp++) PLOTBYTE(*bp); + } +} + +#undef PLOTBYTE + +//+============================================================================ +int main(void) { + show(&img_zzz); + return 0; +} diff --git a/applications/plugins/wii_ec_anal/_images/CLASSIC.png b/applications/plugins/wii_ec_anal/_images/CLASSIC.png new file mode 100644 index 000000000..aa5318b33 Binary files /dev/null and b/applications/plugins/wii_ec_anal/_images/CLASSIC.png differ diff --git a/applications/plugins/wii_ec_anal/_images/CLASSIC_N.png b/applications/plugins/wii_ec_anal/_images/CLASSIC_N.png new file mode 100644 index 000000000..24f4ac225 Binary files /dev/null and b/applications/plugins/wii_ec_anal/_images/CLASSIC_N.png differ diff --git a/applications/plugins/wii_ec_anal/_images/DEBUG.png b/applications/plugins/wii_ec_anal/_images/DEBUG.png new file mode 100644 index 000000000..bca35c693 Binary files /dev/null and b/applications/plugins/wii_ec_anal/_images/DEBUG.png differ diff --git a/applications/plugins/wii_ec_anal/_images/DUMP.png b/applications/plugins/wii_ec_anal/_images/DUMP.png new file mode 100644 index 000000000..dc9328aab Binary files /dev/null and b/applications/plugins/wii_ec_anal/_images/DUMP.png differ diff --git a/applications/plugins/wii_ec_anal/_images/GIMP/Nunchuck_acc.xcf b/applications/plugins/wii_ec_anal/_images/GIMP/Nunchuck_acc.xcf new file mode 100644 index 000000000..67f70139e Binary files /dev/null and b/applications/plugins/wii_ec_anal/_images/GIMP/Nunchuck_acc.xcf differ diff --git a/applications/plugins/wii_ec_anal/_images/GIMP/RIP.xcf b/applications/plugins/wii_ec_anal/_images/GIMP/RIP.xcf new file mode 100644 index 000000000..0058fe9c8 Binary files /dev/null and b/applications/plugins/wii_ec_anal/_images/GIMP/RIP.xcf differ diff --git a/applications/plugins/wii_ec_anal/_images/GIMP/Wiring.xcf b/applications/plugins/wii_ec_anal/_images/GIMP/Wiring.xcf new file mode 100644 index 000000000..aa8078db8 Binary files /dev/null and b/applications/plugins/wii_ec_anal/_images/GIMP/Wiring.xcf differ diff --git a/applications/plugins/wii_ec_anal/_images/GIMP/classic.xcf b/applications/plugins/wii_ec_anal/_images/GIMP/classic.xcf new file mode 100644 index 000000000..6fd152675 Binary files /dev/null and b/applications/plugins/wii_ec_anal/_images/GIMP/classic.xcf differ diff --git a/applications/plugins/wii_ec_anal/_images/GIMP/csLogo.xcf b/applications/plugins/wii_ec_anal/_images/GIMP/csLogo.xcf new file mode 100644 index 000000000..f4e33844a Binary files /dev/null and b/applications/plugins/wii_ec_anal/_images/GIMP/csLogo.xcf differ diff --git a/applications/plugins/wii_ec_anal/_images/GIMP/fonts.xcf b/applications/plugins/wii_ec_anal/_images/GIMP/fonts.xcf new file mode 100644 index 000000000..d05d03fc7 Binary files /dev/null and b/applications/plugins/wii_ec_anal/_images/GIMP/fonts.xcf differ diff --git a/applications/plugins/wii_ec_anal/_images/GIMP/frame.xcf b/applications/plugins/wii_ec_anal/_images/GIMP/frame.xcf new file mode 100644 index 000000000..31705cf72 Binary files /dev/null and b/applications/plugins/wii_ec_anal/_images/GIMP/frame.xcf differ diff --git a/applications/plugins/wii_ec_anal/_images/GIMP/port.xcf b/applications/plugins/wii_ec_anal/_images/GIMP/port.xcf new file mode 100644 index 000000000..10fcd2de2 Binary files /dev/null and b/applications/plugins/wii_ec_anal/_images/GIMP/port.xcf differ diff --git a/applications/plugins/wii_ec_anal/_images/GIMP/social.xcf b/applications/plugins/wii_ec_anal/_images/GIMP/social.xcf new file mode 100644 index 000000000..377eaa63b Binary files /dev/null and b/applications/plugins/wii_ec_anal/_images/GIMP/social.xcf differ diff --git a/applications/plugins/wii_ec_anal/_images/NUNCHUCK.png b/applications/plugins/wii_ec_anal/_images/NUNCHUCK.png new file mode 100644 index 000000000..bc31ae386 Binary files /dev/null and b/applications/plugins/wii_ec_anal/_images/NUNCHUCK.png differ diff --git a/applications/plugins/wii_ec_anal/_images/NUNCHUCK_acc.png b/applications/plugins/wii_ec_anal/_images/NUNCHUCK_acc.png new file mode 100644 index 000000000..895c85e4c Binary files /dev/null and b/applications/plugins/wii_ec_anal/_images/NUNCHUCK_acc.png differ diff --git a/applications/plugins/wii_ec_anal/_images/NUNCHUCK_anal.png b/applications/plugins/wii_ec_anal/_images/NUNCHUCK_anal.png new file mode 100644 index 000000000..e821d7ee2 Binary files /dev/null and b/applications/plugins/wii_ec_anal/_images/NUNCHUCK_anal.png differ diff --git a/applications/plugins/wii_ec_anal/_images/NUNCHUCK_cal.gif b/applications/plugins/wii_ec_anal/_images/NUNCHUCK_cal.gif new file mode 100644 index 000000000..72d807a54 Binary files /dev/null and b/applications/plugins/wii_ec_anal/_images/NUNCHUCK_cal.gif differ diff --git a/applications/plugins/wii_ec_anal/_images/NUNCHUCK_cal.png b/applications/plugins/wii_ec_anal/_images/NUNCHUCK_cal.png new file mode 100644 index 000000000..f9d34bb93 Binary files /dev/null and b/applications/plugins/wii_ec_anal/_images/NUNCHUCK_cal.png differ diff --git a/applications/plugins/wii_ec_anal/_images/Nunchucky.png b/applications/plugins/wii_ec_anal/_images/Nunchucky.png new file mode 100644 index 000000000..3af395da6 Binary files /dev/null and b/applications/plugins/wii_ec_anal/_images/Nunchucky.png differ diff --git a/applications/plugins/wii_ec_anal/_images/RIP.png b/applications/plugins/wii_ec_anal/_images/RIP.png new file mode 100644 index 000000000..0acfe0c00 Binary files /dev/null and b/applications/plugins/wii_ec_anal/_images/RIP.png differ diff --git a/applications/plugins/wii_ec_anal/_images/SPLASH.png b/applications/plugins/wii_ec_anal/_images/SPLASH.png new file mode 100644 index 000000000..a5c3f093a Binary files /dev/null and b/applications/plugins/wii_ec_anal/_images/SPLASH.png differ diff --git a/applications/plugins/wii_ec_anal/_images/WAIT.png b/applications/plugins/wii_ec_anal/_images/WAIT.png new file mode 100644 index 000000000..776edc3f1 Binary files /dev/null and b/applications/plugins/wii_ec_anal/_images/WAIT.png differ diff --git a/applications/plugins/wii_ec_anal/_images/WiiChuck.png b/applications/plugins/wii_ec_anal/_images/WiiChuck.png new file mode 100644 index 000000000..532ce3096 Binary files /dev/null and b/applications/plugins/wii_ec_anal/_images/WiiChuck.png differ diff --git a/applications/plugins/wii_ec_anal/_images/Wiring.png b/applications/plugins/wii_ec_anal/_images/Wiring.png new file mode 100644 index 000000000..300c07ee4 Binary files /dev/null and b/applications/plugins/wii_ec_anal/_images/Wiring.png differ diff --git a/applications/plugins/wii_ec_anal/_images/plug.png b/applications/plugins/wii_ec_anal/_images/plug.png new file mode 100644 index 000000000..c418f43b1 Binary files /dev/null and b/applications/plugins/wii_ec_anal/_images/plug.png differ diff --git a/applications/plugins/wii_ec_anal/_images/social.png b/applications/plugins/wii_ec_anal/_images/social.png new file mode 100644 index 000000000..1d3eddcc5 Binary files /dev/null and b/applications/plugins/wii_ec_anal/_images/social.png differ diff --git a/applications/plugins/wii_ec_anal/application.fam b/applications/plugins/wii_ec_anal/application.fam new file mode 100644 index 000000000..42ed7e979 --- /dev/null +++ b/applications/plugins/wii_ec_anal/application.fam @@ -0,0 +1,36 @@ +# qv. https://github.com/flipperdevices/flipperzero-firmware/blob/dev/documentation/AppManifests.md + +App( + # --- App Info + appid="Wii_EC_Analyser", + name="Wii EC Analyser", + + # --- Entry point + apptype=FlipperAppType.EXTERNAL, + entry_point="wii_ec_anal", + + # --- Interaction + cdefines=["APP_WII_EC_ANAL"], + requires=[ + "gui", + ], + +# conflicts="", +# sdk_headers="", + + # --- Run-time info + stack_size=2 * 1024, + order=20, + + # --- FAP details + sources=["wii_*.c", "gfx/*.c"], + +# fap_weburl="https://github.com/csBlueChip/FlipperZero_plugin_WiiChuck/", +# fap_author="BlueChip", + +# fap_description="Wii Extension Controller Protocol Analyser", +# fap_version=(1,0), + + fap_icon="WiiEC.png", + fap_category="Misc", +) diff --git a/applications/plugins/wii_ec_anal/bc_logging.h b/applications/plugins/wii_ec_anal/bc_logging.h new file mode 100644 index 000000000..73dda80bd --- /dev/null +++ b/applications/plugins/wii_ec_anal/bc_logging.h @@ -0,0 +1,70 @@ +#ifndef BC_LOGGING_H_ +#define BC_LOGGING_H_ + +#include +#include "err.h" // appName + +//! WARNING: There is a bug in Furi such that if you crank LOG_LEVEL up to 6=TRACE +//! AND you have menu->settings->system->logLevel = trace +//! THEN this program will cause the FZ to crash when the plugin exits! +#define LOG_LEVEL 4 + +//----------------------------------------------------------------------------- ---------------------------------------- +// The FlipperZero Settings->System menu allows you to set the logging level at RUN-time +// ... LOG_LEVEL lets you limit it at COMPILE-time +// +// FURI logging has 6 levels (numbered 1 thru 6} +// 1. None +// 2. Errors FURI_LOG_E +// 3. Warnings FURI_LOG_W +// 4. Information FURI_LOG_I +// 5. Debug FURI_LOG_D +// 6. Trace FURI_LOG_T +// +// --> furi/core/log.h +// + +// The FlipperZero Settings->System menu allows you to set the logging level at RUN-time +// This lets you limit it at COMPILE-time +#ifndef LOG_LEVEL +#define LOG_LEVEL 6 // default = full logging +#endif + +#if(LOG_LEVEL < 2) +#undef FURI_LOG_E +#define FURI_LOG_E(tag, fmt, ...) +#endif + +#if(LOG_LEVEL < 3) +#undef FURI_LOG_W +#define FURI_LOG_W(tag, fmt, ...) +#endif + +#if(LOG_LEVEL < 4) +#undef FURI_LOG_I +#define FURI_LOG_I(tag, fmt, ...) +#endif + +#if(LOG_LEVEL < 5) +#undef FURI_LOG_D +#define FURI_LOG_D(tag, fmt, ...) +#endif + +#if(LOG_LEVEL < 6) +#undef FURI_LOG_T +#define FURI_LOG_T(tag, fmt, ...) +#endif + +//---------------------------------------------------------- +// Logging helper macros +// +#define ERROR(fmt, ...) FURI_LOG_E(appName, fmt __VA_OPT__(, ) __VA_ARGS__) +#define WARN(fmt, ...) FURI_LOG_W(appName, fmt __VA_OPT__(, ) __VA_ARGS__) +#define INFO(fmt, ...) FURI_LOG_I(appName, fmt __VA_OPT__(, ) __VA_ARGS__) +#define DEBUG(fmt, ...) FURI_LOG_D(appName, fmt __VA_OPT__(, ) __VA_ARGS__) +#define TRACE(fmt, ...) FURI_LOG_T(appName, fmt __VA_OPT__(, ) __VA_ARGS__) + +#define ENTER TRACE("(+) %s", __func__) +#define LEAVE TRACE("(-) %s", __func__) + +#endif //BC_LOGGING_H_ diff --git a/applications/plugins/wii_ec_anal/err.h b/applications/plugins/wii_ec_anal/err.h new file mode 100644 index 000000000..5a25c93f8 --- /dev/null +++ b/applications/plugins/wii_ec_anal/err.h @@ -0,0 +1,72 @@ +// Avoid circular/nested/mulitple inclusion +#ifndef ERR_H_ +#define ERR_H_ + +//----------------------------------------------------------------------------- ---------------------------------------- +// Application name +// +static const char* const appName = "Wii_i2c"; //$ Name used in log files + +//----------------------------------------------------------------------------- ---------------------------------------- +// Error codes and messages +// + +// You should only ever (need to) edit this list +// ...Watch out for extraneous whitespace after the terminating backslashes +#define FOREACH_ES(esPrial) \ + /* The first line MUST define 'ERR_OK = 0' */ \ + esPrial(0, ERR_OK, "OK (no error)") \ + \ + esPrial(1, ERR_MALLOC_QUEUE, "malloc() fail - queue") esPrial( \ + 2, \ + ERR_MALLOC_STATE, \ + "malloc() fail - state") esPrial(3, ERR_MALLOC_TEXT, "malloc() fail - text") \ + esPrial(4, ERR_MALLOC_VIEW, "malloc() fail - viewport") esPrial( \ + 5, ERR_NO_MUTEX, "Cannot create mutex") esPrial(6, ERR_NO_GUI, "Cannot open GUI") \ + esPrial(7, ERR_NO_TIMER, "Cannot create timer") esPrial( \ + 8, ERR_NO_NOTIFY, "Cannot acquire notifications handle") \ + \ + esPrial(10, ERR_MUTEX_BLOCK, "Mutex block failed") esPrial( \ + 11, ERR_MUTEX_RELEASE, "Mutex release failed") \ + \ + esPrial(20, ERR_QUEUE_RTOS, "queue - Undefined RTOS error") \ + esPrial(21, DEBUG_QUEUE_TIMEOUT, "queue - Timeout") esPrial( \ + 22, ERR_QUEUE_RESOURCE, "queue - Resource not available") \ + esPrial(23, ERR_QUEUE_BADPRM, "queue - Bad parameter") esPrial( \ + 24, ERR_QUEUE_NOMEM, "queue - Out of memory") \ + esPrial(25, ERR_QUEUE_ISR, "queue - Banned in ISR") esPrial( \ + 26, ERR_QUEUE_UNK, "queue - Unknown") \ + \ + esPrial(30, WARN_SCAN_START, "Scan - Already started") \ + esPrial(31, WARN_SCAN_STOP, "Scan - Already stopped") \ + esPrial( \ + 32, \ + ERR_TIMER_START, \ + "Scan - Cannot start timer") \ + esPrial( \ + 33, \ + ERR_TIMER_STOP, \ + "Scan - Cannot stop timer") //[EOT] + +// Declare list extraction macros +#define ES_ENUM(num, ename, string) ename = num, +#define ES_STRING(num, ename, string) string "\r\n", + +// Build the enum +typedef enum err { FOREACH_ES(ES_ENUM) } err_t; + +// You need to '#define ERR_C_' in precisely ONE source file +#ifdef ERR_C_ +// Build the string list +const char* const wii_errs[] = {FOREACH_ES(ES_STRING)}; +#else +// Give access to string list +extern const char* const wii_errs[]; +#endif + +// This is a header file, clean up +#undef ES_ENUM +#undef ES_STRING +#undef FOREACH_ES + +#endif // ERR_H_ diff --git a/applications/plugins/wii_ec_anal/gfx/images.c b/applications/plugins/wii_ec_anal/gfx/images.c new file mode 100644 index 000000000..e8ab899f7 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/images.c @@ -0,0 +1,137 @@ +#include // GUI (screen/keyboard) API + +#include "images.h" + +//----------------------------------------------------------------------------- ---------------------------------------- +static Canvas* _canvas; +static uint8_t _tlx; +static uint8_t _tly; + +static uint8_t _x; +static uint8_t _y; + +static const image_t* _img; + +static bool _blk; +static Color _set; +static Color _clr; + +//+============================================================================ +static void _showByteSet(const uint8_t b) { + for(uint8_t m = 0x80; m; m >>= 1) { + if(b & m) // plot only SET bits + canvas_draw_dot(_canvas, (_tlx + _x), (_tly + _y)); + if(((++_x) == _img->w) && !(_x = 0) && ((++_y) == _img->h)) break; + } +} + +//+============================================================================ +static void _showByteClr(const uint8_t b) { + for(uint8_t m = 0x80; m; m >>= 1) { + if(!(b & m)) // plot only CLR bits + canvas_draw_dot(_canvas, (_tlx + _x), (_tly + _y)); + if(((++_x) == _img->w) && !(_x = 0) && ((++_y) == _img->h)) break; + } +} + +//+============================================================================ +static void _showByteAll(const uint8_t b) { + for(uint8_t m = 0x80; m; m >>= 1) { + if((!!(b & m)) ^ _blk) { // Change colour only when required + canvas_set_color(_canvas, ((b & m) ? _set : _clr)); + _blk = !_blk; + } + canvas_draw_dot(_canvas, (_tlx + _x), (_tly + _y)); + if(((++_x) == _img->w) && !(_x = 0) && ((++_y) == _img->h)) break; + } +} + +//+============================================================================ +// available modes are SHOW_SET_BLK - plot image pixels that are SET in BLACK +// SHOW_XOR - same as SET_BLACK +// SHOW_SET_WHT - plot image pixels that are SET in WHITE +// SHOW_CLR_BLK - plot image pixels that are CLEAR in BLACK +// SHOW_CLR_WHT - plot image pixels that are CLEAR in WHITE +// SHOW_ALL - plot all images pixels as they are +// SHOW_ALL_INV - plot all images pixels inverted +// +void show( + Canvas* const canvas, + const uint8_t tlx, + const uint8_t tly, + const image_t* img, + const showMode_t mode) { + void (*fnShow)(const uint8_t) = NULL; + + const uint8_t* bp = img->data; + + // code size optimisation + switch(mode & SHOW_INV_) { + case SHOW_NRM_: + _set = ColorBlack; + _clr = ColorWhite; + break; + + case SHOW_INV_: + _set = ColorWhite; + _clr = ColorBlack; + break; + + case SHOW_BLK_: + canvas_set_color(canvas, ColorBlack); + break; + + case SHOW_WHT_: + canvas_set_color(canvas, ColorWhite); + break; + } + switch(mode & SHOW_INV_) { + case SHOW_NRM_: + case SHOW_INV_: + fnShow = _showByteAll; + canvas_set_color(canvas, ColorWhite); + _blk = 0; + break; + + case SHOW_BLK_: + case SHOW_WHT_: + switch(mode & SHOW_ALL_) { + case SHOW_SET_: + fnShow = _showByteSet; + break; + case SHOW_CLR_: + fnShow = _showByteClr; + break; + } + break; + } + furi_check(fnShow); + + // I want nested functions! + _canvas = canvas; + _img = img; + _tlx = tlx; + _tly = tly; + _x = 0; + _y = 0; + + // Compressed + if(img->c) { + for(unsigned int i = 0; i < img->len; i++, bp++) { + // Compressed data? {tag, length, value} + if(*bp == img->tag) { + for(uint16_t c = 0; c < bp[1]; c++) fnShow(bp[2]); + bp += 3 - 1; + i += 3 - 1; + + // Uncompressed byte + } else { + fnShow(*bp); + } + } + + // Not compressed + } else { + for(unsigned int i = 0; i < img->len; i++, bp++) fnShow(*bp); + } +} diff --git a/applications/plugins/wii_ec_anal/gfx/images.h b/applications/plugins/wii_ec_anal/gfx/images.h new file mode 100644 index 000000000..d21909176 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/images.h @@ -0,0 +1,134 @@ +#ifndef IMAGES_H_ +#define IMAGES_H_ + +#include +#include + +//----------------------------------------------------------------------------- ---------------------------------------- +typedef enum showMode { + // {INV:--:WHT:BLK::--:--:CLR:SET} + SHOW_SET_ = 0x01, + SHOW_CLR_ = 0x02, + SHOW_ALL_ = SHOW_SET_ | SHOW_CLR_, + + SHOW_BLK_ = 0x10, + SHOW_WHT_ = 0x20, + SHOW_NRM_ = 0x00, + SHOW_INV_ = SHOW_BLK_ | SHOW_WHT_, + + SHOW_SET_BLK = SHOW_SET_ | SHOW_BLK_, + SHOW_SET_WHT = SHOW_SET_ | SHOW_WHT_, + + SHOW_CLR_BLK = SHOW_CLR_ | SHOW_BLK_, + SHOW_CLR_WHT = SHOW_CLR_ | SHOW_WHT_, + + SHOW_ALL = SHOW_ALL_ | SHOW_NRM_, + SHOW_ALL_INV = SHOW_ALL_ | SHOW_INV_, +} showMode_t; + +//----------------------------------------------------------------------------- ---------------------------------------- +typedef struct image { + uint8_t w; // width + uint8_t h; // height + bool c; // compressed? + uint16_t len; // image data length + uint8_t tag; // rle tag + uint8_t data[]; // image data +} image_t; + +//----------------------------------------------------------------------------- ---------------------------------------- +//[TAG] +extern const image_t img_csLogo_Small; +extern const image_t img_3x5_v; +extern const image_t img_3x5_9; +extern const image_t img_3x5_8; +extern const image_t img_3x5_7; +extern const image_t img_3x5_6; +extern const image_t img_3x5_5; +extern const image_t img_3x5_4; +extern const image_t img_3x5_3; +extern const image_t img_3x5_2; +extern const image_t img_3x5_1; +extern const image_t img_3x5_0; +extern const image_t img_key_Ui; +extern const image_t img_key_OKi; +extern const image_t img_RIP; +extern const image_t img_cc_trg_R4; +extern const image_t img_cc_trg_R3; +extern const image_t img_cc_trg_R2; +extern const image_t img_cc_trg_R1; +extern const image_t img_cc_trg_L4; +extern const image_t img_cc_trg_L3; +extern const image_t img_cc_trg_L2; +extern const image_t img_cc_trg_L1; +extern const image_t img_cc_Joy; +extern const image_t img_cc_Main; +extern const image_t img_cc_Cable; +extern const image_t img_key_Back; +extern const image_t img_key_OK; +extern const image_t img_6x8_Z; +extern const image_t img_6x8_Y; +extern const image_t img_6x8_X; +extern const image_t img_key_U; +extern const image_t img_key_D; +extern const image_t img_csLogo_FULL; +extern const image_t img_6x8_7; +extern const image_t img_key_R; +extern const image_t img_key_L; +extern const image_t img_5x7_7; +extern const image_t img_5x7_F; +extern const image_t img_5x7_E; +extern const image_t img_5x7_D; +extern const image_t img_5x7_C; +extern const image_t img_5x7_B; +extern const image_t img_5x7_A; +extern const image_t img_5x7_9; +extern const image_t img_5x7_8; +extern const image_t img_5x7_6; +extern const image_t img_5x7_5; +extern const image_t img_5x7_4; +extern const image_t img_5x7_3; +extern const image_t img_5x7_2; +extern const image_t img_5x7_1; +extern const image_t img_5x7_0; +extern const image_t img_6x8_v; +extern const image_t img_6x8_n; +extern const image_t img_6x8_G; +extern const image_t img_6x8_F; +extern const image_t img_6x8_E; +extern const image_t img_6x8_d; +extern const image_t img_6x8_C; +extern const image_t img_6x8_B; +extern const image_t img_6x8_A; +extern const image_t img_6x8_9; +extern const image_t img_6x8_8; +extern const image_t img_6x8_6; +extern const image_t img_6x8_5; +extern const image_t img_6x8_4; +extern const image_t img_6x8_3; +extern const image_t img_6x8_2; +extern const image_t img_6x8_1; +extern const image_t img_6x8_0; +extern const image_t img_ecp_SDA; +extern const image_t img_ecp_SCL; +extern const image_t img_ecp_port; +extern const image_t img_cc_pad_UD1; +extern const image_t img_cc_pad_LR1; +extern const image_t img_cc_btn_Y1; +extern const image_t img_cc_btn_X1; +extern const image_t img_cc_btn_B1; +extern const image_t img_cc_btn_A1; +extern const image_t img_6x8_D; + +//----------------------------------------------------------------------------- ---------------------------------------- +#ifndef IMGTEST +#include +void show( + Canvas* const canvas, + const uint8_t tlx, + const uint8_t tly, + const image_t* img, + const showMode_t mode); +#endif + +#endif //IMAGES_H_ diff --git a/applications/plugins/wii_ec_anal/gfx/img_3x5_0.c b/applications/plugins/wii_ec_anal/gfx/img_3x5_0.c new file mode 100644 index 000000000..8fc8e0e14 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_3x5_0.c @@ -0,0 +1,9 @@ +// ###### +// ##..## +// ##..## +// ##..## +// ###### + +#include "images.h" + +const image_t img_3x5_0 = {3, 5, false, 2, 0, {0xF6, 0xDE}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_3x5_1.c b/applications/plugins/wii_ec_anal/gfx/img_3x5_1.c new file mode 100644 index 000000000..8b7d4cf80 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_3x5_1.c @@ -0,0 +1,9 @@ +// ####.. +// ..##.. +// ..##.. +// ..##.. +// ###### + +#include "images.h" + +const image_t img_3x5_1 = {3, 5, false, 2, 0, {0xC9, 0x2E}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_3x5_2.c b/applications/plugins/wii_ec_anal/gfx/img_3x5_2.c new file mode 100644 index 000000000..89a81c75e --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_3x5_2.c @@ -0,0 +1,9 @@ +// ###### +// ....## +// ###### +// ##.... +// ###### + +#include "images.h" + +const image_t img_3x5_2 = {3, 5, false, 2, 0, {0xE7, 0xCE}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_3x5_3.c b/applications/plugins/wii_ec_anal/gfx/img_3x5_3.c new file mode 100644 index 000000000..97ff0478a --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_3x5_3.c @@ -0,0 +1,9 @@ +// ###### +// ....## +// ..#### +// ....## +// ###### + +#include "images.h" + +const image_t img_3x5_3 = {3, 5, false, 2, 0, {0xE5, 0x9E}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_3x5_4.c b/applications/plugins/wii_ec_anal/gfx/img_3x5_4.c new file mode 100644 index 000000000..2bbd9ef42 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_3x5_4.c @@ -0,0 +1,9 @@ +// ##.... +// ##..## +// ###### +// ....## +// ....## + +#include "images.h" + +const image_t img_3x5_4 = {3, 5, false, 2, 0, {0x97, 0x92}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_3x5_5.c b/applications/plugins/wii_ec_anal/gfx/img_3x5_5.c new file mode 100644 index 000000000..e0466f37a --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_3x5_5.c @@ -0,0 +1,9 @@ +// ###### +// ##.... +// ###### +// ....## +// ###### + +#include "images.h" + +const image_t img_3x5_5 = {3, 5, false, 2, 0, {0xF3, 0x9E}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_3x5_6.c b/applications/plugins/wii_ec_anal/gfx/img_3x5_6.c new file mode 100644 index 000000000..1b62caf72 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_3x5_6.c @@ -0,0 +1,9 @@ +// ####.. +// ##.... +// ###### +// ##..## +// ###### + +#include "images.h" + +const image_t img_3x5_6 = {3, 5, false, 2, 0, {0xD3, 0xDE}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_3x5_7.c b/applications/plugins/wii_ec_anal/gfx/img_3x5_7.c new file mode 100644 index 000000000..acfe57cf8 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_3x5_7.c @@ -0,0 +1,9 @@ +// ###### +// ....## +// ..##.. +// ..##.. +// ..##.. + +#include "images.h" + +const image_t img_3x5_7 = {3, 5, false, 2, 0, {0xE5, 0x24}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_3x5_8.c b/applications/plugins/wii_ec_anal/gfx/img_3x5_8.c new file mode 100644 index 000000000..31f32af52 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_3x5_8.c @@ -0,0 +1,9 @@ +// ###### +// ##..## +// ###### +// ##..## +// ###### + +#include "images.h" + +const image_t img_3x5_8 = {3, 5, false, 2, 0, {0xF7, 0xDE}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_3x5_9.c b/applications/plugins/wii_ec_anal/gfx/img_3x5_9.c new file mode 100644 index 000000000..4b1ba1e09 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_3x5_9.c @@ -0,0 +1,9 @@ +// ###### +// ##..## +// ###### +// ....## +// ..#### + +#include "images.h" + +const image_t img_3x5_9 = {3, 5, false, 2, 0, {0xF7, 0x96}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_3x5_v.c b/applications/plugins/wii_ec_anal/gfx/img_3x5_v.c new file mode 100644 index 000000000..2282e1697 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_3x5_v.c @@ -0,0 +1,9 @@ +// ...... +// ...... +// ##..## +// ##..## +// ..##.. + +#include "images.h" + +const image_t img_3x5_v = {3, 5, false, 2, 0, {0x02, 0xD4}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_5x7_0.c b/applications/plugins/wii_ec_anal/gfx/img_5x7_0.c new file mode 100644 index 000000000..7ae2186b3 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_5x7_0.c @@ -0,0 +1,11 @@ +// ..######.. +// ##......## +// ##....#### +// ##..##..## +// ####....## +// ##......## +// ..######.. + +#include "images.h" + +const image_t img_5x7_0 = {5, 7, false, 5, 0, {0x74, 0x67, 0x5C, 0xC5, 0xC0}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_5x7_1.c b/applications/plugins/wii_ec_anal/gfx/img_5x7_1.c new file mode 100644 index 000000000..c1a9cec74 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_5x7_1.c @@ -0,0 +1,11 @@ +// ..####.... +// ##..##.... +// ....##.... +// ....##.... +// ....##.... +// ....##.... +// ########## + +#include "images.h" + +const image_t img_5x7_1 = {5, 7, false, 5, 0, {0x65, 0x08, 0x42, 0x13, 0xE0}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_5x7_2.c b/applications/plugins/wii_ec_anal/gfx/img_5x7_2.c new file mode 100644 index 000000000..7fab90010 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_5x7_2.c @@ -0,0 +1,11 @@ +// ..######.. +// ##......## +// ........## +// ......##.. +// ....##.... +// ..##...... +// ########## + +#include "images.h" + +const image_t img_5x7_2 = {5, 7, false, 5, 0, {0x74, 0x42, 0x22, 0x23, 0xE0}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_5x7_3.c b/applications/plugins/wii_ec_anal/gfx/img_5x7_3.c new file mode 100644 index 000000000..2099bf795 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_5x7_3.c @@ -0,0 +1,11 @@ +// ..######.. +// ##......## +// ........## +// ....####.. +// ........## +// ##......## +// ..######.. + +#include "images.h" + +const image_t img_5x7_3 = {5, 7, false, 5, 0, {0x74, 0x42, 0x60, 0xC5, 0xC0}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_5x7_4.c b/applications/plugins/wii_ec_anal/gfx/img_5x7_4.c new file mode 100644 index 000000000..1eee4f07d --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_5x7_4.c @@ -0,0 +1,11 @@ +// ##........ +// ##........ +// ##....##.. +// ##....##.. +// ########## +// ......##.. +// ......##.. + +#include "images.h" + +const image_t img_5x7_4 = {5, 7, false, 5, 0, {0x84, 0x25, 0x2F, 0x88, 0x40}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_5x7_5.c b/applications/plugins/wii_ec_anal/gfx/img_5x7_5.c new file mode 100644 index 000000000..be1e54681 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_5x7_5.c @@ -0,0 +1,11 @@ +// ########## +// ##........ +// ##........ +// ########.. +// ........## +// ........## +// ########.. + +#include "images.h" + +const image_t img_5x7_5 = {5, 7, false, 5, 0, {0xFC, 0x21, 0xE0, 0x87, 0xC0}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_5x7_6.c b/applications/plugins/wii_ec_anal/gfx/img_5x7_6.c new file mode 100644 index 000000000..da155c1b5 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_5x7_6.c @@ -0,0 +1,11 @@ +// ..######.. +// ##........ +// ##........ +// ########.. +// ##......## +// ##......## +// ..######.. + +#include "images.h" + +const image_t img_5x7_6 = {5, 7, false, 5, 0, {0x74, 0x21, 0xE8, 0xC5, 0xC0}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_5x7_7.c b/applications/plugins/wii_ec_anal/gfx/img_5x7_7.c new file mode 100644 index 000000000..fde7e8ea2 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_5x7_7.c @@ -0,0 +1,11 @@ +// ########## +// ........## +// ......##.. +// ......##.. +// ....##.... +// ....##.... +// ....##.... + +#include "images.h" + +const image_t img_5x7_7 = {5, 7, false, 5, 0, {0xF8, 0x44, 0x22, 0x10, 0x80}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_5x7_8.c b/applications/plugins/wii_ec_anal/gfx/img_5x7_8.c new file mode 100644 index 000000000..aff178282 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_5x7_8.c @@ -0,0 +1,11 @@ +// ..######.. +// ##......## +// ##......## +// ..######.. +// ##......## +// ##......## +// ..######.. + +#include "images.h" + +const image_t img_5x7_8 = {5, 7, false, 5, 0, {0x74, 0x62, 0xE8, 0xC5, 0xC0}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_5x7_9.c b/applications/plugins/wii_ec_anal/gfx/img_5x7_9.c new file mode 100644 index 000000000..2417c57e8 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_5x7_9.c @@ -0,0 +1,11 @@ +// ..######.. +// ##......## +// ##......## +// ..######## +// ........## +// ........## +// ..######.. + +#include "images.h" + +const image_t img_5x7_9 = {5, 7, false, 5, 0, {0x74, 0x62, 0xF0, 0x85, 0xC0}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_5x7_A.c b/applications/plugins/wii_ec_anal/gfx/img_5x7_A.c new file mode 100644 index 000000000..910c034a2 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_5x7_A.c @@ -0,0 +1,11 @@ +// ..######.. +// ##......## +// ##......## +// ########## +// ##......## +// ##......## +// ##......## + +#include "images.h" + +const image_t img_5x7_A = {5, 7, false, 5, 0, {0x74, 0x63, 0xF8, 0xC6, 0x20}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_5x7_B.c b/applications/plugins/wii_ec_anal/gfx/img_5x7_B.c new file mode 100644 index 000000000..93808fee2 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_5x7_B.c @@ -0,0 +1,11 @@ +// ########.. +// ##......## +// ##......## +// ##..####.. +// ##......## +// ##......## +// ########.. + +#include "images.h" + +const image_t img_5x7_B = {5, 7, false, 5, 0, {0xF4, 0x63, 0x68, 0xC7, 0xC0}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_5x7_C.c b/applications/plugins/wii_ec_anal/gfx/img_5x7_C.c new file mode 100644 index 000000000..1438eaf44 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_5x7_C.c @@ -0,0 +1,11 @@ +// ..######.. +// ##......## +// ##........ +// ##........ +// ##........ +// ##......## +// ..######.. + +#include "images.h" + +const image_t img_5x7_C = {5, 7, false, 5, 0, {0x74, 0x61, 0x08, 0x45, 0xC0}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_5x7_D.c b/applications/plugins/wii_ec_anal/gfx/img_5x7_D.c new file mode 100644 index 000000000..9c6b590ee --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_5x7_D.c @@ -0,0 +1,11 @@ +// ..######.. +// ##..##..## +// ....##..## +// ....##..## +// ....##..## +// ##..##..## +// ..######.. + +#include "images.h" + +const image_t img_5x7_D = {5, 7, false, 5, 0, {0x75, 0x4A, 0x52, 0xD5, 0xC0}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_5x7_E.c b/applications/plugins/wii_ec_anal/gfx/img_5x7_E.c new file mode 100644 index 000000000..bc15fb240 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_5x7_E.c @@ -0,0 +1,11 @@ +// ########## +// ##........ +// ##........ +// ######.... +// ##........ +// ##........ +// ########## + +#include "images.h" + +const image_t img_5x7_E = {5, 7, false, 5, 0, {0xFC, 0x21, 0xC8, 0x43, 0xE0}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_5x7_F.c b/applications/plugins/wii_ec_anal/gfx/img_5x7_F.c new file mode 100644 index 000000000..e4ad0db69 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_5x7_F.c @@ -0,0 +1,11 @@ +// ########## +// ##........ +// ##........ +// ######.... +// ##........ +// ##........ +// ##........ + +#include "images.h" + +const image_t img_5x7_F = {5, 7, false, 5, 0, {0xFC, 0x21, 0xC8, 0x42, 0x00}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_6x8_0.c b/applications/plugins/wii_ec_anal/gfx/img_6x8_0.c new file mode 100644 index 000000000..952cf34d8 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_6x8_0.c @@ -0,0 +1,12 @@ +// ..########.. +// ############ +// ####....#### +// ####....#### +// ####....#### +// ####....#### +// ############ +// ..########.. + +#include "images.h" + +const image_t img_6x8_0 = {6, 8, false, 6, 0, {0x7B, 0xFC, 0xF3, 0xCF, 0x3F, 0xDE}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_6x8_1.c b/applications/plugins/wii_ec_anal/gfx/img_6x8_1.c new file mode 100644 index 000000000..846a6876c --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_6x8_1.c @@ -0,0 +1,12 @@ +// ..######.... +// ########.... +// ....####.... +// ....####.... +// ....####.... +// ....####.... +// ############ +// ############ + +#include "images.h" + +const image_t img_6x8_1 = {6, 8, false, 6, 0, {0x73, 0xC3, 0x0C, 0x30, 0xCF, 0xFF}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_6x8_2.c b/applications/plugins/wii_ec_anal/gfx/img_6x8_2.c new file mode 100644 index 000000000..4534bb67c --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_6x8_2.c @@ -0,0 +1,12 @@ +// ..########.. +// ############ +// ........#### +// ......###### +// ....####.... +// ..####...... +// ############ +// ############ + +#include "images.h" + +const image_t img_6x8_2 = {6, 8, false, 6, 0, {0x7B, 0xF0, 0xC7, 0x31, 0x8F, 0xFF}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_6x8_3.c b/applications/plugins/wii_ec_anal/gfx/img_6x8_3.c new file mode 100644 index 000000000..7e79eb03a --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_6x8_3.c @@ -0,0 +1,12 @@ +// ..########.. +// ############ +// ........#### +// ....######## +// ....######## +// ........#### +// ############ +// ..########.. + +#include "images.h" + +const image_t img_6x8_3 = {6, 8, false, 6, 0, {0x7B, 0xF0, 0xCF, 0x3C, 0x3F, 0xDE}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_6x8_4.c b/applications/plugins/wii_ec_anal/gfx/img_6x8_4.c new file mode 100644 index 000000000..324b036ce --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_6x8_4.c @@ -0,0 +1,12 @@ +// ####........ +// ####........ +// ####..####.. +// ####..####.. +// ############ +// ############ +// ......####.. +// ......####.. + +#include "images.h" + +const image_t img_6x8_4 = {6, 8, false, 6, 0, {0xC3, 0x0D, 0xB6, 0xFF, 0xF1, 0x86}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_6x8_5.c b/applications/plugins/wii_ec_anal/gfx/img_6x8_5.c new file mode 100644 index 000000000..cdfda5f2b --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_6x8_5.c @@ -0,0 +1,12 @@ +// ############ +// ############ +// ####........ +// ##########.. +// ############ +// ........#### +// ############ +// ##########.. + +#include "images.h" + +const image_t img_6x8_5 = {6, 8, false, 6, 0, {0xFF, 0xFC, 0x3E, 0xFC, 0x3F, 0xFE}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_6x8_6.c b/applications/plugins/wii_ec_anal/gfx/img_6x8_6.c new file mode 100644 index 000000000..781a060f1 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_6x8_6.c @@ -0,0 +1,12 @@ +// ..########.. +// ##########.. +// ####........ +// ##########.. +// ############ +// ####....#### +// ############ +// ..########.. + +#include "images.h" + +const image_t img_6x8_6 = {6, 8, false, 6, 0, {0x7B, 0xEC, 0x3E, 0xFF, 0x3F, 0xDE}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_6x8_7.c b/applications/plugins/wii_ec_anal/gfx/img_6x8_7.c new file mode 100644 index 000000000..fec5f4bf4 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_6x8_7.c @@ -0,0 +1,12 @@ +// ############ +// ############ +// ........#### +// ......####.. +// ......####.. +// ....####.... +// ....####.... +// ....####.... + +#include "images.h" + +const image_t img_6x8_7 = {6, 8, false, 6, 0, {0xFF, 0xF0, 0xC6, 0x18, 0xC3, 0x0C}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_6x8_8.c b/applications/plugins/wii_ec_anal/gfx/img_6x8_8.c new file mode 100644 index 000000000..a5b21c375 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_6x8_8.c @@ -0,0 +1,12 @@ +// ..########.. +// ############ +// ####....#### +// ..########.. +// ############ +// ####....#### +// ############ +// ..########.. + +#include "images.h" + +const image_t img_6x8_8 = {6, 8, false, 6, 0, {0x7B, 0xFC, 0xDE, 0xFF, 0x3F, 0xDE}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_6x8_9.c b/applications/plugins/wii_ec_anal/gfx/img_6x8_9.c new file mode 100644 index 000000000..f7707c0df --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_6x8_9.c @@ -0,0 +1,12 @@ +// ..########.. +// ############ +// ####....#### +// ############ +// ..########## +// ........#### +// ..########## +// ..########.. + +#include "images.h" + +const image_t img_6x8_9 = {6, 8, false, 6, 0, {0x7B, 0xFC, 0xFF, 0x7C, 0x37, 0xDE}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_6x8_A.c b/applications/plugins/wii_ec_anal/gfx/img_6x8_A.c new file mode 100644 index 000000000..1bb65c902 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_6x8_A.c @@ -0,0 +1,12 @@ +// ..########.. +// ############ +// ####....#### +// ####....#### +// ############ +// ############ +// ####....#### +// ####....#### + +#include "images.h" + +const image_t img_6x8_A = {6, 8, false, 6, 0, {0x7B, 0xFC, 0xF3, 0xFF, 0xFC, 0xF3}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_6x8_B.c b/applications/plugins/wii_ec_anal/gfx/img_6x8_B.c new file mode 100644 index 000000000..00e012d53 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_6x8_B.c @@ -0,0 +1,12 @@ +// ##########.. +// ############ +// ####....#### +// ##########.. +// ##########.. +// ####....#### +// ############ +// ##########.. + +#include "images.h" + +const image_t img_6x8_B = {6, 8, false, 6, 0, {0xFB, 0xFC, 0xFE, 0xFB, 0x3F, 0xFE}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_6x8_C.c b/applications/plugins/wii_ec_anal/gfx/img_6x8_C.c new file mode 100644 index 000000000..694901009 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_6x8_C.c @@ -0,0 +1,12 @@ +// ..########## +// ############ +// ####........ +// ####........ +// ####........ +// ####........ +// ############ +// ..########## + +#include "images.h" + +const image_t img_6x8_C = {6, 8, false, 6, 0, {0x7F, 0xFC, 0x30, 0xC3, 0x0F, 0xDF}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_6x8_D.c b/applications/plugins/wii_ec_anal/gfx/img_6x8_D.c new file mode 100644 index 000000000..a95e760eb --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_6x8_D.c @@ -0,0 +1,12 @@ +// ##########.. +// ############ +// ..####..#### +// ..####..#### +// ..####..#### +// ..####..#### +// ############ +// ##########.. + +#include "images.h" + +const image_t img_6x8_D = {6, 8, false, 6, 0, {0xFB, 0xF6, 0xDB, 0x6D, 0xBF, 0xFE}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_6x8_E.c b/applications/plugins/wii_ec_anal/gfx/img_6x8_E.c new file mode 100644 index 000000000..f49503f00 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_6x8_E.c @@ -0,0 +1,12 @@ +// ############ +// ############ +// ####........ +// ########.... +// ########.... +// ####........ +// ############ +// ############ + +#include "images.h" + +const image_t img_6x8_E = {6, 8, false, 6, 0, {0xFF, 0xFC, 0x3C, 0xF3, 0x0F, 0xFF}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_6x8_F.c b/applications/plugins/wii_ec_anal/gfx/img_6x8_F.c new file mode 100644 index 000000000..0037b2544 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_6x8_F.c @@ -0,0 +1,12 @@ +// ############ +// ############ +// ####........ +// ########.... +// ########.... +// ####........ +// ####........ +// ####........ + +#include "images.h" + +const image_t img_6x8_F = {6, 8, false, 6, 0, {0xFF, 0xFC, 0x3C, 0xF3, 0x0C, 0x30}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_6x8_G.c b/applications/plugins/wii_ec_anal/gfx/img_6x8_G.c new file mode 100644 index 000000000..f30bc9952 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_6x8_G.c @@ -0,0 +1,12 @@ +// ..########## +// ############ +// ####........ +// ####........ +// ####..###### +// ####....#### +// ############ +// ..########## + +#include "images.h" + +const image_t img_6x8_G = {6, 8, false, 6, 0, {0x7F, 0xFC, 0x30, 0xDF, 0x3F, 0xDF}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_6x8_X.c b/applications/plugins/wii_ec_anal/gfx/img_6x8_X.c new file mode 100644 index 000000000..4735e82a1 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_6x8_X.c @@ -0,0 +1,12 @@ +// ####....#### +// ####....#### +// ..####..##.. +// ....######.. +// ..######.... +// ..##..####.. +// ####....#### +// ####....#### + +#include "images.h" + +const image_t img_6x8_X = {6, 8, false, 6, 0, {0xCF, 0x36, 0x8E, 0x71, 0x6C, 0xF3}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_6x8_Y.c b/applications/plugins/wii_ec_anal/gfx/img_6x8_Y.c new file mode 100644 index 000000000..508e786bd --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_6x8_Y.c @@ -0,0 +1,12 @@ +// ####....#### +// ####....#### +// ####....#### +// ####....#### +// ..########.. +// ....####.... +// ....####.... +// ....####.... + +#include "images.h" + +const image_t img_6x8_Y = {6, 8, false, 6, 0, {0xCF, 0x3C, 0xF3, 0x78, 0xC3, 0x0C}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_6x8_Z.c b/applications/plugins/wii_ec_anal/gfx/img_6x8_Z.c new file mode 100644 index 000000000..c42d560ac --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_6x8_Z.c @@ -0,0 +1,12 @@ +// ############ +// ############ +// ........#### +// ......####.. +// ....####.... +// ..####...... +// ############ +// ############ + +#include "images.h" + +const image_t img_6x8_Z = {6, 8, false, 6, 0, {0xFF, 0xF0, 0xC6, 0x31, 0x8F, 0xFF}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_6x8_d_.c b/applications/plugins/wii_ec_anal/gfx/img_6x8_d_.c new file mode 100644 index 000000000..1f8123a6c --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_6x8_d_.c @@ -0,0 +1,12 @@ +// ........#### +// ........#### +// ........#### +// ..########## +// ############ +// ####....#### +// ############ +// ..########## + +#include "images.h" + +const image_t img_6x8_d = {6, 8, false, 6, 0, {0x0C, 0x30, 0xDF, 0xFF, 0x3F, 0xDF}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_6x8_n_.c b/applications/plugins/wii_ec_anal/gfx/img_6x8_n_.c new file mode 100644 index 000000000..15d403d28 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_6x8_n_.c @@ -0,0 +1,12 @@ +// ............ +// ............ +// ..########.. +// ############ +// ####....#### +// ####....#### +// ####....#### +// ####....#### + +#include "images.h" + +const image_t img_6x8_n = {6, 8, false, 6, 0, {0x00, 0x07, 0xBF, 0xCF, 0x3C, 0xF3}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_6x8_v_.c b/applications/plugins/wii_ec_anal/gfx/img_6x8_v_.c new file mode 100644 index 000000000..1229701a1 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_6x8_v_.c @@ -0,0 +1,12 @@ +// ............ +// ............ +// ##........## +// ####....#### +// ####....#### +// ############ +// ..########.. +// ....####.... + +#include "images.h" + +const image_t img_6x8_v = {6, 8, false, 6, 0, {0x00, 0x08, 0x73, 0xCF, 0xF7, 0x8C}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_RIP.c b/applications/plugins/wii_ec_anal/gfx/img_RIP.c new file mode 100644 index 000000000..c20877ef0 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_RIP.c @@ -0,0 +1,130 @@ +// ################################################################################################################################################################################################################################################################ +// ################################################################################################################################################################################################################################################################ +// ####........................................................................................................................................................................................................................................................#### +// ####..##..##........................................................................................................................................................................................................................................##..##..#### +// ####....##....................##############..........########........##################........................##############......##..........##......##############......##############......##############......##############....................##....#### +// ####..##..##..................##############............######..........################........................##############....######......######....##############......##############......##############......##############..................##..##..#### +// ####....................................######..........##..##......................######....................####........######..######......######..####........######..######......######..............######..######......######........................#### +// ####........................######......######..........##..##..........######......##..##....................######......##..##..##..##......##..##..######......######..######......######..######......######..######......##..##........................#### +// ####........................######......##..##..........##..##..........######......##..##....................##..##......######..##..##......##..##..######......##..##..##..##......##..##..######......##..##..##..##......##..##........................#### +// ####........................##..##......##..##..........##..##..........##..##......##..##....................##..##..............##..##......##..##..##..##......##..##..##..##......##..##..##..##......##..##..##..##......##..##........................#### +// ####........................##..##......##..##..........##..##..........##..##......##..##....................##..##..............##..##......##..##..##..##......##..##..##..##......##..##..##..##......##..##..##..##......######........................#### +// ####........................##..##......######..........##..##..........##..##......######....................##..##..............##..##......##..##..##..##......######..##..##..##..##..##..##..##......######..##..##....................................#### +// ####........................##..##########..............##..##..........##..##########........................##..##..............######......######..##..##########......##..##......##..##..##..##########......##..##....................................#### +// ####........................##..##########..............##..##..........##..##########........................##..##................##############....##..##########......##..##......##..##..##..##########......##..##..########..........................#### +// ####........................##..##....######............##..##..........##..##................................##..##..................####..####......##..##......######..##..##..##..##..##..##..##....######....##..##..##########........................#### +// ####........................##..##....##..##............##..##..........##..##................................##..##....................##..##........##..##......##..##..##..##......##..##..##..##....##..##....##..##......##..##........................#### +// ####........................##..##....##..##............##..##..........##..##................................##..##....................##..##........##..##......##..##..##..##......##..##..##..##....##..##....##..##......##..##........................#### +// ####........................##..##....##..##............##..##..........##..##................................######......######........##..##........##..##......##..##..##..##......##..##..##..##....##..##....######......##..##........................#### +// ####........................##..##....##..##............##..##..........##..##................................######......##..##........##..##........######......######..######......######..##..##....##..##....######......##..##........................#### +// ####........................##..##....##..##............##..##..........##..##................................####........######........######........######......######..######......######..##..##....##..##....####........######........................#### +// ####........................######....######....####....######..####....######..####............................##############..........######..........##############......##############....######....######......##############..........................#### +// ####........................######......######..####..########..####..########..####............................##############........##########........##############......##############....######......######....##############..........................#### +// ####........................................................................................................................................................................................................................................................#### +// ####........................................................................................................................................................................................................................................................#### +// ####..........................................................................................................................................................................................####......####..............##############....................#### +// ####......................................................................................................................................##................................................##....##..##....##........####..............####................#### +// ####..........######..##..##..######......######..##..##..######..######..######..####..####..######......##......##..######..##....##..##..######......######..######......................####..##..####..##......##......................##..............#### +// ####............##....##..##..##..........##......##..##..##........##....##......##..##..##..##..........##......##..##..##..####..##........##........##..##..##............................####......####......##......##########..........##............#### +// ####............##....######..####........######....##....######....##....####....##..##..##..######......##..##..##..##..##..##..####........##........####....####..............................##..##........##......##..........##........##............#### +// ####............##....##..##..##..............##....##........##....##....##......##......##......##......##..##..##..##..##..##....##........##........##..##..##..................................##..........##....##..............##......##............#### +// ####............##....##..##..######......######....##....######....##....######..##......##..######......####..####..######..##....##........##........######..######............................######........##....##......####....##......##............#### +// ####............................................................................................................................................................................................##......##......##....##....##....##..##......##............#### +// ####..........................................................................................................................................................................................##........##......##....##....##..##....##......##............#### +// ####..........................................................................................................................................................................................##........##......##....##....##........##......##............#### +// ####..........######..##..##..######......######..######..####..####..######......##......##..######..######..##..##..######..##..##..######......##..##..######..##..##........................##........######......##......########......##..............#### +// ####............##....##..##..##..........##......##..##..##..##..##..##..........##......##....##......##....##..##..##..##..##..##....##........##..##..##..##..##..##........................##..............##......##..................####............#### +// ####............##....######..####........######..######..##..##..##..####........##..##..##....##......##....######..##..##..##..##....##..........##....##..##..##..##..........................##..............##########..............##....##..........#### +// ####............##....##..##..##..............##..##..##..##......##..##..........##..##..##....##......##....##..##..##..##..##..##....##..........##....##..##..##..##..........................##......................################........##........#### +// ####............##....##..##..######......######..##..##..##......##..######......####..####..######....##....##..##..######..######....##..........##....######..######............................####..........................................##........#### +// ####....................................................................................................................................................................................................##########################################..........#### +// ####........................................................................................................................................................................................................................................................#### +// ####........................................................................................................................................................................................................................................................#### +// ####......................................##########......##############......##############......##############............................##############......##############......##############......##############......................................#### +// ####......................................##########......##############......##############......##############............................##############......##############......##############......##############......................................#### +// ####......................................##..##..##....######......######..######......######..######......######........................######......######..######......######..######......######..######......######....................................#### +// ####..........................................##..##....######......######..##..##......######..######......######........................##..##......##..##..######......######..##..##......##..##..##..##......##..##....................................#### +// ####..........................................##..##....##..##......##..##..######......##..##..##..##......##..##........................######......##..##..##..##......##..##..######......##..##..######......##..##....................................#### +// ####..........................................##..##....##..##......##..##..............##..##..##..##......##..##....................................##..##..##..##......##..##..............##..##..............##..##....................................#### +// ####..........................................##..##....##..##......##..##..............##..##..##..##....####..##....................................##..##..##..##....####..##..............##..##..............##..##....................................#### +// ####..........................................##..##....######......##..##..............######..##..##....####..##....................................######..##..##....####..##..............######..............######....................................#### +// ####..........................................######........##########..##..............######..##..##..##..##..##....################........############....##..##..##..##..##......############........############......................................#### +// ####..........................................######........##########..##..........######......##..##..##..##..##....##............##......############......##..##..##..##..##....############........############........................................#### +// ####..........................................##..##................##..##..........######......##..####....##..##....################....######..............##..####....##..##..######..............######................................................#### +// ####..........................................##..##................##..##..........##..##......##..####....##..##........................##..##..............##..####....##..##..##..##..............##..##................................................#### +// ####..........................................##..##................##..##..........##..##......##..##......##..##........................##..##..............##..##......##..##..##..##..............##..##................................................#### +// ####..........................................##..##................##..##..........##..##......##..##......##..##........................##..##..............##..##......##..##..##..##..............##..##................................................#### +// ####..........................................##..##................######..........##..##......######......######........................##..##..............######......######..##..##..............##..##................................................#### +// ####..........................................##..##................######..........##..##......######......######........................##..##........####..######......######..##..##........####..##..##........####....................................#### +// ####..##..##..............................##############....############............######........##############..........................##################....##############....##################..##################............................##..##..#### +// ####....##..............................##################..############............######........##############..........................##################....##############....##################..##################..............................##....#### +// ####..##..##........................................................................................................................................................................................................................................##..##..#### +// ####........................................................................................................................................................................................................................................................#### +// ################################################################################################################################################################################################################################################################ +// ################################################################################################################################################################################################################################################################ + +#include "images.h" + +const image_t img_RIP = { + 128, + 64, + true, + 837, + 0x06, + {// orig:1024, comp:18.26% + 0x06, 0x20, 0xFF, 0xC0, 0x06, 0x0E, 0x00, 0x03, 0xD4, 0x06, 0x0E, 0x00, 0x2B, 0xC8, 0x01, + 0xFC, 0x1E, 0x1F, 0xF0, 0x00, 0xFE, 0x20, 0x8F, 0xE3, 0xF8, 0xFE, 0x3F, 0x80, 0x13, 0xD4, + 0x01, 0xFC, 0x0E, 0x0F, 0xF0, 0x00, 0xFE, 0x71, 0xCF, 0xE3, 0xF8, 0xFE, 0x3F, 0x80, 0x2B, + 0xC0, 0x00, 0x0E, 0x0A, 0x00, 0x38, 0x01, 0x87, 0x71, 0xD8, 0x77, 0x1C, 0x07, 0x71, 0xC0, + 0x03, 0xC0, 0x03, 0x8E, 0x0A, 0x0E, 0x28, 0x01, 0xC5, 0x51, 0x5C, 0x77, 0x1D, 0xC7, 0x71, + 0x40, 0x03, 0xC0, 0x03, 0x8A, 0x0A, 0x0E, 0x28, 0x01, 0x47, 0x51, 0x5C, 0x55, 0x15, 0xC5, + 0x51, 0x40, 0x03, 0xC0, 0x02, 0x8A, 0x0A, 0x0A, 0x28, 0x01, 0x40, 0x51, 0x54, 0x55, 0x15, + 0x45, 0x51, 0x40, 0x03, 0xC0, 0x02, 0x8A, 0x0A, 0x0A, 0x28, 0x01, 0x40, 0x51, 0x54, 0x55, + 0x15, 0x45, 0x51, 0xC0, 0x03, 0xC0, 0x02, 0x8E, 0x0A, 0x0A, 0x38, 0x01, 0x40, 0x51, 0x54, + 0x75, 0x55, 0x47, 0x50, 0x00, 0x03, 0xC0, 0x02, 0xF8, 0x0A, 0x0B, 0xE0, 0x01, 0x40, 0x71, + 0xD7, 0xC5, 0x15, 0x7C, 0x50, 0x00, 0x03, 0xC0, 0x02, 0xF8, 0x0A, 0x0B, 0xE0, 0x01, 0x40, + 0x3F, 0x97, 0xC5, 0x15, 0x7C, 0x57, 0x80, 0x03, 0xC0, 0x02, 0x9C, 0x0A, 0x0A, 0x00, 0x01, + 0x40, 0x1B, 0x14, 0x75, 0x55, 0x4E, 0x57, 0xC0, 0x03, 0xC0, 0x02, 0x94, 0x0A, 0x0A, 0x00, + 0x01, 0x40, 0x0A, 0x14, 0x55, 0x15, 0x4A, 0x51, 0x40, 0x03, 0xC0, 0x02, 0x94, 0x0A, 0x0A, + 0x00, 0x01, 0x40, 0x0A, 0x14, 0x55, 0x15, 0x4A, 0x51, 0x40, 0x03, 0xC0, 0x02, 0x94, 0x0A, + 0x0A, 0x00, 0x01, 0xC7, 0x0A, 0x14, 0x55, 0x15, 0x4A, 0x71, 0x40, 0x03, 0xC0, 0x02, 0x94, + 0x0A, 0x0A, 0x00, 0x01, 0xC5, 0x0A, 0x1C, 0x77, 0x1D, 0x4A, 0x71, 0x40, 0x03, 0xC0, 0x02, + 0x94, 0x0A, 0x0A, 0x00, 0x01, 0x87, 0x0E, 0x1C, 0x77, 0x1D, 0x4A, 0x61, 0xC0, 0x03, 0xC0, + 0x03, 0x9C, 0xCE, 0xCE, 0xC0, 0x00, 0xFE, 0x0E, 0x0F, 0xE3, 0xF9, 0xCE, 0x3F, 0x80, 0x03, + 0xC0, 0x03, 0x8E, 0xDE, 0xDE, 0xC0, 0x00, 0xFE, 0x1F, 0x0F, 0xE3, 0xF9, 0xC7, 0x3F, 0x80, + 0x03, 0xC0, 0x06, 0x0E, 0x00, 0x03, 0xC0, 0x06, 0x0E, 0x00, 0x03, 0xC0, 0x06, 0x0A, 0x00, + 0x01, 0x8C, 0x07, 0xF0, 0x03, 0xC0, 0x06, 0x07, 0x00, 0x04, 0x00, 0x00, 0x02, 0x52, 0x18, + 0x0C, 0x03, 0xC1, 0xD5, 0xC7, 0x57, 0x77, 0x6D, 0xC4, 0x5D, 0x2B, 0x8E, 0xE0, 0x03, 0x5A, + 0x20, 0x02, 0x03, 0xC0, 0x95, 0x04, 0x54, 0x24, 0x55, 0x04, 0x55, 0xA1, 0x0A, 0x80, 0x01, + 0x8C, 0x47, 0xC1, 0x03, 0xC0, 0x9D, 0x87, 0x27, 0x26, 0x55, 0xC5, 0x55, 0x61, 0x0C, 0xC0, + 0x00, 0x50, 0x88, 0x21, 0x03, 0xC0, 0x95, 0x01, 0x21, 0x24, 0x44, 0x45, 0x55, 0x21, 0x0A, + 0x80, 0x00, 0x20, 0x90, 0x11, 0x03, 0xC0, 0x95, 0xC7, 0x27, 0x27, 0x45, 0xC6, 0xDD, 0x21, + 0x0E, 0xE0, 0x00, 0x70, 0x91, 0x91, 0x03, 0xC0, 0x06, 0x0B, 0x00, 0x88, 0x92, 0x51, 0x03, + 0xC0, 0x06, 0x0A, 0x00, 0x01, 0x08, 0x92, 0x91, 0x03, 0xC0, 0x06, 0x0A, 0x00, 0x01, 0x08, + 0x92, 0x11, 0x03, 0xC1, 0xD5, 0xC7, 0x76, 0xDC, 0x45, 0xDD, 0x5D, 0x5C, 0x57, 0x50, 0x00, + 0x87, 0x11, 0xE2, 0x03, 0xC0, 0x95, 0x04, 0x55, 0x50, 0x44, 0x89, 0x55, 0x48, 0x55, 0x50, + 0x00, 0x80, 0x88, 0x03, 0x03, 0xC0, 0x9D, 0x87, 0x75, 0x58, 0x54, 0x89, 0xD5, 0x48, 0x25, + 0x50, 0x00, 0x40, 0x7C, 0x04, 0x83, 0xC0, 0x95, 0x01, 0x54, 0x50, 0x54, 0x89, 0x55, 0x48, + 0x25, 0x50, 0x00, 0x40, 0x07, 0xF8, 0x43, 0xC0, 0x95, 0xC7, 0x54, 0x5C, 0x6D, 0xC9, 0x5D, + 0xC8, 0x27, 0x70, 0x00, 0x30, 0x00, 0x00, 0x43, 0xC0, 0x06, 0x0B, 0x00, 0x0F, 0xFF, 0xFF, + 0x83, 0xC0, 0x06, 0x0E, 0x00, 0x03, 0xC0, 0x06, 0x0E, 0x00, 0x03, 0xC0, 0x00, 0x07, 0xC7, + 0xF1, 0xFC, 0x7F, 0x00, 0x03, 0xF8, 0xFE, 0x3F, 0x8F, 0xE0, 0x00, 0x03, 0xC0, 0x00, 0x07, + 0xC7, 0xF1, 0xFC, 0x7F, 0x00, 0x03, 0xF8, 0xFE, 0x3F, 0x8F, 0xE0, 0x00, 0x03, 0xC0, 0x00, + 0x05, 0x4E, 0x3B, 0x8E, 0xE3, 0x80, 0x07, 0x1D, 0xC7, 0x71, 0xDC, 0x70, 0x00, 0x03, 0xC0, + 0x00, 0x01, 0x4E, 0x3A, 0x8E, 0xE3, 0x80, 0x05, 0x15, 0xC7, 0x51, 0x54, 0x50, 0x00, 0x03, + 0xC0, 0x00, 0x01, 0x4A, 0x2B, 0x8A, 0xA2, 0x80, 0x07, 0x15, 0x45, 0x71, 0x5C, 0x50, 0x00, + 0x03, 0xC0, 0x00, 0x01, 0x4A, 0x28, 0x0A, 0xA2, 0x80, 0x00, 0x15, 0x45, 0x01, 0x40, 0x50, + 0x00, 0x03, 0xC0, 0x00, 0x01, 0x4A, 0x28, 0x0A, 0xA6, 0x80, 0x00, 0x15, 0x4D, 0x01, 0x40, + 0x50, 0x00, 0x03, 0xC0, 0x00, 0x01, 0x4E, 0x28, 0x0E, 0xA6, 0x80, 0x00, 0x1D, 0x4D, 0x01, + 0xC0, 0x70, 0x00, 0x03, 0xC0, 0x00, 0x01, 0xC3, 0xE8, 0x0E, 0xAA, 0x9F, 0xE1, 0xF9, 0x55, + 0x1F, 0x87, 0xE0, 0x00, 0x03, 0xC0, 0x00, 0x01, 0xC3, 0xE8, 0x38, 0xAA, 0x90, 0x23, 0xF1, + 0x55, 0x3F, 0x0F, 0xC0, 0x00, 0x03, 0xC0, 0x00, 0x01, 0x40, 0x28, 0x38, 0xB2, 0x9F, 0xE7, + 0x01, 0x65, 0x70, 0x1C, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x01, 0x40, 0x28, 0x28, 0xB2, 0x80, + 0x05, 0x01, 0x65, 0x50, 0x14, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x01, 0x40, 0x28, 0x28, 0xA2, + 0x80, 0x05, 0x01, 0x45, 0x50, 0x14, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x01, 0x40, 0x28, 0x28, + 0xA2, 0x80, 0x05, 0x01, 0x45, 0x50, 0x14, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x01, 0x40, 0x38, + 0x28, 0xE3, 0x80, 0x05, 0x01, 0xC7, 0x50, 0x14, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x01, 0x40, + 0x38, 0x28, 0xE3, 0x80, 0x05, 0x0D, 0xC7, 0x50, 0xD4, 0x30, 0x00, 0x03, 0xD4, 0x00, 0x07, + 0xF3, 0xF0, 0x38, 0x7F, 0x00, 0x07, 0xFC, 0xFE, 0x7F, 0xDF, 0xF0, 0x00, 0x2B, 0xC8, 0x00, + 0x0F, 0xFB, 0xF0, 0x38, 0x7F, 0x00, 0x07, 0xFC, 0xFE, 0x7F, 0xDF, 0xF0, 0x00, 0x13, 0xD4, + 0x06, 0x0E, 0x00, 0x2B, 0xC0, 0x06, 0x0E, 0x00, 0x03, 0x06, 0x20, 0xFF}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_cc_Cable.c b/applications/plugins/wii_ec_anal/gfx/img_cc_Cable.c new file mode 100644 index 000000000..f4ac26173 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_cc_Cable.c @@ -0,0 +1,25 @@ +// ####..## +// ##..#### +// ####..## +// ##..#### +// ####..## +// ##..#### +// ####..## +// ##..#### +// ####..## +// ##..#### +// ####..## + +#include "images.h" + +const image_t img_cc_Cable = { + 4, + 11, + true, + 4, + 0x00, + {// orig:6, comp:33.33% + 0x00, + 0x05, + 0xDB, + 0xD0}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_cc_Joy.c b/applications/plugins/wii_ec_anal/gfx/img_cc_Joy.c new file mode 100644 index 000000000..5054103b3 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_cc_Joy.c @@ -0,0 +1,25 @@ +// ................##................ +// ............##########............ +// ....############..############.... +// ....######..............######.... +// ....####..................####.... +// ....##......................##.... +// ..####......................####.. +// ..####......................####.. +// ####..........................#### +// ..####......................####.. +// ..####......................####.. +// ....##......................##.... +// ....####..................####.... +// ....######..............######.... +// ....############..############.... +// ............##########............ +// ................##................ + +#include "images.h" + +const image_t img_cc_Joy = {17, 17, false, 37, 0, {0x00, 0x80, 0x01, 0xF0, 0x0F, 0xDF, 0x87, 0x01, + 0xC3, 0x00, 0x61, 0x00, 0x11, 0x80, 0x0C, 0xC0, + 0x06, 0xC0, 0x01, 0xB0, 0x01, 0x98, 0x00, 0xC4, + 0x00, 0x43, 0x00, 0x61, 0xC0, 0x70, 0xFD, 0xF8, + 0x07, 0xC0, 0x00, 0x80, 0x00}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_cc_Main.c b/applications/plugins/wii_ec_anal/gfx/img_cc_Main.c new file mode 100644 index 000000000..b29a9ab57 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_cc_Main.c @@ -0,0 +1,100 @@ +// ..................................................##################................................................................................................##################.................................................. +// ......................................############................##............##########....................................................##########............##................############...................................... +// ..................................####............................##..........##..........##......................####......................##..........##..........##............................####.................................. +// ................................##................................##############..........####################################################..........##############................................##................................ +// ............................######................................##..........##..........##......................####......................##..........##..........##................................######............................ +// ..........................##..##....................################..........##..........##......................####......................##..........##..........################....................##..##.......................... +// ........................##....##........############............................##########....................................................##########............................############........##....##........................ +// ......................##......##########........................................................................................................................................................##########......##...................... +// ....................##..........########################################################################################################################################################################..........##.................... +// ..................##......########....................................................................................................................................................................########......##.................. +// ................##....######................................................................................................................................................................................######....##................ +// ..............##....####........................................................................................................................................................................................####....##.............. +// ............##########............................................................................................................................................................................................##########............ +// ............######......................................................................................####......####..####..####....................................................................................######............ +// ..........######......................##################................................................####..##..####..................................................................######..........................######.......... +// ..........####........................##################................................................####..##..####..####..####....................................................##########..........................####.......... +// ........####..........................####..........####................................................####..##..####..####..####..................................................####......####..........................####........ +// ......######..........................####..........####..................................................####..####....####..####................................................####..........####........................######...... +// ......####............................####....##....####........................................................................................................................####....##..##....####........................####...... +// ......##..............................####....##....####........................................................................................................................####......##......####..........................####.... +// ....####..............................####....##....####........................................................................................................................####....##..##....####..........................####.... +// ....##..................##################..........##################............................................................................................######..........####..........####..........######..............##.... +// ..####..................##################..........##################..........................................................................................##########..........####......####..........##########............####.. +// ..##....................####......................................####..........................########........########........########......................####......####..........##########..........####......####............##.. +// ..##....................####......................................####........................####....####....####....####....####....####..................####..........####..........######..........####....##....####..........##.. +// ####....................####....######..................######....####........................##........##....##........##....##........##................####....##..##....####......................####....##..##....####........#### +// ##......................####......................................####........................##........##....##........##....##........##................####....######....####......................####....######....####..........## +// ##......................####......................................####........................####....####....####....####....####....####................####........##....####......................####....##..##....####..........## +// ##......................##################..........##################..........................########........########........########....................####....##....####..........######..........####..........####............## +// ##......................##################..........##################........................................................................................####......####..........##########..........####......####..............## +// ##....................................####....##....####........................................................................................................##########..........####......####..........##########................## +// ##....................................####....##....####..........................................................................................................######..........####..##......####..........######..................## +// ##....................................####....##....####........................................................................................................................####....##........####................................## +// ##....................................####..........####........................................................................................................................####....####......####................................## +// ##....................................####..........####........................................................................................................................####....##..##....####................................## +// ####..................................##################..........................................................................................................................####....####..####................................#### +// ..##..................................##################............................................................................................................................####......####..................................##.. +// ..##..................................................................................................................................................................................##########....................................##.. +// ..####..................................................................................................................................................................................######....................................####.. +// ....##............................................................................................................................................................................................................................##.... +// ....##............................................................................................................................................................................................................................##.... +// ....####........................................................................................................................................................................................................................####.... +// ......##........................................................................................................................................................................................................................##...... +// ......####....................................................................................................................................................................................................................####...... +// ........####................................................................................................................................................................................................................####........ +// ..........####............................................................................................................................................................................................................####.......... +// ............####........................................................................................................................................................................................................####............ +// ..............####....................................................................................................................................................................................................####.............. +// ................######............................................................................................................................................................................................######................ +// ....................####........................................................................................................................................................................................####.................... +// ......................######................................................................................................................................................................................######...................... +// ..........................########....................................................................................................................................................................########.......................... +// ................................########################################################################################################################################################################................................ + +#include "images.h" + +const image_t img_cc_Main = { + 116, + 53, + true, + 542, + 0x05, + {// orig:769, comp:29.52% + 0x00, 0x00, 0x00, 0x7F, 0xC0, 0x05, 0x05, 0x00, 0x3F, 0xE0, 0x05, 0x04, 0x00, 0x01, 0xF8, + 0x04, 0x0F, 0x80, 0x00, 0x00, 0x1F, 0x02, 0x01, 0xF8, 0x05, 0x04, 0x00, 0x60, 0x00, 0x41, + 0x04, 0x00, 0x60, 0x02, 0x08, 0x20, 0x00, 0x60, 0x00, 0x00, 0x00, 0x08, 0x00, 0x07, 0xF0, + 0x7F, 0xFF, 0xFF, 0xE0, 0xFE, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x80, 0x00, 0x41, 0x04, + 0x00, 0x60, 0x02, 0x08, 0x20, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x50, 0x03, 0xFC, 0x10, 0x40, + 0x06, 0x00, 0x20, 0x83, 0xFC, 0x00, 0xA0, 0x00, 0x00, 0x09, 0x0F, 0xC0, 0x00, 0xF8, 0x00, + 0x00, 0x01, 0xF0, 0x00, 0x3F, 0x09, 0x00, 0x00, 0x01, 0x1F, 0x05, 0x09, 0x00, 0x0F, 0x88, + 0x00, 0x00, 0x20, 0x05, 0x0A, 0xFF, 0xF0, 0x40, 0x00, 0x04, 0x78, 0x05, 0x09, 0x00, 0x01, + 0xE2, 0x00, 0x00, 0x9C, 0x05, 0x0A, 0x00, 0x03, 0x90, 0x00, 0x13, 0x05, 0x0B, 0x00, 0x0C, + 0x80, 0x03, 0xE0, 0x05, 0x0B, 0x00, 0x7C, 0x00, 0x38, 0x05, 0x05, 0x00, 0xC6, 0xD8, 0x05, + 0x04, 0x00, 0x01, 0xC0, 0x07, 0x00, 0x1F, 0xF0, 0x00, 0x00, 0x0D, 0x60, 0x00, 0x00, 0x00, + 0x0E, 0x00, 0x0E, 0x00, 0x60, 0x01, 0xFF, 0x00, 0x00, 0x00, 0xD6, 0xD8, 0x00, 0x00, 0x01, + 0xF0, 0x00, 0x60, 0x0C, 0x00, 0x18, 0x30, 0x00, 0x00, 0x0D, 0x6D, 0x80, 0x00, 0x00, 0x31, + 0x80, 0x03, 0x01, 0xC0, 0x01, 0x83, 0x00, 0x00, 0x00, 0x6C, 0xD8, 0x00, 0x00, 0x06, 0x0C, + 0x00, 0x38, 0x18, 0x00, 0x19, 0x30, 0x05, 0x07, 0x00, 0xCA, 0x60, 0x01, 0x81, 0x00, 0x01, + 0x93, 0x05, 0x07, 0x00, 0x0C, 0x46, 0x00, 0x0C, 0x30, 0x00, 0x19, 0x30, 0x05, 0x07, 0x00, + 0xCA, 0x60, 0x00, 0xC2, 0x00, 0xFF, 0x83, 0xFE, 0x05, 0x05, 0x00, 0x07, 0x06, 0x0C, 0x1C, + 0x04, 0x60, 0x0F, 0xF8, 0x3F, 0xE0, 0x05, 0x05, 0x00, 0xF8, 0x31, 0x83, 0xE0, 0x64, 0x00, + 0xC0, 0x00, 0x06, 0x00, 0x0F, 0x0F, 0x0F, 0x00, 0x18, 0xC1, 0xF0, 0x63, 0x02, 0x40, 0x0C, + 0x00, 0x00, 0x60, 0x01, 0x99, 0x99, 0x98, 0x03, 0x06, 0x0E, 0x0C, 0x98, 0x2C, 0x00, 0xCE, + 0x00, 0xE6, 0x00, 0x10, 0x90, 0x90, 0x80, 0x65, 0x30, 0x01, 0x94, 0xC3, 0x80, 0x0C, 0x00, + 0x00, 0x60, 0x01, 0x09, 0x09, 0x08, 0x06, 0x73, 0x00, 0x19, 0xCC, 0x18, 0x00, 0xC0, 0x00, + 0x06, 0x00, 0x19, 0x99, 0x99, 0x80, 0x61, 0x30, 0x01, 0x94, 0xC1, 0x80, 0x0F, 0xF8, 0x3F, + 0xE0, 0x00, 0xF0, 0xF0, 0xF0, 0x03, 0x26, 0x0E, 0x0C, 0x18, 0x18, 0x00, 0xFF, 0x83, 0xFE, + 0x05, 0x05, 0x00, 0x18, 0xC1, 0xF0, 0x63, 0x01, 0x80, 0x00, 0x19, 0x30, 0x05, 0x06, 0x00, + 0xF8, 0x31, 0x83, 0xE0, 0x18, 0x00, 0x01, 0x93, 0x05, 0x06, 0x00, 0x07, 0x06, 0x8C, 0x1C, + 0x01, 0x80, 0x00, 0x19, 0x30, 0x05, 0x07, 0x00, 0xC8, 0x60, 0x00, 0x18, 0x00, 0x01, 0x83, + 0x05, 0x07, 0x00, 0x0C, 0xC6, 0x00, 0x01, 0x80, 0x00, 0x18, 0x30, 0x05, 0x07, 0x00, 0xCA, + 0x60, 0x00, 0x1C, 0x00, 0x01, 0xFF, 0x05, 0x07, 0x00, 0x06, 0x6C, 0x00, 0x03, 0x40, 0x00, + 0x1F, 0xF0, 0x05, 0x07, 0x00, 0x31, 0x80, 0x00, 0x24, 0x05, 0x0A, 0x00, 0x01, 0xF0, 0x00, + 0x02, 0x60, 0x05, 0x0A, 0x00, 0x0E, 0x00, 0x00, 0x62, 0x05, 0x0D, 0x00, 0x04, 0x20, 0x05, + 0x0D, 0x00, 0x43, 0x05, 0x0D, 0x00, 0x0C, 0x10, 0x05, 0x0D, 0x00, 0x81, 0x80, 0x05, 0x0C, + 0x00, 0x18, 0x0C, 0x05, 0x0C, 0x00, 0x03, 0x00, 0x60, 0x05, 0x0C, 0x00, 0x60, 0x03, 0x05, + 0x0C, 0x00, 0x0C, 0x00, 0x18, 0x05, 0x0B, 0x00, 0x01, 0x80, 0x00, 0xE0, 0x05, 0x0B, 0x00, + 0x70, 0x00, 0x03, 0x05, 0x0B, 0x00, 0x0C, 0x00, 0x00, 0x1C, 0x05, 0x0A, 0x00, 0x03, 0x80, + 0x00, 0x00, 0x78, 0x05, 0x09, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x00, 0x05, 0x0A, 0xFF, 0xF0, + 0x00, 0x00}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_cc_btn_A1.c b/applications/plugins/wii_ec_anal/gfx/img_cc_btn_A1.c new file mode 100644 index 000000000..0889b2a08 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_cc_btn_A1.c @@ -0,0 +1,11 @@ +// ############## +// ######..###### +// ####..##..#### +// ####......#### +// ####..##..#### +// ############## +// ############## + +#include "images.h" + +const image_t img_cc_btn_A1 = {7, 7, false, 7, 0, {0xFF, 0xDF, 0x5E, 0x3D, 0x7F, 0xFF, 0x80}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_cc_btn_B1.c b/applications/plugins/wii_ec_anal/gfx/img_cc_btn_B1.c new file mode 100644 index 000000000..bbf5fba1a --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_cc_btn_B1.c @@ -0,0 +1,11 @@ +// ############## +// ####..######## +// ####..######## +// ####....###### +// ####..##..#### +// ######....#### +// ############## + +#include "images.h" + +const image_t img_cc_btn_B1 = {7, 7, false, 7, 0, {0xFF, 0xBF, 0x7E, 0x7D, 0x7C, 0xFF, 0x80}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_cc_btn_X1.c b/applications/plugins/wii_ec_anal/gfx/img_cc_btn_X1.c new file mode 100644 index 000000000..2352ba695 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_cc_btn_X1.c @@ -0,0 +1,11 @@ +// ############## +// ############## +// ####..##..#### +// ######..###### +// ####..##..#### +// ############## +// ############## + +#include "images.h" + +const image_t img_cc_btn_X1 = {7, 7, false, 7, 0, {0xFF, 0xFF, 0x5F, 0x7D, 0x7F, 0xFF, 0x80}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_cc_btn_Y1.c b/applications/plugins/wii_ec_anal/gfx/img_cc_btn_Y1.c new file mode 100644 index 000000000..d7192e3e7 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_cc_btn_Y1.c @@ -0,0 +1,11 @@ +// ############## +// ############## +// ####..##..#### +// ####......#### +// ########..#### +// ######..###### +// ############## + +#include "images.h" + +const image_t img_cc_btn_Y1 = {7, 7, false, 7, 0, {0xFF, 0xFF, 0x5E, 0x3F, 0x7D, 0xFF, 0x80}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_cc_pad_LR1.c b/applications/plugins/wii_ec_anal/gfx/img_cc_pad_LR1.c new file mode 100644 index 000000000..300ed5eee --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_cc_pad_LR1.c @@ -0,0 +1,9 @@ +// ############## +// ############## +// ####......#### +// ############## +// ############## + +#include "images.h" + +const image_t img_cc_pad_LR1 = {7, 5, false, 5, 0, {0xFF, 0xFF, 0x1F, 0xFF, 0xE0}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_cc_pad_UD1.c b/applications/plugins/wii_ec_anal/gfx/img_cc_pad_UD1.c new file mode 100644 index 000000000..feb32d283 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_cc_pad_UD1.c @@ -0,0 +1,11 @@ +// ########## +// ########## +// ####..#### +// ####..#### +// ####..#### +// ########## +// ########## + +#include "images.h" + +const image_t img_cc_pad_UD1 = {5, 7, false, 5, 0, {0xFF, 0xF7, 0xBD, 0xFF, 0xE0}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_cc_trg_L1.c b/applications/plugins/wii_ec_anal/gfx/img_cc_trg_L1.c new file mode 100644 index 000000000..c70e35334 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_cc_trg_L1.c @@ -0,0 +1,16 @@ +// ......##############....##....##.. +// ..####..##....##....##....##....## +// ##....##....##....##....##....##.. +// ##..##....##....##....##....##.... +// ..##....##....##....############## +// ##....##############.............. + +#include "images.h" + +const image_t img_cc_trg_L1 = { + 17, + 6, + false, + 13, + 0, + {0x1F, 0xC9, 0x34, 0x92, 0x64, 0x92, 0x54, 0x92, 0x44, 0x93, 0xFC, 0xFE, 0x00}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_cc_trg_L2.c b/applications/plugins/wii_ec_anal/gfx/img_cc_trg_L2.c new file mode 100644 index 000000000..47561ab98 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_cc_trg_L2.c @@ -0,0 +1,28 @@ +// ......##############..##..##..##.. +// ..####..##..##..##..##..##..##..## +// ####..##..##..##..##..##..##..##.. +// ##..##..##..##..##..##..##..##..## +// ..##..##..##..##..################ +// ##..##..############.............. + +#include "images.h" + +const image_t img_cc_trg_L2 = { + 17, + 6, + true, + 12, + 0x01, + {// orig:13, comp:7.69% + 0x1F, + 0xD5, + 0x35, + 0x55, + 0x75, + 0x01, + 0x04, + 0x55, + 0x57, + 0xFD, + 0x7E, + 0x00}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_cc_trg_L3.c b/applications/plugins/wii_ec_anal/gfx/img_cc_trg_L3.c new file mode 100644 index 000000000..0b51bed35 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_cc_trg_L3.c @@ -0,0 +1,16 @@ +// ......############..####..####..## +// ..######..####..####..####..####.. +// ######..####..####..####..####..## +// ####..####..####..####..####..#### +// ##..####..####..################## +// ..####..############.............. + +#include "images.h" + +const image_t img_cc_trg_L3 = { + 17, + 6, + false, + 13, + 0, + {0x1F, 0xB6, 0xBB, 0x6D, 0xBB, 0x6D, 0xBB, 0x6D, 0xBB, 0x6F, 0xFB, 0x7E, 0x00}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_cc_trg_L4.c b/applications/plugins/wii_ec_anal/gfx/img_cc_trg_L4.c new file mode 100644 index 000000000..062caca77 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_cc_trg_L4.c @@ -0,0 +1,24 @@ +// ......############################ +// ..################################ +// ################################## +// ################################## +// ################################## +// ####################.............. + +#include "images.h" + +const image_t img_cc_trg_L4 = { + 17, + 6, + true, + 8, + 0x01, + {// orig:13, comp:38.46% + 0x1F, + 0xFF, + 0xBF, + 0x01, + 0x08, + 0xFF, + 0xFE, + 0x00}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_cc_trg_R1.c b/applications/plugins/wii_ec_anal/gfx/img_cc_trg_R1.c new file mode 100644 index 000000000..6f08886d3 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_cc_trg_R1.c @@ -0,0 +1,16 @@ +// ..##....##....##############...... +// ##....##....##....##....##..####.. +// ..##....##....##....##....##....## +// ....##....##....##....##....##..## +// ##############....##....##....##.. +// ..............##############....## + +#include "images.h" + +const image_t img_cc_trg_R1 = { + 17, + 6, + false, + 13, + 0, + {0x49, 0xFC, 0x49, 0x25, 0x92, 0x49, 0x24, 0x92, 0x5F, 0xE4, 0x90, 0x0F, 0xE4}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_cc_trg_R2.c b/applications/plugins/wii_ec_anal/gfx/img_cc_trg_R2.c new file mode 100644 index 000000000..d85e45761 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_cc_trg_R2.c @@ -0,0 +1,16 @@ +// ..##..##..##..##############...... +// ##..##..##..##..##..##..##..####.. +// ..##..##..##..##..##..##..##..#### +// ##..##..##..##..##..##..##..##..## +// ################..##..##..##..##.. +// ..............############..##..## + +#include "images.h" + +const image_t img_cc_trg_R2 = { + 17, + 6, + false, + 13, + 0, + {0x55, 0xFC, 0x55, 0x55, 0x95, 0x55, 0x75, 0x55, 0x5F, 0xF5, 0x50, 0x0F, 0xD4}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_cc_trg_R3.c b/applications/plugins/wii_ec_anal/gfx/img_cc_trg_R3.c new file mode 100644 index 000000000..082d160e2 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_cc_trg_R3.c @@ -0,0 +1,16 @@ +// ##..####..####..############...... +// ..####..####..####..####..######.. +// ##..####..####..####..####..###### +// ####..####..####..####..####..#### +// ##################..####..####..## +// ..............############..####.. + +#include "images.h" + +const image_t img_cc_trg_R3 = { + 17, + 6, + false, + 13, + 0, + {0xB6, 0xFC, 0x36, 0xDB, 0xAD, 0xB6, 0xFB, 0x6D, 0xBF, 0xFB, 0x68, 0x0F, 0xD8}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_cc_trg_R4.c b/applications/plugins/wii_ec_anal/gfx/img_cc_trg_R4.c new file mode 100644 index 000000000..0395058b8 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_cc_trg_R4.c @@ -0,0 +1,27 @@ +// ############################...... +// ################################.. +// ################################## +// ################################## +// ################################## +// ..............#################### + +#include "images.h" + +const image_t img_cc_trg_R4 = { + 17, + 6, + true, + 11, + 0x00, + {// orig:13, comp:15.38% + 0xFF, + 0xFC, + 0x7F, + 0xFF, + 0xBF, + 0x00, + 0x05, + 0xFF, + 0xF8, + 0x0F, + 0xFC}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_csLogo_FULL.c b/applications/plugins/wii_ec_anal/gfx/img_csLogo_FULL.c new file mode 100644 index 000000000..a8c030fa2 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_csLogo_FULL.c @@ -0,0 +1,89 @@ +// ....##########################################........##..........##........##############........##############........##############........##############............................................................................................ +// ....##########################################......######......######......##############........##############........##############........##############............................................................................................ +// ############..............................######....######......######....####........######....######......######................######....######......######..............######..######..######...................................................... +// ############..............................##..##....##..##......##..##....######......######....######......######....######......######....######......##..##..............##......##........##........................................................ +// ############..............................##..##....##..##......##..##....######......##..##....##..##......##..##....######......##..##....##..##......##..##..............####....######....##........................................................ +// ####....####..............................##..##....##..##......##..##....##..##......##..##....##..##......##..##....##..##......##..##....##..##......##..##..............##..........##....##........................................................ +// ############..............................######....##..##......##..##....##..##......##..##....##..##......##..##....##..##......##..##....##..##......######..............######..######....##..##.................................................... +// ############........................................##..##......##..##....##..##......######....##..##..##..##..##....##..##......######....##..##...................................................................................................... +// ####....####........................................######......######....##..##########........##..##......##..##....##..##########........##..##........................####..######..######..##...................................................... +// ######..####........####..............................##############......##..##########........##..##......##..##....##..##########........##..##..########................##..##..##..##..##..##..##.................................................. +// ####..######........####................................####..####........##..##......######....##..##..##..##..##....##..##....######......##..##..##########..............##..######..######..######.................................................. +// ######..####................####..........................##..##..........##..##......##..##....##..##......##..##....##..##....##..##......##..##......##..##..............##......##..##..##......##.................................................. +// ####..######..............##....##........................##..##..........##..##......##..##....##..##......##..##....##..##....##..##......##..##......##..##............######....##..######......##..................................##.............. +// ######..####..............##....##........................##..##..........##..##......##..##....##..##......##..##....##..##....##..##......######......##..##........................................................................##..##............ +// ####..######................####..........................##..##..........######......######....######......######....##..##....##..##......######......##..##..................................................................######......##.......... +// ####....####..............................................######..........######......######....######......######....##..##....##..##......####........######..................................................................####....##....##........ +// ####....####..............................................######............##############........##############......######....######........##############....................................................................##....##..##....##...... +// ####....####............................................##########..........##############........##############......######......######......##############..............................................................######....##......##....##.... +// ####....####..............................................................................................................................................................................................................####........##..##..##....##.. +// ####....####..............................................................................................................................................................................................................##....##......##......##....## +// ####....####....................##############........##..........##........##############......##################......##############........##############........##############..................................######....##..........##..##....#### +// ####....####....................##############......######......######......##############......##################......##############........##############........##############..................................####....##......##......##....###### +// ####..######..................####........######....######......######....####........######....####..######..####....####....##########....##################....####........######................................##........##..##..##........######## +// ######..####..................######......##..##....##..##......##..##....######......##..##..........##..##..........######................######..##..######....######......##..##..........................######....##......##......##....########.. +// ####..######..................##..##......##..##....##..##......##..##....##..##......##..##..........##..##..........##..##................##..##..##..##..##....##..##......##..##..........................####....##..##..........##....##########.. +// ######..####..................##..##......##..##....##..##......##..##....##..##......##..##..........##..##..........##..##................##..##..##..##..##....##..##......##..##..........................##....##......##......##....############## +// ####..######..................##..##......######....##..##......##..##....##..##......######..........##..##..........##..##................##..##..##..##..##....##..##......######....................######............##..##........########....#### +// ######..####..................######................##..##......##..##....######......................##..##..........##..##....######......##..##..##..##..##....######................................####....................##....##########........ +// ####....####....................############........######......######......############..............##..##..........##..########..........##..##..##..##..##......############........................##....##..............##....##############...... +// ############........................##########........##############............##########............##..##..........##..########..........##..##..##..##..##..........##########....................##....##..##..........##....########....####...... +// ############..............................######........####..####....................######..........##..##..........##..##....######......##..##..##..##..##................######................##..##........##............##########.............. +// ####....####..................######......##..##..........##..##..........######......##..##..........##..##..........##..##................##..##......##..##....######......##..##................####............##..##....##############............ +// ############..................##..##......##..##..........##..##..........##..##......##..##..........##..##..........##..##................##..##..##..##..##....##..##......##..##................####....##........##....########....####............ +// ############..........####....##..##......##..##..........##..##..........##..##......##..##..........##..##..........##..##................##..##......##..##....##..##......##..##..................##########....##....##########.................... +// ############..........####....##..##......######..........##..##..........##..##......######..........##..##..........######................##..##......##..##....##..##......######....................######..........##############.................. +// ############..........####....######........####..........######..........######........####..........##..##..........####....##########....##..##......##..##....######........####......................####........########....####.................. +// ....######################......##############............######............##############............######............##############......######......######......##############..........................######..##########.......................... +// ....######################......##############..........##########..........##############............######............##############......######......######......##############............................##################........................ +// ................................................................................................................................................................................................................########....####........................ +// ..................................................................................................................................................................................................................####.................................. + +#include "images.h" + +const image_t img_csLogo_FULL = { + 124, + 40, + true, + 571, + 0x0B, + {// orig:620, comp:7.90% + 0x3F, 0xFF, 0xFE, 0x10, 0x43, 0xF8, 0x7F, 0x0F, 0xE1, 0xFC, 0x0B, 0x05, 0x00, 0x03, 0xFF, + 0xFF, 0xE3, 0x8E, 0x3F, 0x87, 0xF0, 0xFE, 0x1F, 0xC0, 0x0B, 0x05, 0x00, 0xFC, 0x00, 0x07, + 0x38, 0xE6, 0x1C, 0xE3, 0x80, 0x73, 0x8E, 0x03, 0xBB, 0x80, 0x00, 0x00, 0x0F, 0xC0, 0x00, + 0x52, 0x8A, 0x71, 0xCE, 0x39, 0xC7, 0x38, 0xA0, 0x22, 0x10, 0x00, 0x00, 0x00, 0xFC, 0x00, + 0x05, 0x28, 0xA7, 0x14, 0xA2, 0x9C, 0x52, 0x8A, 0x03, 0x39, 0x00, 0x00, 0x00, 0x0C, 0xC0, + 0x00, 0x52, 0x8A, 0x51, 0x4A, 0x29, 0x45, 0x28, 0xA0, 0x20, 0x90, 0x00, 0x00, 0x00, 0xFC, + 0x00, 0x07, 0x28, 0xA5, 0x14, 0xA2, 0x94, 0x52, 0x8E, 0x03, 0xB9, 0x40, 0x00, 0x00, 0x0F, + 0xC0, 0x00, 0x02, 0x8A, 0x51, 0xCA, 0xA9, 0x47, 0x28, 0x0B, 0x06, 0x00, 0xCC, 0x00, 0x00, + 0x38, 0xE5, 0xF0, 0xA2, 0x97, 0xC2, 0x80, 0x06, 0xEE, 0x80, 0x00, 0x00, 0x0E, 0xC3, 0x00, + 0x01, 0xFC, 0x5F, 0x0A, 0x29, 0x7C, 0x2B, 0xC0, 0x2A, 0xAA, 0x00, 0x00, 0x00, 0xDC, 0x30, + 0x00, 0x0D, 0x85, 0x1C, 0xAA, 0x94, 0xE2, 0xBE, 0x02, 0xEE, 0xE0, 0x00, 0x00, 0x0E, 0xC0, + 0x30, 0x00, 0x50, 0x51, 0x4A, 0x29, 0x4A, 0x28, 0xA0, 0x22, 0xA2, 0x00, 0x00, 0x00, 0xDC, + 0x04, 0x80, 0x05, 0x05, 0x14, 0xA2, 0x94, 0xA2, 0x8A, 0x07, 0x2E, 0x20, 0x00, 0x08, 0x0E, + 0xC0, 0x48, 0x00, 0x50, 0x51, 0x4A, 0x29, 0x4A, 0x38, 0xA0, 0x00, 0x00, 0x00, 0x01, 0x40, + 0xDC, 0x03, 0x00, 0x05, 0x07, 0x1C, 0xE3, 0x94, 0xA3, 0x8A, 0x0B, 0x04, 0x00, 0xE2, 0x0C, + 0xC0, 0x00, 0x00, 0x70, 0x71, 0xCE, 0x39, 0x4A, 0x30, 0xE0, 0x00, 0x00, 0x00, 0x0C, 0x90, + 0xCC, 0x00, 0x00, 0x07, 0x03, 0xF8, 0x7F, 0x1C, 0xE1, 0xFC, 0x0B, 0x04, 0x00, 0x94, 0x8C, + 0xC0, 0x00, 0x00, 0xF8, 0x3F, 0x87, 0xF1, 0xC7, 0x1F, 0xC0, 0x00, 0x00, 0x00, 0x72, 0x24, + 0xCC, 0x0B, 0x0C, 0x00, 0x06, 0x15, 0x2C, 0xC0, 0x0B, 0x0C, 0x00, 0x48, 0x89, 0xCC, 0x00, + 0xFE, 0x10, 0x43, 0xF8, 0xFF, 0x8F, 0xE1, 0xFC, 0x3F, 0x80, 0x00, 0x39, 0x05, 0x3C, 0xC0, + 0x0F, 0xE3, 0x8E, 0x3F, 0x8F, 0xF8, 0xFE, 0x1F, 0xC3, 0xF8, 0x00, 0x03, 0x22, 0x27, 0xDC, + 0x01, 0x87, 0x38, 0xE6, 0x1C, 0xDD, 0x99, 0xF3, 0xFE, 0x61, 0xC0, 0x00, 0x21, 0x50, 0xFE, + 0xC0, 0x1C, 0x52, 0x8A, 0x71, 0x41, 0x41, 0xC0, 0x3A, 0xE7, 0x14, 0x00, 0x1C, 0x88, 0x9E, + 0xDC, 0x01, 0x45, 0x28, 0xA5, 0x14, 0x14, 0x14, 0x02, 0xAA, 0x51, 0x40, 0x01, 0x94, 0x13, + 0xEE, 0xC0, 0x14, 0x52, 0x8A, 0x51, 0x41, 0x41, 0x40, 0x2A, 0xA5, 0x14, 0x00, 0x12, 0x22, + 0x7F, 0xDC, 0x01, 0x47, 0x28, 0xA5, 0x1C, 0x14, 0x14, 0x02, 0xAA, 0x51, 0xC0, 0x0E, 0x05, + 0x0F, 0x3E, 0xC0, 0x1C, 0x02, 0x8A, 0x70, 0x01, 0x41, 0x4E, 0x2A, 0xA7, 0x00, 0x00, 0xC0, + 0x09, 0xF0, 0xCC, 0x00, 0xFC, 0x38, 0xE3, 0xF0, 0x14, 0x17, 0x82, 0xAA, 0x3F, 0x00, 0x09, + 0x01, 0x3F, 0x8F, 0xC0, 0x03, 0xE1, 0xFC, 0x0F, 0x81, 0x41, 0x78, 0x2A, 0xA0, 0xF8, 0x01, + 0x28, 0x27, 0x98, 0xFC, 0x00, 0x07, 0x0D, 0x80, 0x1C, 0x14, 0x14, 0xE2, 0xAA, 0x01, 0xC0, + 0x28, 0x40, 0xF8, 0x0C, 0xC0, 0x1C, 0x50, 0x50, 0x71, 0x41, 0x41, 0x40, 0x28, 0xA7, 0x14, + 0x03, 0x02, 0x9F, 0xC0, 0xFC, 0x01, 0x45, 0x05, 0x05, 0x14, 0x14, 0x14, 0x02, 0xAA, 0x51, + 0x40, 0x32, 0x13, 0xCC, 0x0F, 0xC1, 0x94, 0x50, 0x50, 0x51, 0x41, 0x41, 0x40, 0x28, 0xA5, + 0x14, 0x01, 0xF2, 0x7C, 0x00, 0xFC, 0x19, 0x47, 0x05, 0x05, 0x1C, 0x14, 0x1C, 0x02, 0x8A, + 0x51, 0xC0, 0x0E, 0x0F, 0xE0, 0x0F, 0xC1, 0x9C, 0x30, 0x70, 0x70, 0xC1, 0x41, 0x9F, 0x28, + 0xA7, 0x0C, 0x00, 0x61, 0xE6, 0x00, 0x3F, 0xF8, 0xFE, 0x07, 0x03, 0xF8, 0x1C, 0x0F, 0xE3, + 0x8E, 0x3F, 0x80, 0x03, 0xBE, 0x00, 0x03, 0xFF, 0x8F, 0xE0, 0xF8, 0x3F, 0x81, 0xC0, 0xFE, + 0x38, 0xE3, 0xF8, 0x00, 0x1F, 0xF0, 0x0B, 0x0E, 0x00, 0xF3, 0x0B, 0x0E, 0x00, 0x06, 0x00, + 0x00}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_csLogo_Small.c b/applications/plugins/wii_ec_anal/gfx/img_csLogo_Small.c new file mode 100644 index 000000000..71debc2ff --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_csLogo_Small.c @@ -0,0 +1,22 @@ +// ################## +// ################## +// ####..........#### +// ####..........#### +// ####..##.......... +// ####......######## +// ####......##....## +// ####......##...... +// ####......######## +// ####............## +// ########..##....## +// ########..######## + +#include "images.h" + +const image_t img_csLogo_Small = { + 9, + 12, + false, + 14, + 0, + {0xFF, 0xFF, 0xF0, 0x78, 0x3D, 0x06, 0x3F, 0x13, 0x88, 0xC7, 0xE0, 0x7D, 0x3E, 0xF0}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_ecp_SCL.c b/applications/plugins/wii_ec_anal/gfx/img_ecp_SCL.c new file mode 100644 index 000000000..e3622a626 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_ecp_SCL.c @@ -0,0 +1,17 @@ +// ....##############......######## +// ....##############......######## +// ....####......####......####.... +// ....####......####......####.... +// ....####......####......####.... +// ########......##############.... +// ########......##############.... + +#include "images.h" + +const image_t img_ecp_SCL = { + 16, + 7, + false, + 14, + 0, + {0x3F, 0x8F, 0x3F, 0x8F, 0x31, 0x8C, 0x31, 0x8C, 0x31, 0x8C, 0xF1, 0xFC, 0xF1, 0xFC}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_ecp_SDA.c b/applications/plugins/wii_ec_anal/gfx/img_ecp_SDA.c new file mode 100644 index 000000000..5ce0cbec4 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_ecp_SDA.c @@ -0,0 +1,19 @@ +// ......##.......................... +// ....####.......................... +// ..####............................ +// ######################............ +// ######################....##...... +// ..####....................####.... +// ....####....................####.. +// ......##....###################### +// ............###################### +// ............................####.. +// ..........................####.... +// ..........................##...... + +#include "images.h" + +const image_t img_ecp_SDA = {17, 12, false, 26, 0, {0x10, 0x00, 0x18, 0x00, 0x18, 0x00, 0x1F, + 0xFC, 0x0F, 0xFE, 0x43, 0x00, 0x30, 0xC0, + 0x0C, 0x27, 0xFF, 0x03, 0xFF, 0x80, 0x01, + 0x80, 0x01, 0x80, 0x00, 0x80}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_ecp_port.c b/applications/plugins/wii_ec_anal/gfx/img_ecp_port.c new file mode 100644 index 000000000..60f535458 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_ecp_port.c @@ -0,0 +1,72 @@ +// ....................##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..## +// ..................##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##.. +// ................##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..## +// ..............##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##.. +// ............##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..## +// ..........##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##.. +// ........##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..##..## +// ......######################################################################################################################..##..##..##.. +// ......########################################################################################################################..##..##..## +// ......####..............................................................................................................####..##..##..##.. +// ......####..............................................................................................................######..##..##..## +// ......####..............................................................................................................####..##..##..##.. +// ......####..............................................................................................................######..##..##..## +// ......####..............................................................................................................####..##..##..##.. +// ......####........##############################################################################################........######..##..##..## +// ......####........##############################################################################################........####..##..##..##.. +// ......####........####........####..........####........####..........####........####..........####........####........######..##..##..## +// ......####........####........####..........####........####..........####........####..........####........####........####..##..##..##.. +// ......####........####........##################........##################........##################........####........######..##..##..## +// ......####........####........##################........##################........##################........####........####..##..##..##.. +// ......####........####......................................................................................####........######..##..##..## +// ..########........####......................................................................................####........####..##########.. +// ##########........####......................................................................................####........################## +// ##########........####......................................................................................####........####..##########.. +// ..########........####......................................................................................####........################## +// ......####........####........##################..................................##################........####........####..##..##..##.. +// ......####........####........##################..................................##################........####........######..##..##..## +// ......####........####........####..........####........##################........####..........####........####........####..##..##..##.. +// ......####........####........####..........####........##..............##........####..........####........####........######..##..##..## +// ......####........##############################################################################################........####..##..##..##.. +// ......####........##############################################################################################........######..##..##..## +// ......####..............................................................................................................####..##..##..##.. +// ......####..............................................................................................................######..##..##..## +// ......####..............................................................................................................####..##..##..##.. +// ......####....................................######################################....................................######..##..##..## +// ......####....................................######################################....................................####..##..##..##.. +// ......####....................................####..##..##..##..................####....................................######..##..##.... +// ......####....................................######..##..##....................####....................................####..##..##...... +// ......####....................................####..##..##......................####....................................######..##........ +// ......####....................................######..##........................####....................................####..##.......... +// ......############################################..##..........................##############################################............ +// ......##############################################............................############################################.............. + +#include "images.h" + +const image_t img_ecp_port = { + 69, + 42, + true, + 290, + 0x04, + {// orig:363, comp:20.11% + 0x00, 0x2A, 0x04, 0x06, 0xAA, 0xA8, 0x02, 0x04, 0x07, 0xAA, 0x80, 0x2A, 0x04, 0x07, 0xAA, + 0x02, 0x04, 0x07, 0xAA, 0xA0, 0x2A, 0x04, 0x07, 0xAA, 0x82, 0x04, 0x07, 0xAA, 0xA8, 0x2A, + 0x04, 0x07, 0xAA, 0xA3, 0x04, 0x07, 0xFF, 0xAA, 0x1F, 0x04, 0x06, 0xFF, 0xFE, 0xA8, 0xC0, + 0x04, 0x06, 0x00, 0x6A, 0x86, 0x04, 0x06, 0x00, 0x03, 0xAA, 0x30, 0x04, 0x06, 0x00, 0x1A, + 0xA1, 0x80, 0x04, 0x06, 0x00, 0xEA, 0x8C, 0x04, 0x06, 0x00, 0x06, 0xA8, 0x61, 0x04, 0x05, + 0xFF, 0xFC, 0x3A, 0xA3, 0x0F, 0x04, 0x05, 0xFF, 0xE1, 0xAA, 0x18, 0x61, 0x83, 0x0C, 0x18, + 0x60, 0xC3, 0x0E, 0xA8, 0xC3, 0x0C, 0x18, 0x60, 0xC3, 0x06, 0x18, 0x6A, 0x86, 0x18, 0x7F, + 0xC3, 0xFE, 0x1F, 0xF0, 0xC3, 0xAA, 0x30, 0xC3, 0xFE, 0x1F, 0xF0, 0xFF, 0x86, 0x1A, 0xA1, + 0x86, 0x04, 0x05, 0x00, 0x30, 0xEA, 0xBC, 0x30, 0x04, 0x04, 0x00, 0x01, 0x86, 0xFB, 0xE1, + 0x80, 0x04, 0x04, 0x00, 0x0C, 0x3F, 0xFF, 0x0C, 0x04, 0x05, 0x00, 0x61, 0xBE, 0x78, 0x60, + 0x04, 0x04, 0x00, 0x03, 0x0F, 0xF8, 0xC3, 0x0F, 0xF8, 0x00, 0x03, 0xFE, 0x18, 0x6A, 0x86, + 0x18, 0x7F, 0xC0, 0x00, 0x1F, 0xF0, 0xC3, 0xAA, 0x30, 0xC3, 0x06, 0x1F, 0xF0, 0xC1, 0x86, + 0x1A, 0xA1, 0x86, 0x18, 0x30, 0x80, 0x86, 0x0C, 0x30, 0xEA, 0x8C, 0x3F, 0x04, 0x05, 0xFF, + 0x86, 0xA8, 0x61, 0x04, 0x05, 0xFF, 0xFC, 0x3A, 0xA3, 0x04, 0x06, 0x00, 0x01, 0xAA, 0x18, + 0x04, 0x06, 0x00, 0x0E, 0xA8, 0xC0, 0x04, 0x06, 0x00, 0x6A, 0x86, 0x00, 0x00, 0x7F, 0xFF, + 0xF0, 0x00, 0x03, 0xAA, 0x30, 0x00, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x1A, 0xA1, 0x80, 0x00, + 0x1A, 0xA0, 0x0C, 0x00, 0x00, 0xEA, 0x0C, 0x00, 0x00, 0xEA, 0x00, 0x60, 0x00, 0x06, 0xA0, + 0x60, 0x00, 0x06, 0xA0, 0x03, 0x00, 0x00, 0x3A, 0x03, 0x00, 0x00, 0x3A, 0x00, 0x18, 0x00, + 0x01, 0xA0, 0x1F, 0xFF, 0xFF, 0xA0, 0x00, 0xFF, 0xFF, 0xFE, 0x00, 0xFF, 0xFF, 0xFE, 0x00, + 0x07, 0xFF, 0xFF, 0xE0, 0x00}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_key_Back.c b/applications/plugins/wii_ec_anal/gfx/img_key_Back.c new file mode 100644 index 000000000..23c17fe2b --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_key_Back.c @@ -0,0 +1,14 @@ +// ..##############.. +// ################## +// ######..########## +// ####........###### +// ######..####..#### +// ############..#### +// ########....###### +// ################## +// ....############.. + +#include "images.h" + +const image_t img_key_Back = + {9, 9, false, 11, 0, {0x7F, 0x7F, 0xFB, 0xF8, 0x7E, 0xDF, 0xEF, 0xCF, 0xFF, 0x3F, 0x00}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_key_D.c b/applications/plugins/wii_ec_anal/gfx/img_key_D.c new file mode 100644 index 000000000..689b9148c --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_key_D.c @@ -0,0 +1,13 @@ +// ..##############.. +// ################## +// ################## +// ####..........#### +// ######......###### +// ########..######## +// ################## +// ..##############.. + +#include "images.h" + +const image_t img_key_D = + {9, 8, false, 9, 0, {0x7F, 0x7F, 0xFF, 0xF8, 0x3E, 0x3F, 0xBF, 0xFE, 0xFE}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_key_L.c b/applications/plugins/wii_ec_anal/gfx/img_key_L.c new file mode 100644 index 000000000..a5fca1a21 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_key_L.c @@ -0,0 +1,14 @@ +// ..############.. +// ################ +// ########..###### +// ######....###### +// ####......###### +// ######....###### +// ########..###### +// ################ +// ..############.. + +#include "images.h" + +const image_t img_key_L = + {8, 9, false, 9, 0, {0x7E, 0xFF, 0xF7, 0xE7, 0xC7, 0xE7, 0xF7, 0xFF, 0x7E}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_key_OK.c b/applications/plugins/wii_ec_anal/gfx/img_key_OK.c new file mode 100644 index 000000000..926d91c2e --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_key_OK.c @@ -0,0 +1,14 @@ +// ..##############.. +// ################## +// ######......###### +// ####..........#### +// ####..........#### +// ####..........#### +// ######......###### +// ################## +// ....############.. + +#include "images.h" + +const image_t img_key_OK = + {9, 9, false, 11, 0, {0x7F, 0x7F, 0xF8, 0xF8, 0x3C, 0x1E, 0x0F, 0x8F, 0xFF, 0x3F, 0x00}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_key_OKi.c b/applications/plugins/wii_ec_anal/gfx/img_key_OKi.c new file mode 100644 index 000000000..aa6f9e692 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_key_OKi.c @@ -0,0 +1,14 @@ +// ..##############.. +// ####..........#### +// ##....######....## +// ##..##########..## +// ##..##########..## +// ##..##########..## +// ##....######....## +// ####..........#### +// ..##############.. + +#include "images.h" + +const image_t img_key_OKi = + {9, 9, false, 11, 0, {0x7F, 0x60, 0xE7, 0x37, 0xDB, 0xED, 0xF6, 0x73, 0x83, 0x7F, 0x00}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_key_R.c b/applications/plugins/wii_ec_anal/gfx/img_key_R.c new file mode 100644 index 000000000..8b97c7b48 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_key_R.c @@ -0,0 +1,14 @@ +// ..############.. +// ################ +// ######..######## +// ######....###### +// ######......#### +// ######....###### +// ######..######## +// ################ +// ..############.. + +#include "images.h" + +const image_t img_key_R = + {8, 9, false, 9, 0, {0x7E, 0xFF, 0xEF, 0xE7, 0xE3, 0xE7, 0xEF, 0xFF, 0x7E}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_key_U.c b/applications/plugins/wii_ec_anal/gfx/img_key_U.c new file mode 100644 index 000000000..65f4cd9e0 --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_key_U.c @@ -0,0 +1,13 @@ +// ..##############.. +// ################## +// ########..######## +// ######......###### +// ####..........#### +// ################## +// ################## +// ..##############.. + +#include "images.h" + +const image_t img_key_U = + {9, 8, false, 9, 0, {0x7F, 0x7F, 0xFD, 0xFC, 0x7C, 0x1F, 0xFF, 0xFE, 0xFE}}; diff --git a/applications/plugins/wii_ec_anal/gfx/img_key_Ui.c b/applications/plugins/wii_ec_anal/gfx/img_key_Ui.c new file mode 100644 index 000000000..30c60c66e --- /dev/null +++ b/applications/plugins/wii_ec_anal/gfx/img_key_Ui.c @@ -0,0 +1,13 @@ +// ..##############.. +// ####..........#### +// ##......##......## +// ##....######....## +// ##..##########..## +// ##..............## +// ####..........#### +// ..##############.. + +#include "images.h" + +const image_t img_key_Ui = + {9, 8, false, 9, 0, {0x7F, 0x60, 0xE2, 0x33, 0x9B, 0xEC, 0x07, 0x06, 0xFE}}; diff --git a/applications/plugins/wii_ec_anal/i2c_workaround.h b/applications/plugins/wii_ec_anal/i2c_workaround.h new file mode 100644 index 000000000..b24efaf48 --- /dev/null +++ b/applications/plugins/wii_ec_anal/i2c_workaround.h @@ -0,0 +1,131 @@ +/* + As of the date of releasing this code, there is (seemingly) a bug in the FZ i2c library code + It is described here: https://github.com/flipperdevices/flipperzero-firmware/issues/1670 + + This is a short-term workaround so I can keep developing while we get to the bottom of the issue + + FYI. *something* in the following code is the fix + +void furi_hal_i2c_acquire (FuriHalI2cBusHandle* handle) +{ + // 1. Disable the power/backlight (it uses i2c) + furi_hal_power_insomnia_enter(); + // 2. Lock bus access + handle->bus->callback(handle->bus, FuriHalI2cBusEventLock); + // 3. Ensuree that no active handle set + furi_check(handle->bus->current_handle == NULL); + // 4. Set current handle + handle->bus->current_handle = handle; + // 5. Activate bus + handle->bus->callback(handle->bus, FuriHalI2cBusEventActivate); + // 6. Activate handle + handle->callback(handle, FuriHalI2cBusHandleEventActivate); +} + +void furi_hal_i2c_release (FuriHalI2cBusHandle* handle) +{ + // Ensure that current handle is our handle + furi_check(handle->bus->current_handle == handle); + // 6. Deactivate handle + handle->callback(handle, FuriHalI2cBusHandleEventDeactivate); + // 5. Deactivate bus + handle->bus->callback(handle->bus, FuriHalI2cBusEventDeactivate); + // 3,4. Reset current handle + handle->bus->current_handle = NULL; + // 2. Unlock bus + handle->bus->callback(handle->bus, FuriHalI2cBusEventUnlock); + // 1. Re-enable the power system + furi_hal_power_insomnia_exit(); +} + +*/ + +#ifndef I2C_WORKAROUND_H_ +#define I2C_WORKAROUND_H_ + +#include + +#define ENABLE_WORKAROUND 1 + +#if ENABLE_WORKAROUND == 1 +//+============================================================================ ======================================== +static inline bool furi_hal_Wi2c_is_device_ready( + FuriHalI2cBusHandle* const bus, + const uint8_t addr, + const uint32_t tmo) { + furi_hal_i2c_acquire(bus); + bool rv = furi_hal_i2c_is_device_ready(bus, addr, tmo); + furi_hal_i2c_release(bus); + return rv; +} + +//+============================================================================ +static inline bool furi_hal_Wi2c_tx( + FuriHalI2cBusHandle* const bus, + const uint8_t addr, + const void* buf, + const size_t len, + const uint32_t tmo) { + furi_hal_i2c_acquire(bus); + bool rv = furi_hal_i2c_tx(bus, addr, buf, len, tmo); + furi_hal_i2c_release(bus); + return rv; +} + +//+============================================================================ +static inline bool furi_hal_Wi2c_rx( + FuriHalI2cBusHandle* const bus, + const uint8_t addr, + void* buf, + const size_t len, + const uint32_t tmo) { + furi_hal_i2c_acquire(bus); + bool rv = furi_hal_i2c_rx(bus, addr, buf, len, tmo); + furi_hal_i2c_release(bus); + return rv; +} + +//+============================================================================ +static inline bool furi_hal_Wi2c_trx( + FuriHalI2cBusHandle* const bus, + const uint8_t addr, + const void* tx, + const size_t txlen, + void* rx, + const size_t rxlen, + const uint32_t tmo) { + bool rv = furi_hal_Wi2c_tx(bus, addr, tx, txlen, tmo); + if(rv) rv = furi_hal_Wi2c_rx(bus, addr, rx, rxlen, tmo); + return rv; +} + +//----------------------------------------------------------------------------- ---------------------------------------- +#define furi_hal_i2c_is_device_ready(...) furi_hal_Wi2c_is_device_ready(__VA_ARGS__) +#define furi_hal_i2c_tx(...) furi_hal_Wi2c_tx(__VA_ARGS__) +#define furi_hal_i2c_rx(...) furi_hal_Wi2c_rx(__VA_ARGS__) +#define furi_hal_i2c_trx(...) furi_hal_Wi2c_trx(__VA_ARGS__) + +#endif //ENABLE_WORKAROUND + +//+============================================================================ ======================================== +// Some devices take a moment to respond to read requests +// The puts a delay between the address being set and the data being read +// +static inline bool furi_hal_i2c_trxd( + FuriHalI2cBusHandle* const bus, + const uint8_t addr, + const void* tx, + const size_t txlen, + void* rx, + const size_t rxlen, + const uint32_t tmo, + const uint32_t us) { + bool rv = furi_hal_i2c_tx(bus, addr, tx, txlen, tmo); + if(rv) { + furi_delay_us(us); + rv = furi_hal_i2c_rx(bus, addr, rx, rxlen, tmo); + } + return rv; +} + +#endif //I2C_WORKAROUND_H_ diff --git a/applications/plugins/wii_ec_anal/info.sh b/applications/plugins/wii_ec_anal/info.sh new file mode 100644 index 000000000..e009eb118 --- /dev/null +++ b/applications/plugins/wii_ec_anal/info.sh @@ -0,0 +1,11 @@ +echo "MARKED AS TODO" +echo "==============" +grep //! *.c *.h + +echo -e "\nSUPPORTED CONTROLLERS" +echo "=====================" +grep '\[PID_.*{ {' wii_ec.c | head -n -3 | sed 's/\s*\(.*\)/\1/' + +echo -e "\nLOGGING" +echo "=======" +grep LOG_LEVEL *.h | grep -v '#if ' diff --git a/applications/plugins/wii_ec_anal/notes.txt b/applications/plugins/wii_ec_anal/notes.txt new file mode 100644 index 000000000..61b6e29af --- /dev/null +++ b/applications/plugins/wii_ec_anal/notes.txt @@ -0,0 +1,87 @@ +//+============================================================================ ======================================== +// Select font +// A full list of u8g2 fonts can be found here: +// https://github.com/olikraus/u8g2/wiki/fntlistall +// ...and here are the ones available in FZ (currently: all of them): +// grep -P '.*u8g2.*\[[0-9]*\]' lib/u8g2/u8g2_fonts.c | sed 's/.*\(u8g2_.*\)\[.*/\1/' +// +#if 0 //! Extra fonts is just too memory hungry +#include +void setFont (Canvas* const canvas, const uint8_t* font) +{ + u8g2_SetFontMode(&canvas->fb, 1); // no idea - but canvas.c does it + u8g2_SetFont(&canvas->fb, font); +} +#endif + +litui : @BlueChip for posterity, the function to break at is flipper_application_spawn. At that point, you can set new breakpoints in your fap code and continue. + +/* + +This is wrong on quite a few levels! +https://training.ti.com/introduction-i2c-reserved-addresses + +void doit (void) +{ + furi_hal_i2c_acquire(&furi_hal_i2c_handle_external); + printf("Scanning external i2c on PC0(SCL)/PC1(SDA)\r\n" + "Clock: 100khz, 7bit address\r\n" + "\r\n"); + printf(" | 0 1 2 3 4 5 6 7 8 9 A B C D E F\r\n"); + printf("--+--------------------------------\r\n"); + for(uint8_t row = 0; row < 0x8; row++) { + printf("%x | ", row); + for(uint8_t column = 0; column <= 0xF; column++) { + bool ret = furi_hal_i2c_is_device_ready( + &furi_hal_i2c_handle_external, ((row << 4) + column) << 1, 2); + printf("%c ", ret ? '#' : '-'); + } + printf("\r\n"); + } + furi_hal_i2c_release(&furi_hal_i2c_handle_external); +} +*/ + + +region locking : firmware/targets/f7/furi_hal/furi_hal_region.c + + +# if 0 //! scrolling works beautifully, but the LCD refresh can't keep up :( + // Waveform + if (cnt) { // start + for (int a = ACC_1; a < ACC_N; a++) { + canvas_draw_dot(canvas, x,y[a]+v[a][idx]); + for (int i = 1; i < aw -cnt; i++) { + canvas_draw_line(canvas, x+i,y[a]+v[a][i-1] , x+i,y[a]+v[a][i]); + } + } + } else { // scroll + for (int a = ACC_1; a < ACC_N; a++) { + for (int i = 0; i < aw; i++) { + int off = (idx +i) %aw; + int prev = off ? off-1 : aw-1; + canvas_draw_line(canvas, x+i,y[a]+v[a][prev] , x+i,y[a]+v[a][off]); + } + } + } + +# else + int end = idx ? idx : aw; + for (int a = ACC_1; a < ACC_N; a++) { + canvas_draw_dot(canvas, x,y[a]+v[a][idx]); + if (state->apause) { + for (int i = 1; i < end; i++) + canvas_draw_line(canvas, x+i,y[a]+v[a][i-1] , x+i,y[a]+v[a][i]); + } else { + for (int i = 1; i < end; i++) + canvas_draw_line(canvas, x+i,y[a]+v[a][i-1] , x+i,y[a]+v[a][i]); + for (int i = end+10; i < aw -cnt; i++) + canvas_draw_line(canvas, x+i,y[a]+v[a][i-1] , x+i,y[a]+v[a][i]); + } + } + + // Wipe bar + if (end < aw) canvas_draw_line(canvas, x+end,y[0], x+end,y[2]+ah-1); + if (++end < aw) canvas_draw_line(canvas, x+end,y[0], x+end,y[2]+ah-1); + if (++end < aw) canvas_draw_line(canvas, x+end,y[0], x+end,y[2]+ah-1); +# endif diff --git a/applications/plugins/wii_ec_anal/wii_anal.c b/applications/plugins/wii_ec_anal/wii_anal.c new file mode 100644 index 000000000..f0af1c9c5 --- /dev/null +++ b/applications/plugins/wii_ec_anal/wii_anal.c @@ -0,0 +1,543 @@ +//----------------------------------------------------------------------------- ---------------------------------------- +// Includes +// + +// System libs +#include // malloc +#include // uint32_t +#include // __VA_ARGS__ +#include +#include + +// FlipperZero libs +#include // Core API +#include // GUI (screen/keyboard) API +#include // GUI Input extensions +#include + +// Do this first! +#define ERR_C_ // Do this in precisely ONE file +#include "err.h" // Error numbers & messages + +#include "bc_logging.h" + +// Local headers +#include "wii_anal.h" // Various enums and struct declarations +#include "wii_i2c.h" // Wii i2c functions +#include "wii_ec.h" // Wii Extension Controller functions (eg. draw) +#include "wii_anal_keys.h" // key mappings +#include "gfx/images.h" // Images +#include "wii_anal_lcd.h" // Drawing functions +#include "wii_anal_ec.h" // Wii controller events + +#include "wii_anal_ver.h" // Version number + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// OOOOO // SSSSS CCCCC AAA L L BBBB AAA CCCC K K SSSSS +// O O /// S C A A L L B B A A C K K S +// O O /// SSSSS C AAAAA L L BBBB AAAAA C KKK SSSSS +// O O /// S C A A L L B B A A C K K S +// OOOOO // SSSSS CCCCC A A LLLLL LLLLL BBBB A A CCCC K K SSSSS +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +//+============================================================================ ======================================== +// OS Callback : Timer tick +// We register this function to be called when the OS signals a timer 'tick' event +// +static void cbTimer(FuriMessageQueue* queue) { + ENTER; + furi_assert(queue); + + eventMsg_t message = {.id = EVID_TICK}; + furi_message_queue_put(queue, &message, 0); + + LEAVE; + return; +} + +//+============================================================================ ======================================== +// OS Callback : Keypress +// We register this function to be called when the OS detects a keypress +// +static void cbInput(InputEvent* event, FuriMessageQueue* queue) { + ENTER; + furi_assert(queue); + furi_assert(event); + + // Put an "input" event message on the message queue + eventMsg_t message = {.id = EVID_KEY, .input = *event}; + furi_message_queue_put(queue, &message, FuriWaitForever); + + LEAVE; + return; +} + +//+============================================================================ +// Show version number +// +static void showVer(Canvas* const canvas) { + show(canvas, 0, 59, &img_3x5_v, SHOW_SET_BLK); + show(canvas, 4, 59, VER_MAJ, SHOW_SET_BLK); + canvas_draw_frame(canvas, 8, 62, 2, 2); + show(canvas, 11, 59, VER_MIN, SHOW_SET_BLK); +} + +//+============================================================================ +// OS Callback : Draw request +// We register this function to be called when the OS requests that the screen is redrawn +// +// We actually instruct the OS to perform this request, after we update the interface +// I guess it's possible that this instruction may able be issued by other threads !? +// +static void cbDraw(Canvas* const canvas, void* ctx) { + ENTER; + furi_assert(canvas); + furi_assert(ctx); + + state_t* state = NULL; + + // Try to acquire the mutex for the plugin state variables, timeout = 25mS + if(!(state = (state_t*)acquire_mutex((ValueMutex*)ctx, 25))) return; + + switch(state->scene) { + //--------------------------------------------------------------------- + case SCENE_SPLASH: + show(canvas, 2, 0, &img_csLogo_FULL, SHOW_SET_BLK); + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 64, 43, AlignCenter, AlignTop, "Wii Extension Controller"); + canvas_draw_str_aligned(canvas, 64, 55, AlignCenter, AlignTop, "Protocol Analyser"); + + showVer(canvas); + + break; + + //--------------------------------------------------------------------- + case SCENE_RIP: + show(canvas, 0, 0, &img_RIP, SHOW_SET_BLK); + break; + + //--------------------------------------------------------------------- + case SCENE_WAIT: +#define xo 2 + + show(canvas, 3 + xo, 10, &img_ecp_port, SHOW_SET_BLK); + + BOX_TL(22 + xo, 6, 82 + xo, 23); // 3v3 + BOX_TL(48 + xo, 21, 82 + xo, 23); // C1 + BOX_BL(22 + xo, 41, 82 + xo, 58); // C0 + BOX_BL(48 + xo, 41, 82 + xo, 44); // Gnd + + show(canvas, 90 + xo, 3, &img_6x8_3, SHOW_SET_BLK); // 3v3 + show(canvas, 97 + xo, 3, &img_6x8_v, SHOW_SET_BLK); + show(canvas, 104 + xo, 3, &img_6x8_3, SHOW_SET_BLK); + + show(canvas, 90 + xo, 18, &img_6x8_C, SHOW_SET_BLK); // C1 <-> + show(canvas, 98 + xo, 18, &img_6x8_1, SHOW_SET_BLK); + show(canvas, 107 + xo, 16, &img_ecp_SDA, SHOW_SET_BLK); + + show(canvas, 90 + xo, 40, &img_6x8_G, SHOW_SET_BLK); // Gnd + show(canvas, 97 + xo, 40, &img_6x8_n, SHOW_SET_BLK); + show(canvas, 104 + xo, 40, &img_6x8_d, SHOW_SET_BLK); + + show(canvas, 90 + xo, 54, &img_6x8_C, SHOW_SET_BLK); // C0 _-_- + show(canvas, 98 + xo, 54, &img_6x8_0, SHOW_SET_BLK); + show(canvas, 108 + xo, 54, &img_ecp_SCL, SHOW_SET_BLK); + + show(canvas, 0, 0, &img_csLogo_Small, SHOW_SET_BLK); + showVer(canvas); + +#undef xo + break; + + //--------------------------------------------------------------------- + case SCENE_DEBUG: + canvas_set_font(canvas, FontSecondary); + + show(canvas, 0, 0, &img_key_U, SHOW_SET_BLK); + canvas_draw_str_aligned(canvas, 11, 0, AlignLeft, AlignTop, "Initialise Perhipheral"); + + show(canvas, 0, 11, &img_key_OK, SHOW_SET_BLK); + canvas_draw_str_aligned(canvas, 11, 11, AlignLeft, AlignTop, "Read values [see log]"); + + show(canvas, 0, 23, &img_key_D, SHOW_SET_BLK); + canvas_draw_str_aligned(canvas, 11, 22, AlignLeft, AlignTop, "Restart Scanner"); + + show(canvas, 0, 33, &img_key_Back, SHOW_SET_BLK); + canvas_draw_str_aligned(canvas, 11, 33, AlignLeft, AlignTop, "Exit"); + + break; + + //--------------------------------------------------------------------- + default: + if(state->ec.pidx >= PID_ERROR) { + ERROR("%s : bad PID = %d", __func__, state->ec.pidx); + } else { + if((state->scene == SCENE_DUMP) || !ecId[state->ec.pidx].show) + ecId[PID_UNKNOWN].show(canvas, state); + else + ecId[state->ec.pidx].show(canvas, state); + } + break; + } + + // Release the mutex + release_mutex((ValueMutex*)ctx, state); + + LEAVE; + return; +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// SSSSS TTTTT AAA TTTTT EEEEE V V AAA RRRR IIIII AAA BBBB L EEEEE SSSSS +// S T A A T E V V A A R R I A A B B L E S +// SSSSS T AAAAA T EEE V V AAAAA RRRR I AAAAA BBBB L EEE SSSSS +// S T A A T E V V A A R R I A A B B L E S +// SSSSS T A A T EEEEE V A A R R IIIII A A BBBB LLLLL EEEEE SSSSS +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +//+============================================================================ ======================================== +// Initialise plugin state variables +// +static inline bool stateInit(state_t* const state) { + ENTER; + furi_assert(state); + + bool rv = true; // assume success + + // Enable the main loop + state->run = true; + + // Timer + state->timerEn = false; + state->timer = NULL; + state->timerHz = furi_kernel_get_tick_frequency(); + state->fps = 30; + + // Scene + state->scene = SCENE_SPLASH; + state->scenePrev = SCENE_NONE; + state->scenePegg = SCENE_NONE; + + state->hold = 0; // show hold meters (-1=lowest, 0=current, +1=highest} + state->calib = CAL_TRACK; + state->pause = false; // animation running + state->apause = false; // auto-pause animation + + // Notifications + state->notify = NULL; + + // Perhipheral + state->ec.init = false; + state->ec.pidx = PID_UNKNOWN; + state->ec.sid = ecId[state->ec.pidx].name; + + // Controller data + memset(state->ec.pid, 0xC5, PID_LEN); // Cyborg 5ystems + memset(state->ec.calF, 0xC5, CAL_LEN); + memset(state->ec.joy, 0xC5, JOY_LEN); + + // Encryption details + state->ec.encrypt = false; + memset(state->ec.encKey, 0x00, ENC_LEN); + + // Seed the PRNG + // CYCCNT --> lib/STM32CubeWB/Drivers/CMSIS/Include/core_cm7.h + // srand(DWT->CYCCNT); + + LEAVE; + return rv; +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// MM MM AAA IIIII N N +// M M M A A I NN N +// M M M AAAAA I N N N +// M M A A I N NN +// M M A A IIIII N N +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +//+============================================================================ ======================================== +// Enable/Disable scanning +// +void timerEn(state_t* state, bool on) { + ENTER; + furi_assert(state); + + // ENable scanning + if(on) { + if(state->timerEn) { + WARN(wii_errs[WARN_SCAN_START]); + } else { + // Set the timer to fire at 'fps' times/second + if(furi_timer_start(state->timer, state->timerHz / state->fps) == FuriStatusOk) { + state->timerEn = true; + INFO("%s : monitor started", __func__); + } else { + ERROR(wii_errs[ERR_TIMER_START]); + } + } + + // DISable scanning + } else { + if(!state->timerEn) { + WARN(wii_errs[WARN_SCAN_STOP]); + } else { + // Stop the timer + if(furi_timer_stop(state->timer) == FuriStatusOk) { + state->timerEn = false; + INFO("%s : monitor stopped", __func__); + } else { + ERROR(wii_errs[ERR_TIMER_STOP]); + } + } + } + + LEAVE; + return; +} + +//+============================================================================ ======================================== +// Plugin entry point +// +int32_t wii_ec_anal(void) { + ENTER; + + // ===== Variables ===== + err_t error = 0; // assume success + Gui* gui = NULL; + ViewPort* vpp = NULL; + state_t* state = NULL; + ValueMutex mutex = {0}; + FuriMessageQueue* queue = NULL; + const uint32_t queueSz = 20; // maximum messages in queue + uint32_t tmo = (3.5f * 1000); // timeout splash screen after N seconds + + // The queue will contain plugin event-messages + // --> local + eventMsg_t msg = {0}; + + INFO("BEGIN"); + + // ===== Message queue ===== + // 1. Create a message queue (for up to 8 (keyboard) event messages) + if(!(queue = furi_message_queue_alloc(queueSz, sizeof(msg)))) { + ERROR(wii_errs[(error = ERR_MALLOC_QUEUE)]); + goto bail; + } + + // ===== Create GUI Interface ===== + // 2. Create a GUI interface + if(!(gui = furi_record_open("gui"))) { + ERROR(wii_errs[(error = ERR_NO_GUI)]); + goto bail; + } + + // ===== Plugin state variables ===== + // 3. Allocate space on the heap for the plugin state variables + if(!(state = malloc(sizeof(state_t)))) { + ERROR(wii_errs[(error = ERR_MALLOC_STATE)]); + goto bail; + } + // 4. Initialise the plugin state variables + if(!stateInit(state)) { + // error message(s) is/are output by stateInit() + error = 15; + goto bail; + } + // 5. Create a mutex for (reading/writing) the plugin state variables + if(!init_mutex(&mutex, state, sizeof(state))) { + ERROR(wii_errs[(error = ERR_NO_MUTEX)]); + goto bail; + } + + // ===== Viewport ===== + // 6. Allocate space on the heap for the viewport + if(!(vpp = view_port_alloc())) { + ERROR(wii_errs[(error = ERR_MALLOC_VIEW)]); + goto bail; + } + // 7a. Register a callback for input events + view_port_input_callback_set(vpp, cbInput, queue); + // 7b. Register a callback for draw events + view_port_draw_callback_set(vpp, cbDraw, &mutex); + + // ===== Start GUI Interface ===== + // 8. Attach the viewport to the GUI + gui_add_view_port(gui, vpp, GuiLayerFullscreen); + + // ===== Timer ===== + // 9. Allocate a timer + if(!(state->timer = furi_timer_alloc(cbTimer, FuriTimerTypePeriodic, queue))) { + ERROR(wii_errs[(error = ERR_NO_TIMER)]); + goto bail; + } + + // === System Notifications === + // 10. Acquire a handle for the system notification queue + if(!(state->notify = furi_record_open(RECORD_NOTIFICATION))) { + ERROR(wii_errs[(error = ERR_NO_NOTIFY)]); + goto bail; + } + patBacklight(state); // Turn on the backlight [qv. remote FAP launch] + + INFO("INITIALISED"); + + // ==================== Main event loop ==================== + + if(state->run) do { + bool redraw = false; + FuriStatus status = FuriStatusErrorTimeout; + + // Wait for a message + // while ((status = furi_message_queue_get(queue, &msg, tmo)) == FuriStatusErrorTimeout) ; + status = furi_message_queue_get(queue, &msg, tmo); + + // Clear splash screen + if((state->scene == SCENE_SPLASH) && + (state->scenePrev == SCENE_NONE) && // Initial splash + ((status == FuriStatusErrorTimeout) || // timeout + ((msg.id == EVID_KEY) && (msg.input.type == InputTypeShort))) // or key-short + ) { + tmo = 60 * 1000; // increase message-wait timeout to 60secs + timerEn(state, true); // start scanning the i2c bus + status = FuriStatusOk; // pass status check + msg.id = EVID_NONE; // valid msg ID + sceneSet(state, SCENE_WAIT); // move to wait screen + } + + // Check for queue errors + if(status != FuriStatusOk) { + switch(status) { + case FuriStatusErrorTimeout: + DEBUG(wii_errs[DEBUG_QUEUE_TIMEOUT]); + continue; + case FuriStatusError: + ERROR(wii_errs[(error = ERR_QUEUE_RTOS)]); + goto bail; + case FuriStatusErrorResource: + ERROR(wii_errs[(error = ERR_QUEUE_RESOURCE)]); + goto bail; + case FuriStatusErrorParameter: + ERROR(wii_errs[(error = ERR_QUEUE_BADPRM)]); + goto bail; + case FuriStatusErrorNoMemory: + ERROR(wii_errs[(error = ERR_QUEUE_NOMEM)]); + goto bail; + case FuriStatusErrorISR: + ERROR(wii_errs[(error = ERR_QUEUE_ISR)]); + goto bail; + default: + ERROR(wii_errs[(error = ERR_QUEUE_UNK)]); + goto bail; + } + } + // Read successful + + // *** Try to lock the plugin state variables *** + if(!(state = (state_t*)acquire_mutex_block(&mutex))) { + ERROR(wii_errs[(error = ERR_MUTEX_BLOCK)]); + goto bail; + } + + // *** Handle events *** + switch(msg.id) { + //--------------------------------------------- + case EVID_TICK: // Timer events + //! I would prefer to have ecPoll() called by cbTimer() + //! ...but how does cbTimer() get the required access to the state variables? Namely: 'state->ec' + //! So, for now, the timer pushes a message to call ecPoll() + //! which, in turn, will push WIIEC event meesages! + ecPoll(&state->ec, queue); + break; + + //--------------------------------------------- + case EVID_WIIEC: // WiiMote Perhipheral + if(evWiiEC(&msg, state)) redraw = true; + break; + + //--------------------------------------------- + case EVID_KEY: // Key events + patBacklight(state); + if(evKey(&msg, state)) redraw = true; + break; + + //--------------------------------------------- + case EVID_NONE: + break; + + //--------------------------------------------- + default: // Unknown event + WARN("Unknown message.ID [%d]", msg.id); + break; + } + + // *** Update the GUI screen via the viewport *** + if(redraw) view_port_update(vpp); + + // *** Try to release the plugin state variables *** + if(!release_mutex(&mutex, state)) { + ERROR(wii_errs[(error = ERR_MUTEX_RELEASE)]); + goto bail; + } + } while(state->run); + + // ===== Game Over ===== + INFO("USER EXIT"); + +bail: + // 10. Release system notification queue + if(state->notify) { + furi_record_close(RECORD_NOTIFICATION); + state->notify = NULL; + } + + // 9. Stop the timer + if(state->timer) { + (void)furi_timer_stop(state->timer); + furi_timer_free(state->timer); + state->timer = NULL; + state->timerEn = false; + } + + // 8. Detach the viewport + gui_remove_view_port(gui, vpp); + + // 7. No need to unreqgister the callbacks + // ...they will go when the viewport is destroyed + + // 6. Destroy the viewport + if(vpp) { + view_port_enabled_set(vpp, false); + view_port_free(vpp); + vpp = NULL; + } + + // 5. Free the mutex + if(mutex.mutex) { + delete_mutex(&mutex); + mutex.mutex = NULL; + } + + // 4. Free up state pointer(s) + // none + + // 3. Free the plugin state variables + if(state) { + free(state); + state = NULL; + } + + // 2. Close the GUI + furi_record_close("gui"); + + // 1. Destroy the message queue + if(queue) { + furi_message_queue_free(queue); + queue = NULL; + } + + INFO("CLEAN EXIT ... Exit code: %d", error); + LEAVE; + return (int32_t)error; +} diff --git a/applications/plugins/wii_ec_anal/wii_anal.h b/applications/plugins/wii_ec_anal/wii_anal.h new file mode 100644 index 000000000..3aae61fdc --- /dev/null +++ b/applications/plugins/wii_ec_anal/wii_anal.h @@ -0,0 +1,89 @@ +#ifndef WII_ANAL_H_ +#define WII_ANAL_H_ + +#include // Core API +#include // GUI Input extensions +#include + +//----------------------------------------------------------------------------- ---------------------------------------- +// GUI scenes +// +typedef enum scene { + SCENE_NONE = 0, + SCENE_SPLASH = 1, + SCENE_RIP = 2, + SCENE_WAIT = 3, + SCENE_DEBUG = 4, + SCENE_DUMP = 5, + SCENE_CLASSIC = 6, + SCENE_CLASSIC_N = 7, + SCENE_NUNCHUCK = 8, + SCENE_NUNCHUCK_ACC = 9, +} scene_t; + +//----------------------------------------------------------------------------- ---------------------------------------- +#include "wii_i2c.h" +#include "wii_ec.h" + +//----------------------------------------------------------------------------- ---------------------------------------- +// A list of event IDs handled by this plugin +// +typedef enum eventID { + EVID_NONE, + EVID_UNKNOWN, + + // A full list of events can be found with: `grep -r --color "void.*set_.*_callback" applications/gui/*` + // ...A free gift to you from the makers of well written code that conforms to a good coding standard + EVID_KEY, // keypad + EVID_TICK, // tick + EVID_WIIEC, // wii extension controller +} eventID_t; + +//----------------------------------------------------------------------------- ---------------------------------------- +// An item in the event message-queue +// +typedef struct eventMsg { + eventID_t id; + union { + InputEvent input; // --> applications/input/input.h + wiiEcEvent_t wiiEc; // --> local + }; +} eventMsg_t; + +//----------------------------------------------------------------------------- ---------------------------------------- +// State variables for this plugin +// An instance of this is allocated on the heap, and the pointer is passed back to the OS +// Access to this memory is controlled by mutex +// +typedef struct state { + bool run; // true : plugin is running + + bool timerEn; // controller scanning enabled + FuriTimer* timer; // the timer + uint32_t timerHz; // system ticks per second + int fps; // poll/refresh [frames]-per-second + + int cnvW; // canvas width + int cnvH; // canvas height + scene_t scene; // current scene + scene_t scenePrev; // previous scene + scene_t scenePegg; // previous scene for easter eggs + int flash; // flash counter (flashing icons) + + int hold; // hold type: {-1=tough-peak, 0=none, +1=peak-hold} + ecCalib_t calib; // Software calibration mode + + bool pause; // Accelerometer animation pause + bool apause; // Accelerometer animation auto-pause + + NotificationApp* notify; // OS nitifcation queue (for patting the backlight watchdog timer) + + wiiEC_t ec; // Extension Controller details +} state_t; + +//============================================================================= ======================================== +// Function prototypes +// +void timerEn(state_t* state, bool on); + +#endif //WII_ANAL_H_ diff --git a/applications/plugins/wii_ec_anal/wii_anal_ec.c b/applications/plugins/wii_ec_anal/wii_anal_ec.c new file mode 100644 index 000000000..dab167bc0 --- /dev/null +++ b/applications/plugins/wii_ec_anal/wii_anal_ec.c @@ -0,0 +1,115 @@ +#include +#include + +#include "wii_anal.h" +#include "wii_anal_lcd.h" +#include "wii_anal_keys.h" + +//+============================================================================ ======================================== +// Handle Wii Extension Controller events +// +bool evWiiEC(const eventMsg_t* const msg, state_t* const state) { + bool redraw = false; + +#if LOG_LEVEL >= 4 + { + const char* s = NULL; + switch(msg->wiiEc.type) { + case WIIEC_NONE: + s = "Error"; + break; + case WIIEC_CONN: + s = "Connect"; + break; + case WIIEC_DISCONN: + s = "Disconnect"; + break; + case WIIEC_PRESS: + s = "Press"; + break; + case WIIEC_RELEASE: + s = "Release"; + break; + case WIIEC_ANALOG: + s = "Analog"; + break; + case WIIEC_ACCEL: + s = "Accel"; + break; + default: + s = "Bug"; + break; + } + INFO( + "WIIP : %s '%c' = %d", + s, + (isprint((int)msg->wiiEc.in) ? msg->wiiEc.in : '_'), + msg->wiiEc.val); + if((msg->wiiEc.type == WIIEC_CONN) || (msg->wiiEc.type == WIIEC_DISCONN)) + INFO("...%d=\"%s\"", msg->wiiEc.val, ecId[msg->wiiEc.val].name); + } +#endif + + switch(msg->wiiEc.type) { + case WIIEC_CONN: + patBacklight(state); + state->hold = 0; + state->calib = CAL_TRACK; + sceneSet(state, ecId[msg->wiiEc.val].scene); + redraw = true; + +#if 1 // Workaround for Classic Controller Pro, which shows 00's for Factory Calibration Data!? + if(state->ec.pidx == PID_CLASSIC_PRO) { + // Simulate a Long-OK keypress, to start Software Calibration mode + eventMsg_t msg = {// .id = EVID_KEY, + .input.type = InputTypeLong, + .input.key = InputKeyOk}; + key_calib(&msg, state); + } +#endif + break; + + case WIIEC_DISCONN: + patBacklight(state); + sceneSet(state, SCENE_WAIT); + redraw = true; + break; + + case WIIEC_PRESS: + if(state->scene == SCENE_NUNCHUCK_ACC) switch(msg->wiiEc.in) { + case 'z': // un-pause + state->pause = !state->pause; + break; + case 'c': // toggle auto-pause + state->pause = false; + state->apause = !state->apause; + break; + default: + break; + } + +#if 1 //! factory calibration method not known for classic triggers - this will set the digital switch point + if((state->ec.pidx == PID_CLASSIC) || (state->ec.pidx == PID_CLASSIC_PRO)) { + if(msg->wiiEc.in == 'l') state->ec.calS.classic[2].trgZL = msg->wiiEc.val; + if(msg->wiiEc.in == 'r') state->ec.calS.classic[2].trgZR = msg->wiiEc.val; + } +#endif + __attribute__((fallthrough)); + + case WIIEC_RELEASE: + patBacklight(state); + redraw = true; + break; + + case WIIEC_ANALOG: + case WIIEC_ACCEL: + ecCalibrate(&state->ec, state->calib); + redraw = true; + break; + + default: + break; + } + + return redraw; +} diff --git a/applications/plugins/wii_ec_anal/wii_anal_ec.h b/applications/plugins/wii_ec_anal/wii_anal_ec.h new file mode 100644 index 000000000..eec6b523c --- /dev/null +++ b/applications/plugins/wii_ec_anal/wii_anal_ec.h @@ -0,0 +1,14 @@ +#ifndef WII_ANAL_EC_H_ +#define WII_ANAL_EC_H_ + +#include + +//============================================================================= ======================================== +// Function prototypes +// +typedef struct eventMsg eventMsg_t; +typedef struct state state_t; + +bool evWiiEC(const eventMsg_t* const msg, state_t* const state); + +#endif //WII_ANAL_EC_H_ diff --git a/applications/plugins/wii_ec_anal/wii_anal_keys.c b/applications/plugins/wii_ec_anal/wii_anal_keys.c new file mode 100644 index 000000000..8c5c99b4e --- /dev/null +++ b/applications/plugins/wii_ec_anal/wii_anal_keys.c @@ -0,0 +1,299 @@ +#include + +#include "bc_logging.h" + +#include "wii_anal.h" + +//+============================================================================ ======================================== +// Stop Calibration mode +// +static void calStop(state_t* const state) { + state->hold = 0; // stop calibration mode + state->calib &= ~(CAL_RANGE | CAL_NOTJOY); // ... +} + +//+============================================================================ ======================================== +// Change to another scene +// +void sceneSet(state_t* const state, const scene_t scene) { + calStop(state); // Stop software calibration + state->scenePrev = state->scene; // Remember where we came from + state->scene = scene; // Go to new scene + INFO("Scene : %d -> %d", state->scenePrev, state->scene); +} + +//+============================================================================ ======================================== +// Change to an easter egg scene +// +static void sceneSetEgg(state_t* const state, const scene_t scene) { + calStop(state); // Stop software calibration + state->scenePegg = state->scene; // Remember where we came from + state->scene = scene; // Go to new scene + INFO("Scene* : %d => %d", state->scenePegg, state->scene); +} + +//+============================================================================ ======================================== +// Several EC screens have 'peak-hold' and 'calibration' features +// Enabling peak-hold on screen with no peak meters will have no effect +// So, to avoid code duplication... +// +bool key_calib(const eventMsg_t* const msg, state_t* const state) { + int used = false; // assume key is NOT-handled + + switch(msg->input.type) { + case InputTypeShort: //# input.key) { + case InputKeyUp: //# hold = (state->hold == +1) ? 0 : +1; // toggle peak hold + used = true; + break; + + case InputKeyDown: //# hold = (state->hold == -1) ? 0 : -1; // toggle trough hold + used = true; + break; + + case InputKeyOk: //# calib & CAL_RANGE) + calStop(state); // STOP softare calibration + else + ecCalibrate(&state->ec, CAL_CENTRE); // perform centre calibration + used = true; + break; + + default: + break; + } + break; + + case InputTypeLong: //# >! After INPUT_LONG_PRESS interval, asynch to InputTypeRelease + switch(msg->input.key) { + case InputKeyOk: //# >O [ LONG-OK ] + ecCalibrate(&state->ec, CAL_RESET | CAL_CENTRE); // START software calibration + state->hold = 0; + state->calib |= CAL_RANGE; + state->flash = 8; // start with flash ON + used = true; + break; + + default: + break; + } + break; + + default: + break; + } + + return used; +} + +//+============================================================================ ======================================== +// WAIT screen +// +static inline bool wait_key(const eventMsg_t* const msg, state_t* const state) { + int used = false; // assume key is NOT-handled + + if(msg->input.type == InputTypeShort) { + switch(msg->input.key) { + case InputKeyLeft: //# run = false; + used = true; + break; + + default: + break; + } + } + + return used; +} + +//+============================================================================ ======================================== +// DEBUG screen +// +static inline bool debug_key(const eventMsg_t* const msg, state_t* const state) { + int used = false; // assume key is NOT-handled + + switch(msg->input.type) { + case InputTypeShort: //# input.key) { + case InputKeyUp: { //# ec, NULL); // Initialise the controller //! NULL = no encryption + (void)init; // in case INFO is optimised out + INFO("%s : %s", __func__, (init ? "init OK" : "init fail")); + used = true; + break; + } + + case InputKeyOk: //# ec) == 0) { // Read the controller + INFO( + "%s : joy: {%02X,%02X,%02X,%02X,%02X,%02X}", + __func__, + state->ec.joy[0], + state->ec.joy[1], + state->ec.joy[2], + state->ec.joy[3], + state->ec.joy[4], + state->ec.joy[5]); + } + used = true; + break; + + case InputKeyDown: //# scenePrev); + used = true; + break; + + case InputKeyBack: //# run = false; + used = true; + break; + + default: + break; //# input.key == InputKeyBack) && (state->scenePrev == SCENE_NONE)) state->run = false; + + // ANY-other-KEY press + if(msg->input.type == InputTypeShort) { + timerEn(state, true); // Restart the timer + state->scene = state->scenePegg; + } + + return true; +} + +//+============================================================================ ======================================== +// "_pre" allows the plugin to use the key before the active scene gets a chance +// +static inline bool key_pre(const eventMsg_t* const msg, state_t* const state) { + (void)msg; + (void)state; + + return false; +} + +//+============================================================================ ======================================== +// "_post" allows the plugin to use a key if it was not used by the active scene +// +static inline bool key_post(const eventMsg_t* const msg, state_t* const state) { + int used = false; // assume key is NOT-handled + + if(msg->input.key == InputKeyBack) { + if(msg->input.type == InputTypeShort) { //# ec.init = false; // reset/disconnect the controller + sceneSet(state, SCENE_WAIT); + used = true; + + } else if(msg->input.type == InputTypeLong) { //# >B [LONG-BACK] + state->run = false; // Signal the plugin to exit + used = true; + } + } + + // Easter eggs + switch(state->scene) { + case SCENE_SPLASH: // Scenes that do NOT offer Easter eggs + case SCENE_RIP: + case SCENE_DEBUG: + break; + default: + if(msg->input.type == InputTypeLong) { + switch(msg->input.key) { + case InputKeyDown: //# >D [LONG-DOWN] + timerEn(state, false); // Stop the timer + sceneSetEgg(state, SCENE_DEBUG); + used = true; + break; + + case InputKeyLeft: //# >L [ LONG-LEFT ] + timerEn(state, false); // Stop the timer + sceneSetEgg(state, SCENE_SPLASH); + used = true; + break; + + case InputKeyUp: //# >U [ LONG-UP ] + timerEn(state, false); // Stop the timer + sceneSetEgg(state, SCENE_RIP); + used = true; + break; + + default: + break; + } + } + break; + } + + return used; +} + +//+============================================================================ ======================================== +// Handle a key press event +// +bool evKey(const eventMsg_t* const msg, state_t* const state) { + furi_assert(msg); + furi_assert(state); + + bool used = key_pre(msg, state); + + if(!used) switch(state->scene) { + case SCENE_SPLASH: //... + case SCENE_RIP: + used = splash_key(msg, state); + break; + + case SCENE_WAIT: + used = wait_key(msg, state); + break; + case SCENE_DEBUG: + used = debug_key(msg, state); + break; + + default: + if(state->ec.pidx >= PID_ERROR) { + ERROR("%s : bad PID = %d", __func__, state->ec.pidx); + } else { + if((state->scene == SCENE_DUMP) || !ecId[state->ec.pidx].keys) + ecId[PID_UNKNOWN].keys(msg, state); + else + ecId[state->ec.pidx].keys(msg, state); + } + break; + + case SCENE_NONE: + break; + } + + if(!used) used = key_post(msg, state); + + return used; +} diff --git a/applications/plugins/wii_ec_anal/wii_anal_keys.h b/applications/plugins/wii_ec_anal/wii_anal_keys.h new file mode 100644 index 000000000..c10fcd1ef --- /dev/null +++ b/applications/plugins/wii_ec_anal/wii_anal_keys.h @@ -0,0 +1,16 @@ +#ifndef WII_ANAL_KEYS_H_ +#define WII_ANAL_KEYS_H_ + +//============================================================================= ======================================== +// Function prototypes +// +#include // bool +typedef struct eventMsg eventMsg_t; +typedef struct state state_t; +typedef enum scene scene_t; + +void sceneSet(state_t* const state, const scene_t scene); +bool key_calib(const eventMsg_t* const msg, state_t* const state); +bool evKey(const eventMsg_t* const msg, state_t* const state); + +#endif //WII_ANAL_KEYS_H_ diff --git a/applications/plugins/wii_ec_anal/wii_anal_lcd.c b/applications/plugins/wii_ec_anal/wii_anal_lcd.c new file mode 100644 index 000000000..921a3708e --- /dev/null +++ b/applications/plugins/wii_ec_anal/wii_anal_lcd.c @@ -0,0 +1,282 @@ +#include "wii_anal.h" +#include "gfx/images.h" // Images + +//----------------------------------------------------------------------------- ---------------------------------------- +// A couple of monospaced hex fonts +// +const image_t* img_6x8[16] = { + &img_6x8_0, + &img_6x8_1, + &img_6x8_2, + &img_6x8_3, + &img_6x8_4, + &img_6x8_5, + &img_6x8_6, + &img_6x8_7, + &img_6x8_8, + &img_6x8_9, + &img_6x8_A, + &img_6x8_B, + &img_6x8_C, + &img_6x8_D, + &img_6x8_E, + &img_6x8_F, +}; + +const image_t* img_5x7[16] = { + &img_5x7_0, + &img_5x7_1, + &img_5x7_2, + &img_5x7_3, + &img_5x7_4, + &img_5x7_5, + &img_5x7_6, + &img_5x7_7, + &img_5x7_8, + &img_5x7_9, + &img_5x7_A, + &img_5x7_B, + &img_5x7_C, + &img_5x7_D, + &img_5x7_E, + &img_5x7_F, +}; + +//+============================================================================ ======================================== +// void backlightOn (void) +// { +// // Acquire a handle for the system notification queue +// // Do this ONCE ... at plugin startup +// NotificationApp* notifications = furi_record_open(RECORD_NOTIFICATION); +// +// // Pat the backlight watchdog +// // Send the (predefined) message sequence {backlight_on, end} +// // --> applications/notification/*.c +// notification_message(notifications, &sequence_display_backlight_on); +// +// // Release the handle for the system notification queue +// // Do this ONCE ... at plugin quit +// furi_record_close(RECORD_NOTIFICATION); +// } +void patBacklight(state_t* state) { + notification_message(state->notify, &sequence_display_backlight_on); +} + +//============================================================================= ======================================== +// Show a hex number in an inverted box (for ananlogue readings) +// +void showHex( + Canvas* const canvas, + uint8_t x, + uint8_t y, + const uint32_t val, + const uint8_t cnt, + const int b) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_box(canvas, x++, y++, 1 + (cnt * (6 + 1)), 10); + + // thicken border + if(b == 2) canvas_draw_frame(canvas, x - 2, y - 2, 1 + (cnt * (6 + 1)) + 2, 10 + 2); + + for(int i = (cnt - 1) * 4; i >= 0; i -= 4, x += 6 + 1) + show(canvas, x, y, img_6x8[(val >> i) & 0xF], SHOW_SET_WHT); +} + +//============================================================================= ======================================== +// Show the up/down "peak hold" controls in the bottom right +// +void showPeakHold(state_t* const state, Canvas* const canvas, const int hold) { + switch(hold) { + case 0: + show(canvas, 119, 51, &img_key_U, SHOW_CLR_BLK); + show(canvas, 119, 56, &img_key_D, SHOW_CLR_BLK); + break; + case +1: + canvas_set_color(canvas, ColorBlack); + canvas_draw_box(canvas, 120, 52, 7, 6); + show(canvas, 119, 51, &img_key_U, SHOW_CLR_WHT); + show(canvas, 119, 56, &img_key_D, SHOW_CLR_BLK); + break; + case -1: + show(canvas, 119, 51, &img_key_U, SHOW_CLR_BLK); + canvas_draw_box(canvas, 120, 57, 7, 6); + show(canvas, 119, 56, &img_key_D, SHOW_CLR_WHT); + break; + default: + break; + } + canvas_set_color(canvas, ColorBlack); + canvas_draw_frame(canvas, 119, 51, 9, 13); + + // calibration indicator + show( + canvas, + 108, + 55, + ((state->calib & CAL_RANGE) && (++state->flash & 8)) ? &img_key_OKi : &img_key_OK, + SHOW_SET_BLK); +} + +//============================================================================= ======================================== +// This code performs a FULL calibration on the device EVERY time it draws a joystick +//...This is NOT a good way forward for anything other than a test tool. +// +// Realistically you would do all the maths when the controller is connected +// or, if you prefer (and it IS a good thing), have a "calibrate controller" menu option +// ...and then just use a lookup table, or trivial formual +// +// THIS algorithm chops the joystick in to one of 9 zones +// Eg. {FullLeft, Left3, Left2, Left1, Middle, Right1, Right2, Right3, FullRight} +// FullLeft and FullRight have a deadzone of N [qv. xDead] ..a total of N+1 positions +// Middle has a deadzone of N EACH WAY ...a total of 2N+1 positions +// +// If the remaining range does not divide evenly in to three zones, +// the first remainder is added to zone3, +// and the second remainder (if there is one) is added to zone2 +// ...giving finer control near the centre of the joystick +// +// The value of the deadzone is based on the number of bits in the +// joystcik {x,y} values - the larger the range, the larger the deadzone. +// +// 03 15 29 +// |<<| Calibration points |==| |>>| +// 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F +// |---| |________________________| |------| |______________________________| |---| +// |r=2| | range = 9 | | r=3 | | range = 11 | |r=2| +// Zones: |-4 | |-3 |-2 |-1 | |0 | |+1 |+2 |+3 | |+4 | +// +// This is not "the right way to do it" ...this is "one way to do it" +// Consider you application, and what the user is trying to achieve +// Aim a gun - probably need to be more accurate +// Turn and object - this is probably good enough +// Start slowly & pick up speed - how about a log or sine curve? +// +void showJoy( + Canvas* const canvas, + const uint8_t x, + const uint8_t y, // x,y is the CENTRE of the Joystick + const uint8_t xMin, + const uint8_t xMid, + const uint8_t xMax, + const uint8_t yMin, + const uint8_t yMid, + const uint8_t yMax, + const uint8_t xPos, + const uint8_t yPos, + const uint8_t bits) { + int xOff = 0; // final offset of joystick hat image + int yOff = 0; + + int xDead = (bits < 7) ? (1 << 0) : (1 << 3); // dead zone (centre & limits) + int yDead = xDead; + + // This code is NOT optimised ...and it's still barely readable! + if((xPos >= (xMid - xDead)) && (xPos <= (xMid + xDead))) + xOff = 0; // centre [most likely] + else if(xPos <= (xMin + xDead)) + xOff = -4; // full left + else if(xPos >= (xMax - xDead)) + xOff = +4; // full right + else if(xPos < (xMid - xDead)) { // part left + // very much hard-coded for 3 interim positions + int lo = (xMin + xDead) + 1; // lowest position + int hi = (xMid - xDead) - 1; // highest position + + // this is the only duplicated bit of code + int range = (hi - lo) + 1; // range covered + int div = range / 3; // each division (base amount, eg. 17/3==5) + int rem = range - (div * 3); // remainder (ie. range%3) + + // int hi1 = hi; // lowest value for zone #-1 + // int lo1 = hi1 -div +1; // highest value for zone #-1 + // int hi2 = lo1 -1; // lowest value for zone #-2 + // int lo2 = hi2 -div +1 -(rem==2); // highest value for zone #-2 expand out remainder + // int hi3 = lo2 -1; // lowest value for zone #-3 + // int lo3 = hi3 -div +1 -(rem>=1); // highest value for zone #-3 expand out remainder + + int lo1 = hi - div + 1; // (in brevity) + int hi3 = hi - div - div - (rem == 2); // ... + + if(xPos <= hi3) + xOff = -3; // zone #-3 + else if(xPos >= lo1) + xOff = -1; // zone #-1 + else + xOff = -2; // zone #-2 + + } else /*if (xPos > (xMid +xDead))*/ { // part right + // very much hard-coded for 3 interim positions + int lo = (xMid + xDead) + 1; // lowest position + int hi = (xMax - xDead) - 1; // highest position + + int range = (hi - lo) + 1; // range covered + int div = range / 3; // each division (base amount, eg. 17/3==5) + int rem = range - (div * 3); // remainder (ie. range%3) + + // int lo1 = lo; // lowest value for zone #+1 + // int hi1 = lo +div -1; // highest value for zone #+1 + // int lo2 = hi1 +1; // lowest value for zone #+2 + // int hi2 = lo2 +div -1 +(rem==2); // highest value for zone #+2 expand out remainder + // int lo3 = hi2 +1; // lowest value for zone #+3 + // int hi3 = lo3 +div -1 +(rem>=1); // highest value for zone #+3 expand out remainder + + int hi1 = lo + div - 1; // (in brevity) + int lo3 = lo + div + div + (rem == 2); // ... + + if(xPos <= hi1) + xOff = 1; // zone #1 + else if(xPos >= lo3) + xOff = 3; // zone #3 + else + xOff = 2; // zone #2 + } + + // All this to print a 3x3 square (in the right place) - LOL! + if((yPos >= (yMid - yDead)) && (yPos <= (yMid + yDead))) + yOff = 0; // centre [most likely] + else if(yPos <= (yMin + yDead)) + yOff = +4; // full down + else if(yPos >= (yMax - yDead)) + yOff = -4; // full up + else if(yPos < (yMid - yDead)) { // part down + int lo = (yMin + yDead) + 1; // lowest position + int hi = (yMid - yDead) - 1; // highest position + + int range = (hi - lo) + 1; // range covered + int div = range / 3; // each division (base amount, eg. 17/3==5) + int rem = range - (div * 3); // remainder (ie. range%3) + + int lo1 = hi - div + 1; // (in brevity) + int hi3 = hi - div - div - (rem == 2); // ... + + if(yPos <= hi3) + yOff = +3; // zone #3 + else if(yPos >= lo1) + yOff = +1; // zone #1 + else + yOff = +2; // zone #2 + + } else /*if (yPos > (yMid +yDead))*/ { // part up + int lo = (yMid + yDead) + 1; // lowest position + int hi = (yMax - yDead) - 1; // highest position + + int range = (hi - lo) + 1; // range covered + int div = range / 3; // each division (base amount, eg. 17/3==5) + int rem = range - (div * 3); // remainder (ie. range%3) + + int hi1 = lo + div - 1; // (in brevity) + int lo3 = lo + div + div + (rem == 2); // ... + + if(yPos <= hi1) + yOff = -1; // zone #-1 + else if(yPos >= lo3) + yOff = -3; // zone #-3 + else + yOff = -2; // zone #-2 + } + + show(canvas, x - (img_cc_Joy.w / 2), y - (img_cc_Joy.h / 2), &img_cc_Joy, SHOW_SET_BLK); + + // All ^that^ for v-this-v - LOL!! + canvas_draw_box(canvas, (x - 1) + xOff, (y - 1) + yOff, 3, 3); +} diff --git a/applications/plugins/wii_ec_anal/wii_anal_lcd.h b/applications/plugins/wii_ec_anal/wii_anal_lcd.h new file mode 100644 index 000000000..e52a3adc6 --- /dev/null +++ b/applications/plugins/wii_ec_anal/wii_anal_lcd.h @@ -0,0 +1,57 @@ +#ifndef WII_ANAL_LCD_H_ +#define WII_ANAL_LCD_H_ + +//----------------------------------------------------------------------------- ---------------------------------------- +// A couple of monospaced hex fonts +// +#include "gfx/images.h" + +extern const image_t* img_6x8[]; +extern const image_t* img_5x7[]; + +//============================================================================= ======================================== +// macros to draw only two sides of a box +// these are used for drawing the wires on the WAIT screen +// +#define BOX_TL(x1, y1, x2, y2) \ + do { \ + canvas_draw_frame(canvas, x1, y1, x2 - x1 + 1, 2); \ + canvas_draw_frame(canvas, x1, y1 + 2, 2, y2 - y1 + 1 - 2); \ + } while(0) + +#define BOX_BL(x1, y1, x2, y2) \ + do { \ + canvas_draw_frame(canvas, x1, y2 - 1, x2 - x1 + 1, 2); \ + canvas_draw_frame(canvas, x1, y1, 2, y2 - y1 + 1 - 2); \ + } while(0) + +//============================================================================= ======================================== +// Function prototypes +// +void patBacklight(state_t* state); + +void showHex( + Canvas* const canvas, + uint8_t x, + uint8_t y, + const uint32_t val, + const uint8_t cnt, + const int b); + +void showPeakHold(state_t* const state, Canvas* const canvas, const int hold); + +void showJoy( + Canvas* const canvas, + const uint8_t x, + const uint8_t y, // x,y is the CENTRE of the Joystick + const uint8_t xMin, + const uint8_t xMid, + const uint8_t xMax, + const uint8_t yMin, + const uint8_t yMid, + const uint8_t yMax, + const uint8_t xPos, + const uint8_t yPos, + const uint8_t bits); + +#endif //WII_ANAL_LCD_H_ diff --git a/applications/plugins/wii_ec_anal/wii_anal_ver.h b/applications/plugins/wii_ec_anal/wii_anal_ver.h new file mode 100644 index 000000000..3f2c8c0e6 --- /dev/null +++ b/applications/plugins/wii_ec_anal/wii_anal_ver.h @@ -0,0 +1,9 @@ +#ifndef WII_ANAL_VER_H_ +#define WII_ANAL_VER_H_ + +#include "gfx/images.h" + +#define VER_MAJ &img_3x5_1 +#define VER_MIN &img_3x5_0 + +#endif //WII_ANAL_VER_H_ diff --git a/applications/plugins/wii_ec_anal/wii_ec.c b/applications/plugins/wii_ec_anal/wii_ec.c new file mode 100644 index 000000000..00dcbf922 --- /dev/null +++ b/applications/plugins/wii_ec_anal/wii_ec.c @@ -0,0 +1,298 @@ +#include +#include // Core API + +#include "wii_anal.h" +#include "wii_i2c.h" +#include "wii_ec.h" +#include "bc_logging.h" + +#include "gfx/images.h" // Images +#include "wii_anal_lcd.h" // Drawing functions +#include "wii_anal_keys.h" // key mappings + +//----------------------------------------------------------------------------- ---------------------------------------- +// List of known perhipherals +// +// More perhipheral ID codes here: https://wiibrew.org/wiki/Wiimote/Extension_Controllers#The_New_Way +// +const ecId_t ecId[PID_CNT] = { + [PID_UNKNOWN] = + {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + "Unknown Perhipheral", + SCENE_DUMP, + NULL, + NULL, + NULL, + NULL, + ec_show, + ec_key}, + + // If you're wise, ONLY edit this bit + [PID_NUNCHUCK] = + {{0x00, 0x00, 0xA4, 0x20, 0x00, 0x00}, + "Nunchuck", + SCENE_NUNCHUCK, + NULL, + nunchuck_decode, + nunchuck_msg, + nunchuck_calib, + nunchuck_show, + nunchuck_key}, + + [PID_NUNCHUCK_R2] = + {{0xFF, 0x00, 0xA4, 0x20, 0x00, 0x00}, + "Nunchuck (rev2)", + SCENE_NUNCHUCK, + NULL, + nunchuck_decode, + nunchuck_msg, + nunchuck_calib, + nunchuck_show, + nunchuck_key}, + + [PID_CLASSIC] = + {{0x00, 0x00, 0xA4, 0x20, 0x01, 0x01}, + "Classic Controller", + SCENE_CLASSIC, + NULL, + classic_decode, + classic_msg, + classic_calib, + classic_show, + classic_key}, + + [PID_CLASSIC_PRO] = + {{0x01, 0x00, 0xA4, 0x20, 0x01, 0x01}, + "Classic Controller Pro", + SCENE_CLASSIC, + NULL, + classic_decode, + classic_msg, + classic_calib, + classic_show, + classic_key}, + + [PID_BALANCE] = + {{0x00, 0x00, 0xA4, 0x20, 0x04, 0x02}, + "Balance Board", + SCENE_DUMP, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL}, + + [PID_GH_GUITAR] = + {{0x00, 0x00, 0xA4, 0x20, 0x01, 0x03}, + "Guitar Hero Guitar", + SCENE_DUMP, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL}, + + [PID_GH_DRUMS] = + {{0x01, 0x00, 0xA4, 0x20, 0x01, 0x03}, + "Guitar Hero World Tour Drums", + SCENE_DUMP, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL}, + + [PID_TURNTABLE] = + {{0x03, 0x00, 0xA4, 0x20, 0x01, 0x03}, + "DJ Hero Turntable", + SCENE_DUMP, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL}, + + [PID_TAIKO_DRUMS] = + {{0x00, 0x00, 0xA4, 0x20, 0x01, 0x11}, + "Taiko Drum Controller)", + SCENE_DUMP, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL}, // Taiko no Tatsujin TaTaCon (Drum controller) + + [PID_UDRAW] = + {{0xFF, 0x00, 0xA4, 0x20, 0x00, 0x13}, + "uDraw Tablet", + SCENE_DUMP, + udraw_init, + NULL, + NULL, + NULL, + NULL, + NULL}, //! same as drawsome? + // ----- + + [PID_ERROR] = + {{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + "Read Error", + SCENE_NONE, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL}, + + [PID_NULL] = {{0}, NULL, SCENE_NONE, NULL, NULL, NULL, NULL, NULL, NULL} // last entry +}; + +//+============================================================================ ======================================== +void ecDecode(wiiEC_t* pec) { + if(ecId[pec->pidx].decode) ecId[pec->pidx].decode(pec); +} + +//+============================================================================ ======================================== +void ecCalibrate(wiiEC_t* const pec, ecCalib_t c) { + if(ecId[pec->pidx].calib) ecId[pec->pidx].calib(pec, c); +} + +//+============================================================================ ======================================== +void ecPoll(wiiEC_t* const pec, FuriMessageQueue* const queue) { + ENTER; + furi_assert(queue); + + if(!pec->init) { + // Attempt to initialise + if(ecInit(pec, NULL)) { //! need a way to auto-start with encryption enabled + eventMsg_t msg = { + .id = EVID_WIIEC, .wiiEc = {.type = WIIEC_CONN, .in = '<', .val = pec->pidx}}; + furi_message_queue_put(queue, &msg, 0); + } + + } else { + // Attempt to read + switch(ecRead(pec)) { + case 2: { // device gone + eventMsg_t msg = { + .id = EVID_WIIEC, .wiiEc = {.type = WIIEC_DISCONN, .in = '>', .val = pec->pidx}}; + furi_message_queue_put(queue, &msg, 0); + break; + } + + case 0: { // read OK + void (*fn)(wiiEC_t*, FuriMessageQueue*) = ecId[pec->pidx].check; + if(fn) fn(pec, queue); + break; + } + + case 3: // read fail + // this is probably temporary just ignore it + break; + + default: // bug: unknown + case 1: // bug: not initialised - should never happen + ERROR("%s : read bug", __func__); + break; + } + } + + LEAVE; + return; +} + +//+============================================================================ ======================================== +// This is the screen drawn for an unknown controller +// It is also available by pressing LEFT (at least once) on a "known controller" screen +// +void ec_show(Canvas* const canvas, state_t* const state) { + wiiEC_t* pec = &state->ec; + int h = 11; // line height + int x = 1; // (initial) offset for bits + int y = -h; // previous y value + int yb = 0; // y for bit patterns + int c2 = 17; // column 2 + + // Headings + canvas_set_font(canvas, FontSecondary); + canvas_set_color(canvas, ColorBlack); + + canvas_draw_str_aligned(canvas, 0, 0, AlignLeft, AlignTop, "SID:"); + canvas_draw_str_aligned(canvas, c2, 0, AlignLeft, AlignTop, pec->sid); + + canvas_draw_str_aligned(canvas, 0, 11, AlignLeft, AlignTop, "PID:"); + canvas_draw_str_aligned(canvas, 0, 22, AlignLeft, AlignTop, "Cal:"); + + // PID + x = c2; + for(int i = 0; i < 6; i++) { + show(canvas, x, 11, img_5x7[pec->pid[i] >> 4], SHOW_SET_BLK); + x += 5 + 1; + show(canvas, x, 11, img_5x7[pec->pid[i] & 0xF], SHOW_SET_BLK); + x += 5 + 1 + 2; + } + + // Calibrations data + y = 11; + for(int j = 0; j <= 8; j += 8) { + x = c2; + y += 11; + for(int i = 0; i < 8; i++) { + show(canvas, x, y, img_5x7[pec->calF[i + j] >> 4], SHOW_SET_BLK); + x += 5 + 1; + show(canvas, x, y, img_5x7[pec->calF[i + j] & 0xF], SHOW_SET_BLK); + x += 5 + 1 + 2; + } + } + + // Reading + x = 1; + y++; + yb = (y += h) + h + 2; + + canvas_draw_line(canvas, x, y - 1, x, yb + 4); + x += 2; + + for(int i = 0; i < JOY_LEN; i++) { + show(canvas, x + 1, y, img_6x8[pec->joy[i] >> 4], SHOW_SET_BLK); + show(canvas, x + 11, y, img_6x8[pec->joy[i] & 0xF], SHOW_SET_BLK); + + // bits + for(int m = 0x80; m; m >>= 1) { + x += 2 * !!(m & 0x08); // nybble step + canvas_draw_box(canvas, x, yb + (2 * !(pec->joy[i] & m)), 2, 2); + x += 2; // bit step + } + + // byte step + x += 1; + canvas_draw_line(canvas, x, y - 1, x, yb + 4); + x += 2; + } + + // Scene navigation + if(state->scenePrev != SCENE_WAIT) show(canvas, 120, 0, &img_key_R, SHOW_SET_BLK); +} + +//+============================================================================ ======================================== +// The DUMP screen is +// +bool ec_key(const eventMsg_t* const msg, state_t* const state) { + int used = false; // assume key is NOT-handled + + if(state->scenePrev != SCENE_WAIT) { + //# input.type == InputTypeShort) && (msg->input.key == InputKeyRight)) { + sceneSet(state, state->scenePrev); + used = true; + } + } + + return used; +} diff --git a/applications/plugins/wii_ec_anal/wii_ec.h b/applications/plugins/wii_ec_anal/wii_ec.h new file mode 100644 index 000000000..a28453740 --- /dev/null +++ b/applications/plugins/wii_ec_anal/wii_ec.h @@ -0,0 +1,161 @@ +#ifndef WII_EC_H_ +#define WII_EC_H_ + +#include + +#include + +#include "wii_ec_nunchuck.h" +#include "wii_ec_classic.h" +#include "wii_ec_udraw.h" + +//----------------------------------------------------------------------------- ---------------------------------------- +// Crypto key (PSK), base register : {0x40..0x4F}[2][8] +#define ENC_LEN (2 * 8) + +// Controller State data, base register : {0x00..0x05}[6] +#define JOY_LEN (6) + +// Calibration data, base register : {0x20..0x2F}[16] +#define CAL_LEN (16) + +// Controller ID, base register : {0xFA..0xFF}[6] +#define PID_LEN (6) + +//----------------------------------------------------------------------------- ---------------------------------------- +// Perhipheral specific parameters union +// +typedef union ecDec { + ecDecNunchuck_t nunchuck; + ecDecClassic_t classic; +} ecDec_t; + +//----------------------------------------------------------------------------- +typedef union ecCal { + // 0=lowest seen ; 1=min ; 2=mid ; 3=max ; 4=highest seen + ecCalNunchuck_t nunchuck[5]; + ecCalClassic_t classic[5]; +} ecCal_t; + +//----------------------------------------------------------------------------- ---------------------------------------- +// Wii Extension Controller events +// +typedef enum wiiEcEventType { + WIIEC_NONE, + WIIEC_CONN, // Connect + WIIEC_DISCONN, // Disconnect + WIIEC_PRESS, // Press button + WIIEC_RELEASE, // Release button + WIIEC_ANALOG, // Analogue change (Joystick/Trigger) + WIIEC_ACCEL, // Accelerometer change +} wiiEcEventType_t; + +//----------------------------------------------------------------------------- +typedef struct wiiEcEvent { + wiiEcEventType_t type; // event type + char in; // input (see device specific options) + uint32_t val; // new value - meaningless for digital button presses +} wiiEcEvent_t; + +//----------------------------------------------------------------------------- ---------------------------------------- +// Known perhipheral types +// +typedef enum ecPid { + PID_UNKNOWN = 0, + PID_FIRST = 1, + PID_NUNCHUCK = PID_FIRST, + + // If you're wise, ONLY edit this section + PID_NUNCHUCK_R2, + PID_CLASSIC, + PID_CLASSIC_PRO, + PID_BALANCE, + PID_GH_GUITAR, + PID_GH_DRUMS, + PID_TURNTABLE, + PID_TAIKO_DRUMS, + PID_UDRAW, //! same as drawsome? + // ----- + + PID_ERROR, + PID_NULL, + PID_CNT, +} ecPid_t; + +//----------------------------------------------------------------------------- +// Calibration strategies +// +typedef enum ecCalib { + CAL_FACTORY = 0x01, // (re)set to factory defaults + CAL_TRACK = 0x02, // track maximum and minimum values seen + CAL_RESET = 0x04, // initialise ready for software calibration + CAL_RANGE = 0x08, // perform software calibration step + CAL_CENTRE = 0x10, // reset centre point of joystick + CAL_NOTJOY = 0x20, // do NOT calibrate the joystick +} ecCalib_t; + +//----------------------------------------------------------------------------- +// ecId table entry +// +typedef struct ecId { + uint8_t id[6]; // 6 byte ID string returned by Extension Controller + char* name; // Friendly name + scene_t scene; // Default scene + bool (*init)(wiiEC_t*); // Additional initialisation code + void (*decode)(wiiEC_t*); // Decode function + void (*check)(wiiEC_t*, FuriMessageQueue*); // check (for action) function + void (*calib)(wiiEC_t*, ecCalib_t); // calibrate analogue controllers [SOFTWARE] + void (*show)(Canvas* const, state_t* const); // Draw scene + bool (*keys)(const eventMsg_t* const, state_t* const); // Interpret keys +} ecId_t; + +//----------------------------------------------------------------------------- +// List of known perhipherals +// +// More perhipheral ID codes here: https://wiibrew.org/wiki/Wiimote/Extension_Controllers#The_New_Way +// +extern const ecId_t ecId[PID_CNT]; + +//----------------------------------------------------------------------------- ---------------------------------------- +// Data pertaining to a single Perhipheral instance +// +typedef struct wiiEC { + // Perhipheral state + bool init; // Initialised? + + uint8_t pid[PID_LEN]; // PID string - eg. {0x00, 0x00, 0xA4, 0x20, 0x00, 0x00} + ecPid_t pidx; // Index in to ecId table + const char* sid; // just for convenience + + bool encrypt; // encryption enabled? + uint8_t encKey[ENC_LEN]; // encryption key + + uint8_t calF[CAL_LEN]; // factory calibration data (not software) + uint8_t joy[JOY_LEN]; // Perhipheral raw data + + ecDec_t dec[2]; // device specific decode (two, so we can spot changes) + int decN; // which decode set is most recent {0, 1} + ecCal_t calS; // software calibration data +} wiiEC_t; + +//----------------------------------------------------------------------------- ---------------------------------------- +// Function prototypes +// +// top level calls will work out which sub-function to call +// top level check() function will handle connect/disconnect messages +// + +#include // Canvas +typedef struct wiiEC wiiEC_t; +typedef enum ecCalib ecCalib_t; +typedef struct state state_t; +typedef struct eventMsg eventMsg_t; + +void ecDecode(wiiEC_t* const pec); +void ecPoll(wiiEC_t* const pec, FuriMessageQueue* const queue); +void ecCalibrate(wiiEC_t* const pec, ecCalib_t c); + +void ec_show(Canvas* const canvas, state_t* const state); +bool ec_key(const eventMsg_t* const msg, state_t* const state); + +#endif //WII_EC_H_ diff --git a/applications/plugins/wii_ec_anal/wii_ec_classic.c b/applications/plugins/wii_ec_anal/wii_ec_classic.c new file mode 100644 index 000000000..5bd3398ca --- /dev/null +++ b/applications/plugins/wii_ec_anal/wii_ec_classic.c @@ -0,0 +1,439 @@ +#include +#include // Core API + +#include "wii_anal.h" +#include "wii_ec.h" +#include "bc_logging.h" + +//#include "gfx/images.h" // Images +#include "wii_anal_lcd.h" // Drawing functions +#include "wii_anal_keys.h" // key mappings + +// ** If you want to see what this source code looks like with all the MACROs expanded +// ** grep -v '#include ' wii_i2c_classic.c | gcc -E -o /dev/stdout -xc - +#include "wii_ec_macros.h" + +//----------------------------------------------------------------------------- ---------------------------------------- +// Classic Controller ... Classic Controller Pro is electronically the same +// +// ANA{l} ANA{r} +// BTN{l} BTN{L} BTN{R} BTN{r} +// ,--------. ,-, ,-, .--------, +// .----------------------------------------------------------. +// | | +// | BTN{W} BTN{x} | +// | BTN{A} BTN{D} BTN{-} BTN{h} BTN{+} BTN{y} BTN{a} | +// | BTN{S} BTN{b} | +// | | +// | ANA{y} ANA{Y} | +// | ANA{x} ANA{X} | +// | | +// `----------------------------------------------------------' + +//+============================================================================ ======================================== +// https://wiibrew.org/wiki/Wiimote/Extension_Controllers/Classic_Controller +// I think a LOT of drugs went in to "designing" this layout +// ...And yes, the left-joystick has an extra 'bit' of precision! +// ...Also: trgZ{L|R} WILL continue to increase after btnZ{L|R} has gone active +// +void classic_decode(wiiEC_t* const pec) { + ecDecClassic_t* p = &pec->dec[(pec->decN = !pec->decN)].classic; + uint8_t* joy = pec->joy; + + p->trgZL = ((joy[2] >> 2) & 0x18) | ((joy[3] >> 5) & 0x07); // {5} + p->btnZL = !(joy[4] & 0x20); // !{1} + + p->trgZR = joy[3] & 0x1F; // {5} + p->btnZR = !(joy[4] & 0x02); // !{1} + + p->btnL = !(joy[5] & 0x80); // !{1} + p->btnR = !(joy[5] & 0x04); // !{1} + + p->padU = !(joy[5] & 0x01); // !{1} + p->padD = !(joy[4] & 0x40); // !{1} + p->padL = !(joy[5] & 0x02); // !{1} + p->padR = !(joy[4] & 0x80); // !{1} + + p->btnM = !(joy[4] & 0x10); // !{1} + p->btnH = !(joy[4] & 0x08); // !{1} + p->btnP = !(joy[4] & 0x04); // !{1} + + p->btnX = !(joy[5] & 0x08); // !{1} + p->btnY = !(joy[5] & 0x20); // !{1} + + p->btnA = !(joy[5] & 0x10); // !{1} + p->btnB = !(joy[5] & 0x40); // !{1} + + p->joyLX = joy[0] & 0x3F; // {6} + p->joyLY = joy[1] & 0x3F; // {6} + + p->joyRX = ((joy[0] >> 3) & 0x18) | ((joy[1] >> 5) & 0x06) | ((joy[2] >> 7) & 0x01); // {5} + p->joyRY = joy[2] & 0x1F; // {5} + + DEBUG( + ">%d> ZL{%02X}%c, L:%c, R:%c, ZR{%02X}%c", + pec->decN, + p->trgZL, + (p->btnZL ? '#' : '.'), + (p->btnL ? '#' : '.'), + (p->btnR ? '#' : '.'), + p->trgZR, + (p->btnZR ? '#' : '.')); + DEBUG( + ">%d> D:{%c,%c,%c,%c}, H:{%c,%c,%c}, B:{%c,%c,%c,%c}", + pec->decN, + (p->padU ? 'U' : '.'), + (p->padD ? 'D' : '.'), + (p->padL ? 'L' : '.'), + (p->padR ? 'R' : '.'), + (p->btnM ? '-' : '.'), + (p->btnH ? 'H' : '.'), + (p->btnP ? '+' : '.'), + (p->btnX ? 'X' : '.'), + (p->btnY ? 'Y' : '.'), + (p->btnA ? 'A' : '.'), + (p->btnB ? 'B' : '.')); + DEBUG( + ">%d> JoyL{x:%02X, y:%02X}, JoyR{x:%02X, y:%02X}", + pec->decN, + p->joyLX, + p->joyLY, + p->joyRX, + p->joyRY); +} + +//+============================================================================ ======================================== +// Give each button a unique character identifier +// +void classic_msg(wiiEC_t* const pec, FuriMessageQueue* const queue) { + ecDecClassic_t* new = &pec->dec[pec->decN].classic; + ecDecClassic_t* old = &pec->dec[!pec->decN].classic; + + eventMsg_t msg = { + .id = EVID_WIIEC, + .wiiEc = { + .type = WIIEC_NONE, + .in = ' ', + .val = 0, + }}; + + ANALOG(trgZL, 'l'); // FIVE bit value + ANABTN(btnZL, trgZL, 'l'); + + BUTTON(btnL, 'L'); + BUTTON(btnR, 'R'); + + ANALOG(trgZR, 'r'); // FIVE bit value + ANABTN(btnZR, trgZR, 'r'); + + BUTTON(padU, 'W'); + BUTTON(padL, 'A'); + BUTTON(padD, 'S'); + BUTTON(padR, 'D'); + + BUTTON(btnM, '-'); + BUTTON(btnH, 'h'); + BUTTON(btnP, '+'); + + BUTTON(btnX, 'x'); + BUTTON(btnY, 'y'); + BUTTON(btnA, 'a'); + BUTTON(btnB, 'b'); + + ANALOG(joyLX, 'x'); // SIX bit values + ANALOG(joyLY, 'y'); + + ANALOG(joyRX, 'X'); // FIVE bit values + ANALOG(joyRY, 'Y'); +} + +//+============================================================================ ======================================== +// https://web.archive.org/web/20090415045219/http://www.wiili.org/index.php/Wiimote/Extension_Controllers/Classic_Controller#Calibration_data +// +// Calibration data +// 0..2 left analog stick X axis {maximum, minimum, center} ... JoyL is 6bits, so >>2 to compare to readings +// 3..5 left analog stick Y axis {maximum, minimum, center} ... JoyL is 6bits, so >>2 to compare to readings +// 6..8 right analog stick X axis {maximum, minimum, center} ... JoyR is 5bits, so >>3 to compare to readings +// 9..11 right analog stick Y axis {maximum, minimum, center} ... JoyR is 5bits, so >>3 to compare to readings +// 12..15 somehow describe the shoulder {5bit} button values!? +// +void classic_calib(wiiEC_t* const pec, ecCalib_t c) { + ecDecClassic_t* src = &pec->dec[pec->decN].classic; // from input + ecCalClassic_t* dst = pec->calS.classic; // to calibration data + + if(c & CAL_RESET) { // initialise ready for software calibration + // LO is set to the MAXIMUM value (so it can be reduced) + // HI is set to ZERO (so it can be increased) + RESET_LO_HI(trgZL, 5); // 5bit value + RESET_LO_HI(trgZR, 5); // 5bit value + + RESET_LO_MID_HI(joyLX, 6); // 6bit value + RESET_LO_MID_HI(joyLY, 6); // 6bit value + + RESET_LO_MID_HI(joyRX, 5); // 5bit value + RESET_LO_MID_HI(joyRY, 5); // 5bit value + } + if(c & CAL_FACTORY) { // (re)set to factory defaults + //! strategy for factory calibration for classic controller [pro] triggers is (currently) unknown + //! FACTORY_LO( trgZL, pec->calF[12..15]); + //! FACTORY_MID(trgZL, pec->calF[12..15]); + //! FACTORY_HI( trgZL, pec->calF[12..15]); + + //! FACTORY_LO( trgZR, pec->calF[12..15]); + //! FACTORY_MID(trgZR, pec->calF[12..15]); + //! FACTORY_HI( trgZR, pec->calF[12..15]); + +#if 1 + FACTORY_LO(trgZL, 0x03); + FACTORY_LO(trgZR, 0x03); + + FACTORY_MID(trgZL, 0x1B); //! these will be set every time the digital switch changes to ON + FACTORY_MID(trgZR, 0x1B); +#endif + + FACTORY_LO(joyLX, pec->calF[1] >> 2); + FACTORY_MID(joyLX, pec->calF[2] >> 2); + FACTORY_HI(joyLX, pec->calF[0] >> 2); + + FACTORY_LO(joyLY, pec->calF[4] >> 2); + FACTORY_MID(joyLY, pec->calF[5] >> 2); + FACTORY_HI(joyLY, pec->calF[3] >> 2); + + FACTORY_LO(joyRX, pec->calF[7] >> 3); + FACTORY_MID(joyRX, pec->calF[8] >> 3); + FACTORY_HI(joyRX, pec->calF[6] >> 3); + + FACTORY_LO(joyRY, pec->calF[10] >> 3); + FACTORY_MID(joyRY, pec->calF[11] >> 3); + FACTORY_HI(joyRY, pec->calF[9] >> 3); + } + if(c & CAL_TRACK) { // track maximum and minimum values seen + TRACK_LO_HI(trgZL); + TRACK_LO_HI(trgZR); + + TRACK_LO_HI(joyLX); + TRACK_LO_HI(joyLY); + + TRACK_LO_HI(joyRX); + TRACK_LO_HI(joyRY); + } + if(c & CAL_RANGE) { // perform software calibration step + RANGE_LO_HI(trgZL); + RANGE_LO_HI(trgZR); + + RANGE_LO_HI(joyLX); + RANGE_LO_HI(joyLY); + + RANGE_LO_HI(joyRX); + RANGE_LO_HI(joyRY); + } + if(c & CAL_CENTRE) { // reset centre point of joystick + CENTRE(joyLX); + CENTRE(joyLY); + + CENTRE(joyRX); + CENTRE(joyRY); + } +} + +//+============================================================================ ======================================== +// bits that are common to both screens +// +static void classic_show_(Canvas* const canvas, state_t* const state) { + ecDecClassic_t* d = &state->ec.dec[state->ec.decN].classic; + ecCalClassic_t* js = state->ec.calS.classic; + + static const int dead = 1; // trigger deadzone + const image_t* img = NULL; // trigger image + + show(canvas, 6, 0, &img_cc_Main, SHOW_SET_BLK); + show(canvas, 62, 53, &img_cc_Cable, SHOW_SET_BLK); + + // classic triggers + if(d->trgZL >= js[2].trgZL) + img = &img_cc_trg_L4; + else if(d->trgZL <= js[1].trgZL + dead) + img = NULL; + else { + // copied from the joystick calibration code + int lo = js[1].trgZL + dead + 1; + int hi = js[2].trgZL - 1; + int range = hi - lo + 1; + int div = range / 3; // each division (base amount, eg. 17/3==5) + int rem = range - (div * 3); // remainder (ie. range%3) + int hi1 = lo + div - 1; // (in brevity) + int lo3 = lo + div + div + (rem == 2); // ... + + if(d->trgZL <= hi1) + img = &img_cc_trg_L1; // zone #1 + else if(d->trgZL >= lo3) + img = &img_cc_trg_L3; // zone #3 + else + img = &img_cc_trg_L2; // zone #2 + } + if(img) show(canvas, 22, 1, img, SHOW_SET_BLK); + + if(d->trgZR >= js[2].trgZR) + img = &img_cc_trg_R4; + else if(d->trgZR <= js[1].trgZR + dead) + img = NULL; + else { + // copied from the joystick calibration code + int lo = js[1].trgZR + dead + 1; + int hi = js[2].trgZR - 1; + int range = hi - lo + 1; + int div = range / 3; // each division (base amount, eg. 17/3==5) + int rem = range - (div * 3); // remainder (ie. range%3) + int hi1 = lo + div - 1; // (in brevity) + int lo3 = lo + div + div + (rem == 2); // ... + + if(d->trgZR <= hi1) + img = &img_cc_trg_R1; // zone #1 + else if(d->trgZR >= lo3) + img = &img_cc_trg_R3; // zone #3 + else + img = &img_cc_trg_R2; // zone #2 + } + if(img) show(canvas, 89, 1, img, SHOW_SET_BLK); + + if(d->padU) show(canvas, 27, 16, &img_cc_pad_UD1, SHOW_ALL); + if(d->padL) show(canvas, 20, 23, &img_cc_pad_LR1, SHOW_ALL); + if(d->padD) show(canvas, 27, 28, &img_cc_pad_UD1, SHOW_ALL); + if(d->padR) show(canvas, 32, 23, &img_cc_pad_LR1, SHOW_ALL); + + if(d->btnX) show(canvas, 96, 16, &img_cc_btn_X1, SHOW_ALL); + if(d->btnY) show(canvas, 85, 23, &img_cc_btn_Y1, SHOW_ALL); + if(d->btnA) show(canvas, 107, 23, &img_cc_btn_A1, SHOW_ALL); + if(d->btnB) show(canvas, 96, 30, &img_cc_btn_B1, SHOW_ALL); + + canvas_set_color(canvas, ColorBlack); + if(d->btnL) canvas_draw_box(canvas, 46, 2, 5, 4); + if(d->btnR) canvas_draw_box(canvas, 77, 2, 5, 4); + + if(d->btnM) canvas_draw_box(canvas, 54, 24, 4, 4); + if(d->btnH) canvas_draw_box(canvas, 62, 24, 4, 4); + if(d->btnP) canvas_draw_box(canvas, 70, 24, 4, 4); + + // Show joysticks + showJoy( + canvas, + 48, + 42, + js[1].joyLX, + js[2].joyLX, + js[3].joyLX, + js[1].joyLY, + js[2].joyLY, + js[3].joyLY, + d->joyLX, + d->joyLY, + 6); + showJoy( + canvas, + 78, + 42, + js[1].joyRX, + js[2].joyRX, + js[3].joyRX, + js[1].joyRY, + js[2].joyRY, + js[3].joyRY, + d->joyRX, + d->joyRY, + 5); + + show(canvas, 0, 55, &img_key_L, SHOW_SET_BLK); +} + +//+============================================================================ ======================================== +static void classic_showN(Canvas* const canvas, state_t* const state) { + ecCalClassic_t* c = (state->hold) ? + &state->ec.calS.classic[(state->hold < 0) ? 0 : 4] : + (ecCalClassic_t*)(&state->ec.dec[state->ec.decN].classic); //! danger + + classic_show_(canvas, state); + + showHex(canvas, 0, 0, c->trgZL, 2, 1); // 5bits + showHex(canvas, 113, 0, c->trgZR, 2, 1); // 5bits + + showHex(canvas, 24, 41, c->joyLX, 2, 1); // 6bits + showHex(canvas, 41, 54, c->joyLY, 2, 1); // 6bits + + showHex(canvas, 88, 41, c->joyRX, 2, 1); // 5bits + showHex(canvas, 71, 54, c->joyRY, 2, 1); // 5bits + + showPeakHold(state, canvas, state->hold); // peak keys +} + +//+============================================================================ ======================================== +void classic_show(Canvas* const canvas, state_t* const state) { + // Classic controllers have TWO scenes + if(state->scene == SCENE_CLASSIC_N) return classic_showN(canvas, state); + + // Default scene + classic_show_(canvas, state); + show(canvas, 9, 55, &img_key_R, SHOW_SET_BLK); + + show( + canvas, + 119, + 55, + ((state->calib & CAL_RANGE) && (++state->flash & 8)) ? &img_key_OKi : &img_key_OK, + SHOW_SET_BLK); +} + +//+============================================================================ ======================================== +static bool classic_keyN(const eventMsg_t* const msg, state_t* const state) { + int used = false; // assume key is NOT-handled + + if((msg->input.type == InputTypeShort) && (msg->input.key == InputKeyLeft)) { + sceneSet(state, SCENE_CLASSIC); + used = true; + } + + // Calibration keys + if(!used) used = key_calib(msg, state); + + return used; +} + +//+============================================================================ ======================================== +bool classic_key(const eventMsg_t* const msg, state_t* const state) { + // Classic controllers have TWO scenes + if(state->scene == SCENE_CLASSIC_N) return classic_keyN(msg, state); + + // Default scene + int used = false; // assume key is NOT-handled + + switch(msg->input.type) { + case InputTypeShort: //# input.key) { + case InputKeyUp: //# +#include + +//----------------------------------------------------------------------------- ---------------------------------------- +// Classic Controller ... Classic Controller Pro is electronically the same +// +// ANA{l} ANA{r} +// BTN{l} BTN{L} BTN{R} BTN{r} +// ,--------. ,-, ,-, .--------, +// .----------------------------------------------------------. +// | | +// | BTN{W} BTN{x} | +// | BTN{A} BTN{D} BTN{-} BTN{h} BTN{+} BTN{y} BTN{a} | +// | BTN{S} BTN{b} | +// | | +// | ANA{y} ANA{Y} | +// | ANA{x} ANA{X} | +// | | +// `----------------------------------------------------------' +// + +//----------------------------------------------------------------------------- ---------------------------------------- +// Controllers which have calibration must have their calibratable controls here +//! Is there a better way to get the start of the decode struct to match the calibration struct ? +#define CLASSIC_ANALOGUE \ + uint8_t trgZL, trgZR; /* ANA{l, l} lowercase=trigger 5bit values {5} */ \ + uint8_t joyLX, joyLY; /* ANA{x, y} left=lowercase 6bit values {6}<-- */ \ + uint8_t joyRX, joyRY; /* ANA{X, Y} 5bit values {5} */ + +//----------------------------------------------------------------------------- +// Calibratable controls +// +typedef struct ecCalClassic { + CLASSIC_ANALOGUE +} ecCalClassic_t; + +//----------------------------------------------------------------------------- +// All controls +// +typedef struct ecDecClassic { + CLASSIC_ANALOGUE // MUST be first + + // Digital controls + bool btnZL, + btnZR; // BTN{l, l} + + bool btnL, btnR; // BTN{L, R} upperrcase=shoulder + + bool padU, padL, padD, padR; // BTN{W, A, S, D} + + bool btnM, btnH, btnP; // BTN{-, h, +} + + bool btnX, btnY; // BTN{x, y} + bool btnA, btnB; // BTN{a, b} + +} ecDecClassic_t; + +#undef CLASSIC_ANALOGUE + +//============================================================================= ======================================== +// Function prototypes +// +#include // Canvas +typedef struct wiiEC wiiEC_t; +typedef enum ecCalib ecCalib_t; +typedef struct state state_t; +typedef struct eventMsg eventMsg_t; + +void classic_decode(wiiEC_t* const pec); +void classic_msg(wiiEC_t* const pec, FuriMessageQueue* const queue); +void classic_calib(wiiEC_t* const pec, ecCalib_t c); + +void classic_show(Canvas* const canvas, state_t* const state); +bool classic_key(const eventMsg_t* const msg, state_t* const state); + +#endif //WII_EC_CLASSIC_H_ diff --git a/applications/plugins/wii_ec_anal/wii_ec_macros.h b/applications/plugins/wii_ec_anal/wii_ec_macros.h new file mode 100644 index 000000000..00ab9825b --- /dev/null +++ b/applications/plugins/wii_ec_anal/wii_ec_macros.h @@ -0,0 +1,138 @@ +#ifndef WII_EC_MACROS_H_ +#define WII_EC_MACROS_H_ + +//----------------------------------------------------------------------------- ---------------------------------------- +// CHECK MACROS +// +// I don't generally like this style of coding - it just (generally) makes things nightmarish to debug +// However, on this occasion I think it's a good choice (to make adding controllers LESS bug-prone) +// + +//if (furi_message_queue_get_count(queue) > 18) WARN("queue high %d", furi_message_queue_get_count(queue)); +#define MSGQ(lbl) \ + do { \ + msg.wiiEc.in = lbl; \ + furi_message_queue_put(queue, &msg, 0); \ + } while(0) + +// A 'standard' "button" is an independent SPST switch +// Eg. Nunchuck 'Z' button +// The "value" will always be 0 +#define BUTTON(btn, lbl) \ + do { \ + if(new->btn != old->btn) { \ + msg.wiiEc.type = (new->btn) ? WIIEC_PRESS : WIIEC_RELEASE; \ + msg.wiiEc.val = 0; \ + MSGQ(lbl); \ + } \ + } while(0) + +// An "analogue button" is an SPST coupled with an ananlogue 'switch' +// Eg. The "bottom out" switches on the triggers of the classic controller +// The "value" will be the value of the associated analogue controller +#define ANABTN(btn, ana, lbl) \ + do { \ + if(new->btn != old->btn) { \ + msg.wiiEc.type = (new->btn) ? WIIEC_PRESS : WIIEC_RELEASE; \ + msg.wiiEc.val = new->ana; \ + MSGQ(lbl); \ + } \ + } while(0) + +#define ANALOG(ana, lbl) \ + do { \ + if(new->ana != old->ana) { \ + msg.wiiEc.type = WIIEC_ANALOG; \ + msg.wiiEc.val = new->ana; \ + MSGQ(lbl); \ + } \ + } while(0) + +#define ACCEL(acc, lbl) \ + do { \ + if(new->acc != old->acc) { \ + msg.wiiEc.type = WIIEC_ACCEL; \ + msg.wiiEc.val = new->acc; \ + MSGQ(lbl); \ + } \ + } while(0) + +//----------------------------------------------------------------------------- ---------------------------------------- +// CALIBRATION MACROS +// +// Again ...I totally agree with anyone who says "MACRO coding" is (gernally) a poor choice of programming style +// But something about this code is making it soooo appealing +// +// ... v=variable, n=number +// +#define FACTORY_LO(v, n) \ + do { \ + (dst[1].v) = n; \ + } while(0) +#define FACTORY_MID(v, n) \ + do { \ + (dst[2].v) = n; \ + } while(0) +#define FACTORY_HI(v, n) \ + do { \ + (dst[3].v) = n; \ + } while(0) + +#define TRACK_LO(v) \ + do { \ + if((src->v) < (dst[0].v)) (dst[0].v) = (src->v); \ + } while(0) +#define TRACK_HI(v) \ + do { \ + if((src->v) > (dst[4].v)) (dst[4].v) = (src->v); \ + } while(0) +#define TRACK_LO_HI(v) \ + do { \ + TRACK_LO(v); \ + TRACK_HI(v); \ + } while(0) + +#define RESET_LO(v, b) \ + do { \ + (dst[0].v) = (dst[1].v) = ((1 << (b)) - 1); \ + } while(0) +#define RESET_HI(v) \ + do { \ + (dst[4].v) = (dst[3].v) = 0; \ + } while(0) +#define RESET_MID(v) \ + do { \ + (dst[2].v) = (src->v); \ + } while(0) +#define RESET_LO_HI(v, b) \ + do { \ + RESET_LO(v, b); \ + RESET_HI(v); \ + } while(0) +#define RESET_LO_MID_HI(v, b) \ + do { \ + RESET_LO(v, b); \ + RESET_MID(v); \ + RESET_HI(v); \ + } while(0) + +#define RANGE_LO(v) \ + do { \ + if((src->v) < (dst[1].v)) (dst[1].v) = (src->v); \ + } while(0) +#define RANGE_HI(v) \ + do { \ + if((src->v) > (dst[3].v)) (dst[3].v) = (src->v); \ + } while(0) +#define RANGE_LO_HI(v) \ + do { \ + RANGE_LO(v); \ + RANGE_HI(v); \ + } while(0) + +#define CENTRE(v) \ + do { \ + (dst[2].v) = (src->v); \ + } while(0) + +#endif //WII_EC_MACROS_H_ diff --git a/applications/plugins/wii_ec_anal/wii_ec_nunchuck.c b/applications/plugins/wii_ec_anal/wii_ec_nunchuck.c new file mode 100644 index 000000000..d88d535b6 --- /dev/null +++ b/applications/plugins/wii_ec_anal/wii_ec_nunchuck.c @@ -0,0 +1,476 @@ +#include +#include // Core API + +#include "wii_anal.h" +#include "wii_i2c.h" +#include "bc_logging.h" + +#include "gfx/images.h" // Images +#include "wii_anal_lcd.h" // Drawing functions +#include "wii_anal_keys.h" // key mappings + +// ** If you want to see what this source code looks like with all the MACROs expanded +// ** grep -v '#include ' wii_ec_nunchuck.c | gcc -E -o /dev/stdout -xc - +#include "wii_ec_macros.h" + +//+============================================================================ ======================================== +// Standard Nunchuck : 2 buttons, 1 analogue joystick, 1 3-axis accelerometer +// +void nunchuck_decode(wiiEC_t* const pec) { + ecDecNunchuck_t* p = &pec->dec[(pec->decN = !pec->decN)].nunchuck; + uint8_t* joy = pec->joy; + + p->btnC = !(joy[5] & 0x02); // !{1} + p->btnZ = !(joy[5] & 0x01); // !{1} + + p->joyX = joy[0]; // {8} + p->joyY = joy[1]; // {8} + + p->accX = ((uint16_t)joy[2] << 2) | ((joy[5] >> 2) & 0x03); // {10} + p->accY = ((uint16_t)joy[3] << 2) | ((joy[5] >> 4) & 0x03); // {10} + p->accZ = ((uint16_t)joy[4] << 2) | ((joy[5] >> 6) & 0x03); // {10} + + DEBUG( + ">%d> C:%c, Z:%c, Joy{x:%02X, y:%02X}, Acc{x:%03X, y:%03X, z:%03X}", + pec->decN, + (p->btnC ? '#' : '.'), + (p->btnZ ? '#' : '.'), + p->joyX, + p->joyY, + p->accX, + p->accY, + p->accZ); +} + +//+============================================================================ ======================================== +// Give each button a unique character identifier +// +void nunchuck_msg(wiiEC_t* const pec, FuriMessageQueue* const queue) { + ecDecNunchuck_t* new = &pec->dec[pec->decN].nunchuck; + ecDecNunchuck_t* old = &pec->dec[!pec->decN].nunchuck; + + eventMsg_t msg = { + .id = EVID_WIIEC, + .wiiEc = { + .type = WIIEC_NONE, + .in = ' ', + .val = 0, + }}; + + BUTTON(btnC, 'c'); + BUTTON(btnZ, 'z'); + + ANALOG(joyX, 'x'); + ANALOG(joyY, 'y'); + + ACCEL(accX, 'x'); + ACCEL(accY, 'y'); + ACCEL(accZ, 'z'); +} + +//+============================================================================ ======================================== +// https://www.hackster.io/infusion/using-a-wii-nunchuk-with-arduino-597254#toc-5--read-actual-calibration-data-from-the-device-14 +// +void nunchuck_calib(wiiEC_t* const pec, ecCalib_t c) { + ecDecNunchuck_t* src = &pec->dec[pec->decN].nunchuck; // from input + ecCalNunchuck_t* dst = pec->calS.nunchuck; // to calibration data + + if(c & CAL_RESET) { // initialise ready for software calibration + // LO is set to the MAXIMUM value (so it can be reduced) + // HI is set to ZERO (so it can be increased) + RESET_LO_HI(accX, 10); // 10bit value + RESET_LO_HI(accY, 10); // 10bit value + RESET_LO_HI(accZ, 10); // 10bit value + + RESET_LO_HI(joyX, 8); // 8bit value + RESET_LO_HI(joyY, 8); // 8bit value + } + if(c & CAL_FACTORY) { // (re)set to factory defaults + //! "[4] LSB of Zero value of X,Y,Z axes" ...helpful! + //! ...Well, my test nunchuck has bits set in the bottom 6 bits, so let's guess ;) + + // No value available - annecdotal tests suggest 8 is reasonable + FACTORY_LO(accX, 8); + FACTORY_LO(accY, 8); + FACTORY_LO(accZ, 8); + + // @ 0G + FACTORY_MID(accX, ((pec->calF[0] << 2) | ((pec->calF[3] >> 4) & 0x3))); + FACTORY_MID(accY, ((pec->calF[1] << 2) | ((pec->calF[3] >> 2) & 0x3))); + FACTORY_MID(accZ, ((pec->calF[2] << 2) | ((pec->calF[3]) & 0x3))); + + // @ 1G + FACTORY_HI(accX, ((pec->calF[4] << 2) | ((pec->calF[7] >> 4) & 0x3))); + FACTORY_HI(accY, ((pec->calF[5] << 2) | ((pec->calF[7] >> 2) & 0x3))); + FACTORY_HI(accZ, ((pec->calF[6] << 2) | ((pec->calF[7]) & 0x3))); + + // Joysticks + FACTORY_LO(joyX, pec->calF[9]); + FACTORY_MID(joyX, pec->calF[10]); + FACTORY_HI(joyX, pec->calF[8]); + + FACTORY_LO(joyY, pec->calF[12]); + FACTORY_MID(joyY, pec->calF[13]); + FACTORY_HI(joyY, pec->calF[11]); + } + if(c & CAL_TRACK) { // track maximum and minimum values seen + TRACK_LO_HI(accX); + TRACK_LO_HI(accY); + TRACK_LO_HI(accZ); + + TRACK_LO_HI(joyX); + TRACK_LO_HI(joyY); + } + if(c & CAL_RANGE) { // perform software calibration step + RANGE_LO_HI(accX); + RANGE_LO_HI(accY); + RANGE_LO_HI(accZ); + + if(!(c & CAL_NOTJOY)) { // double negative! + RANGE_LO_HI(joyX); + RANGE_LO_HI(joyY); + } + } + if(c & CAL_CENTRE) { // reset centre point of joystick + CENTRE(accX); + CENTRE(accY); + CENTRE(accZ); + + CENTRE(joyX); + CENTRE(joyY); + } +} + +//============================================================================= ======================================== +// Accelerometer screen ...might this be useful for other controllers? +// +// https://bootlin.com/labs/doc/nunchuk.pdf +// X : Move Left/Right : -left / +right +// Y : Move Fwd/Bkwd : -fwd / +bkwd +// Z : Move Down/Up : -down / +up +// +// Movement in the direction of an axis changes that axis reading +// Twisting/tilting around an axis changes the other two readings +// +// EG. Move left will effect X ; turn left will effect Y & Z +// +#define aw 110 // axis width +#define ah 15 // height {0......7......14} +#define am 7 // midpoint { 7 } +#define ar 7 // range {1234567 1234567} + +enum { + ACC_X = 0, + ACC_Y = 1, + ACC_Z = 2, + ACC_CNT = 3, + ACC_1 = ACC_X, // first + ACC_N = ACC_Z, // last +}; + +//+============================================================================ +static void nunchuck_showAcc(Canvas* const canvas, state_t* const state) { + ecDecNunchuck_t* d = &state->ec.dec[state->ec.decN].nunchuck; + ecCalNunchuck_t* lo = &state->ec.calS.nunchuck[1]; + ecCalNunchuck_t* mid = &state->ec.calS.nunchuck[2]; + ecCalNunchuck_t* hi = &state->ec.calS.nunchuck[3]; + + int y[ACC_CNT] = {0, 0 + (ah + 4), 0 + ((ah + 4) * 2)}; + int x = 10; + + static uint16_t v[ACC_CNT][aw] = {0}; + // static uint16_t tv[ACC_CNT][aw] = {0}; + + static uint16_t idx = 0; + static uint16_t cnt = aw - 1; + + // Only record when scanner NOT-paused + if(!state->pause) { + uint16_t dead = (1 << 5); + + // Find axes y-offsets + for(int a = ACC_1; a <= ACC_N; a++) { + uint16_t* dp = NULL; // data value (current reading) + uint16_t* lp = NULL; // lo value + uint16_t* mp = NULL; // mid value + uint16_t* hp = NULL; // hi value + uint16_t* vp = NULL; // value (result) + + switch(a) { + case ACC_X: + dp = &d->accX; // data (input) + lp = &lo->accX; // low \. + mp = &mid->accX; // mid > calibration + hp = &hi->accX; // high / + vp = &v[ACC_X][idx]; // value (where to store the result) + break; + case ACC_Y: + dp = &d->accY; + lp = &lo->accY; + mp = &mid->accY; + hp = &hi->accY; + vp = &v[ACC_Y][idx]; + break; + case ACC_Z: + dp = &d->accZ; + lp = &lo->accZ; + mp = &mid->accZ; + hp = &hi->accZ; + vp = &v[ACC_Z][idx]; + break; + default: + break; + } + + // Again - qv. the joysick calibration: + // This is not the "right way" to do this, it is just "one way" to do it + // ...mid point and extreme zones have a deadzone + // ...the rest is evenly divided by the amount of space on the graph + if((*dp >= (*mp - dead)) && (*dp <= (*mp + dead))) + *vp = ar; + else if(*dp >= (*hp - dead)) + *vp = ah - 1; + else if(*dp <= (*lp + dead)) + *vp = 0; + else if(*dp < *mp) { + uint16_t min = ((*lp + dead) + 1); + uint16_t max = ((*mp - dead) - 1); + float range = (max - min) + 1; + float m = range / (ar - 1); // 6 evenly(/fairly) divided zones + *vp = ((int)((*dp - min) / m)) + 1; + + } else { //if (*dp > *mp) + uint16_t min = ((*mp + dead) + 1); + uint16_t max = ((*hp - dead) - 1); + float range = (max - min) + 1; + float m = range / (ar - 1); // 6 evenly(/fairly) divided zones + *vp = ((int)((*dp - min) / m)) + 1 + ar; + } + } + + //! If we decide to offer "export to CSV" + //! I suggest we keep a second array of true-values, rather than do all the maths every time + //! Also - the data will need to me moved to the 'state' table - so a.n.other function can save it off + // tv[ACC_X][idx] = d->accX; + // tv[ACC_Y][idx] = d->accY; + // tv[ACC_Z][idx] = d->accZ; + + // Prepare for the next datapoint + if(++idx >= aw) idx = 0; + if(cnt) cnt--; + } + + // Auto-pause + if(state->apause && !idx) state->pause = true; + + // *** Draw axes *** + show(canvas, 0, y[ACC_X] + ((ah - img_6x8_X.h) / 2), &img_6x8_X, SHOW_SET_BLK); + show(canvas, 0, y[ACC_Y] + ((ah - img_6x8_Y.h) / 2), &img_6x8_Y, SHOW_SET_BLK); + show(canvas, 0, y[ACC_Z] + ((ah - img_6x8_Z.h) / 2), &img_6x8_Z, SHOW_SET_BLK); + + canvas_set_color(canvas, ColorBlack); + for(int a = ACC_1; a <= ACC_N; a++) { + canvas_draw_line(canvas, x - 1, y[a], x - 1, y[a] + ah); + canvas_draw_line(canvas, x, y[a] + ah, x + aw - 1, y[a] + ah); + + // Mid & Peak lines + for(int i = 1; i < aw; i += 3) { + canvas_draw_dot(canvas, x + i, y[a]); + canvas_draw_dot(canvas, x + i, y[a] + (ah / 2)); + } + } + + // Data (wiper display - see notes.txt for scrolling algorithm) + int end = idx ? idx : aw; + for(int a = ACC_1; a <= ACC_N; a++) { + canvas_draw_dot(canvas, x, y[a] + v[a][idx]); + for(int i = 1; i < end; i++) + canvas_draw_line(canvas, x + i, y[a] + v[a][i - 1], x + i, y[a] + v[a][i]); + if(!state->apause) + for(int i = end + 10; i < aw - cnt; i++) + canvas_draw_line(canvas, x + i, y[a] + v[a][i - 1], x + i, y[a] + v[a][i]); + } + // Wipe bar + if(end < aw) canvas_draw_line(canvas, x + end, y[0], x + end, y[2] + ah - 1); + if(++end < aw) canvas_draw_line(canvas, x + end, y[0], x + end, y[2] + ah - 1); + if(++end < aw) canvas_draw_line(canvas, x + end, y[0], x + end, y[2] + ah - 1); + + // *** Mode buttons *** + show(canvas, 0, 55, &img_key_L, SHOW_SET_BLK); // mode key + + if((state->calib & CAL_RANGE) || state->pause) state->flash++; + + // -pause- ...yeah, this got a little out of hand! LOL! + if(state->pause || state->apause) { + if(state->pause && state->apause && !idx) { + if(state->flash & 8) { + show(canvas, 108, 56, &img_key_U, SHOW_SET_BLK); + } else { + show(canvas, 108, 56, &img_key_Ui, SHOW_SET_BLK); + canvas_draw_line(canvas, x + aw, y[0], x + aw, y[2] + ah - 1); + } + } else { + show(canvas, 108, 56, &img_key_Ui, SHOW_SET_BLK); + } + } else { + show(canvas, 108, 56, &img_key_U, SHOW_SET_BLK); // pause + } + + // -calibration- + if(state->calib & CAL_RANGE) { + show(canvas, 119, 55, (state->flash & 8) ? &img_key_OKi : &img_key_OK, SHOW_SET_BLK); + } else { + show(canvas, 119, 55, &img_key_OK, SHOW_SET_BLK); + } +} + +#undef aw +#undef ah +#undef am +#undef ar + +//+============================================================================ ======================================== +// Default nunchuck screen +// +void nunchuck_show(Canvas* const canvas, state_t* const state) { + // Nunchucks have TWO scenes + if(state->scene == SCENE_NUNCHUCK_ACC) return nunchuck_showAcc(canvas, state); + + // Default scene + ecDecNunchuck_t* d = &state->ec.dec[state->ec.decN].nunchuck; + ecCalNunchuck_t* c = (state->hold) ? &state->ec.calS.nunchuck[(state->hold < 0) ? 0 : 4] : + (ecCalNunchuck_t*)d; //! danger will robinson! + ecCalNunchuck_t* js = state->ec.calS.nunchuck; + + // X, Y, Z + show(canvas, 42, 0, &img_6x8_X, SHOW_SET_BLK); + show(canvas, 73, 0, &img_6x8_Y, SHOW_SET_BLK); + show(canvas, 104, 0, &img_6x8_Z, SHOW_SET_BLK); + + canvas_draw_str_aligned(canvas, 0, 14, AlignLeft, AlignTop, "Accel"); + canvas_draw_str_aligned(canvas, 0, 28, AlignLeft, AlignTop, "Joy"); + + // accel values + showHex(canvas, 34, 12, c->accX, 3, 2); + showHex(canvas, 65, 12, c->accY, 3, 2); + showHex(canvas, 96, 12, c->accZ, 3, 2); + // Joy values + showHex(canvas, 38, 27, c->joyX, 2, 2); + showHex(canvas, 69, 27, c->joyY, 2, 2); + + showJoy( + canvas, + 103, + 32, + js[1].joyX, + js[2].joyX, + js[3].joyX, + js[1].joyY, + js[2].joyY, + js[3].joyY, + d->joyX, + d->joyY, + 8); + + // buttons + canvas_set_color(canvas, ColorBlack); + canvas_draw_str_aligned(canvas, 0, 44, AlignLeft, AlignTop, "Button"); + + if(!d->btnC) { + canvas_draw_rframe(canvas, 36, 42, 18, 12, 6); + show(canvas, 42, 44, &img_6x8_C, SHOW_SET_BLK); + } else { + canvas_draw_rbox(canvas, 36, 42, 18, 12, 6); + show(canvas, 42, 44, &img_6x8_C, SHOW_SET_WHT); + canvas_set_color(canvas, ColorBlack); + } + + if(!d->btnZ) { + canvas_draw_rframe(canvas, 64, 40, 24, 16, 2); + show(canvas, 73, 44, &img_6x8_Z, SHOW_SET_BLK); + } else { + canvas_draw_rbox(canvas, 64, 40, 24, 16, 2); + show(canvas, 73, 44, &img_6x8_Z, SHOW_SET_WHT); + } + + // Navigation + showPeakHold(state, canvas, state->hold); // peak keys + show(canvas, 0, 55, &img_key_L, SHOW_SET_BLK); // mode keys + show(canvas, 9, 55, &img_key_R, SHOW_SET_BLK); +} + +//+============================================================================ ======================================== +static bool nunchuck_keyAcc(const eventMsg_t* const msg, state_t* const state) { + int used = false; // assume key is NOT-handled + + switch(msg->input.type) { + case InputTypeShort: //# input.key) { + case InputKeyDown: //# pause) + state->pause = false; // Paused? Restart + else + state->apause = !state->apause; // No? toggle auto-pause + used = true; + break; + + case InputKeyLeft: //# calib &= ~CAL_NOTJOY; // DO calibrate joystick in NUNCHUCK mode + used = true; + break; + + default: + break; //# scene == SCENE_NUNCHUCK_ACC) return nunchuck_keyAcc(msg, state); + + // Default scene + int used = false; // assume key is NOT-handled + + switch(msg->input.type) { + case InputTypeShort: //# input.key) { + case InputKeyLeft: //# calib |= CAL_NOTJOY; // do NOT calibrate joystick in _ACC mode + used = true; + break; + default: + break; //# +#include + +//----------------------------------------------------------------------------- +// Controllers which have calibration must have their calibratable controls here +//! Is there a better way to get the start of the decode struct to match the calibration struct ? +#define NUNCHUCK_ANALOGUE \ + uint8_t joyX, joyY; \ + uint16_t accX, accY, accZ; + +//----------------------------------------------------------------------------- +// Calibratable controls +// +typedef struct ecCalNunchuck { + NUNCHUCK_ANALOGUE +} ecCalNunchuck_t; + +//----------------------------------------------------------------------------- +// All controls +// +typedef struct ecDecNunchuck { + NUNCHUCK_ANALOGUE // MUST be first + + // Digital controls + bool btnC, + btnZ; // BTN{c, z} +} ecDecNunchuck_t; + +#undef NUNCHUCK_ANALOGUE + +//============================================================================= +// Function prototypes +// +#include // Canvas +typedef struct wiiEC wiiEC_t; +typedef enum ecCalib ecCalib_t; +typedef struct state state_t; +typedef struct eventMsg eventMsg_t; + +void nunchuck_decode(wiiEC_t* const pec); +void nunchuck_msg(wiiEC_t* const pec, FuriMessageQueue* const queue); +void nunchuck_calib(wiiEC_t* const pec, ecCalib_t c); + +void nunchuck_show(Canvas* const canvas, state_t* const state); +bool nunchuck_key(const eventMsg_t* const msg, state_t* const state); + +#endif //WII_EC_NUNCHUCK_H_ diff --git a/applications/plugins/wii_ec_anal/wii_ec_udraw.c b/applications/plugins/wii_ec_anal/wii_ec_udraw.c new file mode 100644 index 000000000..82987b205 --- /dev/null +++ b/applications/plugins/wii_ec_anal/wii_ec_udraw.c @@ -0,0 +1,149 @@ +//! udraw support is NOT written - this is just notes about the init function +#include +#include // Core API + +#include "wii_anal.h" +#include "wii_ec.h" +#include "bc_logging.h" + +#include "i2c_workaround.h" //! temporary workaround for a bug in furi i2c [see header] + +// ** If you want to see what this source code looks like with all the MACROs expanded +// ** grep -v '#include ' wii_ec_udraw.c | gcc -E -o /dev/stdout -xc - +#include "wii_ec_macros.h" + +//+============================================================================ ======================================== +// https://github.com/madhephaestus/WiiChuck/blob/master/src/Drawsome.cpp#L3 +// Gratuitously stolen ... never tested (don't own one) - just bought one on ebay +// although it seems like the UK version is a "uDraw" and MIGHT contain a different chipset :/ +// +// read 6 bytes starting from 0x20 +// read 6 bytes starting from 0x28 +// read 6 bytes starting from 0x30 +// read 6 bytes starting from 0x38 +// read 6 bytes starting from 0x00 (#1) +// read 6 bytes starting from 0x00 (#2) +// write 1 byte [0x01] to 0xFB +// read 6 bytes starting from 0x00 (#3) +// read 6 bytes starting from 0x00 (#4) +// +bool udraw_init(wiiEC_t* const pec) { + ENTER; + bool rv = true; + + (void)pec; + /* +//! this is the Drawsome code, NOT the uDraw code !! + static const uint8_t reg[9] = {0x20, 0x28, 0x30, 0x38, 0x00, 0x00, 0xFB, 0x00, 0x00}; // 0..8 + const uint8_t* p = reg; + uint8_t buf[6] = {0}; + + if (!furi_hal_i2c_trxd(bus,addr, p++,1, buf,sizeof(buf), timeout,300)) goto fail ; // 0 + if (!furi_hal_i2c_trxd(bus,addr, p++,1, buf,sizeof(buf), timeout,300)) goto fail ; // 1 + furi_delay_ms(100); + + if (!furi_hal_i2c_trxd(bus,addr, p++,1, buf,sizeof(buf), timeout,300)) goto fail ; // 2 + if (!furi_hal_i2c_trxd(bus,addr, p++,1, buf,sizeof(buf), timeout,300)) goto fail ; // 3 + furi_delay_ms(100); + + if (!furi_hal_i2c_trxd(bus,addr, p++,1, buf,sizeof(buf), timeout,300)) goto fail ; // 4 + furi_delay_ms(100); + + if (!furi_hal_i2c_trxd(bus,addr, p++,1, buf,sizeof(buf), timeout,300)) goto fail ; // 5 + furi_delay_ms(100); + + buf[0] = *p++; + buf[1] = 0x01; + if (!furi_hal_i2c_tx(bus,addr, buf,2, timeout)) goto fail ; // 6 + + if (!furi_hal_i2c_trxd(bus,addr, p++,1, buf,sizeof(buf), timeout,300)) goto fail ; // 7 + furi_delay_ms(100); + + if (!furi_hal_i2c_trxd(bus,addr, p++,1, buf,sizeof(buf), timeout,300)) goto fail ; // 8 + furi_delay_ms(100); + + TRACE("%s : OK #%d", __func__, (p-reg)); + goto done; + +fail: + ERROR("%s : fail #%d", __func__, (p -reg) -1); + rv = false; + +done: +*/ + LEAVE; + return rv; +} + +//+============================================================================ ======================================== +bool udraw_key(const eventMsg_t* const msg, state_t* const state) { + (void)state; + bool run = true; + + switch(msg->input.type) { + case InputTypeShort: //# input.key) { + case InputKeyUp: //# ! After INPUT_LONG_PRESS interval, asynch to InputTypeRelease + switch(msg->input.key) { + case InputKeyUp: //# >U [ LONG-UP ] + case InputKeyDown: //# >D [ LONG-DOWN ] + case InputKeyLeft: //# >L [ LONG-LEFT ] + case InputKeyRight: //# >R [ LONG-RIGHT ] + case InputKeyOk: //# >O [ LONG-OK ] + case InputKeyBack: //# >B [ LONG-BACK ] + default: + break; //# >? + } + break; + case InputTypePress: //# +! After debounce + switch(msg->input.key) { + case InputKeyUp: //# +U [ SHORT-UP ] + case InputKeyDown: //# +D [ SHORT-DOWN ] + case InputKeyLeft: //# +L [ SHORT-LEFT ] + case InputKeyRight: //# +R [ SHORT-RIGHT ] + case InputKeyOk: //# +O [ SHORT-OK ] + case InputKeyBack: //# +B [ SHORT-BACK ] + default: + break; //# +? + } + break; + case InputTypeRepeat: //# *! With INPUT_REPEATE_PRESS period after InputTypeLong event + switch(msg->input.key) { + case InputKeyUp: //# *U [ REPEAT-UP ] + case InputKeyDown: //# *D [ REPEAT-DOWN ] + case InputKeyLeft: //# *L [ REPEAT-LEFT ] + case InputKeyRight: //# *R [ REPEAT-RIGHT ] + case InputKeyOk: //# *O [ REPEAT-OK ] + case InputKeyBack: //# *B [ REPEAT-BACK ] + default: + break; //# *? + } + break; + case InputTypeRelease: //# -! After debounce + switch(msg->input.key) { + case InputKeyUp: //# -U [ RELEASE-UP ] + case InputKeyDown: //# -D [ RELEASE-DOWN ] + case InputKeyLeft: //# -L [ RELEASE-LEFT ] + case InputKeyRight: //# -R [ RELEASE-RIGHT ] + case InputKeyOk: //# -O [ RELEASE-OK ] + case InputKeyBack: //# -B [ RELEASE-BACK ] + default: + break; //# -? + } + break; + default: + return true; + } + + return run; +} diff --git a/applications/plugins/wii_ec_anal/wii_ec_udraw.h b/applications/plugins/wii_ec_anal/wii_ec_udraw.h new file mode 100644 index 000000000..9283fd95d --- /dev/null +++ b/applications/plugins/wii_ec_anal/wii_ec_udraw.h @@ -0,0 +1,18 @@ +#ifndef WII_EC_UDRAW_H_ +#define WII_EC_UDRAW_H_ + +#include +#include + +//============================================================================= ======================================= +// Function prototypes +// +typedef struct wiiEC wiiEC_t; +typedef enum ecCalib ecCalib_t; +typedef struct eventMsg eventMsg_t; +typedef struct state state_t; + +bool udraw_init(wiiEC_t* const pec); +bool udraw_key(const eventMsg_t* const msg, state_t* const state); + +#endif //WII_EC_UDRAW_H_ diff --git a/applications/plugins/wii_ec_anal/wii_i2c.c b/applications/plugins/wii_ec_anal/wii_i2c.c new file mode 100644 index 000000000..f5d6840d9 --- /dev/null +++ b/applications/plugins/wii_ec_anal/wii_i2c.c @@ -0,0 +1,301 @@ +//----------------------------------------------------------------------------- ---------------------------------------- +// Biblio: [standing on the shoulders of giants] +// https://bootlin.com/labs/doc/nunchuk.pdf +// https://www.hackster.io/infusion/using-a-wii-nunchuk-with-arduino-597254#toc-i2c-protocol-9 +// https://web.archive.org/web/20220000000000*/https://www.hackster.io/infusion/using-a-wii-nunchuk-with-arduino-597254 +// https://github.com/madhephaestus/WiiChuck/blob/master/src/Accessory.cpp#L14 +// https://wiibrew.org/wiki/Wiimote/Extension_Controllers +// https://www.best-microcontroller-projects.com/i2c-tutorial.html +// +// WiiMote Extension Controller: +// Bus Address : 0x52 +// Register autoincrements after each (byte is) read +// 0x00..0x05 ( 6 bytes) ... [r] Controller Data +// 0x20..0x2F (16 bytes) ... [r] Calibration Data +// 0x30..0x3F (16 bytes) ... [r] (A copy of the) Calibration Data +// 0x40..0x4F (16 bytes) ... [w] Encryption key(s) +// 0xFA..0xFF ( 6 bytes) ... [r] Perhipheral ID + +//----------------------------------------------------------------------------- ---------------------------------------- +#include +#include +#include + +#include +#include +#include + +#include "i2c_workaround.h" //! temporary workaround for a bug in furi i2c [see header] + +#include "wii_anal.h" +#include "wii_i2c.h" +#include "wii_ec.h" + +#include "bc_logging.h" + +//----------------------------------------------------------------------------- ---------------------------------------- +// Wii Extension Controller i2c Bus address +static const uint8_t ec_i2cAddr = 0x52; + +// Initialise for UNencrypted comms +static const uint8_t regInit1 = 0xF0; +static const uint8_t regInit2 = 0xFB; +static const uint8_t cmdInit1[] = {regInit1, 0x55}; +static const uint8_t cmdInit2[] = {regInit2, 0x00}; + +// Initialise for ENcrypted comms +static const uint8_t regInitEnc = 0x40; +static const uint8_t cmdInitEnc[] = {regInitEnc, 0x00}; + +// Crypto key (PSK), base register : {0x40..0x4F}[2][8] +static const uint8_t regEnc = 0x40; // ENC_LEN + +// Controller State data, base register : {0x00..0x05}[6] +static const uint8_t regJoy = 0x00; // JOY_LEN + +// Calibration data, base register : {0x20..0x2F}[16] +static const uint8_t regCal = 0x20; // CAL_LEN + +// Controller ID, base register : {0xFA..0xFF}[6] +static const uint8_t regPid = 0xFA; // PID_LEN + +//+============================================================================ ======================================== +// Hexdump a buffer to the logfile +// +#if LOG_LEVEL >= 4 // INFO + +static void dump(const uint8_t* buf, const unsigned int len, const char* id) { + // snprintf() would be useful! + char s[128] = {0}; + char* p = NULL; + + strcpy(s, id); + p = s + strlen(s); + *p++ = ':'; + *p++ = ' '; + *p++ = '{'; + + for(unsigned int i = 0; i < len; i++) { + uint8_t hi = (buf[i] & 0xF0) >> 4; + uint8_t lo = (buf[i] & 0x0F); + + hi = hi + ((hi > 9) ? ('A' - 10) : '0'); + lo = lo + ((lo > 9) ? ('A' - 10) : '0'); + + *p++ = (char)hi; + *p++ = (char)lo; + *p++ = ','; + } + *p = '\0'; + *--p = '}'; + INFO(s); +} + +#else +#define dump(...) +#endif + +//+============================================================================ ======================================== +// +//! -W-A-R-N-I-N-G- : THIS ENCRYPTION CODE SHOULD NEVER BE REQUIRED ... AS SUCH, I'VE NEVER TESTED IT +// +static void decrypt(uint8_t* buf, const uint8_t* encKey, const uint8_t reg, unsigned int len) { +#if 1 // Use standard algorithm + // decrypted_byte = (encrypted_byte XOR encKey[1][address%8]) + encKey[2][address%8] + for(uint8_t* p = buf; p < buf + len; p++) + *p = (*p ^ encKey[(reg + (p - buf)) % 8]) + encKey[8 + ((reg + (p - buf)) % 8)]; + +#else //! This is (I think) a shortcut for an all-zero key [not tested] + (void)encKey; + (void)reg; + for(uint8_t* p = buf; p < buf + len; p++) *p = (*p ^ 0x17) + 0x17; +#endif +} + +//+============================================================================ ======================================== +// Read the Extension Controller state +// ...and decode it in to something sane +// +// Returns: {0:OK, >0:Error} +// +int ecRead(wiiEC_t* pec) { + ENTER; + int rv = 0; // assume success + + if(!pec->init) { + WARN("%s : device not initialised", __func__); + rv = 1; + goto bail; + } + + if(!furi_hal_i2c_is_device_ready(i2cBus, i2cAddr, i2cTimeout)) { + INFO("%s : device disconnected", __func__); + pec->init = false; + rv = 2; + goto bail; + } + + if(!furi_hal_i2c_trxd( + i2cBus, i2cAddr, ®Joy, 1, pec->joy, JOY_LEN, i2cTimeout, i2cReadWait)) { + ERROR("%s : trxd fail", __func__); + rv = 3; + goto bail; + } + + if(pec->encrypt) decrypt(pec->joy, pec->encKey, regJoy, JOY_LEN); + + // Decode the readings (according to Controller type) + ecDecode(pec); + +bail: + LEAVE; + return rv; +} + +//+============================================================================ ======================================== +// Initialise an Extension Controller +// +//! To disable encryption, pass a NULL encryption key <-- this is currently ALWAYS the case +// +bool ecInit(wiiEC_t* pec, const uint8_t* encKey) { + ENTER; + + bool rv = false; // assume failure + +#if 0 //! i2c workaround + //! I think this is done during OS startup - long before the plugin starts + furi_hal_i2c_init(); +#endif + +#if 0 //! i2c workaround + // May become relevant when the i2c issues are resolved + // Take control of the i2c bus [which returns void !?] + // --> firmware/targets/f7/furi_hal/furi_hal_i2c.c + furi_hal_i2c_acquire(i2cBus); +#endif + + pec->init = false; // assume failure + + // === See if the device is alive === + if(!furi_hal_i2c_is_device_ready(i2cBus, i2cAddr, i2cTimeout)) { + TRACE("%s : waiting for device", __func__); + goto bail; + } + INFO("%s : device connected", __func__); + + // === Initialise the device === + pec->init = false; // This goes true AFTER the (optional) controller-specific init code + + // === Start the Extension Controller === + if(encKey) { //! start in encrypted mode + + //! todo - should this happen here, or AFTER we've got the ID ? + + } else { + if(!furi_hal_i2c_tx(i2cBus, i2cAddr, cmdInit1, sizeof(cmdInit1), i2cTimeout)) { + ERROR("%s : init fail (dec1)", __func__); + goto bail; + } + TRACE("%s : init OK1", __func__); + + if(!furi_hal_i2c_tx(i2cBus, i2cAddr, cmdInit2, sizeof(cmdInit2), i2cTimeout)) { + ERROR("%s : init fail (dec2)", __func__); + goto bail; + } + TRACE("%s : init OK2", __func__); + } + + // === Retrieve the Extension Controller ID === + if(!furi_hal_i2c_trx(i2cBus, i2cAddr, ®Pid, 1, pec->pid, PID_LEN, i2cTimeout)) { + ERROR("%s : T(R)x fail (pid)", __func__); + goto bail; + } + if(pec->encrypt) decrypt(pec->joy, pec->encKey, regJoy, JOY_LEN); + dump(pec->pid, PID_LEN, "pid"); // debug INFO + + // Find the StringID in the lookup table + for(pec->pidx = PID_FIRST; pec->pidx < PID_ERROR; pec->pidx++) + if(memcmp(pec->pid, ecId[pec->pidx].id, PID_LEN) == 0) break; + if(pec->pidx == PID_ERROR) pec->pidx = PID_UNKNOWN; + pec->sid = ecId[pec->pidx].name; + INFO("sid: %s", pec->sid); + + // === (optionally) Enable encryption === + if(!encKey) { + pec->encrypt = false; + + } else { // Controller WILL encrypt ALL tranmissions + //! this encryption code fails - should it be done earlier? + //! as it is probably never of any use, I'm kinda loathed to spend time on it + //! https://github.com/madhephaestus/WiiChuck/blob/master/src/Accessory.cpp#L138 + uint8_t encTx[1 + ENC_LEN] = {0}; + uint8_t* ep = encTx; + + pec->encrypt = true; + + // ** Start the Controller in ENcrytped mode + if(!furi_hal_i2c_tx(i2cBus, i2cAddr, cmdInitEnc, sizeof(cmdInitEnc), i2cTimeout)) { + ERROR("%s : init fail (enc)", __func__); + goto bail; + } + + // Copy the (symmetric) encryption key to the controller state table + if(pec->encKey != encKey) memcpy(pec->encKey, encKey, ENC_LEN); + + // Build the encryption key packet + *ep++ = regEnc; + memcpy(ep, pec->encKey, ENC_LEN); + + // ** Send encryption key (PSK) + if(!furi_hal_i2c_tx(i2cBus, i2cAddr, encTx, (1 + ENC_LEN), i2cTimeout)) { + ERROR("%s : key fail", __func__); + goto bail; + } + + TRACE("%s : init OK (enc)", __func__); + } + + // === Some devices [eg. Drawsome/uDraw] require additional init code === + if(ecId[pec->init].init && (ecId[pec->init].init(pec) == false)) goto bail; + pec->init = true; + + // === Read calibration data === + if(!furi_hal_i2c_trx(i2cBus, i2cAddr, ®Cal, 1, pec->calF, CAL_LEN, i2cTimeout)) { + ERROR("%s : trx fail (cal)", __func__); + goto bail; + } + if(pec->encrypt) decrypt(pec->joy, pec->encKey, regJoy, JOY_LEN); + dump(pec->calF, CAL_LEN, "cal"); + + ecCalibrate(pec, CAL_RESET | CAL_FACTORY); // Load factory default calibration + + // === Initialise decode buffers === + pec->decN = 0; // read in to decode[1] (yes, N=0 -> read in to dec[1]) + switch(ecRead(pec)) { + case 0: // read OK + memcpy(&pec->dec[0], &pec->dec[1], sizeof(pec->dec[0])); + dump(pec->joy, JOY_LEN, "joy"); + break; + + default: // bug: unknown + case 1: // bug: not initialised - should never happen + ERROR("%s : read bug", __func__); + break; + + case 2: // device gone + case 3: // read fail + // Logging done by ecRead() + pec->init = false; + goto bail; + } + + rv = true; // yay :) + +bail: +#if 0 //! i2c workaround + furi_hal_i2c_release(i2cBus); +#endif + + LEAVE; + return rv; +} diff --git a/applications/plugins/wii_ec_anal/wii_i2c.h b/applications/plugins/wii_ec_anal/wii_i2c.h new file mode 100644 index 000000000..efebefcf9 --- /dev/null +++ b/applications/plugins/wii_ec_anal/wii_i2c.h @@ -0,0 +1,42 @@ +#ifndef WII_I2C_H_ +#define WII_I2C_H_ + +#include + +//#include "wii_ec.h" + +//----------------------------------------------------------------------------- ---------------------------------------- +// i2c bus details +// +// https://www.best-microcontroller-projects.com/i2c-tutorial.html +// https://web.archive.org/web/20220000000000*/https://www.best-microcontroller-projects.com/i2c-tutorial.html +// https://training.ti.com/introduction-i2c-reserved-addresses +// +// After the (special) START "bit"... +// the first 8bits (byte) of i2c data are the 7bit i2c Address, +// FOLLOWED by 1bit to signify a READ or WRITE {0=write, 1=read} +// The data is transmitted BIG-Endian, IE. MSb first [human readable] +// So the address actually lives in the TOP (MSb's) of the first "byte", (with bit0 being used as the read/write flag) +// +// The read() and write() functions on the FZ will set the LSb appropriately, +// BUT they do NOT shift the address left to make room for it! +// So the address you give to read/write() MUST be given as (7bitAddress << 1) +// +// When we read: After we send the read command, we wait for i2cReadWait uS before reading the data +// + +// firmware/targets/f7/furi_hal/furi_hal_i2c_types.h +#define i2cBus (&furi_hal_i2c_handle_external) // FZ external i2c bus +#define i2cAddr (ec_i2cAddr << 1) +#define i2cTimeout (3) // in mS +#define i2cReadWait (300) //! 300uS: how low can we take this? + +//----------------------------------------------------------------------------- ---------------------------------------- +// public functions +// +typedef struct wiiEC wiiEC_t; + +bool ecInit(wiiEC_t* const pec, const uint8_t* encKey); +int ecRead(wiiEC_t* const pec); + +#endif //WII_I2C_H_ diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c index 343181269..5d266ca4c 100644 --- a/lib/nfc/nfc_device.c +++ b/lib/nfc/nfc_device.c @@ -27,6 +27,7 @@ NfcDevice* nfc_device_alloc() { nfc_dev->dialogs = furi_record_open(RECORD_DIALOGS); nfc_dev->load_path = furi_string_alloc(); nfc_dev->dev_data.parsed_data = furi_string_alloc(); + nfc_dev->folder = furi_string_alloc(); // Rename cache folder name for backward compatibility if(storage_common_stat(nfc_dev->storage, "/ext/nfc/cache", NULL) == FSE_OK) { @@ -42,6 +43,7 @@ void nfc_device_free(NfcDevice* nfc_dev) { furi_record_close(RECORD_DIALOGS); furi_string_free(nfc_dev->load_path); furi_string_free(nfc_dev->dev_data.parsed_data); + furi_string_free(nfc_dev->folder); free(nfc_dev); } @@ -1328,6 +1330,11 @@ static void nfc_device_get_shadow_path(FuriString* orig_path, FuriString* shadow furi_string_cat_printf(shadow_path, "%s", NFC_APP_SHADOW_EXTENSION); } +static void nfc_device_get_folder_from_path(FuriString* path, FuriString* folder) { + size_t last_slash = furi_string_search_rchar(path, '/'); + furi_string_set_n(folder, path, 0, last_slash); +} + bool nfc_device_save(NfcDevice* dev, const char* dev_name) { furi_assert(dev); @@ -1338,8 +1345,18 @@ bool nfc_device_save(NfcDevice* dev, const char* dev_name) { temp_str = furi_string_alloc(); do { - // Create nfc directory if necessary - if(!storage_simply_mkdir(dev->storage, NFC_APP_FOLDER)) break; + // Create directory if necessary + FuriString* folder = furi_string_alloc(); + // Get folder from filename (filename is in the form of "folder/filename.nfc", so the folder is "folder/") + furi_string_printf(temp_str, "%s", dev_name); + // Get folder from filename + nfc_device_get_folder_from_path(temp_str, folder); + FURI_LOG_I("Nfc", "Saving to folder %s", furi_string_get_cstr(folder)); + if(!storage_simply_mkdir(dev->storage, furi_string_get_cstr(folder))) { + FURI_LOG_E("Nfc", "Failed to create folder %s", furi_string_get_cstr(folder)); + break; + } + furi_string_free(folder); // First remove nfc device file if it was saved furi_string_printf(temp_str, "%s", dev_name); // Open file @@ -1520,10 +1537,9 @@ bool nfc_device_load(NfcDevice* dev, const char* file_path, bool show_dialog) { bool nfc_file_select(NfcDevice* dev) { furi_assert(dev); + const char* folder = furi_string_get_cstr(dev->folder); // Input events and views are managed by file_browser - FuriString* nfc_app_folder; - nfc_app_folder = furi_string_alloc_set(NFC_APP_FOLDER); const DialogsFileBrowserOptions browser_options = { .extension = NFC_APP_EXTENSION, @@ -1533,13 +1549,12 @@ bool nfc_file_select(NfcDevice* dev) { .hide_ext = true, .item_loader_callback = NULL, .item_loader_context = NULL, - .base_path = NFC_APP_FOLDER, + .base_path = folder, }; bool res = dialog_file_browser_show(dev->dialogs, dev->load_path, dev->load_path, &browser_options); - furi_string_free(nfc_app_folder); if(res) { FuriString* filename; filename = furi_string_alloc(); @@ -1592,7 +1607,11 @@ bool nfc_device_delete(NfcDevice* dev, bool use_load_path) { furi_string_set(file_path, dev->load_path); } else { furi_string_printf( - file_path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); + file_path, + "%s/%s%s", + furi_string_get_cstr(dev->folder), + dev->dev_name, + NFC_APP_EXTENSION); } if(!storage_simply_remove(dev->storage, furi_string_get_cstr(file_path))) break; // Delete shadow file if it exists @@ -1601,7 +1620,11 @@ bool nfc_device_delete(NfcDevice* dev, bool use_load_path) { nfc_device_get_shadow_path(dev->load_path, file_path); } else { furi_string_printf( - file_path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_SHADOW_EXTENSION); + file_path, + "%s/%s%s", + furi_string_get_cstr(dev->folder), + dev->dev_name, + NFC_APP_SHADOW_EXTENSION); } if(!storage_simply_remove(dev->storage, furi_string_get_cstr(file_path))) break; } @@ -1637,7 +1660,12 @@ bool nfc_device_restore(NfcDevice* dev, bool use_load_path) { if(use_load_path && !furi_string_empty(dev->load_path)) { furi_string_set(path, dev->load_path); } else { - furi_string_printf(path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); + furi_string_printf( + path, + "%s/%s%s", + furi_string_get_cstr(dev->folder), + dev->dev_name, + NFC_APP_EXTENSION); } if(!nfc_device_load_data(dev, path, true)) break; restored = true; diff --git a/lib/nfc/nfc_device.h b/lib/nfc/nfc_device.h index 5ad6c4761..c1f95f5b8 100644 --- a/lib/nfc/nfc_device.h +++ b/lib/nfc/nfc_device.h @@ -22,7 +22,6 @@ extern "C" { #define NFC_READER_DATA_MAX_SIZE 64 #define NFC_DICT_KEY_BATCH_SIZE 50 -#define NFC_APP_FOLDER ANY_PATH("nfc") #define NFC_APP_EXTENSION ".nfc" #define NFC_APP_SHADOW_EXTENSION ".shd" @@ -91,6 +90,7 @@ typedef struct { NfcDeviceData dev_data; char dev_name[NFC_DEV_NAME_MAX_LEN + 1]; FuriString* load_path; + FuriString* folder; NfcDeviceSaveFormat format; bool shadow_file_exist;