diff --git a/Applications/Official/DEV_FW/GPIO/BH1750_Lightmeter.fap b/Applications/Official/DEV_FW/GPIO/BH1750_Lightmeter.fap new file mode 100644 index 000000000..031b9b14f Binary files /dev/null and b/Applications/Official/DEV_FW/GPIO/BH1750_Lightmeter.fap differ diff --git a/Applications/Official/DEV_FW/GPIO/ESP32_WiFi_Marauder.fap b/Applications/Official/DEV_FW/GPIO/ESP32_WiFi_Marauder.fap index 43fc4260f..867d5b011 100644 Binary files a/Applications/Official/DEV_FW/GPIO/ESP32_WiFi_Marauder.fap and b/Applications/Official/DEV_FW/GPIO/ESP32_WiFi_Marauder.fap differ diff --git a/Applications/Official/DEV_FW/GPIO/ESP8266_Deauther.fap b/Applications/Official/DEV_FW/GPIO/ESP8266_Deauther.fap new file mode 100644 index 000000000..70c1b5bec Binary files /dev/null and b/Applications/Official/DEV_FW/GPIO/ESP8266_Deauther.fap differ diff --git a/Applications/Official/DEV_FW/GPIO/GPIO_Intervalometer.fap b/Applications/Official/DEV_FW/GPIO/GPIO_Intervalometer.fap new file mode 100644 index 000000000..aea6c05e0 Binary files /dev/null and b/Applications/Official/DEV_FW/GPIO/GPIO_Intervalometer.fap differ diff --git a/Applications/Official/DEV_FW/GPIO/GPIO_Sentry_Safe.fap b/Applications/Official/DEV_FW/GPIO/GPIO_Sentry_Safe.fap new file mode 100644 index 000000000..1cc531ec5 Binary files /dev/null and b/Applications/Official/DEV_FW/GPIO/GPIO_Sentry_Safe.fap differ diff --git a/Applications/Official/DEV_FW/GPIO/I2C_Tools.fap b/Applications/Official/DEV_FW/GPIO/I2C_Tools.fap new file mode 100644 index 000000000..f0ee2d103 Binary files /dev/null and b/Applications/Official/DEV_FW/GPIO/I2C_Tools.fap differ diff --git a/Applications/Official/DEV_FW/GPIO/Temp_Sensors_Reader.fap b/Applications/Official/DEV_FW/GPIO/Temp_Sensors_Reader.fap new file mode 100644 index 000000000..926228231 Binary files /dev/null and b/Applications/Official/DEV_FW/GPIO/Temp_Sensors_Reader.fap differ diff --git a/Applications/Official/DEV_FW/Misc/DolphinBackup.fap b/Applications/Official/DEV_FW/Misc/DolphinBackup.fap new file mode 100644 index 000000000..e8ac475f3 Binary files /dev/null and b/Applications/Official/DEV_FW/Misc/DolphinBackup.fap differ diff --git a/Applications/Official/DEV_FW/Misc/DolphinRestorer.fap b/Applications/Official/DEV_FW/Misc/DolphinRestorer.fap new file mode 100644 index 000000000..301c9e7d9 Binary files /dev/null and b/Applications/Official/DEV_FW/Misc/DolphinRestorer.fap differ diff --git a/Applications/Official/DEV_FW/Misc/morse_code.fap b/Applications/Official/DEV_FW/Misc/morse_code.fap index 86db7d3bc..0410e12fa 100644 Binary files a/Applications/Official/DEV_FW/Misc/morse_code.fap and b/Applications/Official/DEV_FW/Misc/morse_code.fap differ diff --git a/Applications/Official/DEV_FW/Music/BPM_Tapper.fap b/Applications/Official/DEV_FW/Music/BPM_Tapper.fap index 12d47f1bd..94c86e41e 100644 Binary files a/Applications/Official/DEV_FW/Music/BPM_Tapper.fap and b/Applications/Official/DEV_FW/Music/BPM_Tapper.fap differ diff --git a/Applications/Official/DEV_FW/Music/Music_Beeper.fap b/Applications/Official/DEV_FW/Music/Music_Beeper.fap index b91721800..4969a655e 100644 Binary files a/Applications/Official/DEV_FW/Music/Music_Beeper.fap and b/Applications/Official/DEV_FW/Music/Music_Beeper.fap differ diff --git a/Applications/Official/DEV_FW/Music/Tuning_Fork.fap b/Applications/Official/DEV_FW/Music/Tuning_Fork.fap index 4ff7975ea..457c1ab48 100644 Binary files a/Applications/Official/DEV_FW/Music/Tuning_Fork.fap and b/Applications/Official/DEV_FW/Music/Tuning_Fork.fap differ diff --git a/Applications/Official/DEV_FW/Music/USB_Midi.fap b/Applications/Official/DEV_FW/Music/USB_Midi.fap new file mode 100644 index 000000000..268754dfa Binary files /dev/null and b/Applications/Official/DEV_FW/Music/USB_Midi.fap differ diff --git a/Applications/Official/DEV_FW/Music/music_player.fap b/Applications/Official/DEV_FW/Music/music_player.fap index 9da432531..9391dd8d5 100644 Binary files a/Applications/Official/DEV_FW/Music/music_player.fap and b/Applications/Official/DEV_FW/Music/music_player.fap differ diff --git a/Applications/Official/DEV_FW/Tools/Clock.fap b/Applications/Official/DEV_FW/Tools/Clock.fap index f10740eea..fbc032094 100644 Binary files a/Applications/Official/DEV_FW/Tools/Clock.fap and b/Applications/Official/DEV_FW/Tools/Clock.fap differ diff --git a/Applications/Official/DEV_FW/Tools/Count_Down_Timer.fap b/Applications/Official/DEV_FW/Tools/Count_Down_Timer.fap new file mode 100644 index 000000000..f9d5c988d Binary files /dev/null and b/Applications/Official/DEV_FW/Tools/Count_Down_Timer.fap differ diff --git a/Applications/Official/DEV_FW/Tools/MouseJiggler.fap b/Applications/Official/DEV_FW/Tools/MouseJiggler.fap index 09253706e..00fbb4d36 100644 Binary files a/Applications/Official/DEV_FW/Tools/MouseJiggler.fap and b/Applications/Official/DEV_FW/Tools/MouseJiggler.fap differ diff --git a/Applications/Official/DEV_FW/Tools/Multi_Converter.fap b/Applications/Official/DEV_FW/Tools/Multi_Converter.fap index 2ce6cf350..4475aba85 100644 Binary files a/Applications/Official/DEV_FW/Tools/Multi_Converter.fap and b/Applications/Official/DEV_FW/Tools/Multi_Converter.fap differ diff --git a/Applications/Official/DEV_FW/Tools/NameChanger.fap b/Applications/Official/DEV_FW/Tools/NameChanger.fap new file mode 100644 index 000000000..2903b0704 Binary files /dev/null and b/Applications/Official/DEV_FW/Tools/NameChanger.fap differ diff --git a/Applications/Official/DEV_FW/Tools/Password_Generator.fap b/Applications/Official/DEV_FW/Tools/Password_Generator.fap new file mode 100644 index 000000000..2ffd8c153 Binary files /dev/null and b/Applications/Official/DEV_FW/Tools/Password_Generator.fap differ diff --git a/Applications/Official/DEV_FW/Tools/Pomodoro_Timer.fap b/Applications/Official/DEV_FW/Tools/Pomodoro_Timer.fap index 277451bf3..94f369294 100644 Binary files a/Applications/Official/DEV_FW/Tools/Pomodoro_Timer.fap and b/Applications/Official/DEV_FW/Tools/Pomodoro_Timer.fap differ diff --git a/Applications/Official/DEV_FW/Tools/RFID_Fuzzer.fap b/Applications/Official/DEV_FW/Tools/RFID_Fuzzer.fap index d03a15004..55a3c9534 100644 Binary files a/Applications/Official/DEV_FW/Tools/RFID_Fuzzer.fap and b/Applications/Official/DEV_FW/Tools/RFID_Fuzzer.fap differ diff --git a/Applications/Official/DEV_FW/Tools/USB_Keyboard.fap b/Applications/Official/DEV_FW/Tools/USB_Keyboard.fap new file mode 100644 index 000000000..dce64f4f6 Binary files /dev/null and b/Applications/Official/DEV_FW/Tools/USB_Keyboard.fap differ diff --git a/Applications/Official/DEV_FW/Tools/Wii_EC_Analyser.fap b/Applications/Official/DEV_FW/Tools/Wii_EC_Analyser.fap new file mode 100644 index 000000000..5304aeef2 Binary files /dev/null and b/Applications/Official/DEV_FW/Tools/Wii_EC_Analyser.fap differ diff --git a/Applications/Official/DEV_FW/Tools/dap_link.fap b/Applications/Official/DEV_FW/Tools/dap_link.fap index 6239e1db9..418bbb5c9 100644 Binary files a/Applications/Official/DEV_FW/Tools/dap_link.fap and b/Applications/Official/DEV_FW/Tools/dap_link.fap differ diff --git a/Applications/Official/DEV_FW/Tools/dtmf_dolphin.fap b/Applications/Official/DEV_FW/Tools/dtmf_dolphin.fap index 8ba621e45..b2b4c3e7f 100644 Binary files a/Applications/Official/DEV_FW/Tools/dtmf_dolphin.fap and b/Applications/Official/DEV_FW/Tools/dtmf_dolphin.fap differ diff --git a/Applications/Official/DEV_FW/Tools/hid_ble.fap b/Applications/Official/DEV_FW/Tools/hid_ble.fap index 194f162cd..c4ee289c6 100644 Binary files a/Applications/Official/DEV_FW/Tools/hid_ble.fap and b/Applications/Official/DEV_FW/Tools/hid_ble.fap differ diff --git a/Applications/Official/DEV_FW/Tools/hid_usb.fap b/Applications/Official/DEV_FW/Tools/hid_usb.fap index 808ad83c2..5f63df6f9 100644 Binary files a/Applications/Official/DEV_FW/Tools/hid_usb.fap and b/Applications/Official/DEV_FW/Tools/hid_usb.fap differ diff --git a/Applications/Official/DEV_FW/Tools/i2cTools.fap b/Applications/Official/DEV_FW/Tools/i2cTools.fap new file mode 100644 index 000000000..3f43efae4 Binary files /dev/null and b/Applications/Official/DEV_FW/Tools/i2cTools.fap differ diff --git a/Applications/Official/DEV_FW/Tools/nfc_magic.fap b/Applications/Official/DEV_FW/Tools/nfc_magic.fap index 68b0a7daf..b152c6c28 100644 Binary files a/Applications/Official/DEV_FW/Tools/nfc_magic.fap and b/Applications/Official/DEV_FW/Tools/nfc_magic.fap differ diff --git a/Applications/Official/DEV_FW/Tools/picopass.fap b/Applications/Official/DEV_FW/Tools/picopass.fap index 6e72f76f3..92a9b275a 100644 Binary files a/Applications/Official/DEV_FW/Tools/picopass.fap and b/Applications/Official/DEV_FW/Tools/picopass.fap differ diff --git a/Applications/Official/DEV_FW/Tools/pocsag_pager.fap b/Applications/Official/DEV_FW/Tools/pocsag_pager.fap new file mode 100644 index 000000000..d2117c00a Binary files /dev/null and b/Applications/Official/DEV_FW/Tools/pocsag_pager.fap differ diff --git a/Applications/Official/DEV_FW/Tools/signal_generator.fap b/Applications/Official/DEV_FW/Tools/signal_generator.fap index b7d1c0ca4..a9cc988ac 100644 Binary files a/Applications/Official/DEV_FW/Tools/signal_generator.fap and b/Applications/Official/DEV_FW/Tools/signal_generator.fap differ diff --git a/Applications/Official/DEV_FW/Tools/usb_hid_autofire.fap b/Applications/Official/DEV_FW/Tools/usb_hid_autofire.fap new file mode 100644 index 000000000..633b5b708 Binary files /dev/null and b/Applications/Official/DEV_FW/Tools/usb_hid_autofire.fap differ diff --git a/Applications/Official/DEV_FW/Tools/weather_station.fap b/Applications/Official/DEV_FW/Tools/weather_station.fap index a9c7e21eb..673ee20c8 100644 Binary files a/Applications/Official/DEV_FW/Tools/weather_station.fap and b/Applications/Official/DEV_FW/Tools/weather_station.fap differ diff --git a/Applications/Official/DEV_FW/source/README.md b/Applications/Official/DEV_FW/source/README.md new file mode 100644 index 000000000..8bb7823c1 --- /dev/null +++ b/Applications/Official/DEV_FW/source/README.md @@ -0,0 +1 @@ +Put your custom applications in this folder. \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/airmouse/LICENSE b/Applications/Official/DEV_FW/source/airmouse/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Applications/Official/DEV_FW/source/airmouse/README.md b/Applications/Official/DEV_FW/source/airmouse/README.md new file mode 100644 index 000000000..9df0d69b0 --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/README.md @@ -0,0 +1,56 @@ +# Flipper Air Mouse + +## Brief + +> "You can turn anything into an air mouse if you're brave enough" + + — Piper, a.k.a. Pez + +Naturally, the quote above applies to [Flipper](https://flipperzero.one/) as well. + +## What? + +The app allows you to turn your Flipper into a USB or Bluetooth air mouse (you do need an extra module, see the Hardware section below)... + +Using it is really simple: + * Connect the Flipper via a USB cable and pick `USB`, or pick `Bluetooth` and pair it with your PC; + * Hold the Flipper in your hand with the buttons pointing towards the screen; + * Wave your Flipper like you don't care to move the cursor; + * Up button for Left mouse click; + * Down button for Right mouse click; + * Center button for Middle mouse click; + * Use calibration menu option if you notice significant drift (place your Flipper onto a level surface, make sure it doesn't move, run this option, wait 2 seconds, done). + +See early prototype [in action](https://www.youtube.com/watch?v=DdxAmmsYfMA). + +## Hardware + +The custom module is using Bosch BMI160 accelerometer/gyroscope chip connected via I2C. + +Take a look into the [schematic](https://github.com/ginkage/FlippAirMouse/tree/main/schematic) folder for Gerber, BOM and CPL files, so you can order directly from JLCPCB. + +Original idea: + +![What I thought it would look like](https://github.com/ginkage/FlippAirMouse/blob/main/schematic/schematic.png) + +Expectation: + +![What EDA though it would look like](https://github.com/ginkage/FlippAirMouse/blob/main/schematic/render.png) + +Reality: + +![What it looks like](https://github.com/ginkage/FlippAirMouse/blob/main/schematic/flipper.jpg) + + +## Software + +The code is based on the original Bosch [driver](https://github.com/BoschSensortec/BMI160_driver/) and an orientation tracking implementation from the [Cardboard](https://github.com/googlevr/cardboard/tree/master/sdk/sensors) project + +If you're familiar with Flipper applications, start in the [firmware](https://github.com/flipperdevices/flipperzero-firmware) checkout folder and do the following: +``` +cd applications/plugins +git clone https://github.com/ginkage/FlippAirMouse +cd ../.. +./fbt fap_air_mouse +``` +If you're not familiar with those, just grab a `fap` file from Releases. diff --git a/Applications/Official/DEV_FW/source/airmouse/air_mouse.c b/Applications/Official/DEV_FW/source/airmouse/air_mouse.c new file mode 100644 index 000000000..7a90e49f1 --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/air_mouse.c @@ -0,0 +1,156 @@ +#include "air_mouse.h" + +#include +#include + +#include "tracking/imu/imu.h" + +#define TAG "AirMouseApp" + +enum AirMouseSubmenuIndex { + AirMouseSubmenuIndexBtMouse, + AirMouseSubmenuIndexUsbMouse, + AirMouseSubmenuIndexCalibration, +}; + +void air_mouse_submenu_callback(void* context, uint32_t index) { + furi_assert(context); + AirMouse* app = context; + if(index == AirMouseSubmenuIndexBtMouse) { + app->view_id = AirMouseViewBtMouse; + view_dispatcher_switch_to_view(app->view_dispatcher, AirMouseViewBtMouse); + } else if(index == AirMouseSubmenuIndexUsbMouse) { + app->view_id = AirMouseViewUsbMouse; + view_dispatcher_switch_to_view(app->view_dispatcher, AirMouseViewUsbMouse); + } else if(index == AirMouseSubmenuIndexCalibration) { + app->view_id = AirMouseViewCalibration; + view_dispatcher_switch_to_view(app->view_dispatcher, AirMouseViewCalibration); + } +} + +void air_mouse_dialog_callback(DialogExResult result, void* context) { + furi_assert(context); + AirMouse* app = context; + if(result == DialogExResultLeft) { + view_dispatcher_switch_to_view(app->view_dispatcher, VIEW_NONE); // Exit + } else if(result == DialogExResultRight) { + view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); // Show last view + } else if(result == DialogExResultCenter) { + view_dispatcher_switch_to_view(app->view_dispatcher, AirMouseViewSubmenu); // Menu + } +} + +uint32_t air_mouse_exit_confirm_view(void* context) { + UNUSED(context); + return AirMouseViewExitConfirm; +} + +uint32_t air_mouse_exit(void* context) { + UNUSED(context); + return VIEW_NONE; +} + +AirMouse* air_mouse_app_alloc() { + AirMouse* app = malloc(sizeof(AirMouse)); + + // Gui + app->gui = furi_record_open(RECORD_GUI); + + // View dispatcher + app->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + // Submenu view + app->submenu = submenu_alloc(); + submenu_add_item( + app->submenu, "Bluetooth", AirMouseSubmenuIndexBtMouse, air_mouse_submenu_callback, app); + submenu_add_item( + app->submenu, "USB", AirMouseSubmenuIndexUsbMouse, air_mouse_submenu_callback, app); + submenu_add_item( + app->submenu, + "Calibration", + AirMouseSubmenuIndexCalibration, + air_mouse_submenu_callback, + app); + view_set_previous_callback(submenu_get_view(app->submenu), air_mouse_exit); + view_dispatcher_add_view( + app->view_dispatcher, AirMouseViewSubmenu, submenu_get_view(app->submenu)); + + // Dialog view + app->dialog = dialog_ex_alloc(); + dialog_ex_set_result_callback(app->dialog, air_mouse_dialog_callback); + dialog_ex_set_context(app->dialog, app); + dialog_ex_set_left_button_text(app->dialog, "Exit"); + dialog_ex_set_right_button_text(app->dialog, "Stay"); + dialog_ex_set_center_button_text(app->dialog, "Menu"); + dialog_ex_set_header(app->dialog, "Close Current App?", 16, 12, AlignLeft, AlignTop); + view_dispatcher_add_view( + app->view_dispatcher, AirMouseViewExitConfirm, dialog_ex_get_view(app->dialog)); + + // Bluetooth view + app->bt_mouse = bt_mouse_alloc(app->view_dispatcher); + view_set_previous_callback(bt_mouse_get_view(app->bt_mouse), air_mouse_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, AirMouseViewBtMouse, bt_mouse_get_view(app->bt_mouse)); + + // USB view + app->usb_mouse = usb_mouse_alloc(app->view_dispatcher); + view_set_previous_callback(usb_mouse_get_view(app->usb_mouse), air_mouse_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, AirMouseViewUsbMouse, usb_mouse_get_view(app->usb_mouse)); + + // Calibration view + app->calibration = calibration_alloc(app->view_dispatcher); + view_set_previous_callback( + calibration_get_view(app->calibration), air_mouse_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, AirMouseViewCalibration, calibration_get_view(app->calibration)); + + app->view_id = AirMouseViewSubmenu; + view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); + + return app; +} + +void air_mouse_app_free(AirMouse* app) { + furi_assert(app); + + // Free views + view_dispatcher_remove_view(app->view_dispatcher, AirMouseViewSubmenu); + submenu_free(app->submenu); + view_dispatcher_remove_view(app->view_dispatcher, AirMouseViewExitConfirm); + dialog_ex_free(app->dialog); + view_dispatcher_remove_view(app->view_dispatcher, AirMouseViewBtMouse); + bt_mouse_free(app->bt_mouse); + view_dispatcher_remove_view(app->view_dispatcher, AirMouseViewUsbMouse); + usb_mouse_free(app->usb_mouse); + view_dispatcher_remove_view(app->view_dispatcher, AirMouseViewCalibration); + calibration_free(app->calibration); + view_dispatcher_free(app->view_dispatcher); + + // Close records + furi_record_close(RECORD_GUI); + app->gui = NULL; + + // Free rest + free(app); +} + +int32_t air_mouse_app(void* p) { + UNUSED(p); + + AirMouse* app = air_mouse_app_alloc(); + if(!imu_begin()) { + air_mouse_app_free(app); + return -1; + } + + DOLPHIN_DEED(DolphinDeedPluginStart); + view_dispatcher_run(app->view_dispatcher); + + imu_end(); + air_mouse_app_free(app); + + return 0; +} diff --git a/Applications/Official/DEV_FW/source/airmouse/air_mouse.h b/Applications/Official/DEV_FW/source/airmouse/air_mouse.h new file mode 100644 index 000000000..3a1ba783e --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/air_mouse.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "views/bt_mouse.h" +#include "views/usb_mouse.h" +#include "views/calibration.h" + +typedef struct { + Gui* gui; + ViewDispatcher* view_dispatcher; + Submenu* submenu; + DialogEx* dialog; + BtMouse* bt_mouse; + UsbMouse* usb_mouse; + Calibration* calibration; + uint32_t view_id; +} AirMouse; + +typedef enum { + AirMouseViewSubmenu, + AirMouseViewBtMouse, + AirMouseViewUsbMouse, + AirMouseViewCalibration, + AirMouseViewExitConfirm, +} AirMouseView; diff --git a/Applications/Official/DEV_FW/source/airmouse/application.fam b/Applications/Official/DEV_FW/source/airmouse/application.fam new file mode 100644 index 000000000..c646ee7e1 --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/application.fam @@ -0,0 +1,9 @@ +App( + appid="BMI160_Air_Mouse", + name="[BMI160] Air Mouse", + apptype=FlipperAppType.EXTERNAL, + entry_point="air_mouse_app", + stack_size=10 * 1024, + fap_icon="mouse_10px.png", + fap_category="GPIO", +) diff --git a/Applications/Official/DEV_FW/source/airmouse/mouse_10px.png b/Applications/Official/DEV_FW/source/airmouse/mouse_10px.png new file mode 100644 index 000000000..94c3a7a14 Binary files /dev/null and b/Applications/Official/DEV_FW/source/airmouse/mouse_10px.png differ diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/calibration_data.cc b/Applications/Official/DEV_FW/source/airmouse/tracking/calibration_data.cc new file mode 100644 index 000000000..e62311c7a --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/calibration_data.cc @@ -0,0 +1,85 @@ +#include +#include + +#define TAG "tracker" + +#include "calibration_data.h" + +#include +#include + +// Student's distribution T value for 95% (two-sided) confidence interval. +static const double Tn = 1.960; + +// Number of samples (degrees of freedom) for the corresponding T values. +static const int Nn = 200; + +void CalibrationData::reset() +{ + complete = false; + count = 0; + sum = Vector::Zero(); + sumSq = Vector::Zero(); + mean = Vector::Zero(); + median = Vector::Zero(); + sigma = Vector::Zero(); + delta = Vector::Zero(); + xData.clear(); + yData.clear(); + zData.clear(); +} + +bool CalibrationData::add(Vector& data) +{ + if (complete) { + return true; + } + + xData.push_back(data[0]); + yData.push_back(data[1]); + zData.push_back(data[2]); + + sum += data; + sumSq += data * data; + count++; + + if (count >= Nn) { + calcDelta(); + complete = true; + } + + return complete; +} + +static inline double medianOf(std::vector& list) +{ + std::sort(list.begin(), list.end()); + int count = list.size(); + int middle = count / 2; + return (count % 2 == 1) ? list[middle] : (list[middle - 1] + list[middle]) / 2.0l; +} + +void CalibrationData::calcDelta() +{ + median.Set(medianOf(xData), medianOf(yData), medianOf(zData)); + + mean = sum / count; + Vector m2 = mean * mean; + Vector d = sumSq / count - m2; + Vector s2 = (d * count) / (count - 1); + sigma = Vector(std::sqrt(d[0]), std::sqrt(d[1]), std::sqrt(d[2])); + Vector s = Vector(std::sqrt(s2[0]), std::sqrt(s2[1]), std::sqrt(s2[2])); + delta = s * Tn / std::sqrt((double)count); + Vector low = mean - delta; + Vector high = mean + delta; + + FURI_LOG_I(TAG, + "M[x] = { %f ... %f } // median = %f // avg = %f // delta = %f // sigma = %f", + low[0], high[0], median[0], mean[0], delta[0], sigma[0]); + FURI_LOG_I(TAG, + "M[y] = { %f ... %f } // median = %f // avg = %f // delta = %f // sigma = %f", + low[1], high[1], median[1], mean[1], delta[1], sigma[1]); + FURI_LOG_I(TAG, + "M[z] = { %f ... %f } // median = %f // avg = %f // delta = %f // sigma = %f", + low[2], high[2], median[2], mean[2], delta[2], sigma[2]); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/calibration_data.h b/Applications/Official/DEV_FW/source/airmouse/tracking/calibration_data.h new file mode 100644 index 000000000..d47dab08d --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/calibration_data.h @@ -0,0 +1,117 @@ +#pragma once + +#include +#include +#include + +#include "util/vector.h" + +#define CALIBRATION_DATA_VER (1) +#define CALIBRATION_DATA_FILE_NAME ".calibration.data" +#define CALIBRATION_DATA_PATH INT_PATH(CALIBRATION_DATA_FILE_NAME) +#define CALIBRATION_DATA_MAGIC (0x23) + +#define CALIBRATION_DATA_SAVE(x) \ + saved_struct_save( \ + CALIBRATION_DATA_PATH, \ + (x), \ + sizeof(CalibrationMedian), \ + CALIBRATION_DATA_MAGIC, \ + CALIBRATION_DATA_VER) + +#define CALIBRATION_DATA_LOAD(x) \ + saved_struct_load( \ + CALIBRATION_DATA_PATH, \ + (x), \ + sizeof(CalibrationMedian), \ + CALIBRATION_DATA_MAGIC, \ + CALIBRATION_DATA_VER) + +typedef struct { + double x; + double y; + double z; +} CalibrationMedian; + +typedef cardboard::Vector3 Vector; + +/** + * Helper class to gather some stats and store the calibration data. Right now it calculates a lot + * more stats than actually needed. Some of them are used for logging the sensors quality (and + * filing bugs), other may be required in the future, e.g. for bias. + */ +class CalibrationData { +public: + /** + * Check if the sensors were calibrated before. + * + * @return {@code true} if calibration data is available, or {@code false} otherwise. + */ + bool isComplete() { + return complete; + } + + /** Prepare to collect new calibration data. */ + void reset(); + + /** + * Retrieve the median gyroscope readings. + * + * @return Three-axis median vector. + */ + Vector getMedian() { + return median; + } + + /** + * Retrieve the mean gyroscope readings. + * + * @return Three-axis mean vector. + */ + Vector getMean() { + return mean; + } + + /** + * Retrieve the standard deviation of gyroscope readings. + * + * @return Three-axis standard deviation vector. + */ + Vector getSigma() { + return sigma; + } + + /** + * Retrieve the confidence interval size of gyroscope readings. + * + * @return Three-axis confidence interval size vector. + */ + Vector getDelta() { + return delta; + } + + /** + * Add a new gyroscope reading to the stats. + * + * @param data gyroscope values vector. + * @return {@code true} if we now have enough data for calibration, or {@code false} otherwise. + */ + bool add(Vector& data); + +private: + // Calculates the confidence interval (mean +- delta) and some other related values, like + // standard deviation, etc. See https://en.wikipedia.org/wiki/Student%27s_t-distribution + void calcDelta(); + + int count; + bool complete; + Vector sum; + Vector sumSq; + Vector mean; + Vector median; + Vector sigma; + Vector delta; + std::vector xData; + std::vector yData; + std::vector zData; +}; diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/imu/bmi160.c b/Applications/Official/DEV_FW/source/airmouse/tracking/imu/bmi160.c new file mode 100644 index 000000000..968dddd4d --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/imu/bmi160.c @@ -0,0 +1,5988 @@ +/** +* Copyright (c) 2021 Bosch Sensortec GmbH. All rights reserved. +* +* BSD-3-Clause +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* +* 3. Neither the name of the copyright holder nor the names of its +* contributors may be used to endorse or promote products derived from +* this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +* +* @file bmi160.c +* @date 2021-10-05 +* @version v3.9.2 +* +*/ + +#include "bmi160.h" + +/* Below look up table follows the enum bmi160_int_types. + * Hence any change should match to the enum bmi160_int_types + */ +const uint8_t int_mask_lookup_table[13] = { + BMI160_INT1_SLOPE_MASK, + BMI160_INT1_SLOPE_MASK, + BMI160_INT2_LOW_STEP_DETECT_MASK, + BMI160_INT1_DOUBLE_TAP_MASK, + BMI160_INT1_SINGLE_TAP_MASK, + BMI160_INT1_ORIENT_MASK, + BMI160_INT1_FLAT_MASK, + BMI160_INT1_HIGH_G_MASK, + BMI160_INT1_LOW_G_MASK, + BMI160_INT1_NO_MOTION_MASK, + BMI160_INT2_DATA_READY_MASK, + BMI160_INT2_FIFO_FULL_MASK, + BMI160_INT2_FIFO_WM_MASK}; + +/*********************************************************************/ +/* Static function declarations */ + +/*! + * @brief This API configures the pins to fire the + * interrupt signal when it occurs + * + * @param[in] int_config : Structure instance of bmi160_int_settg. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error. + */ +static int8_t + set_intr_pin_config(const struct bmi160_int_settg* int_config, const struct bmi160_dev* dev); + +/*! + * @brief This API sets the any-motion interrupt of the sensor. + * This interrupt occurs when accel values exceeds preset threshold + * for a certain period of time. + * + * @param[in] int_config : Structure instance of bmi160_int_settg. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error. + */ +static int8_t + set_accel_any_motion_int(struct bmi160_int_settg* int_config, struct bmi160_dev* dev); + +/*! + * @brief This API sets tap interrupts.Interrupt is fired when + * tap movements happen. + * + * @param[in] int_config : Structure instance of bmi160_int_settg. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error. + */ +static int8_t set_accel_tap_int(struct bmi160_int_settg* int_config, const struct bmi160_dev* dev); + +/*! + * @brief This API sets the data ready interrupt for both accel and gyro. + * This interrupt occurs when new accel and gyro data come. + * + * @param[in] int_config : Structure instance of bmi160_int_settg. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error. + */ +static int8_t set_accel_gyro_data_ready_int( + const struct bmi160_int_settg* int_config, + const struct bmi160_dev* dev); + +/*! + * @brief This API sets the significant motion interrupt of the sensor.This + * interrupt occurs when there is change in user location. + * + * @param[in] int_config : Structure instance of bmi160_int_settg. + * @param[in] dev : Structure instance of bmi160_dev. + * + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error. + */ +static int8_t + set_accel_sig_motion_int(struct bmi160_int_settg* int_config, struct bmi160_dev* dev); + +/*! + * @brief This API sets the no motion/slow motion interrupt of the sensor. + * Slow motion is similar to any motion interrupt.No motion interrupt + * occurs when slope bet. two accel values falls below preset threshold + * for preset duration. + * + * @param[in] int_config : Structure instance of bmi160_int_settg. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error. + */ +static int8_t + set_accel_no_motion_int(struct bmi160_int_settg* int_config, const struct bmi160_dev* dev); + +/*! + * @brief This API sets the step detection interrupt.This interrupt + * occurs when the single step causes accel values to go above + * preset threshold. + * + * @param[in] int_config : Structure instance of bmi160_int_settg. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error. + */ +static int8_t + set_accel_step_detect_int(struct bmi160_int_settg* int_config, const struct bmi160_dev* dev); + +/*! + * @brief This API sets the orientation interrupt of the sensor.This + * interrupt occurs when there is orientation change in the sensor + * with respect to gravitational field vector g. + * + * @param[in] int_config : Structure instance of bmi160_int_settg. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error. + */ +static int8_t + set_accel_orientation_int(struct bmi160_int_settg* int_config, const struct bmi160_dev* dev); + +/*! + * @brief This API sets the flat interrupt of the sensor.This interrupt + * occurs in case of flat orientation + * + * @param[in] int_config : Structure instance of bmi160_int_settg. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error. + */ +static int8_t + set_accel_flat_detect_int(struct bmi160_int_settg* int_config, const struct bmi160_dev* dev); + +/*! + * @brief This API sets the low-g interrupt of the sensor.This interrupt + * occurs during free-fall. + * + * @param[in] int_config : Structure instance of bmi160_int_settg. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error. + */ +static int8_t + set_accel_low_g_int(struct bmi160_int_settg* int_config, const struct bmi160_dev* dev); + +/*! + * @brief This API sets the high-g interrupt of the sensor.The interrupt + * occurs if the absolute value of acceleration data of any enabled axis + * exceeds the programmed threshold and the sign of the value does not + * change for a preset duration. + * + * @param[in] int_config : Structure instance of bmi160_int_settg. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error. + */ +static int8_t + set_accel_high_g_int(struct bmi160_int_settg* int_config, const struct bmi160_dev* dev); + +/*! + * @brief This API sets the default configuration parameters of accel & gyro. + * Also maintain the previous state of configurations. + * + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error. + */ +static void default_param_settg(struct bmi160_dev* dev); + +/*! + * @brief This API is used to validate the device structure pointer for + * null conditions. + * + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error. + */ +static int8_t null_ptr_check(const struct bmi160_dev* dev); + +/*! + * @brief This API set the accel configuration. + * + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error. + */ +static int8_t set_accel_conf(struct bmi160_dev* dev); + +/*! + * @brief This API gets the accel configuration. + * + * @param[out] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error. + */ +static int8_t get_accel_conf(struct bmi160_dev* dev); + +/*! + * @brief This API check the accel configuration. + * + * @param[in] data : Pointer to store the updated accel config. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error. + */ +static int8_t check_accel_config(uint8_t* data, const struct bmi160_dev* dev); + +/*! + * @brief This API process the accel odr. + * + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error. + */ +static int8_t process_accel_odr(uint8_t* data, const struct bmi160_dev* dev); + +/*! + * @brief This API process the accel bandwidth. + * + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error. + */ +static int8_t process_accel_bw(uint8_t* data, const struct bmi160_dev* dev); + +/*! + * @brief This API process the accel range. + * + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error. + */ +static int8_t process_accel_range(uint8_t* data, const struct bmi160_dev* dev); + +/*! + * @brief This API checks the invalid settings for ODR & Bw for Accel and Gyro. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error. + */ +static int8_t check_invalid_settg(const struct bmi160_dev* dev); + +/*! + * @brief This API set the gyro configuration. + * + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error. + */ +static int8_t set_gyro_conf(struct bmi160_dev* dev); + +/*! + * @brief This API get the gyro configuration. + * + * @param[out] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error. + */ +static int8_t get_gyro_conf(struct bmi160_dev* dev); + +/*! + * @brief This API check the gyro configuration. + * + * @param[in] data : Pointer to store the updated gyro config. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error. + */ +static int8_t check_gyro_config(uint8_t* data, const struct bmi160_dev* dev); + +/*! + * @brief This API process the gyro odr. + * + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error. + */ +static int8_t process_gyro_odr(uint8_t* data, const struct bmi160_dev* dev); + +/*! + * @brief This API process the gyro bandwidth. + * + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error. + */ +static int8_t process_gyro_bw(uint8_t* data, const struct bmi160_dev* dev); + +/*! + * @brief This API process the gyro range. + * + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error. + */ +static int8_t process_gyro_range(uint8_t* data, const struct bmi160_dev* dev); + +/*! + * @brief This API sets the accel power mode. + * + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error. + */ +static int8_t set_accel_pwr(struct bmi160_dev* dev); + +/*! + * @brief This API process the undersampling setting of Accel. + * + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error. + */ +static int8_t process_under_sampling(uint8_t* data, const struct bmi160_dev* dev); + +/*! + * @brief This API sets the gyro power mode. + * + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error. + */ +static int8_t set_gyro_pwr(struct bmi160_dev* dev); + +/*! + * @brief This API reads accel data along with sensor time if time is requested + * by user. Kindly refer the user guide(README.md) for more info. + * + * @param[in] len : len to read no of bytes + * @param[out] accel : Structure pointer to store accel data + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t + get_accel_data(uint8_t len, struct bmi160_sensor_data* accel, const struct bmi160_dev* dev); + +/*! + * @brief This API reads accel data along with sensor time if time is requested + * by user. Kindly refer the user guide(README.md) for more info. + * + * @param[in] len : len to read no of bytes + * @param[out] gyro : Structure pointer to store accel data + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t + get_gyro_data(uint8_t len, struct bmi160_sensor_data* gyro, const struct bmi160_dev* dev); + +/*! + * @brief This API reads accel and gyro data along with sensor time + * if time is requested by user. + * Kindly refer the user guide(README.md) for more info. + * + * @param[in] len : len to read no of bytes + * @param[out] accel : Structure pointer to store accel data + * @param[out] gyro : Structure pointer to store accel data + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t get_accel_gyro_data( + uint8_t len, + struct bmi160_sensor_data* accel, + struct bmi160_sensor_data* gyro, + const struct bmi160_dev* dev); + +/*! + * @brief This API enables the any-motion interrupt for accel. + * + * @param[in] any_motion_int_cfg : Structure instance of + * bmi160_acc_any_mot_int_cfg. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t enable_accel_any_motion_int( + const struct bmi160_acc_any_mot_int_cfg* any_motion_int_cfg, + struct bmi160_dev* dev); + +/*! + * @brief This API disable the sig-motion interrupt. + * + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t disable_sig_motion_int(const struct bmi160_dev* dev); + +/*! + * @brief This API configure the source of data(filter & pre-filter) + * for any-motion interrupt. + * + * @param[in] any_motion_int_cfg : Structure instance of + * bmi160_acc_any_mot_int_cfg. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t config_any_motion_src( + const struct bmi160_acc_any_mot_int_cfg* any_motion_int_cfg, + const struct bmi160_dev* dev); + +/*! + * @brief This API configure the duration and threshold of + * any-motion interrupt. + * + * @param[in] any_motion_int_cfg : Structure instance of + * bmi160_acc_any_mot_int_cfg. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t config_any_dur_threshold( + const struct bmi160_acc_any_mot_int_cfg* any_motion_int_cfg, + const struct bmi160_dev* dev); + +/*! + * @brief This API configure necessary setting of any-motion interrupt. + * + * @param[in] int_config : Structure instance of bmi160_int_settg. + * @param[in] any_motion_int_cfg : Structure instance of + * bmi160_acc_any_mot_int_cfg. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t config_any_motion_int_settg( + const struct bmi160_int_settg* int_config, + const struct bmi160_acc_any_mot_int_cfg* any_motion_int_cfg, + const struct bmi160_dev* dev); + +/*! + * @brief This API enable the data ready interrupt. + * + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t enable_data_ready_int(const struct bmi160_dev* dev); + +/*! + * @brief This API enables the no motion/slow motion interrupt. + * + * @param[in] no_mot_int_cfg : Structure instance of + * bmi160_acc_no_motion_int_cfg. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t enable_no_motion_int( + const struct bmi160_acc_no_motion_int_cfg* no_mot_int_cfg, + const struct bmi160_dev* dev); + +/*! + * @brief This API configure the interrupt PIN setting for + * no motion/slow motion interrupt. + * + * @param[in] int_config : structure instance of bmi160_int_settg. + * @param[in] no_mot_int_cfg : Structure instance of + * bmi160_acc_no_motion_int_cfg. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t config_no_motion_int_settg( + const struct bmi160_int_settg* int_config, + const struct bmi160_acc_no_motion_int_cfg* no_mot_int_cfg, + const struct bmi160_dev* dev); + +/*! + * @brief This API configure the source of interrupt for no motion. + * + * @param[in] no_mot_int_cfg : Structure instance of + * bmi160_acc_no_motion_int_cfg. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t config_no_motion_data_src( + const struct bmi160_acc_no_motion_int_cfg* no_mot_int_cfg, + const struct bmi160_dev* dev); + +/*! + * @brief This API configure the duration and threshold of + * no motion/slow motion interrupt along with selection of no/slow motion. + * + * @param[in] no_mot_int_cfg : Structure instance of + * bmi160_acc_no_motion_int_cfg. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t config_no_motion_dur_thr( + const struct bmi160_acc_no_motion_int_cfg* no_mot_int_cfg, + const struct bmi160_dev* dev); + +/*! + * @brief This API enables the sig-motion motion interrupt. + * + * @param[in] sig_mot_int_cfg : Structure instance of + * bmi160_acc_sig_mot_int_cfg. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t enable_sig_motion_int( + const struct bmi160_acc_sig_mot_int_cfg* sig_mot_int_cfg, + struct bmi160_dev* dev); + +/*! + * @brief This API configure the interrupt PIN setting for + * significant motion interrupt. + * + * @param[in] int_config : Structure instance of bmi160_int_settg. + * @param[in] sig_mot_int_cfg : Structure instance of + * bmi160_acc_sig_mot_int_cfg. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t config_sig_motion_int_settg( + const struct bmi160_int_settg* int_config, + const struct bmi160_acc_sig_mot_int_cfg* sig_mot_int_cfg, + const struct bmi160_dev* dev); + +/*! + * @brief This API configure the source of data(filter & pre-filter) + * for sig motion interrupt. + * + * @param[in] sig_mot_int_cfg : Structure instance of + * bmi160_acc_sig_mot_int_cfg. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t config_sig_motion_data_src( + const struct bmi160_acc_sig_mot_int_cfg* sig_mot_int_cfg, + const struct bmi160_dev* dev); + +/*! + * @brief This API configure the threshold, skip and proof time of + * sig motion interrupt. + * + * @param[in] sig_mot_int_cfg : Structure instance of + * bmi160_acc_sig_mot_int_cfg. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t config_sig_dur_threshold( + const struct bmi160_acc_sig_mot_int_cfg* sig_mot_int_cfg, + const struct bmi160_dev* dev); + +/*! + * @brief This API enables the step detector interrupt. + * + * @param[in] step_detect_int_cfg : Structure instance of + * bmi160_acc_step_detect_int_cfg. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t enable_step_detect_int( + const struct bmi160_acc_step_detect_int_cfg* step_detect_int_cfg, + const struct bmi160_dev* dev); + +/*! + * @brief This API configure the step detector parameter. + * + * @param[in] step_detect_int_cfg : Structure instance of + * bmi160_acc_step_detect_int_cfg. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t config_step_detect( + const struct bmi160_acc_step_detect_int_cfg* step_detect_int_cfg, + const struct bmi160_dev* dev); + +/*! + * @brief This API enables the single/double tap interrupt. + * + * @param[in] int_config : Structure instance of bmi160_int_settg. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t enable_tap_int( + const struct bmi160_int_settg* int_config, + const struct bmi160_acc_tap_int_cfg* tap_int_cfg, + const struct bmi160_dev* dev); + +/*! + * @brief This API configure the interrupt PIN setting for + * tap interrupt. + * + * @param[in] int_config : Structure instance of bmi160_int_settg. + * @param[in] tap_int_cfg : Structure instance of bmi160_acc_tap_int_cfg. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t config_tap_int_settg( + const struct bmi160_int_settg* int_config, + const struct bmi160_acc_tap_int_cfg* tap_int_cfg, + const struct bmi160_dev* dev); + +/*! + * @brief This API configure the source of data(filter & pre-filter) + * for tap interrupt. + * + * @param[in] tap_int_cfg : Structure instance of bmi160_acc_tap_int_cfg. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t config_tap_data_src( + const struct bmi160_acc_tap_int_cfg* tap_int_cfg, + const struct bmi160_dev* dev); + +/*! + * @brief This API configure the parameters of tap interrupt. + * Threshold, quite, shock, and duration. + * + * @param[in] int_config : Structure instance of bmi160_int_settg. + * @param[in] tap_int_cfg : Structure instance of bmi160_acc_tap_int_cfg. + * @param[in] dev : structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t config_tap_param( + const struct bmi160_int_settg* int_config, + const struct bmi160_acc_tap_int_cfg* tap_int_cfg, + const struct bmi160_dev* dev); + +/*! + * @brief This API enable the external mode configuration. + * + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t config_sec_if(const struct bmi160_dev* dev); + +/*! + * @brief This API configure the ODR of the auxiliary sensor. + * + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t config_aux_odr(const struct bmi160_dev* dev); + +/*! + * @brief This API maps the actual burst read length set by user. + * + * @param[in] len : Pointer to store the read length. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t map_read_len(uint16_t* len, const struct bmi160_dev* dev); + +/*! + * @brief This API configure the settings of auxiliary sensor. + * + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t config_aux_settg(const struct bmi160_dev* dev); + +/*! + * @brief This API extract the read data from auxiliary sensor. + * + * @param[in] map_len : burst read value. + * @param[in] reg_addr : Address of register to read. + * @param[in] aux_data : Pointer to store the read data. + * @param[in] len : length to read the data. + * @param[in] dev : Structure instance of bmi160_dev. + * @note : Refer user guide for detailed info. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t extract_aux_read( + uint16_t map_len, + uint8_t reg_addr, + uint8_t* aux_data, + uint16_t len, + const struct bmi160_dev* dev); + +/*! + * @brief This API enables the orient interrupt. + * + * @param[in] orient_int_cfg : Structure instance of bmi160_acc_orient_int_cfg. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t enable_orient_int( + const struct bmi160_acc_orient_int_cfg* orient_int_cfg, + const struct bmi160_dev* dev); + +/*! + * @brief This API configure the necessary setting of orientation interrupt. + * + * @param[in] orient_int_cfg : Structure instance of bmi160_acc_orient_int_cfg. + * @param[in] dev : structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t config_orient_int_settg( + const struct bmi160_acc_orient_int_cfg* orient_int_cfg, + const struct bmi160_dev* dev); + +/*! + * @brief This API enables the flat interrupt. + * + * @param[in] flat_int : Structure instance of bmi160_acc_flat_detect_int_cfg. + * @param[in] dev : structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t enable_flat_int( + const struct bmi160_acc_flat_detect_int_cfg* flat_int, + const struct bmi160_dev* dev); + +/*! + * @brief This API configure the necessary setting of flat interrupt. + * + * @param[in] flat_int : Structure instance of bmi160_acc_flat_detect_int_cfg. + * @param[in] dev : structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t config_flat_int_settg( + const struct bmi160_acc_flat_detect_int_cfg* flat_int, + const struct bmi160_dev* dev); + +/*! + * @brief This API enables the Low-g interrupt. + * + * @param[in] low_g_int : Structure instance of bmi160_acc_low_g_int_cfg. + * @param[in] dev : structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t enable_low_g_int( + const struct bmi160_acc_low_g_int_cfg* low_g_int, + const struct bmi160_dev* dev); + +/*! + * @brief This API configure the source of data(filter & pre-filter) for low-g interrupt. + * + * @param[in] low_g_int : Structure instance of bmi160_acc_low_g_int_cfg. + * @param[in] dev : structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t config_low_g_data_src( + const struct bmi160_acc_low_g_int_cfg* low_g_int, + const struct bmi160_dev* dev); + +/*! + * @brief This API configure the necessary setting of low-g interrupt. + * + * @param[in] low_g_int : Structure instance of bmi160_acc_low_g_int_cfg. + * @param[in] dev : structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t config_low_g_int_settg( + const struct bmi160_acc_low_g_int_cfg* low_g_int, + const struct bmi160_dev* dev); + +/*! + * @brief This API enables the high-g interrupt. + * + * @param[in] high_g_int_cfg : Structure instance of bmi160_acc_high_g_int_cfg. + * @param[in] dev : structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t enable_high_g_int( + const struct bmi160_acc_high_g_int_cfg* high_g_int_cfg, + const struct bmi160_dev* dev); + +/*! + * @brief This API configure the source of data(filter & pre-filter) + * for high-g interrupt. + * + * @param[in] high_g_int_cfg : Structure instance of bmi160_acc_high_g_int_cfg. + * @param[in] dev : structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t config_high_g_data_src( + const struct bmi160_acc_high_g_int_cfg* high_g_int_cfg, + const struct bmi160_dev* dev); + +/*! + * @brief This API configure the necessary setting of high-g interrupt. + * + * @param[in] high_g_int_cfg : Structure instance of bmi160_acc_high_g_int_cfg. + * @param[in] dev : structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t config_high_g_int_settg( + const struct bmi160_acc_high_g_int_cfg* high_g_int_cfg, + const struct bmi160_dev* dev); + +/*! + * @brief This API configure the behavioural setting of interrupt pin. + * + * @param[in] int_config : Structure instance of bmi160_int_settg. + * @param[in] dev : structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t + config_int_out_ctrl(const struct bmi160_int_settg* int_config, const struct bmi160_dev* dev); + +/*! + * @brief This API configure the mode(input enable, latch or non-latch) of interrupt pin. + * + * @param[in] int_config : Structure instance of bmi160_int_settg. + * @param[in] dev : structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t + config_int_latch(const struct bmi160_int_settg* int_config, const struct bmi160_dev* dev); + +/*! + * @brief This API performs the self test for accelerometer of BMI160 + * + * @param[in] dev : structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t perform_accel_self_test(struct bmi160_dev* dev); + +/*! + * @brief This API enables to perform the accel self test by setting proper + * configurations to facilitate accel self test + * + * @param[in] dev : structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t enable_accel_self_test(struct bmi160_dev* dev); + +/*! + * @brief This API performs accel self test with positive excitation + * + * @param[in] accel_pos : Structure pointer to store accel data + * for positive excitation + * @param[in] dev : structure instance of bmi160_dev + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t accel_self_test_positive_excitation( + struct bmi160_sensor_data* accel_pos, + const struct bmi160_dev* dev); + +/*! + * @brief This API performs accel self test with negative excitation + * + * @param[in] accel_neg : Structure pointer to store accel data + * for negative excitation + * @param[in] dev : structure instance of bmi160_dev + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t accel_self_test_negative_excitation( + struct bmi160_sensor_data* accel_neg, + const struct bmi160_dev* dev); + +/*! + * @brief This API validates the accel self test results + * + * @param[in] accel_pos : Structure pointer to store accel data + * for positive excitation + * @param[in] accel_neg : Structure pointer to store accel data + * for negative excitation + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error / +ve value -> Self test fail + */ +static int8_t validate_accel_self_test( + const struct bmi160_sensor_data* accel_pos, + const struct bmi160_sensor_data* accel_neg); + +/*! + * @brief This API performs the self test for gyroscope of BMI160 + * + * @param[in] dev : structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t perform_gyro_self_test(const struct bmi160_dev* dev); + +/*! + * @brief This API enables the self test bit to trigger self test for gyro + * + * @param[in] dev : structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t enable_gyro_self_test(const struct bmi160_dev* dev); + +/*! + * @brief This API validates the self test results of gyro + * + * @param[in] dev : structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t validate_gyro_self_test(const struct bmi160_dev* dev); + +/*! + * @brief This API sets FIFO full interrupt of the sensor.This interrupt + * occurs when the FIFO is full and the next full data sample would cause + * a FIFO overflow, which may delete the old samples. + * + * @param[in] int_config : Structure instance of bmi160_int_settg. + * @param[in] dev : structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t + set_fifo_full_int(const struct bmi160_int_settg* int_config, const struct bmi160_dev* dev); + +/*! + * @brief This enable the FIFO full interrupt engine. + * + * @param[in] int_config : Structure instance of bmi160_int_settg. + * @param[in] dev : structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t + enable_fifo_full_int(const struct bmi160_int_settg* int_config, const struct bmi160_dev* dev); + +/*! + * @brief This API sets FIFO watermark interrupt of the sensor.The FIFO + * watermark interrupt is fired, when the FIFO fill level is above a fifo + * watermark. + * + * @param[in] int_config : Structure instance of bmi160_int_settg. + * @param[in] dev : structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t + set_fifo_watermark_int(const struct bmi160_int_settg* int_config, const struct bmi160_dev* dev); + +/*! + * @brief This enable the FIFO watermark interrupt engine. + * + * @param[in] int_config : Structure instance of bmi160_int_settg. + * @param[in] dev : structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t + enable_fifo_wtm_int(const struct bmi160_int_settg* int_config, const struct bmi160_dev* dev); + +/*! + * @brief This API is used to reset the FIFO related configurations + * in the fifo_frame structure. + * + * @param[in] dev : structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static void reset_fifo_data_structure(const struct bmi160_dev* dev); + +/*! + * @brief This API is used to read number of bytes filled + * currently in FIFO buffer. + * + * @param[in] bytes_to_read : Number of bytes available in FIFO at the + * instant which is obtained from FIFO counter. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error. + * @retval Any non zero value -> Fail + * + */ +static int8_t get_fifo_byte_counter(uint16_t* bytes_to_read, struct bmi160_dev const* dev); + +/*! + * @brief This API is used to compute the number of bytes of accel FIFO data + * which is to be parsed in header-less mode + * + * @param[out] data_index : The start index for parsing data + * @param[out] data_read_length : Number of bytes to be parsed + * @param[in] acc_frame_count : Number of accelerometer frames to be read + * @param[in] dev : Structure instance of bmi160_dev. + * + */ +static void get_accel_len_to_parse( + uint16_t* data_index, + uint16_t* data_read_length, + const uint8_t* acc_frame_count, + const struct bmi160_dev* dev); + +/*! + * @brief This API is used to parse the accelerometer data from the + * FIFO data in both header mode and header-less mode. + * It updates the idx value which is used to store the index of + * the current data byte which is parsed. + * + * @param[in,out] acc : structure instance of sensor data + * @param[in,out] idx : Index value of number of bytes parsed + * @param[in,out] acc_idx : Index value of accelerometer data + * (x,y,z axes) frames parsed + * @param[in] frame_info : It consists of either fifo_data_enable + * parameter in header-less mode or + * frame header data in header mode + * @param[in] dev : structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static void unpack_accel_frame( + struct bmi160_sensor_data* acc, + uint16_t* idx, + uint8_t* acc_idx, + uint8_t frame_info, + const struct bmi160_dev* dev); + +/*! + * @brief This API is used to parse the accelerometer data from the + * FIFO data and store it in the instance of the structure bmi160_sensor_data. + * + * @param[in,out] accel_data : structure instance of sensor data + * @param[in,out] data_start_index : Index value of number of bytes parsed + * @param[in] dev : structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static void unpack_accel_data( + struct bmi160_sensor_data* accel_data, + uint16_t data_start_index, + const struct bmi160_dev* dev); + +/*! + * @brief This API is used to parse the accelerometer data from the + * FIFO data in header mode. + * + * @param[in,out] accel_data : Structure instance of sensor data + * @param[in,out] accel_length : Number of accelerometer frames + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static void extract_accel_header_mode( + struct bmi160_sensor_data* accel_data, + uint8_t* accel_length, + const struct bmi160_dev* dev); + +/*! + * @brief This API computes the number of bytes of gyro FIFO data + * which is to be parsed in header-less mode + * + * @param[out] data_index : The start index for parsing data + * @param[out] data_read_length : No of bytes to be parsed from FIFO buffer + * @param[in] gyro_frame_count : Number of Gyro data frames to be read + * @param[in] dev : Structure instance of bmi160_dev. + */ +static void get_gyro_len_to_parse( + uint16_t* data_index, + uint16_t* data_read_length, + const uint8_t* gyro_frame_count, + const struct bmi160_dev* dev); + +/*! + * @brief This API is used to parse the gyroscope's data from the + * FIFO data in both header mode and header-less mode. + * It updates the idx value which is used to store the index of + * the current data byte which is parsed. + * + * @param[in,out] gyro : structure instance of sensor data + * @param[in,out] idx : Index value of number of bytes parsed + * @param[in,out] gyro_idx : Index value of gyro data + * (x,y,z axes) frames parsed + * @param[in] frame_info : It consists of either fifo_data_enable + * parameter in header-less mode or + * frame header data in header mode + * @param[in] dev : structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static void unpack_gyro_frame( + struct bmi160_sensor_data* gyro, + uint16_t* idx, + uint8_t* gyro_idx, + uint8_t frame_info, + const struct bmi160_dev* dev); + +/*! + * @brief This API is used to parse the gyro data from the + * FIFO data and store it in the instance of the structure bmi160_sensor_data. + * + * @param[in,out] gyro_data : structure instance of sensor data + * @param[in,out] data_start_index : Index value of number of bytes parsed + * @param[in] dev : structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static void unpack_gyro_data( + struct bmi160_sensor_data* gyro_data, + uint16_t data_start_index, + const struct bmi160_dev* dev); + +/*! + * @brief This API is used to parse the gyro data from the + * FIFO data in header mode. + * + * @param[in,out] gyro_data : Structure instance of sensor data + * @param[in,out] gyro_length : Number of gyro frames + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static void extract_gyro_header_mode( + struct bmi160_sensor_data* gyro_data, + uint8_t* gyro_length, + const struct bmi160_dev* dev); + +/*! + * @brief This API computes the number of bytes of aux FIFO data + * which is to be parsed in header-less mode + * + * @param[out] data_index : The start index for parsing data + * @param[out] data_read_length : No of bytes to be parsed from FIFO buffer + * @param[in] aux_frame_count : Number of Aux data frames to be read + * @param[in] dev : Structure instance of bmi160_dev. + */ +static void get_aux_len_to_parse( + uint16_t* data_index, + uint16_t* data_read_length, + const uint8_t* aux_frame_count, + const struct bmi160_dev* dev); + +/*! + * @brief This API is used to parse the aux's data from the + * FIFO data in both header mode and header-less mode. + * It updates the idx value which is used to store the index of + * the current data byte which is parsed + * + * @param[in,out] aux_data : structure instance of sensor data + * @param[in,out] idx : Index value of number of bytes parsed + * @param[in,out] aux_index : Index value of gyro data + * (x,y,z axes) frames parsed + * @param[in] frame_info : It consists of either fifo_data_enable + * parameter in header-less mode or + * frame header data in header mode + * @param[in] dev : structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static void unpack_aux_frame( + struct bmi160_aux_data* aux_data, + uint16_t* idx, + uint8_t* aux_index, + uint8_t frame_info, + const struct bmi160_dev* dev); + +/*! + * @brief This API is used to parse the aux data from the + * FIFO data and store it in the instance of the structure bmi160_aux_data. + * + * @param[in,out] aux_data : structure instance of sensor data + * @param[in,out] data_start_index : Index value of number of bytes parsed + * @param[in] dev : structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static void unpack_aux_data( + struct bmi160_aux_data* aux_data, + uint16_t data_start_index, + const struct bmi160_dev* dev); + +/*! + * @brief This API is used to parse the aux data from the + * FIFO data in header mode. + * + * @param[in,out] aux_data : Structure instance of sensor data + * @param[in,out] aux_length : Number of aux frames + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static void extract_aux_header_mode( + struct bmi160_aux_data* aux_data, + uint8_t* aux_length, + const struct bmi160_dev* dev); + +/*! + * @brief This API checks the presence of non-valid frames in the read fifo data. + * + * @param[in,out] data_index : The index of the current data to + * be parsed from fifo data + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static void check_frame_validity(uint16_t* data_index, const struct bmi160_dev* dev); + +/*! + * @brief This API is used to move the data index ahead of the + * current_frame_length parameter when unnecessary FIFO data appears while + * extracting the user specified data. + * + * @param[in,out] data_index : Index of the FIFO data which + * is to be moved ahead of the + * current_frame_length + * @param[in] current_frame_length : Number of bytes in a particular frame + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static void move_next_frame( + uint16_t* data_index, + uint8_t current_frame_length, + const struct bmi160_dev* dev); + +/*! + * @brief This API is used to parse and store the sensor time from the + * FIFO data in the structure instance dev. + * + * @param[in,out] data_index : Index of the FIFO data which + * has the sensor time. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static void unpack_sensortime_frame(uint16_t* data_index, const struct bmi160_dev* dev); + +/*! + * @brief This API is used to parse and store the skipped_frame_count from + * the FIFO data in the structure instance dev. + * + * @param[in,out] data_index : Index of the FIFO data which + * has the skipped frame count. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static void unpack_skipped_frame(uint16_t* data_index, const struct bmi160_dev* dev); + +/*! + * @brief This API is used to get the FOC status from the sensor + * + * @param[in,out] foc_status : Result of FOC status. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t get_foc_status(uint8_t* foc_status, struct bmi160_dev const* dev); + +/*! + * @brief This API is used to configure the offset enable bits in the sensor + * + * @param[in,out] foc_conf : Structure instance of bmi160_foc_conf which + * has the FOC and offset configurations + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t + configure_offset_enable(const struct bmi160_foc_conf* foc_conf, struct bmi160_dev const* dev); + +/*! + * @brief This API is used to trigger the FOC in the sensor + * + * @param[in,out] offset : Structure instance of bmi160_offsets which + * reads and stores the offset values after FOC + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t trigger_foc(struct bmi160_offsets* offset, struct bmi160_dev const* dev); + +/*! + * @brief This API is used to map/unmap the Dataready(Accel & Gyro), FIFO full + * and FIFO watermark interrupt + * + * @param[in] int_config : Structure instance of bmi160_int_settg which + * stores the interrupt type and interrupt channel + * configurations to map/unmap the interrupt pins + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t + map_hardware_interrupt(const struct bmi160_int_settg* int_config, const struct bmi160_dev* dev); + +/*! + * @brief This API is used to map/unmap the Any/Sig motion, Step det/Low-g, + * Double tap, Single tap, Orientation, Flat, High-G, Nomotion interrupt pins. + * + * @param[in] int_config : Structure instance of bmi160_int_settg which + * stores the interrupt type and interrupt channel + * configurations to map/unmap the interrupt pins + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval zero -> Success / -ve value -> Error + */ +static int8_t + map_feature_interrupt(const struct bmi160_int_settg* int_config, const struct bmi160_dev* dev); + +/*********************** User function definitions ****************************/ + +/*! + * @brief This API reads the data from the given register address + * of sensor. + */ +int8_t + bmi160_get_regs(uint8_t reg_addr, uint8_t* data, uint16_t len, const struct bmi160_dev* dev) { + int8_t rslt = BMI160_OK; + + /* Null-pointer check */ + if((dev == NULL) || (dev->read == NULL)) { + rslt = BMI160_E_NULL_PTR; + } else if(len == 0) { + rslt = BMI160_E_READ_WRITE_LENGTH_INVALID; + } else { + /* Configuring reg_addr for SPI Interface */ + if(dev->intf == BMI160_SPI_INTF) { + reg_addr = (reg_addr | BMI160_SPI_RD_MASK); + } + + rslt = dev->read(dev->id, reg_addr, data, len); + } + + return rslt; +} + +/*! + * @brief This API writes the given data to the register address + * of sensor. + */ +int8_t + bmi160_set_regs(uint8_t reg_addr, uint8_t* data, uint16_t len, const struct bmi160_dev* dev) { + int8_t rslt = BMI160_OK; + uint8_t count = 0; + + /* Null-pointer check */ + if((dev == NULL) || (dev->write == NULL)) { + rslt = BMI160_E_NULL_PTR; + } else if(len == 0) { + rslt = BMI160_E_READ_WRITE_LENGTH_INVALID; + } else { + /* Configuring reg_addr for SPI Interface */ + if(dev->intf == BMI160_SPI_INTF) { + reg_addr = (reg_addr & BMI160_SPI_WR_MASK); + } + + if((dev->prev_accel_cfg.power == BMI160_ACCEL_NORMAL_MODE) || + (dev->prev_gyro_cfg.power == BMI160_GYRO_NORMAL_MODE)) { + rslt = dev->write(dev->id, reg_addr, data, len); + + /* Kindly refer bmi160 data sheet section 3.2.4 */ + dev->delay_ms(1); + + } else { + /*Burst write is not allowed in + * suspend & low power mode */ + for(; count < len; count++) { + rslt = dev->write(dev->id, reg_addr, &data[count], 1); + reg_addr++; + + /* Kindly refer bmi160 data sheet section 3.2.4 */ + dev->delay_ms(1); + } + } + + if(rslt != BMI160_OK) { + rslt = BMI160_E_COM_FAIL; + } + } + + return rslt; +} + +/*! + * @brief This API is the entry point for sensor.It performs + * the selection of I2C/SPI read mechanism according to the + * selected interface and reads the chip-id of bmi160 sensor. + */ +int8_t bmi160_init(struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data; + uint8_t try = 3; + + /* Null-pointer check */ + rslt = null_ptr_check(dev); + + /* Dummy read of 0x7F register to enable SPI Interface + * if SPI is used */ + if((rslt == BMI160_OK) && (dev->intf == BMI160_SPI_INTF)) { + rslt = bmi160_get_regs(BMI160_SPI_COMM_TEST_ADDR, &data, 1, dev); + } + + if(rslt == BMI160_OK) { + /* Assign chip id as zero */ + dev->chip_id = 0; + + while((try--) && (dev->chip_id != BMI160_CHIP_ID)) { + /* Read chip_id */ + rslt = bmi160_get_regs(BMI160_CHIP_ID_ADDR, &dev->chip_id, 1, dev); + } + + if((rslt == BMI160_OK) && (dev->chip_id == BMI160_CHIP_ID)) { + dev->any_sig_sel = BMI160_BOTH_ANY_SIG_MOTION_DISABLED; + + /* Soft reset */ + rslt = bmi160_soft_reset(dev); + } else { + rslt = BMI160_E_DEV_NOT_FOUND; + } + } + + return rslt; +} + +/*! + * @brief This API resets and restarts the device. + * All register values are overwritten with default parameters. + */ +int8_t bmi160_soft_reset(struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data = BMI160_SOFT_RESET_CMD; + + /* Null-pointer check */ + if((dev == NULL) || (dev->delay_ms == NULL)) { + rslt = BMI160_E_NULL_PTR; + } else { + /* Reset the device */ + rslt = bmi160_set_regs(BMI160_COMMAND_REG_ADDR, &data, 1, dev); + dev->delay_ms(BMI160_SOFT_RESET_DELAY_MS); + if((rslt == BMI160_OK) && (dev->intf == BMI160_SPI_INTF)) { + /* Dummy read of 0x7F register to enable SPI Interface + * if SPI is used */ + rslt = bmi160_get_regs(BMI160_SPI_COMM_TEST_ADDR, &data, 1, dev); + } + + if(rslt == BMI160_OK) { + /* Update the default parameters */ + default_param_settg(dev); + } + } + + return rslt; +} + +/*! + * @brief This API configures the power mode, range and bandwidth + * of sensor. + */ +int8_t bmi160_set_sens_conf(struct bmi160_dev* dev) { + int8_t rslt = BMI160_OK; + + /* Null-pointer check */ + if((dev == NULL) || (dev->delay_ms == NULL)) { + rslt = BMI160_E_NULL_PTR; + } else { + rslt = set_accel_conf(dev); + if(rslt == BMI160_OK) { + rslt = set_gyro_conf(dev); + if(rslt == BMI160_OK) { + /* write power mode for accel and gyro */ + rslt = bmi160_set_power_mode(dev); + if(rslt == BMI160_OK) { + rslt = check_invalid_settg(dev); + } + } + } + } + + return rslt; +} + +/*! + * @brief This API gets accel and gyro configurations. + */ +int8_t bmi160_get_sens_conf(struct bmi160_dev* dev) { + int8_t rslt = BMI160_OK; + + /* Null-pointer check */ + if((dev == NULL) || (dev->delay_ms == NULL)) { + rslt = BMI160_E_NULL_PTR; + } else { + rslt = get_accel_conf(dev); + if(rslt == BMI160_OK) { + rslt = get_gyro_conf(dev); + } + } + + return rslt; +} + +/*! + * @brief This API sets the power mode of the sensor. + */ +int8_t bmi160_set_power_mode(struct bmi160_dev* dev) { + int8_t rslt = 0; + + /* Null-pointer check */ + if((dev == NULL) || (dev->delay_ms == NULL)) { + rslt = BMI160_E_NULL_PTR; + } else { + rslt = set_accel_pwr(dev); + if(rslt == BMI160_OK) { + rslt = set_gyro_pwr(dev); + } + } + + return rslt; +} + +/*! + * @brief This API gets the power mode of the sensor. + */ +int8_t bmi160_get_power_mode(struct bmi160_dev* dev) { + int8_t rslt = 0; + uint8_t power_mode = 0; + + /* Null-pointer check */ + if((dev == NULL) || (dev->delay_ms == NULL)) { + rslt = BMI160_E_NULL_PTR; + } else { + rslt = bmi160_get_regs(BMI160_PMU_STATUS_ADDR, &power_mode, 1, dev); + if(rslt == BMI160_OK) { + /* Power mode of the accel, gyro sensor is obtained */ + dev->gyro_cfg.power = BMI160_GET_BITS(power_mode, BMI160_GYRO_POWER_MODE); + dev->accel_cfg.power = BMI160_GET_BITS(power_mode, BMI160_ACCEL_POWER_MODE); + } + } + + return rslt; +} + +/*! + * @brief This API reads sensor data, stores it in + * the bmi160_sensor_data structure pointer passed by the user. + */ +int8_t bmi160_get_sensor_data( + uint8_t select_sensor, + struct bmi160_sensor_data* accel, + struct bmi160_sensor_data* gyro, + const struct bmi160_dev* dev) { + int8_t rslt = BMI160_OK; + uint8_t time_sel; + uint8_t sen_sel; + uint8_t len = 0; + + /*Extract the sensor and time select information*/ + sen_sel = select_sensor & BMI160_SEN_SEL_MASK; + time_sel = ((sen_sel & BMI160_TIME_SEL) >> 2); + sen_sel = sen_sel & (BMI160_ACCEL_SEL | BMI160_GYRO_SEL); + if(time_sel == 1) { + len = 3; + } + + /* Null-pointer check */ + if(dev != NULL) { + switch(sen_sel) { + case BMI160_ACCEL_ONLY: + + /* Null-pointer check */ + if(accel == NULL) { + rslt = BMI160_E_NULL_PTR; + } else { + rslt = get_accel_data(len, accel, dev); + } + + break; + case BMI160_GYRO_ONLY: + + /* Null-pointer check */ + if(gyro == NULL) { + rslt = BMI160_E_NULL_PTR; + } else { + rslt = get_gyro_data(len, gyro, dev); + } + + break; + case BMI160_BOTH_ACCEL_AND_GYRO: + + /* Null-pointer check */ + if((gyro == NULL) || (accel == NULL)) { + rslt = BMI160_E_NULL_PTR; + } else { + rslt = get_accel_gyro_data(len, accel, gyro, dev); + } + + break; + default: + rslt = BMI160_E_INVALID_INPUT; + break; + } + } else { + rslt = BMI160_E_NULL_PTR; + } + + return rslt; +} + +/*! + * @brief This API configures the necessary interrupt based on + * the user settings in the bmi160_int_settg structure instance. + */ +int8_t bmi160_set_int_config(struct bmi160_int_settg* int_config, struct bmi160_dev* dev) { + int8_t rslt = BMI160_OK; + + switch(int_config->int_type) { + case BMI160_ACC_ANY_MOTION_INT: + + /*Any-motion interrupt*/ + rslt = set_accel_any_motion_int(int_config, dev); + break; + case BMI160_ACC_SIG_MOTION_INT: + + /* Significant motion interrupt */ + rslt = set_accel_sig_motion_int(int_config, dev); + break; + case BMI160_ACC_SLOW_NO_MOTION_INT: + + /* Slow or no motion interrupt */ + rslt = set_accel_no_motion_int(int_config, dev); + break; + case BMI160_ACC_DOUBLE_TAP_INT: + case BMI160_ACC_SINGLE_TAP_INT: + + /* Double tap and single tap Interrupt */ + rslt = set_accel_tap_int(int_config, dev); + break; + case BMI160_STEP_DETECT_INT: + + /* Step detector interrupt */ + rslt = set_accel_step_detect_int(int_config, dev); + break; + case BMI160_ACC_ORIENT_INT: + + /* Orientation interrupt */ + rslt = set_accel_orientation_int(int_config, dev); + break; + case BMI160_ACC_FLAT_INT: + + /* Flat detection interrupt */ + rslt = set_accel_flat_detect_int(int_config, dev); + break; + case BMI160_ACC_LOW_G_INT: + + /* Low-g interrupt */ + rslt = set_accel_low_g_int(int_config, dev); + break; + case BMI160_ACC_HIGH_G_INT: + + /* High-g interrupt */ + rslt = set_accel_high_g_int(int_config, dev); + break; + case BMI160_ACC_GYRO_DATA_RDY_INT: + + /* Data ready interrupt */ + rslt = set_accel_gyro_data_ready_int(int_config, dev); + break; + case BMI160_ACC_GYRO_FIFO_FULL_INT: + + /* Fifo full interrupt */ + rslt = set_fifo_full_int(int_config, dev); + break; + case BMI160_ACC_GYRO_FIFO_WATERMARK_INT: + + /* Fifo water-mark interrupt */ + rslt = set_fifo_watermark_int(int_config, dev); + break; + case BMI160_FIFO_TAG_INT_PIN: + + /* Fifo tagging feature support */ + /* Configure Interrupt pins */ + rslt = set_intr_pin_config(int_config, dev); + break; + default: + break; + } + + return rslt; +} + +/*! + * @brief This API enables or disable the step counter feature. + * 1 - enable step counter (0 - disable) + */ +int8_t bmi160_set_step_counter(uint8_t step_cnt_enable, const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data = 0; + + /* Null-pointer check */ + rslt = null_ptr_check(dev); + if(rslt != BMI160_OK) { + rslt = BMI160_E_NULL_PTR; + } else { + rslt = bmi160_get_regs(BMI160_INT_STEP_CONFIG_1_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + if(step_cnt_enable == BMI160_ENABLE) { + data |= (uint8_t)(step_cnt_enable << 3); + } else { + data &= ~BMI160_STEP_COUNT_EN_BIT_MASK; + } + + rslt = bmi160_set_regs(BMI160_INT_STEP_CONFIG_1_ADDR, &data, 1, dev); + } + } + + return rslt; +} + +/*! + * @brief This API reads the step counter value. + */ +int8_t bmi160_read_step_counter(uint16_t* step_val, const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data[2] = {0, 0}; + uint16_t msb = 0; + uint8_t lsb = 0; + + /* Null-pointer check */ + rslt = null_ptr_check(dev); + if(rslt != BMI160_OK) { + rslt = BMI160_E_NULL_PTR; + } else { + rslt = bmi160_get_regs(BMI160_INT_STEP_CNT_0_ADDR, data, 2, dev); + if(rslt == BMI160_OK) { + lsb = data[0]; + msb = data[1] << 8; + *step_val = msb | lsb; + } + } + + return rslt; +} + +/*! + * @brief This API reads the mention no of byte of data from the given + * register address of auxiliary sensor. + */ +int8_t bmi160_aux_read( + uint8_t reg_addr, + uint8_t* aux_data, + uint16_t len, + const struct bmi160_dev* dev) { + int8_t rslt = BMI160_OK; + uint16_t map_len = 0; + + /* Null-pointer check */ + if((dev == NULL) || (dev->read == NULL)) { + rslt = BMI160_E_NULL_PTR; + } else { + if(dev->aux_cfg.aux_sensor_enable == BMI160_ENABLE) { + rslt = map_read_len(&map_len, dev); + if(rslt == BMI160_OK) { + rslt = extract_aux_read(map_len, reg_addr, aux_data, len, dev); + } + } else { + rslt = BMI160_E_INVALID_INPUT; + } + } + + return rslt; +} + +/*! + * @brief This API writes the mention no of byte of data to the given + * register address of auxiliary sensor. + */ +int8_t bmi160_aux_write( + uint8_t reg_addr, + uint8_t* aux_data, + uint16_t len, + const struct bmi160_dev* dev) { + int8_t rslt = BMI160_OK; + uint8_t count = 0; + + /* Null-pointer check */ + if((dev == NULL) || (dev->write == NULL)) { + rslt = BMI160_E_NULL_PTR; + } else { + for(; count < len; count++) { + /* set data to write */ + rslt = bmi160_set_regs(BMI160_AUX_IF_4_ADDR, aux_data, 1, dev); + dev->delay_ms(BMI160_AUX_COM_DELAY); + if(rslt == BMI160_OK) { + /* set address to write */ + rslt = bmi160_set_regs(BMI160_AUX_IF_3_ADDR, ®_addr, 1, dev); + dev->delay_ms(BMI160_AUX_COM_DELAY); + if(rslt == BMI160_OK && (count < len - 1)) { + aux_data++; + reg_addr++; + } + } + } + } + + return rslt; +} + +/*! + * @brief This API initialize the auxiliary sensor + * in order to access it. + */ +int8_t bmi160_aux_init(const struct bmi160_dev* dev) { + int8_t rslt; + + /* Null-pointer check */ + rslt = null_ptr_check(dev); + if(rslt != BMI160_OK) { + rslt = BMI160_E_NULL_PTR; + } else { + if(dev->aux_cfg.aux_sensor_enable == BMI160_ENABLE) { + /* Configures the auxiliary sensor interface settings */ + rslt = config_aux_settg(dev); + } else { + rslt = BMI160_E_INVALID_INPUT; + } + } + + return rslt; +} + +/*! + * @brief This API is used to setup the auxiliary sensor of bmi160 in auto mode + * Thus enabling the auto update of 8 bytes of data from auxiliary sensor + * to BMI160 register address 0x04 to 0x0B + */ +int8_t bmi160_set_aux_auto_mode(uint8_t* data_addr, struct bmi160_dev* dev) { + int8_t rslt; + + /* Null-pointer check */ + rslt = null_ptr_check(dev); + if(rslt != BMI160_OK) { + rslt = BMI160_E_NULL_PTR; + } else { + if(dev->aux_cfg.aux_sensor_enable == BMI160_ENABLE) { + /* Write the aux. address to read in 0x4D of BMI160*/ + rslt = bmi160_set_regs(BMI160_AUX_IF_2_ADDR, data_addr, 1, dev); + dev->delay_ms(BMI160_AUX_COM_DELAY); + if(rslt == BMI160_OK) { + /* Configure the polling ODR for + * auxiliary sensor */ + rslt = config_aux_odr(dev); + if(rslt == BMI160_OK) { + /* Disable the aux. manual mode, i.e aux. + * sensor is in auto-mode (data-mode) */ + dev->aux_cfg.manual_enable = BMI160_DISABLE; + rslt = bmi160_config_aux_mode(dev); + + /* Auxiliary sensor data is obtained + * in auto mode from this point */ + } + } + } else { + rslt = BMI160_E_INVALID_INPUT; + } + } + + return rslt; +} + +/*! + * @brief This API configures the 0x4C register and settings like + * Auxiliary sensor manual enable/ disable and aux burst read length. + */ +int8_t bmi160_config_aux_mode(const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t aux_if[2] = {(uint8_t)(dev->aux_cfg.aux_i2c_addr * 2), 0}; + + rslt = bmi160_get_regs(BMI160_AUX_IF_1_ADDR, &aux_if[1], 1, dev); + if(rslt == BMI160_OK) { + /* update the Auxiliary interface to manual/auto mode */ + aux_if[1] = BMI160_SET_BITS(aux_if[1], BMI160_MANUAL_MODE_EN, dev->aux_cfg.manual_enable); + + /* update the burst read length defined by user */ + aux_if[1] = + BMI160_SET_BITS_POS_0(aux_if[1], BMI160_AUX_READ_BURST, dev->aux_cfg.aux_rd_burst_len); + + /* Set the secondary interface address and manual mode + * along with burst read length */ + rslt = bmi160_set_regs(BMI160_AUX_IF_0_ADDR, &aux_if[0], 2, dev); + dev->delay_ms(BMI160_AUX_COM_DELAY); + } + + return rslt; +} + +/*! + * @brief This API is used to read the raw uncompensated auxiliary sensor + * data of 8 bytes from BMI160 register address 0x04 to 0x0B + */ +int8_t bmi160_read_aux_data_auto_mode(uint8_t* aux_data, const struct bmi160_dev* dev) { + int8_t rslt; + + /* Null-pointer check */ + rslt = null_ptr_check(dev); + if(rslt != BMI160_OK) { + rslt = BMI160_E_NULL_PTR; + } else { + if((dev->aux_cfg.aux_sensor_enable == BMI160_ENABLE) && + (dev->aux_cfg.manual_enable == BMI160_DISABLE)) { + /* Read the aux. sensor's raw data */ + rslt = bmi160_get_regs(BMI160_AUX_DATA_ADDR, aux_data, 8, dev); + } else { + rslt = BMI160_E_INVALID_INPUT; + } + } + + return rslt; +} + +/*! + * @brief This is used to perform self test of accel/gyro of the BMI160 sensor + */ +int8_t bmi160_perform_self_test(uint8_t select_sensor, struct bmi160_dev* dev) { + int8_t rslt; + int8_t self_test_rslt = 0; + + /* Null-pointer check */ + rslt = null_ptr_check(dev); + if(rslt != BMI160_OK) { + rslt = BMI160_E_NULL_PTR; + } else { + /* Proceed if null check is fine */ + switch(select_sensor) { + case BMI160_ACCEL_ONLY: + rslt = perform_accel_self_test(dev); + break; + case BMI160_GYRO_ONLY: + + /* Set the power mode as normal mode */ + dev->gyro_cfg.power = BMI160_GYRO_NORMAL_MODE; + rslt = bmi160_set_power_mode(dev); + + /* Perform gyro self test */ + if(rslt == BMI160_OK) { + /* Perform gyro self test */ + rslt = perform_gyro_self_test(dev); + } + + break; + default: + rslt = BMI160_E_INVALID_INPUT; + break; + } + + /* Check to ensure bus error does not occur */ + if(rslt >= BMI160_OK) { + /* Store the status of self test result */ + self_test_rslt = rslt; + + /* Perform soft reset */ + rslt = bmi160_soft_reset(dev); + } + + /* Check to ensure bus operations are success */ + if(rslt == BMI160_OK) { + /* Restore self_test_rslt as return value */ + rslt = self_test_rslt; + } + } + + return rslt; +} + +/*! + * @brief This API reads the data from fifo buffer. + */ +int8_t bmi160_get_fifo_data(struct bmi160_dev const* dev) { + int8_t rslt = 0; + uint16_t bytes_to_read = 0; + uint16_t user_fifo_len = 0; + + /* check the bmi160 structure as NULL*/ + if((dev == NULL) || (dev->fifo->data == NULL)) { + rslt = BMI160_E_NULL_PTR; + } else { + reset_fifo_data_structure(dev); + + /* get current FIFO fill-level*/ + rslt = get_fifo_byte_counter(&bytes_to_read, dev); + if(rslt == BMI160_OK) { + user_fifo_len = dev->fifo->length; + if((dev->fifo->length > bytes_to_read)) { + /* Handling the case where user requests + * more data than available in FIFO */ + dev->fifo->length = bytes_to_read; + } + + if((dev->fifo->fifo_time_enable == BMI160_FIFO_TIME_ENABLE) && + (bytes_to_read + BMI160_FIFO_BYTES_OVERREAD <= user_fifo_len)) { + /* Handling case of sensor time availability*/ + dev->fifo->length = dev->fifo->length + BMI160_FIFO_BYTES_OVERREAD; + } + + /* read only the filled bytes in the FIFO Buffer */ + rslt = bmi160_get_regs(BMI160_FIFO_DATA_ADDR, dev->fifo->data, dev->fifo->length, dev); + } + } + + return rslt; +} + +/*! + * @brief This API writes fifo_flush command to command register.This + * action clears all data in the Fifo without changing fifo configuration + * settings + */ +int8_t bmi160_set_fifo_flush(const struct bmi160_dev* dev) { + int8_t rslt = 0; + uint8_t data = BMI160_FIFO_FLUSH_VALUE; + uint8_t reg_addr = BMI160_COMMAND_REG_ADDR; + + /* Check the bmi160_dev structure for NULL address*/ + if(dev == NULL) { + rslt = BMI160_E_NULL_PTR; + } else { + rslt = bmi160_set_regs(reg_addr, &data, BMI160_ONE, dev); + } + + return rslt; +} + +/*! + * @brief This API sets the FIFO configuration in the sensor. + */ +int8_t bmi160_set_fifo_config(uint8_t config, uint8_t enable, struct bmi160_dev const* dev) { + int8_t rslt = 0; + uint8_t data = 0; + uint8_t reg_addr = BMI160_FIFO_CONFIG_1_ADDR; + uint8_t fifo_config = config & BMI160_FIFO_CONFIG_1_MASK; + + /* Check the bmi160_dev structure for NULL address*/ + if(dev == NULL) { + rslt = BMI160_E_NULL_PTR; + } else { + rslt = bmi160_get_regs(reg_addr, &data, BMI160_ONE, dev); + if(rslt == BMI160_OK) { + if(fifo_config > 0) { + if(enable == BMI160_ENABLE) { + data = data | fifo_config; + } else { + data = data & (~fifo_config); + } + } + + /* write fifo frame content configuration*/ + rslt = bmi160_set_regs(reg_addr, &data, BMI160_ONE, dev); + if(rslt == BMI160_OK) { + /* read fifo frame content configuration*/ + rslt = bmi160_get_regs(reg_addr, &data, BMI160_ONE, dev); + if(rslt == BMI160_OK) { + /* extract fifo header enabled status */ + dev->fifo->fifo_header_enable = data & BMI160_FIFO_HEAD_ENABLE; + + /* extract accel/gyr/aux. data enabled status */ + dev->fifo->fifo_data_enable = data & BMI160_FIFO_M_G_A_ENABLE; + + /* extract fifo sensor time enabled status */ + dev->fifo->fifo_time_enable = data & BMI160_FIFO_TIME_ENABLE; + } + } + } + } + + return rslt; +} + +/*! @brief This API is used to configure the down sampling ratios of + * the accel and gyro data for FIFO.Also, it configures filtered or + * pre-filtered data for accel and gyro. + * + */ +int8_t bmi160_set_fifo_down(uint8_t fifo_down, const struct bmi160_dev* dev) { + int8_t rslt = 0; + uint8_t data = 0; + uint8_t reg_addr = BMI160_FIFO_DOWN_ADDR; + + /* Check the bmi160_dev structure for NULL address*/ + if(dev == NULL) { + rslt = BMI160_E_NULL_PTR; + } else { + rslt = bmi160_get_regs(reg_addr, &data, BMI160_ONE, dev); + if(rslt == BMI160_OK) { + data = data | fifo_down; + rslt = bmi160_set_regs(reg_addr, &data, BMI160_ONE, dev); + } + } + + return rslt; +} + +/*! + * @brief This API sets the FIFO watermark level in the sensor. + * + */ +int8_t bmi160_set_fifo_wm(uint8_t fifo_wm, const struct bmi160_dev* dev) { + int8_t rslt = 0; + uint8_t data = fifo_wm; + uint8_t reg_addr = BMI160_FIFO_CONFIG_0_ADDR; + + /* Check the bmi160_dev structure for NULL address*/ + if(dev == NULL) { + rslt = BMI160_E_NULL_PTR; + } else { + rslt = bmi160_set_regs(reg_addr, &data, BMI160_ONE, dev); + } + + return rslt; +} + +/*! + * @brief This API parses and extracts the accelerometer frames from + * FIFO data read by the "bmi160_get_fifo_data" API and stores it in + * the "accel_data" structure instance. + */ +int8_t bmi160_extract_accel( + struct bmi160_sensor_data* accel_data, + uint8_t* accel_length, + struct bmi160_dev const* dev) { + int8_t rslt = 0; + uint16_t data_index = 0; + uint16_t data_read_length = 0; + uint8_t accel_index = 0; + uint8_t fifo_data_enable = 0; + + if(dev == NULL || dev->fifo == NULL || dev->fifo->data == NULL) { + rslt = BMI160_E_NULL_PTR; + } else { + /* Parsing the FIFO data in header-less mode */ + if(dev->fifo->fifo_header_enable == 0) { + /* Number of bytes to be parsed from FIFO */ + get_accel_len_to_parse(&data_index, &data_read_length, accel_length, dev); + for(; data_index < data_read_length;) { + /*Check for the availability of next two bytes of FIFO data */ + check_frame_validity(&data_index, dev); + fifo_data_enable = dev->fifo->fifo_data_enable; + unpack_accel_frame(accel_data, &data_index, &accel_index, fifo_data_enable, dev); + } + + /* update number of accel data read*/ + *accel_length = accel_index; + + /*update the accel byte index*/ + dev->fifo->accel_byte_start_idx = data_index; + } else { + /* Parsing the FIFO data in header mode */ + extract_accel_header_mode(accel_data, accel_length, dev); + } + } + + return rslt; +} + +/*! + * @brief This API parses and extracts the gyro frames from + * FIFO data read by the "bmi160_get_fifo_data" API and stores it in + * the "gyro_data" structure instance. + */ +int8_t bmi160_extract_gyro( + struct bmi160_sensor_data* gyro_data, + uint8_t* gyro_length, + struct bmi160_dev const* dev) { + int8_t rslt = 0; + uint16_t data_index = 0; + uint16_t data_read_length = 0; + uint8_t gyro_index = 0; + uint8_t fifo_data_enable = 0; + + if(dev == NULL || dev->fifo->data == NULL) { + rslt = BMI160_E_NULL_PTR; + } else { + /* Parsing the FIFO data in header-less mode */ + if(dev->fifo->fifo_header_enable == 0) { + /* Number of bytes to be parsed from FIFO */ + get_gyro_len_to_parse(&data_index, &data_read_length, gyro_length, dev); + for(; data_index < data_read_length;) { + /*Check for the availability of next two bytes of FIFO data */ + check_frame_validity(&data_index, dev); + fifo_data_enable = dev->fifo->fifo_data_enable; + unpack_gyro_frame(gyro_data, &data_index, &gyro_index, fifo_data_enable, dev); + } + + /* update number of gyro data read */ + *gyro_length = gyro_index; + + /* update the gyro byte index */ + dev->fifo->gyro_byte_start_idx = data_index; + } else { + /* Parsing the FIFO data in header mode */ + extract_gyro_header_mode(gyro_data, gyro_length, dev); + } + } + + return rslt; +} + +/*! + * @brief This API parses and extracts the aux frames from + * FIFO data read by the "bmi160_get_fifo_data" API and stores it in + * the "aux_data" structure instance. + */ +int8_t bmi160_extract_aux( + struct bmi160_aux_data* aux_data, + uint8_t* aux_len, + struct bmi160_dev const* dev) { + int8_t rslt = 0; + uint16_t data_index = 0; + uint16_t data_read_length = 0; + uint8_t aux_index = 0; + uint8_t fifo_data_enable = 0; + + if((dev == NULL) || (dev->fifo->data == NULL) || (aux_data == NULL)) { + rslt = BMI160_E_NULL_PTR; + } else { + /* Parsing the FIFO data in header-less mode */ + if(dev->fifo->fifo_header_enable == 0) { + /* Number of bytes to be parsed from FIFO */ + get_aux_len_to_parse(&data_index, &data_read_length, aux_len, dev); + for(; data_index < data_read_length;) { + /* Check for the availability of next two + * bytes of FIFO data */ + check_frame_validity(&data_index, dev); + fifo_data_enable = dev->fifo->fifo_data_enable; + unpack_aux_frame(aux_data, &data_index, &aux_index, fifo_data_enable, dev); + } + + /* update number of aux data read */ + *aux_len = aux_index; + + /* update the aux byte index */ + dev->fifo->aux_byte_start_idx = data_index; + } else { + /* Parsing the FIFO data in header mode */ + extract_aux_header_mode(aux_data, aux_len, dev); + } + } + + return rslt; +} + +/*! + * @brief This API starts the FOC of accel and gyro + * + * @note FOC should not be used in low-power mode of sensor + * + * @note Accel FOC targets values of +1g , 0g , -1g + * Gyro FOC always targets value of 0 dps + */ +int8_t bmi160_start_foc( + const struct bmi160_foc_conf* foc_conf, + struct bmi160_offsets* offset, + struct bmi160_dev const* dev) { + int8_t rslt; + uint8_t data; + + /* Null-pointer check */ + rslt = null_ptr_check(dev); + if(rslt != BMI160_OK) { + rslt = BMI160_E_NULL_PTR; + } else { + /* Set the offset enable bits */ + rslt = configure_offset_enable(foc_conf, dev); + if(rslt == BMI160_OK) { + /* Read the FOC config from the sensor */ + rslt = bmi160_get_regs(BMI160_FOC_CONF_ADDR, &data, 1, dev); + + /* Set the FOC config for gyro */ + data = BMI160_SET_BITS(data, BMI160_GYRO_FOC_EN, foc_conf->foc_gyr_en); + + /* Set the FOC config for accel xyz axes */ + data = BMI160_SET_BITS(data, BMI160_ACCEL_FOC_X_CONF, foc_conf->foc_acc_x); + data = BMI160_SET_BITS(data, BMI160_ACCEL_FOC_Y_CONF, foc_conf->foc_acc_y); + data = BMI160_SET_BITS_POS_0(data, BMI160_ACCEL_FOC_Z_CONF, foc_conf->foc_acc_z); + if(rslt == BMI160_OK) { + /* Set the FOC config in the sensor */ + rslt = bmi160_set_regs(BMI160_FOC_CONF_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + /* Procedure to trigger + * FOC and check status */ + rslt = trigger_foc(offset, dev); + } + } + } + } + + return rslt; +} + +/*! + * @brief This API reads and stores the offset values of accel and gyro + */ +int8_t bmi160_get_offsets(struct bmi160_offsets* offset, const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data[7]; + uint8_t lsb, msb; + int16_t offset_msb, offset_lsb; + int16_t offset_data; + + /* Null-pointer check */ + rslt = null_ptr_check(dev); + if(rslt != BMI160_OK) { + rslt = BMI160_E_NULL_PTR; + } else { + /* Read the FOC config from the sensor */ + rslt = bmi160_get_regs(BMI160_OFFSET_ADDR, data, 7, dev); + + /* Accel offsets */ + offset->off_acc_x = (int8_t)data[0]; + offset->off_acc_y = (int8_t)data[1]; + offset->off_acc_z = (int8_t)data[2]; + + /* Gyro x-axis offset */ + lsb = data[3]; + msb = BMI160_GET_BITS_POS_0(data[6], BMI160_GYRO_OFFSET_X); + offset_msb = (int16_t)(msb << 14); + offset_lsb = lsb << 6; + offset_data = offset_msb | offset_lsb; + + /* Divide by 64 to get the Right shift by 6 value */ + offset->off_gyro_x = (int16_t)(offset_data / 64); + + /* Gyro y-axis offset */ + lsb = data[4]; + msb = BMI160_GET_BITS(data[6], BMI160_GYRO_OFFSET_Y); + offset_msb = (int16_t)(msb << 14); + offset_lsb = lsb << 6; + offset_data = offset_msb | offset_lsb; + + /* Divide by 64 to get the Right shift by 6 value */ + offset->off_gyro_y = (int16_t)(offset_data / 64); + + /* Gyro z-axis offset */ + lsb = data[5]; + msb = BMI160_GET_BITS(data[6], BMI160_GYRO_OFFSET_Z); + offset_msb = (int16_t)(msb << 14); + offset_lsb = lsb << 6; + offset_data = offset_msb | offset_lsb; + + /* Divide by 64 to get the Right shift by 6 value */ + offset->off_gyro_z = (int16_t)(offset_data / 64); + } + + return rslt; +} + +/*! + * @brief This API writes the offset values of accel and gyro to + * the sensor but these values will be reset on POR or soft reset. + */ +int8_t bmi160_set_offsets( + const struct bmi160_foc_conf* foc_conf, + const struct bmi160_offsets* offset, + struct bmi160_dev const* dev) { + int8_t rslt; + uint8_t data[7]; + uint8_t x_msb, y_msb, z_msb; + + /* Null-pointer check */ + rslt = null_ptr_check(dev); + if(rslt != BMI160_OK) { + rslt = BMI160_E_NULL_PTR; + } else { + /* Update the accel offset */ + data[0] = (uint8_t)offset->off_acc_x; + data[1] = (uint8_t)offset->off_acc_y; + data[2] = (uint8_t)offset->off_acc_z; + + /* Update the LSB of gyro offset */ + data[3] = BMI160_GET_LSB(offset->off_gyro_x); + data[4] = BMI160_GET_LSB(offset->off_gyro_y); + data[5] = BMI160_GET_LSB(offset->off_gyro_z); + + /* Update the MSB of gyro offset */ + x_msb = BMI160_GET_BITS(offset->off_gyro_x, BMI160_GYRO_OFFSET); + y_msb = BMI160_GET_BITS(offset->off_gyro_y, BMI160_GYRO_OFFSET); + z_msb = BMI160_GET_BITS(offset->off_gyro_z, BMI160_GYRO_OFFSET); + data[6] = (uint8_t)(z_msb << 4 | y_msb << 2 | x_msb); + + /* Set the offset enable/disable for gyro and accel */ + data[6] = BMI160_SET_BITS(data[6], BMI160_GYRO_OFFSET_EN, foc_conf->gyro_off_en); + data[6] = BMI160_SET_BITS(data[6], BMI160_ACCEL_OFFSET_EN, foc_conf->acc_off_en); + + /* Set the offset config and values in the sensor */ + rslt = bmi160_set_regs(BMI160_OFFSET_ADDR, data, 7, dev); + } + + return rslt; +} + +/*! + * @brief This API writes the image registers values to NVM which is + * stored even after POR or soft reset + */ +int8_t bmi160_update_nvm(struct bmi160_dev const* dev) { + int8_t rslt; + uint8_t data; + uint8_t cmd = BMI160_NVM_BACKUP_EN; + + /* Read the nvm_prog_en configuration */ + rslt = bmi160_get_regs(BMI160_CONF_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + data = BMI160_SET_BITS(data, BMI160_NVM_UPDATE, 1); + + /* Set the nvm_prog_en bit in the sensor */ + rslt = bmi160_set_regs(BMI160_CONF_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + /* Update NVM */ + rslt = bmi160_set_regs(BMI160_COMMAND_REG_ADDR, &cmd, 1, dev); + if(rslt == BMI160_OK) { + /* Check for NVM ready status */ + rslt = bmi160_get_regs(BMI160_STATUS_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + data = BMI160_GET_BITS(data, BMI160_NVM_STATUS); + if(data != BMI160_ENABLE) { + /* Delay to update NVM */ + dev->delay_ms(25); + } + } + } + } + } + + return rslt; +} + +/*! + * @brief This API gets the interrupt status from the sensor. + */ +int8_t bmi160_get_int_status( + enum bmi160_int_status_sel int_status_sel, + union bmi160_int_status* int_status, + struct bmi160_dev const* dev) { + int8_t rslt = 0; + + /* To get the status of all interrupts */ + if(int_status_sel == BMI160_INT_STATUS_ALL) { + rslt = bmi160_get_regs(BMI160_INT_STATUS_ADDR, &int_status->data[0], 4, dev); + } else { + if(int_status_sel & BMI160_INT_STATUS_0) { + rslt = bmi160_get_regs(BMI160_INT_STATUS_ADDR, &int_status->data[0], 1, dev); + } + + if(int_status_sel & BMI160_INT_STATUS_1) { + rslt = bmi160_get_regs(BMI160_INT_STATUS_ADDR + 1, &int_status->data[1], 1, dev); + } + + if(int_status_sel & BMI160_INT_STATUS_2) { + rslt = bmi160_get_regs(BMI160_INT_STATUS_ADDR + 2, &int_status->data[2], 1, dev); + } + + if(int_status_sel & BMI160_INT_STATUS_3) { + rslt = bmi160_get_regs(BMI160_INT_STATUS_ADDR + 3, &int_status->data[3], 1, dev); + } + } + + return rslt; +} + +/*********************** Local function definitions ***************************/ + +/*! + * @brief This API sets the any-motion interrupt of the sensor. + * This interrupt occurs when accel values exceeds preset threshold + * for a certain period of time. + */ +static int8_t + set_accel_any_motion_int(struct bmi160_int_settg* int_config, struct bmi160_dev* dev) { + int8_t rslt; + + /* Null-pointer check */ + rslt = null_ptr_check(dev); + if((rslt != BMI160_OK) || (int_config == NULL)) { + rslt = BMI160_E_NULL_PTR; + } else { + /* updating the interrupt structure to local structure */ + struct bmi160_acc_any_mot_int_cfg* any_motion_int_cfg = + &(int_config->int_type_cfg.acc_any_motion_int); + rslt = enable_accel_any_motion_int(any_motion_int_cfg, dev); + if(rslt == BMI160_OK) { + rslt = config_any_motion_int_settg(int_config, any_motion_int_cfg, dev); + } + } + + return rslt; +} + +/*! + * @brief This API sets tap interrupts.Interrupt is fired when + * tap movements happen. + */ +static int8_t + set_accel_tap_int(struct bmi160_int_settg* int_config, const struct bmi160_dev* dev) { + int8_t rslt; + + /* Null-pointer check */ + rslt = null_ptr_check(dev); + if((rslt != BMI160_OK) || (int_config == NULL)) { + rslt = BMI160_E_NULL_PTR; + } else { + /* updating the interrupt structure to local structure */ + struct bmi160_acc_tap_int_cfg* tap_int_cfg = &(int_config->int_type_cfg.acc_tap_int); + rslt = enable_tap_int(int_config, tap_int_cfg, dev); + if(rslt == BMI160_OK) { + /* Configure Interrupt pins */ + rslt = set_intr_pin_config(int_config, dev); + if(rslt == BMI160_OK) { + rslt = config_tap_int_settg(int_config, tap_int_cfg, dev); + } + } + } + + return rslt; +} + +/*! + * @brief This API sets the data ready interrupt for both accel and gyro. + * This interrupt occurs when new accel and gyro data comes. + */ +static int8_t set_accel_gyro_data_ready_int( + const struct bmi160_int_settg* int_config, + const struct bmi160_dev* dev) { + int8_t rslt; + + /* Null-pointer check */ + rslt = null_ptr_check(dev); + if((rslt != BMI160_OK) || (int_config == NULL)) { + rslt = BMI160_E_NULL_PTR; + } else { + rslt = enable_data_ready_int(dev); + if(rslt == BMI160_OK) { + /* Configure Interrupt pins */ + rslt = set_intr_pin_config(int_config, dev); + if(rslt == BMI160_OK) { + rslt = map_hardware_interrupt(int_config, dev); + } + } + } + + return rslt; +} + +/*! + * @brief This API sets the significant motion interrupt of the sensor.This + * interrupt occurs when there is change in user location. + */ +static int8_t + set_accel_sig_motion_int(struct bmi160_int_settg* int_config, struct bmi160_dev* dev) { + int8_t rslt; + + /* Null-pointer check */ + rslt = null_ptr_check(dev); + if((rslt != BMI160_OK) || (int_config == NULL)) { + rslt = BMI160_E_NULL_PTR; + } else { + /* updating the interrupt structure to local structure */ + struct bmi160_acc_sig_mot_int_cfg* sig_mot_int_cfg = + &(int_config->int_type_cfg.acc_sig_motion_int); + rslt = enable_sig_motion_int(sig_mot_int_cfg, dev); + if(rslt == BMI160_OK) { + rslt = config_sig_motion_int_settg(int_config, sig_mot_int_cfg, dev); + } + } + + return rslt; +} + +/*! + * @brief This API sets the no motion/slow motion interrupt of the sensor. + * Slow motion is similar to any motion interrupt.No motion interrupt + * occurs when slope bet. two accel values falls below preset threshold + * for preset duration. + */ +static int8_t + set_accel_no_motion_int(struct bmi160_int_settg* int_config, const struct bmi160_dev* dev) { + int8_t rslt; + + /* Null-pointer check */ + rslt = null_ptr_check(dev); + if((rslt != BMI160_OK) || (int_config == NULL)) { + rslt = BMI160_E_NULL_PTR; + } else { + /* updating the interrupt structure to local structure */ + struct bmi160_acc_no_motion_int_cfg* no_mot_int_cfg = + &(int_config->int_type_cfg.acc_no_motion_int); + rslt = enable_no_motion_int(no_mot_int_cfg, dev); + if(rslt == BMI160_OK) { + /* Configure the INT PIN settings*/ + rslt = config_no_motion_int_settg(int_config, no_mot_int_cfg, dev); + } + } + + return rslt; +} + +/*! + * @brief This API sets the step detection interrupt.This interrupt + * occurs when the single step causes accel values to go above + * preset threshold. + */ +static int8_t + set_accel_step_detect_int(struct bmi160_int_settg* int_config, const struct bmi160_dev* dev) { + int8_t rslt; + + /* Null-pointer check */ + rslt = null_ptr_check(dev); + if((rslt != BMI160_OK) || (int_config == NULL)) { + rslt = BMI160_E_NULL_PTR; + } else { + /* updating the interrupt structure to local structure */ + struct bmi160_acc_step_detect_int_cfg* step_detect_int_cfg = + &(int_config->int_type_cfg.acc_step_detect_int); + rslt = enable_step_detect_int(step_detect_int_cfg, dev); + if(rslt == BMI160_OK) { + /* Configure Interrupt pins */ + rslt = set_intr_pin_config(int_config, dev); + if(rslt == BMI160_OK) { + rslt = map_feature_interrupt(int_config, dev); + if(rslt == BMI160_OK) { + rslt = config_step_detect(step_detect_int_cfg, dev); + } + } + } + } + + return rslt; +} + +/*! + * @brief This API sets the orientation interrupt of the sensor.This + * interrupt occurs when there is orientation change in the sensor + * with respect to gravitational field vector g. + */ +static int8_t + set_accel_orientation_int(struct bmi160_int_settg* int_config, const struct bmi160_dev* dev) { + int8_t rslt; + + /* Null-pointer check */ + rslt = null_ptr_check(dev); + if((rslt != BMI160_OK) || (int_config == NULL)) { + rslt = BMI160_E_NULL_PTR; + } else { + /* updating the interrupt structure to local structure */ + struct bmi160_acc_orient_int_cfg* orient_int_cfg = + &(int_config->int_type_cfg.acc_orient_int); + rslt = enable_orient_int(orient_int_cfg, dev); + if(rslt == BMI160_OK) { + /* Configure Interrupt pins */ + rslt = set_intr_pin_config(int_config, dev); + if(rslt == BMI160_OK) { + /* map INT pin to orient interrupt */ + rslt = map_feature_interrupt(int_config, dev); + if(rslt == BMI160_OK) { + /* configure the + * orientation setting*/ + rslt = config_orient_int_settg(orient_int_cfg, dev); + } + } + } + } + + return rslt; +} + +/*! + * @brief This API sets the flat interrupt of the sensor.This interrupt + * occurs in case of flat orientation + */ +static int8_t + set_accel_flat_detect_int(struct bmi160_int_settg* int_config, const struct bmi160_dev* dev) { + int8_t rslt; + + /* Null-pointer check */ + rslt = null_ptr_check(dev); + if((rslt != BMI160_OK) || (int_config == NULL)) { + rslt = BMI160_E_NULL_PTR; + } else { + /* updating the interrupt structure to local structure */ + struct bmi160_acc_flat_detect_int_cfg* flat_detect_int = + &(int_config->int_type_cfg.acc_flat_int); + + /* enable the flat interrupt */ + rslt = enable_flat_int(flat_detect_int, dev); + if(rslt == BMI160_OK) { + /* Configure Interrupt pins */ + rslt = set_intr_pin_config(int_config, dev); + if(rslt == BMI160_OK) { + /* map INT pin to flat interrupt */ + rslt = map_feature_interrupt(int_config, dev); + if(rslt == BMI160_OK) { + /* configure the flat setting*/ + rslt = config_flat_int_settg(flat_detect_int, dev); + } + } + } + } + + return rslt; +} + +/*! + * @brief This API sets the low-g interrupt of the sensor.This interrupt + * occurs during free-fall. + */ +static int8_t + set_accel_low_g_int(struct bmi160_int_settg* int_config, const struct bmi160_dev* dev) { + int8_t rslt; + + /* Null-pointer check */ + rslt = null_ptr_check(dev); + if((rslt != BMI160_OK) || (int_config == NULL)) { + rslt = BMI160_E_NULL_PTR; + } else { + /* updating the interrupt structure to local structure */ + struct bmi160_acc_low_g_int_cfg* low_g_int = &(int_config->int_type_cfg.acc_low_g_int); + + /* Enable the low-g interrupt*/ + rslt = enable_low_g_int(low_g_int, dev); + if(rslt == BMI160_OK) { + /* Configure Interrupt pins */ + rslt = set_intr_pin_config(int_config, dev); + if(rslt == BMI160_OK) { + /* Map INT pin to low-g interrupt */ + rslt = map_feature_interrupt(int_config, dev); + if(rslt == BMI160_OK) { + /* configure the data source + * for low-g interrupt*/ + rslt = config_low_g_data_src(low_g_int, dev); + if(rslt == BMI160_OK) { + rslt = config_low_g_int_settg(low_g_int, dev); + } + } + } + } + } + + return rslt; +} + +/*! + * @brief This API sets the high-g interrupt of the sensor.The interrupt + * occurs if the absolute value of acceleration data of any enabled axis + * exceeds the programmed threshold and the sign of the value does not + * change for a preset duration. + */ +static int8_t + set_accel_high_g_int(struct bmi160_int_settg* int_config, const struct bmi160_dev* dev) { + int8_t rslt; + + /* Null-pointer check */ + rslt = null_ptr_check(dev); + if((rslt != BMI160_OK) || (int_config == NULL)) { + rslt = BMI160_E_NULL_PTR; + } else { + /* updating the interrupt structure to local structure */ + struct bmi160_acc_high_g_int_cfg* high_g_int_cfg = + &(int_config->int_type_cfg.acc_high_g_int); + + /* Enable the high-g interrupt */ + rslt = enable_high_g_int(high_g_int_cfg, dev); + if(rslt == BMI160_OK) { + /* Configure Interrupt pins */ + rslt = set_intr_pin_config(int_config, dev); + if(rslt == BMI160_OK) { + /* Map INT pin to high-g interrupt */ + rslt = map_feature_interrupt(int_config, dev); + if(rslt == BMI160_OK) { + /* configure the data source + * for high-g interrupt*/ + rslt = config_high_g_data_src(high_g_int_cfg, dev); + if(rslt == BMI160_OK) { + rslt = config_high_g_int_settg(high_g_int_cfg, dev); + } + } + } + } + } + + return rslt; +} + +/*! + * @brief This API configures the pins to fire the + * interrupt signal when it occurs. + */ +static int8_t + set_intr_pin_config(const struct bmi160_int_settg* int_config, const struct bmi160_dev* dev) { + int8_t rslt; + + /* configure the behavioural settings of interrupt pin */ + rslt = config_int_out_ctrl(int_config, dev); + if(rslt == BMI160_OK) { + rslt = config_int_latch(int_config, dev); + } + + return rslt; +} + +/*! + * @brief This internal API is used to validate the device structure pointer for + * null conditions. + */ +static int8_t null_ptr_check(const struct bmi160_dev* dev) { + int8_t rslt; + + if((dev == NULL) || (dev->read == NULL) || (dev->write == NULL) || (dev->delay_ms == NULL)) { + rslt = BMI160_E_NULL_PTR; + } else { + /* Device structure is fine */ + rslt = BMI160_OK; + } + + return rslt; +} + +/*! + * @brief This API sets the default configuration parameters of accel & gyro. + * Also maintain the previous state of configurations. + */ +static void default_param_settg(struct bmi160_dev* dev) { + /* Initializing accel and gyro params with + * default values */ + dev->accel_cfg.bw = BMI160_ACCEL_BW_NORMAL_AVG4; + dev->accel_cfg.odr = BMI160_ACCEL_ODR_100HZ; + dev->accel_cfg.power = BMI160_ACCEL_SUSPEND_MODE; + dev->accel_cfg.range = BMI160_ACCEL_RANGE_2G; + dev->gyro_cfg.bw = BMI160_GYRO_BW_NORMAL_MODE; + dev->gyro_cfg.odr = BMI160_GYRO_ODR_100HZ; + dev->gyro_cfg.power = BMI160_GYRO_SUSPEND_MODE; + dev->gyro_cfg.range = BMI160_GYRO_RANGE_2000_DPS; + + /* To maintain the previous state of accel configuration */ + dev->prev_accel_cfg = dev->accel_cfg; + + /* To maintain the previous state of gyro configuration */ + dev->prev_gyro_cfg = dev->gyro_cfg; +} + +/*! + * @brief This API set the accel configuration. + */ +static int8_t set_accel_conf(struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data[2] = {0}; + + rslt = check_accel_config(data, dev); + if(rslt == BMI160_OK) { + /* Write output data rate and bandwidth */ + rslt = bmi160_set_regs(BMI160_ACCEL_CONFIG_ADDR, &data[0], 1, dev); + if(rslt == BMI160_OK) { + dev->prev_accel_cfg.odr = dev->accel_cfg.odr; + dev->prev_accel_cfg.bw = dev->accel_cfg.bw; + + /* write accel range */ + rslt = bmi160_set_regs(BMI160_ACCEL_RANGE_ADDR, &data[1], 1, dev); + if(rslt == BMI160_OK) { + dev->prev_accel_cfg.range = dev->accel_cfg.range; + } + } + } + + return rslt; +} + +/*! + * @brief This API gets the accel configuration. + */ +static int8_t get_accel_conf(struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data[2] = {0}; + + /* Get accel configurations */ + rslt = bmi160_get_regs(BMI160_ACCEL_CONFIG_ADDR, data, 2, dev); + if(rslt == BMI160_OK) { + dev->accel_cfg.odr = (data[0] & BMI160_ACCEL_ODR_MASK); + dev->accel_cfg.bw = (data[0] & BMI160_ACCEL_BW_MASK) >> BMI160_ACCEL_BW_POS; + dev->accel_cfg.range = (data[1] & BMI160_ACCEL_RANGE_MASK); + } + + return rslt; +} + +/*! + * @brief This API check the accel configuration. + */ +static int8_t check_accel_config(uint8_t* data, const struct bmi160_dev* dev) { + int8_t rslt; + + /* read accel Output data rate and bandwidth */ + rslt = bmi160_get_regs(BMI160_ACCEL_CONFIG_ADDR, data, 2, dev); + if(rslt == BMI160_OK) { + rslt = process_accel_odr(&data[0], dev); + if(rslt == BMI160_OK) { + rslt = process_accel_bw(&data[0], dev); + if(rslt == BMI160_OK) { + rslt = process_accel_range(&data[1], dev); + } + } + } + + return rslt; +} + +/*! + * @brief This API process the accel odr. + */ +static int8_t process_accel_odr(uint8_t* data, const struct bmi160_dev* dev) { + int8_t rslt = 0; + uint8_t temp = 0; + uint8_t odr = 0; + + if(dev->accel_cfg.odr <= BMI160_ACCEL_ODR_1600HZ) { + if(dev->accel_cfg.odr != dev->prev_accel_cfg.odr) { + odr = (uint8_t)dev->accel_cfg.odr; + temp = *data & ~BMI160_ACCEL_ODR_MASK; + + /* Adding output data rate */ + *data = temp | (odr & BMI160_ACCEL_ODR_MASK); + } + } else { + rslt = BMI160_E_OUT_OF_RANGE; + } + + return rslt; +} + +/*! + * @brief This API process the accel bandwidth. + */ +static int8_t process_accel_bw(uint8_t* data, const struct bmi160_dev* dev) { + int8_t rslt = 0; + uint8_t temp = 0; + uint8_t bw = 0; + + if(dev->accel_cfg.bw <= BMI160_ACCEL_BW_RES_AVG128) { + if(dev->accel_cfg.bw != dev->prev_accel_cfg.bw) { + bw = (uint8_t)dev->accel_cfg.bw; + temp = *data & ~BMI160_ACCEL_BW_MASK; + + /* Adding bandwidth */ + *data = temp | ((bw << 4) & BMI160_ACCEL_BW_MASK); + } + } else { + rslt = BMI160_E_OUT_OF_RANGE; + } + + return rslt; +} + +/*! + * @brief This API process the accel range. + */ +static int8_t process_accel_range(uint8_t* data, const struct bmi160_dev* dev) { + int8_t rslt = 0; + uint8_t temp = 0; + uint8_t range = 0; + + if(dev->accel_cfg.range <= BMI160_ACCEL_RANGE_16G) { + if(dev->accel_cfg.range != dev->prev_accel_cfg.range) { + range = (uint8_t)dev->accel_cfg.range; + temp = *data & ~BMI160_ACCEL_RANGE_MASK; + + /* Adding range */ + *data = temp | (range & BMI160_ACCEL_RANGE_MASK); + } + } else { + rslt = BMI160_E_OUT_OF_RANGE; + } + + return rslt; +} + +/*! + * @brief This API checks the invalid settings for ODR & Bw for + * Accel and Gyro. + */ +static int8_t check_invalid_settg(const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data = 0; + + /* read the error reg */ + rslt = bmi160_get_regs(BMI160_ERROR_REG_ADDR, &data, 1, dev); + data = data >> 1; + data = data & BMI160_ERR_REG_MASK; + if(data == 1) { + rslt = BMI160_E_ACCEL_ODR_BW_INVALID; + } else if(data == 2) { + rslt = BMI160_E_GYRO_ODR_BW_INVALID; + } else if(data == 3) { + rslt = BMI160_E_LWP_PRE_FLTR_INT_INVALID; + } else if(data == 7) { + rslt = BMI160_E_LWP_PRE_FLTR_INVALID; + } + + return rslt; +} +static int8_t set_gyro_conf(struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data[2] = {0}; + + rslt = check_gyro_config(data, dev); + if(rslt == BMI160_OK) { + /* Write output data rate and bandwidth */ + rslt = bmi160_set_regs(BMI160_GYRO_CONFIG_ADDR, &data[0], 1, dev); + if(rslt == BMI160_OK) { + dev->prev_gyro_cfg.odr = dev->gyro_cfg.odr; + dev->prev_gyro_cfg.bw = dev->gyro_cfg.bw; + + /* Write gyro range */ + rslt = bmi160_set_regs(BMI160_GYRO_RANGE_ADDR, &data[1], 1, dev); + if(rslt == BMI160_OK) { + dev->prev_gyro_cfg.range = dev->gyro_cfg.range; + } + } + } + + return rslt; +} + +/*! + * @brief This API gets the gyro configuration. + */ +static int8_t get_gyro_conf(struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data[2] = {0}; + + /* Get accel configurations */ + rslt = bmi160_get_regs(BMI160_GYRO_CONFIG_ADDR, data, 2, dev); + if(rslt == BMI160_OK) { + dev->gyro_cfg.odr = (data[0] & BMI160_GYRO_ODR_MASK); + dev->gyro_cfg.bw = (data[0] & BMI160_GYRO_BW_MASK) >> BMI160_GYRO_BW_POS; + dev->gyro_cfg.range = (data[1] & BMI160_GYRO_RANGE_MASK); + } + + return rslt; +} + +/*! + * @brief This API check the gyro configuration. + */ +static int8_t check_gyro_config(uint8_t* data, const struct bmi160_dev* dev) { + int8_t rslt; + + /* read gyro Output data rate and bandwidth */ + rslt = bmi160_get_regs(BMI160_GYRO_CONFIG_ADDR, data, 2, dev); + if(rslt == BMI160_OK) { + rslt = process_gyro_odr(&data[0], dev); + if(rslt == BMI160_OK) { + rslt = process_gyro_bw(&data[0], dev); + if(rslt == BMI160_OK) { + rslt = process_gyro_range(&data[1], dev); + } + } + } + + return rslt; +} + +/*! + * @brief This API process the gyro odr. + */ +static int8_t process_gyro_odr(uint8_t* data, const struct bmi160_dev* dev) { + int8_t rslt = 0; + uint8_t temp = 0; + uint8_t odr = 0; + + if(dev->gyro_cfg.odr <= BMI160_GYRO_ODR_3200HZ) { + if(dev->gyro_cfg.odr != dev->prev_gyro_cfg.odr) { + odr = (uint8_t)dev->gyro_cfg.odr; + temp = (*data & ~BMI160_GYRO_ODR_MASK); + + /* Adding output data rate */ + *data = temp | (odr & BMI160_GYRO_ODR_MASK); + } + } else { + rslt = BMI160_E_OUT_OF_RANGE; + } + + return rslt; +} + +/*! + * @brief This API process the gyro bandwidth. + */ +static int8_t process_gyro_bw(uint8_t* data, const struct bmi160_dev* dev) { + int8_t rslt = 0; + uint8_t temp = 0; + uint8_t bw = 0; + + if(dev->gyro_cfg.bw <= BMI160_GYRO_BW_NORMAL_MODE) { + bw = (uint8_t)dev->gyro_cfg.bw; + temp = *data & ~BMI160_GYRO_BW_MASK; + + /* Adding bandwidth */ + *data = temp | ((bw << 4) & BMI160_GYRO_BW_MASK); + } else { + rslt = BMI160_E_OUT_OF_RANGE; + } + + return rslt; +} + +/*! + * @brief This API process the gyro range. + */ +static int8_t process_gyro_range(uint8_t* data, const struct bmi160_dev* dev) { + int8_t rslt = 0; + uint8_t temp = 0; + uint8_t range = 0; + + if(dev->gyro_cfg.range <= BMI160_GYRO_RANGE_125_DPS) { + if(dev->gyro_cfg.range != dev->prev_gyro_cfg.range) { + range = (uint8_t)dev->gyro_cfg.range; + temp = *data & ~BMI160_GYRO_RANGE_MASK; + + /* Adding range */ + *data = temp | (range & BMI160_GYRO_RANGE_MASK); + } + } else { + rslt = BMI160_E_OUT_OF_RANGE; + } + + return rslt; +} + +/*! + * @brief This API sets the accel power. + */ +static int8_t set_accel_pwr(struct bmi160_dev* dev) { + int8_t rslt = 0; + uint8_t data = 0; + + if((dev->accel_cfg.power >= BMI160_ACCEL_SUSPEND_MODE) && + (dev->accel_cfg.power <= BMI160_ACCEL_LOWPOWER_MODE)) { + if(dev->accel_cfg.power != dev->prev_accel_cfg.power) { + rslt = process_under_sampling(&data, dev); + if(rslt == BMI160_OK) { + /* Write accel power */ + rslt = bmi160_set_regs(BMI160_COMMAND_REG_ADDR, &dev->accel_cfg.power, 1, dev); + + /* Add delay of 3.8 ms - refer data sheet table 24*/ + if(dev->prev_accel_cfg.power == BMI160_ACCEL_SUSPEND_MODE) { + dev->delay_ms(BMI160_ACCEL_DELAY_MS); + } + + dev->prev_accel_cfg.power = dev->accel_cfg.power; + } + } + } else { + rslt = BMI160_E_INVALID_CONFIG; + } + + return rslt; +} + +/*! + * @brief This API process the undersampling setting of Accel. + */ +static int8_t process_under_sampling(uint8_t* data, const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t temp = 0; + uint8_t pre_filter[2] = {0}; + + rslt = bmi160_get_regs(BMI160_ACCEL_CONFIG_ADDR, data, 1, dev); + if(rslt == BMI160_OK) { + if(dev->accel_cfg.power == BMI160_ACCEL_LOWPOWER_MODE) { + temp = *data & ~BMI160_ACCEL_UNDERSAMPLING_MASK; + + /* Set under-sampling parameter */ + *data = temp | ((1 << 7) & BMI160_ACCEL_UNDERSAMPLING_MASK); + + /* Write data */ + rslt = bmi160_set_regs(BMI160_ACCEL_CONFIG_ADDR, data, 1, dev); + + /* Disable the pre-filter data in low power mode */ + if(rslt == BMI160_OK) { + /* Disable the Pre-filter data*/ + rslt = bmi160_set_regs(BMI160_INT_DATA_0_ADDR, pre_filter, 2, dev); + } + } else if(*data & BMI160_ACCEL_UNDERSAMPLING_MASK) { + temp = *data & ~BMI160_ACCEL_UNDERSAMPLING_MASK; + + /* Disable under-sampling parameter if already enabled */ + *data = temp; + + /* Write data */ + rslt = bmi160_set_regs(BMI160_ACCEL_CONFIG_ADDR, data, 1, dev); + } + } + + return rslt; +} + +/*! + * @brief This API sets the gyro power mode. + */ +static int8_t set_gyro_pwr(struct bmi160_dev* dev) { + int8_t rslt = 0; + + if((dev->gyro_cfg.power == BMI160_GYRO_SUSPEND_MODE) || + (dev->gyro_cfg.power == BMI160_GYRO_NORMAL_MODE) || + (dev->gyro_cfg.power == BMI160_GYRO_FASTSTARTUP_MODE)) { + if(dev->gyro_cfg.power != dev->prev_gyro_cfg.power) { + /* Write gyro power */ + rslt = bmi160_set_regs(BMI160_COMMAND_REG_ADDR, &dev->gyro_cfg.power, 1, dev); + if(dev->prev_gyro_cfg.power == BMI160_GYRO_SUSPEND_MODE) { + /* Delay of 80 ms - datasheet Table 24 */ + dev->delay_ms(BMI160_GYRO_DELAY_MS); + } else if( + (dev->prev_gyro_cfg.power == BMI160_GYRO_FASTSTARTUP_MODE) && + (dev->gyro_cfg.power == BMI160_GYRO_NORMAL_MODE)) { + /* This delay is required for transition from + * fast-startup mode to normal mode - datasheet Table 3 */ + dev->delay_ms(10); + } else { + /* do nothing */ + } + + dev->prev_gyro_cfg.power = dev->gyro_cfg.power; + } + } else { + rslt = BMI160_E_INVALID_CONFIG; + } + + return rslt; +} + +/*! + * @brief This API reads accel data along with sensor time if time is requested + * by user. Kindly refer the user guide(README.md) for more info. + */ +static int8_t + get_accel_data(uint8_t len, struct bmi160_sensor_data* accel, const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t idx = 0; + uint8_t data_array[9] = {0}; + uint8_t time_0 = 0; + uint16_t time_1 = 0; + uint32_t time_2 = 0; + uint8_t lsb; + uint8_t msb; + int16_t msblsb; + + /* read accel sensor data along with time if requested */ + rslt = bmi160_get_regs(BMI160_ACCEL_DATA_ADDR, data_array, 6 + len, dev); + if(rslt == BMI160_OK) { + /* Accel Data */ + lsb = data_array[idx++]; + msb = data_array[idx++]; + msblsb = (int16_t)((msb << 8) | lsb); + accel->x = msblsb; /* Data in X axis */ + lsb = data_array[idx++]; + msb = data_array[idx++]; + msblsb = (int16_t)((msb << 8) | lsb); + accel->y = msblsb; /* Data in Y axis */ + lsb = data_array[idx++]; + msb = data_array[idx++]; + msblsb = (int16_t)((msb << 8) | lsb); + accel->z = msblsb; /* Data in Z axis */ + if(len == 3) { + time_0 = data_array[idx++]; + time_1 = (uint16_t)(data_array[idx++] << 8); + time_2 = (uint32_t)(data_array[idx++] << 16); + accel->sensortime = (uint32_t)(time_2 | time_1 | time_0); + } else { + accel->sensortime = 0; + } + } else { + rslt = BMI160_E_COM_FAIL; + } + + return rslt; +} + +/*! + * @brief This API reads accel data along with sensor time if time is requested + * by user. Kindly refer the user guide(README.md) for more info. + */ +static int8_t + get_gyro_data(uint8_t len, struct bmi160_sensor_data* gyro, const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t idx = 0; + uint8_t data_array[15] = {0}; + uint8_t time_0 = 0; + uint16_t time_1 = 0; + uint32_t time_2 = 0; + uint8_t lsb; + uint8_t msb; + int16_t msblsb; + + if(len == 0) { + /* read gyro data only */ + rslt = bmi160_get_regs(BMI160_GYRO_DATA_ADDR, data_array, 6, dev); + if(rslt == BMI160_OK) { + /* Gyro Data */ + lsb = data_array[idx++]; + msb = data_array[idx++]; + msblsb = (int16_t)((msb << 8) | lsb); + gyro->x = msblsb; /* Data in X axis */ + lsb = data_array[idx++]; + msb = data_array[idx++]; + msblsb = (int16_t)((msb << 8) | lsb); + gyro->y = msblsb; /* Data in Y axis */ + lsb = data_array[idx++]; + msb = data_array[idx++]; + msblsb = (int16_t)((msb << 8) | lsb); + gyro->z = msblsb; /* Data in Z axis */ + gyro->sensortime = 0; + } else { + rslt = BMI160_E_COM_FAIL; + } + } else { + /* read gyro sensor data along with time */ + rslt = bmi160_get_regs(BMI160_GYRO_DATA_ADDR, data_array, 12 + len, dev); + if(rslt == BMI160_OK) { + /* Gyro Data */ + lsb = data_array[idx++]; + msb = data_array[idx++]; + msblsb = (int16_t)((msb << 8) | lsb); + gyro->x = msblsb; /* gyro X axis data */ + lsb = data_array[idx++]; + msb = data_array[idx++]; + msblsb = (int16_t)((msb << 8) | lsb); + gyro->y = msblsb; /* gyro Y axis data */ + lsb = data_array[idx++]; + msb = data_array[idx++]; + msblsb = (int16_t)((msb << 8) | lsb); + gyro->z = msblsb; /* gyro Z axis data */ + idx = idx + 6; + time_0 = data_array[idx++]; + time_1 = (uint16_t)(data_array[idx++] << 8); + time_2 = (uint32_t)(data_array[idx++] << 16); + gyro->sensortime = (uint32_t)(time_2 | time_1 | time_0); + } else { + rslt = BMI160_E_COM_FAIL; + } + } + + return rslt; +} + +/*! + * @brief This API reads accel and gyro data along with sensor time + * if time is requested by user. + * Kindly refer the user guide(README.md) for more info. + */ +static int8_t get_accel_gyro_data( + uint8_t len, + struct bmi160_sensor_data* accel, + struct bmi160_sensor_data* gyro, + const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t idx = 0; + uint8_t data_array[15] = {0}; + uint8_t time_0 = 0; + uint16_t time_1 = 0; + uint32_t time_2 = 0; + uint8_t lsb; + uint8_t msb; + int16_t msblsb; + + /* read both accel and gyro sensor data + * along with time if requested */ + rslt = bmi160_get_regs(BMI160_GYRO_DATA_ADDR, data_array, 12 + len, dev); + if(rslt == BMI160_OK) { + /* Gyro Data */ + lsb = data_array[idx++]; + msb = data_array[idx++]; + msblsb = (int16_t)((msb << 8) | lsb); + gyro->x = msblsb; /* gyro X axis data */ + lsb = data_array[idx++]; + msb = data_array[idx++]; + msblsb = (int16_t)((msb << 8) | lsb); + gyro->y = msblsb; /* gyro Y axis data */ + lsb = data_array[idx++]; + msb = data_array[idx++]; + msblsb = (int16_t)((msb << 8) | lsb); + gyro->z = msblsb; /* gyro Z axis data */ + /* Accel Data */ + lsb = data_array[idx++]; + msb = data_array[idx++]; + msblsb = (int16_t)((msb << 8) | lsb); + accel->x = (int16_t)msblsb; /* accel X axis data */ + lsb = data_array[idx++]; + msb = data_array[idx++]; + msblsb = (int16_t)((msb << 8) | lsb); + accel->y = (int16_t)msblsb; /* accel Y axis data */ + lsb = data_array[idx++]; + msb = data_array[idx++]; + msblsb = (int16_t)((msb << 8) | lsb); + accel->z = (int16_t)msblsb; /* accel Z axis data */ + if(len == 3) { + time_0 = data_array[idx++]; + time_1 = (uint16_t)(data_array[idx++] << 8); + time_2 = (uint32_t)(data_array[idx++] << 16); + accel->sensortime = (uint32_t)(time_2 | time_1 | time_0); + gyro->sensortime = (uint32_t)(time_2 | time_1 | time_0); + } else { + accel->sensortime = 0; + gyro->sensortime = 0; + } + } else { + rslt = BMI160_E_COM_FAIL; + } + + return rslt; +} + +/*! + * @brief This API enables the any-motion interrupt for accel. + */ +static int8_t enable_accel_any_motion_int( + const struct bmi160_acc_any_mot_int_cfg* any_motion_int_cfg, + struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data = 0; + uint8_t temp = 0; + + /* Enable any motion x, any motion y, any motion z + * in Int Enable 0 register */ + rslt = bmi160_get_regs(BMI160_INT_ENABLE_0_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + if(any_motion_int_cfg->anymotion_en == BMI160_ENABLE) { + temp = data & ~BMI160_ANY_MOTION_X_INT_EN_MASK; + + /* Adding Any_motion x axis */ + data = temp | (any_motion_int_cfg->anymotion_x & BMI160_ANY_MOTION_X_INT_EN_MASK); + temp = data & ~BMI160_ANY_MOTION_Y_INT_EN_MASK; + + /* Adding Any_motion y axis */ + data = temp | + ((any_motion_int_cfg->anymotion_y << 1) & BMI160_ANY_MOTION_Y_INT_EN_MASK); + temp = data & ~BMI160_ANY_MOTION_Z_INT_EN_MASK; + + /* Adding Any_motion z axis */ + data = temp | + ((any_motion_int_cfg->anymotion_z << 2) & BMI160_ANY_MOTION_Z_INT_EN_MASK); + + /* any-motion feature selected*/ + dev->any_sig_sel = BMI160_ANY_MOTION_ENABLED; + } else { + data = data & ~BMI160_ANY_MOTION_ALL_INT_EN_MASK; + + /* neither any-motion feature nor sig-motion selected */ + dev->any_sig_sel = BMI160_BOTH_ANY_SIG_MOTION_DISABLED; + } + + /* write data to Int Enable 0 register */ + rslt = bmi160_set_regs(BMI160_INT_ENABLE_0_ADDR, &data, 1, dev); + } + + return rslt; +} + +/*! + * @brief This API disable the sig-motion interrupt. + */ +static int8_t disable_sig_motion_int(const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data = 0; + uint8_t temp = 0; + + /* Disabling Significant motion interrupt if enabled */ + rslt = bmi160_get_regs(BMI160_INT_MOTION_3_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + temp = (data & BMI160_SIG_MOTION_SEL_MASK); + if(temp) { + temp = data & ~BMI160_SIG_MOTION_SEL_MASK; + data = temp; + + /* Write data to register */ + rslt = bmi160_set_regs(BMI160_INT_MOTION_3_ADDR, &data, 1, dev); + } + } + + return rslt; +} + +/*! + * @brief This API is used to map/unmap the Any/Sig motion, Step det/Low-g, + * Double tap, Single tap, Orientation, Flat, High-G, Nomotion interrupt pins. + */ +static int8_t + map_feature_interrupt(const struct bmi160_int_settg* int_config, const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data[3] = {0, 0, 0}; + uint8_t temp[3] = {0, 0, 0}; + + rslt = bmi160_get_regs(BMI160_INT_MAP_0_ADDR, data, 3, dev); + if(rslt == BMI160_OK) { + temp[0] = data[0] & ~int_mask_lookup_table[int_config->int_type]; + temp[2] = data[2] & ~int_mask_lookup_table[int_config->int_type]; + switch(int_config->int_channel) { + case BMI160_INT_CHANNEL_NONE: + data[0] = temp[0]; + data[2] = temp[2]; + break; + case BMI160_INT_CHANNEL_1: + data[0] = temp[0] | int_mask_lookup_table[int_config->int_type]; + data[2] = temp[2]; + break; + case BMI160_INT_CHANNEL_2: + data[2] = temp[2] | int_mask_lookup_table[int_config->int_type]; + data[0] = temp[0]; + break; + case BMI160_INT_CHANNEL_BOTH: + data[0] = temp[0] | int_mask_lookup_table[int_config->int_type]; + data[2] = temp[2] | int_mask_lookup_table[int_config->int_type]; + break; + default: + rslt = BMI160_E_OUT_OF_RANGE; + } + if(rslt == BMI160_OK) { + rslt = bmi160_set_regs(BMI160_INT_MAP_0_ADDR, data, 3, dev); + } + } + + return rslt; +} + +/*! + * @brief This API is used to map/unmap the Dataready(Accel & Gyro), FIFO full + * and FIFO watermark interrupt. + */ +static int8_t map_hardware_interrupt( + const struct bmi160_int_settg* int_config, + const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data = 0; + uint8_t temp = 0; + + rslt = bmi160_get_regs(BMI160_INT_MAP_1_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + temp = data & ~int_mask_lookup_table[int_config->int_type]; + temp = temp & ~((uint8_t)(int_mask_lookup_table[int_config->int_type] << 4)); + switch(int_config->int_channel) { + case BMI160_INT_CHANNEL_NONE: + data = temp; + break; + case BMI160_INT_CHANNEL_1: + data = temp | (uint8_t)((int_mask_lookup_table[int_config->int_type]) << 4); + break; + case BMI160_INT_CHANNEL_2: + data = temp | int_mask_lookup_table[int_config->int_type]; + break; + case BMI160_INT_CHANNEL_BOTH: + data = temp | int_mask_lookup_table[int_config->int_type]; + data = data | (uint8_t)((int_mask_lookup_table[int_config->int_type]) << 4); + break; + default: + rslt = BMI160_E_OUT_OF_RANGE; + } + if(rslt == BMI160_OK) { + rslt = bmi160_set_regs(BMI160_INT_MAP_1_ADDR, &data, 1, dev); + } + } + + return rslt; +} + +/*! + * @brief This API configure the source of data(filter & pre-filter) + * for any-motion interrupt. + */ +static int8_t config_any_motion_src( + const struct bmi160_acc_any_mot_int_cfg* any_motion_int_cfg, + const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data = 0; + uint8_t temp = 0; + + /* Configure Int data 1 register to add source of interrupt */ + rslt = bmi160_get_regs(BMI160_INT_DATA_1_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + temp = data & ~BMI160_MOTION_SRC_INT_MASK; + data = temp | ((any_motion_int_cfg->anymotion_data_src << 7) & BMI160_MOTION_SRC_INT_MASK); + + /* Write data to DATA 1 address */ + rslt = bmi160_set_regs(BMI160_INT_DATA_1_ADDR, &data, 1, dev); + } + + return rslt; +} + +/*! + * @brief This API configure the duration and threshold of + * any-motion interrupt. + */ +static int8_t config_any_dur_threshold( + const struct bmi160_acc_any_mot_int_cfg* any_motion_int_cfg, + const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data = 0; + uint8_t temp = 0; + uint8_t data_array[2] = {0}; + uint8_t dur; + + /* Configure Int Motion 0 register */ + rslt = bmi160_get_regs(BMI160_INT_MOTION_0_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + /* slope duration */ + dur = (uint8_t)any_motion_int_cfg->anymotion_dur; + temp = data & ~BMI160_SLOPE_INT_DUR_MASK; + data = temp | (dur & BMI160_MOTION_SRC_INT_MASK); + data_array[0] = data; + + /* add slope threshold */ + data_array[1] = any_motion_int_cfg->anymotion_thr; + + /* INT MOTION 0 and INT MOTION 1 address lie consecutively, + * hence writing data to respective registers at one go */ + + /* Writing to Int_motion 0 and + * Int_motion 1 Address simultaneously */ + rslt = bmi160_set_regs(BMI160_INT_MOTION_0_ADDR, data_array, 2, dev); + } + + return rslt; +} + +/*! + * @brief This API configure necessary setting of any-motion interrupt. + */ +static int8_t config_any_motion_int_settg( + const struct bmi160_int_settg* int_config, + const struct bmi160_acc_any_mot_int_cfg* any_motion_int_cfg, + const struct bmi160_dev* dev) { + int8_t rslt; + + /* Configure Interrupt pins */ + rslt = set_intr_pin_config(int_config, dev); + if(rslt == BMI160_OK) { + rslt = disable_sig_motion_int(dev); + if(rslt == BMI160_OK) { + rslt = map_feature_interrupt(int_config, dev); + if(rslt == BMI160_OK) { + rslt = config_any_motion_src(any_motion_int_cfg, dev); + if(rslt == BMI160_OK) { + rslt = config_any_dur_threshold(any_motion_int_cfg, dev); + } + } + } + } + + return rslt; +} + +/*! + * @brief This API enable the data ready interrupt. + */ +static int8_t enable_data_ready_int(const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data = 0; + uint8_t temp = 0; + + /* Enable data ready interrupt in Int Enable 1 register */ + rslt = bmi160_get_regs(BMI160_INT_ENABLE_1_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + temp = data & ~BMI160_DATA_RDY_INT_EN_MASK; + data = temp | ((1 << 4) & BMI160_DATA_RDY_INT_EN_MASK); + + /* Writing data to INT ENABLE 1 Address */ + rslt = bmi160_set_regs(BMI160_INT_ENABLE_1_ADDR, &data, 1, dev); + } + + return rslt; +} + +/*! + * @brief This API enables the no motion/slow motion interrupt. + */ +static int8_t enable_no_motion_int( + const struct bmi160_acc_no_motion_int_cfg* no_mot_int_cfg, + const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data = 0; + uint8_t temp = 0; + + /* Enable no motion x, no motion y, no motion z + * in Int Enable 2 register */ + rslt = bmi160_get_regs(BMI160_INT_ENABLE_2_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + if(no_mot_int_cfg->no_motion_x == 1) { + temp = data & ~BMI160_NO_MOTION_X_INT_EN_MASK; + + /* Adding No_motion x axis */ + data = temp | (1 & BMI160_NO_MOTION_X_INT_EN_MASK); + } + + if(no_mot_int_cfg->no_motion_y == 1) { + temp = data & ~BMI160_NO_MOTION_Y_INT_EN_MASK; + + /* Adding No_motion x axis */ + data = temp | ((1 << 1) & BMI160_NO_MOTION_Y_INT_EN_MASK); + } + + if(no_mot_int_cfg->no_motion_z == 1) { + temp = data & ~BMI160_NO_MOTION_Z_INT_EN_MASK; + + /* Adding No_motion x axis */ + data = temp | ((1 << 2) & BMI160_NO_MOTION_Z_INT_EN_MASK); + } + + /* write data to Int Enable 2 register */ + rslt = bmi160_set_regs(BMI160_INT_ENABLE_2_ADDR, &data, 1, dev); + } + + return rslt; +} + +/*! + * @brief This API configure the interrupt PIN setting for + * no motion/slow motion interrupt. + */ +static int8_t config_no_motion_int_settg( + const struct bmi160_int_settg* int_config, + const struct bmi160_acc_no_motion_int_cfg* no_mot_int_cfg, + const struct bmi160_dev* dev) { + int8_t rslt; + + /* Configure Interrupt pins */ + rslt = set_intr_pin_config(int_config, dev); + if(rslt == BMI160_OK) { + rslt = map_feature_interrupt(int_config, dev); + if(rslt == BMI160_OK) { + rslt = config_no_motion_data_src(no_mot_int_cfg, dev); + if(rslt == BMI160_OK) { + rslt = config_no_motion_dur_thr(no_mot_int_cfg, dev); + } + } + } + + return rslt; +} + +/*! + * @brief This API configure the source of interrupt for no motion. + */ +static int8_t config_no_motion_data_src( + const struct bmi160_acc_no_motion_int_cfg* no_mot_int_cfg, + const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data = 0; + uint8_t temp = 0; + + /* Configure Int data 1 register to add source of interrupt */ + rslt = bmi160_get_regs(BMI160_INT_DATA_1_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + temp = data & ~BMI160_MOTION_SRC_INT_MASK; + data = temp | ((no_mot_int_cfg->no_motion_src << 7) & BMI160_MOTION_SRC_INT_MASK); + + /* Write data to DATA 1 address */ + rslt = bmi160_set_regs(BMI160_INT_DATA_1_ADDR, &data, 1, dev); + } + + return rslt; +} + +/*! + * @brief This API configure the duration and threshold of + * no motion/slow motion interrupt along with selection of no/slow motion. + */ +static int8_t config_no_motion_dur_thr( + const struct bmi160_acc_no_motion_int_cfg* no_mot_int_cfg, + const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data = 0; + uint8_t temp = 0; + uint8_t temp_1 = 0; + uint8_t reg_addr; + uint8_t data_array[2] = {0}; + + /* Configuring INT_MOTION register */ + reg_addr = BMI160_INT_MOTION_0_ADDR; + rslt = bmi160_get_regs(reg_addr, &data, 1, dev); + if(rslt == BMI160_OK) { + temp = data & ~BMI160_NO_MOTION_INT_DUR_MASK; + + /* Adding no_motion duration */ + data = temp | ((no_mot_int_cfg->no_motion_dur << 2) & BMI160_NO_MOTION_INT_DUR_MASK); + + /* Write data to NO_MOTION 0 address */ + rslt = bmi160_set_regs(reg_addr, &data, 1, dev); + if(rslt == BMI160_OK) { + reg_addr = BMI160_INT_MOTION_3_ADDR; + rslt = bmi160_get_regs(reg_addr, &data, 1, dev); + if(rslt == BMI160_OK) { + temp = data & ~BMI160_NO_MOTION_SEL_BIT_MASK; + + /* Adding no_motion_sel bit */ + temp_1 = (no_mot_int_cfg->no_motion_sel & BMI160_NO_MOTION_SEL_BIT_MASK); + data = (temp | temp_1); + data_array[1] = data; + + /* Adding no motion threshold */ + data_array[0] = no_mot_int_cfg->no_motion_thres; + reg_addr = BMI160_INT_MOTION_2_ADDR; + + /* writing data to INT_MOTION 2 and INT_MOTION 3 + * address simultaneously */ + rslt = bmi160_set_regs(reg_addr, data_array, 2, dev); + } + } + } + + return rslt; +} + +/*! + * @brief This API enables the sig-motion motion interrupt. + */ +static int8_t enable_sig_motion_int( + const struct bmi160_acc_sig_mot_int_cfg* sig_mot_int_cfg, + struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data = 0; + uint8_t temp = 0; + + /* For significant motion,enable any motion x,any motion y, + * any motion z in Int Enable 0 register */ + rslt = bmi160_get_regs(BMI160_INT_ENABLE_0_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + if(sig_mot_int_cfg->sig_en == BMI160_ENABLE) { + temp = data & ~BMI160_SIG_MOTION_INT_EN_MASK; + data = temp | (7 & BMI160_SIG_MOTION_INT_EN_MASK); + + /* sig-motion feature selected*/ + dev->any_sig_sel = BMI160_SIG_MOTION_ENABLED; + } else { + data = data & ~BMI160_SIG_MOTION_INT_EN_MASK; + + /* neither any-motion feature nor sig-motion selected */ + dev->any_sig_sel = BMI160_BOTH_ANY_SIG_MOTION_DISABLED; + } + + /* write data to Int Enable 0 register */ + rslt = bmi160_set_regs(BMI160_INT_ENABLE_0_ADDR, &data, 1, dev); + } + + return rslt; +} + +/*! + * @brief This API configure the interrupt PIN setting for + * significant motion interrupt. + */ +static int8_t config_sig_motion_int_settg( + const struct bmi160_int_settg* int_config, + const struct bmi160_acc_sig_mot_int_cfg* sig_mot_int_cfg, + const struct bmi160_dev* dev) { + int8_t rslt; + + /* Configure Interrupt pins */ + rslt = set_intr_pin_config(int_config, dev); + if(rslt == BMI160_OK) { + rslt = map_feature_interrupt(int_config, dev); + if(rslt == BMI160_OK) { + rslt = config_sig_motion_data_src(sig_mot_int_cfg, dev); + if(rslt == BMI160_OK) { + rslt = config_sig_dur_threshold(sig_mot_int_cfg, dev); + } + } + } + + return rslt; +} + +/*! + * @brief This API configure the source of data(filter & pre-filter) + * for sig motion interrupt. + */ +static int8_t config_sig_motion_data_src( + const struct bmi160_acc_sig_mot_int_cfg* sig_mot_int_cfg, + const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data = 0; + uint8_t temp = 0; + + /* Configure Int data 1 register to add source of interrupt */ + rslt = bmi160_get_regs(BMI160_INT_DATA_1_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + temp = data & ~BMI160_MOTION_SRC_INT_MASK; + data = temp | ((sig_mot_int_cfg->sig_data_src << 7) & BMI160_MOTION_SRC_INT_MASK); + + /* Write data to DATA 1 address */ + rslt = bmi160_set_regs(BMI160_INT_DATA_1_ADDR, &data, 1, dev); + } + + return rslt; +} + +/*! + * @brief This API configure the threshold, skip and proof time of + * sig motion interrupt. + */ +static int8_t config_sig_dur_threshold( + const struct bmi160_acc_sig_mot_int_cfg* sig_mot_int_cfg, + const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data; + uint8_t temp = 0; + + /* Configuring INT_MOTION registers */ + + /* Write significant motion threshold. + * This threshold is same as any motion threshold */ + data = sig_mot_int_cfg->sig_mot_thres; + + /* Write data to INT_MOTION 1 address */ + rslt = bmi160_set_regs(BMI160_INT_MOTION_1_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + rslt = bmi160_get_regs(BMI160_INT_MOTION_3_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + temp = data & ~BMI160_SIG_MOTION_SKIP_MASK; + + /* adding skip time of sig_motion interrupt*/ + data = temp | ((sig_mot_int_cfg->sig_mot_skip << 2) & BMI160_SIG_MOTION_SKIP_MASK); + temp = data & ~BMI160_SIG_MOTION_PROOF_MASK; + + /* adding proof time of sig_motion interrupt */ + data = temp | ((sig_mot_int_cfg->sig_mot_proof << 4) & BMI160_SIG_MOTION_PROOF_MASK); + + /* configure the int_sig_mot_sel bit to select + * significant motion interrupt */ + temp = data & ~BMI160_SIG_MOTION_SEL_MASK; + data = temp | ((sig_mot_int_cfg->sig_en << 1) & BMI160_SIG_MOTION_SEL_MASK); + rslt = bmi160_set_regs(BMI160_INT_MOTION_3_ADDR, &data, 1, dev); + } + } + + return rslt; +} + +/*! + * @brief This API enables the step detector interrupt. + */ +static int8_t enable_step_detect_int( + const struct bmi160_acc_step_detect_int_cfg* step_detect_int_cfg, + const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data = 0; + uint8_t temp = 0; + + /* Enable data ready interrupt in Int Enable 2 register */ + rslt = bmi160_get_regs(BMI160_INT_ENABLE_2_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + temp = data & ~BMI160_STEP_DETECT_INT_EN_MASK; + data = temp | + ((step_detect_int_cfg->step_detector_en << 3) & BMI160_STEP_DETECT_INT_EN_MASK); + + /* Writing data to INT ENABLE 2 Address */ + rslt = bmi160_set_regs(BMI160_INT_ENABLE_2_ADDR, &data, 1, dev); + } + + return rslt; +} + +/*! + * @brief This API configure the step detector parameter. + */ +static int8_t config_step_detect( + const struct bmi160_acc_step_detect_int_cfg* step_detect_int_cfg, + const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t temp = 0; + uint8_t data_array[2] = {0}; + + if(step_detect_int_cfg->step_detector_mode == BMI160_STEP_DETECT_NORMAL) { + /* Normal mode setting */ + data_array[0] = 0x15; + data_array[1] = 0x03; + } else if(step_detect_int_cfg->step_detector_mode == BMI160_STEP_DETECT_SENSITIVE) { + /* Sensitive mode setting */ + data_array[0] = 0x2D; + data_array[1] = 0x00; + } else if(step_detect_int_cfg->step_detector_mode == BMI160_STEP_DETECT_ROBUST) { + /* Robust mode setting */ + data_array[0] = 0x1D; + data_array[1] = 0x07; + } else if(step_detect_int_cfg->step_detector_mode == BMI160_STEP_DETECT_USER_DEFINE) { + /* Non recommended User defined setting */ + /* Configuring STEP_CONFIG register */ + rslt = bmi160_get_regs(BMI160_INT_STEP_CONFIG_0_ADDR, &data_array[0], 2, dev); + if(rslt == BMI160_OK) { + temp = data_array[0] & ~BMI160_STEP_DETECT_MIN_THRES_MASK; + + /* Adding min_threshold */ + data_array[0] = temp | ((step_detect_int_cfg->min_threshold << 3) & + BMI160_STEP_DETECT_MIN_THRES_MASK); + temp = data_array[0] & ~BMI160_STEP_DETECT_STEPTIME_MIN_MASK; + + /* Adding steptime_min */ + data_array[0] = temp | ((step_detect_int_cfg->steptime_min) & + BMI160_STEP_DETECT_STEPTIME_MIN_MASK); + temp = data_array[1] & ~BMI160_STEP_MIN_BUF_MASK; + + /* Adding steptime_min */ + data_array[1] = temp | + ((step_detect_int_cfg->step_min_buf) & BMI160_STEP_MIN_BUF_MASK); + } + } + + /* Write data to STEP_CONFIG register */ + rslt = bmi160_set_regs(BMI160_INT_STEP_CONFIG_0_ADDR, data_array, 2, dev); + + return rslt; +} + +/*! + * @brief This API enables the single/double tap interrupt. + */ +static int8_t enable_tap_int( + const struct bmi160_int_settg* int_config, + const struct bmi160_acc_tap_int_cfg* tap_int_cfg, + const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data = 0; + uint8_t temp = 0; + + /* Enable single tap or double tap interrupt in Int Enable 0 register */ + rslt = bmi160_get_regs(BMI160_INT_ENABLE_0_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + if(int_config->int_type == BMI160_ACC_SINGLE_TAP_INT) { + temp = data & ~BMI160_SINGLE_TAP_INT_EN_MASK; + data = temp | ((tap_int_cfg->tap_en << 5) & BMI160_SINGLE_TAP_INT_EN_MASK); + } else { + temp = data & ~BMI160_DOUBLE_TAP_INT_EN_MASK; + data = temp | ((tap_int_cfg->tap_en << 4) & BMI160_DOUBLE_TAP_INT_EN_MASK); + } + + /* Write to Enable 0 Address */ + rslt = bmi160_set_regs(BMI160_INT_ENABLE_0_ADDR, &data, 1, dev); + } + + return rslt; +} + +/*! + * @brief This API configure the interrupt PIN setting for + * tap interrupt. + */ +static int8_t config_tap_int_settg( + const struct bmi160_int_settg* int_config, + const struct bmi160_acc_tap_int_cfg* tap_int_cfg, + const struct bmi160_dev* dev) { + int8_t rslt; + + /* Configure Interrupt pins */ + rslt = set_intr_pin_config(int_config, dev); + if(rslt == BMI160_OK) { + rslt = map_feature_interrupt(int_config, dev); + if(rslt == BMI160_OK) { + rslt = config_tap_data_src(tap_int_cfg, dev); + if(rslt == BMI160_OK) { + rslt = config_tap_param(int_config, tap_int_cfg, dev); + } + } + } + + return rslt; +} + +/*! + * @brief This API configure the source of data(filter & pre-filter) + * for tap interrupt. + */ +static int8_t config_tap_data_src( + const struct bmi160_acc_tap_int_cfg* tap_int_cfg, + const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data = 0; + uint8_t temp = 0; + + /* Configure Int data 0 register to add source of interrupt */ + rslt = bmi160_get_regs(BMI160_INT_DATA_0_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + temp = data & ~BMI160_TAP_SRC_INT_MASK; + data = temp | ((tap_int_cfg->tap_data_src << 3) & BMI160_TAP_SRC_INT_MASK); + + /* Write data to Data 0 address */ + rslt = bmi160_set_regs(BMI160_INT_DATA_0_ADDR, &data, 1, dev); + } + + return rslt; +} + +/*! + * @brief This API configure the parameters of tap interrupt. + * Threshold, quite, shock, and duration. + */ +static int8_t config_tap_param( + const struct bmi160_int_settg* int_config, + const struct bmi160_acc_tap_int_cfg* tap_int_cfg, + const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t temp = 0; + uint8_t data = 0; + uint8_t data_array[2] = {0}; + uint8_t count = 0; + uint8_t dur, shock, quiet, thres; + + /* Configure tap 0 register for tap shock,tap quiet duration + * in case of single tap interrupt */ + rslt = bmi160_get_regs(BMI160_INT_TAP_0_ADDR, data_array, 2, dev); + if(rslt == BMI160_OK) { + data = data_array[count]; + if(int_config->int_type == BMI160_ACC_DOUBLE_TAP_INT) { + dur = (uint8_t)tap_int_cfg->tap_dur; + temp = (data & ~BMI160_TAP_DUR_MASK); + + /* Add tap duration data in case of + * double tap interrupt */ + data = temp | (dur & BMI160_TAP_DUR_MASK); + } + + shock = (uint8_t)tap_int_cfg->tap_shock; + temp = data & ~BMI160_TAP_SHOCK_DUR_MASK; + data = temp | ((shock << 6) & BMI160_TAP_SHOCK_DUR_MASK); + quiet = (uint8_t)tap_int_cfg->tap_quiet; + temp = data & ~BMI160_TAP_QUIET_DUR_MASK; + data = temp | ((quiet << 7) & BMI160_TAP_QUIET_DUR_MASK); + data_array[count++] = data; + data = data_array[count]; + thres = (uint8_t)tap_int_cfg->tap_thr; + temp = data & ~BMI160_TAP_THRES_MASK; + data = temp | (thres & BMI160_TAP_THRES_MASK); + data_array[count++] = data; + + /* TAP 0 and TAP 1 address lie consecutively, + * hence writing data to respective registers at one go */ + + /* Writing to Tap 0 and Tap 1 Address simultaneously */ + rslt = bmi160_set_regs(BMI160_INT_TAP_0_ADDR, data_array, count, dev); + } + + return rslt; +} + +/*! + * @brief This API configure the secondary interface. + */ +static int8_t config_sec_if(const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t if_conf = 0; + uint8_t cmd = BMI160_AUX_NORMAL_MODE; + + /* set the aux power mode to normal*/ + rslt = bmi160_set_regs(BMI160_COMMAND_REG_ADDR, &cmd, 1, dev); + if(rslt == BMI160_OK) { + /* 0.5ms delay - refer datasheet table 24*/ + dev->delay_ms(1); + rslt = bmi160_get_regs(BMI160_IF_CONF_ADDR, &if_conf, 1, dev); + if_conf |= (uint8_t)(1 << 5); + if(rslt == BMI160_OK) { + /*enable the secondary interface also*/ + rslt = bmi160_set_regs(BMI160_IF_CONF_ADDR, &if_conf, 1, dev); + } + } + + return rslt; +} + +/*! + * @brief This API configure the ODR of the auxiliary sensor. + */ +static int8_t config_aux_odr(const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t aux_odr; + + rslt = bmi160_get_regs(BMI160_AUX_ODR_ADDR, &aux_odr, 1, dev); + if(rslt == BMI160_OK) { + aux_odr = (uint8_t)(dev->aux_cfg.aux_odr); + + /* Set the secondary interface ODR + * i.e polling rate of secondary sensor */ + rslt = bmi160_set_regs(BMI160_AUX_ODR_ADDR, &aux_odr, 1, dev); + dev->delay_ms(BMI160_AUX_COM_DELAY); + } + + return rslt; +} + +/*! + * @brief This API maps the actual burst read length set by user. + */ +static int8_t map_read_len(uint16_t* len, const struct bmi160_dev* dev) { + int8_t rslt = BMI160_OK; + + switch(dev->aux_cfg.aux_rd_burst_len) { + case BMI160_AUX_READ_LEN_0: + *len = 1; + break; + case BMI160_AUX_READ_LEN_1: + *len = 2; + break; + case BMI160_AUX_READ_LEN_2: + *len = 6; + break; + case BMI160_AUX_READ_LEN_3: + *len = 8; + break; + default: + rslt = BMI160_E_INVALID_INPUT; + break; + } + + return rslt; +} + +/*! + * @brief This API configure the settings of auxiliary sensor. + */ +static int8_t config_aux_settg(const struct bmi160_dev* dev) { + int8_t rslt; + + rslt = config_sec_if(dev); + if(rslt == BMI160_OK) { + /* Configures the auxiliary interface settings */ + rslt = bmi160_config_aux_mode(dev); + } + + return rslt; +} + +/*! + * @brief This API extract the read data from auxiliary sensor. + */ +static int8_t extract_aux_read( + uint16_t map_len, + uint8_t reg_addr, + uint8_t* aux_data, + uint16_t len, + const struct bmi160_dev* dev) { + int8_t rslt = BMI160_OK; + uint8_t data[8] = { + 0, + }; + uint8_t read_addr = BMI160_AUX_DATA_ADDR; + uint8_t count = 0; + uint8_t read_count; + uint8_t read_len = (uint8_t)map_len; + + for(; count < len;) { + /* set address to read */ + rslt = bmi160_set_regs(BMI160_AUX_IF_2_ADDR, ®_addr, 1, dev); + dev->delay_ms(BMI160_AUX_COM_DELAY); + if(rslt == BMI160_OK) { + rslt = bmi160_get_regs(read_addr, data, map_len, dev); + if(rslt == BMI160_OK) { + read_count = 0; + + /* if read len is less the burst read len + * mention by user*/ + if(len < map_len) { + read_len = (uint8_t)len; + } else if((len - count) < map_len) { + read_len = (uint8_t)(len - count); + } + + for(; read_count < read_len; read_count++) { + aux_data[count + read_count] = data[read_count]; + } + + reg_addr += (uint8_t)map_len; + count += (uint8_t)map_len; + } else { + rslt = BMI160_E_COM_FAIL; + break; + } + } + } + + return rslt; +} + +/*! + * @brief This API enables the orient interrupt. + */ +static int8_t enable_orient_int( + const struct bmi160_acc_orient_int_cfg* orient_int_cfg, + const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data = 0; + uint8_t temp = 0; + + /* Enable data ready interrupt in Int Enable 0 register */ + rslt = bmi160_get_regs(BMI160_INT_ENABLE_0_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + temp = data & ~BMI160_ORIENT_INT_EN_MASK; + data = temp | ((orient_int_cfg->orient_en << 6) & BMI160_ORIENT_INT_EN_MASK); + + /* write data to Int Enable 0 register */ + rslt = bmi160_set_regs(BMI160_INT_ENABLE_0_ADDR, &data, 1, dev); + } + + return rslt; +} + +/*! + * @brief This API configure the necessary setting of orientation interrupt. + */ +static int8_t config_orient_int_settg( + const struct bmi160_acc_orient_int_cfg* orient_int_cfg, + const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data = 0; + uint8_t temp = 0; + uint8_t data_array[2] = {0, 0}; + + /* Configuring INT_ORIENT registers */ + rslt = bmi160_get_regs(BMI160_INT_ORIENT_0_ADDR, data_array, 2, dev); + if(rslt == BMI160_OK) { + data = data_array[0]; + temp = data & ~BMI160_ORIENT_MODE_MASK; + + /* Adding Orientation mode */ + data = temp | ((orient_int_cfg->orient_mode) & BMI160_ORIENT_MODE_MASK); + temp = data & ~BMI160_ORIENT_BLOCK_MASK; + + /* Adding Orientation blocking */ + data = temp | ((orient_int_cfg->orient_blocking << 2) & BMI160_ORIENT_BLOCK_MASK); + temp = data & ~BMI160_ORIENT_HYST_MASK; + + /* Adding Orientation hysteresis */ + data = temp | ((orient_int_cfg->orient_hyst << 4) & BMI160_ORIENT_HYST_MASK); + data_array[0] = data; + data = data_array[1]; + temp = data & ~BMI160_ORIENT_THETA_MASK; + + /* Adding Orientation threshold */ + data = temp | ((orient_int_cfg->orient_theta) & BMI160_ORIENT_THETA_MASK); + temp = data & ~BMI160_ORIENT_UD_ENABLE; + + /* Adding Orient_ud_en */ + data = temp | ((orient_int_cfg->orient_ud_en << 6) & BMI160_ORIENT_UD_ENABLE); + temp = data & ~BMI160_AXES_EN_MASK; + + /* Adding axes_en */ + data = temp | ((orient_int_cfg->axes_ex << 7) & BMI160_AXES_EN_MASK); + data_array[1] = data; + + /* Writing data to INT_ORIENT 0 and INT_ORIENT 1 + * registers simultaneously */ + rslt = bmi160_set_regs(BMI160_INT_ORIENT_0_ADDR, data_array, 2, dev); + } + + return rslt; +} + +/*! + * @brief This API enables the flat interrupt. + */ +static int8_t enable_flat_int( + const struct bmi160_acc_flat_detect_int_cfg* flat_int, + const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data = 0; + uint8_t temp = 0; + + /* Enable flat interrupt in Int Enable 0 register */ + rslt = bmi160_get_regs(BMI160_INT_ENABLE_0_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + temp = data & ~BMI160_FLAT_INT_EN_MASK; + data = temp | ((flat_int->flat_en << 7) & BMI160_FLAT_INT_EN_MASK); + + /* write data to Int Enable 0 register */ + rslt = bmi160_set_regs(BMI160_INT_ENABLE_0_ADDR, &data, 1, dev); + } + + return rslt; +} + +/*! + * @brief This API configure the necessary setting of flat interrupt. + */ +static int8_t config_flat_int_settg( + const struct bmi160_acc_flat_detect_int_cfg* flat_int, + const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data = 0; + uint8_t temp = 0; + uint8_t data_array[2] = {0, 0}; + + /* Configuring INT_FLAT register */ + rslt = bmi160_get_regs(BMI160_INT_FLAT_0_ADDR, data_array, 2, dev); + if(rslt == BMI160_OK) { + data = data_array[0]; + temp = data & ~BMI160_FLAT_THRES_MASK; + + /* Adding flat theta */ + data = temp | ((flat_int->flat_theta) & BMI160_FLAT_THRES_MASK); + data_array[0] = data; + data = data_array[1]; + temp = data & ~BMI160_FLAT_HOLD_TIME_MASK; + + /* Adding flat hold time */ + data = temp | ((flat_int->flat_hold_time << 4) & BMI160_FLAT_HOLD_TIME_MASK); + temp = data & ~BMI160_FLAT_HYST_MASK; + + /* Adding flat hysteresis */ + data = temp | ((flat_int->flat_hy) & BMI160_FLAT_HYST_MASK); + data_array[1] = data; + + /* Writing data to INT_FLAT 0 and INT_FLAT 1 + * registers simultaneously */ + rslt = bmi160_set_regs(BMI160_INT_FLAT_0_ADDR, data_array, 2, dev); + } + + return rslt; +} + +/*! + * @brief This API enables the Low-g interrupt. + */ +static int8_t enable_low_g_int( + const struct bmi160_acc_low_g_int_cfg* low_g_int, + const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data = 0; + uint8_t temp = 0; + + /* Enable low-g interrupt in Int Enable 1 register */ + rslt = bmi160_get_regs(BMI160_INT_ENABLE_1_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + temp = data & ~BMI160_LOW_G_INT_EN_MASK; + data = temp | ((low_g_int->low_en << 3) & BMI160_LOW_G_INT_EN_MASK); + + /* write data to Int Enable 0 register */ + rslt = bmi160_set_regs(BMI160_INT_ENABLE_1_ADDR, &data, 1, dev); + } + + return rslt; +} + +/*! + * @brief This API configure the source of data(filter & pre-filter) + * for low-g interrupt. + */ +static int8_t config_low_g_data_src( + const struct bmi160_acc_low_g_int_cfg* low_g_int, + const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data = 0; + uint8_t temp = 0; + + /* Configure Int data 0 register to add source of interrupt */ + rslt = bmi160_get_regs(BMI160_INT_DATA_0_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + temp = data & ~BMI160_LOW_HIGH_SRC_INT_MASK; + data = temp | ((low_g_int->low_data_src << 7) & BMI160_LOW_HIGH_SRC_INT_MASK); + + /* Write data to Data 0 address */ + rslt = bmi160_set_regs(BMI160_INT_DATA_0_ADDR, &data, 1, dev); + } + + return rslt; +} + +/*! + * @brief This API configure the necessary setting of low-g interrupt. + */ +static int8_t config_low_g_int_settg( + const struct bmi160_acc_low_g_int_cfg* low_g_int, + const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t temp = 0; + uint8_t data_array[3] = {0, 0, 0}; + + /* Configuring INT_LOWHIGH register for low-g interrupt */ + rslt = bmi160_get_regs(BMI160_INT_LOWHIGH_2_ADDR, &data_array[2], 1, dev); + if(rslt == BMI160_OK) { + temp = data_array[2] & ~BMI160_LOW_G_HYST_MASK; + + /* Adding low-g hysteresis */ + data_array[2] = temp | (low_g_int->low_hyst & BMI160_LOW_G_HYST_MASK); + temp = data_array[2] & ~BMI160_LOW_G_LOW_MODE_MASK; + + /* Adding low-mode */ + data_array[2] = temp | ((low_g_int->low_mode << 2) & BMI160_LOW_G_LOW_MODE_MASK); + + /* Adding low-g threshold */ + data_array[1] = low_g_int->low_thres; + + /* Adding low-g interrupt delay */ + data_array[0] = low_g_int->low_dur; + + /* Writing data to INT_LOWHIGH 0,1,2 registers simultaneously*/ + rslt = bmi160_set_regs(BMI160_INT_LOWHIGH_0_ADDR, data_array, 3, dev); + } + + return rslt; +} + +/*! + * @brief This API enables the high-g interrupt. + */ +static int8_t enable_high_g_int( + const struct bmi160_acc_high_g_int_cfg* high_g_int_cfg, + const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data = 0; + uint8_t temp = 0; + + /* Enable low-g interrupt in Int Enable 1 register */ + rslt = bmi160_get_regs(BMI160_INT_ENABLE_1_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + /* Adding high-g X-axis */ + temp = data & ~BMI160_HIGH_G_X_INT_EN_MASK; + data = temp | (high_g_int_cfg->high_g_x & BMI160_HIGH_G_X_INT_EN_MASK); + + /* Adding high-g Y-axis */ + temp = data & ~BMI160_HIGH_G_Y_INT_EN_MASK; + data = temp | ((high_g_int_cfg->high_g_y << 1) & BMI160_HIGH_G_Y_INT_EN_MASK); + + /* Adding high-g Z-axis */ + temp = data & ~BMI160_HIGH_G_Z_INT_EN_MASK; + data = temp | ((high_g_int_cfg->high_g_z << 2) & BMI160_HIGH_G_Z_INT_EN_MASK); + + /* write data to Int Enable 0 register */ + rslt = bmi160_set_regs(BMI160_INT_ENABLE_1_ADDR, &data, 1, dev); + } + + return rslt; +} + +/*! + * @brief This API configure the source of data(filter & pre-filter) + * for high-g interrupt. + */ +static int8_t config_high_g_data_src( + const struct bmi160_acc_high_g_int_cfg* high_g_int_cfg, + const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data = 0; + uint8_t temp = 0; + + /* Configure Int data 0 register to add source of interrupt */ + rslt = bmi160_get_regs(BMI160_INT_DATA_0_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + temp = data & ~BMI160_LOW_HIGH_SRC_INT_MASK; + data = temp | ((high_g_int_cfg->high_data_src << 7) & BMI160_LOW_HIGH_SRC_INT_MASK); + + /* Write data to Data 0 address */ + rslt = bmi160_set_regs(BMI160_INT_DATA_0_ADDR, &data, 1, dev); + } + + return rslt; +} + +/*! + * @brief This API configure the necessary setting of high-g interrupt. + */ +static int8_t config_high_g_int_settg( + const struct bmi160_acc_high_g_int_cfg* high_g_int_cfg, + const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t temp = 0; + uint8_t data_array[3] = {0, 0, 0}; + + rslt = bmi160_get_regs(BMI160_INT_LOWHIGH_2_ADDR, &data_array[0], 1, dev); + if(rslt == BMI160_OK) { + temp = data_array[0] & ~BMI160_HIGH_G_HYST_MASK; + + /* Adding high-g hysteresis */ + data_array[0] = temp | ((high_g_int_cfg->high_hy << 6) & BMI160_HIGH_G_HYST_MASK); + + /* Adding high-g duration */ + data_array[1] = high_g_int_cfg->high_dur; + + /* Adding high-g threshold */ + data_array[2] = high_g_int_cfg->high_thres; + rslt = bmi160_set_regs(BMI160_INT_LOWHIGH_2_ADDR, data_array, 3, dev); + } + + return rslt; +} + +/*! + * @brief This API configure the behavioural setting of interrupt pin. + */ +static int8_t + config_int_out_ctrl(const struct bmi160_int_settg* int_config, const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t temp = 0; + uint8_t data = 0; + + /* Configuration of output interrupt signals on pins INT1 and INT2 are + * done in BMI160_INT_OUT_CTRL_ADDR register*/ + rslt = bmi160_get_regs(BMI160_INT_OUT_CTRL_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + /* updating the interrupt pin structure to local structure */ + const struct bmi160_int_pin_settg* intr_pin_sett = &(int_config->int_pin_settg); + + /* Configuring channel 1 */ + if(int_config->int_channel == BMI160_INT_CHANNEL_1) { + /* Output enable */ + temp = data & ~BMI160_INT1_OUTPUT_EN_MASK; + data = temp | ((intr_pin_sett->output_en << 3) & BMI160_INT1_OUTPUT_EN_MASK); + + /* Output mode */ + temp = data & ~BMI160_INT1_OUTPUT_MODE_MASK; + data = temp | ((intr_pin_sett->output_mode << 2) & BMI160_INT1_OUTPUT_MODE_MASK); + + /* Output type */ + temp = data & ~BMI160_INT1_OUTPUT_TYPE_MASK; + data = temp | ((intr_pin_sett->output_type << 1) & BMI160_INT1_OUTPUT_TYPE_MASK); + + /* edge control */ + temp = data & ~BMI160_INT1_EDGE_CTRL_MASK; + data = temp | ((intr_pin_sett->edge_ctrl) & BMI160_INT1_EDGE_CTRL_MASK); + } else { + /* Configuring channel 2 */ + /* Output enable */ + temp = data & ~BMI160_INT2_OUTPUT_EN_MASK; + data = temp | ((intr_pin_sett->output_en << 7) & BMI160_INT2_OUTPUT_EN_MASK); + + /* Output mode */ + temp = data & ~BMI160_INT2_OUTPUT_MODE_MASK; + data = temp | ((intr_pin_sett->output_mode << 6) & BMI160_INT2_OUTPUT_MODE_MASK); + + /* Output type */ + temp = data & ~BMI160_INT2_OUTPUT_TYPE_MASK; + data = temp | ((intr_pin_sett->output_type << 5) & BMI160_INT2_OUTPUT_TYPE_MASK); + + /* edge control */ + temp = data & ~BMI160_INT2_EDGE_CTRL_MASK; + data = temp | ((intr_pin_sett->edge_ctrl << 4) & BMI160_INT2_EDGE_CTRL_MASK); + } + + rslt = bmi160_set_regs(BMI160_INT_OUT_CTRL_ADDR, &data, 1, dev); + } + + return rslt; +} + +/*! + * @brief This API configure the mode(input enable, latch or non-latch) of interrupt pin. + */ +static int8_t + config_int_latch(const struct bmi160_int_settg* int_config, const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t temp = 0; + uint8_t data = 0; + + /* Configuration of latch on pins INT1 and INT2 are done in + * BMI160_INT_LATCH_ADDR register*/ + rslt = bmi160_get_regs(BMI160_INT_LATCH_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + /* updating the interrupt pin structure to local structure */ + const struct bmi160_int_pin_settg* intr_pin_sett = &(int_config->int_pin_settg); + if(int_config->int_channel == BMI160_INT_CHANNEL_1) { + /* Configuring channel 1 */ + /* Input enable */ + temp = data & ~BMI160_INT1_INPUT_EN_MASK; + data = temp | ((intr_pin_sett->input_en << 4) & BMI160_INT1_INPUT_EN_MASK); + } else { + /* Configuring channel 2 */ + /* Input enable */ + temp = data & ~BMI160_INT2_INPUT_EN_MASK; + data = temp | ((intr_pin_sett->input_en << 5) & BMI160_INT2_INPUT_EN_MASK); + } + + /* In case of latch interrupt,update the latch duration */ + + /* Latching holds the interrupt for the amount of latch + * duration time */ + temp = data & ~BMI160_INT_LATCH_MASK; + data = temp | (intr_pin_sett->latch_dur & BMI160_INT_LATCH_MASK); + + /* OUT_CTRL_INT and LATCH_INT address lie consecutively, + * hence writing data to respective registers at one go */ + rslt = bmi160_set_regs(BMI160_INT_LATCH_ADDR, &data, 1, dev); + } + + return rslt; +} + +/*! + * @brief This API performs the self test for accelerometer of BMI160 + */ +static int8_t perform_accel_self_test(struct bmi160_dev* dev) { + int8_t rslt; + struct bmi160_sensor_data accel_pos, accel_neg; + + /* Enable Gyro self test bit */ + rslt = enable_accel_self_test(dev); + if(rslt == BMI160_OK) { + /* Perform accel self test with positive excitation */ + rslt = accel_self_test_positive_excitation(&accel_pos, dev); + if(rslt == BMI160_OK) { + /* Perform accel self test with negative excitation */ + rslt = accel_self_test_negative_excitation(&accel_neg, dev); + if(rslt == BMI160_OK) { + /* Validate the self test result */ + rslt = validate_accel_self_test(&accel_pos, &accel_neg); + } + } + } + + return rslt; +} + +/*! + * @brief This API enables to perform the accel self test by setting proper + * configurations to facilitate accel self test + */ +static int8_t enable_accel_self_test(struct bmi160_dev* dev) { + int8_t rslt; + uint8_t reg_data; + + /* Set the Accel power mode as normal mode */ + dev->accel_cfg.power = BMI160_ACCEL_NORMAL_MODE; + + /* Set the sensor range configuration as 8G */ + dev->accel_cfg.range = BMI160_ACCEL_RANGE_8G; + rslt = bmi160_set_sens_conf(dev); + if(rslt == BMI160_OK) { + /* Accel configurations are set to facilitate self test + * acc_odr - 1600Hz ; acc_bwp = 2 ; acc_us = 0 */ + reg_data = BMI160_ACCEL_SELF_TEST_CONFIG; + rslt = bmi160_set_regs(BMI160_ACCEL_CONFIG_ADDR, ®_data, 1, dev); + } + + return rslt; +} + +/*! + * @brief This API performs accel self test with positive excitation + */ +static int8_t accel_self_test_positive_excitation( + struct bmi160_sensor_data* accel_pos, + const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t reg_data; + + /* Enable accel self test with positive self-test excitation + * and with amplitude of deflection set as high */ + reg_data = BMI160_ACCEL_SELF_TEST_POSITIVE_EN; + rslt = bmi160_set_regs(BMI160_SELF_TEST_ADDR, ®_data, 1, dev); + if(rslt == BMI160_OK) { + /* Read the data after a delay of 50ms - refer datasheet 2.8.1 accel self test*/ + dev->delay_ms(BMI160_ACCEL_SELF_TEST_DELAY); + rslt = bmi160_get_sensor_data(BMI160_ACCEL_ONLY, accel_pos, NULL, dev); + } + + return rslt; +} + +/*! + * @brief This API performs accel self test with negative excitation + */ +static int8_t accel_self_test_negative_excitation( + struct bmi160_sensor_data* accel_neg, + const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t reg_data; + + /* Enable accel self test with negative self-test excitation + * and with amplitude of deflection set as high */ + reg_data = BMI160_ACCEL_SELF_TEST_NEGATIVE_EN; + rslt = bmi160_set_regs(BMI160_SELF_TEST_ADDR, ®_data, 1, dev); + if(rslt == BMI160_OK) { + /* Read the data after a delay of 50ms */ + dev->delay_ms(BMI160_ACCEL_SELF_TEST_DELAY); + rslt = bmi160_get_sensor_data(BMI160_ACCEL_ONLY, accel_neg, NULL, dev); + } + + return rslt; +} + +/*! + * @brief This API validates the accel self test results + */ +static int8_t validate_accel_self_test( + const struct bmi160_sensor_data* accel_pos, + const struct bmi160_sensor_data* accel_neg) { + int8_t rslt; + + /* Validate the results of self test */ + if(((accel_neg->x - accel_pos->x) > BMI160_ACCEL_SELF_TEST_LIMIT) && + ((accel_neg->y - accel_pos->y) > BMI160_ACCEL_SELF_TEST_LIMIT) && + ((accel_neg->z - accel_pos->z) > BMI160_ACCEL_SELF_TEST_LIMIT)) { + /* Self test pass condition */ + rslt = BMI160_OK; + } else { + rslt = BMI160_W_ACCEl_SELF_TEST_FAIL; + } + + return rslt; +} + +/*! + * @brief This API performs the self test for gyroscope of BMI160 + */ +static int8_t perform_gyro_self_test(const struct bmi160_dev* dev) { + int8_t rslt; + + /* Enable Gyro self test bit */ + rslt = enable_gyro_self_test(dev); + if(rslt == BMI160_OK) { + /* Validate the gyro self test a delay of 50ms */ + dev->delay_ms(50); + + /* Validate the gyro self test results */ + rslt = validate_gyro_self_test(dev); + } + + return rslt; +} + +/*! + * @brief This API enables the self test bit to trigger self test for Gyro + */ +static int8_t enable_gyro_self_test(const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t reg_data; + + /* Enable the Gyro self test bit to trigger the self test */ + rslt = bmi160_get_regs(BMI160_SELF_TEST_ADDR, ®_data, 1, dev); + if(rslt == BMI160_OK) { + reg_data = BMI160_SET_BITS(reg_data, BMI160_GYRO_SELF_TEST, 1); + rslt = bmi160_set_regs(BMI160_SELF_TEST_ADDR, ®_data, 1, dev); + if(rslt == BMI160_OK) { + /* Delay to enable gyro self test */ + dev->delay_ms(15); + } + } + + return rslt; +} + +/*! + * @brief This API validates the self test results of Gyro + */ +static int8_t validate_gyro_self_test(const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t reg_data; + + /* Validate the Gyro self test result */ + rslt = bmi160_get_regs(BMI160_STATUS_ADDR, ®_data, 1, dev); + if(rslt == BMI160_OK) { + reg_data = BMI160_GET_BITS(reg_data, BMI160_GYRO_SELF_TEST_STATUS); + if(reg_data == BMI160_ENABLE) { + /* Gyro self test success case */ + rslt = BMI160_OK; + } else { + rslt = BMI160_W_GYRO_SELF_TEST_FAIL; + } + } + + return rslt; +} + +/*! + * @brief This API sets FIFO full interrupt of the sensor.This interrupt + * occurs when the FIFO is full and the next full data sample would cause + * a FIFO overflow, which may delete the old samples. + */ +static int8_t + set_fifo_full_int(const struct bmi160_int_settg* int_config, const struct bmi160_dev* dev) { + int8_t rslt = BMI160_OK; + + /* Null-pointer check */ + if((dev == NULL) || (dev->delay_ms == NULL)) { + rslt = BMI160_E_NULL_PTR; + } else { + /*enable the fifo full interrupt */ + rslt = enable_fifo_full_int(int_config, dev); + if(rslt == BMI160_OK) { + /* Configure Interrupt pins */ + rslt = set_intr_pin_config(int_config, dev); + if(rslt == BMI160_OK) { + rslt = map_hardware_interrupt(int_config, dev); + } + } + } + + return rslt; +} + +/*! + * @brief This enable the FIFO full interrupt engine. + */ +static int8_t + enable_fifo_full_int(const struct bmi160_int_settg* int_config, const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data = 0; + + rslt = bmi160_get_regs(BMI160_INT_ENABLE_1_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + data = BMI160_SET_BITS(data, BMI160_FIFO_FULL_INT, int_config->fifo_full_int_en); + + /* Writing data to INT ENABLE 1 Address */ + rslt = bmi160_set_regs(BMI160_INT_ENABLE_1_ADDR, &data, 1, dev); + } + + return rslt; +} + +/*! + * @brief This API sets FIFO watermark interrupt of the sensor.The FIFO + * watermark interrupt is fired, when the FIFO fill level is above a fifo + * watermark. + */ +static int8_t set_fifo_watermark_int( + const struct bmi160_int_settg* int_config, + const struct bmi160_dev* dev) { + int8_t rslt = BMI160_OK; + + if((dev == NULL) || (dev->delay_ms == NULL)) { + rslt = BMI160_E_NULL_PTR; + } else { + /* Enable fifo-watermark interrupt in Int Enable 1 register */ + rslt = enable_fifo_wtm_int(int_config, dev); + if(rslt == BMI160_OK) { + /* Configure Interrupt pins */ + rslt = set_intr_pin_config(int_config, dev); + if(rslt == BMI160_OK) { + rslt = map_hardware_interrupt(int_config, dev); + } + } + } + + return rslt; +} + +/*! + * @brief This enable the FIFO watermark interrupt engine. + */ +static int8_t + enable_fifo_wtm_int(const struct bmi160_int_settg* int_config, const struct bmi160_dev* dev) { + int8_t rslt; + uint8_t data = 0; + + rslt = bmi160_get_regs(BMI160_INT_ENABLE_1_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + data = BMI160_SET_BITS(data, BMI160_FIFO_WTM_INT, int_config->fifo_wtm_int_en); + + /* Writing data to INT ENABLE 1 Address */ + rslt = bmi160_set_regs(BMI160_INT_ENABLE_1_ADDR, &data, 1, dev); + } + + return rslt; +} + +/*! + * @brief This API is used to reset the FIFO related configurations + * in the fifo_frame structure. + */ +static void reset_fifo_data_structure(const struct bmi160_dev* dev) { + /*Prepare for next FIFO read by resetting FIFO's + * internal data structures*/ + dev->fifo->accel_byte_start_idx = 0; + dev->fifo->gyro_byte_start_idx = 0; + dev->fifo->aux_byte_start_idx = 0; + dev->fifo->sensor_time = 0; + dev->fifo->skipped_frame_count = 0; +} + +/*! + * @brief This API is used to read fifo_byte_counter value (i.e) + * current fill-level in Fifo buffer. + */ +static int8_t get_fifo_byte_counter(uint16_t* bytes_to_read, struct bmi160_dev const* dev) { + int8_t rslt = 0; + uint8_t data[2]; + uint8_t addr = BMI160_FIFO_LENGTH_ADDR; + + rslt |= bmi160_get_regs(addr, data, 2, dev); + data[1] = data[1] & BMI160_FIFO_BYTE_COUNTER_MASK; + + /* Available data in FIFO is stored in bytes_to_read*/ + *bytes_to_read = (((uint16_t)data[1] << 8) | ((uint16_t)data[0])); + + return rslt; +} + +/*! + * @brief This API is used to compute the number of bytes of accel FIFO data + * which is to be parsed in header-less mode + */ +static void get_accel_len_to_parse( + uint16_t* data_index, + uint16_t* data_read_length, + const uint8_t* acc_frame_count, + const struct bmi160_dev* dev) { + /* Data start index */ + *data_index = dev->fifo->accel_byte_start_idx; + if(dev->fifo->fifo_data_enable == BMI160_FIFO_A_ENABLE) { + *data_read_length = (*acc_frame_count) * BMI160_FIFO_A_LENGTH; + } else if(dev->fifo->fifo_data_enable == BMI160_FIFO_G_A_ENABLE) { + *data_read_length = (*acc_frame_count) * BMI160_FIFO_GA_LENGTH; + } else if(dev->fifo->fifo_data_enable == BMI160_FIFO_M_A_ENABLE) { + *data_read_length = (*acc_frame_count) * BMI160_FIFO_MA_LENGTH; + } else if(dev->fifo->fifo_data_enable == BMI160_FIFO_M_G_A_ENABLE) { + *data_read_length = (*acc_frame_count) * BMI160_FIFO_MGA_LENGTH; + } else { + /* When accel is not enabled ,there will be no accel data. + * so we update the data index as complete */ + *data_index = dev->fifo->length; + } + + if(*data_read_length > dev->fifo->length) { + /* Handling the case where more data is requested + * than that is available*/ + *data_read_length = dev->fifo->length; + } +} + +/*! + * @brief This API is used to parse the accelerometer data from the + * FIFO data in both header mode and header-less mode. + * It updates the idx value which is used to store the index of + * the current data byte which is parsed. + */ +static void unpack_accel_frame( + struct bmi160_sensor_data* acc, + uint16_t* idx, + uint8_t* acc_idx, + uint8_t frame_info, + const struct bmi160_dev* dev) { + switch(frame_info) { + case BMI160_FIFO_HEAD_A: + case BMI160_FIFO_A_ENABLE: + + /*Partial read, then skip the data*/ + if((*idx + BMI160_FIFO_A_LENGTH) > dev->fifo->length) { + /*Update the data index as complete*/ + *idx = dev->fifo->length; + break; + } + + /*Unpack the data array into the structure instance "acc" */ + unpack_accel_data(&acc[*acc_idx], *idx, dev); + + /*Move the data index*/ + *idx = *idx + BMI160_FIFO_A_LENGTH; + (*acc_idx)++; + break; + case BMI160_FIFO_HEAD_G_A: + case BMI160_FIFO_G_A_ENABLE: + + /*Partial read, then skip the data*/ + if((*idx + BMI160_FIFO_GA_LENGTH) > dev->fifo->length) { + /*Update the data index as complete*/ + *idx = dev->fifo->length; + break; + } + + /*Unpack the data array into structure instance "acc"*/ + unpack_accel_data(&acc[*acc_idx], *idx + BMI160_FIFO_G_LENGTH, dev); + + /*Move the data index*/ + *idx = *idx + BMI160_FIFO_GA_LENGTH; + (*acc_idx)++; + break; + case BMI160_FIFO_HEAD_M_A: + case BMI160_FIFO_M_A_ENABLE: + + /*Partial read, then skip the data*/ + if((*idx + BMI160_FIFO_MA_LENGTH) > dev->fifo->length) { + /*Update the data index as complete*/ + *idx = dev->fifo->length; + break; + } + + /*Unpack the data array into structure instance "acc"*/ + unpack_accel_data(&acc[*acc_idx], *idx + BMI160_FIFO_M_LENGTH, dev); + + /*Move the data index*/ + *idx = *idx + BMI160_FIFO_MA_LENGTH; + (*acc_idx)++; + break; + case BMI160_FIFO_HEAD_M_G_A: + case BMI160_FIFO_M_G_A_ENABLE: + + /*Partial read, then skip the data*/ + if((*idx + BMI160_FIFO_MGA_LENGTH) > dev->fifo->length) { + /*Update the data index as complete*/ + *idx = dev->fifo->length; + break; + } + + /*Unpack the data array into structure instance "acc"*/ + unpack_accel_data(&acc[*acc_idx], *idx + BMI160_FIFO_MG_LENGTH, dev); + + /*Move the data index*/ + *idx = *idx + BMI160_FIFO_MGA_LENGTH; + (*acc_idx)++; + break; + case BMI160_FIFO_HEAD_M: + case BMI160_FIFO_M_ENABLE: + (*idx) = (*idx) + BMI160_FIFO_M_LENGTH; + break; + case BMI160_FIFO_HEAD_G: + case BMI160_FIFO_G_ENABLE: + (*idx) = (*idx) + BMI160_FIFO_G_LENGTH; + break; + case BMI160_FIFO_HEAD_M_G: + case BMI160_FIFO_M_G_ENABLE: + (*idx) = (*idx) + BMI160_FIFO_MG_LENGTH; + break; + default: + break; + } +} + +/*! + * @brief This API is used to parse the accelerometer data from the + * FIFO data and store it in the instance of the structure bmi160_sensor_data. + */ +static void unpack_accel_data( + struct bmi160_sensor_data* accel_data, + uint16_t data_start_index, + const struct bmi160_dev* dev) { + uint16_t data_lsb; + uint16_t data_msb; + + /* Accel raw x data */ + data_lsb = dev->fifo->data[data_start_index++]; + data_msb = dev->fifo->data[data_start_index++]; + accel_data->x = (int16_t)((data_msb << 8) | data_lsb); + + /* Accel raw y data */ + data_lsb = dev->fifo->data[data_start_index++]; + data_msb = dev->fifo->data[data_start_index++]; + accel_data->y = (int16_t)((data_msb << 8) | data_lsb); + + /* Accel raw z data */ + data_lsb = dev->fifo->data[data_start_index++]; + data_msb = dev->fifo->data[data_start_index++]; + accel_data->z = (int16_t)((data_msb << 8) | data_lsb); +} + +/*! + * @brief This API is used to parse the accelerometer data from the + * FIFO data in header mode. + */ +static void extract_accel_header_mode( + struct bmi160_sensor_data* accel_data, + uint8_t* accel_length, + const struct bmi160_dev* dev) { + uint8_t frame_header = 0; + uint16_t data_index; + uint8_t accel_index = 0; + + for(data_index = dev->fifo->accel_byte_start_idx; data_index < dev->fifo->length;) { + /* extracting Frame header */ + frame_header = (dev->fifo->data[data_index] & BMI160_FIFO_TAG_INTR_MASK); + + /*Index is moved to next byte where the data is starting*/ + data_index++; + switch(frame_header) { + /* Accel frame */ + case BMI160_FIFO_HEAD_A: + case BMI160_FIFO_HEAD_M_A: + case BMI160_FIFO_HEAD_G_A: + case BMI160_FIFO_HEAD_M_G_A: + unpack_accel_frame(accel_data, &data_index, &accel_index, frame_header, dev); + break; + case BMI160_FIFO_HEAD_M: + move_next_frame(&data_index, BMI160_FIFO_M_LENGTH, dev); + break; + case BMI160_FIFO_HEAD_G: + move_next_frame(&data_index, BMI160_FIFO_G_LENGTH, dev); + break; + case BMI160_FIFO_HEAD_M_G: + move_next_frame(&data_index, BMI160_FIFO_MG_LENGTH, dev); + break; + + /* Sensor time frame */ + case BMI160_FIFO_HEAD_SENSOR_TIME: + unpack_sensortime_frame(&data_index, dev); + break; + + /* Skip frame */ + case BMI160_FIFO_HEAD_SKIP_FRAME: + unpack_skipped_frame(&data_index, dev); + break; + + /* Input config frame */ + case BMI160_FIFO_HEAD_INPUT_CONFIG: + move_next_frame(&data_index, 1, dev); + break; + case BMI160_FIFO_HEAD_OVER_READ: + + /* Update the data index as complete in case of Over read */ + data_index = dev->fifo->length; + break; + default: + break; + } + if(*accel_length == accel_index) { + /* Number of frames to read completed */ + break; + } + } + + /*Update number of accel data read*/ + *accel_length = accel_index; + + /*Update the accel frame index*/ + dev->fifo->accel_byte_start_idx = data_index; +} + +/*! + * @brief This API computes the number of bytes of gyro FIFO data + * which is to be parsed in header-less mode + */ +static void get_gyro_len_to_parse( + uint16_t* data_index, + uint16_t* data_read_length, + const uint8_t* gyro_frame_count, + const struct bmi160_dev* dev) { + /* Data start index */ + *data_index = dev->fifo->gyro_byte_start_idx; + if(dev->fifo->fifo_data_enable == BMI160_FIFO_G_ENABLE) { + *data_read_length = (*gyro_frame_count) * BMI160_FIFO_G_LENGTH; + } else if(dev->fifo->fifo_data_enable == BMI160_FIFO_G_A_ENABLE) { + *data_read_length = (*gyro_frame_count) * BMI160_FIFO_GA_LENGTH; + } else if(dev->fifo->fifo_data_enable == BMI160_FIFO_M_G_ENABLE) { + *data_read_length = (*gyro_frame_count) * BMI160_FIFO_MG_LENGTH; + } else if(dev->fifo->fifo_data_enable == BMI160_FIFO_M_G_A_ENABLE) { + *data_read_length = (*gyro_frame_count) * BMI160_FIFO_MGA_LENGTH; + } else { + /* When gyro is not enabled ,there will be no gyro data. + * so we update the data index as complete */ + *data_index = dev->fifo->length; + } + + if(*data_read_length > dev->fifo->length) { + /* Handling the case where more data is requested + * than that is available*/ + *data_read_length = dev->fifo->length; + } +} + +/*! + * @brief This API is used to parse the gyroscope's data from the + * FIFO data in both header mode and header-less mode. + * It updates the idx value which is used to store the index of + * the current data byte which is parsed. + */ +static void unpack_gyro_frame( + struct bmi160_sensor_data* gyro, + uint16_t* idx, + uint8_t* gyro_idx, + uint8_t frame_info, + const struct bmi160_dev* dev) { + switch(frame_info) { + case BMI160_FIFO_HEAD_G: + case BMI160_FIFO_G_ENABLE: + + /*Partial read, then skip the data*/ + if((*idx + BMI160_FIFO_G_LENGTH) > dev->fifo->length) { + /*Update the data index as complete*/ + *idx = dev->fifo->length; + break; + } + + /*Unpack the data array into structure instance "gyro"*/ + unpack_gyro_data(&gyro[*gyro_idx], *idx, dev); + + /*Move the data index*/ + (*idx) = (*idx) + BMI160_FIFO_G_LENGTH; + (*gyro_idx)++; + break; + case BMI160_FIFO_HEAD_G_A: + case BMI160_FIFO_G_A_ENABLE: + + /*Partial read, then skip the data*/ + if((*idx + BMI160_FIFO_GA_LENGTH) > dev->fifo->length) { + /*Update the data index as complete*/ + *idx = dev->fifo->length; + break; + } + + /* Unpack the data array into structure instance "gyro" */ + unpack_gyro_data(&gyro[*gyro_idx], *idx, dev); + + /* Move the data index */ + *idx = *idx + BMI160_FIFO_GA_LENGTH; + (*gyro_idx)++; + break; + case BMI160_FIFO_HEAD_M_G_A: + case BMI160_FIFO_M_G_A_ENABLE: + + /*Partial read, then skip the data*/ + if((*idx + BMI160_FIFO_MGA_LENGTH) > dev->fifo->length) { + /*Update the data index as complete*/ + *idx = dev->fifo->length; + break; + } + + /*Unpack the data array into structure instance "gyro"*/ + unpack_gyro_data(&gyro[*gyro_idx], *idx + BMI160_FIFO_M_LENGTH, dev); + + /*Move the data index*/ + *idx = *idx + BMI160_FIFO_MGA_LENGTH; + (*gyro_idx)++; + break; + case BMI160_FIFO_HEAD_M_A: + case BMI160_FIFO_M_A_ENABLE: + + /* Move the data index */ + *idx = *idx + BMI160_FIFO_MA_LENGTH; + break; + case BMI160_FIFO_HEAD_M: + case BMI160_FIFO_M_ENABLE: + (*idx) = (*idx) + BMI160_FIFO_M_LENGTH; + break; + case BMI160_FIFO_HEAD_M_G: + case BMI160_FIFO_M_G_ENABLE: + + /*Partial read, then skip the data*/ + if((*idx + BMI160_FIFO_MG_LENGTH) > dev->fifo->length) { + /*Update the data index as complete*/ + *idx = dev->fifo->length; + break; + } + + /*Unpack the data array into structure instance "gyro"*/ + unpack_gyro_data(&gyro[*gyro_idx], *idx + BMI160_FIFO_M_LENGTH, dev); + + /*Move the data index*/ + (*idx) = (*idx) + BMI160_FIFO_MG_LENGTH; + (*gyro_idx)++; + break; + case BMI160_FIFO_HEAD_A: + case BMI160_FIFO_A_ENABLE: + + /*Move the data index*/ + *idx = *idx + BMI160_FIFO_A_LENGTH; + break; + default: + break; + } +} + +/*! + * @brief This API is used to parse the gyro data from the + * FIFO data and store it in the instance of the structure bmi160_sensor_data. + */ +static void unpack_gyro_data( + struct bmi160_sensor_data* gyro_data, + uint16_t data_start_index, + const struct bmi160_dev* dev) { + uint16_t data_lsb; + uint16_t data_msb; + + /* Gyro raw x data */ + data_lsb = dev->fifo->data[data_start_index++]; + data_msb = dev->fifo->data[data_start_index++]; + gyro_data->x = (int16_t)((data_msb << 8) | data_lsb); + + /* Gyro raw y data */ + data_lsb = dev->fifo->data[data_start_index++]; + data_msb = dev->fifo->data[data_start_index++]; + gyro_data->y = (int16_t)((data_msb << 8) | data_lsb); + + /* Gyro raw z data */ + data_lsb = dev->fifo->data[data_start_index++]; + data_msb = dev->fifo->data[data_start_index++]; + gyro_data->z = (int16_t)((data_msb << 8) | data_lsb); +} + +/*! + * @brief This API is used to parse the gyro data from the + * FIFO data in header mode. + */ +static void extract_gyro_header_mode( + struct bmi160_sensor_data* gyro_data, + uint8_t* gyro_length, + const struct bmi160_dev* dev) { + uint8_t frame_header = 0; + uint16_t data_index; + uint8_t gyro_index = 0; + + for(data_index = dev->fifo->gyro_byte_start_idx; data_index < dev->fifo->length;) { + /* extracting Frame header */ + frame_header = (dev->fifo->data[data_index] & BMI160_FIFO_TAG_INTR_MASK); + + /*Index is moved to next byte where the data is starting*/ + data_index++; + switch(frame_header) { + /* GYRO frame */ + case BMI160_FIFO_HEAD_G: + case BMI160_FIFO_HEAD_G_A: + case BMI160_FIFO_HEAD_M_G: + case BMI160_FIFO_HEAD_M_G_A: + unpack_gyro_frame(gyro_data, &data_index, &gyro_index, frame_header, dev); + break; + case BMI160_FIFO_HEAD_A: + move_next_frame(&data_index, BMI160_FIFO_A_LENGTH, dev); + break; + case BMI160_FIFO_HEAD_M: + move_next_frame(&data_index, BMI160_FIFO_M_LENGTH, dev); + break; + case BMI160_FIFO_HEAD_M_A: + move_next_frame(&data_index, BMI160_FIFO_M_LENGTH, dev); + break; + + /* Sensor time frame */ + case BMI160_FIFO_HEAD_SENSOR_TIME: + unpack_sensortime_frame(&data_index, dev); + break; + + /* Skip frame */ + case BMI160_FIFO_HEAD_SKIP_FRAME: + unpack_skipped_frame(&data_index, dev); + break; + + /* Input config frame */ + case BMI160_FIFO_HEAD_INPUT_CONFIG: + move_next_frame(&data_index, 1, dev); + break; + case BMI160_FIFO_HEAD_OVER_READ: + + /* Update the data index as complete in case of over read */ + data_index = dev->fifo->length; + break; + default: + break; + } + if(*gyro_length == gyro_index) { + /*Number of frames to read completed*/ + break; + } + } + + /*Update number of gyro data read*/ + *gyro_length = gyro_index; + + /*Update the gyro frame index*/ + dev->fifo->gyro_byte_start_idx = data_index; +} + +/*! + * @brief This API computes the number of bytes of aux FIFO data + * which is to be parsed in header-less mode + */ +static void get_aux_len_to_parse( + uint16_t* data_index, + uint16_t* data_read_length, + const uint8_t* aux_frame_count, + const struct bmi160_dev* dev) { + /* Data start index */ + *data_index = dev->fifo->gyro_byte_start_idx; + if(dev->fifo->fifo_data_enable == BMI160_FIFO_M_ENABLE) { + *data_read_length = (*aux_frame_count) * BMI160_FIFO_M_LENGTH; + } else if(dev->fifo->fifo_data_enable == BMI160_FIFO_M_A_ENABLE) { + *data_read_length = (*aux_frame_count) * BMI160_FIFO_MA_LENGTH; + } else if(dev->fifo->fifo_data_enable == BMI160_FIFO_M_G_ENABLE) { + *data_read_length = (*aux_frame_count) * BMI160_FIFO_MG_LENGTH; + } else if(dev->fifo->fifo_data_enable == BMI160_FIFO_M_G_A_ENABLE) { + *data_read_length = (*aux_frame_count) * BMI160_FIFO_MGA_LENGTH; + } else { + /* When aux is not enabled ,there will be no aux data. + * so we update the data index as complete */ + *data_index = dev->fifo->length; + } + + if(*data_read_length > dev->fifo->length) { + /* Handling the case where more data is requested + * than that is available */ + *data_read_length = dev->fifo->length; + } +} + +/*! + * @brief This API is used to parse the aux's data from the + * FIFO data in both header mode and header-less mode. + * It updates the idx value which is used to store the index of + * the current data byte which is parsed + */ +static void unpack_aux_frame( + struct bmi160_aux_data* aux_data, + uint16_t* idx, + uint8_t* aux_index, + uint8_t frame_info, + const struct bmi160_dev* dev) { + switch(frame_info) { + case BMI160_FIFO_HEAD_M: + case BMI160_FIFO_M_ENABLE: + + /* Partial read, then skip the data */ + if((*idx + BMI160_FIFO_M_LENGTH) > dev->fifo->length) { + /* Update the data index as complete */ + *idx = dev->fifo->length; + break; + } + + /* Unpack the data array into structure instance */ + unpack_aux_data(&aux_data[*aux_index], *idx, dev); + + /* Move the data index */ + *idx = *idx + BMI160_FIFO_M_LENGTH; + (*aux_index)++; + break; + case BMI160_FIFO_HEAD_M_A: + case BMI160_FIFO_M_A_ENABLE: + + /* Partial read, then skip the data */ + if((*idx + BMI160_FIFO_MA_LENGTH) > dev->fifo->length) { + /* Update the data index as complete */ + *idx = dev->fifo->length; + break; + } + + /* Unpack the data array into structure instance */ + unpack_aux_data(&aux_data[*aux_index], *idx, dev); + + /* Move the data index */ + *idx = *idx + BMI160_FIFO_MA_LENGTH; + (*aux_index)++; + break; + case BMI160_FIFO_HEAD_M_G: + case BMI160_FIFO_M_G_ENABLE: + + /* Partial read, then skip the data */ + if((*idx + BMI160_FIFO_MG_LENGTH) > dev->fifo->length) { + /* Update the data index as complete */ + *idx = dev->fifo->length; + break; + } + + /* Unpack the data array into structure instance */ + unpack_aux_data(&aux_data[*aux_index], *idx, dev); + + /* Move the data index */ + (*idx) = (*idx) + BMI160_FIFO_MG_LENGTH; + (*aux_index)++; + break; + case BMI160_FIFO_HEAD_M_G_A: + case BMI160_FIFO_M_G_A_ENABLE: + + /*Partial read, then skip the data*/ + if((*idx + BMI160_FIFO_MGA_LENGTH) > dev->fifo->length) { + /* Update the data index as complete */ + *idx = dev->fifo->length; + break; + } + + /* Unpack the data array into structure instance */ + unpack_aux_data(&aux_data[*aux_index], *idx, dev); + + /*Move the data index*/ + *idx = *idx + BMI160_FIFO_MGA_LENGTH; + (*aux_index)++; + break; + case BMI160_FIFO_HEAD_G: + case BMI160_FIFO_G_ENABLE: + + /* Move the data index */ + (*idx) = (*idx) + BMI160_FIFO_G_LENGTH; + break; + case BMI160_FIFO_HEAD_G_A: + case BMI160_FIFO_G_A_ENABLE: + + /* Move the data index */ + *idx = *idx + BMI160_FIFO_GA_LENGTH; + break; + case BMI160_FIFO_HEAD_A: + case BMI160_FIFO_A_ENABLE: + + /* Move the data index */ + *idx = *idx + BMI160_FIFO_A_LENGTH; + break; + default: + break; + } +} + +/*! + * @brief This API is used to parse the aux data from the + * FIFO data and store it in the instance of the structure bmi160_aux_data. + */ +static void unpack_aux_data( + struct bmi160_aux_data* aux_data, + uint16_t data_start_index, + const struct bmi160_dev* dev) { + /* Aux data bytes */ + aux_data->data[0] = dev->fifo->data[data_start_index++]; + aux_data->data[1] = dev->fifo->data[data_start_index++]; + aux_data->data[2] = dev->fifo->data[data_start_index++]; + aux_data->data[3] = dev->fifo->data[data_start_index++]; + aux_data->data[4] = dev->fifo->data[data_start_index++]; + aux_data->data[5] = dev->fifo->data[data_start_index++]; + aux_data->data[6] = dev->fifo->data[data_start_index++]; + aux_data->data[7] = dev->fifo->data[data_start_index++]; +} + +/*! + * @brief This API is used to parse the aux data from the + * FIFO data in header mode. + */ +static void extract_aux_header_mode( + struct bmi160_aux_data* aux_data, + uint8_t* aux_length, + const struct bmi160_dev* dev) { + uint8_t frame_header = 0; + uint16_t data_index; + uint8_t aux_index = 0; + + for(data_index = dev->fifo->aux_byte_start_idx; data_index < dev->fifo->length;) { + /* extracting Frame header */ + frame_header = (dev->fifo->data[data_index] & BMI160_FIFO_TAG_INTR_MASK); + + /*Index is moved to next byte where the data is starting*/ + data_index++; + switch(frame_header) { + /* Aux frame */ + case BMI160_FIFO_HEAD_M: + case BMI160_FIFO_HEAD_M_A: + case BMI160_FIFO_HEAD_M_G: + case BMI160_FIFO_HEAD_M_G_A: + unpack_aux_frame(aux_data, &data_index, &aux_index, frame_header, dev); + break; + case BMI160_FIFO_HEAD_G: + move_next_frame(&data_index, BMI160_FIFO_G_LENGTH, dev); + break; + case BMI160_FIFO_HEAD_G_A: + move_next_frame(&data_index, BMI160_FIFO_GA_LENGTH, dev); + break; + case BMI160_FIFO_HEAD_A: + move_next_frame(&data_index, BMI160_FIFO_A_LENGTH, dev); + break; + + /* Sensor time frame */ + case BMI160_FIFO_HEAD_SENSOR_TIME: + unpack_sensortime_frame(&data_index, dev); + break; + + /* Skip frame */ + case BMI160_FIFO_HEAD_SKIP_FRAME: + unpack_skipped_frame(&data_index, dev); + break; + + /* Input config frame */ + case BMI160_FIFO_HEAD_INPUT_CONFIG: + move_next_frame(&data_index, 1, dev); + break; + case BMI160_FIFO_HEAD_OVER_READ: + + /* Update the data index as complete in case + * of over read */ + data_index = dev->fifo->length; + break; + default: + + /* Update the data index as complete in case of + * getting other headers like 0x00 */ + data_index = dev->fifo->length; + break; + } + if(*aux_length == aux_index) { + /*Number of frames to read completed*/ + break; + } + } + + /* Update number of aux data read */ + *aux_length = aux_index; + + /* Update the aux frame index */ + dev->fifo->aux_byte_start_idx = data_index; +} + +/*! + * @brief This API checks the presence of non-valid frames in the read fifo data. + */ +static void check_frame_validity(uint16_t* data_index, const struct bmi160_dev* dev) { + if((*data_index + 2) < dev->fifo->length) { + /* Check if FIFO is empty */ + if((dev->fifo->data[*data_index] == FIFO_CONFIG_MSB_CHECK) && + (dev->fifo->data[*data_index + 1] == FIFO_CONFIG_LSB_CHECK)) { + /*Update the data index as complete*/ + *data_index = dev->fifo->length; + } + } +} + +/*! + * @brief This API is used to move the data index ahead of the + * current_frame_length parameter when unnecessary FIFO data appears while + * extracting the user specified data. + */ +static void move_next_frame( + uint16_t* data_index, + uint8_t current_frame_length, + const struct bmi160_dev* dev) { + /*Partial read, then move the data index to last data*/ + if((*data_index + current_frame_length) > dev->fifo->length) { + /*Update the data index as complete*/ + *data_index = dev->fifo->length; + } else { + /*Move the data index to next frame*/ + *data_index = *data_index + current_frame_length; + } +} + +/*! + * @brief This API is used to parse and store the sensor time from the + * FIFO data in the structure instance dev. + */ +static void unpack_sensortime_frame(uint16_t* data_index, const struct bmi160_dev* dev) { + uint32_t sensor_time_byte3 = 0; + uint16_t sensor_time_byte2 = 0; + uint8_t sensor_time_byte1 = 0; + + /*Partial read, then move the data index to last data*/ + if((*data_index + BMI160_SENSOR_TIME_LENGTH) > dev->fifo->length) { + /*Update the data index as complete*/ + *data_index = dev->fifo->length; + } else { + sensor_time_byte3 = dev->fifo->data[(*data_index) + BMI160_SENSOR_TIME_MSB_BYTE] << 16; + sensor_time_byte2 = dev->fifo->data[(*data_index) + BMI160_SENSOR_TIME_XLSB_BYTE] << 8; + sensor_time_byte1 = dev->fifo->data[(*data_index)]; + + /* Sensor time */ + dev->fifo->sensor_time = + (uint32_t)(sensor_time_byte3 | sensor_time_byte2 | sensor_time_byte1); + *data_index = (*data_index) + BMI160_SENSOR_TIME_LENGTH; + } +} + +/*! + * @brief This API is used to parse and store the skipped_frame_count from + * the FIFO data in the structure instance dev. + */ +static void unpack_skipped_frame(uint16_t* data_index, const struct bmi160_dev* dev) { + /*Partial read, then move the data index to last data*/ + if(*data_index >= dev->fifo->length) { + /*Update the data index as complete*/ + *data_index = dev->fifo->length; + } else { + dev->fifo->skipped_frame_count = dev->fifo->data[*data_index]; + + /*Move the data index*/ + *data_index = (*data_index) + 1; + } +} + +/*! + * @brief This API is used to get the FOC status from the sensor + */ +static int8_t get_foc_status(uint8_t* foc_status, struct bmi160_dev const* dev) { + int8_t rslt; + uint8_t data; + + /* Read the FOC status from sensor */ + rslt = bmi160_get_regs(BMI160_STATUS_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + /* Get the foc_status bit */ + *foc_status = BMI160_GET_BITS(data, BMI160_FOC_STATUS); + } + + return rslt; +} + +/*! + * @brief This API is used to configure the offset enable bits in the sensor + */ +static int8_t + configure_offset_enable(const struct bmi160_foc_conf* foc_conf, struct bmi160_dev const* dev) { + int8_t rslt; + uint8_t data; + + /* Null-pointer check */ + rslt = null_ptr_check(dev); + if(rslt != BMI160_OK) { + rslt = BMI160_E_NULL_PTR; + } else { + /* Read the FOC config from the sensor */ + rslt = bmi160_get_regs(BMI160_OFFSET_CONF_ADDR, &data, 1, dev); + if(rslt == BMI160_OK) { + /* Set the offset enable/disable for gyro */ + data = BMI160_SET_BITS(data, BMI160_GYRO_OFFSET_EN, foc_conf->gyro_off_en); + + /* Set the offset enable/disable for accel */ + data = BMI160_SET_BITS(data, BMI160_ACCEL_OFFSET_EN, foc_conf->acc_off_en); + + /* Set the offset config in the sensor */ + rslt = bmi160_set_regs(BMI160_OFFSET_CONF_ADDR, &data, 1, dev); + } + } + + return rslt; +} + +static int8_t trigger_foc(struct bmi160_offsets* offset, struct bmi160_dev const* dev) { + int8_t rslt; + uint8_t foc_status = BMI160_ENABLE; + uint8_t cmd = BMI160_START_FOC_CMD; + uint8_t timeout = 0; + uint8_t data_array[20]; + + /* Start the FOC process */ + rslt = bmi160_set_regs(BMI160_COMMAND_REG_ADDR, &cmd, 1, dev); + if(rslt == BMI160_OK) { + /* Check the FOC status*/ + rslt = get_foc_status(&foc_status, dev); + + if((rslt != BMI160_OK) || (foc_status != BMI160_ENABLE)) { + while((foc_status != BMI160_ENABLE) && (timeout < 11)) { + /* Maximum time of 250ms is given in 10 + * steps of 25ms each - 250ms refer datasheet 2.9.1 */ + dev->delay_ms(25); + + /* Check the FOC status*/ + rslt = get_foc_status(&foc_status, dev); + timeout++; + } + + if((rslt == BMI160_OK) && (foc_status == BMI160_ENABLE)) { + /* Get offset values from sensor */ + rslt = bmi160_get_offsets(offset, dev); + } else { + /* FOC failure case */ + rslt = BMI160_E_FOC_FAILURE; + } + } + + if(rslt == BMI160_OK) { + /* Read registers 0x04-0x17 */ + rslt = bmi160_get_regs(BMI160_GYRO_DATA_ADDR, data_array, 20, dev); + } + } + + return rslt; +} diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/imu/bmi160.h b/Applications/Official/DEV_FW/source/airmouse/tracking/imu/bmi160.h new file mode 100644 index 000000000..d4d98094c --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/imu/bmi160.h @@ -0,0 +1,992 @@ +/** +* Copyright (c) 2021 Bosch Sensortec GmbH. All rights reserved. +* +* BSD-3-Clause +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* +* 3. Neither the name of the copyright holder nor the names of its +* contributors may be used to endorse or promote products derived from +* this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +* +* @file bmi160.h +* @date 2021-10-05 +* @version v3.9.2 +* +*/ + +/*! + * @defgroup bmi160 BMI160 + */ + +#ifndef BMI160_H_ +#define BMI160_H_ + +/*************************** C++ guard macro *****************************/ +#ifdef __cplusplus +extern "C" { +#endif + +#include "bmi160_defs.h" +#ifdef __KERNEL__ +#include +#else +#include +#include +#include +#endif + +/*********************** User function prototypes ************************/ + +/** + * \ingroup bmi160 + * \defgroup bmi160ApiInit Initialization + * @brief Initialize the sensor and device structure + */ + +/*! + * \ingroup bmi160ApiInit + * \page bmi160_api_bmi160_init bmi160_init + * \code + * int8_t bmi160_init(struct bmi160_dev *dev); + * \endcode + * @details This API is the entry point for sensor.It performs + * the selection of I2C/SPI read mechanism according to the + * selected interface and reads the chip-id of bmi160 sensor. + * + * @param[in,out] dev : Structure instance of bmi160_dev + * @note : Refer user guide for detailed info. + * + * @return Result of API execution status + * @retval Zero Success + * @retval Negative Error + */ +int8_t bmi160_init(struct bmi160_dev* dev); + +/** + * \ingroup bmi160 + * \defgroup bmi160ApiRegs Registers + * @brief Read data from the given register address of sensor + */ + +/*! + * \ingroup bmi160ApiRegs + * \page bmi160_api_bmi160_get_regs bmi160_get_regs + * \code + * int8_t bmi160_get_regs(uint8_t reg_addr, uint8_t *data, uint16_t len, const struct bmi160_dev *dev); + * \endcode + * @details This API reads the data from the given register address of sensor. + * + * @param[in] reg_addr : Register address from where the data to be read + * @param[out] data : Pointer to data buffer to store the read data. + * @param[in] len : No of bytes of data to be read. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @note For most of the registers auto address increment applies, with the + * exception of a few special registers, which trap the address. For e.g., + * Register address - 0x24(BMI160_FIFO_DATA_ADDR) + * + * @return Result of API execution status + * @retval Zero Success + * @retval Negative Error + */ +int8_t + bmi160_get_regs(uint8_t reg_addr, uint8_t* data, uint16_t len, const struct bmi160_dev* dev); + +/*! + * \ingroup bmi160ApiRegs + * \page bmi160_api_bmi160_set_regs bmi160_set_regs + * \code + * int8_t bmi160_set_regs(uint8_t reg_addr, uint8_t *data, uint16_t len, const struct bmi160_dev *dev); + * \endcode + * @details This API writes the given data to the register address + * of sensor. + * + * @param[in] reg_addr : Register address from where the data to be written. + * @param[in] data : Pointer to data buffer which is to be written + * in the sensor. + * @param[in] len : No of bytes of data to write.. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval Zero Success + * @retval Negative Error + */ +int8_t + bmi160_set_regs(uint8_t reg_addr, uint8_t* data, uint16_t len, const struct bmi160_dev* dev); + +/** + * \ingroup bmi160 + * \defgroup bmi160ApiSoftreset Soft reset + * @brief Perform soft reset of the sensor + */ + +/*! + * \ingroup bmi160ApiSoftreset + * \page bmi160_api_bmi160_soft_reset bmi160_soft_reset + * \code + * int8_t bmi160_soft_reset(struct bmi160_dev *dev); + * \endcode + * @details This API resets and restarts the device. + * All register values are overwritten with default parameters. + * + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval Zero Success + * @retval Negative Error + */ +int8_t bmi160_soft_reset(struct bmi160_dev* dev); + +/** + * \ingroup bmi160 + * \defgroup bmi160ApiConfig Configuration + * @brief Configuration of the sensor + */ + +/*! + * \ingroup bmi160ApiConfig + * \page bmi160_api_bmi160_set_sens_conf bmi160_set_sens_conf + * \code + * int8_t bmi160_set_sens_conf(struct bmi160_dev *dev); + * \endcode + * @details This API configures the power mode, range and bandwidth + * of sensor. + * + * @param[in] dev : Structure instance of bmi160_dev. + * @note : Refer user guide for detailed info. + * + * @return Result of API execution status + * @retval Zero Success + * @retval Negative Error + */ +int8_t bmi160_set_sens_conf(struct bmi160_dev* dev); + +/*! + * \ingroup bmi160ApiConfig + * \page bmi160_api_bmi160_get_sens_conf bmi160_get_sens_conf + * \code + * int8_t bmi160_get_sens_conf(struct bmi160_dev *dev); + * \endcode + * @details This API gets accel and gyro configurations. + * + * @param[out] dev : Structure instance of bmi160_dev. + * @note : Refer user guide for detailed info. + * + * @return Result of API execution status + * @retval Zero Success + * @retval Negative Error + */ +int8_t bmi160_get_sens_conf(struct bmi160_dev* dev); + +/** + * \ingroup bmi160 + * \defgroup bmi160ApiPowermode Power mode + * @brief Set / Get power mode of the sensor + */ + +/*! + * \ingroup bmi160ApiPowermode + * \page bmi160_api_bmi160_set_power_mode bmi160_set_power_mode + * \code + * int8_t bmi160_set_power_mode(struct bmi160_dev *dev); + * \endcode + * @details This API sets the power mode of the sensor. + * + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval Zero Success + * @retval Negative Error + */ +int8_t bmi160_set_power_mode(struct bmi160_dev* dev); + +/*! + * \ingroup bmi160ApiPowermode + * \page bmi160_api_bmi160_get_power_mode bmi160_get_power_mode + * \code + * int8_t bmi160_get_power_mode(struct bmi160_dev *dev); + * \endcode + * @details This API gets the power mode of the sensor. + * + * @param[in] dev : Structure instance of bmi160_dev + * + * @return Result of API execution status + * @retval Zero Success + * @retval Negative Error + */ +int8_t bmi160_get_power_mode(struct bmi160_dev* dev); + +/** + * \ingroup bmi160 + * \defgroup bmi160ApiData Sensor Data + * @brief Read sensor data + */ + +/*! + * \ingroup bmi160ApiData + * \page bmi160_api_bmi160_get_sensor_data bmi160_get_sensor_data + * \code + * int8_t bmi160_get_sensor_data(uint8_t select_sensor, + * struct bmi160_sensor_data *accel, + * struct bmi160_sensor_data *gyro, + * const struct bmi160_dev *dev); + * + * \endcode + * @details This API reads sensor data, stores it in + * the bmi160_sensor_data structure pointer passed by the user. + * The user can ask for accel data ,gyro data or both sensor + * data using bmi160_select_sensor enum + * + * @param[in] select_sensor : enum to choose accel,gyro or both sensor data + * @param[out] accel : Structure pointer to store accel data + * @param[out] gyro : Structure pointer to store gyro data + * @param[in] dev : Structure instance of bmi160_dev. + * @note : Refer user guide for detailed info. + * + * @return Result of API execution status + * @retval Zero Success + * @retval Negative Error + */ +int8_t bmi160_get_sensor_data( + uint8_t select_sensor, + struct bmi160_sensor_data* accel, + struct bmi160_sensor_data* gyro, + const struct bmi160_dev* dev); + +/** + * \ingroup bmi160 + * \defgroup bmi160ApiInt Interrupt configuration + * @brief Set interrupt configuration of the sensor + */ + +/*! + * \ingroup bmi160ApiInt + * \page bmi160_api_bmi160_set_int_config bmi160_set_int_config + * \code + * int8_t bmi160_set_int_config(struct bmi160_int_settg *int_config, struct bmi160_dev *dev); + * \endcode + * @details This API configures the necessary interrupt based on + * the user settings in the bmi160_int_settg structure instance. + * + * @param[in] int_config : Structure instance of bmi160_int_settg. + * @param[in] dev : Structure instance of bmi160_dev. + * @note : Refer user guide for detailed info. + * + * @return Result of API execution status + * @retval Zero Success + * @retval Negative Error + */ +int8_t bmi160_set_int_config(struct bmi160_int_settg* int_config, struct bmi160_dev* dev); + +/** + * \ingroup bmi160 + * \defgroup bmi160ApiStepC Step counter + * @brief Step counter operations + */ + +/*! + * \ingroup bmi160ApiStepC + * \page bmi160_api_bmi160_set_step_counter bmi160_set_step_counter + * \code + * int8_t bmi160_set_step_counter(uint8_t step_cnt_enable, const struct bmi160_dev *dev); + * \endcode + * @details This API enables the step counter feature. + * + * @param[in] step_cnt_enable : value to enable or disable + * @param[in] dev : Structure instance of bmi160_dev. + * @note : Refer user guide for detailed info. + * + * @return Result of API execution status + * @retval Zero Success + * @retval Negative Error + */ +int8_t bmi160_set_step_counter(uint8_t step_cnt_enable, const struct bmi160_dev* dev); + +/*! + * \ingroup bmi160ApiStepC + * \page bmi160_api_bmi160_read_step_counter bmi160_read_step_counter + * \code + * int8_t bmi160_read_step_counter(uint16_t *step_val, const struct bmi160_dev *dev); + * \endcode + * @details This API reads the step counter value. + * + * @param[in] step_val : Pointer to store the step counter value. + * @param[in] dev : Structure instance of bmi160_dev. + * @note : Refer user guide for detailed info. + * + * @return Result of API execution status + * @retval Zero Success + * @retval Negative Error + */ +int8_t bmi160_read_step_counter(uint16_t* step_val, const struct bmi160_dev* dev); + +/** + * \ingroup bmi160 + * \defgroup bmi160ApiAux Auxiliary sensor + * @brief Auxiliary sensor operations + */ + +/*! + * \ingroup bmi160ApiAux + * \page bmi160_api_bmi160_aux_read bmi160_aux_read + * \code + * int8_t bmi160_aux_read(uint8_t reg_addr, uint8_t *aux_data, uint16_t len, const struct bmi160_dev *dev); + * \endcode + * @details This API reads the mention no of byte of data from the given + * register address of auxiliary sensor. + * + * @param[in] reg_addr : Address of register to read. + * @param[in] aux_data : Pointer to store the read data. + * @param[in] len : No of bytes to read. + * @param[in] dev : Structure instance of bmi160_dev. + * @note : Refer user guide for detailed info. + * + * @return Result of API execution status + * @retval Zero Success + * @retval Negative Error + */ +int8_t bmi160_aux_read( + uint8_t reg_addr, + uint8_t* aux_data, + uint16_t len, + const struct bmi160_dev* dev); + +/*! + * \ingroup bmi160ApiAux + * \page bmi160_api_bmi160_aux_write bmi160_aux_write + * \code + * int8_t bmi160_aux_write(uint8_t reg_addr, uint8_t *aux_data, uint16_t len, const struct bmi160_dev *dev); + * \endcode + * @details This API writes the mention no of byte of data to the given + * register address of auxiliary sensor. + * + * @param[in] reg_addr : Address of register to write. + * @param[in] aux_data : Pointer to write data. + * @param[in] len : No of bytes to write. + * @param[in] dev : Structure instance of bmi160_dev. + * @note : Refer user guide for detailed info. + * + * @return Result of API execution status + * @retval Zero Success + * @retval Negative Error + */ +int8_t bmi160_aux_write( + uint8_t reg_addr, + uint8_t* aux_data, + uint16_t len, + const struct bmi160_dev* dev); + +/*! + * \ingroup bmi160ApiAux + * \page bmi160_api_bmi160_aux_init bmi160_aux_init + * \code + * int8_t bmi160_aux_init(const struct bmi160_dev *dev); + * \endcode + * @details This API initialize the auxiliary sensor + * in order to access it. + * + * @param[in] dev : Structure instance of bmi160_dev. + * @note : Refer user guide for detailed info. + * + * @return Result of API execution status + * @retval Zero Success + * @retval Negative Error + */ +int8_t bmi160_aux_init(const struct bmi160_dev* dev); + +/*! + * \ingroup bmi160ApiAux + * \page bmi160_api_bmi160_set_aux_auto_mode bmi160_set_aux_auto_mode + * \code + * int8_t bmi160_set_aux_auto_mode(uint8_t *data_addr, struct bmi160_dev *dev); + * \endcode + * @details This API is used to setup the auxiliary sensor of bmi160 in auto mode + * Thus enabling the auto update of 8 bytes of data from auxiliary sensor + * to BMI160 register address 0x04 to 0x0B + * + * @param[in] data_addr : Starting address of aux. sensor's data register + * (BMI160 registers 0x04 to 0x0B will be updated + * with 8 bytes of data from auxiliary sensor + * starting from this register address.) + * @param[in] dev : Structure instance of bmi160_dev. + * + * @note : Set the value of auxiliary polling rate by setting + * dev->aux_cfg.aux_odr to the required value from the table + * before calling this API + * + *@verbatim + * dev->aux_cfg.aux_odr | Auxiliary ODR (Hz) + * -----------------------|----------------------- + * BMI160_AUX_ODR_0_78HZ | 25/32 + * BMI160_AUX_ODR_1_56HZ | 25/16 + * BMI160_AUX_ODR_3_12HZ | 25/8 + * BMI160_AUX_ODR_6_25HZ | 25/4 + * BMI160_AUX_ODR_12_5HZ | 25/2 + * BMI160_AUX_ODR_25HZ | 25 + * BMI160_AUX_ODR_50HZ | 50 + * BMI160_AUX_ODR_100HZ | 100 + * BMI160_AUX_ODR_200HZ | 200 + * BMI160_AUX_ODR_400HZ | 400 + * BMI160_AUX_ODR_800HZ | 800 + *@endverbatim + * + * @note : Other values of dev->aux_cfg.aux_odr are reserved and not for use + * + * @return Result of API execution status + * @retval Zero Success + * @retval Negative Error + */ +int8_t bmi160_set_aux_auto_mode(uint8_t* data_addr, struct bmi160_dev* dev); + +/*! + * \ingroup bmi160ApiAux + * \page bmi160_api_bmi160_config_aux_mode bmi160_config_aux_mode + * \code + * int8_t bmi160_config_aux_mode(const struct bmi160_dev *dev); + * \endcode + * @details This API configures the 0x4C register and settings like + * Auxiliary sensor manual enable/ disable and aux burst read length. + * + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval Zero Success + * @retval Negative Error + */ +int8_t bmi160_config_aux_mode(const struct bmi160_dev* dev); + +/*! + * \ingroup bmi160ApiAux + * \page bmi160_api_bmi160_read_aux_data_auto_mode bmi160_read_aux_data_auto_mode + * \code + * int8_t bmi160_read_aux_data_auto_mode(uint8_t *aux_data, const struct bmi160_dev *dev); + * \endcode + * @details This API is used to read the raw uncompensated auxiliary sensor + * data of 8 bytes from BMI160 register address 0x04 to 0x0B + * + * @param[in] aux_data : Pointer to user array of length 8 bytes + * Ensure that the aux_data array is of + * length 8 bytes + * @param[in] dev : Structure instance of bmi160_dev + * + * @retval zero -> Success / -ve value -> Error + * @retval Zero Success + * @retval Negative Error + */ +int8_t bmi160_read_aux_data_auto_mode(uint8_t* aux_data, const struct bmi160_dev* dev); + +/** + * \ingroup bmi160 + * \defgroup bmi160ApiSelfTest Self test + * @brief Perform self test of the sensor + */ + +/*! + * \ingroup bmi160ApiSelfTest + * \page bmi160_api_bmi160_perform_self_test bmi160_perform_self_test + * \code + * int8_t bmi160_perform_self_test(uint8_t select_sensor, struct bmi160_dev *dev); + * \endcode + * @details This is used to perform self test of accel/gyro of the BMI160 sensor + * + * @param[in] select_sensor : enum to choose accel or gyro for self test + * @param[in] dev : Structure instance of bmi160_dev + * + * @note self test can be performed either for accel/gyro at any instant. + * + *@verbatim + * value of select_sensor | Inference + *----------------------------------|-------------------------------- + * BMI160_ACCEL_ONLY | Accel self test enabled + * BMI160_GYRO_ONLY | Gyro self test enabled + * BMI160_BOTH_ACCEL_AND_GYRO | NOT TO BE USED + *@endverbatim + * + * @note The return value of this API gives us the result of self test. + * + * @note Performing self test does soft reset of the sensor, User can + * set the desired settings after performing the self test. + * + * @return Result of API execution status + * @retval BMI160_OK Self test success + * @retval BMI160_W_GYRO_SELF_TEST_FAIL Gyro self test fail + * @retval BMI160_W_ACCEl_SELF_TEST_FAIL Accel self test fail + */ +int8_t bmi160_perform_self_test(uint8_t select_sensor, struct bmi160_dev* dev); + +/** + * \ingroup bmi160 + * \defgroup bmi160ApiFIFO FIFO + * @brief FIFO operations of the sensor + */ + +/*! + * \ingroup bmi160ApiFIFO + * \page bmi160_api_bmi160_get_fifo_data bmi160_get_fifo_data + * \code + * int8_t bmi160_get_fifo_data(struct bmi160_dev const *dev); + * \endcode + * @details This API reads data from the fifo buffer. + * + * @note User has to allocate the FIFO buffer along with + * corresponding fifo length from his side before calling this API + * as mentioned in the readme.md + * + * @note User must specify the number of bytes to read from the FIFO in + * dev->fifo->length , It will be updated by the number of bytes actually + * read from FIFO after calling this API + * + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval Zero Success + * @retval Negative Error + */ +int8_t bmi160_get_fifo_data(struct bmi160_dev const* dev); + +/*! + * \ingroup bmi160ApiFIFO + * \page bmi160_api_bmi160_set_fifo_flush bmi160_set_fifo_flush + * \code + * int8_t bmi160_set_fifo_flush(const struct bmi160_dev *dev); + * \endcode + * @details This API writes fifo_flush command to command register.This + * action clears all data in the Fifo without changing fifo configuration + * settings. + * + * @param[in] dev : Structure instance of bmi160_dev + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval Any non zero value -> Fail + * + */ +int8_t bmi160_set_fifo_flush(const struct bmi160_dev* dev); + +/*! + * \ingroup bmi160ApiFIFO + * \page bmi160_api_bmi160_set_fifo_config bmi160_set_fifo_config + * \code + * int8_t bmi160_set_fifo_config(uint8_t config, uint8_t enable, struct bmi160_dev const *dev); + * \endcode + * @details This API sets the FIFO configuration in the sensor. + * + * @param[in] config : variable used to specify the FIFO + * configurations which are to be enabled or disabled in the sensor. + * + * @note : User can set either set one or more or all FIFO configurations + * by ORing the below mentioned macros. + * + *@verbatim + * config | Value + * ------------------------|--------------------------- + * BMI160_FIFO_TIME | 0x02 + * BMI160_FIFO_TAG_INT2 | 0x04 + * BMI160_FIFO_TAG_INT1 | 0x08 + * BMI160_FIFO_HEADER | 0x10 + * BMI160_FIFO_AUX | 0x20 + * BMI160_FIFO_ACCEL | 0x40 + * BMI160_FIFO_GYRO | 0x80 + *@endverbatim + * + * @param[in] enable : Parameter used to enable or disable the above + * FIFO configuration + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return status of bus communication result + * @retval 0 -> Success + * @retval Any non zero value -> Fail + * + */ +int8_t bmi160_set_fifo_config(uint8_t config, uint8_t enable, struct bmi160_dev const* dev); + +/*! + * \ingroup bmi160ApiFIFO + * \page bmi160_api_bmi160_set_fifo_down bmi160_set_fifo_down + * \code + * int8_t bmi160_set_fifo_down(uint8_t fifo_down, const struct bmi160_dev *dev); + * \endcode + * @details This API is used to configure the down sampling ratios of + * the accel and gyro data for FIFO.Also, it configures filtered or + * pre-filtered data for the fifo for accel and gyro. + * + * @param[in] fifo_down : variable used to specify the FIFO down + * configurations which are to be enabled or disabled in the sensor. + * + * @note The user must select one among the following macros to + * select down-sampling ratio for accel + * + *@verbatim + * config | Value + * -------------------------------------|--------------------------- + * BMI160_ACCEL_FIFO_DOWN_ZERO | 0x00 + * BMI160_ACCEL_FIFO_DOWN_ONE | 0x10 + * BMI160_ACCEL_FIFO_DOWN_TWO | 0x20 + * BMI160_ACCEL_FIFO_DOWN_THREE | 0x30 + * BMI160_ACCEL_FIFO_DOWN_FOUR | 0x40 + * BMI160_ACCEL_FIFO_DOWN_FIVE | 0x50 + * BMI160_ACCEL_FIFO_DOWN_SIX | 0x60 + * BMI160_ACCEL_FIFO_DOWN_SEVEN | 0x70 + *@endverbatim + * + * @note The user must select one among the following macros to + * select down-sampling ratio for gyro + * + *@verbatim + * config | Value + * -------------------------------------|--------------------------- + * BMI160_GYRO_FIFO_DOWN_ZERO | 0x00 + * BMI160_GYRO_FIFO_DOWN_ONE | 0x01 + * BMI160_GYRO_FIFO_DOWN_TWO | 0x02 + * BMI160_GYRO_FIFO_DOWN_THREE | 0x03 + * BMI160_GYRO_FIFO_DOWN_FOUR | 0x04 + * BMI160_GYRO_FIFO_DOWN_FIVE | 0x05 + * BMI160_GYRO_FIFO_DOWN_SIX | 0x06 + * BMI160_GYRO_FIFO_DOWN_SEVEN | 0x07 + *@endverbatim + * + * @note The user can enable filtered accel data by the following macro + * + *@verbatim + * config | Value + * -------------------------------------|--------------------------- + * BMI160_ACCEL_FIFO_FILT_EN | 0x80 + *@endverbatim + * + * @note The user can enable filtered gyro data by the following macro + * + *@verbatim + * config | Value + * -------------------------------------|--------------------------- + * BMI160_GYRO_FIFO_FILT_EN | 0x08 + *@endverbatim + * + * @note : By ORing the above mentioned macros, the user can select + * the required FIFO down config settings + * + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return status of bus communication result + * @retval 0 -> Success + * @retval Any non zero value -> Fail + * + */ +int8_t bmi160_set_fifo_down(uint8_t fifo_down, const struct bmi160_dev* dev); + +/*! + * \ingroup bmi160ApiFIFO + * \page bmi160_api_bmi160_set_fifo_wm bmi160_set_fifo_wm + * \code + * int8_t bmi160_set_fifo_wm(uint8_t fifo_wm, const struct bmi160_dev *dev); + * \endcode + * @details This API sets the FIFO watermark level in the sensor. + * + * @note The FIFO watermark is issued when the FIFO fill level is + * equal or above the watermark level and units of watermark is 4 bytes. + * + * @param[in] fifo_wm : Variable used to set the FIFO water mark level + * @param[in] dev : Structure instance of bmi160_dev + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval Any non zero value -> Fail + * + */ +int8_t bmi160_set_fifo_wm(uint8_t fifo_wm, const struct bmi160_dev* dev); + +/*! + * \ingroup bmi160ApiFIFO + * \page bmi160_api_bmi160_extract_accel bmi160_extract_accel + * \code + * int8_t bmi160_extract_accel(struct bmi160_sensor_data *accel_data, uint8_t *accel_length, struct bmi160_dev const + **dev); + * \endcode + * @details This API parses and extracts the accelerometer frames from + * FIFO data read by the "bmi160_get_fifo_data" API and stores it in + * the "accel_data" structure instance. + * + * @note The bmi160_extract_accel API should be called only after + * reading the FIFO data by calling the bmi160_get_fifo_data() API. + * + * @param[out] accel_data : Structure instance of bmi160_sensor_data + * where the accelerometer data in FIFO is stored. + * @param[in,out] accel_length : Number of valid accelerometer frames + * (x,y,z axes data) read out from fifo. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @note accel_length is updated with the number of valid accelerometer + * frames extracted from fifo (1 accel frame = 6 bytes) at the end of + * execution of this API. + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval Any non zero value -> Fail + * + */ +int8_t bmi160_extract_accel( + struct bmi160_sensor_data* accel_data, + uint8_t* accel_length, + struct bmi160_dev const* dev); + +/*! + * \ingroup bmi160ApiFIFO + * \page bmi160_api_bmi160_extract_gyro bmi160_extract_gyro + * \code + * int8_t bmi160_extract_gyro(struct bmi160_sensor_data *gyro_data, uint8_t *gyro_length, struct bmi160_dev const *dev); + * \endcode + * @details This API parses and extracts the gyro frames from + * FIFO data read by the "bmi160_get_fifo_data" API and stores it in + * the "gyro_data" structure instance. + * + * @note The bmi160_extract_gyro API should be called only after + * reading the FIFO data by calling the bmi160_get_fifo_data() API. + * + * @param[out] gyro_data : Structure instance of bmi160_sensor_data + * where the gyro data in FIFO is stored. + * @param[in,out] gyro_length : Number of valid gyro frames + * (x,y,z axes data) read out from fifo. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @note gyro_length is updated with the number of valid gyro + * frames extracted from fifo (1 gyro frame = 6 bytes) at the end of + * execution of this API. + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval Any non zero value -> Fail + * + */ +int8_t bmi160_extract_gyro( + struct bmi160_sensor_data* gyro_data, + uint8_t* gyro_length, + struct bmi160_dev const* dev); + +/*! + * \ingroup bmi160ApiFIFO + * \page bmi160_api_bmi160_extract_aux bmi160_extract_aux + * \code + * int8_t bmi160_extract_aux(struct bmi160_aux_data *aux_data, uint8_t *aux_len, struct bmi160_dev const *dev); + * \endcode + * @details This API parses and extracts the aux frames from + * FIFO data read by the "bmi160_get_fifo_data" API and stores it in + * the bmi160_aux_data structure instance. + * + * @note The bmi160_extract_aux API should be called only after + * reading the FIFO data by calling the bmi160_get_fifo_data() API. + * + * @param[out] aux_data : Structure instance of bmi160_aux_data + * where the aux data in FIFO is stored. + * @param[in,out] aux_len : Number of valid aux frames (8bytes) + * read out from FIFO. + * @param[in] dev : Structure instance of bmi160_dev. + * + * @note aux_len is updated with the number of valid aux + * frames extracted from fifo (1 aux frame = 8 bytes) at the end of + * execution of this API. + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval Any non zero value -> Fail + * + */ +int8_t bmi160_extract_aux( + struct bmi160_aux_data* aux_data, + uint8_t* aux_len, + struct bmi160_dev const* dev); + +/** + * \ingroup bmi160 + * \defgroup bmi160ApiFOC FOC + * @brief Start FOC of accel and gyro sensors + */ + +/*! + * \ingroup bmi160ApiFOC + * \page bmi160_api_bmi160_start_foc bmi160_start_foc + * \code + * int8_t bmi160_start_foc(const struct bmi160_foc_conf *foc_conf, + * \endcode + * @details This API starts the FOC of accel and gyro + * + * @note FOC should not be used in low-power mode of sensor + * + * @note Accel FOC targets values of +1g , 0g , -1g + * Gyro FOC always targets value of 0 dps + * + * @param[in] foc_conf : Structure instance of bmi160_foc_conf which + * has the FOC configuration + * @param[in,out] offset : Structure instance to store Offset + * values read from sensor + * @param[in] dev : Structure instance of bmi160_dev. + * + * @note Pre-requisites for triggering FOC in accel , Set the following, + * Enable the acc_off_en + * Ex : foc_conf.acc_off_en = BMI160_ENABLE; + * + * Set the desired target values of FOC to each axes (x,y,z) by using the + * following macros + * - BMI160_FOC_ACCEL_DISABLED + * - BMI160_FOC_ACCEL_POSITIVE_G + * - BMI160_FOC_ACCEL_NEGATIVE_G + * - BMI160_FOC_ACCEL_0G + * + * Ex : foc_conf.foc_acc_x = BMI160_FOC_ACCEL_0G; + * foc_conf.foc_acc_y = BMI160_FOC_ACCEL_0G; + * foc_conf.foc_acc_z = BMI160_FOC_ACCEL_POSITIVE_G; + * + * @note Pre-requisites for triggering FOC in gyro , + * Set the following parameters, + * + * Ex : foc_conf.foc_gyr_en = BMI160_ENABLE; + * foc_conf.gyro_off_en = BMI160_ENABLE; + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval Any non zero value -> Fail + */ +int8_t bmi160_start_foc( + const struct bmi160_foc_conf* foc_conf, + struct bmi160_offsets* offset, + struct bmi160_dev const* dev); + +/** + * \ingroup bmi160 + * \defgroup bmi160ApiOffsets Offsets + * @brief Set / Get offset values of accel and gyro sensors + */ + +/*! + * \ingroup bmi160ApiOffsets + * \page bmi160_api_bmi160_get_offsets bmi160_get_offsets + * \code + * int8_t bmi160_get_offsets(struct bmi160_offsets *offset, const struct bmi160_dev *dev); + * \endcode + * @details This API reads and stores the offset values of accel and gyro + * + * @param[in,out] offset : Structure instance of bmi160_offsets in which + * the offset values are read and stored + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval Any non zero value -> Fail + */ +int8_t bmi160_get_offsets(struct bmi160_offsets* offset, const struct bmi160_dev* dev); + +/*! + * \ingroup bmi160ApiOffsets + * \page bmi160_api_bmi160_set_offsets bmi160_set_offsets + * \code + * int8_t bmi160_set_offsets(const struct bmi160_foc_conf *foc_conf, + * const struct bmi160_offsets *offset, + * struct bmi160_dev const *dev); + * \endcode + * @details This API writes the offset values of accel and gyro to + * the sensor but these values will be reset on POR or soft reset. + * + * @param[in] foc_conf : Structure instance of bmi160_foc_conf which + * has the FOC configuration + * @param[in] offset : Structure instance in which user updates offset + * values which are to be written in the sensor + * @param[in] dev : Structure instance of bmi160_dev. + * + * @note Offsets can be set by user like offset->off_acc_x = 10; + * where 1LSB = 3.9mg and for gyro 1LSB = 0.061degrees/second + * + * @note BMI160 offset values for xyz axes of accel should be within range of + * BMI160_ACCEL_MIN_OFFSET (-128) to BMI160_ACCEL_MAX_OFFSET (127) + * + * @note BMI160 offset values for xyz axes of gyro should be within range of + * BMI160_GYRO_MIN_OFFSET (-512) to BMI160_GYRO_MAX_OFFSET (511) + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval Any non zero value -> Fail + */ +int8_t bmi160_set_offsets( + const struct bmi160_foc_conf* foc_conf, + const struct bmi160_offsets* offset, + struct bmi160_dev const* dev); + +/** + * \ingroup bmi160 + * \defgroup bmi160ApiNVM NVM + * @brief Write image registers values to NVM + */ + +/*! + * \ingroup bmi160ApiNVM + * \page bmi160_api_bmi160_update_nvm bmi160_update_nvm + * \code + * int8_t bmi160_update_nvm(struct bmi160_dev const *dev); + * \endcode + * @details This API writes the image registers values to NVM which is + * stored even after POR or soft reset + * + * @param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval Any non zero value -> Fail + */ +int8_t bmi160_update_nvm(struct bmi160_dev const* dev); + +/** + * \ingroup bmi160 + * \defgroup bmi160ApiInts Interrupt status + * @brief Read interrupt status from the sensor + */ + +/*! + * \ingroup bmi160ApiInts + * \page bmi160_api_bmi160_get_int_status bmi160_get_int_status + * \code + * int8_t bmi160_get_int_status(enum bmi160_int_status_sel int_status_sel, + * union bmi160_int_status *int_status, + * struct bmi160_dev const *dev); + * \endcode + * @details This API gets the interrupt status from the sensor. + * + * @param[in] int_status_sel : Enum variable to select either individual or all the + * interrupt status bits. + * @param[in] int_status : pointer variable to get the interrupt status + * from the sensor. + * param[in] dev : Structure instance of bmi160_dev. + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval Any non zero value -> Fail + */ +int8_t bmi160_get_int_status( + enum bmi160_int_status_sel int_status_sel, + union bmi160_int_status* int_status, + struct bmi160_dev const* dev); + +/*************************** C++ guard macro *****************************/ +#ifdef __cplusplus +} +#endif + +#endif /* BMI160_H_ */ diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/imu/bmi160_defs.h b/Applications/Official/DEV_FW/source/airmouse/tracking/imu/bmi160_defs.h new file mode 100644 index 000000000..458ecaad5 --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/imu/bmi160_defs.h @@ -0,0 +1,1619 @@ +/** +* Copyright (c) 2021 Bosch Sensortec GmbH. All rights reserved. +* +* BSD-3-Clause +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* +* 3. Neither the name of the copyright holder nor the names of its +* contributors may be used to endorse or promote products derived from +* this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +* +* @file bmi160_defs.h +* @date 2021-10-05 +* @version v3.9.2 +* +*/ + +#ifndef BMI160_DEFS_H_ +#define BMI160_DEFS_H_ + +/*************************** C types headers *****************************/ +#ifdef __KERNEL__ +#include +#include +#else +#include +#include +#endif + +/*************************** Common macros *****************************/ + +#if !defined(UINT8_C) && !defined(INT8_C) +#define INT8_C(x) S8_C(x) +#define UINT8_C(x) U8_C(x) +#endif + +#if !defined(UINT16_C) && !defined(INT16_C) +#define INT16_C(x) S16_C(x) +#define UINT16_C(x) U16_C(x) +#endif + +#if !defined(INT32_C) && !defined(UINT32_C) +#define INT32_C(x) S32_C(x) +#define UINT32_C(x) U32_C(x) +#endif + +#if !defined(INT64_C) && !defined(UINT64_C) +#define INT64_C(x) S64_C(x) +#define UINT64_C(x) U64_C(x) +#endif + +/**@}*/ +/**\name C standard macros */ +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void*)0) +#endif +#endif + +/*************************** Sensor macros *****************************/ +/* Test for an endian machine */ +#ifndef __ORDER_LITTLE_ENDIAN__ +#define __ORDER_LITTLE_ENDIAN__ 0 +#endif + +#ifndef __BYTE_ORDER__ +#define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__ +#endif + +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#ifndef LITTLE_ENDIAN +#define LITTLE_ENDIAN 1 +#endif +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#ifndef BIG_ENDIAN +#define BIG_ENDIAN 1 +#endif +#else +#error "Code does not support Endian format of the processor" +#endif + +/** Mask definitions */ +#define BMI160_ACCEL_BW_MASK UINT8_C(0x70) +#define BMI160_ACCEL_ODR_MASK UINT8_C(0x0F) +#define BMI160_ACCEL_UNDERSAMPLING_MASK UINT8_C(0x80) +#define BMI160_ACCEL_RANGE_MASK UINT8_C(0x0F) +#define BMI160_GYRO_BW_MASK UINT8_C(0x30) +#define BMI160_GYRO_ODR_MASK UINT8_C(0x0F) +#define BMI160_GYRO_RANGE_MASK UINT8_C(0x07) + +#define BMI160_ACCEL_BW_POS UINT8_C(4) +#define BMI160_GYRO_BW_POS UINT8_C(4) + +/** Mask definitions for INT_EN registers */ +#define BMI160_ANY_MOTION_X_INT_EN_MASK UINT8_C(0x01) +#define BMI160_HIGH_G_X_INT_EN_MASK UINT8_C(0x01) +#define BMI160_NO_MOTION_X_INT_EN_MASK UINT8_C(0x01) +#define BMI160_ANY_MOTION_Y_INT_EN_MASK UINT8_C(0x02) +#define BMI160_HIGH_G_Y_INT_EN_MASK UINT8_C(0x02) +#define BMI160_NO_MOTION_Y_INT_EN_MASK UINT8_C(0x02) +#define BMI160_ANY_MOTION_Z_INT_EN_MASK UINT8_C(0x04) +#define BMI160_HIGH_G_Z_INT_EN_MASK UINT8_C(0x04) +#define BMI160_NO_MOTION_Z_INT_EN_MASK UINT8_C(0x04) +#define BMI160_SIG_MOTION_INT_EN_MASK UINT8_C(0x07) +#define BMI160_ANY_MOTION_ALL_INT_EN_MASK UINT8_C(0x07) +#define BMI160_STEP_DETECT_INT_EN_MASK UINT8_C(0x08) +#define BMI160_DOUBLE_TAP_INT_EN_MASK UINT8_C(0x10) +#define BMI160_SINGLE_TAP_INT_EN_MASK UINT8_C(0x20) +#define BMI160_FIFO_FULL_INT_EN_MASK UINT8_C(0x20) +#define BMI160_ORIENT_INT_EN_MASK UINT8_C(0x40) +#define BMI160_FIFO_WATERMARK_INT_EN_MASK UINT8_C(0x40) +#define BMI160_LOW_G_INT_EN_MASK UINT8_C(0x08) +#define BMI160_STEP_DETECT_EN_MASK UINT8_C(0x08) +#define BMI160_FLAT_INT_EN_MASK UINT8_C(0x80) +#define BMI160_DATA_RDY_INT_EN_MASK UINT8_C(0x10) + +/** PMU status Macros */ +#define BMI160_AUX_PMU_SUSPEND UINT8_C(0x00) +#define BMI160_AUX_PMU_NORMAL UINT8_C(0x01) +#define BMI160_AUX_PMU_LOW_POWER UINT8_C(0x02) + +#define BMI160_GYRO_PMU_SUSPEND UINT8_C(0x00) +#define BMI160_GYRO_PMU_NORMAL UINT8_C(0x01) +#define BMI160_GYRO_PMU_FSU UINT8_C(0x03) + +#define BMI160_ACCEL_PMU_SUSPEND UINT8_C(0x00) +#define BMI160_ACCEL_PMU_NORMAL UINT8_C(0x01) +#define BMI160_ACCEL_PMU_LOW_POWER UINT8_C(0x02) + +/** Mask definitions for INT_OUT_CTRL register */ +#define BMI160_INT1_EDGE_CTRL_MASK UINT8_C(0x01) +#define BMI160_INT1_OUTPUT_MODE_MASK UINT8_C(0x04) +#define BMI160_INT1_OUTPUT_TYPE_MASK UINT8_C(0x02) +#define BMI160_INT1_OUTPUT_EN_MASK UINT8_C(0x08) +#define BMI160_INT2_EDGE_CTRL_MASK UINT8_C(0x10) +#define BMI160_INT2_OUTPUT_MODE_MASK UINT8_C(0x40) +#define BMI160_INT2_OUTPUT_TYPE_MASK UINT8_C(0x20) +#define BMI160_INT2_OUTPUT_EN_MASK UINT8_C(0x80) + +/** Mask definitions for INT_LATCH register */ +#define BMI160_INT1_INPUT_EN_MASK UINT8_C(0x10) +#define BMI160_INT2_INPUT_EN_MASK UINT8_C(0x20) +#define BMI160_INT_LATCH_MASK UINT8_C(0x0F) + +/** Mask definitions for INT_MAP register */ +#define BMI160_INT1_LOW_G_MASK UINT8_C(0x01) +#define BMI160_INT1_HIGH_G_MASK UINT8_C(0x02) +#define BMI160_INT1_SLOPE_MASK UINT8_C(0x04) +#define BMI160_INT1_NO_MOTION_MASK UINT8_C(0x08) +#define BMI160_INT1_DOUBLE_TAP_MASK UINT8_C(0x10) +#define BMI160_INT1_SINGLE_TAP_MASK UINT8_C(0x20) +#define BMI160_INT1_FIFO_FULL_MASK UINT8_C(0x20) +#define BMI160_INT1_FIFO_WM_MASK UINT8_C(0x40) +#define BMI160_INT1_ORIENT_MASK UINT8_C(0x40) +#define BMI160_INT1_FLAT_MASK UINT8_C(0x80) +#define BMI160_INT1_DATA_READY_MASK UINT8_C(0x80) +#define BMI160_INT2_LOW_G_MASK UINT8_C(0x01) +#define BMI160_INT1_LOW_STEP_DETECT_MASK UINT8_C(0x01) +#define BMI160_INT2_LOW_STEP_DETECT_MASK UINT8_C(0x01) +#define BMI160_INT2_HIGH_G_MASK UINT8_C(0x02) +#define BMI160_INT2_FIFO_FULL_MASK UINT8_C(0x02) +#define BMI160_INT2_FIFO_WM_MASK UINT8_C(0x04) +#define BMI160_INT2_SLOPE_MASK UINT8_C(0x04) +#define BMI160_INT2_DATA_READY_MASK UINT8_C(0x08) +#define BMI160_INT2_NO_MOTION_MASK UINT8_C(0x08) +#define BMI160_INT2_DOUBLE_TAP_MASK UINT8_C(0x10) +#define BMI160_INT2_SINGLE_TAP_MASK UINT8_C(0x20) +#define BMI160_INT2_ORIENT_MASK UINT8_C(0x40) +#define BMI160_INT2_FLAT_MASK UINT8_C(0x80) + +/** Mask definitions for INT_DATA register */ +#define BMI160_TAP_SRC_INT_MASK UINT8_C(0x08) +#define BMI160_LOW_HIGH_SRC_INT_MASK UINT8_C(0x80) +#define BMI160_MOTION_SRC_INT_MASK UINT8_C(0x80) + +/** Mask definitions for INT_MOTION register */ +#define BMI160_SLOPE_INT_DUR_MASK UINT8_C(0x03) +#define BMI160_NO_MOTION_INT_DUR_MASK UINT8_C(0xFC) +#define BMI160_NO_MOTION_SEL_BIT_MASK UINT8_C(0x01) + +/** Mask definitions for INT_TAP register */ +#define BMI160_TAP_DUR_MASK UINT8_C(0x07) +#define BMI160_TAP_SHOCK_DUR_MASK UINT8_C(0x40) +#define BMI160_TAP_QUIET_DUR_MASK UINT8_C(0x80) +#define BMI160_TAP_THRES_MASK UINT8_C(0x1F) + +/** Mask definitions for INT_FLAT register */ +#define BMI160_FLAT_THRES_MASK UINT8_C(0x3F) +#define BMI160_FLAT_HOLD_TIME_MASK UINT8_C(0x30) +#define BMI160_FLAT_HYST_MASK UINT8_C(0x07) + +/** Mask definitions for INT_LOWHIGH register */ +#define BMI160_LOW_G_HYST_MASK UINT8_C(0x03) +#define BMI160_LOW_G_LOW_MODE_MASK UINT8_C(0x04) +#define BMI160_HIGH_G_HYST_MASK UINT8_C(0xC0) + +/** Mask definitions for INT_SIG_MOTION register */ +#define BMI160_SIG_MOTION_SEL_MASK UINT8_C(0x02) +#define BMI160_SIG_MOTION_SKIP_MASK UINT8_C(0x0C) +#define BMI160_SIG_MOTION_PROOF_MASK UINT8_C(0x30) + +/** Mask definitions for INT_ORIENT register */ +#define BMI160_ORIENT_MODE_MASK UINT8_C(0x03) +#define BMI160_ORIENT_BLOCK_MASK UINT8_C(0x0C) +#define BMI160_ORIENT_HYST_MASK UINT8_C(0xF0) +#define BMI160_ORIENT_THETA_MASK UINT8_C(0x3F) +#define BMI160_ORIENT_UD_ENABLE UINT8_C(0x40) +#define BMI160_AXES_EN_MASK UINT8_C(0x80) + +/** Mask definitions for FIFO_CONFIG register */ +#define BMI160_FIFO_GYRO UINT8_C(0x80) +#define BMI160_FIFO_ACCEL UINT8_C(0x40) +#define BMI160_FIFO_AUX UINT8_C(0x20) +#define BMI160_FIFO_TAG_INT1 UINT8_C(0x08) +#define BMI160_FIFO_TAG_INT2 UINT8_C(0x04) +#define BMI160_FIFO_TIME UINT8_C(0x02) +#define BMI160_FIFO_HEADER UINT8_C(0x10) +#define BMI160_FIFO_CONFIG_1_MASK UINT8_C(0xFE) + +/** Mask definitions for STEP_CONF register */ +#define BMI160_STEP_COUNT_EN_BIT_MASK UINT8_C(0x08) +#define BMI160_STEP_DETECT_MIN_THRES_MASK UINT8_C(0x18) +#define BMI160_STEP_DETECT_STEPTIME_MIN_MASK UINT8_C(0x07) +#define BMI160_STEP_MIN_BUF_MASK UINT8_C(0x07) + +/** Mask definition for FIFO Header Data Tag */ +#define BMI160_FIFO_TAG_INTR_MASK UINT8_C(0xFC) + +/** Fifo byte counter mask definitions */ +#define BMI160_FIFO_BYTE_COUNTER_MASK UINT8_C(0x07) + +/** Enable/disable bit value */ +#define BMI160_ENABLE UINT8_C(0x01) +#define BMI160_DISABLE UINT8_C(0x00) + +/** Latch Duration */ +#define BMI160_LATCH_DUR_NONE UINT8_C(0x00) +#define BMI160_LATCH_DUR_312_5_MICRO_SEC UINT8_C(0x01) +#define BMI160_LATCH_DUR_625_MICRO_SEC UINT8_C(0x02) +#define BMI160_LATCH_DUR_1_25_MILLI_SEC UINT8_C(0x03) +#define BMI160_LATCH_DUR_2_5_MILLI_SEC UINT8_C(0x04) +#define BMI160_LATCH_DUR_5_MILLI_SEC UINT8_C(0x05) +#define BMI160_LATCH_DUR_10_MILLI_SEC UINT8_C(0x06) +#define BMI160_LATCH_DUR_20_MILLI_SEC UINT8_C(0x07) +#define BMI160_LATCH_DUR_40_MILLI_SEC UINT8_C(0x08) +#define BMI160_LATCH_DUR_80_MILLI_SEC UINT8_C(0x09) +#define BMI160_LATCH_DUR_160_MILLI_SEC UINT8_C(0x0A) +#define BMI160_LATCH_DUR_320_MILLI_SEC UINT8_C(0x0B) +#define BMI160_LATCH_DUR_640_MILLI_SEC UINT8_C(0x0C) +#define BMI160_LATCH_DUR_1_28_SEC UINT8_C(0x0D) +#define BMI160_LATCH_DUR_2_56_SEC UINT8_C(0x0E) +#define BMI160_LATCHED UINT8_C(0x0F) + +/** BMI160 Register map */ +#define BMI160_CHIP_ID_ADDR UINT8_C(0x00) +#define BMI160_ERROR_REG_ADDR UINT8_C(0x02) +#define BMI160_PMU_STATUS_ADDR UINT8_C(0x03) +#define BMI160_AUX_DATA_ADDR UINT8_C(0x04) +#define BMI160_GYRO_DATA_ADDR UINT8_C(0x0C) +#define BMI160_ACCEL_DATA_ADDR UINT8_C(0x12) +#define BMI160_STATUS_ADDR UINT8_C(0x1B) +#define BMI160_INT_STATUS_ADDR UINT8_C(0x1C) +#define BMI160_FIFO_LENGTH_ADDR UINT8_C(0x22) +#define BMI160_FIFO_DATA_ADDR UINT8_C(0x24) +#define BMI160_ACCEL_CONFIG_ADDR UINT8_C(0x40) +#define BMI160_ACCEL_RANGE_ADDR UINT8_C(0x41) +#define BMI160_GYRO_CONFIG_ADDR UINT8_C(0x42) +#define BMI160_GYRO_RANGE_ADDR UINT8_C(0x43) +#define BMI160_AUX_ODR_ADDR UINT8_C(0x44) +#define BMI160_FIFO_DOWN_ADDR UINT8_C(0x45) +#define BMI160_FIFO_CONFIG_0_ADDR UINT8_C(0x46) +#define BMI160_FIFO_CONFIG_1_ADDR UINT8_C(0x47) +#define BMI160_AUX_IF_0_ADDR UINT8_C(0x4B) +#define BMI160_AUX_IF_1_ADDR UINT8_C(0x4C) +#define BMI160_AUX_IF_2_ADDR UINT8_C(0x4D) +#define BMI160_AUX_IF_3_ADDR UINT8_C(0x4E) +#define BMI160_AUX_IF_4_ADDR UINT8_C(0x4F) +#define BMI160_INT_ENABLE_0_ADDR UINT8_C(0x50) +#define BMI160_INT_ENABLE_1_ADDR UINT8_C(0x51) +#define BMI160_INT_ENABLE_2_ADDR UINT8_C(0x52) +#define BMI160_INT_OUT_CTRL_ADDR UINT8_C(0x53) +#define BMI160_INT_LATCH_ADDR UINT8_C(0x54) +#define BMI160_INT_MAP_0_ADDR UINT8_C(0x55) +#define BMI160_INT_MAP_1_ADDR UINT8_C(0x56) +#define BMI160_INT_MAP_2_ADDR UINT8_C(0x57) +#define BMI160_INT_DATA_0_ADDR UINT8_C(0x58) +#define BMI160_INT_DATA_1_ADDR UINT8_C(0x59) +#define BMI160_INT_LOWHIGH_0_ADDR UINT8_C(0x5A) +#define BMI160_INT_LOWHIGH_1_ADDR UINT8_C(0x5B) +#define BMI160_INT_LOWHIGH_2_ADDR UINT8_C(0x5C) +#define BMI160_INT_LOWHIGH_3_ADDR UINT8_C(0x5D) +#define BMI160_INT_LOWHIGH_4_ADDR UINT8_C(0x5E) +#define BMI160_INT_MOTION_0_ADDR UINT8_C(0x5F) +#define BMI160_INT_MOTION_1_ADDR UINT8_C(0x60) +#define BMI160_INT_MOTION_2_ADDR UINT8_C(0x61) +#define BMI160_INT_MOTION_3_ADDR UINT8_C(0x62) +#define BMI160_INT_TAP_0_ADDR UINT8_C(0x63) +#define BMI160_INT_TAP_1_ADDR UINT8_C(0x64) +#define BMI160_INT_ORIENT_0_ADDR UINT8_C(0x65) +#define BMI160_INT_ORIENT_1_ADDR UINT8_C(0x66) +#define BMI160_INT_FLAT_0_ADDR UINT8_C(0x67) +#define BMI160_INT_FLAT_1_ADDR UINT8_C(0x68) +#define BMI160_FOC_CONF_ADDR UINT8_C(0x69) +#define BMI160_CONF_ADDR UINT8_C(0x6A) + +#define BMI160_IF_CONF_ADDR UINT8_C(0x6B) +#define BMI160_SELF_TEST_ADDR UINT8_C(0x6D) +#define BMI160_OFFSET_ADDR UINT8_C(0x71) +#define BMI160_OFFSET_CONF_ADDR UINT8_C(0x77) +#define BMI160_INT_STEP_CNT_0_ADDR UINT8_C(0x78) +#define BMI160_INT_STEP_CONFIG_0_ADDR UINT8_C(0x7A) +#define BMI160_INT_STEP_CONFIG_1_ADDR UINT8_C(0x7B) +#define BMI160_COMMAND_REG_ADDR UINT8_C(0x7E) +#define BMI160_SPI_COMM_TEST_ADDR UINT8_C(0x7F) +#define BMI160_INTL_PULLUP_CONF_ADDR UINT8_C(0x85) + +/** Error code definitions */ +#define BMI160_OK INT8_C(0) +#define BMI160_E_NULL_PTR INT8_C(-1) +#define BMI160_E_COM_FAIL INT8_C(-2) +#define BMI160_E_DEV_NOT_FOUND INT8_C(-3) +#define BMI160_E_OUT_OF_RANGE INT8_C(-4) +#define BMI160_E_INVALID_INPUT INT8_C(-5) +#define BMI160_E_ACCEL_ODR_BW_INVALID INT8_C(-6) +#define BMI160_E_GYRO_ODR_BW_INVALID INT8_C(-7) +#define BMI160_E_LWP_PRE_FLTR_INT_INVALID INT8_C(-8) +#define BMI160_E_LWP_PRE_FLTR_INVALID INT8_C(-9) +#define BMI160_E_AUX_NOT_FOUND INT8_C(-10) +#define BMI160_E_FOC_FAILURE INT8_C(-11) +#define BMI160_E_READ_WRITE_LENGTH_INVALID INT8_C(-12) +#define BMI160_E_INVALID_CONFIG INT8_C(-13) + +/**\name API warning codes */ +#define BMI160_W_GYRO_SELF_TEST_FAIL INT8_C(1) +#define BMI160_W_ACCEl_SELF_TEST_FAIL INT8_C(2) + +/** BMI160 unique chip identifier */ +#define BMI160_CHIP_ID UINT8_C(0xD1) + +/** Soft reset command */ +#define BMI160_SOFT_RESET_CMD UINT8_C(0xb6) +#define BMI160_SOFT_RESET_DELAY_MS UINT8_C(1) + +/** Start FOC command */ +#define BMI160_START_FOC_CMD UINT8_C(0x03) + +/** NVM backup enabling command */ +#define BMI160_NVM_BACKUP_EN UINT8_C(0xA0) + +/* Delay in ms settings */ +#define BMI160_ACCEL_DELAY_MS UINT8_C(5) +#define BMI160_GYRO_DELAY_MS UINT8_C(80) +#define BMI160_ONE_MS_DELAY UINT8_C(1) +#define BMI160_AUX_COM_DELAY UINT8_C(10) +#define BMI160_GYRO_SELF_TEST_DELAY UINT8_C(20) +#define BMI160_ACCEL_SELF_TEST_DELAY UINT8_C(50) + +/** Self test configurations */ +#define BMI160_ACCEL_SELF_TEST_CONFIG UINT8_C(0x2C) +#define BMI160_ACCEL_SELF_TEST_POSITIVE_EN UINT8_C(0x0D) +#define BMI160_ACCEL_SELF_TEST_NEGATIVE_EN UINT8_C(0x09) +#define BMI160_ACCEL_SELF_TEST_LIMIT UINT16_C(8192) + +/** Power mode settings */ +/* Accel power mode */ +#define BMI160_ACCEL_NORMAL_MODE UINT8_C(0x11) +#define BMI160_ACCEL_LOWPOWER_MODE UINT8_C(0x12) +#define BMI160_ACCEL_SUSPEND_MODE UINT8_C(0x10) + +/* Gyro power mode */ +#define BMI160_GYRO_SUSPEND_MODE UINT8_C(0x14) +#define BMI160_GYRO_NORMAL_MODE UINT8_C(0x15) +#define BMI160_GYRO_FASTSTARTUP_MODE UINT8_C(0x17) + +/* Aux power mode */ +#define BMI160_AUX_SUSPEND_MODE UINT8_C(0x18) +#define BMI160_AUX_NORMAL_MODE UINT8_C(0x19) +#define BMI160_AUX_LOWPOWER_MODE UINT8_C(0x1A) + +/** Range settings */ +/* Accel Range */ +#define BMI160_ACCEL_RANGE_2G UINT8_C(0x03) +#define BMI160_ACCEL_RANGE_4G UINT8_C(0x05) +#define BMI160_ACCEL_RANGE_8G UINT8_C(0x08) +#define BMI160_ACCEL_RANGE_16G UINT8_C(0x0C) + +/* Gyro Range */ +#define BMI160_GYRO_RANGE_2000_DPS UINT8_C(0x00) +#define BMI160_GYRO_RANGE_1000_DPS UINT8_C(0x01) +#define BMI160_GYRO_RANGE_500_DPS UINT8_C(0x02) +#define BMI160_GYRO_RANGE_250_DPS UINT8_C(0x03) +#define BMI160_GYRO_RANGE_125_DPS UINT8_C(0x04) + +/** Bandwidth settings */ +/* Accel Bandwidth */ +#define BMI160_ACCEL_BW_OSR4_AVG1 UINT8_C(0x00) +#define BMI160_ACCEL_BW_OSR2_AVG2 UINT8_C(0x01) +#define BMI160_ACCEL_BW_NORMAL_AVG4 UINT8_C(0x02) +#define BMI160_ACCEL_BW_RES_AVG8 UINT8_C(0x03) +#define BMI160_ACCEL_BW_RES_AVG16 UINT8_C(0x04) +#define BMI160_ACCEL_BW_RES_AVG32 UINT8_C(0x05) +#define BMI160_ACCEL_BW_RES_AVG64 UINT8_C(0x06) +#define BMI160_ACCEL_BW_RES_AVG128 UINT8_C(0x07) + +#define BMI160_GYRO_BW_OSR4_MODE UINT8_C(0x00) +#define BMI160_GYRO_BW_OSR2_MODE UINT8_C(0x01) +#define BMI160_GYRO_BW_NORMAL_MODE UINT8_C(0x02) + +/* Output Data Rate settings */ +/* Accel Output data rate */ +#define BMI160_ACCEL_ODR_RESERVED UINT8_C(0x00) +#define BMI160_ACCEL_ODR_0_78HZ UINT8_C(0x01) +#define BMI160_ACCEL_ODR_1_56HZ UINT8_C(0x02) +#define BMI160_ACCEL_ODR_3_12HZ UINT8_C(0x03) +#define BMI160_ACCEL_ODR_6_25HZ UINT8_C(0x04) +#define BMI160_ACCEL_ODR_12_5HZ UINT8_C(0x05) +#define BMI160_ACCEL_ODR_25HZ UINT8_C(0x06) +#define BMI160_ACCEL_ODR_50HZ UINT8_C(0x07) +#define BMI160_ACCEL_ODR_100HZ UINT8_C(0x08) +#define BMI160_ACCEL_ODR_200HZ UINT8_C(0x09) +#define BMI160_ACCEL_ODR_400HZ UINT8_C(0x0A) +#define BMI160_ACCEL_ODR_800HZ UINT8_C(0x0B) +#define BMI160_ACCEL_ODR_1600HZ UINT8_C(0x0C) +#define BMI160_ACCEL_ODR_RESERVED0 UINT8_C(0x0D) +#define BMI160_ACCEL_ODR_RESERVED1 UINT8_C(0x0E) +#define BMI160_ACCEL_ODR_RESERVED2 UINT8_C(0x0F) + +/* Gyro Output data rate */ +#define BMI160_GYRO_ODR_RESERVED UINT8_C(0x00) +#define BMI160_GYRO_ODR_25HZ UINT8_C(0x06) +#define BMI160_GYRO_ODR_50HZ UINT8_C(0x07) +#define BMI160_GYRO_ODR_100HZ UINT8_C(0x08) +#define BMI160_GYRO_ODR_200HZ UINT8_C(0x09) +#define BMI160_GYRO_ODR_400HZ UINT8_C(0x0A) +#define BMI160_GYRO_ODR_800HZ UINT8_C(0x0B) +#define BMI160_GYRO_ODR_1600HZ UINT8_C(0x0C) +#define BMI160_GYRO_ODR_3200HZ UINT8_C(0x0D) + +/* Auxiliary sensor Output data rate */ +#define BMI160_AUX_ODR_RESERVED UINT8_C(0x00) +#define BMI160_AUX_ODR_0_78HZ UINT8_C(0x01) +#define BMI160_AUX_ODR_1_56HZ UINT8_C(0x02) +#define BMI160_AUX_ODR_3_12HZ UINT8_C(0x03) +#define BMI160_AUX_ODR_6_25HZ UINT8_C(0x04) +#define BMI160_AUX_ODR_12_5HZ UINT8_C(0x05) +#define BMI160_AUX_ODR_25HZ UINT8_C(0x06) +#define BMI160_AUX_ODR_50HZ UINT8_C(0x07) +#define BMI160_AUX_ODR_100HZ UINT8_C(0x08) +#define BMI160_AUX_ODR_200HZ UINT8_C(0x09) +#define BMI160_AUX_ODR_400HZ UINT8_C(0x0A) +#define BMI160_AUX_ODR_800HZ UINT8_C(0x0B) + +/** FIFO_CONFIG Definitions */ +#define BMI160_FIFO_TIME_ENABLE UINT8_C(0x02) +#define BMI160_FIFO_TAG_INT2_ENABLE UINT8_C(0x04) +#define BMI160_FIFO_TAG_INT1_ENABLE UINT8_C(0x08) +#define BMI160_FIFO_HEAD_ENABLE UINT8_C(0x10) +#define BMI160_FIFO_M_ENABLE UINT8_C(0x20) +#define BMI160_FIFO_A_ENABLE UINT8_C(0x40) +#define BMI160_FIFO_M_A_ENABLE UINT8_C(0x60) +#define BMI160_FIFO_G_ENABLE UINT8_C(0x80) +#define BMI160_FIFO_M_G_ENABLE UINT8_C(0xA0) +#define BMI160_FIFO_G_A_ENABLE UINT8_C(0xC0) +#define BMI160_FIFO_M_G_A_ENABLE UINT8_C(0xE0) + +/* Macro to specify the number of bytes over-read from the + * FIFO in order to get the sensor time at the end of FIFO */ +#ifndef BMI160_FIFO_BYTES_OVERREAD +#define BMI160_FIFO_BYTES_OVERREAD UINT8_C(25) +#endif + +/* Accel, gyro and aux. sensor length and also their combined + * length definitions in FIFO */ +#define BMI160_FIFO_G_LENGTH UINT8_C(6) +#define BMI160_FIFO_A_LENGTH UINT8_C(6) +#define BMI160_FIFO_M_LENGTH UINT8_C(8) +#define BMI160_FIFO_GA_LENGTH UINT8_C(12) +#define BMI160_FIFO_MA_LENGTH UINT8_C(14) +#define BMI160_FIFO_MG_LENGTH UINT8_C(14) +#define BMI160_FIFO_MGA_LENGTH UINT8_C(20) + +/** FIFO Header Data definitions */ +#define BMI160_FIFO_HEAD_SKIP_FRAME UINT8_C(0x40) +#define BMI160_FIFO_HEAD_SENSOR_TIME UINT8_C(0x44) +#define BMI160_FIFO_HEAD_INPUT_CONFIG UINT8_C(0x48) +#define BMI160_FIFO_HEAD_OVER_READ UINT8_C(0x80) +#define BMI160_FIFO_HEAD_A UINT8_C(0x84) +#define BMI160_FIFO_HEAD_G UINT8_C(0x88) +#define BMI160_FIFO_HEAD_G_A UINT8_C(0x8C) +#define BMI160_FIFO_HEAD_M UINT8_C(0x90) +#define BMI160_FIFO_HEAD_M_A UINT8_C(0x94) +#define BMI160_FIFO_HEAD_M_G UINT8_C(0x98) +#define BMI160_FIFO_HEAD_M_G_A UINT8_C(0x9C) + +/** FIFO sensor time length definitions */ +#define BMI160_SENSOR_TIME_LENGTH UINT8_C(3) + +/** FIFO DOWN selection */ +/* Accel fifo down-sampling values*/ +#define BMI160_ACCEL_FIFO_DOWN_ZERO UINT8_C(0x00) +#define BMI160_ACCEL_FIFO_DOWN_ONE UINT8_C(0x10) +#define BMI160_ACCEL_FIFO_DOWN_TWO UINT8_C(0x20) +#define BMI160_ACCEL_FIFO_DOWN_THREE UINT8_C(0x30) +#define BMI160_ACCEL_FIFO_DOWN_FOUR UINT8_C(0x40) +#define BMI160_ACCEL_FIFO_DOWN_FIVE UINT8_C(0x50) +#define BMI160_ACCEL_FIFO_DOWN_SIX UINT8_C(0x60) +#define BMI160_ACCEL_FIFO_DOWN_SEVEN UINT8_C(0x70) + +/* Gyro fifo down-smapling values*/ +#define BMI160_GYRO_FIFO_DOWN_ZERO UINT8_C(0x00) +#define BMI160_GYRO_FIFO_DOWN_ONE UINT8_C(0x01) +#define BMI160_GYRO_FIFO_DOWN_TWO UINT8_C(0x02) +#define BMI160_GYRO_FIFO_DOWN_THREE UINT8_C(0x03) +#define BMI160_GYRO_FIFO_DOWN_FOUR UINT8_C(0x04) +#define BMI160_GYRO_FIFO_DOWN_FIVE UINT8_C(0x05) +#define BMI160_GYRO_FIFO_DOWN_SIX UINT8_C(0x06) +#define BMI160_GYRO_FIFO_DOWN_SEVEN UINT8_C(0x07) + +/* Accel Fifo filter enable*/ +#define BMI160_ACCEL_FIFO_FILT_EN UINT8_C(0x80) + +/* Gyro Fifo filter enable*/ +#define BMI160_GYRO_FIFO_FILT_EN UINT8_C(0x08) + +/** Definitions to check validity of FIFO frames */ +#define FIFO_CONFIG_MSB_CHECK UINT8_C(0x80) +#define FIFO_CONFIG_LSB_CHECK UINT8_C(0x00) + +/*! BMI160 accel FOC configurations */ +#define BMI160_FOC_ACCEL_DISABLED UINT8_C(0x00) +#define BMI160_FOC_ACCEL_POSITIVE_G UINT8_C(0x01) +#define BMI160_FOC_ACCEL_NEGATIVE_G UINT8_C(0x02) +#define BMI160_FOC_ACCEL_0G UINT8_C(0x03) + +/** Array Parameter DefinItions */ +#define BMI160_SENSOR_TIME_LSB_BYTE UINT8_C(0) +#define BMI160_SENSOR_TIME_XLSB_BYTE UINT8_C(1) +#define BMI160_SENSOR_TIME_MSB_BYTE UINT8_C(2) + +/** Interface settings */ +#define BMI160_SPI_INTF UINT8_C(1) +#define BMI160_I2C_INTF UINT8_C(0) +#define BMI160_SPI_RD_MASK UINT8_C(0x80) +#define BMI160_SPI_WR_MASK UINT8_C(0x7F) + +/* Sensor & time select definition*/ +#define BMI160_ACCEL_SEL UINT8_C(0x01) +#define BMI160_GYRO_SEL UINT8_C(0x02) +#define BMI160_TIME_SEL UINT8_C(0x04) + +/* Sensor select mask*/ +#define BMI160_SEN_SEL_MASK UINT8_C(0x07) + +/* Error code mask */ +#define BMI160_ERR_REG_MASK UINT8_C(0x0F) + +/* BMI160 I2C address */ +#define BMI160_I2C_ADDR UINT8_C(0x68) + +/* BMI160 secondary IF address */ +#define BMI160_AUX_BMM150_I2C_ADDR UINT8_C(0x10) + +/** BMI160 Length definitions */ +#define BMI160_ONE UINT8_C(1) +#define BMI160_TWO UINT8_C(2) +#define BMI160_THREE UINT8_C(3) +#define BMI160_FOUR UINT8_C(4) +#define BMI160_FIVE UINT8_C(5) + +/** BMI160 fifo level Margin */ +#define BMI160_FIFO_LEVEL_MARGIN UINT8_C(16) + +/** BMI160 fifo flush Command */ +#define BMI160_FIFO_FLUSH_VALUE UINT8_C(0xB0) + +/** BMI160 offset values for xyz axes of accel */ +#define BMI160_ACCEL_MIN_OFFSET INT8_C(-128) +#define BMI160_ACCEL_MAX_OFFSET INT8_C(127) + +/** BMI160 offset values for xyz axes of gyro */ +#define BMI160_GYRO_MIN_OFFSET INT16_C(-512) +#define BMI160_GYRO_MAX_OFFSET INT16_C(511) + +/** BMI160 fifo full interrupt position and mask */ +#define BMI160_FIFO_FULL_INT_POS UINT8_C(5) +#define BMI160_FIFO_FULL_INT_MSK UINT8_C(0x20) +#define BMI160_FIFO_WTM_INT_POS UINT8_C(6) +#define BMI160_FIFO_WTM_INT_MSK UINT8_C(0x40) + +#define BMI160_FIFO_FULL_INT_PIN1_POS UINT8_C(5) +#define BMI160_FIFO_FULL_INT_PIN1_MSK UINT8_C(0x20) +#define BMI160_FIFO_FULL_INT_PIN2_POS UINT8_C(1) +#define BMI160_FIFO_FULL_INT_PIN2_MSK UINT8_C(0x02) + +#define BMI160_FIFO_WTM_INT_PIN1_POS UINT8_C(6) +#define BMI160_FIFO_WTM_INT_PIN1_MSK UINT8_C(0x40) +#define BMI160_FIFO_WTM_INT_PIN2_POS UINT8_C(2) +#define BMI160_FIFO_WTM_INT_PIN2_MSK UINT8_C(0x04) + +#define BMI160_MANUAL_MODE_EN_POS UINT8_C(7) +#define BMI160_MANUAL_MODE_EN_MSK UINT8_C(0x80) +#define BMI160_AUX_READ_BURST_POS UINT8_C(0) +#define BMI160_AUX_READ_BURST_MSK UINT8_C(0x03) + +#define BMI160_GYRO_SELF_TEST_POS UINT8_C(4) +#define BMI160_GYRO_SELF_TEST_MSK UINT8_C(0x10) +#define BMI160_GYRO_SELF_TEST_STATUS_POS UINT8_C(1) +#define BMI160_GYRO_SELF_TEST_STATUS_MSK UINT8_C(0x02) + +#define BMI160_GYRO_FOC_EN_POS UINT8_C(6) +#define BMI160_GYRO_FOC_EN_MSK UINT8_C(0x40) + +#define BMI160_ACCEL_FOC_X_CONF_POS UINT8_C(4) +#define BMI160_ACCEL_FOC_X_CONF_MSK UINT8_C(0x30) + +#define BMI160_ACCEL_FOC_Y_CONF_POS UINT8_C(2) +#define BMI160_ACCEL_FOC_Y_CONF_MSK UINT8_C(0x0C) + +#define BMI160_ACCEL_FOC_Z_CONF_MSK UINT8_C(0x03) + +#define BMI160_FOC_STATUS_POS UINT8_C(3) +#define BMI160_FOC_STATUS_MSK UINT8_C(0x08) + +#define BMI160_GYRO_OFFSET_X_MSK UINT8_C(0x03) + +#define BMI160_GYRO_OFFSET_Y_POS UINT8_C(2) +#define BMI160_GYRO_OFFSET_Y_MSK UINT8_C(0x0C) + +#define BMI160_GYRO_OFFSET_Z_POS UINT8_C(4) +#define BMI160_GYRO_OFFSET_Z_MSK UINT8_C(0x30) + +#define BMI160_GYRO_OFFSET_EN_POS UINT8_C(7) +#define BMI160_GYRO_OFFSET_EN_MSK UINT8_C(0x80) + +#define BMI160_ACCEL_OFFSET_EN_POS UINT8_C(6) +#define BMI160_ACCEL_OFFSET_EN_MSK UINT8_C(0x40) + +#define BMI160_GYRO_OFFSET_POS UINT16_C(8) +#define BMI160_GYRO_OFFSET_MSK UINT16_C(0x0300) + +#define BMI160_NVM_UPDATE_POS UINT8_C(1) +#define BMI160_NVM_UPDATE_MSK UINT8_C(0x02) + +#define BMI160_NVM_STATUS_POS UINT8_C(4) +#define BMI160_NVM_STATUS_MSK UINT8_C(0x10) + +#define BMI160_MAG_POWER_MODE_MSK UINT8_C(0x03) + +#define BMI160_ACCEL_POWER_MODE_MSK UINT8_C(0x30) +#define BMI160_ACCEL_POWER_MODE_POS UINT8_C(4) + +#define BMI160_GYRO_POWER_MODE_MSK UINT8_C(0x0C) +#define BMI160_GYRO_POWER_MODE_POS UINT8_C(2) + +/* BIT SLICE GET AND SET FUNCTIONS */ +#define BMI160_GET_BITS(regvar, bitname) ((regvar & bitname##_MSK) >> bitname##_POS) +#define BMI160_SET_BITS(regvar, bitname, val) \ + ((regvar & ~bitname##_MSK) | ((val << bitname##_POS) & bitname##_MSK)) + +#define BMI160_SET_BITS_POS_0(reg_data, bitname, data) \ + ((reg_data & ~(bitname##_MSK)) | (data & bitname##_MSK)) + +#define BMI160_GET_BITS_POS_0(reg_data, bitname) (reg_data & (bitname##_MSK)) + +/**\name UTILITY MACROS */ +#define BMI160_SET_LOW_BYTE UINT16_C(0x00FF) +#define BMI160_SET_HIGH_BYTE UINT16_C(0xFF00) + +#define BMI160_GET_LSB(var) (uint8_t)(var & BMI160_SET_LOW_BYTE) +#define BMI160_GET_MSB(var) (uint8_t)((var & BMI160_SET_HIGH_BYTE) >> 8) + +/*****************************************************************************/ +/* type definitions */ + +/*! + * @brief Bus communication function pointer which should be mapped to + * the platform specific read functions of the user + */ +typedef int8_t ( + *bmi160_read_fptr_t)(uint8_t dev_addr, uint8_t reg_addr, uint8_t* data, uint16_t len); + +/*! + * @brief Bus communication function pointer which should be mapped to + * the platform specific write functions of the user + */ +typedef int8_t ( + *bmi160_write_fptr_t)(uint8_t dev_addr, uint8_t reg_addr, uint8_t* read_data, uint16_t len); +typedef void (*bmi160_delay_fptr_t)(uint32_t period); + +/*************************** Data structures *********************************/ + +/*! + * @brief bmi160 interrupt status selection enum. + */ +enum bmi160_int_status_sel { + BMI160_INT_STATUS_0 = 1, + BMI160_INT_STATUS_1 = 2, + BMI160_INT_STATUS_2 = 4, + BMI160_INT_STATUS_3 = 8, + BMI160_INT_STATUS_ALL = 15 +}; + +/*! + * @brief bmi160 interrupt status bits structure + */ +struct bmi160_int_status_bits { +#ifdef LITTLE_ENDIAN + + uint32_t step : 1; + uint32_t sigmot : 1; + uint32_t anym : 1; + + /* pmu trigger will be handled later */ + uint32_t pmu_trigger_reserved : 1; + uint32_t d_tap : 1; + uint32_t s_tap : 1; + uint32_t orient : 1; + uint32_t flat_int : 1; + uint32_t reserved : 2; + uint32_t high_g : 1; + uint32_t low_g : 1; + uint32_t drdy : 1; + uint32_t ffull : 1; + uint32_t fwm : 1; + uint32_t nomo : 1; + uint32_t anym_first_x : 1; + uint32_t anym_first_y : 1; + uint32_t anym_first_z : 1; + uint32_t anym_sign : 1; + uint32_t tap_first_x : 1; + uint32_t tap_first_y : 1; + uint32_t tap_first_z : 1; + uint32_t tap_sign : 1; + uint32_t high_first_x : 1; + uint32_t high_first_y : 1; + uint32_t high_first_z : 1; + uint32_t high_sign : 1; + uint32_t orient_1_0 : 2; + uint32_t orient_2 : 1; + uint32_t flat : 1; +#else + uint32_t high_first_x : 1; + uint32_t high_first_y : 1; + uint32_t high_first_z : 1; + uint32_t high_sign : 1; + uint32_t orient_1_0 : 2; + uint32_t orient_2 : 1; + uint32_t flat : 1; + uint32_t anym_first_x : 1; + uint32_t anym_first_y : 1; + uint32_t anym_first_z : 1; + uint32_t anym_sign : 1; + uint32_t tap_first_x : 1; + uint32_t tap_first_y : 1; + uint32_t tap_first_z : 1; + uint32_t tap_sign : 1; + uint32_t reserved : 2; + uint32_t high_g : 1; + uint32_t low_g : 1; + uint32_t drdy : 1; + uint32_t ffull : 1; + uint32_t fwm : 1; + uint32_t nomo : 1; + uint32_t step : 1; + uint32_t sigmot : 1; + uint32_t anym : 1; + + /* pmu trigger will be handled later */ + uint32_t pmu_trigger_reserved : 1; + uint32_t d_tap : 1; + uint32_t s_tap : 1; + uint32_t orient : 1; + uint32_t flat_int : 1; +#endif +}; + +/*! + * @brief bmi160 interrupt status structure + */ +union bmi160_int_status { + uint8_t data[4]; + struct bmi160_int_status_bits bit; +}; + +/*! + * @brief bmi160 sensor data structure which comprises of accel data + */ +struct bmi160_sensor_data { + /*! X-axis sensor data */ + int16_t x; + + /*! Y-axis sensor data */ + int16_t y; + + /*! Z-axis sensor data */ + int16_t z; + + /*! sensor time */ + uint32_t sensortime; +}; + +/*! + * @brief bmi160 aux data structure which comprises of 8 bytes of accel data + */ +struct bmi160_aux_data { + /*! Auxiliary data */ + uint8_t data[8]; +}; + +/*! + * @brief bmi160 FOC configuration structure + */ +struct bmi160_foc_conf { + /*! Enabling FOC in gyro + * Assignable macros : + * - BMI160_ENABLE + * - BMI160_DISABLE + */ + uint8_t foc_gyr_en; + + /*! Accel FOC configurations + * Assignable macros : + * - BMI160_FOC_ACCEL_DISABLED + * - BMI160_FOC_ACCEL_POSITIVE_G + * - BMI160_FOC_ACCEL_NEGATIVE_G + * - BMI160_FOC_ACCEL_0G + */ + uint8_t foc_acc_x; + uint8_t foc_acc_y; + uint8_t foc_acc_z; + + /*! Enabling offset compensation for accel in data registers + * Assignable macros : + * - BMI160_ENABLE + * - BMI160_DISABLE + */ + uint8_t acc_off_en; + + /*! Enabling offset compensation for gyro in data registers + * Assignable macros : + * - BMI160_ENABLE + * - BMI160_DISABLE + */ + uint8_t gyro_off_en; +}; + +/*! + * @brief bmi160 accel gyro offsets + */ +struct bmi160_offsets { + /*! Accel offset for x axis */ + int8_t off_acc_x; + + /*! Accel offset for y axis */ + int8_t off_acc_y; + + /*! Accel offset for z axis */ + int8_t off_acc_z; + + /*! Gyro offset for x axis */ + int16_t off_gyro_x; + + /*! Gyro offset for y axis */ + int16_t off_gyro_y; + + /*! Gyro offset for z axis */ + int16_t off_gyro_z; +}; + +/*! + * @brief FIFO aux. sensor data structure + */ +struct bmi160_aux_fifo_data { + /*! The value of aux. sensor x LSB data */ + uint8_t aux_x_lsb; + + /*! The value of aux. sensor x MSB data */ + uint8_t aux_x_msb; + + /*! The value of aux. sensor y LSB data */ + uint8_t aux_y_lsb; + + /*! The value of aux. sensor y MSB data */ + uint8_t aux_y_msb; + + /*! The value of aux. sensor z LSB data */ + uint8_t aux_z_lsb; + + /*! The value of aux. sensor z MSB data */ + uint8_t aux_z_msb; + + /*! The value of aux. sensor r for BMM150 LSB data */ + uint8_t aux_r_y2_lsb; + + /*! The value of aux. sensor r for BMM150 MSB data */ + uint8_t aux_r_y2_msb; +}; + +/*! + * @brief bmi160 sensor select structure + */ +enum bmi160_select_sensor { BMI160_ACCEL_ONLY = 1, BMI160_GYRO_ONLY, BMI160_BOTH_ACCEL_AND_GYRO }; + +/*! + * @brief bmi160 sensor step detector mode structure + */ +enum bmi160_step_detect_mode { + BMI160_STEP_DETECT_NORMAL, + BMI160_STEP_DETECT_SENSITIVE, + BMI160_STEP_DETECT_ROBUST, + + /*! Non recommended User defined setting */ + BMI160_STEP_DETECT_USER_DEFINE +}; + +/*! + * @brief enum for auxiliary burst read selection + */ +enum bmi160_aux_read_len { + BMI160_AUX_READ_LEN_0, + BMI160_AUX_READ_LEN_1, + BMI160_AUX_READ_LEN_2, + BMI160_AUX_READ_LEN_3 +}; + +/*! + * @brief bmi160 sensor configuration structure + */ +struct bmi160_cfg { + /*! power mode */ + uint8_t power; + + /*! output data rate */ + uint8_t odr; + + /*! range */ + uint8_t range; + + /*! bandwidth */ + uint8_t bw; +}; + +/*! + * @brief Aux sensor configuration structure + */ +struct bmi160_aux_cfg { + /*! Aux sensor, 1 - enable 0 - disable */ + uint8_t aux_sensor_enable : 1; + + /*! Aux manual/auto mode status */ + uint8_t manual_enable : 1; + + /*! Aux read burst length */ + uint8_t aux_rd_burst_len : 2; + + /*! output data rate */ + uint8_t aux_odr : 4; + + /*! i2c addr of auxiliary sensor */ + uint8_t aux_i2c_addr; +}; + +/*! + * @brief bmi160 interrupt channel selection structure + */ +enum bmi160_int_channel { + /*! Un-map both channels */ + BMI160_INT_CHANNEL_NONE, + + /*! interrupt Channel 1 */ + BMI160_INT_CHANNEL_1, + + /*! interrupt Channel 2 */ + BMI160_INT_CHANNEL_2, + + /*! Map both channels */ + BMI160_INT_CHANNEL_BOTH +}; +enum bmi160_int_types { + /*! Slope/Any-motion interrupt */ + BMI160_ACC_ANY_MOTION_INT, + + /*! Significant motion interrupt */ + BMI160_ACC_SIG_MOTION_INT, + + /*! Step detector interrupt */ + BMI160_STEP_DETECT_INT, + + /*! double tap interrupt */ + BMI160_ACC_DOUBLE_TAP_INT, + + /*! single tap interrupt */ + BMI160_ACC_SINGLE_TAP_INT, + + /*! orientation interrupt */ + BMI160_ACC_ORIENT_INT, + + /*! flat interrupt */ + BMI160_ACC_FLAT_INT, + + /*! high-g interrupt */ + BMI160_ACC_HIGH_G_INT, + + /*! low-g interrupt */ + BMI160_ACC_LOW_G_INT, + + /*! slow/no-motion interrupt */ + BMI160_ACC_SLOW_NO_MOTION_INT, + + /*! data ready interrupt */ + BMI160_ACC_GYRO_DATA_RDY_INT, + + /*! fifo full interrupt */ + BMI160_ACC_GYRO_FIFO_FULL_INT, + + /*! fifo watermark interrupt */ + BMI160_ACC_GYRO_FIFO_WATERMARK_INT, + + /*! fifo tagging feature support */ + BMI160_FIFO_TAG_INT_PIN +}; + +/*! + * @brief bmi160 active state of any & sig motion interrupt. + */ +enum bmi160_any_sig_motion_active_interrupt_state { + /*! Both any & sig motion are disabled */ + BMI160_BOTH_ANY_SIG_MOTION_DISABLED = -1, + + /*! Any-motion selected */ + BMI160_ANY_MOTION_ENABLED, + + /*! Sig-motion selected */ + BMI160_SIG_MOTION_ENABLED +}; +struct bmi160_acc_tap_int_cfg { +#ifdef LITTLE_ENDIAN + + /*! tap threshold */ + uint16_t tap_thr : 5; + + /*! tap shock */ + uint16_t tap_shock : 1; + + /*! tap quiet */ + uint16_t tap_quiet : 1; + + /*! tap duration */ + uint16_t tap_dur : 3; + + /*! data source 0- filter & 1 pre-filter*/ + uint16_t tap_data_src : 1; + + /*! tap enable, 1 - enable, 0 - disable */ + uint16_t tap_en : 1; +#else + + /*! tap enable, 1 - enable, 0 - disable */ + uint16_t tap_en : 1; + + /*! data source 0- filter & 1 pre-filter*/ + uint16_t tap_data_src : 1; + + /*! tap duration */ + uint16_t tap_dur : 3; + + /*! tap quiet */ + uint16_t tap_quiet : 1; + + /*! tap shock */ + uint16_t tap_shock : 1; + + /*! tap threshold */ + uint16_t tap_thr : 5; +#endif +}; +struct bmi160_acc_any_mot_int_cfg { +#ifdef LITTLE_ENDIAN + + /*! 1 any-motion enable, 0 - any-motion disable */ + uint8_t anymotion_en : 1; + + /*! slope interrupt x, 1 - enable, 0 - disable */ + uint8_t anymotion_x : 1; + + /*! slope interrupt y, 1 - enable, 0 - disable */ + uint8_t anymotion_y : 1; + + /*! slope interrupt z, 1 - enable, 0 - disable */ + uint8_t anymotion_z : 1; + + /*! slope duration */ + uint8_t anymotion_dur : 2; + + /*! data source 0- filter & 1 pre-filter*/ + uint8_t anymotion_data_src : 1; + + /*! slope threshold */ + uint8_t anymotion_thr; +#else + + /*! slope threshold */ + uint8_t anymotion_thr; + + /*! data source 0- filter & 1 pre-filter*/ + uint8_t anymotion_data_src : 1; + + /*! slope duration */ + uint8_t anymotion_dur : 2; + + /*! slope interrupt z, 1 - enable, 0 - disable */ + uint8_t anymotion_z : 1; + + /*! slope interrupt y, 1 - enable, 0 - disable */ + uint8_t anymotion_y : 1; + + /*! slope interrupt x, 1 - enable, 0 - disable */ + uint8_t anymotion_x : 1; + + /*! 1 any-motion enable, 0 - any-motion disable */ + uint8_t anymotion_en : 1; +#endif +}; +struct bmi160_acc_sig_mot_int_cfg { +#ifdef LITTLE_ENDIAN + + /*! skip time of sig-motion interrupt */ + uint8_t sig_mot_skip : 2; + + /*! proof time of sig-motion interrupt */ + uint8_t sig_mot_proof : 2; + + /*! data source 0- filter & 1 pre-filter*/ + uint8_t sig_data_src : 1; + + /*! 1 - enable sig, 0 - disable sig & enable anymotion */ + uint8_t sig_en : 1; + + /*! sig-motion threshold */ + uint8_t sig_mot_thres; +#else + + /*! sig-motion threshold */ + uint8_t sig_mot_thres; + + /*! 1 - enable sig, 0 - disable sig & enable anymotion */ + uint8_t sig_en : 1; + + /*! data source 0- filter & 1 pre-filter*/ + uint8_t sig_data_src : 1; + + /*! proof time of sig-motion interrupt */ + uint8_t sig_mot_proof : 2; + + /*! skip time of sig-motion interrupt */ + uint8_t sig_mot_skip : 2; +#endif +}; +struct bmi160_acc_step_detect_int_cfg { +#ifdef LITTLE_ENDIAN + + /*! 1- step detector enable, 0- step detector disable */ + uint16_t step_detector_en : 1; + + /*! minimum threshold */ + uint16_t min_threshold : 2; + + /*! minimal detectable step time */ + uint16_t steptime_min : 3; + + /*! enable step counter mode setting */ + uint16_t step_detector_mode : 2; + + /*! minimum step buffer size*/ + uint16_t step_min_buf : 3; +#else + + /*! minimum step buffer size*/ + uint16_t step_min_buf : 3; + + /*! enable step counter mode setting */ + uint16_t step_detector_mode : 2; + + /*! minimal detectable step time */ + uint16_t steptime_min : 3; + + /*! minimum threshold */ + uint16_t min_threshold : 2; + + /*! 1- step detector enable, 0- step detector disable */ + uint16_t step_detector_en : 1; +#endif +}; +struct bmi160_acc_no_motion_int_cfg { +#ifdef LITTLE_ENDIAN + + /*! no motion interrupt x */ + uint16_t no_motion_x : 1; + + /*! no motion interrupt y */ + uint16_t no_motion_y : 1; + + /*! no motion interrupt z */ + uint16_t no_motion_z : 1; + + /*! no motion duration */ + uint16_t no_motion_dur : 6; + + /*! no motion sel , 1 - enable no-motion ,0- enable slow-motion */ + uint16_t no_motion_sel : 1; + + /*! data source 0- filter & 1 pre-filter*/ + uint16_t no_motion_src : 1; + + /*! no motion threshold */ + uint8_t no_motion_thres; +#else + + /*! no motion threshold */ + uint8_t no_motion_thres; + + /*! data source 0- filter & 1 pre-filter*/ + uint16_t no_motion_src : 1; + + /*! no motion sel , 1 - enable no-motion ,0- enable slow-motion */ + uint16_t no_motion_sel : 1; + + /*! no motion duration */ + uint16_t no_motion_dur : 6; + + /* no motion interrupt z */ + uint16_t no_motion_z : 1; + + /*! no motion interrupt y */ + uint16_t no_motion_y : 1; + + /*! no motion interrupt x */ + uint16_t no_motion_x : 1; +#endif +}; +struct bmi160_acc_orient_int_cfg { +#ifdef LITTLE_ENDIAN + + /*! thresholds for switching between the different orientations */ + uint16_t orient_mode : 2; + + /*! blocking_mode */ + uint16_t orient_blocking : 2; + + /*! Orientation interrupt hysteresis */ + uint16_t orient_hyst : 4; + + /*! Orientation interrupt theta */ + uint16_t orient_theta : 6; + + /*! Enable/disable Orientation interrupt */ + uint16_t orient_ud_en : 1; + + /*! exchange x- and z-axis in algorithm ,0 - z, 1 - x */ + uint16_t axes_ex : 1; + + /*! 1 - orient enable, 0 - orient disable */ + uint8_t orient_en : 1; +#else + + /*! 1 - orient enable, 0 - orient disable */ + uint8_t orient_en : 1; + + /*! exchange x- and z-axis in algorithm ,0 - z, 1 - x */ + uint16_t axes_ex : 1; + + /*! Enable/disable Orientation interrupt */ + uint16_t orient_ud_en : 1; + + /*! Orientation interrupt theta */ + uint16_t orient_theta : 6; + + /*! Orientation interrupt hysteresis */ + uint16_t orient_hyst : 4; + + /*! blocking_mode */ + uint16_t orient_blocking : 2; + + /*! thresholds for switching between the different orientations */ + uint16_t orient_mode : 2; +#endif +}; +struct bmi160_acc_flat_detect_int_cfg { +#ifdef LITTLE_ENDIAN + + /*! flat threshold */ + uint16_t flat_theta : 6; + + /*! flat interrupt hysteresis */ + uint16_t flat_hy : 3; + + /*! delay time for which the flat value must remain stable for the + * flat interrupt to be generated */ + uint16_t flat_hold_time : 2; + + /*! 1 - flat enable, 0 - flat disable */ + uint16_t flat_en : 1; +#else + + /*! 1 - flat enable, 0 - flat disable */ + uint16_t flat_en : 1; + + /*! delay time for which the flat value must remain stable for the + * flat interrupt to be generated */ + uint16_t flat_hold_time : 2; + + /*! flat interrupt hysteresis */ + uint16_t flat_hy : 3; + + /*! flat threshold */ + uint16_t flat_theta : 6; +#endif +}; +struct bmi160_acc_low_g_int_cfg { +#ifdef LITTLE_ENDIAN + + /*! low-g interrupt trigger delay */ + uint8_t low_dur; + + /*! low-g interrupt trigger threshold */ + uint8_t low_thres; + + /*! hysteresis of low-g interrupt */ + uint8_t low_hyst : 2; + + /*! 0 - single-axis mode ,1 - axis-summing mode */ + uint8_t low_mode : 1; + + /*! data source 0- filter & 1 pre-filter */ + uint8_t low_data_src : 1; + + /*! 1 - enable low-g, 0 - disable low-g */ + uint8_t low_en : 1; +#else + + /*! 1 - enable low-g, 0 - disable low-g */ + uint8_t low_en : 1; + + /*! data source 0- filter & 1 pre-filter */ + uint8_t low_data_src : 1; + + /*! 0 - single-axis mode ,1 - axis-summing mode */ + uint8_t low_mode : 1; + + /*! hysteresis of low-g interrupt */ + uint8_t low_hyst : 2; + + /*! low-g interrupt trigger threshold */ + uint8_t low_thres; + + /*! low-g interrupt trigger delay */ + uint8_t low_dur; +#endif +}; +struct bmi160_acc_high_g_int_cfg { +#ifdef LITTLE_ENDIAN + + /*! High-g interrupt x, 1 - enable, 0 - disable */ + uint8_t high_g_x : 1; + + /*! High-g interrupt y, 1 - enable, 0 - disable */ + uint8_t high_g_y : 1; + + /*! High-g interrupt z, 1 - enable, 0 - disable */ + uint8_t high_g_z : 1; + + /*! High-g hysteresis */ + uint8_t high_hy : 2; + + /*! data source 0- filter & 1 pre-filter */ + uint8_t high_data_src : 1; + + /*! High-g threshold */ + uint8_t high_thres; + + /*! High-g duration */ + uint8_t high_dur; +#else + + /*! High-g duration */ + uint8_t high_dur; + + /*! High-g threshold */ + uint8_t high_thres; + + /*! data source 0- filter & 1 pre-filter */ + uint8_t high_data_src : 1; + + /*! High-g hysteresis */ + uint8_t high_hy : 2; + + /*! High-g interrupt z, 1 - enable, 0 - disable */ + uint8_t high_g_z : 1; + + /*! High-g interrupt y, 1 - enable, 0 - disable */ + uint8_t high_g_y : 1; + + /*! High-g interrupt x, 1 - enable, 0 - disable */ + uint8_t high_g_x : 1; +#endif +}; +struct bmi160_int_pin_settg { +#ifdef LITTLE_ENDIAN + + /*! To enable either INT1 or INT2 pin as output. + * 0- output disabled ,1- output enabled */ + uint16_t output_en : 1; + + /*! 0 - push-pull 1- open drain,only valid if output_en is set 1 */ + uint16_t output_mode : 1; + + /*! 0 - active low , 1 - active high level. + * if output_en is 1,this applies to interrupts,else PMU_trigger */ + uint16_t output_type : 1; + + /*! 0 - level trigger , 1 - edge trigger */ + uint16_t edge_ctrl : 1; + + /*! To enable either INT1 or INT2 pin as input. + * 0 - input disabled ,1 - input enabled */ + uint16_t input_en : 1; + + /*! latch duration*/ + uint16_t latch_dur : 4; +#else + + /*! latch duration*/ + uint16_t latch_dur : 4; + + /*! Latched,non-latched or temporary interrupt modes */ + uint16_t input_en : 1; + + /*! 1 - edge trigger, 0 - level trigger */ + uint16_t edge_ctrl : 1; + + /*! 0 - active low , 1 - active high level. + * if output_en is 1,this applies to interrupts,else PMU_trigger */ + uint16_t output_type : 1; + + /*! 0 - push-pull , 1 - open drain,only valid if output_en is set 1 */ + uint16_t output_mode : 1; + + /*! To enable either INT1 or INT2 pin as output. + * 0 - output disabled , 1 - output enabled */ + uint16_t output_en : 1; +#endif +}; +union bmi160_int_type_cfg { + /*! Tap interrupt structure */ + struct bmi160_acc_tap_int_cfg acc_tap_int; + + /*! Slope interrupt structure */ + struct bmi160_acc_any_mot_int_cfg acc_any_motion_int; + + /*! Significant motion interrupt structure */ + struct bmi160_acc_sig_mot_int_cfg acc_sig_motion_int; + + /*! Step detector interrupt structure */ + struct bmi160_acc_step_detect_int_cfg acc_step_detect_int; + + /*! No motion interrupt structure */ + struct bmi160_acc_no_motion_int_cfg acc_no_motion_int; + + /*! Orientation interrupt structure */ + struct bmi160_acc_orient_int_cfg acc_orient_int; + + /*! Flat interrupt structure */ + struct bmi160_acc_flat_detect_int_cfg acc_flat_int; + + /*! Low-g interrupt structure */ + struct bmi160_acc_low_g_int_cfg acc_low_g_int; + + /*! High-g interrupt structure */ + struct bmi160_acc_high_g_int_cfg acc_high_g_int; +}; +struct bmi160_int_settg { + /*! Interrupt channel */ + enum bmi160_int_channel int_channel; + + /*! Select Interrupt */ + enum bmi160_int_types int_type; + + /*! Structure configuring Interrupt pins */ + struct bmi160_int_pin_settg int_pin_settg; + + /*! Union configures required interrupt */ + union bmi160_int_type_cfg int_type_cfg; + + /*! FIFO FULL INT 1-enable, 0-disable */ + uint8_t fifo_full_int_en : 1; + + /*! FIFO WTM INT 1-enable, 0-disable */ + uint8_t fifo_wtm_int_en : 1; +}; + +/*! + * @brief This structure holds the information for usage of + * FIFO by the user. + */ +struct bmi160_fifo_frame { + /*! Data buffer of user defined length is to be mapped here */ + uint8_t* data; + + /*! While calling the API "bmi160_get_fifo_data" , length stores + * number of bytes in FIFO to be read (specified by user as input) + * and after execution of the API ,number of FIFO data bytes + * available is provided as an output to user + */ + uint16_t length; + + /*! FIFO time enable */ + uint8_t fifo_time_enable; + + /*! Enabling of the FIFO header to stream in header mode */ + uint8_t fifo_header_enable; + + /*! Streaming of the Accelerometer, Gyroscope + * sensor data or both in FIFO */ + uint8_t fifo_data_enable; + + /*! Will be equal to length when no more frames are there to parse */ + uint16_t accel_byte_start_idx; + + /*! Will be equal to length when no more frames are there to parse */ + uint16_t gyro_byte_start_idx; + + /*! Will be equal to length when no more frames are there to parse */ + uint16_t aux_byte_start_idx; + + /*! Value of FIFO sensor time time */ + uint32_t sensor_time; + + /*! Value of Skipped frame counts */ + uint8_t skipped_frame_count; +}; +struct bmi160_dev { + /*! Chip Id */ + uint8_t chip_id; + + /*! Device Id */ + uint8_t id; + + /*! 0 - I2C , 1 - SPI Interface */ + uint8_t intf; + + /*! Hold active interrupts status for any and sig motion + * 0 - Any-motion enable, 1 - Sig-motion enable, + * -1 neither any-motion nor sig-motion selected */ + enum bmi160_any_sig_motion_active_interrupt_state any_sig_sel; + + /*! Structure to configure Accel sensor */ + struct bmi160_cfg accel_cfg; + + /*! Structure to hold previous/old accel config parameters. + * This is used at driver level to prevent overwriting of same + * data, hence user does not change it in the code */ + struct bmi160_cfg prev_accel_cfg; + + /*! Structure to configure Gyro sensor */ + struct bmi160_cfg gyro_cfg; + + /*! Structure to hold previous/old gyro config parameters. + * This is used at driver level to prevent overwriting of same + * data, hence user does not change it in the code */ + struct bmi160_cfg prev_gyro_cfg; + + /*! Structure to configure the auxiliary sensor */ + struct bmi160_aux_cfg aux_cfg; + + /*! Structure to hold previous/old aux config parameters. + * This is used at driver level to prevent overwriting of same + * data, hence user does not change it in the code */ + struct bmi160_aux_cfg prev_aux_cfg; + + /*! FIFO related configurations */ + struct bmi160_fifo_frame* fifo; + + /*! Read function pointer */ + bmi160_read_fptr_t read; + + /*! Write function pointer */ + bmi160_write_fptr_t write; + + /*! Delay function pointer */ + bmi160_delay_fptr_t delay_ms; + + /*! User set read/write length */ + uint16_t read_write_len; +}; + +#endif /* BMI160_DEFS_H_ */ diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/imu/imu.c b/Applications/Official/DEV_FW/source/airmouse/tracking/imu/imu.c new file mode 100644 index 000000000..5e89c9504 --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/imu/imu.c @@ -0,0 +1,29 @@ +#include "imu.h" +#include + +bool bmi160_begin(); +int bmi160_read(double* vec); + +bool lsm6ds3trc_begin(); +void lsm6ds3trc_end(); +int lsm6ds3trc_read(double* vec); + +bool imu_begin() { + furi_hal_i2c_acquire(&furi_hal_i2c_handle_external); + bool ret = bmi160_begin(); // lsm6ds3trc_begin(); + furi_hal_i2c_release(&furi_hal_i2c_handle_external); + return ret; +} + +void imu_end() { + // furi_hal_i2c_acquire(&furi_hal_i2c_handle_external); + // lsm6ds3trc_end(); + // furi_hal_i2c_release(&furi_hal_i2c_handle_external); +} + +int imu_read(double* vec) { + furi_hal_i2c_acquire(&furi_hal_i2c_handle_external); + int ret = bmi160_read(vec); // lsm6ds3trc_read(vec); + furi_hal_i2c_release(&furi_hal_i2c_handle_external); + return ret; +} diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/imu/imu.h b/Applications/Official/DEV_FW/source/airmouse/tracking/imu/imu.h new file mode 100644 index 000000000..f4c5e4b1d --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/imu/imu.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define ACC_DATA_READY (1 << 0) +#define GYR_DATA_READY (1 << 1) + +bool imu_begin(); +void imu_end(); +int imu_read(double* vec); + +#ifdef __cplusplus +} +#endif diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/imu/imu_bmi160.c b/Applications/Official/DEV_FW/source/airmouse/tracking/imu/imu_bmi160.c new file mode 100644 index 000000000..af771302f --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/imu/imu_bmi160.c @@ -0,0 +1,88 @@ +#include "bmi160.h" + +#include + +#include "imu.h" + +#define TAG "BMI160" + +#define BMI160_DEV_ADDR (0x69 << 1) + +static const double DEG_TO_RAD = 0.017453292519943295769236907684886; +static const double G = 9.81; + +struct bmi160_dev bmi160dev; +struct bmi160_sensor_data bmi160_accel; +struct bmi160_sensor_data bmi160_gyro; + +int8_t bmi160_write_i2c(uint8_t dev_addr, uint8_t reg_addr, uint8_t* data, uint16_t len) { + if(furi_hal_i2c_write_mem(&furi_hal_i2c_handle_external, dev_addr, reg_addr, data, len, 50)) + return BMI160_OK; + return BMI160_E_COM_FAIL; +} + +int8_t bmi160_read_i2c(uint8_t dev_addr, uint8_t reg_addr, uint8_t* read_data, uint16_t len) { + if(furi_hal_i2c_read_mem(&furi_hal_i2c_handle_external, dev_addr, reg_addr, read_data, len, 50)) + return BMI160_OK; + return BMI160_E_COM_FAIL; +} + +bool bmi160_begin() { + FURI_LOG_I(TAG, "Init BMI160"); + + if(!furi_hal_i2c_is_device_ready(&furi_hal_i2c_handle_external, BMI160_DEV_ADDR, 50)) { + FURI_LOG_E(TAG, "Device not ready!"); + return false; + } + + FURI_LOG_I(TAG, "Device ready!"); + + bmi160dev.id = BMI160_DEV_ADDR; + bmi160dev.intf = BMI160_I2C_INTF; + bmi160dev.read = bmi160_read_i2c; + bmi160dev.write = bmi160_write_i2c; + bmi160dev.delay_ms = furi_delay_ms; + + if(bmi160_init(&bmi160dev) != BMI160_OK) { + FURI_LOG_E(TAG, "Initialization failure!"); + FURI_LOG_E(TAG, "Chip ID 0x%X", bmi160dev.chip_id); + return false; + } + + bmi160dev.accel_cfg.odr = BMI160_ACCEL_ODR_400HZ; + bmi160dev.accel_cfg.range = BMI160_ACCEL_RANGE_4G; + bmi160dev.accel_cfg.bw = BMI160_ACCEL_BW_NORMAL_AVG4; + bmi160dev.accel_cfg.power = BMI160_ACCEL_NORMAL_MODE; + bmi160dev.gyro_cfg.odr = BMI160_GYRO_ODR_400HZ; + bmi160dev.gyro_cfg.range = BMI160_GYRO_RANGE_2000_DPS; + bmi160dev.gyro_cfg.bw = BMI160_GYRO_BW_NORMAL_MODE; + bmi160dev.gyro_cfg.power = BMI160_GYRO_NORMAL_MODE; + + if(bmi160_set_sens_conf(&bmi160dev) != BMI160_OK) { + FURI_LOG_E(TAG, "Initialization failure!"); + FURI_LOG_E(TAG, "Chip ID 0x%X", bmi160dev.chip_id); + return false; + } + + FURI_LOG_I(TAG, "Initialization success!"); + FURI_LOG_I(TAG, "Chip ID 0x%X", bmi160dev.chip_id); + + return true; +} + +int bmi160_read(double* vec) { + if(bmi160_get_sensor_data( + (BMI160_ACCEL_SEL | BMI160_GYRO_SEL), &bmi160_accel, &bmi160_gyro, &bmi160dev) != + BMI160_OK) { + return 0; + } + + vec[0] = ((double)bmi160_accel.x * 4 / 32768) * G; + vec[1] = ((double)bmi160_accel.y * 4 / 32768) * G; + vec[2] = ((double)bmi160_accel.z * 4 / 32768) * G; + vec[3] = ((double)bmi160_gyro.x * 2000 / 32768) * DEG_TO_RAD; + vec[4] = ((double)bmi160_gyro.y * 2000 / 32768) * DEG_TO_RAD; + vec[5] = ((double)bmi160_gyro.z * 2000 / 32768) * DEG_TO_RAD; + + return ACC_DATA_READY | GYR_DATA_READY; +} diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/imu/imu_lsm6ds3trc.c b/Applications/Official/DEV_FW/source/airmouse/tracking/imu/imu_lsm6ds3trc.c new file mode 100644 index 000000000..c013fc6e6 --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/imu/imu_lsm6ds3trc.c @@ -0,0 +1,94 @@ +#include "lsm6ds3tr_c_reg.h" + +#include + +#include "imu.h" + +#define TAG "LSM6DS3TR-C" + +#define LSM6DS3_ADDRESS (0x6A << 1) + +static const double DEG_TO_RAD = 0.017453292519943295769236907684886; + +stmdev_ctx_t lsm6ds3trc_ctx; + +int32_t lsm6ds3trc_write_i2c(void* handle, uint8_t reg_addr, const uint8_t* data, uint16_t len) { + if(furi_hal_i2c_write_mem(handle, LSM6DS3_ADDRESS, reg_addr, (uint8_t*)data, len, 50)) + return 0; + return -1; +} + +int32_t lsm6ds3trc_read_i2c(void* handle, uint8_t reg_addr, uint8_t* read_data, uint16_t len) { + if(furi_hal_i2c_read_mem(handle, LSM6DS3_ADDRESS, reg_addr, read_data, len, 50)) return 0; + return -1; +} + +bool lsm6ds3trc_begin() { + FURI_LOG_I(TAG, "Init LSM6DS3TR-C"); + + if(!furi_hal_i2c_is_device_ready(&furi_hal_i2c_handle_external, LSM6DS3_ADDRESS, 50)) { + FURI_LOG_E(TAG, "Not ready"); + return false; + } + + lsm6ds3trc_ctx.write_reg = lsm6ds3trc_write_i2c; + lsm6ds3trc_ctx.read_reg = lsm6ds3trc_read_i2c; + lsm6ds3trc_ctx.mdelay = furi_delay_ms; + lsm6ds3trc_ctx.handle = &furi_hal_i2c_handle_external; + + uint8_t whoami; + lsm6ds3tr_c_device_id_get(&lsm6ds3trc_ctx, &whoami); + if(whoami != LSM6DS3TR_C_ID) { + FURI_LOG_I(TAG, "Unknown model: %x", (int)whoami); + return false; + } + + lsm6ds3tr_c_reset_set(&lsm6ds3trc_ctx, PROPERTY_ENABLE); + uint8_t rst = PROPERTY_ENABLE; + while(rst) lsm6ds3tr_c_reset_get(&lsm6ds3trc_ctx, &rst); + + lsm6ds3tr_c_block_data_update_set(&lsm6ds3trc_ctx, PROPERTY_ENABLE); + lsm6ds3tr_c_fifo_mode_set(&lsm6ds3trc_ctx, LSM6DS3TR_C_BYPASS_MODE); + + lsm6ds3tr_c_xl_data_rate_set(&lsm6ds3trc_ctx, LSM6DS3TR_C_XL_ODR_104Hz); + lsm6ds3tr_c_xl_full_scale_set(&lsm6ds3trc_ctx, LSM6DS3TR_C_4g); + lsm6ds3tr_c_xl_lp1_bandwidth_set(&lsm6ds3trc_ctx, LSM6DS3TR_C_XL_LP1_ODR_DIV_4); + + lsm6ds3tr_c_gy_data_rate_set(&lsm6ds3trc_ctx, LSM6DS3TR_C_GY_ODR_104Hz); + lsm6ds3tr_c_gy_full_scale_set(&lsm6ds3trc_ctx, LSM6DS3TR_C_2000dps); + lsm6ds3tr_c_gy_power_mode_set(&lsm6ds3trc_ctx, LSM6DS3TR_C_GY_HIGH_PERFORMANCE); + lsm6ds3tr_c_gy_band_pass_set(&lsm6ds3trc_ctx, LSM6DS3TR_C_LP2_ONLY); + + FURI_LOG_I(TAG, "Init OK"); + return true; +} + +void lsm6ds3trc_end() { + lsm6ds3tr_c_xl_data_rate_set(&lsm6ds3trc_ctx, LSM6DS3TR_C_XL_ODR_OFF); + lsm6ds3tr_c_gy_data_rate_set(&lsm6ds3trc_ctx, LSM6DS3TR_C_GY_ODR_OFF); +} + +int lsm6ds3trc_read(double* vec) { + int ret = 0; + int16_t data[3]; + lsm6ds3tr_c_reg_t reg; + lsm6ds3tr_c_status_reg_get(&lsm6ds3trc_ctx, ®.status_reg); + + if(reg.status_reg.xlda) { + lsm6ds3tr_c_acceleration_raw_get(&lsm6ds3trc_ctx, data); + vec[2] = (double)lsm6ds3tr_c_from_fs2g_to_mg(data[0]) / 1000; + vec[0] = (double)lsm6ds3tr_c_from_fs2g_to_mg(data[1]) / 1000; + vec[1] = (double)lsm6ds3tr_c_from_fs2g_to_mg(data[2]) / 1000; + ret |= ACC_DATA_READY; + } + + if(reg.status_reg.gda) { + lsm6ds3tr_c_angular_rate_raw_get(&lsm6ds3trc_ctx, data); + vec[5] = (double)lsm6ds3tr_c_from_fs2000dps_to_mdps(data[0]) * DEG_TO_RAD / 1000; + vec[3] = (double)lsm6ds3tr_c_from_fs2000dps_to_mdps(data[1]) * DEG_TO_RAD / 1000; + vec[4] = (double)lsm6ds3tr_c_from_fs2000dps_to_mdps(data[2]) * DEG_TO_RAD / 1000; + ret |= GYR_DATA_READY; + } + + return ret; +} diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/imu/lsm6ds3tr_c_reg.c b/Applications/Official/DEV_FW/source/airmouse/tracking/imu/lsm6ds3tr_c_reg.c new file mode 100644 index 000000000..9f1890d2c --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/imu/lsm6ds3tr_c_reg.c @@ -0,0 +1,7105 @@ +/** + ****************************************************************************** + * @file lsm6ds3tr_c_reg.c + * @author Sensors Software Solution Team + * @brief LSM6DS3TR_C driver file + ****************************************************************************** + * @attention + * + *

© Copyright (c) 2021 STMicroelectronics. + * All rights reserved.

+ * + * This software component is licensed by ST under BSD 3-Clause license, + * the "License"; You may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * opensource.org/licenses/BSD-3-Clause + * + ****************************************************************************** + */ + +#include "lsm6ds3tr_c_reg.h" + +/** + * @defgroup LSM6DS3TR_C + * @brief This file provides a set of functions needed to drive the + * lsm6ds3tr_c enanced inertial module. + * @{ + * + */ + +/** + * @defgroup LSM6DS3TR_C_interfaces_functions + * @brief This section provide a set of functions used to read and + * write a generic register of the device. + * MANDATORY: return 0 -> no Error. + * @{ + * + */ + +/** + * @brief Read generic device register + * + * @param ctx read / write interface definitions(ptr) + * @param reg register to read + * @param data pointer to buffer that store the data read(ptr) + * @param len number of consecutive register to read + * @retval interface status (MANDATORY: return 0 -> no Error) + * + */ +int32_t lsm6ds3tr_c_read_reg(stmdev_ctx_t* ctx, uint8_t reg, uint8_t* data, uint16_t len) { + int32_t ret; + + ret = ctx->read_reg(ctx->handle, reg, data, len); + + return ret; +} + +/** + * @brief Write generic device register + * + * @param ctx read / write interface definitions(ptr) + * @param reg register to write + * @param data pointer to data to write in register reg(ptr) + * @param len number of consecutive register to write + * @retval interface status (MANDATORY: return 0 -> no Error) + * + */ +int32_t lsm6ds3tr_c_write_reg(stmdev_ctx_t* ctx, uint8_t reg, uint8_t* data, uint16_t len) { + int32_t ret; + + ret = ctx->write_reg(ctx->handle, reg, data, len); + + return ret; +} + +/** + * @} + * + */ + +/** + * @defgroup LSM6DS3TR_C_Sensitivity + * @brief These functions convert raw-data into engineering units. + * @{ + * + */ + +float_t lsm6ds3tr_c_from_fs2g_to_mg(int16_t lsb) { + return ((float_t)lsb * 0.061f); +} + +float_t lsm6ds3tr_c_from_fs4g_to_mg(int16_t lsb) { + return ((float_t)lsb * 0.122f); +} + +float_t lsm6ds3tr_c_from_fs8g_to_mg(int16_t lsb) { + return ((float_t)lsb * 0.244f); +} + +float_t lsm6ds3tr_c_from_fs16g_to_mg(int16_t lsb) { + return ((float_t)lsb * 0.488f); +} + +float_t lsm6ds3tr_c_from_fs125dps_to_mdps(int16_t lsb) { + return ((float_t)lsb * 4.375f); +} + +float_t lsm6ds3tr_c_from_fs250dps_to_mdps(int16_t lsb) { + return ((float_t)lsb * 8.750f); +} + +float_t lsm6ds3tr_c_from_fs500dps_to_mdps(int16_t lsb) { + return ((float_t)lsb * 17.50f); +} + +float_t lsm6ds3tr_c_from_fs1000dps_to_mdps(int16_t lsb) { + return ((float_t)lsb * 35.0f); +} + +float_t lsm6ds3tr_c_from_fs2000dps_to_mdps(int16_t lsb) { + return ((float_t)lsb * 70.0f); +} + +float_t lsm6ds3tr_c_from_lsb_to_celsius(int16_t lsb) { + return (((float_t)lsb / 256.0f) + 25.0f); +} + +/** + * @} + * + */ + +/** + * @defgroup LSM6DS3TR_C_data_generation + * @brief This section groups all the functions concerning data + * generation + * @{ + * + */ + +/** + * @brief Accelerometer full-scale selection.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of fs_xl in reg CTRL1_XL + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_xl_full_scale_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_fs_xl_t val) { + lsm6ds3tr_c_ctrl1_xl_t ctrl1_xl; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL1_XL, (uint8_t*)&ctrl1_xl, 1); + + if(ret == 0) { + ctrl1_xl.fs_xl = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL1_XL, (uint8_t*)&ctrl1_xl, 1); + } + + return ret; +} + +/** + * @brief Accelerometer full-scale selection.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of fs_xl in reg CTRL1_XL + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_xl_full_scale_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_fs_xl_t* val) { + lsm6ds3tr_c_ctrl1_xl_t ctrl1_xl; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL1_XL, (uint8_t*)&ctrl1_xl, 1); + + switch(ctrl1_xl.fs_xl) { + case LSM6DS3TR_C_2g: + *val = LSM6DS3TR_C_2g; + break; + + case LSM6DS3TR_C_16g: + *val = LSM6DS3TR_C_16g; + break; + + case LSM6DS3TR_C_4g: + *val = LSM6DS3TR_C_4g; + break; + + case LSM6DS3TR_C_8g: + *val = LSM6DS3TR_C_8g; + break; + + default: + *val = LSM6DS3TR_C_XL_FS_ND; + break; + } + + return ret; +} + +/** + * @brief Accelerometer data rate selection.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of odr_xl in reg CTRL1_XL + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_xl_data_rate_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_odr_xl_t val) { + lsm6ds3tr_c_ctrl1_xl_t ctrl1_xl; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL1_XL, (uint8_t*)&ctrl1_xl, 1); + + if(ret == 0) { + ctrl1_xl.odr_xl = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL1_XL, (uint8_t*)&ctrl1_xl, 1); + } + + return ret; +} + +/** + * @brief Accelerometer data rate selection.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of odr_xl in reg CTRL1_XL + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_xl_data_rate_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_odr_xl_t* val) { + lsm6ds3tr_c_ctrl1_xl_t ctrl1_xl; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL1_XL, (uint8_t*)&ctrl1_xl, 1); + + switch(ctrl1_xl.odr_xl) { + case LSM6DS3TR_C_XL_ODR_OFF: + *val = LSM6DS3TR_C_XL_ODR_OFF; + break; + + case LSM6DS3TR_C_XL_ODR_12Hz5: + *val = LSM6DS3TR_C_XL_ODR_12Hz5; + break; + + case LSM6DS3TR_C_XL_ODR_26Hz: + *val = LSM6DS3TR_C_XL_ODR_26Hz; + break; + + case LSM6DS3TR_C_XL_ODR_52Hz: + *val = LSM6DS3TR_C_XL_ODR_52Hz; + break; + + case LSM6DS3TR_C_XL_ODR_104Hz: + *val = LSM6DS3TR_C_XL_ODR_104Hz; + break; + + case LSM6DS3TR_C_XL_ODR_208Hz: + *val = LSM6DS3TR_C_XL_ODR_208Hz; + break; + + case LSM6DS3TR_C_XL_ODR_416Hz: + *val = LSM6DS3TR_C_XL_ODR_416Hz; + break; + + case LSM6DS3TR_C_XL_ODR_833Hz: + *val = LSM6DS3TR_C_XL_ODR_833Hz; + break; + + case LSM6DS3TR_C_XL_ODR_1k66Hz: + *val = LSM6DS3TR_C_XL_ODR_1k66Hz; + break; + + case LSM6DS3TR_C_XL_ODR_3k33Hz: + *val = LSM6DS3TR_C_XL_ODR_3k33Hz; + break; + + case LSM6DS3TR_C_XL_ODR_6k66Hz: + *val = LSM6DS3TR_C_XL_ODR_6k66Hz; + break; + + case LSM6DS3TR_C_XL_ODR_1Hz6: + *val = LSM6DS3TR_C_XL_ODR_1Hz6; + break; + + default: + *val = LSM6DS3TR_C_XL_ODR_ND; + break; + } + + return ret; +} + +/** + * @brief Gyroscope chain full-scale selection.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of fs_g in reg CTRL2_G + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_gy_full_scale_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_fs_g_t val) { + lsm6ds3tr_c_ctrl2_g_t ctrl2_g; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL2_G, (uint8_t*)&ctrl2_g, 1); + + if(ret == 0) { + ctrl2_g.fs_g = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL2_G, (uint8_t*)&ctrl2_g, 1); + } + + return ret; +} + +/** + * @brief Gyroscope chain full-scale selection.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of fs_g in reg CTRL2_G + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_gy_full_scale_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_fs_g_t* val) { + lsm6ds3tr_c_ctrl2_g_t ctrl2_g; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL2_G, (uint8_t*)&ctrl2_g, 1); + + switch(ctrl2_g.fs_g) { + case LSM6DS3TR_C_250dps: + *val = LSM6DS3TR_C_250dps; + break; + + case LSM6DS3TR_C_125dps: + *val = LSM6DS3TR_C_125dps; + break; + + case LSM6DS3TR_C_500dps: + *val = LSM6DS3TR_C_500dps; + break; + + case LSM6DS3TR_C_1000dps: + *val = LSM6DS3TR_C_1000dps; + break; + + case LSM6DS3TR_C_2000dps: + *val = LSM6DS3TR_C_2000dps; + break; + + default: + *val = LSM6DS3TR_C_GY_FS_ND; + break; + } + + return ret; +} + +/** + * @brief Gyroscope data rate selection.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of odr_g in reg CTRL2_G + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_gy_data_rate_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_odr_g_t val) { + lsm6ds3tr_c_ctrl2_g_t ctrl2_g; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL2_G, (uint8_t*)&ctrl2_g, 1); + + if(ret == 0) { + ctrl2_g.odr_g = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL2_G, (uint8_t*)&ctrl2_g, 1); + } + + return ret; +} + +/** + * @brief Gyroscope data rate selection.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of odr_g in reg CTRL2_G + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_gy_data_rate_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_odr_g_t* val) { + lsm6ds3tr_c_ctrl2_g_t ctrl2_g; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL2_G, (uint8_t*)&ctrl2_g, 1); + + switch(ctrl2_g.odr_g) { + case LSM6DS3TR_C_GY_ODR_OFF: + *val = LSM6DS3TR_C_GY_ODR_OFF; + break; + + case LSM6DS3TR_C_GY_ODR_12Hz5: + *val = LSM6DS3TR_C_GY_ODR_12Hz5; + break; + + case LSM6DS3TR_C_GY_ODR_26Hz: + *val = LSM6DS3TR_C_GY_ODR_26Hz; + break; + + case LSM6DS3TR_C_GY_ODR_52Hz: + *val = LSM6DS3TR_C_GY_ODR_52Hz; + break; + + case LSM6DS3TR_C_GY_ODR_104Hz: + *val = LSM6DS3TR_C_GY_ODR_104Hz; + break; + + case LSM6DS3TR_C_GY_ODR_208Hz: + *val = LSM6DS3TR_C_GY_ODR_208Hz; + break; + + case LSM6DS3TR_C_GY_ODR_416Hz: + *val = LSM6DS3TR_C_GY_ODR_416Hz; + break; + + case LSM6DS3TR_C_GY_ODR_833Hz: + *val = LSM6DS3TR_C_GY_ODR_833Hz; + break; + + case LSM6DS3TR_C_GY_ODR_1k66Hz: + *val = LSM6DS3TR_C_GY_ODR_1k66Hz; + break; + + case LSM6DS3TR_C_GY_ODR_3k33Hz: + *val = LSM6DS3TR_C_GY_ODR_3k33Hz; + break; + + case LSM6DS3TR_C_GY_ODR_6k66Hz: + *val = LSM6DS3TR_C_GY_ODR_6k66Hz; + break; + + default: + *val = LSM6DS3TR_C_GY_ODR_ND; + break; + } + + return ret; +} + +/** + * @brief Block data update.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of bdu in reg CTRL3_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_block_data_update_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_ctrl3_c_t ctrl3_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL3_C, (uint8_t*)&ctrl3_c, 1); + + if(ret == 0) { + ctrl3_c.bdu = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL3_C, (uint8_t*)&ctrl3_c, 1); + } + + return ret; +} + +/** + * @brief Block data update.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of bdu in reg CTRL3_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_block_data_update_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_ctrl3_c_t ctrl3_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL3_C, (uint8_t*)&ctrl3_c, 1); + *val = ctrl3_c.bdu; + + return ret; +} + +/** + * @brief Weight of XL user offset bits of registers + * X_OFS_USR(73h), Y_OFS_USR(74h), Z_OFS_USR(75h).[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of usr_off_w in reg CTRL6_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_xl_offset_weight_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_usr_off_w_t val) { + lsm6ds3tr_c_ctrl6_c_t ctrl6_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL6_C, (uint8_t*)&ctrl6_c, 1); + + if(ret == 0) { + ctrl6_c.usr_off_w = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL6_C, (uint8_t*)&ctrl6_c, 1); + } + + return ret; +} + +/** + * @brief Weight of XL user offset bits of registers + * X_OFS_USR(73h), Y_OFS_USR(74h), Z_OFS_USR(75h).[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of usr_off_w in reg CTRL6_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_xl_offset_weight_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_usr_off_w_t* val) { + lsm6ds3tr_c_ctrl6_c_t ctrl6_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL6_C, (uint8_t*)&ctrl6_c, 1); + + switch(ctrl6_c.usr_off_w) { + case LSM6DS3TR_C_LSb_1mg: + *val = LSM6DS3TR_C_LSb_1mg; + break; + + case LSM6DS3TR_C_LSb_16mg: + *val = LSM6DS3TR_C_LSb_16mg; + break; + + default: + *val = LSM6DS3TR_C_WEIGHT_ND; + break; + } + + return ret; +} + +/** + * @brief High-performance operating mode for accelerometer[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of xl_hm_mode in reg CTRL6_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_xl_power_mode_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_xl_hm_mode_t val) { + lsm6ds3tr_c_ctrl6_c_t ctrl6_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL6_C, (uint8_t*)&ctrl6_c, 1); + + if(ret == 0) { + ctrl6_c.xl_hm_mode = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL6_C, (uint8_t*)&ctrl6_c, 1); + } + + return ret; +} + +/** + * @brief High-performance operating mode for accelerometer.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of xl_hm_mode in reg CTRL6_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_xl_power_mode_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_xl_hm_mode_t* val) { + lsm6ds3tr_c_ctrl6_c_t ctrl6_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL6_C, (uint8_t*)&ctrl6_c, 1); + + switch(ctrl6_c.xl_hm_mode) { + case LSM6DS3TR_C_XL_HIGH_PERFORMANCE: + *val = LSM6DS3TR_C_XL_HIGH_PERFORMANCE; + break; + + case LSM6DS3TR_C_XL_NORMAL: + *val = LSM6DS3TR_C_XL_NORMAL; + break; + + default: + *val = LSM6DS3TR_C_XL_PW_MODE_ND; + break; + } + + return ret; +} + +/** + * @brief Source register rounding function on WAKE_UP_SRC (1Bh), + * TAP_SRC (1Ch), D6D_SRC (1Dh), STATUS_REG (1Eh) and + * FUNC_SRC1 (53h) registers in the primary interface.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of rounding_status in reg CTRL7_G + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_rounding_on_status_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_rounding_status_t val) { + lsm6ds3tr_c_ctrl7_g_t ctrl7_g; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL7_G, (uint8_t*)&ctrl7_g, 1); + + if(ret == 0) { + ctrl7_g.rounding_status = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL7_G, (uint8_t*)&ctrl7_g, 1); + } + + return ret; +} + +/** + * @brief Source register rounding function on WAKE_UP_SRC (1Bh), + * TAP_SRC (1Ch), D6D_SRC (1Dh), STATUS_REG (1Eh) and + * FUNC_SRC1 (53h) registers in the primary interface.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of rounding_status in reg CTRL7_G + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_rounding_on_status_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_rounding_status_t* val) { + lsm6ds3tr_c_ctrl7_g_t ctrl7_g; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL7_G, (uint8_t*)&ctrl7_g, 1); + + switch(ctrl7_g.rounding_status) { + case LSM6DS3TR_C_STAT_RND_DISABLE: + *val = LSM6DS3TR_C_STAT_RND_DISABLE; + break; + + case LSM6DS3TR_C_STAT_RND_ENABLE: + *val = LSM6DS3TR_C_STAT_RND_ENABLE; + break; + + default: + *val = LSM6DS3TR_C_STAT_RND_ND; + break; + } + + return ret; +} + +/** + * @brief High-performance operating mode disable for gyroscope.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of g_hm_mode in reg CTRL7_G + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_gy_power_mode_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_g_hm_mode_t val) { + lsm6ds3tr_c_ctrl7_g_t ctrl7_g; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL7_G, (uint8_t*)&ctrl7_g, 1); + + if(ret == 0) { + ctrl7_g.g_hm_mode = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL7_G, (uint8_t*)&ctrl7_g, 1); + } + + return ret; +} + +/** + * @brief High-performance operating mode disable for gyroscope.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of g_hm_mode in reg CTRL7_G + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_gy_power_mode_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_g_hm_mode_t* val) { + lsm6ds3tr_c_ctrl7_g_t ctrl7_g; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL7_G, (uint8_t*)&ctrl7_g, 1); + + switch(ctrl7_g.g_hm_mode) { + case LSM6DS3TR_C_GY_HIGH_PERFORMANCE: + *val = LSM6DS3TR_C_GY_HIGH_PERFORMANCE; + break; + + case LSM6DS3TR_C_GY_NORMAL: + *val = LSM6DS3TR_C_GY_NORMAL; + break; + + default: + *val = LSM6DS3TR_C_GY_PW_MODE_ND; + break; + } + + return ret; +} + +/** + * @brief Read all the interrupt/status flag of the device.[get] + * + * @param ctx Read / write interface definitions + * @param val WAKE_UP_SRC, TAP_SRC, D6D_SRC, STATUS_REG, + * FUNC_SRC1, FUNC_SRC2, WRIST_TILT_IA, A_WRIST_TILT_Mask + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_all_sources_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_all_sources_t* val) { + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_WAKE_UP_SRC, (uint8_t*)&(val->wake_up_src), 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_TAP_SRC, (uint8_t*)&(val->tap_src), 1); + } + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_D6D_SRC, (uint8_t*)&(val->d6d_src), 1); + } + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_STATUS_REG, (uint8_t*)&(val->status_reg), 1); + } + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FUNC_SRC1, (uint8_t*)&(val->func_src1), 1); + } + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FUNC_SRC2, (uint8_t*)&(val->func_src2), 1); + } + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg( + ctx, LSM6DS3TR_C_WRIST_TILT_IA, (uint8_t*)&(val->wrist_tilt_ia), 1); + } + + if(ret == 0) { + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_B); + } + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg( + ctx, LSM6DS3TR_C_A_WRIST_TILT_MASK, (uint8_t*)&(val->a_wrist_tilt_mask), 1); + } + + if(ret == 0) { + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + + return ret; +} +/** + * @brief The STATUS_REG register is read by the primary interface[get] + * + * @param ctx Read / write interface definitions + * @param val Registers STATUS_REG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_status_reg_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_status_reg_t* val) { + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_STATUS_REG, (uint8_t*)val, 1); + + return ret; +} + +/** + * @brief Accelerometer new data available.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of xlda in reg STATUS_REG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_xl_flag_data_ready_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_status_reg_t status_reg; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_STATUS_REG, (uint8_t*)&status_reg, 1); + *val = status_reg.xlda; + + return ret; +} + +/** + * @brief Gyroscope new data available.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of gda in reg STATUS_REG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_gy_flag_data_ready_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_status_reg_t status_reg; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_STATUS_REG, (uint8_t*)&status_reg, 1); + *val = status_reg.gda; + + return ret; +} + +/** + * @brief Temperature new data available.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of tda in reg STATUS_REG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_temp_flag_data_ready_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_status_reg_t status_reg; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_STATUS_REG, (uint8_t*)&status_reg, 1); + *val = status_reg.tda; + + return ret; +} + +/** + * @brief Accelerometer axis user offset correction expressed in two’s + * complement, weight depends on USR_OFF_W in CTRL6_C. + * The value must be in the range [-127 127].[set] + * + * @param ctx Read / write interface definitions + * @param buff Buffer that contains data to write + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_xl_usr_offset_set(stmdev_ctx_t* ctx, uint8_t* buff) { + int32_t ret; + + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_X_OFS_USR, buff, 3); + + return ret; +} + +/** + * @brief Accelerometer axis user offset correction xpressed in two’s + * complement, weight depends on USR_OFF_W in CTRL6_C. + * The value must be in the range [-127 127].[get] + * + * @param ctx Read / write interface definitions + * @param buff Buffer that stores data read + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_xl_usr_offset_get(stmdev_ctx_t* ctx, uint8_t* buff) { + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_X_OFS_USR, buff, 3); + + return ret; +} + +/** + * @} + * + */ + +/** + * @defgroup LSM6DS3TR_C_Timestamp + * @brief This section groups all the functions that manage the + * timestamp generation. + * @{ + * + */ + +/** + * @brief Enable timestamp count. The count is saved in TIMESTAMP0_REG (40h), + * TIMESTAMP1_REG (41h) and TIMESTAMP2_REG (42h).[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of timer_en in reg CTRL10_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_timestamp_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_ctrl10_c_t ctrl10_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL10_C, (uint8_t*)&ctrl10_c, 1); + + if(ret == 0) { + ctrl10_c.timer_en = val; + + if(val != 0x00U) { + ctrl10_c.func_en = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL10_C, (uint8_t*)&ctrl10_c, 1); + } + } + + return ret; +} + +/** + * @brief Enable timestamp count. The count is saved in TIMESTAMP0_REG (40h), + * TIMESTAMP1_REG (41h) and TIMESTAMP2_REG (42h).[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of timer_en in reg CTRL10_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_timestamp_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_ctrl10_c_t ctrl10_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL10_C, (uint8_t*)&ctrl10_c, 1); + *val = ctrl10_c.timer_en; + + return ret; +} + +/** + * @brief Timestamp register resolution setting. + * Configuration of this bit affects + * TIMESTAMP0_REG(40h), TIMESTAMP1_REG(41h), + * TIMESTAMP2_REG(42h), STEP_TIMESTAMP_L(49h), + * STEP_TIMESTAMP_H(4Ah) and + * STEP_COUNT_DELTA(15h) registers.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of timer_hr in reg WAKE_UP_DUR + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_timestamp_res_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_timer_hr_t val) { + lsm6ds3tr_c_wake_up_dur_t wake_up_dur; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_WAKE_UP_DUR, (uint8_t*)&wake_up_dur, 1); + + if(ret == 0) { + wake_up_dur.timer_hr = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_WAKE_UP_DUR, (uint8_t*)&wake_up_dur, 1); + } + + return ret; +} + +/** + * @brief Timestamp register resolution setting. + * Configuration of this bit affects + * TIMESTAMP0_REG(40h), TIMESTAMP1_REG(41h), + * TIMESTAMP2_REG(42h), STEP_TIMESTAMP_L(49h), + * STEP_TIMESTAMP_H(4Ah) and + * STEP_COUNT_DELTA(15h) registers.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of timer_hr in reg WAKE_UP_DUR + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_timestamp_res_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_timer_hr_t* val) { + lsm6ds3tr_c_wake_up_dur_t wake_up_dur; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_WAKE_UP_DUR, (uint8_t*)&wake_up_dur, 1); + + switch(wake_up_dur.timer_hr) { + case LSM6DS3TR_C_LSB_6ms4: + *val = LSM6DS3TR_C_LSB_6ms4; + break; + + case LSM6DS3TR_C_LSB_25us: + *val = LSM6DS3TR_C_LSB_25us; + break; + + default: + *val = LSM6DS3TR_C_TS_RES_ND; + break; + } + + return ret; +} + +/** + * @} + * + */ + +/** + * @defgroup LSM6DS3TR_C_Dataoutput + * @brief This section groups all the data output functions. + * @{ + * + */ + +/** + * @brief Circular burst-mode (rounding) read from output registers + * through the primary interface.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of rounding in reg CTRL5_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_rounding_mode_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_rounding_t val) { + lsm6ds3tr_c_ctrl5_c_t ctrl5_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL5_C, (uint8_t*)&ctrl5_c, 1); + + if(ret == 0) { + ctrl5_c.rounding = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL5_C, (uint8_t*)&ctrl5_c, 1); + } + + return ret; +} + +/** + * @brief Circular burst-mode (rounding) read from output registers + * through the primary interface.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of rounding in reg CTRL5_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_rounding_mode_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_rounding_t* val) { + lsm6ds3tr_c_ctrl5_c_t ctrl5_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL5_C, (uint8_t*)&ctrl5_c, 1); + + switch(ctrl5_c.rounding) { + case LSM6DS3TR_C_ROUND_DISABLE: + *val = LSM6DS3TR_C_ROUND_DISABLE; + break; + + case LSM6DS3TR_C_ROUND_XL: + *val = LSM6DS3TR_C_ROUND_XL; + break; + + case LSM6DS3TR_C_ROUND_GY: + *val = LSM6DS3TR_C_ROUND_GY; + break; + + case LSM6DS3TR_C_ROUND_GY_XL: + *val = LSM6DS3TR_C_ROUND_GY_XL; + break; + + case LSM6DS3TR_C_ROUND_SH1_TO_SH6: + *val = LSM6DS3TR_C_ROUND_SH1_TO_SH6; + break; + + case LSM6DS3TR_C_ROUND_XL_SH1_TO_SH6: + *val = LSM6DS3TR_C_ROUND_XL_SH1_TO_SH6; + break; + + case LSM6DS3TR_C_ROUND_GY_XL_SH1_TO_SH12: + *val = LSM6DS3TR_C_ROUND_GY_XL_SH1_TO_SH12; + break; + + case LSM6DS3TR_C_ROUND_GY_XL_SH1_TO_SH6: + *val = LSM6DS3TR_C_ROUND_GY_XL_SH1_TO_SH6; + break; + + default: + *val = LSM6DS3TR_C_ROUND_OUT_ND; + break; + } + + return ret; +} + +/** + * @brief Temperature data output register (r). L and H registers together + * express a 16-bit word in two’s complement.[get] + * + * @param ctx Read / write interface definitions + * @param buff Buffer that stores data read + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_temperature_raw_get(stmdev_ctx_t* ctx, int16_t* val) { + uint8_t buff[2]; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_OUT_TEMP_L, buff, 2); + *val = (int16_t)buff[1]; + *val = (*val * 256) + (int16_t)buff[0]; + + return ret; +} + +/** + * @brief Angular rate sensor. The value is expressed as a 16-bit word in + * two’s complement.[get] + * + * @param ctx Read / write interface definitions + * @param buff Buffer that stores data read + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_angular_rate_raw_get(stmdev_ctx_t* ctx, int16_t* val) { + uint8_t buff[6]; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_OUTX_L_G, buff, 6); + val[0] = (int16_t)buff[1]; + val[0] = (val[0] * 256) + (int16_t)buff[0]; + val[1] = (int16_t)buff[3]; + val[1] = (val[1] * 256) + (int16_t)buff[2]; + val[2] = (int16_t)buff[5]; + val[2] = (val[2] * 256) + (int16_t)buff[4]; + + return ret; +} + +/** + * @brief Linear acceleration output register. The value is expressed + * as a 16-bit word in two’s complement.[get] + * + * @param ctx Read / write interface definitions + * @param buff Buffer that stores data read + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_acceleration_raw_get(stmdev_ctx_t* ctx, int16_t* val) { + uint8_t buff[6]; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_OUTX_L_XL, buff, 6); + val[0] = (int16_t)buff[1]; + val[0] = (val[0] * 256) + (int16_t)buff[0]; + val[1] = (int16_t)buff[3]; + val[1] = (val[1] * 256) + (int16_t)buff[2]; + val[2] = (int16_t)buff[5]; + val[2] = (val[2] * 256) + (int16_t)buff[4]; + + return ret; +} + +/** + * @brief External magnetometer raw data.[get] + * + * @param ctx Read / write interface definitions + * @param buff Buffer that stores data read + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_mag_calibrated_raw_get(stmdev_ctx_t* ctx, int16_t* val) { + uint8_t buff[6]; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_OUT_MAG_RAW_X_L, buff, 6); + val[0] = (int16_t)buff[1]; + val[0] = (val[0] * 256) + (int16_t)buff[0]; + val[1] = (int16_t)buff[3]; + val[1] = (val[1] * 256) + (int16_t)buff[2]; + val[2] = (int16_t)buff[5]; + val[2] = (val[2] * 256) + (int16_t)buff[4]; + + return ret; +} + +/** + * @brief Read data in FIFO.[get] + * + * @param ctx Read / write interface definitions + * @param buffer Data buffer to store FIFO data. + * @param len Number of data to read from FIFO. + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_fifo_raw_data_get(stmdev_ctx_t* ctx, uint8_t* buffer, uint8_t len) { + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_DATA_OUT_L, buffer, len); + + return ret; +} + +/** + * @} + * + */ + +/** + * @defgroup LSM6DS3TR_C_common + * @brief This section groups common useful functions. + * @{ + * + */ + +/** + * @brief Enable access to the embedded functions/sensor hub + * configuration registers[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of func_cfg_en in reg FUNC_CFG_ACCESS + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_mem_bank_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_func_cfg_en_t val) { + lsm6ds3tr_c_func_cfg_access_t func_cfg_access; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FUNC_CFG_ACCESS, (uint8_t*)&func_cfg_access, 1); + + if(ret == 0) { + func_cfg_access.func_cfg_en = (uint8_t)val; + ret = + lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_FUNC_CFG_ACCESS, (uint8_t*)&func_cfg_access, 1); + } + + return ret; +} + +/** + * @brief Enable access to the embedded functions/sensor hub configuration + * registers[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of func_cfg_en in reg FUNC_CFG_ACCESS + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_mem_bank_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_func_cfg_en_t* val) { + lsm6ds3tr_c_func_cfg_access_t func_cfg_access; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FUNC_CFG_ACCESS, (uint8_t*)&func_cfg_access, 1); + + switch(func_cfg_access.func_cfg_en) { + case LSM6DS3TR_C_USER_BANK: + *val = LSM6DS3TR_C_USER_BANK; + break; + + case LSM6DS3TR_C_BANK_B: + *val = LSM6DS3TR_C_BANK_B; + break; + + default: + *val = LSM6DS3TR_C_BANK_ND; + break; + } + + return ret; +} + +/** + * @brief Data-ready pulsed / letched mode[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of drdy_pulsed in reg DRDY_PULSE_CFG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_data_ready_mode_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_drdy_pulsed_g_t val) { + lsm6ds3tr_c_drdy_pulse_cfg_g_t drdy_pulse_cfg_g; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_DRDY_PULSE_CFG_G, (uint8_t*)&drdy_pulse_cfg_g, 1); + + if(ret == 0) { + drdy_pulse_cfg_g.drdy_pulsed = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg( + ctx, LSM6DS3TR_C_DRDY_PULSE_CFG_G, (uint8_t*)&drdy_pulse_cfg_g, 1); + } + + return ret; +} + +/** + * @brief Data-ready pulsed / letched mode[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of drdy_pulsed in reg DRDY_PULSE_CFG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_data_ready_mode_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_drdy_pulsed_g_t* val) { + lsm6ds3tr_c_drdy_pulse_cfg_g_t drdy_pulse_cfg_g; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_DRDY_PULSE_CFG_G, (uint8_t*)&drdy_pulse_cfg_g, 1); + + switch(drdy_pulse_cfg_g.drdy_pulsed) { + case LSM6DS3TR_C_DRDY_LATCHED: + *val = LSM6DS3TR_C_DRDY_LATCHED; + break; + + case LSM6DS3TR_C_DRDY_PULSED: + *val = LSM6DS3TR_C_DRDY_PULSED; + break; + + default: + *val = LSM6DS3TR_C_DRDY_ND; + break; + } + + return ret; +} + +/** + * @brief DeviceWhoamI.[get] + * + * @param ctx Read / write interface definitions + * @param buff Buffer that stores data read + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_device_id_get(stmdev_ctx_t* ctx, uint8_t* buff) { + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_WHO_AM_I, buff, 1); + + return ret; +} + +/** + * @brief Software reset. Restore the default values in user registers[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of sw_reset in reg CTRL3_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_reset_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_ctrl3_c_t ctrl3_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL3_C, (uint8_t*)&ctrl3_c, 1); + + if(ret == 0) { + ctrl3_c.sw_reset = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL3_C, (uint8_t*)&ctrl3_c, 1); + } + + return ret; +} + +/** + * @brief Software reset. Restore the default values in user registers[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of sw_reset in reg CTRL3_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_reset_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_ctrl3_c_t ctrl3_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL3_C, (uint8_t*)&ctrl3_c, 1); + *val = ctrl3_c.sw_reset; + + return ret; +} + +/** + * @brief Big/Little Endian Data selection.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of ble in reg CTRL3_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_data_format_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_ble_t val) { + lsm6ds3tr_c_ctrl3_c_t ctrl3_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL3_C, (uint8_t*)&ctrl3_c, 1); + + if(ret == 0) { + ctrl3_c.ble = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL3_C, (uint8_t*)&ctrl3_c, 1); + } + + return ret; +} + +/** + * @brief Big/Little Endian Data selection.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of ble in reg CTRL3_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_data_format_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_ble_t* val) { + lsm6ds3tr_c_ctrl3_c_t ctrl3_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL3_C, (uint8_t*)&ctrl3_c, 1); + + switch(ctrl3_c.ble) { + case LSM6DS3TR_C_LSB_AT_LOW_ADD: + *val = LSM6DS3TR_C_LSB_AT_LOW_ADD; + break; + + case LSM6DS3TR_C_MSB_AT_LOW_ADD: + *val = LSM6DS3TR_C_MSB_AT_LOW_ADD; + break; + + default: + *val = LSM6DS3TR_C_DATA_FMT_ND; + break; + } + + return ret; +} + +/** + * @brief Register address automatically incremented during a multiple byte + * access with a serial interface.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of if_inc in reg CTRL3_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_auto_increment_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_ctrl3_c_t ctrl3_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL3_C, (uint8_t*)&ctrl3_c, 1); + + if(ret == 0) { + ctrl3_c.if_inc = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL3_C, (uint8_t*)&ctrl3_c, 1); + } + + return ret; +} + +/** + * @brief Register address automatically incremented during a multiple byte + * access with a serial interface.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of if_inc in reg CTRL3_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_auto_increment_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_ctrl3_c_t ctrl3_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL3_C, (uint8_t*)&ctrl3_c, 1); + *val = ctrl3_c.if_inc; + + return ret; +} + +/** + * @brief Reboot memory content. Reload the calibration parameters.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of boot in reg CTRL3_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_boot_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_ctrl3_c_t ctrl3_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL3_C, (uint8_t*)&ctrl3_c, 1); + + if(ret == 0) { + ctrl3_c.boot = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL3_C, (uint8_t*)&ctrl3_c, 1); + } + + return ret; +} + +/** + * @brief Reboot memory content. Reload the calibration parameters.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of boot in reg CTRL3_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_boot_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_ctrl3_c_t ctrl3_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL3_C, (uint8_t*)&ctrl3_c, 1); + *val = ctrl3_c.boot; + + return ret; +} + +/** + * @brief Linear acceleration sensor self-test enable.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of st_xl in reg CTRL5_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_xl_self_test_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_st_xl_t val) { + lsm6ds3tr_c_ctrl5_c_t ctrl5_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL5_C, (uint8_t*)&ctrl5_c, 1); + + if(ret == 0) { + ctrl5_c.st_xl = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL5_C, (uint8_t*)&ctrl5_c, 1); + } + + return ret; +} + +/** + * @brief Linear acceleration sensor self-test enable.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of st_xl in reg CTRL5_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_xl_self_test_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_st_xl_t* val) { + lsm6ds3tr_c_ctrl5_c_t ctrl5_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL5_C, (uint8_t*)&ctrl5_c, 1); + + switch(ctrl5_c.st_xl) { + case LSM6DS3TR_C_XL_ST_DISABLE: + *val = LSM6DS3TR_C_XL_ST_DISABLE; + break; + + case LSM6DS3TR_C_XL_ST_POSITIVE: + *val = LSM6DS3TR_C_XL_ST_POSITIVE; + break; + + case LSM6DS3TR_C_XL_ST_NEGATIVE: + *val = LSM6DS3TR_C_XL_ST_NEGATIVE; + break; + + default: + *val = LSM6DS3TR_C_XL_ST_ND; + break; + } + + return ret; +} + +/** + * @brief Angular rate sensor self-test enable.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of st_g in reg CTRL5_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_gy_self_test_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_st_g_t val) { + lsm6ds3tr_c_ctrl5_c_t ctrl5_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL5_C, (uint8_t*)&ctrl5_c, 1); + + if(ret == 0) { + ctrl5_c.st_g = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL5_C, (uint8_t*)&ctrl5_c, 1); + } + + return ret; +} + +/** + * @brief Angular rate sensor self-test enable.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of st_g in reg CTRL5_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_gy_self_test_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_st_g_t* val) { + lsm6ds3tr_c_ctrl5_c_t ctrl5_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL5_C, (uint8_t*)&ctrl5_c, 1); + + switch(ctrl5_c.st_g) { + case LSM6DS3TR_C_GY_ST_DISABLE: + *val = LSM6DS3TR_C_GY_ST_DISABLE; + break; + + case LSM6DS3TR_C_GY_ST_POSITIVE: + *val = LSM6DS3TR_C_GY_ST_POSITIVE; + break; + + case LSM6DS3TR_C_GY_ST_NEGATIVE: + *val = LSM6DS3TR_C_GY_ST_NEGATIVE; + break; + + default: + *val = LSM6DS3TR_C_GY_ST_ND; + break; + } + + return ret; +} + +/** + * @} + * + */ + +/** + * @defgroup LSM6DS3TR_C_filters + * @brief This section group all the functions concerning the filters + * configuration that impact both accelerometer and gyro. + * @{ + * + */ + +/** + * @brief Mask DRDY on pin (both XL & Gyro) until filter settling ends + * (XL and Gyro independently masked).[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of drdy_mask in reg CTRL4_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_filter_settling_mask_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_ctrl4_c_t ctrl4_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL4_C, (uint8_t*)&ctrl4_c, 1); + + if(ret == 0) { + ctrl4_c.drdy_mask = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL4_C, (uint8_t*)&ctrl4_c, 1); + } + + return ret; +} + +/** + * @brief Mask DRDY on pin (both XL & Gyro) until filter settling ends + * (XL and Gyro independently masked).[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of drdy_mask in reg CTRL4_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_filter_settling_mask_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_ctrl4_c_t ctrl4_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL4_C, (uint8_t*)&ctrl4_c, 1); + *val = ctrl4_c.drdy_mask; + + return ret; +} + +/** + * @brief HPF or SLOPE filter selection on wake-up and Activity/Inactivity + * functions.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of slope_fds in reg TAP_CFG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_xl_hp_path_internal_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_slope_fds_t val) { + lsm6ds3tr_c_tap_cfg_t tap_cfg; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_TAP_CFG, (uint8_t*)&tap_cfg, 1); + + if(ret == 0) { + tap_cfg.slope_fds = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_TAP_CFG, (uint8_t*)&tap_cfg, 1); + } + + return ret; +} + +/** + * @brief HPF or SLOPE filter selection on wake-up and Activity/Inactivity + * functions.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of slope_fds in reg TAP_CFG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_xl_hp_path_internal_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_slope_fds_t* val) { + lsm6ds3tr_c_tap_cfg_t tap_cfg; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_TAP_CFG, (uint8_t*)&tap_cfg, 1); + + switch(tap_cfg.slope_fds) { + case LSM6DS3TR_C_USE_SLOPE: + *val = LSM6DS3TR_C_USE_SLOPE; + break; + + case LSM6DS3TR_C_USE_HPF: + *val = LSM6DS3TR_C_USE_HPF; + break; + + default: + *val = LSM6DS3TR_C_HP_PATH_ND; + break; + } + + return ret; +} + +/** + * @} + * + */ + +/** + * @defgroup LSM6DS3TR_C_accelerometer_filters + * @brief This section group all the functions concerning the filters + * configuration that impact accelerometer in every mode. + * @{ + * + */ + +/** + * @brief Accelerometer analog chain bandwidth selection (only for + * accelerometer ODR ≥ 1.67 kHz).[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of bw0_xl in reg CTRL1_XL + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_xl_filter_analog_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_bw0_xl_t val) { + lsm6ds3tr_c_ctrl1_xl_t ctrl1_xl; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL1_XL, (uint8_t*)&ctrl1_xl, 1); + + if(ret == 0) { + ctrl1_xl.bw0_xl = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL1_XL, (uint8_t*)&ctrl1_xl, 1); + } + + return ret; +} + +/** + * @brief Accelerometer analog chain bandwidth selection (only for + * accelerometer ODR ≥ 1.67 kHz).[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of bw0_xl in reg CTRL1_XL + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_xl_filter_analog_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_bw0_xl_t* val) { + lsm6ds3tr_c_ctrl1_xl_t ctrl1_xl; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL1_XL, (uint8_t*)&ctrl1_xl, 1); + + switch(ctrl1_xl.bw0_xl) { + case LSM6DS3TR_C_XL_ANA_BW_1k5Hz: + *val = LSM6DS3TR_C_XL_ANA_BW_1k5Hz; + break; + + case LSM6DS3TR_C_XL_ANA_BW_400Hz: + *val = LSM6DS3TR_C_XL_ANA_BW_400Hz; + break; + + default: + *val = LSM6DS3TR_C_XL_ANA_BW_ND; + break; + } + + return ret; +} + +/** + * @} + * + */ + +/** + * @defgroup LSM6DS3TR_C_accelerometer_filters + * @brief This section group all the functions concerning the filters + * configuration that impact accelerometer. + * @{ + * + */ + +/** + * @brief Accelerometer digital LPF (LPF1) bandwidth selection LPF2 is + * not used.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of lpf1_bw_sel in reg CTRL1_XL + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_xl_lp1_bandwidth_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_lpf1_bw_sel_t val) { + lsm6ds3tr_c_ctrl1_xl_t ctrl1_xl; + lsm6ds3tr_c_ctrl8_xl_t ctrl8_xl; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL1_XL, (uint8_t*)&ctrl1_xl, 1); + + if(ret == 0) { + ctrl1_xl.lpf1_bw_sel = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL1_XL, (uint8_t*)&ctrl1_xl, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL8_XL, (uint8_t*)&ctrl8_xl, 1); + + if(ret == 0) { + ctrl8_xl.lpf2_xl_en = 0; + ctrl8_xl.hp_slope_xl_en = 0; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL8_XL, (uint8_t*)&ctrl8_xl, 1); + } + } + } + + return ret; +} + +/** + * @brief Accelerometer digital LPF (LPF1) bandwidth selection LPF2 + * is not used.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of lpf1_bw_sel in reg CTRL1_XL + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_xl_lp1_bandwidth_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_lpf1_bw_sel_t* val) { + lsm6ds3tr_c_ctrl1_xl_t ctrl1_xl; + lsm6ds3tr_c_ctrl8_xl_t ctrl8_xl; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL8_XL, (uint8_t*)&ctrl8_xl, 1); + + if(ret == 0) { + if((ctrl8_xl.lpf2_xl_en != 0x00U) || (ctrl8_xl.hp_slope_xl_en != 0x00U)) { + *val = LSM6DS3TR_C_XL_LP1_NA; + } + + else { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL1_XL, (uint8_t*)&ctrl1_xl, 1); + + switch(ctrl1_xl.lpf1_bw_sel) { + case LSM6DS3TR_C_XL_LP1_ODR_DIV_2: + *val = LSM6DS3TR_C_XL_LP1_ODR_DIV_2; + break; + + case LSM6DS3TR_C_XL_LP1_ODR_DIV_4: + *val = LSM6DS3TR_C_XL_LP1_ODR_DIV_4; + break; + + default: + *val = LSM6DS3TR_C_XL_LP1_NA; + break; + } + } + } + + return ret; +} + +/** + * @brief LPF2 on outputs[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of input_composite in reg CTRL8_XL + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_xl_lp2_bandwidth_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_input_composite_t val) { + lsm6ds3tr_c_ctrl8_xl_t ctrl8_xl; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL8_XL, (uint8_t*)&ctrl8_xl, 1); + + if(ret == 0) { + ctrl8_xl.input_composite = ((uint8_t)val & 0x10U) >> 4; + ctrl8_xl.hpcf_xl = (uint8_t)val & 0x03U; + ctrl8_xl.lpf2_xl_en = 1; + ctrl8_xl.hp_slope_xl_en = 0; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL8_XL, (uint8_t*)&ctrl8_xl, 1); + } + + return ret; +} + +/** + * @brief LPF2 on outputs[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of input_composite in reg CTRL8_XL + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_xl_lp2_bandwidth_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_input_composite_t* val) { + lsm6ds3tr_c_ctrl8_xl_t ctrl8_xl; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL8_XL, (uint8_t*)&ctrl8_xl, 1); + + if(ret == 0) { + if((ctrl8_xl.lpf2_xl_en == 0x00U) || (ctrl8_xl.hp_slope_xl_en != 0x00U)) { + *val = LSM6DS3TR_C_XL_LP_NA; + } + + else { + switch((ctrl8_xl.input_composite << 4) + ctrl8_xl.hpcf_xl) { + case LSM6DS3TR_C_XL_LOW_LAT_LP_ODR_DIV_50: + *val = LSM6DS3TR_C_XL_LOW_LAT_LP_ODR_DIV_50; + break; + + case LSM6DS3TR_C_XL_LOW_LAT_LP_ODR_DIV_100: + *val = LSM6DS3TR_C_XL_LOW_LAT_LP_ODR_DIV_100; + break; + + case LSM6DS3TR_C_XL_LOW_LAT_LP_ODR_DIV_9: + *val = LSM6DS3TR_C_XL_LOW_LAT_LP_ODR_DIV_9; + break; + + case LSM6DS3TR_C_XL_LOW_LAT_LP_ODR_DIV_400: + *val = LSM6DS3TR_C_XL_LOW_LAT_LP_ODR_DIV_400; + break; + + case LSM6DS3TR_C_XL_LOW_NOISE_LP_ODR_DIV_50: + *val = LSM6DS3TR_C_XL_LOW_NOISE_LP_ODR_DIV_50; + break; + + case LSM6DS3TR_C_XL_LOW_NOISE_LP_ODR_DIV_100: + *val = LSM6DS3TR_C_XL_LOW_NOISE_LP_ODR_DIV_100; + break; + + case LSM6DS3TR_C_XL_LOW_NOISE_LP_ODR_DIV_9: + *val = LSM6DS3TR_C_XL_LOW_NOISE_LP_ODR_DIV_9; + break; + + case LSM6DS3TR_C_XL_LOW_NOISE_LP_ODR_DIV_400: + *val = LSM6DS3TR_C_XL_LOW_NOISE_LP_ODR_DIV_400; + break; + + default: + *val = LSM6DS3TR_C_XL_LP_NA; + break; + } + } + } + + return ret; +} + +/** + * @brief Enable HP filter reference mode.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of hp_ref_mode in reg CTRL8_XL + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_xl_reference_mode_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_ctrl8_xl_t ctrl8_xl; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL8_XL, (uint8_t*)&ctrl8_xl, 1); + + if(ret == 0) { + ctrl8_xl.hp_ref_mode = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL8_XL, (uint8_t*)&ctrl8_xl, 1); + } + + return ret; +} + +/** + * @brief Enable HP filter reference mode.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of hp_ref_mode in reg CTRL8_XL + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_xl_reference_mode_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_ctrl8_xl_t ctrl8_xl; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL8_XL, (uint8_t*)&ctrl8_xl, 1); + *val = ctrl8_xl.hp_ref_mode; + + return ret; +} + +/** + * @brief High pass/Slope on outputs.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of hpcf_xl in reg CTRL8_XL + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_xl_hp_bandwidth_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_hpcf_xl_t val) { + lsm6ds3tr_c_ctrl8_xl_t ctrl8_xl; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL8_XL, (uint8_t*)&ctrl8_xl, 1); + + if(ret == 0) { + ctrl8_xl.input_composite = 0; + ctrl8_xl.hpcf_xl = (uint8_t)val & 0x03U; + ctrl8_xl.hp_slope_xl_en = 1; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL8_XL, (uint8_t*)&ctrl8_xl, 1); + } + + return ret; +} + +/** + * @brief High pass/Slope on outputs.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of hpcf_xl in reg CTRL8_XL + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_xl_hp_bandwidth_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_hpcf_xl_t* val) { + lsm6ds3tr_c_ctrl8_xl_t ctrl8_xl; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL8_XL, (uint8_t*)&ctrl8_xl, 1); + + if(ctrl8_xl.hp_slope_xl_en == 0x00U) { + *val = LSM6DS3TR_C_XL_HP_NA; + } + + switch(ctrl8_xl.hpcf_xl) { + case LSM6DS3TR_C_XL_HP_ODR_DIV_4: + *val = LSM6DS3TR_C_XL_HP_ODR_DIV_4; + break; + + case LSM6DS3TR_C_XL_HP_ODR_DIV_100: + *val = LSM6DS3TR_C_XL_HP_ODR_DIV_100; + break; + + case LSM6DS3TR_C_XL_HP_ODR_DIV_9: + *val = LSM6DS3TR_C_XL_HP_ODR_DIV_9; + break; + + case LSM6DS3TR_C_XL_HP_ODR_DIV_400: + *val = LSM6DS3TR_C_XL_HP_ODR_DIV_400; + break; + + default: + *val = LSM6DS3TR_C_XL_HP_NA; + break; + } + + return ret; +} + +/** + * @} + * + */ + +/** + * @defgroup LSM6DS3TR_C_gyroscope_filters + * @brief This section group all the functions concerning the filters + * configuration that impact gyroscope. + * @{ + * + */ + +/** + * @brief Gyroscope low pass path bandwidth.[set] + * + * @param ctx Read / write interface definitions + * @param val gyroscope filtering chain configuration. + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_gy_band_pass_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_lpf1_sel_g_t val) { + lsm6ds3tr_c_ctrl4_c_t ctrl4_c; + lsm6ds3tr_c_ctrl6_c_t ctrl6_c; + lsm6ds3tr_c_ctrl7_g_t ctrl7_g; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL7_G, (uint8_t*)&ctrl7_g, 1); + + if(ret == 0) { + ctrl7_g.hpm_g = ((uint8_t)val & 0x30U) >> 4; + ctrl7_g.hp_en_g = ((uint8_t)val & 0x80U) >> 7; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL7_G, (uint8_t*)&ctrl7_g, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL6_C, (uint8_t*)&ctrl6_c, 1); + + if(ret == 0) { + ctrl6_c.ftype = (uint8_t)val & 0x03U; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL6_C, (uint8_t*)&ctrl6_c, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL4_C, (uint8_t*)&ctrl4_c, 1); + + if(ret == 0) { + ctrl4_c.lpf1_sel_g = ((uint8_t)val & 0x08U) >> 3; + ret = + lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL4_C, (uint8_t*)&ctrl4_c, 1); + } + } + } + } + } + + return ret; +} + +/** + * @brief Gyroscope low pass path bandwidth.[get] + * + * @param ctx Read / write interface definitions + * @param val gyroscope filtering chain + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_gy_band_pass_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_lpf1_sel_g_t* val) { + lsm6ds3tr_c_ctrl4_c_t ctrl4_c; + lsm6ds3tr_c_ctrl6_c_t ctrl6_c; + lsm6ds3tr_c_ctrl7_g_t ctrl7_g; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL6_C, (uint8_t*)&ctrl6_c, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL4_C, (uint8_t*)&ctrl4_c, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL7_G, (uint8_t*)&ctrl7_g, 1); + + switch((ctrl7_g.hp_en_g << 7) + (ctrl7_g.hpm_g << 4) + (ctrl4_c.lpf1_sel_g << 3) + + ctrl6_c.ftype) { + case LSM6DS3TR_C_HP_16mHz_LP2: + *val = LSM6DS3TR_C_HP_16mHz_LP2; + break; + + case LSM6DS3TR_C_HP_65mHz_LP2: + *val = LSM6DS3TR_C_HP_65mHz_LP2; + break; + + case LSM6DS3TR_C_HP_260mHz_LP2: + *val = LSM6DS3TR_C_HP_260mHz_LP2; + break; + + case LSM6DS3TR_C_HP_1Hz04_LP2: + *val = LSM6DS3TR_C_HP_1Hz04_LP2; + break; + + case LSM6DS3TR_C_HP_DISABLE_LP1_LIGHT: + *val = LSM6DS3TR_C_HP_DISABLE_LP1_LIGHT; + break; + + case LSM6DS3TR_C_HP_DISABLE_LP1_NORMAL: + *val = LSM6DS3TR_C_HP_DISABLE_LP1_NORMAL; + break; + + case LSM6DS3TR_C_HP_DISABLE_LP_STRONG: + *val = LSM6DS3TR_C_HP_DISABLE_LP_STRONG; + break; + + case LSM6DS3TR_C_HP_DISABLE_LP1_AGGRESSIVE: + *val = LSM6DS3TR_C_HP_DISABLE_LP1_AGGRESSIVE; + break; + + case LSM6DS3TR_C_HP_16mHz_LP1_LIGHT: + *val = LSM6DS3TR_C_HP_16mHz_LP1_LIGHT; + break; + + case LSM6DS3TR_C_HP_65mHz_LP1_NORMAL: + *val = LSM6DS3TR_C_HP_65mHz_LP1_NORMAL; + break; + + case LSM6DS3TR_C_HP_260mHz_LP1_STRONG: + *val = LSM6DS3TR_C_HP_260mHz_LP1_STRONG; + break; + + case LSM6DS3TR_C_HP_1Hz04_LP1_AGGRESSIVE: + *val = LSM6DS3TR_C_HP_1Hz04_LP1_AGGRESSIVE; + break; + + default: + *val = LSM6DS3TR_C_HP_GY_BAND_NA; + break; + } + } + } + + return ret; +} + +/** + * @} + * + */ + +/** + * @defgroup LSM6DS3TR_C_serial_interface + * @brief This section groups all the functions concerning serial + * interface management + * @{ + * + */ + +/** + * @brief SPI Serial Interface Mode selection.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of sim in reg CTRL3_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_spi_mode_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_sim_t val) { + lsm6ds3tr_c_ctrl3_c_t ctrl3_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL3_C, (uint8_t*)&ctrl3_c, 1); + + if(ret == 0) { + ctrl3_c.sim = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL3_C, (uint8_t*)&ctrl3_c, 1); + } + + return ret; +} + +/** + * @brief SPI Serial Interface Mode selection.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of sim in reg CTRL3_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_spi_mode_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_sim_t* val) { + lsm6ds3tr_c_ctrl3_c_t ctrl3_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL3_C, (uint8_t*)&ctrl3_c, 1); + + switch(ctrl3_c.sim) { + case LSM6DS3TR_C_SPI_4_WIRE: + *val = LSM6DS3TR_C_SPI_4_WIRE; + break; + + case LSM6DS3TR_C_SPI_3_WIRE: + *val = LSM6DS3TR_C_SPI_3_WIRE; + break; + + default: + *val = LSM6DS3TR_C_SPI_MODE_ND; + break; + } + + return ret; +} + +/** + * @brief Disable / Enable I2C interface.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of i2c_disable in reg CTRL4_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_i2c_interface_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_i2c_disable_t val) { + lsm6ds3tr_c_ctrl4_c_t ctrl4_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL4_C, (uint8_t*)&ctrl4_c, 1); + + if(ret == 0) { + ctrl4_c.i2c_disable = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL4_C, (uint8_t*)&ctrl4_c, 1); + } + + return ret; +} + +/** + * @brief Disable / Enable I2C interface.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of i2c_disable in reg CTRL4_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_i2c_interface_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_i2c_disable_t* val) { + lsm6ds3tr_c_ctrl4_c_t ctrl4_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL4_C, (uint8_t*)&ctrl4_c, 1); + + switch(ctrl4_c.i2c_disable) { + case LSM6DS3TR_C_I2C_ENABLE: + *val = LSM6DS3TR_C_I2C_ENABLE; + break; + + case LSM6DS3TR_C_I2C_DISABLE: + *val = LSM6DS3TR_C_I2C_DISABLE; + break; + + default: + *val = LSM6DS3TR_C_I2C_MODE_ND; + break; + } + + return ret; +} + +/** + * @} + * + */ + +/** + * @defgroup LSM6DS3TR_C_interrupt_pins + * @brief This section groups all the functions that manage + * interrupt pins + * @{ + * + */ + +/** + * @brief Select the signal that need to route on int1 pad[set] + * + * @param ctx Read / write interface definitions + * @param val configure INT1_CTRL, MD1_CFG, CTRL4_C(den_drdy_int1), + * MASTER_CONFIG(drdy_on_int1) + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_pin_int1_route_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_int1_route_t val) { + lsm6ds3tr_c_master_config_t master_config; + lsm6ds3tr_c_int1_ctrl_t int1_ctrl; + lsm6ds3tr_c_md1_cfg_t md1_cfg; + lsm6ds3tr_c_md2_cfg_t md2_cfg; + lsm6ds3tr_c_ctrl4_c_t ctrl4_c; + lsm6ds3tr_c_tap_cfg_t tap_cfg; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_INT1_CTRL, (uint8_t*)&int1_ctrl, 1); + + if(ret == 0) { + int1_ctrl.int1_drdy_xl = val.int1_drdy_xl; + int1_ctrl.int1_drdy_g = val.int1_drdy_g; + int1_ctrl.int1_boot = val.int1_boot; + int1_ctrl.int1_fth = val.int1_fth; + int1_ctrl.int1_fifo_ovr = val.int1_fifo_ovr; + int1_ctrl.int1_full_flag = val.int1_full_flag; + int1_ctrl.int1_sign_mot = val.int1_sign_mot; + int1_ctrl.int1_step_detector = val.int1_step_detector; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_INT1_CTRL, (uint8_t*)&int1_ctrl, 1); + } + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_MD1_CFG, (uint8_t*)&md1_cfg, 1); + } + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_MD2_CFG, (uint8_t*)&md2_cfg, 1); + } + + if(ret == 0) { + md1_cfg.int1_timer = val.int1_timer; + md1_cfg.int1_tilt = val.int1_tilt; + md1_cfg.int1_6d = val.int1_6d; + md1_cfg.int1_double_tap = val.int1_double_tap; + md1_cfg.int1_ff = val.int1_ff; + md1_cfg.int1_wu = val.int1_wu; + md1_cfg.int1_single_tap = val.int1_single_tap; + md1_cfg.int1_inact_state = val.int1_inact_state; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_MD1_CFG, (uint8_t*)&md1_cfg, 1); + } + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL4_C, (uint8_t*)&ctrl4_c, 1); + } + + if(ret == 0) { + ctrl4_c.den_drdy_int1 = val.den_drdy_int1; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL4_C, (uint8_t*)&ctrl4_c, 1); + } + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_MASTER_CONFIG, (uint8_t*)&master_config, 1); + } + + if(ret == 0) { + master_config.drdy_on_int1 = val.den_drdy_int1; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_MASTER_CONFIG, (uint8_t*)&master_config, 1); + } + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_TAP_CFG, (uint8_t*)&tap_cfg, 1); + + if((val.int1_6d != 0x00U) || (val.int1_ff != 0x00U) || (val.int1_wu != 0x00U) || + (val.int1_single_tap != 0x00U) || (val.int1_double_tap != 0x00U) || + (val.int1_inact_state != 0x00U) || (md2_cfg.int2_6d != 0x00U) || + (md2_cfg.int2_ff != 0x00U) || (md2_cfg.int2_wu != 0x00U) || + (md2_cfg.int2_single_tap != 0x00U) || (md2_cfg.int2_double_tap != 0x00U) || + (md2_cfg.int2_inact_state != 0x00U)) { + tap_cfg.interrupts_enable = PROPERTY_ENABLE; + } + + else { + tap_cfg.interrupts_enable = PROPERTY_DISABLE; + } + } + + if(ret == 0) { + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_TAP_CFG, (uint8_t*)&tap_cfg, 1); + } + + return ret; +} + +/** + * @brief Select the signal that need to route on int1 pad[get] + * + * @param ctx Read / write interface definitions + * @param val read INT1_CTRL, MD1_CFG, CTRL4_C(den_drdy_int1), + * MASTER_CONFIG(drdy_on_int1) + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_pin_int1_route_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_int1_route_t* val) { + lsm6ds3tr_c_master_config_t master_config; + lsm6ds3tr_c_int1_ctrl_t int1_ctrl; + lsm6ds3tr_c_md1_cfg_t md1_cfg; + lsm6ds3tr_c_ctrl4_c_t ctrl4_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_INT1_CTRL, (uint8_t*)&int1_ctrl, 1); + + if(ret == 0) { + val->int1_drdy_xl = int1_ctrl.int1_drdy_xl; + val->int1_drdy_g = int1_ctrl.int1_drdy_g; + val->int1_boot = int1_ctrl.int1_boot; + val->int1_fth = int1_ctrl.int1_fth; + val->int1_fifo_ovr = int1_ctrl.int1_fifo_ovr; + val->int1_full_flag = int1_ctrl.int1_full_flag; + val->int1_sign_mot = int1_ctrl.int1_sign_mot; + val->int1_step_detector = int1_ctrl.int1_step_detector; + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_MD1_CFG, (uint8_t*)&md1_cfg, 1); + + if(ret == 0) { + val->int1_timer = md1_cfg.int1_timer; + val->int1_tilt = md1_cfg.int1_tilt; + val->int1_6d = md1_cfg.int1_6d; + val->int1_double_tap = md1_cfg.int1_double_tap; + val->int1_ff = md1_cfg.int1_ff; + val->int1_wu = md1_cfg.int1_wu; + val->int1_single_tap = md1_cfg.int1_single_tap; + val->int1_inact_state = md1_cfg.int1_inact_state; + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL4_C, (uint8_t*)&ctrl4_c, 1); + + if(ret == 0) { + val->den_drdy_int1 = ctrl4_c.den_drdy_int1; + ret = lsm6ds3tr_c_read_reg( + ctx, LSM6DS3TR_C_MASTER_CONFIG, (uint8_t*)&master_config, 1); + val->den_drdy_int1 = master_config.drdy_on_int1; + } + } + } + + return ret; +} + +/** + * @brief Select the signal that need to route on int2 pad[set] + * + * @param ctx Read / write interface definitions + * @param val INT2_CTRL, DRDY_PULSE_CFG(int2_wrist_tilt), MD2_CFG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_pin_int2_route_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_int2_route_t val) { + lsm6ds3tr_c_int2_ctrl_t int2_ctrl; + lsm6ds3tr_c_md1_cfg_t md1_cfg; + lsm6ds3tr_c_md2_cfg_t md2_cfg; + lsm6ds3tr_c_drdy_pulse_cfg_g_t drdy_pulse_cfg_g; + lsm6ds3tr_c_tap_cfg_t tap_cfg; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_INT2_CTRL, (uint8_t*)&int2_ctrl, 1); + + if(ret == 0) { + int2_ctrl.int2_drdy_xl = val.int2_drdy_xl; + int2_ctrl.int2_drdy_g = val.int2_drdy_g; + int2_ctrl.int2_drdy_temp = val.int2_drdy_temp; + int2_ctrl.int2_fth = val.int2_fth; + int2_ctrl.int2_fifo_ovr = val.int2_fifo_ovr; + int2_ctrl.int2_full_flag = val.int2_full_flag; + int2_ctrl.int2_step_count_ov = val.int2_step_count_ov; + int2_ctrl.int2_step_delta = val.int2_step_delta; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_INT2_CTRL, (uint8_t*)&int2_ctrl, 1); + } + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_MD1_CFG, (uint8_t*)&md1_cfg, 1); + } + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_MD2_CFG, (uint8_t*)&md2_cfg, 1); + } + + if(ret == 0) { + md2_cfg.int2_iron = val.int2_iron; + md2_cfg.int2_tilt = val.int2_tilt; + md2_cfg.int2_6d = val.int2_6d; + md2_cfg.int2_double_tap = val.int2_double_tap; + md2_cfg.int2_ff = val.int2_ff; + md2_cfg.int2_wu = val.int2_wu; + md2_cfg.int2_single_tap = val.int2_single_tap; + md2_cfg.int2_inact_state = val.int2_inact_state; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_MD2_CFG, (uint8_t*)&md2_cfg, 1); + } + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg( + ctx, LSM6DS3TR_C_DRDY_PULSE_CFG_G, (uint8_t*)&drdy_pulse_cfg_g, 1); + } + + if(ret == 0) { + drdy_pulse_cfg_g.int2_wrist_tilt = val.int2_wrist_tilt; + ret = lsm6ds3tr_c_write_reg( + ctx, LSM6DS3TR_C_DRDY_PULSE_CFG_G, (uint8_t*)&drdy_pulse_cfg_g, 1); + } + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_TAP_CFG, (uint8_t*)&tap_cfg, 1); + + if((md1_cfg.int1_6d != 0x00U) || (md1_cfg.int1_ff != 0x00U) || + (md1_cfg.int1_wu != 0x00U) || (md1_cfg.int1_single_tap != 0x00U) || + (md1_cfg.int1_double_tap != 0x00U) || (md1_cfg.int1_inact_state != 0x00U) || + (val.int2_6d != 0x00U) || (val.int2_ff != 0x00U) || (val.int2_wu != 0x00U) || + (val.int2_single_tap != 0x00U) || (val.int2_double_tap != 0x00U) || + (val.int2_inact_state != 0x00U)) { + tap_cfg.interrupts_enable = PROPERTY_ENABLE; + } + + else { + tap_cfg.interrupts_enable = PROPERTY_DISABLE; + } + } + + if(ret == 0) { + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_TAP_CFG, (uint8_t*)&tap_cfg, 1); + } + + return ret; +} + +/** + * @brief Select the signal that need to route on int2 pad[get] + * + * @param ctx Read / write interface definitions + * @param val INT2_CTRL, DRDY_PULSE_CFG(int2_wrist_tilt), MD2_CFG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_pin_int2_route_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_int2_route_t* val) { + lsm6ds3tr_c_int2_ctrl_t int2_ctrl; + lsm6ds3tr_c_md2_cfg_t md2_cfg; + lsm6ds3tr_c_drdy_pulse_cfg_g_t drdy_pulse_cfg_g; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_INT2_CTRL, (uint8_t*)&int2_ctrl, 1); + + if(ret == 0) { + val->int2_drdy_xl = int2_ctrl.int2_drdy_xl; + val->int2_drdy_g = int2_ctrl.int2_drdy_g; + val->int2_drdy_temp = int2_ctrl.int2_drdy_temp; + val->int2_fth = int2_ctrl.int2_fth; + val->int2_fifo_ovr = int2_ctrl.int2_fifo_ovr; + val->int2_full_flag = int2_ctrl.int2_full_flag; + val->int2_step_count_ov = int2_ctrl.int2_step_count_ov; + val->int2_step_delta = int2_ctrl.int2_step_delta; + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_MD2_CFG, (uint8_t*)&md2_cfg, 1); + + if(ret == 0) { + val->int2_iron = md2_cfg.int2_iron; + val->int2_tilt = md2_cfg.int2_tilt; + val->int2_6d = md2_cfg.int2_6d; + val->int2_double_tap = md2_cfg.int2_double_tap; + val->int2_ff = md2_cfg.int2_ff; + val->int2_wu = md2_cfg.int2_wu; + val->int2_single_tap = md2_cfg.int2_single_tap; + val->int2_inact_state = md2_cfg.int2_inact_state; + ret = lsm6ds3tr_c_read_reg( + ctx, LSM6DS3TR_C_DRDY_PULSE_CFG_G, (uint8_t*)&drdy_pulse_cfg_g, 1); + val->int2_wrist_tilt = drdy_pulse_cfg_g.int2_wrist_tilt; + } + } + + return ret; +} + +/** + * @brief Push-pull/open drain selection on interrupt pads.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of pp_od in reg CTRL3_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_pin_mode_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_pp_od_t val) { + lsm6ds3tr_c_ctrl3_c_t ctrl3_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL3_C, (uint8_t*)&ctrl3_c, 1); + + if(ret == 0) { + ctrl3_c.pp_od = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL3_C, (uint8_t*)&ctrl3_c, 1); + } + + return ret; +} + +/** + * @brief Push-pull/open drain selection on interrupt pads.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of pp_od in reg CTRL3_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_pin_mode_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_pp_od_t* val) { + lsm6ds3tr_c_ctrl3_c_t ctrl3_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL3_C, (uint8_t*)&ctrl3_c, 1); + + switch(ctrl3_c.pp_od) { + case LSM6DS3TR_C_PUSH_PULL: + *val = LSM6DS3TR_C_PUSH_PULL; + break; + + case LSM6DS3TR_C_OPEN_DRAIN: + *val = LSM6DS3TR_C_OPEN_DRAIN; + break; + + default: + *val = LSM6DS3TR_C_PIN_MODE_ND; + break; + } + + return ret; +} + +/** + * @brief Interrupt active-high/low.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of h_lactive in reg CTRL3_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_pin_polarity_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_h_lactive_t val) { + lsm6ds3tr_c_ctrl3_c_t ctrl3_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL3_C, (uint8_t*)&ctrl3_c, 1); + + if(ret == 0) { + ctrl3_c.h_lactive = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL3_C, (uint8_t*)&ctrl3_c, 1); + } + + return ret; +} + +/** + * @brief Interrupt active-high/low.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of h_lactive in reg CTRL3_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_pin_polarity_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_h_lactive_t* val) { + lsm6ds3tr_c_ctrl3_c_t ctrl3_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL3_C, (uint8_t*)&ctrl3_c, 1); + + switch(ctrl3_c.h_lactive) { + case LSM6DS3TR_C_ACTIVE_HIGH: + *val = LSM6DS3TR_C_ACTIVE_HIGH; + break; + + case LSM6DS3TR_C_ACTIVE_LOW: + *val = LSM6DS3TR_C_ACTIVE_LOW; + break; + + default: + *val = LSM6DS3TR_C_POLARITY_ND; + break; + } + + return ret; +} + +/** + * @brief All interrupt signals become available on INT1 pin.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of int2_on_int1 in reg CTRL4_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_all_on_int1_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_ctrl4_c_t ctrl4_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL4_C, (uint8_t*)&ctrl4_c, 1); + + if(ret == 0) { + ctrl4_c.int2_on_int1 = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL4_C, (uint8_t*)&ctrl4_c, 1); + } + + return ret; +} + +/** + * @brief All interrupt signals become available on INT1 pin.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of int2_on_int1 in reg CTRL4_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_all_on_int1_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_ctrl4_c_t ctrl4_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL4_C, (uint8_t*)&ctrl4_c, 1); + *val = ctrl4_c.int2_on_int1; + + return ret; +} + +/** + * @brief Latched/pulsed interrupt.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of lir in reg TAP_CFG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_int_notification_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_lir_t val) { + lsm6ds3tr_c_tap_cfg_t tap_cfg; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_TAP_CFG, (uint8_t*)&tap_cfg, 1); + + if(ret == 0) { + tap_cfg.lir = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_TAP_CFG, (uint8_t*)&tap_cfg, 1); + } + + return ret; +} + +/** + * @brief Latched/pulsed interrupt.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of lir in reg TAP_CFG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_int_notification_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_lir_t* val) { + lsm6ds3tr_c_tap_cfg_t tap_cfg; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_TAP_CFG, (uint8_t*)&tap_cfg, 1); + + switch(tap_cfg.lir) { + case LSM6DS3TR_C_INT_PULSED: + *val = LSM6DS3TR_C_INT_PULSED; + break; + + case LSM6DS3TR_C_INT_LATCHED: + *val = LSM6DS3TR_C_INT_LATCHED; + break; + + default: + *val = LSM6DS3TR_C_INT_MODE; + break; + } + + return ret; +} + +/** + * @} + * + */ + +/** + * @defgroup LSM6DS3TR_C_Wake_Up_event + * @brief This section groups all the functions that manage the + * Wake Up event generation. + * @{ + * + */ + +/** + * @brief Threshold for wakeup.1 LSB = FS_XL / 64.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of wk_ths in reg WAKE_UP_THS + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_wkup_threshold_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_wake_up_ths_t wake_up_ths; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_WAKE_UP_THS, (uint8_t*)&wake_up_ths, 1); + + if(ret == 0) { + wake_up_ths.wk_ths = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_WAKE_UP_THS, (uint8_t*)&wake_up_ths, 1); + } + + return ret; +} + +/** + * @brief Threshold for wakeup.1 LSB = FS_XL / 64.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of wk_ths in reg WAKE_UP_THS + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_wkup_threshold_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_wake_up_ths_t wake_up_ths; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_WAKE_UP_THS, (uint8_t*)&wake_up_ths, 1); + *val = wake_up_ths.wk_ths; + + return ret; +} + +/** + * @brief Wake up duration event.1LSb = 1 / ODR[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of wake_dur in reg WAKE_UP_DUR + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_wkup_dur_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_wake_up_dur_t wake_up_dur; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_WAKE_UP_DUR, (uint8_t*)&wake_up_dur, 1); + + if(ret == 0) { + wake_up_dur.wake_dur = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_WAKE_UP_DUR, (uint8_t*)&wake_up_dur, 1); + } + + return ret; +} + +/** + * @brief Wake up duration event.1LSb = 1 / ODR[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of wake_dur in reg WAKE_UP_DUR + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_wkup_dur_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_wake_up_dur_t wake_up_dur; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_WAKE_UP_DUR, (uint8_t*)&wake_up_dur, 1); + *val = wake_up_dur.wake_dur; + + return ret; +} + +/** + * @} + * + */ + +/** + * @defgroup LSM6DS3TR_C_Activity/Inactivity_detection + * @brief This section groups all the functions concerning + * activity/inactivity detection. + * @{ + * + */ + +/** + * @brief Enables gyroscope Sleep mode.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of sleep in reg CTRL4_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_gy_sleep_mode_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_ctrl4_c_t ctrl4_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL4_C, (uint8_t*)&ctrl4_c, 1); + + if(ret == 0) { + ctrl4_c.sleep = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL4_C, (uint8_t*)&ctrl4_c, 1); + } + + return ret; +} + +/** + * @brief Enables gyroscope Sleep mode.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of sleep in reg CTRL4_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_gy_sleep_mode_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_ctrl4_c_t ctrl4_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL4_C, (uint8_t*)&ctrl4_c, 1); + *val = ctrl4_c.sleep; + + return ret; +} + +/** + * @brief Enable inactivity function.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of inact_en in reg TAP_CFG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_act_mode_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_inact_en_t val) { + lsm6ds3tr_c_tap_cfg_t tap_cfg; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_TAP_CFG, (uint8_t*)&tap_cfg, 1); + + if(ret == 0) { + tap_cfg.inact_en = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_TAP_CFG, (uint8_t*)&tap_cfg, 1); + } + + return ret; +} + +/** + * @brief Enable inactivity function.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of inact_en in reg TAP_CFG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_act_mode_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_inact_en_t* val) { + lsm6ds3tr_c_tap_cfg_t tap_cfg; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_TAP_CFG, (uint8_t*)&tap_cfg, 1); + + switch(tap_cfg.inact_en) { + case LSM6DS3TR_C_PROPERTY_DISABLE: + *val = LSM6DS3TR_C_PROPERTY_DISABLE; + break; + + case LSM6DS3TR_C_XL_12Hz5_GY_NOT_AFFECTED: + *val = LSM6DS3TR_C_XL_12Hz5_GY_NOT_AFFECTED; + break; + + case LSM6DS3TR_C_XL_12Hz5_GY_SLEEP: + *val = LSM6DS3TR_C_XL_12Hz5_GY_SLEEP; + break; + + case LSM6DS3TR_C_XL_12Hz5_GY_PD: + *val = LSM6DS3TR_C_XL_12Hz5_GY_PD; + break; + + default: + *val = LSM6DS3TR_C_ACT_MODE_ND; + break; + } + + return ret; +} + +/** + * @brief Duration to go in sleep mode.1 LSb = 512 / ODR[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of sleep_dur in reg WAKE_UP_DUR + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_act_sleep_dur_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_wake_up_dur_t wake_up_dur; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_WAKE_UP_DUR, (uint8_t*)&wake_up_dur, 1); + + if(ret == 0) { + wake_up_dur.sleep_dur = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_WAKE_UP_DUR, (uint8_t*)&wake_up_dur, 1); + } + + return ret; +} + +/** + * @brief Duration to go in sleep mode. 1 LSb = 512 / ODR[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of sleep_dur in reg WAKE_UP_DUR + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_act_sleep_dur_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_wake_up_dur_t wake_up_dur; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_WAKE_UP_DUR, (uint8_t*)&wake_up_dur, 1); + *val = wake_up_dur.sleep_dur; + + return ret; +} + +/** + * @} + * + */ + +/** + * @defgroup LSM6DS3TR_C_tap_generator + * @brief This section groups all the functions that manage the + * tap and double tap event generation. + * @{ + * + */ + +/** + * @brief Read the tap / double tap source register.[get] + * + * @param ctx Read / write interface definitions + * @param val Structure of registers from TAP_SRC + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_tap_src_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_tap_src_t* val) { + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_TAP_SRC, (uint8_t*)val, 1); + + return ret; +} + +/** + * @brief Enable Z direction in tap recognition.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of tap_z_en in reg TAP_CFG + * + */ +int32_t lsm6ds3tr_c_tap_detection_on_z_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_tap_cfg_t tap_cfg; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_TAP_CFG, (uint8_t*)&tap_cfg, 1); + + if(ret == 0) { + tap_cfg.tap_z_en = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_TAP_CFG, (uint8_t*)&tap_cfg, 1); + } + + return ret; +} + +/** + * @brief Enable Z direction in tap recognition.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of tap_z_en in reg TAP_CFG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_tap_detection_on_z_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_tap_cfg_t tap_cfg; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_TAP_CFG, (uint8_t*)&tap_cfg, 1); + *val = tap_cfg.tap_z_en; + + return ret; +} + +/** + * @brief Enable Y direction in tap recognition.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of tap_y_en in reg TAP_CFG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_tap_detection_on_y_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_tap_cfg_t tap_cfg; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_TAP_CFG, (uint8_t*)&tap_cfg, 1); + + if(ret == 0) { + tap_cfg.tap_y_en = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_TAP_CFG, (uint8_t*)&tap_cfg, 1); + } + + return ret; +} + +/** + * @brief Enable Y direction in tap recognition.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of tap_y_en in reg TAP_CFG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_tap_detection_on_y_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_tap_cfg_t tap_cfg; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_TAP_CFG, (uint8_t*)&tap_cfg, 1); + *val = tap_cfg.tap_y_en; + + return ret; +} + +/** + * @brief Enable X direction in tap recognition.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of tap_x_en in reg TAP_CFG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_tap_detection_on_x_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_tap_cfg_t tap_cfg; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_TAP_CFG, (uint8_t*)&tap_cfg, 1); + + if(ret == 0) { + tap_cfg.tap_x_en = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_TAP_CFG, (uint8_t*)&tap_cfg, 1); + } + + return ret; +} + +/** + * @brief Enable X direction in tap recognition.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of tap_x_en in reg TAP_CFG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_tap_detection_on_x_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_tap_cfg_t tap_cfg; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_TAP_CFG, (uint8_t*)&tap_cfg, 1); + *val = tap_cfg.tap_x_en; + + return ret; +} + +/** + * @brief Threshold for tap recognition.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of tap_ths in reg TAP_THS_6D + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_tap_threshold_x_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_tap_ths_6d_t tap_ths_6d; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_TAP_THS_6D, (uint8_t*)&tap_ths_6d, 1); + + if(ret == 0) { + tap_ths_6d.tap_ths = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_TAP_THS_6D, (uint8_t*)&tap_ths_6d, 1); + } + + return ret; +} + +/** + * @brief Threshold for tap recognition.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of tap_ths in reg TAP_THS_6D + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_tap_threshold_x_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_tap_ths_6d_t tap_ths_6d; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_TAP_THS_6D, (uint8_t*)&tap_ths_6d, 1); + *val = tap_ths_6d.tap_ths; + + return ret; +} + +/** + * @brief Maximum duration is the maximum time of an overthreshold signal + * detection to be recognized as a tap event. + * The default value of these bits is 00b which corresponds to + * 4*ODR_XL time. + * If the SHOCK[1:0] bits are set to a different + * value, 1LSB corresponds to 8*ODR_XL time.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of shock in reg INT_DUR2 + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_tap_shock_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_int_dur2_t int_dur2; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_INT_DUR2, (uint8_t*)&int_dur2, 1); + + if(ret == 0) { + int_dur2.shock = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_INT_DUR2, (uint8_t*)&int_dur2, 1); + } + + return ret; +} + +/** + * @brief Maximum duration is the maximum time of an overthreshold signal + * detection to be recognized as a tap event. + * The default value of these bits is 00b which corresponds to + * 4*ODR_XL time. + * If the SHOCK[1:0] bits are set to a different value, 1LSB + * corresponds to 8*ODR_XL time.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of shock in reg INT_DUR2 + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_tap_shock_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_int_dur2_t int_dur2; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_INT_DUR2, (uint8_t*)&int_dur2, 1); + *val = int_dur2.shock; + + return ret; +} + +/** + * @brief Quiet time is the time after the first detected tap in which there + * must not be any overthreshold event. + * The default value of these bits is 00b which corresponds to + * 2*ODR_XL time. + * If the QUIET[1:0] bits are set to a different value, 1LSB + * corresponds to 4*ODR_XL time.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of quiet in reg INT_DUR2 + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_tap_quiet_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_int_dur2_t int_dur2; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_INT_DUR2, (uint8_t*)&int_dur2, 1); + + if(ret == 0) { + int_dur2.quiet = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_INT_DUR2, (uint8_t*)&int_dur2, 1); + } + + return ret; +} + +/** + * @brief Quiet time is the time after the first detected tap in which there + * must not be any overthreshold event. + * The default value of these bits is 00b which corresponds to + * 2*ODR_XL time. + * If the QUIET[1:0] bits are set to a different value, 1LSB + * corresponds to 4*ODR_XL time.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of quiet in reg INT_DUR2 + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_tap_quiet_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_int_dur2_t int_dur2; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_INT_DUR2, (uint8_t*)&int_dur2, 1); + *val = int_dur2.quiet; + + return ret; +} + +/** + * @brief When double tap recognition is enabled, this register expresses the + * maximum time between two consecutive detected taps to determine a + * double tap event. + * The default value of these bits is 0000b which corresponds to + * 16*ODR_XL time. + * If the DUR[3:0] bits are set to a different value,1LSB corresponds + * to 32*ODR_XL time.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of dur in reg INT_DUR2 + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_tap_dur_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_int_dur2_t int_dur2; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_INT_DUR2, (uint8_t*)&int_dur2, 1); + + if(ret == 0) { + int_dur2.dur = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_INT_DUR2, (uint8_t*)&int_dur2, 1); + } + + return ret; +} + +/** + * @brief When double tap recognition is enabled, this register expresses the + * maximum time between two consecutive detected taps to determine a + * double tap event. + * The default value of these bits is 0000b which corresponds to + * 16*ODR_XL time. + * If the DUR[3:0] bits are set to a different value,1LSB corresponds + * to 32*ODR_XL time.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of dur in reg INT_DUR2 + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_tap_dur_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_int_dur2_t int_dur2; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_INT_DUR2, (uint8_t*)&int_dur2, 1); + *val = int_dur2.dur; + + return ret; +} + +/** + * @brief Single/double-tap event enable/disable.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of + * single_double_tap in reg WAKE_UP_THS + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_tap_mode_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_single_double_tap_t val) { + lsm6ds3tr_c_wake_up_ths_t wake_up_ths; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_WAKE_UP_THS, (uint8_t*)&wake_up_ths, 1); + + if(ret == 0) { + wake_up_ths.single_double_tap = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_WAKE_UP_THS, (uint8_t*)&wake_up_ths, 1); + } + + return ret; +} + +/** + * @brief Single/double-tap event enable/disable.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of single_double_tap + * in reg WAKE_UP_THS + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_tap_mode_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_single_double_tap_t* val) { + lsm6ds3tr_c_wake_up_ths_t wake_up_ths; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_WAKE_UP_THS, (uint8_t*)&wake_up_ths, 1); + + switch(wake_up_ths.single_double_tap) { + case LSM6DS3TR_C_ONLY_SINGLE: + *val = LSM6DS3TR_C_ONLY_SINGLE; + break; + + case LSM6DS3TR_C_BOTH_SINGLE_DOUBLE: + *val = LSM6DS3TR_C_BOTH_SINGLE_DOUBLE; + break; + + default: + *val = LSM6DS3TR_C_TAP_MODE_ND; + break; + } + + return ret; +} + +/** + * @} + * + */ + +/** + * @defgroup LSM6DS3TR_C_ Six_position_detection(6D/4D) + * @brief This section groups all the functions concerning six + * position detection (6D). + * @{ + * + */ + +/** + * @brief LPF2 feed 6D function selection.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of low_pass_on_6d in + * reg CTRL8_XL + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_6d_feed_data_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_low_pass_on_6d_t val) { + lsm6ds3tr_c_ctrl8_xl_t ctrl8_xl; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL8_XL, (uint8_t*)&ctrl8_xl, 1); + + if(ret == 0) { + ctrl8_xl.low_pass_on_6d = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL8_XL, (uint8_t*)&ctrl8_xl, 1); + } + + return ret; +} + +/** + * @brief LPF2 feed 6D function selection.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of low_pass_on_6d in reg CTRL8_XL + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_6d_feed_data_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_low_pass_on_6d_t* val) { + lsm6ds3tr_c_ctrl8_xl_t ctrl8_xl; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL8_XL, (uint8_t*)&ctrl8_xl, 1); + + switch(ctrl8_xl.low_pass_on_6d) { + case LSM6DS3TR_C_ODR_DIV_2_FEED: + *val = LSM6DS3TR_C_ODR_DIV_2_FEED; + break; + + case LSM6DS3TR_C_LPF2_FEED: + *val = LSM6DS3TR_C_LPF2_FEED; + break; + + default: + *val = LSM6DS3TR_C_6D_FEED_ND; + break; + } + + return ret; +} + +/** + * @brief Threshold for 4D/6D function.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of sixd_ths in reg TAP_THS_6D + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_6d_threshold_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_sixd_ths_t val) { + lsm6ds3tr_c_tap_ths_6d_t tap_ths_6d; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_TAP_THS_6D, (uint8_t*)&tap_ths_6d, 1); + + if(ret == 0) { + tap_ths_6d.sixd_ths = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_TAP_THS_6D, (uint8_t*)&tap_ths_6d, 1); + } + + return ret; +} + +/** + * @brief Threshold for 4D/6D function.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of sixd_ths in reg TAP_THS_6D + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_6d_threshold_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_sixd_ths_t* val) { + lsm6ds3tr_c_tap_ths_6d_t tap_ths_6d; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_TAP_THS_6D, (uint8_t*)&tap_ths_6d, 1); + + switch(tap_ths_6d.sixd_ths) { + case LSM6DS3TR_C_DEG_80: + *val = LSM6DS3TR_C_DEG_80; + break; + + case LSM6DS3TR_C_DEG_70: + *val = LSM6DS3TR_C_DEG_70; + break; + + case LSM6DS3TR_C_DEG_60: + *val = LSM6DS3TR_C_DEG_60; + break; + + case LSM6DS3TR_C_DEG_50: + *val = LSM6DS3TR_C_DEG_50; + break; + + default: + *val = LSM6DS3TR_C_6D_TH_ND; + break; + } + + return ret; +} + +/** + * @brief 4D orientation detection enable.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of d4d_en in reg TAP_THS_6D + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_4d_mode_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_tap_ths_6d_t tap_ths_6d; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_TAP_THS_6D, (uint8_t*)&tap_ths_6d, 1); + + if(ret == 0) { + tap_ths_6d.d4d_en = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_TAP_THS_6D, (uint8_t*)&tap_ths_6d, 1); + } + + return ret; +} + +/** + * @brief 4D orientation detection enable.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of d4d_en in reg TAP_THS_6D + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_4d_mode_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_tap_ths_6d_t tap_ths_6d; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_TAP_THS_6D, (uint8_t*)&tap_ths_6d, 1); + *val = tap_ths_6d.d4d_en; + + return ret; +} + +/** + * @} + * + */ + +/** + * @defgroup LSM6DS3TR_C_free_fall + * @brief This section group all the functions concerning the free + * fall detection. + * @{ + * + */ + +/** + * @brief Free-fall duration event. 1LSb = 1 / ODR[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of ff_dur in reg WAKE_UP_DUR + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_ff_dur_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_wake_up_dur_t wake_up_dur; + lsm6ds3tr_c_free_fall_t free_fall; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FREE_FALL, (uint8_t*)&free_fall, 1); + + if(ret == 0) { + free_fall.ff_dur = (val & 0x1FU); + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_FREE_FALL, (uint8_t*)&free_fall, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_WAKE_UP_DUR, (uint8_t*)&wake_up_dur, 1); + + if(ret == 0) { + wake_up_dur.ff_dur = (val & 0x20U) >> 5; + ret = + lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_WAKE_UP_DUR, (uint8_t*)&wake_up_dur, 1); + } + } + } + + return ret; +} + +/** + * @brief Free-fall duration event. 1LSb = 1 / ODR[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of ff_dur in reg WAKE_UP_DUR + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_ff_dur_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_wake_up_dur_t wake_up_dur; + lsm6ds3tr_c_free_fall_t free_fall; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_WAKE_UP_DUR, (uint8_t*)&wake_up_dur, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FREE_FALL, (uint8_t*)&free_fall, 1); + } + + *val = (wake_up_dur.ff_dur << 5) + free_fall.ff_dur; + + return ret; +} + +/** + * @brief Free fall threshold setting.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of ff_ths in reg FREE_FALL + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_ff_threshold_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_ff_ths_t val) { + lsm6ds3tr_c_free_fall_t free_fall; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FREE_FALL, (uint8_t*)&free_fall, 1); + + if(ret == 0) { + free_fall.ff_ths = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_FREE_FALL, (uint8_t*)&free_fall, 1); + } + + return ret; +} + +/** + * @brief Free fall threshold setting.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of ff_ths in reg FREE_FALL + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_ff_threshold_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_ff_ths_t* val) { + lsm6ds3tr_c_free_fall_t free_fall; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FREE_FALL, (uint8_t*)&free_fall, 1); + + switch(free_fall.ff_ths) { + case LSM6DS3TR_C_FF_TSH_156mg: + *val = LSM6DS3TR_C_FF_TSH_156mg; + break; + + case LSM6DS3TR_C_FF_TSH_219mg: + *val = LSM6DS3TR_C_FF_TSH_219mg; + break; + + case LSM6DS3TR_C_FF_TSH_250mg: + *val = LSM6DS3TR_C_FF_TSH_250mg; + break; + + case LSM6DS3TR_C_FF_TSH_312mg: + *val = LSM6DS3TR_C_FF_TSH_312mg; + break; + + case LSM6DS3TR_C_FF_TSH_344mg: + *val = LSM6DS3TR_C_FF_TSH_344mg; + break; + + case LSM6DS3TR_C_FF_TSH_406mg: + *val = LSM6DS3TR_C_FF_TSH_406mg; + break; + + case LSM6DS3TR_C_FF_TSH_469mg: + *val = LSM6DS3TR_C_FF_TSH_469mg; + break; + + case LSM6DS3TR_C_FF_TSH_500mg: + *val = LSM6DS3TR_C_FF_TSH_500mg; + break; + + default: + *val = LSM6DS3TR_C_FF_TSH_ND; + break; + } + + return ret; +} + +/** + * @} + * + */ + +/** + * @defgroup LSM6DS3TR_C_fifo + * @brief This section group all the functions concerning the + * fifo usage + * @{ + * + */ + +/** + * @brief FIFO watermark level selection.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of fth in reg FIFO_CTRL1 + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_fifo_watermark_set(stmdev_ctx_t* ctx, uint16_t val) { + lsm6ds3tr_c_fifo_ctrl1_t fifo_ctrl1; + lsm6ds3tr_c_fifo_ctrl2_t fifo_ctrl2; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_CTRL2, (uint8_t*)&fifo_ctrl2, 1); + + if(ret == 0) { + fifo_ctrl1.fth = (uint8_t)(0x00FFU & val); + fifo_ctrl2.fth = (uint8_t)((0x0700U & val) >> 8); + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_FIFO_CTRL1, (uint8_t*)&fifo_ctrl1, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_FIFO_CTRL2, (uint8_t*)&fifo_ctrl2, 1); + } + } + + return ret; +} + +/** + * @brief FIFO watermark level selection.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of fth in reg FIFO_CTRL1 + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_fifo_watermark_get(stmdev_ctx_t* ctx, uint16_t* val) { + lsm6ds3tr_c_fifo_ctrl1_t fifo_ctrl1; + lsm6ds3tr_c_fifo_ctrl2_t fifo_ctrl2; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_CTRL1, (uint8_t*)&fifo_ctrl1, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_CTRL2, (uint8_t*)&fifo_ctrl2, 1); + } + + *val = ((uint16_t)fifo_ctrl2.fth << 8) + (uint16_t)fifo_ctrl1.fth; + + return ret; +} + +/** + * @brief FIFO data level.[get] + * + * @param ctx Read / write interface definitions + * @param val get the values of diff_fifo in reg FIFO_STATUS1 and + * FIFO_STATUS2(diff_fifo), it is recommended to set the + * BDU bit. + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_fifo_data_level_get(stmdev_ctx_t* ctx, uint16_t* val) { + lsm6ds3tr_c_fifo_status1_t fifo_status1; + lsm6ds3tr_c_fifo_status2_t fifo_status2; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_STATUS1, (uint8_t*)&fifo_status1, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_STATUS2, (uint8_t*)&fifo_status2, 1); + *val = ((uint16_t)fifo_status2.diff_fifo << 8) + (uint16_t)fifo_status1.diff_fifo; + } + + return ret; +} + +/** + * @brief FIFO watermark.[get] + * + * @param ctx Read / write interface definitions + * @param val get the values of watermark in reg FIFO_STATUS2 and + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_fifo_wtm_flag_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_fifo_status2_t fifo_status2; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_STATUS2, (uint8_t*)&fifo_status2, 1); + *val = fifo_status2.waterm; + + return ret; +} + +/** + * @brief FIFO pattern.[get] + * + * @param ctx Read / write interface definitions + * @param val get the values of fifo_pattern in reg FIFO_STATUS3 and + * FIFO_STATUS4, it is recommended to set the BDU bit + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_fifo_pattern_get(stmdev_ctx_t* ctx, uint16_t* val) { + lsm6ds3tr_c_fifo_status3_t fifo_status3; + lsm6ds3tr_c_fifo_status4_t fifo_status4; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_STATUS3, (uint8_t*)&fifo_status3, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_STATUS4, (uint8_t*)&fifo_status4, 1); + *val = ((uint16_t)fifo_status4.fifo_pattern << 8) + fifo_status3.fifo_pattern; + } + + return ret; +} + +/** + * @brief Batching of temperature data[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of fifo_temp_en in reg FIFO_CTRL2 + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_fifo_temp_batch_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_fifo_ctrl2_t fifo_ctrl2; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_CTRL2, (uint8_t*)&fifo_ctrl2, 1); + + if(ret == 0) { + fifo_ctrl2.fifo_temp_en = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_FIFO_CTRL2, (uint8_t*)&fifo_ctrl2, 1); + } + + return ret; +} + +/** + * @brief Batching of temperature data[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of fifo_temp_en in reg FIFO_CTRL2 + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_fifo_temp_batch_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_fifo_ctrl2_t fifo_ctrl2; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_CTRL2, (uint8_t*)&fifo_ctrl2, 1); + *val = fifo_ctrl2.fifo_temp_en; + + return ret; +} + +/** + * @brief Trigger signal for FIFO write operation.[set] + * + * @param ctx Read / write interface definitions + * @param val act on FIFO_CTRL2(timer_pedo_fifo_drdy) + * and MASTER_CONFIG(data_valid_sel_fifo) + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_fifo_write_trigger_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_trigger_fifo_t val) { + lsm6ds3tr_c_fifo_ctrl2_t fifo_ctrl2; + lsm6ds3tr_c_master_config_t master_config; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_CTRL2, (uint8_t*)&fifo_ctrl2, 1); + + if(ret == 0) { + fifo_ctrl2.timer_pedo_fifo_drdy = (uint8_t)val & 0x01U; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_FIFO_CTRL2, (uint8_t*)&fifo_ctrl2, 1); + + if(ret == 0) { + ret = + lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_MASTER_CONFIG, (uint8_t*)&master_config, 1); + + if(ret == 0) { + master_config.data_valid_sel_fifo = (((uint8_t)val & 0x02U) >> 1); + ret = lsm6ds3tr_c_write_reg( + ctx, LSM6DS3TR_C_MASTER_CONFIG, (uint8_t*)&master_config, 1); + } + } + } + + return ret; +} + +/** + * @brief Trigger signal for FIFO write operation.[get] + * + * @param ctx Read / write interface definitions + * @param val act on FIFO_CTRL2(timer_pedo_fifo_drdy) + * and MASTER_CONFIG(data_valid_sel_fifo) + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_fifo_write_trigger_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_trigger_fifo_t* val) { + lsm6ds3tr_c_fifo_ctrl2_t fifo_ctrl2; + lsm6ds3tr_c_master_config_t master_config; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_CTRL2, (uint8_t*)&fifo_ctrl2, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_MASTER_CONFIG, (uint8_t*)&master_config, 1); + + switch((fifo_ctrl2.timer_pedo_fifo_drdy << 1) + fifo_ctrl2.timer_pedo_fifo_drdy) { + case LSM6DS3TR_C_TRG_XL_GY_DRDY: + *val = LSM6DS3TR_C_TRG_XL_GY_DRDY; + break; + + case LSM6DS3TR_C_TRG_STEP_DETECT: + *val = LSM6DS3TR_C_TRG_STEP_DETECT; + break; + + case LSM6DS3TR_C_TRG_SH_DRDY: + *val = LSM6DS3TR_C_TRG_SH_DRDY; + break; + + default: + *val = LSM6DS3TR_C_TRG_SH_ND; + break; + } + } + + return ret; +} + +/** + * @brief Enable pedometer step counter and timestamp as 4th + * FIFO data set.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of timer_pedo_fifo_en in reg FIFO_CTRL2 + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_fifo_pedo_and_timestamp_batch_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_fifo_ctrl2_t fifo_ctrl2; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_CTRL2, (uint8_t*)&fifo_ctrl2, 1); + + if(ret == 0) { + fifo_ctrl2.timer_pedo_fifo_en = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_FIFO_CTRL2, (uint8_t*)&fifo_ctrl2, 1); + } + + return ret; +} + +/** + * @brief Enable pedometer step counter and timestamp as 4th + * FIFO data set.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of timer_pedo_fifo_en in reg FIFO_CTRL2 + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_fifo_pedo_and_timestamp_batch_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_fifo_ctrl2_t fifo_ctrl2; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_CTRL2, (uint8_t*)&fifo_ctrl2, 1); + *val = fifo_ctrl2.timer_pedo_fifo_en; + + return ret; +} + +/** + * @brief Selects Batching Data Rate (writing frequency in FIFO) for + * accelerometer data.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of dec_fifo_xl in reg FIFO_CTRL3 + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_fifo_xl_batch_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_dec_fifo_xl_t val) { + lsm6ds3tr_c_fifo_ctrl3_t fifo_ctrl3; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_CTRL3, (uint8_t*)&fifo_ctrl3, 1); + + if(ret == 0) { + fifo_ctrl3.dec_fifo_xl = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_FIFO_CTRL3, (uint8_t*)&fifo_ctrl3, 1); + } + + return ret; +} + +/** + * @brief Selects Batching Data Rate (writing frequency in FIFO) for + * accelerometer data.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of dec_fifo_xl in reg FIFO_CTRL3 + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_fifo_xl_batch_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_dec_fifo_xl_t* val) { + lsm6ds3tr_c_fifo_ctrl3_t fifo_ctrl3; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_CTRL3, (uint8_t*)&fifo_ctrl3, 1); + + switch(fifo_ctrl3.dec_fifo_xl) { + case LSM6DS3TR_C_FIFO_XL_DISABLE: + *val = LSM6DS3TR_C_FIFO_XL_DISABLE; + break; + + case LSM6DS3TR_C_FIFO_XL_NO_DEC: + *val = LSM6DS3TR_C_FIFO_XL_NO_DEC; + break; + + case LSM6DS3TR_C_FIFO_XL_DEC_2: + *val = LSM6DS3TR_C_FIFO_XL_DEC_2; + break; + + case LSM6DS3TR_C_FIFO_XL_DEC_3: + *val = LSM6DS3TR_C_FIFO_XL_DEC_3; + break; + + case LSM6DS3TR_C_FIFO_XL_DEC_4: + *val = LSM6DS3TR_C_FIFO_XL_DEC_4; + break; + + case LSM6DS3TR_C_FIFO_XL_DEC_8: + *val = LSM6DS3TR_C_FIFO_XL_DEC_8; + break; + + case LSM6DS3TR_C_FIFO_XL_DEC_16: + *val = LSM6DS3TR_C_FIFO_XL_DEC_16; + break; + + case LSM6DS3TR_C_FIFO_XL_DEC_32: + *val = LSM6DS3TR_C_FIFO_XL_DEC_32; + break; + + default: + *val = LSM6DS3TR_C_FIFO_XL_DEC_ND; + break; + } + + return ret; +} + +/** + * @brief Selects Batching Data Rate (writing frequency in FIFO) + * for gyroscope data.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of dec_fifo_gyro in reg FIFO_CTRL3 + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_fifo_gy_batch_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_dec_fifo_gyro_t val) { + lsm6ds3tr_c_fifo_ctrl3_t fifo_ctrl3; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_CTRL3, (uint8_t*)&fifo_ctrl3, 1); + + if(ret == 0) { + fifo_ctrl3.dec_fifo_gyro = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_FIFO_CTRL3, (uint8_t*)&fifo_ctrl3, 1); + } + + return ret; +} + +/** + * @brief Selects Batching Data Rate (writing frequency in FIFO) + * for gyroscope data.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of dec_fifo_gyro in reg FIFO_CTRL3 + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_fifo_gy_batch_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_dec_fifo_gyro_t* val) { + lsm6ds3tr_c_fifo_ctrl3_t fifo_ctrl3; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_CTRL3, (uint8_t*)&fifo_ctrl3, 1); + + switch(fifo_ctrl3.dec_fifo_gyro) { + case LSM6DS3TR_C_FIFO_GY_DISABLE: + *val = LSM6DS3TR_C_FIFO_GY_DISABLE; + break; + + case LSM6DS3TR_C_FIFO_GY_NO_DEC: + *val = LSM6DS3TR_C_FIFO_GY_NO_DEC; + break; + + case LSM6DS3TR_C_FIFO_GY_DEC_2: + *val = LSM6DS3TR_C_FIFO_GY_DEC_2; + break; + + case LSM6DS3TR_C_FIFO_GY_DEC_3: + *val = LSM6DS3TR_C_FIFO_GY_DEC_3; + break; + + case LSM6DS3TR_C_FIFO_GY_DEC_4: + *val = LSM6DS3TR_C_FIFO_GY_DEC_4; + break; + + case LSM6DS3TR_C_FIFO_GY_DEC_8: + *val = LSM6DS3TR_C_FIFO_GY_DEC_8; + break; + + case LSM6DS3TR_C_FIFO_GY_DEC_16: + *val = LSM6DS3TR_C_FIFO_GY_DEC_16; + break; + + case LSM6DS3TR_C_FIFO_GY_DEC_32: + *val = LSM6DS3TR_C_FIFO_GY_DEC_32; + break; + + default: + *val = LSM6DS3TR_C_FIFO_GY_DEC_ND; + break; + } + + return ret; +} + +/** + * @brief Selects Batching Data Rate (writing frequency in FIFO) + * for third data set.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of dec_ds3_fifo in reg FIFO_CTRL4 + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_fifo_dataset_3_batch_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_dec_ds3_fifo_t val) { + lsm6ds3tr_c_fifo_ctrl4_t fifo_ctrl4; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_CTRL4, (uint8_t*)&fifo_ctrl4, 1); + + if(ret == 0) { + fifo_ctrl4.dec_ds3_fifo = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_FIFO_CTRL4, (uint8_t*)&fifo_ctrl4, 1); + } + + return ret; +} + +/** + * @brief Selects Batching Data Rate (writing frequency in FIFO) + * for third data set.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of dec_ds3_fifo in reg FIFO_CTRL4 + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_fifo_dataset_3_batch_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_dec_ds3_fifo_t* val) { + lsm6ds3tr_c_fifo_ctrl4_t fifo_ctrl4; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_CTRL4, (uint8_t*)&fifo_ctrl4, 1); + + switch(fifo_ctrl4.dec_ds3_fifo) { + case LSM6DS3TR_C_FIFO_DS3_DISABLE: + *val = LSM6DS3TR_C_FIFO_DS3_DISABLE; + break; + + case LSM6DS3TR_C_FIFO_DS3_NO_DEC: + *val = LSM6DS3TR_C_FIFO_DS3_NO_DEC; + break; + + case LSM6DS3TR_C_FIFO_DS3_DEC_2: + *val = LSM6DS3TR_C_FIFO_DS3_DEC_2; + break; + + case LSM6DS3TR_C_FIFO_DS3_DEC_3: + *val = LSM6DS3TR_C_FIFO_DS3_DEC_3; + break; + + case LSM6DS3TR_C_FIFO_DS3_DEC_4: + *val = LSM6DS3TR_C_FIFO_DS3_DEC_4; + break; + + case LSM6DS3TR_C_FIFO_DS3_DEC_8: + *val = LSM6DS3TR_C_FIFO_DS3_DEC_8; + break; + + case LSM6DS3TR_C_FIFO_DS3_DEC_16: + *val = LSM6DS3TR_C_FIFO_DS3_DEC_16; + break; + + case LSM6DS3TR_C_FIFO_DS3_DEC_32: + *val = LSM6DS3TR_C_FIFO_DS3_DEC_32; + break; + + default: + *val = LSM6DS3TR_C_FIFO_DS3_DEC_ND; + break; + } + + return ret; +} + +/** + * @brief Selects Batching Data Rate (writing frequency in FIFO) + * for fourth data set.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of dec_ds4_fifo in reg FIFO_CTRL4 + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_fifo_dataset_4_batch_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_dec_ds4_fifo_t val) { + lsm6ds3tr_c_fifo_ctrl4_t fifo_ctrl4; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_CTRL4, (uint8_t*)&fifo_ctrl4, 1); + + if(ret == 0) { + fifo_ctrl4.dec_ds4_fifo = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_FIFO_CTRL4, (uint8_t*)&fifo_ctrl4, 1); + } + + return ret; +} + +/** + * @brief Selects Batching Data Rate (writing frequency in FIFO) for + * fourth data set.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of dec_ds4_fifo in reg FIFO_CTRL4 + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_fifo_dataset_4_batch_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_dec_ds4_fifo_t* val) { + lsm6ds3tr_c_fifo_ctrl4_t fifo_ctrl4; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_CTRL4, (uint8_t*)&fifo_ctrl4, 1); + + switch(fifo_ctrl4.dec_ds4_fifo) { + case LSM6DS3TR_C_FIFO_DS4_DISABLE: + *val = LSM6DS3TR_C_FIFO_DS4_DISABLE; + break; + + case LSM6DS3TR_C_FIFO_DS4_NO_DEC: + *val = LSM6DS3TR_C_FIFO_DS4_NO_DEC; + break; + + case LSM6DS3TR_C_FIFO_DS4_DEC_2: + *val = LSM6DS3TR_C_FIFO_DS4_DEC_2; + break; + + case LSM6DS3TR_C_FIFO_DS4_DEC_3: + *val = LSM6DS3TR_C_FIFO_DS4_DEC_3; + break; + + case LSM6DS3TR_C_FIFO_DS4_DEC_4: + *val = LSM6DS3TR_C_FIFO_DS4_DEC_4; + break; + + case LSM6DS3TR_C_FIFO_DS4_DEC_8: + *val = LSM6DS3TR_C_FIFO_DS4_DEC_8; + break; + + case LSM6DS3TR_C_FIFO_DS4_DEC_16: + *val = LSM6DS3TR_C_FIFO_DS4_DEC_16; + break; + + case LSM6DS3TR_C_FIFO_DS4_DEC_32: + *val = LSM6DS3TR_C_FIFO_DS4_DEC_32; + break; + + default: + *val = LSM6DS3TR_C_FIFO_DS4_DEC_ND; + break; + } + + return ret; +} + +/** + * @brief 8-bit data storage in FIFO.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of only_high_data in reg FIFO_CTRL4 + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_fifo_xl_gy_8bit_format_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_fifo_ctrl4_t fifo_ctrl4; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_CTRL4, (uint8_t*)&fifo_ctrl4, 1); + + if(ret == 0) { + fifo_ctrl4.only_high_data = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_FIFO_CTRL4, (uint8_t*)&fifo_ctrl4, 1); + } + + return ret; +} + +/** + * @brief 8-bit data storage in FIFO.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of only_high_data in reg FIFO_CTRL4 + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_fifo_xl_gy_8bit_format_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_fifo_ctrl4_t fifo_ctrl4; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_CTRL4, (uint8_t*)&fifo_ctrl4, 1); + *val = fifo_ctrl4.only_high_data; + + return ret; +} + +/** + * @brief Sensing chain FIFO stop values memorization at threshold + * level.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of stop_on_fth in reg FIFO_CTRL4 + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_fifo_stop_on_wtm_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_fifo_ctrl4_t fifo_ctrl4; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_CTRL4, (uint8_t*)&fifo_ctrl4, 1); + + if(ret == 0) { + fifo_ctrl4.stop_on_fth = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_FIFO_CTRL4, (uint8_t*)&fifo_ctrl4, 1); + } + + return ret; +} + +/** + * @brief Sensing chain FIFO stop values memorization at threshold + * level.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of stop_on_fth in reg FIFO_CTRL4 + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_fifo_stop_on_wtm_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_fifo_ctrl4_t fifo_ctrl4; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_CTRL4, (uint8_t*)&fifo_ctrl4, 1); + *val = fifo_ctrl4.stop_on_fth; + + return ret; +} + +/** + * @brief FIFO mode selection.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of fifo_mode in reg FIFO_CTRL5 + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_fifo_mode_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_fifo_mode_t val) { + lsm6ds3tr_c_fifo_ctrl5_t fifo_ctrl5; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_CTRL5, (uint8_t*)&fifo_ctrl5, 1); + + if(ret == 0) { + fifo_ctrl5.fifo_mode = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_FIFO_CTRL5, (uint8_t*)&fifo_ctrl5, 1); + } + + return ret; +} + +/** + * @brief FIFO mode selection.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of fifo_mode in reg FIFO_CTRL5 + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_fifo_mode_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_fifo_mode_t* val) { + lsm6ds3tr_c_fifo_ctrl5_t fifo_ctrl5; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_CTRL5, (uint8_t*)&fifo_ctrl5, 1); + + switch(fifo_ctrl5.fifo_mode) { + case LSM6DS3TR_C_BYPASS_MODE: + *val = LSM6DS3TR_C_BYPASS_MODE; + break; + + case LSM6DS3TR_C_FIFO_MODE: + *val = LSM6DS3TR_C_FIFO_MODE; + break; + + case LSM6DS3TR_C_STREAM_TO_FIFO_MODE: + *val = LSM6DS3TR_C_STREAM_TO_FIFO_MODE; + break; + + case LSM6DS3TR_C_BYPASS_TO_STREAM_MODE: + *val = LSM6DS3TR_C_BYPASS_TO_STREAM_MODE; + break; + + case LSM6DS3TR_C_STREAM_MODE: + *val = LSM6DS3TR_C_STREAM_MODE; + break; + + default: + *val = LSM6DS3TR_C_FIFO_MODE_ND; + break; + } + + return ret; +} + +/** + * @brief FIFO ODR selection, setting FIFO_MODE also.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of odr_fifo in reg FIFO_CTRL5 + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_fifo_data_rate_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_odr_fifo_t val) { + lsm6ds3tr_c_fifo_ctrl5_t fifo_ctrl5; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_CTRL5, (uint8_t*)&fifo_ctrl5, 1); + + if(ret == 0) { + fifo_ctrl5.odr_fifo = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_FIFO_CTRL5, (uint8_t*)&fifo_ctrl5, 1); + } + + return ret; +} + +/** + * @brief FIFO ODR selection, setting FIFO_MODE also.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of odr_fifo in reg FIFO_CTRL5 + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_fifo_data_rate_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_odr_fifo_t* val) { + lsm6ds3tr_c_fifo_ctrl5_t fifo_ctrl5; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_FIFO_CTRL5, (uint8_t*)&fifo_ctrl5, 1); + + switch(fifo_ctrl5.odr_fifo) { + case LSM6DS3TR_C_FIFO_DISABLE: + *val = LSM6DS3TR_C_FIFO_DISABLE; + break; + + case LSM6DS3TR_C_FIFO_12Hz5: + *val = LSM6DS3TR_C_FIFO_12Hz5; + break; + + case LSM6DS3TR_C_FIFO_26Hz: + *val = LSM6DS3TR_C_FIFO_26Hz; + break; + + case LSM6DS3TR_C_FIFO_52Hz: + *val = LSM6DS3TR_C_FIFO_52Hz; + break; + + case LSM6DS3TR_C_FIFO_104Hz: + *val = LSM6DS3TR_C_FIFO_104Hz; + break; + + case LSM6DS3TR_C_FIFO_208Hz: + *val = LSM6DS3TR_C_FIFO_208Hz; + break; + + case LSM6DS3TR_C_FIFO_416Hz: + *val = LSM6DS3TR_C_FIFO_416Hz; + break; + + case LSM6DS3TR_C_FIFO_833Hz: + *val = LSM6DS3TR_C_FIFO_833Hz; + break; + + case LSM6DS3TR_C_FIFO_1k66Hz: + *val = LSM6DS3TR_C_FIFO_1k66Hz; + break; + + case LSM6DS3TR_C_FIFO_3k33Hz: + *val = LSM6DS3TR_C_FIFO_3k33Hz; + break; + + case LSM6DS3TR_C_FIFO_6k66Hz: + *val = LSM6DS3TR_C_FIFO_6k66Hz; + break; + + default: + *val = LSM6DS3TR_C_FIFO_RATE_ND; + break; + } + + return ret; +} + +/** + * @} + * + */ + +/** + * @defgroup LSM6DS3TR_C_DEN_functionality + * @brief This section groups all the functions concerning DEN + * functionality. + * @{ + * + */ + +/** + * @brief DEN active level configuration.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of den_lh in reg CTRL5_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_den_polarity_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_den_lh_t val) { + lsm6ds3tr_c_ctrl5_c_t ctrl5_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL5_C, (uint8_t*)&ctrl5_c, 1); + + if(ret == 0) { + ctrl5_c.den_lh = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL5_C, (uint8_t*)&ctrl5_c, 1); + } + + return ret; +} + +/** + * @brief DEN active level configuration.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of den_lh in reg CTRL5_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_den_polarity_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_den_lh_t* val) { + lsm6ds3tr_c_ctrl5_c_t ctrl5_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL5_C, (uint8_t*)&ctrl5_c, 1); + + switch(ctrl5_c.den_lh) { + case LSM6DS3TR_C_DEN_ACT_LOW: + *val = LSM6DS3TR_C_DEN_ACT_LOW; + break; + + case LSM6DS3TR_C_DEN_ACT_HIGH: + *val = LSM6DS3TR_C_DEN_ACT_HIGH; + break; + + default: + *val = LSM6DS3TR_C_DEN_POL_ND; + break; + } + + return ret; +} + +/** + * @brief DEN functionality marking mode[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of den_mode in reg CTRL6_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_den_mode_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_den_mode_t val) { + lsm6ds3tr_c_ctrl6_c_t ctrl6_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL6_C, (uint8_t*)&ctrl6_c, 1); + + if(ret == 0) { + ctrl6_c.den_mode = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL6_C, (uint8_t*)&ctrl6_c, 1); + } + + return ret; +} + +/** + * @brief DEN functionality marking mode[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of den_mode in reg CTRL6_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_den_mode_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_den_mode_t* val) { + lsm6ds3tr_c_ctrl6_c_t ctrl6_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL6_C, (uint8_t*)&ctrl6_c, 1); + + switch(ctrl6_c.den_mode) { + case LSM6DS3TR_C_DEN_DISABLE: + *val = LSM6DS3TR_C_DEN_DISABLE; + break; + + case LSM6DS3TR_C_LEVEL_LETCHED: + *val = LSM6DS3TR_C_LEVEL_LETCHED; + break; + + case LSM6DS3TR_C_LEVEL_TRIGGER: + *val = LSM6DS3TR_C_LEVEL_TRIGGER; + break; + + case LSM6DS3TR_C_EDGE_TRIGGER: + *val = LSM6DS3TR_C_EDGE_TRIGGER; + break; + + default: + *val = LSM6DS3TR_C_DEN_MODE_ND; + break; + } + + return ret; +} + +/** + * @brief Extend DEN functionality to accelerometer sensor.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of den_xl_g in reg CTRL9_XL + * and den_xl_en in CTRL4_C. + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_den_enable_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_den_xl_en_t val) { + lsm6ds3tr_c_ctrl4_c_t ctrl4_c; + lsm6ds3tr_c_ctrl9_xl_t ctrl9_xl; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL9_XL, (uint8_t*)&ctrl9_xl, 1); + + if(ret == 0) { + ctrl9_xl.den_xl_g = (uint8_t)val & 0x01U; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL9_XL, (uint8_t*)&ctrl9_xl, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL4_C, (uint8_t*)&ctrl4_c, 1); + + if(ret == 0) { + ctrl4_c.den_xl_en = (uint8_t)val & 0x02U; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL4_C, (uint8_t*)&ctrl4_c, 1); + } + } + } + + return ret; +} + +/** + * @brief Extend DEN functionality to accelerometer sensor. [get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of den_xl_g in reg CTRL9_XL + * and den_xl_en in CTRL4_C. + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_den_enable_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_den_xl_en_t* val) { + lsm6ds3tr_c_ctrl4_c_t ctrl4_c; + lsm6ds3tr_c_ctrl9_xl_t ctrl9_xl; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL4_C, (uint8_t*)&ctrl4_c, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL9_XL, (uint8_t*)&ctrl9_xl, 1); + + switch((ctrl4_c.den_xl_en << 1) + ctrl9_xl.den_xl_g) { + case LSM6DS3TR_C_STAMP_IN_GY_DATA: + *val = LSM6DS3TR_C_STAMP_IN_GY_DATA; + break; + + case LSM6DS3TR_C_STAMP_IN_XL_DATA: + *val = LSM6DS3TR_C_STAMP_IN_XL_DATA; + break; + + case LSM6DS3TR_C_STAMP_IN_GY_XL_DATA: + *val = LSM6DS3TR_C_STAMP_IN_GY_XL_DATA; + break; + + default: + *val = LSM6DS3TR_C_DEN_STAMP_ND; + break; + } + } + + return ret; +} + +/** + * @brief DEN value stored in LSB of Z-axis.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of den_z in reg CTRL9_XL + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_den_mark_axis_z_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_ctrl9_xl_t ctrl9_xl; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL9_XL, (uint8_t*)&ctrl9_xl, 1); + + if(ret == 0) { + ctrl9_xl.den_z = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL9_XL, (uint8_t*)&ctrl9_xl, 1); + } + + return ret; +} + +/** + * @brief DEN value stored in LSB of Z-axis.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of den_z in reg CTRL9_XL + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_den_mark_axis_z_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_ctrl9_xl_t ctrl9_xl; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL9_XL, (uint8_t*)&ctrl9_xl, 1); + *val = ctrl9_xl.den_z; + + return ret; +} + +/** + * @brief DEN value stored in LSB of Y-axis.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of den_y in reg CTRL9_XL + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_den_mark_axis_y_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_ctrl9_xl_t ctrl9_xl; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL9_XL, (uint8_t*)&ctrl9_xl, 1); + + if(ret == 0) { + ctrl9_xl.den_y = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL9_XL, (uint8_t*)&ctrl9_xl, 1); + } + + return ret; +} + +/** + * @brief DEN value stored in LSB of Y-axis.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of den_y in reg CTRL9_XL + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_den_mark_axis_y_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_ctrl9_xl_t ctrl9_xl; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL9_XL, (uint8_t*)&ctrl9_xl, 1); + *val = ctrl9_xl.den_y; + + return ret; +} + +/** + * @brief DEN value stored in LSB of X-axis.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of den_x in reg CTRL9_XL + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_den_mark_axis_x_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_ctrl9_xl_t ctrl9_xl; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL9_XL, (uint8_t*)&ctrl9_xl, 1); + + if(ret == 0) { + ctrl9_xl.den_x = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL9_XL, (uint8_t*)&ctrl9_xl, 1); + } + + return ret; +} + +/** + * @brief DEN value stored in LSB of X-axis.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of den_x in reg CTRL9_XL + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_den_mark_axis_x_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_ctrl9_xl_t ctrl9_xl; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL9_XL, (uint8_t*)&ctrl9_xl, 1); + *val = ctrl9_xl.den_x; + + return ret; +} + +/** + * @} + * + */ + +/** + * @defgroup LSM6DS3TR_C_Pedometer + * @brief This section groups all the functions that manage pedometer. + * @{ + * + */ + +/** + * @brief Reset pedometer step counter.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of pedo_rst_step in reg CTRL10_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_pedo_step_reset_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_ctrl10_c_t ctrl10_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL10_C, (uint8_t*)&ctrl10_c, 1); + + if(ret == 0) { + ctrl10_c.pedo_rst_step = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL10_C, (uint8_t*)&ctrl10_c, 1); + } + + return ret; +} + +/** + * @brief Reset pedometer step counter.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of pedo_rst_step in reg CTRL10_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_pedo_step_reset_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_ctrl10_c_t ctrl10_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL10_C, (uint8_t*)&ctrl10_c, 1); + *val = ctrl10_c.pedo_rst_step; + + return ret; +} + +/** + * @brief Enable pedometer algorithm.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of pedo_en in reg CTRL10_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_pedo_sens_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_ctrl10_c_t ctrl10_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL10_C, (uint8_t*)&ctrl10_c, 1); + + if(ret == 0) { + ctrl10_c.pedo_en = val; + + if(val != 0x00U) { + ctrl10_c.func_en = val; + } + + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL10_C, (uint8_t*)&ctrl10_c, 1); + } + + return ret; +} + +/** + * @brief pedo_sens: Enable pedometer algorithm.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of pedo_en in reg CTRL10_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_pedo_sens_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_ctrl10_c_t ctrl10_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL10_C, (uint8_t*)&ctrl10_c, 1); + *val = ctrl10_c.pedo_en; + + return ret; +} + +/** + * @brief Minimum threshold to detect a peak. Default is 10h.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of ths_min in reg + * CONFIG_PEDO_THS_MIN + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_pedo_threshold_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_config_pedo_ths_min_t config_pedo_ths_min; + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg( + ctx, LSM6DS3TR_C_CONFIG_PEDO_THS_MIN, (uint8_t*)&config_pedo_ths_min, 1); + + if(ret == 0) { + config_pedo_ths_min.ths_min = val; + ret = lsm6ds3tr_c_write_reg( + ctx, LSM6DS3TR_C_CONFIG_PEDO_THS_MIN, (uint8_t*)&config_pedo_ths_min, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + } + + return ret; +} + +/** + * @brief Minimum threshold to detect a peak. Default is 10h.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of ths_min in reg CONFIG_PEDO_THS_MIN + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_pedo_threshold_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_config_pedo_ths_min_t config_pedo_ths_min; + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg( + ctx, LSM6DS3TR_C_CONFIG_PEDO_THS_MIN, (uint8_t*)&config_pedo_ths_min, 1); + + if(ret == 0) { + *val = config_pedo_ths_min.ths_min; + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + + return ret; +} + +/** + * @brief pedo_full_scale: Pedometer data range.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of pedo_fs in + * reg CONFIG_PEDO_THS_MIN + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_pedo_full_scale_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_pedo_fs_t val) { + lsm6ds3tr_c_config_pedo_ths_min_t config_pedo_ths_min; + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg( + ctx, LSM6DS3TR_C_CONFIG_PEDO_THS_MIN, (uint8_t*)&config_pedo_ths_min, 1); + + if(ret == 0) { + config_pedo_ths_min.pedo_fs = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg( + ctx, LSM6DS3TR_C_CONFIG_PEDO_THS_MIN, (uint8_t*)&config_pedo_ths_min, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + } + + return ret; +} + +/** + * @brief Pedometer data range.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of pedo_fs in + * reg CONFIG_PEDO_THS_MIN + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_pedo_full_scale_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_pedo_fs_t* val) { + lsm6ds3tr_c_config_pedo_ths_min_t config_pedo_ths_min; + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg( + ctx, LSM6DS3TR_C_CONFIG_PEDO_THS_MIN, (uint8_t*)&config_pedo_ths_min, 1); + + if(ret == 0) { + switch(config_pedo_ths_min.pedo_fs) { + case LSM6DS3TR_C_PEDO_AT_2g: + *val = LSM6DS3TR_C_PEDO_AT_2g; + break; + + case LSM6DS3TR_C_PEDO_AT_4g: + *val = LSM6DS3TR_C_PEDO_AT_4g; + break; + + default: + *val = LSM6DS3TR_C_PEDO_FS_ND; + break; + } + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + + return ret; +} + +/** + * @brief Pedometer debounce configuration register (r/w).[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of deb_step in reg PEDO_DEB_REG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_pedo_debounce_steps_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_pedo_deb_reg_t pedo_deb_reg; + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_PEDO_DEB_REG, (uint8_t*)&pedo_deb_reg, 1); + + if(ret == 0) { + pedo_deb_reg.deb_step = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_PEDO_DEB_REG, (uint8_t*)&pedo_deb_reg, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + } + + return ret; +} + +/** + * @brief Pedometer debounce configuration register (r/w).[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of deb_step in reg PEDO_DEB_REG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_pedo_debounce_steps_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_pedo_deb_reg_t pedo_deb_reg; + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_PEDO_DEB_REG, (uint8_t*)&pedo_deb_reg, 1); + + if(ret == 0) { + *val = pedo_deb_reg.deb_step; + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + + return ret; +} + +/** + * @brief Debounce time. If the time between two consecutive steps is + * greater than DEB_TIME*80ms, the debouncer is reactivated. + * Default value: 01101[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of deb_time in reg PEDO_DEB_REG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_pedo_timeout_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_pedo_deb_reg_t pedo_deb_reg; + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_PEDO_DEB_REG, (uint8_t*)&pedo_deb_reg, 1); + + if(ret == 0) { + pedo_deb_reg.deb_time = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_PEDO_DEB_REG, (uint8_t*)&pedo_deb_reg, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + } + + return ret; +} + +/** + * @brief Debounce time. If the time between two consecutive steps is + * greater than DEB_TIME*80ms, the debouncer is reactivated. + * Default value: 01101[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of deb_time in reg PEDO_DEB_REG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_pedo_timeout_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_pedo_deb_reg_t pedo_deb_reg; + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_PEDO_DEB_REG, (uint8_t*)&pedo_deb_reg, 1); + + if(ret == 0) { + *val = pedo_deb_reg.deb_time; + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + + return ret; +} + +/** + * @brief Time period register for step detection on delta time (r/w).[set] + * + * @param ctx Read / write interface definitions + * @param buff Buffer that contains data to write + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_pedo_steps_period_set(stmdev_ctx_t* ctx, uint8_t* buff) { + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_STEP_COUNT_DELTA, buff, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + + return ret; +} + +/** + * @brief Time period register for step detection on delta time (r/w).[get] + * + * @param ctx Read / write interface definitions + * @param buff Buffer that stores data read + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_pedo_steps_period_get(stmdev_ctx_t* ctx, uint8_t* buff) { + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_STEP_COUNT_DELTA, buff, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + + return ret; +} + +/** + * @} + * + */ + +/** + * @defgroup LSM6DS3TR_C_significant_motion + * @brief This section groups all the functions that manage the + * significant motion detection. + * @{ + * + */ + +/** + * @brief Enable significant motion detection function.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of sign_motion_en in reg CTRL10_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_motion_sens_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_ctrl10_c_t ctrl10_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL10_C, (uint8_t*)&ctrl10_c, 1); + + if(ret == 0) { + ctrl10_c.sign_motion_en = val; + + if(val != 0x00U) { + ctrl10_c.func_en = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL10_C, (uint8_t*)&ctrl10_c, 1); + } + } + + return ret; +} + +/** + * @brief Enable significant motion detection function.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of sign_motion_en in reg CTRL10_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_motion_sens_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_ctrl10_c_t ctrl10_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL10_C, (uint8_t*)&ctrl10_c, 1); + *val = ctrl10_c.sign_motion_en; + + return ret; +} + +/** + * @brief Significant motion threshold.[set] + * + * @param ctx Read / write interface definitions + * @param buff Buffer that store significant motion threshold. + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_motion_threshold_set(stmdev_ctx_t* ctx, uint8_t* buff) { + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_SM_THS, buff, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + + return ret; +} + +/** + * @brief Significant motion threshold.[get] + * + * @param ctx Read / write interface definitions + * @param buff Buffer that store significant motion threshold. + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_motion_threshold_get(stmdev_ctx_t* ctx, uint8_t* buff) { + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_SM_THS, buff, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + + return ret; +} + +/** + * @} + * + */ + +/** + * @defgroup LSM6DS3TR_C_tilt_detection + * @brief This section groups all the functions that manage the tilt + * event detection. + * @{ + * + */ + +/** + * @brief Enable tilt calculation.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of tilt_en in reg CTRL10_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_tilt_sens_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_ctrl10_c_t ctrl10_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL10_C, (uint8_t*)&ctrl10_c, 1); + + if(ret == 0) { + ctrl10_c.tilt_en = val; + + if(val != 0x00U) { + ctrl10_c.func_en = val; + } + + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL10_C, (uint8_t*)&ctrl10_c, 1); + } + + return ret; +} + +/** + * @brief Enable tilt calculation.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of tilt_en in reg CTRL10_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_tilt_sens_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_ctrl10_c_t ctrl10_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL10_C, (uint8_t*)&ctrl10_c, 1); + *val = ctrl10_c.tilt_en; + + return ret; +} + +/** + * @brief Enable tilt calculation.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of tilt_en in reg CTRL10_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_wrist_tilt_sens_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_ctrl10_c_t ctrl10_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL10_C, (uint8_t*)&ctrl10_c, 1); + + if(ret == 0) { + ctrl10_c.wrist_tilt_en = val; + + if(val != 0x00U) { + ctrl10_c.func_en = val; + } + + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL10_C, (uint8_t*)&ctrl10_c, 1); + } + + return ret; +} + +/** + * @brief Enable tilt calculation.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of tilt_en in reg CTRL10_C + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_wrist_tilt_sens_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_ctrl10_c_t ctrl10_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL10_C, (uint8_t*)&ctrl10_c, 1); + *val = ctrl10_c.wrist_tilt_en; + + return ret; +} + +/** + * @brief Absolute Wrist Tilt latency register (r/w). + * Absolute wrist tilt latency parameters. + * 1 LSB = 40 ms. Default value: 0Fh (600 ms).[set] + * + * @param ctx Read / write interface definitions + * @param buff Buffer that contains data to write + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_tilt_latency_set(stmdev_ctx_t* ctx, uint8_t* buff) { + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_B); + + if(ret == 0) { + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_A_WRIST_TILT_LAT, buff, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + + return ret; +} + +/** + * @brief Absolute Wrist Tilt latency register (r/w). + * Absolute wrist tilt latency parameters. + * 1 LSB = 40 ms. Default value: 0Fh (600 ms).[get] + * + * @param ctx Read / write interface definitions + * @param buff Buffer that stores data read + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_tilt_latency_get(stmdev_ctx_t* ctx, uint8_t* buff) { + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_B); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_A_WRIST_TILT_LAT, buff, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + + return ret; +} + +/** + * @brief Absolute Wrist Tilt threshold register(r/w). + * Absolute wrist tilt threshold parameters. + * 1 LSB = 15.625 mg.Default value: 20h (500 mg).[set] + * + * @param ctx Read / write interface definitions + * @param buff Buffer that contains data to write + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_tilt_threshold_set(stmdev_ctx_t* ctx, uint8_t* buff) { + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_B); + + if(ret == 0) { + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_A_WRIST_TILT_THS, buff, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + + return ret; +} + +/** + * @brief Absolute Wrist Tilt threshold register(r/w). + * Absolute wrist tilt threshold parameters. + * 1 LSB = 15.625 mg.Default value: 20h (500 mg).[get] + * + * @param ctx Read / write interface definitions + * @param buff Buffer that stores data read + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_tilt_threshold_get(stmdev_ctx_t* ctx, uint8_t* buff) { + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_B); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_A_WRIST_TILT_THS, buff, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + + return ret; +} + +/** + * @brief Absolute Wrist Tilt mask register (r/w).[set] + * + * @param ctx Read / write interface definitions + * @param val Registers A_WRIST_TILT_MASK + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_tilt_src_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_a_wrist_tilt_mask_t* val) { + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_B); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_A_WRIST_TILT_MASK, (uint8_t*)val, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + + return ret; +} + +/** + * @brief Absolute Wrist Tilt mask register (r/w).[get] + * + * @param ctx Read / write interface definitions + * @param val Registers A_WRIST_TILT_MASK + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_tilt_src_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_a_wrist_tilt_mask_t* val) { + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_B); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_A_WRIST_TILT_MASK, (uint8_t*)val, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + + return ret; +} + +/** + * @} + * + */ + +/** + * @defgroup LSM6DS3TR_C_ magnetometer_sensor + * @brief This section groups all the functions that manage additional + * magnetometer sensor. + * @{ + * + */ + +/** + * @brief Enable soft-iron correction algorithm for magnetometer.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of soft_en in reg CTRL9_XL + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_mag_soft_iron_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_ctrl9_xl_t ctrl9_xl; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL9_XL, (uint8_t*)&ctrl9_xl, 1); + + if(ret == 0) { + ctrl9_xl.soft_en = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL9_XL, (uint8_t*)&ctrl9_xl, 1); + } + + return ret; +} + +/** + * @brief Enable soft-iron correction algorithm for magnetometer.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of soft_en in reg CTRL9_XL + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_mag_soft_iron_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_ctrl9_xl_t ctrl9_xl; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL9_XL, (uint8_t*)&ctrl9_xl, 1); + *val = ctrl9_xl.soft_en; + + return ret; +} + +/** + * @brief Enable hard-iron correction algorithm for magnetometer.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of iron_en in reg MASTER_CONFIG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_mag_hard_iron_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_master_config_t master_config; + lsm6ds3tr_c_ctrl10_c_t ctrl10_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_MASTER_CONFIG, (uint8_t*)&master_config, 1); + + if(ret == 0) { + master_config.iron_en = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_MASTER_CONFIG, (uint8_t*)&master_config, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL10_C, (uint8_t*)&ctrl10_c, 1); + + if(ret == 0) { + if(val != 0x00U) { + ctrl10_c.func_en = val; + } + + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL10_C, (uint8_t*)&ctrl10_c, 1); + } + } + } + + return ret; +} + +/** + * @brief Enable hard-iron correction algorithm for magnetometer.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of iron_en in reg MASTER_CONFIG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_mag_hard_iron_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_master_config_t master_config; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_MASTER_CONFIG, (uint8_t*)&master_config, 1); + *val = master_config.iron_en; + + return ret; +} + +/** + * @brief Soft iron 3x3 matrix. Value are expressed in sign-module format. + * (Es. SVVVVVVVb where S is the sign 0/+1/- and V is the value).[set] + * + * @param ctx Read / write interface definitions + * @param buff Buffer that contains data to write + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_mag_soft_iron_mat_set(stmdev_ctx_t* ctx, uint8_t* buff) { + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_MAG_SI_XX, buff, 9); + + if(ret == 0) { + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + + return ret; +} + +/** + * @brief Soft iron 3x3 matrix. Value are expressed in sign-module format. + * (Es. SVVVVVVVb where S is the sign 0/+1/- and V is the value).[get] + * + * @param ctx Read / write interface definitions + * @param buff Buffer that stores data read + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_mag_soft_iron_mat_get(stmdev_ctx_t* ctx, uint8_t* buff) { + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_MAG_SI_XX, buff, 9); + + if(ret == 0) { + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + + return ret; +} + +/** + * @brief Offset for hard-iron compensation register (r/w). The value is + * expressed as a 16-bit word in two’s complement.[set] + * + * @param ctx Read / write interface definitions + * @param buff Buffer that contains data to write + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_mag_offset_set(stmdev_ctx_t* ctx, int16_t* val) { + uint8_t buff[6]; + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + buff[1] = (uint8_t)((uint16_t)val[0] / 256U); + buff[0] = (uint8_t)((uint16_t)val[0] - (buff[1] * 256U)); + buff[3] = (uint8_t)((uint16_t)val[1] / 256U); + buff[2] = (uint8_t)((uint16_t)val[1] - (buff[3] * 256U)); + buff[5] = (uint8_t)((uint16_t)val[2] / 256U); + buff[4] = (uint8_t)((uint16_t)val[2] - (buff[5] * 256U)); + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_MAG_OFFX_L, buff, 6); + + if(ret == 0) { + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + + return ret; +} + +/** + * @brief Offset for hard-iron compensation register(r/w). + * The value is expressed as a 16-bit word in two’s complement.[get] + * + * @param ctx Read / write interface definitions + * @param buff Buffer that stores data read + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_mag_offset_get(stmdev_ctx_t* ctx, int16_t* val) { + uint8_t buff[6]; + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_MAG_OFFX_L, buff, 6); + + if(ret == 0) { + val[0] = (int16_t)buff[1]; + val[0] = (val[0] * 256) + (int16_t)buff[0]; + val[1] = (int16_t)buff[3]; + val[1] = (val[1] * 256) + (int16_t)buff[2]; + val[2] = (int16_t)buff[5]; + val[2] = (val[2] * 256) + (int16_t)buff[4]; + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + + return ret; +} + +/** + * @} + * + */ + +/** + * @defgroup LSM6DS3TR_C_Sensor_hub + * @brief This section groups all the functions that manage the sensor + * hub functionality. + * @{ + * + */ + +/** + * @brief Enable function.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values func_en + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_func_en_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_ctrl10_c_t ctrl10_c; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_CTRL10_C, (uint8_t*)&ctrl10_c, 1); + + if(ret == 0) { + ctrl10_c.func_en = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_CTRL10_C, (uint8_t*)&ctrl10_c, 1); + } + + return ret; +} + +/** + * @brief Sensor synchronization time frame with the step of 500 ms and + * full range of 5s. Unsigned 8-bit.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of tph in reg SENSOR_SYNC_TIME_FRAME + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_sync_sens_frame_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_sensor_sync_time_frame_t sensor_sync_time_frame; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg( + ctx, LSM6DS3TR_C_SENSOR_SYNC_TIME_FRAME, (uint8_t*)&sensor_sync_time_frame, 1); + + if(ret == 0) { + sensor_sync_time_frame.tph = val; + ret = lsm6ds3tr_c_write_reg( + ctx, LSM6DS3TR_C_SENSOR_SYNC_TIME_FRAME, (uint8_t*)&sensor_sync_time_frame, 1); + } + + return ret; +} + +/** + * @brief Sensor synchronization time frame with the step of 500 ms and + * full range of 5s. Unsigned 8-bit.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of tph in reg SENSOR_SYNC_TIME_FRAME + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_sync_sens_frame_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_sensor_sync_time_frame_t sensor_sync_time_frame; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg( + ctx, LSM6DS3TR_C_SENSOR_SYNC_TIME_FRAME, (uint8_t*)&sensor_sync_time_frame, 1); + *val = sensor_sync_time_frame.tph; + + return ret; +} + +/** + * @brief Resolution ratio of error code for sensor synchronization.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of rr in reg SENSOR_SYNC_RES_RATIO + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_sync_sens_ratio_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_rr_t val) { + lsm6ds3tr_c_sensor_sync_res_ratio_t sensor_sync_res_ratio; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg( + ctx, LSM6DS3TR_C_SENSOR_SYNC_RES_RATIO, (uint8_t*)&sensor_sync_res_ratio, 1); + + if(ret == 0) { + sensor_sync_res_ratio.rr = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg( + ctx, LSM6DS3TR_C_SENSOR_SYNC_RES_RATIO, (uint8_t*)&sensor_sync_res_ratio, 1); + } + + return ret; +} + +/** + * @brief Resolution ratio of error code for sensor synchronization.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of rr in reg SENSOR_SYNC_RES_RATIO + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_sync_sens_ratio_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_rr_t* val) { + lsm6ds3tr_c_sensor_sync_res_ratio_t sensor_sync_res_ratio; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg( + ctx, LSM6DS3TR_C_SENSOR_SYNC_RES_RATIO, (uint8_t*)&sensor_sync_res_ratio, 1); + + switch(sensor_sync_res_ratio.rr) { + case LSM6DS3TR_C_RES_RATIO_2_11: + *val = LSM6DS3TR_C_RES_RATIO_2_11; + break; + + case LSM6DS3TR_C_RES_RATIO_2_12: + *val = LSM6DS3TR_C_RES_RATIO_2_12; + break; + + case LSM6DS3TR_C_RES_RATIO_2_13: + *val = LSM6DS3TR_C_RES_RATIO_2_13; + break; + + case LSM6DS3TR_C_RES_RATIO_2_14: + *val = LSM6DS3TR_C_RES_RATIO_2_14; + break; + + default: + *val = LSM6DS3TR_C_RES_RATIO_ND; + break; + } + + return ret; +} + +/** + * @brief Sensor hub I2C master enable.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of master_on in reg MASTER_CONFIG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_master_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_master_config_t master_config; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_MASTER_CONFIG, (uint8_t*)&master_config, 1); + + if(ret == 0) { + master_config.master_on = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_MASTER_CONFIG, (uint8_t*)&master_config, 1); + } + + return ret; +} + +/** + * @brief Sensor hub I2C master enable.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of master_on in reg MASTER_CONFIG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_master_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_master_config_t master_config; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_MASTER_CONFIG, (uint8_t*)&master_config, 1); + *val = master_config.master_on; + + return ret; +} + +/** + * @brief I2C interface pass-through.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of pass_through_mode in reg MASTER_CONFIG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_pass_through_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_master_config_t master_config; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_MASTER_CONFIG, (uint8_t*)&master_config, 1); + + if(ret == 0) { + master_config.pass_through_mode = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_MASTER_CONFIG, (uint8_t*)&master_config, 1); + } + + return ret; +} + +/** + * @brief I2C interface pass-through.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of pass_through_mode in reg MASTER_CONFIG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_pass_through_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_master_config_t master_config; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_MASTER_CONFIG, (uint8_t*)&master_config, 1); + *val = master_config.pass_through_mode; + + return ret; +} + +/** + * @brief Master I2C pull-up enable/disable.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of pull_up_en in reg MASTER_CONFIG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_pin_mode_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_pull_up_en_t val) { + lsm6ds3tr_c_master_config_t master_config; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_MASTER_CONFIG, (uint8_t*)&master_config, 1); + + if(ret == 0) { + master_config.pull_up_en = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_MASTER_CONFIG, (uint8_t*)&master_config, 1); + } + + return ret; +} + +/** + * @brief Master I2C pull-up enable/disable.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of pull_up_en in reg MASTER_CONFIG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_pin_mode_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_pull_up_en_t* val) { + lsm6ds3tr_c_master_config_t master_config; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_MASTER_CONFIG, (uint8_t*)&master_config, 1); + + switch(master_config.pull_up_en) { + case LSM6DS3TR_C_EXT_PULL_UP: + *val = LSM6DS3TR_C_EXT_PULL_UP; + break; + + case LSM6DS3TR_C_INTERNAL_PULL_UP: + *val = LSM6DS3TR_C_INTERNAL_PULL_UP; + break; + + default: + *val = LSM6DS3TR_C_SH_PIN_MODE; + break; + } + + return ret; +} + +/** + * @brief Sensor hub trigger signal selection.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of start_config in reg MASTER_CONFIG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_syncro_mode_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_start_config_t val) { + lsm6ds3tr_c_master_config_t master_config; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_MASTER_CONFIG, (uint8_t*)&master_config, 1); + + if(ret == 0) { + master_config.start_config = (uint8_t)val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_MASTER_CONFIG, (uint8_t*)&master_config, 1); + } + + return ret; +} + +/** + * @brief Sensor hub trigger signal selection.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of start_config in reg MASTER_CONFIG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_syncro_mode_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_start_config_t* val) { + lsm6ds3tr_c_master_config_t master_config; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_MASTER_CONFIG, (uint8_t*)&master_config, 1); + + switch(master_config.start_config) { + case LSM6DS3TR_C_XL_GY_DRDY: + *val = LSM6DS3TR_C_XL_GY_DRDY; + break; + + case LSM6DS3TR_C_EXT_ON_INT2_PIN: + *val = LSM6DS3TR_C_EXT_ON_INT2_PIN; + break; + + default: + *val = LSM6DS3TR_C_SH_SYNCRO_ND; + break; + } + + return ret; +} + +/** + * @brief Manage the Master DRDY signal on INT1 pad.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of drdy_on_int1 in reg MASTER_CONFIG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_drdy_on_int1_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_master_config_t master_config; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_MASTER_CONFIG, (uint8_t*)&master_config, 1); + + if(ret == 0) { + master_config.drdy_on_int1 = val; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_MASTER_CONFIG, (uint8_t*)&master_config, 1); + } + + return ret; +} + +/** + * @brief Manage the Master DRDY signal on INT1 pad.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of drdy_on_int1 in reg MASTER_CONFIG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_drdy_on_int1_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_master_config_t master_config; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_MASTER_CONFIG, (uint8_t*)&master_config, 1); + *val = master_config.drdy_on_int1; + + return ret; +} + +/** + * @brief Sensor hub output registers.[get] + * + * @param ctx Read / write interface definitions + * @param val Structure of registers from SENSORHUB1_REG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_read_data_raw_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_emb_sh_read_t* val) { + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_SENSORHUB1_REG, (uint8_t*)&(val->sh_byte_1), 12); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg( + ctx, LSM6DS3TR_C_SENSORHUB13_REG, (uint8_t*)&(val->sh_byte_13), 6); + } + + return ret; +} + +/** + * @brief Master command code used for stamping for sensor sync.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of master_cmd_code in + * reg MASTER_CMD_CODE + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_cmd_sens_sync_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_master_cmd_code_t master_cmd_code; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_MASTER_CMD_CODE, (uint8_t*)&master_cmd_code, 1); + + if(ret == 0) { + master_cmd_code.master_cmd_code = val; + ret = + lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_MASTER_CMD_CODE, (uint8_t*)&master_cmd_code, 1); + } + + return ret; +} + +/** + * @brief Master command code used for stamping for sensor sync.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of master_cmd_code in + * reg MASTER_CMD_CODE + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_cmd_sens_sync_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_master_cmd_code_t master_cmd_code; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_MASTER_CMD_CODE, (uint8_t*)&master_cmd_code, 1); + *val = master_cmd_code.master_cmd_code; + + return ret; +} + +/** + * @brief Error code used for sensor synchronization.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of error_code in + * reg SENS_SYNC_SPI_ERROR_CODE. + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_spi_sync_error_set(stmdev_ctx_t* ctx, uint8_t val) { + lsm6ds3tr_c_sens_sync_spi_error_code_t sens_sync_spi_error_code; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg( + ctx, LSM6DS3TR_C_SENS_SYNC_SPI_ERROR_CODE, (uint8_t*)&sens_sync_spi_error_code, 1); + + if(ret == 0) { + sens_sync_spi_error_code.error_code = val; + ret = lsm6ds3tr_c_write_reg( + ctx, LSM6DS3TR_C_SENS_SYNC_SPI_ERROR_CODE, (uint8_t*)&sens_sync_spi_error_code, 1); + } + + return ret; +} + +/** + * @brief Error code used for sensor synchronization.[get] + * + * @param ctx Read / write interface definitions + * @param val Change the values of error_code in + * reg SENS_SYNC_SPI_ERROR_CODE. + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_spi_sync_error_get(stmdev_ctx_t* ctx, uint8_t* val) { + lsm6ds3tr_c_sens_sync_spi_error_code_t sens_sync_spi_error_code; + int32_t ret; + + ret = lsm6ds3tr_c_read_reg( + ctx, LSM6DS3TR_C_SENS_SYNC_SPI_ERROR_CODE, (uint8_t*)&sens_sync_spi_error_code, 1); + *val = sens_sync_spi_error_code.error_code; + + return ret; +} + +/** + * @brief Number of external sensors to be read by the sensor hub.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of aux_sens_on in reg SLAVE0_CONFIG. + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_num_of_dev_connected_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_aux_sens_on_t val) { + lsm6ds3tr_c_slave0_config_t slave0_config; + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_SLAVE0_CONFIG, (uint8_t*)&slave0_config, 1); + + if(ret == 0) { + slave0_config.aux_sens_on = (uint8_t)val; + ret = + lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_SLAVE0_CONFIG, (uint8_t*)&slave0_config, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + } + + return ret; +} + +/** + * @brief Number of external sensors to be read by the sensor hub.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of aux_sens_on in reg SLAVE0_CONFIG. + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t + lsm6ds3tr_c_sh_num_of_dev_connected_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_aux_sens_on_t* val) { + lsm6ds3tr_c_slave0_config_t slave0_config; + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_SLAVE0_CONFIG, (uint8_t*)&slave0_config, 1); + + if(ret == 0) { + switch(slave0_config.aux_sens_on) { + case LSM6DS3TR_C_SLV_0: + *val = LSM6DS3TR_C_SLV_0; + break; + + case LSM6DS3TR_C_SLV_0_1: + *val = LSM6DS3TR_C_SLV_0_1; + break; + + case LSM6DS3TR_C_SLV_0_1_2: + *val = LSM6DS3TR_C_SLV_0_1_2; + break; + + case LSM6DS3TR_C_SLV_0_1_2_3: + *val = LSM6DS3TR_C_SLV_0_1_2_3; + break; + + default: + *val = LSM6DS3TR_C_SLV_EN_ND; + break; + } + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + + return ret; +} + +/** + * @brief Configure slave 0 for perform a write.[set] + * + * @param ctx Read / write interface definitions + * @param val Structure that contain: + * - uint8_t slv_add; 8 bit i2c device address + * - uint8_t slv_subadd; 8 bit register device address + * - uint8_t slv_data; 8 bit data to write + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_cfg_write(stmdev_ctx_t* ctx, lsm6ds3tr_c_sh_cfg_write_t* val) { + lsm6ds3tr_c_slv0_add_t slv0_add; + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + slv0_add.slave0_add = val->slv0_add; + slv0_add.rw_0 = 0; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_SLV0_ADD, (uint8_t*)&slv0_add, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_SLV0_SUBADD, &(val->slv0_subadd), 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_write_reg( + ctx, LSM6DS3TR_C_DATAWRITE_SRC_MODE_SUB_SLV0, &(val->slv0_data), 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + } + } + + return ret; +} + +/** + * @brief Configure slave 0 for perform a read.[get] + * + * @param ctx Read / write interface definitions + * @param val Structure that contain: + * - uint8_t slv_add; 8 bit i2c device address + * - uint8_t slv_subadd; 8 bit register device address + * - uint8_t slv_len; num of bit to read + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_slv0_cfg_read(stmdev_ctx_t* ctx, lsm6ds3tr_c_sh_cfg_read_t* val) { + lsm6ds3tr_c_slave0_config_t slave0_config; + lsm6ds3tr_c_slv0_add_t slv0_add; + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + slv0_add.slave0_add = val->slv_add; + slv0_add.rw_0 = 1; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_SLV0_ADD, (uint8_t*)&slv0_add, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_SLV0_SUBADD, &(val->slv_subadd), 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg( + ctx, LSM6DS3TR_C_SLAVE0_CONFIG, (uint8_t*)&slave0_config, 1); + slave0_config.slave0_numop = val->slv_len; + + if(ret == 0) { + ret = lsm6ds3tr_c_write_reg( + ctx, LSM6DS3TR_C_SLAVE0_CONFIG, (uint8_t*)&slave0_config, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + } + } + } + + return ret; +} + +/** + * @brief Configure slave 1 for perform a read.[get] + * + * @param ctx Read / write interface definitions + * @param val Structure that contain: + * - uint8_t slv_add; 8 bit i2c device address + * - uint8_t slv_subadd; 8 bit register device address + * - uint8_t slv_len; num of bit to read + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_slv1_cfg_read(stmdev_ctx_t* ctx, lsm6ds3tr_c_sh_cfg_read_t* val) { + lsm6ds3tr_c_slave1_config_t slave1_config; + lsm6ds3tr_c_slv1_add_t slv1_add; + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + slv1_add.slave1_add = val->slv_add; + slv1_add.r_1 = 1; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_SLV1_ADD, (uint8_t*)&slv1_add, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_SLV1_SUBADD, &(val->slv_subadd), 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg( + ctx, LSM6DS3TR_C_SLAVE1_CONFIG, (uint8_t*)&slave1_config, 1); + slave1_config.slave1_numop = val->slv_len; + + if(ret == 0) { + ret = lsm6ds3tr_c_write_reg( + ctx, LSM6DS3TR_C_SLAVE1_CONFIG, (uint8_t*)&slave1_config, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + } + } + } + + return ret; +} + +/** + * @brief Configure slave 2 for perform a read.[get] + * + * @param ctx Read / write interface definitions + * @param val Structure that contain: + * - uint8_t slv_add; 8 bit i2c device address + * - uint8_t slv_subadd; 8 bit register device address + * - uint8_t slv_len; num of bit to read + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_slv2_cfg_read(stmdev_ctx_t* ctx, lsm6ds3tr_c_sh_cfg_read_t* val) { + lsm6ds3tr_c_slv2_add_t slv2_add; + lsm6ds3tr_c_slave2_config_t slave2_config; + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + slv2_add.slave2_add = val->slv_add; + slv2_add.r_2 = 1; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_SLV2_ADD, (uint8_t*)&slv2_add, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_SLV2_SUBADD, &(val->slv_subadd), 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg( + ctx, LSM6DS3TR_C_SLAVE2_CONFIG, (uint8_t*)&slave2_config, 1); + + if(ret == 0) { + slave2_config.slave2_numop = val->slv_len; + ret = lsm6ds3tr_c_write_reg( + ctx, LSM6DS3TR_C_SLAVE2_CONFIG, (uint8_t*)&slave2_config, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + } + } + } + + return ret; +} + +/** + * @brief Configure slave 3 for perform a read.[get] + * + * @param ctx Read / write interface definitions + * @param val Structure that contain: + * - uint8_t slv_add; 8 bit i2c device address + * - uint8_t slv_subadd; 8 bit register device address + * - uint8_t slv_len; num of bit to read + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_slv3_cfg_read(stmdev_ctx_t* ctx, lsm6ds3tr_c_sh_cfg_read_t* val) { + lsm6ds3tr_c_slave3_config_t slave3_config; + lsm6ds3tr_c_slv3_add_t slv3_add; + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + slv3_add.slave3_add = val->slv_add; + slv3_add.r_3 = 1; + ret = lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_SLV3_ADD, (uint8_t*)&slv3_add, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_write_reg( + ctx, LSM6DS3TR_C_SLV3_SUBADD, (uint8_t*)&(val->slv_subadd), 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg( + ctx, LSM6DS3TR_C_SLAVE3_CONFIG, (uint8_t*)&slave3_config, 1); + + if(ret == 0) { + slave3_config.slave3_numop = val->slv_len; + ret = lsm6ds3tr_c_write_reg( + ctx, LSM6DS3TR_C_SLAVE3_CONFIG, (uint8_t*)&slave3_config, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + } + } + } + + return ret; +} + +/** + * @brief Decimation of read operation on Slave 0 starting from the + * sensor hub trigger.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of slave0_rate in reg SLAVE0_CONFIG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_slave_0_dec_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_slave0_rate_t val) { + lsm6ds3tr_c_slave0_config_t slave0_config; + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_SLAVE0_CONFIG, (uint8_t*)&slave0_config, 1); + + if(ret == 0) { + slave0_config.slave0_rate = (uint8_t)val; + ret = + lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_SLAVE0_CONFIG, (uint8_t*)&slave0_config, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + } + + return ret; +} + +/** + * @brief Decimation of read operation on Slave 0 starting from the + * sensor hub trigger.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of slave0_rate in reg SLAVE0_CONFIG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_slave_0_dec_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_slave0_rate_t* val) { + lsm6ds3tr_c_slave0_config_t slave0_config; + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_SLAVE0_CONFIG, (uint8_t*)&slave0_config, 1); + + if(ret == 0) { + switch(slave0_config.slave0_rate) { + case LSM6DS3TR_C_SL0_NO_DEC: + *val = LSM6DS3TR_C_SL0_NO_DEC; + break; + + case LSM6DS3TR_C_SL0_DEC_2: + *val = LSM6DS3TR_C_SL0_DEC_2; + break; + + case LSM6DS3TR_C_SL0_DEC_4: + *val = LSM6DS3TR_C_SL0_DEC_4; + break; + + case LSM6DS3TR_C_SL0_DEC_8: + *val = LSM6DS3TR_C_SL0_DEC_8; + break; + + default: + *val = LSM6DS3TR_C_SL0_DEC_ND; + break; + } + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + + return ret; +} + +/** + * @brief Slave 0 write operation is performed only at the first sensor + * hub cycle. + * This is effective if the Aux_sens_on[1:0] field in + * SLAVE0_CONFIG(04h) is set to a value other than 00.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of write_once in reg SLAVE1_CONFIG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_write_mode_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_write_once_t val) { + lsm6ds3tr_c_slave1_config_t slave1_config; + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_SLAVE1_CONFIG, (uint8_t*)&slave1_config, 1); + slave1_config.write_once = (uint8_t)val; + + if(ret == 0) { + ret = + lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_SLAVE1_CONFIG, (uint8_t*)&slave1_config, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + } + + return ret; +} + +/** + * @brief Slave 0 write operation is performed only at the first sensor + * hub cycle. + * This is effective if the Aux_sens_on[1:0] field in + * SLAVE0_CONFIG(04h) is set to a value other than 00.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of write_once in reg SLAVE1_CONFIG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_write_mode_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_write_once_t* val) { + lsm6ds3tr_c_slave1_config_t slave1_config; + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_SLAVE1_CONFIG, (uint8_t*)&slave1_config, 1); + + if(ret == 0) { + switch(slave1_config.write_once) { + case LSM6DS3TR_C_EACH_SH_CYCLE: + *val = LSM6DS3TR_C_EACH_SH_CYCLE; + break; + + case LSM6DS3TR_C_ONLY_FIRST_CYCLE: + *val = LSM6DS3TR_C_ONLY_FIRST_CYCLE; + break; + + default: + *val = LSM6DS3TR_C_SH_WR_MODE_ND; + break; + } + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + + return ret; +} + +/** + * @brief Decimation of read operation on Slave 1 starting from the + * sensor hub trigger.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of slave1_rate in reg SLAVE1_CONFIG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_slave_1_dec_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_slave1_rate_t val) { + lsm6ds3tr_c_slave1_config_t slave1_config; + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_SLAVE1_CONFIG, (uint8_t*)&slave1_config, 1); + + if(ret == 0) { + slave1_config.slave1_rate = (uint8_t)val; + ret = + lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_SLAVE1_CONFIG, (uint8_t*)&slave1_config, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + } + + return ret; +} + +/** + * @brief Decimation of read operation on Slave 1 starting from the + * sensor hub trigger.[get] + * + * @param ctx Read / write interface definitions reg SLAVE1_CONFIG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_slave_1_dec_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_slave1_rate_t* val) { + lsm6ds3tr_c_slave1_config_t slave1_config; + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_SLAVE1_CONFIG, (uint8_t*)&slave1_config, 1); + + if(ret == 0) { + switch(slave1_config.slave1_rate) { + case LSM6DS3TR_C_SL1_NO_DEC: + *val = LSM6DS3TR_C_SL1_NO_DEC; + break; + + case LSM6DS3TR_C_SL1_DEC_2: + *val = LSM6DS3TR_C_SL1_DEC_2; + break; + + case LSM6DS3TR_C_SL1_DEC_4: + *val = LSM6DS3TR_C_SL1_DEC_4; + break; + + case LSM6DS3TR_C_SL1_DEC_8: + *val = LSM6DS3TR_C_SL1_DEC_8; + break; + + default: + *val = LSM6DS3TR_C_SL1_DEC_ND; + break; + } + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + + return ret; +} + +/** + * @brief Decimation of read operation on Slave 2 starting from the + * sensor hub trigger.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of slave2_rate in reg SLAVE2_CONFIG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_slave_2_dec_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_slave2_rate_t val) { + lsm6ds3tr_c_slave2_config_t slave2_config; + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_SLAVE2_CONFIG, (uint8_t*)&slave2_config, 1); + + if(ret == 0) { + slave2_config.slave2_rate = (uint8_t)val; + ret = + lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_SLAVE2_CONFIG, (uint8_t*)&slave2_config, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + } + + return ret; +} + +/** + * @brief Decimation of read operation on Slave 2 starting from the + * sensor hub trigger.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of slave2_rate in reg SLAVE2_CONFIG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_slave_2_dec_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_slave2_rate_t* val) { + lsm6ds3tr_c_slave2_config_t slave2_config; + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_SLAVE2_CONFIG, (uint8_t*)&slave2_config, 1); + + if(ret == 0) { + switch(slave2_config.slave2_rate) { + case LSM6DS3TR_C_SL2_NO_DEC: + *val = LSM6DS3TR_C_SL2_NO_DEC; + break; + + case LSM6DS3TR_C_SL2_DEC_2: + *val = LSM6DS3TR_C_SL2_DEC_2; + break; + + case LSM6DS3TR_C_SL2_DEC_4: + *val = LSM6DS3TR_C_SL2_DEC_4; + break; + + case LSM6DS3TR_C_SL2_DEC_8: + *val = LSM6DS3TR_C_SL2_DEC_8; + break; + + default: + *val = LSM6DS3TR_C_SL2_DEC_ND; + break; + } + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + + return ret; +} + +/** + * @brief Decimation of read operation on Slave 3 starting from the + * sensor hub trigger.[set] + * + * @param ctx Read / write interface definitions + * @param val Change the values of slave3_rate in reg SLAVE3_CONFIG + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_slave_3_dec_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_slave3_rate_t val) { + lsm6ds3tr_c_slave3_config_t slave3_config; + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_SLAVE3_CONFIG, (uint8_t*)&slave3_config, 1); + slave3_config.slave3_rate = (uint8_t)val; + + if(ret == 0) { + ret = + lsm6ds3tr_c_write_reg(ctx, LSM6DS3TR_C_SLAVE3_CONFIG, (uint8_t*)&slave3_config, 1); + + if(ret == 0) { + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + } + + return ret; +} + +/** + * @brief Decimation of read operation on Slave 3 starting from the + * sensor hub trigger.[get] + * + * @param ctx Read / write interface definitions + * @param val Get the values of slave3_rate in reg SLAVE3_CONFIG. + * @retval Interface status (MANDATORY: return 0 -> no Error). + * + */ +int32_t lsm6ds3tr_c_sh_slave_3_dec_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_slave3_rate_t* val) { + lsm6ds3tr_c_slave3_config_t slave3_config; + int32_t ret; + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_BANK_A); + + if(ret == 0) { + ret = lsm6ds3tr_c_read_reg(ctx, LSM6DS3TR_C_SLAVE3_CONFIG, (uint8_t*)&slave3_config, 1); + + if(ret == 0) { + switch(slave3_config.slave3_rate) { + case LSM6DS3TR_C_SL3_NO_DEC: + *val = LSM6DS3TR_C_SL3_NO_DEC; + break; + + case LSM6DS3TR_C_SL3_DEC_2: + *val = LSM6DS3TR_C_SL3_DEC_2; + break; + + case LSM6DS3TR_C_SL3_DEC_4: + *val = LSM6DS3TR_C_SL3_DEC_4; + break; + + case LSM6DS3TR_C_SL3_DEC_8: + *val = LSM6DS3TR_C_SL3_DEC_8; + break; + + default: + *val = LSM6DS3TR_C_SL3_DEC_ND; + break; + } + + ret = lsm6ds3tr_c_mem_bank_set(ctx, LSM6DS3TR_C_USER_BANK); + } + } + + return ret; +} + +/** + * @} + * + */ + +/** + * @} + * + */ + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/imu/lsm6ds3tr_c_reg.h b/Applications/Official/DEV_FW/source/airmouse/tracking/imu/lsm6ds3tr_c_reg.h new file mode 100644 index 000000000..8cb592c0d --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/imu/lsm6ds3tr_c_reg.h @@ -0,0 +1,2448 @@ +/** + ****************************************************************************** + * @file lsm6ds3tr_c_reg.h + * @author Sensors Software Solution Team + * @brief This file contains all the functions prototypes for the + * lsm6ds3tr_c_reg.c driver. + ****************************************************************************** + * @attention + * + *

© Copyright (c) 2021 STMicroelectronics. + * All rights reserved.

+ * + * This software component is licensed by ST under BSD 3-Clause license, + * the "License"; You may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * opensource.org/licenses/BSD-3-Clause + * + ****************************************************************************** + */ + +/* Define to prevent recursive inclusion -------------------------------------*/ +#ifndef LSM6DS3TR_C_DRIVER_H +#define LSM6DS3TR_C_DRIVER_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Includes ------------------------------------------------------------------*/ +#include +#include +#include + +/** @addtogroup LSM6DS3TR_C + * @{ + * + */ + +/** @defgroup Endianness definitions + * @{ + * + */ + +#ifndef DRV_BYTE_ORDER +#ifndef __BYTE_ORDER__ + +#define DRV_LITTLE_ENDIAN 1234 +#define DRV_BIG_ENDIAN 4321 + +/** if _BYTE_ORDER is not defined, choose the endianness of your architecture + * by uncommenting the define which fits your platform endianness + */ +//#define DRV_BYTE_ORDER DRV_BIG_ENDIAN +#define DRV_BYTE_ORDER DRV_LITTLE_ENDIAN + +#else /* defined __BYTE_ORDER__ */ + +#define DRV_LITTLE_ENDIAN __ORDER_LITTLE_ENDIAN__ +#define DRV_BIG_ENDIAN __ORDER_BIG_ENDIAN__ +#define DRV_BYTE_ORDER __BYTE_ORDER__ + +#endif /* __BYTE_ORDER__*/ +#endif /* DRV_BYTE_ORDER */ + +/** + * @} + * + */ + +/** @defgroup STMicroelectronics sensors common types + * @{ + * + */ + +#ifndef MEMS_SHARED_TYPES +#define MEMS_SHARED_TYPES + +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t bit0 : 1; + uint8_t bit1 : 1; + uint8_t bit2 : 1; + uint8_t bit3 : 1; + uint8_t bit4 : 1; + uint8_t bit5 : 1; + uint8_t bit6 : 1; + uint8_t bit7 : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t bit7 : 1; + uint8_t bit6 : 1; + uint8_t bit5 : 1; + uint8_t bit4 : 1; + uint8_t bit3 : 1; + uint8_t bit2 : 1; + uint8_t bit1 : 1; + uint8_t bit0 : 1; +#endif /* DRV_BYTE_ORDER */ +} bitwise_t; + +#define PROPERTY_DISABLE (0U) +#define PROPERTY_ENABLE (1U) + +/** @addtogroup Interfaces_Functions + * @brief This section provide a set of functions used to read and + * write a generic register of the device. + * MANDATORY: return 0 -> no Error. + * @{ + * + */ + +typedef int32_t (*stmdev_write_ptr)(void*, uint8_t, const uint8_t*, uint16_t); +typedef int32_t (*stmdev_read_ptr)(void*, uint8_t, uint8_t*, uint16_t); +typedef void (*stmdev_mdelay_ptr)(uint32_t millisec); + +typedef struct { + /** Component mandatory fields **/ + stmdev_write_ptr write_reg; + stmdev_read_ptr read_reg; + /** Component optional fields **/ + stmdev_mdelay_ptr mdelay; + /** Customizable optional pointer **/ + void* handle; +} stmdev_ctx_t; + +/** + * @} + * + */ + +#endif /* MEMS_SHARED_TYPES */ + +#ifndef MEMS_UCF_SHARED_TYPES +#define MEMS_UCF_SHARED_TYPES + +/** @defgroup Generic address-data structure definition + * @brief This structure is useful to load a predefined configuration + * of a sensor. + * You can create a sensor configuration by your own or using + * Unico / Unicleo tools available on STMicroelectronics + * web site. + * + * @{ + * + */ + +typedef struct { + uint8_t address; + uint8_t data; +} ucf_line_t; + +/** + * @} + * + */ + +#endif /* MEMS_UCF_SHARED_TYPES */ + +/** + * @} + * + */ + +/** @defgroup LSM6DS3TR_C_Infos + * @{ + * + */ + +/** I2C Device Address 8 bit format if SA0=0 -> D5 if SA0=1 -> D7 **/ +#define LSM6DS3TR_C_I2C_ADD_L 0xD5U +#define LSM6DS3TR_C_I2C_ADD_H 0xD7U + +/** Device Identification (Who am I) **/ +#define LSM6DS3TR_C_ID 0x6AU + +/** + * @} + * + */ + +#define LSM6DS3TR_C_FUNC_CFG_ACCESS 0x01U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t not_used_01 : 5; + uint8_t func_cfg_en : 3; /* func_cfg_en + func_cfg_en_b */ +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t func_cfg_en : 3; /* func_cfg_en + func_cfg_en_b */ + uint8_t not_used_01 : 5; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_func_cfg_access_t; + +#define LSM6DS3TR_C_SENSOR_SYNC_TIME_FRAME 0x04U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t tph : 4; + uint8_t not_used_01 : 4; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t not_used_01 : 4; + uint8_t tph : 4; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_sensor_sync_time_frame_t; + +#define LSM6DS3TR_C_SENSOR_SYNC_RES_RATIO 0x05U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t rr : 2; + uint8_t not_used_01 : 6; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t not_used_01 : 6; + uint8_t rr : 2; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_sensor_sync_res_ratio_t; + +#define LSM6DS3TR_C_FIFO_CTRL1 0x06U +typedef struct { + uint8_t fth : 8; /* + FIFO_CTRL2(fth) */ +} lsm6ds3tr_c_fifo_ctrl1_t; + +#define LSM6DS3TR_C_FIFO_CTRL2 0x07U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t fth : 3; /* + FIFO_CTRL1(fth) */ + uint8_t fifo_temp_en : 1; + uint8_t not_used_01 : 2; + uint8_t timer_pedo_fifo_drdy : 1; + uint8_t timer_pedo_fifo_en : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t timer_pedo_fifo_en : 1; + uint8_t timer_pedo_fifo_drdy : 1; + uint8_t not_used_01 : 2; + uint8_t fifo_temp_en : 1; + uint8_t fth : 3; /* + FIFO_CTRL1(fth) */ +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_fifo_ctrl2_t; + +#define LSM6DS3TR_C_FIFO_CTRL3 0x08U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t dec_fifo_xl : 3; + uint8_t dec_fifo_gyro : 3; + uint8_t not_used_01 : 2; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t not_used_01 : 2; + uint8_t dec_fifo_gyro : 3; + uint8_t dec_fifo_xl : 3; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_fifo_ctrl3_t; + +#define LSM6DS3TR_C_FIFO_CTRL4 0x09U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t dec_ds3_fifo : 3; + uint8_t dec_ds4_fifo : 3; + uint8_t only_high_data : 1; + uint8_t stop_on_fth : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t stop_on_fth : 1; + uint8_t only_high_data : 1; + uint8_t dec_ds4_fifo : 3; + uint8_t dec_ds3_fifo : 3; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_fifo_ctrl4_t; + +#define LSM6DS3TR_C_FIFO_CTRL5 0x0AU +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t fifo_mode : 3; + uint8_t odr_fifo : 4; + uint8_t not_used_01 : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t not_used_01 : 1; + uint8_t odr_fifo : 4; + uint8_t fifo_mode : 3; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_fifo_ctrl5_t; + +#define LSM6DS3TR_C_DRDY_PULSE_CFG_G 0x0BU +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t int2_wrist_tilt : 1; + uint8_t not_used_01 : 6; + uint8_t drdy_pulsed : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t drdy_pulsed : 1; + uint8_t not_used_01 : 6; + uint8_t int2_wrist_tilt : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_drdy_pulse_cfg_g_t; + +#define LSM6DS3TR_C_INT1_CTRL 0x0DU +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t int1_drdy_xl : 1; + uint8_t int1_drdy_g : 1; + uint8_t int1_boot : 1; + uint8_t int1_fth : 1; + uint8_t int1_fifo_ovr : 1; + uint8_t int1_full_flag : 1; + uint8_t int1_sign_mot : 1; + uint8_t int1_step_detector : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t int1_step_detector : 1; + uint8_t int1_sign_mot : 1; + uint8_t int1_full_flag : 1; + uint8_t int1_fifo_ovr : 1; + uint8_t int1_fth : 1; + uint8_t int1_boot : 1; + uint8_t int1_drdy_g : 1; + uint8_t int1_drdy_xl : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_int1_ctrl_t; + +#define LSM6DS3TR_C_INT2_CTRL 0x0EU +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t int2_drdy_xl : 1; + uint8_t int2_drdy_g : 1; + uint8_t int2_drdy_temp : 1; + uint8_t int2_fth : 1; + uint8_t int2_fifo_ovr : 1; + uint8_t int2_full_flag : 1; + uint8_t int2_step_count_ov : 1; + uint8_t int2_step_delta : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t int2_step_delta : 1; + uint8_t int2_step_count_ov : 1; + uint8_t int2_full_flag : 1; + uint8_t int2_fifo_ovr : 1; + uint8_t int2_fth : 1; + uint8_t int2_drdy_temp : 1; + uint8_t int2_drdy_g : 1; + uint8_t int2_drdy_xl : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_int2_ctrl_t; + +#define LSM6DS3TR_C_WHO_AM_I 0x0FU +#define LSM6DS3TR_C_CTRL1_XL 0x10U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t bw0_xl : 1; + uint8_t lpf1_bw_sel : 1; + uint8_t fs_xl : 2; + uint8_t odr_xl : 4; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t odr_xl : 4; + uint8_t fs_xl : 2; + uint8_t lpf1_bw_sel : 1; + uint8_t bw0_xl : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_ctrl1_xl_t; + +#define LSM6DS3TR_C_CTRL2_G 0x11U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t not_used_01 : 1; + uint8_t fs_g : 3; /* fs_g + fs_125 */ + uint8_t odr_g : 4; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t odr_g : 4; + uint8_t fs_g : 3; /* fs_g + fs_125 */ + uint8_t not_used_01 : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_ctrl2_g_t; + +#define LSM6DS3TR_C_CTRL3_C 0x12U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t sw_reset : 1; + uint8_t ble : 1; + uint8_t if_inc : 1; + uint8_t sim : 1; + uint8_t pp_od : 1; + uint8_t h_lactive : 1; + uint8_t bdu : 1; + uint8_t boot : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t boot : 1; + uint8_t bdu : 1; + uint8_t h_lactive : 1; + uint8_t pp_od : 1; + uint8_t sim : 1; + uint8_t if_inc : 1; + uint8_t ble : 1; + uint8_t sw_reset : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_ctrl3_c_t; + +#define LSM6DS3TR_C_CTRL4_C 0x13U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t not_used_01 : 1; + uint8_t lpf1_sel_g : 1; + uint8_t i2c_disable : 1; + uint8_t drdy_mask : 1; + uint8_t den_drdy_int1 : 1; + uint8_t int2_on_int1 : 1; + uint8_t sleep : 1; + uint8_t den_xl_en : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t den_xl_en : 1; + uint8_t sleep : 1; + uint8_t int2_on_int1 : 1; + uint8_t den_drdy_int1 : 1; + uint8_t drdy_mask : 1; + uint8_t i2c_disable : 1; + uint8_t lpf1_sel_g : 1; + uint8_t not_used_01 : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_ctrl4_c_t; + +#define LSM6DS3TR_C_CTRL5_C 0x14U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t st_xl : 2; + uint8_t st_g : 2; + uint8_t den_lh : 1; + uint8_t rounding : 3; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t rounding : 3; + uint8_t den_lh : 1; + uint8_t st_g : 2; + uint8_t st_xl : 2; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_ctrl5_c_t; + +#define LSM6DS3TR_C_CTRL6_C 0x15U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t ftype : 2; + uint8_t not_used_01 : 1; + uint8_t usr_off_w : 1; + uint8_t xl_hm_mode : 1; + uint8_t den_mode : 3; /* trig_en + lvl_en + lvl2_en */ +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t den_mode : 3; /* trig_en + lvl_en + lvl2_en */ + uint8_t xl_hm_mode : 1; + uint8_t usr_off_w : 1; + uint8_t not_used_01 : 1; + uint8_t ftype : 2; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_ctrl6_c_t; + +#define LSM6DS3TR_C_CTRL7_G 0x16U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t not_used_01 : 2; + uint8_t rounding_status : 1; + uint8_t not_used_02 : 1; + uint8_t hpm_g : 2; + uint8_t hp_en_g : 1; + uint8_t g_hm_mode : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t g_hm_mode : 1; + uint8_t hp_en_g : 1; + uint8_t hpm_g : 2; + uint8_t not_used_02 : 1; + uint8_t rounding_status : 1; + uint8_t not_used_01 : 2; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_ctrl7_g_t; + +#define LSM6DS3TR_C_CTRL8_XL 0x17U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t low_pass_on_6d : 1; + uint8_t not_used_01 : 1; + uint8_t hp_slope_xl_en : 1; + uint8_t input_composite : 1; + uint8_t hp_ref_mode : 1; + uint8_t hpcf_xl : 2; + uint8_t lpf2_xl_en : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t lpf2_xl_en : 1; + uint8_t hpcf_xl : 2; + uint8_t hp_ref_mode : 1; + uint8_t input_composite : 1; + uint8_t hp_slope_xl_en : 1; + uint8_t not_used_01 : 1; + uint8_t low_pass_on_6d : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_ctrl8_xl_t; + +#define LSM6DS3TR_C_CTRL9_XL 0x18U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t not_used_01 : 2; + uint8_t soft_en : 1; + uint8_t not_used_02 : 1; + uint8_t den_xl_g : 1; + uint8_t den_z : 1; + uint8_t den_y : 1; + uint8_t den_x : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t den_x : 1; + uint8_t den_y : 1; + uint8_t den_z : 1; + uint8_t den_xl_g : 1; + uint8_t not_used_02 : 1; + uint8_t soft_en : 1; + uint8_t not_used_01 : 2; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_ctrl9_xl_t; + +#define LSM6DS3TR_C_CTRL10_C 0x19U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t sign_motion_en : 1; + uint8_t pedo_rst_step : 1; + uint8_t func_en : 1; + uint8_t tilt_en : 1; + uint8_t pedo_en : 1; + uint8_t timer_en : 1; + uint8_t not_used_01 : 1; + uint8_t wrist_tilt_en : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t wrist_tilt_en : 1; + uint8_t not_used_01 : 1; + uint8_t timer_en : 1; + uint8_t pedo_en : 1; + uint8_t tilt_en : 1; + uint8_t func_en : 1; + uint8_t pedo_rst_step : 1; + uint8_t sign_motion_en : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_ctrl10_c_t; + +#define LSM6DS3TR_C_MASTER_CONFIG 0x1AU +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t master_on : 1; + uint8_t iron_en : 1; + uint8_t pass_through_mode : 1; + uint8_t pull_up_en : 1; + uint8_t start_config : 1; + uint8_t not_used_01 : 1; + uint8_t data_valid_sel_fifo : 1; + uint8_t drdy_on_int1 : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t drdy_on_int1 : 1; + uint8_t data_valid_sel_fifo : 1; + uint8_t not_used_01 : 1; + uint8_t start_config : 1; + uint8_t pull_up_en : 1; + uint8_t pass_through_mode : 1; + uint8_t iron_en : 1; + uint8_t master_on : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_master_config_t; + +#define LSM6DS3TR_C_WAKE_UP_SRC 0x1BU +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t z_wu : 1; + uint8_t y_wu : 1; + uint8_t x_wu : 1; + uint8_t wu_ia : 1; + uint8_t sleep_state_ia : 1; + uint8_t ff_ia : 1; + uint8_t not_used_01 : 2; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t not_used_01 : 2; + uint8_t ff_ia : 1; + uint8_t sleep_state_ia : 1; + uint8_t wu_ia : 1; + uint8_t x_wu : 1; + uint8_t y_wu : 1; + uint8_t z_wu : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_wake_up_src_t; + +#define LSM6DS3TR_C_TAP_SRC 0x1CU +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t z_tap : 1; + uint8_t y_tap : 1; + uint8_t x_tap : 1; + uint8_t tap_sign : 1; + uint8_t double_tap : 1; + uint8_t single_tap : 1; + uint8_t tap_ia : 1; + uint8_t not_used_01 : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t not_used_01 : 1; + uint8_t tap_ia : 1; + uint8_t single_tap : 1; + uint8_t double_tap : 1; + uint8_t tap_sign : 1; + uint8_t x_tap : 1; + uint8_t y_tap : 1; + uint8_t z_tap : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_tap_src_t; + +#define LSM6DS3TR_C_D6D_SRC 0x1DU +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t xl : 1; + uint8_t xh : 1; + uint8_t yl : 1; + uint8_t yh : 1; + uint8_t zl : 1; + uint8_t zh : 1; + uint8_t d6d_ia : 1; + uint8_t den_drdy : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t den_drdy : 1; + uint8_t d6d_ia : 1; + uint8_t zh : 1; + uint8_t zl : 1; + uint8_t yh : 1; + uint8_t yl : 1; + uint8_t xh : 1; + uint8_t xl : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_d6d_src_t; + +#define LSM6DS3TR_C_STATUS_REG 0x1EU +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t xlda : 1; + uint8_t gda : 1; + uint8_t tda : 1; + uint8_t not_used_01 : 5; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t not_used_01 : 5; + uint8_t tda : 1; + uint8_t gda : 1; + uint8_t xlda : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_status_reg_t; + +#define LSM6DS3TR_C_OUT_TEMP_L 0x20U +#define LSM6DS3TR_C_OUT_TEMP_H 0x21U +#define LSM6DS3TR_C_OUTX_L_G 0x22U +#define LSM6DS3TR_C_OUTX_H_G 0x23U +#define LSM6DS3TR_C_OUTY_L_G 0x24U +#define LSM6DS3TR_C_OUTY_H_G 0x25U +#define LSM6DS3TR_C_OUTZ_L_G 0x26U +#define LSM6DS3TR_C_OUTZ_H_G 0x27U +#define LSM6DS3TR_C_OUTX_L_XL 0x28U +#define LSM6DS3TR_C_OUTX_H_XL 0x29U +#define LSM6DS3TR_C_OUTY_L_XL 0x2AU +#define LSM6DS3TR_C_OUTY_H_XL 0x2BU +#define LSM6DS3TR_C_OUTZ_L_XL 0x2CU +#define LSM6DS3TR_C_OUTZ_H_XL 0x2DU +#define LSM6DS3TR_C_SENSORHUB1_REG 0x2EU +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t bit0 : 1; + uint8_t bit1 : 1; + uint8_t bit2 : 1; + uint8_t bit3 : 1; + uint8_t bit4 : 1; + uint8_t bit5 : 1; + uint8_t bit6 : 1; + uint8_t bit7 : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t bit7 : 1; + uint8_t bit6 : 1; + uint8_t bit5 : 1; + uint8_t bit4 : 1; + uint8_t bit3 : 1; + uint8_t bit2 : 1; + uint8_t bit1 : 1; + uint8_t bit0 : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_sensorhub1_reg_t; + +#define LSM6DS3TR_C_SENSORHUB2_REG 0x2FU +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t bit0 : 1; + uint8_t bit1 : 1; + uint8_t bit2 : 1; + uint8_t bit3 : 1; + uint8_t bit4 : 1; + uint8_t bit5 : 1; + uint8_t bit6 : 1; + uint8_t bit7 : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t bit7 : 1; + uint8_t bit6 : 1; + uint8_t bit5 : 1; + uint8_t bit4 : 1; + uint8_t bit3 : 1; + uint8_t bit2 : 1; + uint8_t bit1 : 1; + uint8_t bit0 : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_sensorhub2_reg_t; + +#define LSM6DS3TR_C_SENSORHUB3_REG 0x30U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t bit0 : 1; + uint8_t bit1 : 1; + uint8_t bit2 : 1; + uint8_t bit3 : 1; + uint8_t bit4 : 1; + uint8_t bit5 : 1; + uint8_t bit6 : 1; + uint8_t bit7 : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t bit7 : 1; + uint8_t bit6 : 1; + uint8_t bit5 : 1; + uint8_t bit4 : 1; + uint8_t bit3 : 1; + uint8_t bit2 : 1; + uint8_t bit1 : 1; + uint8_t bit0 : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_sensorhub3_reg_t; + +#define LSM6DS3TR_C_SENSORHUB4_REG 0x31U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t bit0 : 1; + uint8_t bit1 : 1; + uint8_t bit2 : 1; + uint8_t bit3 : 1; + uint8_t bit4 : 1; + uint8_t bit5 : 1; + uint8_t bit6 : 1; + uint8_t bit7 : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t bit7 : 1; + uint8_t bit6 : 1; + uint8_t bit5 : 1; + uint8_t bit4 : 1; + uint8_t bit3 : 1; + uint8_t bit2 : 1; + uint8_t bit1 : 1; + uint8_t bit0 : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_sensorhub4_reg_t; + +#define LSM6DS3TR_C_SENSORHUB5_REG 0x32U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t bit0 : 1; + uint8_t bit1 : 1; + uint8_t bit2 : 1; + uint8_t bit3 : 1; + uint8_t bit4 : 1; + uint8_t bit5 : 1; + uint8_t bit6 : 1; + uint8_t bit7 : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t bit7 : 1; + uint8_t bit6 : 1; + uint8_t bit5 : 1; + uint8_t bit4 : 1; + uint8_t bit3 : 1; + uint8_t bit2 : 1; + uint8_t bit1 : 1; + uint8_t bit0 : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_sensorhub5_reg_t; + +#define LSM6DS3TR_C_SENSORHUB6_REG 0x33U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t bit0 : 1; + uint8_t bit1 : 1; + uint8_t bit2 : 1; + uint8_t bit3 : 1; + uint8_t bit4 : 1; + uint8_t bit5 : 1; + uint8_t bit6 : 1; + uint8_t bit7 : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t bit7 : 1; + uint8_t bit6 : 1; + uint8_t bit5 : 1; + uint8_t bit4 : 1; + uint8_t bit3 : 1; + uint8_t bit2 : 1; + uint8_t bit1 : 1; + uint8_t bit0 : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_sensorhub6_reg_t; + +#define LSM6DS3TR_C_SENSORHUB7_REG 0x34U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t bit0 : 1; + uint8_t bit1 : 1; + uint8_t bit2 : 1; + uint8_t bit3 : 1; + uint8_t bit4 : 1; + uint8_t bit5 : 1; + uint8_t bit6 : 1; + uint8_t bit7 : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t bit7 : 1; + uint8_t bit6 : 1; + uint8_t bit5 : 1; + uint8_t bit4 : 1; + uint8_t bit3 : 1; + uint8_t bit2 : 1; + uint8_t bit1 : 1; + uint8_t bit0 : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_sensorhub7_reg_t; + +#define LSM6DS3TR_C_SENSORHUB8_REG 0x35U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t bit0 : 1; + uint8_t bit1 : 1; + uint8_t bit2 : 1; + uint8_t bit3 : 1; + uint8_t bit4 : 1; + uint8_t bit5 : 1; + uint8_t bit6 : 1; + uint8_t bit7 : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t bit7 : 1; + uint8_t bit6 : 1; + uint8_t bit5 : 1; + uint8_t bit4 : 1; + uint8_t bit3 : 1; + uint8_t bit2 : 1; + uint8_t bit1 : 1; + uint8_t bit0 : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_sensorhub8_reg_t; + +#define LSM6DS3TR_C_SENSORHUB9_REG 0x36U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t bit0 : 1; + uint8_t bit1 : 1; + uint8_t bit2 : 1; + uint8_t bit3 : 1; + uint8_t bit4 : 1; + uint8_t bit5 : 1; + uint8_t bit6 : 1; + uint8_t bit7 : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t bit7 : 1; + uint8_t bit6 : 1; + uint8_t bit5 : 1; + uint8_t bit4 : 1; + uint8_t bit3 : 1; + uint8_t bit2 : 1; + uint8_t bit1 : 1; + uint8_t bit0 : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_sensorhub9_reg_t; + +#define LSM6DS3TR_C_SENSORHUB10_REG 0x37U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t bit0 : 1; + uint8_t bit1 : 1; + uint8_t bit2 : 1; + uint8_t bit3 : 1; + uint8_t bit4 : 1; + uint8_t bit5 : 1; + uint8_t bit6 : 1; + uint8_t bit7 : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t bit7 : 1; + uint8_t bit6 : 1; + uint8_t bit5 : 1; + uint8_t bit4 : 1; + uint8_t bit3 : 1; + uint8_t bit2 : 1; + uint8_t bit1 : 1; + uint8_t bit0 : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_sensorhub10_reg_t; + +#define LSM6DS3TR_C_SENSORHUB11_REG 0x38U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t bit0 : 1; + uint8_t bit1 : 1; + uint8_t bit2 : 1; + uint8_t bit3 : 1; + uint8_t bit4 : 1; + uint8_t bit5 : 1; + uint8_t bit6 : 1; + uint8_t bit7 : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t bit7 : 1; + uint8_t bit6 : 1; + uint8_t bit5 : 1; + uint8_t bit4 : 1; + uint8_t bit3 : 1; + uint8_t bit2 : 1; + uint8_t bit1 : 1; + uint8_t bit0 : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_sensorhub11_reg_t; + +#define LSM6DS3TR_C_SENSORHUB12_REG 0x39U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t bit0 : 1; + uint8_t bit1 : 1; + uint8_t bit2 : 1; + uint8_t bit3 : 1; + uint8_t bit4 : 1; + uint8_t bit5 : 1; + uint8_t bit6 : 1; + uint8_t bit7 : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t bit7 : 1; + uint8_t bit6 : 1; + uint8_t bit5 : 1; + uint8_t bit4 : 1; + uint8_t bit3 : 1; + uint8_t bit2 : 1; + uint8_t bit1 : 1; + uint8_t bit0 : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_sensorhub12_reg_t; + +#define LSM6DS3TR_C_FIFO_STATUS1 0x3AU +typedef struct { + uint8_t diff_fifo : 8; /* + FIFO_STATUS2(diff_fifo) */ +} lsm6ds3tr_c_fifo_status1_t; + +#define LSM6DS3TR_C_FIFO_STATUS2 0x3BU +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t diff_fifo : 3; /* + FIFO_STATUS1(diff_fifo) */ + uint8_t not_used_01 : 1; + uint8_t fifo_empty : 1; + uint8_t fifo_full_smart : 1; + uint8_t over_run : 1; + uint8_t waterm : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t waterm : 1; + uint8_t over_run : 1; + uint8_t fifo_full_smart : 1; + uint8_t fifo_empty : 1; + uint8_t not_used_01 : 1; + uint8_t diff_fifo : 3; /* + FIFO_STATUS1(diff_fifo) */ +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_fifo_status2_t; + +#define LSM6DS3TR_C_FIFO_STATUS3 0x3CU +typedef struct { + uint8_t fifo_pattern : 8; /* + FIFO_STATUS4(fifo_pattern) */ +} lsm6ds3tr_c_fifo_status3_t; + +#define LSM6DS3TR_C_FIFO_STATUS4 0x3DU +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t fifo_pattern : 2; /* + FIFO_STATUS3(fifo_pattern) */ + uint8_t not_used_01 : 6; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t not_used_01 : 6; + uint8_t fifo_pattern : 2; /* + FIFO_STATUS3(fifo_pattern) */ +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_fifo_status4_t; + +#define LSM6DS3TR_C_FIFO_DATA_OUT_L 0x3EU +#define LSM6DS3TR_C_FIFO_DATA_OUT_H 0x3FU +#define LSM6DS3TR_C_TIMESTAMP0_REG 0x40U +#define LSM6DS3TR_C_TIMESTAMP1_REG 0x41U +#define LSM6DS3TR_C_TIMESTAMP2_REG 0x42U +#define LSM6DS3TR_C_STEP_TIMESTAMP_L 0x49U +#define LSM6DS3TR_C_STEP_TIMESTAMP_H 0x4AU +#define LSM6DS3TR_C_STEP_COUNTER_L 0x4BU +#define LSM6DS3TR_C_STEP_COUNTER_H 0x4CU + +#define LSM6DS3TR_C_SENSORHUB13_REG 0x4DU +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t bit0 : 1; + uint8_t bit1 : 1; + uint8_t bit2 : 1; + uint8_t bit3 : 1; + uint8_t bit4 : 1; + uint8_t bit5 : 1; + uint8_t bit6 : 1; + uint8_t bit7 : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t bit7 : 1; + uint8_t bit6 : 1; + uint8_t bit5 : 1; + uint8_t bit4 : 1; + uint8_t bit3 : 1; + uint8_t bit2 : 1; + uint8_t bit1 : 1; + uint8_t bit0 : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_sensorhub13_reg_t; + +#define LSM6DS3TR_C_SENSORHUB14_REG 0x4EU +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t bit0 : 1; + uint8_t bit1 : 1; + uint8_t bit2 : 1; + uint8_t bit3 : 1; + uint8_t bit4 : 1; + uint8_t bit5 : 1; + uint8_t bit6 : 1; + uint8_t bit7 : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t bit7 : 1; + uint8_t bit6 : 1; + uint8_t bit5 : 1; + uint8_t bit4 : 1; + uint8_t bit3 : 1; + uint8_t bit2 : 1; + uint8_t bit1 : 1; + uint8_t bit0 : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_sensorhub14_reg_t; + +#define LSM6DS3TR_C_SENSORHUB15_REG 0x4FU +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t bit0 : 1; + uint8_t bit1 : 1; + uint8_t bit2 : 1; + uint8_t bit3 : 1; + uint8_t bit4 : 1; + uint8_t bit5 : 1; + uint8_t bit6 : 1; + uint8_t bit7 : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t bit7 : 1; + uint8_t bit6 : 1; + uint8_t bit5 : 1; + uint8_t bit4 : 1; + uint8_t bit3 : 1; + uint8_t bit2 : 1; + uint8_t bit1 : 1; + uint8_t bit0 : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_sensorhub15_reg_t; + +#define LSM6DS3TR_C_SENSORHUB16_REG 0x50U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t bit0 : 1; + uint8_t bit1 : 1; + uint8_t bit2 : 1; + uint8_t bit3 : 1; + uint8_t bit4 : 1; + uint8_t bit5 : 1; + uint8_t bit6 : 1; + uint8_t bit7 : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t bit7 : 1; + uint8_t bit6 : 1; + uint8_t bit5 : 1; + uint8_t bit4 : 1; + uint8_t bit3 : 1; + uint8_t bit2 : 1; + uint8_t bit1 : 1; + uint8_t bit0 : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_sensorhub16_reg_t; + +#define LSM6DS3TR_C_SENSORHUB17_REG 0x51U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t bit0 : 1; + uint8_t bit1 : 1; + uint8_t bit2 : 1; + uint8_t bit3 : 1; + uint8_t bit4 : 1; + uint8_t bit5 : 1; + uint8_t bit6 : 1; + uint8_t bit7 : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t bit7 : 1; + uint8_t bit6 : 1; + uint8_t bit5 : 1; + uint8_t bit4 : 1; + uint8_t bit3 : 1; + uint8_t bit2 : 1; + uint8_t bit1 : 1; + uint8_t bit0 : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_sensorhub17_reg_t; + +#define LSM6DS3TR_C_SENSORHUB18_REG 0x52U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t bit0 : 1; + uint8_t bit1 : 1; + uint8_t bit2 : 1; + uint8_t bit3 : 1; + uint8_t bit4 : 1; + uint8_t bit5 : 1; + uint8_t bit6 : 1; + uint8_t bit7 : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t bit7 : 1; + uint8_t bit6 : 1; + uint8_t bit5 : 1; + uint8_t bit4 : 1; + uint8_t bit3 : 1; + uint8_t bit2 : 1; + uint8_t bit1 : 1; + uint8_t bit0 : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_sensorhub18_reg_t; + +#define LSM6DS3TR_C_FUNC_SRC1 0x53U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t sensorhub_end_op : 1; + uint8_t si_end_op : 1; + uint8_t hi_fail : 1; + uint8_t step_overflow : 1; + uint8_t step_detected : 1; + uint8_t tilt_ia : 1; + uint8_t sign_motion_ia : 1; + uint8_t step_count_delta_ia : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t step_count_delta_ia : 1; + uint8_t sign_motion_ia : 1; + uint8_t tilt_ia : 1; + uint8_t step_detected : 1; + uint8_t step_overflow : 1; + uint8_t hi_fail : 1; + uint8_t si_end_op : 1; + uint8_t sensorhub_end_op : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_func_src1_t; + +#define LSM6DS3TR_C_FUNC_SRC2 0x54U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t wrist_tilt_ia : 1; + uint8_t not_used_01 : 2; + uint8_t slave0_nack : 1; + uint8_t slave1_nack : 1; + uint8_t slave2_nack : 1; + uint8_t slave3_nack : 1; + uint8_t not_used_02 : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t not_used_02 : 1; + uint8_t slave3_nack : 1; + uint8_t slave2_nack : 1; + uint8_t slave1_nack : 1; + uint8_t slave0_nack : 1; + uint8_t not_used_01 : 2; + uint8_t wrist_tilt_ia : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_func_src2_t; + +#define LSM6DS3TR_C_WRIST_TILT_IA 0x55U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t not_used_01 : 2; + uint8_t wrist_tilt_ia_zneg : 1; + uint8_t wrist_tilt_ia_zpos : 1; + uint8_t wrist_tilt_ia_yneg : 1; + uint8_t wrist_tilt_ia_ypos : 1; + uint8_t wrist_tilt_ia_xneg : 1; + uint8_t wrist_tilt_ia_xpos : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t wrist_tilt_ia_xpos : 1; + uint8_t wrist_tilt_ia_xneg : 1; + uint8_t wrist_tilt_ia_ypos : 1; + uint8_t wrist_tilt_ia_yneg : 1; + uint8_t wrist_tilt_ia_zpos : 1; + uint8_t wrist_tilt_ia_zneg : 1; + uint8_t not_used_01 : 2; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_wrist_tilt_ia_t; + +#define LSM6DS3TR_C_TAP_CFG 0x58U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t lir : 1; + uint8_t tap_z_en : 1; + uint8_t tap_y_en : 1; + uint8_t tap_x_en : 1; + uint8_t slope_fds : 1; + uint8_t inact_en : 2; + uint8_t interrupts_enable : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t interrupts_enable : 1; + uint8_t inact_en : 2; + uint8_t slope_fds : 1; + uint8_t tap_x_en : 1; + uint8_t tap_y_en : 1; + uint8_t tap_z_en : 1; + uint8_t lir : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_tap_cfg_t; + +#define LSM6DS3TR_C_TAP_THS_6D 0x59U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t tap_ths : 5; + uint8_t sixd_ths : 2; + uint8_t d4d_en : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t d4d_en : 1; + uint8_t sixd_ths : 2; + uint8_t tap_ths : 5; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_tap_ths_6d_t; + +#define LSM6DS3TR_C_INT_DUR2 0x5AU +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t shock : 2; + uint8_t quiet : 2; + uint8_t dur : 4; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t dur : 4; + uint8_t quiet : 2; + uint8_t shock : 2; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_int_dur2_t; + +#define LSM6DS3TR_C_WAKE_UP_THS 0x5BU +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t wk_ths : 6; + uint8_t not_used_01 : 1; + uint8_t single_double_tap : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t single_double_tap : 1; + uint8_t not_used_01 : 1; + uint8_t wk_ths : 6; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_wake_up_ths_t; + +#define LSM6DS3TR_C_WAKE_UP_DUR 0x5CU +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t sleep_dur : 4; + uint8_t timer_hr : 1; + uint8_t wake_dur : 2; + uint8_t ff_dur : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t ff_dur : 1; + uint8_t wake_dur : 2; + uint8_t timer_hr : 1; + uint8_t sleep_dur : 4; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_wake_up_dur_t; + +#define LSM6DS3TR_C_FREE_FALL 0x5DU +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t ff_ths : 3; + uint8_t ff_dur : 5; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t ff_dur : 5; + uint8_t ff_ths : 3; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_free_fall_t; + +#define LSM6DS3TR_C_MD1_CFG 0x5EU +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t int1_timer : 1; + uint8_t int1_tilt : 1; + uint8_t int1_6d : 1; + uint8_t int1_double_tap : 1; + uint8_t int1_ff : 1; + uint8_t int1_wu : 1; + uint8_t int1_single_tap : 1; + uint8_t int1_inact_state : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t int1_inact_state : 1; + uint8_t int1_single_tap : 1; + uint8_t int1_wu : 1; + uint8_t int1_ff : 1; + uint8_t int1_double_tap : 1; + uint8_t int1_6d : 1; + uint8_t int1_tilt : 1; + uint8_t int1_timer : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_md1_cfg_t; + +#define LSM6DS3TR_C_MD2_CFG 0x5FU +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t int2_iron : 1; + uint8_t int2_tilt : 1; + uint8_t int2_6d : 1; + uint8_t int2_double_tap : 1; + uint8_t int2_ff : 1; + uint8_t int2_wu : 1; + uint8_t int2_single_tap : 1; + uint8_t int2_inact_state : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t int2_inact_state : 1; + uint8_t int2_single_tap : 1; + uint8_t int2_wu : 1; + uint8_t int2_ff : 1; + uint8_t int2_double_tap : 1; + uint8_t int2_6d : 1; + uint8_t int2_tilt : 1; + uint8_t int2_iron : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_md2_cfg_t; + +#define LSM6DS3TR_C_MASTER_CMD_CODE 0x60U +typedef struct { + uint8_t master_cmd_code : 8; +} lsm6ds3tr_c_master_cmd_code_t; + +#define LSM6DS3TR_C_SENS_SYNC_SPI_ERROR_CODE 0x61U +typedef struct { + uint8_t error_code : 8; +} lsm6ds3tr_c_sens_sync_spi_error_code_t; + +#define LSM6DS3TR_C_OUT_MAG_RAW_X_L 0x66U +#define LSM6DS3TR_C_OUT_MAG_RAW_X_H 0x67U +#define LSM6DS3TR_C_OUT_MAG_RAW_Y_L 0x68U +#define LSM6DS3TR_C_OUT_MAG_RAW_Y_H 0x69U +#define LSM6DS3TR_C_OUT_MAG_RAW_Z_L 0x6AU +#define LSM6DS3TR_C_OUT_MAG_RAW_Z_H 0x6BU +#define LSM6DS3TR_C_X_OFS_USR 0x73U +#define LSM6DS3TR_C_Y_OFS_USR 0x74U +#define LSM6DS3TR_C_Z_OFS_USR 0x75U +#define LSM6DS3TR_C_SLV0_ADD 0x02U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t rw_0 : 1; + uint8_t slave0_add : 7; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t slave0_add : 7; + uint8_t rw_0 : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_slv0_add_t; + +#define LSM6DS3TR_C_SLV0_SUBADD 0x03U +typedef struct { + uint8_t slave0_reg : 8; +} lsm6ds3tr_c_slv0_subadd_t; + +#define LSM6DS3TR_C_SLAVE0_CONFIG 0x04U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t slave0_numop : 3; + uint8_t src_mode : 1; + uint8_t aux_sens_on : 2; + uint8_t slave0_rate : 2; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t slave0_rate : 2; + uint8_t aux_sens_on : 2; + uint8_t src_mode : 1; + uint8_t slave0_numop : 3; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_slave0_config_t; + +#define LSM6DS3TR_C_SLV1_ADD 0x05U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t r_1 : 1; + uint8_t slave1_add : 7; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t slave1_add : 7; + uint8_t r_1 : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_slv1_add_t; + +#define LSM6DS3TR_C_SLV1_SUBADD 0x06U +typedef struct { + uint8_t slave1_reg : 8; +} lsm6ds3tr_c_slv1_subadd_t; + +#define LSM6DS3TR_C_SLAVE1_CONFIG 0x07U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t slave1_numop : 3; + uint8_t not_used_01 : 2; + uint8_t write_once : 1; + uint8_t slave1_rate : 2; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t slave1_rate : 2; + uint8_t write_once : 1; + uint8_t not_used_01 : 2; + uint8_t slave1_numop : 3; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_slave1_config_t; + +#define LSM6DS3TR_C_SLV2_ADD 0x08U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t r_2 : 1; + uint8_t slave2_add : 7; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t slave2_add : 7; + uint8_t r_2 : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_slv2_add_t; + +#define LSM6DS3TR_C_SLV2_SUBADD 0x09U +typedef struct { + uint8_t slave2_reg : 8; +} lsm6ds3tr_c_slv2_subadd_t; + +#define LSM6DS3TR_C_SLAVE2_CONFIG 0x0AU +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t slave2_numop : 3; + uint8_t not_used_01 : 3; + uint8_t slave2_rate : 2; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t slave2_rate : 2; + uint8_t not_used_01 : 3; + uint8_t slave2_numop : 3; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_slave2_config_t; + +#define LSM6DS3TR_C_SLV3_ADD 0x0BU +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t r_3 : 1; + uint8_t slave3_add : 7; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t slave3_add : 7; + uint8_t r_3 : 1; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_slv3_add_t; + +#define LSM6DS3TR_C_SLV3_SUBADD 0x0CU +typedef struct { + uint8_t slave3_reg : 8; +} lsm6ds3tr_c_slv3_subadd_t; + +#define LSM6DS3TR_C_SLAVE3_CONFIG 0x0DU +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t slave3_numop : 3; + uint8_t not_used_01 : 3; + uint8_t slave3_rate : 2; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t slave3_rate : 2; + uint8_t not_used_01 : 3; + uint8_t slave3_numop : 3; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_slave3_config_t; + +#define LSM6DS3TR_C_DATAWRITE_SRC_MODE_SUB_SLV0 0x0EU +typedef struct { + uint8_t slave_dataw : 8; +} lsm6ds3tr_c_datawrite_src_mode_sub_slv0_t; + +#define LSM6DS3TR_C_CONFIG_PEDO_THS_MIN 0x0FU +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t ths_min : 5; + uint8_t not_used_01 : 2; + uint8_t pedo_fs : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t pedo_fs : 1; + uint8_t not_used_01 : 2; + uint8_t ths_min : 5; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_config_pedo_ths_min_t; + +#define LSM6DS3TR_C_SM_THS 0x13U +#define LSM6DS3TR_C_PEDO_DEB_REG 0x14U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t deb_step : 3; + uint8_t deb_time : 5; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t deb_time : 5; + uint8_t deb_step : 3; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_pedo_deb_reg_t; + +#define LSM6DS3TR_C_STEP_COUNT_DELTA 0x15U +#define LSM6DS3TR_C_MAG_SI_XX 0x24U +#define LSM6DS3TR_C_MAG_SI_XY 0x25U +#define LSM6DS3TR_C_MAG_SI_XZ 0x26U +#define LSM6DS3TR_C_MAG_SI_YX 0x27U +#define LSM6DS3TR_C_MAG_SI_YY 0x28U +#define LSM6DS3TR_C_MAG_SI_YZ 0x29U +#define LSM6DS3TR_C_MAG_SI_ZX 0x2AU +#define LSM6DS3TR_C_MAG_SI_ZY 0x2BU +#define LSM6DS3TR_C_MAG_SI_ZZ 0x2CU +#define LSM6DS3TR_C_MAG_OFFX_L 0x2DU +#define LSM6DS3TR_C_MAG_OFFX_H 0x2EU +#define LSM6DS3TR_C_MAG_OFFY_L 0x2FU +#define LSM6DS3TR_C_MAG_OFFY_H 0x30U +#define LSM6DS3TR_C_MAG_OFFZ_L 0x31U +#define LSM6DS3TR_C_MAG_OFFZ_H 0x32U +#define LSM6DS3TR_C_A_WRIST_TILT_LAT 0x50U +#define LSM6DS3TR_C_A_WRIST_TILT_THS 0x54U +#define LSM6DS3TR_C_A_WRIST_TILT_MASK 0x59U +typedef struct { +#if DRV_BYTE_ORDER == DRV_LITTLE_ENDIAN + uint8_t not_used_01 : 2; + uint8_t wrist_tilt_mask_zneg : 1; + uint8_t wrist_tilt_mask_zpos : 1; + uint8_t wrist_tilt_mask_yneg : 1; + uint8_t wrist_tilt_mask_ypos : 1; + uint8_t wrist_tilt_mask_xneg : 1; + uint8_t wrist_tilt_mask_xpos : 1; +#elif DRV_BYTE_ORDER == DRV_BIG_ENDIAN + uint8_t wrist_tilt_mask_xpos : 1; + uint8_t wrist_tilt_mask_xneg : 1; + uint8_t wrist_tilt_mask_ypos : 1; + uint8_t wrist_tilt_mask_yneg : 1; + uint8_t wrist_tilt_mask_zpos : 1; + uint8_t wrist_tilt_mask_zneg : 1; + uint8_t not_used_01 : 2; +#endif /* DRV_BYTE_ORDER */ +} lsm6ds3tr_c_a_wrist_tilt_mask_t; + +/** + * @defgroup LSM6DS3TR_C_Register_Union + * @brief This union group all the registers having a bit-field + * description. + * This union is useful but it's not needed by the driver. + * + * REMOVING this union you are compliant with: + * MISRA-C 2012 [Rule 19.2] -> " Union are not allowed " + * + * @{ + * + */ +typedef union { + lsm6ds3tr_c_func_cfg_access_t func_cfg_access; + lsm6ds3tr_c_sensor_sync_time_frame_t sensor_sync_time_frame; + lsm6ds3tr_c_sensor_sync_res_ratio_t sensor_sync_res_ratio; + lsm6ds3tr_c_fifo_ctrl1_t fifo_ctrl1; + lsm6ds3tr_c_fifo_ctrl2_t fifo_ctrl2; + lsm6ds3tr_c_fifo_ctrl3_t fifo_ctrl3; + lsm6ds3tr_c_fifo_ctrl4_t fifo_ctrl4; + lsm6ds3tr_c_fifo_ctrl5_t fifo_ctrl5; + lsm6ds3tr_c_drdy_pulse_cfg_g_t drdy_pulse_cfg_g; + lsm6ds3tr_c_int1_ctrl_t int1_ctrl; + lsm6ds3tr_c_int2_ctrl_t int2_ctrl; + lsm6ds3tr_c_ctrl1_xl_t ctrl1_xl; + lsm6ds3tr_c_ctrl2_g_t ctrl2_g; + lsm6ds3tr_c_ctrl3_c_t ctrl3_c; + lsm6ds3tr_c_ctrl4_c_t ctrl4_c; + lsm6ds3tr_c_ctrl5_c_t ctrl5_c; + lsm6ds3tr_c_ctrl6_c_t ctrl6_c; + lsm6ds3tr_c_ctrl7_g_t ctrl7_g; + lsm6ds3tr_c_ctrl8_xl_t ctrl8_xl; + lsm6ds3tr_c_ctrl9_xl_t ctrl9_xl; + lsm6ds3tr_c_ctrl10_c_t ctrl10_c; + lsm6ds3tr_c_master_config_t master_config; + lsm6ds3tr_c_wake_up_src_t wake_up_src; + lsm6ds3tr_c_tap_src_t tap_src; + lsm6ds3tr_c_d6d_src_t d6d_src; + lsm6ds3tr_c_status_reg_t status_reg; + lsm6ds3tr_c_sensorhub1_reg_t sensorhub1_reg; + lsm6ds3tr_c_sensorhub2_reg_t sensorhub2_reg; + lsm6ds3tr_c_sensorhub3_reg_t sensorhub3_reg; + lsm6ds3tr_c_sensorhub4_reg_t sensorhub4_reg; + lsm6ds3tr_c_sensorhub5_reg_t sensorhub5_reg; + lsm6ds3tr_c_sensorhub6_reg_t sensorhub6_reg; + lsm6ds3tr_c_sensorhub7_reg_t sensorhub7_reg; + lsm6ds3tr_c_sensorhub8_reg_t sensorhub8_reg; + lsm6ds3tr_c_sensorhub9_reg_t sensorhub9_reg; + lsm6ds3tr_c_sensorhub10_reg_t sensorhub10_reg; + lsm6ds3tr_c_sensorhub11_reg_t sensorhub11_reg; + lsm6ds3tr_c_sensorhub12_reg_t sensorhub12_reg; + lsm6ds3tr_c_fifo_status1_t fifo_status1; + lsm6ds3tr_c_fifo_status2_t fifo_status2; + lsm6ds3tr_c_fifo_status3_t fifo_status3; + lsm6ds3tr_c_fifo_status4_t fifo_status4; + lsm6ds3tr_c_sensorhub13_reg_t sensorhub13_reg; + lsm6ds3tr_c_sensorhub14_reg_t sensorhub14_reg; + lsm6ds3tr_c_sensorhub15_reg_t sensorhub15_reg; + lsm6ds3tr_c_sensorhub16_reg_t sensorhub16_reg; + lsm6ds3tr_c_sensorhub17_reg_t sensorhub17_reg; + lsm6ds3tr_c_sensorhub18_reg_t sensorhub18_reg; + lsm6ds3tr_c_func_src1_t func_src1; + lsm6ds3tr_c_func_src2_t func_src2; + lsm6ds3tr_c_wrist_tilt_ia_t wrist_tilt_ia; + lsm6ds3tr_c_tap_cfg_t tap_cfg; + lsm6ds3tr_c_tap_ths_6d_t tap_ths_6d; + lsm6ds3tr_c_int_dur2_t int_dur2; + lsm6ds3tr_c_wake_up_ths_t wake_up_ths; + lsm6ds3tr_c_wake_up_dur_t wake_up_dur; + lsm6ds3tr_c_free_fall_t free_fall; + lsm6ds3tr_c_md1_cfg_t md1_cfg; + lsm6ds3tr_c_md2_cfg_t md2_cfg; + lsm6ds3tr_c_master_cmd_code_t master_cmd_code; + lsm6ds3tr_c_sens_sync_spi_error_code_t sens_sync_spi_error_code; + lsm6ds3tr_c_slv0_add_t slv0_add; + lsm6ds3tr_c_slv0_subadd_t slv0_subadd; + lsm6ds3tr_c_slave0_config_t slave0_config; + lsm6ds3tr_c_slv1_add_t slv1_add; + lsm6ds3tr_c_slv1_subadd_t slv1_subadd; + lsm6ds3tr_c_slave1_config_t slave1_config; + lsm6ds3tr_c_slv2_add_t slv2_add; + lsm6ds3tr_c_slv2_subadd_t slv2_subadd; + lsm6ds3tr_c_slave2_config_t slave2_config; + lsm6ds3tr_c_slv3_add_t slv3_add; + lsm6ds3tr_c_slv3_subadd_t slv3_subadd; + lsm6ds3tr_c_slave3_config_t slave3_config; + lsm6ds3tr_c_datawrite_src_mode_sub_slv0_t datawrite_src_mode_sub_slv0; + lsm6ds3tr_c_config_pedo_ths_min_t config_pedo_ths_min; + lsm6ds3tr_c_pedo_deb_reg_t pedo_deb_reg; + lsm6ds3tr_c_a_wrist_tilt_mask_t a_wrist_tilt_mask; + bitwise_t bitwise; + uint8_t byte; +} lsm6ds3tr_c_reg_t; + +/** + * @} + * + */ + +int32_t lsm6ds3tr_c_read_reg(stmdev_ctx_t* ctx, uint8_t reg, uint8_t* data, uint16_t len); +int32_t lsm6ds3tr_c_write_reg(stmdev_ctx_t* ctx, uint8_t reg, uint8_t* data, uint16_t len); + +float_t lsm6ds3tr_c_from_fs2g_to_mg(int16_t lsb); +float_t lsm6ds3tr_c_from_fs4g_to_mg(int16_t lsb); +float_t lsm6ds3tr_c_from_fs8g_to_mg(int16_t lsb); +float_t lsm6ds3tr_c_from_fs16g_to_mg(int16_t lsb); + +float_t lsm6ds3tr_c_from_fs125dps_to_mdps(int16_t lsb); +float_t lsm6ds3tr_c_from_fs250dps_to_mdps(int16_t lsb); +float_t lsm6ds3tr_c_from_fs500dps_to_mdps(int16_t lsb); +float_t lsm6ds3tr_c_from_fs1000dps_to_mdps(int16_t lsb); +float_t lsm6ds3tr_c_from_fs2000dps_to_mdps(int16_t lsb); + +float_t lsm6ds3tr_c_from_lsb_to_celsius(int16_t lsb); + +typedef enum { + LSM6DS3TR_C_2g = 0, + LSM6DS3TR_C_16g = 1, + LSM6DS3TR_C_4g = 2, + LSM6DS3TR_C_8g = 3, + LSM6DS3TR_C_XL_FS_ND = 4, /* ERROR CODE */ +} lsm6ds3tr_c_fs_xl_t; +int32_t lsm6ds3tr_c_xl_full_scale_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_fs_xl_t val); +int32_t lsm6ds3tr_c_xl_full_scale_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_fs_xl_t* val); + +typedef enum { + LSM6DS3TR_C_XL_ODR_OFF = 0, + LSM6DS3TR_C_XL_ODR_12Hz5 = 1, + LSM6DS3TR_C_XL_ODR_26Hz = 2, + LSM6DS3TR_C_XL_ODR_52Hz = 3, + LSM6DS3TR_C_XL_ODR_104Hz = 4, + LSM6DS3TR_C_XL_ODR_208Hz = 5, + LSM6DS3TR_C_XL_ODR_416Hz = 6, + LSM6DS3TR_C_XL_ODR_833Hz = 7, + LSM6DS3TR_C_XL_ODR_1k66Hz = 8, + LSM6DS3TR_C_XL_ODR_3k33Hz = 9, + LSM6DS3TR_C_XL_ODR_6k66Hz = 10, + LSM6DS3TR_C_XL_ODR_1Hz6 = 11, + LSM6DS3TR_C_XL_ODR_ND = 12, /* ERROR CODE */ +} lsm6ds3tr_c_odr_xl_t; +int32_t lsm6ds3tr_c_xl_data_rate_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_odr_xl_t val); +int32_t lsm6ds3tr_c_xl_data_rate_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_odr_xl_t* val); + +typedef enum { + LSM6DS3TR_C_250dps = 0, + LSM6DS3TR_C_125dps = 1, + LSM6DS3TR_C_500dps = 2, + LSM6DS3TR_C_1000dps = 4, + LSM6DS3TR_C_2000dps = 6, + LSM6DS3TR_C_GY_FS_ND = 7, /* ERROR CODE */ +} lsm6ds3tr_c_fs_g_t; +int32_t lsm6ds3tr_c_gy_full_scale_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_fs_g_t val); +int32_t lsm6ds3tr_c_gy_full_scale_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_fs_g_t* val); + +typedef enum { + LSM6DS3TR_C_GY_ODR_OFF = 0, + LSM6DS3TR_C_GY_ODR_12Hz5 = 1, + LSM6DS3TR_C_GY_ODR_26Hz = 2, + LSM6DS3TR_C_GY_ODR_52Hz = 3, + LSM6DS3TR_C_GY_ODR_104Hz = 4, + LSM6DS3TR_C_GY_ODR_208Hz = 5, + LSM6DS3TR_C_GY_ODR_416Hz = 6, + LSM6DS3TR_C_GY_ODR_833Hz = 7, + LSM6DS3TR_C_GY_ODR_1k66Hz = 8, + LSM6DS3TR_C_GY_ODR_3k33Hz = 9, + LSM6DS3TR_C_GY_ODR_6k66Hz = 10, + LSM6DS3TR_C_GY_ODR_ND = 11, /* ERROR CODE */ +} lsm6ds3tr_c_odr_g_t; +int32_t lsm6ds3tr_c_gy_data_rate_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_odr_g_t val); +int32_t lsm6ds3tr_c_gy_data_rate_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_odr_g_t* val); + +int32_t lsm6ds3tr_c_block_data_update_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_block_data_update_get(stmdev_ctx_t* ctx, uint8_t* val); + +typedef enum { + LSM6DS3TR_C_LSb_1mg = 0, + LSM6DS3TR_C_LSb_16mg = 1, + LSM6DS3TR_C_WEIGHT_ND = 2, +} lsm6ds3tr_c_usr_off_w_t; +int32_t lsm6ds3tr_c_xl_offset_weight_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_usr_off_w_t val); +int32_t lsm6ds3tr_c_xl_offset_weight_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_usr_off_w_t* val); + +typedef enum { + LSM6DS3TR_C_XL_HIGH_PERFORMANCE = 0, + LSM6DS3TR_C_XL_NORMAL = 1, + LSM6DS3TR_C_XL_PW_MODE_ND = 2, /* ERROR CODE */ +} lsm6ds3tr_c_xl_hm_mode_t; +int32_t lsm6ds3tr_c_xl_power_mode_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_xl_hm_mode_t val); +int32_t lsm6ds3tr_c_xl_power_mode_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_xl_hm_mode_t* val); + +typedef enum { + LSM6DS3TR_C_STAT_RND_DISABLE = 0, + LSM6DS3TR_C_STAT_RND_ENABLE = 1, + LSM6DS3TR_C_STAT_RND_ND = 2, /* ERROR CODE */ +} lsm6ds3tr_c_rounding_status_t; +int32_t lsm6ds3tr_c_rounding_on_status_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_rounding_status_t val); +int32_t lsm6ds3tr_c_rounding_on_status_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_rounding_status_t* val); + +typedef enum { + LSM6DS3TR_C_GY_HIGH_PERFORMANCE = 0, + LSM6DS3TR_C_GY_NORMAL = 1, + LSM6DS3TR_C_GY_PW_MODE_ND = 2, /* ERROR CODE */ +} lsm6ds3tr_c_g_hm_mode_t; +int32_t lsm6ds3tr_c_gy_power_mode_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_g_hm_mode_t val); +int32_t lsm6ds3tr_c_gy_power_mode_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_g_hm_mode_t* val); + +typedef struct { + lsm6ds3tr_c_wake_up_src_t wake_up_src; + lsm6ds3tr_c_tap_src_t tap_src; + lsm6ds3tr_c_d6d_src_t d6d_src; + lsm6ds3tr_c_status_reg_t status_reg; + lsm6ds3tr_c_func_src1_t func_src1; + lsm6ds3tr_c_func_src2_t func_src2; + lsm6ds3tr_c_wrist_tilt_ia_t wrist_tilt_ia; + lsm6ds3tr_c_a_wrist_tilt_mask_t a_wrist_tilt_mask; +} lsm6ds3tr_c_all_sources_t; +int32_t lsm6ds3tr_c_all_sources_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_all_sources_t* val); + +int32_t lsm6ds3tr_c_status_reg_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_status_reg_t* val); + +int32_t lsm6ds3tr_c_xl_flag_data_ready_get(stmdev_ctx_t* ctx, uint8_t* val); + +int32_t lsm6ds3tr_c_gy_flag_data_ready_get(stmdev_ctx_t* ctx, uint8_t* val); + +int32_t lsm6ds3tr_c_temp_flag_data_ready_get(stmdev_ctx_t* ctx, uint8_t* val); + +int32_t lsm6ds3tr_c_xl_usr_offset_set(stmdev_ctx_t* ctx, uint8_t* buff); +int32_t lsm6ds3tr_c_xl_usr_offset_get(stmdev_ctx_t* ctx, uint8_t* buff); +int32_t lsm6ds3tr_c_timestamp_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_timestamp_get(stmdev_ctx_t* ctx, uint8_t* val); + +typedef enum { + LSM6DS3TR_C_LSB_6ms4 = 0, + LSM6DS3TR_C_LSB_25us = 1, + LSM6DS3TR_C_TS_RES_ND = 2, /* ERROR CODE */ +} lsm6ds3tr_c_timer_hr_t; +int32_t lsm6ds3tr_c_timestamp_res_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_timer_hr_t val); +int32_t lsm6ds3tr_c_timestamp_res_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_timer_hr_t* val); + +typedef enum { + LSM6DS3TR_C_ROUND_DISABLE = 0, + LSM6DS3TR_C_ROUND_XL = 1, + LSM6DS3TR_C_ROUND_GY = 2, + LSM6DS3TR_C_ROUND_GY_XL = 3, + LSM6DS3TR_C_ROUND_SH1_TO_SH6 = 4, + LSM6DS3TR_C_ROUND_XL_SH1_TO_SH6 = 5, + LSM6DS3TR_C_ROUND_GY_XL_SH1_TO_SH12 = 6, + LSM6DS3TR_C_ROUND_GY_XL_SH1_TO_SH6 = 7, + LSM6DS3TR_C_ROUND_OUT_ND = 8, /* ERROR CODE */ +} lsm6ds3tr_c_rounding_t; +int32_t lsm6ds3tr_c_rounding_mode_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_rounding_t val); +int32_t lsm6ds3tr_c_rounding_mode_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_rounding_t* val); + +int32_t lsm6ds3tr_c_temperature_raw_get(stmdev_ctx_t* ctx, int16_t* val); +int32_t lsm6ds3tr_c_angular_rate_raw_get(stmdev_ctx_t* ctx, int16_t* val); +int32_t lsm6ds3tr_c_acceleration_raw_get(stmdev_ctx_t* ctx, int16_t* val); + +int32_t lsm6ds3tr_c_mag_calibrated_raw_get(stmdev_ctx_t* ctx, int16_t* val); + +int32_t lsm6ds3tr_c_fifo_raw_data_get(stmdev_ctx_t* ctx, uint8_t* buffer, uint8_t len); + +typedef enum { + LSM6DS3TR_C_USER_BANK = 0, + LSM6DS3TR_C_BANK_A = 4, + LSM6DS3TR_C_BANK_B = 5, + LSM6DS3TR_C_BANK_ND = 6, /* ERROR CODE */ +} lsm6ds3tr_c_func_cfg_en_t; +int32_t lsm6ds3tr_c_mem_bank_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_func_cfg_en_t val); +int32_t lsm6ds3tr_c_mem_bank_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_func_cfg_en_t* val); + +typedef enum { + LSM6DS3TR_C_DRDY_LATCHED = 0, + LSM6DS3TR_C_DRDY_PULSED = 1, + LSM6DS3TR_C_DRDY_ND = 2, /* ERROR CODE */ +} lsm6ds3tr_c_drdy_pulsed_g_t; +int32_t lsm6ds3tr_c_data_ready_mode_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_drdy_pulsed_g_t val); +int32_t lsm6ds3tr_c_data_ready_mode_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_drdy_pulsed_g_t* val); + +int32_t lsm6ds3tr_c_device_id_get(stmdev_ctx_t* ctx, uint8_t* buff); +int32_t lsm6ds3tr_c_reset_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_reset_get(stmdev_ctx_t* ctx, uint8_t* val); + +typedef enum { + LSM6DS3TR_C_LSB_AT_LOW_ADD = 0, + LSM6DS3TR_C_MSB_AT_LOW_ADD = 1, + LSM6DS3TR_C_DATA_FMT_ND = 2, /* ERROR CODE */ +} lsm6ds3tr_c_ble_t; +int32_t lsm6ds3tr_c_data_format_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_ble_t val); +int32_t lsm6ds3tr_c_data_format_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_ble_t* val); + +int32_t lsm6ds3tr_c_auto_increment_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_auto_increment_get(stmdev_ctx_t* ctx, uint8_t* val); + +int32_t lsm6ds3tr_c_boot_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_boot_get(stmdev_ctx_t* ctx, uint8_t* val); + +typedef enum { + LSM6DS3TR_C_XL_ST_DISABLE = 0, + LSM6DS3TR_C_XL_ST_POSITIVE = 1, + LSM6DS3TR_C_XL_ST_NEGATIVE = 2, + LSM6DS3TR_C_XL_ST_ND = 3, /* ERROR CODE */ +} lsm6ds3tr_c_st_xl_t; +int32_t lsm6ds3tr_c_xl_self_test_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_st_xl_t val); +int32_t lsm6ds3tr_c_xl_self_test_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_st_xl_t* val); + +typedef enum { + LSM6DS3TR_C_GY_ST_DISABLE = 0, + LSM6DS3TR_C_GY_ST_POSITIVE = 1, + LSM6DS3TR_C_GY_ST_NEGATIVE = 3, + LSM6DS3TR_C_GY_ST_ND = 4, /* ERROR CODE */ +} lsm6ds3tr_c_st_g_t; +int32_t lsm6ds3tr_c_gy_self_test_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_st_g_t val); +int32_t lsm6ds3tr_c_gy_self_test_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_st_g_t* val); + +int32_t lsm6ds3tr_c_filter_settling_mask_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_filter_settling_mask_get(stmdev_ctx_t* ctx, uint8_t* val); + +typedef enum { + LSM6DS3TR_C_USE_SLOPE = 0, + LSM6DS3TR_C_USE_HPF = 1, + LSM6DS3TR_C_HP_PATH_ND = 2, /* ERROR CODE */ +} lsm6ds3tr_c_slope_fds_t; +int32_t lsm6ds3tr_c_xl_hp_path_internal_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_slope_fds_t val); +int32_t lsm6ds3tr_c_xl_hp_path_internal_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_slope_fds_t* val); + +typedef enum { + LSM6DS3TR_C_XL_ANA_BW_1k5Hz = 0, + LSM6DS3TR_C_XL_ANA_BW_400Hz = 1, + LSM6DS3TR_C_XL_ANA_BW_ND = 2, /* ERROR CODE */ +} lsm6ds3tr_c_bw0_xl_t; +int32_t lsm6ds3tr_c_xl_filter_analog_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_bw0_xl_t val); +int32_t lsm6ds3tr_c_xl_filter_analog_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_bw0_xl_t* val); + +typedef enum { + LSM6DS3TR_C_XL_LP1_ODR_DIV_2 = 0, + LSM6DS3TR_C_XL_LP1_ODR_DIV_4 = 1, + LSM6DS3TR_C_XL_LP1_NA = 2, /* ERROR CODE */ +} lsm6ds3tr_c_lpf1_bw_sel_t; +int32_t lsm6ds3tr_c_xl_lp1_bandwidth_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_lpf1_bw_sel_t val); +int32_t lsm6ds3tr_c_xl_lp1_bandwidth_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_lpf1_bw_sel_t* val); + +typedef enum { + LSM6DS3TR_C_XL_LOW_LAT_LP_ODR_DIV_50 = 0x00, + LSM6DS3TR_C_XL_LOW_LAT_LP_ODR_DIV_100 = 0x01, + LSM6DS3TR_C_XL_LOW_LAT_LP_ODR_DIV_9 = 0x02, + LSM6DS3TR_C_XL_LOW_LAT_LP_ODR_DIV_400 = 0x03, + LSM6DS3TR_C_XL_LOW_NOISE_LP_ODR_DIV_50 = 0x10, + LSM6DS3TR_C_XL_LOW_NOISE_LP_ODR_DIV_100 = 0x11, + LSM6DS3TR_C_XL_LOW_NOISE_LP_ODR_DIV_9 = 0x12, + LSM6DS3TR_C_XL_LOW_NOISE_LP_ODR_DIV_400 = 0x13, + LSM6DS3TR_C_XL_LP_NA = 0x20, /* ERROR CODE */ +} lsm6ds3tr_c_input_composite_t; +int32_t lsm6ds3tr_c_xl_lp2_bandwidth_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_input_composite_t val); +int32_t lsm6ds3tr_c_xl_lp2_bandwidth_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_input_composite_t* val); + +int32_t lsm6ds3tr_c_xl_reference_mode_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_xl_reference_mode_get(stmdev_ctx_t* ctx, uint8_t* val); + +typedef enum { + LSM6DS3TR_C_XL_HP_ODR_DIV_4 = 0x00, /* Slope filter */ + LSM6DS3TR_C_XL_HP_ODR_DIV_100 = 0x01, + LSM6DS3TR_C_XL_HP_ODR_DIV_9 = 0x02, + LSM6DS3TR_C_XL_HP_ODR_DIV_400 = 0x03, + LSM6DS3TR_C_XL_HP_NA = 0x10, /* ERROR CODE */ +} lsm6ds3tr_c_hpcf_xl_t; +int32_t lsm6ds3tr_c_xl_hp_bandwidth_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_hpcf_xl_t val); +int32_t lsm6ds3tr_c_xl_hp_bandwidth_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_hpcf_xl_t* val); + +typedef enum { + LSM6DS3TR_C_LP2_ONLY = 0x00, + + LSM6DS3TR_C_HP_16mHz_LP2 = 0x80, + LSM6DS3TR_C_HP_65mHz_LP2 = 0x90, + LSM6DS3TR_C_HP_260mHz_LP2 = 0xA0, + LSM6DS3TR_C_HP_1Hz04_LP2 = 0xB0, + + LSM6DS3TR_C_HP_DISABLE_LP1_LIGHT = 0x0A, + LSM6DS3TR_C_HP_DISABLE_LP1_NORMAL = 0x09, + LSM6DS3TR_C_HP_DISABLE_LP_STRONG = 0x08, + LSM6DS3TR_C_HP_DISABLE_LP1_AGGRESSIVE = 0x0B, + + LSM6DS3TR_C_HP_16mHz_LP1_LIGHT = 0x8A, + LSM6DS3TR_C_HP_65mHz_LP1_NORMAL = 0x99, + LSM6DS3TR_C_HP_260mHz_LP1_STRONG = 0xA8, + LSM6DS3TR_C_HP_1Hz04_LP1_AGGRESSIVE = 0xBB, + + LSM6DS3TR_C_HP_GY_BAND_NA = 0xFF, /* ERROR CODE */ +} lsm6ds3tr_c_lpf1_sel_g_t; +int32_t lsm6ds3tr_c_gy_band_pass_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_lpf1_sel_g_t val); +int32_t lsm6ds3tr_c_gy_band_pass_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_lpf1_sel_g_t* val); + +typedef enum { + LSM6DS3TR_C_SPI_4_WIRE = 0, + LSM6DS3TR_C_SPI_3_WIRE = 1, + LSM6DS3TR_C_SPI_MODE_ND = 2, /* ERROR CODE */ +} lsm6ds3tr_c_sim_t; +int32_t lsm6ds3tr_c_spi_mode_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_sim_t val); +int32_t lsm6ds3tr_c_spi_mode_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_sim_t* val); + +typedef enum { + LSM6DS3TR_C_I2C_ENABLE = 0, + LSM6DS3TR_C_I2C_DISABLE = 1, + LSM6DS3TR_C_I2C_MODE_ND = 2, /* ERROR CODE */ +} lsm6ds3tr_c_i2c_disable_t; +int32_t lsm6ds3tr_c_i2c_interface_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_i2c_disable_t val); +int32_t lsm6ds3tr_c_i2c_interface_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_i2c_disable_t* val); + +typedef struct { + uint8_t int1_drdy_xl : 1; + uint8_t int1_drdy_g : 1; + uint8_t int1_boot : 1; + uint8_t int1_fth : 1; + uint8_t int1_fifo_ovr : 1; + uint8_t int1_full_flag : 1; + uint8_t int1_sign_mot : 1; + uint8_t int1_step_detector : 1; + uint8_t int1_timer : 1; + uint8_t int1_tilt : 1; + uint8_t int1_6d : 1; + uint8_t int1_double_tap : 1; + uint8_t int1_ff : 1; + uint8_t int1_wu : 1; + uint8_t int1_single_tap : 1; + uint8_t int1_inact_state : 1; + uint8_t den_drdy_int1 : 1; + uint8_t drdy_on_int1 : 1; +} lsm6ds3tr_c_int1_route_t; +int32_t lsm6ds3tr_c_pin_int1_route_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_int1_route_t val); +int32_t lsm6ds3tr_c_pin_int1_route_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_int1_route_t* val); + +typedef struct { + uint8_t int2_drdy_xl : 1; + uint8_t int2_drdy_g : 1; + uint8_t int2_drdy_temp : 1; + uint8_t int2_fth : 1; + uint8_t int2_fifo_ovr : 1; + uint8_t int2_full_flag : 1; + uint8_t int2_step_count_ov : 1; + uint8_t int2_step_delta : 1; + uint8_t int2_iron : 1; + uint8_t int2_tilt : 1; + uint8_t int2_6d : 1; + uint8_t int2_double_tap : 1; + uint8_t int2_ff : 1; + uint8_t int2_wu : 1; + uint8_t int2_single_tap : 1; + uint8_t int2_inact_state : 1; + uint8_t int2_wrist_tilt : 1; +} lsm6ds3tr_c_int2_route_t; +int32_t lsm6ds3tr_c_pin_int2_route_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_int2_route_t val); +int32_t lsm6ds3tr_c_pin_int2_route_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_int2_route_t* val); + +typedef enum { + LSM6DS3TR_C_PUSH_PULL = 0, + LSM6DS3TR_C_OPEN_DRAIN = 1, + LSM6DS3TR_C_PIN_MODE_ND = 2, /* ERROR CODE */ +} lsm6ds3tr_c_pp_od_t; +int32_t lsm6ds3tr_c_pin_mode_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_pp_od_t val); +int32_t lsm6ds3tr_c_pin_mode_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_pp_od_t* val); + +typedef enum { + LSM6DS3TR_C_ACTIVE_HIGH = 0, + LSM6DS3TR_C_ACTIVE_LOW = 1, + LSM6DS3TR_C_POLARITY_ND = 2, /* ERROR CODE */ +} lsm6ds3tr_c_h_lactive_t; +int32_t lsm6ds3tr_c_pin_polarity_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_h_lactive_t val); +int32_t lsm6ds3tr_c_pin_polarity_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_h_lactive_t* val); + +int32_t lsm6ds3tr_c_all_on_int1_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_all_on_int1_get(stmdev_ctx_t* ctx, uint8_t* val); + +typedef enum { + LSM6DS3TR_C_INT_PULSED = 0, + LSM6DS3TR_C_INT_LATCHED = 1, + LSM6DS3TR_C_INT_MODE = 2, /* ERROR CODE */ +} lsm6ds3tr_c_lir_t; +int32_t lsm6ds3tr_c_int_notification_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_lir_t val); +int32_t lsm6ds3tr_c_int_notification_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_lir_t* val); + +int32_t lsm6ds3tr_c_wkup_threshold_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_wkup_threshold_get(stmdev_ctx_t* ctx, uint8_t* val); + +int32_t lsm6ds3tr_c_wkup_dur_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_wkup_dur_get(stmdev_ctx_t* ctx, uint8_t* val); + +int32_t lsm6ds3tr_c_gy_sleep_mode_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_gy_sleep_mode_get(stmdev_ctx_t* ctx, uint8_t* val); + +typedef enum { + LSM6DS3TR_C_PROPERTY_DISABLE = 0, + LSM6DS3TR_C_XL_12Hz5_GY_NOT_AFFECTED = 1, + LSM6DS3TR_C_XL_12Hz5_GY_SLEEP = 2, + LSM6DS3TR_C_XL_12Hz5_GY_PD = 3, + LSM6DS3TR_C_ACT_MODE_ND = 4, /* ERROR CODE */ +} lsm6ds3tr_c_inact_en_t; +int32_t lsm6ds3tr_c_act_mode_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_inact_en_t val); +int32_t lsm6ds3tr_c_act_mode_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_inact_en_t* val); + +int32_t lsm6ds3tr_c_act_sleep_dur_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_act_sleep_dur_get(stmdev_ctx_t* ctx, uint8_t* val); + +int32_t lsm6ds3tr_c_tap_src_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_tap_src_t* val); + +int32_t lsm6ds3tr_c_tap_detection_on_z_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_tap_detection_on_z_get(stmdev_ctx_t* ctx, uint8_t* val); + +int32_t lsm6ds3tr_c_tap_detection_on_y_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_tap_detection_on_y_get(stmdev_ctx_t* ctx, uint8_t* val); + +int32_t lsm6ds3tr_c_tap_detection_on_x_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_tap_detection_on_x_get(stmdev_ctx_t* ctx, uint8_t* val); + +int32_t lsm6ds3tr_c_tap_threshold_x_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_tap_threshold_x_get(stmdev_ctx_t* ctx, uint8_t* val); + +int32_t lsm6ds3tr_c_tap_shock_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_tap_shock_get(stmdev_ctx_t* ctx, uint8_t* val); + +int32_t lsm6ds3tr_c_tap_quiet_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_tap_quiet_get(stmdev_ctx_t* ctx, uint8_t* val); + +int32_t lsm6ds3tr_c_tap_dur_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_tap_dur_get(stmdev_ctx_t* ctx, uint8_t* val); + +typedef enum { + LSM6DS3TR_C_ONLY_SINGLE = 0, + LSM6DS3TR_C_BOTH_SINGLE_DOUBLE = 1, + LSM6DS3TR_C_TAP_MODE_ND = 2, /* ERROR CODE */ +} lsm6ds3tr_c_single_double_tap_t; +int32_t lsm6ds3tr_c_tap_mode_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_single_double_tap_t val); +int32_t lsm6ds3tr_c_tap_mode_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_single_double_tap_t* val); + +typedef enum { + LSM6DS3TR_C_ODR_DIV_2_FEED = 0, + LSM6DS3TR_C_LPF2_FEED = 1, + LSM6DS3TR_C_6D_FEED_ND = 2, /* ERROR CODE */ +} lsm6ds3tr_c_low_pass_on_6d_t; +int32_t lsm6ds3tr_c_6d_feed_data_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_low_pass_on_6d_t val); +int32_t lsm6ds3tr_c_6d_feed_data_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_low_pass_on_6d_t* val); + +typedef enum { + LSM6DS3TR_C_DEG_80 = 0, + LSM6DS3TR_C_DEG_70 = 1, + LSM6DS3TR_C_DEG_60 = 2, + LSM6DS3TR_C_DEG_50 = 3, + LSM6DS3TR_C_6D_TH_ND = 4, /* ERROR CODE */ +} lsm6ds3tr_c_sixd_ths_t; +int32_t lsm6ds3tr_c_6d_threshold_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_sixd_ths_t val); +int32_t lsm6ds3tr_c_6d_threshold_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_sixd_ths_t* val); + +int32_t lsm6ds3tr_c_4d_mode_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_4d_mode_get(stmdev_ctx_t* ctx, uint8_t* val); + +int32_t lsm6ds3tr_c_ff_dur_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_ff_dur_get(stmdev_ctx_t* ctx, uint8_t* val); + +typedef enum { + LSM6DS3TR_C_FF_TSH_156mg = 0, + LSM6DS3TR_C_FF_TSH_219mg = 1, + LSM6DS3TR_C_FF_TSH_250mg = 2, + LSM6DS3TR_C_FF_TSH_312mg = 3, + LSM6DS3TR_C_FF_TSH_344mg = 4, + LSM6DS3TR_C_FF_TSH_406mg = 5, + LSM6DS3TR_C_FF_TSH_469mg = 6, + LSM6DS3TR_C_FF_TSH_500mg = 7, + LSM6DS3TR_C_FF_TSH_ND = 8, /* ERROR CODE */ +} lsm6ds3tr_c_ff_ths_t; +int32_t lsm6ds3tr_c_ff_threshold_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_ff_ths_t val); +int32_t lsm6ds3tr_c_ff_threshold_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_ff_ths_t* val); + +int32_t lsm6ds3tr_c_fifo_watermark_set(stmdev_ctx_t* ctx, uint16_t val); +int32_t lsm6ds3tr_c_fifo_watermark_get(stmdev_ctx_t* ctx, uint16_t* val); + +int32_t lsm6ds3tr_c_fifo_data_level_get(stmdev_ctx_t* ctx, uint16_t* val); + +int32_t lsm6ds3tr_c_fifo_wtm_flag_get(stmdev_ctx_t* ctx, uint8_t* val); + +int32_t lsm6ds3tr_c_fifo_pattern_get(stmdev_ctx_t* ctx, uint16_t* val); + +int32_t lsm6ds3tr_c_fifo_temp_batch_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_fifo_temp_batch_get(stmdev_ctx_t* ctx, uint8_t* val); + +typedef enum { + LSM6DS3TR_C_TRG_XL_GY_DRDY = 0, + LSM6DS3TR_C_TRG_STEP_DETECT = 1, + LSM6DS3TR_C_TRG_SH_DRDY = 2, + LSM6DS3TR_C_TRG_SH_ND = 3, /* ERROR CODE */ +} lsm6ds3tr_c_trigger_fifo_t; +int32_t lsm6ds3tr_c_fifo_write_trigger_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_trigger_fifo_t val); +int32_t lsm6ds3tr_c_fifo_write_trigger_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_trigger_fifo_t* val); + +int32_t lsm6ds3tr_c_fifo_pedo_and_timestamp_batch_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_fifo_pedo_and_timestamp_batch_get(stmdev_ctx_t* ctx, uint8_t* val); + +typedef enum { + LSM6DS3TR_C_FIFO_XL_DISABLE = 0, + LSM6DS3TR_C_FIFO_XL_NO_DEC = 1, + LSM6DS3TR_C_FIFO_XL_DEC_2 = 2, + LSM6DS3TR_C_FIFO_XL_DEC_3 = 3, + LSM6DS3TR_C_FIFO_XL_DEC_4 = 4, + LSM6DS3TR_C_FIFO_XL_DEC_8 = 5, + LSM6DS3TR_C_FIFO_XL_DEC_16 = 6, + LSM6DS3TR_C_FIFO_XL_DEC_32 = 7, + LSM6DS3TR_C_FIFO_XL_DEC_ND = 8, /* ERROR CODE */ +} lsm6ds3tr_c_dec_fifo_xl_t; +int32_t lsm6ds3tr_c_fifo_xl_batch_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_dec_fifo_xl_t val); +int32_t lsm6ds3tr_c_fifo_xl_batch_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_dec_fifo_xl_t* val); + +typedef enum { + LSM6DS3TR_C_FIFO_GY_DISABLE = 0, + LSM6DS3TR_C_FIFO_GY_NO_DEC = 1, + LSM6DS3TR_C_FIFO_GY_DEC_2 = 2, + LSM6DS3TR_C_FIFO_GY_DEC_3 = 3, + LSM6DS3TR_C_FIFO_GY_DEC_4 = 4, + LSM6DS3TR_C_FIFO_GY_DEC_8 = 5, + LSM6DS3TR_C_FIFO_GY_DEC_16 = 6, + LSM6DS3TR_C_FIFO_GY_DEC_32 = 7, + LSM6DS3TR_C_FIFO_GY_DEC_ND = 8, /* ERROR CODE */ +} lsm6ds3tr_c_dec_fifo_gyro_t; +int32_t lsm6ds3tr_c_fifo_gy_batch_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_dec_fifo_gyro_t val); +int32_t lsm6ds3tr_c_fifo_gy_batch_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_dec_fifo_gyro_t* val); + +typedef enum { + LSM6DS3TR_C_FIFO_DS3_DISABLE = 0, + LSM6DS3TR_C_FIFO_DS3_NO_DEC = 1, + LSM6DS3TR_C_FIFO_DS3_DEC_2 = 2, + LSM6DS3TR_C_FIFO_DS3_DEC_3 = 3, + LSM6DS3TR_C_FIFO_DS3_DEC_4 = 4, + LSM6DS3TR_C_FIFO_DS3_DEC_8 = 5, + LSM6DS3TR_C_FIFO_DS3_DEC_16 = 6, + LSM6DS3TR_C_FIFO_DS3_DEC_32 = 7, + LSM6DS3TR_C_FIFO_DS3_DEC_ND = 8, /* ERROR CODE */ +} lsm6ds3tr_c_dec_ds3_fifo_t; +int32_t lsm6ds3tr_c_fifo_dataset_3_batch_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_dec_ds3_fifo_t val); +int32_t lsm6ds3tr_c_fifo_dataset_3_batch_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_dec_ds3_fifo_t* val); + +typedef enum { + LSM6DS3TR_C_FIFO_DS4_DISABLE = 0, + LSM6DS3TR_C_FIFO_DS4_NO_DEC = 1, + LSM6DS3TR_C_FIFO_DS4_DEC_2 = 2, + LSM6DS3TR_C_FIFO_DS4_DEC_3 = 3, + LSM6DS3TR_C_FIFO_DS4_DEC_4 = 4, + LSM6DS3TR_C_FIFO_DS4_DEC_8 = 5, + LSM6DS3TR_C_FIFO_DS4_DEC_16 = 6, + LSM6DS3TR_C_FIFO_DS4_DEC_32 = 7, + LSM6DS3TR_C_FIFO_DS4_DEC_ND = 8, /* ERROR CODE */ +} lsm6ds3tr_c_dec_ds4_fifo_t; +int32_t lsm6ds3tr_c_fifo_dataset_4_batch_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_dec_ds4_fifo_t val); +int32_t lsm6ds3tr_c_fifo_dataset_4_batch_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_dec_ds4_fifo_t* val); + +int32_t lsm6ds3tr_c_fifo_xl_gy_8bit_format_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_fifo_xl_gy_8bit_format_get(stmdev_ctx_t* ctx, uint8_t* val); + +int32_t lsm6ds3tr_c_fifo_stop_on_wtm_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_fifo_stop_on_wtm_get(stmdev_ctx_t* ctx, uint8_t* val); + +typedef enum { + LSM6DS3TR_C_BYPASS_MODE = 0, + LSM6DS3TR_C_FIFO_MODE = 1, + LSM6DS3TR_C_STREAM_TO_FIFO_MODE = 3, + LSM6DS3TR_C_BYPASS_TO_STREAM_MODE = 4, + LSM6DS3TR_C_STREAM_MODE = 6, + LSM6DS3TR_C_FIFO_MODE_ND = 8, /* ERROR CODE */ +} lsm6ds3tr_c_fifo_mode_t; +int32_t lsm6ds3tr_c_fifo_mode_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_fifo_mode_t val); +int32_t lsm6ds3tr_c_fifo_mode_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_fifo_mode_t* val); + +typedef enum { + LSM6DS3TR_C_FIFO_DISABLE = 0, + LSM6DS3TR_C_FIFO_12Hz5 = 1, + LSM6DS3TR_C_FIFO_26Hz = 2, + LSM6DS3TR_C_FIFO_52Hz = 3, + LSM6DS3TR_C_FIFO_104Hz = 4, + LSM6DS3TR_C_FIFO_208Hz = 5, + LSM6DS3TR_C_FIFO_416Hz = 6, + LSM6DS3TR_C_FIFO_833Hz = 7, + LSM6DS3TR_C_FIFO_1k66Hz = 8, + LSM6DS3TR_C_FIFO_3k33Hz = 9, + LSM6DS3TR_C_FIFO_6k66Hz = 10, + LSM6DS3TR_C_FIFO_RATE_ND = 11, /* ERROR CODE */ +} lsm6ds3tr_c_odr_fifo_t; +int32_t lsm6ds3tr_c_fifo_data_rate_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_odr_fifo_t val); +int32_t lsm6ds3tr_c_fifo_data_rate_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_odr_fifo_t* val); + +typedef enum { + LSM6DS3TR_C_DEN_ACT_LOW = 0, + LSM6DS3TR_C_DEN_ACT_HIGH = 1, + LSM6DS3TR_C_DEN_POL_ND = 2, /* ERROR CODE */ +} lsm6ds3tr_c_den_lh_t; +int32_t lsm6ds3tr_c_den_polarity_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_den_lh_t val); +int32_t lsm6ds3tr_c_den_polarity_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_den_lh_t* val); + +typedef enum { + LSM6DS3TR_C_DEN_DISABLE = 0, + LSM6DS3TR_C_LEVEL_FIFO = 6, + LSM6DS3TR_C_LEVEL_LETCHED = 3, + LSM6DS3TR_C_LEVEL_TRIGGER = 2, + LSM6DS3TR_C_EDGE_TRIGGER = 4, + LSM6DS3TR_C_DEN_MODE_ND = 5, /* ERROR CODE */ +} lsm6ds3tr_c_den_mode_t; +int32_t lsm6ds3tr_c_den_mode_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_den_mode_t val); +int32_t lsm6ds3tr_c_den_mode_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_den_mode_t* val); + +typedef enum { + LSM6DS3TR_C_STAMP_IN_GY_DATA = 0, + LSM6DS3TR_C_STAMP_IN_XL_DATA = 1, + LSM6DS3TR_C_STAMP_IN_GY_XL_DATA = 2, + LSM6DS3TR_C_DEN_STAMP_ND = 3, /* ERROR CODE */ +} lsm6ds3tr_c_den_xl_en_t; +int32_t lsm6ds3tr_c_den_enable_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_den_xl_en_t val); +int32_t lsm6ds3tr_c_den_enable_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_den_xl_en_t* val); + +int32_t lsm6ds3tr_c_den_mark_axis_z_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_den_mark_axis_z_get(stmdev_ctx_t* ctx, uint8_t* val); + +int32_t lsm6ds3tr_c_den_mark_axis_y_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_den_mark_axis_y_get(stmdev_ctx_t* ctx, uint8_t* val); + +int32_t lsm6ds3tr_c_den_mark_axis_x_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_den_mark_axis_x_get(stmdev_ctx_t* ctx, uint8_t* val); + +int32_t lsm6ds3tr_c_pedo_step_reset_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_pedo_step_reset_get(stmdev_ctx_t* ctx, uint8_t* val); + +int32_t lsm6ds3tr_c_pedo_sens_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_pedo_sens_get(stmdev_ctx_t* ctx, uint8_t* val); + +int32_t lsm6ds3tr_c_pedo_threshold_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_pedo_threshold_get(stmdev_ctx_t* ctx, uint8_t* val); + +typedef enum { + LSM6DS3TR_C_PEDO_AT_2g = 0, + LSM6DS3TR_C_PEDO_AT_4g = 1, + LSM6DS3TR_C_PEDO_FS_ND = 2, /* ERROR CODE */ +} lsm6ds3tr_c_pedo_fs_t; +int32_t lsm6ds3tr_c_pedo_full_scale_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_pedo_fs_t val); +int32_t lsm6ds3tr_c_pedo_full_scale_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_pedo_fs_t* val); + +int32_t lsm6ds3tr_c_pedo_debounce_steps_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_pedo_debounce_steps_get(stmdev_ctx_t* ctx, uint8_t* val); + +int32_t lsm6ds3tr_c_pedo_timeout_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_pedo_timeout_get(stmdev_ctx_t* ctx, uint8_t* val); + +int32_t lsm6ds3tr_c_pedo_steps_period_set(stmdev_ctx_t* ctx, uint8_t* buff); +int32_t lsm6ds3tr_c_pedo_steps_period_get(stmdev_ctx_t* ctx, uint8_t* buff); + +int32_t lsm6ds3tr_c_motion_sens_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_motion_sens_get(stmdev_ctx_t* ctx, uint8_t* val); + +int32_t lsm6ds3tr_c_motion_threshold_set(stmdev_ctx_t* ctx, uint8_t* buff); +int32_t lsm6ds3tr_c_motion_threshold_get(stmdev_ctx_t* ctx, uint8_t* buff); + +int32_t lsm6ds3tr_c_tilt_sens_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_tilt_sens_get(stmdev_ctx_t* ctx, uint8_t* val); + +int32_t lsm6ds3tr_c_wrist_tilt_sens_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_wrist_tilt_sens_get(stmdev_ctx_t* ctx, uint8_t* val); + +int32_t lsm6ds3tr_c_tilt_latency_set(stmdev_ctx_t* ctx, uint8_t* buff); +int32_t lsm6ds3tr_c_tilt_latency_get(stmdev_ctx_t* ctx, uint8_t* buff); + +int32_t lsm6ds3tr_c_tilt_threshold_set(stmdev_ctx_t* ctx, uint8_t* buff); +int32_t lsm6ds3tr_c_tilt_threshold_get(stmdev_ctx_t* ctx, uint8_t* buff); + +int32_t lsm6ds3tr_c_tilt_src_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_a_wrist_tilt_mask_t* val); +int32_t lsm6ds3tr_c_tilt_src_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_a_wrist_tilt_mask_t* val); + +int32_t lsm6ds3tr_c_mag_soft_iron_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_mag_soft_iron_get(stmdev_ctx_t* ctx, uint8_t* val); + +int32_t lsm6ds3tr_c_mag_hard_iron_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_mag_hard_iron_get(stmdev_ctx_t* ctx, uint8_t* val); + +int32_t lsm6ds3tr_c_mag_soft_iron_mat_set(stmdev_ctx_t* ctx, uint8_t* buff); +int32_t lsm6ds3tr_c_mag_soft_iron_mat_get(stmdev_ctx_t* ctx, uint8_t* buff); + +int32_t lsm6ds3tr_c_mag_offset_set(stmdev_ctx_t* ctx, int16_t* val); +int32_t lsm6ds3tr_c_mag_offset_get(stmdev_ctx_t* ctx, int16_t* val); + +int32_t lsm6ds3tr_c_func_en_set(stmdev_ctx_t* ctx, uint8_t val); + +int32_t lsm6ds3tr_c_sh_sync_sens_frame_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_sh_sync_sens_frame_get(stmdev_ctx_t* ctx, uint8_t* val); + +typedef enum { + LSM6DS3TR_C_RES_RATIO_2_11 = 0, + LSM6DS3TR_C_RES_RATIO_2_12 = 1, + LSM6DS3TR_C_RES_RATIO_2_13 = 2, + LSM6DS3TR_C_RES_RATIO_2_14 = 3, + LSM6DS3TR_C_RES_RATIO_ND = 4, /* ERROR CODE */ +} lsm6ds3tr_c_rr_t; +int32_t lsm6ds3tr_c_sh_sync_sens_ratio_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_rr_t val); +int32_t lsm6ds3tr_c_sh_sync_sens_ratio_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_rr_t* val); + +int32_t lsm6ds3tr_c_sh_master_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_sh_master_get(stmdev_ctx_t* ctx, uint8_t* val); + +int32_t lsm6ds3tr_c_sh_pass_through_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_sh_pass_through_get(stmdev_ctx_t* ctx, uint8_t* val); + +typedef enum { + LSM6DS3TR_C_EXT_PULL_UP = 0, + LSM6DS3TR_C_INTERNAL_PULL_UP = 1, + LSM6DS3TR_C_SH_PIN_MODE = 2, /* ERROR CODE */ +} lsm6ds3tr_c_pull_up_en_t; +int32_t lsm6ds3tr_c_sh_pin_mode_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_pull_up_en_t val); +int32_t lsm6ds3tr_c_sh_pin_mode_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_pull_up_en_t* val); + +typedef enum { + LSM6DS3TR_C_XL_GY_DRDY = 0, + LSM6DS3TR_C_EXT_ON_INT2_PIN = 1, + LSM6DS3TR_C_SH_SYNCRO_ND = 2, /* ERROR CODE */ +} lsm6ds3tr_c_start_config_t; +int32_t lsm6ds3tr_c_sh_syncro_mode_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_start_config_t val); +int32_t lsm6ds3tr_c_sh_syncro_mode_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_start_config_t* val); + +int32_t lsm6ds3tr_c_sh_drdy_on_int1_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_sh_drdy_on_int1_get(stmdev_ctx_t* ctx, uint8_t* val); + +typedef struct { + lsm6ds3tr_c_sensorhub1_reg_t sh_byte_1; + lsm6ds3tr_c_sensorhub2_reg_t sh_byte_2; + lsm6ds3tr_c_sensorhub3_reg_t sh_byte_3; + lsm6ds3tr_c_sensorhub4_reg_t sh_byte_4; + lsm6ds3tr_c_sensorhub5_reg_t sh_byte_5; + lsm6ds3tr_c_sensorhub6_reg_t sh_byte_6; + lsm6ds3tr_c_sensorhub7_reg_t sh_byte_7; + lsm6ds3tr_c_sensorhub8_reg_t sh_byte_8; + lsm6ds3tr_c_sensorhub9_reg_t sh_byte_9; + lsm6ds3tr_c_sensorhub10_reg_t sh_byte_10; + lsm6ds3tr_c_sensorhub11_reg_t sh_byte_11; + lsm6ds3tr_c_sensorhub12_reg_t sh_byte_12; + lsm6ds3tr_c_sensorhub13_reg_t sh_byte_13; + lsm6ds3tr_c_sensorhub14_reg_t sh_byte_14; + lsm6ds3tr_c_sensorhub15_reg_t sh_byte_15; + lsm6ds3tr_c_sensorhub16_reg_t sh_byte_16; + lsm6ds3tr_c_sensorhub17_reg_t sh_byte_17; + lsm6ds3tr_c_sensorhub18_reg_t sh_byte_18; +} lsm6ds3tr_c_emb_sh_read_t; +int32_t lsm6ds3tr_c_sh_read_data_raw_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_emb_sh_read_t* val); + +int32_t lsm6ds3tr_c_sh_cmd_sens_sync_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_sh_cmd_sens_sync_get(stmdev_ctx_t* ctx, uint8_t* val); + +int32_t lsm6ds3tr_c_sh_spi_sync_error_set(stmdev_ctx_t* ctx, uint8_t val); +int32_t lsm6ds3tr_c_sh_spi_sync_error_get(stmdev_ctx_t* ctx, uint8_t* val); + +typedef enum { + LSM6DS3TR_C_SLV_0 = 0, + LSM6DS3TR_C_SLV_0_1 = 1, + LSM6DS3TR_C_SLV_0_1_2 = 2, + LSM6DS3TR_C_SLV_0_1_2_3 = 3, + LSM6DS3TR_C_SLV_EN_ND = 4, /* ERROR CODE */ +} lsm6ds3tr_c_aux_sens_on_t; +int32_t lsm6ds3tr_c_sh_num_of_dev_connected_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_aux_sens_on_t val); +int32_t lsm6ds3tr_c_sh_num_of_dev_connected_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_aux_sens_on_t* val); + +typedef struct { + uint8_t slv0_add; + uint8_t slv0_subadd; + uint8_t slv0_data; +} lsm6ds3tr_c_sh_cfg_write_t; +int32_t lsm6ds3tr_c_sh_cfg_write(stmdev_ctx_t* ctx, lsm6ds3tr_c_sh_cfg_write_t* val); + +typedef struct { + uint8_t slv_add; + uint8_t slv_subadd; + uint8_t slv_len; +} lsm6ds3tr_c_sh_cfg_read_t; +int32_t lsm6ds3tr_c_sh_slv0_cfg_read(stmdev_ctx_t* ctx, lsm6ds3tr_c_sh_cfg_read_t* val); +int32_t lsm6ds3tr_c_sh_slv1_cfg_read(stmdev_ctx_t* ctx, lsm6ds3tr_c_sh_cfg_read_t* val); +int32_t lsm6ds3tr_c_sh_slv2_cfg_read(stmdev_ctx_t* ctx, lsm6ds3tr_c_sh_cfg_read_t* val); +int32_t lsm6ds3tr_c_sh_slv3_cfg_read(stmdev_ctx_t* ctx, lsm6ds3tr_c_sh_cfg_read_t* val); + +typedef enum { + LSM6DS3TR_C_SL0_NO_DEC = 0, + LSM6DS3TR_C_SL0_DEC_2 = 1, + LSM6DS3TR_C_SL0_DEC_4 = 2, + LSM6DS3TR_C_SL0_DEC_8 = 3, + LSM6DS3TR_C_SL0_DEC_ND = 4, /* ERROR CODE */ +} lsm6ds3tr_c_slave0_rate_t; +int32_t lsm6ds3tr_c_sh_slave_0_dec_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_slave0_rate_t val); +int32_t lsm6ds3tr_c_sh_slave_0_dec_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_slave0_rate_t* val); + +typedef enum { + LSM6DS3TR_C_EACH_SH_CYCLE = 0, + LSM6DS3TR_C_ONLY_FIRST_CYCLE = 1, + LSM6DS3TR_C_SH_WR_MODE_ND = 2, /* ERROR CODE */ +} lsm6ds3tr_c_write_once_t; +int32_t lsm6ds3tr_c_sh_write_mode_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_write_once_t val); +int32_t lsm6ds3tr_c_sh_write_mode_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_write_once_t* val); + +typedef enum { + LSM6DS3TR_C_SL1_NO_DEC = 0, + LSM6DS3TR_C_SL1_DEC_2 = 1, + LSM6DS3TR_C_SL1_DEC_4 = 2, + LSM6DS3TR_C_SL1_DEC_8 = 3, + LSM6DS3TR_C_SL1_DEC_ND = 4, /* ERROR CODE */ +} lsm6ds3tr_c_slave1_rate_t; +int32_t lsm6ds3tr_c_sh_slave_1_dec_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_slave1_rate_t val); +int32_t lsm6ds3tr_c_sh_slave_1_dec_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_slave1_rate_t* val); + +typedef enum { + LSM6DS3TR_C_SL2_NO_DEC = 0, + LSM6DS3TR_C_SL2_DEC_2 = 1, + LSM6DS3TR_C_SL2_DEC_4 = 2, + LSM6DS3TR_C_SL2_DEC_8 = 3, + LSM6DS3TR_C_SL2_DEC_ND = 4, /* ERROR CODE */ +} lsm6ds3tr_c_slave2_rate_t; +int32_t lsm6ds3tr_c_sh_slave_2_dec_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_slave2_rate_t val); +int32_t lsm6ds3tr_c_sh_slave_2_dec_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_slave2_rate_t* val); + +typedef enum { + LSM6DS3TR_C_SL3_NO_DEC = 0, + LSM6DS3TR_C_SL3_DEC_2 = 1, + LSM6DS3TR_C_SL3_DEC_4 = 2, + LSM6DS3TR_C_SL3_DEC_8 = 3, + LSM6DS3TR_C_SL3_DEC_ND = 4, /* ERROR CODE */ +} lsm6ds3tr_c_slave3_rate_t; +int32_t lsm6ds3tr_c_sh_slave_3_dec_set(stmdev_ctx_t* ctx, lsm6ds3tr_c_slave3_rate_t val); +int32_t lsm6ds3tr_c_sh_slave_3_dec_get(stmdev_ctx_t* ctx, lsm6ds3tr_c_slave3_rate_t* val); + +/** + * @} + * + */ + +#ifdef __cplusplus +} +#endif + +#endif /* LSM6DS3TR_C_DRIVER_H */ + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/main_loop.cc b/Applications/Official/DEV_FW/source/airmouse/tracking/main_loop.cc new file mode 100644 index 000000000..2caffb717 --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/main_loop.cc @@ -0,0 +1,189 @@ +#include "main_loop.h" + +#include +#include + +#include "imu/imu.h" +#include "orientation_tracker.h" +#include "calibration_data.h" + +#define TAG "tracker" + +static const float CURSOR_SPEED = 1024.0 / (M_PI / 4); +static const float STABILIZE_BIAS = 16.0; + +float g_yaw = 0; +float g_pitch = 0; +float g_dYaw = 0; +float g_dPitch = 0; +bool firstRead = true; +bool stabilize = true; +CalibrationData calibration; +cardboard::OrientationTracker tracker(10000000l); // 10 ms / 100 Hz +uint64_t ippms, ippms2; + +static inline float clamp(float val) +{ + while (val <= -M_PI) { + val += 2 * M_PI; + } + while (val >= M_PI) { + val -= 2 * M_PI; + } + return val; +} + +static inline float highpass(float oldVal, float newVal) +{ + if (!stabilize) { + return newVal; + } + float delta = clamp(oldVal - newVal); + float alpha = (float) std::max(0.0, 1 - std::pow(std::fabs(delta) * CURSOR_SPEED / STABILIZE_BIAS, 3.0)); + return newVal + alpha * delta; +} + +void sendCurrentState(MouseMoveCallback mouse_move, void *context) +{ + float dX = g_dYaw * CURSOR_SPEED; + float dY = g_dPitch * CURSOR_SPEED; + + // Scale the shift down to fit the protocol. + if (dX > 127) { + dY *= 127.0 / dX; + dX = 127; + } + if (dX < -127) { + dY *= -127.0 / dX; + dX = -127; + } + if (dY > 127) { + dX *= 127.0 / dY; + dY = 127; + } + if (dY < -127) { + dX *= -127.0 / dY; + dY = -127; + } + + const int8_t x = (int8_t)std::floor(dX + 0.5); + const int8_t y = (int8_t)std::floor(dY + 0.5); + + mouse_move(x, y, context); + + // Only subtract the part of the error that was already sent. + if (x != 0) { + g_dYaw -= x / CURSOR_SPEED; + } + if (y != 0) { + g_dPitch -= y / CURSOR_SPEED; + } +} + +void onOrientation(cardboard::Vector4& quaternion) +{ + float q1 = quaternion[0]; // X * sin(T/2) + float q2 = quaternion[1]; // Y * sin(T/2) + float q3 = quaternion[2]; // Z * sin(T/2) + float q0 = quaternion[3]; // cos(T/2) + + float yaw = std::atan2(2 * (q0 * q3 - q1 * q2), (1 - 2 * (q1 * q1 + q3 * q3))); + float pitch = std::asin(2 * (q0 * q1 + q2 * q3)); + // float roll = std::atan2(2 * (q0 * q2 - q1 * q3), (1 - 2 * (q1 * q1 + q2 * q2))); + + if (yaw == NAN || pitch == NAN) { + // NaN case, skip it + return; + } + + if (firstRead) { + g_yaw = yaw; + g_pitch = pitch; + firstRead = false; + } else { + const float newYaw = highpass(g_yaw, yaw); + const float newPitch = highpass(g_pitch, pitch); + + float dYaw = clamp(g_yaw - newYaw); + float dPitch = g_pitch - newPitch; + g_yaw = newYaw; + g_pitch = newPitch; + + // Accumulate the error locally. + g_dYaw += dYaw; + g_dPitch += dPitch; + } +} + +extern "C" { + +void calibration_begin() { + calibration.reset(); + FURI_LOG_I(TAG, "Calibrating"); +} + +bool calibration_step() { + if (calibration.isComplete()) + return true; + + double vec[6]; + if (imu_read(vec) & GYR_DATA_READY) { + cardboard::Vector3 data(vec[3], vec[4], vec[5]); + furi_delay_ms(9); // Artificially limit to ~100Hz + return calibration.add(data); + } + + return false; +} + +void calibration_end() { + CalibrationMedian store; + cardboard::Vector3 median = calibration.getMedian(); + store.x = median[0]; + store.y = median[1]; + store.z = median[2]; + CALIBRATION_DATA_SAVE(&store); +} + +void tracking_begin() { + CalibrationMedian store; + cardboard::Vector3 median = calibration.getMedian(); + if (CALIBRATION_DATA_LOAD(&store)) { + median[0] = store.x; + median[1] = store.y; + median[2] = store.z; + } + + ippms = furi_hal_cortex_instructions_per_microsecond(); + ippms2 = ippms / 2; + tracker.SetCalibration(median); + tracker.Resume(); +} + +void tracking_step(MouseMoveCallback mouse_move, void *context) { + double vec[6]; + int ret = imu_read(vec); + if (ret != 0) { + uint64_t t = (DWT->CYCCNT * 1000llu + ippms2) / ippms; + if (ret & ACC_DATA_READY) { + cardboard::AccelerometerData adata + = { .system_timestamp = t, .sensor_timestamp_ns = t, + .data = cardboard::Vector3(vec[0], vec[1], vec[2]) }; + tracker.OnAccelerometerData(adata); + } + if (ret & GYR_DATA_READY) { + cardboard::GyroscopeData gdata + = { .system_timestamp = t, .sensor_timestamp_ns = t, + .data = cardboard::Vector3(vec[3], vec[4], vec[5]) }; + cardboard::Vector4 pose = tracker.OnGyroscopeData(gdata); + onOrientation(pose); + sendCurrentState(mouse_move, context); + } + } +} + +void tracking_end() { + tracker.Pause(); +} + +} diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/main_loop.h b/Applications/Official/DEV_FW/source/airmouse/tracking/main_loop.h new file mode 100644 index 000000000..cd592161f --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/main_loop.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef bool (*MouseMoveCallback)(int8_t x, int8_t y, void* context); + +void calibration_begin(); +bool calibration_step(); +void calibration_end(); + +void tracking_begin(); +void tracking_step(MouseMoveCallback mouse_move, void* context); +void tracking_end(); + +#ifdef __cplusplus +} +#endif diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/orientation_tracker.cc b/Applications/Official/DEV_FW/source/airmouse/tracking/orientation_tracker.cc new file mode 100644 index 000000000..ac20e9672 --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/orientation_tracker.cc @@ -0,0 +1,95 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "orientation_tracker.h" + +#include "sensors/pose_prediction.h" +#include "util/logging.h" +#include "util/vector.h" +#include "util/vectorutils.h" + +namespace cardboard { + +OrientationTracker::OrientationTracker(const long sampling_period_ns) + : sampling_period_ns_(sampling_period_ns) + , calibration_(Vector3::Zero()) + , is_tracking_(false) + , sensor_fusion_(new SensorFusionEkf()) + , latest_gyroscope_data_({ 0, 0, Vector3::Zero() }) +{ + sensor_fusion_->SetBiasEstimationEnabled(/*kGyroBiasEstimationEnabled*/ true); +} + +void OrientationTracker::SetCalibration(const Vector3& calibration) { + calibration_ = calibration; +} + +void OrientationTracker::Pause() +{ + if (!is_tracking_) { + return; + } + + // Create a gyro event with zero velocity. This effectively stops the prediction. + GyroscopeData event = latest_gyroscope_data_; + event.data = Vector3::Zero(); + + OnGyroscopeData(event); + + is_tracking_ = false; +} + +void OrientationTracker::Resume() { is_tracking_ = true; } + +Vector4 OrientationTracker::GetPose(int64_t timestamp_ns) const +{ + Rotation predicted_rotation; + const PoseState pose_state = sensor_fusion_->GetLatestPoseState(); + if (sensor_fusion_->IsFullyInitialized()) { + predicted_rotation = pose_state.sensor_from_start_rotation; + } else { + CARDBOARD_LOGI("Tracker not fully initialized yet. Using pose prediction only."); + predicted_rotation = pose_prediction::PredictPose(timestamp_ns, pose_state); + } + + return (-predicted_rotation).GetQuaternion(); +} + +void OrientationTracker::OnAccelerometerData(const AccelerometerData& event) +{ + if (!is_tracking_) { + return; + } + sensor_fusion_->ProcessAccelerometerSample(event); +} + +Vector4 OrientationTracker::OnGyroscopeData(const GyroscopeData& event) +{ + if (!is_tracking_) { + return Vector4(); + } + + const GyroscopeData data = { .system_timestamp = event.system_timestamp, + .sensor_timestamp_ns = event.sensor_timestamp_ns, + .data = event.data - calibration_ }; + + latest_gyroscope_data_ = data; + + sensor_fusion_->ProcessGyroscopeSample(data); + + return OrientationTracker::GetPose(data.sensor_timestamp_ns + sampling_period_ns_); +} + +} // namespace cardboard diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/orientation_tracker.h b/Applications/Official/DEV_FW/source/airmouse/tracking/orientation_tracker.h new file mode 100644 index 000000000..fb49c9859 --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/orientation_tracker.h @@ -0,0 +1,68 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include // NOLINT + +#include "sensors/accelerometer_data.h" +#include "sensors/gyroscope_data.h" +#include "sensors/sensor_fusion_ekf.h" +#include "util/rotation.h" + +namespace cardboard { + +// OrientationTracker encapsulates pose tracking by connecting sensors +// to SensorFusion. +// This pose tracker reports poses in display space. +class OrientationTracker { +public: + OrientationTracker(const long sampling_period_ns); + + void SetCalibration(const Vector3& calibration); + + // Pauses tracking and sensors. + void Pause(); + + // Resumes tracking ans sensors. + void Resume(); + + // Gets the predicted pose for a given timestamp. + Vector4 GetPose(int64_t timestamp_ns) const; + + // Function called when receiving AccelerometerData. + // + // @param event sensor event. + void OnAccelerometerData(const AccelerometerData& event); + + // Function called when receiving GyroscopeData. + // + // @param event sensor event. + Vector4 OnGyroscopeData(const GyroscopeData& event); + +private: + long sampling_period_ns_; + Vector3 calibration_; + + std::atomic is_tracking_; + // Sensor Fusion object that stores the internal state of the filter. + std::unique_ptr sensor_fusion_; + // Latest gyroscope data. + GyroscopeData latest_gyroscope_data_; +}; + +} // namespace cardboard diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/accelerometer_data.h b/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/accelerometer_data.h new file mode 100644 index 000000000..bdf3289af --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/accelerometer_data.h @@ -0,0 +1,38 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef CARDBOARD_SDK_SENSORS_ACCELEROMETER_DATA_H_ +#define CARDBOARD_SDK_SENSORS_ACCELEROMETER_DATA_H_ + +#include "../util/vector.h" + +namespace cardboard { + +struct AccelerometerData { + // System wall time. + uint64_t system_timestamp; + + // Sensor clock time in nanoseconds. + uint64_t sensor_timestamp_ns; + + // Acceleration force along the x,y,z axes in m/s^2. This follows android + // specification + // (https://developer.android.com/guide/topics/sensors/sensors_overview.html#sensors-coords). + Vector3 data; +}; + +} // namespace cardboard + +#endif // CARDBOARD_SDK_SENSORS_ACCELEROMETER_DATA_H_ diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/gyroscope_bias_estimator.cc b/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/gyroscope_bias_estimator.cc new file mode 100644 index 000000000..96f2f7346 --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/gyroscope_bias_estimator.cc @@ -0,0 +1,313 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "gyroscope_bias_estimator.h" + +#include +#include // NOLINT + +#include "../util/rotation.h" +#include "../util/vector.h" + +namespace { + +// Cutoff frequencies in Hertz applied to our various signals, and their +// corresponding filters. +const float kAccelerometerLowPassCutOffFrequencyHz = 1.0f; +const float kRotationVelocityBasedAccelerometerLowPassCutOffFrequencyHz = 0.15f; +const float kGyroscopeLowPassCutOffFrequencyHz = 1.0f; +const float kGyroscopeBiasLowPassCutOffFrequencyHz = 0.15f; + +// Note that MEMS IMU are not that precise. +const double kEpsilon = 1.0e-8; + +// Size of the filtering window for the mean and median filter. The larger the +// windows the larger the filter delay. +const int kFilterWindowSize = 5; + +// Threshold used to compare rotation computed from the accelerometer and the +// gyroscope bias. +const double kRatioBetweenGyroBiasAndAccel = 1.5; + +// The minimum sum of weights we need to acquire before returning a bias +// estimation. +const float kMinSumOfWeightsGyroBiasThreshold = 25.0f; + +// Amount of change in m/s^3 we allow on the smoothed accelerometer values to +// consider the phone static. +const double kAccelerometerDeltaStaticThreshold = 0.5; + +// Amount of change in radians/s^2 we allow on the smoothed gyroscope values to +// consider the phone static. +const double kGyroscopeDeltaStaticThreshold = 0.03; + +// If the gyroscope value is above this threshold, don't update the gyroscope +// bias estimation. This threshold is applied to the magnitude of gyroscope +// vectors in radians/s. +const float kGyroscopeForBiasThreshold = 0.30f; + +// Used to monitor if accelerometer and gyroscope have been static for a few +// frames. +const int kStaticFrameDetectionThreshold = 50; + +// Minimum time step between sensor updates. +const double kMinTimestep = 1; // std::chrono::nanoseconds(1); +} // namespace + +namespace cardboard { + +// A helper class to keep track of whether some signal can be considered static +// over specified number of frames. +class GyroscopeBiasEstimator::IsStaticCounter { +public: + // Initializes a counter with the number of consecutive frames we require + // the signal to be static before IsRecentlyStatic returns true. + // + // @param min_static_frames_threshold number of consecutive frames we + // require the signal to be static before IsRecentlyStatic returns true. + explicit IsStaticCounter(int min_static_frames_threshold) + : min_static_frames_threshold_(min_static_frames_threshold) + , consecutive_static_frames_(0) + { + } + + // Specifies whether the current frame is considered static. + // + // @param is_static static flag for current frame. + void AppendFrame(bool is_static) + { + if (is_static) { + ++consecutive_static_frames_; + } else { + consecutive_static_frames_ = 0; + } + } + + // Returns if static movement is assumed. + bool IsRecentlyStatic() const + { + return consecutive_static_frames_ >= min_static_frames_threshold_; + } + // Resets counter. + void Reset() { consecutive_static_frames_ = 0; } + +private: + const int min_static_frames_threshold_; + int consecutive_static_frames_; +}; + +GyroscopeBiasEstimator::GyroscopeBiasEstimator() + : accelerometer_lowpass_filter_(kAccelerometerLowPassCutOffFrequencyHz) + , simulated_gyroscope_from_accelerometer_lowpass_filter_( + kRotationVelocityBasedAccelerometerLowPassCutOffFrequencyHz) + , gyroscope_lowpass_filter_(kGyroscopeLowPassCutOffFrequencyHz) + , gyroscope_bias_lowpass_filter_(kGyroscopeBiasLowPassCutOffFrequencyHz) + , accelerometer_static_counter_(new IsStaticCounter(kStaticFrameDetectionThreshold)) + , gyroscope_static_counter_(new IsStaticCounter(kStaticFrameDetectionThreshold)) + , current_accumulated_weights_gyroscope_bias_(0.f) + , mean_filter_(kFilterWindowSize) + , median_filter_(kFilterWindowSize) + , last_mean_filtered_accelerometer_value_({ 0, 0, 0 }) +{ + Reset(); +} + +GyroscopeBiasEstimator::~GyroscopeBiasEstimator() { } + +void GyroscopeBiasEstimator::Reset() +{ + accelerometer_lowpass_filter_.Reset(); + gyroscope_lowpass_filter_.Reset(); + gyroscope_bias_lowpass_filter_.Reset(); + accelerometer_static_counter_->Reset(); + gyroscope_static_counter_->Reset(); +} + +void GyroscopeBiasEstimator::ProcessGyroscope( + const Vector3& gyroscope_sample, uint64_t timestamp_ns) +{ + // Update gyroscope and gyroscope delta low-pass filters. + gyroscope_lowpass_filter_.AddSample(gyroscope_sample, timestamp_ns); + + const auto smoothed_gyroscope_delta + = gyroscope_sample - gyroscope_lowpass_filter_.GetFilteredData(); + + gyroscope_static_counter_->AppendFrame( + Length(smoothed_gyroscope_delta) < kGyroscopeDeltaStaticThreshold); + + // Only update the bias if the gyroscope and accelerometer signals have been + // relatively static recently. + if (gyroscope_static_counter_->IsRecentlyStatic() + && accelerometer_static_counter_->IsRecentlyStatic()) { + // Reset static counter when updating the bias fails. + if (!UpdateGyroscopeBias(gyroscope_sample, timestamp_ns)) { + // Bias update fails because of large motion, thus reset the static + // counter. + gyroscope_static_counter_->AppendFrame(false); + } + } else { + // Reset weights, if not static. + current_accumulated_weights_gyroscope_bias_ = 0; + } +} + +void GyroscopeBiasEstimator::ProcessAccelerometer( + const Vector3& accelerometer_sample, uint64_t timestamp_ns) +{ + // Get current state of the filter. + const uint64_t previous_accel_timestamp_ns + = accelerometer_lowpass_filter_.GetMostRecentTimestampNs(); + const bool is_low_pass_filter_init = accelerometer_lowpass_filter_.IsInitialized(); + + // Update accel and accel delta low-pass filters. + accelerometer_lowpass_filter_.AddSample(accelerometer_sample, timestamp_ns); + + const auto smoothed_accelerometer_delta + = accelerometer_sample - accelerometer_lowpass_filter_.GetFilteredData(); + + accelerometer_static_counter_->AppendFrame( + Length(smoothed_accelerometer_delta) < kAccelerometerDeltaStaticThreshold); + + // Rotation from accel cannot be differentiated with only one sample. + if (!is_low_pass_filter_init) { + simulated_gyroscope_from_accelerometer_lowpass_filter_.AddSample({ 0, 0, 0 }, timestamp_ns); + return; + } + + // No need to update the simulated gyroscope at this point because the motion + // is too large. + if (!accelerometer_static_counter_->IsRecentlyStatic()) { + return; + } + + median_filter_.AddSample(accelerometer_lowpass_filter_.GetFilteredData()); + + // This processing can only be started if the buffer is fully initialized. + if (!median_filter_.IsValid()) { + mean_filter_.AddSample(accelerometer_lowpass_filter_.GetFilteredData()); + + // Update the last filtered accelerometer value. + last_mean_filtered_accelerometer_value_ = accelerometer_lowpass_filter_.GetFilteredData(); + return; + } + + mean_filter_.AddSample(median_filter_.GetFilteredData()); + + // Compute a mock gyroscope value from accelerometer. + const int64_t diff = timestamp_ns - previous_accel_timestamp_ns; + const double timestep = static_cast(diff); + + simulated_gyroscope_from_accelerometer_lowpass_filter_.AddSample( + ComputeAngularVelocityFromLatestAccelerometer(timestep), timestamp_ns); + last_mean_filtered_accelerometer_value_ = mean_filter_.GetFilteredData(); +} + +Vector3 GyroscopeBiasEstimator::ComputeAngularVelocityFromLatestAccelerometer(double timestep) const +{ + if (timestep < kMinTimestep) { + return { 0, 0, 0 }; + } + + const auto mean_of_median = mean_filter_.GetFilteredData(); + + // Compute an incremental rotation between the last state and the current + // state. + // + // Note that we switch to double precision here because of precision problem + // with small rotation. + const auto incremental_rotation = Rotation::RotateInto( + Vector3(last_mean_filtered_accelerometer_value_[0], + last_mean_filtered_accelerometer_value_[1], last_mean_filtered_accelerometer_value_[2]), + Vector3(mean_of_median[0], mean_of_median[1], mean_of_median[2])); + + // We use axis angle here because this is how gyroscope values are stored. + Vector3 incremental_rotation_axis; + double incremental_rotation_angle; + incremental_rotation.GetAxisAndAngle(&incremental_rotation_axis, &incremental_rotation_angle); + + incremental_rotation_axis *= incremental_rotation_angle / timestep; + + return { static_cast(incremental_rotation_axis[0]), + static_cast(incremental_rotation_axis[1]), + static_cast(incremental_rotation_axis[2]) }; +} + +bool GyroscopeBiasEstimator::UpdateGyroscopeBias( + const Vector3& gyroscope_sample, uint64_t timestamp_ns) +{ + // Gyroscope values that are too big are potentially dangerous as they could + // originate from slow and steady head rotations. + // + // Therefore we compute an update weight which: + // * favors gyroscope values that are closer to 0 + // * is set to zero if gyroscope values are greater than a threshold. + // + // This way, the gyroscope bias estimation converges faster if the phone is + // flat on a table, as opposed to held up somewhat stationary in the user's + // hands. + + // If magnitude is too big, don't update the filter at all so that we don't + // artificially increase the number of samples accumulated by the filter. + const float gyroscope_sample_norm2 = Length(gyroscope_sample); + if (gyroscope_sample_norm2 >= kGyroscopeForBiasThreshold) { + return false; + } + + float update_weight + = std::max(0.0f, 1 - gyroscope_sample_norm2 / kGyroscopeForBiasThreshold); + update_weight *= update_weight; + gyroscope_bias_lowpass_filter_.AddWeightedSample( + gyroscope_lowpass_filter_.GetFilteredData(), timestamp_ns, update_weight); + + // This counter is only partially valid as the low pass filter drops large + // samples. + current_accumulated_weights_gyroscope_bias_ += update_weight; + + return true; +} + +Vector3 GyroscopeBiasEstimator::GetGyroscopeBias() const +{ + return gyroscope_bias_lowpass_filter_.GetFilteredData(); +} + +bool GyroscopeBiasEstimator::IsCurrentEstimateValid() const +{ + // Remove any bias component along the gravity because they cannot be + // evaluated from accelerometer. + const auto current_gravity_dir = Normalized(last_mean_filtered_accelerometer_value_); + const auto gyro_bias_lowpass = gyroscope_bias_lowpass_filter_.GetFilteredData(); + + const auto off_gravity_gyro_bias + = gyro_bias_lowpass - current_gravity_dir * Dot(gyro_bias_lowpass, current_gravity_dir); + + // Checks that the current bias estimate is not correlated with the + // rotation computed from accelerometer. + const auto gyro_from_accel + = simulated_gyroscope_from_accelerometer_lowpass_filter_.GetFilteredData(); + const bool isGyroscopeBiasCorrelatedWithSimulatedGyro + = (Length(gyro_from_accel) * kRatioBetweenGyroBiasAndAccel + > (Length(off_gravity_gyro_bias) + kEpsilon)); + const bool hasEnoughSamples + = current_accumulated_weights_gyroscope_bias_ > kMinSumOfWeightsGyroBiasThreshold; + const bool areCountersStatic = gyroscope_static_counter_->IsRecentlyStatic() + && accelerometer_static_counter_->IsRecentlyStatic(); + + const bool isStatic + = hasEnoughSamples && areCountersStatic && !isGyroscopeBiasCorrelatedWithSimulatedGyro; + return isStatic; +} + +} // namespace cardboard diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/gyroscope_bias_estimator.h b/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/gyroscope_bias_estimator.h new file mode 100644 index 000000000..1a46f96be --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/gyroscope_bias_estimator.h @@ -0,0 +1,134 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef CARDBOARD_SDK_SENSORS_GYROSCOPE_BIAS_ESTIMATOR_H_ +#define CARDBOARD_SDK_SENSORS_GYROSCOPE_BIAS_ESTIMATOR_H_ + +#include // NOLINT +#include +#include +#include +#include + +#include "lowpass_filter.h" +#include "mean_filter.h" +#include "median_filter.h" +#include "../util/vector.h" + +namespace cardboard { + +// Class that attempts to estimate the gyroscope's bias. +// Its main idea is that it averages the gyroscope values when the phone is +// considered stationary. +// Usage: A client should call the ProcessGyroscope and ProcessAccelerometer +// methods for every accelerometer and gyroscope sensor sample. This class +// expects these calls to be frequent, i.e., at least at 10 Hz. The client can +// then call GetGyroBias to retrieve the current estimate of the gyroscope bias. +// For best results, the fastest available delay option should be used when +// registering to sensors. Note that this class is not thread-safe. +// +// The filtering applied to the accelerometer to estimate a rotation +// from it follows : +// Baptiste Delporte, Laurent Perroton, Thierry Grandpierre, Jacques Trichet. +// Accelerometer and Magnetometer Based Gyroscope Emulation on Smart Sensor +// for a Virtual Reality Application. Sensor and Transducers Journal, 2012. +// +// which is a combination of a IIR filter, a median and a mean filter. +class GyroscopeBiasEstimator { +public: + GyroscopeBiasEstimator(); + virtual ~GyroscopeBiasEstimator(); + + // Updates the estimator with a gyroscope event. + // + // @param gyroscope_sample the angular speed around the x, y, z axis in + // radians/sec. + // @param timestamp_ns the nanosecond at which the event occurred. Only + // guaranteed to be comparable with timestamps from other PocessGyroscope + // invocations. + virtual void ProcessGyroscope(const Vector3& gyroscope_sample, uint64_t timestamp_ns); + + // Processes accelerometer samples to estimate if device is + // stable or not. + // + // First we filter the accelerometer. This is done with 3 filters. + // - A IIR low-pass filter + // - A median filter + // - A mean filter. + // Then a rotation is computed between consecutive filtered accelerometer + // samples. + // Finally this is converted to a velocity to emulate a gyroscope. + // + // @param accelerometer_sample the acceleration (including gravity) on the x, + // y, z axis in meters/s^2. + // @param timestamp_ns the nanosecond at which the event occurred. Only + // guaranteed to be comparable with timestamps from other + // ProcessAccelerometer invocations. + virtual void ProcessAccelerometer(const Vector3& accelerometer_sample, uint64_t timestamp_ns); + + // Returns the estimated gyroscope bias. + // + // @return Estimated gyroscope bias. A vector with zeros is returned if no + // estimate has been computed. + virtual Vector3 GetGyroscopeBias() const; + + // Resets the estimator state. + void Reset(); + + // Returns true if the current estimate returned by GetGyroscopeBias is + // correct. The device (measured using the sensors) has to be static for this + // function to return true. + virtual bool IsCurrentEstimateValid() const; + +private: + // A helper class to keep track of whether some signal can be considered + // static over specified number of frames. + class IsStaticCounter; + + // Updates gyroscope bias estimation. + // + // @return false if the current sample is too large. + bool UpdateGyroscopeBias(const Vector3& gyroscope_sample, uint64_t timestamp_ns); + + // Returns device angular velocity (rad/s) from the latest accelerometer data. + // + // @param timestep in seconds between the last two samples. + // @return rotation velocity from latest accelerometer. This can be + // interpreted as an gyroscope. + Vector3 ComputeAngularVelocityFromLatestAccelerometer(double timestep) const; + + LowpassFilter accelerometer_lowpass_filter_; + LowpassFilter simulated_gyroscope_from_accelerometer_lowpass_filter_; + LowpassFilter gyroscope_lowpass_filter_; + LowpassFilter gyroscope_bias_lowpass_filter_; + + std::unique_ptr accelerometer_static_counter_; + std::unique_ptr gyroscope_static_counter_; + + // Sum of the weight of sample used for gyroscope filtering. + float current_accumulated_weights_gyroscope_bias_; + + // Set of filters for accelerometer data to estimate a rotation + // based only on accelerometer. + MeanFilter mean_filter_; + MedianFilter median_filter_; + + // Last computed filter accelerometer value used for finite differences. + Vector3 last_mean_filtered_accelerometer_value_; +}; + +} // namespace cardboard + +#endif // CARDBOARD_SDK_SENSORS_GYROSCOPE_BIAS_ESTIMATOR_H_ diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/gyroscope_data.h b/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/gyroscope_data.h new file mode 100644 index 000000000..085e85209 --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/gyroscope_data.h @@ -0,0 +1,38 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef CARDBOARD_SDK_SENSORS_GYROSCOPE_DATA_H_ +#define CARDBOARD_SDK_SENSORS_GYROSCOPE_DATA_H_ + +#include "../util/vector.h" + +namespace cardboard { + +struct GyroscopeData { + // System wall time. + uint64_t system_timestamp; + + // Sensor clock time in nanoseconds. + uint64_t sensor_timestamp_ns; + + // Rate of rotation around the x,y,z axes in rad/s. This follows android + // specification + // (https://developer.android.com/guide/topics/sensors/sensors_overview.html#sensors-coords). + Vector3 data; +}; + +} // namespace cardboard + +#endif // CARDBOARD_SDK_SENSORS_GYROSCOPE_DATA_H_ diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/lowpass_filter.cc b/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/lowpass_filter.cc new file mode 100644 index 000000000..efadfbf4e --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/lowpass_filter.cc @@ -0,0 +1,84 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "lowpass_filter.h" + +#include + +namespace { + +const double kSecondsFromNanoseconds = 1.0e-9; + +// Minimum time step between sensor updates. This corresponds to 1000 Hz. +const double kMinTimestepS = 0.001f; + +// Maximum time step between sensor updates. This corresponds to 1 Hz. +const double kMaxTimestepS = 1.00f; + +} // namespace + +namespace cardboard { + +LowpassFilter::LowpassFilter(double cutoff_freq_hz) + : cutoff_time_constant_(1 / (2 * (double)M_PI * cutoff_freq_hz)) + , initialized_(false) +{ + Reset(); +} + +void LowpassFilter::AddSample(const Vector3& sample, uint64_t timestamp_ns) +{ + AddWeightedSample(sample, timestamp_ns, 1.0); +} + +void LowpassFilter::AddWeightedSample(const Vector3& sample, uint64_t timestamp_ns, double weight) +{ + if (!initialized_) { + // Initialize filter state + filtered_data_ = { sample[0], sample[1], sample[2] }; + timestamp_most_recent_update_ns_ = timestamp_ns; + initialized_ = true; + return; + } + + if (timestamp_ns < timestamp_most_recent_update_ns_) { + timestamp_most_recent_update_ns_ = timestamp_ns; + return; + } + + const double delta_s = static_cast(timestamp_ns - timestamp_most_recent_update_ns_) + * kSecondsFromNanoseconds; + if (delta_s <= kMinTimestepS || delta_s > kMaxTimestepS) { + timestamp_most_recent_update_ns_ = timestamp_ns; + return; + } + + const double weighted_delta_secs = weight * delta_s; + + const double alpha = weighted_delta_secs / (cutoff_time_constant_ + weighted_delta_secs); + + for (int i = 0; i < 3; ++i) { + filtered_data_[i] = (1 - alpha) * filtered_data_[i] + alpha * sample[i]; + } + timestamp_most_recent_update_ns_ = timestamp_ns; +} + +void LowpassFilter::Reset() +{ + initialized_ = false; + filtered_data_ = { 0, 0, 0 }; +} + +} // namespace cardboard diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/lowpass_filter.h b/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/lowpass_filter.h new file mode 100644 index 000000000..c4994c425 --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/lowpass_filter.h @@ -0,0 +1,81 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef CARDBOARD_SDK_SENSORS_LOWPASS_FILTER_H_ +#define CARDBOARD_SDK_SENSORS_LOWPASS_FILTER_H_ + +#include +#include + +#include "../util/vector.h" + +namespace cardboard { + +// Implements an IIR, first order, low pass filter over vectors of the given +// dimension = 3. +// See http://en.wikipedia.org/wiki/Low-pass_filter +class LowpassFilter { +public: + // Initializes a filter with the given cutoff frequency in Hz. + explicit LowpassFilter(double cutoff_freq_hz); + + // Updates the filter with the given sample. Note that samples with + // non-monotonic timestamps and successive samples with a time steps below 1 + // ms or above 1 s are ignored. + // + // @param sample current sample data. + // @param timestamp_ns timestamp associated to this sample in nanoseconds. + void AddSample(const Vector3& sample, uint64_t timestamp_ns); + + // Updates the filter with the given weighted sample. + // + // @param sample current sample data. + // @param timestamp_ns timestamp associated to this sample in nanoseconds. + // @param weight typically a [0, 1] weight factor used when applying a new + // sample. A weight of 1 corresponds to calling AddSample. A weight of 0 + // makes the update no-op. The first initial sample is not affected by + // this. + void AddWeightedSample(const Vector3& sample, uint64_t timestamp_ns, double weight); + + // Returns the filtered value. A vector with zeros is returned if no samples + // have been added. + Vector3 GetFilteredData() const { + return filtered_data_; + } + + // Returns the most recent update timestamp in ns. + uint64_t GetMostRecentTimestampNs() const { + return timestamp_most_recent_update_ns_; + } + + // Returns true when the filter is initialized. + bool IsInitialized() const { + return initialized_; + } + + // Resets filter state. + void Reset(); + +private: + const double cutoff_time_constant_; + uint64_t timestamp_most_recent_update_ns_; + bool initialized_; + + Vector3 filtered_data_; +}; + +} // namespace cardboard + +#endif // CARDBOARD_SDK_SENSORS_LOWPASS_FILTER_H_ diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/mean_filter.cc b/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/mean_filter.cc new file mode 100644 index 000000000..02fb8034c --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/mean_filter.cc @@ -0,0 +1,46 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "mean_filter.h" + +namespace cardboard { + +MeanFilter::MeanFilter(size_t filter_size) + : filter_size_(filter_size) +{ +} + +void MeanFilter::AddSample(const Vector3& sample) +{ + buffer_.push_back(sample); + if (buffer_.size() > filter_size_) { + buffer_.pop_front(); + } +} + +bool MeanFilter::IsValid() const { return buffer_.size() == filter_size_; } + +Vector3 MeanFilter::GetFilteredData() const +{ + // Compute mean of the samples stored in buffer_. + Vector3 mean = Vector3::Zero(); + for (auto sample : buffer_) { + mean += sample; + } + + return mean / static_cast(filter_size_); +} + +} // namespace cardboard diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/mean_filter.h b/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/mean_filter.h new file mode 100644 index 000000000..6b4956fef --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/mean_filter.h @@ -0,0 +1,48 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef CARDBOARD_SDK_SENSORS_MEAN_FILTER_H_ +#define CARDBOARD_SDK_SENSORS_MEAN_FILTER_H_ + +#include + +#include "../util/vector.h" + +namespace cardboard { + +// Fixed window FIFO mean filter for vectors of the given dimension. +class MeanFilter { +public: + // Create a mean filter of size filter_size. + // @param filter_size size of the internal filter. + explicit MeanFilter(size_t filter_size); + + // Add sample to buffer_ if buffer_ is full it drop the oldest sample. + void AddSample(const Vector3& sample); + + // Returns true if buffer has filter_size_ sample, false otherwise. + bool IsValid() const; + + // Returns the mean of values stored in the internal buffer. + Vector3 GetFilteredData() const; + +private: + const size_t filter_size_; + std::deque buffer_; +}; + +} // namespace cardboard + +#endif // CARDBOARD_SDK_SENSORS_MEAN_FILTER_H_ diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/median_filter.cc b/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/median_filter.cc new file mode 100644 index 000000000..d27c19f47 --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/median_filter.cc @@ -0,0 +1,69 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "median_filter.h" + +#include +#include + +#include "../util/vector.h" +#include "../util/vectorutils.h" + +namespace cardboard { + +MedianFilter::MedianFilter(size_t filter_size) + : filter_size_(filter_size) +{ +} + +void MedianFilter::AddSample(const Vector3& sample) +{ + buffer_.push_back(sample); + norms_.push_back(Length(sample)); + if (buffer_.size() > filter_size_) { + buffer_.pop_front(); + norms_.pop_front(); + } +} + +bool MedianFilter::IsValid() const { return buffer_.size() == filter_size_; } + +Vector3 MedianFilter::GetFilteredData() const +{ + std::vector norms(norms_.begin(), norms_.end()); + + // Get median of value of the norms. + std::nth_element(norms.begin(), norms.begin() + filter_size_ / 2, norms.end()); + const float median_norm = norms[filter_size_ / 2]; + + // Get median value based on their norm. + auto median_it = buffer_.begin(); + for (const auto norm : norms_) { + if (norm == median_norm) { + break; + } + ++median_it; + } + + return *median_it; +} + +void MedianFilter::Reset() +{ + buffer_.clear(); + norms_.clear(); +} + +} // namespace cardboard diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/median_filter.h b/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/median_filter.h new file mode 100644 index 000000000..9a8e7cfc7 --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/median_filter.h @@ -0,0 +1,53 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef CARDBOARD_SDK_SENSORS_MEDIAN_FILTER_H_ +#define CARDBOARD_SDK_SENSORS_MEDIAN_FILTER_H_ + +#include + +#include "../util/vector.h" + +namespace cardboard { + +// Fixed window FIFO median filter for vectors of the given dimension = 3. +class MedianFilter { +public: + // Creates a median filter of size filter_size. + // @param filter_size size of the internal filter. + explicit MedianFilter(size_t filter_size); + + // Adds sample to buffer_ if buffer_ is full it drops the oldest sample. + void AddSample(const Vector3& sample); + + // Returns true if buffer has filter_size_ sample, false otherwise. + bool IsValid() const; + + // Returns the median of values store in the internal buffer. + Vector3 GetFilteredData() const; + + // Resets the filter, removing all samples that have been added. + void Reset(); + +private: + const size_t filter_size_; + std::deque buffer_; + // Contains norms of the elements stored in buffer_. + std::deque norms_; +}; + +} // namespace cardboard + +#endif // CARDBOARD_SDK_SENSORS_MEDIAN_FILTER_H_ diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/pose_prediction.cc b/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/pose_prediction.cc new file mode 100644 index 000000000..baaf844dd --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/pose_prediction.cc @@ -0,0 +1,71 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "pose_prediction.h" + +#include // NOLINT + +#include "../util/logging.h" +#include "../util/vectorutils.h" + +namespace cardboard { + +namespace { + const double kEpsilon = 1.0e-15; +} // namespace + +namespace pose_prediction { + + Rotation GetRotationFromGyroscope(const Vector3& gyroscope_value, double timestep_s) + { + const double velocity = Length(gyroscope_value); + + // When there is no rotation data return an identity rotation. + if (velocity < kEpsilon) { + CARDBOARD_LOGI("PosePrediction::GetRotationFromGyroscope: Velocity really small, " + "returning identity rotation."); + return Rotation::Identity(); + } + // Since the gyroscope_value is a start from sensor transformation we need to + // invert it to have a sensor from start transformation, hence the minus sign. + // For more info: + // http://developer.android.com/guide/topics/sensors/sensors_motion.html#sensors-motion-gyro + return Rotation::FromAxisAndAngle(gyroscope_value / velocity, -timestep_s * velocity); + } + + Rotation PredictPose(int64_t requested_pose_timestamp, const PoseState& current_state) + { + // Subtracting unsigned numbers is bad when the result is negative. + const int64_t diff = requested_pose_timestamp - current_state.timestamp; + const double timestep_s = diff * 1.0e-9; + + const Rotation update = GetRotationFromGyroscope( + current_state.sensor_from_start_rotation_velocity, timestep_s); + return update * current_state.sensor_from_start_rotation; + } + + Rotation PredictPoseInv(int64_t requested_pose_timestamp, const PoseState& current_state) + { + // Subtracting unsigned numbers is bad when the result is negative. + const int64_t diff = requested_pose_timestamp - current_state.timestamp; + const double timestep_s = diff * 1.0e-9; + + const Rotation update = GetRotationFromGyroscope( + current_state.sensor_from_start_rotation_velocity, timestep_s); + return current_state.sensor_from_start_rotation * (-update); + } + +} // namespace pose_prediction +} // namespace cardboard diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/pose_prediction.h b/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/pose_prediction.h new file mode 100644 index 000000000..9ab311b33 --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/pose_prediction.h @@ -0,0 +1,55 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef CARDBOARD_SDK_SENSORS_POSE_PREDICTION_H_ +#define CARDBOARD_SDK_SENSORS_POSE_PREDICTION_H_ + +#include + +#include "pose_state.h" +#include "../util/rotation.h" + +namespace cardboard { +namespace pose_prediction { + +// Returns a rotation matrix based on the integration of the gyroscope_value +// over the timestep_s in seconds. +// TODO(pfg): Document the space better here. +// +// @param gyroscope_value gyroscope sensor values. +// @param timestep_s integration period in seconds. +// @return Integration of the gyroscope value the rotation is from Start to +// Sensor Space. +Rotation GetRotationFromGyroscope(const Vector3& gyroscope_value, double timestep_s); + +// Gets a predicted pose for a given time in the future (e.g. rendering time) +// based on a linear prediction model. This uses the system current state +// (position, velocity, etc) from the past to extrapolate a position in the +// future. +// +// @param requested_pose_timestamp time at which you want the pose. +// @param current_state current state that stores the pose and linear model at a +// given time prior to requested_pose_timestamp_ns. +// @return pose from Start to Sensor Space. +Rotation PredictPose(int64_t requested_pose_timestamp, const PoseState& current_state); + +// Equivalent to PredictPose, but for use with poses relative to Start Space +// rather than sensor space. +Rotation PredictPoseInv(int64_t requested_pose_timestamp, const PoseState& current_state); + +} // namespace pose_prediction +} // namespace cardboard + +#endif // CARDBOARD_SDK_SENSORS_POSE_PREDICTION_H_ diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/pose_state.h b/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/pose_state.h new file mode 100644 index 000000000..f7801c9f3 --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/pose_state.h @@ -0,0 +1,56 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef CARDBOARD_SDK_SENSORS_POSE_STATE_H_ +#define CARDBOARD_SDK_SENSORS_POSE_STATE_H_ + +#include "../util/rotation.h" +#include "../util/vector.h" + +namespace cardboard { + +enum { + kPoseStateFlagInvalid = 1U << 0, + kPoseStateFlagInitializing = 1U << 1, + kPoseStateFlagHas6DoF = 1U << 2, +}; + +// Stores a head pose pose plus derivatives. This can be used for prediction. +struct PoseState { + // System wall time. + int64_t timestamp; + + // Rotation from Sensor Space to Start Space. + Rotation sensor_from_start_rotation; + + // First derivative of the rotation. + Vector3 sensor_from_start_rotation_velocity; + + // Current gyroscope bias in rad/s. + Vector3 bias; + + // The position of the headset. + Vector3 position = Vector3(0, 0, 0); + + // In the same coordinate frame as the position. + Vector3 velocity = Vector3(0, 0, 0); + + // Flags indicating the status of the pose. + uint64_t flags = 0U; +}; + +} // namespace cardboard + +#endif // CARDBOARD_SDK_SENSORS_POSE_STATE_H_ diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/sensor_fusion_ekf.cc b/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/sensor_fusion_ekf.cc new file mode 100644 index 000000000..575dde6f0 --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/sensor_fusion_ekf.cc @@ -0,0 +1,333 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "sensor_fusion_ekf.h" + +#include +#include + +#include "accelerometer_data.h" +#include "gyroscope_data.h" +#include "pose_prediction.h" +#include "../util/matrixutils.h" + +namespace cardboard { + +namespace { + + const double kFiniteDifferencingEpsilon = 1.0e-7; + const double kEpsilon = 1.0e-15; + // Default gyroscope frequency. This corresponds to 100 Hz. + const double kDefaultGyroscopeTimestep_s = 0.01f; + // Maximum time between gyroscope before we start limiting the integration. + const double kMaximumGyroscopeSampleDelay_s = 0.04f; + // Compute a first-order exponential moving average of changes in accel norm per + // frame. + const double kSmoothingFactor = 0.5; + // Minimum and maximum values used for accelerometer noise covariance matrix. + // The smaller the sigma value, the more weight is given to the accelerometer + // signal. + const double kMinAccelNoiseSigma = 0.75; + const double kMaxAccelNoiseSigma = 7.0; + // Initial value for the diagonal elements of the different covariance matrices. + const double kInitialStateCovarianceValue = 25.0; + const double kInitialProcessCovarianceValue = 1.0; + // Maximum accelerometer norm change allowed before capping it covariance to a + // large value. + const double kMaxAccelNormChange = 0.15; + // Timestep IIR filtering coefficient. + const double kTimestepFilterCoeff = 0.95; + // Minimum number of sample for timestep filtering. + const int kTimestepFilterMinSamples = 10; + + // Z direction in start space. + const Vector3 kCanonicalZDirection(0.0, 0.0, 1.0); + + // Computes an axis-angle rotation from the input vector. + // angle = norm(a) + // axis = a.normalized() + // If norm(a) == 0, it returns an identity rotation. + static inline Rotation RotationFromVector(const Vector3& a) + { + const double norm_a = Length(a); + if (norm_a < kEpsilon) { + return Rotation::Identity(); + } + return Rotation::FromAxisAndAngle(a / norm_a, norm_a); + } + +} // namespace + +SensorFusionEkf::SensorFusionEkf() + : execute_reset_with_next_accelerometer_sample_(false) + , bias_estimation_enabled_(true) + , gyroscope_bias_estimate_({ 0, 0, 0 }) +{ + ResetState(); +} + +void SensorFusionEkf::Reset() { execute_reset_with_next_accelerometer_sample_ = true; } + +void SensorFusionEkf::ResetState() +{ + current_state_.sensor_from_start_rotation = Rotation::Identity(); + current_state_.sensor_from_start_rotation_velocity = Vector3::Zero(); + + current_gyroscope_sensor_timestamp_ns_ = 0; + current_accelerometer_sensor_timestamp_ns_ = 0; + + state_covariance_ = Matrix3x3::Identity() * kInitialStateCovarianceValue; + process_covariance_ = Matrix3x3::Identity() * kInitialProcessCovarianceValue; + accelerometer_measurement_covariance_ + = Matrix3x3::Identity() * kMinAccelNoiseSigma * kMinAccelNoiseSigma; + innovation_covariance_ = Matrix3x3::Identity(); + + accelerometer_measurement_jacobian_ = Matrix3x3::Zero(); + kalman_gain_ = Matrix3x3::Zero(); + innovation_ = Vector3::Zero(); + accelerometer_measurement_ = Vector3::Zero(); + prediction_ = Vector3::Zero(); + control_input_ = Vector3::Zero(); + state_update_ = Vector3::Zero(); + + moving_average_accelerometer_norm_change_ = 0.0; + + is_timestep_filter_initialized_ = false; + is_gyroscope_filter_valid_ = false; + is_aligned_with_gravity_ = false; + + // Reset biases. + gyroscope_bias_estimator_.Reset(); + gyroscope_bias_estimate_ = { 0, 0, 0 }; +} + +// Here I am doing something wrong relative to time stamps. The state timestamps +// always correspond to the gyrostamps because it would require additional +// extrapolation if I wanted to do otherwise. +PoseState SensorFusionEkf::GetLatestPoseState() const { return current_state_; } + +void SensorFusionEkf::ProcessGyroscopeSample(const GyroscopeData& sample) +{ + // Don't accept gyroscope sample when waiting for a reset. + if (execute_reset_with_next_accelerometer_sample_) { + return; + } + + // Discard outdated samples. + if (current_gyroscope_sensor_timestamp_ns_ >= sample.sensor_timestamp_ns) { + current_gyroscope_sensor_timestamp_ns_ = sample.sensor_timestamp_ns; + return; + } + + // Checks that we received at least one gyroscope sample in the past. + if (current_gyroscope_sensor_timestamp_ns_ != 0) { + double current_timestep_s = std::chrono::duration_cast>( + std::chrono::nanoseconds( + sample.sensor_timestamp_ns - current_gyroscope_sensor_timestamp_ns_)) + .count(); + if (current_timestep_s > kMaximumGyroscopeSampleDelay_s) { + if (is_gyroscope_filter_valid_) { + // Replaces the delta timestamp by the filtered estimates of the delta time. + current_timestep_s = filtered_gyroscope_timestep_s_; + } else { + current_timestep_s = kDefaultGyroscopeTimestep_s; + } + } else { + FilterGyroscopeTimestep(current_timestep_s); + } + + if (bias_estimation_enabled_) { + gyroscope_bias_estimator_.ProcessGyroscope(sample.data, sample.sensor_timestamp_ns); + + if (gyroscope_bias_estimator_.IsCurrentEstimateValid()) { + // As soon as the device is considered to be static, the bias estimator + // should have a precise estimate of the gyroscope bias. + gyroscope_bias_estimate_ = gyroscope_bias_estimator_.GetGyroscopeBias(); + } + } + + // Only integrate after receiving an accelerometer sample. + if (is_aligned_with_gravity_) { + const Rotation rotation_from_gyroscope = pose_prediction::GetRotationFromGyroscope( + { sample.data[0] - gyroscope_bias_estimate_[0], + sample.data[1] - gyroscope_bias_estimate_[1], + sample.data[2] - gyroscope_bias_estimate_[2] }, + current_timestep_s); + current_state_.sensor_from_start_rotation + = rotation_from_gyroscope * current_state_.sensor_from_start_rotation; + UpdateStateCovariance(RotationMatrixNH(rotation_from_gyroscope)); + state_covariance_ = state_covariance_ + + ((current_timestep_s * current_timestep_s) * process_covariance_); + } + } + + // Saves gyroscope event for future prediction. + current_state_.timestamp = sample.system_timestamp; + current_gyroscope_sensor_timestamp_ns_ = sample.sensor_timestamp_ns; + current_state_.sensor_from_start_rotation_velocity.Set( + sample.data[0] - gyroscope_bias_estimate_[0], sample.data[1] - gyroscope_bias_estimate_[1], + sample.data[2] - gyroscope_bias_estimate_[2]); +} + +Vector3 SensorFusionEkf::ComputeInnovation(const Rotation& pose) +{ + const Vector3 predicted_down_direction = pose * kCanonicalZDirection; + + const Rotation rotation + = Rotation::RotateInto(predicted_down_direction, accelerometer_measurement_); + Vector3 axis; + double angle; + rotation.GetAxisAndAngle(&axis, &angle); + return axis * angle; +} + +void SensorFusionEkf::ComputeMeasurementJacobian() +{ + for (int dof = 0; dof < 3; dof++) { + Vector3 delta = Vector3::Zero(); + delta[dof] = kFiniteDifferencingEpsilon; + + const Rotation epsilon_rotation = RotationFromVector(delta); + const Vector3 delta_rotation + = ComputeInnovation(epsilon_rotation * current_state_.sensor_from_start_rotation); + + const Vector3 col = (innovation_ - delta_rotation) / kFiniteDifferencingEpsilon; + accelerometer_measurement_jacobian_(0, dof) = col[0]; + accelerometer_measurement_jacobian_(1, dof) = col[1]; + accelerometer_measurement_jacobian_(2, dof) = col[2]; + } +} + +void SensorFusionEkf::ProcessAccelerometerSample(const AccelerometerData& sample) +{ + // Discard outdated samples. + if (current_accelerometer_sensor_timestamp_ns_ >= sample.sensor_timestamp_ns) { + current_accelerometer_sensor_timestamp_ns_ = sample.sensor_timestamp_ns; + return; + } + + // Call reset state if required. + if (execute_reset_with_next_accelerometer_sample_.exchange(false)) { + ResetState(); + } + + accelerometer_measurement_.Set(sample.data[0], sample.data[1], sample.data[2]); + current_accelerometer_sensor_timestamp_ns_ = sample.sensor_timestamp_ns; + + if (bias_estimation_enabled_) { + gyroscope_bias_estimator_.ProcessAccelerometer(sample.data, sample.sensor_timestamp_ns); + } + + if (!is_aligned_with_gravity_) { + // This is the first accelerometer measurement so it initializes the + // orientation estimate. + current_state_.sensor_from_start_rotation + = Rotation::RotateInto(kCanonicalZDirection, accelerometer_measurement_); + is_aligned_with_gravity_ = true; + + previous_accelerometer_norm_ = Length(accelerometer_measurement_); + return; + } + + UpdateMeasurementCovariance(); + + innovation_ = ComputeInnovation(current_state_.sensor_from_start_rotation); + ComputeMeasurementJacobian(); + + // S = H * P * H' + R + innovation_covariance_ = accelerometer_measurement_jacobian_ * state_covariance_ + * Transpose(accelerometer_measurement_jacobian_) + + accelerometer_measurement_covariance_; + + // K = P * H' * S^-1 + kalman_gain_ = state_covariance_ * Transpose(accelerometer_measurement_jacobian_) + * Inverse(innovation_covariance_); + + // x_update = K*nu + state_update_ = kalman_gain_ * innovation_; + + // P = (I - K * H) * P; + state_covariance_ = (Matrix3x3::Identity() - kalman_gain_ * accelerometer_measurement_jacobian_) + * state_covariance_; + + // Updates pose and associate covariance matrix. + const Rotation rotation_from_state_update = RotationFromVector(state_update_); + + current_state_.sensor_from_start_rotation + = rotation_from_state_update * current_state_.sensor_from_start_rotation; + UpdateStateCovariance(RotationMatrixNH(rotation_from_state_update)); +} + +void SensorFusionEkf::UpdateStateCovariance(const Matrix3x3& motion_update) +{ + state_covariance_ = motion_update * state_covariance_ * Transpose(motion_update); +} + +void SensorFusionEkf::FilterGyroscopeTimestep(double gyroscope_timestep_s) +{ + if (!is_timestep_filter_initialized_) { + // Initializes the filter. + filtered_gyroscope_timestep_s_ = gyroscope_timestep_s; + num_gyroscope_timestep_samples_ = 1; + is_timestep_filter_initialized_ = true; + return; + } + + // Computes the IIR filter response. + filtered_gyroscope_timestep_s_ = kTimestepFilterCoeff * filtered_gyroscope_timestep_s_ + + (1 - kTimestepFilterCoeff) * gyroscope_timestep_s; + ++num_gyroscope_timestep_samples_; + + if (num_gyroscope_timestep_samples_ > kTimestepFilterMinSamples) { + is_gyroscope_filter_valid_ = true; + } +} + +void SensorFusionEkf::UpdateMeasurementCovariance() +{ + const double current_accelerometer_norm = Length(accelerometer_measurement_); + // Norm change between current and previous accel readings. + const double current_accelerometer_norm_change + = std::abs(current_accelerometer_norm - previous_accelerometer_norm_); + previous_accelerometer_norm_ = current_accelerometer_norm; + + moving_average_accelerometer_norm_change_ = kSmoothingFactor * current_accelerometer_norm_change + + (1 - kSmoothingFactor) * moving_average_accelerometer_norm_change_; + + // If we hit the accel norm change threshold, we use the maximum noise sigma + // for the accel covariance. For anything below that, we use a linear + // combination between min and max sigma values. + const double norm_change_ratio + = moving_average_accelerometer_norm_change_ / kMaxAccelNormChange; + const double accelerometer_noise_sigma = std::min(kMaxAccelNoiseSigma, + kMinAccelNoiseSigma + norm_change_ratio * (kMaxAccelNoiseSigma - kMinAccelNoiseSigma)); + + // Updates the accel covariance matrix with the new sigma value. + accelerometer_measurement_covariance_ + = Matrix3x3::Identity() * accelerometer_noise_sigma * accelerometer_noise_sigma; +} + +bool SensorFusionEkf::IsBiasEstimationEnabled() const { return bias_estimation_enabled_; } + +void SensorFusionEkf::SetBiasEstimationEnabled(bool enable) +{ + if (bias_estimation_enabled_ != enable) { + bias_estimation_enabled_ = enable; + gyroscope_bias_estimate_ = { 0, 0, 0 }; + gyroscope_bias_estimator_.Reset(); + } +} + +} // namespace cardboard diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/sensor_fusion_ekf.h b/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/sensor_fusion_ekf.h new file mode 100644 index 000000000..a66fe33f4 --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/sensors/sensor_fusion_ekf.h @@ -0,0 +1,188 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef CARDBOARD_SDK_SENSORS_SENSOR_FUSION_EKF_H_ +#define CARDBOARD_SDK_SENSORS_SENSOR_FUSION_EKF_H_ + +#include +#include +#include + +#include "accelerometer_data.h" +#include "gyroscope_bias_estimator.h" +#include "gyroscope_data.h" +#include "pose_state.h" +#include "../util/matrix_3x3.h" +#include "../util/rotation.h" +#include "../util/vector.h" + +namespace cardboard { + +// Sensor fusion class that implements an Extended Kalman Filter (EKF) to +// estimate a 3D rotation from a gyroscope and an accelerometer. +// This system only has one state, the pose. It does not estimate any velocity +// or acceleration. +// +// To learn more about Kalman filtering one can read this article which is a +// good introduction: https://en.wikipedia.org/wiki/Kalman_filter +class SensorFusionEkf { +public: + SensorFusionEkf(); + + // Resets the state of the sensor fusion. It sets the velocity for + // prediction to zero. The reset will happen with the next + // accelerometer sample. Gyroscope sample will be discarded until a new + // accelerometer sample arrives. + void Reset(); + + // Gets the PoseState representing the latest pose and derivatives at a + // particular timestamp as estimated by SensorFusion. + PoseState GetLatestPoseState() const; + + // Processes one gyroscope sample event. This updates the pose of the system + // and the prediction model. The gyroscope data is assumed to be in axis angle + // form. Angle = ||v|| and Axis = v / ||v||, with v = [v_x, v_y, v_z]^T. + // + // @param sample gyroscope sample data. + void ProcessGyroscopeSample(const GyroscopeData& sample); + + // Processes one accelerometer sample event. This updates the pose of the + // system. If the Accelerometer norm changes too much between sample it is not + // trusted as much. + // + // @param sample accelerometer sample data. + void ProcessAccelerometerSample(const AccelerometerData& sample); + + // Enables or disables the drift correction by estimating the gyroscope bias. + // + // @param enable Enable drift correction. + void SetBiasEstimationEnabled(bool enable); + + // Returns a boolean that indicates if bias estimation is enabled or disabled. + // + // @return true if bias estimation is enabled, false otherwise. + bool IsBiasEstimationEnabled() const; + + // Returns the current gyroscope bias estimate from GyroscopeBiasEstimator. + Vector3 GetGyroscopeBias() const { + return { + gyroscope_bias_estimate_[0], gyroscope_bias_estimate_[1], gyroscope_bias_estimate_[2]}; + } + + // Returns true after receiving the first accelerometer measurement. + bool IsFullyInitialized() const { + return is_aligned_with_gravity_; + } + +private: + // Estimates the average timestep between gyroscope event. + void FilterGyroscopeTimestep(double gyroscope_timestep); + + // Updates the state covariance with an incremental motion. It changes the + // space of the quadric. + void UpdateStateCovariance(const Matrix3x3& motion_update); + + // Computes the innovation vector of the Kalman based on the input pose. + // It uses the latest measurement vector (i.e. accelerometer data), which must + // be set prior to calling this function. + Vector3 ComputeInnovation(const Rotation& pose); + + // This computes the measurement_jacobian_ via numerical differentiation based + // on the current value of sensor_from_start_rotation_. + void ComputeMeasurementJacobian(); + + // Updates the accelerometer covariance matrix. + // + // This looks at the norm of recent accelerometer readings. If it has changed + // significantly, it means the phone receives additional acceleration than + // just gravity, and so the down vector information gravity signal is noisier. + void UpdateMeasurementCovariance(); + + // Reset all internal states. This is not thread safe. Lock should be acquired + // outside of it. This function is called in ProcessAccelerometerSample. + void ResetState(); + + // Current transformation from Sensor Space to Start Space. + // x_sensor = sensor_from_start_rotation_ * x_start; + PoseState current_state_; + + // Filtering of the gyroscope timestep started? + bool is_timestep_filter_initialized_; + // Filtered gyroscope timestep valid? + bool is_gyroscope_filter_valid_; + // Sensor fusion currently aligned with gravity? After initialization + // it will requires a couple of accelerometer data for the system to get + // aligned. + std::atomic is_aligned_with_gravity_; + + // Covariance of Kalman filter state (P in common formulation). + Matrix3x3 state_covariance_; + // Covariance of the process noise (Q in common formulation). + Matrix3x3 process_covariance_; + // Covariance of the accelerometer measurement (R in common formulation). + Matrix3x3 accelerometer_measurement_covariance_; + // Covariance of innovation (S in common formulation). + Matrix3x3 innovation_covariance_; + // Jacobian of the measurements (H in common formulation). + Matrix3x3 accelerometer_measurement_jacobian_; + // Gain of the Kalman filter (K in common formulation). + Matrix3x3 kalman_gain_; + // Parameter update a.k.a. innovation vector. (\nu in common formulation). + Vector3 innovation_; + // Measurement vector (z in common formulation). + Vector3 accelerometer_measurement_; + // Current prediction vector (g in common formulation). + Vector3 prediction_; + // Control input, currently this is only the gyroscope data (\mu in common + // formulation). + Vector3 control_input_; + // Update of the state vector. (x in common formulation). + Vector3 state_update_; + + // Sensor time of the last gyroscope processed event. + uint64_t current_gyroscope_sensor_timestamp_ns_; + // Sensor time of the last accelerometer processed event. + uint64_t current_accelerometer_sensor_timestamp_ns_; + + // Estimates of the timestep between gyroscope event in seconds. + double filtered_gyroscope_timestep_s_; + // Number of timestep samples processed so far by the filter. + uint32_t num_gyroscope_timestep_samples_; + // Norm of the accelerometer for the previous measurement. + double previous_accelerometer_norm_; + // Moving average of the accelerometer norm changes. It is computed for every + // sensor datum. + double moving_average_accelerometer_norm_change_; + + // Flag indicating if a state reset should be executed with the next + // accelerometer sample. + std::atomic execute_reset_with_next_accelerometer_sample_; + + // Flag indicating if bias estimation is enabled (enabled by default). + std::atomic bias_estimation_enabled_; + + // Bias estimator and static device detector. + GyroscopeBiasEstimator gyroscope_bias_estimator_; + + // Current bias estimate_; + Vector3 gyroscope_bias_estimate_; + + SensorFusionEkf(const SensorFusionEkf&) = delete; + SensorFusionEkf& operator=(const SensorFusionEkf&) = delete; +}; + +} // namespace cardboard + +#endif // CARDBOARD_SDK_SENSORS_SENSOR_FUSION_EKF_H_ diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/util/logging.h b/Applications/Official/DEV_FW/source/airmouse/tracking/util/logging.h new file mode 100644 index 000000000..dee224b1c --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/util/logging.h @@ -0,0 +1,38 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef CARDBOARD_SDK_UTIL_LOGGING_H_ +#define CARDBOARD_SDK_UTIL_LOGGING_H_ + +#include +#include + +#if defined(__ANDROID__) + +#include + +// Uncomment these to enable debug logging from native code + +#define CARDBOARD_LOGI(...) // __android_log_print(ANDROID_LOG_INFO, "CardboardSDK", __VA_ARGS__) +#define CARDBOARD_LOGE(...) // __android_log_print(ANDROID_LOG_ERROR, "CardboardSDK", __VA_ARGS__) + +#else + +#define CARDBOARD_LOGI(...) // FURI_LOG_I("CardboardSDK", __VA_ARGS__) +#define CARDBOARD_LOGE(...) // FURI_LOG_E("CardboardSDK", __VA_ARGS__) + +#endif + +#endif // CARDBOARD_SDK_UTIL_LOGGING_H_ diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/util/matrix_3x3.cc b/Applications/Official/DEV_FW/source/airmouse/tracking/util/matrix_3x3.cc new file mode 100644 index 000000000..9ddd847b0 --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/util/matrix_3x3.cc @@ -0,0 +1,121 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "matrix_3x3.h" + +namespace cardboard { + +Matrix3x3::Matrix3x3(double m00, double m01, double m02, double m10, double m11, double m12, + double m20, double m21, double m22) + : elem_ { { { m00, m01, m02 }, { m10, m11, m12 }, { m20, m21, m22 } } } +{ +} + +Matrix3x3::Matrix3x3() +{ + for (int row = 0; row < 3; ++row) { + for (int col = 0; col < 3; ++col) + elem_[row][col] = 0; + } +} + +Matrix3x3 Matrix3x3::Zero() +{ + Matrix3x3 result; + return result; +} + +Matrix3x3 Matrix3x3::Identity() +{ + Matrix3x3 result; + for (int row = 0; row < 3; ++row) { + result.elem_[row][row] = 1; + } + return result; +} + +void Matrix3x3::MultiplyScalar(double s) +{ + for (int row = 0; row < 3; ++row) { + for (int col = 0; col < 3; ++col) + elem_[row][col] *= s; + } +} + +Matrix3x3 Matrix3x3::Negation() const +{ + Matrix3x3 result; + for (int row = 0; row < 3; ++row) { + for (int col = 0; col < 3; ++col) + result.elem_[row][col] = -elem_[row][col]; + } + return result; +} + +Matrix3x3 Matrix3x3::Scale(const Matrix3x3& m, double s) +{ + Matrix3x3 result; + for (int row = 0; row < 3; ++row) { + for (int col = 0; col < 3; ++col) + result.elem_[row][col] = m.elem_[row][col] * s; + } + return result; +} + +Matrix3x3 Matrix3x3::Addition(const Matrix3x3& lhs, const Matrix3x3& rhs) +{ + Matrix3x3 result; + for (int row = 0; row < 3; ++row) { + for (int col = 0; col < 3; ++col) + result.elem_[row][col] = lhs.elem_[row][col] + rhs.elem_[row][col]; + } + return result; +} + +Matrix3x3 Matrix3x3::Subtraction(const Matrix3x3& lhs, const Matrix3x3& rhs) +{ + Matrix3x3 result; + for (int row = 0; row < 3; ++row) { + for (int col = 0; col < 3; ++col) + result.elem_[row][col] = lhs.elem_[row][col] - rhs.elem_[row][col]; + } + return result; +} + +Matrix3x3 Matrix3x3::Product(const Matrix3x3& m0, const Matrix3x3& m1) +{ + Matrix3x3 result; + for (int row = 0; row < 3; ++row) { + for (int col = 0; col < 3; ++col) { + result.elem_[row][col] = 0; + for (int i = 0; i < 3; ++i) + result.elem_[row][col] += m0.elem_[row][i] * m1.elem_[i][col]; + } + } + return result; +} + +bool Matrix3x3::AreEqual(const Matrix3x3& m0, const Matrix3x3& m1) +{ + for (int row = 0; row < 3; ++row) { + for (int col = 0; col < 3; ++col) { + if (m0.elem_[row][col] != m1.elem_[row][col]) + return false; + } + } + return true; +} + +} // namespace cardboard diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/util/matrix_3x3.h b/Applications/Official/DEV_FW/source/airmouse/tracking/util/matrix_3x3.h new file mode 100644 index 000000000..81e4f2158 --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/util/matrix_3x3.h @@ -0,0 +1,138 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef CARDBOARD_SDK_UTIL_MATRIX_3X3_H_ +#define CARDBOARD_SDK_UTIL_MATRIX_3X3_H_ + +#include +#include // For memcpy(). +#include // NOLINT +#include // NOLINT + +namespace cardboard { + +// The Matrix3x3 class defines a square 3-dimensional matrix. Elements are +// stored in row-major order. +// TODO(b/135461889): Make this class consistent with Matrix4x4. +class Matrix3x3 { +public: + // The default constructor zero-initializes all elements. + Matrix3x3(); + + // Dimension-specific constructors that are passed individual element values. + Matrix3x3( + double m00, + double m01, + double m02, + double m10, + double m11, + double m12, + double m20, + double m21, + double m22); + + // Constructor that reads elements from a linear array of the correct size. + explicit Matrix3x3(const double array[3 * 3]); + + // Returns a Matrix3x3 containing all zeroes. + static Matrix3x3 Zero(); + + // Returns an identity Matrix3x3. + static Matrix3x3 Identity(); + + // Mutable element accessors. + double& operator()(int row, int col) { + return elem_[row][col]; + } + std::array& operator[](int row) { + return elem_[row]; + } + + // Read-only element accessors. + const double& operator()(int row, int col) const { + return elem_[row][col]; + } + const std::array& operator[](int row) const { + return elem_[row]; + } + + // Return a pointer to the data for interfacing with libraries. + double* Data() { + return &elem_[0][0]; + } + const double* Data() const { + return &elem_[0][0]; + } + + // Self-modifying multiplication operators. + void operator*=(double s) { + MultiplyScalar(s); + } + void operator*=(const Matrix3x3& m) { + *this = Product(*this, m); + } + + // Unary operators. + Matrix3x3 operator-() const { + return Negation(); + } + + // Binary scale operators. + friend Matrix3x3 operator*(const Matrix3x3& m, double s) { + return Scale(m, s); + } + friend Matrix3x3 operator*(double s, const Matrix3x3& m) { + return Scale(m, s); + } + + // Binary matrix addition. + friend Matrix3x3 operator+(const Matrix3x3& lhs, const Matrix3x3& rhs) { + return Addition(lhs, rhs); + } + + // Binary matrix subtraction. + friend Matrix3x3 operator-(const Matrix3x3& lhs, const Matrix3x3& rhs) { + return Subtraction(lhs, rhs); + } + + // Binary multiplication operator. + friend Matrix3x3 operator*(const Matrix3x3& m0, const Matrix3x3& m1) { + return Product(m0, m1); + } + + // Exact equality and inequality comparisons. + friend bool operator==(const Matrix3x3& m0, const Matrix3x3& m1) { + return AreEqual(m0, m1); + } + friend bool operator!=(const Matrix3x3& m0, const Matrix3x3& m1) { + return !AreEqual(m0, m1); + } + +private: + // These private functions implement most of the operators. + void MultiplyScalar(double s); + Matrix3x3 Negation() const; + static Matrix3x3 Addition(const Matrix3x3& lhs, const Matrix3x3& rhs); + static Matrix3x3 Subtraction(const Matrix3x3& lhs, const Matrix3x3& rhs); + static Matrix3x3 Scale(const Matrix3x3& m, double s); + static Matrix3x3 Product(const Matrix3x3& m0, const Matrix3x3& m1); + static bool AreEqual(const Matrix3x3& m0, const Matrix3x3& m1); + + std::array, 3> elem_; +}; + +} // namespace cardboard + +#endif // CARDBOARD_SDK_UTIL_MATRIX_3X3_H_ diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/util/matrix_4x4.cc b/Applications/Official/DEV_FW/source/airmouse/tracking/util/matrix_4x4.cc new file mode 100644 index 000000000..8db3cbc5b --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/util/matrix_4x4.cc @@ -0,0 +1,87 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "matrix_4x4.h" + +#include +#include +#include + +namespace cardboard { + +Matrix4x4 Matrix4x4::Identity() +{ + Matrix4x4 ret; + for (int j = 0; j < 4; ++j) { + for (int i = 0; i < 4; ++i) { + ret.m[j][i] = (i == j) ? 1 : 0; + } + } + + return ret; +} + +Matrix4x4 Matrix4x4::Zeros() +{ + Matrix4x4 ret; + for (int j = 0; j < 4; ++j) { + for (int i = 0; i < 4; ++i) { + ret.m[j][i] = 0; + } + } + + return ret; +} + +Matrix4x4 Matrix4x4::Translation(float x, float y, float z) +{ + Matrix4x4 ret = Matrix4x4::Identity(); + ret.m[3][0] = x; + ret.m[3][1] = y; + ret.m[3][2] = z; + + return ret; +} + +Matrix4x4 Matrix4x4::Perspective(const std::array& fov, float zNear, float zFar) +{ + Matrix4x4 ret = Matrix4x4::Zeros(); + + const float xLeft = -std::tan(fov[0] * M_PI / 180.0f) * zNear; + const float xRight = std::tan(fov[1] * M_PI / 180.0f) * zNear; + const float yBottom = -std::tan(fov[2] * M_PI / 180.0f) * zNear; + const float yTop = std::tan(fov[3] * M_PI / 180.0f) * zNear; + + const float X = (2 * zNear) / (xRight - xLeft); + const float Y = (2 * zNear) / (yTop - yBottom); + const float A = (xRight + xLeft) / (xRight - xLeft); + const float B = (yTop + yBottom) / (yTop - yBottom); + const float C = (zNear + zFar) / (zNear - zFar); + const float D = (2 * zNear * zFar) / (zNear - zFar); + + ret.m[0][0] = X; + ret.m[2][0] = A; + ret.m[1][1] = Y; + ret.m[2][1] = B; + ret.m[2][2] = C; + ret.m[3][2] = D; + ret.m[2][3] = -1; + + return ret; +} + +void Matrix4x4::ToArray(float* array) const { std::memcpy(array, &m[0][0], 16 * sizeof(float)); } + +} // namespace cardboard diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/util/matrix_4x4.h b/Applications/Official/DEV_FW/source/airmouse/tracking/util/matrix_4x4.h new file mode 100644 index 000000000..9934f6be0 --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/util/matrix_4x4.h @@ -0,0 +1,37 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef CARDBOARD_SDK_UTIL_MATRIX_4X4_H_ +#define CARDBOARD_SDK_UTIL_MATRIX_4X4_H_ + +#include + +namespace cardboard { + +class Matrix4x4 { +public: + static Matrix4x4 Identity(); + static Matrix4x4 Zeros(); + static Matrix4x4 Translation(float x, float y, float z); + static Matrix4x4 Perspective(const std::array& fov, float zNear, float zFar); + void ToArray(float* array) const; + +private: + std::array, 4> m; +}; + +} // namespace cardboard + +#endif // CARDBOARD_SDK_UTIL_MATRIX4X4_H_ diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/util/matrixutils.cc b/Applications/Official/DEV_FW/source/airmouse/tracking/util/matrixutils.cc new file mode 100644 index 000000000..12470beae --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/util/matrixutils.cc @@ -0,0 +1,148 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "matrixutils.h" + +#include "vectorutils.h" + +namespace cardboard { + +namespace { + + // Returns true if the cofactor for a given row and column should be negated. + static bool IsCofactorNegated(int row, int col) + { + // Negated iff (row + col) is odd. + return ((row + col) & 1) != 0; + } + + static double CofactorElement3(const Matrix3x3& m, int row, int col) + { + static const int index[3][2] = { { 1, 2 }, { 0, 2 }, { 0, 1 } }; + const int i0 = index[row][0]; + const int i1 = index[row][1]; + const int j0 = index[col][0]; + const int j1 = index[col][1]; + const double cofactor = m(i0, j0) * m(i1, j1) - m(i0, j1) * m(i1, j0); + return IsCofactorNegated(row, col) ? -cofactor : cofactor; + } + + // Multiplies a matrix and some type of column vector to + // produce another column vector of the same type. + Vector3 MultiplyMatrixAndVector(const Matrix3x3& m, const Vector3& v) + { + Vector3 result = Vector3::Zero(); + for (int row = 0; row < 3; ++row) { + for (int col = 0; col < 3; ++col) + result[row] += m(row, col) * v[col]; + } + return result; + } + + // Sets the upper 3x3 of a Matrix to represent a 3D rotation. + void RotationMatrix3x3(const Rotation& r, Matrix3x3* matrix) + { + // + // Given a quaternion (a,b,c,d) where d is the scalar part, the 3x3 rotation + // matrix is: + // + // a^2 - b^2 - c^2 + d^2 2ab - 2cd 2ac + 2bd + // 2ab + 2cd -a^2 + b^2 - c^2 + d^2 2bc - 2ad + // 2ac - 2bd 2bc + 2ad -a^2 - b^2 + c^2 + d^2 + // + const Vector<4>& quat = r.GetQuaternion(); + const double aa = quat[0] * quat[0]; + const double bb = quat[1] * quat[1]; + const double cc = quat[2] * quat[2]; + const double dd = quat[3] * quat[3]; + + const double ab = quat[0] * quat[1]; + const double ac = quat[0] * quat[2]; + const double bc = quat[1] * quat[2]; + + const double ad = quat[0] * quat[3]; + const double bd = quat[1] * quat[3]; + const double cd = quat[2] * quat[3]; + + Matrix3x3& m = *matrix; + m[0][0] = aa - bb - cc + dd; + m[0][1] = 2 * ab - 2 * cd; + m[0][2] = 2 * ac + 2 * bd; + m[1][0] = 2 * ab + 2 * cd; + m[1][1] = -aa + bb - cc + dd; + m[1][2] = 2 * bc - 2 * ad; + m[2][0] = 2 * ac - 2 * bd; + m[2][1] = 2 * bc + 2 * ad; + m[2][2] = -aa - bb + cc + dd; + } + +} // anonymous namespace + +Vector3 operator*(const Matrix3x3& m, const Vector3& v) { return MultiplyMatrixAndVector(m, v); } + +Matrix3x3 CofactorMatrix(const Matrix3x3& m) +{ + Matrix3x3 result; + for (int row = 0; row < 3; ++row) { + for (int col = 0; col < 3; ++col) + result(row, col) = CofactorElement3(m, row, col); + } + return result; +} + +Matrix3x3 AdjugateWithDeterminant(const Matrix3x3& m, double* determinant) +{ + const Matrix3x3 cofactor_matrix = CofactorMatrix(m); + if (determinant) { + *determinant = m(0, 0) * cofactor_matrix(0, 0) + m(0, 1) * cofactor_matrix(0, 1) + + m(0, 2) * cofactor_matrix(0, 2); + } + return Transpose(cofactor_matrix); +} + +// Returns the transpose of a matrix. +Matrix3x3 Transpose(const Matrix3x3& m) +{ + Matrix3x3 result; + for (int row = 0; row < 3; ++row) { + for (int col = 0; col < 3; ++col) + result(row, col) = m(col, row); + } + return result; +} + +Matrix3x3 InverseWithDeterminant(const Matrix3x3& m, double* determinant) +{ + // The inverse is the adjugate divided by the determinant. + double det; + Matrix3x3 adjugate = AdjugateWithDeterminant(m, &det); + if (determinant) + *determinant = det; + if (det == 0) + return Matrix3x3::Zero(); + else + return adjugate * (1 / det); +} + +Matrix3x3 Inverse(const Matrix3x3& m) { return InverseWithDeterminant(m, nullptr); } + +Matrix3x3 RotationMatrixNH(const Rotation& r) +{ + Matrix3x3 m; + RotationMatrix3x3(r, &m); + return m; +} + +} // namespace cardboard diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/util/matrixutils.h b/Applications/Official/DEV_FW/source/airmouse/tracking/util/matrixutils.h new file mode 100644 index 000000000..80f9b2168 --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/util/matrixutils.h @@ -0,0 +1,65 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef CARDBOARD_SDK_UTIL_MATRIXUTILS_H_ +#define CARDBOARD_SDK_UTIL_MATRIXUTILS_H_ + +// +// This file contains operators and free functions that define generic Matrix +// operations. +// + +#include "matrix_3x3.h" +#include "rotation.h" +#include "vector.h" + +namespace cardboard { + +// Returns the transpose of a matrix. +Matrix3x3 Transpose(const Matrix3x3& m); + +// Multiplies a Matrix and a column Vector of the same Dimension to produce +// another column Vector. +Vector3 operator*(const Matrix3x3& m, const Vector3& v); + +// Returns the determinant of the matrix. This function is defined for all the +// typedef'ed Matrix types. +double Determinant(const Matrix3x3& m); + +// Returns the adjugate of the matrix, which is defined as the transpose of the +// cofactor matrix. This function is defined for all the typedef'ed Matrix +// types. The determinant of the matrix is computed as a side effect, so it is +// returned in the determinant parameter if it is not null. +Matrix3x3 AdjugateWithDeterminant(const Matrix3x3& m, double* determinant); + +// Returns the inverse of the matrix. This function is defined for all the +// typedef'ed Matrix types. The determinant of the matrix is computed as a +// side effect, so it is returned in the determinant parameter if it is not +// null. If the determinant is 0, the returned matrix has all zeroes. +Matrix3x3 InverseWithDeterminant(const Matrix3x3& m, double* determinant); + +// Returns the inverse of the matrix. This function is defined for all the +// typedef'ed Matrix types. If the determinant of the matrix is 0, the returned +// matrix has all zeroes. +Matrix3x3 Inverse(const Matrix3x3& m); + +// Returns a 3x3 Matrix representing a 3D rotation. This creates a Matrix that +// does not work with homogeneous coordinates, so the function name ends in +// "NH". +Matrix3x3 RotationMatrixNH(const Rotation& r); + +} // namespace cardboard + +#endif // CARDBOARD_SDK_UTIL_MATRIXUTILS_H_ diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/util/rotation.cc b/Applications/Official/DEV_FW/source/airmouse/tracking/util/rotation.cc new file mode 100644 index 000000000..5c3d09a2b --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/util/rotation.cc @@ -0,0 +1,117 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "rotation.h" + +#include +#include + +#include "vectorutils.h" + +namespace cardboard { + +void Rotation::SetAxisAndAngle(const VectorType& axis, double angle) +{ + VectorType unit_axis = axis; + if (!Normalize(&unit_axis)) { + *this = Identity(); + } else { + double a = angle / 2; + const double s = sin(a); + const VectorType v(unit_axis * s); + SetQuaternion(QuaternionType(v[0], v[1], v[2], cos(a))); + } +} + +Rotation Rotation::FromRotationMatrix(const Matrix3x3& mat) +{ + static const double kOne = 1.0; + static const double kFour = 4.0; + + const double d0 = mat(0, 0), d1 = mat(1, 1), d2 = mat(2, 2); + const double ww = kOne + d0 + d1 + d2; + const double xx = kOne + d0 - d1 - d2; + const double yy = kOne - d0 + d1 - d2; + const double zz = kOne - d0 - d1 + d2; + + const double max = std::max(ww, std::max(xx, std::max(yy, zz))); + if (ww == max) { + const double w4 = sqrt(ww * kFour); + return Rotation::FromQuaternion(QuaternionType((mat(2, 1) - mat(1, 2)) / w4, + (mat(0, 2) - mat(2, 0)) / w4, (mat(1, 0) - mat(0, 1)) / w4, w4 / kFour)); + } + + if (xx == max) { + const double x4 = sqrt(xx * kFour); + return Rotation::FromQuaternion(QuaternionType(x4 / kFour, (mat(0, 1) + mat(1, 0)) / x4, + (mat(0, 2) + mat(2, 0)) / x4, (mat(2, 1) - mat(1, 2)) / x4)); + } + + if (yy == max) { + const double y4 = sqrt(yy * kFour); + return Rotation::FromQuaternion(QuaternionType((mat(0, 1) + mat(1, 0)) / y4, y4 / kFour, + (mat(1, 2) + mat(2, 1)) / y4, (mat(0, 2) - mat(2, 0)) / y4)); + } + + // zz is the largest component. + const double z4 = sqrt(zz * kFour); + return Rotation::FromQuaternion(QuaternionType((mat(0, 2) + mat(2, 0)) / z4, + (mat(1, 2) + mat(2, 1)) / z4, z4 / kFour, (mat(1, 0) - mat(0, 1)) / z4)); +} + +void Rotation::GetAxisAndAngle(VectorType* axis, double* angle) const +{ + VectorType vec(quat_[0], quat_[1], quat_[2]); + if (Normalize(&vec)) { + *angle = 2 * acos(quat_[3]); + *axis = vec; + } else { + *axis = VectorType(1, 0, 0); + *angle = 0.0; + } +} + +Rotation Rotation::RotateInto(const VectorType& from, const VectorType& to) +{ + static const double kTolerance = std::numeric_limits::epsilon() * 100; + + // Directly build the quaternion using the following technique: + // http://lolengine.net/blog/2014/02/24/quaternion-from-two-vectors-final + const double norm_u_norm_v = sqrt(LengthSquared(from) * LengthSquared(to)); + double real_part = norm_u_norm_v + Dot(from, to); + VectorType w; + if (real_part < kTolerance * norm_u_norm_v) { + // If |from| and |to| are exactly opposite, rotate 180 degrees around an + // arbitrary orthogonal axis. Axis normalization can happen later, when we + // normalize the quaternion. + real_part = 0.0; + w = (abs(from[0]) > abs(from[2])) ? VectorType(-from[1], from[0], 0) + : VectorType(0, -from[2], from[1]); + } else { + // Otherwise, build the quaternion the standard way. + w = Cross(from, to); + } + + // Build and return a normalized quaternion. + // Note that Rotation::FromQuaternion automatically performs normalization. + return Rotation::FromQuaternion(QuaternionType(w[0], w[1], w[2], real_part)); +} + +Rotation::VectorType Rotation::operator*(const Rotation::VectorType& v) const +{ + return ApplyToVector(v); +} + +} // namespace cardboard diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/util/rotation.h b/Applications/Official/DEV_FW/source/airmouse/tracking/util/rotation.h new file mode 100644 index 000000000..8730cb3b0 --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/util/rotation.h @@ -0,0 +1,156 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef CARDBOARD_SDK_UTIL_ROTATION_H_ +#define CARDBOARD_SDK_UTIL_ROTATION_H_ + +#include "matrix_3x3.h" +#include "vector.h" +#include "vectorutils.h" + +namespace cardboard { + +// The Rotation class represents a rotation around a 3-dimensional axis. It +// uses normalized quaternions internally to make the math robust. +class Rotation { +public: + // Convenience typedefs for vector of the correct type. + typedef Vector<3> VectorType; + typedef Vector<4> QuaternionType; + + // The default constructor creates an identity Rotation, which has no effect. + Rotation() { + quat_.Set(0, 0, 0, 1); + } + + // Returns an identity Rotation, which has no effect. + static Rotation Identity() { + return Rotation(); + } + + // Sets the Rotation from a quaternion (4D vector), which is first normalized. + void SetQuaternion(const QuaternionType& quaternion) { + quat_ = Normalized(quaternion); + } + + // Returns the Rotation as a normalized quaternion (4D vector). + const QuaternionType& GetQuaternion() const { + return quat_; + } + + // Sets the Rotation to rotate by the given angle around the given axis, + // following the right-hand rule. The axis does not need to be unit + // length. If it is zero length, this results in an identity Rotation. + void SetAxisAndAngle(const VectorType& axis, double angle); + + // Returns the right-hand rule axis and angle corresponding to the + // Rotation. If the Rotation is the identity rotation, this returns the +X + // axis and an angle of 0. + void GetAxisAndAngle(VectorType* axis, double* angle) const; + + // Convenience function that constructs and returns a Rotation given an axis + // and angle. + static Rotation FromAxisAndAngle(const VectorType& axis, double angle) { + Rotation r; + r.SetAxisAndAngle(axis, angle); + return r; + } + + // Convenience function that constructs and returns a Rotation given a + // quaternion. + static Rotation FromQuaternion(const QuaternionType& quat) { + Rotation r; + r.SetQuaternion(quat); + return r; + } + + // Convenience function that constructs and returns a Rotation given a + // rotation matrix R with $R^\top R = I && det(R) = 1$. + static Rotation FromRotationMatrix(const Matrix3x3& mat); + + // Convenience function that constructs and returns a Rotation given Euler + // angles that are applied in the order of rotate-Z by roll, rotate-X by + // pitch, rotate-Y by yaw (same as GetRollPitchYaw). + static Rotation FromRollPitchYaw(double roll, double pitch, double yaw) { + VectorType x(1, 0, 0), y(0, 1, 0), z(0, 0, 1); + return FromAxisAndAngle(z, roll) * (FromAxisAndAngle(x, pitch) * FromAxisAndAngle(y, yaw)); + } + + // Convenience function that constructs and returns a Rotation given Euler + // angles that are applied in the order of rotate-Y by yaw, rotate-X by + // pitch, rotate-Z by roll (same as GetYawPitchRoll). + static Rotation FromYawPitchRoll(double yaw, double pitch, double roll) { + VectorType x(1, 0, 0), y(0, 1, 0), z(0, 0, 1); + return FromAxisAndAngle(y, yaw) * (FromAxisAndAngle(x, pitch) * FromAxisAndAngle(z, roll)); + } + + // Constructs and returns a Rotation that rotates one vector to another along + // the shortest arc. This returns an identity rotation if either vector has + // zero length. + static Rotation RotateInto(const VectorType& from, const VectorType& to); + + // The negation operator returns the inverse rotation. + friend Rotation operator-(const Rotation& r) { + // Because we store normalized quaternions, the inverse is found by + // negating the vector part. + return Rotation(-r.quat_[0], -r.quat_[1], -r.quat_[2], r.quat_[3]); + } + + // Appends a rotation to this one. + Rotation& operator*=(const Rotation& r) { + const QuaternionType& qr = r.quat_; + QuaternionType& qt = quat_; + SetQuaternion(QuaternionType( + qr[3] * qt[0] + qr[0] * qt[3] + qr[2] * qt[1] - qr[1] * qt[2], + qr[3] * qt[1] + qr[1] * qt[3] + qr[0] * qt[2] - qr[2] * qt[0], + qr[3] * qt[2] + qr[2] * qt[3] + qr[1] * qt[0] - qr[0] * qt[1], + qr[3] * qt[3] - qr[0] * qt[0] - qr[1] * qt[1] - qr[2] * qt[2])); + return *this; + } + + // Binary multiplication operator - returns a composite Rotation. + friend const Rotation operator*(const Rotation& r0, const Rotation& r1) { + Rotation r = r0; + r *= r1; + return r; + } + + // Multiply a Rotation and a Vector to get a Vector. + VectorType operator*(const VectorType& v) const; + +private: + // Private constructor that builds a Rotation from quaternion components. + Rotation(double q0, double q1, double q2, double q3) + : quat_(q0, q1, q2, q3) { + } + + // Applies a Rotation to a Vector to rotate the Vector. Method borrowed from: + // http://blog.molecular-matters.com/2013/05/24/a-faster-quaternion-vector-multiplication/ + VectorType ApplyToVector(const VectorType& v) const { + VectorType im(quat_[0], quat_[1], quat_[2]); + VectorType temp = 2.0 * Cross(im, v); + return v + quat_[3] * temp + Cross(im, temp); + } + + // The rotation represented as a normalized quaternion. (Unit quaternions are + // required for constructing rotation matrices, so it makes sense to always + // store them that way.) The vector part is in the first 3 elements, and the + // scalar part is in the last element. + QuaternionType quat_; +}; + +} // namespace cardboard + +#endif // CARDBOARD_SDK_UTIL_ROTATION_H_ diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/util/vector.h b/Applications/Official/DEV_FW/source/airmouse/tracking/util/vector.h new file mode 100644 index 000000000..64c4f2546 --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/util/vector.h @@ -0,0 +1,251 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef CARDBOARD_SDK_UTIL_VECTOR_H_ +#define CARDBOARD_SDK_UTIL_VECTOR_H_ + +#include + +namespace cardboard { + +// Geometric N-dimensional Vector class. +template +class Vector { +public: + // The default constructor zero-initializes all elements. + Vector(); + + // Dimension-specific constructors that are passed individual element values. + constexpr Vector(double e0, double e1, double e2); + constexpr Vector(double e0, double e1, double e2, double e3); + + // Constructor for a Vector of dimension N from a Vector of dimension N-1 and + // a scalar of the correct type, assuming N is at least 2. + // constexpr Vector(const Vector& v, double s); + + void Set(double e0, double e1, double e2); // Only when Dimension == 3. + void Set(double e0, double e1, double e2, + double e3); // Only when Dimension == 4. + + // Mutable element accessor. + double& operator[](int index) { + return elem_[index]; + } + + // Element accessor. + double operator[](int index) const { + return elem_[index]; + } + + // Returns a Vector containing all zeroes. + static Vector Zero(); + + // Self-modifying operators. + void operator+=(const Vector& v) { + Add(v); + } + void operator-=(const Vector& v) { + Subtract(v); + } + void operator*=(double s) { + Multiply(s); + } + void operator/=(double s) { + Divide(s); + } + + // Unary negation operator. + Vector operator-() const { + return Negation(); + } + + // Binary operators. + friend Vector operator+(const Vector& v0, const Vector& v1) { + return Sum(v0, v1); + } + friend Vector operator-(const Vector& v0, const Vector& v1) { + return Difference(v0, v1); + } + friend Vector operator*(const Vector& v, double s) { + return Scale(v, s); + } + friend Vector operator*(double s, const Vector& v) { + return Scale(v, s); + } + friend Vector operator*(const Vector& v, const Vector& s) { + return Product(v, s); + } + friend Vector operator/(const Vector& v, double s) { + return Divide(v, s); + } + + // Self-modifying addition. + void Add(const Vector& v); + // Self-modifying subtraction. + void Subtract(const Vector& v); + // Self-modifying multiplication by a scalar. + void Multiply(double s); + // Self-modifying division by a scalar. + void Divide(double s); + + // Unary negation. + Vector Negation() const; + + // Binary component-wise multiplication. + static Vector Product(const Vector& v0, const Vector& v1); + // Binary component-wise addition. + static Vector Sum(const Vector& v0, const Vector& v1); + // Binary component-wise subtraction. + static Vector Difference(const Vector& v0, const Vector& v1); + // Binary multiplication by a scalar. + static Vector Scale(const Vector& v, double s); + // Binary division by a scalar. + static Vector Divide(const Vector& v, double s); + +private: + std::array elem_; +}; +//------------------------------------------------------------------------------ + +template +Vector::Vector() { + for(int i = 0; i < Dimension; i++) { + elem_[i] = 0; + } +} + +template +constexpr Vector::Vector(double e0, double e1, double e2) + : elem_{e0, e1, e2} { +} + +template +constexpr Vector::Vector(double e0, double e1, double e2, double e3) + : elem_{e0, e1, e2, e3} { +} +/* +template <> +constexpr Vector<4>::Vector(const Vector<3>& v, double s) + : elem_{v[0], v[1], v[2], s} {} +*/ +template +void Vector::Set(double e0, double e1, double e2) { + elem_[0] = e0; + elem_[1] = e1; + elem_[2] = e2; +} + +template +void Vector::Set(double e0, double e1, double e2, double e3) { + elem_[0] = e0; + elem_[1] = e1; + elem_[2] = e2; + elem_[3] = e3; +} + +template +Vector Vector::Zero() { + Vector v; + return v; +} + +template +void Vector::Add(const Vector& v) { + for(int i = 0; i < Dimension; i++) { + elem_[i] += v[i]; + } +} + +template +void Vector::Subtract(const Vector& v) { + for(int i = 0; i < Dimension; i++) { + elem_[i] -= v[i]; + } +} + +template +void Vector::Multiply(double s) { + for(int i = 0; i < Dimension; i++) { + elem_[i] *= s; + } +} + +template +void Vector::Divide(double s) { + for(int i = 0; i < Dimension; i++) { + elem_[i] /= s; + } +} + +template +Vector Vector::Negation() const { + Vector ret; + for(int i = 0; i < Dimension; i++) { + ret.elem_[i] = -elem_[i]; + } + return ret; +} + +template +Vector Vector::Product(const Vector& v0, const Vector& v1) { + Vector ret; + for(int i = 0; i < Dimension; i++) { + ret.elem_[i] = v0[i] * v1[i]; + } + return ret; +} + +template +Vector Vector::Sum(const Vector& v0, const Vector& v1) { + Vector ret; + for(int i = 0; i < Dimension; i++) { + ret.elem_[i] = v0[i] + v1[i]; + } + return ret; +} + +template +Vector Vector::Difference(const Vector& v0, const Vector& v1) { + Vector ret; + for(int i = 0; i < Dimension; i++) { + ret.elem_[i] = v0[i] - v1[i]; + } + return ret; +} + +template +Vector Vector::Scale(const Vector& v, double s) { + Vector ret; + for(int i = 0; i < Dimension; i++) { + ret.elem_[i] = v[i] * s; + } + return ret; +} + +template +Vector Vector::Divide(const Vector& v, double s) { + Vector ret; + for(int i = 0; i < Dimension; i++) { + ret.elem_[i] = v[i] / s; + } + return ret; +} + +typedef Vector<3> Vector3; +typedef Vector<4> Vector4; + +} // namespace cardboard + +#endif // CARDBOARD_SDK_UTIL_VECTOR_H_ diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/util/vectorutils.cc b/Applications/Official/DEV_FW/source/airmouse/tracking/util/vectorutils.cc new file mode 100644 index 000000000..b8f419c04 --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/util/vectorutils.cc @@ -0,0 +1,40 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "vectorutils.h" + +namespace cardboard { + +// Returns the dot (inner) product of two Vectors. +double Dot(const Vector<3>& v0, const Vector<3>& v1) +{ + return v0[0] * v1[0] + v0[1] * v1[1] + v0[2] * v1[2]; +} + +// Returns the dot (inner) product of two Vectors. +double Dot(const Vector<4>& v0, const Vector<4>& v1) +{ + return v0[0] * v1[0] + v0[1] * v1[1] + v0[2] * v1[2] + v0[3] * v1[3]; +} + +// Returns the 3-dimensional cross product of 2 Vectors. Note that this is +// defined only for 3-dimensional Vectors. +Vector<3> Cross(const Vector<3>& v0, const Vector<3>& v1) +{ + return Vector<3>(v0[1] * v1[2] - v0[2] * v1[1], v0[2] * v1[0] - v0[0] * v1[2], + v0[0] * v1[1] - v0[1] * v1[0]); +} + +} // namespace cardboard diff --git a/Applications/Official/DEV_FW/source/airmouse/tracking/util/vectorutils.h b/Applications/Official/DEV_FW/source/airmouse/tracking/util/vectorutils.h new file mode 100644 index 000000000..054236713 --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/tracking/util/vectorutils.h @@ -0,0 +1,76 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef CARDBOARD_SDK_UTIL_VECTORUTILS_H_ +#define CARDBOARD_SDK_UTIL_VECTORUTILS_H_ + +// +// This file contains free functions that operate on Vector instances. +// + +#include + +#include "vector.h" + +namespace cardboard { + +// Returns the dot (inner) product of two Vectors. +double Dot(const Vector<3>& v0, const Vector<3>& v1); + +// Returns the dot (inner) product of two Vectors. +double Dot(const Vector<4>& v0, const Vector<4>& v1); + +// Returns the 3-dimensional cross product of 2 Vectors. Note that this is +// defined only for 3-dimensional Vectors. +Vector<3> Cross(const Vector<3>& v0, const Vector<3>& v1); + +// Returns the square of the length of a Vector. +template +double LengthSquared(const Vector& v) { + return Dot(v, v); +} + +// Returns the geometric length of a Vector. +template +double Length(const Vector& v) { + return sqrt(LengthSquared(v)); +} + +// the Vector untouched and returns false. +template +bool Normalize(Vector* v) { + const double len = Length(*v); + if(len == 0) { + return false; + } else { + (*v) /= len; + return true; + } +} + +// Returns a unit-length version of a Vector. If the given Vector has no +// length, this returns a Zero() Vector. +template +Vector Normalized(const Vector& v) { + Vector result = v; + if(Normalize(&result)) + return result; + else + return Vector::Zero(); +} + +} // namespace cardboard + +#endif // CARDBOARD_SDK_UTIL_VECTORUTILS_H_ diff --git a/Applications/Official/DEV_FW/source/airmouse/views/bt_mouse.c b/Applications/Official/DEV_FW/source/airmouse/views/bt_mouse.c new file mode 100644 index 000000000..e6e0ae45a --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/views/bt_mouse.c @@ -0,0 +1,303 @@ +#include "bt_mouse.h" +#include "../tracking/main_loop.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct ButtonEvent { + int8_t button; + bool state; +} ButtonEvent; + +#define BTN_EVT_QUEUE_SIZE 32 + +struct BtMouse { + View* view; + ViewDispatcher* view_dispatcher; + Bt* bt; + NotificationApp* notifications; + FuriMutex* mutex; + FuriThread* thread; + bool connected; + + // Current mouse state + uint8_t btn; + int dx; + int dy; + int wheel; + + // Circular buffer; + // (qhead == qtail) means either empty or overflow. + // We'll ignore overflow and treat it as empty. + int qhead; + int qtail; + ButtonEvent queue[BTN_EVT_QUEUE_SIZE]; +}; + +#define BT_MOUSE_FLAG_INPUT_EVENT (1UL << 0) +#define BT_MOUSE_FLAG_KILL_THREAD (1UL << 1) +#define BT_MOUSE_FLAG_ALL (BT_MOUSE_FLAG_INPUT_EVENT | BT_MOUSE_FLAG_KILL_THREAD) + +#define MOUSE_MOVE_SHORT 5 +#define MOUSE_MOVE_LONG 20 + +static void bt_mouse_notify_event(BtMouse* bt_mouse) { + FuriThreadId thread_id = furi_thread_get_id(bt_mouse->thread); + furi_assert(thread_id); + furi_thread_flags_set(thread_id, BT_MOUSE_FLAG_INPUT_EVENT); +} + +static void bt_mouse_draw_callback(Canvas* canvas, void* context) { + UNUSED(context); + canvas_clear(canvas); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 0, 10, "Bluetooth Mouse mode"); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 0, 63, "Hold [back] to exit"); +} + +static void bt_mouse_button_state(BtMouse* bt_mouse, int8_t button, bool state) { + ButtonEvent event; + event.button = button; + event.state = state; + + if(bt_mouse->connected) { + furi_mutex_acquire(bt_mouse->mutex, FuriWaitForever); + bt_mouse->queue[bt_mouse->qtail++] = event; + bt_mouse->qtail %= BTN_EVT_QUEUE_SIZE; + furi_mutex_release(bt_mouse->mutex); + bt_mouse_notify_event(bt_mouse); + } +} + +static void bt_mouse_process(BtMouse* bt_mouse, InputEvent* event) { + with_view_model( + bt_mouse->view, + void* model, + { + UNUSED(model); + if(event->key == InputKeyUp) { + if(event->type == InputTypePress) { + bt_mouse_button_state(bt_mouse, HID_MOUSE_BTN_LEFT, true); + } else if(event->type == InputTypeRelease) { + bt_mouse_button_state(bt_mouse, HID_MOUSE_BTN_LEFT, false); + } + } else if(event->key == InputKeyDown) { + if(event->type == InputTypePress) { + bt_mouse_button_state(bt_mouse, HID_MOUSE_BTN_RIGHT, true); + } else if(event->type == InputTypeRelease) { + bt_mouse_button_state(bt_mouse, HID_MOUSE_BTN_RIGHT, false); + } + } else if(event->key == InputKeyOk) { + if(event->type == InputTypePress) { + bt_mouse_button_state(bt_mouse, HID_MOUSE_BTN_WHEEL, true); + } else if(event->type == InputTypeRelease) { + bt_mouse_button_state(bt_mouse, HID_MOUSE_BTN_WHEEL, false); + } + } + }, + true); +} + +static bool bt_mouse_input_callback(InputEvent* event, void* context) { + furi_assert(context); + BtMouse* bt_mouse = context; + bool consumed = false; + + if(event->type == InputTypeLong && event->key == InputKeyBack) { + furi_hal_bt_hid_mouse_release_all(); + } else { + bt_mouse_process(bt_mouse, event); + consumed = true; + } + + return consumed; +} + +void bt_mouse_connection_status_changed_callback(BtStatus status, void* context) { + furi_assert(context); + BtMouse* bt_mouse = context; + + bt_mouse->connected = (status == BtStatusConnected); + if(bt_mouse->connected) { + notification_internal_message(bt_mouse->notifications, &sequence_set_blue_255); + tracking_begin(); + view_dispatcher_send_custom_event(bt_mouse->view_dispatcher, 0); + } else { + tracking_end(); + notification_internal_message(bt_mouse->notifications, &sequence_reset_blue); + } + + //with_view_model( + // bt_mouse->view, void * model, { model->connected = connected; }, true); +} + +bool bt_mouse_move(int8_t dx, int8_t dy, void* context) { + furi_assert(context); + BtMouse* bt_mouse = context; + + if(bt_mouse->connected) { + furi_mutex_acquire(bt_mouse->mutex, FuriWaitForever); + bt_mouse->dx += dx; + bt_mouse->dy += dy; + furi_mutex_release(bt_mouse->mutex); + bt_mouse_notify_event(bt_mouse); + } + + return true; +} + +void bt_mouse_enter_callback(void* context) { + furi_assert(context); + BtMouse* bt_mouse = context; + + bt_mouse->bt = furi_record_open(RECORD_BT); + bt_mouse->notifications = furi_record_open(RECORD_NOTIFICATION); + bt_set_status_changed_callback( + bt_mouse->bt, bt_mouse_connection_status_changed_callback, bt_mouse); + furi_assert(bt_set_profile(bt_mouse->bt, BtProfileHidKeyboard)); + furi_hal_bt_start_advertising(); +} + +bool bt_mouse_custom_callback(uint32_t event, void* context) { + UNUSED(event); + furi_assert(context); + BtMouse* bt_mouse = context; + + tracking_step(bt_mouse_move, context); + furi_delay_ms(3); // Magic! Removing this will break the buttons + + view_dispatcher_send_custom_event(bt_mouse->view_dispatcher, 0); + return true; +} + +void bt_mouse_exit_callback(void* context) { + furi_assert(context); + BtMouse* bt_mouse = context; + + tracking_end(); + notification_internal_message(bt_mouse->notifications, &sequence_reset_blue); + + furi_hal_bt_stop_advertising(); + bt_set_profile(bt_mouse->bt, BtProfileSerial); + + furi_record_close(RECORD_NOTIFICATION); + bt_mouse->notifications = NULL; + furi_record_close(RECORD_BT); + bt_mouse->bt = NULL; +} + +static int8_t clamp(int t) { + if(t < -128) { + return -128; + } else if(t > 127) { + return 127; + } + return t; +} + +static int32_t bt_mouse_thread_callback(void* context) { + furi_assert(context); + BtMouse* bt_mouse = (BtMouse*)context; + + while(1) { + uint32_t flags = + furi_thread_flags_wait(BT_MOUSE_FLAG_ALL, FuriFlagWaitAny, FuriWaitForever); + if(flags & BT_MOUSE_FLAG_KILL_THREAD) { + break; + } + if(flags & BT_MOUSE_FLAG_INPUT_EVENT) { + furi_mutex_acquire(bt_mouse->mutex, FuriWaitForever); + + ButtonEvent event; + bool send_buttons = false; + if(bt_mouse->qhead != bt_mouse->qtail) { + event = bt_mouse->queue[bt_mouse->qhead++]; + bt_mouse->qhead %= BTN_EVT_QUEUE_SIZE; + send_buttons = true; + } + + int8_t dx = clamp(bt_mouse->dx); + bt_mouse->dx -= dx; + int8_t dy = clamp(bt_mouse->dy); + bt_mouse->dy -= dy; + int8_t wheel = clamp(bt_mouse->wheel); + bt_mouse->wheel -= wheel; + + furi_mutex_release(bt_mouse->mutex); + + if(bt_mouse->connected && send_buttons) { + if(event.state) { + furi_hal_bt_hid_mouse_press(event.button); + } else { + furi_hal_bt_hid_mouse_release(event.button); + } + } + + if(bt_mouse->connected && (dx != 0 || dy != 0)) { + furi_hal_bt_hid_mouse_move(dx, dy); + } + + if(bt_mouse->connected && wheel != 0) { + furi_hal_bt_hid_mouse_scroll(wheel); + } + } + } + + return 0; +} + +void bt_mouse_thread_start(BtMouse* bt_mouse) { + furi_assert(bt_mouse); + bt_mouse->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + bt_mouse->thread = furi_thread_alloc(); + furi_thread_set_name(bt_mouse->thread, "BtSender"); + furi_thread_set_stack_size(bt_mouse->thread, 1024); + furi_thread_set_context(bt_mouse->thread, bt_mouse); + furi_thread_set_callback(bt_mouse->thread, bt_mouse_thread_callback); + furi_thread_start(bt_mouse->thread); +} + +void bt_mouse_thread_stop(BtMouse* bt_mouse) { + furi_assert(bt_mouse); + FuriThreadId thread_id = furi_thread_get_id(bt_mouse->thread); + furi_assert(thread_id); + furi_thread_flags_set(thread_id, BT_MOUSE_FLAG_KILL_THREAD); + furi_thread_join(bt_mouse->thread); + furi_thread_free(bt_mouse->thread); + furi_mutex_free(bt_mouse->mutex); +} + +BtMouse* bt_mouse_alloc(ViewDispatcher* view_dispatcher) { + BtMouse* bt_mouse = malloc(sizeof(BtMouse)); + memset(bt_mouse, 0, sizeof(BtMouse)); + + bt_mouse->view = view_alloc(); + bt_mouse->view_dispatcher = view_dispatcher; + view_set_context(bt_mouse->view, bt_mouse); + view_set_draw_callback(bt_mouse->view, bt_mouse_draw_callback); + view_set_input_callback(bt_mouse->view, bt_mouse_input_callback); + view_set_enter_callback(bt_mouse->view, bt_mouse_enter_callback); + view_set_custom_callback(bt_mouse->view, bt_mouse_custom_callback); + view_set_exit_callback(bt_mouse->view, bt_mouse_exit_callback); + bt_mouse_thread_start(bt_mouse); + return bt_mouse; +} + +void bt_mouse_free(BtMouse* bt_mouse) { + furi_assert(bt_mouse); + bt_mouse_thread_stop(bt_mouse); + view_free(bt_mouse->view); + free(bt_mouse); +} + +View* bt_mouse_get_view(BtMouse* bt_mouse) { + furi_assert(bt_mouse); + return bt_mouse->view; +} diff --git a/Applications/Official/DEV_FW/source/airmouse/views/bt_mouse.h b/Applications/Official/DEV_FW/source/airmouse/views/bt_mouse.h new file mode 100644 index 000000000..09153d8fa --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/views/bt_mouse.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +typedef struct BtMouse BtMouse; + +BtMouse* bt_mouse_alloc(ViewDispatcher* view_dispatcher); + +void bt_mouse_free(BtMouse* bt_mouse); + +View* bt_mouse_get_view(BtMouse* bt_mouse); + +void bt_mouse_set_connected_status(BtMouse* bt_mouse, bool connected); diff --git a/Applications/Official/DEV_FW/source/airmouse/views/calibration.c b/Applications/Official/DEV_FW/source/airmouse/views/calibration.c new file mode 100644 index 000000000..a92f68be4 --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/views/calibration.c @@ -0,0 +1,69 @@ +#include "calibration.h" +#include "../tracking/main_loop.h" +#include "../air_mouse.h" + +#include +#include + +struct Calibration { + View* view; + ViewDispatcher* view_dispatcher; +}; + +static void calibration_draw_callback(Canvas* canvas, void* context) { + UNUSED(context); + canvas_clear(canvas); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 0, 10, "Calibrating..."); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 0, 63, "Please wait"); +} + +void calibration_enter_callback(void* context) { + furi_assert(context); + Calibration* calibration = context; + calibration_begin(); + view_dispatcher_send_custom_event(calibration->view_dispatcher, 0); +} + +bool calibration_custom_callback(uint32_t event, void* context) { + UNUSED(event); + furi_assert(context); + Calibration* calibration = context; + + if(calibration_step()) { + view_dispatcher_switch_to_view(calibration->view_dispatcher, AirMouseViewSubmenu); + } else { + view_dispatcher_send_custom_event(calibration->view_dispatcher, 0); + } + + return true; +} + +void calibration_exit_callback(void* context) { + furi_assert(context); + calibration_end(); +} + +Calibration* calibration_alloc(ViewDispatcher* view_dispatcher) { + Calibration* calibration = malloc(sizeof(Calibration)); + calibration->view = view_alloc(); + calibration->view_dispatcher = view_dispatcher; + view_set_context(calibration->view, calibration); + view_set_draw_callback(calibration->view, calibration_draw_callback); + view_set_enter_callback(calibration->view, calibration_enter_callback); + view_set_custom_callback(calibration->view, calibration_custom_callback); + view_set_exit_callback(calibration->view, calibration_exit_callback); + return calibration; +} + +void calibration_free(Calibration* calibration) { + furi_assert(calibration); + view_free(calibration->view); + free(calibration); +} + +View* calibration_get_view(Calibration* calibration) { + furi_assert(calibration); + return calibration->view; +} diff --git a/Applications/Official/DEV_FW/source/airmouse/views/calibration.h b/Applications/Official/DEV_FW/source/airmouse/views/calibration.h new file mode 100644 index 000000000..da44ce0cd --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/views/calibration.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +typedef struct Calibration Calibration; + +Calibration* calibration_alloc(ViewDispatcher* view_dispatcher); + +void calibration_free(Calibration* calibration); + +View* calibration_get_view(Calibration* calibration); diff --git a/Applications/Official/DEV_FW/source/airmouse/views/usb_mouse.c b/Applications/Official/DEV_FW/source/airmouse/views/usb_mouse.c new file mode 100644 index 000000000..5d9ab4352 --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/views/usb_mouse.c @@ -0,0 +1,129 @@ +#include "usb_mouse.h" +#include "../tracking/main_loop.h" + +#include +#include +#include +#include + +struct UsbMouse { + View* view; + ViewDispatcher* view_dispatcher; + FuriHalUsbInterface* usb_mode_prev; +}; + +static void usb_mouse_draw_callback(Canvas* canvas, void* context) { + UNUSED(context); + canvas_clear(canvas); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 0, 10, "USB Mouse mode"); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 0, 63, "Hold [back] to exit"); +} + +static void usb_mouse_process(UsbMouse* usb_mouse, InputEvent* event) { + with_view_model( + usb_mouse->view, + void* model, + { + UNUSED(model); + if(event->key == InputKeyUp) { + if(event->type == InputTypePress) { + furi_hal_hid_mouse_press(HID_MOUSE_BTN_LEFT); + } else if(event->type == InputTypeRelease) { + furi_hal_hid_mouse_release(HID_MOUSE_BTN_LEFT); + } + } else if(event->key == InputKeyDown) { + if(event->type == InputTypePress) { + furi_hal_hid_mouse_press(HID_MOUSE_BTN_RIGHT); + } else if(event->type == InputTypeRelease) { + furi_hal_hid_mouse_release(HID_MOUSE_BTN_RIGHT); + } + } else if(event->key == InputKeyOk) { + if(event->type == InputTypePress) { + furi_hal_hid_mouse_press(HID_MOUSE_BTN_WHEEL); + } else if(event->type == InputTypeRelease) { + furi_hal_hid_mouse_release(HID_MOUSE_BTN_WHEEL); + } + } + }, + true); +} + +static bool usb_mouse_input_callback(InputEvent* event, void* context) { + furi_assert(context); + UsbMouse* usb_mouse = context; + bool consumed = false; + + if(event->type == InputTypeLong && event->key == InputKeyBack) { + // furi_hal_hid_mouse_release_all(); + } else { + usb_mouse_process(usb_mouse, event); + consumed = true; + } + + return consumed; +} + +void usb_mouse_enter_callback(void* context) { + furi_assert(context); + UsbMouse* usb_mouse = context; + + usb_mouse->usb_mode_prev = furi_hal_usb_get_config(); + furi_hal_usb_unlock(); + furi_check(furi_hal_usb_set_config(&usb_hid, NULL) == true); + + tracking_begin(); + + view_dispatcher_send_custom_event(usb_mouse->view_dispatcher, 0); +} + +bool usb_mouse_move(int8_t dx, int8_t dy, void* context) { + UNUSED(context); + return furi_hal_hid_mouse_move(dx, dy); +} + +bool usb_mouse_custom_callback(uint32_t event, void* context) { + UNUSED(event); + furi_assert(context); + UsbMouse* usb_mouse = context; + + tracking_step(usb_mouse_move, context); + furi_delay_ms(3); // Magic! Removing this will break the buttons + + view_dispatcher_send_custom_event(usb_mouse->view_dispatcher, 0); + return true; +} + +void usb_mouse_exit_callback(void* context) { + furi_assert(context); + UsbMouse* usb_mouse = context; + + tracking_end(); + + furi_hal_usb_set_config(usb_mouse->usb_mode_prev, NULL); +} + +UsbMouse* usb_mouse_alloc(ViewDispatcher* view_dispatcher) { + UsbMouse* usb_mouse = malloc(sizeof(UsbMouse)); + usb_mouse->view = view_alloc(); + usb_mouse->view_dispatcher = view_dispatcher; + view_set_context(usb_mouse->view, usb_mouse); + view_set_draw_callback(usb_mouse->view, usb_mouse_draw_callback); + view_set_input_callback(usb_mouse->view, usb_mouse_input_callback); + view_set_enter_callback(usb_mouse->view, usb_mouse_enter_callback); + view_set_custom_callback(usb_mouse->view, usb_mouse_custom_callback); + view_set_exit_callback(usb_mouse->view, usb_mouse_exit_callback); + return usb_mouse; +} + +void usb_mouse_free(UsbMouse* usb_mouse) { + furi_assert(usb_mouse); + view_free(usb_mouse->view); + free(usb_mouse); +} + +View* usb_mouse_get_view(UsbMouse* usb_mouse) { + furi_assert(usb_mouse); + return usb_mouse->view; +} diff --git a/Applications/Official/DEV_FW/source/airmouse/views/usb_mouse.h b/Applications/Official/DEV_FW/source/airmouse/views/usb_mouse.h new file mode 100644 index 000000000..5ce589a69 --- /dev/null +++ b/Applications/Official/DEV_FW/source/airmouse/views/usb_mouse.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +typedef struct UsbMouse UsbMouse; + +UsbMouse* usb_mouse_alloc(ViewDispatcher* view_dispatcher); + +void usb_mouse_free(UsbMouse* usb_mouse); + +View* usb_mouse_get_view(UsbMouse* usb_mouse); diff --git a/Applications/Official/DEV_FW/source/arkanoid/application.fam b/Applications/Official/DEV_FW/source/arkanoid/application.fam new file mode 100644 index 000000000..bc202ff00 --- /dev/null +++ b/Applications/Official/DEV_FW/source/arkanoid/application.fam @@ -0,0 +1,12 @@ +App( + appid="Arkanoid", + name="Arkanoid", + apptype=FlipperAppType.EXTERNAL, + entry_point="arkanoid_game_app", + cdefines=["APP_ARKANOID_GAME"], + requires=["gui"], + stack_size=1 * 1024, + order=20, + fap_icon="arkanoid_10px.png", + fap_category="Games", +) diff --git a/Applications/Official/DEV_FW/source/arkanoid/arkanoid_10px.png b/Applications/Official/DEV_FW/source/arkanoid/arkanoid_10px.png new file mode 100644 index 000000000..344d2db73 Binary files /dev/null and b/Applications/Official/DEV_FW/source/arkanoid/arkanoid_10px.png differ diff --git a/Applications/Official/DEV_FW/source/arkanoid/arkanoid_game.c b/Applications/Official/DEV_FW/source/arkanoid/arkanoid_game.c new file mode 100644 index 000000000..0b0458424 --- /dev/null +++ b/Applications/Official/DEV_FW/source/arkanoid/arkanoid_game.c @@ -0,0 +1,474 @@ +#include +#include +#include +#include +#include +#include +#include + +#define TAG "Arkanoid" + +#define FLIPPER_LCD_WIDTH 128 +#define FLIPPER_LCD_HEIGHT 64 +#define MAX_SPEED 3 + +typedef enum { EventTypeTick, EventTypeKey } EventType; + +typedef struct { + //Brick Bounds used in collision detection + int leftBrick; + int rightBrick; + int topBrick; + int bottomBrick; + bool isHit[4][13]; //Array of if bricks are hit or not +} BrickState; + +typedef struct { + int dx; //Initial movement of ball + int dy; //Initial movement of ball + int xb; //Balls starting possition + int yb; //Balls starting possition + bool released; //If the ball has been released by the player + //Ball Bounds used in collision detection + int leftBall; + int rightBall; + int topBall; + int bottomBall; +} BallState; + +typedef struct { + BallState ball_state; + BrickState brick_state; + NotificationApp* notify; + unsigned int COLUMNS; //Columns of bricks + unsigned int ROWS; //Rows of bricks + bool initialDraw; //If the inital draw has happened + int xPaddle; //X position of paddle + char text[16]; //General string buffer + bool bounced; //Used to fix double bounce glitch + int lives; //Amount of lives + int level; //Current level + unsigned int score; //Score for the game + unsigned int brickCount; //Amount of bricks hit + int tick; //Tick counter + bool gameStarted; // Did the game start? + int speed; // Ball speed +} ArkanoidState; + +typedef struct { + EventType type; + InputEvent input; +} GameEvent; + +static const NotificationSequence sequence_short_sound = { + &message_note_c5, + &message_delay_50, + &message_sound_off, + NULL, +}; + +// generate number in range [min,max) +int rand_range(int min, int max) { + return min + rand() % (max - min); +} + +void move_ball(Canvas* canvas, ArkanoidState* st) { + st->tick++; + + int current_speed = abs(st->speed - 1 - MAX_SPEED); + if(st->tick % current_speed != 0 && st->tick % (current_speed + 1) != 0) { + return; + } + + if(st->ball_state.released) { + //Move ball + if(abs(st->ball_state.dx) == 2) { + st->ball_state.xb += st->ball_state.dx / 2; + // 2x speed is really 1.5 speed + if((st->tick / current_speed) % 2 == 0) st->ball_state.xb += st->ball_state.dx / 2; + } else { + st->ball_state.xb += st->ball_state.dx; + } + st->ball_state.yb = st->ball_state.yb + st->ball_state.dy; + + //Set bounds + st->ball_state.leftBall = st->ball_state.xb; + st->ball_state.rightBall = st->ball_state.xb + 2; + st->ball_state.topBall = st->ball_state.yb; + st->ball_state.bottomBall = st->ball_state.yb + 2; + + //Bounce off top edge + if(st->ball_state.yb <= 0) { + st->ball_state.yb = 2; + st->ball_state.dy = -st->ball_state.dy; + } + + //Lose a life if bottom edge hit + if(st->ball_state.yb >= FLIPPER_LCD_HEIGHT) { + canvas_draw_frame(canvas, st->xPaddle, FLIPPER_LCD_HEIGHT - 1, 11, 1); + st->xPaddle = 54; + st->ball_state.yb = 60; + st->ball_state.released = false; + st->lives--; + st->gameStarted = false; + + if(rand_range(0, 2) == 0) { + st->ball_state.dx = 1; + } else { + st->ball_state.dx = -1; + } + } + + //Bounce off left side + if(st->ball_state.xb <= 0) { + st->ball_state.xb = 2; + st->ball_state.dx = -st->ball_state.dx; + } + + //Bounce off right side + if(st->ball_state.xb >= FLIPPER_LCD_WIDTH - 2) { + st->ball_state.xb = FLIPPER_LCD_WIDTH - 4; + st->ball_state.dx = -st->ball_state.dx; + // arduboy.tunes.tone(523, 250); + } + + //Bounce off paddle + if(st->ball_state.xb + 1 >= st->xPaddle && st->ball_state.xb <= st->xPaddle + 12 && + st->ball_state.yb + 2 >= FLIPPER_LCD_HEIGHT - 1 && + st->ball_state.yb <= FLIPPER_LCD_HEIGHT) { + st->ball_state.dy = -st->ball_state.dy; + st->ball_state.dx = + ((st->ball_state.xb - (st->xPaddle + 6)) / 3); //Applies spin on the ball + // prevent straight bounce, but not prevent roguuemaster from stealing + if(st->ball_state.dx == 0) { + st->ball_state.dx = (rand_range(0, 2) == 1) ? 1 : -1; + } + } + + //Bounce off Bricks + for(unsigned int row = 0; row < st->ROWS; row++) { + for(unsigned int column = 0; column < st->COLUMNS; column++) { + if(!st->brick_state.isHit[row][column]) { + //Sets Brick bounds + st->brick_state.leftBrick = 10 * column; + st->brick_state.rightBrick = 10 * column + 10; + st->brick_state.topBrick = 6 * row + 1; + st->brick_state.bottomBrick = 6 * row + 7; + + //If A collison has occured + if(st->ball_state.topBall <= st->brick_state.bottomBrick && + st->ball_state.bottomBall >= st->brick_state.topBrick && + st->ball_state.leftBall <= st->brick_state.rightBrick && + st->ball_state.rightBall >= st->brick_state.leftBrick) { + st->score += st->level; + // Blink led when we hit some brick + notification_message(st->notify, &sequence_short_sound); + //notification_message(st->notify, &sequence_blink_white_100); + + st->brickCount++; + st->brick_state.isHit[row][column] = true; + canvas_draw_frame(canvas, 10 * column, 2 + 6 * row, 8, 4); + + //Vertical collision + if(st->ball_state.bottomBall > st->brick_state.bottomBrick || + st->ball_state.topBall < st->brick_state.topBrick) { + //Only bounce once each ball move + if(!st->bounced) { + st->ball_state.dy = -st->ball_state.dy; + st->ball_state.yb += st->ball_state.dy; + st->bounced = true; + } + } + + //Hoizontal collision + if(st->ball_state.leftBall < st->brick_state.leftBrick || + st->ball_state.rightBall > st->brick_state.rightBrick) { + //Only bounce once brick each ball move + if(!st->bounced) { + st->ball_state.dx = -st->ball_state.dx; + st->ball_state.xb += st->ball_state.dx; + st->bounced = true; + } + } + } + } + } + } + + //Reset Bounce + st->bounced = false; + } else { + //Ball follows paddle + st->ball_state.xb = st->xPaddle + 5; + } +} + +void draw_lives(Canvas* canvas, ArkanoidState* arkanoid_state) { + if(arkanoid_state->lives == 3) { + canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 7); + canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 7); + canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 8); + canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 8); + + canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 11); + canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 11); + canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 12); + canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 12); + + canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 15); + canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 15); + canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 16); + canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 16); + } else if(arkanoid_state->lives == 2) { + canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 7); + canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 7); + canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 8); + canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 8); + + canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 11); + canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 11); + canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 12); + canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 12); + } else { + canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 7); + canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 7); + canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 8); + canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 8); + } +} + +void draw_score(Canvas* canvas, ArkanoidState* arkanoid_state) { + snprintf(arkanoid_state->text, sizeof(arkanoid_state->text), "%u", arkanoid_state->score); + canvas_draw_str_aligned( + canvas, + FLIPPER_LCD_WIDTH - 2, + FLIPPER_LCD_HEIGHT - 6, + AlignRight, + AlignBottom, + arkanoid_state->text); +} + +void draw_ball(Canvas* canvas, ArkanoidState* ast) { + canvas_draw_dot(canvas, ast->ball_state.xb, ast->ball_state.yb); + canvas_draw_dot(canvas, ast->ball_state.xb + 1, ast->ball_state.yb); + canvas_draw_dot(canvas, ast->ball_state.xb, ast->ball_state.yb + 1); + canvas_draw_dot(canvas, ast->ball_state.xb + 1, ast->ball_state.yb + 1); + + move_ball(canvas, ast); +} + +void draw_paddle(Canvas* canvas, ArkanoidState* arkanoid_state) { + canvas_draw_frame(canvas, arkanoid_state->xPaddle, FLIPPER_LCD_HEIGHT - 1, 11, 1); +} + +void reset_level(Canvas* canvas, ArkanoidState* arkanoid_state) { + //Undraw paddle + canvas_draw_frame(canvas, arkanoid_state->xPaddle, FLIPPER_LCD_HEIGHT - 1, 11, 1); + + //Undraw ball + canvas_draw_dot(canvas, arkanoid_state->ball_state.xb, arkanoid_state->ball_state.yb); + canvas_draw_dot(canvas, arkanoid_state->ball_state.xb + 1, arkanoid_state->ball_state.yb); + canvas_draw_dot(canvas, arkanoid_state->ball_state.xb, arkanoid_state->ball_state.yb + 1); + canvas_draw_dot(canvas, arkanoid_state->ball_state.xb + 1, arkanoid_state->ball_state.yb + 1); + + //Alter various variables to reset the game + arkanoid_state->xPaddle = 54; + arkanoid_state->ball_state.yb = 60; + arkanoid_state->brickCount = 0; + arkanoid_state->ball_state.released = false; + + // Reset all brick hit states + for(unsigned int row = 0; row < arkanoid_state->ROWS; row++) { + for(unsigned int column = 0; column < arkanoid_state->COLUMNS; column++) { + arkanoid_state->brick_state.isHit[row][column] = false; + } + } +} + +static void arkanoid_state_init(ArkanoidState* arkanoid_state) { + // Init notification + arkanoid_state->notify = furi_record_open(RECORD_NOTIFICATION); + + // Set the initial game state + arkanoid_state->COLUMNS = 13; + arkanoid_state->ROWS = 4; + arkanoid_state->ball_state.dx = -1; + arkanoid_state->ball_state.dy = -1; + arkanoid_state->speed = 2; + arkanoid_state->bounced = false; + arkanoid_state->lives = 3; + arkanoid_state->level = 1; + arkanoid_state->score = 0; + arkanoid_state->COLUMNS = 13; + arkanoid_state->COLUMNS = 13; + + // Reset initial state + arkanoid_state->initialDraw = false; + arkanoid_state->gameStarted = false; +} + +static void arkanoid_draw_callback(Canvas* const canvas, void* ctx) { + ArkanoidState* arkanoid_state = acquire_mutex((ValueMutex*)ctx, 25); + if(arkanoid_state == NULL) { + return; + } + + //Initial level draw + if(!arkanoid_state->initialDraw) { + arkanoid_state->initialDraw = true; + + // Set default font for text + canvas_set_font(canvas, FontSecondary); + + //Draws the new level + reset_level(canvas, arkanoid_state); + } + + //Draws new bricks and resets their values + for(unsigned int row = 0; row < arkanoid_state->ROWS; row++) { + for(unsigned int column = 0; column < arkanoid_state->COLUMNS; column++) { + if(!arkanoid_state->brick_state.isHit[row][column]) { + canvas_draw_frame(canvas, 10 * column, 2 + 6 * row, 8, 4); + } + } + } + + if(arkanoid_state->lives > 0) { + draw_paddle(canvas, arkanoid_state); + draw_ball(canvas, arkanoid_state); + draw_score(canvas, arkanoid_state); + draw_lives(canvas, arkanoid_state); + + if(arkanoid_state->brickCount == arkanoid_state->ROWS * arkanoid_state->COLUMNS) { + arkanoid_state->level++; + reset_level(canvas, arkanoid_state); + } + } else { + reset_level(canvas, arkanoid_state); + arkanoid_state->initialDraw = false; + arkanoid_state->lives = 3; + arkanoid_state->score = 0; + } + + release_mutex((ValueMutex*)ctx, arkanoid_state); +} + +static void arkanoid_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + GameEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void arkanoid_update_timer_callback(FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + GameEvent event = {.type = EventTypeTick}; + furi_message_queue_put(event_queue, &event, 0); +} + +int32_t arkanoid_game_app(void* p) { + UNUSED(p); + int32_t return_code = 0; + + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(GameEvent)); + + ArkanoidState* arkanoid_state = malloc(sizeof(ArkanoidState)); + arkanoid_state_init(arkanoid_state); + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, arkanoid_state, sizeof(ArkanoidState))) { + FURI_LOG_E(TAG, "Cannot create mutex\r\n"); + return_code = 255; + goto free_and_exit; + } + + // Set system callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, arkanoid_draw_callback, &state_mutex); + view_port_input_callback_set(view_port, arkanoid_input_callback, event_queue); + + FuriTimer* timer = + furi_timer_alloc(arkanoid_update_timer_callback, FuriTimerTypePeriodic, event_queue); + furi_timer_start(timer, furi_kernel_get_tick_frequency() / 22); + + // Open GUI and register view_port + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + GameEvent event; + for(bool processing = true; processing;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + ArkanoidState* arkanoid_state = (ArkanoidState*)acquire_mutex_block(&state_mutex); + + if(event_status == FuriStatusOk) { + // Key events + if(event.type == EventTypeKey) { + if(event.input.type == InputTypePress || event.input.type == InputTypeLong || + event.input.type == InputTypeRepeat) { + switch(event.input.key) { + case InputKeyBack: + processing = false; + break; + case InputKeyRight: + if(arkanoid_state->xPaddle < FLIPPER_LCD_WIDTH - 12) { + arkanoid_state->xPaddle += 8; + } + break; + case InputKeyLeft: + if(arkanoid_state->xPaddle > 0) { + arkanoid_state->xPaddle -= 8; + } + break; + case InputKeyUp: + if(arkanoid_state->speed < MAX_SPEED) { + arkanoid_state->speed++; + } + break; + case InputKeyDown: + if(arkanoid_state->speed > 1) { + arkanoid_state->speed--; + } + break; + case InputKeyOk: + if(arkanoid_state->gameStarted == false) { + //Release ball if FIRE pressed + arkanoid_state->ball_state.released = true; + + //Apply random direction to ball on release + if(rand_range(0, 2) == 0) { + arkanoid_state->ball_state.dx = 1; + } else { + arkanoid_state->ball_state.dx = -1; + } + + //Makes sure the ball heads upwards + arkanoid_state->ball_state.dy = -1; + //start the game flag + arkanoid_state->gameStarted = true; + } + break; + default: + break; + } + } + } + } + + view_port_update(view_port); + release_mutex(&state_mutex, arkanoid_state); + } + furi_timer_free(timer); + 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); + delete_mutex(&state_mutex); + +free_and_exit: + free(arkanoid_state); + furi_message_queue_free(event_queue); + + return return_code; +} diff --git a/Applications/Official/DEV_FW/source/badapple/application.fam b/Applications/Official/DEV_FW/source/badapple/application.fam new file mode 100644 index 000000000..6b03d33d4 --- /dev/null +++ b/Applications/Official/DEV_FW/source/badapple/application.fam @@ -0,0 +1,10 @@ +App( + appid="BadApple", + name="Bad Apple", + apptype=FlipperAppType.EXTERNAL, + entry_point="bad_apple_main", + requires=["gui"], + stack_size=1 * 1024, + fap_icon="bad_apple_10px.png", + fap_category="Misc" +) diff --git a/Applications/Official/DEV_FW/source/badapple/bad_apple.c b/Applications/Official/DEV_FW/source/badapple/bad_apple.c new file mode 100644 index 000000000..325d6ea55 --- /dev/null +++ b/Applications/Official/DEV_FW/source/badapple/bad_apple.c @@ -0,0 +1,183 @@ +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "bad_apple.h" +#include "video_player.h" + +#define TAG "badapple" + +typedef enum { + BadAppleEventTypeInput, + BadAppleEventTypeTick, +} BadAppleEventType; + +typedef struct { + BadAppleEventType type; + InputEvent* input; +} BadAppleEvent; + +// Screen is 128x64 px +static void app_draw_callback(Canvas* canvas, void* ctx) { + BadAppleCtx* inst = ctx; + + canvas_clear(canvas); + canvas_draw_xbm(canvas, VIDEO_X, VIDEO_Y, VIDEO_WIDTH, SCREEN_HEIGHT, inst->framebuffer); + canvas_draw_box(canvas, 0, 0, VIDEO_X, SCREEN_HEIGHT); + canvas_draw_box( + canvas, VIDEO_X + VIDEO_WIDTH, 0, SCREEN_WIDTH - VIDEO_WIDTH - VIDEO_X, SCREEN_HEIGHT); +} + +static void app_input_callback(InputEvent* input_event, void* ctx) { + furi_assert(ctx); + + FuriMessageQueue* event_queue = ctx; + BadAppleEvent event = {.type = BadAppleEventTypeInput, .input = input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +BadAppleCtx* bad_apple_ctx_alloc(void) { + BadAppleCtx* inst = malloc(sizeof(BadAppleCtx)); + memset(inst, 0, sizeof(BadAppleCtx)); + + if(inst) { + inst->storage = furi_record_open(RECORD_STORAGE); + inst->video_file = storage_file_alloc(inst->storage); + inst->file_buffer_offset = sizeof(inst->file_buffer); + } + + return inst; +} + +void bad_apple_ctx_free(BadAppleCtx* inst) { + if(inst) { + storage_file_free(inst->video_file); + furi_record_close(RECORD_STORAGE); + free(inst); + } +} + +void bad_apple_load_next_video_chunk(BadAppleCtx* inst) { + size_t bytes_to_read = sizeof(inst->file_buffer); + uint8_t* buf_ptr = inst->file_buffer; + while(bytes_to_read > 0) { + uint16_t curr_bytes_to_read = bytes_to_read > (UINT16_MAX / 2 + 1) ? (UINT16_MAX / 2 + 1) : + bytes_to_read; + uint16_t read = storage_file_read(inst->video_file, buf_ptr, curr_bytes_to_read); + bytes_to_read -= read; + buf_ptr += read; + if(read == 0) break; + } + inst->file_buffer_offset = 0; +} + +uint8_t bad_apple_read_byte(BadAppleCtx* inst) { + if(inst->file_buffer_offset >= sizeof(inst->file_buffer)) { + bad_apple_load_next_video_chunk(inst); + } + return inst->file_buffer[inst->file_buffer_offset++]; +} + +void bad_apple_timer_isr(void* ctx) { + FuriMessageQueue* event_queue = ctx; + BadAppleEvent event = {.type = BadAppleEventTypeTick}; + furi_message_queue_put(event_queue, &event, 0); + LL_TIM_ClearFlag_UPDATE(TIM2); +} + +void bad_apple_timer_setup(BadAppleCtx* inst, void* ctx) { + UNUSED(inst); + + LL_TIM_InitTypeDef tim_init = { + .Prescaler = 63999, + .CounterMode = LL_TIM_COUNTERMODE_UP, + .Autoreload = 30, + }; + + LL_TIM_Init(TIM2, &tim_init); + LL_TIM_SetClockSource(TIM2, LL_TIM_CLOCKSOURCE_INTERNAL); + LL_TIM_DisableCounter(TIM2); + LL_TIM_SetCounter(TIM2, 0); + furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, bad_apple_timer_isr, ctx); + LL_TIM_EnableIT_UPDATE(TIM2); +} + +void bad_apple_timer_deinit(void) { + LL_TIM_DisableCounter(TIM2); + LL_TIM_DisableIT_UPDATE(TIM2); + furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, NULL, NULL); + LL_TIM_DeInit(TIM2); +} + +int32_t bad_apple_main(void* p) { + UNUSED(p); + BadAppleCtx* ctx = bad_apple_ctx_alloc(); + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(BadAppleEvent)); + + // Configure view port + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, app_draw_callback, ctx); + view_port_input_callback_set(view_port, app_input_callback, event_queue); + + // Register view port in GUI + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + + // Frame rate: 32 FPS + bad_apple_timer_setup(ctx, event_queue); + + bool is_opened = storage_file_open(ctx->video_file, VIDEO_PATH, FSAM_READ, FSOM_OPEN_EXISTING); + if(is_opened) { + BadAppleEvent event; + notification_message(notification, &sequence_display_backlight_enforce_on); + + bool running = true; + LL_TIM_EnableCounter(TIM2); + while(running) { + if(furi_message_queue_get(event_queue, &event, 100) == FuriStatusOk) { + if(event.type == BadAppleEventTypeInput) { + InputEvent* input_event = event.input; + if(input_event->type == InputTypeLong) { + if(input_event->key == InputKeyBack) { + running = false; + } + } + } else if(event.type == BadAppleEventTypeTick) { + // FURI_LOG_D(TAG, "Update frame"); + if(!vp_play_frame(ctx)) { + running = false; + } + } + } + view_port_update(view_port); + } + LL_TIM_DisableCounter(TIM2); + notification_message(notification, &sequence_display_backlight_enforce_auto); + storage_file_close(ctx->video_file); + } + + furi_record_close(RECORD_NOTIFICATION); + + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + view_port_free(view_port); + furi_message_queue_free(event_queue); + + furi_record_close(RECORD_GUI); + bad_apple_timer_deinit(); + bad_apple_ctx_free(ctx); + + return 0; +} diff --git a/Applications/Official/DEV_FW/source/badapple/bad_apple.h b/Applications/Official/DEV_FW/source/badapple/bad_apple.h new file mode 100644 index 000000000..de842b8d7 --- /dev/null +++ b/Applications/Official/DEV_FW/source/badapple/bad_apple.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include + +#define SCREEN_WIDTH 128 +#define SCREEN_HEIGHT 64 +#define VIDEO_WIDTH 104 +#define VIDEO_HEIGHT 80 +#define FILE_BUFFER_SIZE (1024 * 64) +#define VIDEO_PATH EXT_PATH("apps_data/bad_apple/video.bin") +#define VIDEO_X ((SCREEN_WIDTH - VIDEO_WIDTH) / 2) +#define VIDEO_Y 0 + +typedef struct { + uint8_t framebuffer[VIDEO_HEIGHT * VIDEO_WIDTH / 8]; + uint8_t file_buffer[FILE_BUFFER_SIZE]; + uint32_t file_buffer_offset; + uint32_t frame_write_offset; // bit index + Storage* storage; + File* video_file; +} BadAppleCtx; + +uint8_t bad_apple_read_byte(BadAppleCtx* inst); diff --git a/Applications/Official/DEV_FW/source/badapple/bad_apple_10px.png b/Applications/Official/DEV_FW/source/badapple/bad_apple_10px.png new file mode 100644 index 000000000..303269273 Binary files /dev/null and b/Applications/Official/DEV_FW/source/badapple/bad_apple_10px.png differ diff --git a/Applications/Official/DEV_FW/source/badapple/video_player.c b/Applications/Official/DEV_FW/source/badapple/video_player.c new file mode 100644 index 000000000..945837d6b --- /dev/null +++ b/Applications/Official/DEV_FW/source/badapple/video_player.c @@ -0,0 +1,137 @@ +#include + +#include + +#include "bad_apple.h" +#include "video_player.h" + +#define TAG "video_player" + +static void vp_decode_rle(BadAppleCtx* ctx); +static void vp_decode_delta(BadAppleCtx* ctx); + +int vp_play_frame(BadAppleCtx* ctx) { + // Check frame type + // FURI_LOG_D(TAG, "Buffer offset: %04lx", ctx->file_buffer_offset); + uint8_t b = bad_apple_read_byte(ctx); + // FURI_LOG_D(TAG, "Frame byte: %02x", b); + switch(b) { + case 1: // PFrame (delta) + vp_decode_delta(ctx); + break; + case 2: // IFrame (rle) + vp_decode_rle(ctx); + break; + case 3: // DFrame (duplicate) + break; + case 4: // next page (not supported) + case 5: // end + default: + return 0; + } + + return 1; +} + +static inline void vp_write_pixel(BadAppleCtx* ctx, bool color) { + if(color) { + // White + ctx->framebuffer[ctx->frame_write_offset / 8] &= ~(1 << (ctx->frame_write_offset % 8)); + } else { + // Black + ctx->framebuffer[ctx->frame_write_offset / 8] |= (1 << (ctx->frame_write_offset % 8)); + } + ++ctx->frame_write_offset; +} + +static inline void vp_set_rect_fast(BadAppleCtx* ctx, int x, int y) { + ctx->frame_write_offset = y * VIDEO_WIDTH + x; +} + +static void vp_decode_rle(BadAppleCtx* ctx) { + int i = 0; + int repeat_byte = bad_apple_read_byte(ctx); + + // Set rect to video area + vp_set_rect_fast(ctx, 0, 0); + + while(i < VIDEO_WIDTH * VIDEO_HEIGHT) { + int count; + unsigned pixels; + int j; + int b = bad_apple_read_byte(ctx); + + if(b == repeat_byte) { + count = bad_apple_read_byte(ctx); + if(count == 0) count = 256; + pixels = bad_apple_read_byte(ctx); + } else { + count = 1; + pixels = (unsigned)b; + } + + for(j = 0; j < count; ++j) { + bool out_color; + int k; + unsigned loop_pixels = pixels; + + for(k = 0; k < 8; ++k) { + if(loop_pixels & 0x80) + out_color = COLOR_WHITE; + else + out_color = COLOR_BLACK; + + vp_write_pixel(ctx, out_color); + loop_pixels <<= 1; + ++i; + } + } + } +} + +static void vp_decode_delta(BadAppleCtx* ctx) { + int frame_header[(VIDEO_HEIGHT + 7) / 8]; + uint i; + int fh_byte = 0; + int fh_index = 0; + + for(i = 0; i < sizeof(frame_header) / sizeof(frame_header[0]); ++i) { + frame_header[i] = bad_apple_read_byte(ctx); + } + + for(i = 0; i < VIDEO_HEIGHT; ++i) { + if(i % 8 == 0) fh_byte = frame_header[fh_index++]; + + if(fh_byte & 0x80) { + int j; + int sl_byte = 0; + + for(j = 0; j < VIDEO_WIDTH;) { + if(j % (8 * 8) == 0) sl_byte = bad_apple_read_byte(ctx); + + if(sl_byte & 0x80) { + unsigned out_color; + int k; + unsigned pixel_group = bad_apple_read_byte(ctx); + + // Note: this needs to be revised for screen width not multiple of 8 + vp_set_rect_fast(ctx, j, i); + + for(k = 0; k < 8 && j < VIDEO_WIDTH; ++k, ++j) { + if(pixel_group & 0x80) + out_color = COLOR_WHITE; + else + out_color = COLOR_BLACK; + + vp_write_pixel(ctx, out_color); + pixel_group <<= 1; + } + } else { + j += 8; + } + sl_byte <<= 1; + } + } + fh_byte <<= 1; + } +} diff --git a/Applications/Official/DEV_FW/source/badapple/video_player.h b/Applications/Official/DEV_FW/source/badapple/video_player.h new file mode 100644 index 000000000..c81a6c2e8 --- /dev/null +++ b/Applications/Official/DEV_FW/source/badapple/video_player.h @@ -0,0 +1,6 @@ +#pragma once + +#define COLOR_WHITE true +#define COLOR_BLACK false + +int vp_play_frame(BadAppleCtx* ctx); diff --git a/Applications/Official/DEV_FW/source/barcode_generator/application.fam b/Applications/Official/DEV_FW/source/barcode_generator/application.fam new file mode 100644 index 000000000..97dc9acef --- /dev/null +++ b/Applications/Official/DEV_FW/source/barcode_generator/application.fam @@ -0,0 +1,15 @@ +App( + appid="Barcode_Generator", + name="Barcode Generator", + apptype=FlipperAppType.EXTERNAL, + entry_point="barcode_generator_app", + cdefines=["APP_BARCODE_GEN"], + requires=[ + "gui", + "dialogs", + ], + stack_size=1 * 1024, + order=250, + fap_icon="barcode_10px.png", + fap_category="Misc", +) diff --git a/Applications/Official/DEV_FW/source/barcode_generator/barcode_10px.png b/Applications/Official/DEV_FW/source/barcode_generator/barcode_10px.png new file mode 100644 index 000000000..7c19c6656 Binary files /dev/null and b/Applications/Official/DEV_FW/source/barcode_generator/barcode_10px.png differ diff --git a/Applications/Official/DEV_FW/source/barcode_generator/barcode_generator.c b/Applications/Official/DEV_FW/source/barcode_generator/barcode_generator.c new file mode 100644 index 000000000..776862531 --- /dev/null +++ b/Applications/Official/DEV_FW/source/barcode_generator/barcode_generator.c @@ -0,0 +1,355 @@ +#include +#include +#include +#include + +#include "barcode_generator.h" + +static BarcodeType* barcodeTypes[NUMBER_OF_BARCODE_TYPES]; + +void init_types() { + BarcodeType* upcA = malloc(sizeof(BarcodeType)); + upcA->name = "UPC-A"; + upcA->numberOfDigits = 12; + upcA->startPos = 19; + barcodeTypes[0] = upcA; + + BarcodeType* ean8 = malloc(sizeof(BarcodeType)); + ean8->name = "EAN-8"; + ean8->numberOfDigits = 8; + ean8->startPos = 33; + barcodeTypes[1] = ean8; +} + +void draw_digit(Canvas* canvas, int digit, bool rightHand, int startingPosition) { + char digitStr[2]; + snprintf(digitStr, 2, "%u", digit); + canvas_set_color(canvas, ColorBlack); + canvas_draw_str( + canvas, startingPosition, BARCODE_Y_START + BARCODE_HEIGHT + BARCODE_TEXT_OFFSET, digitStr); + if(rightHand) { + canvas_set_color(canvas, ColorBlack); + } else { + canvas_set_color(canvas, ColorWhite); + } + + int count = 0; + for(int i = 0; i < 4; i++) { + canvas_draw_box( + canvas, startingPosition + count, BARCODE_Y_START, DIGITS[digit][i], BARCODE_HEIGHT); + canvas_invert_color(canvas); + count += DIGITS[digit][i]; + } +} + +int get_digit_position(int index, BarcodeType* type) { + int pos = type->startPos + index * 7; + if(index >= type->numberOfDigits / 2) { + pos += 5; + } + return pos; +} + +int get_menu_text_location(int index) { + return 20 + 10 * index; +} + +int calculate_check_digit(PluginState* plugin_state, BarcodeType* type) { + int checkDigit = 0; + //add all odd positions. Confusing because 0index + for(int i = 0; i < type->numberOfDigits - 1; i += 2) { + checkDigit += plugin_state->barcodeNumeral[i]; + } + + checkDigit = checkDigit * 3; //times 3 + + //add all even positions to above. Confusing because 0index + for(int i = 1; i < type->numberOfDigits - 1; i += 2) { + checkDigit += plugin_state->barcodeNumeral[i]; + } + + checkDigit = checkDigit % 10; //mod 10 + + //if m = 0 then x12 = 0, otherwise x12 is 10 - m + return (10 - checkDigit) % 10; +} + +static void render_callback(Canvas* const canvas, void* ctx) { + PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25); + if(plugin_state == NULL) { + return; + } + + if(plugin_state->mode == MenuMode) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_str_aligned(canvas, 64, 6, AlignCenter, AlignCenter, "MENU"); + canvas_draw_frame(canvas, 50, 0, 29, 11); //box around Menu + canvas_draw_str_aligned( + canvas, 64, get_menu_text_location(0), AlignCenter, AlignCenter, "View"); + canvas_draw_str_aligned( + canvas, 64, get_menu_text_location(1), AlignCenter, AlignCenter, "Edit"); + canvas_draw_str_aligned( + canvas, 64, get_menu_text_location(2), AlignCenter, AlignCenter, "Parity?"); + + canvas_draw_frame(canvas, 83, get_menu_text_location(2) - 3, 6, 6); + if(plugin_state->doParityCalculation == true) { + canvas_draw_box(canvas, 85, get_menu_text_location(2) - 1, 2, 2); + } + canvas_draw_str_aligned( + canvas, + 64, + get_menu_text_location(3), + AlignCenter, + AlignCenter, + (barcodeTypes[plugin_state->barcodeTypeIndex])->name); + canvas_draw_disc( + canvas, 40, get_menu_text_location(plugin_state->menuIndex) - 1, 2); //draw menu cursor + } else { + BarcodeType* type = barcodeTypes[plugin_state->barcodeTypeIndex]; + + canvas_set_color(canvas, ColorBlack); + canvas_draw_box(canvas, type->startPos - 3, BARCODE_Y_START, 1, BARCODE_HEIGHT + 2); + canvas_draw_box( + canvas, + (type->startPos - 1), + BARCODE_Y_START, + 1, + BARCODE_HEIGHT + 2); //start saftey + + for(int index = 0; index < type->numberOfDigits; index++) { + bool isOnRight = false; + if(index >= type->numberOfDigits / 2) { + isOnRight = true; + } + if((index == type->numberOfDigits - 1) && + (plugin_state->doParityCalculation)) { //calculate the check digit + int checkDigit = calculate_check_digit(plugin_state, type); + plugin_state->barcodeNumeral[type->numberOfDigits - 1] = checkDigit; + } + int digitPosition = + get_digit_position(index, barcodeTypes[plugin_state->barcodeTypeIndex]); + draw_digit(canvas, plugin_state->barcodeNumeral[index], isOnRight, digitPosition); + } + + canvas_set_color(canvas, ColorBlack); + canvas_draw_box(canvas, 62, BARCODE_Y_START, 1, BARCODE_HEIGHT + 2); + canvas_draw_box(canvas, 64, BARCODE_Y_START, 1, BARCODE_HEIGHT + 2); + + if(plugin_state->mode == EditMode) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_box( + canvas, + get_digit_position( + plugin_state->editingIndex, barcodeTypes[plugin_state->barcodeTypeIndex]) - + 1, + 63, + 7, + 1); //draw editing cursor + } + + int endSafetyPosition = get_digit_position(type->numberOfDigits - 1, type) + 7; + canvas_set_color(canvas, ColorBlack); + canvas_draw_box(canvas, endSafetyPosition, BARCODE_Y_START, 1, BARCODE_HEIGHT + 2); + canvas_draw_box( + canvas, + (endSafetyPosition + 2), + BARCODE_Y_START, + 1, + BARCODE_HEIGHT + 2); //end safety + } + + 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 barcode_generator_state_init(PluginState* const plugin_state) { + for(int i = 0; i < 12; ++i) { + plugin_state->barcodeNumeral[i] = i % 10; + } + plugin_state->editingIndex = 0; + plugin_state->mode = ViewMode; + plugin_state->doParityCalculation = true; + plugin_state->menuIndex = MENU_INDEX_VIEW; + plugin_state->barcodeTypeIndex = 0; +} + +static bool handle_key_press_view(InputKey key, PluginState* plugin_state) { + switch(key) { + case InputKeyOk: + case InputKeyBack: + plugin_state->mode = MenuMode; + break; + + default: + break; + } + + return true; +} + +static bool handle_key_press_edit(InputKey key, PluginState* plugin_state) { + int barcodeMaxIndex = plugin_state->doParityCalculation ? + barcodeTypes[plugin_state->barcodeTypeIndex]->numberOfDigits - 1 : + barcodeTypes[plugin_state->barcodeTypeIndex]->numberOfDigits; + + switch(key) { + case InputKeyUp: + plugin_state->barcodeNumeral[plugin_state->editingIndex] = + (plugin_state->barcodeNumeral[plugin_state->editingIndex] + 1) % 10; + break; + + case InputKeyDown: + plugin_state->barcodeNumeral[plugin_state->editingIndex] = + (plugin_state->barcodeNumeral[plugin_state->editingIndex] == 0) ? + 9 : + plugin_state->barcodeNumeral[plugin_state->editingIndex] - 1; + break; + + case InputKeyRight: + plugin_state->editingIndex = (plugin_state->editingIndex + 1) % barcodeMaxIndex; + break; + + case InputKeyLeft: + plugin_state->editingIndex = (plugin_state->editingIndex == 0) ? + barcodeMaxIndex - 1 : + plugin_state->editingIndex - 1; + break; + + case InputKeyOk: + case InputKeyBack: + plugin_state->mode = MenuMode; + break; + + default: + break; + } + + return true; +} + +static bool handle_key_press_menu(InputKey key, PluginState* plugin_state) { + switch(key) { + case InputKeyUp: + plugin_state->menuIndex = (plugin_state->menuIndex == MENU_INDEX_VIEW) ? + MENU_INDEX_TYPE : + plugin_state->menuIndex - 1; + break; + + case InputKeyDown: + plugin_state->menuIndex = (plugin_state->menuIndex + 1) % 4; + break; + + case InputKeyRight: + if(plugin_state->menuIndex == MENU_INDEX_TYPE) { + plugin_state->barcodeTypeIndex = + (plugin_state->barcodeTypeIndex == NUMBER_OF_BARCODE_TYPES - 1) ? + 0 : + plugin_state->barcodeTypeIndex + 1; + } else if(plugin_state->menuIndex == MENU_INDEX_PARITY) { + plugin_state->doParityCalculation = !plugin_state->doParityCalculation; + } + break; + case InputKeyLeft: + if(plugin_state->menuIndex == MENU_INDEX_TYPE) { + plugin_state->barcodeTypeIndex = (plugin_state->barcodeTypeIndex == 0) ? + NUMBER_OF_BARCODE_TYPES - 1 : + plugin_state->barcodeTypeIndex - 1; + } else if(plugin_state->menuIndex == MENU_INDEX_PARITY) { + plugin_state->doParityCalculation = !plugin_state->doParityCalculation; + } + break; + + case InputKeyOk: + if(plugin_state->menuIndex == MENU_INDEX_VIEW) { + plugin_state->mode = ViewMode; + } else if(plugin_state->menuIndex == MENU_INDEX_EDIT) { + plugin_state->mode = EditMode; + } else if(plugin_state->menuIndex == MENU_INDEX_PARITY) { + plugin_state->doParityCalculation = !plugin_state->doParityCalculation; + } else if(plugin_state->menuIndex == MENU_INDEX_TYPE) { + plugin_state->barcodeTypeIndex = + (plugin_state->barcodeTypeIndex == NUMBER_OF_BARCODE_TYPES - 1) ? + 0 : + plugin_state->barcodeTypeIndex + 1; + } + break; + + case InputKeyBack: + return false; + + default: + break; + } + + return true; +} + +int32_t barcode_generator_app(void* p) { + UNUSED(p); + + init_types(); + + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); + + PluginState* plugin_state = malloc(sizeof(PluginState)); + barcode_generator_state_init(plugin_state); + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, plugin_state, sizeof(PluginState))) { + FURI_LOG_E("barcode_generator", "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 && + ((event.input.type == InputTypePress) || (event.input.type == InputTypeRepeat))) { + switch(plugin_state->mode) { + case ViewMode: + processing = handle_key_press_view(event.input.key, plugin_state); + break; + case EditMode: + processing = handle_key_press_edit(event.input.key, plugin_state); + break; + case MenuMode: + processing = handle_key_press_menu(event.input.key, plugin_state); + 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); + + return 0; +} diff --git a/Applications/Official/DEV_FW/source/barcode_generator/barcode_generator.h b/Applications/Official/DEV_FW/source/barcode_generator/barcode_generator.h new file mode 100644 index 000000000..7e1a078e8 --- /dev/null +++ b/Applications/Official/DEV_FW/source/barcode_generator/barcode_generator.h @@ -0,0 +1,52 @@ +#define BARCODE_HEIGHT 50 +#define BARCODE_Y_START 3 +#define BARCODE_TEXT_OFFSET 9 +#define NUMBER_OF_BARCODE_TYPES 2 +#define MENU_INDEX_VIEW 0 +#define MENU_INDEX_EDIT 1 +#define MENU_INDEX_PARITY 2 +#define MENU_INDEX_TYPE 3 + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} PluginEvent; + +typedef enum { + ViewMode, + EditMode, + MenuMode, +} Mode; + +typedef struct { + char* name; + int numberOfDigits; + int startPos; +} BarcodeType; + +typedef struct { + int barcodeNumeral[12]; //The current barcode number + int editingIndex; //The index of the editing symbol + int menuIndex; //The index of the menu cursor + Mode mode; //View, edit or menu + bool doParityCalculation; //Should do parity check? + int barcodeTypeIndex; +} PluginState; + +static const int DIGITS[10][4] = { + {3, 2, 1, 1}, + {2, 2, 2, 1}, + {2, 1, 2, 2}, + {1, 4, 1, 1}, + {1, 1, 3, 2}, + {1, 2, 3, 1}, + {1, 1, 1, 4}, + {1, 3, 1, 2}, + {1, 2, 1, 3}, + {3, 1, 1, 2}, +}; diff --git a/Applications/Official/DEV_FW/source/blackjack/application.fam b/Applications/Official/DEV_FW/source/blackjack/application.fam new file mode 100644 index 000000000..8230cd047 --- /dev/null +++ b/Applications/Official/DEV_FW/source/blackjack/application.fam @@ -0,0 +1,13 @@ +App( + appid="BlackJack", + name="BlackJack", + apptype=FlipperAppType.EXTERNAL, + entry_point="blackjack_app", + cdefines=["APP_BLACKJACK"], + requires=["gui","storage","canvas"], + stack_size=2 * 1024, + order=30, + fap_icon="blackjack_10px.png", + fap_category="Games", + fap_icon_assets="assets" +) \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/blackjack/assets/blackjack.png b/Applications/Official/DEV_FW/source/blackjack/assets/blackjack.png new file mode 100644 index 000000000..bb367f28e Binary files /dev/null and b/Applications/Official/DEV_FW/source/blackjack/assets/blackjack.png differ diff --git a/Applications/Official/DEV_FW/source/blackjack/assets/card_graphics.png b/Applications/Official/DEV_FW/source/blackjack/assets/card_graphics.png new file mode 100644 index 000000000..8b00e351f Binary files /dev/null and b/Applications/Official/DEV_FW/source/blackjack/assets/card_graphics.png differ diff --git a/Applications/Official/DEV_FW/source/blackjack/assets/endscreen.png b/Applications/Official/DEV_FW/source/blackjack/assets/endscreen.png new file mode 100644 index 000000000..7a3abc927 Binary files /dev/null and b/Applications/Official/DEV_FW/source/blackjack/assets/endscreen.png differ diff --git a/Applications/Official/DEV_FW/source/blackjack/blackjack.c b/Applications/Official/DEV_FW/source/blackjack/blackjack.c new file mode 100644 index 000000000..73d393f8b --- /dev/null +++ b/Applications/Official/DEV_FW/source/blackjack/blackjack.c @@ -0,0 +1,634 @@ + +#include +#include +#include +#include +#include + +#include +#include "util.h" +#include "defines.h" +#include "common/card.h" +#include "common/dml.h" +#include "common/queue.h" +#include "util.h" +#include "ui.h" + +#include "BlackJack_icons.h" + +#define DEALER_MAX 17 + +void start_round(GameState* game_state); + +void init(GameState* game_state); + +static void draw_ui(Canvas* const canvas, const GameState* game_state) { + draw_money(canvas, game_state->player_score); + + draw_score(canvas, true, hand_count(game_state->player_cards, game_state->player_card_count)); + + if(!game_state->queue_state.running && game_state->state == GameStatePlay) { + render_menu(game_state->menu, canvas, 2, 47); + } +} + +static void render_callback(Canvas* const canvas, void* ctx) { + const GameState* game_state = acquire_mutex((ValueMutex*)ctx, 25); + + if(game_state == NULL) { + return; + } + + canvas_set_color(canvas, ColorBlack); + canvas_draw_frame(canvas, 0, 0, 128, 64); + + if(game_state->state == GameStateStart) { + canvas_draw_icon(canvas, 0, 0, &I_blackjack); + } + if(game_state->state == GameStateGameOver) { + canvas_draw_icon(canvas, 0, 0, &I_endscreen); + } + + if(game_state->state == GameStatePlay || game_state->state == GameStateDealer) { + if(game_state->state == GameStatePlay) + draw_player_scene(canvas, game_state); + else + draw_dealer_scene(canvas, game_state); + render_queue(&(game_state->queue_state), game_state, canvas); + draw_ui(canvas, game_state); + } else if(game_state->state == GameStateSettings) { + settings_page(canvas, game_state); + } + + release_mutex((ValueMutex*)ctx, game_state); +} + +//region card draw +Card draw_card(GameState* game_state) { + Card c = game_state->deck.cards[game_state->deck.index]; + game_state->deck.index++; + return c; +} + +void drawPlayerCard(void* ctx) { + GameState* game_state = ctx; + Card c = draw_card(game_state); + game_state->player_cards[game_state->player_card_count] = c; + game_state->player_card_count++; + if(game_state->player_score < game_state->settings.round_price || game_state->doubled) { + set_menu_state(game_state->menu, 0, false); + } +} + +void drawDealerCard(void* ctx) { + GameState* game_state = ctx; + Card c = draw_card(game_state); + game_state->dealer_cards[game_state->dealer_card_count] = c; + game_state->dealer_card_count++; +} +//endregion + +//region queue callbacks +void to_lose_state(const void* ctx, Canvas* const canvas) { + const GameState* game_state = ctx; + if(game_state->settings.message_duration == 0) return; + popup_frame(canvas); + elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "You lost"); +} + +void to_bust_state(const void* ctx, Canvas* const canvas) { + const GameState* game_state = ctx; + if(game_state->settings.message_duration == 0) return; + popup_frame(canvas); + elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Busted!"); +} + +void to_draw_state(const void* ctx, Canvas* const canvas) { + const GameState* game_state = ctx; + if(game_state->settings.message_duration == 0) return; + popup_frame(canvas); + elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Draw"); +} + +void to_dealer_turn(const void* ctx, Canvas* const canvas) { + const GameState* game_state = ctx; + if(game_state->settings.message_duration == 0) return; + popup_frame(canvas); + elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Dealers turn"); +} + +void to_win_state(const void* ctx, Canvas* const canvas) { + const GameState* game_state = ctx; + if(game_state->settings.message_duration == 0) return; + popup_frame(canvas); + elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "You win"); +} + +void to_start(const void* ctx, Canvas* const canvas) { + const GameState* game_state = ctx; + if(game_state->settings.message_duration == 0) return; + popup_frame(canvas); + elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Round started"); +} + +void before_start(void* ctx) { + GameState* game_state = ctx; + game_state->dealer_card_count = 0; + game_state->player_card_count = 0; +} + +void start(void* ctx) { + GameState* game_state = ctx; + start_round(game_state); +} + +void draw(void* ctx) { + GameState* game_state = ctx; + game_state->player_score += game_state->bet; + game_state->bet = 0; + enqueue( + &(game_state->queue_state), + game_state, + start, + before_start, + to_start, + game_state->settings.message_duration); +} + +void game_over(void* ctx) { + GameState* game_state = ctx; + game_state->state = GameStateGameOver; +} + +void lose(void* ctx) { + GameState* game_state = ctx; + game_state->state = GameStatePlay; + game_state->bet = 0; + if(game_state->player_score >= game_state->settings.round_price) { + enqueue( + &(game_state->queue_state), + game_state, + start, + before_start, + to_start, + game_state->settings.message_duration); + } else { + enqueue(&(game_state->queue_state), game_state, game_over, NULL, NULL, 0); + } +} + +void win(void* ctx) { + GameState* game_state = ctx; + game_state->state = GameStatePlay; + game_state->player_score += game_state->bet * 2; + game_state->bet = 0; + enqueue( + &(game_state->queue_state), + game_state, + start, + before_start, + to_start, + game_state->settings.message_duration); +} + +void dealerTurn(void* ctx) { + GameState* game_state = ctx; + game_state->state = GameStateDealer; +} + +float animationTime(const GameState* game_state) { + return (float)(furi_get_tick() - game_state->queue_state.start) / + (float)(game_state->settings.animation_duration); +} + +void dealer_card_animation(const void* ctx, Canvas* const canvas) { + const GameState* game_state = ctx; + float t = animationTime(game_state); + + Card animatingCard = game_state->deck.cards[game_state->deck.index]; + if(game_state->dealer_card_count > 1) { + Vector end = card_pos_at_index(game_state->dealer_card_count); + draw_card_animation(animatingCard, (Vector){0, 64}, (Vector){0, 32}, end, t, true, canvas); + } else { + draw_card_animation( + animatingCard, + (Vector){32, -CARD_HEIGHT}, + (Vector){64, 32}, + (Vector){2, 2}, + t, + false, + canvas); + } +} + +void dealer_back_card_animation(const void* ctx, Canvas* const canvas) { + const GameState* game_state = ctx; + float t = animationTime(game_state); + + Vector currentPos = + quadratic_2d((Vector){32, -CARD_HEIGHT}, (Vector){64, 32}, (Vector){13, 5}, t); + draw_card_back_at(currentPos.x, currentPos.y, canvas); +} + +void player_card_animation(const void* ctx, Canvas* const canvas) { + const GameState* game_state = ctx; + float t = animationTime(game_state); + + Card animatingCard = game_state->deck.cards[game_state->deck.index]; + Vector end = card_pos_at_index(game_state->player_card_count); + + draw_card_animation( + animatingCard, (Vector){32, -CARD_HEIGHT}, (Vector){0, 32}, end, t, true, canvas); +} +//endregion + +void player_tick(GameState* game_state) { + uint8_t score = hand_count(game_state->player_cards, game_state->player_card_count); + if((game_state->doubled && score <= 21) || score == 21) { + enqueue( + &(game_state->queue_state), + game_state, + dealerTurn, + NULL, + to_dealer_turn, + game_state->settings.message_duration); + } else if(score > 21) { + enqueue( + &(game_state->queue_state), + game_state, + lose, + NULL, + to_bust_state, + game_state->settings.message_duration); + } else { + if(game_state->selectDirection == DirectionUp || + game_state->selectDirection == DirectionDown) { + move_menu(game_state->menu, game_state->selectDirection == DirectionUp ? -1 : 1); + } + + if(game_state->selectDirection == Select) { + activate_menu(game_state->menu, game_state); + } + } +} + +void dealer_tick(GameState* game_state) { + uint8_t dealer_score = hand_count(game_state->dealer_cards, game_state->dealer_card_count); + uint8_t player_score = hand_count(game_state->player_cards, game_state->player_card_count); + + if(dealer_score >= DEALER_MAX) { + if(dealer_score > 21 || dealer_score < player_score) { + enqueue( + &(game_state->queue_state), + game_state, + win, + NULL, + to_win_state, + game_state->settings.message_duration); + } else if(dealer_score > player_score) { + enqueue( + &(game_state->queue_state), + game_state, + lose, + NULL, + to_lose_state, + game_state->settings.message_duration); + } else if(dealer_score == player_score) { + enqueue( + &(game_state->queue_state), + game_state, + draw, + NULL, + to_draw_state, + game_state->settings.message_duration); + } + } else { + enqueue( + &(game_state->queue_state), + game_state, + drawDealerCard, + NULL, + dealer_card_animation, + game_state->settings.animation_duration); + } +} + +void settings_tick(GameState* game_state) { + if(game_state->selectDirection == DirectionDown && game_state->selectedMenu < 4) { + game_state->selectedMenu++; + } + if(game_state->selectDirection == DirectionUp && game_state->selectedMenu > 0) { + game_state->selectedMenu--; + } + + if(game_state->selectDirection == DirectionLeft || + game_state->selectDirection == DirectionRight) { + int nextScore = 0; + switch(game_state->selectedMenu) { + case 0: + nextScore = game_state->settings.starting_money; + if(game_state->selectDirection == DirectionLeft) + nextScore -= 10; + else + nextScore += 10; + if(nextScore >= (int)game_state->settings.round_price && nextScore < 400) + game_state->settings.starting_money = nextScore; + break; + case 1: + nextScore = game_state->settings.round_price; + if(game_state->selectDirection == DirectionLeft) + nextScore -= 10; + else + nextScore += 10; + if(nextScore >= 5 && nextScore <= (int)game_state->settings.starting_money) + game_state->settings.round_price = nextScore; + break; + case 2: + nextScore = game_state->settings.animation_duration; + if(game_state->selectDirection == DirectionLeft) + nextScore -= 100; + else + nextScore += 100; + if(nextScore >= 0 && nextScore < 2000) + game_state->settings.animation_duration = nextScore; + break; + case 3: + nextScore = game_state->settings.message_duration; + if(game_state->selectDirection == DirectionLeft) + nextScore -= 100; + else + nextScore += 100; + if(nextScore >= 0 && nextScore < 2000) + game_state->settings.message_duration = nextScore; + break; + case 4: + game_state->settings.sound_effects = !game_state->settings.sound_effects; + default: + break; + } + } +} + +void tick(GameState* game_state) { + game_state->last_tick = furi_get_tick(); + bool queue_ran = run_queue(&(game_state->queue_state), game_state); + + switch(game_state->state) { + case GameStateGameOver: + case GameStateStart: + if(game_state->selectDirection == Select) + init(game_state); + else if(game_state->selectDirection == DirectionRight) { + game_state->selectedMenu = 0; + game_state->state = GameStateSettings; + } + break; + case GameStatePlay: + if(!game_state->started) { + game_state->selectedMenu = 0; + game_state->started = true; + enqueue( + &(game_state->queue_state), + game_state, + drawDealerCard, + NULL, + dealer_back_card_animation, + game_state->settings.animation_duration); + enqueue( + &(game_state->queue_state), + game_state, + drawPlayerCard, + NULL, + player_card_animation, + game_state->settings.animation_duration); + enqueue( + &(game_state->queue_state), + game_state, + drawDealerCard, + NULL, + dealer_card_animation, + game_state->settings.animation_duration); + enqueue( + &(game_state->queue_state), + game_state, + drawPlayerCard, + NULL, + player_card_animation, + game_state->settings.animation_duration); + } + if(!queue_ran) player_tick(game_state); + break; + case GameStateDealer: + if(!queue_ran) dealer_tick(game_state); + break; + case GameStateSettings: + settings_tick(game_state); + break; + default: + break; + } + + game_state->selectDirection = None; +} + +void start_round(GameState* game_state) { + game_state->menu->current_menu = 1; + game_state->player_card_count = 0; + game_state->dealer_card_count = 0; + set_menu_state(game_state->menu, 0, true); + game_state->menu->enabled = true; + game_state->started = false; + game_state->doubled = false; + game_state->queue_state.running = true; + shuffle_deck(&(game_state->deck)); + game_state->doubled = false; + game_state->bet = game_state->settings.round_price; + if(game_state->player_score < game_state->settings.round_price) { + game_state->state = GameStateGameOver; + } else { + game_state->player_score -= game_state->settings.round_price; + } + game_state->state = GameStatePlay; +} + +void init(GameState* game_state) { + set_menu_state(game_state->menu, 0, true); + game_state->menu->enabled = true; + game_state->menu->current_menu = 1; + game_state->settings = load_settings(); + game_state->last_tick = 0; + game_state->processing = true; + game_state->selectedMenu = 0; + game_state->player_score = game_state->settings.starting_money; + generate_deck(&(game_state->deck), 6); + start_round(game_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 update_timer_callback(FuriMessageQueue* event_queue) { + furi_assert(event_queue); + AppEvent event = {.type = EventTypeTick}; + furi_message_queue_put(event_queue, &event, 0); +} + +void doubleAction(void* state) { + GameState* game_state = state; + if(!game_state->doubled && game_state->player_score >= game_state->settings.round_price) { + game_state->player_score -= game_state->settings.round_price; + game_state->bet += game_state->settings.round_price; + game_state->doubled = true; + enqueue( + &(game_state->queue_state), + game_state, + drawPlayerCard, + NULL, + player_card_animation, + game_state->settings.animation_duration); + game_state->player_cards[game_state->player_card_count] = + game_state->deck.cards[game_state->deck.index]; + uint8_t score = hand_count(game_state->player_cards, game_state->player_card_count + 1); + if(score > 21) { + enqueue( + &(game_state->queue_state), + game_state, + lose, + NULL, + to_bust_state, + game_state->settings.message_duration); + } else { + enqueue( + &(game_state->queue_state), + game_state, + dealerTurn, + NULL, + to_dealer_turn, + game_state->settings.message_duration); + } + set_menu_state(game_state->menu, 0, false); + } +} + +void hitAction(void* state) { + GameState* game_state = state; + enqueue( + &(game_state->queue_state), + game_state, + drawPlayerCard, + NULL, + player_card_animation, + game_state->settings.animation_duration); +} +void stayAction(void* state) { + GameState* game_state = state; + enqueue( + &(game_state->queue_state), + game_state, + dealerTurn, + NULL, + to_dealer_turn, + game_state->settings.message_duration); +} + +int32_t blackjack_app(void* p) { + UNUSED(p); + + int32_t return_code = 0; + + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(AppEvent)); + + GameState* game_state = malloc(sizeof(GameState)); + game_state->menu = malloc(sizeof(Menu)); + game_state->menu->menu_width = 40; + init(game_state); + add_menu(game_state->menu, "Double", doubleAction); + add_menu(game_state->menu, "Hit", hitAction); + add_menu(game_state->menu, "Stay", stayAction); + set_card_graphics(&I_card_graphics); + + game_state->state = GameStateStart; + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, game_state, sizeof(GameState))) { + FURI_LOG_E(APP_NAME, "cannot create mutex\r\n"); + return_code = 255; + goto free_and_exit; + } + + 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); + + FuriTimer* timer = furi_timer_alloc(update_timer_callback, FuriTimerTypePeriodic, event_queue); + furi_timer_start(timer, furi_kernel_get_tick_frequency() / 25); + + Gui* gui = furi_record_open("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); + GameState* localstate = (GameState*)acquire_mutex_block(&state_mutex); + if(event_status == FuriStatusOk) { + if(event.type == EventTypeKey) { + if(event.input.type == InputTypePress) { + switch(event.input.key) { + case InputKeyUp: + localstate->selectDirection = DirectionUp; + break; + case InputKeyDown: + localstate->selectDirection = DirectionDown; + break; + case InputKeyRight: + localstate->selectDirection = DirectionRight; + break; + case InputKeyLeft: + localstate->selectDirection = DirectionLeft; + break; + case InputKeyBack: + if(localstate->state == GameStateSettings) { + localstate->state = GameStateStart; + save_settings(localstate->settings); + } else + processing = false; + break; + case InputKeyOk: + localstate->selectDirection = Select; + break; + default: + break; + } + } + } else if(event.type == EventTypeTick) { + tick(localstate); + processing = localstate->processing; + } + } else { + //FURI_LOG_D(APP_NAME, "osMessageQueue: event timeout"); + // event timeout + } + view_port_update(view_port); + release_mutex(&state_mutex, localstate); + } + + furi_timer_free(timer); + 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); + +free_and_exit: + free(game_state->deck.cards); + free_menu(game_state->menu); + queue_clear(&(game_state->queue_state)); + free(game_state); + furi_message_queue_free(event_queue); + + return return_code; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/blackjack/blackjack_10px.png b/Applications/Official/DEV_FW/source/blackjack/blackjack_10px.png new file mode 100644 index 000000000..7382d6358 Binary files /dev/null and b/Applications/Official/DEV_FW/source/blackjack/blackjack_10px.png differ diff --git a/Applications/Official/DEV_FW/source/blackjack/common/card.c b/Applications/Official/DEV_FW/source/blackjack/common/card.c new file mode 100644 index 000000000..199135bb5 --- /dev/null +++ b/Applications/Official/DEV_FW/source/blackjack/common/card.c @@ -0,0 +1,353 @@ +#include "card.h" +#include "dml.h" +#include "ui.h" + +#define CARD_DRAW_X_START 108 +#define CARD_DRAW_Y_START 38 +#define CARD_DRAW_X_SPACE 10 +#define CARD_DRAW_Y_SPACE 8 +#define CARD_DRAW_X_OFFSET 4 +#define CARD_DRAW_FIRST_ROW_LENGTH 7 + +uint8_t pips[4][3] = { + {21, 10, 7}, //spades + {7, 10, 7}, //hearts + {0, 10, 7}, //diamonds + {14, 10, 7}, //clubs +}; +uint8_t letters[13][3] = { + {0, 0, 5}, + {5, 0, 5}, + {10, 0, 5}, + {15, 0, 5}, + {20, 0, 5}, + {25, 0, 5}, + {30, 0, 5}, + {0, 5, 5}, + {5, 5, 5}, + {10, 5, 5}, + {15, 5, 5}, + {20, 5, 5}, + {25, 5, 5}, +}; + +//region Player card positions +uint8_t playerCardPositions[22][4] = { + //first row + {108, 38}, + {98, 38}, + {88, 38}, + {78, 38}, + {68, 38}, + {58, 38}, + {48, 38}, + {38, 38}, + //second row + {104, 26}, + {94, 26}, + {84, 26}, + {74, 26}, + {64, 26}, + {54, 26}, + {44, 26}, + //third row + {99, 14}, + {89, 14}, + {79, 14}, + {69, 14}, + {59, 14}, + {49, 14}, +}; +//endregion +Icon* card_graphics = NULL; + +void set_card_graphics(const Icon* graphics) { + card_graphics = (Icon*)graphics; +} + +void draw_card_at_colored( + int8_t pos_x, + int8_t pos_y, + uint8_t pip, + uint8_t character, + bool inverted, + Canvas* const canvas) { + DrawMode primary = inverted ? Black : White; + DrawMode secondary = inverted ? White : Black; + draw_rounded_box(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, primary); + draw_rounded_box_frame(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, Black); + + uint8_t* drawInfo = pips[pip]; + uint8_t px = drawInfo[0], py = drawInfo[1], s = drawInfo[2]; + + uint8_t left = pos_x + 2; + uint8_t right = (pos_x + CARD_WIDTH - s - 2); + uint8_t top = pos_y + 2; + uint8_t bottom = (pos_y + CARD_HEIGHT - s - 2); + + draw_icon_clip(canvas, card_graphics, right, top, px, py, s, s, secondary); + draw_icon_clip_flipped(canvas, card_graphics, left, bottom, px, py, s, s, secondary); + + drawInfo = letters[character]; + px = drawInfo[0], py = drawInfo[1], s = drawInfo[2]; + left = pos_x + 2; + right = (pos_x + CARD_WIDTH - s - 2); + top = pos_y + 2; + bottom = (pos_y + CARD_HEIGHT - s - 2); + + draw_icon_clip(canvas, card_graphics, left, top + 1, px, py, s, s, secondary); + draw_icon_clip_flipped(canvas, card_graphics, right, bottom - 1, px, py, s, s, secondary); +} + +void draw_card_at(int8_t pos_x, int8_t pos_y, uint8_t pip, uint8_t character, Canvas* const canvas) { + draw_card_at_colored(pos_x, pos_y, pip, character, false, canvas); +} + +void draw_deck(const Card* cards, uint8_t count, Canvas* const canvas) { + for(int i = count - 1; i >= 0; i--) { + draw_card_at( + playerCardPositions[i][0], + playerCardPositions[i][1], + cards[i].pip, + cards[i].character, + canvas); + } +} + +Vector card_pos_at_index(uint8_t index) { + return (Vector){playerCardPositions[index][0], playerCardPositions[index][1]}; +} + +void draw_card_back_at(int8_t pos_x, int8_t pos_y, Canvas* const canvas) { + draw_rounded_box(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, White); + draw_rounded_box_frame(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, Black); + + draw_icon_clip(canvas, card_graphics, pos_x + 1, pos_y + 1, 35, 0, 15, 21, Black); +} + +void generate_deck(Deck* deck_ptr, uint8_t deck_count) { + uint16_t counter = 0; + if(deck_ptr->cards != NULL) { + free(deck_ptr->cards); + } + + deck_ptr->deck_count = deck_count; + deck_ptr->card_count = deck_count * 52; + deck_ptr->cards = malloc(sizeof(Card) * deck_ptr->card_count); + + for(uint8_t deck = 0; deck < deck_count; deck++) { + for(uint8_t pip = 0; pip < 4; pip++) { + for(uint8_t label = 0; label < 13; label++) { + deck_ptr->cards[counter] = (Card){pip, label, false, false}; + counter++; + } + } + } +} + +void shuffle_deck(Deck* deck_ptr) { + srand(DWT->CYCCNT); + deck_ptr->index = 0; + int max = deck_ptr->deck_count * 52; + for(int i = 0; i < max; i++) { + int r = i + (rand() % (max - i)); + Card tmp = deck_ptr->cards[i]; + deck_ptr->cards[i] = deck_ptr->cards[r]; + deck_ptr->cards[r] = tmp; + } +} + +uint8_t hand_count(const Card* cards, uint8_t count) { + uint8_t aceCount = 0; + uint8_t score = 0; + + for(uint8_t i = 0; i < count; i++) { + if(cards[i].character == 12) + aceCount++; + else { + if(cards[i].character > 8) + score += 10; + else + score += cards[i].character + 2; + } + } + + for(uint8_t i = 0; i < aceCount; i++) { + if((score + 11) <= 21) + score += 11; + else + score++; + } + + return score; +} + +void draw_card_animation( + Card animatingCard, + Vector from, + Vector control, + Vector to, + float t, + bool extra_margin, + Canvas* const canvas) { + float time = t; + if(extra_margin) { + time += 0.2; + } + + Vector currentPos = quadratic_2d(from, control, to, time); + if(t > 1) { + draw_card_at( + currentPos.x, currentPos.y, animatingCard.pip, animatingCard.character, canvas); + } else { + if(t < 0.5) + draw_card_back_at(currentPos.x, currentPos.y, canvas); + else + draw_card_at( + currentPos.x, currentPos.y, animatingCard.pip, animatingCard.character, canvas); + } +} + +void init_hand(Hand* hand_ptr, uint8_t count) { + hand_ptr->cards = malloc(sizeof(Card) * count); + hand_ptr->index = 0; + hand_ptr->max = count; +} + +void free_hand(Hand* hand_ptr) { + FURI_LOG_D("CARD", "Freeing hand"); + free(hand_ptr->cards); +} + +void add_to_hand(Hand* hand_ptr, Card card) { + FURI_LOG_D("CARD", "Adding to hand"); + if(hand_ptr->index < hand_ptr->max) { + hand_ptr->cards[hand_ptr->index] = card; + hand_ptr->index++; + } +} + +void draw_card_space(int16_t pos_x, int16_t pos_y, bool highlighted, Canvas* const canvas) { + if(highlighted) { + draw_rounded_box_frame(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, Black); + draw_rounded_box_frame( + canvas, pos_x + 2, pos_y + 2, CARD_WIDTH - 4, CARD_HEIGHT - 4, White); + } else { + draw_rounded_box(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, Black); + draw_rounded_box_frame( + canvas, pos_x + 2, pos_y + 2, CARD_WIDTH - 4, CARD_HEIGHT - 4, White); + } +} + +int first_non_flipped_card(Hand hand) { + for(int i = 0; i < hand.index; i++) { + if(!hand.cards[i].flipped) { + return i; + } + } + return hand.index; +} + +void draw_hand_column( + Hand hand, + int16_t pos_x, + int16_t pos_y, + int8_t highlight, + Canvas* const canvas) { + if(hand.index == 0) { + draw_card_space(pos_x, pos_y, highlight > 0, canvas); + if(highlight == 0) + draw_rounded_box(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, Inverse); + return; + } + + int loopEnd = hand.index; + int hStart = max(loopEnd - 4, 0); + int pos = 0; + int first = first_non_flipped_card(hand); + bool wastop = false; + if(first >= 0 && first <= hStart && highlight != first) { + if(first > 0) { + draw_card_back_at(pos_x, pos_y + pos, canvas); + pos += 4; + hStart++; + wastop = true; + } + draw_card_at_colored( + pos_x, pos_y + pos, hand.cards[first].pip, hand.cards[first].character, false, canvas); + pos += 8; + hStart++; + } + if(hStart > highlight && highlight >= 0) { + if(!wastop && first > 0) { + draw_card_back_at(pos_x, pos_y + pos, canvas); + pos += 4; + hStart++; + } + draw_card_at_colored( + pos_x, + pos_y + pos, + hand.cards[highlight].pip, + hand.cards[highlight].character, + true, + canvas); + pos += 8; + hStart++; + } + for(int i = hStart; i < loopEnd; i++, pos += 4) { + if(hand.cards[i].flipped) { + draw_card_back_at(pos_x, pos_y + pos, canvas); + if(i == highlight) + draw_rounded_box( + canvas, pos_x + 1, pos_y + pos + 1, CARD_WIDTH - 2, CARD_HEIGHT - 2, Inverse); + } else { + draw_card_at_colored( + pos_x, + pos_y + pos, + hand.cards[i].pip, + hand.cards[i].character, + (i == highlight), + canvas); + if(i == highlight || i == first) pos += 4; + } + } +} + +Card remove_from_deck(uint16_t index, Deck* deck) { + FURI_LOG_D("CARD", "Removing from deck"); + Card result = {0, 0, true, false}; + if(deck->card_count > 0) { + deck->card_count--; + for(int i = 0, curr_index = 0; i <= deck->card_count; i++) { + if(i != index) { + deck->cards[curr_index] = deck->cards[i]; + curr_index++; + } else { + result = deck->cards[i]; + } + } + if(deck->index >= 0) { + deck->index--; + } + } + return result; +} + +void extract_hand_region(Hand* hand, Hand* to, uint8_t start_index) { + FURI_LOG_D("CARD", "Extracting hand region"); + if(start_index >= hand->index) return; + + for(uint8_t i = start_index; i < hand->index; i++) { + add_to_hand(to, hand->cards[i]); + } + hand->index = start_index; +} + +void add_hand_region(Hand* to, Hand* from) { + FURI_LOG_D("CARD", "Adding hand region"); + if((to->index + from->index) <= to->max) { + for(int i = 0; i < from->index; i++) { + add_to_hand(to, from->cards[i]); + } + } +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/blackjack/common/card.h b/Applications/Official/DEV_FW/source/blackjack/common/card.h new file mode 100644 index 000000000..8e5e23bbf --- /dev/null +++ b/Applications/Official/DEV_FW/source/blackjack/common/card.h @@ -0,0 +1,192 @@ +#pragma once + +#include +#include +#include +#include "dml.h" + +#define CARD_HEIGHT 23 +#define CARD_HALF_HEIGHT 11 +#define CARD_WIDTH 17 +#define CARD_HALF_WIDTH 8 + +//region types +typedef struct { + uint8_t pip; //Pip index 0:spades, 1:hearths, 2:diamonds, 3:clubs + uint8_t character; //Card letter [0-12], 0 means 2, 12 is Ace + bool disabled; + bool flipped; +} Card; + +typedef struct { + uint8_t deck_count; //Number of decks used + Card* cards; //Cards in the deck + int card_count; + int index; //Card index (to know where we at in the deck) +} Deck; + +typedef struct { + Card* cards; //Cards in the deck + uint8_t index; //Current index + uint8_t max; //How many cards we want to store +} Hand; +//endregion + +void set_card_graphics(const Icon* graphics); + +/** + * Gets card coordinates at the index (range: 0-20). + * + * @param index Index to check 0-20 + * @return Position of the card + */ +Vector card_pos_at_index(uint8_t index); + +/** + * Draws card at a given coordinate (top-left corner) + * + * @param pos_x X position + * @param pos_y Y position + * @param pip Pip index 0:spades, 1:hearths, 2:diamonds, 3:clubs + * @param character Letter [0-12] 0 is 2, 12 is A + * @param canvas Pointer to Flipper's canvas object + */ +void draw_card_at(int8_t pos_x, int8_t pos_y, uint8_t pip, uint8_t character, Canvas* const canvas); + +/** + * Draws card at a given coordinate (top-left corner) + * + * @param pos_x X position + * @param pos_y Y position + * @param pip Pip index 0:spades, 1:hearths, 2:diamonds, 3:clubs + * @param character Letter [0-12] 0 is 2, 12 is A + * @param inverted Invert colors + * @param canvas Pointer to Flipper's canvas object + */ +void draw_card_at_colored( + int8_t pos_x, + int8_t pos_y, + uint8_t pip, + uint8_t character, + bool inverted, + Canvas* const canvas); + +/** + * Draws 'count' cards at the bottom right corner + * + * @param cards List of cards + * @param count Count of cards + * @param canvas Pointer to Flipper's canvas object + */ +void draw_deck(const Card* cards, uint8_t count, Canvas* const canvas); + +/** + * Draws card back at a given coordinate (top-left corner) + * + * @param pos_x X coordinate + * @param pos_y Y coordinate + * @param canvas Pointer to Flipper's canvas object + */ +void draw_card_back_at(int8_t pos_x, int8_t pos_y, Canvas* const canvas); + +/** + * Generates the deck + * + * @param deck_ptr Pointer to the deck + * @param deck_count Number of decks + */ +void generate_deck(Deck* deck_ptr, uint8_t deck_count); + +/** + * Shuffles the deck + * + * @param deck_ptr Pointer to the deck + */ +void shuffle_deck(Deck* deck_ptr); + +/** + * Calculates the hand count for blackjack + * + * @param cards List of cards + * @param count Count of cards + * @return Hand value + */ +uint8_t hand_count(const Card* cards, uint8_t count); + +/** + * Draws card animation + * + * @param animatingCard Card to animate + * @param from Starting position + * @param control Quadratic lerp control point + * @param to End point + * @param t Current time (0-1) + * @param extra_margin Use extra margin at the end (arrives 0.2 unit before the end so it can stay there a bit) + * @param canvas Pointer to Flipper's canvas object + */ +void draw_card_animation( + Card animatingCard, + Vector from, + Vector control, + Vector to, + float t, + bool extra_margin, + Canvas* const canvas); + +/** + * Init hand pointer + * @param hand_ptr Pointer to hand + * @param count Number of cards we want to store + */ +void init_hand(Hand* hand_ptr, uint8_t count); + +/** + * Free hand resources + * @param hand_ptr Pointer to hand + */ +void free_hand(Hand* hand_ptr); + +/** + * Add card to hand + * @param hand_ptr Pointer to hand + * @param card Card to add + */ +void add_to_hand(Hand* hand_ptr, Card card); + +/** + * Draw card placement position at coordinate + * @param pos_x X coordinate + * @param pos_y Y coordinate + * @param highlighted Apply highlight effect + * @param canvas Canvas object + */ +void draw_card_space(int16_t pos_x, int16_t pos_y, bool highlighted, Canvas* const canvas); + +/** + * Draws a column of card, displaying the last [max_cards] cards on the list + * @param hand Hand object + * @param pos_x X coordinate to draw + * @param pos_y Y coordinate to draw + * @param highlight Index to highlight, negative means no highlight + * @param canvas Canvas object + */ +void draw_hand_column( + Hand hand, + int16_t pos_x, + int16_t pos_y, + int8_t highlight, + Canvas* const canvas); + +/** + * Removes a card from the deck (Be aware, if you remove the first item, the deck index will be at -1 so you have to handle that) + * @param index Index to remove + * @param deck Deck reference + * @return The removed card + */ +Card remove_from_deck(uint16_t index, Deck* deck); + +int first_non_flipped_card(Hand hand); + +void extract_hand_region(Hand* hand, Hand* to, uint8_t start_index); + +void add_hand_region(Hand* to, Hand* from); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/blackjack/common/dml.c b/Applications/Official/DEV_FW/source/blackjack/common/dml.c new file mode 100644 index 000000000..b9a0e395f --- /dev/null +++ b/Applications/Official/DEV_FW/source/blackjack/common/dml.c @@ -0,0 +1,53 @@ +#include "dml.h" +#include + +float lerp(float v0, float v1, float t) { + if(t > 1) return v1; + return (1 - t) * v0 + t * v1; +} + +Vector lerp_2d(Vector start, Vector end, float t) { + return (Vector){ + lerp(start.x, end.x, t), + lerp(start.y, end.y, t), + }; +} + +Vector quadratic_2d(Vector start, Vector control, Vector end, float t) { + return lerp_2d(lerp_2d(start, control, t), lerp_2d(control, end, t), t); +} + +Vector vector_add(Vector a, Vector b) { + return (Vector){a.x + b.x, a.y + b.y}; +} + +Vector vector_sub(Vector a, Vector b) { + return (Vector){a.x - b.x, a.y - b.y}; +} + +Vector vector_mul_components(Vector a, Vector b) { + return (Vector){a.x * b.x, a.y * b.y}; +} + +Vector vector_div_components(Vector a, Vector b) { + return (Vector){a.x / b.x, a.y / b.y}; +} + +Vector vector_normalized(Vector a) { + float length = vector_magnitude(a); + return (Vector){a.x / length, a.y / length}; +} + +float vector_magnitude(Vector a) { + return sqrt(a.x * a.x + a.y * a.y); +} + +float vector_distance(Vector a, Vector b) { + return vector_magnitude(vector_sub(a, b)); +} + +float vector_dot(Vector a, Vector b) { + Vector _a = vector_normalized(a); + Vector _b = vector_normalized(b); + return _a.x * _b.x + _a.y * _b.y; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/blackjack/common/dml.h b/Applications/Official/DEV_FW/source/blackjack/common/dml.h new file mode 100644 index 000000000..0e1a23e23 --- /dev/null +++ b/Applications/Official/DEV_FW/source/blackjack/common/dml.h @@ -0,0 +1,116 @@ +// +// Doofy's Math library +// + +#pragma once + +typedef struct { + float x; + float y; +} Vector; + +#define min(a, b) ((a) < (b) ? (a) : (b)) +#define max(a, b) ((a) > (b) ? (a) : (b)) +#define abs(x) ((x) > 0 ? (x) : -(x)) + +/** + * Lerp function + * + * @param v0 Start value + * @param v1 End value + * @param t Time (0-1 range) + * @return Point between v0-v1 at a given time + */ +float lerp(float v0, float v1, float t); + +/** + * 2D lerp function + * + * @param start Start vector + * @param end End vector + * @param t Time (0-1 range) + * @return 2d Vector between start and end at time + */ +Vector lerp_2d(Vector start, Vector end, float t); + +/** + * Quadratic lerp function + * + * @param start Start vector + * @param control Control point + * @param end End vector + * @param t Time (0-1 range) + * @return 2d Vector at time + */ +Vector quadratic_2d(Vector start, Vector control, Vector end, float t); + +/** + * Add vector components together + * + * @param a First vector + * @param b Second vector + * @return Resulting vector + */ +Vector vector_add(Vector a, Vector b); + +/** + * Subtract vector components together + * + * @param a First vector + * @param b Second vector + * @return Resulting vector + */ +Vector vector_sub(Vector a, Vector b); + +/** + * Multiplying vector components together + * + * @param a First vector + * @param b Second vector + * @return Resulting vector + */ +Vector vector_mul_components(Vector a, Vector b); + +/** + * Dividing vector components + * + * @param a First vector + * @param b Second vector + * @return Resulting vector + */ +Vector vector_div_components(Vector a, Vector b); + +/** + * Calculating Vector length + * + * @param a Direction vector + * @return Length of the vector + */ +float vector_magnitude(Vector a); + +/** + * Get a normalized vector (length of 1) + * + * @param a Direction vector + * @return Normalized vector + */ +Vector vector_normalized(Vector a); + +/** + * Calculate two vector's distance + * + * @param a First vector + * @param b Second vector + * @return Distance between vectors + */ +float vector_distance(Vector a, Vector b); + +/** + * Calculate the dot product of the vectors. + * No need to normalize, it will do it + * + * @param a First vector + * @param b Second vector + * @return value from -1 to 1 + */ +float vector_dot(Vector a, Vector b); diff --git a/Applications/Official/DEV_FW/source/blackjack/common/menu.c b/Applications/Official/DEV_FW/source/blackjack/common/menu.c new file mode 100644 index 000000000..ffc3921b7 --- /dev/null +++ b/Applications/Official/DEV_FW/source/blackjack/common/menu.c @@ -0,0 +1,103 @@ +#include "menu.h" + +void add_menu(Menu* menu, const char* name, void (*callback)(void*)) { + MenuItem* items = menu->items; + + menu->items = malloc(sizeof(MenuItem) * (menu->menu_count + 1)); + for(uint8_t i = 0; i < menu->menu_count; i++) { + menu->items[i] = items[i]; + } + free(items); + + menu->items[menu->menu_count] = (MenuItem){name, true, callback}; + menu->menu_count++; +} + +void free_menu(Menu* menu) { + free(menu->items); + free(menu); +} + +void set_menu_state(Menu* menu, uint8_t index, bool state) { + if(menu->menu_count > index) { + menu->items[index].enabled = state; + } + if(!state && menu->current_menu == index) move_menu(menu, 1); +} + +void move_menu(Menu* menu, int8_t direction) { + if(!menu->enabled) return; + int max = menu->menu_count; + for(int8_t i = 0; i < max; i++) { + FURI_LOG_D( + "MENU", + "Iteration %i, current %i, direction %i, state %i", + i, + menu->current_menu, + direction, + menu->items[menu->current_menu].enabled ? 1 : 0); + if(direction < 0 && menu->current_menu == 0) { + menu->current_menu = menu->menu_count - 1; + } else { + menu->current_menu = (menu->current_menu + direction) % menu->menu_count; + } + FURI_LOG_D( + "MENU", + "After process current %i, direction %i, state %i", + menu->current_menu, + direction, + menu->items[menu->current_menu].enabled ? 1 : 0); + if(menu->items[menu->current_menu].enabled) { + FURI_LOG_D("MENU", "Next menu %i", menu->current_menu); + return; + } + } + FURI_LOG_D("MENU", "Not found, setting false"); + menu->enabled = false; +} + +void activate_menu(Menu* menu, void* state) { + if(!menu->enabled) return; + menu->items[menu->current_menu].callback(state); +} + +void render_menu(Menu* menu, Canvas* canvas, uint8_t pos_x, uint8_t pos_y) { + if(!menu->enabled) return; + canvas_set_color(canvas, ColorWhite); + canvas_draw_rbox(canvas, pos_x, pos_y, menu->menu_width + 2, 10, 2); + + uint8_t w = pos_x + menu->menu_width; + uint8_t h = pos_y + 10; + uint8_t p1x = pos_x + 2; + uint8_t p2x = pos_x + menu->menu_width - 2; + uint8_t p1y = pos_y + 2; + uint8_t p2y = pos_y + 8; + + canvas_set_color(canvas, ColorBlack); + canvas_draw_line(canvas, p1x, pos_y, p2x, pos_y); + canvas_draw_line(canvas, p1x, h, p2x, h); + canvas_draw_line(canvas, pos_x, p1y, pos_x, p2y); + canvas_draw_line(canvas, w, p1y, w, p2y); + canvas_draw_dot(canvas, pos_x + 1, pos_y + 1); + canvas_draw_dot(canvas, w - 1, pos_y + 1); + canvas_draw_dot(canvas, w - 1, h - 1); + canvas_draw_dot(canvas, pos_x + 1, h - 1); + + // canvas_draw_rbox(canvas, pos_x, pos_y, menu->menu_width + 2, 10, 2); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned( + canvas, + pos_x + menu->menu_width / 2, + pos_y + 6, + AlignCenter, + AlignCenter, + menu->items[menu->current_menu].name); + //9*5 + int center = pos_x + menu->menu_width / 2; + for(uint8_t i = 0; i < 4; i++) { + for(int8_t j = -i; j <= i; j++) { + canvas_draw_dot(canvas, center + j, pos_y - 4 + i); + canvas_draw_dot(canvas, center + j, pos_y + 14 - i); + } + } +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/blackjack/common/menu.h b/Applications/Official/DEV_FW/source/blackjack/common/menu.h new file mode 100644 index 000000000..9f2852522 --- /dev/null +++ b/Applications/Official/DEV_FW/source/blackjack/common/menu.h @@ -0,0 +1,77 @@ +#pragma once + +#include +#include + +typedef struct { + const char* name; //Name of the menu + bool enabled; //Is the menu item enabled (it will not render, you cannot select it) + + void (*callback)( + void* state); //Callback for when the activate_menu is called while this menu is selected +} MenuItem; + +typedef struct { + MenuItem* items; //list of menu items + uint8_t menu_count; //count of menu items (do not change) + uint8_t current_menu; //currently selected menu item + uint8_t menu_width; //width of the menu + bool enabled; //is the menu enabled (it will not render and accept events when disabled) +} Menu; + +/** + * Cleans up the pointers used by the menu + * + * @param menu Pointer of the menu to clean up + */ +void free_menu(Menu* menu); + +/** + * Add a new menu item + * + * @param menu Pointer of the menu + * @param name Name of the menu item + * @param callback Callback called on activation + */ +void add_menu(Menu* menu, const char* name, void (*callback)(void*)); + +/** + * Setting menu item to be enabled/disabled + * + * @param menu Pointer of the menu + * @param index Menu index to set + * @param state Enabled (true), Disabled(false) + */ +void set_menu_state(Menu* menu, uint8_t index, bool state); + +/** + * Moves selection up or down + * + * @param menu Pointer of the menu + * @param direction Direction to move -1 down, 1 up + */ +void move_menu(Menu* menu, int8_t direction); + +/** + * Triggers the current menu callback + * + * @param menu Pointer of the menu + * @param state Usually your application state + */ +void activate_menu(Menu* menu, void* state); + +/** + * Renders the menu at a coordinate (call it in your render function). + * + * Keep in mind that Flipper has a 128x64 pixel screen resolution and the coordinate + * you give is the menu's rectangle top-left corner (arrows not included). + * The rectangle height is 10 px, the arrows have a 4 pixel height. Space needed is 18px. + * The width of the menu can be configured in the menu object. + * + * + * @param menu Pointer of the menu + * @param canvas Flippers Canvas pointer + * @param pos_x X position to draw + * @param pos_y Y position to draw + */ +void render_menu(Menu* menu, Canvas* canvas, uint8_t pos_x, uint8_t pos_y); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/blackjack/common/queue.c b/Applications/Official/DEV_FW/source/blackjack/common/queue.c new file mode 100644 index 000000000..a80373460 --- /dev/null +++ b/Applications/Official/DEV_FW/source/blackjack/common/queue.c @@ -0,0 +1,69 @@ +#include "queue.h" + +void render_queue(const QueueState* queue_state, const void* app_state, Canvas* const canvas) { + if(queue_state->current != NULL && queue_state->current->render != NULL) + ((QueueItem*)queue_state->current)->render(app_state, canvas); +} + +bool run_queue(QueueState* queue_state, void* app_state) { + if(queue_state->current != NULL) { + queue_state->running = true; + if((furi_get_tick() - queue_state->start) >= queue_state->current->duration) + dequeue(queue_state, app_state); + + return true; + } + return false; +} + +void dequeue(QueueState* queue_state, void* app_state) { + ((QueueItem*)queue_state->current)->callback(app_state); + QueueItem* f = queue_state->current; + queue_state->current = f->next; + free(f); + if(queue_state->current != NULL) { + if(queue_state->current->start != NULL) queue_state->current->start(app_state); + queue_state->start = furi_get_tick(); + } else { + queue_state->running = false; + } +} + +void queue_clear(QueueState* queue_state) { + queue_state->running = false; + QueueItem* curr = queue_state->current; + while(curr != NULL) { + QueueItem* f = curr; + curr = curr->next; + free(f); + } +} + +void enqueue( + QueueState* queue_state, + void* app_state, + void (*done)(void* state), + void (*start)(void* state), + void (*render)(const void* state, Canvas* const canvas), + uint32_t duration) { + QueueItem* next; + if(queue_state->current == NULL) { + queue_state->start = furi_get_tick(); + queue_state->current = malloc(sizeof(QueueItem)); + next = queue_state->current; + if(next->start != NULL) next->start(app_state); + + } else { + next = queue_state->current; + while(next->next != NULL) { + next = (QueueItem*)(next->next); + } + next->next = malloc(sizeof(QueueItem)); + next = next->next; + } + next->callback = done; + next->render = render; + next->start = start; + next->duration = duration; + next->next = NULL; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/blackjack/common/queue.h b/Applications/Official/DEV_FW/source/blackjack/common/queue.h new file mode 100644 index 000000000..dcfe0c091 --- /dev/null +++ b/Applications/Official/DEV_FW/source/blackjack/common/queue.h @@ -0,0 +1,70 @@ +#pragma once + +#include +#include + +typedef struct { + void (*callback)(void* state); //Callback for when the item is dequeued + void (*render)( + const void* state, + Canvas* const canvas); //Callback for the rendering loop while this item is running + void (*start)(void* state); //Callback when this item is started running + void* next; //Pointer to the next item + uint32_t duration; //duration of the item +} QueueItem; + +typedef struct { + unsigned int start; //current queue item start time + QueueItem* current; //current queue item + bool running; //is the queue running +} QueueState; + +/** + * Enqueue a new item. + * + * @param queue_state The queue state pointer + * @param app_state Your app state + * @param done Callback for dequeue event + * @param start Callback for when the item is activated + * @param render Callback to render loop if needed + * @param duration Length of the item + */ +void enqueue( + QueueState* queue_state, + void* app_state, + void (*done)(void* state), + void (*start)(void* state), + void (*render)(const void* state, Canvas* const canvas), + uint32_t duration); +/** + * Clears all queue items + * + * @param queue_state The queue state pointer + */ +void queue_clear(QueueState* queue_state); + +/** + * Dequeues the active queue item. Usually you don't need to call it directly. + * + * @param queue_state The queue state pointer + * @param app_state Your application state + */ +void dequeue(QueueState* queue_state, void* app_state); + +/** + * Runs the queue logic (place it in your tick function) + * + * @param queue_state The queue state pointer + * @param app_state Your application state + * @return FALSE when there is nothing to run, TRUE otherwise + */ +bool run_queue(QueueState* queue_state, void* app_state); + +/** + * Calls the currently active queue items render callback (if there is any) + * + * @param queue_state The queue state pointer + * @param app_state Your application state + * @param canvas Pointer to Flipper's canvas object + */ +void render_queue(const QueueState* queue_state, const void* app_state, Canvas* const canvas); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/blackjack/common/ui.c b/Applications/Official/DEV_FW/source/blackjack/common/ui.c new file mode 100644 index 000000000..032877a9e --- /dev/null +++ b/Applications/Official/DEV_FW/source/blackjack/common/ui.c @@ -0,0 +1,257 @@ +#include "ui.h" +#include +#include +#include +#include +#include +#include + +TileMap* tileMap; +uint8_t tileMapCount = 0; + +void ui_cleanup() { + if(tileMap != NULL) { + for(uint8_t i = 0; i < tileMapCount; i++) { + if(tileMap[i].data != NULL) free(tileMap[i].data); + } + free(tileMap); + } +} + +void add_new_tilemap(uint8_t* data, unsigned long iconId) { + TileMap* old = tileMap; + tileMapCount++; + tileMap = malloc(sizeof(TileMap) * tileMapCount); + if(tileMapCount > 1) { + for(uint8_t i = 0; i < tileMapCount; i++) tileMap[i] = old[i]; + } + tileMap[tileMapCount - 1] = (TileMap){data, iconId}; +} + +uint8_t* get_tilemap(unsigned long icon_id) { + for(uint8_t i = 0; i < tileMapCount; i++) { + if(tileMap[i].iconId == icon_id) return tileMap[i].data; + } + + return NULL; +} + +uint32_t pixel_index(uint8_t x, uint8_t y) { + return y * SCREEN_WIDTH + x; +} + +bool in_screen(int16_t x, int16_t y) { + return x >= 0 && x < SCREEN_WIDTH && y >= 0 && y < SCREEN_HEIGHT; +} + +unsigned flipBit(uint8_t x, uint8_t bit) { + return x ^ (1 << bit); +} + +unsigned setBit(uint8_t x, uint8_t bit) { + return x | (1 << bit); +} + +unsigned unsetBit(uint8_t x, uint8_t bit) { + return x & ~(1 << bit); +} + +bool test_pixel(uint8_t* data, uint8_t x, uint8_t y, uint8_t w) { + uint8_t current_bit = (y % 8); + uint8_t current_row = ((y - current_bit) / 8); + uint8_t current_value = data[current_row * w + x]; + return current_value & (1 << current_bit); +} + +uint8_t* get_buffer(Canvas* const canvas) { + return canvas->fb.tile_buf_ptr; + // return canvas_get_buffer(canvas); +} +uint8_t* make_buffer() { + return malloc(sizeof(uint8_t) * 8 * 128); +} +void clone_buffer(uint8_t* canvas, uint8_t* data) { + for(int i = 0; i < 1024; i++) { + data[i] = canvas[i]; + } +} + +bool read_pixel(Canvas* const canvas, int16_t x, int16_t y) { + if(in_screen(x, y)) { + return test_pixel(get_buffer(canvas), x, y, SCREEN_WIDTH); + } + return false; +} + +void set_pixel(Canvas* const canvas, int16_t x, int16_t y, DrawMode draw_mode) { + if(in_screen(x, y)) { + uint8_t current_bit = (y % 8); + uint8_t current_row = ((y - current_bit) / 8); + uint32_t i = pixel_index(x, current_row); + uint8_t* buffer = get_buffer(canvas); + + uint8_t current_value = buffer[i]; + if(draw_mode == Inverse) { + buffer[i] = flipBit(current_value, current_bit); + } else { + if(draw_mode == White) { + buffer[i] = unsetBit(current_value, current_bit); + } else { + buffer[i] = setBit(current_value, current_bit); + } + } + } +} + +void draw_line( + Canvas* const canvas, + int16_t x1, + int16_t y1, + int16_t x2, + int16_t y2, + DrawMode draw_mode) { + for(int16_t x = x2; x >= x1; x--) { + for(int16_t y = y2; y >= y1; y--) { + set_pixel(canvas, x, y, draw_mode); + } + } +} + +void draw_rounded_box_frame( + Canvas* const canvas, + int16_t x, + int16_t y, + uint8_t w, + uint8_t h, + DrawMode draw_mode) { + int16_t xMinCorner = x + 1; + int16_t xMax = x + w - 1; + int16_t xMaxCorner = x + w - 2; + int16_t yMinCorner = y + 1; + int16_t yMax = y + h - 1; + int16_t yMaxCorner = y + h - 2; + draw_line(canvas, xMinCorner, y, xMaxCorner, y, draw_mode); + draw_line(canvas, xMinCorner, yMax, xMaxCorner, yMax, draw_mode); + draw_line(canvas, x, yMinCorner, x, yMaxCorner, draw_mode); + draw_line(canvas, xMax, yMinCorner, xMax, yMaxCorner, draw_mode); +} + +void draw_rounded_box( + Canvas* const canvas, + int16_t x, + int16_t y, + uint8_t w, + uint8_t h, + DrawMode draw_mode) { + for(int16_t o = w - 2; o >= 1; o--) { + for(int16_t p = h - 2; p >= 1; p--) { + set_pixel(canvas, x + o, y + p, draw_mode); + } + } + draw_rounded_box_frame(canvas, x, y, w, h, draw_mode); +} + +void invert_shape(Canvas* const canvas, uint8_t* data, int16_t x, int16_t y, uint8_t w, uint8_t h) { + draw_pixels(canvas, data, x, y, w, h, Inverse); +} + +void draw_pixels( + Canvas* const canvas, + uint8_t* data, + int16_t x, + int16_t y, + uint8_t w, + uint8_t h, + DrawMode drawMode) { + for(int8_t o = 0; o < w; o++) { + for(int8_t p = 0; p < h; p++) { + if(in_screen(o + x, p + y) && data[p * w + o] == 1) + set_pixel(canvas, o + x, p + y, drawMode); + } + } +} + +void draw_rectangle( + Canvas* const canvas, + int16_t x, + int16_t y, + uint8_t w, + uint8_t h, + DrawMode drawMode) { + for(int8_t o = 0; o < w; o++) { + for(int8_t p = 0; p < h; p++) { + if(in_screen(o + x, p + y)) { + set_pixel(canvas, o + x, p + y, drawMode); + } + } + } +} + +void invert_rectangle(Canvas* const canvas, int16_t x, int16_t y, uint8_t w, uint8_t h) { + draw_rectangle(canvas, x, y, w, h, Inverse); +} + +uint8_t* image_data(Canvas* const canvas, const Icon* icon) { + uint8_t* data = malloc(sizeof(uint8_t) * 8 * 128); + uint8_t* screen = canvas->fb.tile_buf_ptr; + canvas->fb.tile_buf_ptr = data; + canvas_draw_icon(canvas, 0, 0, icon); + canvas->fb.tile_buf_ptr = screen; + return data; +} + +uint8_t* getOrAddIconData(Canvas* const canvas, const Icon* icon) { + uint8_t* icon_data = get_tilemap((unsigned long)icon); + if(icon_data == NULL) { + icon_data = image_data(canvas, icon); + add_new_tilemap(icon_data, (unsigned long)icon); + } + return icon_data; +} + +void draw_icon_clip( + Canvas* const canvas, + const Icon* icon, + int16_t x, + int16_t y, + uint8_t left, + uint8_t top, + uint8_t w, + uint8_t h, + DrawMode drawMode) { + uint8_t* icon_data = getOrAddIconData(canvas, icon); + + for(int i = 0; i < w; i++) { + for(int j = 0; j < h; j++) { + bool on = test_pixel(icon_data, left + i, top + j, SCREEN_WIDTH); + if(drawMode == Filled) { + set_pixel(canvas, x + i, y + j, on ? Black : White); + } else if(on) + set_pixel(canvas, x + i, y + j, drawMode); + } + } +} + +void draw_icon_clip_flipped( + Canvas* const canvas, + const Icon* icon, + int16_t x, + int16_t y, + uint8_t left, + uint8_t top, + uint8_t w, + uint8_t h, + DrawMode drawMode) { + uint8_t* icon_data = getOrAddIconData(canvas, icon); + + for(int i = 0; i < w; i++) { + for(int j = 0; j < h; j++) { + bool on = test_pixel(icon_data, left + i, top + j, SCREEN_WIDTH); + + if(drawMode == Filled) { + set_pixel(canvas, x + w - i - 1, y + h - j - 1, on ? Black : White); + } else if(on) + set_pixel(canvas, x + w - i - 1, y + h - j - 1, drawMode); + } + } +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/blackjack/common/ui.h b/Applications/Official/DEV_FW/source/blackjack/common/ui.h new file mode 100644 index 000000000..13d8da257 --- /dev/null +++ b/Applications/Official/DEV_FW/source/blackjack/common/ui.h @@ -0,0 +1,105 @@ +#pragma once + +#include +#include + +#define SCREEN_WIDTH 128 +#define SCREEN_HEIGHT 64 + +typedef enum { + Black, + White, + Inverse, + Filled //Currently only for Icon clip drawing +} DrawMode; + +// size is the screen size + +typedef struct { + uint8_t* data; + unsigned long iconId; +} TileMap; + +bool test_pixel(uint8_t* data, uint8_t x, uint8_t y, uint8_t w); + +uint8_t* image_data(Canvas* const canvas, const Icon* icon); + +uint32_t pixel_index(uint8_t x, uint8_t y); + +void draw_icon_clip( + Canvas* const canvas, + const Icon* icon, + int16_t x, + int16_t y, + uint8_t left, + uint8_t top, + uint8_t w, + uint8_t h, + DrawMode drawMode); + +void draw_icon_clip_flipped( + Canvas* const canvas, + const Icon* icon, + int16_t x, + int16_t y, + uint8_t left, + uint8_t top, + uint8_t w, + uint8_t h, + DrawMode drawMode); + +void draw_rounded_box( + Canvas* const canvas, + int16_t x, + int16_t y, + uint8_t w, + uint8_t h, + DrawMode drawMode); + +void draw_rounded_box_frame( + Canvas* const canvas, + int16_t x, + int16_t y, + uint8_t w, + uint8_t h, + DrawMode drawMode); + +void draw_rectangle( + Canvas* const canvas, + int16_t x, + int16_t y, + uint8_t w, + uint8_t h, + DrawMode drawMode); + +void invert_rectangle(Canvas* const canvas, int16_t x, int16_t y, uint8_t w, uint8_t h); + +void invert_shape(Canvas* const canvas, uint8_t* data, int16_t x, int16_t y, uint8_t w, uint8_t h); + +void draw_pixels( + Canvas* const canvas, + uint8_t* data, + int16_t x, + int16_t y, + uint8_t w, + uint8_t h, + DrawMode drawMode); + +bool read_pixel(Canvas* const canvas, int16_t x, int16_t y); + +void set_pixel(Canvas* const canvas, int16_t x, int16_t y, DrawMode draw_mode); + +void draw_line( + Canvas* const canvas, + int16_t x1, + int16_t y1, + int16_t x2, + int16_t y2, + DrawMode draw_mode); + +bool in_screen(int16_t x, int16_t y); + +void ui_cleanup(); +uint8_t* get_buffer(Canvas* const canvas); +uint8_t* make_buffer(); +void clone_buffer(uint8_t* canvas, uint8_t* data); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/blackjack/defines.h b/Applications/Official/DEV_FW/source/blackjack/defines.h new file mode 100644 index 000000000..b400badfb --- /dev/null +++ b/Applications/Official/DEV_FW/source/blackjack/defines.h @@ -0,0 +1,76 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "common/card.h" +#include "common/queue.h" +#include "common/menu.h" + +#define APP_NAME "Blackjack" + +#define CONF_ANIMATION_DURATION "AnimationDuration" +#define CONF_MESSAGE_DURATION "MessageDuration" +#define CONF_STARTING_MONEY "StartingMoney" +#define CONF_ROUND_PRICE "RoundPrice" +#define CONF_SOUND_EFFECTS "SoundEffects" + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + uint32_t animation_duration; + uint32_t message_duration; + uint32_t starting_money; + uint32_t round_price; + bool sound_effects; +} Settings; + +typedef struct { + EventType type; + InputEvent input; +} AppEvent; + +typedef enum { + GameStateGameOver, + GameStateStart, + GameStatePlay, + GameStateSettings, + GameStateDealer, +} PlayState; + +typedef enum { + DirectionUp, + DirectionDown, + DirectionRight, + DirectionLeft, + Select, + Back, + None +} Direction; + +typedef struct { + Card player_cards[21]; + Card dealer_cards[21]; + uint8_t player_card_count; + uint8_t dealer_card_count; + + Direction selectDirection; + Settings settings; + + uint32_t player_score; + uint32_t bet; + uint8_t selectedMenu; + bool doubled; + bool started; + bool processing; + Deck deck; + PlayState state; + QueueState queue_state; + Menu* menu; + unsigned int last_tick; +} GameState; diff --git a/Applications/Official/DEV_FW/source/blackjack/ui.c b/Applications/Official/DEV_FW/source/blackjack/ui.c new file mode 100644 index 000000000..d4ee82191 --- /dev/null +++ b/Applications/Official/DEV_FW/source/blackjack/ui.c @@ -0,0 +1,186 @@ +#include +#include + +#include "ui.h" + +#define LINE_HEIGHT 16 +#define ITEM_PADDING 4 + +const char MoneyMul[4] = {'K', 'B', 'T', 'S'}; + +void draw_player_scene(Canvas* const canvas, const GameState* game_state) { + int max_card = game_state->player_card_count; + + if(max_card > 0) draw_deck((game_state->player_cards), max_card, canvas); + + if(game_state->dealer_card_count > 0) draw_card_back_at(13, 5, canvas); + + max_card = game_state->dealer_card_count; + if(max_card > 1) { + draw_card_at( + 2, 2, game_state->dealer_cards[1].pip, game_state->dealer_cards[1].character, canvas); + } +} + +void draw_dealer_scene(Canvas* const canvas, const GameState* game_state) { + uint8_t max_card = game_state->dealer_card_count; + draw_deck((game_state->dealer_cards), max_card, canvas); +} + +void popup_frame(Canvas* const canvas) { + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 32, 15, 66, 13); + canvas_set_color(canvas, ColorBlack); + canvas_draw_frame(canvas, 32, 15, 66, 13); + canvas_set_font(canvas, FontSecondary); +} + +void draw_play_menu(Canvas* const canvas, const GameState* game_state) { + const char* menus[3] = {"Double", "Hit", "Stay"}; + for(uint8_t m = 0; m < 3; m++) { + if(m == 0 && + (game_state->doubled || game_state->player_score < game_state->settings.round_price)) + continue; + int y = m * 13 + 25; + canvas_set_color(canvas, ColorBlack); + + if(game_state->selectedMenu == m) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_box(canvas, 1, y, 31, 12); + } else { + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 1, y, 31, 12); + canvas_set_color(canvas, ColorBlack); + canvas_draw_frame(canvas, 1, y, 31, 12); + } + + if(game_state->selectedMenu == m) + canvas_set_color(canvas, ColorWhite); + else + canvas_set_color(canvas, ColorBlack); + canvas_draw_str_aligned(canvas, 16, y + 6, AlignCenter, AlignCenter, menus[m]); + } +} + +void draw_screen(Canvas* const canvas, const bool* points) { + for(uint8_t x = 0; x < 128; x++) { + for(uint8_t y = 0; y < 64; y++) { + if(points[y * 128 + x]) canvas_draw_dot(canvas, x, y); + } + } +} + +void draw_score(Canvas* const canvas, bool top, uint8_t amount) { + char drawChar[20]; + snprintf(drawChar, sizeof(drawChar), "Player score: %i", amount); + if(top) + canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, drawChar); + else + canvas_draw_str_aligned(canvas, 64, 62, AlignCenter, AlignBottom, drawChar); +} + +void draw_money(Canvas* const canvas, uint32_t score) { + canvas_set_font(canvas, FontSecondary); + char drawChar[11]; + uint32_t currAmount = score; + if(currAmount < 1000) { + snprintf(drawChar, sizeof(drawChar), "$%lu", currAmount); + } else { + char c = 'K'; + for(uint8_t i = 0; i < 4; i++) { + currAmount = currAmount / 1000; + if(currAmount < 1000) { + c = MoneyMul[i]; + break; + } + } + + snprintf(drawChar, sizeof(drawChar), "$%lu %c", currAmount, c); + } + canvas_draw_str_aligned(canvas, 126, 2, AlignRight, AlignTop, drawChar); +} + +void draw_menu( + Canvas* const canvas, + const char* text, + const char* value, + int8_t y, + bool left_caret, + bool right_caret, + bool selected) { + UNUSED(selected); + if(y < 0 || y >= 64) return; + + if(selected) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_box(canvas, 0, y, 122, LINE_HEIGHT); + canvas_set_color(canvas, ColorWhite); + } + + canvas_draw_str_aligned(canvas, 4, y + ITEM_PADDING, AlignLeft, AlignTop, text); + if(left_caret) canvas_draw_str_aligned(canvas, 80, y + ITEM_PADDING, AlignLeft, AlignTop, "<"); + canvas_draw_str_aligned(canvas, 100, y + ITEM_PADDING, AlignCenter, AlignTop, value); + if(right_caret) + canvas_draw_str_aligned(canvas, 120, y + ITEM_PADDING, AlignRight, AlignTop, ">"); + + canvas_set_color(canvas, ColorBlack); +} + +void settings_page(Canvas* const canvas, const GameState* gameState) { + char drawChar[10]; + int startY = 0; + if(LINE_HEIGHT * (gameState->selectedMenu + 1) >= 64) { + startY -= (LINE_HEIGHT * (gameState->selectedMenu + 1)) - 64; + } + + int scrollHeight = round(64 / 6.0) + ITEM_PADDING * 2; + int scrollPos = 64 / (6.0 / (gameState->selectedMenu + 1)) - ITEM_PADDING * 2; + + canvas_set_color(canvas, ColorBlack); + canvas_draw_box(canvas, 123, scrollPos, 4, scrollHeight); + canvas_draw_box(canvas, 125, 0, 1, 64); + + snprintf(drawChar, sizeof(drawChar), "%li", gameState->settings.starting_money); + draw_menu( + canvas, + "Start money", + drawChar, + 0 * LINE_HEIGHT + startY, + gameState->settings.starting_money > gameState->settings.round_price, + gameState->settings.starting_money < 400, + gameState->selectedMenu == 0); + snprintf(drawChar, sizeof(drawChar), "%li", gameState->settings.round_price); + draw_menu( + canvas, + "Round price", + drawChar, + 1 * LINE_HEIGHT + startY, + gameState->settings.round_price > 10, + gameState->settings.round_price < gameState->settings.starting_money, + gameState->selectedMenu == 1); + + snprintf(drawChar, sizeof(drawChar), "%li", gameState->settings.animation_duration); + draw_menu( + canvas, + "Anim. length", + drawChar, + 2 * LINE_HEIGHT + startY, + gameState->settings.animation_duration > 0, + gameState->settings.animation_duration < 2000, + gameState->selectedMenu == 2); + snprintf(drawChar, sizeof(drawChar), "%li", gameState->settings.message_duration); + draw_menu( + canvas, + "Popup time", + drawChar, + 3 * LINE_HEIGHT + startY, + gameState->settings.message_duration > 0, + gameState->settings.message_duration < 2000, + gameState->selectedMenu == 3); + // draw_menu(canvas, "Sound", gameState->settings.sound_effects ? "Yes" : "No", + // 5 * LINE_HEIGHT + startY, + // true, + // true, + // gameState->selectedMenu == 5 + // ); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/blackjack/ui.h b/Applications/Official/DEV_FW/source/blackjack/ui.h new file mode 100644 index 000000000..51b388010 --- /dev/null +++ b/Applications/Official/DEV_FW/source/blackjack/ui.h @@ -0,0 +1,18 @@ +#pragma once + +#include "defines.h" +#include + +void draw_player_scene(Canvas* const canvas, const GameState* game_state); + +void draw_dealer_scene(Canvas* const canvas, const GameState* game_state); + +void draw_play_menu(Canvas* const canvas, const GameState* game_state); + +void draw_score(Canvas* const canvas, bool top, uint8_t amount); + +void draw_money(Canvas* const canvas, uint32_t score); +void settings_page(Canvas* const canvas, const GameState* gameState); + +void popup_frame(Canvas* const canvas); +void draw_screen(Canvas* const canvas, const bool* points); diff --git a/Applications/Official/DEV_FW/source/blackjack/util.c b/Applications/Official/DEV_FW/source/blackjack/util.c new file mode 100644 index 000000000..8e88c2231 --- /dev/null +++ b/Applications/Official/DEV_FW/source/blackjack/util.c @@ -0,0 +1,123 @@ +#include +#include "util.h" + +const char* CONFIG_FILE_PATH = EXT_PATH(".blackjack.settings"); + +void save_settings(Settings settings) { + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* file = flipper_format_file_alloc(storage); + FURI_LOG_D(APP_NAME, "Saving config"); + if(flipper_format_file_open_existing(file, CONFIG_FILE_PATH)) { + FURI_LOG_D( + APP_NAME, "Saving %s: %ld", CONF_ANIMATION_DURATION, settings.animation_duration); + flipper_format_update_uint32( + file, CONF_ANIMATION_DURATION, &(settings.animation_duration), 1); + + FURI_LOG_D(APP_NAME, "Saving %s: %ld", CONF_MESSAGE_DURATION, settings.message_duration); + flipper_format_update_uint32(file, CONF_MESSAGE_DURATION, &(settings.message_duration), 1); + + FURI_LOG_D(APP_NAME, "Saving %s: %ld", CONF_STARTING_MONEY, settings.starting_money); + flipper_format_update_uint32(file, CONF_STARTING_MONEY, &(settings.starting_money), 1); + + FURI_LOG_D(APP_NAME, "Saving %s: %ld", CONF_ROUND_PRICE, settings.round_price); + flipper_format_update_uint32(file, CONF_ROUND_PRICE, &(settings.round_price), 1); + + FURI_LOG_D(APP_NAME, "Saving %s: %i", CONF_SOUND_EFFECTS, settings.sound_effects ? 1 : 0); + flipper_format_update_bool(file, CONF_SOUND_EFFECTS, &(settings.sound_effects), 1); + FURI_LOG_D(APP_NAME, "Config saved"); + } else { + FURI_LOG_E(APP_NAME, "Save error"); + } + flipper_format_file_close(file); + flipper_format_free(file); + furi_record_close(RECORD_STORAGE); +} + +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, "Card animation duration in ms"); + flipper_format_write_uint32(file, CONF_ANIMATION_DURATION, &(settings->animation_duration), 1); + flipper_format_write_comment_cstr(file, "Popup message duration in ms"); + flipper_format_write_uint32(file, CONF_MESSAGE_DURATION, &(settings->message_duration), 1); + flipper_format_write_comment_cstr(file, "Player's starting money"); + flipper_format_write_uint32(file, CONF_STARTING_MONEY, &(settings->starting_money), 1); + flipper_format_write_comment_cstr(file, "Round price"); + flipper_format_write_uint32(file, CONF_ROUND_PRICE, &(settings->round_price), 1); + flipper_format_write_comment_cstr(file, "Enable sound effects"); + flipper_format_write_bool(file, CONF_SOUND_EFFECTS, &(settings->sound_effects), 1); +} + +Settings load_settings() { + Settings settings; + + FURI_LOG_D(APP_NAME, "Loading default settings"); + settings.animation_duration = 800; + settings.message_duration = 1500; + settings.starting_money = 200; + settings.round_price = 10; + settings.sound_effects = true; + + FURI_LOG_D(APP_NAME, "Opening storage"); + Storage* storage = furi_record_open(RECORD_STORAGE); + FURI_LOG_D(APP_NAME, "Allocating file"); + FlipperFormat* file = flipper_format_file_alloc(storage); + + FURI_LOG_D(APP_NAME, "Allocating string"); + FuriString* string_value; + string_value = furi_string_alloc(); + + if(storage_common_stat(storage, CONFIG_FILE_PATH, NULL) != FSE_OK) { + FURI_LOG_D(APP_NAME, "Config file %s not found, creating new one...", CONFIG_FILE_PATH); + if(!flipper_format_file_open_new(file, CONFIG_FILE_PATH)) { + FURI_LOG_E(APP_NAME, "Error creating new file %s", CONFIG_FILE_PATH); + flipper_format_file_close(file); + } else { + save_settings_file(file, &settings); + } + } else { + if(!flipper_format_file_open_existing(file, CONFIG_FILE_PATH)) { + FURI_LOG_E(APP_NAME, "Error opening existing file %s", CONFIG_FILE_PATH); + flipper_format_file_close(file); + } else { + uint32_t value; + bool valueBool; + FURI_LOG_D(APP_NAME, "Checking version"); + if(!flipper_format_read_header(file, string_value, &value)) { + FURI_LOG_E(APP_NAME, "Config file mismatch"); + } else { + FURI_LOG_D(APP_NAME, "Loading %s", CONF_ANIMATION_DURATION); + if(flipper_format_read_uint32(file, CONF_ANIMATION_DURATION, &value, 1)) { + settings.animation_duration = value; + FURI_LOG_D(APP_NAME, "Loaded %s: %ld", CONF_ANIMATION_DURATION, value); + } + FURI_LOG_D(APP_NAME, "Loading %s", CONF_MESSAGE_DURATION); + if(flipper_format_read_uint32(file, CONF_MESSAGE_DURATION, &value, 1)) { + settings.message_duration = value; + FURI_LOG_D(APP_NAME, "Loaded %s: %ld", CONF_MESSAGE_DURATION, value); + } + FURI_LOG_D(APP_NAME, "Loading %s", CONF_STARTING_MONEY); + if(flipper_format_read_uint32(file, CONF_STARTING_MONEY, &value, 1)) { + settings.starting_money = value; + FURI_LOG_D(APP_NAME, "Loaded %s: %ld", CONF_STARTING_MONEY, value); + } + FURI_LOG_D(APP_NAME, "Loading %s", CONF_ROUND_PRICE); + if(flipper_format_read_uint32(file, CONF_ROUND_PRICE, &value, 1)) { + settings.round_price = value; + FURI_LOG_D(APP_NAME, "Loaded %s: %ld", CONF_ROUND_PRICE, value); + } + FURI_LOG_D(APP_NAME, "Loading %s", CONF_SOUND_EFFECTS); + if(flipper_format_read_bool(file, CONF_SOUND_EFFECTS, &valueBool, 1)) { + settings.sound_effects = valueBool; + FURI_LOG_D(APP_NAME, "Loaded %s: %i", CONF_ROUND_PRICE, valueBool ? 1 : 0); + } + } + flipper_format_file_close(file); + } + } + + furi_string_free(string_value); + // flipper_format_file_close(file); + flipper_format_free(file); + furi_record_close(RECORD_STORAGE); + return settings; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/blackjack/util.h b/Applications/Official/DEV_FW/source/blackjack/util.h new file mode 100644 index 000000000..4bcc4d890 --- /dev/null +++ b/Applications/Official/DEV_FW/source/blackjack/util.h @@ -0,0 +1,7 @@ +#pragma once +#include "defines.h" +#define CONFIG_FILE_HEADER "Blackjack config file" +#define CONFIG_FILE_VERSION 1 + +void save_settings(Settings settings); +Settings load_settings(); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/bpmtapper/LICENSE b/Applications/Official/DEV_FW/source/bpmtapper/LICENSE new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/bpmtapper/README.md b/Applications/Official/DEV_FW/source/bpmtapper/README.md new file mode 100644 index 000000000..224d2cb5b --- /dev/null +++ b/Applications/Official/DEV_FW/source/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 fap_bpm_tapper +``` + diff --git a/Applications/Official/DEV_FW/source/bpmtapper/application.fam b/Applications/Official/DEV_FW/source/bpmtapper/application.fam new file mode 100644 index 000000000..b6ad315b6 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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_icon_assets="icons", + fap_category="Music", + order=35, +) diff --git a/Applications/Official/DEV_FW/source/bpmtapper/bpm.c b/Applications/Official/DEV_FW/source/bpmtapper/bpm.c new file mode 100644 index 000000000..323a898a4 --- /dev/null +++ b/Applications/Official/DEV_FW/source/bpmtapper/bpm.c @@ -0,0 +1,261 @@ +#include +#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) { + string_t 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); + + string_init(tempStr); + + string_printf(tempStr, "Taps: %d", bpm_state->taps); + canvas_draw_str_aligned(canvas, 5, 10, AlignLeft, AlignBottom, string_get_cstr(tempStr)); + string_reset(tempStr); + + string_printf(tempStr, "Queue: %d", bpm_state->tap_queue->size); + canvas_draw_str_aligned(canvas, 70, 10, AlignLeft, AlignBottom, string_get_cstr(tempStr)); + string_reset(tempStr); + + string_printf(tempStr, "Interval: %dms", bpm_state->interval); + canvas_draw_str_aligned(canvas, 5, 20, AlignLeft, AlignBottom, string_get_cstr(tempStr)); + string_reset(tempStr); + + string_printf(tempStr, "x2 %.2f /2 %.2f", bpm_state->bpm * 2, bpm_state->bpm / 2); + canvas_draw_str_aligned(canvas, 64, 60, AlignCenter, AlignCenter, string_get_cstr(tempStr)); + string_reset(tempStr); + + string_printf(tempStr, "%.2f", bpm_state->bpm); + canvas_set_font(canvas, FontBigNumbers); + canvas_draw_str_aligned(canvas, 64, 40, AlignCenter, AlignCenter, string_get_cstr(tempStr)); + string_reset(tempStr); + + string_clear(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/Official/DEV_FW/source/bpmtapper/bpm_10px.png b/Applications/Official/DEV_FW/source/bpmtapper/bpm_10px.png new file mode 100644 index 000000000..ebf27486c Binary files /dev/null and b/Applications/Official/DEV_FW/source/bpmtapper/bpm_10px.png differ diff --git a/Applications/Official/DEV_FW/source/bpmtapper/icons/DolphinCommon_56x48.png b/Applications/Official/DEV_FW/source/bpmtapper/icons/DolphinCommon_56x48.png new file mode 100644 index 000000000..089aaed83 Binary files /dev/null and b/Applications/Official/DEV_FW/source/bpmtapper/icons/DolphinCommon_56x48.png differ diff --git a/Applications/Official/DEV_FW/source/bpmtapper/img/screenshot.png b/Applications/Official/DEV_FW/source/bpmtapper/img/screenshot.png new file mode 100644 index 000000000..fbba2aad9 Binary files /dev/null and b/Applications/Official/DEV_FW/source/bpmtapper/img/screenshot.png differ diff --git a/Applications/Official/DEV_FW/source/caesarcipher/LICENSE b/Applications/Official/DEV_FW/source/caesarcipher/LICENSE new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/Applications/Official/DEV_FW/source/caesarcipher/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/Official/DEV_FW/source/caesarcipher/README.md b/Applications/Official/DEV_FW/source/caesarcipher/README.md new file mode 100644 index 000000000..0d434f9de --- /dev/null +++ b/Applications/Official/DEV_FW/source/caesarcipher/README.md @@ -0,0 +1,17 @@ +# Caesar Cipher + +A [caesar cipher](https://en.wikipedia.org/wiki/Caesar_cipher) encoder for the Flipper Zero device. + +![input](img/1.png) +![output](img/2.png) + +## Usage + +Start app, painfully input your ciphertext with the onscreen keyboard. Replace spaces with underscores. Hit "Save", scroll output. + +## Compiling + +``` +./fbt firmware_caesar_cipher +``` + diff --git a/Applications/Official/DEV_FW/source/caesarcipher/application.fam b/Applications/Official/DEV_FW/source/caesarcipher/application.fam new file mode 100644 index 000000000..4f438d2b3 --- /dev/null +++ b/Applications/Official/DEV_FW/source/caesarcipher/application.fam @@ -0,0 +1,14 @@ +App( + appid="Caesar_Cipher", + name="Caesar Cipher", + apptype=FlipperAppType.EXTERNAL, + entry_point="caesar_cipher_app", + cdefines=["APP_CAESAR_CIPHER"], + requires=[ + "gui", + ], + stack_size=2 * 1024, + fap_icon="caesar_cipher_icon.png", + fap_category="Misc", + order=20, +) diff --git a/Applications/Official/DEV_FW/source/caesarcipher/caesar_cipher.c b/Applications/Official/DEV_FW/source/caesarcipher/caesar_cipher.c new file mode 100644 index 000000000..9eb93e925 --- /dev/null +++ b/Applications/Official/DEV_FW/source/caesarcipher/caesar_cipher.c @@ -0,0 +1,147 @@ +#include +#include +#include + +#include +#include +#include +#include +#include + +#define TEXT_BUFFER_SIZE 256 + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} PluginEvent; + +typedef struct { + ViewDispatcher* view_dispatcher; + TextInput* text_input; + TextBox* text_box; + char input[TEXT_BUFFER_SIZE]; + char output[(TEXT_BUFFER_SIZE * 26) + (26)]; // linebreaks +} CaesarState; + +static void string_to_uppercase(char* input) { + int i; + for(i = 0; input[i] != '\0'; i++) { + if(input[i] >= 'a' && input[i] <= 'z') { + input[i] = input[i] - 32; + } else { + input[i] = input[i]; + } + } +} + +static void build_output(char* input, char* output) { + int out = 0; + for(int rot = 1; rot < 26; rot++) { + int in; + for(in = 0; input[in] != '\0'; in++) { + if(input[in] >= 'A' && input[in] <= 'Z') { + output[out] = 65 + (((input[in] - 65) + rot) % 26); + } else { + output[out] = input[in]; + } + out++; + } + output[out] = '\n'; + out++; + } + output[out] = '\0'; +} + +static void text_input_callback(void* ctx) { + CaesarState* caesar_state = acquire_mutex((ValueMutex*)ctx, 25); + FURI_LOG_D("caesar_cipher", "Input text: %s", caesar_state->input); + // this is where we build the output. + string_to_uppercase(caesar_state->input); + FURI_LOG_D("caesar_cipher", "Upper text: %s", caesar_state->input); + build_output(caesar_state->input, caesar_state->output); + text_box_set_text(caesar_state->text_box, caesar_state->output); + view_dispatcher_switch_to_view(caesar_state->view_dispatcher, 1); + + release_mutex((ValueMutex*)ctx, caesar_state); +} + +static bool back_event_callback(void* ctx) { + const CaesarState* caesar_state = acquire_mutex((ValueMutex*)ctx, 25); + view_dispatcher_stop(caesar_state->view_dispatcher); + release_mutex((ValueMutex*)ctx, caesar_state); + return true; +} + +static void caesar_cipher_state_init(CaesarState* const caesar_state) { + caesar_state->view_dispatcher = view_dispatcher_alloc(); + caesar_state->text_input = text_input_alloc(); + caesar_state->text_box = text_box_alloc(); + text_box_set_font(caesar_state->text_box, TextBoxFontText); +} + +static void caesar_cipher_state_free(CaesarState* const caesar_state) { + text_input_free(caesar_state->text_input); + text_box_free(caesar_state->text_box); + view_dispatcher_remove_view(caesar_state->view_dispatcher, 0); + view_dispatcher_remove_view(caesar_state->view_dispatcher, 1); + view_dispatcher_free(caesar_state->view_dispatcher); + free(caesar_state); +} + +int32_t caesar_cipher_app() { + CaesarState* caesar_state = malloc(sizeof(CaesarState)); + + FURI_LOG_D("caesar_cipher", "Running caesar_cipher_state_init"); + caesar_cipher_state_init(caesar_state); + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, caesar_state, sizeof(CaesarState))) { + FURI_LOG_E("caesar_cipher", "cannot create mutex\r\n"); + free(caesar_state); + return 255; + } + + FURI_LOG_D("caesar_cipher", "Assigning text input callback"); + text_input_set_result_callback( + caesar_state->text_input, + text_input_callback, + &state_mutex, + caesar_state->input, + TEXT_BUFFER_SIZE, + //clear default text + true); + text_input_set_header_text(caesar_state->text_input, "Input"); + + // Open GUI and register view_port + Gui* gui = furi_record_open("gui"); + //gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + FURI_LOG_D("caesar_cipher", "Enabling view dispatcher queue"); + view_dispatcher_enable_queue(caesar_state->view_dispatcher); + + FURI_LOG_D("caesar_cipher", "Adding text input view to dispatcher"); + view_dispatcher_add_view( + caesar_state->view_dispatcher, 0, text_input_get_view(caesar_state->text_input)); + view_dispatcher_add_view( + caesar_state->view_dispatcher, 1, text_box_get_view(caesar_state->text_box)); + FURI_LOG_D("caesar_cipher", "Attaching view dispatcher to GUI"); + view_dispatcher_attach_to_gui( + caesar_state->view_dispatcher, gui, ViewDispatcherTypeFullscreen); + FURI_LOG_D("ceasar_cipher", "starting view dispatcher"); + view_dispatcher_set_navigation_event_callback( + caesar_state->view_dispatcher, back_event_callback); + view_dispatcher_set_event_callback_context(caesar_state->view_dispatcher, &state_mutex); + view_dispatcher_switch_to_view(caesar_state->view_dispatcher, 0); + view_dispatcher_run(caesar_state->view_dispatcher); + + furi_record_close("gui"); + delete_mutex(&state_mutex); + caesar_cipher_state_free(caesar_state); + + return 0; +} diff --git a/Applications/Official/DEV_FW/source/caesarcipher/caesar_cipher_icon.png b/Applications/Official/DEV_FW/source/caesarcipher/caesar_cipher_icon.png new file mode 100644 index 000000000..13077e892 Binary files /dev/null and b/Applications/Official/DEV_FW/source/caesarcipher/caesar_cipher_icon.png differ diff --git a/Applications/Official/DEV_FW/source/caesarcipher/img/1.png b/Applications/Official/DEV_FW/source/caesarcipher/img/1.png new file mode 100644 index 000000000..93a9bcdbe Binary files /dev/null and b/Applications/Official/DEV_FW/source/caesarcipher/img/1.png differ diff --git a/Applications/Official/DEV_FW/source/caesarcipher/img/2.png b/Applications/Official/DEV_FW/source/caesarcipher/img/2.png new file mode 100644 index 000000000..99d1bcc63 Binary files /dev/null and b/Applications/Official/DEV_FW/source/caesarcipher/img/2.png differ diff --git a/Applications/Official/DEV_FW/source/calculator/application.fam b/Applications/Official/DEV_FW/source/calculator/application.fam new file mode 100644 index 000000000..ac78af7a5 --- /dev/null +++ b/Applications/Official/DEV_FW/source/calculator/application.fam @@ -0,0 +1,12 @@ +App( + appid="Calculator", + name="Calculator", + apptype=FlipperAppType.EXTERNAL, + entry_point="calculator_app", + cdefines=["APP_CALCULATOR"], + requires=["gui"], + stack_size=1 * 1024, + order=45, + fap_icon="calcIcon.png", + fap_category="Misc", +) diff --git a/Applications/Official/DEV_FW/source/calculator/calc.png b/Applications/Official/DEV_FW/source/calculator/calc.png new file mode 100644 index 000000000..838d7b964 Binary files /dev/null and b/Applications/Official/DEV_FW/source/calculator/calc.png differ diff --git a/Applications/Official/DEV_FW/source/calculator/calcIcon.png b/Applications/Official/DEV_FW/source/calculator/calcIcon.png new file mode 100644 index 000000000..05cda6ce6 Binary files /dev/null and b/Applications/Official/DEV_FW/source/calculator/calcIcon.png differ diff --git a/Applications/Official/DEV_FW/source/calculator/calculator.c b/Applications/Official/DEV_FW/source/calculator/calculator.c new file mode 100644 index 000000000..b121641b0 --- /dev/null +++ b/Applications/Official/DEV_FW/source/calculator/calculator.c @@ -0,0 +1,453 @@ +#include +#include +#include +#include +#include +#include +#include // Header-file for boolean data-type. +#include // Header-file for string functions. +#include "tinyexpr.h" // Header-file for the TinyExpr library. + +#include +#include + +const short MAX_TEXT_LENGTH = 20; + +typedef struct { + short x; + short y; +} selectedPosition; + +typedef struct { + selectedPosition position; + //string with the inputted calculator text + char text[20]; + short textLength; + char log[20]; +} Calculator; + +char getKeyAtPosition(short x, short y) { + if(x == 0 && y == 0) { + return 'C'; + } + if(x == 1 && y == 0) { + return '<'; + } + if(x == 2 && y == 0) { + return '%'; + } + if(x == 3 && y == 0) { + return '/'; + } + if(x == 0 && y == 1) { + return '1'; + } + if(x == 1 && y == 1) { + return '2'; + } + if(x == 2 && y == 1) { + return '3'; + } + if(x == 3 && y == 1) { + return '*'; + } + if(x == 0 && y == 2) { + return '4'; + } + if(x == 1 && y == 2) { + return '5'; + } + if(x == 2 && y == 2) { + return '6'; + } + if(x == 3 && y == 2) { + return '-'; + } + if(x == 0 && y == 3) { + return '7'; + } + if(x == 1 && y == 3) { + return '8'; + } + if(x == 2 && y == 3) { + return '9'; + } + if(x == 3 && y == 3) { + return '+'; + } + if(x == 0 && y == 4) { + return '('; + } + if(x == 1 && y == 4) { + return '0'; + } + if(x == 2 && y == 4) { + return '.'; + } + if(x == 3 && y == 4) { + return '='; + } + return ' '; +} + +short calculateStringWidth(const char* str, short lenght) { + /* widths: + 1 = 2 + 2, 3, 4, 5, 6, 7, 8, 9, 0, X, -, +, . = = 5 + %, / = 7 + S = 5 + (, ) = 3 + + */ + short width = 0; + for(short i = 0; i < lenght; i++) { + switch(str[i]) { + case '1': + width += 2; + break; + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '0': + case '*': + case '-': + case '+': + case '.': + width += 5; + break; + case '%': + case '/': + width += 7; + break; + case 'S': + width += 5; + break; + case '(': + case ')': + width += 3; + break; + default: + break; + } + width += 1; + } + + return width; +} + +void generate_calculator_layout(Canvas* canvas) { + //draw dotted lines + for(int i = 0; i <= 64; i++) { + if(i % 2 == 0) { + canvas_draw_dot(canvas, i, 14); + canvas_draw_dot(canvas, i, 33); + } + if(i % 2 == 1) { + canvas_draw_dot(canvas, i, 15); + canvas_draw_dot(canvas, i, 34); + } + } + + //draw horizontal lines + canvas_draw_box(canvas, 0, 41, 64, 2); + canvas_draw_box(canvas, 0, 57, 64, 2); + canvas_draw_box(canvas, 0, 73, 64, 2); + canvas_draw_box(canvas, 0, 89, 64, 2); + canvas_draw_box(canvas, 0, 105, 64, 2); + canvas_draw_box(canvas, 0, 121, 64, 2); + + //draw vertical lines + canvas_draw_box(canvas, 0, 43, 1, 80); + canvas_draw_box(canvas, 15, 43, 2, 80); + canvas_draw_box(canvas, 31, 43, 2, 80); + canvas_draw_box(canvas, 47, 43, 2, 80); + canvas_draw_box(canvas, 63, 43, 1, 80); + + //draw buttons + //row 1 (C, ;, %, ÷) + canvas_draw_str(canvas, 5, 54, "C"); + canvas_draw_str(canvas, 19, 54, " <-"); + canvas_draw_str(canvas, 35, 54, " %"); + canvas_draw_str(canvas, 51, 54, " /"); + + //row 2 (1, 2, 3, X) + canvas_draw_str(canvas, 5, 70, " 1"); + canvas_draw_str(canvas, 19, 70, " 2"); + canvas_draw_str(canvas, 35, 70, " 3"); + canvas_draw_str(canvas, 51, 70, " X"); + + //row 3 (4, 5, 6, -) + canvas_draw_str(canvas, 5, 86, " 4"); + canvas_draw_str(canvas, 19, 86, " 5"); + canvas_draw_str(canvas, 35, 86, " 6"); + canvas_draw_str(canvas, 51, 86, " -"); + + //row 4 (7, 8, 9, +) + canvas_draw_str(canvas, 5, 102, " 7"); + canvas_draw_str(canvas, 19, 102, " 8"); + canvas_draw_str(canvas, 35, 102, " 9"); + canvas_draw_str(canvas, 51, 102, " +"); + + //row 5 (+/-, 0, ., =) + canvas_draw_str(canvas, 3, 118, "( )"); + canvas_draw_str(canvas, 19, 118, " 0"); + canvas_draw_str(canvas, 35, 118, " ."); + canvas_draw_str(canvas, 51, 118, " ="); +}; + +void calculator_draw_callback(Canvas* canvas, void* ctx) { + const Calculator* calculator_state = acquire_mutex((ValueMutex*)ctx, 25); + UNUSED(ctx); + canvas_clear(canvas); + + //show selected button + short startX = 1; + short startY = 43; + + canvas_set_color(canvas, ColorBlack); + canvas_draw_box( + canvas, + startX + (calculator_state->position.x) * 16, + (startY) + (calculator_state->position.y) * 16, + 16, + 16); + canvas_set_color(canvas, ColorWhite); + canvas_draw_box( + canvas, + startX + (calculator_state->position.x) * 16 + 2, + (startY) + (calculator_state->position.y) * 16 + 2, + 10, + 10); + + canvas_set_color(canvas, ColorBlack); + generate_calculator_layout(canvas); + + //draw text + short stringWidth = calculateStringWidth(calculator_state->text, calculator_state->textLength); + short startingPosition = 5; + if(stringWidth > 60) { + startingPosition += 60 - (stringWidth + 5); + } + canvas_set_color(canvas, ColorBlack); + canvas_draw_str(canvas, startingPosition, 28, calculator_state->text); + //canvas_draw_str(canvas, 10, 10, calculator_state->log); + + //draw cursor + canvas_draw_box(canvas, stringWidth + 5, 29, 5, 1); + + release_mutex((ValueMutex*)ctx, calculator_state); +} + +void calculator_input_callback(InputEvent* input_event, void* ctx) { + furi_assert(ctx); + FuriMessageQueue* event_queue = ctx; + furi_message_queue_put(event_queue, input_event, FuriWaitForever); +} + +void calculate(Calculator* calculator_state) { + double result; + result = te_interp(calculator_state->text, 0); + + calculator_state->textLength = 0; + calculator_state->text[0] = '\0'; + // sprintf(calculator_state->text, "%f", result); + + //invert sign if negative + if(result < 0) { + calculator_state->text[calculator_state->textLength++] = '-'; + result = -result; + } + + //get numbers before and after decimal + int beforeDecimal = result; + int afterDecimal = (result - beforeDecimal) * 100; + + char beforeDecimalString[10]; + char afterDecimalString[10]; + int i = 0; + //parse to a string + while(beforeDecimal > 0) { + beforeDecimalString[i++] = beforeDecimal % 10 + '0'; + beforeDecimal /= 10; + } + // invert string + for(int j = 0; j < i / 2; j++) { + char temp = beforeDecimalString[j]; + beforeDecimalString[j] = beforeDecimalString[i - j - 1]; + beforeDecimalString[i - j - 1] = temp; + } + //add it to the answer + for(int j = 0; j < i; j++) { + calculator_state->text[calculator_state->textLength++] = beforeDecimalString[j]; + } + + i = 0; + if(afterDecimal > 0) { + while(afterDecimal > 0) { + afterDecimalString[i++] = afterDecimal % 10 + '0'; + afterDecimal /= 10; + } + // invert string + for(int j = 0; j < i / 2; j++) { + char temp = afterDecimalString[j]; + afterDecimalString[j] = afterDecimalString[i - j - 1]; + afterDecimalString[i - j - 1] = temp; + } + + //add decimal point + calculator_state->text[calculator_state->textLength++] = '.'; + + //add numbers after decimal + for(int j = 0; j < i; j++) { + calculator_state->text[calculator_state->textLength++] = afterDecimalString[j]; + } + } + calculator_state->text[calculator_state->textLength] = '\0'; +} + +int32_t calculator_app(void* p) { + UNUSED(p); + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + + Calculator* calculator_state = malloc(sizeof(Calculator)); + ValueMutex calculator_state_mutex; + if(!init_mutex(&calculator_state_mutex, calculator_state, sizeof(Calculator))) { + //FURI_LOG_E("calculator", "cannot create mutex\r\n"); + free(calculator_state); + return -1; + } + + // Configure view port + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, calculator_draw_callback, &calculator_state_mutex); + view_port_input_callback_set(view_port, calculator_input_callback, event_queue); + view_port_set_orientation(view_port, ViewPortOrientationVertical); + + // Register view port in GUI + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + //NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + + InputEvent event; + + while(furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk) { + //break out of the loop if the back key is pressed + if(event.type == InputTypeShort && event.key == InputKeyBack) { + break; + } + + if(event.type == InputTypeShort) { + switch(event.key) { + case InputKeyUp: + if(calculator_state->position.y > 0) { + calculator_state->position.y--; + } + break; + case InputKeyDown: + if(calculator_state->position.y < 4) { + calculator_state->position.y++; + } + break; + case InputKeyLeft: + if(calculator_state->position.x > 0) { + calculator_state->position.x--; + } + break; + case InputKeyRight: + if(calculator_state->position.x < 3) { + calculator_state->position.x++; + } + break; + case InputKeyOk: { + //add the selected button to the text + //char* text = calculator_state->text; + // short* textLength = &calculator_state->textLength; + + char key = + getKeyAtPosition(calculator_state->position.x, calculator_state->position.y); + + switch(key) { + case 'C': + while(calculator_state->textLength > 0) { + calculator_state->text[calculator_state->textLength--] = '\0'; + } + calculator_state->text[0] = '\0'; + calculator_state->log[2] = key; + break; + case '<': + calculator_state->log[2] = key; + if(calculator_state->textLength > 0) { + calculator_state->text[--calculator_state->textLength] = '\0'; + } else { + calculator_state->text[0] = '\0'; + } + break; + case '=': + calculator_state->log[2] = key; + calculate(calculator_state); + break; + case '%': + case '/': + case '*': + case '-': + case '+': + case '.': + case '(': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '0': + if(calculator_state->textLength < MAX_TEXT_LENGTH) { + calculator_state->text[calculator_state->textLength++] = key; + calculator_state->text[calculator_state->textLength] = '\0'; + } + //calculator_state->log[1] = calculator_state->text[*textLength]; + break; + default: + break; + } + } + default: + break; + } + + view_port_update(view_port); + } + + if(event.type == InputTypeLong) { + switch(event.key) { + case InputKeyOk: + if(calculator_state->position.x == 0 && calculator_state->position.y == 4) { + if(calculator_state->textLength < MAX_TEXT_LENGTH) { + calculator_state->text[calculator_state->textLength++] = ')'; + calculator_state->text[calculator_state->textLength] = '\0'; + } + view_port_update(view_port); + } + break; + default: + break; + } + } + } + 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/Official/DEV_FW/source/calculator/tinyexpr.c b/Applications/Official/DEV_FW/source/calculator/tinyexpr.c new file mode 100644 index 000000000..0c0e79f45 --- /dev/null +++ b/Applications/Official/DEV_FW/source/calculator/tinyexpr.c @@ -0,0 +1,785 @@ +// SPDX-License-Identifier: Zlib +/* + * TINYEXPR - Tiny recursive descent parser and evaluation engine in C + * + * Copyright (c) 2015-2020 Lewis Van Winkle + * + * http://CodePlea.com + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgement in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* COMPILE TIME OPTIONS */ + +/* Exponentiation associativity: +For a^b^c = (a^b)^c and -a^b = (-a)^b do nothing. +For a^b^c = a^(b^c) and -a^b = -(a^b) uncomment the next line.*/ +/* #define TE_POW_FROM_RIGHT */ + +/* Logarithms +For log = base 10 log do nothing +For log = natural log uncomment the next line. */ +/* #define TE_NAT_LOG */ + +#include "tinyexpr.h" +#include +#include +#include +#include +#include +#include + +#ifndef NAN +#define NAN (0.0 / 0.0) +#endif + +#ifndef INFINITY +#define INFINITY (1.0 / 0.0) +#endif + +typedef double (*te_fun2)(double, double); + +enum { + TOK_NULL = TE_CLOSURE7 + 1, + TOK_ERROR, + TOK_END, + TOK_SEP, + TOK_OPEN, + TOK_CLOSE, + TOK_NUMBER, + TOK_VARIABLE, + TOK_INFIX +}; + +enum { TE_CONSTANT = 1 }; + +typedef struct state { + const char* start; + const char* next; + int type; + union { + double value; + const double* bound; + const void* function; + }; + void* context; + + const te_variable* lookup; + int lookup_len; +} state; + +#define TYPE_MASK(TYPE) ((TYPE)&0x0000001F) + +#define IS_PURE(TYPE) (((TYPE)&TE_FLAG_PURE) != 0) +#define IS_FUNCTION(TYPE) (((TYPE)&TE_FUNCTION0) != 0) +#define IS_CLOSURE(TYPE) (((TYPE)&TE_CLOSURE0) != 0) +#define ARITY(TYPE) (((TYPE) & (TE_FUNCTION0 | TE_CLOSURE0)) ? ((TYPE)&0x00000007) : 0) +#define NEW_EXPR(type, ...) new_expr((type), (const te_expr*[]){__VA_ARGS__}) + +static te_expr* new_expr(const int type, const te_expr* parameters[]) { + const int arity = ARITY(type); + const int psize = sizeof(void*) * arity; + const int size = + (sizeof(te_expr) - sizeof(void*)) + psize + (IS_CLOSURE(type) ? sizeof(void*) : 0); + te_expr* ret = malloc(size); + memset(ret, 0, size); + if(arity && parameters) { + memcpy(ret->parameters, parameters, psize); + } + ret->type = type; + ret->bound = 0; + return ret; +} + +void te_free_parameters(te_expr* n) { + if(!n) return; + switch(TYPE_MASK(n->type)) { + case TE_FUNCTION7: + case TE_CLOSURE7: + te_free(n->parameters[6]); /* Falls through. */ + case TE_FUNCTION6: + case TE_CLOSURE6: + te_free(n->parameters[5]); /* Falls through. */ + case TE_FUNCTION5: + case TE_CLOSURE5: + te_free(n->parameters[4]); /* Falls through. */ + case TE_FUNCTION4: + case TE_CLOSURE4: + te_free(n->parameters[3]); /* Falls through. */ + case TE_FUNCTION3: + case TE_CLOSURE3: + te_free(n->parameters[2]); /* Falls through. */ + case TE_FUNCTION2: + case TE_CLOSURE2: + te_free(n->parameters[1]); /* Falls through. */ + case TE_FUNCTION1: + case TE_CLOSURE1: + te_free(n->parameters[0]); + } +} + +void te_free(te_expr* n) { + if(!n) return; + te_free_parameters(n); + free(n); +} + +static double pi(void) { + return 3.14159265358979323846; +} +static double e(void) { + return 2.71828182845904523536; +} +static double fac(double a) { /* simplest version of fac */ + if(a < 0) return NAN; + if(a > UINT_MAX) return INFINITY; + unsigned int ua = (unsigned int)(a); + unsigned long int result = 1, i; + for(i = 1; i <= ua; i++) { + if(i > ULONG_MAX / result) return INFINITY; + result *= i; + } + return (double)result; +} +static double ncr(double n, double r) { + if(n < 0 || r < 0 || n < r) return NAN; + if(n > UINT_MAX || r > UINT_MAX) return INFINITY; + unsigned long int un = (unsigned int)(n), ur = (unsigned int)(r), i; + unsigned long int result = 1; + if(ur > un / 2) ur = un - ur; + for(i = 1; i <= ur; i++) { + if(result > ULONG_MAX / (un - ur + i)) return INFINITY; + result *= un - ur + i; + result /= i; + } + return result; +} +static double npr(double n, double r) { + return ncr(n, r) * fac(r); +} + +#ifdef _MSC_VER +#pragma function(ceil) +#pragma function(floor) +#endif + +static const te_variable functions[] = { + /* must be in alphabetical order */ + {"abs", fabs, TE_FUNCTION1 | TE_FLAG_PURE, 0}, + {"acos", acos, TE_FUNCTION1 | TE_FLAG_PURE, 0}, + {"asin", asin, TE_FUNCTION1 | TE_FLAG_PURE, 0}, + {"atan", atan, TE_FUNCTION1 | TE_FLAG_PURE, 0}, + {"atan2", atan2, TE_FUNCTION2 | TE_FLAG_PURE, 0}, + {"ceil", ceil, TE_FUNCTION1 | TE_FLAG_PURE, 0}, + {"cos", cos, TE_FUNCTION1 | TE_FLAG_PURE, 0}, + {"cosh", cosh, TE_FUNCTION1 | TE_FLAG_PURE, 0}, + {"e", e, TE_FUNCTION0 | TE_FLAG_PURE, 0}, + {"exp", exp, TE_FUNCTION1 | TE_FLAG_PURE, 0}, + {"fac", fac, TE_FUNCTION1 | TE_FLAG_PURE, 0}, + {"floor", floor, TE_FUNCTION1 | TE_FLAG_PURE, 0}, + {"ln", log, TE_FUNCTION1 | TE_FLAG_PURE, 0}, +#ifdef TE_NAT_LOG + {"log", log, TE_FUNCTION1 | TE_FLAG_PURE, 0}, +#else + {"log", log10, TE_FUNCTION1 | TE_FLAG_PURE, 0}, +#endif + {"log10", log10, TE_FUNCTION1 | TE_FLAG_PURE, 0}, + {"ncr", ncr, TE_FUNCTION2 | TE_FLAG_PURE, 0}, + {"npr", npr, TE_FUNCTION2 | TE_FLAG_PURE, 0}, + {"pi", pi, TE_FUNCTION0 | TE_FLAG_PURE, 0}, + {"pow", pow, TE_FUNCTION2 | TE_FLAG_PURE, 0}, + {"sin", sin, TE_FUNCTION1 | TE_FLAG_PURE, 0}, + {"sinh", sinh, TE_FUNCTION1 | TE_FLAG_PURE, 0}, + {"sqrt", sqrt, TE_FUNCTION1 | TE_FLAG_PURE, 0}, + {"tan", tan, TE_FUNCTION1 | TE_FLAG_PURE, 0}, + {"tanh", tanh, TE_FUNCTION1 | TE_FLAG_PURE, 0}, + {0, 0, 0, 0}}; + +static const te_variable* find_builtin(const char* name, int len) { + int imin = 0; + int imax = sizeof(functions) / sizeof(te_variable) - 2; + + /*Binary search.*/ + while(imax >= imin) { + const int i = (imin + ((imax - imin) / 2)); + int c = strncmp(name, functions[i].name, len); + if(!c) c = '\0' - functions[i].name[len]; + if(c == 0) { + return functions + i; + } else if(c > 0) { + imin = i + 1; + } else { + imax = i - 1; + } + } + + return 0; +} + +static const te_variable* find_lookup(const state* s, const char* name, int len) { + int iters; + const te_variable* var; + if(!s->lookup) return 0; + + for(var = s->lookup, iters = s->lookup_len; iters; ++var, --iters) { + if(strncmp(name, var->name, len) == 0 && var->name[len] == '\0') { + return var; + } + } + return 0; +} + +static double add(double a, double b) { + return a + b; +} +static double sub(double a, double b) { + return a - b; +} +static double mul(double a, double b) { + return a * b; +} +static double divide(double a, double b) { + return a / b; +} +static double negate(double a) { + return -a; +} +static double comma(double a, double b) { + (void)a; + return b; +} + +void next_token(state* s) { + s->type = TOK_NULL; + + do { + if(!*s->next) { + s->type = TOK_END; + return; + } + + /* Try reading a number. */ + if((s->next[0] >= '0' && s->next[0] <= '9') || s->next[0] == '.') { + s->value = strtof(s->next, (char**)&s->next); + s->type = TOK_NUMBER; + } else { + /* Look for a variable or builtin function call. */ + if(isalpha(s->next[0])) { + const char* start; + start = s->next; + while(isalpha(s->next[0]) || isdigit(s->next[0]) || (s->next[0] == '_')) s->next++; + + const te_variable* var = find_lookup(s, start, s->next - start); + if(!var) var = find_builtin(start, s->next - start); + + if(!var) { + s->type = TOK_ERROR; + } else { + switch(TYPE_MASK(var->type)) { + case TE_VARIABLE: + s->type = TOK_VARIABLE; + s->bound = var->address; + break; + + case TE_CLOSURE0: + case TE_CLOSURE1: + case TE_CLOSURE2: + case TE_CLOSURE3: /* Falls through. */ + case TE_CLOSURE4: + case TE_CLOSURE5: + case TE_CLOSURE6: + case TE_CLOSURE7: /* Falls through. */ + s->context = var->context; /* Falls through. */ + + case TE_FUNCTION0: + case TE_FUNCTION1: + case TE_FUNCTION2: + case TE_FUNCTION3: /* Falls through. */ + case TE_FUNCTION4: + case TE_FUNCTION5: + case TE_FUNCTION6: + case TE_FUNCTION7: /* Falls through. */ + s->type = var->type; + s->function = var->address; + break; + } + } + + } else { + /* Look for an operator or special character. */ + switch(s->next++[0]) { + case '+': + s->type = TOK_INFIX; + s->function = add; + break; + case '-': + s->type = TOK_INFIX; + s->function = sub; + break; + case '*': + s->type = TOK_INFIX; + s->function = mul; + break; + case '/': + s->type = TOK_INFIX; + s->function = divide; + break; + case '^': + s->type = TOK_INFIX; + s->function = pow; + break; + case '%': + s->type = TOK_INFIX; + s->function = fmod; + break; + case '(': + s->type = TOK_OPEN; + break; + case ')': + s->type = TOK_CLOSE; + break; + case ',': + s->type = TOK_SEP; + break; + case ' ': + case '\t': + case '\n': + case '\r': + break; + default: + s->type = TOK_ERROR; + break; + } + } + } + } while(s->type == TOK_NULL); +} + +static te_expr* list(state* s); +static te_expr* expr(state* s); +static te_expr* power(state* s); + +static te_expr* base(state* s) { + /* = | | {"(" ")"} | | "(" {"," } ")" | "(" ")" */ + te_expr* ret; + int arity; + + switch(TYPE_MASK(s->type)) { + case TOK_NUMBER: + ret = new_expr(TE_CONSTANT, 0); + ret->value = s->value; + next_token(s); + break; + + case TOK_VARIABLE: + ret = new_expr(TE_VARIABLE, 0); + ret->bound = s->bound; + next_token(s); + break; + + case TE_FUNCTION0: + case TE_CLOSURE0: + ret = new_expr(s->type, 0); + ret->function = s->function; + if(IS_CLOSURE(s->type)) ret->parameters[0] = s->context; + next_token(s); + if(s->type == TOK_OPEN) { + next_token(s); + if(s->type != TOK_CLOSE) { + s->type = TOK_ERROR; + } else { + next_token(s); + } + } + break; + + case TE_FUNCTION1: + case TE_CLOSURE1: + ret = new_expr(s->type, 0); + ret->function = s->function; + if(IS_CLOSURE(s->type)) ret->parameters[1] = s->context; + next_token(s); + ret->parameters[0] = power(s); + break; + + case TE_FUNCTION2: + case TE_FUNCTION3: + case TE_FUNCTION4: + case TE_FUNCTION5: + case TE_FUNCTION6: + case TE_FUNCTION7: + case TE_CLOSURE2: + case TE_CLOSURE3: + case TE_CLOSURE4: + case TE_CLOSURE5: + case TE_CLOSURE6: + case TE_CLOSURE7: + arity = ARITY(s->type); + + ret = new_expr(s->type, 0); + ret->function = s->function; + if(IS_CLOSURE(s->type)) ret->parameters[arity] = s->context; + next_token(s); + + if(s->type != TOK_OPEN) { + s->type = TOK_ERROR; + } else { + int i; + for(i = 0; i < arity; i++) { + next_token(s); + ret->parameters[i] = expr(s); + if(s->type != TOK_SEP) { + break; + } + } + if(s->type != TOK_CLOSE || i != arity - 1) { + s->type = TOK_ERROR; + } else { + next_token(s); + } + } + + break; + + case TOK_OPEN: + next_token(s); + ret = list(s); + if(s->type != TOK_CLOSE) { + s->type = TOK_ERROR; + } else { + next_token(s); + } + break; + + default: + ret = new_expr(0, 0); + s->type = TOK_ERROR; + ret->value = NAN; + break; + } + + return ret; +} + +static te_expr* power(state* s) { + /* = {("-" | "+")} */ + int sign = 1; + while(s->type == TOK_INFIX && (s->function == add || s->function == sub)) { + if(s->function == sub) sign = -sign; + next_token(s); + } + + te_expr* ret; + + if(sign == 1) { + ret = base(s); + } else { + ret = NEW_EXPR(TE_FUNCTION1 | TE_FLAG_PURE, base(s)); + ret->function = negate; + } + + return ret; +} + +#ifdef TE_POW_FROM_RIGHT +static te_expr* factor(state* s) { + /* = {"^" } */ + te_expr* ret = power(s); + + int neg = 0; + + if(ret->type == (TE_FUNCTION1 | TE_FLAG_PURE) && ret->function == negate) { + te_expr* se = ret->parameters[0]; + free(ret); + ret = se; + neg = 1; + } + + te_expr* insertion = 0; + + while(s->type == TOK_INFIX && (s->function == pow)) { + te_fun2 t = s->function; + next_token(s); + + if(insertion) { + /* Make exponentiation go right-to-left. */ + te_expr* insert = + NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, insertion->parameters[1], power(s)); + insert->function = t; + insertion->parameters[1] = insert; + insertion = insert; + } else { + ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, power(s)); + ret->function = t; + insertion = ret; + } + } + + if(neg) { + ret = NEW_EXPR(TE_FUNCTION1 | TE_FLAG_PURE, ret); + ret->function = negate; + } + + return ret; +} +#else +static te_expr* factor(state* s) { + /* = {"^" } */ + te_expr* ret = power(s); + + while(s->type == TOK_INFIX && (s->function == pow)) { + te_fun2 t = s->function; + next_token(s); + ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, power(s)); + ret->function = t; + } + + return ret; +} +#endif + +static te_expr* term(state* s) { + /* = {("*" | "/" | "%") } */ + te_expr* ret = factor(s); + + while(s->type == TOK_INFIX && + (s->function == mul || s->function == divide || s->function == fmod)) { + te_fun2 t = s->function; + next_token(s); + ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, factor(s)); + ret->function = t; + } + + return ret; +} + +static te_expr* expr(state* s) { + /* = {("+" | "-") } */ + te_expr* ret = term(s); + + while(s->type == TOK_INFIX && (s->function == add || s->function == sub)) { + te_fun2 t = s->function; + next_token(s); + ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, term(s)); + ret->function = t; + } + + return ret; +} + +static te_expr* list(state* s) { + /* = {"," } */ + te_expr* ret = expr(s); + + while(s->type == TOK_SEP) { + next_token(s); + ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, expr(s)); + ret->function = comma; + } + + return ret; +} + +#define TE_FUN(...) ((double (*)(__VA_ARGS__))n->function) +#define M(e) te_eval(n->parameters[e]) + +double te_eval(const te_expr* n) { + if(!n) return NAN; + + switch(TYPE_MASK(n->type)) { + case TE_CONSTANT: + return n->value; + case TE_VARIABLE: + return *n->bound; + + case TE_FUNCTION0: + case TE_FUNCTION1: + case TE_FUNCTION2: + case TE_FUNCTION3: + case TE_FUNCTION4: + case TE_FUNCTION5: + case TE_FUNCTION6: + case TE_FUNCTION7: + switch(ARITY(n->type)) { + case 0: + return TE_FUN(void)(); + case 1: + return TE_FUN(double)(M(0)); + case 2: + return TE_FUN(double, double)(M(0), M(1)); + case 3: + return TE_FUN(double, double, double)(M(0), M(1), M(2)); + case 4: + return TE_FUN(double, double, double, double)(M(0), M(1), M(2), M(3)); + case 5: + return TE_FUN(double, double, double, double, double)(M(0), M(1), M(2), M(3), M(4)); + case 6: + return TE_FUN(double, double, double, double, double, double)( + M(0), M(1), M(2), M(3), M(4), M(5)); + case 7: + return TE_FUN(double, double, double, double, double, double, double)( + M(0), M(1), M(2), M(3), M(4), M(5), M(6)); + default: + return NAN; + } + + case TE_CLOSURE0: + case TE_CLOSURE1: + case TE_CLOSURE2: + case TE_CLOSURE3: + case TE_CLOSURE4: + case TE_CLOSURE5: + case TE_CLOSURE6: + case TE_CLOSURE7: + switch(ARITY(n->type)) { + case 0: + return TE_FUN(void*)(n->parameters[0]); + case 1: + return TE_FUN(void*, double)(n->parameters[1], M(0)); + case 2: + return TE_FUN(void*, double, double)(n->parameters[2], M(0), M(1)); + case 3: + return TE_FUN(void*, double, double, double)(n->parameters[3], M(0), M(1), M(2)); + case 4: + return TE_FUN(void*, double, double, double, double)( + n->parameters[4], M(0), M(1), M(2), M(3)); + case 5: + return TE_FUN(void*, double, double, double, double, double)( + n->parameters[5], M(0), M(1), M(2), M(3), M(4)); + case 6: + return TE_FUN(void*, double, double, double, double, double, double)( + n->parameters[6], M(0), M(1), M(2), M(3), M(4), M(5)); + case 7: + return TE_FUN(void*, double, double, double, double, double, double, double)( + n->parameters[7], M(0), M(1), M(2), M(3), M(4), M(5), M(6)); + default: + return NAN; + } + + default: + return NAN; + } +} + +#undef TE_FUN +#undef M + +static void optimize(te_expr* n) { + /* Evaluates as much as possible. */ + if(n->type == TE_CONSTANT) return; + if(n->type == TE_VARIABLE) return; + + /* Only optimize out functions flagged as pure. */ + if(IS_PURE(n->type)) { + const int arity = ARITY(n->type); + int known = 1; + int i; + for(i = 0; i < arity; ++i) { + optimize(n->parameters[i]); + if(((te_expr*)(n->parameters[i]))->type != TE_CONSTANT) { + known = 0; + } + } + if(known) { + const double value = te_eval(n); + te_free_parameters(n); + n->type = TE_CONSTANT; + n->value = value; + } + } +} + +te_expr* + te_compile(const char* expression, const te_variable* variables, int var_count, int* error) { + state s; + s.start = s.next = expression; + s.lookup = variables; + s.lookup_len = var_count; + + next_token(&s); + te_expr* root = list(&s); + + if(s.type != TOK_END) { + te_free(root); + if(error) { + *error = (s.next - s.start); + if(*error == 0) *error = 1; + } + return 0; + } else { + optimize(root); + if(error) *error = 0; + return root; + } +} + +double te_interp(const char* expression, int* error) { + te_expr* n = te_compile(expression, 0, 0, error); + double ret; + if(n) { + ret = te_eval(n); + te_free(n); + } else { + ret = NAN; + } + return ret; +} + +static void pn(const te_expr* n, int depth) { + int i, arity; + printf("%*s", depth, ""); + + switch(TYPE_MASK(n->type)) { + case TE_CONSTANT: + printf("%f\n", n->value); + break; + case TE_VARIABLE: + printf("bound %p\n", n->bound); + break; + + case TE_FUNCTION0: + case TE_FUNCTION1: + case TE_FUNCTION2: + case TE_FUNCTION3: + case TE_FUNCTION4: + case TE_FUNCTION5: + case TE_FUNCTION6: + case TE_FUNCTION7: + case TE_CLOSURE0: + case TE_CLOSURE1: + case TE_CLOSURE2: + case TE_CLOSURE3: + case TE_CLOSURE4: + case TE_CLOSURE5: + case TE_CLOSURE6: + case TE_CLOSURE7: + arity = ARITY(n->type); + printf("f%d", arity); + for(i = 0; i < arity; i++) { + printf(" %p", n->parameters[i]); + } + printf("\n"); + for(i = 0; i < arity; i++) { + pn(n->parameters[i], depth + 1); + } + break; + } +} + +void te_print(const te_expr* n) { + pn(n, 0); +} diff --git a/Applications/Official/DEV_FW/source/calculator/tinyexpr.h b/Applications/Official/DEV_FW/source/calculator/tinyexpr.h new file mode 100644 index 000000000..3833965a1 --- /dev/null +++ b/Applications/Official/DEV_FW/source/calculator/tinyexpr.h @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: Zlib +/* + * TINYEXPR - Tiny recursive descent parser and evaluation engine in C + * + * Copyright (c) 2015-2020 Lewis Van Winkle + * + * http://CodePlea.com + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgement in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#ifndef TINYEXPR_H +#define TINYEXPR_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct te_expr { + int type; + union { + double value; + const double* bound; + const void* function; + }; + void* parameters[1]; +} te_expr; + +enum { + TE_VARIABLE = 0, + + TE_FUNCTION0 = 8, + TE_FUNCTION1, + TE_FUNCTION2, + TE_FUNCTION3, + TE_FUNCTION4, + TE_FUNCTION5, + TE_FUNCTION6, + TE_FUNCTION7, + + TE_CLOSURE0 = 16, + TE_CLOSURE1, + TE_CLOSURE2, + TE_CLOSURE3, + TE_CLOSURE4, + TE_CLOSURE5, + TE_CLOSURE6, + TE_CLOSURE7, + + TE_FLAG_PURE = 32 +}; + +typedef struct te_variable { + const char* name; + const void* address; + int type; + void* context; +} te_variable; + +/* Parses the input expression, evaluates it, and frees it. */ +/* Returns NaN on error. */ +double te_interp(const char* expression, int* error); + +/* Parses the input expression and binds variables. */ +/* Returns NULL on error. */ +te_expr* + te_compile(const char* expression, const te_variable* variables, int var_count, int* error); + +/* Evaluates the expression. */ +double te_eval(const te_expr* n); + +/* Prints debugging information on the syntax tree. */ +void te_print(const te_expr* n); + +/* Frees the expression. */ +/* This is safe to call on NULL pointers. */ +void te_free(te_expr* n); + +#ifdef __cplusplus +} +#endif + +#endif /*TINYEXPR_H*/ diff --git a/Applications/Official/DEV_FW/source/chess/application.fam b/Applications/Official/DEV_FW/source/chess/application.fam new file mode 100644 index 000000000..7bf1d30e6 --- /dev/null +++ b/Applications/Official/DEV_FW/source/chess/application.fam @@ -0,0 +1,12 @@ +App( + appid="zBroken_Chess", + name="Chess", + apptype=FlipperAppType.EXTERNAL, + entry_point="chess_app", + cdefines=["APP_CHESS"], + requires=["storage","gui"], + stack_size= 4 * 1024, + order=500, + fap_icon="chessIcon.png", + fap_category="Games", +) diff --git a/Applications/Official/DEV_FW/source/chess/chessIcon.png b/Applications/Official/DEV_FW/source/chess/chessIcon.png new file mode 100644 index 000000000..d98ebb1d4 Binary files /dev/null and b/Applications/Official/DEV_FW/source/chess/chessIcon.png differ diff --git a/Applications/Official/DEV_FW/source/chess/chess_app.c b/Applications/Official/DEV_FW/source/chess/chess_app.c new file mode 100644 index 000000000..a3f118f2c --- /dev/null +++ b/Applications/Official/DEV_FW/source/chess/chess_app.c @@ -0,0 +1,686 @@ +#include +#include +#include +#include +#include + +#include +#include "fast_chess.h" + +static bool flag = true; +static bool should_exit = false; +// static bool ai_should_make_move = false; +static bool thinking = false; +static bool should_update_screen = true; +static uint32_t anim = 0; +static char white_move_str[8] = "", black_move_str[8] = ""; // last moves + +static NotificationApp* notification; + +const uint8_t _I_Chess_0[] = { + 0x01, 0x00, 0x2a, 0x01, 0x80, 0x7f, 0xc0, 0x2c, 0x0f, 0xf0, 0x7f, 0x83, 0xfc, 0x1f, 0xe0, 0xff, + 0x07, 0xf8, 0x3f, 0xc1, 0xde, 0x0f, 0xf0, 0x7f, 0x83, 0xfc, 0x1f, 0xe0, 0xff, 0x07, 0xf8, 0x3f, + 0xc2, 0x1e, 0x0f, 0xf0, 0x7f, 0x80, 0x07, 0x00, 0x0f, 0xf1, 0x7f, 0xc0, 0x41, 0xf8, 0x1e, 0x18, + 0x0f, 0xf2, 0xfe, 0x1f, 0xb8, 0x0e, 0x0a, 0x07, 0x80, 0x81, 0x83, 0xea, 0x05, 0x62, 0x01, 0x8c, + 0x30, 0x7d, 0x50, 0x48, 0x80, 0x0c, 0x66, 0x00, 0xfa, 0x84, 0x42, 0x00, 0x63, 0x40, 0x07, 0xd4, + 0x42, 0x08, 0x54, 0x24, 0xf5, 0xc0, 0x8f, 0xd9, 0x70, 0x3f, 0xa2, 0x7a, 0xb0, 0x69, 0xee, 0x57, + 0xa0, 0x3e, 0xa8, 0x0b, 0xfc, 0xc0, 0x4f, 0xc1, 0xe5, 0x3c, 0x07, 0xcd, 0x03, 0x81, 0x41, 0x06, + 0x0f, 0x0c, 0x1f, 0x32, 0x08, 0x04, 0x98, 0x46, 0x31, 0xf3, 0x10, 0x83, 0xdd, 0x58, 0x33, 0x88, + 0x07, 0x02, 0xfe, 0x82, 0x10, 0x7c, 0x88, 0x47, 0x81, 0x73, 0x07, 0xcf, 0x42, 0x07, 0xc0, 0x80, + 0x78, 0x3e, 0x2b, 0x21, 0x07, 0xbf, 0xc2, 0x1f, 0x00, 0x81, 0x83, 0xef, 0xc1, 0x1f, 0x7e, 0x0f, + 0x83, 0xff, 0x04, 0x47, 0xc7, 0x82, 0x7e, 0x42, 0x10, 0x7d, 0x96, 0xc4, 0x06, 0x71, 0x60, 0x7c, + 0x3e, 0x48, 0x1e, 0x52, 0xa5, 0xfc, 0xff, 0xd1, 0x62, 0x8f, 0x1a, 0xa8, 0x3e, 0x7f, 0xd0, 0x31, + 0x19, 0x07, 0xeb, 0xf9, 0x07, 0x80, 0x58, 0x2c, 0x01, 0xfa, 0xfc, 0x20, 0x06, 0x21, 0x80, 0x0f, + 0xd7, 0xc1, 0x00, 0x20, 0x01, 0xaa, 0xa3, 0xe1, 0x00, 0x60, 0x01, 0x95, 0x03, 0xe7, 0x80, 0x24, + 0x38, 0xa0, 0x3e, 0x7f, 0x1c, 0x73, 0x00, 0x9d, 0x8c, 0x02, 0xdc, 0x0d, 0x7c, 0x04, 0xa0, 0x2e, + 0xe1, 0x07, 0xc5, 0xc2, 0xeb, 0x00, 0x9f, 0x40, 0x12, 0x42, 0x0f, 0x8d, 0xc4, 0x69, 0x22, 0x3c, + 0x11, 0x7d, 0x5e, 0x20, 0xa0, 0x41, 0x30, 0x98, 0x3d, 0xf1, 0x1f, 0xff, 0xfa, 0x00, 0xcb, 0xd1, + 0x10, 0x2e, 0x18, 0x3e, 0xa4, 0x00, 0xfe, 0xa0, 0x03, 0xfb, 0x00, 0x6b, 0x20, 0x7d, 0xc0, 0x21, + 0xc0, 0xfe, 0xf8, 0x3f, 0xc4, 0x1f, 0x90, 0x0f, 0xe0, 0x40, 0xc1, 0xf6, 0x05, 0x30, +}; +const uint8_t* const _I_Chess[] = {_I_Chess_0}; + +const uint8_t _I_Chess_Selection1_0[] = { + 0x00, + 0x55, + 0x80, + 0x01, + 0x80, + 0x01, + 0x80, + 0x01, + 0xAA, +}; +const uint8_t* const _I_Chess_Selection1[] = {_I_Chess_Selection1_0}; + +const uint8_t _I_Chess_Selection2_0[] = { + 0x00, + 0xAA, + 0x01, + 0x80, + 0x01, + 0x80, + 0x01, + 0x80, + 0x55, +}; +const uint8_t* const _I_Chess_Selection2[] = {_I_Chess_Selection2_0}; + +const uint8_t _I_Chess_bb_0[] = { + 0x00, + 0x0C, + 0x1A, + 0x3D, + 0x1E, + 0x0C, + 0x3F, +}; +const uint8_t* const _I_Chess_bb[] = {_I_Chess_bb_0}; + +const uint8_t _I_Chess_bw_0[] = { + 0x00, + 0x0C, + 0x16, + 0x23, + 0x16, + 0x0C, + 0x3F, +}; +const uint8_t* const _I_Chess_bw[] = {_I_Chess_bw_0}; + +const uint8_t _I_Chess_kb_0[] = { + 0x00, + 0x0C, + 0x2D, + 0x21, + 0x12, + 0x0C, + 0x3F, +}; +const uint8_t* const _I_Chess_kb[] = {_I_Chess_kb_0}; + +const uint8_t _I_Chess_kw_0[] = { + 0x00, + 0x0C, + 0x21, + 0x21, + 0x12, + 0x0C, + 0x3F, +}; +const uint8_t* const _I_Chess_kw[] = {_I_Chess_kw_0}; + +const uint8_t _I_Chess_nb_0[] = { + 0x00, + 0x06, + 0x0F, + 0x1F, + 0x2E, + 0x0E, + 0x3F, +}; +const uint8_t* const _I_Chess_nb[] = {_I_Chess_nb_0}; + +const uint8_t _I_Chess_nw_0[] = { + 0x00, + 0x06, + 0x09, + 0x11, + 0x2A, + 0x0A, + 0x3F, +}; +const uint8_t* const _I_Chess_nw[] = {_I_Chess_nw_0}; + +const uint8_t _I_Chess_old_0[] = { + 0x01, 0x00, 0x35, 0x01, 0x80, 0x7f, 0xc0, 0x2c, 0x0f, 0xf0, 0x7f, 0x83, 0xfc, 0x1f, 0xe0, 0xff, + 0x07, 0xf8, 0x3f, 0xc0, 0x03, 0x80, 0x0f, 0x70, 0x3f, 0xe0, 0x10, 0x11, 0x77, 0x40, 0x7f, 0x97, + 0xf0, 0xfd, 0xc0, 0x70, 0x50, 0x3c, 0x04, 0x0c, 0x1f, 0x50, 0x2b, 0x10, 0x0c, 0x61, 0x80, 0xfa, + 0x82, 0x44, 0x00, 0x63, 0x30, 0x07, 0xd4, 0x22, 0x10, 0x03, 0x1a, 0x00, 0x3e, 0xa2, 0x10, 0x42, + 0xa1, 0x90, 0x2d, 0x01, 0x97, 0x03, 0xfa, 0x03, 0xe7, 0x01, 0x83, 0x5f, 0xf8, 0x3f, 0xa8, 0x00, + 0xfc, 0xc0, 0x4f, 0xc1, 0xe5, 0x3c, 0x07, 0xcd, 0x03, 0x81, 0x41, 0x06, 0x0f, 0x0c, 0x1f, 0x32, + 0x08, 0x04, 0x98, 0x46, 0x31, 0xf8, 0x08, 0xfa, 0x15, 0x83, 0x38, 0x80, 0x70, 0x2f, 0xf0, 0x20, + 0x7d, 0x08, 0x47, 0x81, 0x73, 0x07, 0xcf, 0x42, 0x07, 0xc0, 0x80, 0x78, 0x3e, 0x30, 0x40, 0x7c, + 0x7c, 0x21, 0xf0, 0x08, 0x18, 0x3e, 0xfc, 0x11, 0xf7, 0xe0, 0xf8, 0x3f, 0xe0, 0xfa, 0x9f, 0x90, + 0x84, 0x1f, 0x65, 0xb1, 0x01, 0x9c, 0x59, 0x9d, 0x21, 0x34, 0x95, 0x2f, 0xe7, 0xfe, 0xee, 0x14, + 0x78, 0xd5, 0x41, 0xf3, 0xfe, 0x81, 0x88, 0xc8, 0x3f, 0x5f, 0xc8, 0x3c, 0x02, 0xc1, 0x60, 0x0f, + 0xd7, 0xe1, 0x00, 0x31, 0x0c, 0x00, 0x7e, 0xbe, 0x08, 0x01, 0x00, 0x08, 0x7e, 0x90, 0x04, 0x00, + 0x10, 0xfd, 0x70, 0x00, 0x65, 0x00, 0x8a, 0x0f, 0xeb, 0x8e, 0x60, 0x13, 0xa9, 0x07, 0xa3, 0x5f, + 0x01, 0x28, 0x0c, 0x08, 0x1f, 0x37, 0x0b, 0xac, 0x02, 0x7d, 0x00, 0x49, 0x08, 0x3e, 0x37, 0x11, + 0xa4, 0x88, 0xf0, 0x45, 0xf5, 0x78, 0x82, 0x81, 0x44, 0xc2, 0x40, 0xf8, 0xc4, 0x7f, 0xff, 0xe8, + 0x03, 0x07, 0xc4, 0x40, 0x18, 0x40, 0xfb, 0x90, 0x03, 0xfa, 0x80, 0x0f, 0x50, 0x84, 0x60, 0x0d, + 0x62, 0x20, 0xd8, 0x70, 0x3f, 0xbe, 0x0f, 0xf1, 0x07, 0xe4, 0x03, 0xf8, 0x10, 0x40, 0x7d, 0x01, + 0x0c, 0x1f, 0x77, 0xf2, 0x91, 0x83, 0xeb, 0x7e, 0x1f, 0xdf, 0xa5, 0x7c, 0x1d, 0x90, 0x0d, 0x54, + 0xa8, 0x1f, 0xb5, 0x68, 0xa8, 0x7f, 0xa1, 0x40, 0xfd, 0xaa, 0xc1, 0x41, 0xfb, 0xa1, 0x81, 0x03, + 0xf5, 0xa0, 0x80, 0xff, 0x07, 0xce, 0x01, 0x9c, 0x80, +}; +const uint8_t* const _I_Chess_old[] = {_I_Chess_old_0}; + +const uint8_t _I_Chess_pb_0[] = { + 0x00, + 0x00, + 0x0C, + 0x1E, + 0x1E, + 0x0C, + 0x1E, +}; +const uint8_t* const _I_Chess_pb[] = {_I_Chess_pb_0}; + +const uint8_t _I_Chess_pw_0[] = { + 0x00, + 0x00, + 0x0C, + 0x12, + 0x12, + 0x0C, + 0x1E, +}; +const uint8_t* const _I_Chess_pw[] = {_I_Chess_pw_0}; + +const uint8_t _I_Chess_qb_0[] = { + 0x00, + 0x2D, + 0x2D, + 0x2D, + 0x1E, + 0x1E, + 0x3F, +}; +const uint8_t* const _I_Chess_qb[] = {_I_Chess_qb_0}; + +const uint8_t _I_Chess_qw_0[] = { + 0x00, + 0x2D, + 0x2D, + 0x2D, + 0x1E, + 0x1E, + 0x3F, +}; +const uint8_t* const _I_Chess_qw[] = {_I_Chess_qw_0}; + +const uint8_t _I_Chess_rb_0[] = { + 0x00, + 0x2D, + 0x2D, + 0x1E, + 0x1E, + 0x1E, + 0x3F, +}; +const uint8_t* const _I_Chess_rb[] = {_I_Chess_rb_0}; + +const uint8_t _I_Chess_rw_0[] = { + 0x00, + 0x2D, + 0x2D, + 0x12, + 0x12, + 0x12, + 0x3F, +}; +const uint8_t* const _I_Chess_rw[] = {_I_Chess_rw_0}; + +const Icon I_Chess_Selection2 = + {.width = 8, .height = 8, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess_Selection2}; +const Icon I_Chess_old = + {.width = 128, .height = 64, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess_old}; +const Icon I_Chess_Selection1 = + {.width = 8, .height = 8, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess_Selection1}; +const Icon I_Chess = + {.width = 128, .height = 64, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess}; +const Icon I_Chess_kb = + {.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess_kb}; +const Icon I_Chess_rw = + {.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess_rw}; +const Icon I_Chess_rb = + {.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess_rb}; +const Icon I_Chess_kw = + {.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess_kw}; +const Icon I_Chess_qb = + {.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess_qb}; +const Icon I_Chess_qw = + {.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess_qw}; +const Icon I_Chess_pw = + {.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess_pw}; +const Icon I_Chess_pb = + {.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess_pb}; +const Icon I_Chess_nb = + {.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess_nb}; +const Icon I_Chess_bw = + {.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess_bw}; +const Icon I_Chess_bb = + {.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess_bb}; +const Icon I_Chess_nw = + {.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess_nw}; + +typedef struct { + uint8_t col, row; +} _Position; + +typedef struct { + enum { + None = 0, + Pawn, + King, + Queen, + Bishop, + Knight, + Rook, + } type; + enum { White, Black } side; +} Piece; + +static const _Position PosNone = {.col = 255, .row = 255}; +// static Piece board[8][8]; // col, row +static _Position sel, move_from = PosNone, move_to = PosNone; + +Game* game; + +// uint8_t sel_col = 0, sel_row = 0; + +// static enum { +// SelectingFrom, +// SelectingTo +// } state = SelectingFrom; + +// static void reset_board() { +// memset(board, 0, sizeof(board)); + +// board[0][0].type = Rook; +// board[1][0].type = Knight; +// board[2][0].type = Bishop; +// board[3][0].type = Queen; +// board[4][0].type = King; +// board[5][0].type = Bishop; +// board[6][0].type = Knight; +// board[7][0].type = Rook; + +// board[0][1].type = Pawn; +// board[1][1].type = Pawn; +// board[2][1].type = Pawn; +// board[3][1].type = Pawn; +// board[4][1].type = Pawn; +// board[5][1].type = Pawn; +// board[6][1].type = Pawn; +// board[7][1].type = Pawn; + +// board[0][7].type = Rook; board[0][7].side = Black; +// board[1][7].type = Knight; board[1][7].side = Black; +// board[2][7].type = Bishop; board[2][7].side = Black; +// board[3][7].type = Queen; board[3][7].side = Black; +// board[4][7].type = King; board[4][7].side = Black; +// board[5][7].type = Bishop; board[5][7].side = Black; +// board[6][7].type = Knight; board[6][7].side = Black; +// board[7][7].type = Rook; board[7][7].side = Black; + +// board[0][6].type = Pawn; board[0][6].side = Black; +// board[1][6].type = Pawn; board[1][6].side = Black; +// board[2][6].type = Pawn; board[2][6].side = Black; +// board[3][6].type = Pawn; board[3][6].side = Black; +// board[4][6].type = Pawn; board[4][6].side = Black; +// board[5][6].type = Pawn; board[5][6].side = Black; +// board[6][6].type = Pawn; board[6][6].side = Black; +// board[7][6].type = Pawn; board[7][6].side = Black; +// } + +// static const Icon* get_icon(const Piece* piece) { +// if (piece->side == White) { +// switch (piece->type) { +// case Pawn: return &I_Chess_pw; +// case King: return &I_Chess_kw; +// case Queen: return &I_Chess_qw; +// case Bishop: return &I_Chess_bw; +// case Knight: return &I_Chess_nw; +// case Rook: return &I_Chess_rw; +// default: return NULL; +// } +// } else { +// switch (piece->type) { +// case Pawn: return &I_Chess_pb; +// case King: return &I_Chess_kb; +// case Queen: return &I_Chess_qb; +// case Bishop: return &I_Chess_bb; +// case Knight: return &I_Chess_nb; +// case Rook: return &I_Chess_rb; +// default: return NULL; +// } +// } +// } + +static void notify_click() { + // static const NotificationSequence sequence = { + // &message_click, + // &message_delay_1, + // &message_sound_off, + // NULL, + // }; + + // notification_message_block(notification, &sequence); + notification_message(notification, &sequence_single_vibro); +} +static const Icon* _get_icon(uint8_t file, uint8_t rank) { + char piece = getPieceChar((FILES_BB[file] & RANKS_BB[7 - rank]), &(game->position.board)); + switch(piece) { + case 'P': + return &I_Chess_pw; + case 'K': + return &I_Chess_kw; + case 'Q': + return &I_Chess_qw; + case 'B': + return &I_Chess_bw; + case 'N': + return &I_Chess_nw; + case 'R': + return &I_Chess_rw; + case 'p': + return &I_Chess_pb; + case 'k': + return &I_Chess_kb; + case 'q': + return &I_Chess_qb; + case 'b': + return &I_Chess_bb; + case 'n': + return &I_Chess_nb; + case 'r': + return &I_Chess_rb; + default: + return NULL; + } +} + +static int get_position(uint8_t file, uint8_t rank) { + return 8 * rank + file; +} + +static int get_rank(int position) { + return (int)(position / 8); +} + +static int get_file(int position) { + return position % 8; +} + +static void make_move(uint8_t file1, uint8_t rank1, uint8_t file2, uint8_t rank2) { + int from = get_position(file1, rank1); + int to = get_position(file2, rank2); + Move move = generateMove(from, to); + if(!isLegalMove(&game->position, move)) { + return; + } + makeMove(game, move); + move2str(white_move_str, game, game->moveListLen - 1); + notify_click(); + black_move_str[0] = 0; + anim = furi_get_tick(); + thinking = true; +} + +static int32_t make_ai_move(void* context) { + UNUSED(context); + // thinking = true; + int depth = 1; + Move move; + Node node = + iterativeDeepeningAlphaBeta(&(game->position), (char)depth, INT32_MIN, INT32_MAX, FALSE); + move = node.move; + makeMove(game, move); + move2str(black_move_str, game, game->moveListLen - 1); + notify_click(); + thinking = false; + anim = furi_get_tick(); + return 0; +} + +static FuriThread* worker_thread = NULL; + +static int32_t ai_thread(void* context) { + while(true) { + if(should_exit) break; + if(thinking) make_ai_move(context); + furi_delay_ms(100); + } + return 0; +} + +static void run_ai_thread() { + if(worker_thread == NULL) { + worker_thread = furi_thread_alloc(); + } + + furi_thread_set_name(worker_thread, "ChessEngine"); + furi_thread_set_stack_size(worker_thread, 7000); + // furi_thread_set_context(thread, bad_usb); + furi_thread_set_callback(worker_thread, ai_thread); + furi_thread_start(worker_thread); + + // furi_thread_join(worker_thread); + // furi_thread_free(worker_thread); +} + +static void chess_draw_callback(Canvas* canvas, void* ctx) { + UNUSED(ctx); + should_update_screen = false; + canvas_clear(canvas); + + // canvas_set_color(canvas, flag ? ColorBlack : ColorWhite); + + canvas_draw_icon(canvas, 0, 0, &I_Chess); + + if(!thinking) { + canvas_set_color(canvas, (sel.col + sel.row) % 2 != 0 ? ColorBlack : ColorWhite); + canvas_draw_icon( + canvas, + sel.col * 8, + (7 - sel.row) * 8, + flag ? &I_Chess_Selection1 : &I_Chess_Selection2); + + if(move_from.col != 255) { + canvas_set_color( + canvas, (move_from.col + move_from.row) % 2 != 0 ? ColorBlack : ColorWhite); + canvas_draw_icon( + canvas, + move_from.col * 8, + (7 - move_from.row) * 8, + flag ? &I_Chess_Selection1 : &I_Chess_Selection2); + } + } + + // print moves + if(game->moveListLen > 0) { + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontSecondary); + + // int num = game->moveListLen; + + // char white_str[8], black_str[8] = "..."; + + // if (num == 0) { + // } else if (num % 2 == 0) { + // // white move + // move2str(white_str, game, game->moveListLen - 2); + // move2str(black_str, game, game->moveListLen - 1); + // } else { + // move2str(white_str, game, game->moveListLen - 1); + // } + + char str[28]; + snprintf( + str, 28, "%d. %s %s", (game->moveListLen + 1) / 2, white_move_str, black_move_str); + canvas_draw_str(canvas, 75, 12, str); + } + + Move last_move = getLastMove(game); + + for(uint8_t row = 0; row < 8; row++) { + for(uint8_t col = 0; col < 8; col++) { + bool white_field = (row + col) % 2 != 0; + + // if (!white_field) { + // canvas_draw_box(canvas, col * 8, row * 8, 8, 8); + // } + const Icon* icon = _get_icon(col, row); + if(icon != NULL) { + int x = col * 8; + int y = row * 8; + + int dt = furi_get_tick() - anim; + if(anim && dt >= 300) { + anim = 0; + } + + if(anim && last_move && get_file(getTo(last_move)) == col && + get_rank(getTo(last_move)) == (7 - row)) { + // moving piece + uint8_t from_x = get_file(getFrom(last_move)) * 8; + uint8_t from_y = (7 - get_rank(getFrom(last_move))) * 8; + x = from_x + (x - from_x) * dt / 300; + y = from_y + (y - from_y) * dt / 300; + } + + canvas_set_color(canvas, white_field ? ColorWhite : ColorBlack); + canvas_draw_icon(canvas, x + 1, y + 1, icon); + } + + // if (board[col][7 - row].type != None) { + // canvas_set_color(canvas, white_field ? ColorWhite : ColorBlack); + // canvas_draw_icon(canvas, col * 8 + 1, row * 8 + 1, get_icon(&board[col][7 - row])); + // } + } + } + + // for (uint8_t i = 0; i < 4; i++) { + // canvas_draw_dot(canvas, sel_col * 8, sel_row * 8); + // canvas_draw_dot(canvas, sel_col * 8 + 2, sel_row * 8); + // canvas_draw_dot(canvas, sel_col * 8, sel_row * 8); + // canvas_draw_dot(canvas, sel_col * 8, sel_row * 8); + // } + + // canvas_draw_disc(canvas, GUI_DISPLAY_WIDTH / 2 - 40, GUI_DISPLAY_HEIGHT / 2, 15); + // canvas_set_color(canvas, flag ? ColorBlack : ColorWhite); + // canvas_draw_disc(canvas, GUI_DISPLAY_WIDTH / 2, GUI_DISPLAY_HEIGHT / 2, 15); +} + +static void chess_input_callback(InputEvent* event, void* ctx) { + UNUSED(ctx); + if(event->type == InputTypeShort) { + if(event->key == InputKeyLeft) { + sel.col = (sel.col == 0) ? 0 : sel.col - 1; + } else if(event->key == InputKeyRight) { + sel.col++; + } else if(event->key == InputKeyDown) { + sel.row = (sel.row == 0) ? 0 : sel.row - 1; + } else if(event->key == InputKeyUp) { + sel.row++; + } else if(event->key == InputKeyOk) { + if(move_from.col == 255) { + move_from = sel; + } else if(move_to.col == 255) { + move_to = sel; + make_move(move_from.col, move_from.row, move_to.col, move_to.row); + // thinking = true; + // ai_should_make_move = true; + // make_ai_move_threaded(); + // Piece piece = board[move_from.col][move_from.row]; + // board[move_from.col][move_from.row].type = None; + // board[move_to.col][move_to.row] = piece; + move_from = PosNone; + move_to = PosNone; + } + } else if(event->key == InputKeyBack) { + should_exit = true; + } + sel.col = CLAMP(sel.col, 7, 0); + sel.row = CLAMP(sel.row, 7, 0); + } +} + +static void setup_engine() { + // int depth = 1; // DEFAULT_AI_DEPTH; + + getInitialGame(game); + + // Move move; + // Node node = iterativeDeepeningAlphaBeta(&(game.position), (char) depth, INT32_MIN, INT32_MAX, FALSE); + // move = node.move; + + // node = iterativeDeepeningAlphaBeta(&(game.position), (char) 2, INT32_MIN, INT32_MAX, FALSE); + + // node = iterativeDeepeningAlphaBeta(&(game.position), (char) 3, INT32_MIN, INT32_MAX, FALSE); + + // printf("%d\n", move); +} + +// void test_engine() { +// FuriThread* thread; // + +// thread = furi_thread_alloc(); +// furi_thread_set_name(thread, "ChessEngine"); +// furi_thread_set_stack_size(thread, 20000); +// // furi_thread_set_context(thread, bad_usb); +// furi_thread_set_callback(thread, setup_engine); + +// furi_thread_start(thread); + +// furi_thread_join(thread); + +// furi_thread_free(thread); +// } + +int32_t chess_app(void* p) { + UNUSED(p); + // Configure view port + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, chess_draw_callback, NULL); + view_port_input_callback_set(view_port, chess_input_callback, NULL); + + // Register view port in GUI + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + notification = furi_record_open(RECORD_NOTIFICATION); + + should_exit = false; + + game = malloc(sizeof(Game)); + + setup_engine(); + run_ai_thread(); + + // test_engine(); + + while(!should_exit) { + furi_delay_ms(100); + if(!thinking) { + flag = !flag; + should_update_screen = true; + } + if(anim) { + should_update_screen = true; + } + if(should_update_screen) { + view_port_update(view_port); + } + // flag = true; + // delay(40); + // flag = false; + // view_port_update(view_port); + // delay(80); + } + + furi_thread_join(worker_thread); + furi_thread_free(worker_thread); + worker_thread = NULL; + + gui_remove_view_port(gui, view_port); + view_port_free(view_port); + + furi_record_close(RECORD_GUI); + + free(game); + + return 0; +} diff --git a/Applications/Official/DEV_FW/source/chess/fast_chess.c b/Applications/Official/DEV_FW/source/chess/fast_chess.c new file mode 100644 index 000000000..8d9315513 --- /dev/null +++ b/Applications/Official/DEV_FW/source/chess/fast_chess.c @@ -0,0 +1,2978 @@ +/* + ============================================================================ + Name : fast-chess.c + Author : Frederico Jordan + Version : + Copyright : Copyright (c) 2016 Frederico Jordan + Description : Simple chess game! + ============================================================================ + */ + +#include +#include +#include +#include +#include +#include + +#include "fast_chess.h" + +char FILES[8] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}; +char RANKS[8] = {'1', '2', '3', '4', '5', '6', '7', '8'}; + +Bitboard FILES_BB[8] = {FILE_A, FILE_B, FILE_C, FILE_D, FILE_E, FILE_F, FILE_G, FILE_H}; +Bitboard RANKS_BB[8] = {RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8}; + +char INITIAL_FEN[] = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; + +Board EMPTY_BOARD = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +Board INITIAL_BOARD = { + FILE_E & RANK_1, // whiteKing; + FILE_D& RANK_1, // whiteQueens; + (FILE_A | FILE_H) & RANK_1, // whiteRooks; + (FILE_B | FILE_G) & RANK_1, // whiteKnights; + (FILE_C | FILE_F) & RANK_1, // whiteBishops; + RANK_2, // whitePawns; + + FILE_E& RANK_8, // blackKing; + FILE_D& RANK_8, // blackQueens; + (FILE_A | FILE_H) & RANK_8, // blackRooks; + (FILE_B | FILE_G) & RANK_8, // blackKnights; + (FILE_C | FILE_F) & RANK_8, // blackBishops; + RANK_7, // blackPawns; +}; + +int PIECE_VALUES[] = { + 0, // EMPTY + 100, // PAWN + 300, // KNIGHT + 300, // BISHOP + 500, // ROOK + 900, // QUEEN + 42000 // KING +}; + +int PAWN_BONUS[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -40, -40, 0, 0, 0, + 1, 2, 3, -10, -10, 3, 2, 1, 2, 4, 6, 8, 8, 6, 4, 2, + 3, 6, 9, 12, 12, 9, 6, 3, 4, 8, 12, 16, 16, 12, 8, 4, + 5, 10, 15, 20, 20, 15, 10, 5, 0, 0, 0, 0, 0, 0, 0, 0}; + +int KNIGHT_BONUS[] = {-10, -30, -10, -10, -10, -10, -30, -10, -10, 0, 0, 0, 0, + 0, 0, -10, -10, 0, 5, 5, 5, 5, 0, -10, -10, 0, + 5, 10, 10, 5, 0, -10, -10, 0, 5, 10, 10, 5, 0, + -10, -10, 0, 5, 5, 5, 5, 0, -10, -10, 0, 0, 0, + 0, 0, 0, -10, -10, -10, -10, -10, -10, -10, -10, -10}; + +int BISHOP_BONUS[] = {-10, -10, -20, -10, -10, -20, -10, -10, -10, 0, 0, 0, 0, + 0, 0, -10, -10, 0, 5, 5, 5, 5, 0, -10, -10, 0, + 5, 10, 10, 5, 0, -10, -10, 0, 5, 10, 10, 5, 0, + -10, -10, 0, 5, 5, 5, 5, 0, -10, -10, 0, 0, 0, + 0, 0, 0, -10, -10, -10, -10, -10, -10, -10, -10, -10}; + +int KING_BONUS[] = {0, 20, 40, -20, 0, -20, 40, 20, -20, -20, -20, -20, -20, + -20, -20, -20, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, + -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, + -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, + -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40}; + +int KING_ENDGAME_BONUS[] = {0, 10, 20, 30, 30, 20, 10, 0, 10, 20, 30, 40, 40, 30, 20, 10, + 20, 30, 40, 50, 50, 40, 30, 20, 30, 40, 50, 60, 60, 50, 40, 30, + 30, 40, 50, 60, 60, 50, 40, 30, 20, 30, 40, 50, 50, 40, 30, 20, + 10, 20, 30, 40, 40, 30, 20, 10, 0, 10, 20, 30, 30, 20, 10, 0}; + +int FLIP_VERTICAL[] = {56, 57, 58, 59, 60, 61, 62, 63, 48, 49, 50, 51, 52, 53, 54, 55, + 40, 41, 42, 43, 44, 45, 46, 47, 32, 33, 34, 35, 36, 37, 38, 39, + 24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22, 23, + 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7}; + +void getInitialGame(Game* game) { + game->position.board = INITIAL_BOARD; + game->position.toMove = WHITE; + game->position.epSquare = -1; + game->position.castlingRights = CASTLE_KINGSIDE_WHITE | CASTLE_QUEENSIDE_WHITE | + CASTLE_KINGSIDE_BLACK | CASTLE_QUEENSIDE_BLACK; + game->position.halfmoveClock = 0; + game->position.fullmoveNumber = 1; + + game->moveListLen = 0; + memset(game->moveList, 0, MAX_PLYS_PER_GAME * sizeof(int)); + memset(game->positionHistory, 0, MAX_PLYS_PER_GAME * MAX_FEN_LEN * sizeof(char)); + memcpy(game->positionHistory[0], INITIAL_FEN, sizeof(INITIAL_FEN)); +} + +void getFenGame(Game* game, char fen[]) { + int fenLen = loadFen(&(game->position), fen); + + game->moveListLen = 0; + memset(game->moveList, 0, MAX_PLYS_PER_GAME * sizeof(int)); + memset(game->positionHistory, 0, MAX_PLYS_PER_GAME * MAX_FEN_LEN * sizeof(char)); + memcpy(game->positionHistory[0], fen, fenLen); +} + +void insertPiece(Board* board, Bitboard position, char pieceCode) { + switch(pieceCode) { + case 'P': + board->whitePawns |= position; + break; + case 'N': + board->whiteKnights |= position; + break; + case 'B': + board->whiteBishops |= position; + break; + case 'R': + board->whiteRooks |= position; + break; + case 'Q': + board->whiteQueens |= position; + break; + case 'K': + board->whiteKing |= position; + break; + + case 'p': + board->blackPawns |= position; + break; + case 'n': + board->blackKnights |= position; + break; + case 'b': + board->blackBishops |= position; + break; + case 'r': + board->blackRooks |= position; + break; + case 'q': + board->blackQueens |= position; + break; + case 'k': + board->blackKing |= position; + break; + } +} + +int loadFen(Position* position, char fen[]) { + // ===== BOARD ===== + position->board = EMPTY_BOARD; + + int rank = 7; + int boardPos = rank * 8; + char* charPos = fen; + + char pieceCode = *(charPos); + + while(pieceCode != ' ') { + if(pieceCode == '/') { + rank--; + boardPos = rank * 8; + } else if(isdigit(pieceCode)) { + int emptySquares = atoi(charPos); + boardPos += emptySquares; + } else { + insertPiece(&(position->board), index2bb(boardPos++), pieceCode); + } + + pieceCode = *(++charPos); + } + + // ===== TO MOVE ===== + char* nextFenField = strchr(fen, ' ') + 1; + + if(*nextFenField == 'b') { + position->toMove = BLACK; + } else { + position->toMove = WHITE; + } + + // ===== CASTLING RIGHTS ===== + nextFenField = strchr(nextFenField, ' ') + 1; + + position->castlingRights = 0; + if(strchr(nextFenField, 'K')) position->castlingRights |= CASTLE_KINGSIDE_WHITE; + if(strchr(nextFenField, 'Q')) position->castlingRights |= CASTLE_QUEENSIDE_WHITE; + if(strchr(nextFenField, 'k')) position->castlingRights |= CASTLE_KINGSIDE_BLACK; + if(strchr(nextFenField, 'q')) position->castlingRights |= CASTLE_QUEENSIDE_BLACK; + + // ===== EN PASSANT ===== + nextFenField = strchr(nextFenField, ' ') + 1; + + if(*nextFenField == '-') { + position->epSquare = -1; + } else { + position->epSquare = str2index(nextFenField); + } + + // ===== HALF MOVE CLOCK ===== + if(!strchr(nextFenField, ' ')) { + position->halfmoveClock = 0; + position->fullmoveNumber = 1; + return 1 + nextFenField - fen; + } + nextFenField = strchr(nextFenField, ' ') + 1; + + position->halfmoveClock = atoi(nextFenField); + + // ===== FULL MOVE NUMBER ===== + if(!strchr(nextFenField, ' ')) { + position->fullmoveNumber = 1; + return 1 + nextFenField - fen; + } + nextFenField = strchr(nextFenField, ' ') + 1; + + position->fullmoveNumber = atoi(nextFenField); + + return 1 + nextFenField - fen; +} + +int toFen(char* fen, Position* position) { + int charCount = toMinFen(fen, position); + fen[charCount - 1] = ' '; + + // ===== HALF MOVE CLOCK ===== + snprintf(&fen[charCount++], sizeof(&fen[charCount++]), "%d", position->halfmoveClock); + if(position->halfmoveClock >= 10) { + charCount++; + if(position->halfmoveClock >= 100) { + charCount++; + } + } + fen[charCount++] = ' '; + + // ===== FULL MOVE NUMBER ===== + snprintf(&fen[charCount++], sizeof(&fen[charCount++]), "%d", position->fullmoveNumber); + if(position->fullmoveNumber >= 10) { + charCount++; + if(position->fullmoveNumber >= 100) { + charCount++; + } + } + fen[charCount++] = '\0'; + + return charCount; +} + +int toMinFen(char* fen, Position* position) { + int charCount = 0; + + // ===== BOARD ===== + int rank = 7; + int file = 0; + int* emptyCount = 0; + + Bitboard empties = getEmptySquares(&(position->board)); + Bitboard bb; + + while(rank >= 0) { + bb = index2bb(8 * rank + file); + + if(bb & empties) { + emptyCount++; + } else { + if(emptyCount != 0) { + snprintf(&fen[charCount++], 2, "%ls", emptyCount); + emptyCount = 0; + } + fen[charCount++] = bb2char(bb, &(position->board)); + } + + file++; + if(file > 7) { + if(emptyCount != 0) { + snprintf(&fen[charCount++], 2, "%ls", emptyCount); + emptyCount = 0; + } + file = 0; + rank--; + fen[charCount++] = '/'; + } + } + fen[charCount - 1] = ' '; + + // ===== TO MOVE ===== + if(position->toMove == BLACK) { + fen[charCount++] = 'b'; + } else { + fen[charCount++] = 'w'; + } + fen[charCount++] = ' '; + + // ===== CASTLING RIGHTS ===== + if(position->castlingRights == 0) { + fen[charCount++] = '-'; + } else { + if(position->castlingRights & CASTLE_KINGSIDE_WHITE) { + fen[charCount++] = 'K'; + } + if(position->castlingRights & CASTLE_QUEENSIDE_WHITE) { + fen[charCount++] = 'Q'; + } + if(position->castlingRights & CASTLE_KINGSIDE_BLACK) { + fen[charCount++] = 'k'; + } + if(position->castlingRights & CASTLE_QUEENSIDE_BLACK) { + fen[charCount++] = 'q'; + } + } + fen[charCount++] = ' '; + + // ===== EN PASSANT ===== + if(position->epSquare == -1) { + fen[charCount++] = '-'; + } else { + fen[charCount++] = getFile(position->epSquare); + fen[charCount++] = getRank(position->epSquare); + } + fen[charCount++] = '\0'; + + return charCount; +} + +void getMovelistGame(Game* game, char moves[]) { + getInitialGame(game); + + for(int i = 0; i < strlen(moves) - 3; i += 5) { + makeMove(game, parseMove(&moves[i])); + if(moves[i + 5] == ' ') i++; // FIXME Queening + } +} + +// ========= UTILITY ========= + +BOOL fromInitial(Game* game) { + if(strcmp(game->positionHistory[0], INITIAL_FEN) == 0) + return TRUE; + else + return FALSE; +} + +Bitboard index2bb(int index) { + Bitboard bb = 1; + return bb << index; +} + +int str2index(char* str) { + int i, file_num = 0, rank_num = 0; + for(i = 0; i < 8; i++) { + if(str[0] == FILES[i]) file_num = i; + if(str[1] == RANKS[i]) rank_num = i; + } + return 8 * rank_num + file_num; +} + +Bitboard str2bb(char* str) { + return index2bb(str2index(str)); +} + +BOOL isSet(Bitboard bb, int index) { + if(bb & index2bb(index)) + return TRUE; + else + return FALSE; +} + +Bitboard lsb(Bitboard bb) { + int i; + for(i = 0; i < NUM_SQUARES; i++) { + Bitboard bit = index2bb(i); + if(bb & bit) return bit; + } + return 0; +} + +Bitboard msb(Bitboard bb) { + int i; + for(i = 0; i < NUM_SQUARES; i++) { + Bitboard bit = index2bb(63 - i); + if(bb & bit) return bit; + } + return 0; +} + +int bb2index(Bitboard bb) { + int i; + for(i = 0; i < NUM_SQUARES; i++) { + Bitboard bit = index2bb(i); + if(bb & bit) return i; + } + return -1; +} + +char* movelist2str(Game* game) { + char* movestr = NULL; + + if(game->moveListLen == 0) { + movestr = (char*)malloc(sizeof(char)); + movestr[0] = 0; + return movestr; + } + + movestr = (char*)malloc(5 * game->moveListLen); + + int i; + for(i = 0; i < game->moveListLen; i++) { + int leaving = getFrom(game->moveList[i]); + int arriving = getTo(game->moveList[i]); + movestr[5 * i] = getFile(leaving); + movestr[5 * i + 1] = getRank(leaving); + movestr[5 * i + 2] = getFile(arriving); + movestr[5 * i + 3] = getRank(arriving); + movestr[5 * i + 4] = ' '; + } + + movestr[5 * game->moveListLen - 1] = 0; + + return movestr; +} + +Move getLastMove(Game* game) { + if(game->moveListLen == 0) + return 0; + else + return game->moveList[game->moveListLen - 1]; +} + +BOOL startsWith(const char* str, const char* pre) { + size_t lenpre = strlen(pre), lenstr = strlen(str); + + if(lenpre > lenstr) return FALSE; + + return strncmp(pre, str, lenpre) == 0 ? TRUE : FALSE; +} + +int countBookOccurrences(Game* game) { + FILE* fp = fopen("book.txt", "r"); + + if(fp == NULL) return 0; + + char* moveList = movelist2str(game); + char* line = (char*)malloc(sizeof(char) * MAX_BOOK_ENTRY_LEN); + int charPos = 0, occurrences = 0; + + while(TRUE) { + char ch = getc(fp); + line[charPos++] = ch; + + if(ch == '\n' || ch == EOF) { + line[charPos - 1] = '\0'; + + if(startsWith(line, moveList) && strlen(line) > strlen(moveList) + 4) { + occurrences++; + } + + if(ch == EOF) break; + + charPos = 0; + } + } + + fclose(fp); + free(line); + free(moveList); + + return occurrences; +} + +Move getBookMove(Game* game) { + Move move = 0; + int moveNum = rand() % countBookOccurrences(game); + + FILE* fp = fopen("book.txt", "r"); + + if(fp == NULL) return 0; + + char* moveList = movelist2str(game); + char* line = (char*)malloc(sizeof(char) * MAX_BOOK_ENTRY_LEN); + int charPos = 0, occurrences = 0; + + while(TRUE) { + char ch = getc(fp); + line[charPos++] = ch; + + if(ch == '\n') { + line[charPos] = '\0'; + + if(startsWith(line, moveList)) { + if(occurrences == moveNum) { + int ind = game->moveListLen * 5; + move = parseMove(&line[ind]); + break; + } + occurrences++; + } + + charPos = 0; + } + } + + fclose(fp); + free(line); + free(moveList); + + return move; +} + +char getFile(int position) { + int file = position % 8; + return FILES[file]; +} + +char getRank(int position) { + int rank = (int)(position / 8); + return RANKS[rank]; +} + +Move generateMove(int leavingSquare, int arrivingSquare) { + int leaving = (leavingSquare << 8); + int arriving = arrivingSquare; + return (Move)(leaving + arriving); +} + +int getFrom(Move move) { + return (move >> 8) & 0xFF; +} + +int getTo(Move move) { + return move & 0xFF; +} + +int bb2piece(Bitboard position, Board* board) { + if(position & board->whitePawns) return WHITE | PAWN; + if(position & board->whiteKnights) return WHITE | KNIGHT; + if(position & board->whiteBishops) return WHITE | BISHOP; + if(position & board->whiteRooks) return WHITE | ROOK; + if(position & board->whiteQueens) return WHITE | QUEEN; + if(position & board->whiteKing) return WHITE | KING; + if(position & board->blackPawns) return BLACK | PAWN; + if(position & board->blackKnights) return BLACK | KNIGHT; + if(position & board->blackBishops) return BLACK | BISHOP; + if(position & board->blackRooks) return BLACK | ROOK; + if(position & board->blackQueens) return BLACK | QUEEN; + if(position & board->blackKing) return BLACK | KING; + + return EMPTY; +} + +char bb2char(Bitboard position, Board* board) { + if(position & board->whitePawns) return 'P'; + if(position & board->whiteKnights) return 'N'; + if(position & board->whiteBishops) return 'B'; + if(position & board->whiteRooks) return 'R'; + if(position & board->whiteQueens) return 'Q'; + if(position & board->whiteKing) return 'K'; + if(position & board->blackPawns) return 'p'; + if(position & board->blackKnights) return 'n'; + if(position & board->blackBishops) return 'b'; + if(position & board->blackRooks) return 'r'; + if(position & board->blackQueens) return 'q'; + if(position & board->blackKing) return 'k'; + + return '?'; +} + +char* bb2str(Bitboard position, Board* board) { + if((position & board->whitePawns) | (position & board->blackPawns)) return "Pawn"; + if((position & board->whiteKnights) | (position & board->blackKnights)) return "Knight"; + if((position & board->whiteBishops) | (position & board->blackBishops)) return "Bishop"; + if((position & board->whiteRooks) | (position & board->blackRooks)) return "Rook"; + if((position & board->whiteQueens) | (position & board->blackQueens)) return "Queen"; + if((position & board->whiteKing) | (position & board->blackKing)) return "King"; + + return "?"; +} + +void printBitboard(Bitboard bitboard) { + int rank, file; + + printf("\n"); + for(rank = 0; rank < 8; rank++) { + printf("%d", 8 - rank); + for(file = 0; file < 8; file++) { + if(bitboard >> (file + (7 - rank) * 8) & 1) { + printf(" #"); + } else { + printf(" ."); + } + } + printf("\n"); + } + printf(" a b c d e f g h\n"); + fflush(stdout); +} + +char getPieceChar(Bitboard position, Board* board) { + if(position & board->whiteKing) return 'K'; + if(position & board->whiteQueens) return 'Q'; + if(position & board->whiteRooks) return 'R'; + if(position & board->whiteKnights) return 'N'; + if(position & board->whiteBishops) return 'B'; + if(position & board->whitePawns) return 'P'; + if(position & board->blackKing) return 'k'; + if(position & board->blackQueens) return 'q'; + if(position & board->blackRooks) return 'r'; + if(position & board->blackKnights) return 'n'; + if(position & board->blackBishops) return 'b'; + if(position & board->blackPawns) return 'p'; + return '.'; +} + +void printBoard(Board* board) { + int rank, file; + + printf("\n"); + for(rank = 0; rank < 8; rank++) { + printf("%d", 8 - rank); + for(file = 0; file < 8; file++) { + printf(" %c", getPieceChar((FILES_BB[file] & RANKS_BB[7 - rank]), board)); + } + printf("\n"); + } + printf(" a b c d e f g h\n"); + + fflush(stdout); +} + +void printGame(Game* game) { + printf("Game -> %p (%lu)", game, sizeof(*game)); + // printBoard(&(game->position.board)); + // printf("board -> %p (%lu)\n", &(game->position.board), sizeof(game->position.board)); + // printf("toMove = %d -> %p (%lu)\n", game->position.toMove, &game->position.toMove, sizeof(game->position.toMove)); + // printf("ep = %d -> %p (%lu)\n", game->position.epSquare, &game->position.epSquare, sizeof(game->position.epSquare)); + // printf("castle rights = %d -> %p (%lu)\n", game->position.castlingRights, &game->position.castlingRights, sizeof(game->position.castlingRights)); + // printf("half clock = %d -> %p (%lu)\n", game->position.halfmoveClock, &game->position.halfmoveClock, sizeof(game->position.halfmoveClock)); + // printf("full num = %d -> %p (%lu)\n", game->position.fullmoveNumber, &game->position.fullmoveNumber, sizeof(game->position.fullmoveNumber)); + + // printf("moveListLen = %d -> %p (%lu)\n", game->moveListLen, &game->moveListLen, sizeof(game->moveListLen)); + // printf("moveList -> %p (%lu)\n", game->moveList, sizeof(game->moveList)); + // printf("positionHistory -> %p (%lu)\n", game->positionHistory, sizeof(game->positionHistory)); + // fflush(stdout); +} + +Bitboard not(Bitboard bb) { + return ~bb & ALL_SQUARES; +} + +char opponent(char color) { + switch(color) { + case WHITE: + return BLACK; + case BLACK: + return WHITE; + } + return -1; +} + +int countBits(Bitboard bb) { + int bitCount = 0; + Bitboard position = 1; + + for(int i = 0; i < NUM_SQUARES; i++) { + if(position & bb) { + bitCount++; + } + position = position << 1; + } + + return bitCount; +} + +void sortNodes(Node* sortedNodes, Node* nodes, int len, char color) { + Node nodeBuffer[len]; + + int i, j; + BOOL sorted; + for(i = 0; i < len; i++) { + sorted = FALSE; + + for(j = 0; j < i; j++) { + if((color == WHITE && nodes[i].score > sortedNodes[j].score) || + (color == BLACK && nodes[i].score < sortedNodes[j].score)) { + sorted = TRUE; + memcpy(nodeBuffer, &sortedNodes[j], (i - j) * sizeof(Node)); + memcpy(&sortedNodes[j + 1], nodeBuffer, (i - j) * sizeof(Node)); + sortedNodes[j] = nodes[i]; + break; + } + } + + if(sorted == FALSE) { + sortedNodes[i] = nodes[i]; + } + } +} + +void printMove(Move move) { + printf( + "%c%c to %c%c", + getFile(getFrom(move)), + getRank(getFrom(move)), + getFile(getTo(move)), + getRank(getTo(move))); +} + +void printFullMove(Move move, Board* board) { + Bitboard leavingBB = index2bb(getFrom(move)); + printf("%s from ", bb2str(leavingBB, board)); + printMove(move); +} + +void printLegalMoves(Position* position) { + int i; + Move moves[MAX_BRANCHING_FACTOR]; + int moveCount = legalMoves(moves, position, position->toMove); + for(i = 0; i < moveCount; i++) { + printf("%2d. ", i + 1); + printFullMove(moves[i], &(position->board)); + // printMove(moves[i]); + printf("\n"); + } + fflush(stdout); +} + +void printNode(Node node) { + printMove(node.move); + printf(": %d", node.score); +} + +void getTimestamp(char* timestamp) { + time_t timer; + struct tm* tm_info; + + time(&timer); + tm_info = localtime(&timer); + + strftime(timestamp, 20, "%Y-%m-%d_%H.%M.%S", tm_info); +} + +void dumpContent(Game* game) { + char* movelist = movelist2str(game); + + char filename[50]; + sprintf(filename, "chess_game_"); + getTimestamp(&filename[strlen(filename)]); + sprintf(&filename[strlen(filename)], ".txt"); + + FILE* file = fopen(filename, "w+"); + + fprintf(file, "movelist = %s\nposition history:\n", movelist); + + int i; + for(i = 0; i < game->moveListLen + 1; i++) fprintf(file, "%s\n", game->positionHistory[i]); + + free(movelist); + fclose(file); + + printf("Dumped game content to: %s\n", filename); + fflush(stdout); +} + +void dumpPGN(Game* game, char color, BOOL hasAI) { + char filename[50]; + sprintf(filename, "chess_game_"); + getTimestamp(&filename[strlen(filename)]); + sprintf(&filename[strlen(filename)], ".pgn"); + + FILE* file = fopen(filename, "w+"); + + char date[12]; + time_t timer; + struct tm* tm_info; + time(&timer); + tm_info = localtime(&timer); + strftime(date, 11, "%Y.%m.%d", tm_info); + + fprintf(file, "[Event \"Casual Game\"]\n"); + fprintf(file, "[Site \"?\"]\n"); + fprintf(file, "[Date \"%s\"]\n", date); + fprintf(file, "[Round \"-\"]\n"); + + if(hasAI) { + if(color == WHITE) { + fprintf(file, "[White \"%s\"]\n", HUMAN_NAME); + fprintf(file, "[Black \"%s\"]\n", ENGINE_NAME); + } else { + fprintf(file, "[White \"%s\"]\n", ENGINE_NAME); + fprintf(file, "[Black \"%s\"]\n", HUMAN_NAME); + } + } else { + fprintf(file, "[White \"Unknown Human Player\"]\n"); + fprintf(file, "[Black \"Unknown Human Player\"]\n"); + } + + if(hasGameEnded(&game->position)) { + if(endNodeEvaluation(&game->position) == winScore(WHITE)) { + fprintf(file, "[Result \"1-0\"]\n"); + } else if(endNodeEvaluation(&game->position) == winScore(BLACK)) { + fprintf(file, "[Result \"0-1\"]\n"); + } else if(endNodeEvaluation(&game->position) == 0) { + fprintf(file, "[Result \"1/2-1/2\"]\n"); + } + } else { + fprintf(file, "[Result \"*\"]\n"); + } + + if(strcmp(game->positionHistory[0], INITIAL_FEN) == 0) { + fprintf(file, "[Variant \"Standard\"]\n"); + } else { + fprintf(file, "[Variant \"From Position\"]\n"); + fprintf(file, "[FEN \"%s\"]\n", game->positionHistory[0]); + } + + fprintf(file, "[PlyCount \"%d\"]\n\n", game->moveListLen); + + int i; + char ply[8]; + for(i = 0; i < game->moveListLen; i++) { + if(i % 2 == 0) fprintf(file, "%d. ", 1 + (i / 2)); + move2str(ply, game, i); + fprintf(file, "%s ", ply); + } + + fclose(file); + + printf("Dumped game pgn to: %s\n", filename); + fflush(stdout); +} + +void move2str(char* str, Game* game, int moveNumber) { + Position posBefore, posAfter; + loadFen(&posBefore, game->positionHistory[moveNumber]); + loadFen(&posAfter, game->positionHistory[moveNumber + 1]); + Move move = game->moveList[moveNumber]; + int leavingSquare = getFrom(move); + int arrivingSquare = getTo(move); + + Bitboard leavingBB = index2bb(leavingSquare); + Bitboard arrivingBB = index2bb(arrivingSquare); + + int length = 0; + if((leavingBB & (posBefore.board.whiteKing | posBefore.board.blackKing)) && + abs(leavingSquare - arrivingSquare) == 2) { // if castling + if(arrivingBB & FILE_G) { + sprintf(str, "O-O"); + length += 3; + } else if(arrivingBB & FILE_C) { + sprintf(str, "O-O-O"); + length += 5; + } + } else { // if not castling + if(leavingBB & (posBefore.board.whitePawns | posBefore.board.blackPawns)) { + if(arrivingBB & getOccupiedSquares(&(posBefore.board))) { + str[length++] = getFile(leavingSquare); + } + } else { + str[length++] = bb2char(leavingBB, &(posBefore.board)); + } + + if(isAmbiguous(&posBefore, move)) { + if(countBits(getTwinPieces(leavingBB, &(posBefore.board)) & fileFilter(leavingBB)) == + 1) { + str[length++] = getFile(leavingSquare); + } else { + str[length++] = getRank(leavingSquare); + } + } + + if(bb2piece(arrivingBB, &(posBefore.board)) != EMPTY) { + str[length++] = 'x'; + } + + str[length++] = getFile(arrivingSquare); + str[length++] = getRank(arrivingSquare); + } + + if(isCheckmate(&posAfter)) { + str[length++] = '#'; + } else if(isCheck(&(posAfter.board), posAfter.toMove)) { + str[length++] = '+'; + } + + str[length++] = 0; +} + +BOOL isAmbiguous(Position* position, Move move) { + Move moves[MAX_BRANCHING_FACTOR]; + int moveCount = legalMoves(moves, position, position->toMove); + Bitboard targetBB = index2bb(getTo(move)); + Bitboard attackerBB = index2bb(getFrom(move)); + int attackerPiece = bb2piece(attackerBB, &(position->board)); + + int attackerCount = 0; + for(int i = 0; i < moveCount; i++) { + Bitboard tgtBB = index2bb(getTo(moves[i])); + Bitboard atkBB = index2bb(getFrom(moves[i])); + + if(attackerPiece == bb2piece(atkBB, &(position->board)) && (targetBB & tgtBB)) { + attackerCount++; + } + } + + return attackerCount > 1; +} + +unsigned long hashPosition(Position* position) { + char fen[MAX_FEN_LEN]; + toMinFen(fen, position); + + unsigned long hash = 5381; + int c, i = 0; + + while((c = fen[i++])) { + hash = ((hash << 5) + hash) + c; + } + + return hash; +} + +void writeToHashFile(Position* position, int evaluation, int depth) { + FILE* fp = fopen("hashfile", "a"); + + if(fp == NULL) return; + + char fen[MAX_FEN_LEN]; + toMinFen(fen, position); + + fprintf(fp, "%08lx %d %d %s\n", hashPosition(position), depth, evaluation, fen); + fclose(fp); +} + +// ====== BOARD FILTERS ====== + +Bitboard getColoredPieces(Board* board, char color) { + if(color == WHITE) { + return board->whiteKing | board->whiteQueens | board->whiteRooks | board->whiteKnights | + board->whiteBishops | board->whitePawns; + } else { + return board->blackKing | board->blackQueens | board->blackRooks | board->blackKnights | + board->blackBishops | board->blackPawns; + } +} + +Bitboard getEmptySquares(Board* board) { + return not(getOccupiedSquares(board)); +} + +Bitboard getOccupiedSquares(Board* board) { + return getColoredPieces(board, WHITE) | getColoredPieces(board, BLACK); +} + +Bitboard getTwinPieces(Bitboard position, Board* board) { + if(position & board->whiteKing) return board->whiteKing; + if(position & board->whiteQueens) return board->whiteQueens; + if(position & board->whiteRooks) return board->whiteRooks; + if(position & board->whiteKnights) return board->whiteKnights; + if(position & board->whiteBishops) return board->whiteBishops; + if(position & board->whitePawns) return board->whitePawns; + + if(position & board->blackKing) return board->blackKing; + if(position & board->blackQueens) return board->blackQueens; + if(position & board->blackRooks) return board->blackRooks; + if(position & board->blackKnights) return board->blackKnights; + if(position & board->blackBishops) return board->blackBishops; + if(position & board->blackPawns) return board->blackPawns; + + return position; +} + +Bitboard fileFilter(Bitboard positions) { + Bitboard filter = 0; + int i; + + for(i = 0; i < 8; i++) + if(positions & FILES_BB[i]) filter |= FILES_BB[i]; + return filter; +} + +Bitboard rankFilter(Bitboard positions) { + Bitboard filter = 0; + int i; + + for(i = 0; i < 8; i++) + if(positions & RANKS_BB[i]) filter |= RANKS_BB[i]; + return filter; +} + +// ======= DIRECTIONS ======== + +Bitboard east(Bitboard bb) { + return (bb << 1) & 0xfefefefefefefefe; // not(FILE_A) +} + +Bitboard west(Bitboard bb) { + return (bb >> 1) & 0x7f7f7f7f7f7f7f7f; // not(FILE_H) +} + +Bitboard north(Bitboard bb) { + return (bb << 8) & 0xffffffffffffff00; // not(RANK_1) +} + +Bitboard south(Bitboard bb) { + return (bb >> 8) & 0x00ffffffffffffff; // not(RANK_8) +} + +Bitboard NE(Bitboard bb) { + return (bb << 9) & 0xfefefefefefefe00; // not(RANK_1 | FILE_A) +} + +Bitboard NW(Bitboard bb) { + return (bb << 7) & 0x7f7f7f7f7f7f7f00; // not(RANK_1 | FILE_H) +} + +Bitboard SE(Bitboard bb) { + return (bb >> 7) & 0x00fefefefefefefe; // not(RANK_8 | FILE_A) +} + +Bitboard SW(Bitboard bb) { + return (bb >> 9) & 0x007f7f7f7f7f7f7f; // not(RANK_8 | FILE_H) +} + +Bitboard WNW(Bitboard moving_piece) { + return (moving_piece << 6) & 0x3f3f3f3f3f3f3f00; // not(RANK_1 | FILE_G | FILE_H) +} + +Bitboard ENE(Bitboard moving_piece) { + return (moving_piece << 10) & 0xfcfcfcfcfcfcfc00; // not(RANK_1 | FILE_A | FILE_B) +} + +Bitboard NNW(Bitboard moving_piece) { + return (moving_piece << 15) & 0x7f7f7f7f7f7f0000; // not(RANK_1 | RANK_2 | FILE_H) +} + +Bitboard NNE(Bitboard moving_piece) { + return (moving_piece << 17) & 0xfefefefefefe0000; // not(RANK_1 | RANK_2 | FILE_A) +} + +Bitboard ESE(Bitboard moving_piece) { + return (moving_piece >> 6) & 0x00fcfcfcfcfcfcfc; // not(RANK_8| FILE_A | FILE_B) +} + +Bitboard WSW(Bitboard moving_piece) { + return (moving_piece >> 10) & 0x003f3f3f3f3f3f3f; // not(RANK_8 | FILE_G | FILE_H) +} + +Bitboard SSE(Bitboard moving_piece) { + return (moving_piece >> 15) & 0x0000fefefefefefe; // not(RANK_7 | RANK_8 | FILE_A) +} + +Bitboard SSW(Bitboard moving_piece) { + return (moving_piece >> 17) & 0x00007f7f7f7f7f7f; // not(RANK_7 | RANK_8 | FILE_H) +} + +// ========== PAWN =========== + +Bitboard getPawns(Board* board) { + return board->whitePawns | board->blackPawns; +} + +Bitboard pawnSimplePushes(Bitboard moving_piece, Board* board, char color) { + switch(color) { + case WHITE: + return north(moving_piece) & getEmptySquares(board); + case BLACK: + return south(moving_piece) & getEmptySquares(board); + } + return 0; +} + +Bitboard pawnDoublePushes(Bitboard moving_piece, Board* board, char color) { + switch(color) { + case WHITE: + return north(pawnSimplePushes(moving_piece, board, color)) & + (getEmptySquares(board) & RANK_4); + case BLACK: + return south(pawnSimplePushes(moving_piece, board, color)) & + (getEmptySquares(board) & RANK_5); + } + return 0; +} + +Bitboard pawnPushes(Bitboard moving_piece, Board* board, char color) { + return pawnSimplePushes(moving_piece, board, color) | + pawnDoublePushes(moving_piece, board, color); +} + +Bitboard pawnEastAttacks(Bitboard moving_piece, Board* board, char color) { + switch(color) { + case WHITE: + return NE(moving_piece); + case BLACK: + return SE(moving_piece); + } + return 0; +} + +Bitboard pawnWestAttacks(Bitboard moving_piece, Board* board, char color) { + switch(color) { + case WHITE: + return NW(moving_piece); + case BLACK: + return SW(moving_piece); + } + return 0; +} + +Bitboard pawnAttacks(Bitboard moving_piece, Board* board, char color) { + return pawnEastAttacks(moving_piece, board, color) | + pawnWestAttacks(moving_piece, board, color); +} + +Bitboard pawnSimpleCaptures(Bitboard moving_piece, Board* board, char color) { + return pawnAttacks(moving_piece, board, color) & getColoredPieces(board, opponent(color)); +} + +Bitboard pawnEpCaptures(Bitboard moving_piece, Position* position, char color) { + if(position->epSquare == -1) return 0; + + Bitboard valid_ep_square = 0; + + switch(color) { + case WHITE: + valid_ep_square = index2bb(position->epSquare) & RANK_6; + break; + case BLACK: + valid_ep_square = index2bb(position->epSquare) & RANK_3; + break; + } + + return pawnAttacks(moving_piece, &(position->board), color) & valid_ep_square; +} + +Bitboard pawnCaptures(Bitboard moving_piece, Position* position, char color) { + return pawnSimpleCaptures(moving_piece, &(position->board), color) | + pawnEpCaptures(moving_piece, position, color); +} + +Bitboard pawnMoves(Bitboard moving_piece, Position* position, char color) { + return pawnPushes(moving_piece, &(position->board), color) | + pawnCaptures(moving_piece, position, color); +} + +BOOL isDoublePush(int leaving, int arriving) { + if((index2bb(leaving) & RANK_2) && (index2bb(arriving) & RANK_4)) return TRUE; + if((index2bb(leaving) & RANK_7) && (index2bb(arriving) & RANK_5)) return TRUE; + return FALSE; +} + +char getEpSquare(int leaving) { + if(index2bb(leaving) & RANK_2) return leaving + 8; + if(index2bb(leaving) & RANK_7) return leaving - 8; + return -1; +} + +BOOL isDoubledPawn(Bitboard position, Board* board, char color) { + if(color == WHITE) { + if(countBits(board->whitePawns & fileFilter(position)) > 1) return TRUE; + } else { + if(countBits(board->blackPawns & fileFilter(position)) > 1) return TRUE; + } + + return FALSE; +} + +BOOL isIsolatedPawn(Bitboard position, Board* board, char color) { + Bitboard sideFiles = fileFilter(east(position) | west(position)); + + if(color == WHITE) { + if(countBits(board->whitePawns & sideFiles) == 0) return TRUE; + } else { + if(countBits(board->blackPawns & sideFiles) == 0) return TRUE; + } + + return FALSE; +} + +BOOL isBackwardsPawn(Bitboard position, Board* board, char color) { + Bitboard squaresFilter = east(position) | west(position); + + if(color == BLACK) { + squaresFilter |= northRay(squaresFilter); + if(countBits(board->blackPawns & squaresFilter) == 0) return TRUE; + } else { + squaresFilter |= southRay(squaresFilter); + if(countBits(board->whitePawns & squaresFilter) == 0) return TRUE; + } + + return FALSE; +} + +BOOL isPassedPawn(Bitboard position, Board* board, char color) { + Bitboard squaresFilter = 0; + + if(color == BLACK) { + squaresFilter |= southRay(east(position)) | southRay(west(position)) | southRay(position); + if(countBits(board->whitePawns & squaresFilter) == 0) return TRUE; + } else { + squaresFilter |= northRay(east(position)) | northRay(west(position)) | northRay(position); + if(countBits(board->blackPawns & squaresFilter) == 0) return TRUE; + } + + return FALSE; +} + +BOOL isOpenFile(Bitboard position, Board* board) { + if(countBits(getPawns(board) & fileFilter(position)) == 0) return TRUE; + return FALSE; +} + +BOOL isSemiOpenFile(Bitboard position, Board* board) { + if(countBits(getPawns(board) & fileFilter(position)) == 1) return TRUE; + return FALSE; +} + +// ========== KNIGHT ========= + +Bitboard getKnights(Board* board) { + return board->whiteKnights | board->blackKnights; +} + +Bitboard knightAttacks(Bitboard moving_piece) { + return NNE(moving_piece) | ENE(moving_piece) | NNW(moving_piece) | WNW(moving_piece) | + SSE(moving_piece) | ESE(moving_piece) | SSW(moving_piece) | WSW(moving_piece); +} + +Bitboard knightMoves(Bitboard moving_piece, Board* board, char color) { + return knightAttacks(moving_piece) & not(getColoredPieces(board, color)); +} + +Bitboard knightFill(Bitboard moving_piece, int jumps) { + Bitboard fill = moving_piece; + int i; + for(i = 0; i < jumps; i++) { + fill |= knightAttacks(fill); + } + return fill; +} + +int knightDistance(Bitboard leaving_square, Bitboard arriving_square) { + Bitboard fill = leaving_square; + int dist = 0; + + while((fill & arriving_square) == 0) { + dist++; + fill |= knightAttacks(fill); + } + return dist; +} + +// ========== KING =========== + +Bitboard getKing(Board* board, char color) { + if(color == WHITE) { + return board->whiteKing; + } else { + return board->blackKing; + } +} + +Bitboard kingAttacks(Bitboard moving_piece) { + Bitboard kingAtks = moving_piece | east(moving_piece) | west(moving_piece); + kingAtks |= north(kingAtks) | south(kingAtks); + return kingAtks & not(moving_piece); +} + +Bitboard kingMoves(Bitboard moving_piece, Board* board, char color) { + return kingAttacks(moving_piece) & not(getColoredPieces(board, color)); +} + +BOOL canCastleKingside(Position* position, char color) { + Bitboard emptyPositions = getEmptySquares(&(position->board)); + + switch(color) { + case WHITE: + if((position->castlingRights & CASTLE_KINGSIDE_WHITE) && + (FILE_E & RANK_1 & position->board.whiteKing) && (FILE_F & RANK_1 & emptyPositions) && + (FILE_G & RANK_1 & emptyPositions) && (FILE_H & RANK_1 & position->board.whiteRooks) && + (!isAttacked((FILE_E & RANK_1), &(position->board), opponent(color))) && + (!isAttacked((FILE_F & RANK_1), &(position->board), opponent(color))) && + (!isAttacked((FILE_G & RANK_1), &(position->board), opponent(color)))) + return TRUE; + else + return FALSE; + + case BLACK: + if((position->castlingRights & CASTLE_KINGSIDE_BLACK) && + (FILE_E & RANK_8 & position->board.blackKing) && (FILE_F & RANK_8 & emptyPositions) && + (FILE_G & RANK_8 & emptyPositions) && (FILE_H & RANK_8 & position->board.blackRooks) && + (!isAttacked((FILE_E & RANK_8), &(position->board), opponent(color))) && + (!isAttacked((FILE_F & RANK_8), &(position->board), opponent(color))) && + (!isAttacked((FILE_G & RANK_8), &(position->board), opponent(color)))) + return TRUE; + else + return FALSE; + } + return FALSE; +} + +BOOL canCastleQueenside(Position* position, char color) { + Bitboard emptyPositions = getEmptySquares(&(position->board)); + + switch(color) { + case WHITE: + if((position->castlingRights & CASTLE_QUEENSIDE_WHITE) && + (FILE_A & RANK_1 & position->board.whiteRooks) && (FILE_B & RANK_1 & emptyPositions) && + (FILE_C & RANK_1 & emptyPositions) && (FILE_D & RANK_1 & emptyPositions) && + (FILE_E & RANK_1 & position->board.whiteKing) && + (!isAttacked((FILE_C & RANK_1), &(position->board), opponent(color))) && + (!isAttacked((FILE_D & RANK_1), &(position->board), opponent(color))) && + (!isAttacked((FILE_E & RANK_1), &(position->board), opponent(color)))) + return TRUE; + else + return FALSE; + + case BLACK: + if((position->castlingRights & CASTLE_QUEENSIDE_BLACK) && + (FILE_A & RANK_8 & position->board.blackRooks) && (FILE_B & RANK_8 & emptyPositions) && + (FILE_C & RANK_8 & emptyPositions) && (FILE_D & RANK_8 & emptyPositions) && + (FILE_E & RANK_8 & position->board.blackKing) && + (!isAttacked((FILE_C & RANK_8), &(position->board), opponent(color))) && + (!isAttacked((FILE_D & RANK_8), &(position->board), opponent(color))) && + (!isAttacked((FILE_E & RANK_8), &(position->board), opponent(color)))) + return TRUE; + else + return FALSE; + } + return FALSE; +} + +char removeCastlingRights(char original_rights, char removed_rights) { + return (char)(original_rights & ~(removed_rights)); +} + +// ========== BISHOP ========= + +Bitboard getBishops(Board* board) { + return board->whiteBishops | board->blackBishops; +} + +Bitboard NE_ray(Bitboard bb) { + int i; + Bitboard ray = NE(bb); + + for(i = 0; i < 6; i++) { + ray |= NE(ray); + } + + return ray & ALL_SQUARES; +} + +Bitboard SE_ray(Bitboard bb) { + int i; + Bitboard ray = SE(bb); + + for(i = 0; i < 6; i++) { + ray |= SE(ray); + } + + return ray & ALL_SQUARES; +} + +Bitboard NW_ray(Bitboard bb) { + int i; + Bitboard ray = NW(bb); + + for(i = 0; i < 6; i++) { + ray |= NW(ray); + } + + return ray & ALL_SQUARES; +} + +Bitboard SW_ray(Bitboard bb) { + int i; + Bitboard ray = SW(bb); + + for(i = 0; i < 6; i++) { + ray |= SW(ray); + } + + return ray & ALL_SQUARES; +} + +Bitboard NE_attack(Bitboard single_piece, Board* board, char color) { + Bitboard blocker = lsb(NE_ray(single_piece) & getOccupiedSquares(board)); + if(blocker) { + return NE_ray(single_piece) ^ NE_ray(blocker); + } else { + return NE_ray(single_piece); + } +} + +Bitboard NW_attack(Bitboard single_piece, Board* board, char color) { + Bitboard blocker = lsb(NW_ray(single_piece) & getOccupiedSquares(board)); + if(blocker) { + return NW_ray(single_piece) ^ NW_ray(blocker); + } else { + return NW_ray(single_piece); + } +} + +Bitboard SE_attack(Bitboard single_piece, Board* board, char color) { + Bitboard blocker = msb(SE_ray(single_piece) & getOccupiedSquares(board)); + if(blocker) { + return SE_ray(single_piece) ^ SE_ray(blocker); + } else { + return SE_ray(single_piece); + } +} + +Bitboard SW_attack(Bitboard single_piece, Board* board, char color) { + Bitboard blocker = msb(SW_ray(single_piece) & getOccupiedSquares(board)); + if(blocker) { + return SW_ray(single_piece) ^ SW_ray(blocker); + } else { + return SW_ray(single_piece); + } +} + +Bitboard diagonalAttacks(Bitboard single_piece, Board* board, char color) { + return NE_attack(single_piece, board, color) | SW_attack(single_piece, board, color); +} + +Bitboard antiDiagonalAttacks(Bitboard single_piece, Board* board, char color) { + return NW_attack(single_piece, board, color) | SE_attack(single_piece, board, color); +} + +Bitboard bishopAttacks(Bitboard moving_pieces, Board* board, char color) { + return diagonalAttacks(moving_pieces, board, color) | + antiDiagonalAttacks(moving_pieces, board, color); +} + +Bitboard bishopMoves(Bitboard moving_piece, Board* board, char color) { + return bishopAttacks(moving_piece, board, color) & not(getColoredPieces(board, color)); +} + +// ========== ROOK =========== + +Bitboard getRooks(Board* board) { + return board->whiteRooks | board->blackRooks; +} + +Bitboard northRay(Bitboard moving_pieces) { + Bitboard ray_atks = north(moving_pieces); + + int i; + for(i = 0; i < 6; i++) { + ray_atks |= north(ray_atks); + } + + return ray_atks & ALL_SQUARES; +} + +Bitboard southRay(Bitboard moving_pieces) { + Bitboard ray_atks = south(moving_pieces); + + int i; + for(i = 0; i < 6; i++) { + ray_atks |= south(ray_atks); + } + + return ray_atks & ALL_SQUARES; +} + +Bitboard eastRay(Bitboard moving_pieces) { + Bitboard ray_atks = east(moving_pieces); + + int i; + for(i = 0; i < 6; i++) { + ray_atks |= east(ray_atks); + } + + return ray_atks & ALL_SQUARES; +} + +Bitboard westRay(Bitboard moving_pieces) { + Bitboard ray_atks = west(moving_pieces); + + int i; + for(i = 0; i < 6; i++) { + ray_atks |= west(ray_atks); + } + + return ray_atks & ALL_SQUARES; +} + +Bitboard northAttack(Bitboard single_piece, Board* board, char color) { + Bitboard blocker = lsb(northRay(single_piece) & getOccupiedSquares(board)); + + if(blocker) + return northRay(single_piece) ^ northRay(blocker); + else + return northRay(single_piece); +} + +Bitboard southAttack(Bitboard single_piece, Board* board, char color) { + Bitboard blocker = msb(southRay(single_piece) & getOccupiedSquares(board)); + + if(blocker) + return southRay(single_piece) ^ southRay(blocker); + else + return southRay(single_piece); +} + +Bitboard fileAttacks(Bitboard single_piece, Board* board, char color) { + return northAttack(single_piece, board, color) | southAttack(single_piece, board, color); +} + +Bitboard eastAttack(Bitboard single_piece, Board* board, char color) { + Bitboard blocker = lsb(eastRay(single_piece) & getOccupiedSquares(board)); + + if(blocker) + return eastRay(single_piece) ^ eastRay(blocker); + else + return eastRay(single_piece); +} + +Bitboard westAttack(Bitboard single_piece, Board* board, char color) { + Bitboard blocker = msb(westRay(single_piece) & getOccupiedSquares(board)); + + if(blocker) + return westRay(single_piece) ^ westRay(blocker); + else + return westRay(single_piece); +} + +Bitboard rankAttacks(Bitboard single_piece, Board* board, char color) { + return eastAttack(single_piece, board, color) | westAttack(single_piece, board, color); +} + +Bitboard rookAttacks(Bitboard moving_piece, Board* board, char color) { + return fileAttacks(moving_piece, board, color) | rankAttacks(moving_piece, board, color); +} + +Bitboard rookMoves(Bitboard moving_piece, Board* board, char color) { + return rookAttacks(moving_piece, board, color) & not(getColoredPieces(board, color)); +} + +// ========== QUEEN ========== + +Bitboard getQueens(Board* board) { + return board->whiteQueens | board->blackQueens; +} + +Bitboard queenAttacks(Bitboard moving_piece, Board* board, char color) { + return rookAttacks(moving_piece, board, color) | bishopAttacks(moving_piece, board, color); +} + +Bitboard queenMoves(Bitboard moving_piece, Board* board, char color) { + return rookMoves(moving_piece, board, color) | bishopMoves(moving_piece, board, color); +} + +// ======== MAKE MOVE ======== + +void clearPositions(Board* board, Bitboard positions) { + board->whiteKing = board->whiteKing & not(positions); + board->whiteQueens = board->whiteQueens & not(positions); + board->whiteRooks = board->whiteRooks & not(positions); + board->whiteKnights = board->whiteKnights & not(positions); + board->whiteBishops = board->whiteBishops & not(positions); + board->whitePawns = board->whitePawns & not(positions); + board->blackKing = board->blackKing & not(positions); + board->blackQueens = board->blackQueens & not(positions); + board->blackRooks = board->blackRooks & not(positions); + board->blackKnights = board->blackKnights & not(positions); + board->blackBishops = board->blackBishops & not(positions); + board->blackPawns = board->blackPawns & not(positions); +} + +void movePiece(Board* board, Move move) { + Bitboard leaving = index2bb(getFrom(move)); + Bitboard arriving = index2bb(getTo(move)); + + if(leaving & board->whiteKing) { + clearPositions(board, arriving | leaving); + board->whiteKing = arriving; + } else if(leaving & board->whiteQueens) { + clearPositions(board, arriving | leaving); + board->whiteQueens = board->whiteQueens | arriving; + } else if(leaving & board->whiteRooks) { + clearPositions(board, arriving | leaving); + board->whiteRooks = board->whiteRooks | arriving; + } else if(leaving & board->whiteKnights) { + clearPositions(board, arriving | leaving); + board->whiteKnights = board->whiteKnights | arriving; + } else if(leaving & board->whiteBishops) { + clearPositions(board, arriving | leaving); + board->whiteBishops = board->whiteBishops | arriving; + } else if(leaving & board->whitePawns) { + clearPositions(board, arriving | leaving); + board->whitePawns = board->whitePawns | arriving; + } else if(leaving & board->blackKing) { + clearPositions(board, arriving | leaving); + board->blackKing = arriving; + } else if(leaving & board->blackQueens) { + clearPositions(board, arriving | leaving); + board->blackQueens = board->blackQueens | arriving; + } else if(leaving & board->blackRooks) { + clearPositions(board, arriving | leaving); + board->blackRooks = board->blackRooks | arriving; + } else if(leaving & board->blackKnights) { + clearPositions(board, arriving | leaving); + board->blackKnights = board->blackKnights | arriving; + } else if(leaving & board->blackBishops) { + clearPositions(board, arriving | leaving); + board->blackBishops = board->blackBishops | arriving; + } else if(leaving & board->blackPawns) { + clearPositions(board, arriving | leaving); + board->blackPawns = board->blackPawns | arriving; + } +} + +void updatePosition(Position* newPosition, Position* position, Move move) { + memcpy(newPosition, position, sizeof(Position)); + int leavingSquare = getFrom(move); + int arrivingSquare = getTo(move); + Bitboard leavingBB = index2bb(leavingSquare); + Bitboard arrivingBB = index2bb(arrivingSquare); + + // ===== MOVE PIECE ===== + movePiece(&(newPosition->board), move); + + // ===== TO MOVE ===== + newPosition->toMove = opponent(position->toMove); + + // ===== MOVE COUNTS ===== + newPosition->halfmoveClock += 1; + if(position->toMove == BLACK) { + newPosition->fullmoveNumber += 1; + } + + if(arrivingBB & getOccupiedSquares(&(position->board))) { + newPosition->halfmoveClock = 0; + } + + // ===== PAWNS ===== + newPosition->epSquare = -1; + if((leavingBB & position->board.whitePawns) | (leavingBB & position->board.blackPawns)) { + newPosition->halfmoveClock = 0; + + if(arrivingSquare == position->epSquare) { + if(index2bb(position->epSquare) & RANK_3) { + clearPositions(&(newPosition->board), index2bb((int)(position->epSquare + 8))); + } + + if(index2bb(position->epSquare) & RANK_6) { + clearPositions(&(newPosition->board), index2bb((int)(position->epSquare - 8))); + } + } + + if(isDoublePush(leavingSquare, arrivingSquare)) { + newPosition->epSquare = getEpSquare(leavingSquare); + } + + if(arrivingBB & (RANK_1 | RANK_8)) { + clearPositions(&(newPosition->board), arrivingBB); + + if(position->toMove == WHITE) { + newPosition->board.whiteQueens = newPosition->board.whiteQueens | arrivingBB; + } else { + newPosition->board.blackQueens = newPosition->board.blackQueens | arrivingBB; + } + } + } + + // ===== CASTLING ===== + if(leavingSquare == str2index("a1")) { + newPosition->castlingRights = + removeCastlingRights(newPosition->castlingRights, CASTLE_QUEENSIDE_WHITE); + } else if(leavingSquare == str2index("h1")) { + newPosition->castlingRights = + removeCastlingRights(newPosition->castlingRights, CASTLE_KINGSIDE_WHITE); + } else if(leavingSquare == str2index("a8")) { + newPosition->castlingRights = + removeCastlingRights(newPosition->castlingRights, CASTLE_QUEENSIDE_BLACK); + } else if(leavingSquare == str2index("h8")) { + newPosition->castlingRights = + removeCastlingRights(newPosition->castlingRights, CASTLE_KINGSIDE_BLACK); + } + + if(leavingBB & position->board.whiteKing) { + newPosition->castlingRights = removeCastlingRights( + newPosition->castlingRights, (CASTLE_KINGSIDE_WHITE | CASTLE_QUEENSIDE_WHITE)); + if(leavingSquare == str2index("e1")) { + if(arrivingSquare == str2index("g1")) + movePiece(&(newPosition->board), generateMove(str2index("h1"), str2index("f1"))); + + if(arrivingSquare == str2index("c1")) + movePiece(&(newPosition->board), generateMove(str2index("a1"), str2index("d1"))); + } + } else if(leavingBB & position->board.blackKing) { + newPosition->castlingRights = removeCastlingRights( + newPosition->castlingRights, CASTLE_KINGSIDE_BLACK | CASTLE_QUEENSIDE_BLACK); + if(leavingSquare == str2index("e8")) { + if(arrivingSquare == str2index("g8")) + movePiece(&(newPosition->board), generateMove(str2index("h8"), str2index("f8"))); + + if(arrivingSquare == str2index("c8")) + movePiece(&(newPosition->board), generateMove(str2index("a8"), str2index("d8"))); + } + } +} + +void makeMove(Game* game, Move move) { + Position newPosition; + updatePosition(&newPosition, &(game->position), move); + memcpy(&(game->position), &newPosition, sizeof(Position)); + + game->moveListLen += 1; + + // ===== MOVE LIST ===== + game->moveList[game->moveListLen - 1] = move; + + // ===== POSITION HISTORY ===== + toFen(game->positionHistory[game->moveListLen], &game->position); +} + +void unmakeMove(Game* game) { + Position newPosition; + if(game->moveListLen >= 1) { + loadFen(&newPosition, game->positionHistory[game->moveListLen - 1]); + memcpy(&(game->position), &newPosition, sizeof(Position)); + + game->moveList[game->moveListLen - 1] = 0; + memset(game->positionHistory[game->moveListLen], 0, MAX_FEN_LEN * sizeof(char)); + + game->moveListLen -= 1; + } else { // return to initial game + loadFen(&newPosition, game->positionHistory[0]); + memcpy(&(game->position), &newPosition, sizeof(Position)); + + game->moveListLen = 0; + memset(game->moveList, 0, MAX_PLYS_PER_GAME * sizeof(int)); + memset(&game->positionHistory[1], 0, (MAX_PLYS_PER_GAME - 1) * MAX_FEN_LEN * sizeof(char)); + } +} + +// ======== MOVE GEN ========= + +Bitboard getMoves(Bitboard movingPiece, Position* position, char color) { + if((movingPiece & position->board.whitePawns) | (movingPiece & position->board.blackPawns)) + return pawnMoves(movingPiece, position, color); + if((movingPiece & position->board.whiteKnights) | (movingPiece & position->board.blackKnights)) + return knightMoves(movingPiece, &(position->board), color); + if((movingPiece & position->board.whiteBishops) | (movingPiece & position->board.blackBishops)) + return bishopMoves(movingPiece, &(position->board), color); + if((movingPiece & position->board.whiteRooks) | (movingPiece & position->board.blackRooks)) + return rookMoves(movingPiece, &(position->board), color); + if((movingPiece & position->board.whiteQueens) | (movingPiece & position->board.blackQueens)) + return queenMoves(movingPiece, &(position->board), color); + if((movingPiece & position->board.whiteKing) | (movingPiece & position->board.blackKing)) + return kingMoves(movingPiece, &(position->board), color); + + return 0; +} + +int pseudoLegalMoves(Move* moves, Position* position, char color) { + int leavingSquare, arrivingSquare, moveCount = 0; + Bitboard leavingBB = 1; + Bitboard attackers = getColoredPieces(&(position->board), color); + + for(leavingSquare = 0; leavingSquare < NUM_SQUARES; leavingSquare++) { + if(leavingBB & attackers) { + Bitboard targets = getMoves(leavingBB, position, color); + + for(arrivingSquare = 0; arrivingSquare < NUM_SQUARES; arrivingSquare++) { + if(isSet(targets, arrivingSquare)) { + moves[moveCount++] = generateMove(leavingSquare, arrivingSquare); + } + } + + if((leavingBB & position->board.whiteKing) | (leavingBB & position->board.blackKing)) { + if(canCastleKingside(position, color)) { + moves[moveCount++] = generateMove(leavingSquare, leavingSquare + 2); + } + if(canCastleQueenside(position, color)) { + moves[moveCount++] = generateMove(leavingSquare, leavingSquare - 2); + } + } + } + leavingBB <<= 1; + } + + return moveCount; +} + +Bitboard getAttacks(Bitboard movingPiece, Board* board, char color) { + if((movingPiece & board->whitePawns) | (movingPiece & board->blackPawns)) + return pawnAttacks(movingPiece, board, color); + if((movingPiece & board->whiteKnights) | (movingPiece & board->blackKnights)) + return knightAttacks(movingPiece); + if((movingPiece & board->whiteBishops) | (movingPiece & board->blackBishops)) + return bishopAttacks(movingPiece, board, color); + if((movingPiece & board->whiteRooks) | (movingPiece & board->blackRooks)) + return rookAttacks(movingPiece, board, color); + if((movingPiece & board->whiteQueens) | (movingPiece & board->blackQueens)) + return queenAttacks(movingPiece, board, color); + if((movingPiece & board->whiteKing) | (movingPiece & board->blackKing)) + return kingAttacks(movingPiece); + + return 0; +} + +int countAttacks(Bitboard target, Board* board, char color) { + int i, attackCount = 0; + + Bitboard attackers = getColoredPieces(board, color); + Bitboard position = 1; + + for(i = 0; i < NUM_SQUARES; i++) { + if(position & attackers) { + if(getAttacks(position, board, color) & target) { + attackCount += 1; + } + } + position = position << 1; + } + + return attackCount; +} + +BOOL isAttacked(Bitboard target, Board* board, char color) { + int i = 0; + + Bitboard attackers = getColoredPieces(board, color); + Bitboard position = 1; + + for(i = 0; i < NUM_SQUARES; i++) { + if(position & attackers) { + if(getAttacks(position, board, color) & target) { + return TRUE; + } + } + position = position << 1; + } + + return FALSE; +} + +BOOL isCheck(Board* board, char color) { + return isAttacked(getKing(board, color), board, opponent(color)); +} + +BOOL isLegalMove(Position* position, Move move) { + Position newPosition; + updatePosition(&newPosition, position, move); + if(isCheck(&(newPosition.board), position->toMove)) return FALSE; + return TRUE; +} + +int legalMoves(Move* legalMoves, Position* position, char color) { + int i, legalCount = 0; + + Move pseudoMoves[MAX_BRANCHING_FACTOR]; + int pseudoCount = pseudoLegalMoves(pseudoMoves, position, color); + + for(i = 0; i < pseudoCount; i++) { + if(isLegalMove(position, pseudoMoves[i])) { + legalMoves[legalCount++] = pseudoMoves[i]; + } + } + + return legalCount; +} + +int legalMovesCount(Position* position, char color) { + int i, legalCount = 0; + + Move pseudoMoves[MAX_BRANCHING_FACTOR]; + int pseudoCount = pseudoLegalMoves(pseudoMoves, position, color); + + for(i = 0; i < pseudoCount; i++) { + if(isLegalMove(position, pseudoMoves[i])) { + legalCount++; + } + } + + return legalCount; +} + +int staticOrderLegalMoves(Move* orderedLegalMoves, Position* position, char color) { + Move moves[MAX_BRANCHING_FACTOR]; + int legalCount = legalMoves(moves, position, color); + + Position newPosition; + Node nodes[legalCount], orderedNodes[legalCount]; + + int i; + for(i = 0; i < legalCount; i++) { + updatePosition(&newPosition, position, moves[i]); + nodes[i] = (Node){.move = moves[i], .score = staticEvaluation(&newPosition)}; + } + + sortNodes(orderedNodes, nodes, legalCount, color); + + for(i = 0; i < legalCount; i++) { + orderedLegalMoves[i] = orderedNodes[i].move; + } + + return legalCount; +} + +int legalCaptures(Move* legalCaptures, Position* position, char color) { + int i, captureCount = 0; + + Move moves[MAX_BRANCHING_FACTOR]; + int legalCount = legalMoves(moves, position, color); + + for(i = 0; i < legalCount; i++) { + int arrivingSquare = getTo(moves[i]); + if(index2bb(arrivingSquare) & getColoredPieces(&(position->board), opponent(color))) { + legalCaptures[captureCount++] = moves[i]; + } + } + + return captureCount; +} + +// ====== GAME CONTROL ======= + +BOOL isCheckmate(Position* position) { + if(isCheck(&(position->board), position->toMove) && + legalMovesCount(position, position->toMove) == 0) + return TRUE; + else + return FALSE; +} + +BOOL isStalemate(Position* position) { + if(!isCheck(&(position->board), position->toMove) && + legalMovesCount(position, position->toMove) == 0) + return TRUE; + else + return FALSE; +} + +BOOL hasInsufficientMaterial(Board* board) { + int pieceCount = countBits(getOccupiedSquares(board)); + + if(pieceCount <= 3) { + if(pieceCount == 2 || getKnights(board) != 0 || getBishops(board) != 0) return TRUE; + } + + return FALSE; +} + +BOOL isEndgame(Board* board) { + if(countBits(getOccupiedSquares(board)) <= ENDGAME_PIECE_COUNT) return TRUE; + return FALSE; +} + +BOOL isOver75MovesRule(Position* position) { + if(position->halfmoveClock >= 150) + return TRUE; + else + return FALSE; +} + +BOOL hasGameEnded(Position* position) { + if(isCheckmate(position) || isStalemate(position) || + hasInsufficientMaterial(&(position->board)) || isOver75MovesRule(position)) + return TRUE; + else + return FALSE; +} + +void printOutcome(Position* position) { + if(isCheckmate(position) && position->toMove == BLACK) printf("WHITE wins!\n"); + if(isCheckmate(position) && position->toMove == WHITE) printf("BLACK wins!\n"); + if(isStalemate(position)) printf("Draw by stalemate!\n"); + if(hasInsufficientMaterial(&(position->board))) printf("Draw by insufficient material!\n"); + if(isOver75MovesRule(position)) printf("Draw by 75 move rule!\n"); + fflush(stdout); +} + +// ========== EVAL =========== + +int winScore(char color) { + if(color == WHITE) return 10 * PIECE_VALUES[KING]; + if(color == BLACK) return -10 * PIECE_VALUES[KING]; + return 0; +} + +int materialSum(Board* board, char color) { + if(color == WHITE) { + return PIECE_VALUES[PAWN] * countBits(board->whitePawns) + + PIECE_VALUES[KNIGHT] * countBits(board->whiteKnights) + + PIECE_VALUES[BISHOP] * countBits(board->whiteBishops) + + PIECE_VALUES[ROOK] * countBits(board->whiteRooks) + + PIECE_VALUES[QUEEN] * countBits(board->whiteQueens); + } else { + return PIECE_VALUES[PAWN] * countBits(board->blackPawns) + + PIECE_VALUES[KNIGHT] * countBits(board->blackKnights) + + PIECE_VALUES[BISHOP] * countBits(board->blackBishops) + + PIECE_VALUES[ROOK] * countBits(board->blackRooks) + + PIECE_VALUES[QUEEN] * countBits(board->blackQueens); + } +} + +int materialBalance(Board* board) { + return materialSum(board, WHITE) - materialSum(board, BLACK); +} + +int positionalBonus(Board* board, char color) { + int bonus = 0; + Bitboard positionBB; + Bitboard coloredPieces = getColoredPieces(board, color); + + int i; + for(i = 0; i < NUM_SQUARES; i++) { + positionBB = index2bb(i); + + if(positionBB & coloredPieces) { + if((positionBB & board->whitePawns) | (positionBB & board->blackPawns)) { + if(color == WHITE) { + bonus += PAWN_BONUS[i]; + } else { + bonus += PAWN_BONUS[FLIP_VERTICAL[i]]; + } + + if(isDoubledPawn(positionBB, board, color)) { + bonus -= DOUBLED_PAWN_PENALTY / 2; + } + if(isPassedPawn(positionBB, board, color)) { + bonus += PASSED_PAWN_BONUS; + } + + if(isIsolatedPawn(positionBB, board, color)) { + bonus -= ISOLATED_PAWN_PENALTY; + } else if(isBackwardsPawn(positionBB, board, color)) { + bonus -= BACKWARDS_PAWN_PENALTY; + } + } else if((positionBB & board->whiteKnights) | (positionBB & board->blackKnights)) { + if(color == WHITE) { + bonus += KNIGHT_BONUS[i]; + } else { + bonus += KNIGHT_BONUS[FLIP_VERTICAL[i]]; + } + } else if((positionBB & board->whiteBishops) | (positionBB & board->blackBishops)) { + if(color == WHITE) { + bonus += BISHOP_BONUS[i]; + } else { + bonus += BISHOP_BONUS[FLIP_VERTICAL[i]]; + } + } else if((positionBB & board->whiteRooks) | (positionBB & board->blackRooks)) { + if(isOpenFile(positionBB, board)) { + bonus += ROOK_OPEN_FILE_BONUS; + } else if(isSemiOpenFile(positionBB, board)) { + bonus += ROOK_SEMI_OPEN_FILE_BONUS; + } + + if(color == WHITE) { + if(positionBB & RANK_7) { + bonus += ROOK_ON_SEVENTH_BONUS; + } + } else { + if(positionBB & RANK_2) { + bonus += ROOK_ON_SEVENTH_BONUS; + } + } + } else if((positionBB & board->whiteKing) | (positionBB & board->blackKing)) { + if(isEndgame(board)) { + if(color == WHITE) { + bonus += KING_ENDGAME_BONUS[i]; + } else { + bonus += KING_ENDGAME_BONUS[FLIP_VERTICAL[i]]; + } + } else { + if(color == WHITE) { + bonus += KING_BONUS[i]; + } else { + bonus += KING_BONUS[FLIP_VERTICAL[i]]; + } + } + } + } + } + + return bonus; +} + +int positionalBalance(Board* board) { + return positionalBonus(board, WHITE) - positionalBonus(board, BLACK); +} + +int endNodeEvaluation(Position* position) { + if(isCheckmate(position)) { + return winScore(opponent(position->toMove)); + } + if(isStalemate(position) || hasInsufficientMaterial(&(position->board)) || + isOver75MovesRule(position)) { + return 0; + } + return 0; +} + +int staticEvaluation(Position* position) { + if(hasGameEnded(position)) + return endNodeEvaluation(position); + else + return materialBalance(&(position->board)) + positionalBalance(&(position->board)); +} + +int getPieceValue(Bitboard position, Board* board) { + if((position & board->whitePawns) | (position & board->blackPawns)) return PIECE_VALUES[PAWN]; + if((position & board->whiteKnights) | (position & board->blackKnights)) + return PIECE_VALUES[KNIGHT]; + if((position & board->whiteBishops) | (position & board->blackBishops)) + return PIECE_VALUES[BISHOP]; + if((position & board->whiteRooks) | (position & board->blackRooks)) return PIECE_VALUES[ROOK]; + if((position & board->whiteQueens) | (position & board->blackQueens)) + return PIECE_VALUES[QUEEN]; + if((position & board->whiteKing) | (position & board->blackKing)) return PIECE_VALUES[KING]; + return 0; +} + +int getCaptureSequence(Move* captures, Position* position, int targetSquare) { + Move allCaptures[MAX_BRANCHING_FACTOR], targetCaptures[MAX_ATTACKING_PIECES]; + int captureCount = legalCaptures(allCaptures, position, position->toMove); + int i, j, targetCount = 0; + + for(i = 0; i < captureCount; i++) { + if(getTo(allCaptures[i]) == targetSquare) { + targetCaptures[targetCount++] = allCaptures[i]; + } + } + + Move captureBuffer[targetCount]; + + BOOL sorted; + for(i = 0; i < targetCount; i++) { + sorted = FALSE; + int pieceValue = getPieceValue(index2bb(getFrom(targetCaptures[i])), &(position->board)); + + for(j = 0; j < i; j++) { + int sortedPieceValue = + getPieceValue(index2bb(getFrom(captures[j])), &(position->board)); + + if(pieceValue < sortedPieceValue) { + sorted = TRUE; + memcpy(captureBuffer, &captures[j], (i - j) * sizeof(Move)); + memcpy(&captures[j + 1], captureBuffer, (i - j) * sizeof(Move)); + captures[j] = targetCaptures[i]; + break; + } + } + + if(sorted == FALSE) { + captures[i] = targetCaptures[i]; + } + } + + return targetCount; +} + +int staticExchangeEvaluation(Position* position, int targetSquare) { + Move captures[MAX_ATTACKING_PIECES]; + int attackCount = getCaptureSequence(captures, position, targetSquare); + int value = 0; + + if(attackCount > 0) { + Position newPosition; + updatePosition(&newPosition, position, captures[0]); + int pieceValue = getPieceValue(index2bb(targetSquare), &(position->board)); + value = pieceValue - staticExchangeEvaluation(&newPosition, targetSquare); + } + + return value > 0 ? value : 0; +} + +int quiescenceEvaluation(Position* position) { + int staticScore = staticEvaluation(position); + + if(hasGameEnded(position)) return staticScore; + + Move captures[MAX_BRANCHING_FACTOR]; + int captureCount = legalCaptures(captures, position, position->toMove); + + if(captureCount == 0) { + return staticScore; + } else { + Position newPosition; + int i, bestScore = staticScore; + + for(i = 0; i < captureCount; i++) { + if(staticExchangeEvaluation(position, getTo(captures[i])) <= 0) continue; + + updatePosition(&newPosition, position, captures[i]); + int score = quiescenceEvaluation(&newPosition); + + if((position->toMove == WHITE && score > bestScore) || + (position->toMove == BLACK && score < bestScore)) { + bestScore = score; + } + } + + return bestScore; + } +} + +// ========= SEARCH ========== + +Node staticSearch(Position* position) { + int bestScore = position->toMove == WHITE ? INT32_MIN : INT32_MAX; + Move bestMove = 0; + + Move moves[MAX_BRANCHING_FACTOR]; + int moveCount = legalMoves(moves, position, position->toMove); + + Position newPosition; + int i; + for(i = 0; i < moveCount; i++) { + updatePosition(&newPosition, position, moves[i]); + int score = staticEvaluation(&newPosition); + + if(score == winScore(position->toMove)) { + return (Node){.move = moves[i], .score = score}; + } + + if((position->toMove == WHITE && score > bestScore) || + (position->toMove == BLACK && score < bestScore)) { + bestScore = score; + bestMove = moves[i]; + } + } + + return (Node){.move = bestMove, .score = bestScore}; +} + +Node quiescenceSearch(Position* position) { + int bestScore = position->toMove == WHITE ? INT32_MIN : INT32_MAX; + Move bestMove = 0; + + Move moves[MAX_BRANCHING_FACTOR]; + int moveCount = legalMoves(moves, position, position->toMove); + + Position newPosition; + int i; + for(i = 0; i < moveCount; i++) { + updatePosition(&newPosition, position, moves[i]); + int score = quiescenceEvaluation(&newPosition); + + if(score == winScore(position->toMove)) { + return (Node){.move = moves[i], .score = score}; + } + + if((position->toMove == WHITE && score > bestScore) || + (position->toMove == BLACK && score < bestScore)) { + bestScore = score; + bestMove = moves[i]; + } + } + + return (Node){.move = bestMove, .score = bestScore}; +} + +Node alphaBeta(Position* position, char depth, int alpha, int beta) { + if(hasGameEnded(position)) return (Node){.score = endNodeEvaluation(position)}; + + if(depth == 1) return staticSearch(position); + + // Mate in 1 + Node staticNode = staticSearch(position); + if(staticNode.score == winScore(position->toMove)) return staticNode; + + Move bestMove = 0; + + Move moves[MAX_BRANCHING_FACTOR]; + int moveCount = staticOrderLegalMoves(moves, position, position->toMove); + + Position newPosition; + int i; + for(i = 0; i < moveCount; i++) { + updatePosition(&newPosition, position, moves[i]); + + int score = alphaBeta(&newPosition, depth - 1, alpha, beta).score; + + if(score == winScore(position->toMove)) { + return (Node){.move = moves[i], .score = score}; + } + + if(position->toMove == WHITE && score > alpha) { + alpha = score; + bestMove = moves[i]; + } else if(position->toMove == BLACK && score < beta) { + beta = score; + bestMove = moves[i]; + } + + if(alpha > beta) { + break; + } + } + + return (Node){.move = bestMove, .score = position->toMove == WHITE ? alpha : beta}; +} + +int alphaBetaNodes(Node* sortedNodes, Position* position, char depth) { + Node nodes[MAX_BRANCHING_FACTOR]; + Move moves[MAX_BRANCHING_FACTOR]; + int moveCount = legalMoves(moves, position, position->toMove); + + Position newPosition; + int i; + for(i = 0; i < moveCount; i++) { + updatePosition(&newPosition, position, moves[i]); + + nodes[i].move = moves[i]; + nodes[i].score = depth > 1 ? + alphaBeta(&newPosition, depth - 1, INT32_MIN, INT32_MAX).score : + staticEvaluation(&newPosition); + } + + sortNodes(sortedNodes, nodes, moveCount, position->toMove); + + return moveCount; +} + +Node iterativeDeepeningAlphaBeta(Position* position, char depth, int alpha, int beta, BOOL verbose) { + if(hasGameEnded(position)) return (Node){.score = endNodeEvaluation(position)}; + + if(depth == 1) return quiescenceSearch(position); + // return staticSearch(position); + + // Mate in 1 + Node staticNode = staticSearch(position); + if(staticNode.score == winScore(position->toMove)) return staticNode; + + Move bestMove = 0; + + if(verbose) { + printf("Ordering moves...\n"); + fflush(stdout); + } + + Node nodes[MAX_BRANCHING_FACTOR]; + int moveCount = alphaBetaNodes(nodes, position, depth - 1); + + Position newPosition; + int i; + for(i = 0; i < moveCount; i++) { + updatePosition(&newPosition, position, nodes[i].move); + + if(verbose) { + printf("(Move %2d/%d) ", i + 1, moveCount); + printFullMove(nodes[i].move, &(position->board)); + printf(" = "); + fflush(stdout); + } + + int score = iterativeDeepeningAlphaBeta(&newPosition, depth - 1, alpha, beta, FALSE).score; + + if(verbose) { + printf("%.2f\n", (double)score / (double)100.00); + fflush(stdout); + } + + if(score == winScore(position->toMove)) { + return (Node){.move = nodes[i].move, .score = score}; + } + + if(position->toMove == WHITE && score > alpha) { + alpha = score; + bestMove = nodes[i].move; + } else if(position->toMove == BLACK && score < beta) { + beta = score; + bestMove = nodes[i].move; + } + + if(alpha > beta) { + break; + } + } + + return (Node){.move = bestMove, .score = position->toMove == WHITE ? alpha : beta}; +} + +Node pIDAB(Position* position, char depth, int* p_alpha, int* p_beta) { + if(hasGameEnded(position)) return (Node){.score = endNodeEvaluation(position)}; + + if(depth == 1) return quiescenceSearch(position); + + // Mate in 1 + Node staticNode = staticSearch(position); + if(staticNode.score == winScore(position->toMove)) return staticNode; + + Move bestMove = 0; + + Node nodes[MAX_BRANCHING_FACTOR]; + int moveCount = alphaBetaNodes(nodes, position, depth - 1); + + Position newPosition; + int i; + int alpha = *p_alpha; + int beta = *p_beta; + for(i = 0; i < moveCount; i++) { + updatePosition(&newPosition, position, nodes[i].move); + + int score = iterativeDeepeningAlphaBeta(&newPosition, depth - 1, alpha, beta, FALSE).score; + + if(score == winScore(position->toMove)) { + return (Node){.move = nodes[i].move, .score = score}; + } + + if(position->toMove == WHITE && score > alpha) { + alpha = score; + bestMove = nodes[i].move; + } else if(position->toMove == BLACK && score < beta) { + beta = score; + bestMove = nodes[i].move; + } + + if(alpha > beta || alpha > *p_beta || *p_alpha > beta) { + break; + } + } + + return (Node){.move = bestMove, .score = position->toMove == WHITE ? alpha : beta}; +} + +Node pIDABhashed(Position* position, char depth, int* p_alpha, int* p_beta) { + if(hasGameEnded(position)) { + int score = endNodeEvaluation(position); + writeToHashFile(position, score, 0); + return (Node){.score = score}; + } + + if(depth <= 1) { + Node quie = quiescenceSearch(position); + writeToHashFile(position, quie.score, depth); + return quie; + } + + // Mate in 1 + Node staticNode = staticSearch(position); + if(staticNode.score == winScore(position->toMove)) { + writeToHashFile(position, staticNode.score, 1); + return staticNode; + } + + Move bestMove = 0; + + Node nodes[MAX_BRANCHING_FACTOR]; + int moveCount = alphaBetaNodes(nodes, position, depth - 1); + + Position newPosition; + int i; + int alpha = *p_alpha; + int beta = *p_beta; + for(i = 0; i < moveCount; i++) { + updatePosition(&newPosition, position, nodes[i].move); + + int score = iterativeDeepeningAlphaBeta(&newPosition, depth - 1, alpha, beta, FALSE).score; + writeToHashFile(&newPosition, score, depth - 1); + + if(score == winScore(position->toMove)) { + return (Node){.move = nodes[i].move, .score = score}; + } + + if(position->toMove == WHITE && score > alpha) { + alpha = score; + bestMove = nodes[i].move; + } else if(position->toMove == BLACK && score < beta) { + beta = score; + bestMove = nodes[i].move; + } + + if(alpha > beta || alpha > *p_beta || *p_alpha > beta) { + break; + } + } + + writeToHashFile(position, position->toMove == WHITE ? alpha : beta, depth); + return (Node){.move = bestMove, .score = position->toMove == WHITE ? alpha : beta}; +} + +// Parallel processing currently only implemented for Windows +#ifdef _WIN32 + +DWORD WINAPI evaluatePositionThreadFunction(LPVOID lpParam) { + ThreadInfo* tInfo = (ThreadInfo*)lpParam; + Position* pos = &tInfo->pos; + + Node node = pIDAB(pos, tInfo->depth, tInfo->alpha, tInfo->beta); + + if(pos->toMove == BLACK && node.score > *tInfo->alpha) { + *tInfo->alpha = node.score; + } else if(pos->toMove == WHITE && node.score < *tInfo->beta) { + *tInfo->beta = node.score; + } + + if(tInfo->verbose) { + printf("-"); + fflush(stdout); + } + + return node.score; +} + +DWORD WINAPI evaluatePositionThreadFunctionHashed(LPVOID lpParam) { + ThreadInfo* tInfo = (ThreadInfo*)lpParam; + Position* pos = &tInfo->pos; + + Node node = pIDABhashed(pos, tInfo->depth, tInfo->alpha, tInfo->beta); + + if(pos->toMove == BLACK && node.score > *tInfo->alpha) { + *tInfo->alpha = node.score; + } else if(pos->toMove == WHITE && node.score < *tInfo->beta) { + *tInfo->beta = node.score; + } + + if(tInfo->verbose) { + printf("-"); + fflush(stdout); + } + + return node.score; +} + +Node idabThreaded(Position* position, int depth, BOOL verbose) { + if(hasGameEnded(position)) return (Node){.score = endNodeEvaluation(position)}; + + if(depth <= 1) return quiescenceSearch(position); + + int i; + Node nodes[MAX_BRANCHING_FACTOR]; + int moveCount = alphaBetaNodes(nodes, position, depth - 1); + + if(moveCount == 1) { + return nodes[0]; + } + + if(verbose) { + printf("Analyzing %d possible moves with base depth %d:\n[", moveCount, depth); + for(i = 0; i < moveCount; i++) printf(" "); + printf("]\r["); + fflush(stdout); + } + + HANDLE threadHandles[MAX_BRANCHING_FACTOR]; + ThreadInfo threadInfo[MAX_BRANCHING_FACTOR]; + int alpha = INT32_MIN; + int beta = INT32_MAX; + + for(i = 0; i < moveCount; i++) { + threadInfo[i].depth = depth - 1; + updatePosition(&threadInfo[i].pos, position, nodes[i].move); + threadInfo[i].alpha = α + threadInfo[i].beta = β + threadInfo[i].verbose = verbose; + + threadHandles[i] = + CreateThread(NULL, 0, evaluatePositionThreadFunction, (LPVOID)&threadInfo[i], 0, NULL); + + if(threadHandles[i] == NULL) { + // printf("Error launching process on move #%d!\n", i); + printf("!"); + fflush(stdout); + } + } + + WaitForMultipleObjects((DWORD)moveCount, threadHandles, TRUE, INFINITE); + if(verbose) { + printf("]\n"); + fflush(stdout); + } + + Move bestMove = 0; + int bestMoveScore = position->toMove == WHITE ? INT32_MIN : INT32_MAX; + long unsigned int retVal; + int score; + for(i = 0; i < moveCount; i++) { + GetExitCodeThread(threadHandles[i], &retVal); + score = (int)retVal; + + if((position->toMove == WHITE && score > bestMoveScore) || + (position->toMove == BLACK && score < bestMoveScore)) { + bestMove = nodes[i].move; + bestMoveScore = score; + } + + if(CloseHandle(threadHandles[i]) == 0) { + // printf("Error on closing thread #%d!\n", i); + printf("x"); + fflush(stdout); + } + } + + return (Node){.move = bestMove, .score = bestMoveScore}; +} + +Node idabThreadedBestFirst(Position* position, int depth, BOOL verbose) { + if(hasGameEnded(position)) return (Node){.score = endNodeEvaluation(position)}; + + if(depth <= 1) return quiescenceSearch(position); + + int i; + Node nodes[MAX_BRANCHING_FACTOR]; + int moveCount = alphaBetaNodes(nodes, position, depth - 1); + + if(moveCount == 1) { + return nodes[0]; + } + + Position firstPos; + updatePosition(&firstPos, position, nodes[0].move); + Node firstReply = idabThreaded(&firstPos, depth - 1, FALSE); + + if(firstReply.score == winScore(position->toMove)) { + if(verbose) { + printf("Playing checkmate move: "); + printFullMove(nodes[0].move, position->board); + printf(".\n"); + } + return (Node){.move = nodes[0].move, .score = firstReply.score}; + } + + if(verbose) { + printf("Move "); + printFullMove(nodes[0].move, position->board); + printf(" had score of %+.2f.\n", firstReply.score / 100.0); + printf( + "Analyzing other %d possible moves with minimum depth of %d plies:\n[", + moveCount - 1, + depth); + for(i = 0; i < moveCount - 1; i++) printf(" "); + printf("]\r["); + fflush(stdout); + } + + HANDLE threadHandles[MAX_BRANCHING_FACTOR]; + ThreadInfo threadInfo[MAX_BRANCHING_FACTOR]; + int alpha = INT32_MIN; + int beta = INT32_MAX; + + if(position->toMove == WHITE) { + alpha = firstReply.score; + } else { + beta = firstReply.score; + } + + for(i = 0; i < moveCount - 1; i++) { + threadInfo[i].depth = depth - 1; + updatePosition(&threadInfo[i].pos, position, nodes[i + 1].move); + threadInfo[i].alpha = α + threadInfo[i].beta = β + threadInfo[i].verbose = verbose; + + threadHandles[i] = + CreateThread(NULL, 0, evaluatePositionThreadFunction, (LPVOID)&threadInfo[i], 0, NULL); + + if(threadHandles[i] == NULL) { + // printf("Error launching process on move #%d!\n", i); + printf("!"); + fflush(stdout); + } + } + + WaitForMultipleObjects((DWORD)moveCount - 1, threadHandles, TRUE, INFINITE); + if(verbose) { + printf("] Done!\n"); + fflush(stdout); + } + + Move bestMove = nodes[0].move; + int bestMoveScore = firstReply.score; + long unsigned int retVal; + int score; + for(i = 0; i < moveCount - 1; i++) { + GetExitCodeThread(threadHandles[i], &retVal); + score = (int)retVal; + + if((position->toMove == WHITE && score > bestMoveScore) || + (position->toMove == BLACK && score < bestMoveScore)) { + bestMove = nodes[i + 1].move; + bestMoveScore = score; + } + + if(CloseHandle(threadHandles[i]) == 0) { + // printf("Error on closing thread #%d!\n", i); + printf("x"); + fflush(stdout); + } + } + + return (Node){.move = bestMove, .score = bestMoveScore}; +} + +Node idabThreadedBestFirstHashed(Position* position, int depth, BOOL verbose) { + if(hasGameEnded(position)) { + int score = endNodeEvaluation(position); + writeToHashFile(position, score, 0); + return (Node){.score = score}; + } + + if(depth <= 1) { + Node quie = quiescenceSearch(position); + writeToHashFile(position, quie.score, depth); + return quie; + } + + int i; + Node nodes[MAX_BRANCHING_FACTOR]; + int moveCount = alphaBetaNodes(nodes, position, depth - 1); + + if(moveCount == 1) { + return nodes[0]; + } + + Position firstPos; + updatePosition(&firstPos, position, nodes[0].move); + Node firstReply = idabThreaded(&firstPos, depth - 1, FALSE); + + if(firstReply.score == winScore(position->toMove)) { + if(verbose) { + printf("Playing checkmate move: "); + printFullMove(nodes[0].move, position->board); + printf(".\n"); + } + writeToHashFile(position, firstReply.score, depth); + return (Node){.move = nodes[0].move, .score = firstReply.score}; + } + + if(verbose) { + printf("Move "); + printFullMove(nodes[0].move, position->board); + printf(" had score of %+.2f.\n", firstReply.score / 100.0); + printf( + "Analyzing other %d possible moves with minimum depth of %d plies:\n[", + moveCount - 1, + depth); + for(i = 0; i < moveCount - 1; i++) printf(" "); + printf("]\r["); + fflush(stdout); + } + + HANDLE threadHandles[MAX_BRANCHING_FACTOR]; + ThreadInfo threadInfo[MAX_BRANCHING_FACTOR]; + int alpha = INT32_MIN; + int beta = INT32_MAX; + + if(position->toMove == WHITE) { + alpha = firstReply.score; + } else { + beta = firstReply.score; + } + + for(i = 0; i < moveCount - 1; i++) { + threadInfo[i].depth = depth - 1; + updatePosition(&threadInfo[i].pos, position, nodes[i + 1].move); + threadInfo[i].alpha = α + threadInfo[i].beta = β + threadInfo[i].verbose = verbose; + + threadHandles[i] = CreateThread( + NULL, 0, evaluatePositionThreadFunctionHashed, (LPVOID)&threadInfo[i], 0, NULL); + + if(threadHandles[i] == NULL) { + // printf("Error launching process on move #%d!\n", i); + printf("!"); + fflush(stdout); + } + } + + WaitForMultipleObjects((DWORD)moveCount - 1, threadHandles, TRUE, INFINITE); + if(verbose) { + printf("] Done!\n"); + fflush(stdout); + } + + Move bestMove = nodes[0].move; + int bestMoveScore = firstReply.score; + long unsigned int retVal; + int score; + for(i = 0; i < moveCount - 1; i++) { + GetExitCodeThread(threadHandles[i], &retVal); + score = (int)retVal; + + writeToHashFile(&threadInfo[i].pos, score, depth - 1); + + if((position->toMove == WHITE && score > bestMoveScore) || + (position->toMove == BLACK && score < bestMoveScore)) { + bestMove = nodes[i + 1].move; + bestMoveScore = score; + } + + if(CloseHandle(threadHandles[i]) == 0) { + // printf("Error on closing thread #%d!\n", i); + printf("x"); + fflush(stdout); + } + } + + writeToHashFile(position, bestMoveScore, depth); + return (Node){.move = bestMove, .score = bestMoveScore}; +} + +#endif /* _WIN32 */ + +Move getRandomMove(Position* position) { + Move moves[MAX_BRANCHING_FACTOR]; + int totalMoves = legalMoves(moves, position, position->toMove); + int chosenMove = rand() % totalMoves; + return moves[chosenMove]; +} + +Move getAIMove(Game* game, int depth) { + printf("--- AI ---\n"); + fflush(stdout); + + if(fromInitial(game) && countBookOccurrences(game) > 0) { + printf("There are %d available book continuations.\n", countBookOccurrences(game)); + fflush(stdout); + Move bookMove = getBookMove(game); + printf("CHOSEN book move: "); + printFullMove(bookMove, &(game->position.board)); + printf(".\n"); + fflush(stdout); + return bookMove; + } + + time_t startTime, endTime; + startTime = time(NULL); + + // Move move = getRandomMove(&game->position); + // Move move = simpleEvaluation(&game->position).move; + // Move move = minimax(&game->position, AI_DEPTH).move; + // Node node = alphaBeta(&game->position, depth, INT32_MIN, INT32_MAX, TRUE); + // Node node = iterativeDeepeningAlphaBeta(&game->position, depth, INT32_MIN, INT32_MAX, TRUE); + // Node node = idabThreaded(&game->position, depth, TRUE); + // Node node = idabThreadedBestFirst(&game->position, depth, TRUE); + // Node node = idabThreadedBestFirstHashed(&game->position, depth, TRUE); + +#ifdef _WIN32 + Node node = idabThreadedBestFirst(&game->position, depth, TRUE); +#else + Node node = iterativeDeepeningAlphaBeta(&game->position, depth, INT32_MIN, INT32_MAX, TRUE); +#endif + + endTime = time(NULL); + + printf("CHOSEN move: "); + printFullMove(node.move, &(game->position.board)); + printf( + " in %d seconds [%+.2f, %+.2f]\n", + (int)(endTime - startTime), + (double)staticEvaluation(&game->position) / (double)100.0, + (double)node.score / (double)100.0); + fflush(stdout); + + return node.move; +} + +Move parseMove(char* move) { + int pos1 = str2index(&move[0]); + int pos2 = str2index(&move[2]); + return generateMove(pos1, pos2); +} + +Move getPlayerMove() { + char input[100]; + fgets(input, 100, stdin); + return parseMove(input); +} + +Move suggestMove(char fen[], int depth) { + Game game; + getFenGame(&game, fen); + return getAIMove(&game, depth); +} + +// ===== PLAY LOOP (TEXT) ==== + +void playTextWhite(int depth) { + printf("Playing as WHITE!\n"); + fflush(stdout); + + Game game; + getInitialGame(&game); + + while(TRUE) { + printBoard(&(game.position.board)); + if(hasGameEnded(&game.position)) break; + + makeMove(&game, getPlayerMove()); + + printBoard(&(game.position.board)); + if(hasGameEnded(&game.position)) break; + + makeMove(&game, getAIMove(&game, depth)); + } + printOutcome(&game.position); +} + +void playTextBlack(int depth) { + printf("Playing as BLACK!\n"); + fflush(stdout); + + Game game; + getInitialGame(&game); + + while(TRUE) { + printBoard(&(game.position.board)); + if(hasGameEnded(&game.position)) break; + + makeMove(&game, getAIMove(&game, depth)); + + printBoard(&(game.position.board)); + if(hasGameEnded(&game.position)) break; + + makeMove(&game, getPlayerMove()); + } + printOutcome(&game.position); +} + +void playTextAs(char color, int depth) { + if(color == WHITE) playTextWhite(depth); + if(color == BLACK) playTextBlack(depth); +} + +void playTextRandomColor(int depth) { + char colors[] = {WHITE, BLACK}; + char color = colors[rand() % 2]; + playTextAs(color, depth); +} + +// =========================== + +/* +int main(int argc, char *argv[]) { + srand(time(NULL)); + + int opt; + int FEN_MODE = 0, MOVES_MODE = 1, mode = FEN_MODE; + int depth = DEFAULT_AI_DEPTH; + + while ((opt = getopt(argc, argv, "fmd:v")) != -1) { + switch (opt) { + case 'f': mode = FEN_MODE; break; + case 'm': mode = MOVES_MODE; break; + case 'd': depth = atoi(optarg); break; + case 'v': printf(ENGINE_VERSION); exit(0); + default: + fprintf(stderr, "Usage: %s [-d depth] [-f fen] [-m move_list]\n", argv[0]); + exit(EXIT_FAILURE); + } + } + + Game game; + if (argc > optind) { + if (mode == FEN_MODE) { + getFenGame(&game, argv[optind]); + } else if (mode == MOVES_MODE) { + getMovelistGame(&game, argv[optind]); + } + } else { + getInitialGame(&game); + } + + Move move; + if ( mode == MOVES_MODE && countBookOccurrences(&game) > 0 ) { + move = getBookMove(&game); + } else { + Node node = iterativeDeepeningAlphaBeta(&(game.position), (char) depth, INT32_MIN, INT32_MAX, FALSE); + move = node.move; + } + + printf("%c%c%c%c", getFile(getFrom(move)), getRank(getFrom(move)), getFile(getTo(move)), getRank(getTo(move))); + + return EXIT_SUCCESS; +} +// */ diff --git a/Applications/Official/DEV_FW/source/chess/fast_chess.h b/Applications/Official/DEV_FW/source/chess/fast_chess.h new file mode 100644 index 000000000..b56a32cb2 --- /dev/null +++ b/Applications/Official/DEV_FW/source/chess/fast_chess.h @@ -0,0 +1,388 @@ +/* + * fast-chess.h + * + * Created on: 20 de set de 2016 + * Author: fvj + */ + +#ifndef FAST_CHESS_H_ +#define FAST_CHESS_H_ + +#ifdef _WIN32 +#include +#endif + +#include + +#define ENGINE_VERSION "v1.8.1" + +#define ENGINE_NAME "github.com/fredericojordan/fast-chess " ENGINE_VERSION +#define HUMAN_NAME "Unknown Human Player" + +#define NUM_SQUARES (64) +#define ENDGAME_PIECE_COUNT (7) + +#define COLOR_MASK (1 << 3) +#define WHITE (0) +#define BLACK (1 << 3) + +#define PIECE_MASK (0x7) +#define EMPTY (0) +#define PAWN (1) +#define KNIGHT (2) +#define BISHOP (3) +#define ROOK (4) +#define QUEEN (5) +#define KING (6) + +#define ALL_SQUARES (0xFFFFFFFFFFFFFFFF) +#define FILE_A (0x0101010101010101) +#define FILE_B (0x0202020202020202) +#define FILE_C (0x0404040404040404) +#define FILE_D (0x0808080808080808) +#define FILE_E (0x1010101010101010) +#define FILE_F (0x2020202020202020) +#define FILE_G (0x4040404040404040) +#define FILE_H (0x8080808080808080) +#define RANK_1 (0x00000000000000FF) +#define RANK_2 (0x000000000000FF00) +#define RANK_3 (0x0000000000FF0000) +#define RANK_4 (0x00000000FF000000) +#define RANK_5 (0x000000FF00000000) +#define RANK_6 (0x0000FF0000000000) +#define RANK_7 (0x00FF000000000000) +#define RANK_8 (0xFF00000000000000) +#define DIAG_A1H8 (0x8040201008040201) +#define ANTI_DIAG_H1A8 (0x0102040810204080) +#define LIGHT_SQUARES (0x55AA55AA55AA55AA) +#define DARK_SQUARES (0xAA55AA55AA55AA55) + +#define CASTLE_KINGSIDE_WHITE (1 << 0) +#define CASTLE_QUEENSIDE_WHITE (1 << 1) +#define CASTLE_KINGSIDE_BLACK (1 << 2) +#define CASTLE_QUEENSIDE_BLACK (1 << 3) + +#define BOOL char + +#ifndef FALSE +#define TRUE (1) +#define FALSE (0) +#endif + +typedef uint_fast64_t Bitboard; +typedef int Move; + +#define MAX_BOOK_ENTRY_LEN (300) +#define MAX_PLYS_PER_GAME (1024) +#define MAX_FEN_LEN (100) +// #define MAX_BRANCHING_FACTOR (218) /* R6R/3Q4/1Q4Q1/4Q3/2Q4Q/Q4Q2/pp1Q4/kBNN1KB1 w - - 0 1 3Q4/1Q4Q1/4Q3/2Q4R/Q4Q2/3Q4/1Q4Rp/1K1BBNNk w - - 0 1 */ +#define MAX_BRANCHING_FACTOR (100) // okalachev +#define MAX_ATTACKING_PIECES (12) + +#define DEFAULT_AI_DEPTH (3) + +typedef struct { + Bitboard whiteKing; + Bitboard whiteQueens; + Bitboard whiteRooks; + Bitboard whiteKnights; + Bitboard whiteBishops; + Bitboard whitePawns; + + Bitboard blackKing; + Bitboard blackQueens; + Bitboard blackRooks; + Bitboard blackKnights; + Bitboard blackBishops; + Bitboard blackPawns; +} Board; + +typedef struct { + Board board; + char toMove; + char epSquare; + char castlingRights; + unsigned int halfmoveClock; + unsigned int fullmoveNumber; +} Position; + +typedef struct { + Position position; + + unsigned int moveListLen; + Move moveList[MAX_PLYS_PER_GAME]; + char positionHistory[MAX_PLYS_PER_GAME][MAX_FEN_LEN]; +} Game; + +typedef struct { + Move move; + int score; +} Node; + +typedef struct { + int depth; + Position pos; + int* alpha; + int* beta; + BOOL verbose; +} ThreadInfo; + +extern char FILES[8]; +extern char RANKS[8]; + +extern Bitboard FILES_BB[8]; +extern Bitboard RANKS_BB[8]; + +extern char INITIAL_FEN[]; +extern Board INITIAL_BOARD; +extern int PIECE_VALUES[]; + +#define DOUBLED_PAWN_PENALTY (10) +#define ISOLATED_PAWN_PENALTY (20) +#define BACKWARDS_PAWN_PENALTY (8) +#define PASSED_PAWN_BONUS (20) +#define ROOK_SEMI_OPEN_FILE_BONUS (10) +#define ROOK_OPEN_FILE_BONUS (15) +#define ROOK_ON_SEVENTH_BONUS (20) + +extern int PAWN_BONUS[]; +extern int KNIGHT_BONUS[]; +extern int BISHOP_BONUS[]; +extern int KING_BONUS[]; +extern int KING_ENDGAME_BONUS[]; +extern int FLIP_VERTICAL[]; + +void getInitialGame(Game* game); +void getFenGame(Game* game, char fen[]); +void insertPiece(Board* board, Bitboard position, char pieceCode); +int loadFen(Position* position, char fen[]); +int toFen(char* fen, Position* position); +int toMinFen(char* fen, Position* position); +void getMovelistGame(Game* game, char moves[]); + +// ========= UTILITY ========= + +BOOL fromInitial(Game* game); +Bitboard index2bb(int index); +int str2index(char* str); +Bitboard str2bb(char* str); +BOOL isSet(Bitboard bb, int index); +Bitboard lsb(Bitboard bb); +Bitboard msb(Bitboard bb); +int bb2index(Bitboard bb); +char* movelist2str(Game* game); +Move getLastMove(Game* game); +BOOL startsWith(const char* str, const char* pre); +int countBookOccurrences(Game* game); +Move getBookMove(Game* game); +char getFile(int position); +char getRank(int position); +Move generateMove(int leavingSquare, int arrivingSquare); +int getFrom(Move move); +int getTo(Move move); +int char2piece(char pieceCode); +int bb2piece(Bitboard position, Board* board); +char bb2char(Bitboard position, Board* board); +char* bb2str(Bitboard position, Board* board); +void printBitboard(Bitboard bitboard); +char getPieceChar(Bitboard position, Board* board); +void printBoard(Board* board); +void printGame(Game* game); +Bitboard not(Bitboard bb); +char opponent(char color); +int countBits(Bitboard bb); +void sortNodes(Node* sortedNodes, Node* nodes, int len, char color); +void printMove(Move move); +void printFullMove(Move move, Board* board); +void printLegalMoves(Position* position); +void printNode(Node node); +void getTimestamp(char* timestamp); +void dumpContent(Game* game); +void dumpPGN(Game* game, char color, BOOL hasAI); +void move2str(char* str, Game* game, int moveNumber); +BOOL isAmbiguous(Position* posBefore, Move move); +unsigned long hashPosition(Position* position); +void writeToHashFile(Position* position, int evaluation, int depth); + +// ====== BOARD FILTERS ====== + +Bitboard getColoredPieces(Board* board, char color); +Bitboard getEmptySquares(Board* board); +Bitboard getOccupiedSquares(Board* board); +Bitboard getTwinPieces(Bitboard position, Board* board); +Bitboard fileFilter(Bitboard positions); +Bitboard rankFilter(Bitboard positions); + +// ======= DIRECTIONS ======== + +Bitboard east(Bitboard bb); +Bitboard west(Bitboard bb); +Bitboard north(Bitboard bb); +Bitboard south(Bitboard bb); +Bitboard NE(Bitboard bb); +Bitboard NW(Bitboard bb); +Bitboard SE(Bitboard bb); +Bitboard SW(Bitboard bb); +Bitboard WNW(Bitboard moving_piece); +Bitboard ENE(Bitboard moving_piece); +Bitboard NNW(Bitboard moving_piece); +Bitboard NNE(Bitboard moving_piece); +Bitboard ESE(Bitboard moving_piece); +Bitboard WSW(Bitboard moving_piece); +Bitboard SSE(Bitboard moving_piece); +Bitboard SSW(Bitboard moving_piece); + +// ========== PAWN =========== + +Bitboard getPawns(Board* board); +Bitboard pawnSimplePushes(Bitboard moving_piece, Board* board, char color); +Bitboard pawnDoublePushes(Bitboard moving_piece, Board* board, char color); +Bitboard pawnPushes(Bitboard moving_piece, Board* board, char color); +Bitboard pawnEastAttacks(Bitboard moving_piece, Board* board, char color); +Bitboard pawnWestAttacks(Bitboard moving_piece, Board* board, char color); +Bitboard pawnAttacks(Bitboard moving_piece, Board* board, char color); +Bitboard pawnSimpleCaptures(Bitboard moving_piece, Board* board, char color); +Bitboard pawnEpCaptures(Bitboard moving_piece, Position* position, char color); +Bitboard pawnCaptures(Bitboard moving_piece, Position* position, char color); +Bitboard pawnMoves(Bitboard moving_piece, Position* position, char color); +BOOL isDoublePush(int leaving, int arriving); +char getEpSquare(int leaving); +BOOL isDoubledPawn(Bitboard position, Board* board, char color); +BOOL isIsolatedPawn(Bitboard position, Board* board, char color); +BOOL isBackwardsPawn(Bitboard position, Board* board, char color); +BOOL isPassedPawn(Bitboard position, Board* board, char color); +BOOL isOpenFile(Bitboard position, Board* board); +BOOL isSemiOpenFile(Bitboard position, Board* board); + +// ========== KNIGHT ========= + +Bitboard getKnights(Board* board); +Bitboard knightAttacks(Bitboard moving_piece); +Bitboard knightMoves(Bitboard moving_piece, Board* board, char color); + +// ========== KING =========== + +Bitboard getKing(Board* board, char color); +Bitboard kingAttacks(Bitboard moving_piece); +Bitboard kingMoves(Bitboard moving_piece, Board* board, char color); +BOOL canCastleKingside(Position* position, char color); +BOOL canCastleQueenside(Position* position, char color); +char removeCastlingRights(char original_rights, char removed_rights); + +// ========== BISHOP ========= + +Bitboard getBishops(Board* board); +Bitboard NE_ray(Bitboard bb); +Bitboard SE_ray(Bitboard bb); +Bitboard NW_ray(Bitboard bb); +Bitboard SW_ray(Bitboard bb); +Bitboard NE_attack(Bitboard single_piece, Board* board, char color); +Bitboard NW_attack(Bitboard single_piece, Board* board, char color); +Bitboard SE_attack(Bitboard single_piece, Board* board, char color); +Bitboard SW_attack(Bitboard single_piece, Board* board, char color); +Bitboard diagonalAttacks(Bitboard single_piece, Board* board, char color); +Bitboard antiDiagonalAttacks(Bitboard single_piece, Board* board, char color); +Bitboard bishopAttacks(Bitboard moving_pieces, Board* board, char color); +Bitboard bishopMoves(Bitboard moving_piece, Board* board, char color); + +// ========== ROOK =========== + +Bitboard getRooks(Board* board); +Bitboard northRay(Bitboard moving_pieces); +Bitboard southRay(Bitboard moving_pieces); +Bitboard eastRay(Bitboard moving_pieces); +Bitboard westRay(Bitboard moving_pieces); +Bitboard northAttack(Bitboard single_piece, Board* board, char color); +Bitboard southAttack(Bitboard single_piece, Board* board, char color); +Bitboard fileAttacks(Bitboard single_piece, Board* board, char color); +Bitboard eastAttack(Bitboard single_piece, Board* board, char color); +Bitboard westAttack(Bitboard single_piece, Board* board, char color); +Bitboard rankAttacks(Bitboard single_piece, Board* board, char color); +Bitboard rookAttacks(Bitboard moving_piece, Board* board, char color); +Bitboard rookMoves(Bitboard moving_piece, Board* board, char color); + +// ========== QUEEN ========== + +Bitboard getQueens(Board* board); +Bitboard queenAttacks(Bitboard moving_piece, Board* board, char color); +Bitboard queenMoves(Bitboard moving_piece, Board* board, char color); + +// ======== MAKE MOVE ======== + +void clearPositions(Board* board, Bitboard positions); +void movePiece(Board* board, Move move); +void updatePosition(Position* newPosition, Position* position, Move move); +void makeMove(Game* game, Move move); +void unmakeMove(Game* game); + +// ======== MOVE GEN ========= + +Bitboard getMoves(Bitboard movingPiece, Position* position, char color); +int pseudoLegalMoves(Move* moves, Position* position, char color); +Bitboard getAttacks(Bitboard movingPiece, Board* board, char color); +int countAttacks(Bitboard target, Board* board, char color); +BOOL isAttacked(Bitboard target, Board* board, char color); +BOOL isCheck(Board* board, char color); +BOOL isLegalMove(Position* position, Move move); +int legalMoves(Move* legalMoves, Position* position, char color); +int legalMovesCount(Position* position, char color); +int staticOrderLegalMoves(Move* orderedLegalMoves, Position* position, char color); +int legalCaptures(Move* legalCaptures, Position* position, char color); + +// ====== GAME CONTROL ======= + +BOOL isCheckmate(Position* position); +BOOL isStalemate(Position* position); +BOOL hasInsufficientMaterial(Board* board); +BOOL isEndgame(Board* board); +BOOL isOver75MovesRule(Position* position); +BOOL hasGameEnded(Position* position); +void printOutcome(Position* position); + +// ========== EVAL =========== + +int winScore(char color); +int materialSum(Board* board, char color); +int materialBalance(Board* board); +int positionalBonus(Board* board, char color); +int positionalBalance(Board* board); +int endNodeEvaluation(Position* position); +int staticEvaluation(Position* position); +int getCaptureSequence(Move* captures, Position* position, int targetSquare); +int staticExchangeEvaluation(Position* position, int targetSquare); +int quiescenceEvaluation(Position* position); + +// ========= SEARCH ========== + +Node staticSearch(Position* position); +Node quiescenceSearch(Position* position); +Node alphaBeta(Position* position, char depth, int alpha, int beta); +int alphaBetaNodes(Node* nodes, Position* position, char depth); +Node iterativeDeepeningAlphaBeta(Position* position, char depth, int alpha, int beta, BOOL verbose); +Node pIDAB(Position* position, char depth, int* p_alpha, int* p_beta); +Node pIDABhashed(Position* position, char depth, int* p_alpha, int* p_beta); +Move getRandomMove(Position* position); +Move getAIMove(Game* game, int depth); +Move parseMove(char* move); +Move getPlayerMove(); +Move suggestMove(char fen[], int depth); + +// Parallel processing currently only implemented for Windows +#ifdef _WIN32 +DWORD WINAPI evaluatePositionThreadFunction(LPVOID lpParam); +DWORD WINAPI evaluatePositionThreadFunctionHashed(LPVOID lpParam); +Node idabThreaded(Position* position, int depth, BOOL verbose); +Node idabThreadedBestFirst(Position* position, int depth, BOOL verbose); +Node idabThreadedBestFirstHashed(Position* position, int depth, BOOL verbose); +#endif + +// ===== PLAY LOOP (TEXT) ==== + +void playTextWhite(int depth); +void playTextBlack(int depth); +void playTextAs(char color, int depth); +void playTextRandomColor(int depth); + +// =========================== + +#endif /* FAST_CHESS_H_ */ \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/cntdown_timer/README.md b/Applications/Official/DEV_FW/source/cntdown_timer/README.md new file mode 100644 index 000000000..3cd3d4d53 --- /dev/null +++ b/Applications/Official/DEV_FW/source/cntdown_timer/README.md @@ -0,0 +1,11 @@ +## Simple count down timer application for flipper zero + +### How to use +`up/down`: set second/minute/hour value. + +`ok`: start/stop counting. + +`long press on ok`: stop counting and reset counter. + +`left/right`: select second/minute/hour value. + diff --git a/Applications/Official/DEV_FW/source/cntdown_timer/app.c b/Applications/Official/DEV_FW/source/cntdown_timer/app.c new file mode 100644 index 000000000..5c03ea246 --- /dev/null +++ b/Applications/Official/DEV_FW/source/cntdown_timer/app.c @@ -0,0 +1,71 @@ +#include "views/countdown_view.h" +#include "app.h" + +static void register_view(ViewDispatcher* dispatcher, View* view, uint32_t viewid); + +int32_t app_main(void* p) { + UNUSED(p); + + CountDownTimerApp* app = countdown_app_new(); + + countdown_app_run(app); + + countdown_app_delete(app); + + return 0; +} + +static uint32_t view_exit(void* ctx) { + furi_assert(ctx); + + return VIEW_NONE; +} + +CountDownTimerApp* countdown_app_new(void) { + CountDownTimerApp* app = (CountDownTimerApp*)(malloc(sizeof(CountDownTimerApp))); + + // 1.1 open gui + app->gui = furi_record_open(RECORD_GUI); + + // 2.1 setup view dispatcher + app->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(app->view_dispatcher); + + // 2.2 attach view dispatcher to gui + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + // 2.3 attach views to the dispatcher + // helloworld view + app->helloworld_view = countdown_timer_view_new(); + register_view(app->view_dispatcher, countdown_timer_view_get_view(app->helloworld_view), 0xff); + + // 2.5 switch to default view + view_dispatcher_switch_to_view(app->view_dispatcher, 0xff); + + return app; +} + +void countdown_app_delete(CountDownTimerApp* app) { + furi_assert(app); + + // delete views + view_dispatcher_remove_view(app->view_dispatcher, 0xff); + countdown_timer_view_delete(app->helloworld_view); // hello world view + + // delete view dispatcher + view_dispatcher_free(app->view_dispatcher); + furi_record_close(RECORD_GUI); + + // self + free(app); +} + +void countdown_app_run(CountDownTimerApp* app) { + view_dispatcher_run(app->view_dispatcher); +} + +static void register_view(ViewDispatcher* dispatcher, View* view, uint32_t viewid) { + view_dispatcher_add_view(dispatcher, viewid, view); + + view_set_previous_callback(view, view_exit); +} diff --git a/Applications/Official/DEV_FW/source/cntdown_timer/app.h b/Applications/Official/DEV_FW/source/cntdown_timer/app.h new file mode 100644 index 000000000..413b3dbbf --- /dev/null +++ b/Applications/Official/DEV_FW/source/cntdown_timer/app.h @@ -0,0 +1,22 @@ +#ifndef __APP_H__ +#define __APP_H__ + +#include +#include +#include + +// app +typedef struct { + Gui* gui; // gui object + ViewDispatcher* view_dispatcher; // view dispacther of the gui + + // views + CountDownTimView* helloworld_view; + +} CountDownTimerApp; + +CountDownTimerApp* countdown_app_new(void); +void countdown_app_delete(CountDownTimerApp* app); +void countdown_app_run(CountDownTimerApp* app); + +#endif \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/cntdown_timer/application.fam b/Applications/Official/DEV_FW/source/cntdown_timer/application.fam new file mode 100644 index 000000000..f40ccaa51 --- /dev/null +++ b/Applications/Official/DEV_FW/source/cntdown_timer/application.fam @@ -0,0 +1,14 @@ +App( + appid="Count_Down_Timer", + name="Count Down Timer", + apptype=FlipperAppType.EXTERNAL, + entry_point="app_main", + cdefines=["APP_COUNT_DOWN_TIMER"], + requires=[ + "gui", + ], + stack_size=2 * 1024, + order=20, + fap_icon="cntdown_timer.png", + fap_category="Tools", +) diff --git a/Applications/Official/DEV_FW/source/cntdown_timer/cntdown_timer.png b/Applications/Official/DEV_FW/source/cntdown_timer/cntdown_timer.png new file mode 100644 index 000000000..b25c2718e Binary files /dev/null and b/Applications/Official/DEV_FW/source/cntdown_timer/cntdown_timer.png differ diff --git a/Applications/Official/DEV_FW/source/cntdown_timer/utils/utils.c b/Applications/Official/DEV_FW/source/cntdown_timer/utils/utils.c new file mode 100644 index 000000000..8ce82f1c6 --- /dev/null +++ b/Applications/Official/DEV_FW/source/cntdown_timer/utils/utils.c @@ -0,0 +1,34 @@ +#include +#include "utils.h" + +static const NotificationSequence sequence_beep = { + &message_blue_255, + &message_note_d5, + &message_delay_100, + &message_sound_off, + + NULL, +}; + +void notification_beep_once() { + notification_message(furi_record_open(RECORD_NOTIFICATION), &sequence_beep); + notification_off(); +} + +void notification_off() { + furi_record_close(RECORD_NOTIFICATION); +} + +void notification_timeup() { + notification_message(furi_record_open(RECORD_NOTIFICATION), &sequence_audiovisual_alert); +} + +void parse_sec_to_time_str(char* buffer, size_t len, int32_t sec) { + snprintf( + buffer, + len, + "%02ld:%02ld:%02ld", + (sec % (60 * 60 * 24)) / (60 * 60), // hour + (sec % (60 * 60)) / 60, // minute + sec % 60); // second +} diff --git a/Applications/Official/DEV_FW/source/cntdown_timer/utils/utils.h b/Applications/Official/DEV_FW/source/cntdown_timer/utils/utils.h new file mode 100644 index 000000000..814e16222 --- /dev/null +++ b/Applications/Official/DEV_FW/source/cntdown_timer/utils/utils.h @@ -0,0 +1,12 @@ +#ifndef __UTILS_H__ +#define __UTILS_H__ +#include +#include + +void notification_beep_once(); +void notification_off(); +void notification_timeup(); + +void parse_sec_to_time_str(char* buffer, size_t len, int32_t sec); + +#endif // __UTILS_H__ \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/cntdown_timer/views/countdown_view.c b/Applications/Official/DEV_FW/source/cntdown_timer/views/countdown_view.c new file mode 100644 index 000000000..97e8cb248 --- /dev/null +++ b/Applications/Official/DEV_FW/source/cntdown_timer/views/countdown_view.c @@ -0,0 +1,346 @@ +#include "countdown_view.h" +#include "../utils/utils.h" + +// internal +static void handle_misc_cmd(CountDownTimView* hw, CountDownViewCmd cmd); +static void handle_time_setting_updown(CountDownTimView* cdv, CountDownViewCmd cmd); +static void handle_time_setting_select(InputKey key, CountDownTimView* cdv); +static void draw_selection(Canvas* canvas, CountDownViewSelect selection); + +static void countdown_timer_start_counting(CountDownTimView* cdv); +static void countdown_timer_pause_counting(CountDownTimView* cdv); + +// callbacks +static void countdown_timer_view_on_enter(void* ctx); +static void countdown_timer_view_on_draw(Canvas* canvas, void* ctx); +static bool countdown_timer_view_on_input(InputEvent* event, void* ctx); +static void timer_cb(void* ctx); + +CountDownTimView* countdown_timer_view_new() { + CountDownTimView* cdv = (CountDownTimView*)(malloc(sizeof(CountDownTimView))); + + cdv->view = view_alloc(); + + cdv->timer = furi_timer_alloc(timer_cb, FuriTimerTypePeriodic, cdv); + + cdv->counting = false; + + view_set_context(cdv->view, cdv); + + view_allocate_model(cdv->view, ViewModelTypeLocking, sizeof(CountDownModel)); + + view_set_draw_callback(cdv->view, countdown_timer_view_on_draw); + view_set_input_callback(cdv->view, countdown_timer_view_on_input); + view_set_enter_callback(cdv->view, countdown_timer_view_on_enter); + + return cdv; +} + +void countdown_timer_view_delete(CountDownTimView* cdv) { + furi_assert(cdv); + + view_free(cdv->view); + furi_timer_stop(cdv->timer); + furi_timer_free(cdv->timer); + + free(cdv); +} + +View* countdown_timer_view_get_view(CountDownTimView* cdv) { + return cdv->view; +} + +void countdown_timer_view_state_reset(CountDownTimView* cdv) { + cdv->counting = false; + + with_view_model( + cdv->view, CountDownModel * model, { model->count = model->saved_count_setting; }, true) +} + +void countdown_timer_state_toggle(CountDownTimView* cdv) { + bool on = cdv->counting; + if(!on) { + countdown_timer_start_counting(cdv); + } else { + countdown_timer_pause_counting(cdv); + } + + cdv->counting = !on; +} + +// on enter callback, CountDownTimView as ctx +static void countdown_timer_view_on_enter(void* ctx) { + furi_assert(ctx); + + CountDownTimView* cdv = (CountDownTimView*)ctx; + + // set current count to a initial value + with_view_model( + cdv->view, + CountDownModel * model, + { + model->count = INIT_COUNT; + model->saved_count_setting = INIT_COUNT; + }, + true); +} + +// view draw callback, CountDownModel as ctx +static void countdown_timer_view_on_draw(Canvas* canvas, void* ctx) { + furi_assert(ctx); + CountDownModel* model = (CountDownModel*)ctx; + + char buffer[64]; + + int32_t count = model->count; + int32_t expected_count = model->saved_count_setting; + + CountDownViewSelect select = model->select; + + // elements_frame(canvas, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + + canvas_set_font(canvas, FontBigNumbers); + draw_selection(canvas, select); + + parse_sec_to_time_str(buffer, sizeof(buffer), count); + canvas_draw_str_aligned( + canvas, SCREEN_CENTER_X, SCREEN_CENTER_Y, AlignCenter, AlignCenter, buffer); + + elements_progress_bar(canvas, 0, 0, SCREEN_WIDTH, (1.0 * count / expected_count)); +} + +// keys input event callback, CountDownTimView as ctx +static bool countdown_timer_view_on_input(InputEvent* event, void* ctx) { + furi_assert(ctx); + + CountDownTimView* hw = (CountDownTimView*)ctx; + + if(event->type == InputTypeShort || event->type == InputTypeRepeat) { + switch(event->key) { + case InputKeyUp: + case InputKeyDown: + case InputKeyRight: + case InputKeyLeft: + handle_time_setting_select(event->key, hw); + break; + + case InputKeyOk: + if(event->type == InputTypeShort) { + handle_misc_cmd(hw, CountDownTimerToggleCounting); + } + break; + + default: + break; + } + + return true; + } + + if(event->type == InputTypeLong) { + switch(event->key) { + case InputKeyOk: + handle_misc_cmd(hw, CountDownTimerReset); + break; + + case InputKeyBack: + return false; + break; + + default: + break; + } + + return true; + } + + return false; +} + +static void timer_cb(void* ctx) { + furi_assert(ctx); + + CountDownTimView* cdv = (CountDownTimView*)ctx; + + int32_t count; + bool timeup = false; + + // decrement counter + with_view_model( + cdv->view, + CountDownModel * model, + { + count = model->count; + count--; + + // check timeup + if(count <= 0) { + count = 0; + timeup = true; + } + + model->count = count; + }, + true); + + if(timeup) { + handle_misc_cmd(cdv, CountDownTimerTimeUp); + } +} + +static void handle_time_setting_updown(CountDownTimView* cdv, CountDownViewCmd cmd) { + int32_t count; + + with_view_model( + cdv->view, + CountDownModel * model, + { + count = model->count; + switch(cmd) { + case CountDownTimerMinuteUp: + count += 60; + break; + case CountDownTimerMinuteDown: + count -= 60; + break; + case CountDownTimerHourDown: + count -= 3600; + break; + case CountDownTimerHourUp: + count += 3600; + break; + case CountDownTimerSecUp: + count++; + break; + case CountDownTimerSecDown: + count--; + break; + default: + break; + } + + if(count < 0) { + count = 0; + } + + // update count state + model->count = count; + + // save the count time setting + model->saved_count_setting = count; + }, + true); +} + +static void handle_misc_cmd(CountDownTimView* hw, CountDownViewCmd cmd) { + switch(cmd) { + case CountDownTimerTimeUp: + notification_timeup(); + break; + + case CountDownTimerReset: + furi_timer_stop(hw->timer); + countdown_timer_view_state_reset(hw); + notification_off(); + + break; + + case CountDownTimerToggleCounting: + countdown_timer_state_toggle(hw); + break; + + default: + break; + } + + return; +} + +static void handle_time_setting_select(InputKey key, CountDownTimView* cdv) { + bool counting = cdv->counting; + CountDownViewCmd setting_cmd = CountDownTimerSecUp; + CountDownViewSelect selection; + + if(counting) { + return; + } + + // load current selection from model context + with_view_model( + cdv->view, CountDownModel * model, { selection = model->select; }, false); + + // select + switch(key) { + case InputKeyUp: + switch(selection) { + case CountDownTimerSelectSec: + setting_cmd = CountDownTimerSecUp; + break; + case CountDownTimerSelectMinute: + setting_cmd = CountDownTimerMinuteUp; + break; + case CountDownTimerSelectHour: + setting_cmd = CountDownTimerHourUp; + break; + } + + handle_time_setting_updown(cdv, setting_cmd); + break; + + case InputKeyDown: + switch(selection) { + case CountDownTimerSelectSec: + setting_cmd = CountDownTimerSecDown; + break; + case CountDownTimerSelectMinute: + setting_cmd = CountDownTimerMinuteDown; + break; + case CountDownTimerSelectHour: + setting_cmd = CountDownTimerHourDown; + break; + } + + handle_time_setting_updown(cdv, setting_cmd); + break; + + case InputKeyRight: + selection--; + selection = selection % 3; + break; + + case InputKeyLeft: + selection++; + selection = selection % 3; + break; + + default: + break; + } + + // save selection to model context + with_view_model( + cdv->view, CountDownModel * model, { model->select = selection; }, false); +} + +static void draw_selection(Canvas* canvas, CountDownViewSelect selection) { + switch(selection) { + case CountDownTimerSelectSec: + elements_slightly_rounded_box(canvas, SCREEN_CENTER_X + 25, SCREEN_CENTER_Y + 11, 24, 2); + break; + case CountDownTimerSelectMinute: + elements_slightly_rounded_box(canvas, SCREEN_CENTER_X - 10, SCREEN_CENTER_Y + 11, 21, 2); + break; + case CountDownTimerSelectHour: + elements_slightly_rounded_box(canvas, SCREEN_CENTER_X - 47, SCREEN_CENTER_Y + 11, 24, 2); + break; + } +} + +static void countdown_timer_start_counting(CountDownTimView* cdv) { + furi_timer_start(cdv->timer, furi_kernel_get_tick_frequency() * 1); // 1s +} + +static void countdown_timer_pause_counting(CountDownTimView* cdv) { + furi_timer_stop(cdv->timer); + notification_off(); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/cntdown_timer/views/countdown_view.h b/Applications/Official/DEV_FW/source/cntdown_timer/views/countdown_view.h new file mode 100644 index 000000000..ed8114f8e --- /dev/null +++ b/Applications/Official/DEV_FW/source/cntdown_timer/views/countdown_view.h @@ -0,0 +1,59 @@ +#ifndef __COUNTDOWN_VIEW_H__ +#define __COUNTDOWN_VIEW_H__ + +#include +#include +#include +#include + +#define SCREEN_WIDTH 128 +#define SCREEN_HEIGHT 64 +#define SCREEN_CENTER_X (SCREEN_WIDTH / 2) +#define SCREEN_CENTER_Y (SCREEN_HEIGHT / 2) + +#define INIT_COUNT 10 + +typedef enum { + CountDownTimerMinuteUp, + CountDownTimerMinuteDown, + CountDownTimerSecDown, + CountDownTimerSecUp, + CountDownTimerHourUp, + CountDownTimerHourDown, + CountDownTimerReset, + CountDownTimerTimeUp, + CountDownTimerToggleCounting, +} CountDownViewCmd; + +typedef enum { + CountDownTimerSelectSec, + CountDownTimerSelectMinute, + CountDownTimerSelectHour, +} CountDownViewSelect; + +typedef struct { + int32_t count; + int32_t saved_count_setting; + CountDownViewSelect select; // setting +} CountDownModel; + +typedef struct { + View* view; + FuriTimer* timer; // 1Hz tick timer + bool counting; + +} CountDownTimView; + +// functions +// allocate helloworld view +CountDownTimView* countdown_timer_view_new(); + +// delete helloworld view +void countdown_timer_view_delete(CountDownTimView* cdv); + +// return view +View* countdown_timer_view_get_view(CountDownTimView* cdv); + +void countdown_timer_view_state_reset(CountDownTimView* cdv); // set initial state +void countdown_timer_state_toggle(CountDownTimView* cdv); +#endif // __COUNTDOWN_VIEW_H__ \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/dice/application.fam b/Applications/Official/DEV_FW/source/dice/application.fam new file mode 100644 index 000000000..4cf6506b7 --- /dev/null +++ b/Applications/Official/DEV_FW/source/dice/application.fam @@ -0,0 +1,12 @@ +App( + appid="Dice", + name="Dice [RM]", + apptype=FlipperAppType.EXTERNAL, + entry_point="dice_app", + cdefines=["APP_DICE"], + requires=["gui"], + stack_size=2 * 1024, + order=70, + fap_icon="dice.png", + fap_category="Games", +) diff --git a/Applications/Official/DEV_FW/source/dice/dice.c b/Applications/Official/DEV_FW/source/dice/dice.c new file mode 100644 index 000000000..2f9655843 --- /dev/null +++ b/Applications/Official/DEV_FW/source/dice/dice.c @@ -0,0 +1,581 @@ +#include +#include +#include "furi_hal_random.h" +#include +#include +#include +#include +#include "applications/settings/desktop_settings/desktop_settings_app.h" +#include + +#define TAG "Dice Roller" + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +DolphinDeed getRandomDeed() { + DolphinDeed returnGrp[14] = {1, 5, 8, 10, 12, 15, 17, 20, 21, 25, 26, 28, 29, 32}; + static bool rand_generator_inited = false; + if(!rand_generator_inited) { + srand(furi_get_tick()); + rand_generator_inited = true; + } + uint8_t diceRoll = (rand() % COUNT_OF(returnGrp)); // JUST TO GET IT GOING? AND FIX BUG + diceRoll = (rand() % COUNT_OF(returnGrp)); + return returnGrp[diceRoll]; +} +typedef struct { + EventType type; + InputEvent input; +} PluginEvent; + +typedef struct { + FuriMutex* mutex; + FuriMessageQueue* event_queue; + DesktopSettings* desktop_settings; + FuriHalRtcDateTime datetime; + uint8_t diceSelect; + uint8_t diceQty; + uint8_t diceRoll; + uint8_t playerOneScore; + uint8_t playerTwoScore; + char rollTime[1][15]; + char diceType[1][11]; + char strings[5][45]; + char theScores[1][45]; + bool letsRoll; +} DiceState; + +static void dice_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 dice_render_callback(Canvas* const canvas, void* ctx) { + DiceState* state = ctx; + if(furi_mutex_acquire(state->mutex, 200) != FuriStatusOk) { + // Can't obtain mutex, requeue render + PluginEvent event = {.type = EventTypeTick}; + furi_message_queue_put(state->event_queue, &event, 0); + return; + } + + canvas_set_font(canvas, FontSecondary); + if(state->diceSelect < 220) { + if(state->diceQty == 1) { + elements_button_left(canvas, "x1"); + } else if(state->diceQty == 2) { + elements_button_left(canvas, "x2"); + } else if(state->diceQty == 3) { + elements_button_left(canvas, "x3"); + } else if(state->diceQty == 4) { + elements_button_left(canvas, "x4"); + } else if(state->diceQty == 5) { + elements_button_left(canvas, "x5"); + } else if(state->diceQty == 6) { + elements_button_left(canvas, "x6"); + } + } + if(state->letsRoll) { + furi_hal_rtc_get_datetime(&state->datetime); + uint8_t hour = state->datetime.hour; + char strAMPM[3]; + snprintf(strAMPM, sizeof(strAMPM), "%s", "AM"); + if(hour > 12) { + hour -= 12; + snprintf(strAMPM, sizeof(strAMPM), "%s", "PM"); + } + snprintf( + state->rollTime[0], + sizeof(state->rollTime[0]), + "%.2d:%.2d:%.2d %s", + hour, + state->datetime.minute, + state->datetime.second, + strAMPM); + if(state->diceSelect == 229) { + const char* eightBall[] = { + "It is certain", + "Without a doubt", + "You may rely on it", + "Yes definitely", + "It is decidedly so", + "As I see it, yes", + "Most likely", + "Yes", + "Outlook good", + "Signs point to yes", + "Reply hazy try again", + "Better not tell you now", + "Ask again later", + "Cannot predict now", + "Concentrate and ask again", + "Don't count on it", + "Outlook not so good", + "My sources say no", + "Very doubtful", + "My reply is no"}; + state->diceRoll = + ((rand() % state->diceSelect) + 1); // JUST TO GET IT GOING? AND FIX BUG + snprintf(state->diceType[0], sizeof(state->diceType[0]), "%s", "8BALL"); + snprintf( + state->strings[0], + sizeof(state->strings[0]), + "%s at %s", + state->diceType[0], + state->rollTime[0]); + uint8_t d1_i = rand() % COUNT_OF(eightBall); + snprintf(state->strings[1], sizeof(state->strings[1]), "%s", eightBall[d1_i]); + } else if(state->diceSelect == 228) { + const char* eightBall[] = { + "I'd do it.", + "Hell, yeah!", + "You bet your life!", + "What are you waiting for?", + "You could do worse things.", + "Sure, I won't tell.", + "Yeah, you got this. Would I lie to you?", + "Looks like fun to me. ", + "Yeah, sure, why not?", + "DO IT!!!", + "Who's it gonna hurt?", + "Can you blame someone else?", + "Ask me again later.", + "Maybe, maybe not, I can't tell right now. ", + "Are you the betting type? ", + "Don't blame me if you get caught.", + "What have you got to lose?", + "I wouldn't if I were you.", + "My money's on the snowball.", + "Oh Hell no!"}; + state->diceRoll = + ((rand() % state->diceSelect) + 1); // JUST TO GET IT GOING? AND FIX BUG + snprintf(state->diceType[0], sizeof(state->diceType[0]), "%s", "Devil Ball"); + snprintf( + state->strings[0], + sizeof(state->strings[0]), + "%s at %s", + state->diceType[0], + state->rollTime[0]); + uint8_t d1_i = rand() % COUNT_OF(eightBall); + snprintf(state->strings[1], sizeof(state->strings[1]), "%s", eightBall[d1_i]); + } else if(state->diceSelect == 230) { + const char* diceOne[] = { + "Nibble", + "Massage", + "Touch", + "Caress", + "Pet", + "Fondle", + "Suck", + "Lick", + "Blow", + "Kiss", + "???"}; + const char* diceTwo[] = { + "Navel", + "Ears", + "Lips", + "Neck", + "Hand", + "Thigh", + "Nipple", + "Breasts", + "???", + "Genitals"}; + state->diceRoll = + ((rand() % state->diceSelect) + 1); // JUST TO GET IT GOING? AND FIX BUG + snprintf(state->diceType[0], sizeof(state->diceType[0]), "%s", "SEX?"); + snprintf( + state->strings[0], + sizeof(state->strings[0]), + "%s at %s", + state->diceType[0], + state->rollTime[0]); + uint8_t d1_i = rand() % COUNT_OF(diceOne); + uint8_t d2_i = rand() % COUNT_OF(diceTwo); + snprintf( + state->strings[1], + sizeof(state->strings[1]), + "%s %s", + diceOne[d1_i], + diceTwo[d2_i]); + } else if(state->diceSelect == 231) { + const char* deckOne[] = {"2H", "2C", "2D", "2S", "3H", "3C", "3D", "3S", "4H", + "4C", "4D", "4S", "5H", "5C", "5D", "5S", "6H", "6C", + "6D", "6S", "7H", "7C", "7D", "7S", "8H", "8C", "8D", + "8S", "9H", "9C", "9D", "9S", "10H", "10C", "10D", "10S", + "JH", "JC", "JD", "JS", "QH", "QC", "QD", "QS", "KH", + "KC", "KD", "KS", "AH", "AC", "AD", "AS"}; + char* deckTwo[] = {"2H", "2C", "2D", "2S", "3H", "3C", "3D", "3S", "4H", + "4C", "4D", "4S", "5H", "5C", "5D", "5S", "6H", "6C", + "6D", "6S", "7H", "7C", "7D", "7S", "8H", "8C", "8D", + "8S", "9H", "9C", "9D", "9S", "10H", "10C", "10D", "10S", + "JH", "JC", "JD", "JS", "QH", "QC", "QD", "QS", "KH", + "KC", "KD", "KS", "AH", "AC", "AD"}; // ONE LESS SINCE ONE WILL BE REMOVED + state->diceRoll = + ((rand() % state->diceSelect) + 1); // JUST TO GET IT GOING? AND FIX BUG + snprintf(state->diceType[0], sizeof(state->diceType[0]), "%s", "WAR!"); + snprintf( + state->strings[0], + sizeof(state->strings[0]), + "%s at %s", + state->diceType[0], + state->rollTime[0]); + uint8_t d1_i = rand() % COUNT_OF(deckOne); + // INITIALIZE WITH PLACEHOLDERS TO AVOID MAYBE UNINITIALIZED ERROR + for(uint8_t i = 0; i < COUNT_OF(deckOne); i++) { + if(i < d1_i) { + snprintf(deckTwo[i], 8, "%s", deckOne[i]); + } else if(i > d1_i) { + snprintf(deckTwo[i - 1], 8, "%s", deckOne[i]); + } + } + uint8_t d2_i = rand() % COUNT_OF(deckTwo); + if(d1_i > d2_i) { + state->playerOneScore++; + snprintf( + state->strings[1], + sizeof(state->strings[1]), + "%s > %s", + deckOne[d1_i], + deckTwo[d2_i]); + } else { + state->playerTwoScore++; + snprintf( + state->strings[1], + sizeof(state->strings[1]), + "%s < %s", + deckOne[d1_i], + deckTwo[d2_i]); + } + } else if(state->diceSelect == 232) { + const char* diceOne[] = { + "You", "You choose", "Nobody", "Everyone", "Nose goes", "Player to your right"}; + const char* diceTwo[] = { + "take a tiny toke", + "just chill", + "take 2 tokes", + "take a huge hit", + "bogart it", + "take a puff"}; + const char* diceThree[] = { + "while humming a tune", + "with your eyes closed", + "on your knees", + "while holding your nose", + "while spinning in a circle", + "in slow motion"}; + const char* diceFour[] = { + "twice", + "then tell a joke", + "then laugh as hard as you can", + "with the player to your left", + "then sing a song", + "then do a dance"}; + state->diceRoll = + ((rand() % state->diceSelect) + 1); // JUST TO GET IT GOING? AND FIX BUG + snprintf(state->diceType[0], sizeof(state->diceType[0]), "%s", "WEED!"); + snprintf( + state->strings[0], + sizeof(state->strings[0]), + "%s at %s", + state->diceType[0], + state->rollTime[0]); + uint8_t d1_i = rand() % COUNT_OF(diceOne); + uint8_t d2_i = rand() % COUNT_OF(diceTwo); + uint8_t d3_i = rand() % COUNT_OF(diceThree); + uint8_t d4_i = rand() % COUNT_OF(diceFour); + snprintf(state->strings[1], sizeof(state->strings[1]), "%s", diceOne[d1_i]); + snprintf(state->strings[2], sizeof(state->strings[2]), "%s", diceTwo[d2_i]); + snprintf(state->strings[3], sizeof(state->strings[3]), "%s", diceThree[d3_i]); + snprintf(state->strings[4], sizeof(state->strings[4]), "%s", diceFour[d4_i]); + } else { + state->diceRoll = ((rand() % state->diceSelect) + 1); + snprintf( + state->diceType[0], sizeof(state->diceType[0]), "%s%d", "d", state->diceSelect); + snprintf( + state->strings[0], + sizeof(state->strings[0]), + "%d%s at %s", + state->diceQty, + state->diceType[0], + state->rollTime[0]); + if(state->diceSelect >= 20 && state->diceRoll == state->diceSelect) + DOLPHIN_DEED(getRandomDeed()); + if(state->diceSelect >= 20 && state->diceRoll == state->diceSelect - 1) + DOLPHIN_DEED(getRandomDeed()); + if(state->diceQty == 1) { + snprintf(state->strings[1], sizeof(state->strings[1]), "%d", state->diceRoll); + } else if(state->diceQty == 2) { + snprintf( + state->strings[1], + sizeof(state->strings[1]), + "%d %d", + state->diceRoll, + ((rand() % state->diceSelect) + 1)); + } else if(state->diceQty == 3) { + snprintf( + state->strings[1], + sizeof(state->strings[1]), + "%d %d %d", + state->diceRoll, + ((rand() % state->diceSelect) + 1), + ((rand() % state->diceSelect) + 1)); + } else if(state->diceQty == 4) { + snprintf( + state->strings[1], + sizeof(state->strings[1]), + "%d %d %d %d", + state->diceRoll, + ((rand() % state->diceSelect) + 1), + ((rand() % state->diceSelect) + 1), + ((rand() % state->diceSelect) + 1)); + } else if(state->diceQty == 5) { + snprintf( + state->strings[1], + sizeof(state->strings[1]), + "%d %d %d %d %d", + state->diceRoll, + ((rand() % state->diceSelect) + 1), + ((rand() % state->diceSelect) + 1), + ((rand() % state->diceSelect) + 1), + ((rand() % state->diceSelect) + 1)); + } else if(state->diceQty == 6) { + snprintf( + state->strings[1], + sizeof(state->strings[1]), + "%d %d %d %d %d %d", + state->diceRoll, + ((rand() % state->diceSelect) + 1), + ((rand() % state->diceSelect) + 1), + ((rand() % state->diceSelect) + 1), + ((rand() % state->diceSelect) + 1), + ((rand() % state->diceSelect) + 1)); + } + } + state->letsRoll = false; + } + furi_mutex_release(state->mutex); + if(state->diceRoll != 0) { + if(state->diceSelect == 232) { + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 64, 8, AlignCenter, AlignCenter, state->strings[0]); + canvas_draw_str_aligned(canvas, 64, 18, AlignCenter, AlignCenter, state->strings[1]); + canvas_draw_str_aligned(canvas, 64, 26, AlignCenter, AlignCenter, state->strings[2]); + canvas_draw_str_aligned(canvas, 64, 34, AlignCenter, AlignCenter, state->strings[3]); + canvas_draw_str_aligned(canvas, 64, 42, AlignCenter, AlignCenter, state->strings[4]); + } else if(state->diceSelect == 228 || state->diceSelect == 229) { + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 64, 20, AlignCenter, AlignCenter, state->strings[1]); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 64, 8, AlignCenter, AlignCenter, state->strings[0]); + } else { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 64, 20, AlignCenter, AlignCenter, state->strings[1]); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 64, 8, AlignCenter, AlignCenter, state->strings[0]); + } + if(state->diceSelect == 231 && + !(state->playerOneScore == 0 && state->playerTwoScore == 0)) { + canvas_set_font(canvas, FontSecondary); + snprintf( + state->theScores[0], + sizeof(state->theScores[0]), + "%d %d", + state->playerOneScore, + state->playerTwoScore); + canvas_draw_str_aligned(canvas, 64, 34, AlignCenter, AlignCenter, state->theScores[0]); + } + } + if(state->diceSelect == 229 || state->diceSelect == 228) { + elements_button_center(canvas, "Shake"); + } else if(state->diceSelect == 231) { + elements_button_center(canvas, "Draw"); + } else { + elements_button_center(canvas, "Roll"); + } + if(state->diceSelect == 2) { + elements_button_right(canvas, "d2"); + } else if(state->diceSelect == 3) { + elements_button_right(canvas, "d3"); + } else if(state->diceSelect == 4) { + elements_button_right(canvas, "d4"); + } else if(state->diceSelect == 6) { + elements_button_right(canvas, "d6"); + } else if(state->diceSelect == 8) { + elements_button_right(canvas, "d8"); + } else if(state->diceSelect == 10) { + elements_button_right(canvas, "d10"); + } else if(state->diceSelect == 12) { + elements_button_right(canvas, "d12"); + } else if(state->diceSelect == 20) { + elements_button_right(canvas, "d20"); + } else if(state->diceSelect == 59) { + elements_button_right(canvas, "d59"); + } else if(state->diceSelect == 69) { + elements_button_right(canvas, "d69"); + } else if(state->diceSelect == 100) { + elements_button_right(canvas, "d100"); + } else if(state->diceSelect == 229) { + elements_button_right(canvas, "8BALL"); + } else if(state->diceSelect == 228) { + elements_button_right(canvas, "DBALL"); + } else if(state->diceSelect == 230) { + elements_button_right(canvas, "SEX"); + } else if(state->diceSelect == 231) { + elements_button_right(canvas, "WAR"); + } else if(state->diceSelect == 232) { + elements_button_right(canvas, "WEED"); + } +} + +static void dice_state_init(DiceState* const state) { + memset(state, 0, sizeof(DiceState)); + furi_hal_rtc_get_datetime(&state->datetime); + state->diceSelect = 20; + state->diceQty = 1; + state->diceRoll = 0; + state->playerOneScore = 0; + state->playerTwoScore = 0; + state->letsRoll = false; + state->desktop_settings = malloc(sizeof(DesktopSettings)); +} + +static void dice_tick(void* ctx) { + furi_assert(ctx); + FuriMessageQueue* event_queue = ctx; + PluginEvent event = {.type = EventTypeTick}; + // It's OK to lose this event if system overloaded + furi_message_queue_put(event_queue, &event, 0); +} + +int32_t dice_app(void* p) { + UNUSED(p); + DiceState* plugin_state = malloc(sizeof(DiceState)); + dice_state_init(plugin_state); + plugin_state->event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); + if(plugin_state->event_queue == NULL) { + FURI_LOG_E(TAG, "cannot create event queue\n"); + free(plugin_state); + return 255; + } + + plugin_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + if(plugin_state->mutex == NULL) { + FURI_LOG_E(TAG, "cannot create mutex\n"); + furi_message_queue_free(plugin_state->event_queue); + free(plugin_state); + return 255; + } + + FuriTimer* timer = + furi_timer_alloc(dice_tick, FuriTimerTypePeriodic, plugin_state->event_queue); + if(timer == NULL) { + FURI_LOG_E(TAG, "cannot create timer\n"); + furi_mutex_free(plugin_state->mutex); + furi_message_queue_free(plugin_state->event_queue); + free(plugin_state); + return 255; + } + + DESKTOP_SETTINGS_LOAD(plugin_state->desktop_settings); + + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, dice_render_callback, plugin_state); + view_port_input_callback_set(view_port, dice_input_callback, plugin_state->event_queue); + + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + furi_timer_start(timer, furi_kernel_get_tick_frequency()); + + // Main loop + PluginEvent event; + for(bool processing = true; processing;) { + FuriStatus event_status = furi_message_queue_get(plugin_state->event_queue, &event, 100); + if(event_status == FuriStatusOk) { + if(event.type == EventTypeKey) { + if(event.input.type == InputTypeShort || event.input.type == InputTypeRepeat) { + switch(event.input.key) { + case InputKeyUp: + break; + case InputKeyDown: + break; + case InputKeyRight: + if(plugin_state->diceSelect == 2) { + plugin_state->diceSelect = 3; + } else if(plugin_state->diceSelect == 3) { + plugin_state->diceSelect = 4; + } else if(plugin_state->diceSelect == 4) { + plugin_state->diceSelect = 6; + } else if(plugin_state->diceSelect == 6) { + plugin_state->diceSelect = 8; + } else if(plugin_state->diceSelect == 8) { + plugin_state->diceSelect = 10; + } else if(plugin_state->diceSelect == 10) { + plugin_state->diceSelect = 12; + } else if(plugin_state->diceSelect == 12) { + plugin_state->diceSelect = 20; + } else if(plugin_state->diceSelect == 20) { + plugin_state->diceSelect = 100; + } else if(plugin_state->diceSelect == 100) { + plugin_state->diceSelect = 230; + } else if(plugin_state->diceSelect == 230) { + plugin_state->playerOneScore = 0; + plugin_state->playerTwoScore = 0; + plugin_state->diceSelect = 231; + } else if(plugin_state->diceSelect == 231) { + plugin_state->diceSelect = 229; + } else if(plugin_state->diceSelect == 229) { + plugin_state->diceSelect = 228; + } else if(plugin_state->diceSelect == 228) { + plugin_state->diceSelect = 232; + } else if(plugin_state->diceSelect == 232) { + plugin_state->diceSelect = 59; + } else if(plugin_state->diceSelect == 59) { + plugin_state->diceSelect = 69; + } else { + plugin_state->diceSelect = 2; + } + break; + case InputKeyLeft: + if(plugin_state->diceQty <= 5) { + plugin_state->diceQty = plugin_state->diceQty + 1; + } else { + plugin_state->diceQty = 1; + } + break; + case InputKeyOk: + plugin_state->letsRoll = true; + break; + case InputKeyBack: + processing = false; + break; + default: + break; + } + } + } else if(event.type == EventTypeTick) { + // furi_hal_rtc_get_datetime(&plugin_state->datetime); + } + view_port_update(view_port); + furi_mutex_release(plugin_state->mutex); + } else { + // FURI_LOG_D(TAG, "osMessageQueue: event timeout"); + } + } + // Cleanup + furi_timer_free(timer); + 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(plugin_state->event_queue); + furi_mutex_free(plugin_state->mutex); + free(plugin_state->desktop_settings); + free(plugin_state); + return 0; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/dice/dice.png b/Applications/Official/DEV_FW/source/dice/dice.png new file mode 100644 index 000000000..64928b1de Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice/dice.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/LICENSE.md b/Applications/Official/DEV_FW/source/dice2/LICENSE.md new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dice2/README.md b/Applications/Official/DEV_FW/source/dice2/README.md new file mode 100644 index 000000000..43ac42ee8 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dice2/application.fam b/Applications/Official/DEV_FW/source/dice2/application.fam new file mode 100644 index 000000000..e8ec7ccd2 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dice2/assets/coin_1.png b/Applications/Official/DEV_FW/source/dice2/assets/coin_1.png new file mode 100644 index 000000000..6f56f9644 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/coin_1.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/coin_2.png b/Applications/Official/DEV_FW/source/dice2/assets/coin_2.png new file mode 100644 index 000000000..08c5872d9 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/coin_2.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/coin_3.png b/Applications/Official/DEV_FW/source/dice2/assets/coin_3.png new file mode 100644 index 000000000..c757caa66 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/coin_3.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/coin_4.png b/Applications/Official/DEV_FW/source/dice2/assets/coin_4.png new file mode 100644 index 000000000..508184d14 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/coin_4.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/coin_5.png b/Applications/Official/DEV_FW/source/dice2/assets/coin_5.png new file mode 100644 index 000000000..85831d239 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/coin_5.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/coin_6.png b/Applications/Official/DEV_FW/source/dice2/assets/coin_6.png new file mode 100644 index 000000000..17cdbf105 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/coin_6.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/coin_7.png b/Applications/Official/DEV_FW/source/dice2/assets/coin_7.png new file mode 100644 index 000000000..82f828a94 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/coin_7.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/d100_1.png b/Applications/Official/DEV_FW/source/dice2/assets/d100_1.png new file mode 100644 index 000000000..a7f2c18b0 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/d100_1.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/d100_2.png b/Applications/Official/DEV_FW/source/dice2/assets/d100_2.png new file mode 100644 index 000000000..783486976 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/d100_2.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/d100_3.png b/Applications/Official/DEV_FW/source/dice2/assets/d100_3.png new file mode 100644 index 000000000..92d5a5c0c Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/d100_3.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/d100_4.png b/Applications/Official/DEV_FW/source/dice2/assets/d100_4.png new file mode 100644 index 000000000..324b7f633 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/d100_4.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/d10_1.png b/Applications/Official/DEV_FW/source/dice2/assets/d10_1.png new file mode 100644 index 000000000..d742026c9 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/d10_1.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/d10_2.png b/Applications/Official/DEV_FW/source/dice2/assets/d10_2.png new file mode 100644 index 000000000..0d9ca60ef Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/d10_2.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/d10_3.png b/Applications/Official/DEV_FW/source/dice2/assets/d10_3.png new file mode 100644 index 000000000..ab67316c6 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/d10_3.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/d10_4.png b/Applications/Official/DEV_FW/source/dice2/assets/d10_4.png new file mode 100644 index 000000000..e89b03f3a Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/d10_4.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/d12_1.png b/Applications/Official/DEV_FW/source/dice2/assets/d12_1.png new file mode 100644 index 000000000..053ead3cd Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/d12_1.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/d12_2.png b/Applications/Official/DEV_FW/source/dice2/assets/d12_2.png new file mode 100644 index 000000000..752abaf33 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/d12_2.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/d12_3.png b/Applications/Official/DEV_FW/source/dice2/assets/d12_3.png new file mode 100644 index 000000000..711d18514 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/d12_3.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/d12_4.png b/Applications/Official/DEV_FW/source/dice2/assets/d12_4.png new file mode 100644 index 000000000..dff920c42 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/d12_4.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/d20_1.png b/Applications/Official/DEV_FW/source/dice2/assets/d20_1.png new file mode 100644 index 000000000..593adbad9 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/d20_1.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/d20_2.png b/Applications/Official/DEV_FW/source/dice2/assets/d20_2.png new file mode 100644 index 000000000..b8d11952d Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/d20_2.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/d20_3.png b/Applications/Official/DEV_FW/source/dice2/assets/d20_3.png new file mode 100644 index 000000000..bea6e8ffc Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/d20_3.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/d20_4.png b/Applications/Official/DEV_FW/source/dice2/assets/d20_4.png new file mode 100644 index 000000000..1fa19dc4c Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/d20_4.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/d4_1.png b/Applications/Official/DEV_FW/source/dice2/assets/d4_1.png new file mode 100644 index 000000000..8007cdca5 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/d4_1.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/d4_2.png b/Applications/Official/DEV_FW/source/dice2/assets/d4_2.png new file mode 100644 index 000000000..8757c56e3 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/d4_2.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/d4_3.png b/Applications/Official/DEV_FW/source/dice2/assets/d4_3.png new file mode 100644 index 000000000..8d1687ac8 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/d4_3.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/d6_1.png b/Applications/Official/DEV_FW/source/dice2/assets/d6_1.png new file mode 100644 index 000000000..f3b2ba293 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/d6_1.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/d6_2.png b/Applications/Official/DEV_FW/source/dice2/assets/d6_2.png new file mode 100644 index 000000000..257c87a0d Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/d6_2.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/d6_3.png b/Applications/Official/DEV_FW/source/dice2/assets/d6_3.png new file mode 100644 index 000000000..882be3a67 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/d6_3.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/d6_4.png b/Applications/Official/DEV_FW/source/dice2/assets/d6_4.png new file mode 100644 index 000000000..ac7928e2c Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/d6_4.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/d8_1.png b/Applications/Official/DEV_FW/source/dice2/assets/d8_1.png new file mode 100644 index 000000000..b4c3b692e Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/d8_1.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/d8_2.png b/Applications/Official/DEV_FW/source/dice2/assets/d8_2.png new file mode 100644 index 000000000..705416e49 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/d8_2.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/d8_3.png b/Applications/Official/DEV_FW/source/dice2/assets/d8_3.png new file mode 100644 index 000000000..4c95fdcbf Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/d8_3.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/d8_4.png b/Applications/Official/DEV_FW/source/dice2/assets/d8_4.png new file mode 100644 index 000000000..a5f7fe838 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/d8_4.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/ui_button_back.png b/Applications/Official/DEV_FW/source/dice2/assets/ui_button_back.png new file mode 100644 index 000000000..2c22d19c6 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/ui_button_back.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/ui_button_down.png b/Applications/Official/DEV_FW/source/dice2/assets/ui_button_down.png new file mode 100644 index 000000000..2954bb6a6 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/ui_button_down.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/ui_button_exit.png b/Applications/Official/DEV_FW/source/dice2/assets/ui_button_exit.png new file mode 100644 index 000000000..22f357913 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/ui_button_exit.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/ui_button_left.png b/Applications/Official/DEV_FW/source/dice2/assets/ui_button_left.png new file mode 100644 index 000000000..0b4655d43 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/ui_button_left.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/ui_button_right.png b/Applications/Official/DEV_FW/source/dice2/assets/ui_button_right.png new file mode 100644 index 000000000..8e1c74c1c Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/ui_button_right.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/ui_button_roll.png b/Applications/Official/DEV_FW/source/dice2/assets/ui_button_roll.png new file mode 100644 index 000000000..f20d7f565 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/ui_button_roll.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/ui_button_up.png b/Applications/Official/DEV_FW/source/dice2/assets/ui_button_up.png new file mode 100644 index 000000000..1be79328b Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/ui_button_up.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/ui_count.png b/Applications/Official/DEV_FW/source/dice2/assets/ui_count.png new file mode 100644 index 000000000..a408de025 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/ui_count.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/ui_count_1.png b/Applications/Official/DEV_FW/source/dice2/assets/ui_count_1.png new file mode 100644 index 000000000..ec61bde96 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/ui_count_1.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/assets/ui_result_border.png b/Applications/Official/DEV_FW/source/dice2/assets/ui_result_border.png new file mode 100644 index 000000000..576402050 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/assets/ui_result_border.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/constants.h b/Applications/Official/DEV_FW/source/dice2/constants.h new file mode 100644 index 000000000..3f263433d --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dice2/dice_app.c b/Applications/Official/DEV_FW/source/dice2/dice_app.c new file mode 100644 index 000000000..5e06c4aad --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dice2/icon.png b/Applications/Official/DEV_FW/source/dice2/icon.png new file mode 100644 index 000000000..840088565 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/icon.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/sources/coin.pixil b/Applications/Official/DEV_FW/source/dice2/sources/coin.pixil new file mode 100644 index 000000000..838aeafc0 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dice2/sources/d10.pixil b/Applications/Official/DEV_FW/source/dice2/sources/d10.pixil new file mode 100644 index 000000000..3356c453c --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dice2/sources/d100.pixil b/Applications/Official/DEV_FW/source/dice2/sources/d100.pixil new file mode 100644 index 000000000..c4fd81342 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dice2/sources/d12.pixil b/Applications/Official/DEV_FW/source/dice2/sources/d12.pixil new file mode 100644 index 000000000..70e25c5ae --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dice2/sources/d20.pixil b/Applications/Official/DEV_FW/source/dice2/sources/d20.pixil new file mode 100644 index 000000000..4e2baeb61 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dice2/sources/d6.pixil b/Applications/Official/DEV_FW/source/dice2/sources/d6.pixil new file mode 100644 index 000000000..4d9d44d7b --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dice2/sources/d8.pixil b/Applications/Official/DEV_FW/source/dice2/sources/d8.pixil new file mode 100644 index 000000000..4f7ff440e --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dice2/sources/flipper-screen.png b/Applications/Official/DEV_FW/source/dice2/sources/flipper-screen.png new file mode 100644 index 000000000..af759a20f Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/sources/flipper-screen.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/sources/main-screen.png b/Applications/Official/DEV_FW/source/dice2/sources/main-screen.png new file mode 100644 index 000000000..20a4e9c2c Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/sources/main-screen.png differ diff --git a/Applications/Official/DEV_FW/source/dice2/sources/result_border.pixil b/Applications/Official/DEV_FW/source/dice2/sources/result_border.pixil new file mode 100644 index 000000000..afe25bf07 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dice2/sources/roll-screen.png b/Applications/Official/DEV_FW/source/dice2/sources/roll-screen.png new file mode 100644 index 000000000..a1f13cec1 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dice2/sources/roll-screen.png differ diff --git a/Applications/Official/DEV_FW/source/dolphin-counter-main/README.md b/Applications/Official/DEV_FW/source/dolphin-counter-main/README.md new file mode 100644 index 000000000..803c68634 --- /dev/null +++ b/Applications/Official/DEV_FW/source/dolphin-counter-main/README.md @@ -0,0 +1,8 @@ +# Dolphin counter +This is a simple plugin for the [Flipper Zero](https://www.flipperzero.one). +It gives you access to a counter which you can increment and decrement using the up and down buttons respectively. + +![preview](https://github.com/Krulknul/dolphin-counter/blob/main/media/preview.gif) + +# How to install this? +I'd recommend using [flipperzero-ufbt](https://github.com/flipperdevices/flipperzero-ufbt), which is a lightweight tool for quickly testing Flipper Zero applications. The app will stay present on your device so it is not necessary to flash the entire firmware. diff --git a/Applications/Official/DEV_FW/source/dolphin-counter-main/application.fam b/Applications/Official/DEV_FW/source/dolphin-counter-main/application.fam new file mode 100644 index 000000000..8f7eef884 --- /dev/null +++ b/Applications/Official/DEV_FW/source/dolphin-counter-main/application.fam @@ -0,0 +1,12 @@ +App( + appid="counter", + name="Counter", + apptype=FlipperAppType.PLUGIN, + entry_point="counterapp", + requires=[ + "gui", + ], + fap_category="Misc", + fap_icon="icons/counter_icon.png", + fap_icon_assets="icons", +) \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/dolphin-counter-main/counter.c b/Applications/Official/DEV_FW/source/dolphin-counter-main/counter.c new file mode 100644 index 000000000..c76ed7293 --- /dev/null +++ b/Applications/Official/DEV_FW/source/dolphin-counter-main/counter.c @@ -0,0 +1,108 @@ +#include +#include +#include +#include +#include + +#define MAX_COUNT 99 +#define BOXTIME 2 +#define BOXWIDTH 30 +#define MIDDLE_X 64 - BOXWIDTH / 2 +#define MIDDLE_Y 32 - BOXWIDTH / 2 +#define OFFSET_Y 9 + +typedef struct { + FuriMessageQueue* input_queue; + ViewPort* view_port; + Gui* gui; + FuriMutex** mutex; + + int count; + bool pressed; + int boxtimer; +} Counter; + +void state_free(Counter* c) { + gui_remove_view_port(c->gui, c->view_port); + furi_record_close(RECORD_GUI); + view_port_free(c->view_port); + furi_message_queue_free(c->input_queue); + furi_mutex_free(c->mutex); + free(c); +} + +static void input_callback(InputEvent* input_event, void* ctx) { + Counter* c = ctx; + if(input_event->type == InputTypeShort) { + furi_message_queue_put(c->input_queue, input_event, 0); + } +} + +static void render_callback(Canvas* canvas, void* ctx) { + Counter* c = ctx; + furi_check(furi_mutex_acquire(c->mutex, FuriWaitForever) == FuriStatusOk); + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 64, 10, AlignCenter, AlignCenter, "Counter :)"); + canvas_set_font(canvas, FontBigNumbers); + + char scount[5]; + if(c->pressed == true || c->boxtimer > 0) { + canvas_draw_rframe(canvas, MIDDLE_X, MIDDLE_Y + OFFSET_Y, BOXWIDTH, BOXWIDTH, 5); + canvas_draw_rframe( + canvas, MIDDLE_X - 1, MIDDLE_Y + OFFSET_Y - 1, BOXWIDTH + 2, BOXWIDTH + 2, 5); + canvas_draw_rframe( + canvas, MIDDLE_X - 2, MIDDLE_Y + OFFSET_Y - 2, BOXWIDTH + 4, BOXWIDTH + 4, 5); + c->pressed = false; + c->boxtimer--; + } else { + canvas_draw_rframe(canvas, MIDDLE_X, MIDDLE_Y + OFFSET_Y, BOXWIDTH, BOXWIDTH, 5); + } + snprintf(scount, sizeof(scount), "%d", c->count); + canvas_draw_str_aligned(canvas, 64, 32 + OFFSET_Y, AlignCenter, AlignCenter, scount); + furi_mutex_release(c->mutex); +} + +Counter* state_init() { + Counter* c = malloc(sizeof(Counter)); + c->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + c->view_port = view_port_alloc(); + c->gui = furi_record_open(RECORD_GUI); + c->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + c->count = 0; + c->boxtimer = 0; + view_port_input_callback_set(c->view_port, input_callback, c); + view_port_draw_callback_set(c->view_port, render_callback, c); + gui_add_view_port(c->gui, c->view_port, GuiLayerFullscreen); + return c; +} + +int32_t counterapp(void) { + Counter* c = state_init(); + + while(1) { + InputEvent input; + while(furi_message_queue_get(c->input_queue, &input, FuriWaitForever) == FuriStatusOk) { + furi_check(furi_mutex_acquire(c->mutex, FuriWaitForever) == FuriStatusOk); + + if(input.key == InputKeyBack) { + furi_mutex_release(c->mutex); + state_free(c); + return 0; + } else if(input.key == InputKeyUp && c->count < MAX_COUNT) { + c->pressed = true; + c->boxtimer = BOXTIME; + c->count++; + } else if(input.key == InputKeyDown && c->count != 0) { + c->pressed = true; + c->boxtimer = BOXTIME; + c->count--; + } + furi_mutex_release(c->mutex); + view_port_update(c->view_port); + } + } + state_free(c); + return 0; +} diff --git a/Applications/Official/DEV_FW/source/dolphin-counter-main/icons/counter_icon.png b/Applications/Official/DEV_FW/source/dolphin-counter-main/icons/counter_icon.png new file mode 100644 index 000000000..4b8358b42 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dolphin-counter-main/icons/counter_icon.png differ diff --git a/Applications/Official/DEV_FW/source/dolphin-counter-main/media/preview.gif b/Applications/Official/DEV_FW/source/dolphin-counter-main/media/preview.gif new file mode 100644 index 000000000..87098b733 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dolphin-counter-main/media/preview.gif differ diff --git a/Applications/Official/DEV_FW/source/dolphinbackup/application.fam b/Applications/Official/DEV_FW/source/dolphinbackup/application.fam new file mode 100644 index 000000000..024c1b197 --- /dev/null +++ b/Applications/Official/DEV_FW/source/dolphinbackup/application.fam @@ -0,0 +1,12 @@ +App( + appid="DolphinBackup", + name="Dolphin Backup", + apptype=FlipperAppType.EXTERNAL, + entry_point="storage_DolphinBackup_app", + cdefines=["APP_DBACKUP"], + requires=["gui", "storage"], + stack_size=2 * 1024, + order=85, + fap_icon="bckupIcon.png", + fap_category="Tools", +) \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/dolphinbackup/bckupIcon.png b/Applications/Official/DEV_FW/source/dolphinbackup/bckupIcon.png new file mode 100644 index 000000000..3f3d48c58 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dolphinbackup/bckupIcon.png differ diff --git a/Applications/Official/DEV_FW/source/dolphinbackup/scenes/storage_DolphinBackup_scene.c b/Applications/Official/DEV_FW/source/dolphinbackup/scenes/storage_DolphinBackup_scene.c new file mode 100644 index 000000000..dfaf8e463 --- /dev/null +++ b/Applications/Official/DEV_FW/source/dolphinbackup/scenes/storage_DolphinBackup_scene.c @@ -0,0 +1,30 @@ +#include "storage_DolphinBackup_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const storage_DolphinBackup_on_enter_handlers[])(void*) = { +#include "storage_DolphinBackup_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 storage_DolphinBackup_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "storage_DolphinBackup_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 storage_DolphinBackup_on_exit_handlers[])(void* context) = { +#include "storage_DolphinBackup_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers storage_DolphinBackup_scene_handlers = { + .on_enter_handlers = storage_DolphinBackup_on_enter_handlers, + .on_event_handlers = storage_DolphinBackup_on_event_handlers, + .on_exit_handlers = storage_DolphinBackup_on_exit_handlers, + .scene_num = StorageDolphinBackupSceneNum, +}; diff --git a/Applications/Official/DEV_FW/source/dolphinbackup/scenes/storage_DolphinBackup_scene.h b/Applications/Official/DEV_FW/source/dolphinbackup/scenes/storage_DolphinBackup_scene.h new file mode 100644 index 000000000..b243b16e1 --- /dev/null +++ b/Applications/Official/DEV_FW/source/dolphinbackup/scenes/storage_DolphinBackup_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) StorageDolphinBackup##id, +typedef enum { +#include "storage_DolphinBackup_scene_config.h" + StorageDolphinBackupSceneNum, +} StorageDolphinBackupScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers storage_DolphinBackup_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "storage_DolphinBackup_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 "storage_DolphinBackup_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 "storage_DolphinBackup_scene_config.h" +#undef ADD_SCENE diff --git a/Applications/Official/DEV_FW/source/dolphinbackup/scenes/storage_DolphinBackup_scene_config.h b/Applications/Official/DEV_FW/source/dolphinbackup/scenes/storage_DolphinBackup_scene_config.h new file mode 100644 index 000000000..270810ffd --- /dev/null +++ b/Applications/Official/DEV_FW/source/dolphinbackup/scenes/storage_DolphinBackup_scene_config.h @@ -0,0 +1,2 @@ +ADD_SCENE(storage_DolphinBackup, confirm, Confirm) +ADD_SCENE(storage_DolphinBackup, progress, Progress) diff --git a/Applications/Official/DEV_FW/source/dolphinbackup/scenes/storage_DolphinBackup_scene_confirm.c b/Applications/Official/DEV_FW/source/dolphinbackup/scenes/storage_DolphinBackup_scene_confirm.c new file mode 100644 index 000000000..6e5c16d66 --- /dev/null +++ b/Applications/Official/DEV_FW/source/dolphinbackup/scenes/storage_DolphinBackup_scene_confirm.c @@ -0,0 +1,71 @@ +#include "../storage_DolphinBackup.h" +#include "gui/canvas.h" +#include "gui/modules/widget_elements/widget_element_i.h" +#include "storage/storage.h" + +static void storage_DolphinBackup_scene_confirm_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + StorageDolphinBackup* app = context; + furi_assert(app); + if(type == InputTypeShort) { + if(result == GuiButtonTypeRight) { + view_dispatcher_send_custom_event( + app->view_dispatcher, DolphinBackupCustomEventConfirm); + } else if(result == GuiButtonTypeLeft) { + view_dispatcher_send_custom_event(app->view_dispatcher, DolphinBackupCustomEventExit); + } + } +} + +void storage_DolphinBackup_scene_confirm_on_enter(void* context) { + StorageDolphinBackup* app = context; + + widget_add_button_element( + app->widget, + GuiButtonTypeLeft, + "Cancel", + storage_DolphinBackup_scene_confirm_widget_callback, + app); + widget_add_button_element( + app->widget, + GuiButtonTypeRight, + "Confirm", + storage_DolphinBackup_scene_confirm_widget_callback, + app); + + widget_add_string_element( + app->widget, 64, 10, AlignCenter, AlignCenter, FontPrimary, "SD Card Present"); + widget_add_string_multiline_element( + app->widget, + 64, + 32, + AlignCenter, + AlignCenter, + FontSecondary, + "Copy data from\ninternal storage to SD card?"); + + view_dispatcher_switch_to_view(app->view_dispatcher, StorageDolphinBackupViewWidget); +} + +bool storage_DolphinBackup_scene_confirm_on_event(void* context, SceneManagerEvent event) { + StorageDolphinBackup* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == DolphinBackupCustomEventConfirm) { + scene_manager_next_scene(app->scene_manager, StorageDolphinBackupProgress); + consumed = true; + } else if(event.event == DolphinBackupCustomEventExit) { + view_dispatcher_stop(app->view_dispatcher); + } + } + + return consumed; +} + +void storage_DolphinBackup_scene_confirm_on_exit(void* context) { + StorageDolphinBackup* app = context; + widget_reset(app->widget); +} diff --git a/Applications/Official/DEV_FW/source/dolphinbackup/scenes/storage_DolphinBackup_scene_progress.c b/Applications/Official/DEV_FW/source/dolphinbackup/scenes/storage_DolphinBackup_scene_progress.c new file mode 100644 index 000000000..6a5ffd9c1 --- /dev/null +++ b/Applications/Official/DEV_FW/source/dolphinbackup/scenes/storage_DolphinBackup_scene_progress.c @@ -0,0 +1,31 @@ +#include "../storage_DolphinBackup.h" + +void storage_DolphinBackup_scene_progress_on_enter(void* context) { + StorageDolphinBackup* app = context; + + widget_add_string_element( + app->widget, 64, 10, AlignCenter, AlignCenter, FontPrimary, "Moving..."); + + view_dispatcher_switch_to_view(app->view_dispatcher, StorageDolphinBackupViewWidget); + + storage_DolphinBackup_perform(); + view_dispatcher_send_custom_event(app->view_dispatcher, DolphinBackupCustomEventExit); +} + +bool storage_DolphinBackup_scene_progress_on_event(void* context, SceneManagerEvent event) { + StorageDolphinBackup* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + view_dispatcher_stop(app->view_dispatcher); + } else if(event.type == SceneManagerEventTypeBack) { + consumed = true; + } + + return consumed; +} + +void storage_DolphinBackup_scene_progress_on_exit(void* context) { + StorageDolphinBackup* app = context; + widget_reset(app->widget); +} diff --git a/Applications/Official/DEV_FW/source/dolphinbackup/storage_DolphinBackup.c b/Applications/Official/DEV_FW/source/dolphinbackup/storage_DolphinBackup.c new file mode 100644 index 000000000..02baddc0f --- /dev/null +++ b/Applications/Official/DEV_FW/source/dolphinbackup/storage_DolphinBackup.c @@ -0,0 +1,197 @@ +#include "storage_DolphinBackup.h" +#include +#include +#include "loader/loader.h" +#include "m-string.h" +#include + +#define TAG "DolphinBackup" + +#define MOVE_SRC "/int" +#define MOVE_DST "/ext" + +static const char* app_dirsDolphinBackup[] = { + "subghz", + "lfrfid", + "nfc", + "infrared", + "ibutton", + "badusb", + ".bt.settings", + ".desktop.settings", + ".dolphin.state", + ".notification.settings", + ".bt.keys", + ".power.settings", +}; + +bool storage_DolphinBackup_perform(void) { + Storage* storage = furi_record_open(RECORD_STORAGE); + FuriString* path_src; + FuriString* path_dst; + FuriString* new_path; + path_src = furi_string_alloc(); + path_dst = furi_string_alloc(); + new_path = furi_string_alloc(); + + furi_string_printf(new_path, "%s/dolphin_restorer", MOVE_DST); + storage_common_mkdir(storage, furi_string_get_cstr(new_path)); + furi_string_free(new_path); + for(uint32_t i = 0; i < COUNT_OF(app_dirsDolphinBackup); i++) { + if(i > 5) { + furi_string_printf(path_src, "%s/%s", MOVE_SRC, app_dirsDolphinBackup[i]); + furi_string_printf( + path_dst, "%s/dolphin_restorer/%s", MOVE_DST, app_dirsDolphinBackup[i]); + storage_simply_remove_recursive(storage, furi_string_get_cstr(path_dst)); + storage_common_copy( + storage, furi_string_get_cstr(path_src), furi_string_get_cstr(path_dst)); + } else { + furi_string_printf(path_src, "%s/%s", MOVE_SRC, app_dirsDolphinBackup[i]); + furi_string_printf(path_dst, "%s/%s", MOVE_DST, app_dirsDolphinBackup[i]); + storage_common_merge( + storage, furi_string_get_cstr(path_src), furi_string_get_cstr(path_dst)); + storage_simply_remove_recursive(storage, furi_string_get_cstr(path_src)); + } + } + + furi_string_free(path_src); + furi_string_free(path_dst); + + furi_record_close(RECORD_STORAGE); + + return false; +} + +static bool storage_DolphinBackup_check(void) { + Storage* storage = furi_record_open(RECORD_STORAGE); + + FileInfo file_info; + bool state = false; + FuriString* path; + path = furi_string_alloc(); + + for(uint32_t i = 0; i < COUNT_OF(app_dirsDolphinBackup); i++) { + furi_string_printf(path, "%s/%s", MOVE_SRC, app_dirsDolphinBackup[i]); + if(storage_common_stat(storage, furi_string_get_cstr(path), &file_info) == FSE_OK) { + // if((file_info.flags & FSF_DIRECTORY) != 0) { + state = true; + break; + // } + } + } + + furi_string_free(path); + + furi_record_close(RECORD_STORAGE); + + return state; +} + +static bool storage_DolphinBackup_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + StorageDolphinBackup* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool storage_DolphinBackup_back_event_callback(void* context) { + furi_assert(context); + StorageDolphinBackup* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void storage_DolphinBackup_unmount_callback(const void* message, void* context) { + StorageDolphinBackup* app = context; + furi_assert(app); + const StorageEvent* storage_event = message; + + if((storage_event->type == StorageEventTypeCardUnmount) || + (storage_event->type == StorageEventTypeCardMountError)) { + view_dispatcher_send_custom_event(app->view_dispatcher, DolphinBackupCustomEventExit); + } +} + +static StorageDolphinBackup* storage_DolphinBackup_alloc() { + StorageDolphinBackup* app = malloc(sizeof(StorageDolphinBackup)); + + app->gui = furi_record_open(RECORD_GUI); + app->notifications = furi_record_open(RECORD_NOTIFICATION); + + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&storage_DolphinBackup_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, storage_DolphinBackup_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, storage_DolphinBackup_back_event_callback); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, StorageDolphinBackupViewWidget, widget_get_view(app->widget)); + + scene_manager_next_scene(app->scene_manager, StorageDolphinBackupConfirm); + + Storage* storage = furi_record_open(RECORD_STORAGE); + app->sub = furi_pubsub_subscribe( + storage_get_pubsub(storage), storage_DolphinBackup_unmount_callback, app); + furi_record_close(RECORD_STORAGE); + + return app; +} + +static void storage_DolphinBackup_free(StorageDolphinBackup* app) { + Storage* storage = furi_record_open(RECORD_STORAGE); + furi_pubsub_unsubscribe(storage_get_pubsub(storage), app->sub); + furi_record_close(RECORD_STORAGE); + furi_record_close(RECORD_NOTIFICATION); + + view_dispatcher_remove_view(app->view_dispatcher, StorageDolphinBackupViewWidget); + widget_free(app->widget); + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + furi_record_close(RECORD_GUI); + + free(app); +} + +int32_t storage_DolphinBackup_app(void* p) { + UNUSED(p); + + if(storage_DolphinBackup_check()) { + StorageDolphinBackup* app = storage_DolphinBackup_alloc(); + notification_message(app->notifications, &sequence_display_backlight_on); + view_dispatcher_run(app->view_dispatcher); + storage_DolphinBackup_free(app); + } else { + FURI_LOG_I(TAG, "Nothing to move"); + } + + return 0; +} + +static void storage_DolphinBackup_mount_callback(const void* message, void* context) { + UNUSED(context); + + const StorageEvent* storage_event = message; + + if(storage_event->type == StorageEventTypeCardMount) { + Loader* loader = furi_record_open("loader"); + loader_start(loader, "StorageDolphinBackup", NULL); + furi_record_close("loader"); + } +} + +int32_t storage_DolphinBackup_start(void* p) { + UNUSED(p); + Storage* storage = furi_record_open(RECORD_STORAGE); + + furi_pubsub_subscribe(storage_get_pubsub(storage), storage_DolphinBackup_mount_callback, NULL); + + furi_record_close(RECORD_STORAGE); + return 0; +} diff --git a/Applications/Official/DEV_FW/source/dolphinbackup/storage_DolphinBackup.h b/Applications/Official/DEV_FW/source/dolphinbackup/storage_DolphinBackup.h new file mode 100644 index 000000000..f9ff74a65 --- /dev/null +++ b/Applications/Official/DEV_FW/source/dolphinbackup/storage_DolphinBackup.h @@ -0,0 +1,49 @@ +#pragma once +#include "gui/modules/widget_elements/widget_element_i.h" +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "scenes/storage_DolphinBackup_scene.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + DolphinBackupCustomEventExit, + DolphinBackupCustomEventConfirm, +} DolphinBackupCustomEvent; + +typedef struct { + // records + Gui* gui; + Widget* widget; + NotificationApp* notifications; + + // view managment + SceneManager* scene_manager; + ViewDispatcher* view_dispatcher; + + FuriPubSubSubscription* sub; + +} StorageDolphinBackup; + +typedef enum { + StorageDolphinBackupViewWidget, +} StorageDolphinBackupView; + +bool storage_DolphinBackup_perform(void); + +#ifdef __cplusplus +} +#endif diff --git a/Applications/Official/DEV_FW/source/dolphinrestorer/application.fam b/Applications/Official/DEV_FW/source/dolphinrestorer/application.fam new file mode 100644 index 000000000..46699404a --- /dev/null +++ b/Applications/Official/DEV_FW/source/dolphinrestorer/application.fam @@ -0,0 +1,12 @@ +App( + appid="DolphinRestorer", + name="Dolphin Restorer", + apptype=FlipperAppType.EXTERNAL, + entry_point="drestorer_app", + cdefines=["APP_DRESTORER"], + requires=["gui","storage"], + stack_size= 2 * 1024, + order=90, + fap_icon="restoreIcon.png", + fap_category="Tools", +) \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/dolphinrestorer/drestorer.c b/Applications/Official/DEV_FW/source/dolphinrestorer/drestorer.c new file mode 100644 index 000000000..d2c0273d2 --- /dev/null +++ b/Applications/Official/DEV_FW/source/dolphinrestorer/drestorer.c @@ -0,0 +1,176 @@ +#include "drestorer.h" +#include +#include +#include "loader/loader.h" +#include "m-string.h" +#include + +#define TAG "MoveToInt" + +#define MOVE_SRC "/ext/dolphin_restorer" +#define MOVE_DST "/int" + +static const char* app_dirs[] = { + ".bt.settings", + ".desktop.settings", + ".dolphin.state", + ".notification.settings", + ".bt.keys", + ".power.settings", +}; + +bool drestorer_perform(void) { + Storage* storage = furi_record_open(RECORD_STORAGE); + FuriString* path_src; + FuriString* path_dst; + path_src = furi_string_alloc(); + path_dst = furi_string_alloc(); + + for(uint32_t i = 0; i < COUNT_OF(app_dirs); i++) { + furi_string_printf(path_src, "%s/%s", MOVE_SRC, app_dirs[i]); + furi_string_printf(path_dst, "%s/%s", MOVE_DST, app_dirs[i]); + storage_simply_remove_recursive(storage, furi_string_get_cstr(path_dst)); + storage_common_copy( + storage, furi_string_get_cstr(path_src), furi_string_get_cstr(path_dst)); + } + + furi_string_free(path_src); + furi_string_free(path_dst); + + furi_record_close(RECORD_STORAGE); + + return false; +} + +static bool drestorer_check(void) { + Storage* storage = furi_record_open(RECORD_STORAGE); + + FileInfo file_info; + bool state = false; + FuriString* path; + path = furi_string_alloc(); + + for(uint32_t i = 0; i < COUNT_OF(app_dirs); i++) { + furi_string_printf(path, "%s/%s", MOVE_SRC, app_dirs[i]); + if(storage_common_stat(storage, furi_string_get_cstr(path), &file_info) == FSE_OK) { + // if((file_info.flags & FSF_DIRECTORY) != 0) { + state = true; + break; + // } + } + } + + furi_string_free(path); + + furi_record_close(RECORD_STORAGE); + + return state; +} + +static bool drestorer_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + StorageMoveToSd* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool drestorer_back_event_callback(void* context) { + furi_assert(context); + StorageMoveToSd* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void drestorer_unmount_callback(const void* message, void* context) { + StorageMoveToSd* app = context; + furi_assert(app); + const StorageEvent* storage_event = message; + + if((storage_event->type == StorageEventTypeCardUnmount) || + (storage_event->type == StorageEventTypeCardMountError)) { + view_dispatcher_send_custom_event(app->view_dispatcher, MoveToSdCustomEventExit); + } +} + +static StorageMoveToSd* drestorer_alloc() { + StorageMoveToSd* app = malloc(sizeof(StorageMoveToSd)); + + app->gui = furi_record_open(RECORD_GUI); + app->notifications = furi_record_open(RECORD_NOTIFICATION); + + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&drestorer_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, drestorer_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, drestorer_back_event_callback); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, StorageMoveToSdViewWidget, widget_get_view(app->widget)); + + scene_manager_next_scene(app->scene_manager, StorageMoveToSdConfirm); + + Storage* storage = furi_record_open(RECORD_STORAGE); + app->sub = furi_pubsub_subscribe(storage_get_pubsub(storage), drestorer_unmount_callback, app); + furi_record_close(RECORD_STORAGE); + + return app; +} + +static void drestorer_free(StorageMoveToSd* app) { + Storage* storage = furi_record_open(RECORD_STORAGE); + furi_pubsub_unsubscribe(storage_get_pubsub(storage), app->sub); + furi_record_close(RECORD_STORAGE); + furi_record_close(RECORD_NOTIFICATION); + + view_dispatcher_remove_view(app->view_dispatcher, StorageMoveToSdViewWidget); + widget_free(app->widget); + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + furi_record_close(RECORD_GUI); + + free(app); +} + +int32_t drestorer_app(void* p) { + UNUSED(p); + + if(drestorer_check()) { + StorageMoveToSd* app = drestorer_alloc(); + notification_message(app->notifications, &sequence_display_backlight_on); + view_dispatcher_run(app->view_dispatcher); + drestorer_free(app); + } else { + FURI_LOG_I(TAG, "Nothing to move"); + } + + return 0; +} + +static void drestorer_mount_callback(const void* message, void* context) { + UNUSED(context); + + const StorageEvent* storage_event = message; + + if(storage_event->type == StorageEventTypeCardMount) { + Loader* loader = furi_record_open("loader"); + loader_start(loader, "StorageMoveToSd", NULL); + furi_record_close("loader"); + } +} + +int32_t drestorer_start(void* p) { + UNUSED(p); + Storage* storage = furi_record_open(RECORD_STORAGE); + + furi_pubsub_subscribe(storage_get_pubsub(storage), drestorer_mount_callback, NULL); + + furi_record_close(RECORD_STORAGE); + return 0; +} diff --git a/Applications/Official/DEV_FW/source/dolphinrestorer/drestorer.h b/Applications/Official/DEV_FW/source/dolphinrestorer/drestorer.h new file mode 100644 index 000000000..c62adab37 --- /dev/null +++ b/Applications/Official/DEV_FW/source/dolphinrestorer/drestorer.h @@ -0,0 +1,49 @@ +#pragma once +#include "gui/modules/widget_elements/widget_element_i.h" +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "scenes/drestorer_scene.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + MoveToSdCustomEventExit, + MoveToSdCustomEventConfirm, +} MoveToSdCustomEvent; + +typedef struct { + // records + Gui* gui; + Widget* widget; + NotificationApp* notifications; + + // view managment + SceneManager* scene_manager; + ViewDispatcher* view_dispatcher; + + FuriPubSubSubscription* sub; + +} StorageMoveToSd; + +typedef enum { + StorageMoveToSdViewWidget, +} StorageMoveToSdView; + +bool drestorer_perform(void); + +#ifdef __cplusplus +} +#endif diff --git a/Applications/Official/DEV_FW/source/dolphinrestorer/restoreIcon.png b/Applications/Official/DEV_FW/source/dolphinrestorer/restoreIcon.png new file mode 100644 index 000000000..752be3bce Binary files /dev/null and b/Applications/Official/DEV_FW/source/dolphinrestorer/restoreIcon.png differ diff --git a/Applications/Official/DEV_FW/source/dolphinrestorer/scenes/drestorer_scene.c b/Applications/Official/DEV_FW/source/dolphinrestorer/scenes/drestorer_scene.c new file mode 100644 index 000000000..7a183b695 --- /dev/null +++ b/Applications/Official/DEV_FW/source/dolphinrestorer/scenes/drestorer_scene.c @@ -0,0 +1,30 @@ +#include "drestorer_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const drestorer_on_enter_handlers[])(void*) = { +#include "drestorer_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 drestorer_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "drestorer_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 drestorer_on_exit_handlers[])(void* context) = { +#include "drestorer_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers drestorer_scene_handlers = { + .on_enter_handlers = drestorer_on_enter_handlers, + .on_event_handlers = drestorer_on_event_handlers, + .on_exit_handlers = drestorer_on_exit_handlers, + .scene_num = StorageMoveToSdSceneNum, +}; diff --git a/Applications/Official/DEV_FW/source/dolphinrestorer/scenes/drestorer_scene.h b/Applications/Official/DEV_FW/source/dolphinrestorer/scenes/drestorer_scene.h new file mode 100644 index 000000000..9843ff676 --- /dev/null +++ b/Applications/Official/DEV_FW/source/dolphinrestorer/scenes/drestorer_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) StorageMoveToSd##id, +typedef enum { +#include "drestorer_scene_config.h" + StorageMoveToSdSceneNum, +} StorageMoveToSdScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers drestorer_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "drestorer_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 "drestorer_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 "drestorer_scene_config.h" +#undef ADD_SCENE diff --git a/Applications/Official/DEV_FW/source/dolphinrestorer/scenes/drestorer_scene_config.h b/Applications/Official/DEV_FW/source/dolphinrestorer/scenes/drestorer_scene_config.h new file mode 100644 index 000000000..80a07e22e --- /dev/null +++ b/Applications/Official/DEV_FW/source/dolphinrestorer/scenes/drestorer_scene_config.h @@ -0,0 +1,2 @@ +ADD_SCENE(drestorer, confirm, Confirm) +ADD_SCENE(drestorer, progress, Progress) diff --git a/Applications/Official/DEV_FW/source/dolphinrestorer/scenes/drestorer_scene_confirm.c b/Applications/Official/DEV_FW/source/dolphinrestorer/scenes/drestorer_scene_confirm.c new file mode 100644 index 000000000..f9de4bccd --- /dev/null +++ b/Applications/Official/DEV_FW/source/dolphinrestorer/scenes/drestorer_scene_confirm.c @@ -0,0 +1,60 @@ +#include "../drestorer.h" +#include "gui/canvas.h" +#include "gui/modules/widget_elements/widget_element_i.h" +#include "storage/storage.h" + +static void + drestorer_scene_confirm_widget_callback(GuiButtonType result, InputType type, void* context) { + StorageMoveToSd* app = context; + furi_assert(app); + if(type == InputTypeShort) { + if(result == GuiButtonTypeRight) { + view_dispatcher_send_custom_event(app->view_dispatcher, MoveToSdCustomEventConfirm); + } else if(result == GuiButtonTypeLeft) { + view_dispatcher_send_custom_event(app->view_dispatcher, MoveToSdCustomEventExit); + } + } +} + +void drestorer_scene_confirm_on_enter(void* context) { + StorageMoveToSd* app = context; + + widget_add_button_element( + app->widget, GuiButtonTypeLeft, "Cancel", drestorer_scene_confirm_widget_callback, app); + widget_add_button_element( + app->widget, GuiButtonTypeRight, "Confirm", drestorer_scene_confirm_widget_callback, app); + + widget_add_string_element( + app->widget, 64, 10, AlignCenter, AlignCenter, FontPrimary, "Backup Found"); + widget_add_string_multiline_element( + app->widget, + 64, + 32, + AlignCenter, + AlignCenter, + FontSecondary, + "Copy backup from\nSD card to internal storage?"); + + view_dispatcher_switch_to_view(app->view_dispatcher, StorageMoveToSdViewWidget); +} + +bool drestorer_scene_confirm_on_event(void* context, SceneManagerEvent event) { + StorageMoveToSd* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == MoveToSdCustomEventConfirm) { + scene_manager_next_scene(app->scene_manager, StorageMoveToSdProgress); + consumed = true; + } else if(event.event == MoveToSdCustomEventExit) { + view_dispatcher_stop(app->view_dispatcher); + } + } + + return consumed; +} + +void drestorer_scene_confirm_on_exit(void* context) { + StorageMoveToSd* app = context; + widget_reset(app->widget); +} diff --git a/Applications/Official/DEV_FW/source/dolphinrestorer/scenes/drestorer_scene_progress.c b/Applications/Official/DEV_FW/source/dolphinrestorer/scenes/drestorer_scene_progress.c new file mode 100644 index 000000000..7c65afb4c --- /dev/null +++ b/Applications/Official/DEV_FW/source/dolphinrestorer/scenes/drestorer_scene_progress.c @@ -0,0 +1,31 @@ +#include "../drestorer.h" + +void drestorer_scene_progress_on_enter(void* context) { + StorageMoveToSd* app = context; + + widget_add_string_element( + app->widget, 64, 10, AlignCenter, AlignCenter, FontPrimary, "Moving..."); + + view_dispatcher_switch_to_view(app->view_dispatcher, StorageMoveToSdViewWidget); + + drestorer_perform(); + view_dispatcher_send_custom_event(app->view_dispatcher, MoveToSdCustomEventExit); +} + +bool drestorer_scene_progress_on_event(void* context, SceneManagerEvent event) { + StorageMoveToSd* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + view_dispatcher_stop(app->view_dispatcher); + } else if(event.type == SceneManagerEventTypeBack) { + consumed = true; + } + + return consumed; +} + +void drestorer_scene_progress_on_exit(void* context) { + StorageMoveToSd* app = context; + widget_reset(app->widget); +} diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/README.md b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/README.md new file mode 100644 index 000000000..0451b8c22 --- /dev/null +++ b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/README.md @@ -0,0 +1,19 @@ +# Doom Flipper Zero edition + +
+ +## Will it run Doom? +As tradition goes, Doom is being ported to almost every possible embedded electronic device. Therefore I did an attempt to come up with something close to Doom and still compatible on the Flipper Zero's hardware.
This is not the actual Doom game but a port made from yet another Doom port to the Arduino Nano (https://github.com/daveruiz/doom-nano/). This port is basically a raycasting engine, using Doom sprites.
+This version is very basic and might be improved over time. + +## How to install on Flipper Zero +Build just like any other external application (.fap): https://github.com/flipperdevices/flipperzero-firmware/blob/dev/documentation/AppsOnSDCard.md. No firmware modification needed anymore! + +## Screenshots +![Intro screen](assets/screenshot-intro2.jpg) + +![Start screen](assets/screenshot-start2.jpg) + +![Imp](assets/screenshot-imp2.jpg) + +![Medkit](assets/screenshot-medkit2.jpg) diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/application.fam b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/application.fam new file mode 100644 index 000000000..d9ae4d67d --- /dev/null +++ b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/application.fam @@ -0,0 +1,15 @@ +App( + appid="DOOM", + name="DOOM", + apptype=FlipperAppType.EXTERNAL, + entry_point="doom_app", + cdefines=["APP_DOOM_GAME"], + requires=[ + "gui", + "music_player", + ], + stack_size=4 * 1024, + order=75, + fap_icon="doom_10px.png", + fap_category="Games", +) diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/door2.png b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/door2.png new file mode 100644 index 000000000..b4b4f0399 Binary files /dev/null and b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/door2.png differ diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/door_inv.png b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/door_inv.png new file mode 100644 index 000000000..3185f524c Binary files /dev/null and b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/door_inv.png differ diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/fire_inv.png b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/fire_inv.png new file mode 100644 index 000000000..46af8691b Binary files /dev/null and b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/fire_inv.png differ diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/fireball_inv.png b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/fireball_inv.png new file mode 100644 index 000000000..b046288f8 Binary files /dev/null and b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/fireball_inv.png differ diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/fireball_mask_inv.png b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/fireball_mask_inv.png new file mode 100644 index 000000000..548c654b7 Binary files /dev/null and b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/fireball_mask_inv.png differ diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/gradient_inv.png b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/gradient_inv.png new file mode 100644 index 000000000..78eec8c20 Binary files /dev/null and b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/gradient_inv.png differ diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/gun_inv.png b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/gun_inv.png new file mode 100644 index 000000000..e2ec05295 Binary files /dev/null and b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/gun_inv.png differ diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/gun_mask_inv.png b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/gun_mask_inv.png new file mode 100644 index 000000000..2d761a70a Binary files /dev/null and b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/gun_mask_inv.png differ diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/imp_inv.png b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/imp_inv.png new file mode 100644 index 000000000..4b480f1c5 Binary files /dev/null and b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/imp_inv.png differ diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/imp_mask_inv.png b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/imp_mask_inv.png new file mode 100644 index 000000000..70e991270 Binary files /dev/null and b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/imp_mask_inv.png differ diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/item_inv.png b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/item_inv.png new file mode 100644 index 000000000..1d32dbcd8 Binary files /dev/null and b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/item_inv.png differ diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/item_mask_inv.png b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/item_mask_inv.png new file mode 100644 index 000000000..a0bde9c76 Binary files /dev/null and b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/item_mask_inv.png differ diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/logo_inv.png b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/logo_inv.png new file mode 100644 index 000000000..c75bd3028 Binary files /dev/null and b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/logo_inv.png differ diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/screenshot-imp2.jpg b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/screenshot-imp2.jpg new file mode 100644 index 000000000..0b4c29c4f Binary files /dev/null and b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/screenshot-imp2.jpg differ diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/screenshot-intro2.jpg b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/screenshot-intro2.jpg new file mode 100644 index 000000000..21fd72840 Binary files /dev/null and b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/screenshot-intro2.jpg differ diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/screenshot-medkit2.jpg b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/screenshot-medkit2.jpg new file mode 100644 index 000000000..4ee0269f3 Binary files /dev/null and b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/screenshot-medkit2.jpg differ diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/screenshot-start2.jpg b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/screenshot-start2.jpg new file mode 100644 index 000000000..6b28ed987 Binary files /dev/null and b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/screenshot-start2.jpg differ diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/screenshot1.png b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/screenshot1.png new file mode 100644 index 000000000..1ecb073a6 Binary files /dev/null and b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/screenshot1.png differ diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/screenshot2.png b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/screenshot2.png new file mode 100644 index 000000000..216b699de Binary files /dev/null and b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/screenshot2.png differ diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/screenshot3.png b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/screenshot3.png new file mode 100644 index 000000000..b5aec03fd Binary files /dev/null and b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/assets/screenshot3.png differ diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/compiled/assets_icons.c b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/compiled/assets_icons.c new file mode 100644 index 000000000..cbdbbc94d --- /dev/null +++ b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/compiled/assets_icons.c @@ -0,0 +1,405 @@ +#include "assets_icons.h" + +#include + +// Inverted icons + +const uint8_t _I_fire_inv_0[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x30, 0x00, 0x40, 0xee, 0x00, 0x80, 0xe6, 0x00, + 0x80, 0xa7, 0x01, 0x80, 0xc7, 0x03, 0x40, 0x45, 0x03, 0xe0, 0x41, 0x07, 0xf8, 0x82, 0x9f, 0xb9, + 0x01, 0x3e, 0x7c, 0x00, 0x7a, 0x6e, 0x00, 0x56, 0x1c, 0x00, 0x6c, 0xf4, 0x01, 0x3a, 0x6c, 0x00, + 0x7e, 0xfc, 0x00, 0x1a, 0x08, 0x00, 0x3c, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, +}; +const uint8_t* const _I_fire_inv[] = {_I_fire_inv_0}; + +const uint8_t _I_gun_inv_0[] = { + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x80, 0x23, 0x00, 0x00, 0x40, + 0x20, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x40, 0x57, 0x00, 0x00, 0x20, 0x8b, 0x00, 0x00, + 0x90, 0x11, 0x01, 0x00, 0x98, 0x00, 0x00, 0x00, 0xb0, 0x43, 0x01, 0x00, 0x94, 0x81, 0x03, + 0x00, 0xd0, 0x45, 0x04, 0x00, 0x8c, 0x02, 0x02, 0x00, 0xc4, 0x00, 0x03, 0x00, 0xc8, 0x00, + 0x02, 0x00, 0x4e, 0x40, 0x00, 0x00, 0x92, 0x00, 0x02, 0x80, 0x07, 0x15, 0x04, 0xe0, 0x8f, + 0x00, 0x0c, 0xd0, 0x9d, 0x07, 0x17, 0xe0, 0x3a, 0xc0, 0x3f, 0xe0, 0xf7, 0xff, 0x77, 0xe0, + 0xae, 0xfe, 0x4b, 0xd8, 0xdd, 0xff, 0x4d, 0x88, 0xea, 0xbe, 0x26, 0x4c, 0xf5, 0xff, 0x17, + 0xc8, 0xfa, 0xae, 0x0b, 0xcc, 0xff, 0xdf, 0x19, 0xe8, 0xeb, 0xa7, 0x00, 0xd8, 0xf1, 0x4d, + 0x0c, 0xc0, 0xbe, 0x1a, 0x08, 0xf6, 0xfd, 0x37, 0x04, +}; +const uint8_t* const _I_gun_inv[] = {_I_gun_inv_0}; + +const uint8_t _I_gun_mask_inv_0[] = { + 0x01, 0x00, 0x53, 0x00, 0x00, 0x0c, 0x38, 0x04, 0x38, 0x09, 0xf8, 0x0c, 0x78, 0x17, 0xf0, + 0x18, 0xf8, 0x00, 0x67, 0xff, 0x01, 0xaf, 0xc3, 0xff, 0x01, 0x80, 0x7e, 0x3f, 0xf0, 0x38, + 0x07, 0xf0, 0x0e, 0x40, 0x31, 0x03, 0x8f, 0xfb, 0xff, 0x07, 0x01, 0x94, 0x3c, 0x0e, 0xc0, + 0x35, 0xff, 0x85, 0xc8, 0x06, 0x30, 0x7e, 0x00, 0x0c, 0x61, 0xe2, 0xe1, 0xff, 0xc7, 0xc5, + 0xc3, 0xff, 0x9f, 0x80, 0xca, 0xfe, 0x03, 0x2f, 0xf8, 0x0c, 0xc6, 0xc2, 0x03, 0x4b, 0xf8, + 0xa8, 0x42, 0xe2, 0x03, 0x28, 0xf8, 0x1e, 0x80, 0x68, 0x1e, 0x28, 0x78, +}; +const uint8_t* const _I_gun_mask_inv[] = {_I_gun_mask_inv_0}; + +const uint8_t _I_logo_inv_0[] = { + 0x01, 0x00, 0x92, 0x01, 0x00, 0x78, 0x03, 0xc0, 0x03, 0xfc, 0xff, 0xff, 0xfc, 0x1f, 0xf9, 0xff, + 0xe3, 0xff, 0x0f, 0x8f, 0xfc, 0x2f, 0xe0, 0xf3, 0xdc, 0x6e, 0xf7, 0x7b, 0x1d, 0xdd, 0xef, 0x79, + 0xbb, 0xcd, 0xce, 0xf7, 0x13, 0xb0, 0x79, 0xfc, 0x03, 0xe3, 0xfb, 0x01, 0x0f, 0xfb, 0xff, 0xbf, + 0x11, 0x8c, 0x7c, 0x1e, 0x7e, 0x0f, 0x77, 0xbb, 0xd4, 0x02, 0x10, 0x00, 0xeb, 0xad, 0xde, 0xc8, + 0x70, 0x3c, 0xf8, 0x01, 0xf7, 0xbf, 0xff, 0x20, 0xe0, 0xf3, 0xc0, 0x6e, 0x80, 0x0d, 0x7a, 0xde, + 0x40, 0x83, 0xf8, 0x03, 0x10, 0xfa, 0x70, 0x07, 0xee, 0x02, 0x18, 0x30, 0x3d, 0x0a, 0xe3, 0xfb, + 0x83, 0x86, 0xc7, 0x82, 0x1f, 0x1f, 0xf8, 0xfd, 0x61, 0x5a, 0x0d, 0x54, 0x0b, 0x55, 0xaa, 0xc0, + 0x00, 0x84, 0x0c, 0x21, 0xf4, 0x8f, 0xf4, 0x3b, 0x70, 0x3e, 0xf7, 0x7b, 0x01, 0x9f, 0xef, 0xf7, + 0xc3, 0xfe, 0x1f, 0x6f, 0x87, 0xee, 0x07, 0xfe, 0xff, 0x60, 0x07, 0xfe, 0x1f, 0x88, 0xef, 0xc3, + 0xf3, 0x01, 0xfe, 0x7f, 0x30, 0x1b, 0xdf, 0xef, 0xf6, 0x0a, 0x1f, 0xf0, 0x79, 0xd0, 0x22, 0xf7, + 0x0b, 0x9c, 0x0e, 0xed, 0x76, 0x80, 0x4d, 0xee, 0xf7, 0x71, 0xff, 0x87, 0xd6, 0x2b, 0x50, 0xa8, + 0xc0, 0x6a, 0x95, 0x48, 0x04, 0x56, 0xab, 0x55, 0x1f, 0xf8, 0xff, 0xc7, 0xfe, 0x3f, 0xaa, 0xe4, + 0x02, 0x3b, 0x55, 0xae, 0x8f, 0xfc, 0xbf, 0xe1, 0xff, 0x0f, 0xf8, 0x7d, 0x61, 0x18, 0x0c, 0x44, + 0x03, 0x11, 0x88, 0x02, 0x0e, 0x23, 0x04, 0x0e, 0x30, 0xfa, 0x41, 0x43, 0xe2, 0x06, 0x18, 0xa8, + 0x18, 0x43, 0xe9, 0x02, 0xd0, 0x68, 0xa1, 0x5a, 0x2d, 0x51, 0x10, 0x70, 0x5a, 0x21, 0xfc, 0x45, + 0x43, 0xe3, 0x50, 0x0f, 0xc4, 0x20, 0x72, 0x20, 0x22, 0x02, 0x16, 0x00, 0x7e, 0xd5, 0x4a, 0x8d, + 0x54, 0x3e, 0x35, 0x40, 0xfb, 0x80, 0x3c, 0x3e, 0x35, 0x5a, 0x09, 0xe8, 0x6a, 0x87, 0xc1, 0x23, + 0x88, 0xfd, 0x40, 0x0c, 0x09, 0x20, 0xfa, 0x87, 0x06, 0x00, 0x1f, 0x38, 0x0c, 0x50, 0x3e, 0xa0, + 0x0f, 0x0f, 0x8c, 0x56, 0x00, 0x5c, 0x1a, 0x80, 0x90, 0x62, 0x03, 0xf6, 0x80, 0x42, 0x17, 0xc2, + 0x7b, 0x10, 0x20, 0x87, 0xde, 0xa9, 0x04, 0x80, 0x54, 0x20, 0x94, 0x08, 0xa0, 0x24, 0x47, 0xf5, + 0x01, 0x20, 0x54, 0x44, 0x18, 0x80, 0x42, 0x88, 0x17, 0xfc, 0x7e, 0xb4, 0x40, 0x4c, 0x12, 0x04, + 0x01, 0xe3, 0xf4, 0x2a, 0xf8, 0x03, 0xc0, 0x1f, 0xe0, 0xbc, 0xcf, 0xb8, 0xf8, 0x1e, 0xbf, 0xcc, + 0x5b, 0x12, 0x0c, 0x56, 0x0a, 0x51, 0x82, 0xa8, 0x28, 0x08, 0x1f, 0x32, 0x0c, 0x00, 0x3e, 0x85, + 0xe3, 0x1e, 0x17, 0x8f, 0x4f, 0xe6, 0x1f, 0x99, 0x44, 0x0a, 0x10, 0x2f, 0x13, 0xb4, 0xc8, 0x29, + 0x03, 0xf3, 0x89, 0x03, 0xe7, 0x10, 0x5f, 0x2a, 0x87, 0xd1, 0x1b, 0xe0, 0x0f, 0x00, 0x78, 0x03, + 0xc0, 0x1e, 0x00, 0xf0, 0x02, 0x00, +}; +const uint8_t* const _I_logo_inv[] = {_I_logo_inv_0}; + +const Icon I_fire_inv = + {.width = 24, .height = 20, .frame_count = 1, .frame_rate = 0, .frames = _I_fire_inv}; +const Icon I_gun_inv = + {.width = 32, .height = 32, .frame_count = 1, .frame_rate = 0, .frames = _I_gun_inv}; +const Icon I_gun_mask_inv = + {.width = 32, .height = 32, .frame_count = 1, .frame_rate = 0, .frames = _I_gun_mask_inv}; +const Icon I_logo_inv = + {.width = 128, .height = 64, .frame_count = 1, .frame_rate = 0, .frames = _I_logo_inv}; + +const uint8_t space[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +const uint8_t zero[] = {0x00, 0x60, 0x90, 0x90, 0x90, 0x60}; +const uint8_t one[] = {0x00, 0x20, 0x20, 0x20, 0x20, 0x70}; +const uint8_t two[] = {0x00, 0x60, 0x90, 0x20, 0x40, 0xf0}; +const uint8_t three[] = {0x00, 0x60, 0x90, 0x20, 0x90, 0x60}; +const uint8_t four[] = {0x00, 0x90, 0x90, 0xf0, 0x10, 0x10}; +const uint8_t five[] = {0x00, 0xf0, 0x80, 0xe0, 0x10, 0xe0}; +const uint8_t six[] = {0x00, 0x60, 0x80, 0xe0, 0x90, 0x60}; +const uint8_t seven[] = {0x00, 0xf0, 0x10, 0x10, 0x10, 0x10}; +const uint8_t eight[] = {0x00, 0x60, 0x90, 0x60, 0x90, 0x60}; +const uint8_t nine[] = {0x00, 0x60, 0x90, 0x70, 0x10, 0x60}; +const uint8_t A[] = {0x00, 0x60, 0x90, 0xf0, 0x90, 0x90}; +const uint8_t B[] = {0x00, 0xe0, 0x90, 0xe0, 0x90, 0xe0}; +const uint8_t C[] = {0x00, 0x60, 0x90, 0x80, 0x90, 0x60}; +const uint8_t D[] = {0x00, 0xe0, 0x90, 0x90, 0x90, 0xe0}; +const uint8_t E[] = {0x00, 0xf0, 0x80, 0xe0, 0x80, 0xf0}; +const uint8_t F[] = {0x00, 0xf0, 0x80, 0xe0, 0x80, 0x80}; +const uint8_t G[] = {0x00, 0x60, 0x80, 0x80, 0x90, 0x60}; +const uint8_t H[] = {0x00, 0x90, 0x90, 0xf0, 0x90, 0x90}; +const uint8_t I[] = {0x00, 0x20, 0x20, 0x20, 0x20, 0x20}; +const uint8_t J[] = {0x00, 0x10, 0x10, 0x10, 0x90, 0x60}; +const uint8_t K[] = {0x00, 0x90, 0xa0, 0xc0, 0xa0, 0x90}; +const uint8_t L[] = {0x00, 0x80, 0x80, 0x80, 0x80, 0xf0}; +const uint8_t M[] = {0x00, 0x90, 0xf0, 0x90, 0x90, 0x90}; +const uint8_t N[] = {0x00, 0x90, 0xd0, 0xb0, 0x90, 0x90}; +const uint8_t O[] = {0x00, 0x60, 0x90, 0x90, 0x90, 0x60}; +const uint8_t P[] = {0x00, 0xe0, 0x90, 0xe0, 0x80, 0x80}; +const uint8_t Q[] = {0x00, 0x60, 0x90, 0x90, 0xb0, 0x70}; +const uint8_t R[] = {0x00, 0xe0, 0x90, 0xe0, 0x90, 0x90}; +const uint8_t S[] = {0x00, 0x60, 0x80, 0x60, 0x10, 0xe0}; +const uint8_t T[] = {0x00, 0xe0, 0x40, 0x40, 0x40, 0x40}; +const uint8_t U[] = {0x00, 0x90, 0x90, 0x90, 0x90, 0x60}; +const uint8_t V[] = {0x00, 0x90, 0x90, 0x90, 0x60, 0x60}; +const uint8_t W[] = {0x00, 0x90, 0x90, 0x90, 0xf0, 0x90}; +const uint8_t X[] = {0x00, 0x90, 0x90, 0x60, 0x90, 0x90}; +const uint8_t Y[] = {0x00, 0x90, 0x90, 0x60, 0x60, 0x60}; +const uint8_t Z[] = {0x00, 0xf0, 0x10, 0x60, 0x80, 0xf0}; +const uint8_t dot[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x40}; +const uint8_t comma[] = {0x00, 0x00, 0x00, 0x00, 0x20, 0x40}; +const uint8_t dash[] = {0x00, 0x00, 0x00, 0x60, 0x00, 0x00}; +const uint8_t underscore[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0xf0}; +const uint8_t bracket_open[] = {0x00, 0x20, 0x40, 0x40, 0x40, 0x20}; +const uint8_t bracket_close[] = {0x00, 0x40, 0x20, 0x20, 0x20, 0x40}; +const uint8_t cross_left[] = {0x10, 0x10, 0x70, 0x70, 0x10, 0x10}; +const uint8_t cross_right[] = {0x80, 0x80, 0xe0, 0xe0, 0x80, 0x80}; +const uint8_t pacman_left[] = {0x00, 0x30, 0x50, 0x70, 0x70, 0x00}; +const uint8_t pacman_right[] = {0x00, 0xc0, 0x60, 0xe0, 0xe0, 0xe0}; +const uint8_t box[] = {0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0x00}; +const uint8_t* char_arr[48] = { + space, + zero, + one, + two, + three, + four, + five, + six, + seven, + eight, + nine, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + dot, + comma, + dash, + underscore, + bracket_open, + bracket_close, + cross_left, + cross_right, + pacman_left, + pacman_right, + box}; +const uint8_t gradient[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x88, 0x00, 0x00, 0x22, 0x22, + 0x00, 0x00, 0x8a, 0x8a, 0x00, 0x00, 0x22, 0x22, 0x00, 0x00, 0xaa, 0xaa, + 0x10, 0x10, 0xaa, 0xaa, 0x00, 0x00, 0xaa, 0xaa, 0x01, 0x01, 0xaa, 0xaa, + 0x44, 0x44, 0xaa, 0xaa, 0x55, 0x55, 0xaa, 0xaa, 0x44, 0x44, 0xaa, 0xaa, + 0x15, 0x55, 0xaa, 0xaa, 0x55, 0x55, 0xaa, 0xaa, 0x55, 0x55, 0xbb, 0xbb, + 0x55, 0x55, 0xaa, 0xea, 0x55, 0x55, 0xbb, 0xbb, 0x55, 0x55, 0xff, 0xff, + 0x55, 0x55, 0xfb, 0xfb, 0x55, 0x55, 0xff, 0xff, 0x55, 0x55, 0xbb, 0xbf, + 0x57, 0x57, 0xff, 0xff, 0xdd, 0xdd, 0xff, 0xff, 0x77, 0x75, 0xff, 0xff, + 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; +//const uint8_t gun[] = {0xff, 0xff, 0xdf, 0xff, 0xff, 0xff, 0x27, 0xff, 0xff, 0xfe, 0x3b, 0xff, 0xff, 0xfd, 0xfb, 0xff, 0xff, 0xfd, 0xfd, 0xff, 0xff, 0xfd, 0x15, 0xff, 0xff, 0xfb, 0x2e, 0xff, 0xff, 0xf6, 0x77, 0x7f, 0xff, 0xe6, 0xff, 0xff, 0xff, 0xf2, 0x3d, 0x7f, 0xff, 0xd6, 0x7e, 0x3f, 0xff, 0xf4, 0x5d, 0xdf, 0xff, 0xce, 0xbf, 0xbf, 0xff, 0xdc, 0xff, 0x3f, 0xff, 0xec, 0xff, 0xbf, 0xff, 0x8d, 0xfd, 0xff, 0xff, 0xb6, 0xff, 0xbf, 0xfe, 0x1f, 0x57, 0xdf, 0xf8, 0x0e, 0xff, 0xcf, 0xf4, 0x46, 0x1f, 0x17, 0xf8, 0xa3, 0xfc, 0x03, 0xf8, 0x10, 0x00, 0x11, 0xf8, 0x8a, 0x80, 0x2d, 0xe4, 0x44, 0x00, 0x4d, 0xee, 0xa8, 0x82, 0x9b, 0xcd, 0x50, 0x00, 0x17, 0xec, 0xa0, 0x8a, 0x2f, 0xcc, 0x00, 0x04, 0x67, 0xe8, 0x28, 0x1a, 0xff, 0xe4, 0x70, 0x4d, 0xcf, 0xfc, 0x82, 0xa7, 0xef, 0x90, 0x40, 0x13, 0xdf}; +// const uint8_t gun_mask[] = {0xff, 0xff, 0x8f, 0xff, 0xff, 0xfe, 0x03, 0xff, 0xff, 0xfc, 0x01, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xf8, 0x00, 0xff, 0xff, 0xf8, 0x00, 0xff, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xc0, 0x00, 0x3f, 0xff, 0x80, 0x00, 0x1f, 0xff, 0x80, 0x00, 0x0f, 0xff, 0x80, 0x00, 0x1f, 0xff, 0x80, 0x00, 0x1f, 0xff, 0x80, 0x00, 0x1f, 0xff, 0x00, 0x00, 0x3f, 0xff, 0x00, 0x00, 0x1f, 0xfc, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0f}; +const uint8_t gun[] = {0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x01, 0xc4, 0x00, + 0x00, 0x02, 0x04, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x02, 0xea, 0x00, + 0x00, 0x04, 0xd1, 0x00, 0x00, 0x09, 0x88, 0x80, 0x00, 0x19, 0x00, 0x00, + 0x00, 0x0d, 0xc2, 0x80, 0x00, 0x29, 0x81, 0xc0, 0x00, 0x0b, 0xa2, 0x20, + 0x00, 0x31, 0x40, 0x40, 0x00, 0x23, 0x00, 0xc0, 0x00, 0x13, 0x00, 0x40, + 0x00, 0x72, 0x02, 0x00, 0x00, 0x49, 0x00, 0x40, 0x01, 0xe0, 0xa8, 0x20, + 0x07, 0xf1, 0x00, 0x30, 0x0b, 0xb9, 0xe0, 0xe8, 0x07, 0x5c, 0x03, 0xfc, + 0x07, 0xef, 0xff, 0xee, 0x07, 0x75, 0x7f, 0xd2, 0x1b, 0xbb, 0xff, 0xb2, + 0x11, 0x57, 0x7d, 0x64, 0x32, 0xaf, 0xff, 0xe8, 0x13, 0x5f, 0x75, 0xd0, + 0x33, 0xff, 0xfb, 0x98, 0x17, 0xd7, 0xe5, 0x00, 0x1b, 0x8f, 0xb2, 0x30, + 0x03, 0x7d, 0x58, 0x10, 0x6f, 0xbf, 0xec, 0x20}; +const uint8_t gun_mask[] = {0x00, 0x00, 0x70, 0x00, 0x00, 0x01, 0xfc, 0x00, 0x00, 0x03, 0xfe, 0x00, + 0x00, 0x07, 0xfe, 0x00, 0x00, 0x07, 0xff, 0x00, 0x00, 0x07, 0xff, 0x00, + 0x00, 0x0f, 0xff, 0x80, 0x00, 0x1f, 0xff, 0xc0, 0x00, 0x3f, 0xff, 0x80, + 0x00, 0x3f, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xe0, 0x00, 0x7f, 0xff, 0xf0, + 0x00, 0x7f, 0xff, 0xe0, 0x00, 0x7f, 0xff, 0xe0, 0x00, 0x7f, 0xff, 0xe0, + 0x00, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xff, 0xe0, 0x03, 0xff, 0xff, 0xf0, + 0x0f, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xfc, 0x1f, 0xff, 0xff, 0xfe, + 0x1f, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, + 0x3f, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xf8, + 0x7f, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xf8, 0x7f, 0xff, 0xff, 0xf8, + 0x7f, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xf0}; + +const uint8_t + imp_inv[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, + 0x02, 0x80, 0x00, 0x00, 0x07, 0x40, 0x00, 0x00, 0x02, 0x80, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x01, 0x0f, 0xb3, 0x00, 0x00, 0xd0, 0x4e, 0x00, 0x00, 0x79, 0x8c, + 0x00, 0x00, 0x1c, 0x19, 0x00, 0x01, 0x8a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x40, 0x02, 0x08, 0x00, 0x80, 0x00, 0x00, 0x01, 0x00, 0x01, 0x8e, + 0x30, 0x00, 0x00, 0x04, 0x10, 0x00, 0x00, 0x0c, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x06, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x20, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x03, 0xe0, + 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0xa1, 0x80, 0x01, 0x80, 0x13, 0x00, + 0x00, 0xf3, 0x8a, 0x00, 0x00, 0x09, 0x94, 0x00, 0x00, 0x88, 0x38, 0x80, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x23, 0x00, 0x00, 0x00, 0x00, 0x40, 0x01, 0x80, + 0x00, 0x80, 0x00, 0x00, 0x01, 0x00, 0x00, 0xe2, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0c, 0x20, 0x00, 0x00, 0x04, 0x30, 0x00, 0x00, 0x02, 0x20, 0x00, + 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x20, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, + 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, + 0x00, 0x1f, 0x00, 0x00, 0x02, 0x2a, 0x80, 0x00, 0x01, 0x05, 0x00, 0x00, 0x01, + 0xae, 0x20, 0x00, 0x01, 0x24, 0x40, 0x00, 0x02, 0xac, 0x80, 0x00, 0x02, 0x86, + 0x00, 0x00, 0x03, 0x20, 0x20, 0x00, 0x04, 0x30, 0x40, 0x00, 0x0c, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x20, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x1a, 0x00, 0x00, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x98, + 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, + 0x00, 0x00, 0x08, 0x40, 0x00, 0x00, 0x00, 0x80, 0x00, 0x01, 0xd6, 0x80, 0x00, + 0x02, 0xbf, 0x80, 0x00, 0x06, 0x61, 0xa0, 0x00, 0x0c, 0xe8, 0x80, 0x00, 0x0c, + 0x10, 0x00, 0x00, 0x1a, 0x22, 0x00, 0x00, 0x12, 0x40, 0x00, 0x00, 0x06, 0x0c, + 0x00, 0x00, 0x04, 0x0d, 0x00, 0x00, 0x3a, 0x03, 0x00, 0x00, 0x10, 0x02, 0x00, + 0x00, 0x60, 0x0a, 0x00, 0x00, 0x50, 0x04, 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x24, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x01, + 0x18, 0x00, 0x00, 0x01, 0x41, 0x40, 0x02, 0x33, 0xb6, 0x80, 0x01, 0x9c, 0x04, + 0x00, 0x08, 0xfa, 0x02, 0x08, 0x05, 0x00, 0x01, 0x0c, 0x27, 0x83, 0xa2, 0x2a, + 0x00, 0x04, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00}; //{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x02, 0x80, 0x00, 0x00, 0x07, 0x40, 0x00, 0x00, 0x02, 0x80, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x0f, 0xb3, 0x00, 0x00, 0xd0, 0x4e, 0x00, 0x00, 0x79, 0x8c, 0x00, 0x00, 0x1c, 0x19, 0x00, 0x01, 0x8a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x02, 0x08, 0x00, 0x80, 0x00, 0x00, 0x01, 0x00, 0x01, 0x8e, 0x30, 0x00, 0x00, 0x04, 0x10, 0x00, 0x00, 0x0c, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x20, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +const uint8_t imp_mask_inv[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x03, 0xc0, 0x00, + 0x00, 0x07, 0xe0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x01, 0x07, 0xf1, 0x80, + 0x00, 0xdf, 0xfe, 0x00, 0x00, 0x3f, 0xfe, 0x00, 0x00, 0x7f, 0xff, 0x00, 0x01, 0xff, 0xff, 0x80, + 0x00, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0x80, 0x03, 0xcf, 0xf1, 0xc0, 0x01, 0xc7, 0xf1, 0xc0, + 0x01, 0x87, 0xf1, 0xc0, 0x03, 0x0f, 0xf9, 0x80, 0x03, 0x0f, 0xfb, 0x80, 0x01, 0x8f, 0xff, 0x80, + 0x03, 0x9f, 0x79, 0x00, 0x00, 0x1f, 0x7c, 0x00, 0x00, 0x0f, 0x78, 0x00, 0x00, 0x0f, 0x78, 0x00, + 0x00, 0x07, 0x30, 0x00, 0x00, 0x07, 0x38, 0x00, 0x00, 0x07, 0x30, 0x00, 0x00, 0x07, 0x30, 0x00, + 0x00, 0x03, 0x78, 0x00, 0x00, 0x07, 0x30, 0x00, 0x00, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x07, 0xe0, 0x00, + 0x00, 0x07, 0xc0, 0x00, 0x01, 0x07, 0xe1, 0x00, 0x00, 0x8f, 0xfa, 0x00, 0x00, 0xff, 0xfe, 0x00, + 0x00, 0x3f, 0xfe, 0x00, 0x01, 0x7f, 0xff, 0x80, 0x00, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0x80, + 0x03, 0xcf, 0xfb, 0xc0, 0x03, 0x87, 0xf1, 0xc0, 0x03, 0xcf, 0xf3, 0xc0, 0x01, 0xcf, 0xf1, 0x80, + 0x00, 0xcf, 0xf1, 0x00, 0x00, 0x0f, 0xfb, 0x80, 0x00, 0x1e, 0x78, 0x00, 0x00, 0x0e, 0x78, 0x00, + 0x00, 0x1e, 0x78, 0x00, 0x00, 0x0f, 0x70, 0x00, 0x00, 0x0f, 0x78, 0x00, 0x00, 0x07, 0x70, 0x00, + 0x00, 0x07, 0x70, 0x00, 0x00, 0x07, 0x38, 0x00, 0x00, 0x03, 0x30, 0x00, 0x00, 0x03, 0x20, 0x00, + 0x00, 0x07, 0x30, 0x00, 0x00, 0x05, 0x70, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x1f, 0x00, + 0x00, 0x00, 0x1f, 0x00, 0x00, 0x03, 0x3f, 0x80, 0x00, 0x01, 0x3f, 0x00, 0x00, 0x01, 0xff, 0x30, + 0x00, 0x03, 0xff, 0xc0, 0x00, 0x03, 0xff, 0xc0, 0x00, 0x03, 0xff, 0x80, 0x00, 0x07, 0xff, 0xe0, + 0x00, 0x07, 0xff, 0xc0, 0x00, 0x05, 0xff, 0xe0, 0x00, 0x00, 0xfc, 0xe0, 0x00, 0x01, 0xfc, 0xe0, + 0x00, 0x01, 0xfc, 0x70, 0x00, 0x03, 0xfc, 0x38, 0x00, 0x03, 0xfe, 0x70, 0x00, 0x07, 0xfc, 0x00, + 0x00, 0x07, 0x9e, 0x00, 0x00, 0x0f, 0xbc, 0x00, 0x00, 0x0f, 0x3e, 0x00, 0x00, 0x07, 0x9c, 0x00, + 0x00, 0x03, 0x9c, 0x00, 0x00, 0x03, 0xb8, 0x00, 0x00, 0x03, 0x98, 0x00, 0x00, 0x01, 0x98, 0x00, + 0x00, 0x02, 0x1c, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x1f, 0x00, + 0x00, 0x00, 0x1f, 0x40, 0x00, 0x00, 0x3e, 0x80, 0x00, 0x01, 0xff, 0x80, 0x00, 0x03, 0xff, 0x80, + 0x00, 0x07, 0xff, 0xe0, 0x00, 0x0e, 0xff, 0xc0, 0x00, 0x0c, 0xff, 0x80, 0x00, 0x1f, 0xfe, 0x00, + 0x00, 0x13, 0xfc, 0x00, 0x00, 0x07, 0xfe, 0x00, 0x00, 0x1f, 0xff, 0x00, 0x00, 0x3f, 0x9f, 0x00, + 0x00, 0x3e, 0x0f, 0x00, 0x00, 0x7c, 0x0f, 0x00, 0x00, 0x78, 0x0f, 0x00, 0x00, 0x78, 0x07, 0x80, + 0x00, 0x78, 0x07, 0x40, 0x00, 0x38, 0x07, 0x80, 0x00, 0x30, 0x07, 0x00, 0x00, 0x30, 0x01, 0x00, + 0x01, 0xf0, 0x00, 0x00, 0x01, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x1c, 0x00, + 0x00, 0x01, 0x3e, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x0f, 0xff, 0xe0, 0x01, 0x3f, 0xff, 0xc0, + 0x01, 0xff, 0xff, 0xc0, 0x19, 0xff, 0xff, 0xe8, 0x7f, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xfe, + 0x1f, 0xc2, 0x07, 0xe0, 0x1f, 0x00, 0x01, 0xe0, 0x0e, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, +}; //{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x01, 0x07, 0xf1, 0x80, 0x00, 0xdf, 0xfe, 0x00, 0x00, 0x3f, 0xfe, 0x00, 0x00, 0x7f, 0xff, 0x00, 0x01, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0x80, 0x03, 0xcf, 0xf1, 0xc0, 0x01, 0xc7, 0xf1, 0xc0, 0x01, 0x87, 0xf1, 0xc0, 0x03, 0x0f, 0xf9, 0x80, 0x03, 0x0f, 0xfb, 0x80, 0x01, 0x8f, 0xff, 0x80, 0x03, 0x9f, 0x79, 0x00, 0x00, 0x1f, 0x7c, 0x00, 0x00, 0x0f, 0x78, 0x00, 0x00, 0x0f, 0x78, 0x00, 0x00, 0x07, 0x30, 0x00, 0x00, 0x07, 0x38, 0x00, 0x00, 0x07, 0x30, 0x00, 0x00, 0x07, 0x30, 0x00, 0x00, 0x03, 0x78, 0x00, 0x00, 0x07, 0x30, 0x00, 0x00, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00}; +const uint8_t fireball[] = {0x00, 0x00, 0x01, 0x40, 0x0a, 0xb0, 0x0e, 0xd0, 0x00, 0x68, 0x53, + 0xb4, 0x0f, 0x48, 0x27, 0x78, 0x17, 0xa8, 0x27, 0xf0, 0x21, 0xd6, + 0x02, 0xf8, 0x20, 0x48, 0x06, 0x20, 0x01, 0x00, 0x00, 0x00}; +const uint8_t fireball_mask[] = {0x1f, 0x40, 0x0f, 0xf0, 0x3f, 0xf8, 0x1f, 0xfc, 0x7f, 0xfd, 0x7f, + 0xfc, 0x7f, 0xfd, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0xff, 0xfe, 0x3f, 0xfe, 0x17, 0xf8, 0x07, 0xf4, 0x01, 0xe0}; +const uint8_t item[] = {0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, 0x7f, 0xfe, 0x77, 0xee, 0x3f, + 0xfc, 0x5f, 0xfa, 0x2f, 0xf6, 0x53, 0xcc, 0x3e, 0x7e, 0x5e, 0x7c, + 0x38, 0x1e, 0x58, 0x1c, 0x3e, 0x7e, 0x5e, 0x7e, 0x2e, 0xfc, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xfc, + 0x17, 0xfc, 0x22, 0x6c, 0x36, 0x44, 0x3f, 0xfc, 0x1f, 0xfc, 0x2b, + 0xfc, 0x05, 0x54, 0x02, 0xa8, 0x00, 0x00, 0x00, 0x00}; +const uint8_t item_mask[] = {0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, + 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, + 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x3f, 0xfc, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xfc, + 0x1f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, + 0xfc, 0x07, 0xfc, 0x03, 0xf8, 0x00, 0x00, 0x00, 0x00}; + +//const uint8_t door[] = {0xff, 0xff, 0xff, 0xff,0xb2, 0xbd, 0xcd, 0x5b,0x9a, 0xf4, 0x6d, 0x71,0xff, 0xff, 0xff, 0xff,0x00, 0x00, 0x00, 0x00,0xbf, 0xff, 0xff, 0xfd,0x3f, 0x00, 0xfe, 0xfc,0x3e, 0x00, 0xc6, 0xfc,0xbc, 0xaa, 0xfe, 0xbd,0x39, 0x54, 0xc6, 0xbc,0x32, 0x8e, 0xfe, 0xac,0xb5, 0xfe, 0xc6, 0xad,0x3f, 0xe0, 0xfe, 0xac,0x31, 0xe0, 0xc6, 0xac,0xb3, 0xf4, 0xfe, 0xad,0x3f, 0xe8, 0xc6, 0xac,0x3c, 0xf4, 0xd6, 0xac,0xb8, 0xff, 0xfe, 0xad,0x34, 0xc7, 0xfe, 0xfc,0x38, 0xd6, 0x0e, 0x0c,0xb0, 0xd6, 0x4e, 0x0d,0x3f, 0xd6, 0xaf, 0x5c,0x30, 0x47, 0xff, 0xac,0xb7, 0x57, 0xff, 0xfd,0x3f, 0xc6, 0x0e, 0x0c,0x35, 0x56, 0x40, 0x4c,0xb5, 0x46, 0xaa, 0xad,0x35, 0x56, 0x55, 0x4c,0xff, 0xff, 0xff, 0xff,0xb0, 0x1f, 0xf8, 0x0d,0xd9, 0x30, 0x0c, 0x9b,0xff, 0xe0, 0x07, 0xff}; +const uint8_t door[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x07, 0xe1, 0x8c, 0x00, 0x04, + 0x00, 0x7c, 0x03, 0x18, 0x60, 0x08, 0x00, 0x3e, 0x0f, 0xf7, 0xdf, 0x00, 0x1f, 0x00, 0xfe, 0x0f, + 0xbe, 0xf8, 0x3e, 0x00, 0x3f, 0x1f, 0xff, 0xdf, 0x00, 0x1f, 0x81, 0xff, 0x0f, 0xff, 0xf8, 0x7e, + 0x00, 0x3f, 0x8f, 0xff, 0xdf, 0x00, 0xff, 0xf9, 0xff, 0x1f, 0xff, 0xf8, 0xff, 0x80, 0x3f, 0xc7, + 0xff, 0xcc, 0x07, 0xff, 0xfc, 0xff, 0x1f, 0xff, 0xe3, 0xff, 0x80, 0x3f, 0xc7, 0xff, 0xc0, 0x07, + 0xff, 0xfc, 0x7f, 0x0f, 0xfe, 0x03, 0xff, 0xc0, 0x3f, 0xc3, 0xf7, 0xc0, 0x07, 0xdf, 0xf8, 0x3e, + 0x0f, 0xbe, 0x01, 0xff, 0x80, 0x1f, 0x80, 0xe3, 0x80, 0x07, 0x8f, 0xf8, 0x1e, 0x07, 0x1c, 0x01, + 0xff, 0x80, 0x3f, 0xc1, 0xff, 0xc0, 0x0f, 0xff, 0xfc, 0x3f, 0x0f, 0xbe, 0x03, 0xff, 0xc0, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, 0x80, 0x00, + 0x7f, 0xff, 0xff, 0xc0, 0x00, 0x00, 0xe0, 0x00, 0x1f, 0xf0, 0xff, 0x00, 0x00, 0x7f, 0xff, 0xff, + 0xc0, 0x00, 0x00, 0xe0, 0x00, 0x0f, 0xf0, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x01, + 0xe0, 0x00, 0x0f, 0xf0, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x01, 0xf0, 0x00, 0x0f, + 0xf0, 0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0xe0, 0x7f, 0x81, 0xf0, 0x00, 0x0f, 0xf0, 0xff, 0x00, + 0x07, 0xff, 0xff, 0xff, 0xe0, 0xff, 0xc1, 0xf0, 0x00, 0x0f, 0xf0, 0xff, 0x00, 0x0f, 0xff, 0xff, + 0xff, 0xe1, 0xff, 0xe1, 0xf0, 0x00, 0x0f, 0xf0, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xe0, 0xff, + 0xc1, 0xf3, 0x00, 0x0f, 0xf0, 0xff, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xe0, 0xff, 0x81, 0xff, 0xc0, + 0x0f, 0xf0, 0xff, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xe0, 0x3f, 0x01, 0xff, 0xc0, 0x0f, 0xf0, 0xff, + 0x00, 0xff, 0xff, 0xff, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xe0, 0x0f, 0xf0, 0xff, 0x01, 0xff, 0xff, + 0xff, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xe0, 0x0f, 0xf0, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xe1, + 0xff, 0xe1, 0xff, 0xe0, 0x0f, 0xf0, 0xff, 0x07, 0xff, 0xff, 0xff, 0xff, 0xe0, 0xff, 0xc1, 0xff, + 0xf0, 0x0f, 0xf0, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x8f, 0xe0, 0xff, 0x81, 0xff, 0xff, 0x0f, 0xf0, + 0xff, 0x1f, 0xff, 0xff, 0xfe, 0x07, 0xe0, 0x7f, 0x81, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xff, + 0xff, 0xfc, 0x07, 0xe0, 0xff, 0xc1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xff, 0x8f, 0xfc, 0x03, + 0xe0, 0xff, 0xc1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xff, 0x07, 0xfc, 0x07, 0xe0, 0xff, 0xc1, + 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xfe, 0x00, 0x7f, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xff, 0x8f, + 0xf0, 0xff, 0x0f, 0x9c, 0x00, 0x3f, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x07, + 0xfc, 0x00, 0x7f, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x07, 0xfc, 0x00, 0x7f, + 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x0f, 0xfc, 0x00, 0x7f, 0xff, 0xe0, 0xff, + 0xc1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x0f, 0xfc, 0x00, 0x7f, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xff, + 0x8f, 0xf0, 0xff, 0x1f, 0xfe, 0x00, 0x7f, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xff, 0x8f, 0xf0, 0xff, + 0x1f, 0xfc, 0x00, 0x3f, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xfc, 0x00, + 0x3f, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xf0, 0x00, 0x1f, 0xff, 0xe0, + 0x7f, 0x81, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x0f, 0xf0, 0x00, 0x1f, 0xff, 0xe0, 0xff, 0x81, 0xff, + 0xff, 0x8f, 0xf0, 0xff, 0x07, 0xe0, 0x00, 0x3f, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xff, 0x8f, 0xf0, + 0xff, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xe1, 0xff, 0xe1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x00, 0x1f, + 0x80, 0x3f, 0xff, 0xe1, 0xff, 0xe1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x00, 0x3f, 0xc0, 0x1f, 0xff, + 0xe1, 0xff, 0xe1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x00, 0x7f, 0xc0, 0x0f, 0xff, 0xe1, 0xff, 0xe1, + 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x00, 0xff, 0xc0, 0x07, 0xff, 0xe1, 0xff, 0xe1, 0xff, 0xff, 0x8f, + 0xf0, 0xff, 0x01, 0xff, 0xc0, 0x03, 0x8f, 0xc0, 0xc1, 0x81, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x03, + 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x07, 0xff, 0xc0, 0xff, + 0x80, 0x00, 0x00, 0x01, 0xff, 0xff, 0x0f, 0xf0, 0xff, 0x0f, 0xff, 0xc1, 0xff, 0x80, 0x00, 0x00, + 0x01, 0xf3, 0x8e, 0x0f, 0xf0, 0xff, 0x0f, 0xff, 0xc3, 0xff, 0xc0, 0x00, 0x00, 0x01, 0xff, 0x9c, + 0x0f, 0xf0, 0xff, 0x0f, 0xff, 0xc3, 0xff, 0xc0, 0xff, 0xfc, 0x01, 0xff, 0xfe, 0x0f, 0xf0, 0xff, + 0x0f, 0xff, 0xc3, 0xff, 0xc1, 0xff, 0xfe, 0x01, 0xff, 0xff, 0x0f, 0xf0, 0xff, 0x07, 0xff, 0xc3, + 0xff, 0xc3, 0xff, 0xff, 0x01, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x0f, 0xff, 0xc3, 0xff, 0xc3, 0xff, + 0xff, 0x01, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x0f, 0xff, 0xc3, 0xff, 0xc3, 0xff, 0xff, 0x01, 0xff, + 0xff, 0x8f, 0xf0, 0xff, 0x0f, 0xff, 0xc3, 0xff, 0xc3, 0xff, 0xff, 0x01, 0xff, 0xff, 0x8f, 0xf0, + 0xff, 0x0f, 0xff, 0xc3, 0xff, 0xc3, 0xff, 0xff, 0x01, 0xff, 0xff, 0x0f, 0xf0, 0xff, 0x0f, 0xff, + 0xc3, 0xff, 0xc3, 0xff, 0xff, 0x00, 0xff, 0xff, 0x0f, 0xf0, 0xff, 0x0f, 0xff, 0xc3, 0xff, 0xc3, + 0xff, 0xff, 0x00, 0xff, 0xff, 0x0f, 0xf0, 0xff, 0x07, 0xff, 0xe3, 0xff, 0xc3, 0xff, 0xff, 0x00, + 0x3f, 0xfe, 0x0f, 0xf0, 0xff, 0x07, 0xff, 0xf3, 0xff, 0xc1, 0xef, 0xfe, 0x00, 0x3f, 0xfe, 0x0f, + 0xf0, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xc0, 0x82, 0x00, 0x00, 0x1f, 0xff, 0x0f, 0xf0, 0xff, 0x1f, + 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x07, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xff, 0xff, 0xff, + 0xc0, 0x00, 0x00, 0x00, 0x07, 0xff, 0x0f, 0xf0, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, + 0x00, 0x03, 0x8e, 0x0f, 0xf0, 0xff, 0x1f, 0xc1, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x03, 0x88, + 0x0f, 0xf0, 0xff, 0x0f, 0x80, 0xff, 0xff, 0xc1, 0xff, 0xfc, 0x00, 0xff, 0xfe, 0x0f, 0xf0, 0xff, + 0x06, 0x00, 0x73, 0xff, 0xc3, 0xff, 0xfe, 0x01, 0xff, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0x00, 0x03, + 0xff, 0xc3, 0xff, 0xff, 0x83, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x07, 0x0c, 0x73, 0xff, 0xc3, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x0f, 0xfe, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0x8f, 0xf0, + 0xff, 0x1f, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xff, + 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xc3, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0x8f, + 0xf0, 0xfe, 0x1f, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0x87, 0xf0, 0xfe, 0x1f, + 0xfe, 0xfb, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0xf0, 0xfc, 0x0f, 0x9e, 0x73, 0xff, + 0x81, 0xf9, 0xf7, 0xe7, 0x9c, 0xff, 0x03, 0xf0, 0xfc, 0x07, 0xfe, 0xfb, 0xc0, 0x00, 0xf0, 0x00, + 0x6f, 0xbe, 0xfe, 0x03, 0xf0, 0x3c, 0x07, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xfe, + 0x03, 0xc0, 0x1c, 0x0f, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0x03, 0x80, 0x1e, + 0x0f, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0x07, 0x80, 0x3f, 0x0f, 0xff, 0xff, + 0xe0, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0x0f, 0xc0, 0x1f, 0x8f, 0xff, 0xff, 0xe7, 0xff, 0xff, + 0xfe, 0x7f, 0xff, 0xff, 0x1f, 0x80, 0x1f, 0xc7, 0xff, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfe, 0x3f, 0x80, 0x07, 0xc3, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xfc, 0x3e, 0x00, + 0x07, 0xc1, 0xfe, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xcf, 0xf7, 0xf8, 0x3e, 0x00, 0x01, 0x00, 0xfc, + 0x7e, 0x7f, 0xff, 0xff, 0xff, 0xe7, 0xe3, 0xf0, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, + 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xe0, + 0x00, 0x00, 0x00, 0x00}; \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/compiled/assets_icons.h b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/compiled/assets_icons.h new file mode 100644 index 000000000..3521728de --- /dev/null +++ b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/compiled/assets_icons.h @@ -0,0 +1,114 @@ +#pragma once +#include + +#ifndef _sprites_h +#define _sprites_h + +#define bmp_font_width 24 // in bytes +#define bmp_font_height 6 +#define bmp_font_width_pxs 192 +#define bmp_font_height_pxs 48 +#define CHAR_MAP " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.,-_(){}[]#" +#define CHAR_WIDTH 4 +#define CHAR_HEIGHT 6 + +#define BMP_GUN_WIDTH 32 +#define BMP_GUN_HEIGHT 32 + +#define BMP_FIRE_WIDTH 24 +#define BMP_FIRE_HEIGHT 20 + +#define BMP_IMP_WIDTH 32 +#define BMP_IMP_HEIGHT 32 +#define BMP_IMP_COUNT 5 + +#define BMP_FIREBALL_WIDTH 16 +#define BMP_FIREBALL_HEIGHT 16 + +#define BMP_DOOR_WIDTH 100 +#define BMP_DOOR_HEIGHT 100 + +#define BMP_ITEMS_WIDTH 16 +#define BMP_ITEMS_HEIGHT 16 +#define BMP_ITEMS_COUNT 2 + +#define BMP_LOGO_WIDTH 128 +#define BMP_LOGO_HEIGHT 64 + +#define GRADIENT_WIDTH 2 +#define GRADIENT_HEIGHT 8 +#define GRADIENT_COUNT 8 +#define GRADIENT_WHITE 7 +#define GRADIENT_BLACK 0 + +// Inverted icons +extern const Icon I_fire_inv; +extern const Icon I_gun_inv; +extern const Icon I_gun_mask_inv; +extern const Icon I_logo_inv; + +// Fonts +extern const uint8_t zero[]; +extern const uint8_t one[]; +extern const uint8_t two[]; +extern const uint8_t three[]; +extern const uint8_t four[]; +extern const uint8_t five[]; +extern const uint8_t six[]; +extern const uint8_t seven[]; +extern const uint8_t eight[]; +extern const uint8_t nine[]; +extern const uint8_t A[]; +extern const uint8_t B[]; +extern const uint8_t C[]; +extern const uint8_t D[]; +extern const uint8_t E[]; +extern const uint8_t F[]; +extern const uint8_t G[]; +extern const uint8_t H[]; +extern const uint8_t I[]; +extern const uint8_t J[]; +extern const uint8_t K[]; +extern const uint8_t L[]; +extern const uint8_t M[]; +extern const uint8_t N[]; +extern const uint8_t O[]; +extern const uint8_t P[]; +extern const uint8_t Q[]; +extern const uint8_t R[]; +extern const uint8_t S[]; +extern const uint8_t T[]; +extern const uint8_t U[]; +extern const uint8_t V[]; +extern const uint8_t W[]; +extern const uint8_t X[]; +extern const uint8_t Y[]; +extern const uint8_t Z[]; +extern const uint8_t dot[]; +extern const uint8_t comma[]; +extern const uint8_t dash[]; +extern const uint8_t underscore[]; +extern const uint8_t bracket_open[]; +extern const uint8_t bracket_close[]; +extern const uint8_t cross_left[]; +extern const uint8_t cross_right[]; +extern const uint8_t pacman_left[]; +extern const uint8_t pacman_right[]; +extern const uint8_t box[]; +extern const uint8_t* char_arr[48]; +extern const uint8_t gradient[]; +//extern const uint8_t gun[] +//extern const uint8_t gun_mask[] +extern const uint8_t gun[]; +extern const uint8_t gun_mask[]; + +extern const uint8_t imp_inv[]; +extern const uint8_t imp_mask_inv[]; +extern const uint8_t fireball[]; +extern const uint8_t fireball_mask[]; +extern const uint8_t item[]; +extern const uint8_t item_mask[]; + +extern const uint8_t door[]; + +#endif diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/constants.h b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/constants.h new file mode 100644 index 000000000..4e0f10118 --- /dev/null +++ b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/constants.h @@ -0,0 +1,91 @@ +#ifndef _constants_h +#define _constants_h +#define PB_CONSTEXPR constexpr + +#define PI 3.14159265358979323846 + +// Key pinout +#define USE_INPUT_PULLUP +#define K_LEFT 6 +#define K_RIGHT 7 +#define K_UP 8 +#define K_DOWN 3 +#define K_FIRE 10 + +// SNES Controller +// uncomment following line to enable snes controller support +// #define SNES_CONTROLLER +const uint8_t DATA_CLOCK = 11; +const uint8_t DATA_LATCH = 12; +const uint8_t DATA_SERIAL = 13; + +// Sound +const uint8_t SOUND_PIN = 9; // do not change, belongs to used timer + +// GFX settings +#define OPTIMIZE_SSD1306 // Optimizations for SSD1366 displays + +#define FRAME_TIME 66.666666 // Desired time per frame in ms (66.666666 is ~15 fps) +#define RES_DIVIDER 2 + +/* Higher values will result in lower horizontal resolution when rasterize and lower process and memory usage + Lower will require more process and memory, but looks nicer + */ +#define Z_RES_DIVIDER 2 // Zbuffer resolution divider. We sacrifice resolution to save memory +#define DISTANCE_MULTIPLIER 20 + +/* Distances are stored as uint8_t, multiplying the distance we can obtain more precision taking care + of keep numbers inside the type range. Max is 256 / MAX_RENDER_DEPTH + */ + +#define MAX_RENDER_DEPTH 12 +#define MAX_SPRITE_DEPTH 8 + +#define ZBUFFER_SIZE SCREEN_WIDTH / Z_RES_DIVIDER + +// Level +#define LEVEL_WIDTH_BASE 6 +#define LEVEL_WIDTH (1 << LEVEL_WIDTH_BASE) +#define LEVEL_HEIGHT 57 +#define LEVEL_SIZE LEVEL_WIDTH / 2 * LEVEL_HEIGHT + +// scenes +#define INTRO 0 +#define GAME_PLAY 1 + +// Game +#define GUN_TARGET_POS 18 +#define GUN_SHOT_POS GUN_TARGET_POS + 4 + +#define ROT_SPEED .12 +#define MOV_SPEED .2 +#define MOV_SPEED_INV 5 // 1 / MOV_SPEED + +#define JOGGING_SPEED .005 +#define ENEMY_SPEED .02 +#define FIREBALL_SPEED .2 +#define FIREBALL_ANGLES 45 // Num of angles per PI + +#define MAX_ENTITIES 10 // Max num of active entities +#define MAX_STATIC_ENTITIES 28 // Max num of entities in sleep mode + +#define MAX_ENTITY_DISTANCE 200 // * DISTANCE_MULTIPLIER +#define MAX_ENEMY_VIEW 80 // * DISTANCE_MULTIPLIER +#define ITEM_COLLIDER_DIST 6 // * DISTANCE_MULTIPLIER +#define ENEMY_COLLIDER_DIST 4 // * DISTANCE_MULTIPLIER +#define FIREBALL_COLLIDER_DIST 2 // * DISTANCE_MULTIPLIER +#define ENEMY_MELEE_DIST 6 // * DISTANCE_MULTIPLIER +#define WALL_COLLIDER_DIST .2 + +#define ENEMY_MELEE_DAMAGE 8 +#define ENEMY_FIREBALL_DAMAGE 20 +#define GUN_MAX_DAMAGE 20 + +// display +const uint8_t SCREEN_WIDTH = 128; +const uint8_t SCREEN_HEIGHT = 64; +const uint8_t HALF_WIDTH = SCREEN_WIDTH / 2; +const uint8_t RENDER_HEIGHT = 56; // raycaster working height (the rest is for the hud) +const uint8_t HALF_HEIGHT = SCREEN_HEIGHT / 2; + +#endif diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/display.h b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/display.h new file mode 100644 index 000000000..037c9d10a --- /dev/null +++ b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/display.h @@ -0,0 +1,288 @@ +#include +#include +#include +#include +#include "constants.h" +#include "compiled/assets_icons.h" + +#define CHECK_BIT(var, pos) ((var) & (1 << (pos))) + +static const uint8_t bit_mask[8] = {128, 64, 32, 16, 8, 4, 2, 1}; + +#define pgm_read_byte(addr) (*(const unsigned char*)(addr)) +#define read_bit(b, n) b& pgm_read_byte(bit_mask + n) ? 1 : 0 +//#define read_bit(byte, index) (((unsigned)(byte) >> (index)) & 1) + +void drawVLine(uint8_t x, int8_t start_y, int8_t end_y, uint8_t intensity, Canvas* const canvas); +void drawPixel(int8_t x, int8_t y, bool color, bool raycasterViewport, Canvas* const canvas); +void drawSprite( + int8_t x, + int8_t y, + const uint8_t* bitmap, + const uint8_t* bitmap_mask, + int16_t w, + int16_t h, + uint8_t sprite, + double distance, + Canvas* const canvas); +void drawBitmap( + int16_t x, + int16_t y, + const Icon* i, + int16_t w, + int16_t h, + uint16_t color, + Canvas* const canvas); +void drawTextSpace(int8_t x, int8_t y, char* txt, uint8_t space, Canvas* const canvas); +void drawChar(int8_t x, int8_t y, char ch, Canvas* const canvas); +void clearRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, Canvas* const canvas); +void drawGun( + int16_t x, + int16_t y, + const uint8_t* bitmap, + int16_t w, + int16_t h, + uint16_t color, + Canvas* const canvas); +void drawRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, Canvas* const canvas); +void drawText(uint8_t x, uint8_t y, uint8_t num, Canvas* const canvas); +void fadeScreen(uint8_t intensity, bool color, Canvas* const canvas); +bool getGradientPixel(uint8_t x, uint8_t y, uint8_t i); +double getActualFps(); +void fps(); +void setupDisplay(Canvas* canvas); +uint8_t reverse_bits(uint8_t num); + +// FPS control +double delta = 1; +uint32_t lastFrameTime = 0; +uint8_t zbuffer[128]; /// 128 = screen width & REMOVE WHEN DISPLAY.H IMPLEMENTED +uint8_t* display_buf = NULL; + +void drawGun( + int16_t x, + int16_t y, + const uint8_t* bitmap, + int16_t w, + int16_t h, + uint16_t color, + Canvas* const canvas) { + int16_t byteWidth = (w + 7) / 8; + uint8_t byte = 0; + for(int16_t j = 0; j < h; j++, y++) { + for(int16_t i = 0; i < w; i++) { + if(i & 7) + byte <<= 1; + else + byte = pgm_read_byte(&bitmap[j * byteWidth + i / 8]); + if(byte & 0x80) drawPixel(x + i, y, color, false, canvas); + } + } +} + +void drawVLine(uint8_t x, int8_t start_y, int8_t end_y, uint8_t intensity, Canvas* const canvas) { + UNUSED(intensity); + uint8_t dots = end_y - start_y; + for(int i = 0; i < dots; i++) { + canvas_draw_dot(canvas, x, start_y + i); + } +} + +void setupDisplay(Canvas* canvas) { + memset(zbuffer, 0xff, 128); + //display_buf = (uint8_t*)canvas_get_buffer(canvas); + display_buf = u8g2_GetBufferPtr(&canvas->fb); +} + +void drawBitmap( + int16_t x, + int16_t y, + const Icon* i, + int16_t w, + int16_t h, + uint16_t color, + Canvas* const canvas) { + UNUSED(color); + UNUSED(w); + UNUSED(h); + canvas_draw_icon(canvas, x, y, i); +} + +void drawText(uint8_t x, uint8_t y, uint8_t num, Canvas* const canvas) { + FuriString* text; + text = furi_string_alloc(); + furi_string_printf(text, "%d", num); + char buf[4]; + strcpy(buf, (char*)furi_string_get_cstr(text)); + drawTextSpace(x, y, buf, 1, canvas); + furi_string_free(text); +} + +void drawTextSpace(int8_t x, int8_t y, char* txt, uint8_t space, Canvas* const canvas) { + uint8_t pos = x; + uint8_t i = 0; + char ch; + while((ch = txt[i]) != '\0') { + drawChar(pos, y, ch, canvas); + i++; + pos += CHAR_WIDTH + space; + + // shortcut on end of screen + if(pos > SCREEN_WIDTH) return; + } +} + +// Custom drawBitmap method with scale support, mask, zindex and pattern filling +void drawSprite( + int8_t x, + int8_t y, + const uint8_t* bitmap, + const uint8_t* bitmap_mask, + int16_t w, + int16_t h, + uint8_t sprite, + double distance, + Canvas* const canvas) { + uint8_t tw = (double)w / distance; + uint8_t th = (double)h / distance; + uint8_t byte_width = w / 8; + uint8_t pixel_size = fmax(1, (double)1.0 / (double)distance); + uint16_t sprite_offset = byte_width * h * sprite; + + bool pixel; + bool maskPixel; + + // Don't draw the whole sprite if the anchor is hidden by z buffer + // Not checked per pixel for performance reasons + if(zbuffer[(int)(fmin(fmax(x, 0), ZBUFFER_SIZE - 1) / Z_RES_DIVIDER)] < + distance * DISTANCE_MULTIPLIER) { + return; + } + + for(uint8_t ty = 0; ty < th; ty += pixel_size) { + // Don't draw out of screen + if(y + ty < 0 || y + ty >= RENDER_HEIGHT) { + continue; + } + + uint8_t sy = ty * distance; // The y from the sprite + + for(uint8_t tx = 0; tx < tw; tx += pixel_size) { + uint8_t sx = tx * distance; // The x from the sprite + uint16_t byte_offset = sprite_offset + sy * byte_width + sx / 8; + + // Don't draw out of screen + if(x + tx < 0 || x + tx >= SCREEN_WIDTH) { + continue; + } + + maskPixel = read_bit(pgm_read_byte(bitmap_mask + byte_offset), sx % 8); + + if(maskPixel) { + pixel = read_bit(pgm_read_byte(bitmap + byte_offset), sx % 8); + for(uint8_t ox = 0; ox < pixel_size; ox++) { + for(uint8_t oy = 0; oy < pixel_size; oy++) { + if(bitmap == imp_inv) + drawPixel(x + tx + ox, y + ty + oy, 1, true, canvas); + else + drawPixel(x + tx + ox, y + ty + oy, pixel, true, canvas); + } + } + } + } + } +} + +void drawPixel(int8_t x, int8_t y, bool color, bool raycasterViewport, Canvas* const canvas) { + if(x < 0 || x >= SCREEN_WIDTH || y < 0 || + y >= (raycasterViewport ? RENDER_HEIGHT : SCREEN_HEIGHT)) { + return; + } + if(color) + canvas_draw_dot(canvas, x, y); + else { + canvas_invert_color(canvas); + canvas_draw_dot(canvas, x, y); + canvas_invert_color(canvas); + } +} + +void drawChar(int8_t x, int8_t y, char ch, Canvas* const canvas) { + uint8_t lsb; + uint8_t c = 0; + while(CHAR_MAP[c] != ch && CHAR_MAP[c] != '\0') c++; + for(uint8_t i = 0; i < 6; i++) { + //lsb = (char_arr[c][i] >> 4); + lsb = reverse_bits(char_arr[c][i]); + for(uint8_t n = 0; n < 4; n++) { + if(CHECK_BIT(lsb, n)) { + drawPixel(x + n, y + i, true, false, canvas); + } + } + } +} + +void clearRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, Canvas* const canvas) { + canvas_invert_color(canvas); + + for(int i = 0; i < w; i++) { + for(int j = 0; j < h; j++) { + canvas_draw_dot(canvas, x + i, y + j); + } + } + + canvas_invert_color(canvas); +} + +void drawRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, Canvas* const canvas) { + for(int i = 0; i < w; i++) { + for(int j = 0; j < h; j++) { + canvas_draw_dot(canvas, x + i, y + j); + } + } +} + +bool getGradientPixel(uint8_t x, uint8_t y, uint8_t i) { + if(i == 0) return 0; + if(i >= GRADIENT_COUNT - 1) return 1; + + uint8_t index = + fmax(0, fmin(GRADIENT_COUNT - 1, i)) * GRADIENT_WIDTH * GRADIENT_HEIGHT // gradient index + + y * GRADIENT_WIDTH % (GRADIENT_WIDTH * GRADIENT_HEIGHT) // y byte offset + + x / GRADIENT_HEIGHT % GRADIENT_WIDTH; // x byte offset + //uint8_t *gradient_data = NULL; + //furi_hal_compress_icon_decode(icon_get_data(&I_gradient_inv), &gradient_data); + // return the bit based on x + return read_bit(pgm_read_byte(gradient + index), x % 8); +} + +void fadeScreen(uint8_t intensity, bool color, Canvas* const canvas) { + for(uint8_t x = 0; x < SCREEN_WIDTH; x++) { + for(uint8_t y = 0; y < SCREEN_HEIGHT; y++) { + if(getGradientPixel(x, y, intensity)) drawPixel(x, y, color, false, canvas); + } + } +} + +// Adds a delay to limit play to specified fps +// Calculates also delta to keep movement consistent in lower framerates +void fps() { + while(furi_get_tick() - lastFrameTime < FRAME_TIME) + ; + delta = (double)(furi_get_tick() - lastFrameTime) / (double)FRAME_TIME; + lastFrameTime = furi_get_tick(); +} + +double getActualFps() { + return 1000 / ((double)FRAME_TIME * (double)delta); +} + +uint8_t reverse_bits(uint8_t num) { + unsigned int NO_OF_BITS = sizeof(num) * 8; + uint8_t reverse_num = 0; + uint8_t i; + for(i = 0; i < NO_OF_BITS; i++) { + if((num & (1 << i))) reverse_num |= 1 << ((NO_OF_BITS - 1) - i); + } + return reverse_num; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/doom.c b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/doom.c new file mode 100644 index 000000000..7e64acf81 --- /dev/null +++ b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/doom.c @@ -0,0 +1,1101 @@ +#include +#include +#include +#include +#include +#include +#include "sound.h" +#include "display.h" +#include "compiled/assets_icons.h" +#include "constants.h" +#include "entities.h" +#include "types.h" +#include "level.h" +#include +#include + +#define SOUND + +// Useful macros +#define swap(a, b) \ + do { \ + typeof(a) temp = a; \ + a = b; \ + b = temp; \ + } while(0) +#define sign(a, b) (double)(a > b ? 1 : (b > a ? -1 : 0)) +#define pgm_read_byte(addr) (*(const unsigned char*)(addr)) + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} PluginEvent; + +typedef struct { + Player player; + Entity entity[MAX_ENTITIES]; + StaticEntity static_entity[MAX_STATIC_ENTITIES]; + uint8_t num_entities; + uint8_t num_static_entities; + + uint8_t scene; + uint8_t gun_pos; + double jogging; + double view_height; + bool init; + + bool up; + bool down; + bool left; + bool right; + bool fired; + bool gun_fired; + + double rot_speed; + double old_dir_x; + double old_plane_x; + NotificationApp* notify; +#ifdef SOUND + MusicPlayer* music_instance; + bool intro_sound; +#endif +} PluginState; + +static const NotificationSequence sequence_short_sound = { + &message_note_c5, + &message_delay_50, + &message_sound_off, + NULL, +}; +static const NotificationSequence sequence_long_sound = { + &message_note_c3, + &message_delay_100, + &message_sound_off, + NULL, +}; + +Coords translateIntoView(Coords* pos, PluginState* const plugin_state); +void updateHud(Canvas* const canvas, PluginState* const plugin_state); +// general + +bool invert_screen = false; +uint8_t flash_screen = 0; + +// game +// player and entities + +uint8_t getBlockAt(const uint8_t level[], uint8_t x, uint8_t y) { + if(x >= LEVEL_WIDTH || y >= LEVEL_HEIGHT) { + return E_FLOOR; + } + + // y is read in inverse order + return pgm_read_byte(level + (((LEVEL_HEIGHT - 1 - y) * LEVEL_WIDTH + x) / 2)) >> + (!(x % 2) * 4) // displace part of wanted bits + & 0b1111; // mask wanted bits +} + +// Finds the player in the map +void initializeLevel(const uint8_t level[], PluginState* const plugin_state) { + for(uint8_t y = LEVEL_HEIGHT - 1; y > 0; y--) { + for(uint8_t x = 0; x < LEVEL_WIDTH; x++) { + uint8_t block = getBlockAt(level, x, y); + + if(block == E_PLAYER) { + plugin_state->player = create_player(x, y); + return; + } + + // todo create other static entities + } + } +} + +bool isSpawned(UID uid, PluginState* const plugin_state) { + for(uint8_t i = 0; i < plugin_state->num_entities; i++) { + if(plugin_state->entity[i].uid == uid) return true; + } + + return false; +} + +bool isStatic(UID uid, PluginState* const plugin_state) { + for(uint8_t i = 0; i < plugin_state->num_static_entities; i++) { + if(plugin_state->static_entity[i].uid == uid) return true; + } + + return false; +} + +void spawnEntity(uint8_t type, uint8_t x, uint8_t y, PluginState* const plugin_state) { + // Limit the number of spawned entities + if(plugin_state->num_entities >= MAX_ENTITIES) { + return; + } + + // todo: read static entity status + + switch(type) { + case E_ENEMY: + plugin_state->entity[plugin_state->num_entities] = create_enemy(x, y); + plugin_state->num_entities++; + break; + + case E_KEY: + plugin_state->entity[plugin_state->num_entities] = create_key(x, y); + plugin_state->num_entities++; + break; + + case E_MEDIKIT: + plugin_state->entity[plugin_state->num_entities] = create_medikit(x, y); + plugin_state->num_entities++; + break; + } +} + +void spawnFireball(double x, double y, PluginState* const plugin_state) { + // Limit the number of spawned entities + if(plugin_state->num_entities >= MAX_ENTITIES) { + return; + } + + UID uid = create_uid(E_FIREBALL, x, y); + // Remove if already exists, don't throw anything. Not the best, but shouldn't happen too often + if(isSpawned(uid, plugin_state)) return; + + // Calculate direction. 32 angles + int16_t dir = + FIREBALL_ANGLES + atan2(y - plugin_state->player.pos.y, x - plugin_state->player.pos.x) / + (double)PI * FIREBALL_ANGLES; + if(dir < 0) dir += FIREBALL_ANGLES * 2; + plugin_state->entity[plugin_state->num_entities] = create_fireball(x, y, dir); + plugin_state->num_entities++; +} + +void removeEntity(UID uid, PluginState* const plugin_state) { + uint8_t i = 0; + bool found = false; + + while(i < plugin_state->num_entities) { + if(!found && plugin_state->entity[i].uid == uid) { + // todo: doze it + found = true; + plugin_state->num_entities--; + } + + // displace entities + if(found) { + plugin_state->entity[i] = plugin_state->entity[i + 1]; + } + + i++; + } +} + +void removeStaticEntity(UID uid, PluginState* const plugin_state) { + uint8_t i = 0; + bool found = false; + + while(i < plugin_state->num_static_entities) { + if(!found && plugin_state->static_entity[i].uid == uid) { + found = true; + plugin_state->num_static_entities--; + } + + // displace entities + if(found) { + plugin_state->static_entity[i] = plugin_state->static_entity[i + 1]; + } + + i++; + } +} + +UID detectCollision( + const uint8_t level[], + Coords* pos, + double relative_x, + double relative_y, + bool only_walls, + PluginState* const plugin_state) { + // Wall collision + uint8_t round_x = (int)pos->x + (int)relative_x; + uint8_t round_y = (int)pos->y + (int)relative_y; + uint8_t block = getBlockAt(level, round_x, round_y); + + if(block == E_WALL) { + //playSound(hit_wall_snd, HIT_WALL_SND_LEN); + return create_uid(block, round_x, round_y); + } + + if(only_walls) { + return UID_null; + } + + // Entity collision + for(uint8_t i = 0; i < plugin_state->num_entities; i++) { + // Don't collide with itself + if(&(plugin_state->entity[i].pos) == pos) { + continue; + } + + uint8_t type = uid_get_type(plugin_state->entity[i].uid); + + // Only ALIVE enemy collision + if(type != E_ENEMY || plugin_state->entity[i].state == S_DEAD || + plugin_state->entity[i].state == S_HIDDEN) { + continue; + } + + Coords new_coords = { + plugin_state->entity[i].pos.x - relative_x, + plugin_state->entity[i].pos.y - relative_y}; + uint8_t distance = coords_distance(pos, &new_coords); + + // Check distance and if it's getting closer + if(distance < ENEMY_COLLIDER_DIST && distance < plugin_state->entity[i].distance) { + return plugin_state->entity[i].uid; + } + } + + return UID_null; +} + +// Shoot +void fire(PluginState* const plugin_state) { + //playSound(shoot_snd, SHOOT_SND_LEN); + + for(uint8_t i = 0; i < plugin_state->num_entities; i++) { + // Shoot only ALIVE enemies + if(uid_get_type(plugin_state->entity[i].uid) != E_ENEMY || + plugin_state->entity[i].state == S_DEAD || plugin_state->entity[i].state == S_HIDDEN) { + continue; + } + + Coords transform = translateIntoView(&(plugin_state->entity[i].pos), plugin_state); + if(fabs(transform.x) < 20 && transform.y > 0) { + uint8_t damage = (double)fmin( + GUN_MAX_DAMAGE, + GUN_MAX_DAMAGE / (fabs(transform.x) * plugin_state->entity[i].distance) / 5); + if(damage > 0) { + plugin_state->entity[i].health = fmax(0, plugin_state->entity[i].health - damage); + plugin_state->entity[i].state = S_HIT; + plugin_state->entity[i].timer = 4; + } + } + } +} + +UID updatePosition( + const uint8_t level[], + Coords* pos, + double relative_x, + double relative_y, + bool only_walls, + PluginState* const plugin_state) { + UID collide_x = detectCollision(level, pos, relative_x, 0, only_walls, plugin_state); + UID collide_y = detectCollision(level, pos, 0, relative_y, only_walls, plugin_state); + + if(!collide_x) pos->x += relative_x; + if(!collide_y) pos->y += relative_y; + + return collide_x || collide_y || UID_null; +} + +void updateEntities(const uint8_t level[], Canvas* const canvas, PluginState* const plugin_state) { + uint8_t i = 0; + while(i < plugin_state->num_entities) { + // update distance + plugin_state->entity[i].distance = + coords_distance(&(plugin_state->player.pos), &(plugin_state->entity[i].pos)); + + // Run the timer. Works with actual frames. + // Todo: use delta here. But needs double type and more memory + if(plugin_state->entity[i].timer > 0) plugin_state->entity[i].timer--; + + // too far away. put it in doze mode + if(plugin_state->entity[i].distance > MAX_ENTITY_DISTANCE) { + removeEntity(plugin_state->entity[i].uid, plugin_state); + // don't increase 'i', since current one has been removed + continue; + } + + // bypass render if hidden + if(plugin_state->entity[i].state == S_HIDDEN) { + i++; + continue; + } + + uint8_t type = uid_get_type(plugin_state->entity[i].uid); + + switch(type) { + case E_ENEMY: { + // Enemy "IA" + if(plugin_state->entity[i].health == 0) { + if(plugin_state->entity[i].state != S_DEAD) { + plugin_state->entity[i].state = S_DEAD; + plugin_state->entity[i].timer = 6; + } + } else if(plugin_state->entity[i].state == S_HIT) { + if(plugin_state->entity[i].timer == 0) { + // Back to alert state + plugin_state->entity[i].state = S_ALERT; + plugin_state->entity[i].timer = 40; // delay next fireball thrown + } + } else if(plugin_state->entity[i].state == S_FIRING) { + if(plugin_state->entity[i].timer == 0) { + // Back to alert state + plugin_state->entity[i].state = S_ALERT; + plugin_state->entity[i].timer = 40; // delay next fireball throwm + } + } else { + // ALERT STATE + if(plugin_state->entity[i].distance > ENEMY_MELEE_DIST && + plugin_state->entity[i].distance < MAX_ENEMY_VIEW) { + if(plugin_state->entity[i].state != S_ALERT) { + plugin_state->entity[i].state = S_ALERT; + plugin_state->entity[i].timer = 20; // used to throw fireballs + } else { + if(plugin_state->entity[i].timer == 0) { + // Throw a fireball + spawnFireball( + plugin_state->entity[i].pos.x, + plugin_state->entity[i].pos.y, + plugin_state); + plugin_state->entity[i].state = S_FIRING; + plugin_state->entity[i].timer = 6; + } else { + // move towards to the player. + updatePosition( + level, + &(plugin_state->entity[i].pos), + sign(plugin_state->player.pos.x, plugin_state->entity[i].pos.x) * + (double)ENEMY_SPEED * 1, // NOT SURE (delta) + sign(plugin_state->player.pos.y, plugin_state->entity[i].pos.y) * + (double)ENEMY_SPEED * 1, // NOT SURE (delta) + true, + plugin_state); + } + } + } else if(plugin_state->entity[i].distance <= ENEMY_MELEE_DIST) { + if(plugin_state->entity[i].state != S_MELEE) { + // Preparing the melee attack + plugin_state->entity[i].state = S_MELEE; + plugin_state->entity[i].timer = 10; + } else if(plugin_state->entity[i].timer == 0) { + // Melee attack + plugin_state->player.health = + fmax(0, plugin_state->player.health - ENEMY_MELEE_DAMAGE); + plugin_state->entity[i].timer = 14; + flash_screen = 1; + updateHud(canvas, plugin_state); + } + } else { + // stand + plugin_state->entity[i].state = S_STAND; + } + } + break; + } + + case E_FIREBALL: { + if(plugin_state->entity[i].distance < FIREBALL_COLLIDER_DIST) { + // Hit the player and disappear + plugin_state->player.health = + fmax(0, plugin_state->player.health - ENEMY_FIREBALL_DAMAGE); + flash_screen = 1; + updateHud(canvas, plugin_state); + removeEntity(plugin_state->entity[i].uid, plugin_state); + continue; // continue in the loop + } else { + // Move. Only collide with walls. + // Note: using health to store the angle of the movement + UID collided = updatePosition( + level, + &(plugin_state->entity[i].pos), + cos((double)plugin_state->entity[i].health / FIREBALL_ANGLES * (double)PI) * + (double)FIREBALL_SPEED, + sin((double)plugin_state->entity[i].health / FIREBALL_ANGLES * (double)PI) * + (double)FIREBALL_SPEED, + true, + plugin_state); + + if(collided) { + removeEntity(plugin_state->entity[i].uid, plugin_state); + continue; // continue in the entity check loop + } + } + break; + } + + case E_MEDIKIT: { + if(plugin_state->entity[i].distance < ITEM_COLLIDER_DIST) { + // pickup + notification_message(plugin_state->notify, &sequence_long_sound); + //playSound(medkit_snd, MEDKIT_SND_LEN); + plugin_state->entity[i].state = S_HIDDEN; + plugin_state->player.health = fmin(100, plugin_state->player.health + 50); + updateHud(canvas, plugin_state); + flash_screen = 1; + } + break; + } + + case E_KEY: { + if(plugin_state->entity[i].distance < ITEM_COLLIDER_DIST) { + // pickup + notification_message(plugin_state->notify, &sequence_long_sound); + //playSound(get_key_snd, GET_KEY_SND_LEN); + plugin_state->entity[i].state = S_HIDDEN; + plugin_state->player.keys++; + updateHud(canvas, plugin_state); + flash_screen = 1; + } + break; + } + } + + i++; + } +} + +// The map raycaster. Based on https://lodev.org/cgtutor/raycasting.html +void renderMap( + const uint8_t level[], + double view_height, + Canvas* const canvas, + PluginState* const plugin_state) { + UID last_uid = 0; // NOT SURE ? + + for(uint8_t x = 0; x < SCREEN_WIDTH; x += RES_DIVIDER) { + double camera_x = 2 * (double)x / SCREEN_WIDTH - 1; + double ray_x = plugin_state->player.dir.x + plugin_state->player.plane.x * camera_x; + double ray_y = plugin_state->player.dir.y + plugin_state->player.plane.y * camera_x; + uint8_t map_x = (uint8_t)plugin_state->player.pos.x; + uint8_t map_y = (uint8_t)plugin_state->player.pos.y; + Coords map_coords = {plugin_state->player.pos.x, plugin_state->player.pos.y}; + double delta_x = fabs(1 / ray_x); + double delta_y = fabs(1 / ray_y); + + int8_t step_x; + int8_t step_y; + double side_x; + double side_y; + + if(ray_x < 0) { + step_x = -1; + side_x = (plugin_state->player.pos.x - map_x) * delta_x; + } else { + step_x = 1; + side_x = (map_x + (double)1.0 - plugin_state->player.pos.x) * delta_x; + } + + if(ray_y < 0) { + step_y = -1; + side_y = (plugin_state->player.pos.y - map_y) * delta_y; + } else { + step_y = 1; + side_y = (map_y + (double)1.0 - plugin_state->player.pos.y) * delta_y; + } + + // Wall detection + uint8_t depth = 0; + bool hit = 0; + bool side; + while(!hit && depth < MAX_RENDER_DEPTH) { + if(side_x < side_y) { + side_x += delta_x; + map_x += step_x; + side = 0; + } else { + side_y += delta_y; + map_y += step_y; + side = 1; + } + + uint8_t block = getBlockAt(level, map_x, map_y); + + if(block == E_WALL) { + hit = 1; + } else { + // Spawning entities here, as soon they are visible for the + // player. Not the best place, but would be a very performance + // cost scan for them in another loop + if(block == E_ENEMY || (block & 0b00001000) /* all collectable items */) { + // Check that it's close to the player + if(coords_distance(&(plugin_state->player.pos), &map_coords) < + MAX_ENTITY_DISTANCE) { + UID uid = create_uid(block, map_x, map_y); + if(last_uid != uid && !isSpawned(uid, plugin_state)) { + spawnEntity(block, map_x, map_y, plugin_state); + last_uid = uid; + } + } + } + } + + depth++; + } + + if(hit) { + double distance; + + if(side == 0) { + distance = + fmax(1, (map_x - plugin_state->player.pos.x + (1 - step_x) / 2) / ray_x); + } else { + distance = + fmax(1, (map_y - plugin_state->player.pos.y + (1 - step_y) / 2) / ray_y); + } + + // store zbuffer value for the column + zbuffer[x / Z_RES_DIVIDER] = fmin(distance * DISTANCE_MULTIPLIER, 255); + + // rendered line height + uint8_t line_height = RENDER_HEIGHT / distance; + + drawVLine( + x, + view_height / distance - line_height / 2 + RENDER_HEIGHT / 2, + view_height / distance + line_height / 2 + RENDER_HEIGHT / 2, + GRADIENT_COUNT - (int)distance / MAX_RENDER_DEPTH * GRADIENT_COUNT - side * 2, + canvas); + } + } +} + +// Sort entities from far to close +uint8_t sortEntities(PluginState* const plugin_state) { + uint8_t gap = plugin_state->num_entities; + bool swapped = false; + while(gap > 1 || swapped) { + //shrink factor 1.3 + gap = (gap * 10) / 13; + if(gap == 9 || gap == 10) gap = 11; + if(gap < 1) gap = 1; + swapped = false; + for(uint8_t i = 0; i < plugin_state->num_entities - gap; i++) { + uint8_t j = i + gap; + if(plugin_state->entity[i].distance < plugin_state->entity[j].distance) { + swap(plugin_state->entity[i], plugin_state->entity[j]); + swapped = true; + } + } + } + return swapped; +} + +Coords translateIntoView(Coords* pos, PluginState* const plugin_state) { + //translate sprite position to relative to camera + double sprite_x = pos->x - plugin_state->player.pos.x; + double sprite_y = pos->y - plugin_state->player.pos.y; + + //required for correct matrix multiplication + double inv_det = + ((double)1.0 / + ((double)plugin_state->player.plane.x * (double)plugin_state->player.dir.y - + (double)plugin_state->player.dir.x * (double)plugin_state->player.plane.y)); + double transform_x = + inv_det * (plugin_state->player.dir.y * sprite_x - plugin_state->player.dir.x * sprite_y); + double transform_y = inv_det * (-plugin_state->player.plane.y * sprite_x + + plugin_state->player.plane.x * sprite_y); // Z in screen + Coords res = {transform_x, transform_y}; + return res; +} + +void renderEntities(double view_height, Canvas* const canvas, PluginState* const plugin_state) { + sortEntities(plugin_state); + + for(uint8_t i = 0; i < plugin_state->num_entities; i++) { + if(plugin_state->entity[i].state == S_HIDDEN) continue; + + Coords transform = translateIntoView(&(plugin_state->entity[i].pos), plugin_state); + + // don´t render if behind the player or too far away + if(transform.y <= (double)0.1 || transform.y > MAX_SPRITE_DEPTH) { + continue; + } + + int16_t sprite_screen_x = HALF_WIDTH * ((double)1.0 + transform.x / transform.y); + int8_t sprite_screen_y = RENDER_HEIGHT / 2 + view_height / transform.y; + uint8_t type = uid_get_type(plugin_state->entity[i].uid); + + // don´t try to render if outside of screen + // doing this pre-shortcut due int16 -> int8 conversion makes out-of-screen + // values fit into the screen space + if(sprite_screen_x < -HALF_WIDTH || sprite_screen_x > SCREEN_WIDTH + HALF_WIDTH) { + continue; + } + + switch(type) { + case E_ENEMY: { + uint8_t sprite; + if(plugin_state->entity[i].state == S_ALERT) { + // walking + sprite = ((int)furi_get_tick() / 500) % 2; + } else if(plugin_state->entity[i].state == S_FIRING) { + // fireball + sprite = 2; + } else if(plugin_state->entity[i].state == S_HIT) { + // hit + sprite = 3; + } else if(plugin_state->entity[i].state == S_MELEE) { + // melee atack + sprite = plugin_state->entity[i].timer > 10 ? 2 : 1; + } else if(plugin_state->entity[i].state == S_DEAD) { + // dying + sprite = plugin_state->entity[i].timer > 0 ? 3 : 4; + } else { + // stand + sprite = 0; + } + + drawSprite( + sprite_screen_x - BMP_IMP_WIDTH * (double).5 / transform.y, + sprite_screen_y - 8 / transform.y, + imp_inv, + imp_mask_inv, + BMP_IMP_WIDTH, + BMP_IMP_HEIGHT, + sprite, + transform.y, + canvas); + break; + } + + case E_FIREBALL: { + drawSprite( + sprite_screen_x - BMP_FIREBALL_WIDTH / 2 / transform.y, + sprite_screen_y - BMP_FIREBALL_HEIGHT / 2 / transform.y, + fireball, + fireball_mask, + BMP_FIREBALL_WIDTH, + BMP_FIREBALL_HEIGHT, + 0, + transform.y, + canvas); + break; + } + + case E_MEDIKIT: { + drawSprite( + sprite_screen_x - BMP_ITEMS_WIDTH / 2 / transform.y, + sprite_screen_y + 5 / transform.y, + item, + item_mask, + BMP_ITEMS_WIDTH, + BMP_ITEMS_HEIGHT, + 0, + transform.y, + canvas); + break; + } + + case E_KEY: { + drawSprite( + sprite_screen_x - BMP_ITEMS_WIDTH / 2 / transform.y, + sprite_screen_y + 5 / transform.y, + item, + item_mask, + BMP_ITEMS_WIDTH, + BMP_ITEMS_HEIGHT, + 1, + transform.y, + canvas); + break; + } + } + } +} + +void renderGun(uint8_t gun_pos, double amount_jogging, Canvas* const canvas) { + // jogging + char x = 48 + sin((double)furi_get_tick() * (double)JOGGING_SPEED) * 10 * amount_jogging; + char y = RENDER_HEIGHT - gun_pos + + fabs(cos((double)furi_get_tick() * (double)JOGGING_SPEED)) * 8 * amount_jogging; + + if(gun_pos > GUN_SHOT_POS - 2) { + // Gun fire + drawBitmap(x + 6, y - 11, &I_fire_inv, BMP_FIRE_WIDTH, BMP_FIRE_HEIGHT, 1, canvas); + } + + // Don't draw over the hud! + uint8_t clip_height = fmax(0, fmin(y + BMP_GUN_HEIGHT, RENDER_HEIGHT) - y); + + // Draw the gun (black mask + actual sprite). + drawBitmap(x, y, &I_gun_mask_inv, BMP_GUN_WIDTH, clip_height, 0, canvas); + drawBitmap(x, y, &I_gun_inv, BMP_GUN_WIDTH, clip_height, 1, canvas); + //drawGun(x,y,gun_mask, BMP_GUN_WIDTH, clip_height, 0, canvas); + //drawGun(x,y,gun, BMP_GUN_WIDTH, clip_height, 1, canvas); +} + +// Only needed first time +void renderHud(Canvas* const canvas, PluginState* plugin_state) { + drawTextSpace(2, 58, "{}", 0, canvas); // Health symbol + drawTextSpace(40, 58, "[]", 0, canvas); // Keys symbol + updateHud(canvas, plugin_state); +} + +// Render values for the HUD +void updateHud(Canvas* const canvas, PluginState* plugin_state) { + clearRect(12, 58, 15, 6, canvas); + clearRect(50, 58, 15, 6, canvas); + drawText(12, 58, plugin_state->player.health, canvas); + drawText(50, 58, plugin_state->player.keys, canvas); +} + +// Debug stats +void renderStats(Canvas* const canvas, PluginState* plugin_state) { + clearRect(58, 58, 70, 6, canvas); + drawText(114, 58, (int)getActualFps(), canvas); + drawText(82, 58, plugin_state->num_entities, canvas); + // drawText(94, 58, freeMemory()); +} + +// Intro screen +void loopIntro(Canvas* const canvas) { + canvas_draw_icon(canvas, 0, 0, &I_logo_inv); + //drawTextSpace(SCREEN_WIDTH / 2 - 25, SCREEN_HEIGHT * .8, "PRESS FIRE", 1, canvas); +} + +static void render_callback(Canvas* const canvas, void* ctx) { + PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25); + if(plugin_state == NULL) { + return; + } + if(plugin_state->init) setupDisplay(canvas); + + canvas_set_font(canvas, FontPrimary); + + switch(plugin_state->scene) { + case INTRO: { + loopIntro(canvas); + break; + } + case GAME_PLAY: { + updateEntities(sto_level_1, canvas, plugin_state); + + renderGun(plugin_state->gun_pos, plugin_state->jogging, canvas); + renderMap(sto_level_1, plugin_state->view_height, canvas, plugin_state); + + renderEntities(plugin_state->view_height, canvas, plugin_state); + + renderHud(canvas, plugin_state); + updateHud(canvas, plugin_state); + renderStats(canvas, plugin_state); + break; + } + } + 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, 0); +} + +static void doom_state_init(PluginState* const plugin_state) { + plugin_state->notify = furi_record_open(RECORD_NOTIFICATION); + plugin_state->num_entities = 0; + plugin_state->num_static_entities = 0; + + plugin_state->scene = INTRO; + plugin_state->gun_pos = 0; + plugin_state->view_height = 0; + plugin_state->init = true; + + plugin_state->up = false; + plugin_state->down = false; + plugin_state->left = false; + plugin_state->right = false; + plugin_state->fired = false; + plugin_state->gun_fired = false; +#ifdef SOUND + + plugin_state->music_instance = malloc(sizeof(MusicPlayer)); + plugin_state->music_instance->model = malloc(sizeof(MusicPlayerModel)); + memset( + plugin_state->music_instance->model->duration_history, + 0xff, + MUSIC_PLAYER_SEMITONE_HISTORY_SIZE); + memset( + plugin_state->music_instance->model->semitone_history, + 0xff, + MUSIC_PLAYER_SEMITONE_HISTORY_SIZE); + plugin_state->music_instance->model->volume = 2; + + plugin_state->music_instance->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal); + //plugin_state->music_instance->view_port = view_port_alloc(); + + plugin_state->music_instance->worker = music_player_worker_alloc(); + //music_player_worker_set_volume(plugin_state->music_instance->worker, 0.75); + music_player_worker_set_volume( + plugin_state->music_instance->worker, + MUSIC_PLAYER_VOLUMES[plugin_state->music_instance->model->volume]); + plugin_state->intro_sound = true; + //init_sound(plugin_state->music_instance); +#endif +} + +static void doom_game_update_timer_callback(FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + PluginEvent event = {.type = EventTypeTick}; + furi_message_queue_put(event_queue, &event, 0); +} + +static void doom_game_tick(PluginState* const plugin_state) { + if(plugin_state->scene == GAME_PLAY) { + //fps(); + memset(display_buf, 0, SCREEN_WIDTH * (RENDER_HEIGHT / 8)); + //player is alive + if(plugin_state->player.health > 0) { + if(plugin_state->up) { + plugin_state->player.velocity += + ((double)MOV_SPEED - plugin_state->player.velocity) * (double).4; + plugin_state->jogging = fabs(plugin_state->player.velocity) * MOV_SPEED_INV; + //plugin_state->up = false; + } else if(plugin_state->down) { + plugin_state->player.velocity += + (-(double)MOV_SPEED - plugin_state->player.velocity) * (double).4; + plugin_state->jogging = fabs(plugin_state->player.velocity) * MOV_SPEED_INV; + //plugin_state->down = false; + } else { + plugin_state->player.velocity *= (double).5; + plugin_state->jogging = fabs(plugin_state->player.velocity) * MOV_SPEED_INV; + } + + if(plugin_state->right) { + plugin_state->rot_speed = (double)ROT_SPEED * delta; + plugin_state->old_dir_x = plugin_state->player.dir.x; + plugin_state->player.dir.x = + plugin_state->player.dir.x * cos(-(plugin_state->rot_speed)) - + plugin_state->player.dir.y * sin(-(plugin_state->rot_speed)); + plugin_state->player.dir.y = + plugin_state->old_dir_x * sin(-(plugin_state->rot_speed)) + + plugin_state->player.dir.y * cos(-(plugin_state->rot_speed)); + plugin_state->old_plane_x = plugin_state->player.plane.x; + plugin_state->player.plane.x = + plugin_state->player.plane.x * cos(-(plugin_state->rot_speed)) - + plugin_state->player.plane.y * sin(-(plugin_state->rot_speed)); + plugin_state->player.plane.y = + plugin_state->old_plane_x * sin(-(plugin_state->rot_speed)) + + plugin_state->player.plane.y * cos(-(plugin_state->rot_speed)); + + //plugin_state->right = false; + } else if(plugin_state->left) { + plugin_state->rot_speed = (double)ROT_SPEED * delta; + plugin_state->old_dir_x = plugin_state->player.dir.x; + plugin_state->player.dir.x = + plugin_state->player.dir.x * cos(plugin_state->rot_speed) - + plugin_state->player.dir.y * sin(plugin_state->rot_speed); + plugin_state->player.dir.y = + plugin_state->old_dir_x * sin(plugin_state->rot_speed) + + plugin_state->player.dir.y * cos(plugin_state->rot_speed); + plugin_state->old_plane_x = plugin_state->player.plane.x; + plugin_state->player.plane.x = + plugin_state->player.plane.x * cos(plugin_state->rot_speed) - + plugin_state->player.plane.y * sin(plugin_state->rot_speed); + plugin_state->player.plane.y = + plugin_state->old_plane_x * sin(plugin_state->rot_speed) + + plugin_state->player.plane.y * cos(plugin_state->rot_speed); + //plugin_state->left = false; + } + plugin_state->view_height = + fabs(sin((double)furi_get_tick() * (double)JOGGING_SPEED)) * 6 * + plugin_state->jogging; + + if(plugin_state->gun_pos > GUN_TARGET_POS) { + // Right after fire + plugin_state->gun_pos -= 1; + } else if(plugin_state->gun_pos < GUN_TARGET_POS) { + plugin_state->gun_pos += 2; + } else if(!plugin_state->gun_fired && plugin_state->fired) { + //furi_hal_speaker_start(20480 / 10, 0.45f); + /*#ifdef SOUND + music_player_worker_start(plugin_state->music_instance->worker); +#endif*/ + plugin_state->gun_pos = GUN_SHOT_POS; + plugin_state->gun_fired = true; + plugin_state->fired = false; + fire(plugin_state); + + } else if(plugin_state->gun_fired && !plugin_state->fired) { + //furi_hal_speaker_stop(); + plugin_state->gun_fired = false; + + notification_message(plugin_state->notify, &sequence_short_sound); + + /*#ifdef SOUND + music_player_worker_stop(plugin_state->music_instance->worker); +#endif*/ + } + } else { + // Player is dead + if(plugin_state->view_height > -10) plugin_state->view_height--; + if(plugin_state->gun_pos > 1) plugin_state->gun_pos -= 2; + } + + if(fabs(plugin_state->player.velocity) > (double)0.003) { + updatePosition( + sto_level_1, + &(plugin_state->player.pos), + plugin_state->player.dir.x * plugin_state->player.velocity * delta, + plugin_state->player.dir.y * plugin_state->player.velocity * delta, + false, + plugin_state); + } else { + plugin_state->player.velocity = 0; + } + } +} + +int32_t doom_app() { + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); + PluginState* plugin_state = malloc(sizeof(PluginState)); + doom_state_init(plugin_state); + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, plugin_state, sizeof(PluginState))) { + FURI_LOG_E("Doom_game", "cannot create mutex\r\n"); + furi_record_close(RECORD_NOTIFICATION); + furi_message_queue_free(event_queue); + free(plugin_state); + return 255; + } + FuriTimer* timer = + furi_timer_alloc(doom_game_update_timer_callback, FuriTimerTypePeriodic, event_queue); + furi_timer_start(timer, furi_kernel_get_tick_frequency() / 12); + // 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); + + ////////////////////////////////// + if(display_buf != NULL) plugin_state->init = false; + + PluginEvent event; +#ifdef SOUND + music_player_worker_load_rtttl_from_string(plugin_state->music_instance->worker, dsintro); + music_player_worker_start(plugin_state->music_instance->worker); +#endif + 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); +#ifdef SOUND + furi_check( + furi_mutex_acquire(plugin_state->music_instance->model_mutex, FuriWaitForever) == + FuriStatusOk); +#endif + if(event_status == FuriStatusOk) { + // press events + if(event.type == EventTypeKey) { + if(event.input.key == InputKeyBack) { + processing = false; +#ifdef SOUND + if(plugin_state->intro_sound) { + furi_mutex_release(plugin_state->music_instance->model_mutex); + music_player_worker_stop(plugin_state->music_instance->worker); + } +#endif + } + + if(event.input.type == InputTypePress) { + if(plugin_state->scene == INTRO && event.input.key == InputKeyOk) { + plugin_state->scene = GAME_PLAY; + initializeLevel(sto_level_1, plugin_state); +#ifdef SOUND + furi_mutex_release(plugin_state->music_instance->model_mutex); + music_player_worker_stop(plugin_state->music_instance->worker); + plugin_state->intro_sound = false; +#endif + goto skipintro; + } + + //While playing game + if(plugin_state->scene == GAME_PLAY) { + // If the player is alive + if(plugin_state->player.health > 0) { + //Player speed + if(event.input.key == InputKeyUp) { + plugin_state->up = true; + } else if(event.input.key == InputKeyDown) { + plugin_state->down = true; + } + // Player rotation + if(event.input.key == InputKeyRight) { + plugin_state->right = true; + } else if(event.input.key == InputKeyLeft) { + plugin_state->left = true; + } + if(event.input.key == InputKeyOk) { + /*#ifdef SOUND + music_player_worker_load_rtttl_from_string(plugin_state->music_instance->worker, dspistol); +#endif*/ + if(plugin_state->fired) { + plugin_state->fired = false; + } else { + plugin_state->fired = true; + } + } + } else { + // Player is dead + if(event.input.key == InputKeyOk) plugin_state->scene = INTRO; + } + } + } + if(event.input.type == InputTypeRelease) { + if(plugin_state->player.health > 0) { + //Player speed + if(event.input.key == InputKeyUp) { + plugin_state->up = false; + } else if(event.input.key == InputKeyDown) { + plugin_state->down = false; + } + // Player rotation + if(event.input.key == InputKeyRight) { + plugin_state->right = false; + } else if(event.input.key == InputKeyLeft) { + plugin_state->left = false; + } + } + } + } + + skipintro: + if(event.type == EventTypeTick) { + doom_game_tick(plugin_state); + } + } +#ifdef SOUND + furi_mutex_release(plugin_state->music_instance->model_mutex); +#endif + view_port_update(view_port); + release_mutex(&state_mutex, plugin_state); + } +#ifdef SOUND + music_player_worker_free(plugin_state->music_instance->worker); + furi_mutex_free(plugin_state->music_instance->model_mutex); + free(plugin_state->music_instance->model); + free(plugin_state->music_instance); +#endif + furi_record_close(RECORD_NOTIFICATION); + furi_timer_free(timer); + 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); + free(plugin_state); + return 0; +} diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/doom_10px.png b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/doom_10px.png new file mode 100644 index 000000000..17fe22c87 Binary files /dev/null and b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/doom_10px.png differ diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/doom_music_player_worker.c b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/doom_music_player_worker.c new file mode 100644 index 000000000..d691f3cae --- /dev/null +++ b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/doom_music_player_worker.c @@ -0,0 +1,500 @@ +#include "doom_music_player_worker.h" + +#include +#include + +#include +#include + +#include + +#define TAG "MusicPlayerWorker" + +#define MUSIC_PLAYER_FILETYPE "Flipper Music Format" +#define MUSIC_PLAYER_VERSION 0 + +#define SEMITONE_PAUSE 0xFF + +#define NOTE_C4 261.63f +#define NOTE_C4_SEMITONE (4.0f * 12.0f) +#define TWO_POW_TWELTH_ROOT 1.059463094359f + +typedef struct { + uint8_t semitone; + uint8_t duration; + uint8_t dots; +} NoteBlock; + +ARRAY_DEF(NoteBlockArray, NoteBlock, M_POD_OPLIST); + +struct MusicPlayerWorker { + FuriThread* thread; + bool should_work; + + MusicPlayerWorkerCallback callback; + void* callback_context; + + float volume; + uint32_t bpm; + uint32_t duration; + uint32_t octave; + NoteBlockArray_t notes; +}; + +static int32_t music_player_worker_thread_callback(void* context) { + furi_assert(context); + MusicPlayerWorker* instance = context; + + NoteBlockArray_it_t it; + NoteBlockArray_it(it, instance->notes); + + while(instance->should_work) { + if(NoteBlockArray_end_p(it)) { + NoteBlockArray_it(it, instance->notes); + furi_delay_ms(10); + } else { + NoteBlock* note_block = NoteBlockArray_ref(it); + + float note_from_a4 = (float)note_block->semitone - NOTE_C4_SEMITONE; + float frequency = NOTE_C4 * powf(TWO_POW_TWELTH_ROOT, note_from_a4); + float duration = + 60.0 * furi_kernel_get_tick_frequency() * 4 / instance->bpm / note_block->duration; + uint32_t dots = note_block->dots; + while(dots > 0) { + duration += duration / 2; + dots--; + } + uint32_t next_tick = furi_get_tick() + duration; + float volume = instance->volume; + + if(instance->callback) { + instance->callback( + note_block->semitone, + note_block->dots, + note_block->duration, + 0.0, + instance->callback_context); + } + + furi_hal_speaker_stop(); + furi_hal_speaker_start(frequency, volume); + while(instance->should_work && furi_get_tick() < next_tick) { + volume *= 0.9945679; + furi_hal_speaker_set_volume(volume); + furi_delay_ms(2); + } + NoteBlockArray_next(it); + } + } + + furi_hal_speaker_stop(); + + return 0; +} + +MusicPlayerWorker* music_player_worker_alloc() { + MusicPlayerWorker* instance = malloc(sizeof(MusicPlayerWorker)); + + NoteBlockArray_init(instance->notes); + + instance->thread = furi_thread_alloc(); + furi_thread_set_name(instance->thread, "MusicPlayerWorker"); + furi_thread_set_stack_size(instance->thread, 1024); + furi_thread_set_context(instance->thread, instance); + furi_thread_set_callback(instance->thread, music_player_worker_thread_callback); + + instance->volume = 1.0f; + + return instance; +} + +void music_player_worker_free(MusicPlayerWorker* instance) { + furi_assert(instance); + furi_thread_free(instance->thread); + NoteBlockArray_clear(instance->notes); + free(instance); +} + +static bool is_digit(const char c) { + return isdigit(c) != 0; +} + +static bool is_letter(const char c) { + return islower(c) != 0 || isupper(c) != 0; +} + +static bool is_space(const char c) { + return c == ' ' || c == '\t'; +} + +static size_t extract_number(const char* string, uint32_t* number) { + size_t ret = 0; + while(is_digit(*string)) { + *number *= 10; + *number += (*string - '0'); + string++; + ret++; + } + return ret; +} + +static size_t extract_dots(const char* string, uint32_t* number) { + size_t ret = 0; + while(*string == '.') { + *number += 1; + string++; + ret++; + } + return ret; +} + +static size_t extract_char(const char* string, char* symbol) { + if(is_letter(*string)) { + *symbol = *string; + return 1; + } else { + return 0; + } +} + +static size_t extract_sharp(const char* string, char* symbol) { + if(*string == '#' || *string == '_') { + *symbol = '#'; + return 1; + } else { + return 0; + } +} + +static size_t skip_till(const char* string, const char symbol) { + size_t ret = 0; + while(*string != '\0' && *string != symbol) { + string++; + ret++; + } + if(*string != symbol) { + ret = 0; + } + return ret; +} + +static bool music_player_worker_add_note( + MusicPlayerWorker* instance, + uint8_t semitone, + uint8_t duration, + uint8_t dots) { + NoteBlock note_block; + + note_block.semitone = semitone; + note_block.duration = duration; + note_block.dots = dots; + + NoteBlockArray_push_back(instance->notes, note_block); + + return true; +} + +static int8_t note_to_semitone(const char note) { + switch(note) { + case 'C': + return 0; + // C# + case 'D': + return 2; + // D# + case 'E': + return 4; + case 'F': + return 5; + // F# + case 'G': + return 7; + // G# + case 'A': + return 9; + // A# + case 'B': + return 11; + default: + return 0; + } +} + +static bool music_player_worker_parse_notes(MusicPlayerWorker* instance, const char* string) { + const char* cursor = string; + bool result = true; + + while(*cursor != '\0') { + if(!is_space(*cursor)) { + uint32_t duration = 0; + char note_char = '\0'; + char sharp_char = '\0'; + uint32_t octave = 0; + uint32_t dots = 0; + + // Parsing + cursor += extract_number(cursor, &duration); + cursor += extract_char(cursor, ¬e_char); + cursor += extract_sharp(cursor, &sharp_char); + cursor += extract_number(cursor, &octave); + cursor += extract_dots(cursor, &dots); + + // Post processing + note_char = toupper(note_char); + if(!duration) { + duration = instance->duration; + } + if(!octave) { + octave = instance->octave; + } + + // Validation + bool is_valid = true; + is_valid &= (duration >= 1 && duration <= 128); + is_valid &= ((note_char >= 'A' && note_char <= 'G') || note_char == 'P'); + is_valid &= (sharp_char == '#' || sharp_char == '\0'); + is_valid &= (octave <= 16); + is_valid &= (dots <= 16); + if(!is_valid) { + FURI_LOG_E( + TAG, + "Invalid note: %lu%c%c%lu.%lu", + duration, + note_char == '\0' ? '_' : note_char, + sharp_char == '\0' ? '_' : sharp_char, + octave, + dots); + result = false; + break; + } + + // Note to semitones + uint8_t semitone = 0; + if(note_char == 'P') { + semitone = SEMITONE_PAUSE; + } else { + semitone += octave * 12; + semitone += note_to_semitone(note_char); + semitone += sharp_char == '#' ? 1 : 0; + } + + if(music_player_worker_add_note(instance, semitone, duration, dots)) { + FURI_LOG_D( + TAG, + "Added note: %c%c%lu.%lu = %u %lu", + note_char == '\0' ? '_' : note_char, + sharp_char == '\0' ? '_' : sharp_char, + octave, + dots, + semitone, + duration); + } else { + FURI_LOG_E( + TAG, + "Invalid note: %c%c%lu.%lu = %u %lu", + note_char == '\0' ? '_' : note_char, + sharp_char == '\0' ? '_' : sharp_char, + octave, + dots, + semitone, + duration); + } + cursor += skip_till(cursor, ','); + } + + if(*cursor != '\0') cursor++; + } + + return result; +} + +bool music_player_worker_load(MusicPlayerWorker* instance, const char* file_path) { + furi_assert(instance); + furi_assert(file_path); + + bool ret = false; + if(strcasestr(file_path, ".fmf")) { + ret = music_player_worker_load_fmf_from_file(instance, file_path); + } else { + ret = music_player_worker_load_rtttl_from_file(instance, file_path); + } + return ret; +} + +bool music_player_worker_load_fmf_from_file(MusicPlayerWorker* instance, const char* file_path) { + furi_assert(instance); + furi_assert(file_path); + + bool result = false; + FuriString* temp_str; + temp_str = furi_string_alloc(); + + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* file = flipper_format_file_alloc(storage); + + do { + if(!flipper_format_file_open_existing(file, file_path)) break; + + uint32_t version = 0; + if(!flipper_format_read_header(file, temp_str, &version)) break; + if(furi_string_cmp_str(temp_str, MUSIC_PLAYER_FILETYPE) || + (version != MUSIC_PLAYER_VERSION)) { + FURI_LOG_E(TAG, "Incorrect file format or version"); + break; + } + + if(!flipper_format_read_uint32(file, "BPM", &instance->bpm, 1)) { + FURI_LOG_E(TAG, "BPM is missing"); + break; + } + if(!flipper_format_read_uint32(file, "Duration", &instance->duration, 1)) { + FURI_LOG_E(TAG, "Duration is missing"); + break; + } + if(!flipper_format_read_uint32(file, "Octave", &instance->octave, 1)) { + FURI_LOG_E(TAG, "Octave is missing"); + break; + } + + if(!flipper_format_read_string(file, "Notes", temp_str)) { + FURI_LOG_E(TAG, "Notes is missing"); + break; + } + + if(!music_player_worker_parse_notes(instance, furi_string_get_cstr(temp_str))) { + break; + } + + result = true; + } while(false); + + furi_record_close(RECORD_STORAGE); + flipper_format_free(file); + furi_string_free(temp_str); + + return result; +} + +bool music_player_worker_load_rtttl_from_file(MusicPlayerWorker* instance, const char* file_path) { + furi_assert(instance); + furi_assert(file_path); + + bool result = false; + FuriString* content; + content = furi_string_alloc(); + Storage* storage = furi_record_open(RECORD_STORAGE); + File* file = storage_file_alloc(storage); + + do { + if(!storage_file_open(file, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) { + FURI_LOG_E(TAG, "Unable to open file"); + break; + }; + + uint16_t ret = 0; + do { + uint8_t buffer[65] = {0}; + ret = storage_file_read(file, buffer, sizeof(buffer) - 1); + for(size_t i = 0; i < ret; i++) { + furi_string_push_back(content, buffer[i]); + } + } while(ret > 0); + + furi_string_trim(content); + if(!furi_string_size(content)) { + FURI_LOG_E(TAG, "Empty file"); + break; + } + + if(!music_player_worker_load_rtttl_from_string(instance, furi_string_get_cstr(content))) { + FURI_LOG_E(TAG, "Invalid file content"); + break; + } + + result = true; + } while(0); + + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + furi_string_free(content); + + return result; +} + +bool music_player_worker_load_rtttl_from_string(MusicPlayerWorker* instance, const char* string) { + furi_assert(instance); + + const char* cursor = string; + + // Skip name + cursor += skip_till(cursor, ':'); + if(*cursor != ':') { + return false; + } + + // Duration + cursor += skip_till(cursor, '='); + if(*cursor != '=') { + return false; + } + cursor++; + cursor += extract_number(cursor, &instance->duration); + + // Octave + cursor += skip_till(cursor, '='); + if(*cursor != '=') { + return false; + } + cursor++; + cursor += extract_number(cursor, &instance->octave); + + // BPM + cursor += skip_till(cursor, '='); + if(*cursor != '=') { + return false; + } + cursor++; + cursor += extract_number(cursor, &instance->bpm); + + // Notes + cursor += skip_till(cursor, ':'); + if(*cursor != ':') { + return false; + } + cursor++; + if(!music_player_worker_parse_notes(instance, cursor)) { + return false; + } + + return true; +} + +void music_player_worker_set_callback( + MusicPlayerWorker* instance, + MusicPlayerWorkerCallback callback, + void* context) { + furi_assert(instance); + instance->callback = callback; + instance->callback_context = context; +} + +void music_player_worker_set_volume(MusicPlayerWorker* instance, float volume) { + furi_assert(instance); + instance->volume = volume; +} + +void music_player_worker_start(MusicPlayerWorker* instance) { + furi_assert(instance); + furi_assert(instance->should_work == false); + + instance->should_work = true; + furi_thread_start(instance->thread); +} + +void music_player_worker_stop(MusicPlayerWorker* instance) { + furi_assert(instance); + furi_assert(instance->should_work == true); + + instance->should_work = false; + furi_thread_join(instance->thread); +} diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/doom_music_player_worker.h b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/doom_music_player_worker.h new file mode 100644 index 000000000..9958a9273 --- /dev/null +++ b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/doom_music_player_worker.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*MusicPlayerWorkerCallback)( + uint8_t semitone, + uint8_t dots, + uint8_t duration, + float position, + void* context); + +typedef struct MusicPlayerWorker MusicPlayerWorker; + +MusicPlayerWorker* music_player_worker_alloc(); + +void music_player_worker_free(MusicPlayerWorker* instance); + +bool music_player_worker_load(MusicPlayerWorker* instance, const char* file_path); + +bool music_player_worker_load_fmf_from_file(MusicPlayerWorker* instance, const char* file_path); + +bool music_player_worker_load_rtttl_from_file(MusicPlayerWorker* instance, const char* file_path); + +bool music_player_worker_load_rtttl_from_string(MusicPlayerWorker* instance, const char* string); + +void music_player_worker_set_callback( + MusicPlayerWorker* instance, + MusicPlayerWorkerCallback callback, + void* context); + +void music_player_worker_set_volume(MusicPlayerWorker* instance, float volume); + +void music_player_worker_start(MusicPlayerWorker* instance); + +void music_player_worker_stop(MusicPlayerWorker* instance); + +#ifdef __cplusplus +} +#endif diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/entities.c b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/entities.c new file mode 100644 index 000000000..86c7f6ae1 --- /dev/null +++ b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/entities.c @@ -0,0 +1,42 @@ +#include "entities.h" + +//extern "C" +/*Player create_player(double x, double y){ + return {create_coords((double) x + (double)0.5, (double) y + (double)0.5), create_coords(1, 0), create_coords(0, -0.66), 0, 100, 0}; +}*/ + +Player create_player(double x, double y) { + Player p; + p.pos = create_coords((double)x + (double)0.5, (double)y + (double)0.5); + p.dir = create_coords(1, 0); + p.plane = create_coords(0, -0.66); + p.velocity = 0; + p.health = 100; + p.keys = 0; + return p; //{create_coords((double) x + (double)0.5, (double) y + (double)0.5), create_coords(1, 0), create_coords(0, -0.66), 0, 100, 0}; +} + +//extern "C" +Entity + create_entity(uint8_t type, uint8_t x, uint8_t y, uint8_t initialState, uint8_t initialHealth) { + UID uid = create_uid(type, x, y); + Coords pos = create_coords((double)x + (double).5, (double)y + (double).5); + Entity new_entity; // = { uid, pos, initialState, initialHealth, 0, 0 }; + new_entity.uid = uid; + new_entity.pos = pos; + new_entity.state = initialState; + new_entity.health = initialHealth; + new_entity.distance = 0; + new_entity.timer = 0; + return new_entity; +} + +//extern "C" +StaticEntity crate_static_entity(UID uid, uint8_t x, uint8_t y, bool active) { + StaticEntity ent; + ent.uid = uid; + ent.x = x; + ent.y = y; + ent.active = active; + return ent; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/entities.h b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/entities.h new file mode 100644 index 000000000..ef5eb1a78 --- /dev/null +++ b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/entities.h @@ -0,0 +1,56 @@ +#ifndef _entities_h +#define _entities_h +#include +#include +#include "types.h" + +// Shortcuts +//#define create_player(x, y) {create_coords((double) x + (double)0.5, (double) y + (double)0.5), create_coords(1, 0), create_coords(0, -0.66), 0, 100} + +#define create_enemy(x, y) create_entity(E_ENEMY, x, y, S_STAND, 50) +#define create_medikit(x, y) create_entity(E_MEDIKIT, x, y, S_STAND, 0) +#define create_key(x, y) create_entity(E_KEY, x, y, S_STAND, 0) +#define create_fireball(x, y, dir) create_entity(E_FIREBALL, x, y, S_STAND, dir) +#define create_door(x, y) create_entity(E_DOOR, x, y, S_STAND, 0) + +// entity statuses +#define S_STAND 0 +#define S_ALERT 1 +#define S_FIRING 2 +#define S_MELEE 3 +#define S_HIT 4 +#define S_DEAD 5 +#define S_HIDDEN 6 +#define S_OPEN 7 +#define S_CLOSE 8 + +typedef struct Player { + Coords pos; + Coords dir; + Coords plane; + double velocity; + uint8_t health; + uint8_t keys; +} Player; + +typedef struct Entity { + UID uid; + Coords pos; + uint8_t state; + uint8_t health; // angle for fireballs + uint8_t distance; + uint8_t timer; +} Entity; + +typedef struct StaticEntity { + UID uid; + uint8_t x; + uint8_t y; + bool active; +} StaticEntity; + +Entity + create_entity(uint8_t type, uint8_t x, uint8_t y, uint8_t initialState, uint8_t initialHealth); +StaticEntity create_static_entity(UID uid, uint8_t x, uint8_t y, bool active); +Player create_player(double x, double y); +#endif diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/level.h b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/level.h new file mode 100644 index 000000000..4d92a1cc6 --- /dev/null +++ b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/level.h @@ -0,0 +1,188 @@ +#ifndef _level_h +#define _level_h + +#include "constants.h" + +/* + Based on E1M1 from Wolfenstein 3D + + ################################################################ + #############################...........######################## + ######....###################........E..######################## + ######....########..........#...........#...#################### + ######.....#######..........L.....E.......M.#################### + ######.....#######..........#...........#...#################### + ##################...########...........######################## + ######.........###...########...........######################## + ######.........###...#############D############################# + ######.........#......E##########...############################ + ######....E....D...E...##########...############################ + ######.........#.......##########...############################ + ######....E....##################...############################ + #...##.........##################...############################ + #.K.######D######################...############################ + #...#####...###############...#E.....K########################## + ##D######...###############..####...############################ + #...#####...###############..####...############################ + #...#...#...###############..####...############################ + #...D...#...#####################...############################ + #...#...#...#####################...############################ + #...######D#######################L############################# + #.E.##.........#################.....#################........## + #...##.........############...............############........## + #...##...E.....############...............############........## + #....#.........############...E.......E....#.........#........## + #....L....K....############................D....E....D....E...## + #....#.........############................#.........#........## + #...##.....E...############...............####....####........## + #...##.........############...............#####..#####.....M..## + #...##.........#################.....##########..#####........## + #...######L#######################D############..############### + #...#####...#####################...###########..############### + #E.E#####...#####################...###########..############### + #...#...#...#####################.E.###########..############### + #...D.M.#...#####################...###########..############### + #...#...#...#####################...###########..###.#.#.#.##### + #...#####...#####################...###########...#.........#### + #...#####...#####################...###########...D....E..K.#### + #................##......########...###########...#.........#### + #....E........E...L...E...X######...################.#.#.#.##### + #................##......########...############################ + #################################...############################ + #############..#..#..#############L############################# + ###########....#..#.########....#...#....####################### + #############.....##########.P..D...D....####################### + ############################....#...#....####################### + ##############..#################...############################ + ##############..############....#...#....####################### + ############################....D...D....####################### + ############################....#...#....####################### + #################################...############################ + ############################.............####################### + ############################..........EK.####################### + ############################.............####################### + ################################################################ +*/ + +/* + Same map above built from some regexp replacements using the legend above. + Using this way lets me use only 4 bit to store each block +*/ +const uint8_t sto_level_1[LEVEL_SIZE] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, + 0x00, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, + 0x00, 0x20, 0x00, 0x00, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x02, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x20, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x90, 0xFF, 0xFF, 0xFF, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xF2, + 0x00, 0x00, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x4F, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0x40, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x5F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x20, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xF0, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xF0, 0x00, 0xFF, 0x00, 0x02, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xF0, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x20, + 0x00, 0x00, 0x00, 0x20, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xF0, 0x00, 0x05, 0x00, 0x00, 0x90, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x00, 0x20, 0x00, 0xFF, + 0xF0, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xF0, 0x00, 0xFF, 0x00, 0x00, 0x02, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xF0, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x08, 0x00, 0xFF, + 0xF0, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0x5F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF2, 0x02, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x20, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0x40, 0x80, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0x0F, 0x0F, 0x0F, 0x0F, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x40, 0x00, 0x02, 0x00, 0x90, 0xFF, 0xFF, + 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xF0, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x50, 0x00, 0x20, 0x00, 0x7F, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x0F, 0x0F, 0x0F, 0xFF, 0xFF, + 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0x00, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x5F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x0F, 0x00, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, + 0xF0, 0x00, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, + 0x40, 0x00, 0x40, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, + 0xF0, 0x00, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, + 0xF0, 0x00, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, + 0x40, 0x00, 0x40, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, + 0xF0, 0x00, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x29, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +}; + +#endif diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/sound.h b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/sound.h new file mode 100644 index 000000000..514381334 --- /dev/null +++ b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/sound.h @@ -0,0 +1,34 @@ +#ifndef sound_h +#define sound_h +#include +#include +#include +#include "doom_music_player_worker.h" + +//static const char dspistol[] = "AnyConv:d=,o=,b=120:408,40p,40p,40p,40p,405,40p,40p,40p,405,30p.,30p.,30p.,13p"; +static const char dsintro[] = + "Doom:d=32,o=4,b=56:f,f,f5,f,f,d#5,f,f,c#5,f,f,b,f,f,c5,c#5,f,f,f5,f,f,d#5,f,f,c#5,f,f,8b.,f,f,f5,f,f,d#5,f,f,c#5,f,f,b,f,f,c5,c#5,f,f,f5,f,f,d#5,f,f,c#5,f,f,8b.,a#,a#,a#5,a#,a#,g#5,a#,a#,f#5,a#,a#,e5,a#,a#,f5,f#5,a#,a#,a#5,a#,a#,g#5,a#,a#,f#5,a#,a#,8e5"; +//static const char dsgetpow[] = "dsgetpow:d=,o=,b=120:407,40p,30.6,407,40p,406,40p,407,40p,40p,407,30p.,407"; +//static const char dsnoway[] = "dsnoway:d=,o=,b=120:407,30.4"; + +#define MUSIC_PLAYER_SEMITONE_HISTORY_SIZE 4 +static const float MUSIC_PLAYER_VOLUMES[] = {0, .25, .5, .75, 1}; + +typedef struct { + uint8_t semitone_history[MUSIC_PLAYER_SEMITONE_HISTORY_SIZE]; + uint8_t duration_history[MUSIC_PLAYER_SEMITONE_HISTORY_SIZE]; + + uint8_t volume; + uint8_t semitone; + uint8_t dots; + uint8_t duration; + float position; +} MusicPlayerModel; + +typedef struct { + MusicPlayerModel* model; + MusicPlayerWorker* worker; + FuriMutex** model_mutex; +} MusicPlayer; + +#endif \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/types.c b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/types.c new file mode 100644 index 000000000..6b55d56a7 --- /dev/null +++ b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/types.c @@ -0,0 +1,33 @@ +#include "types.h" + +/*template +inline T sq(T value) { + return value * value; +}*/ + +double sq(double val) { + return val * val; +} + +//extern "C" +Coords create_coords(double x, double y) { + Coords cord; + cord.x = x; + cord.y = y; + return cord; +} + +//extern "C" +uint8_t coords_distance(Coords* a, Coords* b) { + return sqrt(sq(a->x - b->x) + sq(a->y - b->y)) * 20; +} + +//extern "C" +UID create_uid(uint8_t type, uint8_t x, uint8_t y) { + return ((y << 6) | x) << 4 | type; +} + +//extern "C" +uint8_t uid_get_type(UID uid) { + return uid & 0x0F; +} diff --git a/Applications/Official/DEV_FW/source/doom-flipper-zero-main/types.h b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/types.h new file mode 100644 index 000000000..b8579f645 --- /dev/null +++ b/Applications/Official/DEV_FW/source/doom-flipper-zero-main/types.h @@ -0,0 +1,36 @@ +#ifndef _types_h +#define _types_h + +#include +#include +//#include "constants.h" + +#define UID_null 0 + +// Entity types (legend applies to level.h) +#define E_FLOOR 0x0 // . (also null) +#define E_WALL 0xF // # +#define E_PLAYER 0x1 // P +#define E_ENEMY 0x2 // E +#define E_DOOR 0x4 // D +#define E_LOCKEDDOOR 0x5 // L +#define E_EXIT 0x7 // X +// collectable entities >= 0x8 +#define E_MEDIKIT 0x8 // M +#define E_KEY 0x9 // K +#define E_FIREBALL 0xA // not in map + +typedef uint16_t UID; +typedef uint8_t EType; + +typedef struct Coords { + double x; + double y; +} Coords; + +UID create_uid(EType type, uint8_t x, uint8_t y); +EType uid_get_type(UID uid); +Coords create_coords(double x, double y); +uint8_t coords_distance(Coords* a, Coords* b); + +#endif diff --git a/Applications/Official/DEV_FW/source/dtmf_dolphin/LICENSE b/Applications/Official/DEV_FW/source/dtmf_dolphin/LICENSE new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dtmf_dolphin/README.md b/Applications/Official/DEV_FW/source/dtmf_dolphin/README.md new file mode 100644 index 000000000..5c9561f4b --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dtmf_dolphin/application.fam b/Applications/Official/DEV_FW/source/dtmf_dolphin/application.fam new file mode 100644 index 000000000..98fbe7363 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dtmf_dolphin/assets/dialer.jpg b/Applications/Official/DEV_FW/source/dtmf_dolphin/assets/dialer.jpg new file mode 100644 index 000000000..ff6fad7a8 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dtmf_dolphin/assets/dialer.jpg differ diff --git a/Applications/Official/DEV_FW/source/dtmf_dolphin/dtmf_dolphin.c b/Applications/Official/DEV_FW/source/dtmf_dolphin/dtmf_dolphin.c new file mode 100644 index 000000000..c1b10defa --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dtmf_dolphin/dtmf_dolphin_audio.c b/Applications/Official/DEV_FW/source/dtmf_dolphin/dtmf_dolphin_audio.c new file mode 100644 index 000000000..4b84ceb97 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dtmf_dolphin/dtmf_dolphin_audio.h b/Applications/Official/DEV_FW/source/dtmf_dolphin/dtmf_dolphin_audio.h new file mode 100644 index 000000000..2dd1d6eb6 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dtmf_dolphin/dtmf_dolphin_data.c b/Applications/Official/DEV_FW/source/dtmf_dolphin/dtmf_dolphin_data.c new file mode 100644 index 000000000..72386b83d --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dtmf_dolphin/dtmf_dolphin_data.h b/Applications/Official/DEV_FW/source/dtmf_dolphin/dtmf_dolphin_data.h new file mode 100644 index 000000000..56ceaf03d --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dtmf_dolphin/dtmf_dolphin_event.h b/Applications/Official/DEV_FW/source/dtmf_dolphin/dtmf_dolphin_event.h new file mode 100644 index 000000000..525d0eb04 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dtmf_dolphin/dtmf_dolphin_hal.c b/Applications/Official/DEV_FW/source/dtmf_dolphin/dtmf_dolphin_hal.c new file mode 100644 index 000000000..3c694c8ee --- /dev/null +++ b/Applications/Official/DEV_FW/source/dtmf_dolphin/dtmf_dolphin_hal.c @@ -0,0 +1,56 @@ +#include "dtmf_dolphin_hal.h" + +void dtmf_dolphin_speaker_init() { + bool acq; + acq = furi_hal_speaker_acquire(1000); + if (acq) { + 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/Official/DEV_FW/source/dtmf_dolphin/dtmf_dolphin_hal.h b/Applications/Official/DEV_FW/source/dtmf_dolphin/dtmf_dolphin_hal.h new file mode 100644 index 000000000..5b426f6a0 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dtmf_dolphin/dtmf_dolphin_i.h b/Applications/Official/DEV_FW/source/dtmf_dolphin/dtmf_dolphin_i.h new file mode 100644 index 000000000..f8ae1530f --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dtmf_dolphin/phone.png b/Applications/Official/DEV_FW/source/dtmf_dolphin/phone.png new file mode 100644 index 000000000..443f847c3 Binary files /dev/null and b/Applications/Official/DEV_FW/source/dtmf_dolphin/phone.png differ diff --git a/Applications/Official/DEV_FW/source/dtmf_dolphin/scenes/dtmf_dolphin_scene.c b/Applications/Official/DEV_FW/source/dtmf_dolphin/scenes/dtmf_dolphin_scene.c new file mode 100644 index 000000000..bfb8da1da --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dtmf_dolphin/scenes/dtmf_dolphin_scene.h b/Applications/Official/DEV_FW/source/dtmf_dolphin/scenes/dtmf_dolphin_scene.h new file mode 100644 index 000000000..e45dc68ae --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dtmf_dolphin/scenes/dtmf_dolphin_scene_config.h b/Applications/Official/DEV_FW/source/dtmf_dolphin/scenes/dtmf_dolphin_scene_config.h new file mode 100644 index 000000000..b6dab07dc --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dtmf_dolphin/scenes/dtmf_dolphin_scene_dialer.c b/Applications/Official/DEV_FW/source/dtmf_dolphin/scenes/dtmf_dolphin_scene_dialer.c new file mode 100644 index 000000000..06da595e0 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dtmf_dolphin/scenes/dtmf_dolphin_scene_start.c b/Applications/Official/DEV_FW/source/dtmf_dolphin/scenes/dtmf_dolphin_scene_start.c new file mode 100644 index 000000000..484e9e8eb --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dtmf_dolphin/views/dtmf_dolphin_common.h b/Applications/Official/DEV_FW/source/dtmf_dolphin/views/dtmf_dolphin_common.h new file mode 100644 index 000000000..f2f4838d6 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dtmf_dolphin/views/dtmf_dolphin_dialer.c b/Applications/Official/DEV_FW/source/dtmf_dolphin/views/dtmf_dolphin_dialer.c new file mode 100644 index 000000000..bdffa2313 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/dtmf_dolphin/views/dtmf_dolphin_dialer.h b/Applications/Official/DEV_FW/source/dtmf_dolphin/views/dtmf_dolphin_dialer.h new file mode 100644 index 000000000..1929afbc5 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/flappy_bird/application.fam b/Applications/Official/DEV_FW/source/flappy_bird/application.fam new file mode 100644 index 000000000..0912178ca --- /dev/null +++ b/Applications/Official/DEV_FW/source/flappy_bird/application.fam @@ -0,0 +1,13 @@ +App( + appid="FlappyBird", + name="Flappy Bird", + apptype=FlipperAppType.EXTERNAL, + entry_point="flappy_game_app", + cdefines=["APP_FLAPPY_GAME"], + requires=["gui"], + stack_size=4 * 1024, + order=100, + fap_icon="flappy_10px.png", + fap_category="Games", + fap_icon_assets="assets", +) diff --git a/Applications/Official/DEV_FW/source/flappy_bird/assets/bird/frame_01.png b/Applications/Official/DEV_FW/source/flappy_bird/assets/bird/frame_01.png new file mode 100644 index 000000000..0cd187053 Binary files /dev/null and b/Applications/Official/DEV_FW/source/flappy_bird/assets/bird/frame_01.png differ diff --git a/Applications/Official/DEV_FW/source/flappy_bird/assets/bird/frame_02.png b/Applications/Official/DEV_FW/source/flappy_bird/assets/bird/frame_02.png new file mode 100644 index 000000000..3c37cdb8b Binary files /dev/null and b/Applications/Official/DEV_FW/source/flappy_bird/assets/bird/frame_02.png differ diff --git a/Applications/Official/DEV_FW/source/flappy_bird/assets/bird/frame_03.png b/Applications/Official/DEV_FW/source/flappy_bird/assets/bird/frame_03.png new file mode 100644 index 000000000..a111ce163 Binary files /dev/null and b/Applications/Official/DEV_FW/source/flappy_bird/assets/bird/frame_03.png differ diff --git a/Applications/Official/DEV_FW/source/flappy_bird/assets/bird/frame_rate b/Applications/Official/DEV_FW/source/flappy_bird/assets/bird/frame_rate new file mode 100644 index 000000000..e440e5c84 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flappy_bird/assets/bird/frame_rate @@ -0,0 +1 @@ +3 \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flappy_bird/flappy_10px.png b/Applications/Official/DEV_FW/source/flappy_bird/flappy_10px.png new file mode 100644 index 000000000..d624d3571 Binary files /dev/null and b/Applications/Official/DEV_FW/source/flappy_bird/flappy_10px.png differ diff --git a/Applications/Official/DEV_FW/source/flappy_bird/flappy_bird.c b/Applications/Official/DEV_FW/source/flappy_bird/flappy_bird.c new file mode 100644 index 000000000..4c6117b78 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flappy_bird/flappy_bird.c @@ -0,0 +1,386 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + +#define TAG "Flappy" +#define DEBUG false + +#define FLAPPY_BIRD_HEIGHT 15 +#define FLAPPY_BIRD_WIDTH 10 + +#define FLAPPY_PILAR_MAX 6 +#define FLAPPY_PILAR_DIST 35 + +#define FLAPPY_GAB_HEIGHT 25 +#define FLAPPY_GAB_WIDTH 10 + +#define FLAPPY_GRAVITY_JUMP -1.1 +#define FLAPPY_GRAVITY_TICK 0.15 + +#define FLIPPER_LCD_WIDTH 128 +#define FLIPPER_LCD_HEIGHT 64 + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; +DolphinDeed getRandomDeed() { + DolphinDeed returnGrp[14] = {1, 5, 8, 10, 12, 15, 17, 20, 21, 25, 26, 28, 29, 32}; + static bool rand_generator_inited = false; + if(!rand_generator_inited) { + srand(furi_get_tick()); + rand_generator_inited = true; + } + uint8_t diceRoll = (rand() % COUNT_OF(returnGrp)); // JUST TO GET IT GOING? AND FIX BUG + diceRoll = (rand() % COUNT_OF(returnGrp)); + return returnGrp[diceRoll]; +} +typedef struct { + int x; + int y; +} POINT; + +typedef struct { + float gravity; + POINT point; + IconAnimation* sprite; +} BIRD; + +typedef struct { + POINT point; + int height; + int visible; + bool passed; +} PILAR; + +typedef enum { + GameStateLife, + GameStateGameOver, +} State; + +typedef struct { + BIRD bird; + int points; + int pilars_count; + PILAR pilars[FLAPPY_PILAR_MAX]; + bool debug; + State state; +} GameState; + +typedef struct { + EventType type; + InputEvent input; +} GameEvent; + +typedef enum { + DirectionUp, + DirectionRight, + DirectionDown, + DirectionLeft, +} Direction; + +static void flappy_game_random_pilar(GameState* const game_state) { + PILAR pilar; + + pilar.passed = false; + pilar.visible = 1; + pilar.height = random() % (FLIPPER_LCD_HEIGHT - FLAPPY_GAB_HEIGHT) + 1; + pilar.point.y = 0; + pilar.point.x = FLIPPER_LCD_WIDTH + FLAPPY_GAB_WIDTH + 1; + + game_state->pilars_count++; + game_state->pilars[game_state->pilars_count % FLAPPY_PILAR_MAX] = pilar; +} + +static void flappy_game_state_init(GameState* const game_state) { + BIRD bird; + bird.gravity = 0.0f; + bird.point.x = 15; + bird.point.y = 32; + bird.sprite = icon_animation_alloc(&A_bird); + + game_state->debug = DEBUG; + game_state->bird = bird; + game_state->pilars_count = 0; + game_state->points = 0; + game_state->state = GameStateLife; + memset(game_state->pilars, 0, sizeof(game_state->pilars)); + + flappy_game_random_pilar(game_state); +} + +static void flappy_game_state_free(GameState* const game_state) { + icon_animation_free(game_state->bird.sprite); + free(game_state); +} + +static void flappy_game_tick(GameState* const game_state) { + if(game_state->state == GameStateLife) { + if(!game_state->debug) { + game_state->bird.gravity += FLAPPY_GRAVITY_TICK; + game_state->bird.point.y += game_state->bird.gravity; + } + + // Checking the location of the last respawned pilar. + PILAR* pilar = &game_state->pilars[game_state->pilars_count % FLAPPY_PILAR_MAX]; + if(pilar->point.x == (FLIPPER_LCD_WIDTH - FLAPPY_PILAR_DIST)) + flappy_game_random_pilar(game_state); + + // Updating the position/status of the pilars (visiblity, posotion, game points) + // | | | | | + // | | | | | + // |__| | |__| + // _____X | X_____ + // | | | | | // [Pos + Width of pilar] >= [Bird Pos] + // |_____| | |_____| + // X <----> | X <-> + // Bird Pos + Lenght of the bird] >= [Pilar] + for(int i = 0; i < FLAPPY_PILAR_MAX; i++) { + PILAR* pilar = &game_state->pilars[i]; + if(pilar != NULL && pilar->visible && game_state->state == GameStateLife) { + pilar->point.x--; + if(game_state->bird.point.x >= pilar->point.x + FLAPPY_GAB_WIDTH && + pilar->passed == false) { + pilar->passed = true; + game_state->points++; + } + if(pilar->point.x < -FLAPPY_GAB_WIDTH) pilar->visible = 0; + + if(game_state->bird.point.y <= 0 - FLAPPY_BIRD_WIDTH) { + game_state->bird.point.y = 64; + } + + if(game_state->bird.point.y > 64 - FLAPPY_BIRD_WIDTH) { + game_state->bird.point.y = FLIPPER_LCD_HEIGHT - FLAPPY_BIRD_WIDTH; + } + + // Bird inbetween pipes + if((game_state->bird.point.x + FLAPPY_BIRD_HEIGHT >= pilar->point.x) && + (game_state->bird.point.x <= pilar->point.x + FLAPPY_GAB_WIDTH)) { + // Bird below Bottom Pipe + if(game_state->bird.point.y + FLAPPY_BIRD_WIDTH - 2 >= + pilar->height + FLAPPY_GAB_HEIGHT) { + game_state->state = GameStateGameOver; + break; + } + + // Bird above Upper Pipe + if(game_state->bird.point.y < pilar->height) { + game_state->state = GameStateGameOver; + break; + } + } + } + } + } +} + +static void flappy_game_flap(GameState* const game_state) { + game_state->bird.gravity = FLAPPY_GRAVITY_JUMP; +} + +static void flappy_game_render_callback(Canvas* const canvas, void* ctx) { + const GameState* game_state = acquire_mutex((ValueMutex*)ctx, 25); + if(game_state == NULL) { + return; + } + + canvas_draw_frame(canvas, 0, 0, 128, 64); + + if(game_state->state == GameStateLife) { + // Pilars + for(int i = 0; i < FLAPPY_PILAR_MAX; i++) { + const PILAR* pilar = &game_state->pilars[i]; + if(pilar != NULL && pilar->visible == 1) { + canvas_draw_frame( + canvas, pilar->point.x, pilar->point.y, FLAPPY_GAB_WIDTH, pilar->height); + + canvas_draw_frame( + canvas, pilar->point.x + 1, pilar->point.y, FLAPPY_GAB_WIDTH, pilar->height); + + canvas_draw_frame( + canvas, + pilar->point.x + 2, + pilar->point.y, + FLAPPY_GAB_WIDTH - 1, + pilar->height); + + canvas_draw_frame( + canvas, + pilar->point.x, + pilar->point.y + pilar->height + FLAPPY_GAB_HEIGHT, + FLAPPY_GAB_WIDTH, + FLIPPER_LCD_HEIGHT - pilar->height - FLAPPY_GAB_HEIGHT); + + canvas_draw_frame( + canvas, + pilar->point.x + 1, + pilar->point.y + pilar->height + FLAPPY_GAB_HEIGHT, + FLAPPY_GAB_WIDTH - 1, + FLIPPER_LCD_HEIGHT - pilar->height - FLAPPY_GAB_HEIGHT); + + canvas_draw_frame( + canvas, + pilar->point.x + 2, + pilar->point.y + pilar->height + FLAPPY_GAB_HEIGHT, + FLAPPY_GAB_WIDTH - 1, + FLIPPER_LCD_HEIGHT - pilar->height - FLAPPY_GAB_HEIGHT); + } + } + + // Switch animation + game_state->bird.sprite->frame = 1; + if(game_state->bird.gravity < -0.5) + game_state->bird.sprite->frame = 0; + else if(game_state->bird.gravity > 0.5) + game_state->bird.sprite->frame = 2; + + canvas_draw_icon_animation( + canvas, game_state->bird.point.x, game_state->bird.point.y, game_state->bird.sprite); + + canvas_set_font(canvas, FontSecondary); + char buffer[12]; + snprintf(buffer, sizeof(buffer), "Score: %u", game_state->points); + canvas_draw_str_aligned(canvas, 100, 12, AlignCenter, AlignBottom, buffer); + + if(game_state->debug) { + char coordinates[20]; + snprintf(coordinates, sizeof(coordinates), "Y: %u", game_state->bird.point.y); + canvas_draw_str_aligned(canvas, 1, 12, AlignCenter, AlignBottom, coordinates); + } + } + + if(game_state->state == GameStateGameOver) { + // Screen is 128x64 px + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 34, 20, 62, 24); + + canvas_set_color(canvas, ColorBlack); + canvas_draw_frame(canvas, 34, 20, 62, 24); + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 37, 31, "Game Over"); + + if(game_state->points != 0 && game_state->points % 5 == 0) { + DOLPHIN_DEED(getRandomDeed()); + } + + canvas_set_font(canvas, FontSecondary); + char buffer[12]; + snprintf(buffer, sizeof(buffer), "Score: %u", game_state->points); + canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignBottom, buffer); + } + + release_mutex((ValueMutex*)ctx, game_state); +} + +static void flappy_game_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + GameEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void flappy_game_update_timer_callback(FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + GameEvent event = {.type = EventTypeTick}; + furi_message_queue_put(event_queue, &event, 0); +} + +int32_t flappy_game_app(void* p) { + UNUSED(p); + int32_t return_code = 0; + + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(GameEvent)); + + GameState* game_state = malloc(sizeof(GameState)); + flappy_game_state_init(game_state); + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, game_state, sizeof(GameState))) { + FURI_LOG_E(TAG, "cannot create mutex\r\n"); + return_code = 255; + goto free_and_exit; + } + + // Set system callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, flappy_game_render_callback, &state_mutex); + view_port_input_callback_set(view_port, flappy_game_input_callback, event_queue); + + FuriTimer* timer = + furi_timer_alloc(flappy_game_update_timer_callback, FuriTimerTypePeriodic, event_queue); + furi_timer_start(timer, furi_kernel_get_tick_frequency() / 25); + + // Open GUI and register view_port + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + GameEvent event; + for(bool processing = true; processing;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + GameState* game_state = (GameState*)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: + if(game_state->state == GameStateLife) { + flappy_game_flap(game_state); + } + + break; + case InputKeyDown: + break; + case InputKeyRight: + break; + case InputKeyLeft: + break; + case InputKeyOk: + if(game_state->state == GameStateGameOver) { + flappy_game_state_init(game_state); + } + + if(game_state->state == GameStateLife) { + flappy_game_flap(game_state); + } + + break; + case InputKeyBack: + processing = false; + break; + default: + break; + } + } + } else if(event.type == EventTypeTick) { + flappy_game_tick(game_state); + } + } + + view_port_update(view_port); + release_mutex(&state_mutex, game_state); + } + + furi_timer_free(timer); + 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); + +free_and_exit: + flappy_game_state_free(game_state); + furi_message_queue_free(event_queue); + + return return_code; +} diff --git a/Applications/Official/DEV_FW/source/flashlight/LICENSE b/Applications/Official/DEV_FW/source/flashlight/LICENSE new file mode 100644 index 000000000..28d693a7c --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/flashlight/README.md b/Applications/Official/DEV_FW/source/flashlight/README.md new file mode 100644 index 000000000..a40cb2d5a --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/flashlight/application.fam b/Applications/Official/DEV_FW/source/flashlight/application.fam new file mode 100644 index 000000000..d6d5aa791 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/flashlight/flash10px.png b/Applications/Official/DEV_FW/source/flashlight/flash10px.png new file mode 100644 index 000000000..963a9ab5f Binary files /dev/null and b/Applications/Official/DEV_FW/source/flashlight/flash10px.png differ diff --git a/Applications/Official/DEV_FW/source/flashlight/flashlight.c b/Applications/Official/DEV_FW/source/flashlight/flashlight.c new file mode 100644 index 000000000..9c5f600f7 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/flipfrid/LICENSE.md b/Applications/Official/DEV_FW/source/flipfrid/LICENSE.md new file mode 100644 index 000000000..a856581c9 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipfrid/LICENSE.md @@ -0,0 +1,8 @@ +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * @G4N4P4T1 wrote this file. As long as you retain this notice you + * can do whatever you want with this stuff. If we meet some day, and you think + * this stuff is worth it, you can buy me a beer in return. + * ---------------------------------------------------------------------------- + */ \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipfrid/README.md b/Applications/Official/DEV_FW/source/flipfrid/README.md new file mode 100644 index 000000000..69fdb3e66 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipfrid/README.md @@ -0,0 +1,35 @@ +# Flipfrid + +Basic EM4100 and HIDProx Fuzzer. + +## Why + +Flipfrid is a simple Rfid fuzzer using EM4100 protocol (125khz). +Objective is to provide a simple to use fuzzer to test readers by emulating various cards. + +- EM4100 cards use a 1 byte customer id and 4 bytes card id. +- HIDProx cards use a 2 byte customer id and 3 byte card id. + +## How + +1) Select the Protocol with the left and right arrows +2) Select the Mode with the up and down arrows + +### Info + +There are 2 Protocols: +- EM4100 +- HIDProx + +There are 4 modes: +- Default Values: Try factory/default keys and emulate one after the other. +- BF customer id: An iteration from 0X00 to 0XFF on the first byte. +- Load Dump file: Load an existing dump (.rfid) generated by Flipperzero, select an index and bruteforce from 0X00 to 0XFF; +- Uids list: Iterate over an input text file (one uid per line) and emulate one after the other. + + + + +TODO : +- blank screen on back press +- Add second byte test to `BF customer id` diff --git a/Applications/Official/DEV_FW/source/flipfrid/application.fam b/Applications/Official/DEV_FW/source/flipfrid/application.fam new file mode 100644 index 000000000..fc089de48 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipfrid/application.fam @@ -0,0 +1,13 @@ +App( + appid="RFID_Fuzzer", + name="RFID Fuzzer", + apptype=FlipperAppType.EXTERNAL, + entry_point="flipfrid_start", + cdefines=["APP_FLIP_FRID"], + requires=["gui", "storage", "dialogs", "input", "notification"], + stack_size=1 * 1024, + order=180, + fap_icon="rfid_10px.png", + fap_category="Tools", + fap_icon_assets="images", +) diff --git a/Applications/Official/DEV_FW/source/flipfrid/flipfrid.c b/Applications/Official/DEV_FW/source/flipfrid/flipfrid.c new file mode 100644 index 000000000..c2831f2f6 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipfrid/flipfrid.c @@ -0,0 +1,274 @@ +#include "flipfrid.h" + +#include "scene/flipfrid_scene_entrypoint.h" +#include "scene/flipfrid_scene_load_file.h" +#include "scene/flipfrid_scene_select_field.h" +#include "scene/flipfrid_scene_run_attack.h" +#include "scene/flipfrid_scene_load_custom_uids.h" + +#define RFIDFUZZER_APP_FOLDER "/ext/lrfid/rfidfuzzer" + +static void flipfrid_draw_callback(Canvas* const canvas, void* ctx) { + FlipFridState* flipfrid_state = (FlipFridState*)acquire_mutex((ValueMutex*)ctx, 100); + + if(flipfrid_state == NULL) { + return; + } + + // Draw correct Canvas + switch(flipfrid_state->current_scene) { + case NoneScene: + case SceneEntryPoint: + flipfrid_scene_entrypoint_on_draw(canvas, flipfrid_state); + break; + case SceneSelectFile: + flipfrid_scene_load_file_on_draw(canvas, flipfrid_state); + break; + case SceneSelectField: + flipfrid_scene_select_field_on_draw(canvas, flipfrid_state); + break; + case SceneAttack: + flipfrid_scene_run_attack_on_draw(canvas, flipfrid_state); + break; + case SceneLoadCustomUids: + flipfrid_scene_load_custom_uids_on_draw(canvas, flipfrid_state); + break; + default: + break; + } + + release_mutex((ValueMutex*)ctx, flipfrid_state); +} + +void flipfrid_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + FlipFridEvent event = { + .evt_type = EventTypeKey, .key = input_event->key, .input_type = input_event->type}; + furi_message_queue_put(event_queue, &event, 25); +} + +static void flipfrid_timer_callback(FuriMessageQueue* event_queue) { + furi_assert(event_queue); + FlipFridEvent event = { + .evt_type = EventTypeTick, .key = InputKeyUp, .input_type = InputTypeRelease}; + furi_message_queue_put(event_queue, &event, 25); +} + +FlipFridState* flipfrid_alloc() { + FlipFridState* flipfrid = malloc(sizeof(FlipFridState)); + flipfrid->notification_msg = furi_string_alloc(); + flipfrid->attack_name = furi_string_alloc(); + flipfrid->proto_name = furi_string_alloc(); + flipfrid->data_str = furi_string_alloc(); + + flipfrid->previous_scene = NoneScene; + flipfrid->current_scene = SceneEntryPoint; + flipfrid->is_running = true; + flipfrid->is_attacking = false; + flipfrid->key_index = 0; + flipfrid->menu_index = 0; + flipfrid->menu_proto_index = 0; + + flipfrid->attack = FlipFridAttackDefaultValues; + flipfrid->notify = furi_record_open(RECORD_NOTIFICATION); + + flipfrid->data[0] = 0x00; + flipfrid->data[1] = 0x00; + flipfrid->data[2] = 0x00; + flipfrid->data[3] = 0x00; + flipfrid->data[4] = 0x00; + flipfrid->data[5] = 0x00; + + flipfrid->payload[0] = 0x00; + flipfrid->payload[1] = 0x00; + flipfrid->payload[2] = 0x00; + flipfrid->payload[3] = 0x00; + flipfrid->payload[4] = 0x00; + flipfrid->payload[5] = 0x00; + + //Dialog + flipfrid->dialogs = furi_record_open(RECORD_DIALOGS); + + return flipfrid; +} + +void flipfrid_free(FlipFridState* flipfrid) { + //Dialog + furi_record_close(RECORD_DIALOGS); + notification_message(flipfrid->notify, &sequence_blink_stop); + + // Strings + furi_string_free(flipfrid->notification_msg); + furi_string_free(flipfrid->attack_name); + furi_string_free(flipfrid->proto_name); + furi_string_free(flipfrid->data_str); + + free(flipfrid->data); + free(flipfrid->payload); + + // The rest + free(flipfrid); +} + +// ENTRYPOINT +int32_t flipfrid_start(void* p) { + UNUSED(p); + // Input + FURI_LOG_I(TAG, "Initializing input"); + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(FlipFridEvent)); + FlipFridState* flipfrid_state = flipfrid_alloc(); + ValueMutex flipfrid_state_mutex; + + // Mutex + FURI_LOG_I(TAG, "Initializing flipfrid mutex"); + if(!init_mutex(&flipfrid_state_mutex, flipfrid_state, sizeof(FlipFridState))) { + FURI_LOG_E(TAG, "cannot create mutex\r\n"); + furi_message_queue_free(event_queue); + furi_record_close(RECORD_NOTIFICATION); + flipfrid_free(flipfrid_state); + return 255; + } + + Storage* storage = furi_record_open(RECORD_STORAGE); + if(!storage_simply_mkdir(storage, RFIDFUZZER_APP_FOLDER)) { + FURI_LOG_E(TAG, "Could not create folder %s", RFIDFUZZER_APP_FOLDER); + } + furi_record_close(RECORD_STORAGE); + + // Configure view port + FURI_LOG_I(TAG, "Initializing viewport"); + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, flipfrid_draw_callback, &flipfrid_state_mutex); + view_port_input_callback_set(view_port, flipfrid_input_callback, event_queue); + + // Configure timer + FURI_LOG_I(TAG, "Initializing timer"); + FuriTimer* timer = + furi_timer_alloc(flipfrid_timer_callback, FuriTimerTypePeriodic, event_queue); + furi_timer_start(timer, furi_kernel_get_tick_frequency() / 10); // 10 times per second + + // Register view port in GUI + FURI_LOG_I(TAG, "Initializing gui"); + Gui* gui = (Gui*)furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + // Init values + FlipFridEvent event; + while(flipfrid_state->is_running) { + // Get next event + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 25); + if(event_status == FuriStatusOk) { + if(event.evt_type == EventTypeKey) { + //Handle event key + switch(flipfrid_state->current_scene) { + case NoneScene: + case SceneEntryPoint: + flipfrid_scene_entrypoint_on_event(event, flipfrid_state); + break; + case SceneSelectFile: + flipfrid_scene_load_file_on_event(event, flipfrid_state); + break; + case SceneSelectField: + flipfrid_scene_select_field_on_event(event, flipfrid_state); + break; + case SceneAttack: + flipfrid_scene_run_attack_on_event(event, flipfrid_state); + break; + case SceneLoadCustomUids: + flipfrid_scene_load_custom_uids_on_event(event, flipfrid_state); + break; + default: + break; + } + + } else if(event.evt_type == EventTypeTick) { + //Handle event tick + if(flipfrid_state->current_scene != flipfrid_state->previous_scene) { + // Trigger Exit Scene + switch(flipfrid_state->previous_scene) { + case SceneEntryPoint: + flipfrid_scene_entrypoint_on_exit(flipfrid_state); + break; + case SceneSelectFile: + flipfrid_scene_load_file_on_exit(flipfrid_state); + break; + case SceneSelectField: + flipfrid_scene_select_field_on_exit(flipfrid_state); + break; + case SceneAttack: + flipfrid_scene_run_attack_on_exit(flipfrid_state); + break; + case SceneLoadCustomUids: + flipfrid_scene_load_custom_uids_on_exit(flipfrid_state); + break; + case NoneScene: + break; + default: + break; + } + + // Trigger Entry Scene + switch(flipfrid_state->current_scene) { + case NoneScene: + case SceneEntryPoint: + flipfrid_scene_entrypoint_on_enter(flipfrid_state); + break; + case SceneSelectFile: + flipfrid_scene_load_file_on_enter(flipfrid_state); + break; + case SceneSelectField: + flipfrid_scene_select_field_on_enter(flipfrid_state); + break; + case SceneAttack: + flipfrid_scene_run_attack_on_enter(flipfrid_state); + break; + case SceneLoadCustomUids: + flipfrid_scene_load_custom_uids_on_enter(flipfrid_state); + break; + default: + break; + } + flipfrid_state->previous_scene = flipfrid_state->current_scene; + } + + // Trigger Tick Scene + switch(flipfrid_state->current_scene) { + case NoneScene: + case SceneEntryPoint: + flipfrid_scene_entrypoint_on_tick(flipfrid_state); + break; + case SceneSelectFile: + flipfrid_scene_load_file_on_tick(flipfrid_state); + break; + case SceneSelectField: + flipfrid_scene_select_field_on_tick(flipfrid_state); + break; + case SceneAttack: + flipfrid_scene_run_attack_on_tick(flipfrid_state); + break; + case SceneLoadCustomUids: + flipfrid_scene_load_custom_uids_on_tick(flipfrid_state); + break; + default: + break; + } + view_port_update(view_port); + } + } + } + + // Cleanup + furi_timer_stop(timer); + furi_timer_free(timer); + + FURI_LOG_I(TAG, "Cleaning up"); + gui_remove_view_port(gui, view_port); + view_port_free(view_port); + furi_message_queue_free(event_queue); + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_NOTIFICATION); + flipfrid_free(flipfrid_state); + + return 0; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipfrid/flipfrid.h b/Applications/Official/DEV_FW/source/flipfrid/flipfrid.h new file mode 100644 index 000000000..6b8662e65 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipfrid/flipfrid.h @@ -0,0 +1,91 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include + +#define TAG "FlipFrid" + +typedef enum { + FlipFridAttackDefaultValues, + FlipFridAttackBfCustomerId, + FlipFridAttackLoadFile, + FlipFridAttackLoadFileCustomUids, +} FlipFridAttacks; + +typedef enum { + EM4100, + HIDProx, + PAC, + H10301, +} FlipFridProtos; + +typedef enum { + NoneScene, + SceneEntryPoint, + SceneSelectFile, + SceneSelectField, + SceneAttack, + SceneLoadCustomUids, +} FlipFridScene; + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType evt_type; + InputKey key; + InputType input_type; +} FlipFridEvent; + +// STRUCTS +typedef struct { + bool is_running; + bool is_attacking; + FlipFridScene current_scene; + FlipFridScene previous_scene; + NotificationApp* notify; + u_int8_t menu_index; + u_int8_t menu_proto_index; + + FuriString* data_str; + uint8_t data[6]; + uint8_t payload[6]; + uint8_t attack_step; + FlipFridAttacks attack; + FlipFridProtos proto; + FuriString* attack_name; + FuriString* proto_name; + + DialogsApp* dialogs; + FuriString* notification_msg; + uint8_t key_index; + LFRFIDWorker* worker; + ProtocolDict* dict; + ProtocolId protocol; + bool workr_rund; + bool attack_stop_called; + + uint8_t time_between_cards; + + // Used for custom dictionnary + Stream* uids_stream; +} FlipFridState; \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipfrid/images/125_10px.png b/Applications/Official/DEV_FW/source/flipfrid/images/125_10px.png new file mode 100644 index 000000000..ce01284a2 Binary files /dev/null and b/Applications/Official/DEV_FW/source/flipfrid/images/125_10px.png differ diff --git a/Applications/Official/DEV_FW/source/flipfrid/rfid_10px.png b/Applications/Official/DEV_FW/source/flipfrid/rfid_10px.png new file mode 100644 index 000000000..8097f4775 Binary files /dev/null and b/Applications/Official/DEV_FW/source/flipfrid/rfid_10px.png differ diff --git a/Applications/Official/DEV_FW/source/flipfrid/scene/flipfrid_scene_entrypoint.c b/Applications/Official/DEV_FW/source/flipfrid/scene/flipfrid_scene_entrypoint.c new file mode 100644 index 000000000..24c19dc4c --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipfrid/scene/flipfrid_scene_entrypoint.c @@ -0,0 +1,217 @@ +#include "flipfrid_scene_entrypoint.h" + +FuriString* main_menu_items[4]; +FuriString* main_menu_proto_items[4]; + +void flipfrid_scene_entrypoint_menu_callback( + FlipFridState* context, + uint32_t index, + uint32_t proto_index) { + switch(index) { + case FlipFridAttackDefaultValues: + context->attack = FlipFridAttackDefaultValues; + context->current_scene = SceneAttack; + furi_string_set(context->attack_name, "Default Values"); + break; + case FlipFridAttackBfCustomerId: + context->attack = FlipFridAttackBfCustomerId; + context->current_scene = SceneAttack; + furi_string_set(context->attack_name, "Bad Customer ID"); + break; + case FlipFridAttackLoadFile: + context->attack = FlipFridAttackLoadFile; + context->current_scene = SceneSelectFile; + furi_string_set(context->attack_name, "Load File"); + break; + case FlipFridAttackLoadFileCustomUids: + context->attack = FlipFridAttackLoadFileCustomUids; + context->current_scene = SceneLoadCustomUids; + furi_string_set(context->attack_name, "Load Custom UIDs"); + break; + default: + break; + } + + switch(proto_index) { + case EM4100: + context->proto = EM4100; + furi_string_set(context->proto_name, "EM4100"); + break; + case HIDProx: + context->proto = HIDProx; + furi_string_set(context->proto_name, "HIDProx"); + break; + case PAC: + context->proto = PAC; + furi_string_set(context->proto_name, "PAC/Stanley"); + break; + case H10301: + context->proto = H10301; + furi_string_set(context->proto_name, "H10301"); + break; + default: + break; + } +} + +void flipfrid_scene_entrypoint_on_enter(FlipFridState* context) { + // Clear the previous payload + context->payload[0] = 0x00; + context->payload[1] = 0x00; + context->payload[2] = 0x00; + context->payload[3] = 0x00; + context->payload[4] = 0x00; + context->payload[5] = 0x00; + + context->menu_index = 0; + /*for(uint32_t i = 0; i < 4; i++) { + menu_items[i] = furi_string_alloc(); + }*/ + + main_menu_items[0] = furi_string_alloc_set("Default Values"); + main_menu_items[1] = furi_string_alloc_set("BF Customer ID"); + main_menu_items[2] = furi_string_alloc_set("Load File"); + main_menu_items[3] = furi_string_alloc_set("Load UIDs from file"); + + context->menu_proto_index = 0; + /*for(uint32_t i = 0; i < 4; i++) { + menu_proto_items[i] = furi_string_alloc(); + }*/ + + main_menu_proto_items[0] = furi_string_alloc_set("EM4100"); + main_menu_proto_items[1] = furi_string_alloc_set("HIDProx"); + main_menu_proto_items[2] = furi_string_alloc_set("PAC/Stanley"); + main_menu_proto_items[3] = furi_string_alloc_set("H10301"); +} + +void flipfrid_scene_entrypoint_on_exit(FlipFridState* context) { + UNUSED(context); + for(uint32_t i = 0; i < 4; i++) { + furi_string_free(main_menu_items[i]); + } + + for(uint32_t i = 0; i < 4; i++) { + furi_string_free(main_menu_proto_items[i]); + } +} + +void flipfrid_scene_entrypoint_on_tick(FlipFridState* context) { + UNUSED(context); +} + +void flipfrid_scene_entrypoint_on_event(FlipFridEvent event, FlipFridState* context) { + if(event.evt_type == EventTypeKey) { + if(event.input_type == InputTypeShort) { + switch(event.key) { + case InputKeyDown: + if(context->menu_index < FlipFridAttackLoadFileCustomUids) { + context->menu_index++; + } + break; + case InputKeyUp: + if(context->menu_index > FlipFridAttackDefaultValues) { + context->menu_index--; + } + break; + case InputKeyLeft: + if(context->menu_proto_index > EM4100) { + context->menu_proto_index--; + } else if(context->menu_proto_index == EM4100) { + context->menu_proto_index = H10301; + } + break; + case InputKeyRight: + if(context->menu_proto_index < H10301) { + context->menu_proto_index++; + } else if(context->menu_proto_index == H10301) { + context->menu_proto_index = EM4100; + } + break; + case InputKeyOk: + flipfrid_scene_entrypoint_menu_callback( + context, context->menu_index, context->menu_proto_index); + break; + case InputKeyBack: + context->is_running = false; + break; + default: + break; + } + } + } +} + +void flipfrid_scene_entrypoint_on_draw(Canvas* canvas, FlipFridState* context) { + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + + if(main_menu_items[context->menu_index] != NULL) { + if(context->menu_index > FlipFridAttackDefaultValues) { + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned( + canvas, + 64, + 24, + AlignCenter, + AlignTop, + furi_string_get_cstr(main_menu_items[context->menu_index - 1])); + } + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned( + canvas, + 64, + 36, + AlignCenter, + AlignTop, + furi_string_get_cstr(main_menu_items[context->menu_index])); + + if(context->menu_index < FlipFridAttackLoadFileCustomUids) { + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned( + canvas, + 64, + 48, + AlignCenter, + AlignTop, + furi_string_get_cstr(main_menu_items[context->menu_index + 1])); + } + + if(context->menu_proto_index > EM4100) { + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned( + canvas, + 64, + -12, + AlignCenter, + AlignTop, + furi_string_get_cstr(main_menu_proto_items[context->menu_proto_index - 1])); + } + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 27, 4, AlignCenter, AlignTop, "<"); + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned( + canvas, + 64, + 4, + AlignCenter, + AlignTop, + furi_string_get_cstr(main_menu_proto_items[context->menu_proto_index])); + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 101, 4, AlignCenter, AlignTop, ">"); + + if(context->menu_proto_index < H10301) { + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned( + canvas, + 64, + -12, + AlignCenter, + AlignTop, + furi_string_get_cstr(main_menu_proto_items[context->menu_proto_index + 1])); + } + } +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipfrid/scene/flipfrid_scene_entrypoint.h b/Applications/Official/DEV_FW/source/flipfrid/scene/flipfrid_scene_entrypoint.h new file mode 100644 index 000000000..29ca5bdfa --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipfrid/scene/flipfrid_scene_entrypoint.h @@ -0,0 +1,8 @@ +#pragma once +#include "../flipfrid.h" + +void flipfrid_scene_entrypoint_on_enter(FlipFridState* context); +void flipfrid_scene_entrypoint_on_exit(FlipFridState* context); +void flipfrid_scene_entrypoint_on_tick(FlipFridState* context); +void flipfrid_scene_entrypoint_on_event(FlipFridEvent event, FlipFridState* context); +void flipfrid_scene_entrypoint_on_draw(Canvas* canvas, FlipFridState* context); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipfrid/scene/flipfrid_scene_load_custom_uids.c b/Applications/Official/DEV_FW/source/flipfrid/scene/flipfrid_scene_load_custom_uids.c new file mode 100644 index 000000000..e2e8b76a2 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipfrid/scene/flipfrid_scene_load_custom_uids.c @@ -0,0 +1,86 @@ +#include "flipfrid_scene_load_custom_uids.h" +#include "flipfrid_scene_run_attack.h" +#include "flipfrid_scene_entrypoint.h" +#include "RFID_Fuzzer_icons.h" + +#define LFRFID_UIDS_EXTENSION ".txt" +#define RFIDFUZZER_APP_PATH_FOLDER "/ext/rfidfuzzer" + +bool flipfrid_load_uids(FlipFridState* context, const char* file_path) { + bool result = false; + Storage* storage = furi_record_open(RECORD_STORAGE); + context->uids_stream = buffered_file_stream_alloc(storage); + result = + buffered_file_stream_open(context->uids_stream, file_path, FSAM_READ, FSOM_OPEN_EXISTING); + // Close if loading fails + if(!result) { + buffered_file_stream_close(context->uids_stream); + return false; + } + return result; +} + +bool flipfrid_load_custom_uids_from_file(FlipFridState* context) { + // Input events and views are managed by file_select + FuriString* uid_path; + uid_path = furi_string_alloc(); + furi_string_set(uid_path, RFIDFUZZER_APP_PATH_FOLDER); + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options(&browser_options, LFRFID_UIDS_EXTENSION, &I_125_10px); + browser_options.base_path = RFIDFUZZER_APP_PATH_FOLDER; + browser_options.hide_ext = false; + + bool res = dialog_file_browser_show(context->dialogs, uid_path, uid_path, &browser_options); + + if(res) { + res = flipfrid_load_uids(context, furi_string_get_cstr(uid_path)); + } + + furi_string_free(uid_path); + + return res; +} + +void flipfrid_scene_load_custom_uids_on_enter(FlipFridState* context) { + if(flipfrid_load_custom_uids_from_file(context)) { + // Force context loading + flipfrid_scene_run_attack_on_enter(context); + context->current_scene = SceneAttack; + } else { + flipfrid_scene_entrypoint_on_enter(context); + context->current_scene = SceneEntryPoint; + } +} + +void flipfrid_scene_load_custom_uids_on_exit(FlipFridState* context) { + UNUSED(context); +} + +void flipfrid_scene_load_custom_uids_on_tick(FlipFridState* context) { + UNUSED(context); +} + +void flipfrid_scene_load_custom_uids_on_event(FlipFridEvent event, FlipFridState* context) { + if(event.evt_type == EventTypeKey) { + if(event.input_type == InputTypeShort) { + switch(event.key) { + case InputKeyDown: + case InputKeyUp: + case InputKeyLeft: + case InputKeyRight: + case InputKeyOk: + case InputKeyBack: + context->current_scene = SceneEntryPoint; + break; + default: + break; + } + } + } +} + +void flipfrid_scene_load_custom_uids_on_draw(Canvas* canvas, FlipFridState* context) { + UNUSED(context); + UNUSED(canvas); +} diff --git a/Applications/Official/DEV_FW/source/flipfrid/scene/flipfrid_scene_load_custom_uids.h b/Applications/Official/DEV_FW/source/flipfrid/scene/flipfrid_scene_load_custom_uids.h new file mode 100644 index 000000000..a8ed982b6 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipfrid/scene/flipfrid_scene_load_custom_uids.h @@ -0,0 +1,9 @@ +#pragma once +#include "../flipfrid.h" + +void flipfrid_scene_load_custom_uids_on_enter(FlipFridState* context); +void flipfrid_scene_load_custom_uids_on_exit(FlipFridState* context); +void flipfrid_scene_load_custom_uids_on_tick(FlipFridState* context); +void flipfrid_scene_load_custom_uids_on_event(FlipFridEvent event, FlipFridState* context); +void flipfrid_scene_load_custom_uids_on_draw(Canvas* canvas, FlipFridState* context); +bool flipfrid_load_custom_uids_from_file(FlipFridState* context); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipfrid/scene/flipfrid_scene_load_file.c b/Applications/Official/DEV_FW/source/flipfrid/scene/flipfrid_scene_load_file.c new file mode 100644 index 000000000..ce6a40b11 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipfrid/scene/flipfrid_scene_load_file.c @@ -0,0 +1,194 @@ +#include "flipfrid_scene_load_file.h" +#include "flipfrid_scene_entrypoint.h" +#include "RFID_Fuzzer_icons.h" + +#define LFRFID_APP_EXTENSION ".rfid" +#define LFRFID_APP_PATH_FOLDER "/ext/lfrfid" + +bool flipfrid_load(FlipFridState* context, const char* file_path) { + bool result = false; + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); + FuriString* temp_str; + temp_str = furi_string_alloc(); + do { + if(!flipper_format_file_open_existing(fff_data_file, file_path)) { + FURI_LOG_E(TAG, "Error open file %s", file_path); + furi_string_reset(context->notification_msg); + furi_string_set(context->notification_msg, "Error open file"); + break; + } + + // FileType + if(!flipper_format_read_string(fff_data_file, "Filetype", temp_str)) { + FURI_LOG_E(TAG, "Missing or incorrect Filetype"); + furi_string_reset(context->notification_msg); + furi_string_set(context->notification_msg, "Missing or incorrect Filetypes"); + break; + } else { + FURI_LOG_I(TAG, "Filetype: %s", furi_string_get_cstr(temp_str)); + } + + // Key type + if(!flipper_format_read_string(fff_data_file, "Key type", temp_str)) { + FURI_LOG_E(TAG, "Missing or incorrect Key type"); + furi_string_reset(context->notification_msg); + furi_string_set(context->notification_msg, "Missing or incorrect Key type"); + break; + } else { + FURI_LOG_I(TAG, "Key type: %s", furi_string_get_cstr(temp_str)); + + if(context->proto == EM4100) { + if(strcmp(furi_string_get_cstr(temp_str), "EM4100") != 0) { + FURI_LOG_E(TAG, "Unsupported Key type"); + furi_string_reset(context->notification_msg); + furi_string_set(context->notification_msg, "Unsupported Key type"); + break; + } + } else if(context->proto == PAC) { + if(strcmp(furi_string_get_cstr(temp_str), "PAC/Stanley") != 0) { + FURI_LOG_E(TAG, "Unsupported Key type"); + furi_string_reset(context->notification_msg); + furi_string_set(context->notification_msg, "Unsupported Key type"); + break; + } + } else if(context->proto == H10301) { + if(strcmp(furi_string_get_cstr(temp_str), "H10301") != 0) { + FURI_LOG_E(TAG, "Unsupported Key type"); + furi_string_reset(context->notification_msg); + furi_string_set(context->notification_msg, "Unsupported Key type"); + break; + } + } else { + if(strcmp(furi_string_get_cstr(temp_str), "HIDProx") != 0) { + FURI_LOG_E(TAG, "Unsupported Key type"); + furi_string_reset(context->notification_msg); + furi_string_set(context->notification_msg, "Unsupported Key type"); + break; + } + } + } + + // Data + if(!flipper_format_read_string(fff_data_file, "Data", context->data_str)) { + FURI_LOG_E(TAG, "Missing or incorrect Data"); + furi_string_reset(context->notification_msg); + furi_string_set(context->notification_msg, "Missing or incorrect Key"); + break; + } else { + FURI_LOG_I(TAG, "Key: %s", furi_string_get_cstr(context->data_str)); + + if(context->proto == EM4100) { + if(furi_string_size(context->data_str) != 14) { + FURI_LOG_E(TAG, "Incorrect Key length"); + furi_string_reset(context->notification_msg); + furi_string_set(context->notification_msg, "Incorrect Key length"); + break; + } + } else if(context->proto == PAC) { + if(furi_string_size(context->data_str) != 11) { + FURI_LOG_E(TAG, "Incorrect Key length"); + furi_string_reset(context->notification_msg); + furi_string_set(context->notification_msg, "Incorrect Key length"); + break; + } + } else if(context->proto == H10301) { + if(furi_string_size(context->data_str) != 8) { + FURI_LOG_E(TAG, "Incorrect Key length"); + furi_string_reset(context->notification_msg); + furi_string_set(context->notification_msg, "Incorrect Key length"); + break; + } + } else { + if(furi_string_size(context->data_str) != 17) { + FURI_LOG_E(TAG, "Incorrect Key length"); + furi_string_reset(context->notification_msg); + furi_string_set(context->notification_msg, "Incorrect Key length"); + break; + } + } + + // String to uint8_t + for(uint8_t i = 0; i < 6; i++) { + char temp_str2[3]; + temp_str2[0] = furi_string_get_cstr(context->data_str)[i * 3]; + temp_str2[1] = furi_string_get_cstr(context->data_str)[i * 3 + 1]; + temp_str2[2] = '\0'; + context->data[i] = (uint8_t)strtol(temp_str2, NULL, 16); + } + } + + result = true; + } while(0); + furi_string_free(temp_str); + flipper_format_free(fff_data_file); + if(result) { + FURI_LOG_I(TAG, "Loaded successfully"); + furi_string_reset(context->notification_msg); + furi_string_set(context->notification_msg, "Source loaded."); + } + return result; +} + +void flipfrid_scene_load_file_on_enter(FlipFridState* context) { + if(flipfrid_load_protocol_from_file(context)) { + context->current_scene = SceneSelectField; + } else { + flipfrid_scene_entrypoint_on_enter(context); + context->current_scene = SceneEntryPoint; + } +} + +void flipfrid_scene_load_file_on_exit(FlipFridState* context) { + UNUSED(context); +} + +void flipfrid_scene_load_file_on_tick(FlipFridState* context) { + UNUSED(context); +} + +void flipfrid_scene_load_file_on_event(FlipFridEvent event, FlipFridState* context) { + if(event.evt_type == EventTypeKey) { + if(event.input_type == InputTypeShort) { + switch(event.key) { + case InputKeyDown: + case InputKeyUp: + case InputKeyLeft: + case InputKeyRight: + case InputKeyOk: + case InputKeyBack: + context->current_scene = SceneEntryPoint; + break; + default: + break; + } + } + } +} + +void flipfrid_scene_load_file_on_draw(Canvas* canvas, FlipFridState* context) { + UNUSED(context); + UNUSED(canvas); +} + +bool flipfrid_load_protocol_from_file(FlipFridState* context) { + FuriString* user_file_path; + user_file_path = furi_string_alloc(); + furi_string_set(user_file_path, LFRFID_APP_PATH_FOLDER); + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options(&browser_options, LFRFID_APP_EXTENSION, &I_125_10px); + browser_options.base_path = LFRFID_APP_PATH_FOLDER; + + // Input events and views are managed by file_select + bool res = dialog_file_browser_show( + context->dialogs, user_file_path, user_file_path, &browser_options); + + if(res) { + res = flipfrid_load(context, furi_string_get_cstr(user_file_path)); + } + + furi_string_free(user_file_path); + + return res; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipfrid/scene/flipfrid_scene_load_file.h b/Applications/Official/DEV_FW/source/flipfrid/scene/flipfrid_scene_load_file.h new file mode 100644 index 000000000..ca82daab4 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipfrid/scene/flipfrid_scene_load_file.h @@ -0,0 +1,9 @@ +#pragma once +#include "../flipfrid.h" + +void flipfrid_scene_load_file_on_enter(FlipFridState* context); +void flipfrid_scene_load_file_on_exit(FlipFridState* context); +void flipfrid_scene_load_file_on_tick(FlipFridState* context); +void flipfrid_scene_load_file_on_event(FlipFridEvent event, FlipFridState* context); +void flipfrid_scene_load_file_on_draw(Canvas* canvas, FlipFridState* context); +bool flipfrid_load_protocol_from_file(FlipFridState* context); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipfrid/scene/flipfrid_scene_run_attack.c b/Applications/Official/DEV_FW/source/flipfrid/scene/flipfrid_scene_run_attack.c new file mode 100644 index 000000000..6c726832a --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipfrid/scene/flipfrid_scene_run_attack.c @@ -0,0 +1,668 @@ +#include "flipfrid_scene_run_attack.h" +#include + +uint8_t counter = 0; + +uint8_t id_list[17][5] = { + {0x00, 0x00, 0x00, 0x00, 0x00}, // Null bytes + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, // Only FF + {0x11, 0x11, 0x11, 0x11, 0x11}, // Only 11 + {0x22, 0x22, 0x22, 0x22, 0x22}, // Only 22 + {0x33, 0x33, 0x33, 0x33, 0x33}, // Only 33 + {0x44, 0x44, 0x44, 0x44, 0x44}, // Only 44 + {0x55, 0x55, 0x55, 0x55, 0x55}, // Only 55 + {0x66, 0x66, 0x66, 0x66, 0x66}, // Only 66 + {0x77, 0x77, 0x77, 0x77, 0x77}, // Only 77 + {0x88, 0x88, 0x88, 0x88, 0x88}, // Only 88 + {0x99, 0x99, 0x99, 0x99, 0x99}, // Only 99 + {0x12, 0x34, 0x56, 0x78, 0x9A}, // Incremental UID + {0x9A, 0x78, 0x56, 0x34, 0x12}, // Decremental UID + {0x04, 0xd0, 0x9b, 0x0d, 0x6a}, // From arha + {0x34, 0x00, 0x29, 0x3d, 0x9e}, // From arha + {0x04, 0xdf, 0x00, 0x00, 0x01}, // From arha + {0xCA, 0xCA, 0xCA, 0xCA, 0xCA}, // From arha +}; + +uint8_t id_list_hid[14][6] = { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // Null bytes + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, // Only FF + {0x11, 0x11, 0x11, 0x11, 0x11, 0x11}, // Only 11 + {0x22, 0x22, 0x22, 0x22, 0x22, 0x22}, // Only 22 + {0x33, 0x33, 0x33, 0x33, 0x33, 0x33}, // Only 33 + {0x44, 0x44, 0x44, 0x44, 0x44, 0x44}, // Only 44 + {0x55, 0x55, 0x55, 0x55, 0x55, 0x55}, // Only 55 + {0x66, 0x66, 0x66, 0x66, 0x66, 0x66}, // Only 66 + {0x77, 0x77, 0x77, 0x77, 0x77, 0x77}, // Only 77 + {0x88, 0x88, 0x88, 0x88, 0x88, 0x88}, // Only 88 + {0x99, 0x99, 0x99, 0x99, 0x99, 0x99}, // Only 99 + {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC}, // Incremental UID + {0xBC, 0x9A, 0x78, 0x56, 0x34, 0x12}, // Decremental UID + {0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA}, // From arha +}; + +uint8_t id_list_pac[17][4] = { + {0x00, 0x00, 0x00, 0x00}, // Null bytes + {0xFF, 0xFF, 0xFF, 0xFF}, // Only FF + {0x11, 0x11, 0x11, 0x11}, // Only 11 + {0x22, 0x22, 0x22, 0x22}, // Only 22 + {0x33, 0x33, 0x33, 0x33}, // Only 33 + {0x44, 0x44, 0x44, 0x44}, // Only 44 + {0x55, 0x55, 0x55, 0x55}, // Only 55 + {0x66, 0x66, 0x66, 0x66}, // Only 66 + {0x77, 0x77, 0x77, 0x77}, // Only 77 + {0x88, 0x88, 0x88, 0x88}, // Only 88 + {0x99, 0x99, 0x99, 0x99}, // Only 99 + {0x12, 0x34, 0x56, 0x78}, // Incremental UID + {0x9A, 0x78, 0x56, 0x34}, // Decremental UID + {0x04, 0xd0, 0x9b, 0x0d}, // From arha + {0x34, 0x00, 0x29, 0x3d}, // From arha + {0x04, 0xdf, 0x00, 0x00}, // From arha + {0xCA, 0xCA, 0xCA, 0xCA}, // From arha +}; + +uint8_t id_list_h[14][3] = { + {0x00, 0x00, 0x00}, // Null bytes + {0xFF, 0xFF, 0xFF}, // Only FF + {0x11, 0x11, 0x11}, // Only 11 + {0x22, 0x22, 0x22}, // Only 22 + {0x33, 0x33, 0x33}, // Only 33 + {0x44, 0x44, 0x44}, // Only 44 + {0x55, 0x55, 0x55}, // Only 55 + {0x66, 0x66, 0x66}, // Only 66 + {0x77, 0x77, 0x77}, // Only 77 + {0x88, 0x88, 0x88}, // Only 88 + {0x99, 0x99, 0x99}, // Only 99 + {0x12, 0x34, 0x56}, // Incremental UID + {0x56, 0x34, 0x12}, // Decremental UID + {0xCA, 0xCA, 0xCA}, // From arha +}; + +void flipfrid_scene_run_attack_on_enter(FlipFridState* context) { + context->time_between_cards = 10; + context->attack_step = 0; + context->attack_stop_called = false; + context->dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); + context->worker = lfrfid_worker_alloc(context->dict); + if(context->proto == HIDProx) { + context->protocol = protocol_dict_get_protocol_by_name(context->dict, "HIDProx"); + } else if(context->proto == PAC) { + context->protocol = protocol_dict_get_protocol_by_name(context->dict, "PAC/Stanley"); + } else if(context->proto == H10301) { + context->protocol = protocol_dict_get_protocol_by_name(context->dict, "H10301"); + } else { + context->protocol = protocol_dict_get_protocol_by_name(context->dict, "EM4100"); + } +} + +void flipfrid_scene_run_attack_on_exit(FlipFridState* context) { + if(context->workr_rund) { + lfrfid_worker_stop(context->worker); + lfrfid_worker_stop_thread(context->worker); + context->workr_rund = false; + } + lfrfid_worker_free(context->worker); + protocol_dict_free(context->dict); + notification_message(context->notify, &sequence_blink_stop); +} + +void flipfrid_scene_run_attack_on_tick(FlipFridState* context) { + if(context->is_attacking) { + if(1 == counter) { + protocol_dict_set_data(context->dict, context->protocol, context->payload, 6); + lfrfid_worker_free(context->worker); + context->worker = lfrfid_worker_alloc(context->dict); + lfrfid_worker_start_thread(context->worker); + lfrfid_worker_emulate_start(context->worker, context->protocol); + context->workr_rund = true; + } else if(0 == counter) { + if(context->workr_rund) { + lfrfid_worker_stop(context->worker); + lfrfid_worker_stop_thread(context->worker); + context->workr_rund = false; + furi_delay_ms(200); + } + switch(context->attack) { + case FlipFridAttackDefaultValues: + if(context->proto == EM4100) { + context->payload[0] = id_list[context->attack_step][0]; + context->payload[1] = id_list[context->attack_step][1]; + context->payload[2] = id_list[context->attack_step][2]; + context->payload[3] = id_list[context->attack_step][3]; + context->payload[4] = id_list[context->attack_step][4]; + + if(context->attack_step == 16) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_single_vibro); + } else { + context->attack_step++; + } + break; + } else if(context->proto == PAC) { + context->payload[0] = id_list_pac[context->attack_step][0]; + context->payload[1] = id_list_pac[context->attack_step][1]; + context->payload[2] = id_list_pac[context->attack_step][2]; + context->payload[3] = id_list_pac[context->attack_step][3]; + + if(context->attack_step == 16) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_single_vibro); + } else { + context->attack_step++; + } + break; + } else if(context->proto == H10301) { + context->payload[0] = id_list_h[context->attack_step][0]; + context->payload[1] = id_list_h[context->attack_step][1]; + context->payload[2] = id_list_h[context->attack_step][2]; + + if(context->attack_step == 13) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_single_vibro); + } else { + context->attack_step++; + } + break; + } else { + context->payload[0] = id_list_hid[context->attack_step][0]; + context->payload[1] = id_list_hid[context->attack_step][1]; + context->payload[2] = id_list_hid[context->attack_step][2]; + context->payload[3] = id_list_hid[context->attack_step][3]; + context->payload[4] = id_list_hid[context->attack_step][4]; + context->payload[5] = id_list_hid[context->attack_step][5]; + + if(context->attack_step == 13) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_single_vibro); + + } else { + context->attack_step++; + } + break; + } + + case FlipFridAttackBfCustomerId: + if(context->proto == EM4100) { + context->payload[0] = context->attack_step; + context->payload[1] = 0x00; + context->payload[2] = 0x00; + context->payload[3] = 0x00; + context->payload[4] = 0x00; + + if(context->attack_step == 255) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_single_vibro); + } else { + context->attack_step++; + } + break; + } else if(context->proto == PAC) { + context->payload[0] = context->attack_step; + context->payload[1] = 0x00; + context->payload[2] = 0x00; + context->payload[3] = 0x00; + + if(context->attack_step == 255) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_single_vibro); + } else { + context->attack_step++; + } + break; + } else if(context->proto == H10301) { + context->payload[0] = context->attack_step; + context->payload[1] = 0x00; + context->payload[2] = 0x00; + + if(context->attack_step == 255) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_single_vibro); + } else { + context->attack_step++; + } + break; + } else { + context->payload[0] = context->attack_step; + context->payload[1] = 0x00; + context->payload[2] = 0x00; + context->payload[3] = 0x00; + context->payload[4] = 0x00; + context->payload[5] = 0x00; + + if(context->attack_step == 255) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_single_vibro); + } else { + context->attack_step++; + } + break; + } + + case FlipFridAttackLoadFile: + if(context->proto == EM4100) { + context->payload[0] = context->data[0]; + context->payload[1] = context->data[1]; + context->payload[2] = context->data[2]; + context->payload[3] = context->data[3]; + context->payload[4] = context->data[4]; + + context->payload[context->key_index] = context->attack_step; + + if(context->attack_step == 255) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_single_vibro); + break; + } else { + context->attack_step++; + } + break; + } else if(context->proto == PAC) { + context->payload[0] = context->data[0]; + context->payload[1] = context->data[1]; + context->payload[2] = context->data[2]; + context->payload[3] = context->data[3]; + + context->payload[context->key_index] = context->attack_step; + + if(context->attack_step == 255) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_single_vibro); + break; + } else { + context->attack_step++; + } + break; + } else if(context->proto == H10301) { + context->payload[0] = context->data[0]; + context->payload[1] = context->data[1]; + context->payload[2] = context->data[2]; + + context->payload[context->key_index] = context->attack_step; + + if(context->attack_step == 255) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_single_vibro); + break; + } else { + context->attack_step++; + } + break; + } else { + context->payload[0] = context->data[0]; + context->payload[1] = context->data[1]; + context->payload[2] = context->data[2]; + context->payload[3] = context->data[3]; + context->payload[4] = context->data[4]; + context->payload[5] = context->data[5]; + + context->payload[context->key_index] = context->attack_step; + + if(context->attack_step == 255) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_single_vibro); + break; + } else { + context->attack_step++; + } + break; + } + + case FlipFridAttackLoadFileCustomUids: + if(context->proto == EM4100) { + bool end_of_list = false; + while(true) { + furi_string_reset(context->data_str); + if(!stream_read_line(context->uids_stream, context->data_str)) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_single_vibro); + stream_rewind(context->uids_stream); + end_of_list = true; + break; + }; + if(furi_string_get_char(context->data_str, 0) == '#') continue; + if(furi_string_size(context->data_str) != 11) break; + break; + } + if(end_of_list) break; + FURI_LOG_D(TAG, furi_string_get_cstr(context->data_str)); + if(furi_string_size(context->data_str) != 11) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_error); + break; + }; + + // string is valid, parse it in context->payload + for(uint8_t i = 0; i < 5; i++) { + char temp_str[3]; + temp_str[0] = furi_string_get_cstr(context->data_str)[i * 2]; + temp_str[1] = furi_string_get_cstr(context->data_str)[i * 2 + 1]; + temp_str[2] = '\0'; + context->payload[i] = (uint8_t)strtol(temp_str, NULL, 16); + } + break; + } else if(context->proto == PAC) { + bool end_of_list = false; + while(true) { + furi_string_reset(context->data_str); + if(!stream_read_line(context->uids_stream, context->data_str)) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_single_vibro); + stream_rewind(context->uids_stream); + end_of_list = true; + break; + }; + if(furi_string_get_char(context->data_str, 0) == '#') continue; + if(furi_string_size(context->data_str) != 9) break; + break; + } + if(end_of_list) break; + FURI_LOG_D(TAG, furi_string_get_cstr(context->data_str)); + if(furi_string_size(context->data_str) != 9) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_error); + break; + }; + + // string is valid, parse it in context->payload + for(uint8_t i = 0; i < 4; i++) { + char temp_str[3]; + temp_str[0] = furi_string_get_cstr(context->data_str)[i * 2]; + temp_str[1] = furi_string_get_cstr(context->data_str)[i * 2 + 1]; + temp_str[2] = '\0'; + context->payload[i] = (uint8_t)strtol(temp_str, NULL, 16); + } + break; + } else if(context->proto == H10301) { + bool end_of_list = false; + while(true) { + furi_string_reset(context->data_str); + if(!stream_read_line(context->uids_stream, context->data_str)) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_single_vibro); + stream_rewind(context->uids_stream); + end_of_list = true; + break; + }; + if(furi_string_get_char(context->data_str, 0) == '#') continue; + if(furi_string_size(context->data_str) != 7) break; + break; + } + if(end_of_list) break; + FURI_LOG_D(TAG, furi_string_get_cstr(context->data_str)); + if(furi_string_size(context->data_str) != 7) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_error); + break; + }; + + // string is valid, parse it in context->payload + for(uint8_t i = 0; i < 3; i++) { + char temp_str[3]; + temp_str[0] = furi_string_get_cstr(context->data_str)[i * 2]; + temp_str[1] = furi_string_get_cstr(context->data_str)[i * 2 + 1]; + temp_str[2] = '\0'; + context->payload[i] = (uint8_t)strtol(temp_str, NULL, 16); + } + break; + } else { + bool end_of_list = false; + while(true) { + furi_string_reset(context->data_str); + if(!stream_read_line(context->uids_stream, context->data_str)) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_single_vibro); + stream_rewind(context->uids_stream); + end_of_list = true; + break; + }; + if(furi_string_get_char(context->data_str, 0) == '#') continue; + if(furi_string_size(context->data_str) != 13) break; + break; + } + FURI_LOG_D(TAG, furi_string_get_cstr(context->data_str)); + if(end_of_list) break; + if(furi_string_size(context->data_str) != 13) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_error); + break; + }; + + // string is valid, parse it in context->payload + for(uint8_t i = 0; i < 6; i++) { + char temp_str[3]; + temp_str[0] = furi_string_get_cstr(context->data_str)[i * 2]; + temp_str[1] = furi_string_get_cstr(context->data_str)[i * 2 + 1]; + temp_str[2] = '\0'; + context->payload[i] = (uint8_t)strtol(temp_str, NULL, 16); + } + break; + default: + break; + } + } + } + if(counter > context->time_between_cards) { + counter = 0; + } else { + counter++; + } + } +} + +void flipfrid_scene_run_attack_on_event(FlipFridEvent event, FlipFridState* context) { + if(event.evt_type == EventTypeKey) { + if(event.input_type == InputTypeShort) { + switch(event.key) { + case InputKeyDown: + break; + case InputKeyUp: + break; + case InputKeyLeft: + if(!context->is_attacking) { + if(context->time_between_cards > 5) { + context->time_between_cards--; + } + } + break; + case InputKeyRight: + if(!context->is_attacking) { + if(context->time_between_cards < 70) { + context->time_between_cards++; + } + } + break; + case InputKeyOk: + counter = 0; + if(!context->is_attacking) { + notification_message(context->notify, &sequence_blink_start_blue); + context->is_attacking = true; + } else { + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_single_vibro); + } + break; + case InputKeyBack: + context->is_attacking = false; + counter = 0; + + notification_message(context->notify, &sequence_blink_stop); + if(context->attack_stop_called) { + context->attack_stop_called = false; + context->attack_step = 0; + if(context->attack == FlipFridAttackLoadFileCustomUids) { + furi_string_reset(context->data_str); + stream_rewind(context->uids_stream); + buffered_file_stream_close(context->uids_stream); + } + + furi_string_reset(context->notification_msg); + context->current_scene = SceneEntryPoint; + } + + context->attack_stop_called = true; + break; + default: + break; + } + } + if(event.input_type == InputTypeLong) { + switch(event.key) { + case InputKeyLeft: + if(!context->is_attacking) { + if(context->time_between_cards > 0) { + if((context->time_between_cards - 10) > 5) { + context->time_between_cards -= 10; + } + } + } + break; + case InputKeyRight: + if(!context->is_attacking) { + if(context->time_between_cards < 70) { + context->time_between_cards += 10; + } + } + break; + default: + break; + } + } + } +} + +void flipfrid_scene_run_attack_on_draw(Canvas* canvas, FlipFridState* context) { + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + + // Frame + //canvas_draw_frame(canvas, 0, 0, 128, 64); + + // Title + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned( + canvas, 64, 2, AlignCenter, AlignTop, furi_string_get_cstr(context->attack_name)); + + char uid[18]; + char speed[16]; + if(context->proto == HIDProx) { + snprintf( + uid, + sizeof(uid), + "%02X:%02X:%02X:%02X:%02X:%02X", + context->payload[0], + context->payload[1], + context->payload[2], + context->payload[3], + context->payload[4], + context->payload[5]); + } else if(context->proto == PAC) { + snprintf( + uid, + sizeof(uid), + "%02X:%02X:%02X:%02X", + context->payload[0], + context->payload[1], + context->payload[2], + context->payload[3]); + } else if(context->proto == H10301) { + snprintf( + uid, + sizeof(uid), + "%02X:%02X:%02X", + context->payload[0], + context->payload[1], + context->payload[2]); + } else { + snprintf( + uid, + sizeof(uid), + "%02X:%02X:%02X:%02X:%02X", + context->payload[0], + context->payload[1], + context->payload[2], + context->payload[3], + context->payload[4]); + } + + canvas_draw_str_aligned(canvas, 64, 38, AlignCenter, AlignTop, uid); + + canvas_set_font(canvas, FontSecondary); + + canvas_draw_str_aligned( + canvas, 64, 26, AlignCenter, AlignTop, furi_string_get_cstr(context->proto_name)); + + snprintf(speed, sizeof(speed), "Time delay: %d", context->time_between_cards); + + //canvas_draw_str_aligned(canvas, 0, 22, AlignLeft, AlignTop, "Speed:"); + canvas_draw_str_aligned(canvas, 64, 14, AlignCenter, AlignTop, speed); + //char start_stop_msg[20]; + if(context->is_attacking) { + elements_button_center(canvas, "Stop"); + //snprintf(start_stop_msg, sizeof(start_stop_msg), " Press OK to stop "); + } else { + elements_button_center(canvas, "Start"); + elements_button_left(canvas, "TD -"); + elements_button_right(canvas, "+ TD"); + } + //canvas_draw_str_aligned(canvas, 64, 44, AlignCenter, AlignTop, start_stop_msg); +} diff --git a/Applications/Official/DEV_FW/source/flipfrid/scene/flipfrid_scene_run_attack.h b/Applications/Official/DEV_FW/source/flipfrid/scene/flipfrid_scene_run_attack.h new file mode 100644 index 000000000..ae56d35e7 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipfrid/scene/flipfrid_scene_run_attack.h @@ -0,0 +1,8 @@ +#pragma once +#include "../flipfrid.h" + +void flipfrid_scene_run_attack_on_enter(FlipFridState* context); +void flipfrid_scene_run_attack_on_exit(FlipFridState* context); +void flipfrid_scene_run_attack_on_tick(FlipFridState* context); +void flipfrid_scene_run_attack_on_event(FlipFridEvent event, FlipFridState* context); +void flipfrid_scene_run_attack_on_draw(Canvas* canvas, FlipFridState* context); diff --git a/Applications/Official/DEV_FW/source/flipfrid/scene/flipfrid_scene_select_field.c b/Applications/Official/DEV_FW/source/flipfrid/scene/flipfrid_scene_select_field.c new file mode 100644 index 000000000..ccb49e910 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipfrid/scene/flipfrid_scene_select_field.c @@ -0,0 +1,160 @@ +#include "flipfrid_scene_select_field.h" + +void flipfrid_center_displayed_key(FlipFridState* context, uint8_t index) { + char key_cstr[18]; + uint8_t key_len = 18; + uint8_t str_index = (index * 3); + int data_len = sizeof(context->data) / sizeof(context->data[0]); + int key_index = 0; + + if(context->proto == EM4100) { + key_len = 16; + } + if(context->proto == PAC) { + key_len = 13; + } + if(context->proto == H10301) { + key_len = 10; + } + + for(uint8_t i = 0; i < data_len; i++) { + if(context->data[i] < 9) { + key_index += + snprintf(&key_cstr[key_index], key_len - key_index, "0%X ", context->data[i]); + } else { + key_index += + snprintf(&key_cstr[key_index], key_len - key_index, "%X ", context->data[i]); + } + } + + char display_menu[17] = { + 'X', 'X', ' ', 'X', 'X', ' ', '<', 'X', 'X', '>', ' ', 'X', 'X', ' ', 'X', 'X', '\0'}; + + if(index > 1) { + display_menu[0] = key_cstr[str_index - 6]; + display_menu[1] = key_cstr[str_index - 5]; + } else { + display_menu[0] = ' '; + display_menu[1] = ' '; + } + + if(index > 0) { + display_menu[3] = key_cstr[str_index - 3]; + display_menu[4] = key_cstr[str_index - 2]; + } else { + display_menu[3] = ' '; + display_menu[4] = ' '; + } + + display_menu[7] = key_cstr[str_index]; + display_menu[8] = key_cstr[str_index + 1]; + + if((str_index + 4) <= (uint8_t)strlen(key_cstr)) { + display_menu[11] = key_cstr[str_index + 3]; + display_menu[12] = key_cstr[str_index + 4]; + } else { + display_menu[11] = ' '; + display_menu[12] = ' '; + } + + if((str_index + 8) <= (uint8_t)strlen(key_cstr)) { + display_menu[14] = key_cstr[str_index + 6]; + display_menu[15] = key_cstr[str_index + 7]; + } else { + display_menu[14] = ' '; + display_menu[15] = ' '; + } + + furi_string_reset(context->notification_msg); + furi_string_set(context->notification_msg, display_menu); +} + +void flipfrid_scene_select_field_on_enter(FlipFridState* context) { + furi_string_reset(context->notification_msg); +} + +void flipfrid_scene_select_field_on_exit(FlipFridState* context) { + UNUSED(context); +} + +void flipfrid_scene_select_field_on_tick(FlipFridState* context) { + UNUSED(context); +} + +void flipfrid_scene_select_field_on_event(FlipFridEvent event, FlipFridState* context) { + if(event.evt_type == EventTypeKey) { + if(event.input_type == InputTypeShort) { + const char* key_cstr = furi_string_get_cstr(context->data_str); + int data_len = sizeof(context->data) / sizeof(context->data[0]); + + // don't look, it's ugly but I'm a python dev so... + uint8_t nb_bytes = 0; + for(uint8_t i = 0; i < strlen(key_cstr); i++) { + if(' ' == key_cstr[i]) { + nb_bytes++; + } + } + + switch(event.key) { + case InputKeyDown: + for(uint8_t i = 0; i < data_len; i++) { + if(context->key_index == i) { + context->data[i] = (context->data[i] - 1); + } + } + break; + case InputKeyUp: + for(uint8_t i = 0; i < data_len; i++) { + if(context->key_index == i) { + context->data[i] = (context->data[i] + 1); + } + } + break; + case InputKeyLeft: + if(context->key_index > 0) { + context->key_index = context->key_index - 1; + } + break; + case InputKeyRight: + if(context->key_index < nb_bytes) { + context->key_index = context->key_index + 1; + } + break; + case InputKeyOk: + furi_string_reset(context->notification_msg); + context->current_scene = SceneAttack; + break; + case InputKeyBack: + context->key_index = 0; + furi_string_reset(context->notification_msg); + context->current_scene = SceneSelectFile; + break; + default: + break; + } + FURI_LOG_D(TAG, "Position: %d/%d", context->key_index, nb_bytes); + } + } +} + +void flipfrid_scene_select_field_on_draw(Canvas* canvas, FlipFridState* context) { + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + + // Frame + //canvas_draw_frame(canvas, 0, 0, 128, 64); + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 12, 5, AlignLeft, AlignTop, "Left and right: select byte"); + canvas_draw_str_aligned(canvas, 12, 15, AlignLeft, AlignTop, "Up and down: adjust byte"); + + char msg_index[18]; + canvas_set_font(canvas, FontPrimary); + snprintf(msg_index, sizeof(msg_index), "Field index : %d", context->key_index); + canvas_draw_str_aligned(canvas, 64, 30, AlignCenter, AlignTop, msg_index); + + flipfrid_center_displayed_key(context, context->key_index); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned( + canvas, 64, 45, AlignCenter, AlignTop, furi_string_get_cstr(context->notification_msg)); +} diff --git a/Applications/Official/DEV_FW/source/flipfrid/scene/flipfrid_scene_select_field.h b/Applications/Official/DEV_FW/source/flipfrid/scene/flipfrid_scene_select_field.h new file mode 100644 index 000000000..5533e321c --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipfrid/scene/flipfrid_scene_select_field.h @@ -0,0 +1,9 @@ +#pragma once +#include "../flipfrid.h" + +void flipfrid_scene_select_field_on_enter(FlipFridState* context); +void flipfrid_scene_select_field_on_exit(FlipFridState* context); +void flipfrid_scene_select_field_on_tick(FlipFridState* context); +void flipfrid_scene_select_field_on_event(FlipFridEvent event, FlipFridState* context); +void flipfrid_scene_select_field_on_draw(Canvas* canvas, FlipFridState* context); +void center_displayed_key(FlipFridState* context, uint8_t index); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipper_i2ctools/.gitignore b/Applications/Official/DEV_FW/source/flipper_i2ctools/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/Applications/Official/DEV_FW/source/flipper_i2ctools/README.md b/Applications/Official/DEV_FW/source/flipper_i2ctools/README.md new file mode 100644 index 000000000..8ecf47c8c --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipper_i2ctools/README.md @@ -0,0 +1,43 @@ +# flipperzero-i2ctools + +Set of i2c tools for Flipper Zero + +![Preview](i2ctools.gif) + +## Wiring + +C0 -> SCL + +C1 -> SDA + +GND -> GND + +>/!\ Target must use 3v3 logic levels. If you not sure use an i2c isolator like ISO1541 + +## Tools + +### Scanner + +Look for i2c peripherals adresses + +### Sniffer + +Spy i2c traffic + +### Sender + +Send command to i2c peripherals and read result + +## TODO + +- [ ] Read more than 2 bytes in sender mode +- [ ] Add 10-bits adresses support +- [ ] Test with rate > 100khz +- [ ] Save records +- [ ] Play from files +- [ ] Kicad module +- [ ] Improve UI +- [ ] Refactor Event Management Code +- [ ] Add Documentation +- [ ] Remove max data size +- [ ] Remove max frames read size \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipper_i2ctools/application.fam b/Applications/Official/DEV_FW/source/flipper_i2ctools/application.fam new file mode 100644 index 000000000..7d9c5944c --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipper_i2ctools/application.fam @@ -0,0 +1,13 @@ +App( + appid="I2C_Tools", + name="i2c Tools", + apptype=FlipperAppType.EXTERNAL, + entry_point="i2ctools_app", + cdefines=["APP_I2CTOOLS"], + requires=["gui"], + stack_size=2 * 1024, + order=175, + fap_icon="i2ctools.png", + fap_category="GPIO", + fap_icon_assets="images", +) \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipper_i2ctools/i2cscanner.c b/Applications/Official/DEV_FW/source/flipper_i2ctools/i2cscanner.c new file mode 100644 index 000000000..dab618b78 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipper_i2ctools/i2cscanner.c @@ -0,0 +1,35 @@ +#include "i2cscanner.h" + +void scan_i2c_bus(i2cScanner* i2c_scanner) { + i2c_scanner->nb_found = 0; + i2c_scanner->scanned = true; + // Get the bus + furi_hal_i2c_acquire(I2C_BUS); + // scan + for(uint8_t addr = 0x01; addr <= MAX_I2C_ADDR << 1; addr++) { + // Check for peripherals + if(furi_hal_i2c_is_device_ready(I2C_BUS, addr, I2C_TIMEOUT)) { + // skip even 8-bit addr + if(addr % 2 != 0) { + continue; + } + // convert addr to 7-bits + i2c_scanner->addresses[i2c_scanner->nb_found] = addr >> 1; + i2c_scanner->nb_found++; + } + } + furi_hal_i2c_release(I2C_BUS); +} + +i2cScanner* i2c_scanner_alloc() { + i2cScanner* i2c_scanner = malloc(sizeof(i2cScanner)); + i2c_scanner->nb_found = 0; + i2c_scanner->menu_index = 0; + i2c_scanner->scanned = false; + return i2c_scanner; +} + +void i2c_scanner_free(i2cScanner* i2c_scanner) { + furi_assert(i2c_scanner); + free(i2c_scanner); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipper_i2ctools/i2cscanner.h b/Applications/Official/DEV_FW/source/flipper_i2ctools/i2cscanner.h new file mode 100644 index 000000000..5320ebb9e --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipper_i2ctools/i2cscanner.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +// I2C BUS +#define I2C_BUS &furi_hal_i2c_handle_external +#define I2C_TIMEOUT 3 + +// 7 bits addresses +#define MAX_I2C_ADDR 0x7F + +typedef struct { + uint8_t addresses[MAX_I2C_ADDR + 1]; + uint8_t nb_found; + uint8_t menu_index; + bool scanned; +} i2cScanner; + +void scan_i2c_bus(i2cScanner* i2c_scanner); + +i2cScanner* i2c_scanner_alloc(); +void i2c_scanner_free(i2cScanner* i2c_scanner); diff --git a/Applications/Official/DEV_FW/source/flipper_i2ctools/i2csender.c b/Applications/Official/DEV_FW/source/flipper_i2ctools/i2csender.c new file mode 100644 index 000000000..bdc98cd9e --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipper_i2ctools/i2csender.c @@ -0,0 +1,29 @@ +#include "i2csender.h" + +void i2c_send(i2cSender* i2c_sender) { + furi_hal_i2c_acquire(I2C_BUS); + uint8_t adress = i2c_sender->scanner->addresses[i2c_sender->address_idx] << 1; + i2c_sender->error = furi_hal_i2c_trx( + I2C_BUS, + adress, + &i2c_sender->value, + sizeof(i2c_sender->value), + i2c_sender->recv, + sizeof(i2c_sender->recv), + I2C_TIMEOUT); + furi_hal_i2c_release(I2C_BUS); + i2c_sender->must_send = false; + i2c_sender->sended = true; +} + +i2cSender* i2c_sender_alloc() { + i2cSender* i2c_sender = malloc(sizeof(i2cSender)); + i2c_sender->must_send = false; + i2c_sender->sended = false; + return i2c_sender; +} + +void i2c_sender_free(i2cSender* i2c_sender) { + furi_assert(i2c_sender); + free(i2c_sender); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipper_i2ctools/i2csender.h b/Applications/Official/DEV_FW/source/flipper_i2ctools/i2csender.h new file mode 100644 index 000000000..2aa74d6e2 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipper_i2ctools/i2csender.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include "i2cscanner.h" + +typedef struct { + uint8_t address_idx; + uint8_t value; + uint8_t recv[2]; + bool must_send; + bool sended; + bool error; + + i2cScanner* scanner; +} i2cSender; + +void i2c_send(); + +i2cSender* i2c_sender_alloc(); +void i2c_sender_free(i2cSender* i2c_sender); diff --git a/Applications/Official/DEV_FW/source/flipper_i2ctools/i2csniffer.c b/Applications/Official/DEV_FW/source/flipper_i2ctools/i2csniffer.c new file mode 100644 index 000000000..6a633cfaf --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipper_i2ctools/i2csniffer.c @@ -0,0 +1,101 @@ +#include "i2csniffer.h" + +void clear_sniffer_buffers(i2cSniffer* i2c_sniffer) { + furi_assert(i2c_sniffer); + for(uint8_t i = 0; i < MAX_RECORDS; i++) { + for(uint8_t j = 0; j < MAX_MESSAGE_SIZE; j++) { + i2c_sniffer->frames[i].ack[j] = false; + i2c_sniffer->frames[i].data[j] = 0; + } + i2c_sniffer->frames[i].bit_index = 0; + i2c_sniffer->frames[i].data_index = 0; + } + i2c_sniffer->frame_index = 0; + i2c_sniffer->state = I2C_BUS_FREE; + i2c_sniffer->first = true; +} + +void start_interrupts(i2cSniffer* i2c_sniffer) { + furi_assert(i2c_sniffer); + furi_hal_gpio_init(pinSCL, GpioModeInterruptRise, GpioPullNo, GpioSpeedHigh); + furi_hal_gpio_add_int_callback(pinSCL, SCLcallback, i2c_sniffer); + + // Add Rise and Fall Interrupt on SDA pin + furi_hal_gpio_init(pinSDA, GpioModeInterruptRiseFall, GpioPullNo, GpioSpeedHigh); + furi_hal_gpio_add_int_callback(pinSDA, SDAcallback, i2c_sniffer); +} + +void stop_interrupts() { + furi_hal_gpio_remove_int_callback(pinSCL); + furi_hal_gpio_remove_int_callback(pinSDA); + // Reset GPIO pins to default state + furi_hal_gpio_init(pinSCL, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(pinSDA, GpioModeAnalog, GpioPullNo, GpioSpeedLow); +} + +// Called on Fallin/Rising SDA +// Used to monitor i2c bus state +void SDAcallback(void* _i2c_sniffer) { + i2cSniffer* i2c_sniffer = _i2c_sniffer; + // SCL is low maybe cclock strecching + if(furi_hal_gpio_read(pinSCL) == false) { + return; + } + // Check for stop condition: SDA rising while SCL is High + if(i2c_sniffer->state == I2C_BUS_STARTED) { + if(furi_hal_gpio_read(pinSDA) == true) { + i2c_sniffer->state = I2C_BUS_FREE; + } + } + // Check for start condition: SDA falling while SCL is high + else if(furi_hal_gpio_read(pinSDA) == false) { + i2c_sniffer->state = I2C_BUS_STARTED; + if(i2c_sniffer->first) { + i2c_sniffer->first = false; + return; + } + i2c_sniffer->frame_index++; + if(i2c_sniffer->frame_index >= MAX_RECORDS) { + clear_sniffer_buffers(i2c_sniffer); + } + } + return; +} + +// Called on Rising SCL +// Used to read bus datas +void SCLcallback(void* _i2c_sniffer) { + i2cSniffer* i2c_sniffer = _i2c_sniffer; + if(i2c_sniffer->state == I2C_BUS_FREE) { + return; + } + uint8_t frame = i2c_sniffer->frame_index; + uint8_t bit = i2c_sniffer->frames[frame].bit_index; + uint8_t data_idx = i2c_sniffer->frames[frame].data_index; + if(bit < 8) { + i2c_sniffer->frames[frame].data[data_idx] <<= 1; + i2c_sniffer->frames[frame].data[data_idx] |= (int)furi_hal_gpio_read(pinSDA); + i2c_sniffer->frames[frame].bit_index++; + } else { + i2c_sniffer->frames[frame].ack[data_idx] = !furi_hal_gpio_read(pinSDA); + i2c_sniffer->frames[frame].data_index++; + i2c_sniffer->frames[frame].bit_index = 0; + } +} + +i2cSniffer* i2c_sniffer_alloc() { + i2cSniffer* i2c_sniffer = malloc(sizeof(i2cSniffer)); + i2c_sniffer->started = false; + i2c_sniffer->row_index = 0; + i2c_sniffer->menu_index = 0; + clear_sniffer_buffers(i2c_sniffer); + return i2c_sniffer; +} + +void i2c_sniffer_free(i2cSniffer* i2c_sniffer) { + furi_assert(i2c_sniffer); + if(i2c_sniffer->started) { + stop_interrupts(); + } + free(i2c_sniffer); +} diff --git a/Applications/Official/DEV_FW/source/flipper_i2ctools/i2csniffer.h b/Applications/Official/DEV_FW/source/flipper_i2ctools/i2csniffer.h new file mode 100644 index 000000000..eef26bea3 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipper_i2ctools/i2csniffer.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include + +// I2C Pins +#define pinSCL &gpio_ext_pc0 +#define pinSDA &gpio_ext_pc1 + +// Bus States +typedef enum { I2C_BUS_FREE, I2C_BUS_STARTED } i2cBusStates; + +// Max read size of i2c frame by message +// Arbitraly defined +// They're not real limit to maximum frames send +#define MAX_MESSAGE_SIZE 128 + +// Nb of records +#define MAX_RECORDS 128 + +/// @brief Struct used to store our reads +typedef struct { + uint8_t data[MAX_MESSAGE_SIZE]; + bool ack[MAX_MESSAGE_SIZE]; + uint8_t bit_index; + uint8_t data_index; +} i2cFrame; + +typedef struct { + bool started; + bool first; + i2cBusStates state; + i2cFrame frames[MAX_RECORDS]; + uint8_t frame_index; + uint8_t menu_index; + uint8_t row_index; +} i2cSniffer; + +void clear_sniffer_buffers(i2cSniffer* i2c_sniffer); +void start_interrupts(i2cSniffer* i2c_sniffer); +void stop_interrupts(); +void SDAcallback(void* _i2c_sniffer); +void SCLcallback(void* _i2c_sniffer); + +i2cSniffer* i2c_sniffer_alloc(); +void i2c_sniffer_free(i2cSniffer* i2c_sniffer); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipper_i2ctools/i2ctools.c b/Applications/Official/DEV_FW/source/flipper_i2ctools/i2ctools.c new file mode 100644 index 000000000..9d73a73b8 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipper_i2ctools/i2ctools.c @@ -0,0 +1,222 @@ +#include "i2ctools_i.h" + +void i2ctools_draw_callback(Canvas* canvas, void* ctx) { + i2cTools* i2ctools = acquire_mutex((ValueMutex*)ctx, 25); + + switch(i2ctools->main_view->current_view) { + case MAIN_VIEW: + draw_main_view(canvas, i2ctools->main_view); + break; + + case SCAN_VIEW: + draw_scanner_view(canvas, i2ctools->scanner); + break; + + case SNIFF_VIEW: + draw_sniffer_view(canvas, i2ctools->sniffer); + break; + + case SEND_VIEW: + draw_sender_view(canvas, i2ctools->sender); + break; + + default: + break; + } + release_mutex((ValueMutex*)ctx, i2ctools); +} + +void i2ctools_input_callback(InputEvent* input_event, void* ctx) { + furi_assert(ctx); + FuriMessageQueue* event_queue = ctx; + furi_message_queue_put(event_queue, input_event, FuriWaitForever); +} + +int32_t i2ctools_app(void* p) { + UNUSED(p); + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + + // Alloc i2ctools + i2cTools* i2ctools = malloc(sizeof(i2cTools)); + ValueMutex i2ctools_mutex; + if(!init_mutex(&i2ctools_mutex, i2ctools, sizeof(i2cTools))) { + FURI_LOG_E(APP_NAME, "cannot create mutex\r\n"); + free(i2ctools); + return -1; + } + + // Alloc viewport + i2ctools->view_port = view_port_alloc(); + view_port_draw_callback_set(i2ctools->view_port, i2ctools_draw_callback, &i2ctools_mutex); + view_port_input_callback_set(i2ctools->view_port, i2ctools_input_callback, event_queue); + + // Register view port in GUI + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, i2ctools->view_port, GuiLayerFullscreen); + + InputEvent event; + + i2ctools->main_view = i2c_main_view_alloc(); + + i2ctools->sniffer = i2c_sniffer_alloc(); + i2ctools->sniffer->menu_index = 0; + + i2ctools->scanner = i2c_scanner_alloc(); + + i2ctools->sender = i2c_sender_alloc(); + // Share scanner with sender + i2ctools->sender->scanner = i2ctools->scanner; + + while(furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk) { + // Back + if(event.key == InputKeyBack && event.type == InputTypeRelease) { + if(i2ctools->main_view->current_view == MAIN_VIEW) { + break; + } else { + if(i2ctools->main_view->current_view == SNIFF_VIEW) { + stop_interrupts(); + i2ctools->sniffer->started = false; + i2ctools->sniffer->state = I2C_BUS_FREE; + } + i2ctools->main_view->current_view = MAIN_VIEW; + } + } + // Up + else if(event.key == InputKeyUp && event.type == InputTypeRelease) { + if(i2ctools->main_view->current_view == MAIN_VIEW) { + if((i2ctools->main_view->menu_index > SCAN_VIEW)) { + i2ctools->main_view->menu_index--; + } + } else if(i2ctools->main_view->current_view == SCAN_VIEW) { + if(i2ctools->scanner->menu_index > 0) { + i2ctools->scanner->menu_index--; + } + } else if(i2ctools->main_view->current_view == SNIFF_VIEW) { + if(i2ctools->sniffer->row_index > 0) { + i2ctools->sniffer->row_index--; + } + } else if(i2ctools->main_view->current_view == SEND_VIEW) { + if(i2ctools->sender->value < 0xFF) { + i2ctools->sender->value++; + i2ctools->sender->sended = false; + } + } + } + // Long Up + else if( + event.key == InputKeyUp && + (event.type == InputTypeLong || event.type == InputTypeRepeat)) { + if(i2ctools->main_view->current_view == SCAN_VIEW) { + if(i2ctools->scanner->menu_index > 5) { + i2ctools->scanner->menu_index -= 5; + } + } else if(i2ctools->main_view->current_view == SEND_VIEW) { + if(i2ctools->sender->value < 0xF9) { + i2ctools->sender->value += 5; + i2ctools->sender->sended = false; + } + } else if(i2ctools->main_view->current_view == SNIFF_VIEW) { + if(i2ctools->sniffer->row_index > 5) { + i2ctools->sniffer->row_index -= 5; + } else { + i2ctools->sniffer->row_index = 0; + } + } + } + // Down + else if(event.key == InputKeyDown && event.type == InputTypeRelease) { + if(i2ctools->main_view->current_view == MAIN_VIEW) { + if(i2ctools->main_view->menu_index < MENU_SIZE - 1) { + i2ctools->main_view->menu_index++; + } + } else if(i2ctools->main_view->current_view == SCAN_VIEW) { + if(i2ctools->scanner->menu_index < ((int)i2ctools->scanner->nb_found / 3)) { + i2ctools->scanner->menu_index++; + } + } else if(i2ctools->main_view->current_view == SNIFF_VIEW) { + if((i2ctools->sniffer->row_index + 3) < + (int)i2ctools->sniffer->frames[i2ctools->sniffer->menu_index].data_index) { + i2ctools->sniffer->row_index++; + } + } else if(i2ctools->main_view->current_view == SEND_VIEW) { + if(i2ctools->sender->value > 0x00) { + i2ctools->sender->value--; + i2ctools->sender->sended = false; + } + } + } + // Long Down + else if( + event.key == InputKeyDown && + (event.type == InputTypeLong || event.type == InputTypeRepeat)) { + if(i2ctools->main_view->current_view == SEND_VIEW) { + if(i2ctools->sender->value > 0x05) { + i2ctools->sender->value -= 5; + i2ctools->sender->sended = false; + } else { + i2ctools->sender->value = 0; + i2ctools->sender->sended = false; + } + } else if(i2ctools->main_view->current_view == SNIFF_VIEW) { + if((i2ctools->sniffer->row_index + 8) < + (int)i2ctools->sniffer->frames[i2ctools->sniffer->menu_index].data_index) { + i2ctools->sniffer->row_index += 5; + } + } + + } else if(event.key == InputKeyOk && event.type == InputTypeRelease) { + if(i2ctools->main_view->current_view == MAIN_VIEW) { + i2ctools->main_view->current_view = i2ctools->main_view->menu_index; + } else if(i2ctools->main_view->current_view == SCAN_VIEW) { + scan_i2c_bus(i2ctools->scanner); + } else if(i2ctools->main_view->current_view == SEND_VIEW) { + i2ctools->sender->must_send = true; + } else if(i2ctools->main_view->current_view == SNIFF_VIEW) { + if(i2ctools->sniffer->started) { + stop_interrupts(); + i2ctools->sniffer->started = false; + i2ctools->sniffer->state = I2C_BUS_FREE; + } else { + start_interrupts(i2ctools->sniffer); + i2ctools->sniffer->started = true; + i2ctools->sniffer->state = I2C_BUS_FREE; + } + } + } else if(event.key == InputKeyRight && event.type == InputTypeRelease) { + if(i2ctools->main_view->current_view == SEND_VIEW) { + if(i2ctools->sender->address_idx < (i2ctools->scanner->nb_found - 1)) { + i2ctools->sender->address_idx++; + i2ctools->sender->sended = false; + } + } else if(i2ctools->main_view->current_view == SNIFF_VIEW) { + if(i2ctools->sniffer->menu_index < i2ctools->sniffer->frame_index) { + i2ctools->sniffer->menu_index++; + i2ctools->sniffer->row_index = 0; + } + } + } else if(event.key == InputKeyLeft && event.type == InputTypeRelease) { + if(i2ctools->main_view->current_view == SEND_VIEW) { + if(i2ctools->sender->address_idx > 0) { + i2ctools->sender->address_idx--; + i2ctools->sender->sended = false; + } + } else if(i2ctools->main_view->current_view == SNIFF_VIEW) { + if(i2ctools->sniffer->menu_index > 0) { + i2ctools->sniffer->menu_index--; + i2ctools->sniffer->row_index = 0; + } + } + } + view_port_update(i2ctools->view_port); + } + gui_remove_view_port(gui, i2ctools->view_port); + view_port_free(i2ctools->view_port); + furi_message_queue_free(event_queue); + i2c_sniffer_free(i2ctools->sniffer); + i2c_scanner_free(i2ctools->scanner); + i2c_sender_free(i2ctools->sender); + i2c_main_view_free(i2ctools->main_view); + free(i2ctools); + furi_record_close(RECORD_GUI); + return 0; +} diff --git a/Applications/Official/DEV_FW/source/flipper_i2ctools/i2ctools.gif b/Applications/Official/DEV_FW/source/flipper_i2ctools/i2ctools.gif new file mode 100644 index 000000000..7ad9a582c Binary files /dev/null and b/Applications/Official/DEV_FW/source/flipper_i2ctools/i2ctools.gif differ diff --git a/Applications/Official/DEV_FW/source/flipper_i2ctools/i2ctools.png b/Applications/Official/DEV_FW/source/flipper_i2ctools/i2ctools.png new file mode 100644 index 000000000..ba8485be8 Binary files /dev/null and b/Applications/Official/DEV_FW/source/flipper_i2ctools/i2ctools.png differ diff --git a/Applications/Official/DEV_FW/source/flipper_i2ctools/i2ctools_i.h b/Applications/Official/DEV_FW/source/flipper_i2ctools/i2ctools_i.h new file mode 100644 index 000000000..33917dc34 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipper_i2ctools/i2ctools_i.h @@ -0,0 +1,22 @@ +#include +#include +#include +#include + +#include "i2csniffer.h" +#include "i2cscanner.h" +#include "i2csender.h" +#include "views/main_view.h" +#include "views/sniffer_view.h" +#include "views/scanner_view.h" +#include "views/sender_view.h" + +// App datas +typedef struct { + ViewPort* view_port; + i2cMainView* main_view; + + i2cScanner* scanner; + i2cSniffer* sniffer; + i2cSender* sender; +} i2cTools; diff --git a/Applications/Official/DEV_FW/source/flipper_i2ctools/images/ButtonDown_7x4.png b/Applications/Official/DEV_FW/source/flipper_i2ctools/images/ButtonDown_7x4.png new file mode 100644 index 000000000..2954bb6a6 Binary files /dev/null and b/Applications/Official/DEV_FW/source/flipper_i2ctools/images/ButtonDown_7x4.png differ diff --git a/Applications/Official/DEV_FW/source/flipper_i2ctools/images/ButtonLeft_4x7.png b/Applications/Official/DEV_FW/source/flipper_i2ctools/images/ButtonLeft_4x7.png new file mode 100644 index 000000000..0b4655d43 Binary files /dev/null and b/Applications/Official/DEV_FW/source/flipper_i2ctools/images/ButtonLeft_4x7.png differ diff --git a/Applications/Official/DEV_FW/source/flipper_i2ctools/images/ButtonRight_4x7.png b/Applications/Official/DEV_FW/source/flipper_i2ctools/images/ButtonRight_4x7.png new file mode 100644 index 000000000..8e1c74c1c Binary files /dev/null and b/Applications/Official/DEV_FW/source/flipper_i2ctools/images/ButtonRight_4x7.png differ diff --git a/Applications/Official/DEV_FW/source/flipper_i2ctools/images/ButtonUp_7x4.png b/Applications/Official/DEV_FW/source/flipper_i2ctools/images/ButtonUp_7x4.png new file mode 100644 index 000000000..1be79328b Binary files /dev/null and b/Applications/Official/DEV_FW/source/flipper_i2ctools/images/ButtonUp_7x4.png differ diff --git a/Applications/Official/DEV_FW/source/flipper_i2ctools/images/Ok_btn_9x9.png b/Applications/Official/DEV_FW/source/flipper_i2ctools/images/Ok_btn_9x9.png new file mode 100644 index 000000000..9a1539da2 Binary files /dev/null and b/Applications/Official/DEV_FW/source/flipper_i2ctools/images/Ok_btn_9x9.png differ diff --git a/Applications/Official/DEV_FW/source/flipper_i2ctools/images/i2ctools_main_76x59.png b/Applications/Official/DEV_FW/source/flipper_i2ctools/images/i2ctools_main_76x59.png new file mode 100644 index 000000000..a0b2a8983 Binary files /dev/null and b/Applications/Official/DEV_FW/source/flipper_i2ctools/images/i2ctools_main_76x59.png differ diff --git a/Applications/Official/DEV_FW/source/flipper_i2ctools/views/main_view.c b/Applications/Official/DEV_FW/source/flipper_i2ctools/views/main_view.c new file mode 100644 index 000000000..abcb26224 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipper_i2ctools/views/main_view.c @@ -0,0 +1,62 @@ +#include "main_view.h" + +void draw_main_view(Canvas* canvas, i2cMainView* main_view) { + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + canvas_draw_rframe(canvas, 0, 0, 128, 64, 3); + canvas_draw_icon(canvas, 2, 2, &I_i2ctools_main_76x59); + canvas_set_font(canvas, FontPrimary); + + switch(main_view->menu_index) { + case SCAN_VIEW: + canvas_set_color(canvas, ColorBlack); + canvas_draw_str_aligned( + canvas, SNIFF_MENU_X, SNIFF_MENU_Y, AlignLeft, AlignTop, SNIFF_MENU_TEXT); + canvas_draw_str_aligned( + canvas, SEND_MENU_X, SEND_MENU_Y, AlignLeft, AlignTop, SEND_MENU_TEXT); + canvas_draw_rbox(canvas, 80, SCAN_MENU_Y - 2, 43, 13, 3); + canvas_set_color(canvas, ColorWhite); + canvas_draw_str_aligned( + canvas, SCAN_MENU_X, SCAN_MENU_Y, AlignLeft, AlignTop, SCAN_MENU_TEXT); + break; + + case SNIFF_VIEW: + canvas_set_color(canvas, ColorBlack); + canvas_draw_str_aligned( + canvas, SCAN_MENU_X, SCAN_MENU_Y, AlignLeft, AlignTop, SCAN_MENU_TEXT); + canvas_draw_str_aligned( + canvas, SEND_MENU_X, SEND_MENU_Y, AlignLeft, AlignTop, SEND_MENU_TEXT); + canvas_draw_rbox(canvas, 80, SNIFF_MENU_Y - 2, 43, 13, 3); + canvas_set_color(canvas, ColorWhite); + canvas_draw_str_aligned( + canvas, SNIFF_MENU_X, SNIFF_MENU_Y, AlignLeft, AlignTop, SNIFF_MENU_TEXT); + break; + + case SEND_VIEW: + canvas_set_color(canvas, ColorBlack); + canvas_draw_str_aligned( + canvas, SCAN_MENU_X, SCAN_MENU_Y, AlignLeft, AlignTop, SCAN_MENU_TEXT); + canvas_draw_str_aligned( + canvas, SNIFF_MENU_X, SNIFF_MENU_Y, AlignLeft, AlignTop, SNIFF_MENU_TEXT); + canvas_draw_rbox(canvas, 80, SEND_MENU_Y - 2, 43, 13, 3); + canvas_set_color(canvas, ColorWhite); + canvas_draw_str_aligned( + canvas, SEND_MENU_X, SEND_MENU_Y, AlignLeft, AlignTop, SEND_MENU_TEXT); + break; + + default: + break; + } +} + +i2cMainView* i2c_main_view_alloc() { + i2cMainView* main_view = malloc(sizeof(i2cMainView)); + main_view->menu_index = SCAN_VIEW; + main_view->current_view = MAIN_VIEW; + return main_view; +} + +void i2c_main_view_free(i2cMainView* main_view) { + furi_assert(main_view); + free(main_view); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipper_i2ctools/views/main_view.h b/Applications/Official/DEV_FW/source/flipper_i2ctools/views/main_view.h new file mode 100644 index 000000000..dc9379190 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipper_i2ctools/views/main_view.h @@ -0,0 +1,38 @@ +#include +#include +#include +#include +#define APP_NAME "I2C Tools" + +#define SCAN_MENU_TEXT "Scan" +#define SCAN_MENU_X 90 +#define SCAN_MENU_Y 13 + +#define SNIFF_MENU_TEXT "Sniff" +#define SNIFF_MENU_X 90 +#define SNIFF_MENU_Y 27 + +#define SEND_MENU_TEXT "Send" +#define SEND_MENU_X 90 +#define SEND_MENU_Y 41 + +// Menu +typedef enum { + MAIN_VIEW, + SCAN_VIEW, + SNIFF_VIEW, + SEND_VIEW, + + /* Know menu Size*/ + MENU_SIZE +} i2cToolsViews; + +typedef struct { + i2cToolsViews current_view; + i2cToolsViews menu_index; +} i2cMainView; + +void draw_main_view(Canvas* canvas, i2cMainView* main_view); + +i2cMainView* i2c_main_view_alloc(); +void i2c_main_view_free(i2cMainView* main_view); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipper_i2ctools/views/scanner_view.c b/Applications/Official/DEV_FW/source/flipper_i2ctools/views/scanner_view.c new file mode 100644 index 000000000..f8bea6f40 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipper_i2ctools/views/scanner_view.c @@ -0,0 +1,47 @@ +#include "scanner_view.h" + +void draw_scanner_view(Canvas* canvas, i2cScanner* i2c_scanner) { + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + canvas_draw_rframe(canvas, 0, 0, 128, 64, 3); + + char count_text[46]; + char count_text_fmt[] = "Peripherals Found: %d"; + canvas_set_font(canvas, FontSecondary); + snprintf(count_text, sizeof(count_text), count_text_fmt, (int)i2c_scanner->nb_found); + canvas_draw_str_aligned(canvas, 3, 3, AlignLeft, AlignTop, count_text); + uint8_t x_pos = 0; + uint8_t y_pos = 0; + uint8_t idx_to_print = 0; + for(uint8_t i = 0; i < (int)i2c_scanner->nb_found; i++) { + idx_to_print = i + i2c_scanner->menu_index * 3; + if(idx_to_print >= MAX_I2C_ADDR) { + break; + } + snprintf( + count_text, sizeof(count_text), "0x%02x ", (int)i2c_scanner->addresses[idx_to_print]); + const uint8_t x_start = 3; + if(i < 4) { + x_pos = x_start + (i * 26); + y_pos = 15; + } else if(i < 8) { + x_pos = x_start + ((i - 4) * 26); + y_pos = 25; + } else if(i < 12) { + x_pos = x_start + ((i - 8) * 26); + y_pos = 35; + } else { + break; + } + canvas_draw_str_aligned(canvas, x_pos, y_pos, AlignLeft, AlignTop, count_text); + } + // Right cursor + y_pos = 14 + i2c_scanner->menu_index; + canvas_draw_rbox(canvas, 125, y_pos, 3, 10, 1); + + // Button + canvas_draw_rbox(canvas, 45, 48, 45, 13, 3); + canvas_set_color(canvas, ColorWhite); + canvas_draw_icon(canvas, 50, 50, &I_Ok_btn_9x9); + canvas_draw_str_aligned(canvas, 62, 51, AlignLeft, AlignTop, "Scan"); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipper_i2ctools/views/scanner_view.h b/Applications/Official/DEV_FW/source/flipper_i2ctools/views/scanner_view.h new file mode 100644 index 000000000..bd591a740 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipper_i2ctools/views/scanner_view.h @@ -0,0 +1,9 @@ +#include +#include +#include +#include +#include "../i2cscanner.h" + +#define SCAN_TEXT "SCAN" + +void draw_scanner_view(Canvas* canvas, i2cScanner* i2c_scanner); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipper_i2ctools/views/sender_view.c b/Applications/Official/DEV_FW/source/flipper_i2ctools/views/sender_view.c new file mode 100644 index 000000000..216220209 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipper_i2ctools/views/sender_view.c @@ -0,0 +1,70 @@ +#include "sender_view.h" + +void draw_sender_view(Canvas* canvas, i2cSender* i2c_sender) { + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + canvas_draw_rframe(canvas, 0, 0, 128, 64, 3); + + if(!i2c_sender->scanner->scanned) { + scan_i2c_bus(i2c_sender->scanner); + } + + canvas_set_font(canvas, FontSecondary); + if(i2c_sender->scanner->nb_found <= 0) { + canvas_draw_str_aligned(canvas, 20, 5, AlignLeft, AlignTop, "No peripherals found"); + return; + } + // Send Button + canvas_draw_rbox(canvas, 45, 48, 45, 13, 3); + canvas_set_color(canvas, ColorWhite); + canvas_draw_icon(canvas, 50, 50, &I_Ok_btn_9x9); + canvas_draw_str_aligned(canvas, 62, 51, AlignLeft, AlignTop, "Send"); + // Addr + canvas_set_color(canvas, ColorBlack); + canvas_draw_str_aligned(canvas, 3, 5, AlignLeft, AlignTop, "Addr: "); + canvas_draw_icon(canvas, 33, 5, &I_ButtonLeft_4x7); + canvas_draw_icon(canvas, 68, 5, &I_ButtonRight_4x7); + char addr_text[8]; + snprintf( + addr_text, + sizeof(addr_text), + "0x%02x", + (int)i2c_sender->scanner->addresses[i2c_sender->address_idx]); + canvas_draw_str_aligned(canvas, 43, 5, AlignLeft, AlignTop, addr_text); + // Value + canvas_draw_str_aligned(canvas, 3, 15, AlignLeft, AlignTop, "Value: "); + canvas_draw_icon(canvas, 33, 17, &I_ButtonUp_7x4); + canvas_draw_icon(canvas, 68, 17, &I_ButtonDown_7x4); + snprintf(addr_text, sizeof(addr_text), "0x%02x", (int)i2c_sender->value); + canvas_draw_str_aligned(canvas, 43, 15, AlignLeft, AlignTop, addr_text); + if(i2c_sender->must_send) { + i2c_send(i2c_sender); + } + // Result + canvas_draw_str_aligned(canvas, 3, 25, AlignLeft, AlignTop, "Result: "); + if(i2c_sender->sended) { + uint8_t row = 1; + uint8_t column = 1; + const uint8_t x_min = 3; + const uint8_t y_min = 25; + uint8_t x_pos = 0; + uint8_t y_pos = 0; + for(uint8_t i = 0; i < sizeof(i2c_sender->recv); i++) { + x_pos = x_min + (column - 1) * 35; + if(row == 1) { + x_pos += 40; + } + y_pos = y_min + (row - 1) * 10; + snprintf(addr_text, sizeof(addr_text), "0x%02x", (int)i2c_sender->recv[i]); + canvas_draw_str_aligned(canvas, x_pos, y_pos, AlignLeft, AlignTop, addr_text); + column++; + if((row > 1 && column > 3) || (row == 1 && column > 2)) { + column = 1; + row++; + } + if(row > 2) { + break; + } + } + } +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipper_i2ctools/views/sender_view.h b/Applications/Official/DEV_FW/source/flipper_i2ctools/views/sender_view.h new file mode 100644 index 000000000..8a21912dc --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipper_i2ctools/views/sender_view.h @@ -0,0 +1,9 @@ +#include +#include +#include +#include +#include "../i2csender.h" + +#define SEND_TEXT "SEND" + +void draw_sender_view(Canvas* canvas, i2cSender* i2c_sender); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipper_i2ctools/views/sniffer_view.c b/Applications/Official/DEV_FW/source/flipper_i2ctools/views/sniffer_view.c new file mode 100644 index 000000000..a05873930 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipper_i2ctools/views/sniffer_view.c @@ -0,0 +1,92 @@ +#include "sniffer_view.h" + +void draw_sniffer_view(Canvas* canvas, i2cSniffer* i2c_sniffer) { + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + canvas_draw_rframe(canvas, 0, 0, 128, 64, 3); + canvas_set_font(canvas, FontSecondary); + + // Button + canvas_draw_rbox(canvas, 40, 48, 45, 13, 3); + canvas_set_color(canvas, ColorWhite); + canvas_draw_icon(canvas, 45, 50, &I_Ok_btn_9x9); + if(!i2c_sniffer->started) { + canvas_draw_str_aligned(canvas, 57, 51, AlignLeft, AlignTop, "Start"); + } else { + canvas_draw_str_aligned(canvas, 57, 51, AlignLeft, AlignTop, "Stop"); + } + canvas_set_color(canvas, ColorBlack); + if(i2c_sniffer->first) { + canvas_draw_str_aligned(canvas, 30, 3, AlignLeft, AlignTop, "Nothing Recorded"); + return; + } + char text_buffer[10]; + // nbFrame text + canvas_draw_str_aligned(canvas, 3, 3, AlignLeft, AlignTop, "Frame: "); + snprintf( + text_buffer, + sizeof(text_buffer), + "%d/%d", + (int)i2c_sniffer->menu_index + 1, + (int)i2c_sniffer->frame_index + 1); + canvas_draw_str_aligned(canvas, 38, 3, AlignLeft, AlignTop, text_buffer); + // Address text + snprintf( + text_buffer, + sizeof(text_buffer), + "0x%02x", + (int)(i2c_sniffer->frames[i2c_sniffer->menu_index].data[0] >> 1)); + canvas_draw_str_aligned(canvas, 3, 13, AlignLeft, AlignTop, "Addr: "); + canvas_draw_str_aligned(canvas, 30, 13, AlignLeft, AlignTop, text_buffer); + // R/W + if((int)(i2c_sniffer->frames[i2c_sniffer->menu_index].data[0]) % 2 == 0) { + canvas_draw_str_aligned(canvas, 58, 13, AlignLeft, AlignTop, "Write"); + } else { + canvas_draw_str_aligned(canvas, 58, 13, AlignLeft, AlignTop, "Read"); + } + // ACK + if(i2c_sniffer->frames[i2c_sniffer->menu_index].ack[0]) { + canvas_draw_str_aligned(canvas, 90, 13, AlignLeft, AlignTop, "ACK"); + } else { + canvas_draw_str_aligned(canvas, 90, 13, AlignLeft, AlignTop, "NACK"); + } + // Frames content + const uint8_t x_min = 3; + const uint8_t y_min = 23; + uint8_t x_pos = 0; + uint8_t y_pos = 0; + uint8_t row = 1; + uint8_t column = 1; + uint8_t frame_size = i2c_sniffer->frames[i2c_sniffer->menu_index].data_index; + uint8_t offset = i2c_sniffer->row_index; + if(i2c_sniffer->row_index > 0) { + offset += 1; + } + canvas_draw_str_aligned(canvas, x_min, y_min, AlignLeft, AlignTop, "Data:"); + for(uint8_t i = 1 + offset; i < frame_size; i++) { + snprintf( + text_buffer, + sizeof(text_buffer), + "0x%02x", + (int)i2c_sniffer->frames[i2c_sniffer->menu_index].data[i]); + x_pos = x_min + (column - 1) * 35; + if(row == 1) { + x_pos += 30; + } + y_pos = y_min + (row - 1) * 10; + canvas_draw_str_aligned(canvas, x_pos, y_pos, AlignLeft, AlignTop, text_buffer); + if(i2c_sniffer->frames[i2c_sniffer->menu_index].ack[i]) { + canvas_draw_str_aligned(canvas, x_pos + 24, y_pos, AlignLeft, AlignTop, "A"); + } else { + canvas_draw_str_aligned(canvas, x_pos + 24, y_pos, AlignLeft, AlignTop, "N"); + } + column++; + if((row > 1 && column > 3) || (row == 1 && column > 2)) { + column = 1; + row++; + } + if(row > 2) { + break; + } + } +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipper_i2ctools/views/sniffer_view.h b/Applications/Official/DEV_FW/source/flipper_i2ctools/views/sniffer_view.h new file mode 100644 index 000000000..d5cc373d1 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipper_i2ctools/views/sniffer_view.h @@ -0,0 +1,9 @@ +#include +#include +#include +#include +#include "../i2csniffer.h" + +#define SNIFF_TEXT "SNIFF" + +void draw_sniffer_view(Canvas* canvas, i2cSniffer* i2c_sniffer); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/.gitignore b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/README.md b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/README.md new file mode 100644 index 000000000..8ecf47c8c --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/README.md @@ -0,0 +1,43 @@ +# flipperzero-i2ctools + +Set of i2c tools for Flipper Zero + +![Preview](i2ctools.gif) + +## Wiring + +C0 -> SCL + +C1 -> SDA + +GND -> GND + +>/!\ Target must use 3v3 logic levels. If you not sure use an i2c isolator like ISO1541 + +## Tools + +### Scanner + +Look for i2c peripherals adresses + +### Sniffer + +Spy i2c traffic + +### Sender + +Send command to i2c peripherals and read result + +## TODO + +- [ ] Read more than 2 bytes in sender mode +- [ ] Add 10-bits adresses support +- [ ] Test with rate > 100khz +- [ ] Save records +- [ ] Play from files +- [ ] Kicad module +- [ ] Improve UI +- [ ] Refactor Event Management Code +- [ ] Add Documentation +- [ ] Remove max data size +- [ ] Remove max frames read size \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/application.fam b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/application.fam new file mode 100644 index 000000000..b029e90fb --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/application.fam @@ -0,0 +1,13 @@ +App( + appid="i2cTools", + name="i2c Tools", + apptype=FlipperAppType.EXTERNAL, + entry_point="i2ctools_app", + cdefines=["APP_I2CTOOLS"], + requires=["gui"], + stack_size=2 * 1024, + order=175, + fap_icon="i2ctools.png", + fap_category="GPIO", + fap_icon_assets="images", +) \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/i2cscanner.c b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/i2cscanner.c new file mode 100644 index 000000000..dab618b78 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/i2cscanner.c @@ -0,0 +1,35 @@ +#include "i2cscanner.h" + +void scan_i2c_bus(i2cScanner* i2c_scanner) { + i2c_scanner->nb_found = 0; + i2c_scanner->scanned = true; + // Get the bus + furi_hal_i2c_acquire(I2C_BUS); + // scan + for(uint8_t addr = 0x01; addr <= MAX_I2C_ADDR << 1; addr++) { + // Check for peripherals + if(furi_hal_i2c_is_device_ready(I2C_BUS, addr, I2C_TIMEOUT)) { + // skip even 8-bit addr + if(addr % 2 != 0) { + continue; + } + // convert addr to 7-bits + i2c_scanner->addresses[i2c_scanner->nb_found] = addr >> 1; + i2c_scanner->nb_found++; + } + } + furi_hal_i2c_release(I2C_BUS); +} + +i2cScanner* i2c_scanner_alloc() { + i2cScanner* i2c_scanner = malloc(sizeof(i2cScanner)); + i2c_scanner->nb_found = 0; + i2c_scanner->menu_index = 0; + i2c_scanner->scanned = false; + return i2c_scanner; +} + +void i2c_scanner_free(i2cScanner* i2c_scanner) { + furi_assert(i2c_scanner); + free(i2c_scanner); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/i2cscanner.h b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/i2cscanner.h new file mode 100644 index 000000000..5320ebb9e --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/i2cscanner.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +// I2C BUS +#define I2C_BUS &furi_hal_i2c_handle_external +#define I2C_TIMEOUT 3 + +// 7 bits addresses +#define MAX_I2C_ADDR 0x7F + +typedef struct { + uint8_t addresses[MAX_I2C_ADDR + 1]; + uint8_t nb_found; + uint8_t menu_index; + bool scanned; +} i2cScanner; + +void scan_i2c_bus(i2cScanner* i2c_scanner); + +i2cScanner* i2c_scanner_alloc(); +void i2c_scanner_free(i2cScanner* i2c_scanner); diff --git a/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/i2csender.c b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/i2csender.c new file mode 100644 index 000000000..bdc98cd9e --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/i2csender.c @@ -0,0 +1,29 @@ +#include "i2csender.h" + +void i2c_send(i2cSender* i2c_sender) { + furi_hal_i2c_acquire(I2C_BUS); + uint8_t adress = i2c_sender->scanner->addresses[i2c_sender->address_idx] << 1; + i2c_sender->error = furi_hal_i2c_trx( + I2C_BUS, + adress, + &i2c_sender->value, + sizeof(i2c_sender->value), + i2c_sender->recv, + sizeof(i2c_sender->recv), + I2C_TIMEOUT); + furi_hal_i2c_release(I2C_BUS); + i2c_sender->must_send = false; + i2c_sender->sended = true; +} + +i2cSender* i2c_sender_alloc() { + i2cSender* i2c_sender = malloc(sizeof(i2cSender)); + i2c_sender->must_send = false; + i2c_sender->sended = false; + return i2c_sender; +} + +void i2c_sender_free(i2cSender* i2c_sender) { + furi_assert(i2c_sender); + free(i2c_sender); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/i2csender.h b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/i2csender.h new file mode 100644 index 000000000..2aa74d6e2 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/i2csender.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include "i2cscanner.h" + +typedef struct { + uint8_t address_idx; + uint8_t value; + uint8_t recv[2]; + bool must_send; + bool sended; + bool error; + + i2cScanner* scanner; +} i2cSender; + +void i2c_send(); + +i2cSender* i2c_sender_alloc(); +void i2c_sender_free(i2cSender* i2c_sender); diff --git a/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/i2csniffer.c b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/i2csniffer.c new file mode 100644 index 000000000..6a633cfaf --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/i2csniffer.c @@ -0,0 +1,101 @@ +#include "i2csniffer.h" + +void clear_sniffer_buffers(i2cSniffer* i2c_sniffer) { + furi_assert(i2c_sniffer); + for(uint8_t i = 0; i < MAX_RECORDS; i++) { + for(uint8_t j = 0; j < MAX_MESSAGE_SIZE; j++) { + i2c_sniffer->frames[i].ack[j] = false; + i2c_sniffer->frames[i].data[j] = 0; + } + i2c_sniffer->frames[i].bit_index = 0; + i2c_sniffer->frames[i].data_index = 0; + } + i2c_sniffer->frame_index = 0; + i2c_sniffer->state = I2C_BUS_FREE; + i2c_sniffer->first = true; +} + +void start_interrupts(i2cSniffer* i2c_sniffer) { + furi_assert(i2c_sniffer); + furi_hal_gpio_init(pinSCL, GpioModeInterruptRise, GpioPullNo, GpioSpeedHigh); + furi_hal_gpio_add_int_callback(pinSCL, SCLcallback, i2c_sniffer); + + // Add Rise and Fall Interrupt on SDA pin + furi_hal_gpio_init(pinSDA, GpioModeInterruptRiseFall, GpioPullNo, GpioSpeedHigh); + furi_hal_gpio_add_int_callback(pinSDA, SDAcallback, i2c_sniffer); +} + +void stop_interrupts() { + furi_hal_gpio_remove_int_callback(pinSCL); + furi_hal_gpio_remove_int_callback(pinSDA); + // Reset GPIO pins to default state + furi_hal_gpio_init(pinSCL, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(pinSDA, GpioModeAnalog, GpioPullNo, GpioSpeedLow); +} + +// Called on Fallin/Rising SDA +// Used to monitor i2c bus state +void SDAcallback(void* _i2c_sniffer) { + i2cSniffer* i2c_sniffer = _i2c_sniffer; + // SCL is low maybe cclock strecching + if(furi_hal_gpio_read(pinSCL) == false) { + return; + } + // Check for stop condition: SDA rising while SCL is High + if(i2c_sniffer->state == I2C_BUS_STARTED) { + if(furi_hal_gpio_read(pinSDA) == true) { + i2c_sniffer->state = I2C_BUS_FREE; + } + } + // Check for start condition: SDA falling while SCL is high + else if(furi_hal_gpio_read(pinSDA) == false) { + i2c_sniffer->state = I2C_BUS_STARTED; + if(i2c_sniffer->first) { + i2c_sniffer->first = false; + return; + } + i2c_sniffer->frame_index++; + if(i2c_sniffer->frame_index >= MAX_RECORDS) { + clear_sniffer_buffers(i2c_sniffer); + } + } + return; +} + +// Called on Rising SCL +// Used to read bus datas +void SCLcallback(void* _i2c_sniffer) { + i2cSniffer* i2c_sniffer = _i2c_sniffer; + if(i2c_sniffer->state == I2C_BUS_FREE) { + return; + } + uint8_t frame = i2c_sniffer->frame_index; + uint8_t bit = i2c_sniffer->frames[frame].bit_index; + uint8_t data_idx = i2c_sniffer->frames[frame].data_index; + if(bit < 8) { + i2c_sniffer->frames[frame].data[data_idx] <<= 1; + i2c_sniffer->frames[frame].data[data_idx] |= (int)furi_hal_gpio_read(pinSDA); + i2c_sniffer->frames[frame].bit_index++; + } else { + i2c_sniffer->frames[frame].ack[data_idx] = !furi_hal_gpio_read(pinSDA); + i2c_sniffer->frames[frame].data_index++; + i2c_sniffer->frames[frame].bit_index = 0; + } +} + +i2cSniffer* i2c_sniffer_alloc() { + i2cSniffer* i2c_sniffer = malloc(sizeof(i2cSniffer)); + i2c_sniffer->started = false; + i2c_sniffer->row_index = 0; + i2c_sniffer->menu_index = 0; + clear_sniffer_buffers(i2c_sniffer); + return i2c_sniffer; +} + +void i2c_sniffer_free(i2cSniffer* i2c_sniffer) { + furi_assert(i2c_sniffer); + if(i2c_sniffer->started) { + stop_interrupts(); + } + free(i2c_sniffer); +} diff --git a/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/i2csniffer.h b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/i2csniffer.h new file mode 100644 index 000000000..eef26bea3 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/i2csniffer.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include + +// I2C Pins +#define pinSCL &gpio_ext_pc0 +#define pinSDA &gpio_ext_pc1 + +// Bus States +typedef enum { I2C_BUS_FREE, I2C_BUS_STARTED } i2cBusStates; + +// Max read size of i2c frame by message +// Arbitraly defined +// They're not real limit to maximum frames send +#define MAX_MESSAGE_SIZE 128 + +// Nb of records +#define MAX_RECORDS 128 + +/// @brief Struct used to store our reads +typedef struct { + uint8_t data[MAX_MESSAGE_SIZE]; + bool ack[MAX_MESSAGE_SIZE]; + uint8_t bit_index; + uint8_t data_index; +} i2cFrame; + +typedef struct { + bool started; + bool first; + i2cBusStates state; + i2cFrame frames[MAX_RECORDS]; + uint8_t frame_index; + uint8_t menu_index; + uint8_t row_index; +} i2cSniffer; + +void clear_sniffer_buffers(i2cSniffer* i2c_sniffer); +void start_interrupts(i2cSniffer* i2c_sniffer); +void stop_interrupts(); +void SDAcallback(void* _i2c_sniffer); +void SCLcallback(void* _i2c_sniffer); + +i2cSniffer* i2c_sniffer_alloc(); +void i2c_sniffer_free(i2cSniffer* i2c_sniffer); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/i2ctools.c b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/i2ctools.c new file mode 100644 index 000000000..9d73a73b8 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/i2ctools.c @@ -0,0 +1,222 @@ +#include "i2ctools_i.h" + +void i2ctools_draw_callback(Canvas* canvas, void* ctx) { + i2cTools* i2ctools = acquire_mutex((ValueMutex*)ctx, 25); + + switch(i2ctools->main_view->current_view) { + case MAIN_VIEW: + draw_main_view(canvas, i2ctools->main_view); + break; + + case SCAN_VIEW: + draw_scanner_view(canvas, i2ctools->scanner); + break; + + case SNIFF_VIEW: + draw_sniffer_view(canvas, i2ctools->sniffer); + break; + + case SEND_VIEW: + draw_sender_view(canvas, i2ctools->sender); + break; + + default: + break; + } + release_mutex((ValueMutex*)ctx, i2ctools); +} + +void i2ctools_input_callback(InputEvent* input_event, void* ctx) { + furi_assert(ctx); + FuriMessageQueue* event_queue = ctx; + furi_message_queue_put(event_queue, input_event, FuriWaitForever); +} + +int32_t i2ctools_app(void* p) { + UNUSED(p); + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + + // Alloc i2ctools + i2cTools* i2ctools = malloc(sizeof(i2cTools)); + ValueMutex i2ctools_mutex; + if(!init_mutex(&i2ctools_mutex, i2ctools, sizeof(i2cTools))) { + FURI_LOG_E(APP_NAME, "cannot create mutex\r\n"); + free(i2ctools); + return -1; + } + + // Alloc viewport + i2ctools->view_port = view_port_alloc(); + view_port_draw_callback_set(i2ctools->view_port, i2ctools_draw_callback, &i2ctools_mutex); + view_port_input_callback_set(i2ctools->view_port, i2ctools_input_callback, event_queue); + + // Register view port in GUI + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, i2ctools->view_port, GuiLayerFullscreen); + + InputEvent event; + + i2ctools->main_view = i2c_main_view_alloc(); + + i2ctools->sniffer = i2c_sniffer_alloc(); + i2ctools->sniffer->menu_index = 0; + + i2ctools->scanner = i2c_scanner_alloc(); + + i2ctools->sender = i2c_sender_alloc(); + // Share scanner with sender + i2ctools->sender->scanner = i2ctools->scanner; + + while(furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk) { + // Back + if(event.key == InputKeyBack && event.type == InputTypeRelease) { + if(i2ctools->main_view->current_view == MAIN_VIEW) { + break; + } else { + if(i2ctools->main_view->current_view == SNIFF_VIEW) { + stop_interrupts(); + i2ctools->sniffer->started = false; + i2ctools->sniffer->state = I2C_BUS_FREE; + } + i2ctools->main_view->current_view = MAIN_VIEW; + } + } + // Up + else if(event.key == InputKeyUp && event.type == InputTypeRelease) { + if(i2ctools->main_view->current_view == MAIN_VIEW) { + if((i2ctools->main_view->menu_index > SCAN_VIEW)) { + i2ctools->main_view->menu_index--; + } + } else if(i2ctools->main_view->current_view == SCAN_VIEW) { + if(i2ctools->scanner->menu_index > 0) { + i2ctools->scanner->menu_index--; + } + } else if(i2ctools->main_view->current_view == SNIFF_VIEW) { + if(i2ctools->sniffer->row_index > 0) { + i2ctools->sniffer->row_index--; + } + } else if(i2ctools->main_view->current_view == SEND_VIEW) { + if(i2ctools->sender->value < 0xFF) { + i2ctools->sender->value++; + i2ctools->sender->sended = false; + } + } + } + // Long Up + else if( + event.key == InputKeyUp && + (event.type == InputTypeLong || event.type == InputTypeRepeat)) { + if(i2ctools->main_view->current_view == SCAN_VIEW) { + if(i2ctools->scanner->menu_index > 5) { + i2ctools->scanner->menu_index -= 5; + } + } else if(i2ctools->main_view->current_view == SEND_VIEW) { + if(i2ctools->sender->value < 0xF9) { + i2ctools->sender->value += 5; + i2ctools->sender->sended = false; + } + } else if(i2ctools->main_view->current_view == SNIFF_VIEW) { + if(i2ctools->sniffer->row_index > 5) { + i2ctools->sniffer->row_index -= 5; + } else { + i2ctools->sniffer->row_index = 0; + } + } + } + // Down + else if(event.key == InputKeyDown && event.type == InputTypeRelease) { + if(i2ctools->main_view->current_view == MAIN_VIEW) { + if(i2ctools->main_view->menu_index < MENU_SIZE - 1) { + i2ctools->main_view->menu_index++; + } + } else if(i2ctools->main_view->current_view == SCAN_VIEW) { + if(i2ctools->scanner->menu_index < ((int)i2ctools->scanner->nb_found / 3)) { + i2ctools->scanner->menu_index++; + } + } else if(i2ctools->main_view->current_view == SNIFF_VIEW) { + if((i2ctools->sniffer->row_index + 3) < + (int)i2ctools->sniffer->frames[i2ctools->sniffer->menu_index].data_index) { + i2ctools->sniffer->row_index++; + } + } else if(i2ctools->main_view->current_view == SEND_VIEW) { + if(i2ctools->sender->value > 0x00) { + i2ctools->sender->value--; + i2ctools->sender->sended = false; + } + } + } + // Long Down + else if( + event.key == InputKeyDown && + (event.type == InputTypeLong || event.type == InputTypeRepeat)) { + if(i2ctools->main_view->current_view == SEND_VIEW) { + if(i2ctools->sender->value > 0x05) { + i2ctools->sender->value -= 5; + i2ctools->sender->sended = false; + } else { + i2ctools->sender->value = 0; + i2ctools->sender->sended = false; + } + } else if(i2ctools->main_view->current_view == SNIFF_VIEW) { + if((i2ctools->sniffer->row_index + 8) < + (int)i2ctools->sniffer->frames[i2ctools->sniffer->menu_index].data_index) { + i2ctools->sniffer->row_index += 5; + } + } + + } else if(event.key == InputKeyOk && event.type == InputTypeRelease) { + if(i2ctools->main_view->current_view == MAIN_VIEW) { + i2ctools->main_view->current_view = i2ctools->main_view->menu_index; + } else if(i2ctools->main_view->current_view == SCAN_VIEW) { + scan_i2c_bus(i2ctools->scanner); + } else if(i2ctools->main_view->current_view == SEND_VIEW) { + i2ctools->sender->must_send = true; + } else if(i2ctools->main_view->current_view == SNIFF_VIEW) { + if(i2ctools->sniffer->started) { + stop_interrupts(); + i2ctools->sniffer->started = false; + i2ctools->sniffer->state = I2C_BUS_FREE; + } else { + start_interrupts(i2ctools->sniffer); + i2ctools->sniffer->started = true; + i2ctools->sniffer->state = I2C_BUS_FREE; + } + } + } else if(event.key == InputKeyRight && event.type == InputTypeRelease) { + if(i2ctools->main_view->current_view == SEND_VIEW) { + if(i2ctools->sender->address_idx < (i2ctools->scanner->nb_found - 1)) { + i2ctools->sender->address_idx++; + i2ctools->sender->sended = false; + } + } else if(i2ctools->main_view->current_view == SNIFF_VIEW) { + if(i2ctools->sniffer->menu_index < i2ctools->sniffer->frame_index) { + i2ctools->sniffer->menu_index++; + i2ctools->sniffer->row_index = 0; + } + } + } else if(event.key == InputKeyLeft && event.type == InputTypeRelease) { + if(i2ctools->main_view->current_view == SEND_VIEW) { + if(i2ctools->sender->address_idx > 0) { + i2ctools->sender->address_idx--; + i2ctools->sender->sended = false; + } + } else if(i2ctools->main_view->current_view == SNIFF_VIEW) { + if(i2ctools->sniffer->menu_index > 0) { + i2ctools->sniffer->menu_index--; + i2ctools->sniffer->row_index = 0; + } + } + } + view_port_update(i2ctools->view_port); + } + gui_remove_view_port(gui, i2ctools->view_port); + view_port_free(i2ctools->view_port); + furi_message_queue_free(event_queue); + i2c_sniffer_free(i2ctools->sniffer); + i2c_scanner_free(i2ctools->scanner); + i2c_sender_free(i2ctools->sender); + i2c_main_view_free(i2ctools->main_view); + free(i2ctools); + furi_record_close(RECORD_GUI); + return 0; +} diff --git a/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/i2ctools.gif b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/i2ctools.gif new file mode 100644 index 000000000..7ad9a582c Binary files /dev/null and b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/i2ctools.gif differ diff --git a/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/i2ctools.png b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/i2ctools.png new file mode 100644 index 000000000..ba8485be8 Binary files /dev/null and b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/i2ctools.png differ diff --git a/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/i2ctools_i.h b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/i2ctools_i.h new file mode 100644 index 000000000..33917dc34 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/i2ctools_i.h @@ -0,0 +1,22 @@ +#include +#include +#include +#include + +#include "i2csniffer.h" +#include "i2cscanner.h" +#include "i2csender.h" +#include "views/main_view.h" +#include "views/sniffer_view.h" +#include "views/scanner_view.h" +#include "views/sender_view.h" + +// App datas +typedef struct { + ViewPort* view_port; + i2cMainView* main_view; + + i2cScanner* scanner; + i2cSniffer* sniffer; + i2cSender* sender; +} i2cTools; diff --git a/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/images/ButtonDown_7x4.png b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/images/ButtonDown_7x4.png new file mode 100644 index 000000000..2954bb6a6 Binary files /dev/null and b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/images/ButtonDown_7x4.png differ diff --git a/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/images/ButtonLeft_4x7.png b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/images/ButtonLeft_4x7.png new file mode 100644 index 000000000..0b4655d43 Binary files /dev/null and b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/images/ButtonLeft_4x7.png differ diff --git a/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/images/ButtonRight_4x7.png b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/images/ButtonRight_4x7.png new file mode 100644 index 000000000..8e1c74c1c Binary files /dev/null and b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/images/ButtonRight_4x7.png differ diff --git a/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/images/ButtonUp_7x4.png b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/images/ButtonUp_7x4.png new file mode 100644 index 000000000..1be79328b Binary files /dev/null and b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/images/ButtonUp_7x4.png differ diff --git a/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/images/Ok_btn_9x9.png b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/images/Ok_btn_9x9.png new file mode 100644 index 000000000..9a1539da2 Binary files /dev/null and b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/images/Ok_btn_9x9.png differ diff --git a/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/images/i2ctools_main_76x59.png b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/images/i2ctools_main_76x59.png new file mode 100644 index 000000000..a0b2a8983 Binary files /dev/null and b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/images/i2ctools_main_76x59.png differ diff --git a/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/views/main_view.c b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/views/main_view.c new file mode 100644 index 000000000..abcb26224 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/views/main_view.c @@ -0,0 +1,62 @@ +#include "main_view.h" + +void draw_main_view(Canvas* canvas, i2cMainView* main_view) { + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + canvas_draw_rframe(canvas, 0, 0, 128, 64, 3); + canvas_draw_icon(canvas, 2, 2, &I_i2ctools_main_76x59); + canvas_set_font(canvas, FontPrimary); + + switch(main_view->menu_index) { + case SCAN_VIEW: + canvas_set_color(canvas, ColorBlack); + canvas_draw_str_aligned( + canvas, SNIFF_MENU_X, SNIFF_MENU_Y, AlignLeft, AlignTop, SNIFF_MENU_TEXT); + canvas_draw_str_aligned( + canvas, SEND_MENU_X, SEND_MENU_Y, AlignLeft, AlignTop, SEND_MENU_TEXT); + canvas_draw_rbox(canvas, 80, SCAN_MENU_Y - 2, 43, 13, 3); + canvas_set_color(canvas, ColorWhite); + canvas_draw_str_aligned( + canvas, SCAN_MENU_X, SCAN_MENU_Y, AlignLeft, AlignTop, SCAN_MENU_TEXT); + break; + + case SNIFF_VIEW: + canvas_set_color(canvas, ColorBlack); + canvas_draw_str_aligned( + canvas, SCAN_MENU_X, SCAN_MENU_Y, AlignLeft, AlignTop, SCAN_MENU_TEXT); + canvas_draw_str_aligned( + canvas, SEND_MENU_X, SEND_MENU_Y, AlignLeft, AlignTop, SEND_MENU_TEXT); + canvas_draw_rbox(canvas, 80, SNIFF_MENU_Y - 2, 43, 13, 3); + canvas_set_color(canvas, ColorWhite); + canvas_draw_str_aligned( + canvas, SNIFF_MENU_X, SNIFF_MENU_Y, AlignLeft, AlignTop, SNIFF_MENU_TEXT); + break; + + case SEND_VIEW: + canvas_set_color(canvas, ColorBlack); + canvas_draw_str_aligned( + canvas, SCAN_MENU_X, SCAN_MENU_Y, AlignLeft, AlignTop, SCAN_MENU_TEXT); + canvas_draw_str_aligned( + canvas, SNIFF_MENU_X, SNIFF_MENU_Y, AlignLeft, AlignTop, SNIFF_MENU_TEXT); + canvas_draw_rbox(canvas, 80, SEND_MENU_Y - 2, 43, 13, 3); + canvas_set_color(canvas, ColorWhite); + canvas_draw_str_aligned( + canvas, SEND_MENU_X, SEND_MENU_Y, AlignLeft, AlignTop, SEND_MENU_TEXT); + break; + + default: + break; + } +} + +i2cMainView* i2c_main_view_alloc() { + i2cMainView* main_view = malloc(sizeof(i2cMainView)); + main_view->menu_index = SCAN_VIEW; + main_view->current_view = MAIN_VIEW; + return main_view; +} + +void i2c_main_view_free(i2cMainView* main_view) { + furi_assert(main_view); + free(main_view); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/views/main_view.h b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/views/main_view.h new file mode 100644 index 000000000..050e41130 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/views/main_view.h @@ -0,0 +1,38 @@ +#include +#include +#include +#include +#define APP_NAME "I2C Tools" + +#define SCAN_MENU_TEXT "Scan" +#define SCAN_MENU_X 90 +#define SCAN_MENU_Y 13 + +#define SNIFF_MENU_TEXT "Sniff" +#define SNIFF_MENU_X 90 +#define SNIFF_MENU_Y 27 + +#define SEND_MENU_TEXT "Send" +#define SEND_MENU_X 90 +#define SEND_MENU_Y 41 + +// Menu +typedef enum { + MAIN_VIEW, + SCAN_VIEW, + SNIFF_VIEW, + SEND_VIEW, + + /* Know menu Size*/ + MENU_SIZE +} i2cToolsViews; + +typedef struct { + i2cToolsViews current_view; + i2cToolsViews menu_index; +} i2cMainView; + +void draw_main_view(Canvas* canvas, i2cMainView* main_view); + +i2cMainView* i2c_main_view_alloc(); +void i2c_main_view_free(i2cMainView* main_view); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/views/scanner_view.c b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/views/scanner_view.c new file mode 100644 index 000000000..f8bea6f40 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/views/scanner_view.c @@ -0,0 +1,47 @@ +#include "scanner_view.h" + +void draw_scanner_view(Canvas* canvas, i2cScanner* i2c_scanner) { + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + canvas_draw_rframe(canvas, 0, 0, 128, 64, 3); + + char count_text[46]; + char count_text_fmt[] = "Peripherals Found: %d"; + canvas_set_font(canvas, FontSecondary); + snprintf(count_text, sizeof(count_text), count_text_fmt, (int)i2c_scanner->nb_found); + canvas_draw_str_aligned(canvas, 3, 3, AlignLeft, AlignTop, count_text); + uint8_t x_pos = 0; + uint8_t y_pos = 0; + uint8_t idx_to_print = 0; + for(uint8_t i = 0; i < (int)i2c_scanner->nb_found; i++) { + idx_to_print = i + i2c_scanner->menu_index * 3; + if(idx_to_print >= MAX_I2C_ADDR) { + break; + } + snprintf( + count_text, sizeof(count_text), "0x%02x ", (int)i2c_scanner->addresses[idx_to_print]); + const uint8_t x_start = 3; + if(i < 4) { + x_pos = x_start + (i * 26); + y_pos = 15; + } else if(i < 8) { + x_pos = x_start + ((i - 4) * 26); + y_pos = 25; + } else if(i < 12) { + x_pos = x_start + ((i - 8) * 26); + y_pos = 35; + } else { + break; + } + canvas_draw_str_aligned(canvas, x_pos, y_pos, AlignLeft, AlignTop, count_text); + } + // Right cursor + y_pos = 14 + i2c_scanner->menu_index; + canvas_draw_rbox(canvas, 125, y_pos, 3, 10, 1); + + // Button + canvas_draw_rbox(canvas, 45, 48, 45, 13, 3); + canvas_set_color(canvas, ColorWhite); + canvas_draw_icon(canvas, 50, 50, &I_Ok_btn_9x9); + canvas_draw_str_aligned(canvas, 62, 51, AlignLeft, AlignTop, "Scan"); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/views/scanner_view.h b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/views/scanner_view.h new file mode 100644 index 000000000..02bc8fb1c --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/views/scanner_view.h @@ -0,0 +1,9 @@ +#include +#include +#include +#include +#include "../i2cscanner.h" + +#define SCAN_TEXT "SCAN" + +void draw_scanner_view(Canvas* canvas, i2cScanner* i2c_scanner); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/views/sender_view.c b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/views/sender_view.c new file mode 100644 index 000000000..216220209 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/views/sender_view.c @@ -0,0 +1,70 @@ +#include "sender_view.h" + +void draw_sender_view(Canvas* canvas, i2cSender* i2c_sender) { + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + canvas_draw_rframe(canvas, 0, 0, 128, 64, 3); + + if(!i2c_sender->scanner->scanned) { + scan_i2c_bus(i2c_sender->scanner); + } + + canvas_set_font(canvas, FontSecondary); + if(i2c_sender->scanner->nb_found <= 0) { + canvas_draw_str_aligned(canvas, 20, 5, AlignLeft, AlignTop, "No peripherals found"); + return; + } + // Send Button + canvas_draw_rbox(canvas, 45, 48, 45, 13, 3); + canvas_set_color(canvas, ColorWhite); + canvas_draw_icon(canvas, 50, 50, &I_Ok_btn_9x9); + canvas_draw_str_aligned(canvas, 62, 51, AlignLeft, AlignTop, "Send"); + // Addr + canvas_set_color(canvas, ColorBlack); + canvas_draw_str_aligned(canvas, 3, 5, AlignLeft, AlignTop, "Addr: "); + canvas_draw_icon(canvas, 33, 5, &I_ButtonLeft_4x7); + canvas_draw_icon(canvas, 68, 5, &I_ButtonRight_4x7); + char addr_text[8]; + snprintf( + addr_text, + sizeof(addr_text), + "0x%02x", + (int)i2c_sender->scanner->addresses[i2c_sender->address_idx]); + canvas_draw_str_aligned(canvas, 43, 5, AlignLeft, AlignTop, addr_text); + // Value + canvas_draw_str_aligned(canvas, 3, 15, AlignLeft, AlignTop, "Value: "); + canvas_draw_icon(canvas, 33, 17, &I_ButtonUp_7x4); + canvas_draw_icon(canvas, 68, 17, &I_ButtonDown_7x4); + snprintf(addr_text, sizeof(addr_text), "0x%02x", (int)i2c_sender->value); + canvas_draw_str_aligned(canvas, 43, 15, AlignLeft, AlignTop, addr_text); + if(i2c_sender->must_send) { + i2c_send(i2c_sender); + } + // Result + canvas_draw_str_aligned(canvas, 3, 25, AlignLeft, AlignTop, "Result: "); + if(i2c_sender->sended) { + uint8_t row = 1; + uint8_t column = 1; + const uint8_t x_min = 3; + const uint8_t y_min = 25; + uint8_t x_pos = 0; + uint8_t y_pos = 0; + for(uint8_t i = 0; i < sizeof(i2c_sender->recv); i++) { + x_pos = x_min + (column - 1) * 35; + if(row == 1) { + x_pos += 40; + } + y_pos = y_min + (row - 1) * 10; + snprintf(addr_text, sizeof(addr_text), "0x%02x", (int)i2c_sender->recv[i]); + canvas_draw_str_aligned(canvas, x_pos, y_pos, AlignLeft, AlignTop, addr_text); + column++; + if((row > 1 && column > 3) || (row == 1 && column > 2)) { + column = 1; + row++; + } + if(row > 2) { + break; + } + } + } +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/views/sender_view.h b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/views/sender_view.h new file mode 100644 index 000000000..5f48081dd --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/views/sender_view.h @@ -0,0 +1,9 @@ +#include +#include +#include +#include +#include "../i2csender.h" + +#define SEND_TEXT "SEND" + +void draw_sender_view(Canvas* canvas, i2cSender* i2c_sender); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/views/sniffer_view.c b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/views/sniffer_view.c new file mode 100644 index 000000000..a05873930 --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/views/sniffer_view.c @@ -0,0 +1,92 @@ +#include "sniffer_view.h" + +void draw_sniffer_view(Canvas* canvas, i2cSniffer* i2c_sniffer) { + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + canvas_draw_rframe(canvas, 0, 0, 128, 64, 3); + canvas_set_font(canvas, FontSecondary); + + // Button + canvas_draw_rbox(canvas, 40, 48, 45, 13, 3); + canvas_set_color(canvas, ColorWhite); + canvas_draw_icon(canvas, 45, 50, &I_Ok_btn_9x9); + if(!i2c_sniffer->started) { + canvas_draw_str_aligned(canvas, 57, 51, AlignLeft, AlignTop, "Start"); + } else { + canvas_draw_str_aligned(canvas, 57, 51, AlignLeft, AlignTop, "Stop"); + } + canvas_set_color(canvas, ColorBlack); + if(i2c_sniffer->first) { + canvas_draw_str_aligned(canvas, 30, 3, AlignLeft, AlignTop, "Nothing Recorded"); + return; + } + char text_buffer[10]; + // nbFrame text + canvas_draw_str_aligned(canvas, 3, 3, AlignLeft, AlignTop, "Frame: "); + snprintf( + text_buffer, + sizeof(text_buffer), + "%d/%d", + (int)i2c_sniffer->menu_index + 1, + (int)i2c_sniffer->frame_index + 1); + canvas_draw_str_aligned(canvas, 38, 3, AlignLeft, AlignTop, text_buffer); + // Address text + snprintf( + text_buffer, + sizeof(text_buffer), + "0x%02x", + (int)(i2c_sniffer->frames[i2c_sniffer->menu_index].data[0] >> 1)); + canvas_draw_str_aligned(canvas, 3, 13, AlignLeft, AlignTop, "Addr: "); + canvas_draw_str_aligned(canvas, 30, 13, AlignLeft, AlignTop, text_buffer); + // R/W + if((int)(i2c_sniffer->frames[i2c_sniffer->menu_index].data[0]) % 2 == 0) { + canvas_draw_str_aligned(canvas, 58, 13, AlignLeft, AlignTop, "Write"); + } else { + canvas_draw_str_aligned(canvas, 58, 13, AlignLeft, AlignTop, "Read"); + } + // ACK + if(i2c_sniffer->frames[i2c_sniffer->menu_index].ack[0]) { + canvas_draw_str_aligned(canvas, 90, 13, AlignLeft, AlignTop, "ACK"); + } else { + canvas_draw_str_aligned(canvas, 90, 13, AlignLeft, AlignTop, "NACK"); + } + // Frames content + const uint8_t x_min = 3; + const uint8_t y_min = 23; + uint8_t x_pos = 0; + uint8_t y_pos = 0; + uint8_t row = 1; + uint8_t column = 1; + uint8_t frame_size = i2c_sniffer->frames[i2c_sniffer->menu_index].data_index; + uint8_t offset = i2c_sniffer->row_index; + if(i2c_sniffer->row_index > 0) { + offset += 1; + } + canvas_draw_str_aligned(canvas, x_min, y_min, AlignLeft, AlignTop, "Data:"); + for(uint8_t i = 1 + offset; i < frame_size; i++) { + snprintf( + text_buffer, + sizeof(text_buffer), + "0x%02x", + (int)i2c_sniffer->frames[i2c_sniffer->menu_index].data[i]); + x_pos = x_min + (column - 1) * 35; + if(row == 1) { + x_pos += 30; + } + y_pos = y_min + (row - 1) * 10; + canvas_draw_str_aligned(canvas, x_pos, y_pos, AlignLeft, AlignTop, text_buffer); + if(i2c_sniffer->frames[i2c_sniffer->menu_index].ack[i]) { + canvas_draw_str_aligned(canvas, x_pos + 24, y_pos, AlignLeft, AlignTop, "A"); + } else { + canvas_draw_str_aligned(canvas, x_pos + 24, y_pos, AlignLeft, AlignTop, "N"); + } + column++; + if((row > 1 && column > 3) || (row == 1 && column > 2)) { + column = 1; + row++; + } + if(row > 2) { + break; + } + } +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/views/sniffer_view.h b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/views/sniffer_view.h new file mode 100644 index 000000000..80c92f7fc --- /dev/null +++ b/Applications/Official/DEV_FW/source/flipperzero-i2ctools-main/views/sniffer_view.h @@ -0,0 +1,9 @@ +#include +#include +#include +#include +#include "../i2csniffer.h" + +#define SNIFF_TEXT "SNIFF" + +void draw_sniffer_view(Canvas* canvas, i2cSniffer* i2c_sniffer); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/game15/README.md b/Applications/Official/DEV_FW/source/game15/README.md new file mode 100644 index 000000000..b1710c919 --- /dev/null +++ b/Applications/Official/DEV_FW/source/game15/README.md @@ -0,0 +1,13 @@ + +# Game "15" for Flipper Zero + +[Original link](https://github.com/x27/flipperzero-game15) + +Logic game [Wikipedia](https://en.wikipedia.org/wiki/15_puzzle) + +![Game screen](images/Game15.png) + +![Restore game](images/Game15Restore.png) + +![Popoup](images/Game15Popup.png) + diff --git a/Applications/Official/DEV_FW/source/game15/application.fam b/Applications/Official/DEV_FW/source/game15/application.fam new file mode 100644 index 000000000..dc3a0da0b --- /dev/null +++ b/Applications/Official/DEV_FW/source/game15/application.fam @@ -0,0 +1,12 @@ +App( + appid="Game15", + name="Game 15", + apptype=FlipperAppType.EXTERNAL, + entry_point="game15_app", + cdefines=["APP_GAME15"], + requires=["gui"], + stack_size=1 * 1024, + fap_icon="game15_10px.png", + order=30, + fap_category="Games", +) diff --git a/Applications/Official/DEV_FW/source/game15/game15.c b/Applications/Official/DEV_FW/source/game15/game15.c new file mode 100644 index 000000000..d9b059466 --- /dev/null +++ b/Applications/Official/DEV_FW/source/game15/game15.c @@ -0,0 +1,468 @@ +#include +#include +#include +#include +#include + +#include "sandbox.h" + +#define FPS 20 +#define CELL_WIDTH 10 +#define CELL_HEIGHT 8 +#define MOVE_TICKS 5 +#define KEY_STACK_SIZE 16 +#define SAVING_DIRECTORY "/ext/apps/Games" +#define SAVING_FILENAME SAVING_DIRECTORY "/game15.save" +#define POPUP_MENU_ITEMS 2 + +typedef enum { + DirectionNone, + DirectionUp, + DirectionDown, + DirectionLeft, + DirectionRight +} direction_e; + +typedef enum { ScenePlay, SceneWin, ScenePopup } scene_e; + +typedef struct { + uint8_t cell_index; + uint8_t zero_index; + uint8_t move_direction; + uint8_t move_ticks; +} moving_cell_t; + +typedef struct { + uint16_t top_record; + scene_e scene; + uint16_t move_count; + uint32_t tick_count; + uint8_t board[16]; +} game_state_t; + +static game_state_t game_state; +static NotificationApp* notification; +static moving_cell_t moving_cell; +static uint8_t loaded_saving_ticks; +static uint8_t popup_menu_selected_item; + +static const char* popup_menu_strings[] = {"Continue", "Reset"}; + +static uint8_t keys[KEY_STACK_SIZE]; +static uint8_t key_stack_head = 0; + +static const uint8_t pic_cells[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x30, 0xfc, 0x38, 0xfc, 0x30, 0xfc, 0x30, 0xfc, 0x30, 0xfc, 0x30, 0xfc, 0x30, 0xfc, 0x30, 0xfc, + 0x78, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0x60, 0xfc, 0x30, 0xfc, 0x18, 0xfc, 0x0c, 0xfc, 0xfc, 0xfc, + 0x78, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0x60, 0xfc, 0xc0, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0x78, 0xfc, + 0x70, 0xfc, 0x78, 0xfc, 0x68, 0xfc, 0x6c, 0xfc, 0x6c, 0xfc, 0xec, 0xfc, 0xfc, 0xfc, 0x60, 0xfc, + 0xfc, 0xfc, 0x0c, 0xfc, 0x0c, 0xfc, 0x7c, 0xfc, 0xc0, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0x78, 0xfc, + 0x78, 0xfc, 0x0c, 0xfc, 0x0c, 0xfc, 0x7c, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0x78, 0xfc, + 0xfc, 0xfc, 0xc0, 0xfc, 0xc0, 0xfc, 0xc0, 0xfc, 0xc0, 0xfc, 0xc0, 0xfc, 0xc0, 0xfc, 0xc0, 0xfc, + 0x78, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0x78, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0x78, 0xfc, + 0x78, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0xf8, 0xfc, 0xc0, 0xfc, 0xc0, 0xfc, 0x78, 0xfc, + 0xe6, 0xfd, 0x37, 0xff, 0x36, 0xff, 0x36, 0xff, 0x36, 0xff, 0x36, 0xff, 0x36, 0xff, 0xe6, 0xfd, + 0x8c, 0xfd, 0xce, 0xfd, 0x8c, 0xfd, 0x8c, 0xfd, 0x8c, 0xfd, 0x8c, 0xfd, 0x8c, 0xfd, 0x8c, 0xfd, + 0xe6, 0xfd, 0x37, 0xff, 0x36, 0xff, 0x86, 0xfd, 0xc6, 0xfc, 0x66, 0xfc, 0x36, 0xfc, 0xf6, 0xff, + 0xe6, 0xfd, 0x37, 0xff, 0x36, 0xff, 0x86, 0xfd, 0x06, 0xff, 0x36, 0xff, 0x36, 0xff, 0xe6, 0xfd, + 0xc6, 0xfd, 0xe7, 0xfd, 0xa6, 0xfd, 0xb6, 0xfd, 0xb6, 0xfd, 0xb6, 0xff, 0xf6, 0xff, 0x86, 0xfd, + 0xf6, 0xff, 0x37, 0xfc, 0x36, 0xfc, 0xf6, 0xfd, 0x06, 0xff, 0x36, 0xff, 0x36, 0xff, 0xe6, 0xfd, +}; + +static const uint8_t pic_digits[] = { + 0xf0, 0xf2, 0xf2, 0xf2, 0xf2, 0xf0, 0xf9, 0xf8, 0xf9, 0xf9, 0xf9, 0xf0, 0xf0, 0xf2, 0xf3, + 0xf1, 0xfc, 0xf0, 0xf0, 0xf3, 0xf1, 0xf3, 0xf2, 0xf0, 0xf3, 0xf1, 0xf2, 0xf2, 0xf0, 0xf3, + 0xf0, 0xfc, 0xf0, 0xf3, 0xf2, 0xf0, 0x00, 0x0c, 0x00, 0x02, 0x02, 0x00, 0x00, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x00, 0x02, 0x00, 0x02, 0x02, 0x00, 0x00, 0x02, 0x00, 0x03, 0x03, 0x00, +}; + +static const uint8_t pic_top[] = {11, 4, 0x88, 0xf8, 0xad, 0xfa, 0xad, 0xf8, 0x8d, 0xfe}; +static const uint8_t pic_move[] = + {17, 4, 0x2e, 0x2a, 0xfe, 0xa4, 0xaa, 0xff, 0xaa, 0x2a, 0xff, 0x2e, 0x36, 0xfe}; +static const uint8_t pic_time[] = {15, 4, 0xa8, 0x8b, 0x2d, 0xe9, 0xad, 0xca, 0xad, 0x8b}; + +static const uint8_t pic_puzzled[] = { + 0xff, 0xcf, 0x00, 0xf3, 0xff, 0xfc, 0x3f, 0x03, 0xc0, 0xff, 0xf3, 0x0f, 0xdc, 0xff, 0xcf, + 0x00, 0xf3, 0xff, 0xfc, 0x3f, 0x03, 0xc0, 0xff, 0xf3, 0x0f, 0xdc, 0x03, 0xcc, 0x00, 0x03, + 0x38, 0x00, 0x0e, 0x03, 0xc0, 0x00, 0x30, 0x30, 0xdc, 0x03, 0xcc, 0x00, 0x03, 0x1c, 0x00, + 0x07, 0x03, 0xc0, 0x00, 0x30, 0x30, 0xdc, 0xff, 0xcf, 0x00, 0x03, 0x0e, 0x80, 0x03, 0x03, + 0xc0, 0xff, 0x33, 0xc0, 0xdc, 0xff, 0xcf, 0x00, 0x03, 0x07, 0xc0, 0x01, 0x03, 0xc0, 0xff, + 0x33, 0xc0, 0xdc, 0x03, 0xc0, 0x00, 0x83, 0x03, 0xe0, 0x00, 0x03, 0xc0, 0x00, 0x30, 0xc0, + 0xd0, 0x03, 0xc0, 0x00, 0xc3, 0x01, 0x70, 0x00, 0x03, 0xc0, 0x00, 0x30, 0xc0, 0xd0, 0x03, + 0xc0, 0xff, 0xf3, 0xff, 0xfc, 0x3f, 0xff, 0xcf, 0xff, 0xf3, 0xff, 0xdc, 0x03, 0xc0, 0xff, + 0xf3, 0xff, 0xfc, 0x3f, 0xff, 0xcf, 0xff, 0xf3, 0xff, 0xdc}; + +static void key_stack_init() { + key_stack_head = 0; +} + +static uint8_t key_stack_pop() { + return keys[--key_stack_head]; +} + +static bool key_stack_is_empty() { + return key_stack_head == 0; +} + +static int key_stack_push(uint8_t value) { + if(key_stack_head != KEY_STACK_SIZE) { + keys[key_stack_head] = value; + key_stack_head++; + return key_stack_head; + } else + return -1; +} + +static bool storage_game_state_load() { + Storage* storage = furi_record_open(RECORD_STORAGE); + File* file = storage_file_alloc(storage); + + uint16_t bytes_readed = 0; + if(storage_file_open(file, SAVING_FILENAME, FSAM_READ, FSOM_OPEN_EXISTING)) + bytes_readed = storage_file_read(file, &game_state, sizeof(game_state_t)); + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + return bytes_readed == sizeof(game_state_t); +} + +static void storage_game_state_save() { + Storage* storage = furi_record_open(RECORD_STORAGE); + + if(storage_common_stat(storage, SAVING_DIRECTORY, NULL) == FSE_NOT_EXIST) { + if(!storage_simply_mkdir(storage, SAVING_DIRECTORY)) { + return; + } + } + + File* file = storage_file_alloc(storage); + if(storage_file_open(file, SAVING_FILENAME, FSAM_WRITE, FSOM_CREATE_ALWAYS)) { + storage_file_write(file, &game_state, sizeof(game_state_t)); + } + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); +} + +static void set_moving_cell_by_direction(direction_e direction) { + moving_cell.move_direction = DirectionNone; + moving_cell.zero_index = 0xff; + + for(int i = 0; i < 16; i++) { + if(!game_state.board[i]) { + moving_cell.zero_index = i; + break; + } + } + if(moving_cell.zero_index == 0xff) return; + + uint8_t x = moving_cell.zero_index % 4; + uint8_t y = moving_cell.zero_index / 4; + + moving_cell.cell_index = moving_cell.zero_index; + + if(direction == DirectionUp && y < 3) + moving_cell.cell_index += 4; + else if(direction == DirectionDown && y > 0) + moving_cell.cell_index -= 4; + else if(direction == DirectionLeft && x < 3) + moving_cell.cell_index++; + else if(direction == DirectionRight && x > 0) + moving_cell.cell_index--; + else + return; + + moving_cell.move_ticks = 0; + moving_cell.move_direction = direction; +} + +static bool is_board_has_solution() { + uint8_t i, j, inv = 0; + for(i = 0; i < 16; ++i) + if(game_state.board[i]) + for(j = 0; j < i; ++j) + if(game_state.board[j] > game_state.board[i]) ++inv; + for(i = 0; i < 16; ++i) + if(game_state.board[i] == 0) inv += 1 + i / 4; + + return inv % 2 == 0; +} + +static void board_init() { + for(int i = 0; i < 16; i++) { + game_state.board[i] = (i + 1) % 16; + } + + do { + for(int i = 15; i >= 1; i--) { + int j = rand() % (i + 1); + uint8_t tmp = game_state.board[j]; + game_state.board[j] = game_state.board[i]; + game_state.board[i] = tmp; + } + } while(!is_board_has_solution()); +} + +static void game_init() { + game_state.scene = ScenePlay; + game_state.move_count = 0; + game_state.tick_count = 0; + moving_cell.move_direction = DirectionNone; + board_init(); + key_stack_init(); + popup_menu_selected_item = 0; +} + +static bool is_board_solved() { + for(int i = 0; i < 16; i++) + if(((i + 1) % 16) != game_state.board[i]) return false; + return true; +} + +static void game_tick() { + switch(game_state.scene) { + case ScenePlay: + game_state.tick_count++; + if(loaded_saving_ticks) loaded_saving_ticks--; + if(moving_cell.move_direction == DirectionNone && !key_stack_is_empty()) { + set_moving_cell_by_direction(key_stack_pop()); + if(moving_cell.move_direction == DirectionNone) { + notification_message(notification, &sequence_single_vibro); + key_stack_init(); + } + } + + if(moving_cell.move_direction != DirectionNone) { + moving_cell.move_ticks++; + if(moving_cell.move_ticks == MOVE_TICKS) { + game_state.board[moving_cell.zero_index] = + game_state.board[moving_cell.cell_index]; + game_state.board[moving_cell.cell_index] = 0; + moving_cell.move_direction = DirectionNone; + game_state.move_count++; + } + if(is_board_solved()) { + notification_message(notification, &sequence_double_vibro); + if(game_state.move_count < game_state.top_record || game_state.top_record == 0) { + game_state.top_record = game_state.move_count; + storage_game_state_save(); + } + game_state.scene = SceneWin; + } + } + break; + + case SceneWin: + if(!key_stack_is_empty()) game_init(); + break; + + case ScenePopup: + if(!key_stack_is_empty()) { + switch(key_stack_pop()) { + case DirectionDown: + popup_menu_selected_item++; + popup_menu_selected_item = popup_menu_selected_item % POPUP_MENU_ITEMS; + break; + case DirectionUp: + popup_menu_selected_item--; + popup_menu_selected_item = popup_menu_selected_item % POPUP_MENU_ITEMS; + break; + case DirectionNone: + if(popup_menu_selected_item == 0) { + game_state.scene = ScenePlay; + notification_message(notification, &sequence_single_vibro); + } else if(popup_menu_selected_item == 1) { + notification_message(notification, &sequence_single_vibro); + game_init(); + } + break; + } + } + break; + } +} + +static void draw_cell(Canvas* canvas, uint8_t x, uint8_t y, uint8_t cell_number) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_rframe(canvas, x, y, 18, 14, 1); + canvas_set_color(canvas, ColorBlack); + canvas_draw_xbm(canvas, x + 4, y + 3, CELL_WIDTH, CELL_HEIGHT, pic_cells + cell_number * 16); +} + +static void board_draw(Canvas* canvas) { + for(int i = 0; i < 16; i++) { + if(game_state.board[i]) { + if(moving_cell.move_direction == DirectionNone || moving_cell.cell_index != i) + draw_cell(canvas, (i % 4) * 20 + 7, (i / 4) * 16 + 1, game_state.board[i]); + if(moving_cell.move_direction != DirectionNone && moving_cell.cell_index == i) { + uint8_t from_x = (moving_cell.cell_index % 4) * 20 + 7; + uint8_t from_y = (moving_cell.cell_index / 4) * 16 + 1; + uint8_t to_x = (moving_cell.zero_index % 4) * 20 + 7; + uint8_t to_y = (moving_cell.zero_index / 4) * 16 + 1; + int now_x = from_x + (to_x - from_x) * moving_cell.move_ticks / MOVE_TICKS; + int now_y = from_y + (to_y - from_y) * moving_cell.move_ticks / MOVE_TICKS; + draw_cell(canvas, now_x, now_y, game_state.board[i]); + } + } + } +} + +static void number_draw(Canvas* canvas, uint8_t y, uint32_t value) { + uint8_t x = 121; + while(true) { + uint8_t digit = value % 10; + canvas_draw_xbm(canvas, x, y, 4, 6, pic_digits + digit * 6); + x -= 5; + value = value / 10; + if(!value) break; + } +} + +static void plate_draw( + Canvas* canvas, + uint8_t y, + const uint8_t* header, + uint32_t value, + bool dont_draw_zero_value) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_rbox(canvas, 92, y, 35, 19, 2); + canvas_set_color(canvas, ColorBlack); + canvas_draw_xbm(canvas, 95, y + 3, header[0], header[1], &header[2]); + if((!value && !dont_draw_zero_value) || value) number_draw(canvas, y + 10, value); +} + +static void info_draw(Canvas* canvas) { + plate_draw(canvas, 1, pic_top, game_state.top_record, true); + plate_draw(canvas, 22, pic_move, game_state.move_count, false); + plate_draw(canvas, 43, pic_time, game_state.tick_count / FPS, false); +} + +static void gray_screen(Canvas* const canvas) { + canvas_set_color(canvas, ColorWhite); + for(int x = 0; x < 128; x += 2) { + for(int y = 0; y < 64; y++) { + canvas_draw_dot(canvas, x + (y % 2 == 1 ? 0 : 1), y); + } + } +} + +static void render_callback(Canvas* const canvas) { + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 0, 0, 128, 64); + + if(game_state.scene == ScenePlay || game_state.scene == SceneWin || + game_state.scene == ScenePopup) { + canvas_set_color(canvas, ColorBlack); + board_draw(canvas); + info_draw(canvas); + + if(loaded_saving_ticks && game_state.scene != ScenePopup) { + canvas_set_color(canvas, ColorWhite); + canvas_draw_rbox(canvas, 20, 24, 88, 16, 4); + canvas_set_color(canvas, ColorBlack); + canvas_draw_rframe(canvas, 20, 24, 88, 16, 4); + canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignCenter, "Restore game ..."); + } + } + + if(game_state.scene == SceneWin) { + gray_screen(canvas); + canvas_draw_box(canvas, 7, 20, 114, 24); + canvas_set_color(canvas, ColorBlack); + canvas_draw_box(canvas, 8, 21, 112, 22); + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 10, 23, 108, 18); + canvas_set_color(canvas, ColorBlack); + canvas_draw_xbm(canvas, 14, 27, 100, 10, pic_puzzled); + } else if(game_state.scene == ScenePopup) { + gray_screen(canvas); + canvas_set_color(canvas, ColorWhite); + canvas_draw_rbox(canvas, 28, 16, 72, 32, 4); + canvas_set_color(canvas, ColorBlack); + canvas_draw_rframe(canvas, 28, 16, 72, 32, 4); + + for(int i = 0; i < POPUP_MENU_ITEMS; i++) { + if(i == popup_menu_selected_item) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_box(canvas, 34, 20 + 12 * i, 60, 12); + } + + canvas_set_color(canvas, i == popup_menu_selected_item ? ColorWhite : ColorBlack); + canvas_draw_str_aligned( + canvas, 64, 26 + 12 * i, AlignCenter, AlignCenter, popup_menu_strings[i]); + } + } +} + +static void game_event_handler(GameEvent const event) { + if(event.type == EventTypeKey) { + if(event.input.type == InputTypePress) { + switch(event.input.key) { + case InputKeyUp: + key_stack_push(DirectionUp); + break; + case InputKeyDown: + key_stack_push(DirectionDown); + break; + case InputKeyRight: + key_stack_push(DirectionRight); + break; + case InputKeyLeft: + key_stack_push(DirectionLeft); + break; + case InputKeyOk: + if(game_state.scene == ScenePlay) { + game_state.scene = ScenePopup; + key_stack_init(); + } else + key_stack_push(DirectionNone); + break; + case InputKeyBack: + if(game_state.scene == ScenePopup) { + game_state.scene = ScenePlay; + } else { + storage_game_state_save(); + sandbox_loop_exit(); + } + break; + default: + break; + } + } + } else if(event.type == EventTypeTick) { + game_tick(); + } +} + +static void game_alloc() { + key_stack_init(); + notification = furi_record_open(RECORD_NOTIFICATION); + notification_message_block(notification, &sequence_display_backlight_enforce_on); +} + +static void game_free() { + notification_message_block(notification, &sequence_display_backlight_enforce_auto); + furi_record_close(RECORD_NOTIFICATION); +} + +int32_t game15_app() { + game_alloc(); + game_init(); + + loaded_saving_ticks = 0; + if(storage_game_state_load()) { + if(game_state.scene != ScenePlay) + game_init(); + else + loaded_saving_ticks = FPS; + } else + game_init(); + + sandbox_init( + FPS, (SandboxRenderCallback)render_callback, (SandboxEventHandler)game_event_handler); + sandbox_loop(); + sandbox_free(); + game_free(); + return 0; +} diff --git a/Applications/Official/DEV_FW/source/game15/game15_10px.png b/Applications/Official/DEV_FW/source/game15/game15_10px.png new file mode 100644 index 000000000..16c4f1038 Binary files /dev/null and b/Applications/Official/DEV_FW/source/game15/game15_10px.png differ diff --git a/Applications/Official/DEV_FW/source/game15/images/Game15.png b/Applications/Official/DEV_FW/source/game15/images/Game15.png new file mode 100644 index 000000000..f13c2907b Binary files /dev/null and b/Applications/Official/DEV_FW/source/game15/images/Game15.png differ diff --git a/Applications/Official/DEV_FW/source/game15/images/Game15Popup.png b/Applications/Official/DEV_FW/source/game15/images/Game15Popup.png new file mode 100644 index 000000000..1df14729f Binary files /dev/null and b/Applications/Official/DEV_FW/source/game15/images/Game15Popup.png differ diff --git a/Applications/Official/DEV_FW/source/game15/images/Game15Restore.png b/Applications/Official/DEV_FW/source/game15/images/Game15Restore.png new file mode 100644 index 000000000..05aac27f6 Binary files /dev/null and b/Applications/Official/DEV_FW/source/game15/images/Game15Restore.png differ diff --git a/Applications/Official/DEV_FW/source/game15/sandbox.c b/Applications/Official/DEV_FW/source/game15/sandbox.c new file mode 100644 index 000000000..e3b759fc8 --- /dev/null +++ b/Applications/Official/DEV_FW/source/game15/sandbox.c @@ -0,0 +1,93 @@ +#include +#include +#include "sandbox.h" + +FuriMessageQueue* sandbox_event_queue; +FuriMutex** sandbox_mutex; +ViewPort* sandbox_view_port; +Gui* sandbox_gui; +FuriTimer* sandbox_timer; +bool sandbox_loop_processing; +SandboxRenderCallback sandbox_user_render_callback; +SandboxEventHandler sandbox_user_event_handler; + +static void sandbox_render_callback(Canvas* const canvas, void* context) { + UNUSED(context); + if(furi_mutex_acquire(sandbox_mutex, 25) != FuriStatusOk) return; + + if(sandbox_user_render_callback) sandbox_user_render_callback(canvas); + + furi_mutex_release(sandbox_mutex); +} + +static void sandbox_input_callback(InputEvent* input_event, void* context) { + UNUSED(context); + GameEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(sandbox_event_queue, &event, FuriWaitForever); +} + +static void sandbox_timer_callback(void* context) { + UNUSED(context); + GameEvent event = {.type = EventTypeTick}; + furi_message_queue_put(sandbox_event_queue, &event, 0); +} + +void sandbox_loop() { + sandbox_loop_processing = true; + while(sandbox_loop_processing) { + GameEvent event; + FuriStatus event_status = furi_message_queue_get(sandbox_event_queue, &event, 100); + if(event_status != FuriStatusOk) { + // timeout + continue; + } + + furi_mutex_acquire(sandbox_mutex, FuriWaitForever); + + if(sandbox_user_event_handler) sandbox_user_event_handler(event); + + view_port_update(sandbox_view_port); + furi_mutex_release(sandbox_mutex); + } +} + +void sandbox_loop_exit() { + sandbox_loop_processing = false; +} + +void sandbox_init( + uint8_t fps, + SandboxRenderCallback u_render_callback, + SandboxEventHandler u_event_handler) { + sandbox_user_render_callback = u_render_callback; + sandbox_user_event_handler = u_event_handler; + + sandbox_event_queue = furi_message_queue_alloc(8, sizeof(GameEvent)); + sandbox_mutex = furi_mutex_alloc(FuriMutexTypeNormal); + + sandbox_view_port = view_port_alloc(); + view_port_draw_callback_set(sandbox_view_port, sandbox_render_callback, NULL); + view_port_input_callback_set(sandbox_view_port, sandbox_input_callback, NULL); + + sandbox_gui = furi_record_open(RECORD_GUI); + gui_add_view_port(sandbox_gui, sandbox_view_port, GuiLayerFullscreen); + + if(fps > 0) { + sandbox_timer = furi_timer_alloc(sandbox_timer_callback, FuriTimerTypePeriodic, NULL); + furi_timer_start(sandbox_timer, furi_kernel_get_tick_frequency() / fps); + } else + sandbox_timer = NULL; +} + +void sandbox_free() { + if(sandbox_timer) furi_timer_free(sandbox_timer); + + gui_remove_view_port(sandbox_gui, sandbox_view_port); + view_port_enabled_set(sandbox_view_port, false); + view_port_free(sandbox_view_port); + + if(furi_mutex_acquire(sandbox_mutex, FuriWaitForever) == FuriStatusOk) { + furi_mutex_free(sandbox_mutex); + } + furi_message_queue_free(sandbox_event_queue); +} diff --git a/Applications/Official/DEV_FW/source/game15/sandbox.h b/Applications/Official/DEV_FW/source/game15/sandbox.h new file mode 100644 index 000000000..ea7dff37b --- /dev/null +++ b/Applications/Official/DEV_FW/source/game15/sandbox.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} GameEvent; + +typedef void (*SandboxRenderCallback)(Canvas* canvas); +typedef void (*SandboxEventHandler)(GameEvent event); + +void sandbox_init( + uint8_t fps, + SandboxRenderCallback render_callback, + SandboxEventHandler event_handler); +void sandbox_loop(); +void sandbox_loop_exit(); +void sandbox_free(); diff --git a/Applications/Official/DEV_FW/source/game2048/2048.png b/Applications/Official/DEV_FW/source/game2048/2048.png new file mode 100644 index 000000000..6f46d4de5 Binary files /dev/null and b/Applications/Official/DEV_FW/source/game2048/2048.png differ diff --git a/Applications/Official/DEV_FW/source/game2048/application.fam b/Applications/Official/DEV_FW/source/game2048/application.fam new file mode 100644 index 000000000..a08cf7e1f --- /dev/null +++ b/Applications/Official/DEV_FW/source/game2048/application.fam @@ -0,0 +1,12 @@ +App( + appid="2048", + name="2048", + apptype=FlipperAppType.EXTERNAL, + entry_point="game_2048_app", + cdefines=["APP_2048_GAME"], + requires=["gui"], + stack_size=2 * 1024, + order=10, + fap_icon="2048.png", + fap_category="Games", +) diff --git a/Applications/Official/DEV_FW/source/game2048/font.c b/Applications/Official/DEV_FW/source/game2048/font.c new file mode 100644 index 000000000..9acfe8d23 --- /dev/null +++ b/Applications/Official/DEV_FW/source/game2048/font.c @@ -0,0 +1,155 @@ +#include +#include +#include + +/* 7px 3 width digit font by Sefjor + * digit encoding example + *7 111 + *6 101 + *5 101 + *4 101 + *3 101 + *2 101 + *1 111 + *0 000 this string is empty, used to align + * ? ? ? + * FE 82 FE //0 + */ + +static uint8_t font[10][3] = { + {0xFE, 0x82, 0xFE}, // 0; + {0x00, 0xFE, 0x00}, // 1; + {0xF2, 0x92, 0x9E}, // 2; + {0x92, 0x92, 0xFE}, // 3; + {0x1E, 0x10, 0xFE}, // 4; + {0x9E, 0x92, 0xF2}, // 5; + {0xFE, 0x92, 0xF2}, // 6; + {0x02, 0x02, 0xFE}, // 7; + {0xFE, 0x92, 0xFE}, // 8; + {0x9E, 0x92, 0xFE}, // 9; +}; + +#define FONT_HEIGHT 8 +#define FONT_WIDTH 3 + +static void game_2048_draw_black_point(Canvas* const canvas, uint8_t x, uint8_t y) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_dot(canvas, x, y); +} + +static void game_2048_draw_white_square(Canvas* const canvas, uint8_t x, uint8_t y) { + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, x, y, 15 - 1, 15 - 3); +} + +static void _game_2048_draw_column( + Canvas* const canvas, + int digit, + int coord_x, + int coord_y, + uint8_t column) { + for(int x = 0; x < FONT_HEIGHT; ++x) { + bool is_filled = (font[digit][column] >> x) & 0x1; + if(is_filled) { + game_2048_draw_black_point(canvas, coord_x, coord_y + x); + } + } +} + +static uint8_t + _game_2048_draw_digit(Canvas* const canvas, uint8_t digit, uint8_t coord_x, uint8_t coord_y) { + uint8_t x_shift = 0; + + if(digit != 1) { + for(int column = 0; column < FONT_WIDTH; column++) { + _game_2048_draw_column(canvas, digit, coord_x + column, coord_y, column); + } + x_shift = 3; + } else { + _game_2048_draw_column(canvas, digit, coord_x, coord_y, true); + x_shift = 1; + } + + return x_shift; +} + +/* We drawing text field with 1px white border + * at given coords. Total size is: + * x = 9 = 1 + 7 + 1 + * y = 1 + total text width + 1 + */ + +/* + * Returns array of digits and it's size, + * digits should be at least 4 size + * works from 1 to 9999 + */ +static void _game_2048_parse_number(uint16_t number, uint8_t* digits, uint8_t* size) { + *size = 0; + uint16_t divider = 1000; + //find first digit, result is highest divider + while(number / divider == 0) { + divider /= 10; + if(divider == 0) { + break; + } + } + + for(int i = 0; divider != 0; i++) { + digits[i] = number / divider; + number %= divider; + *size += 1; + divider /= 10; + } +} + +uint8_t _game_2048_calculate_shift(uint16_t num) { + uint8_t shift = 0; + switch(num) { + case 1: + shift = 7; + break; + case 2: + case 4: + case 8: + shift = 6; + break; + case 16: + shift = 5; + break; + case 32: + case 64: + shift = 4; + break; + case 128: + shift = 3; + break; + case 256: + shift = 2; + break; + case 512: + shift = 3; + break; + case 1024: + shift = 2; + break; + } + return shift; +} + +void game_2048_draw_number(Canvas* const canvas, uint8_t x, uint8_t y, int number) { + uint8_t digits[4]; + uint8_t size; + + _game_2048_parse_number(number, digits, &size); + if(number > 512) { + game_2048_draw_white_square(canvas, x, y); + } + + x += _game_2048_calculate_shift(number); + y += 4; + for(int i = 0; i < size; ++i) { + x += _game_2048_draw_digit(canvas, digits[i], x, y); + x++; + } +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/game2048/font.h b/Applications/Official/DEV_FW/source/game2048/font.h new file mode 100644 index 000000000..500123ac3 --- /dev/null +++ b/Applications/Official/DEV_FW/source/game2048/font.h @@ -0,0 +1,3 @@ +#include + +void game_2048_draw_number(Canvas* const canvas, uint8_t x, uint8_t y, int number); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/game2048/game_2048.c b/Applications/Official/DEV_FW/source/game2048/game_2048.c new file mode 100644 index 000000000..96f0fa7c3 --- /dev/null +++ b/Applications/Official/DEV_FW/source/game2048/game_2048.c @@ -0,0 +1,506 @@ +#include +#include +#include +#include +#include +#include +#include "font.h" + +#define DEBUG false +/* + 0 empty + 1 2 + 2 4 + 3 8 + 4 16 + 5 32 + 6 64 + 7 128 + 8 256 + 9 512 +10 1024 +11 2048 +12 4096 +... + */ +typedef uint8_t cell_state; + +/* DirectionLeft <-- +┌╌╌╌╌┐┌╌╌╌╌┐┌╌╌╌╌┐┌╌╌╌╌┐ +╎ ╎╎ ╎╎ ╎╎ ╎ +└╌╌╌╌┘└╌╌╌╌┘└╌╌╌╌┘└╌╌╌╌┘ +┌╌╌╌╌┐┌╌╌╌╌┐┌╌╌╌╌┐┌╌╌╌╌┐ +╎ ╎╎ ╎╎ ╎╎ ╎ +└╌╌╌╌┘└╌╌╌╌┘└╌╌╌╌┘└╌╌╌╌┘ +┌╌╌┌╌╌╌╌┐╌╌┐┌╌╌╌╌┐┌╌╌╌╌┐ +╎ 2╎ 2 ╎ ╎╎ ╎╎ ╎ +└╌╌└╌╌╌╌┘╌╌┘└╌╌╌╌┘└╌╌╌╌┘ +┌╌╌┌╌╌╌╌┐┌╌╌┌╌╌╌╌┐┌╌╌╌╌┐ +╎ 4╎ 4 ╎╎ 2╎ 2 ╎╎ ╎ +└╌╌└╌╌╌╌┘└╌╌└╌╌╌╌┘└╌╌╌╌┘ +*/ +DolphinDeed getRandomDeed() { + DolphinDeed returnGrp[14] = {1, 5, 8, 10, 12, 15, 17, 20, 21, 25, 26, 28, 29, 32}; + static bool rand_generator_inited = false; + if(!rand_generator_inited) { + srand(furi_get_tick()); + rand_generator_inited = true; + } + uint8_t diceRoll = (rand() % COUNT_OF(returnGrp)); // JUST TO GET IT GOING? AND FIX BUG + diceRoll = (rand() % COUNT_OF(returnGrp)); + return returnGrp[diceRoll]; +} +typedef enum { + DirectionIdle, + DirectionUp, + DirectionRight, + DirectionDown, + DirectionLeft, +} Direction; + +typedef struct { + uint8_t y; // 0 <= y <= 3 + uint8_t x; // 0 <= x <= 3 +} Point; + +typedef struct { + uint32_t gameScore; + uint32_t highScore; +} Score; + +typedef struct { + /* + +----X + | + | field[x][y] + Y + */ + uint8_t field[4][4]; + + uint8_t next_field[4][4]; + + Score score; // original scoring + + Direction direction; + /* + field { + animation-timing-function: linear; + animation-duration: 300ms; + } + */ + uint32_t animation_start_ticks; + + Point keyframe_from[4][4]; + + Point keyframe_to[4][4]; + + bool debug; + +} GameState; + +#define XtoPx(x) (33 + x * 15) + +#define YtoPx(x) (1 + y * 15) + +static void game_2048_render_callback(Canvas* const canvas, ValueMutex* const vm) { + const GameState* game_state = acquire_mutex(vm, 25); + if(game_state == NULL) { + return; + } + + // Before the function is called, the state is set with the canvas_reset(canvas) + + if(game_state->direction == DirectionIdle) { + for(uint8_t y = 0; y < 4; y++) { + for(uint8_t x = 0; x < 4; x++) { + uint8_t field = game_state->field[y][x]; + canvas_set_color(canvas, ColorBlack); + canvas_draw_frame(canvas, XtoPx(x), YtoPx(y), 16, 16); + if(field != 0) { + game_2048_draw_number(canvas, XtoPx(x), YtoPx(y), 1 << field); + } + } + } + + // display score + char buffer[12]; + snprintf(buffer, sizeof(buffer), "%lu", game_state->score.gameScore); + canvas_draw_str_aligned(canvas, 127, 8, AlignRight, AlignBottom, buffer); + + if(game_state->score.highScore > 0) { + char buffer2[12]; + snprintf(buffer2, sizeof(buffer2), "%lu", game_state->score.highScore); + canvas_draw_str_aligned(canvas, 127, 62, AlignRight, AlignBottom, buffer2); + } + } else { // if animation + // for animation + // (osKernelGetSysTimerCount() - game_state->animation_start_ticks) / osKernelGetSysTimerFreq(); + + // TODO: end animation event/callback/set AnimationIdle + } + + release_mutex(vm, game_state); +} + +static void + game_2048_input_callback(const InputEvent* const input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + furi_message_queue_put(event_queue, input_event, FuriWaitForever); +} + +// if return false then Game Over +static bool game_2048_set_new_number(GameState* const game_state) { + uint8_t empty = 0; + for(uint8_t y = 0; y < 4; y++) { + for(uint8_t x = 0; x < 4; x++) { + if(game_state->field[y][x] == 0) { + empty++; + } + } + } + + if(empty == 0) { + return false; + } + + if(empty == 1) { + // If it is 1 move before losing, we help the player and get rid of randomness. + for(uint8_t y = 0; y < 4; y++) { + for(uint8_t x = 0; x < 4; x++) { + if(game_state->field[y][x] == 0) { + bool haveFour = + // +----X + // | + // | field[x][y], 0 <= x, y <= 3 + // Y + + // up == 4 or + (y > 0 && game_state->field[y - 1][x] == 2) || + // right == 4 or + (x < 3 && game_state->field[y][x + 1] == 2) || + // down == 4 + (y < 3 && game_state->field[y + 1][x] == 2) || + // left == 4 + (x > 0 && game_state->field[y][x - 1] == 2); + + if(haveFour) { + game_state->field[y][x] = 2; + return true; + } + + game_state->field[y][x] = 1; + return true; + } + } + } + } + + uint8_t target = rand() % empty; + uint8_t twoOrFore = rand() % 4 < 3; + for(uint8_t y = 0; y < 4; y++) { + for(uint8_t x = 0; x < 4; x++) { + if(game_state->field[y][x] == 0) { + if(target == 0) { + if(twoOrFore) { + game_state->field[y][x] = 1; // 2^1 == 2 75% + } else { + game_state->field[y][x] = 2; // 2^2 == 4 25% + } + goto exit; + } + target--; + } + } + } +exit: + return true; +} + +// static void game_2048_process_row(uint8_t before[4], uint8_t *(after[4])) { +// // move 1 row left. +// for(uint8_t i = 0; i <= 2; i++) { +// if(before[i] != 0 && before[i] == before[i + 1]) { +// before[i]++; +// before[i + 1] = 0; +// i++; +// } +// } +// for(uint8_t i = 0, j = 0; i <= 3; i++) { +// if (before[i] != 0) { +// before[j] = before[i]; +// i++; +// } +// } +// } + +static void game_2048_process_move(GameState* const game_state) { + memset(game_state->next_field, 0, sizeof(game_state->next_field)); + // +----X + // | + // | field[x][y], 0 <= x, y <= 3 + // Y + + // up + if(game_state->direction == DirectionUp) { + for(uint8_t x = 0; x < 4; x++) { + uint8_t next_y = 0; + for(int8_t y = 0; y < 4; y++) { + uint8_t field = game_state->field[y][x]; + if(field == 0) { + continue; + } + + if(game_state->next_field[next_y][x] == 0) { + game_state->next_field[next_y][x] = field; + continue; + } + + if(field == game_state->next_field[next_y][x]) { + game_state->next_field[next_y][x]++; + game_state->score.gameScore += pow(2, game_state->next_field[next_y][x]); + if(game_state->next_field[next_y][x] == 11 && !game_state->debug) { + DOLPHIN_DEED(getRandomDeed()); + } // get some xp for making a 2048 tile + next_y++; + continue; + } + + next_y++; + game_state->next_field[next_y][x] = field; + } + } + } + + // right + if(game_state->direction == DirectionRight) { + for(uint8_t y = 0; y < 4; y++) { + uint8_t next_x = 3; + for(int8_t x = 3; x >= 0; x--) { + uint8_t field = game_state->field[y][x]; + if(field == 0) { + continue; + } + + if(game_state->next_field[y][next_x] == 0) { + game_state->next_field[y][next_x] = field; + continue; + } + + if(field == game_state->next_field[y][next_x]) { + game_state->next_field[y][next_x]++; + game_state->score.gameScore += pow(2, game_state->next_field[y][next_x]); + if(game_state->next_field[y][next_x] == 11 && !game_state->debug) { + DOLPHIN_DEED(getRandomDeed()); + } // get some xp for making a 2048 tile + next_x--; + continue; + } + + next_x--; + game_state->next_field[y][next_x] = field; + } + } + } + + // down + if(game_state->direction == DirectionDown) { + for(uint8_t x = 0; x < 4; x++) { + uint8_t next_y = 3; + for(int8_t y = 3; y >= 0; y--) { + uint8_t field = game_state->field[y][x]; + if(field == 0) { + continue; + } + + if(game_state->next_field[next_y][x] == 0) { + game_state->next_field[next_y][x] = field; + continue; + } + + if(field == game_state->next_field[next_y][x]) { + game_state->next_field[next_y][x]++; + game_state->score.gameScore += pow(2, game_state->next_field[next_y][x]); + if(game_state->next_field[next_y][x] == 11 && !game_state->debug) { + DOLPHIN_DEED(getRandomDeed()); + } // get some xp for making a 2048 tile + next_y--; + continue; + } + + next_y--; + game_state->next_field[next_y][x] = field; + } + } + } + + // 0, 0, 1, 1 + // 1, 0, 0, 0 + + // left + if(game_state->direction == DirectionLeft) { + for(uint8_t y = 0; y < 4; y++) { + uint8_t next_x = 0; + for(uint8_t x = 0; x < 4; x++) { + uint8_t field = game_state->field[y][x]; + if(field == 0) { + continue; + } + + if(game_state->next_field[y][next_x] == 0) { + game_state->next_field[y][next_x] = field; + continue; + } + + if(field == game_state->next_field[y][next_x]) { + game_state->next_field[y][next_x]++; + game_state->score.gameScore += pow(2, game_state->next_field[y][next_x]); + if(game_state->next_field[y][next_x] == 11 && !game_state->debug) { + DOLPHIN_DEED(getRandomDeed()); + } // get some xp for making a 2048 tile + next_x++; + continue; + } + + next_x++; + game_state->next_field[y][next_x] = field; + } + } + } + + // + game_state->direction = DirectionIdle; + memcpy(game_state->field, game_state->next_field, sizeof(game_state->field)); + // +} + +static void game_2048_restart(GameState* const game_state) { + game_state->debug = DEBUG; + + // check score + if(game_state->score.gameScore > game_state->score.highScore) { + game_state->score.highScore = game_state->score.gameScore; + } + + // clear all cells + for(uint8_t y = 0; y < 4; y++) { + for(uint8_t x = 0; x < 4; x++) { + game_state->field[y][x] = 0; + } + } + + // start next game + game_state->score.gameScore = 0; + game_2048_set_new_number(game_state); + game_2048_set_new_number(game_state); +} + +int32_t game_2048_app(void* p) { + UNUSED(p); + int32_t return_code = 0; + + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + + GameState* game_state = malloc(sizeof(GameState)); + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, game_state, sizeof(GameState))) { + return_code = 255; + goto free_and_exit; + } + + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set( + view_port, (ViewPortDrawCallback)game_2048_render_callback, &state_mutex); + view_port_input_callback_set( + view_port, (ViewPortInputCallback)game_2048_input_callback, event_queue); + + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + game_state->direction = DirectionIdle; + game_2048_restart(game_state); + + if(game_state->debug) { + game_state->field[0][0] = 0; + game_state->field[0][1] = 0; + game_state->field[0][2] = 0; + game_state->field[0][3] = 0; + + game_state->field[1][0] = 1; + game_state->field[1][1] = 2; + game_state->field[1][2] = 3; + game_state->field[1][3] = 4; + + game_state->field[2][0] = 5; + game_state->field[2][1] = 6; + game_state->field[2][2] = 7; + game_state->field[2][3] = 8; + + game_state->field[3][0] = 9; + game_state->field[3][1] = 10; + game_state->field[3][2] = 11; + game_state->field[3][3] = 12; + } + + InputEvent event; + for(bool loop = true; loop;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + GameState* game_state = (GameState*)acquire_mutex_block(&state_mutex); + + if(event_status == FuriStatusOk) { + if(event.type == InputTypeShort) { + switch(event.key) { + case InputKeyUp: + game_state->direction = DirectionUp; + game_2048_process_move(game_state); + game_2048_set_new_number(game_state); + break; + case InputKeyDown: + game_state->direction = DirectionDown; + game_2048_process_move(game_state); + game_2048_set_new_number(game_state); + break; + case InputKeyRight: + game_state->direction = DirectionRight; + game_2048_process_move(game_state); + game_2048_set_new_number(game_state); + break; + case InputKeyLeft: + game_state->direction = DirectionLeft; + game_2048_process_move(game_state); + game_2048_set_new_number(game_state); + break; + case InputKeyOk: + game_state->direction = DirectionIdle; + break; + case InputKeyBack: + loop = false; + break; + default: + break; + } + } else if(event.type == InputTypeLong) { + if(event.key == InputKeyOk) { + game_state->direction = DirectionIdle; + game_2048_restart(game_state); + } + } + } + + view_port_update(view_port); + release_mutex(&state_mutex, game_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); + delete_mutex(&state_mutex); + +free_and_exit: + free(game_state); + furi_message_queue_free(event_queue); + + return return_code; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/game_of_life/application.fam b/Applications/Official/DEV_FW/source/game_of_life/application.fam new file mode 100644 index 000000000..55e244d22 --- /dev/null +++ b/Applications/Official/DEV_FW/source/game_of_life/application.fam @@ -0,0 +1,12 @@ +App( + appid="GameOfLife", + name="Game of Life", + apptype=FlipperAppType.EXTERNAL, + entry_point="game_of_life_app", + cdefines=["APP_GAMEOFLIFE_GAME"], + requires=["gui"], + stack_size=2 * 1024, + order=110, + fap_icon="golIcon.png", + fap_category="Games", +) diff --git a/Applications/Official/DEV_FW/source/game_of_life/game_of_life.c b/Applications/Official/DEV_FW/source/game_of_life/game_of_life.c new file mode 100644 index 000000000..65be8cb72 --- /dev/null +++ b/Applications/Official/DEV_FW/source/game_of_life/game_of_life.c @@ -0,0 +1,160 @@ +#include +#include + +#include +#include + +#define SCREEN_WIDTH 128 +#define SCREEN_HEIGHT 64 +#define TOTAL_PIXELS SCREEN_WIDTH* SCREEN_HEIGHT + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} AppEvent; + +typedef struct { + bool revive; + int evo; +} State; + +unsigned char new[TOTAL_PIXELS] = {}; +unsigned char old[TOTAL_PIXELS] = {}; +unsigned char* fields[] = {new, old}; + +int current = 0; +int next = 1; + +unsigned char get_cell(int x, int y) { + if(x <= 0 || x >= SCREEN_WIDTH) return 0; + if(y <= 0 || y >= SCREEN_HEIGHT) return 0; + + int pix = (y * SCREEN_WIDTH) + x; + return fields[current][pix]; +} + +int count_neightbors(int x, int y) { + return get_cell(x + 1, y - 1) + get_cell(x - 1, y - 1) + get_cell(x - 1, y + 1) + + get_cell(x + 1, y + 1) + get_cell(x + 1, y) + get_cell(x - 1, y) + get_cell(x, y - 1) + + get_cell(x, y + 1); +} + +static void update_field(State* state) { + if(state->revive) { + for(int i = 0; i < TOTAL_PIXELS; ++i) { + if((random() % 100) == 1) { + fields[current][i] = 1; + } + state->revive = false; + } + } + + for(int i = 0; i < TOTAL_PIXELS; ++i) { + int x = i % SCREEN_WIDTH; + int y = (int)(i / SCREEN_WIDTH); + + int v = get_cell(x, y); + int n = count_neightbors(x, y); + + if(v && n == 3) { + ++state->evo; + } else if(v && (n < 2 || n > 3)) { + ++state->evo; + v = 0; + } else if(!v && n == 3) { + ++state->evo; + v = 1; + } + + fields[next][i] = v; + } + + next ^= current; + current ^= next; + next ^= current; + + if(state->evo < TOTAL_PIXELS) { + state->revive = true; + state->evo = 0; + } +} + +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, 0); +} + +static void render_callback(Canvas* canvas, void* ctx) { + State* state = (State*)acquire_mutex((ValueMutex*)ctx, 25); + canvas_clear(canvas); + + for(int i = 0; i < TOTAL_PIXELS; ++i) { + int x = i % SCREEN_WIDTH; + int y = (int)(i / SCREEN_WIDTH); + if(fields[current][i] == 1) canvas_draw_dot(canvas, x, y); + } + release_mutex((ValueMutex*)ctx, state); +} + +int32_t game_of_life_app(void* p) { + UNUSED(p); + srand(DWT->CYCCNT); + + FuriMessageQueue* event_queue = furi_message_queue_alloc(1, sizeof(AppEvent)); + furi_check(event_queue); + + State* _state = malloc(sizeof(State)); + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, _state, sizeof(State))) { + printf("cannot create mutex\r\n"); + furi_message_queue_free(event_queue); + free(_state); + return 255; + } + + 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(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + AppEvent event; + for(bool processing = true; processing;) { + State* state = (State*)acquire_mutex_block(&state_mutex); + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 25); + + if(event_status == FuriStatusOk && event.type == EventTypeKey && + event.input.type == InputTypePress) { + if(event.input.key == InputKeyBack) { + // furiac_exit(NULL); + processing = false; + release_mutex(&state_mutex, state); + break; + } + } + + update_field(state); + + view_port_update(view_port); + release_mutex(&state_mutex, 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); + free(_state); + + return 0; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/game_of_life/golIcon.png b/Applications/Official/DEV_FW/source/game_of_life/golIcon.png new file mode 100644 index 000000000..df14f812c Binary files /dev/null and b/Applications/Official/DEV_FW/source/game_of_life/golIcon.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/application.fam b/Applications/Official/DEV_FW/source/heap_defence_game/application.fam new file mode 100644 index 000000000..bedd27dbc --- /dev/null +++ b/Applications/Official/DEV_FW/source/heap_defence_game/application.fam @@ -0,0 +1,11 @@ +App( + appid="Heap_Defence", + name="Heap Defence", + apptype=FlipperAppType.EXTERNAL, + entry_point="heap_defence_app", + requires=["gui"], + stack_size=1 * 1024, + fap_category="Games", + fap_icon="box.png", + fap_icon_assets="assets_images", +) diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Background_128x64.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Background_128x64.png new file mode 100644 index 000000000..a7eb1326f Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Background_128x64.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Box1_10x10.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Box1_10x10.png new file mode 100644 index 000000000..d168f9511 Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Box1_10x10.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Box2_10x10.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Box2_10x10.png new file mode 100644 index 000000000..e3bbb48e7 Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Box2_10x10.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Box3_10x10.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Box3_10x10.png new file mode 100644 index 000000000..e20e75b00 Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Box3_10x10.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Box4_10x10.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Box4_10x10.png new file mode 100644 index 000000000..98d134104 Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Box4_10x10.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Box5_10x10.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Box5_10x10.png new file mode 100644 index 000000000..f8dbf339f Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Box5_10x10.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Box6p_10x10.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Box6p_10x10.png new file mode 100644 index 000000000..da50f8c86 Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Box6p_10x10.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Box7p_10x10.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Box7p_10x10.png new file mode 100644 index 000000000..efcd2ac0c Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Box7p_10x10.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Box8p_10x10.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Box8p_10x10.png new file mode 100644 index 000000000..57ca46e9c Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Box8p_10x10.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Game_over_128x64.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Game_over_128x64.png new file mode 100644 index 000000000..d4837e635 Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Game_over_128x64.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_game_over_128x64/frame_01.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_game_over_128x64/frame_01.png new file mode 100644 index 000000000..d4837e635 Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_game_over_128x64/frame_01.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_game_over_128x64/frame_02.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_game_over_128x64/frame_02.png new file mode 100644 index 000000000..a88122d90 Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_game_over_128x64/frame_02.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_game_over_128x64/frame_03.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_game_over_128x64/frame_03.png new file mode 100644 index 000000000..02fa41df0 Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_game_over_128x64/frame_03.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_game_over_128x64/frame_04.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_game_over_128x64/frame_04.png new file mode 100644 index 000000000..d0c63d484 Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_game_over_128x64/frame_04.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_game_over_128x64/frame_05.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_game_over_128x64/frame_05.png new file mode 100644 index 000000000..1c2756fdb Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_game_over_128x64/frame_05.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_game_over_128x64/frame_06.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_game_over_128x64/frame_06.png new file mode 100644 index 000000000..313fd6961 Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_game_over_128x64/frame_06.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_game_over_128x64/frame_07.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_game_over_128x64/frame_07.png new file mode 100644 index 000000000..cf854656c Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_game_over_128x64/frame_07.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_game_over_128x64/frame_rate b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_game_over_128x64/frame_rate new file mode 100644 index 000000000..b8626c4cf --- /dev/null +++ b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_game_over_128x64/frame_rate @@ -0,0 +1 @@ +4 diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_block_left_10x20/frame_01.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_block_left_10x20/frame_01.png new file mode 100644 index 000000000..104a779c9 Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_block_left_10x20/frame_01.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_block_left_10x20/frame_02.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_block_left_10x20/frame_02.png new file mode 100644 index 000000000..afc4a932f Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_block_left_10x20/frame_02.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_block_left_10x20/frame_rate b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_block_left_10x20/frame_rate new file mode 100644 index 000000000..0cfbf0888 --- /dev/null +++ b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_block_left_10x20/frame_rate @@ -0,0 +1 @@ +2 diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_block_right_10x20/frame_01.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_block_right_10x20/frame_01.png new file mode 100644 index 000000000..58c2ef68d Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_block_right_10x20/frame_01.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_block_right_10x20/frame_02.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_block_right_10x20/frame_02.png new file mode 100644 index 000000000..afee3ec83 Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_block_right_10x20/frame_02.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_block_right_10x20/frame_rate b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_block_right_10x20/frame_rate new file mode 100644 index 000000000..0cfbf0888 --- /dev/null +++ b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_block_right_10x20/frame_rate @@ -0,0 +1 @@ +2 diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_left_10x20/frame_01.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_left_10x20/frame_01.png new file mode 100644 index 000000000..e8bb70be6 Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_left_10x20/frame_01.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_left_10x20/frame_02.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_left_10x20/frame_02.png new file mode 100644 index 000000000..d7dd740f7 Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_left_10x20/frame_02.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_left_10x20/frame_03.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_left_10x20/frame_03.png new file mode 100644 index 000000000..a8a9fe7e7 Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_left_10x20/frame_03.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_left_10x20/frame_04.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_left_10x20/frame_04.png new file mode 100644 index 000000000..d7dd740f7 Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_left_10x20/frame_04.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_left_10x20/frame_rate b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_left_10x20/frame_rate new file mode 100644 index 000000000..0cfbf0888 --- /dev/null +++ b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_left_10x20/frame_rate @@ -0,0 +1 @@ +2 diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_right_10x20/frame_01.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_right_10x20/frame_01.png new file mode 100644 index 000000000..fc2150343 Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_right_10x20/frame_01.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_right_10x20/frame_02.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_right_10x20/frame_02.png new file mode 100644 index 000000000..9a03083a0 Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_right_10x20/frame_02.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_right_10x20/frame_03.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_right_10x20/frame_03.png new file mode 100644 index 000000000..5c2911fbc Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_right_10x20/frame_03.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_right_10x20/frame_04.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_right_10x20/frame_04.png new file mode 100644 index 000000000..9a03083a0 Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_right_10x20/frame_04.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_right_10x20/frame_rate b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_right_10x20/frame_rate new file mode 100644 index 000000000..0cfbf0888 --- /dev/null +++ b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_person_right_10x20/frame_rate @@ -0,0 +1 @@ +2 diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_start_128x64/frame_01.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_start_128x64/frame_01.png new file mode 100644 index 000000000..7fd2f8627 Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_start_128x64/frame_01.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_start_128x64/frame_02.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_start_128x64/frame_02.png new file mode 100644 index 000000000..32fc98b74 Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_start_128x64/frame_02.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_start_128x64/frame_03.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_start_128x64/frame_03.png new file mode 100644 index 000000000..e7beb004d Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_start_128x64/frame_03.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_start_128x64/frame_04.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_start_128x64/frame_04.png new file mode 100644 index 000000000..32fc98b74 Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_start_128x64/frame_04.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_start_128x64/frame_rate b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_start_128x64/frame_rate new file mode 100644 index 000000000..00750edc0 --- /dev/null +++ b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/HD_start_128x64/frame_rate @@ -0,0 +1 @@ +3 diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Person4_1_10x20.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Person4_1_10x20.png new file mode 100644 index 000000000..104a779c9 Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Person4_1_10x20.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Person4_2_10x20.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Person4_2_10x20.png new file mode 100644 index 000000000..afc4a932f Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Person4_2_10x20.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Person5_1_10x20.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Person5_1_10x20.png new file mode 100644 index 000000000..58c2ef68d Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Person5_1_10x20.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Person5_2_10x20.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Person5_2_10x20.png new file mode 100644 index 000000000..afee3ec83 Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Person5_2_10x20.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Start_128x64.png b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Start_128x64.png new file mode 100644 index 000000000..32fc98b74 Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/assets_images/Start_128x64.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/box.png b/Applications/Official/DEV_FW/source/heap_defence_game/box.png new file mode 100644 index 000000000..9ad9b3900 Binary files /dev/null and b/Applications/Official/DEV_FW/source/heap_defence_game/box.png differ diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/heap_defence.c b/Applications/Official/DEV_FW/source/heap_defence_game/heap_defence.c new file mode 100644 index 000000000..a22a8db22 --- /dev/null +++ b/Applications/Official/DEV_FW/source/heap_defence_game/heap_defence.c @@ -0,0 +1,592 @@ +// +// Created by moh on 30.11.2021. +// +// Ported to latest firmware by @xMasterX - 18 Oct 2022 +// + +#include + +#include "hede_assets.h" +#include "Heap_Defence_icons.h" + +#include +#include +#include +#include +#include +#include + +#define Y_FIELD_SIZE 6 +#define Y_LAST (Y_FIELD_SIZE - 1) +#define X_FIELD_SIZE 12 +#define X_LAST (X_FIELD_SIZE - 1) + +#define DRAW_X_OFFSET 4 + +#define TAG "HeDe" + +#define BOX_HEIGHT 10 +#define BOX_WIDTH 10 +#define TIMER_UPDATE_FREQ 8 +#define BOX_GENERATION_RATE 15 + +static IconAnimation* BOX_DESTROYED; +static const Icon* boxes[] = { + (Icon*)&A_HD_BoxDestroyed_10x10, + &I_Box1_10x10, + &I_Box2_10x10, + &I_Box3_10x10, + &I_Box4_10x10, + &I_Box5_10x10}; + +static uint8_t BOX_TEXTURE_COUNT = sizeof(boxes) / sizeof(Icon*); + +typedef enum { + AnimationGameOver = 0, + AnimationPause, + AnimationLeft, + AnimationRight, +} Animations; + +static IconAnimation* animations[4]; + +typedef u_int8_t byte; + +typedef enum { + GameStatusVibro = 1 << 0, + GameStatusInProgress = 1 << 1, +} GameStatuses; + +typedef struct { + uint8_t x; + uint8_t y; +} Position; + +typedef enum { PlayerRising = 1, PlayerFalling = -1, PlayerNothing = 0 } PlayerStates; + +typedef struct { + Position p; + int8_t x_direction; + int8_t j_tick; + int8_t h_tick; + int8_t states; + bool right_frame; +} Person; + +typedef struct { + uint8_t offset : 4; + uint8_t box_id : 3; + uint8_t exists : 1; +} Box; + +static const uint8_t ROW_BYTE_SIZE = sizeof(Box) * X_FIELD_SIZE; + +typedef struct { + Box** field; + Person* person; + Animations animation; + GameStatuses game_status; +} GameState; + +typedef Box** Field; + +typedef enum { EventGameTick, EventKeyPress } EventType; + +typedef struct { + EventType type; + InputEvent input; +} GameEvent; + +/** + * #Construct / Destroy + */ + +static void game_reset_field_and_player(GameState* game) { + ///Reset field + bzero(game->field[0], X_FIELD_SIZE * Y_FIELD_SIZE * sizeof(Box)); + + ///Reset person + bzero(game->person, sizeof(Person)); + game->person->p.x = X_FIELD_SIZE / 2; + game->person->p.y = Y_LAST; +} + +static GameState* allocGameState() { + GameState* game = malloc(sizeof(GameState)); + + game->person = malloc(sizeof(Person)); + + game->field = malloc(Y_FIELD_SIZE * sizeof(Box*)); + game->field[0] = malloc(X_FIELD_SIZE * Y_FIELD_SIZE * sizeof(Box)); + for(int y = 1; y < Y_FIELD_SIZE; ++y) { + game->field[y] = game->field[0] + (y * X_FIELD_SIZE); + } + game_reset_field_and_player(game); + + game->game_status = GameStatusInProgress; + return game; +} + +static void game_destroy(GameState* game) { + furi_assert(game); + free(game->field[0]); + free(game->field); + free(game); +} + +static void assets_load() { + /// Init animations + animations[AnimationPause] = icon_animation_alloc(&A_HD_start_128x64); + animations[AnimationGameOver] = icon_animation_alloc(&A_HD_game_over_128x64); + animations[AnimationLeft] = icon_animation_alloc(&A_HD_person_left_10x20); + animations[AnimationRight] = icon_animation_alloc(&A_HD_person_right_10x20); + + BOX_DESTROYED = icon_animation_alloc(&A_HD_BoxDestroyed_10x10); + + icon_animation_start(animations[AnimationLeft]); + icon_animation_start(animations[AnimationRight]); +} + +static void assets_clear() { + for(int i = 0; i < 4; ++i) { + icon_animation_stop(animations[i]); + icon_animation_free(animations[i]); + } + icon_animation_free(BOX_DESTROYED); +} + +/** + * Box utils + */ + +static inline bool is_empty(Box* box) { + return !box->exists; +} + +static inline bool has_dropped(Box* box) { + return box->offset == 0; +} + +static Box* get_upper_box(Field field, Position current) { + return (&field[current.y - 1][current.x]); +} + +static Box* get_lower_box(Field field, Position current) { + return (&field[current.y + 1][current.x]); +} + +static Box* get_next_box(Field field, Position current, int x_direction) { + return (&field[current.y][current.x + x_direction]); +} + +static inline void decrement_y_offset_to_zero(Box* n) { + if(n->offset) --n->offset; +} + +static inline void heap_swap(Box* first, Box* second) { + Box temp = *first; + + *first = *second; + *second = temp; +} + +/** + * #Box logic + */ + +static void generate_box(GameState const* game) { + furi_assert(game); + + static byte tick_count = BOX_GENERATION_RATE; + if(tick_count++ != BOX_GENERATION_RATE) { + return; + } + tick_count = 0; + + int x_offset = rand() % X_FIELD_SIZE; + while(game->field[1][x_offset].exists) { + x_offset = rand() % X_FIELD_SIZE; + } + + game->field[1][x_offset].exists = true; + game->field[1][x_offset].offset = BOX_HEIGHT; + game->field[1][x_offset].box_id = (rand() % (BOX_TEXTURE_COUNT - 1)) + 1; +} + +static void drop_box(GameState* game) { + furi_assert(game); + + for(int y = Y_LAST; y > 0; y--) { + for(int x = 0; x < X_FIELD_SIZE; x++) { + Box* current_box = game->field[y] + x; + Box* upper_box = game->field[y - 1] + x; + + if(y == Y_LAST) { + decrement_y_offset_to_zero(current_box); + } + + decrement_y_offset_to_zero(upper_box); + + if(is_empty(current_box) && !is_empty(upper_box) && has_dropped(upper_box)) { + upper_box->offset = BOX_HEIGHT; + heap_swap(current_box, upper_box); + } + } + } +} + +static bool clear_rows(Box** field) { + for(int x = 0; x < X_FIELD_SIZE; ++x) { + if(is_empty(field[Y_LAST] + x) || !has_dropped(field[Y_LAST] + x)) { + return false; + } + } + + memset(field[Y_LAST], 128, ROW_BYTE_SIZE); + return true; +} + +/** + * Input Handling + */ + +static inline bool on_ground(Person* person, Field field) { + return person->p.y == Y_LAST || field[person->p.y + 1][person->p.x].exists; +} + +static void handle_key_presses(Person* person, InputEvent* input, GameState* game) { + switch(input->key) { + case InputKeyUp: + if(person->states == PlayerNothing && on_ground(person, game->field)) { + person->states = PlayerRising; + person->j_tick = 0; + } + break; + case InputKeyLeft: + person->right_frame = false; + if(person->h_tick == 0) { + person->h_tick = 1; + person->x_direction = -1; + } + break; + case InputKeyRight: + person->right_frame = true; + if(person->h_tick == 0) { + person->h_tick = 1; + person->x_direction = 1; + } + break; + case InputKeyOk: + game->game_status &= ~GameStatusInProgress; + game->animation = AnimationPause; + icon_animation_start(animations[AnimationPause]); + default: + break; + } +} + +/** + * #Person logic + */ + +static inline bool ground_box_check(Field field, Position new_position) { + Box* lower_box = get_lower_box(field, new_position); + + bool ground_box_dropped = + (new_position.y == Y_LAST || //Eсли мы и так в самом низу + is_empty(lower_box) || // Ecли снизу пустота + has_dropped(lower_box)); //Eсли бокс снизу допадал + return ground_box_dropped; +} + +static inline bool is_movable(Field field, Position box_pos, int x_direction) { + //TODO::Moжет и не двух, предположение + bool out_of_bounds = box_pos.x == 0 || box_pos.x == X_LAST; + if(out_of_bounds) return false; + bool box_on_top = box_pos.y < 1 || get_upper_box(field, box_pos)->exists; + if(box_on_top) return false; + bool has_next_box = get_next_box(field, box_pos, x_direction)->exists; + if(has_next_box) return false; + + return true; +} + +static bool horizontal_move(Person* person, Field field) { + Position new_position = person->p; + + if(!person->x_direction) return false; + + new_position.x += person->x_direction; + + bool on_edge_column = new_position.x > X_LAST; + if(on_edge_column) return false; + + if(is_empty(&field[new_position.y][new_position.x])) { + bool ground_box_dropped = ground_box_check(field, new_position); + if(ground_box_dropped) { + person->p = new_position; + return true; + } + } else if(is_movable(field, new_position, person->x_direction)) { + *get_next_box(field, new_position, person->x_direction) = + field[new_position.y][new_position.x]; + + field[new_position.y][new_position.x] = (Box){0}; + person->p = new_position; + return true; + } + return false; +} + +void hd_person_set_state(Person* person, PlayerStates state) { + person->states = state; + person->j_tick = 0; +} + +static void person_move(Person* person, Field field) { + /// Left-right logic + FURI_LOG_W(TAG, "[JUMP]func:[%s] line: %d", __FUNCTION__, __LINE__); + + if(person->states == PlayerNothing) { + if(!on_ground(person, field)) { + hd_person_set_state(person, PlayerFalling); + } + } else if(person->states == PlayerRising) { + if(person->j_tick++ == 0) { + person->p.y--; + } else if(person->j_tick == 6) { + hd_person_set_state(person, PlayerNothing); + } + + /// Destroy upper box + get_upper_box(field, person->p)->box_id = 0; + field[person->p.y][person->p.x].box_id = 0; + + } else if(person->states == PlayerFalling) { + if(person->j_tick++ == 0) { + if(on_ground(person, field)) { // TODO: Test the bugfix + hd_person_set_state(person, PlayerNothing); + } else { + person->p.y++; + } + } else if(person->j_tick == 5) { + if(on_ground(person, field)) { + hd_person_set_state(person, PlayerNothing); + } else { + hd_person_set_state(person, PlayerFalling); + } + } + } + + switch(person->h_tick) { + case 0: + break; + case 1: + person->h_tick++; + FURI_LOG_W(TAG, "[JUMP]func:[%s] line: %d", __FUNCTION__, __LINE__); + bool moved = horizontal_move(person, field); + if(!moved) { + person->h_tick = 0; + person->x_direction = 0; + } + break; + case 5: + FURI_LOG_W(TAG, "[JUMP]func:[%s] line: %d", __FUNCTION__, __LINE__); + person->h_tick = 0; + person->x_direction = 0; + break; + default: + FURI_LOG_W(TAG, "[JUMP]func:[%s] line: %d", __FUNCTION__, __LINE__); + person->h_tick++; + } +} + +static inline bool is_person_dead(Person* person, Box** field) { + return get_upper_box(field, person->p)->box_id != 0; +} + +/** + * #Callback + */ + +static void draw_box(Canvas* canvas, Box* box, int x, int y) { + if(is_empty(box)) { + return; + } + byte y_screen = y * BOX_HEIGHT - box->offset; + byte x_screen = x * BOX_WIDTH + DRAW_X_OFFSET; + + if(box->box_id == 0) { + canvas_set_bitmap_mode(canvas, true); + icon_animation_start(BOX_DESTROYED); + canvas_draw_icon_animation(canvas, x_screen, y_screen, BOX_DESTROYED); + if(icon_animation_is_last_frame(BOX_DESTROYED)) { + *box = (Box){0}; + icon_animation_stop(BOX_DESTROYED); + } + canvas_set_bitmap_mode(canvas, false); + } else { + canvas_draw_icon(canvas, x_screen, y_screen, boxes[box->box_id]); + } +} + +static void heap_defense_render_callback(Canvas* const canvas, void* mutex) { + furi_assert(mutex); + + const GameState* game = acquire_mutex((ValueMutex*)mutex, 25); + + ///Draw GameOver or Pause + if(!(game->game_status & GameStatusInProgress)) { + FURI_LOG_W(TAG, "[DAED_DRAW]func: [%s] line: %d ", __FUNCTION__, __LINE__); + + canvas_draw_icon_animation(canvas, 0, 0, animations[game->animation]); + release_mutex((ValueMutex*)mutex, game); + return; + } + + ///Draw field + canvas_draw_icon(canvas, 0, 0, &I_Background_128x64); + + ///Draw Person + const Person* person = game->person; + IconAnimation* player_animation = person->right_frame ? animations[AnimationRight] : + animations[AnimationLeft]; + + uint8_t x_screen = person->p.x * BOX_WIDTH + DRAW_X_OFFSET; + if(person->h_tick && person->h_tick != 1) { + if(person->right_frame) { + x_screen += (person->h_tick) * 2 - BOX_WIDTH; + } else { + x_screen -= (person->h_tick) * 2 - BOX_WIDTH; + } + } + + uint8_t y_screen = (person->p.y - 1) * BOX_HEIGHT; + if(person->j_tick) { + if(person->states == PlayerRising) { + y_screen += BOX_HEIGHT - (person->j_tick) * 2; + } else if(person->states == PlayerFalling) { + y_screen -= BOX_HEIGHT - (person->j_tick) * 2; + } + } + + canvas_draw_icon_animation(canvas, x_screen, y_screen, player_animation); + + ///Draw Boxes + canvas_set_color(canvas, ColorBlack); + for(int y = 1; y < Y_FIELD_SIZE; ++y) { + for(int x = 0; x < X_FIELD_SIZE; ++x) { + draw_box(canvas, &(game->field[y][x]), x, y); + } + } + + release_mutex((ValueMutex*)mutex, game); +} + +static void heap_defense_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + if(input_event->type != InputTypePress && input_event->type != InputTypeLong) return; + + furi_assert(event_queue); + GameEvent event = {.type = EventKeyPress, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void heap_defense_timer_callback(FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + GameEvent event = {.type = EventGameTick, .input = {0}}; + furi_message_queue_put(event_queue, &event, 0); +} + +int32_t heap_defence_app(void* p) { + UNUSED(p); + + //FURI_LOG_W(TAG, "Heap defence start %d", __LINE__); + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(GameEvent)); + GameState* game = allocGameState(); + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, game, sizeof(GameState))) { + game_destroy(game); + return 1; + } + + assets_load(); + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, heap_defense_render_callback, &state_mutex); + view_port_input_callback_set(view_port, heap_defense_input_callback, event_queue); + + FuriTimer* timer = + furi_timer_alloc(heap_defense_timer_callback, FuriTimerTypePeriodic, event_queue); + furi_timer_start(timer, furi_kernel_get_tick_frequency() / TIMER_UPDATE_FREQ); + + Gui* gui = furi_record_open("gui"); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + NotificationApp* notification = furi_record_open("notification"); + + memset(game->field[Y_LAST], 128, ROW_BYTE_SIZE); + game->person->p.y -= 2; + game->game_status = 0; + game->animation = AnimationPause; + + GameEvent event = {0}; + while(event.input.key != InputKeyBack) { + if(furi_message_queue_get(event_queue, &event, 100) != FuriStatusOk) { + continue; + } + + game = (GameState*)acquire_mutex_block(&state_mutex); + + //unset vibration + if(game->game_status & GameStatusVibro) { + notification_message(notification, &sequence_reset_vibro); + game->game_status &= ~GameStatusVibro; + icon_animation_stop(BOX_DESTROYED); + memset(game->field[Y_LAST], 0, ROW_BYTE_SIZE); + } + + if(!(game->game_status & GameStatusInProgress)) { + if(event.type == EventKeyPress && event.input.key == InputKeyOk) { + game->game_status |= GameStatusInProgress; + icon_animation_stop(animations[game->animation]); + } + + } else if(event.type == EventKeyPress) { + handle_key_presses(game->person, &(event.input), game); + } else { // EventGameTick + + drop_box(game); + generate_box(game); + if(clear_rows(game->field)) { + notification_message(notification, &sequence_set_vibro_on); + icon_animation_start(BOX_DESTROYED); + game->game_status |= GameStatusVibro; + } + person_move(game->person, game->field); + + if(is_person_dead(game->person, game->field)) { + game->game_status &= ~GameStatusInProgress; + game->animation = AnimationGameOver; + icon_animation_start(animations[AnimationGameOver]); + game_reset_field_and_player(game); + notification_message(notification, &sequence_error); + } + } + release_mutex(&state_mutex, game); + view_port_update(view_port); + } + + furi_timer_free(timer); + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + view_port_free(view_port); + furi_record_close("gui"); + furi_record_close("notification"); + furi_message_queue_free(event_queue); + assets_clear(); + delete_mutex(&state_mutex); + game_destroy(game); + + return 0; +} diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/hede_assets.c b/Applications/Official/DEV_FW/source/heap_defence_game/hede_assets.c new file mode 100644 index 000000000..f45c0583d --- /dev/null +++ b/Applications/Official/DEV_FW/source/heap_defence_game/hede_assets.c @@ -0,0 +1,28 @@ +// +// Created by user on 15.12.2021. +// +#include "hede_assets.h" +#include + +const uint8_t _A_HD_BoxDestroyed_10x10_0[] = { + 0x01, 0x00, 0x10, 0x00, 0x00, 0x1d, 0xa2, 0x01, 0xc8, 0x80, + 0x6d, 0x20, 0x15, 0x08, 0x06, 0x72, 0x01, 0x48, 0x07, 0xa0, +}; +const uint8_t _A_HD_BoxDestroyed_10x10_1[] = { + 0x00, 0x00, 0x00, 0x28, 0x01, 0x4A, 0x00, 0xA8, 0x01, 0x84, 0x00, + 0x22, 0x00, 0x88, 0x00, 0x58, 0x01, 0x22, 0x00, 0x00, 0x00, +}; +const uint8_t _A_HD_BoxDestroyed_10x10_2[] = { + 0x00, 0x00, 0x00, 0x08, 0x01, 0x42, 0x00, 0x09, 0x01, 0x00, 0x02, + 0x02, 0x00, 0x01, 0x02, 0x00, 0x01, 0x21, 0x00, 0x42, 0x02, +}; +const uint8_t* _A_HD_BoxDestroyed_10x10[] = { + _A_HD_BoxDestroyed_10x10_0, + _A_HD_BoxDestroyed_10x10_1, + _A_HD_BoxDestroyed_10x10_2}; +const Icon A_HD_BoxDestroyed_10x10 = { + .width = 10, + .height = 10, + .frame_count = 3, + .frame_rate = 4, + .frames = _A_HD_BoxDestroyed_10x10}; \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/heap_defence_game/hede_assets.h b/Applications/Official/DEV_FW/source/heap_defence_game/hede_assets.h new file mode 100644 index 000000000..3bcabc59f --- /dev/null +++ b/Applications/Official/DEV_FW/source/heap_defence_game/hede_assets.h @@ -0,0 +1,11 @@ +// +// Created by user on 15.12.2021. +// + +#ifndef HEDE_ASSETS_H +#define HEDE_ASSETS_H +#include + +extern const Icon A_HD_BoxDestroyed_10x10; + +#endif diff --git a/Applications/Official/DEV_FW/source/hex_viewer/LICENSE b/Applications/Official/DEV_FW/source/hex_viewer/LICENSE new file mode 100644 index 000000000..69004dc62 --- /dev/null +++ b/Applications/Official/DEV_FW/source/hex_viewer/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Roman Shchekin + +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/Official/DEV_FW/source/hex_viewer/README.md b/Applications/Official/DEV_FW/source/hex_viewer/README.md new file mode 100644 index 000000000..e830eef8e --- /dev/null +++ b/Applications/Official/DEV_FW/source/hex_viewer/README.md @@ -0,0 +1,7 @@ +# flipper-zero-hex-viewer + +Hex Viewer application for Flipper Zero! + +![Hex Viewer app!](https://habrastorage.org/r/w1560/getpro/habr/upload_files/46e/28a/d97/46e28ad973d144b123a4ce513c895d18.png) + +[Link to FAP](https://nightly.link/QtRoS/flipper-zero-hex-viewer/actions/artifacts/448677581.zip) (firmware version 0.72) diff --git a/Applications/Official/DEV_FW/source/hex_viewer/application.fam b/Applications/Official/DEV_FW/source/hex_viewer/application.fam new file mode 100644 index 000000000..cfddabb0d --- /dev/null +++ b/Applications/Official/DEV_FW/source/hex_viewer/application.fam @@ -0,0 +1,17 @@ +App( + appid="HEX_Viewer", + name="HEX Viewer", + apptype=FlipperAppType.EXTERNAL, + entry_point="hex_viewer_app", + cdefines=["APP_HEX_VIEWER"], + requires=[ + "gui", + "dialogs", + ], + stack_size=2 * 1024, + order=20, + fap_icon="icons/hex_10px.png", + fap_category="Misc", + fap_icon_assets="icons", + fap_icon_assets_symbol="hex_viewer", +) diff --git a/Applications/Official/DEV_FW/source/hex_viewer/hex_viewer.c b/Applications/Official/DEV_FW/source/hex_viewer/hex_viewer.c new file mode 100644 index 000000000..50c34d634 --- /dev/null +++ b/Applications/Official/DEV_FW/source/hex_viewer/hex_viewer.c @@ -0,0 +1,289 @@ +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#define TAG "HexViewer" + +#define HEX_VIEWER_APP_PATH_FOLDER "/any" +#define HEX_VIEWER_APP_EXTENSION "*" + +#define HEX_VIEWER_BYTES_PER_LINE 4u +#define HEX_VIEWER_LINES_ON_SCREEN 4u +#define HEX_VIEWER_BUF_SIZE (HEX_VIEWER_LINES_ON_SCREEN * HEX_VIEWER_BYTES_PER_LINE) + +typedef struct { + uint8_t file_bytes[HEX_VIEWER_LINES_ON_SCREEN][HEX_VIEWER_BYTES_PER_LINE]; + uint32_t file_offset; + uint32_t file_read_bytes; + uint32_t file_size; + Stream* stream; + bool mode; // Print address or content +} HexViewerModel; + +typedef struct { + HexViewerModel* model; + FuriMutex** mutex; + + FuriMessageQueue* input_queue; + + ViewPort* view_port; + Gui* gui; + Storage* storage; +} HexViewer; + +static void render_callback(Canvas* canvas, void* ctx) { + HexViewer* hex_viewer = ctx; + furi_check(furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk); + + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + + elements_button_left(canvas, hex_viewer->model->mode ? "Addr" : "Text"); + elements_button_right(canvas, "Info"); + + int ROW_HEIGHT = 12; + int TOP_OFFSET = 10; + int LEFT_OFFSET = 3; + + uint32_t line_count = hex_viewer->model->file_size / HEX_VIEWER_BYTES_PER_LINE; + if(hex_viewer->model->file_size % HEX_VIEWER_BYTES_PER_LINE != 0) line_count += 1; + uint32_t first_line_on_screen = hex_viewer->model->file_offset / HEX_VIEWER_BYTES_PER_LINE; + if(line_count > HEX_VIEWER_LINES_ON_SCREEN) { + uint8_t width = canvas_width(canvas); + elements_scrollbar_pos( + canvas, + width, + 0, + ROW_HEIGHT * HEX_VIEWER_LINES_ON_SCREEN, + first_line_on_screen, // TODO + line_count - (HEX_VIEWER_LINES_ON_SCREEN - 1)); + } + + char temp_buf[32]; + uint32_t row_iters = hex_viewer->model->file_read_bytes / HEX_VIEWER_BYTES_PER_LINE; + if(hex_viewer->model->file_read_bytes % HEX_VIEWER_BYTES_PER_LINE != 0) row_iters += 1; + + for(uint32_t i = 0; i < row_iters; ++i) { + uint32_t bytes_left_per_row = + hex_viewer->model->file_read_bytes - i * HEX_VIEWER_BYTES_PER_LINE; + bytes_left_per_row = MIN(bytes_left_per_row, HEX_VIEWER_BYTES_PER_LINE); + + if(hex_viewer->model->mode) { + memcpy(temp_buf, hex_viewer->model->file_bytes[i], bytes_left_per_row); + temp_buf[bytes_left_per_row] = '\0'; + for(uint32_t j = 0; j < bytes_left_per_row; ++j) + if(!isprint((int)temp_buf[j])) temp_buf[j] = '.'; + + canvas_set_font(canvas, FontKeyboard); + canvas_draw_str(canvas, LEFT_OFFSET, TOP_OFFSET + i * ROW_HEIGHT, temp_buf); + } else { + uint32_t addr = hex_viewer->model->file_offset + i * HEX_VIEWER_BYTES_PER_LINE; + snprintf(temp_buf, 32, "%04lX", addr); + + canvas_set_font(canvas, FontKeyboard); + canvas_draw_str(canvas, LEFT_OFFSET, TOP_OFFSET + i * ROW_HEIGHT, temp_buf); + } + + char* p = temp_buf; + for(uint32_t j = 0; j < bytes_left_per_row; ++j) + p += snprintf(p, 32, "%02X ", hex_viewer->model->file_bytes[i][j]); + + canvas_set_font(canvas, FontKeyboard); + canvas_draw_str(canvas, LEFT_OFFSET + 41, TOP_OFFSET + i * ROW_HEIGHT, temp_buf); + } + + furi_mutex_release(hex_viewer->mutex); +} + +static void input_callback(InputEvent* input_event, void* ctx) { + HexViewer* hex_viewer = ctx; + if(input_event->type == InputTypeShort || input_event->type == InputTypeRepeat) { + furi_message_queue_put(hex_viewer->input_queue, input_event, 0); + } +} + +static HexViewer* hex_viewer_alloc() { + HexViewer* instance = malloc(sizeof(HexViewer)); + + instance->model = malloc(sizeof(HexViewerModel)); + memset(instance->model, 0x0, sizeof(HexViewerModel)); + + instance->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + + instance->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + + instance->view_port = view_port_alloc(); + view_port_draw_callback_set(instance->view_port, render_callback, instance); + view_port_input_callback_set(instance->view_port, input_callback, instance); + + instance->gui = furi_record_open(RECORD_GUI); + gui_add_view_port(instance->gui, instance->view_port, GuiLayerFullscreen); + + instance->storage = furi_record_open(RECORD_STORAGE); + + return instance; +} + +static void hex_viewer_free(HexViewer* instance) { + furi_record_close(RECORD_STORAGE); + + gui_remove_view_port(instance->gui, instance->view_port); + furi_record_close(RECORD_GUI); + view_port_free(instance->view_port); + + furi_message_queue_free(instance->input_queue); + + furi_mutex_free(instance->mutex); + + if(instance->model->stream) buffered_file_stream_close(instance->model->stream); + + free(instance->model); + free(instance); +} + +static bool hex_viewer_open_file(HexViewer* hex_viewer, const char* file_path) { + furi_assert(hex_viewer); + furi_assert(file_path); + + hex_viewer->model->stream = buffered_file_stream_alloc(hex_viewer->storage); + bool isOk = true; + + do { + if(!buffered_file_stream_open( + hex_viewer->model->stream, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) { + FURI_LOG_E(TAG, "Unable to open stream: %s", file_path); + isOk = false; + break; + }; + + hex_viewer->model->file_size = stream_size(hex_viewer->model->stream); + } while(false); + + return isOk; +} + +static bool hex_viewer_read_file(HexViewer* hex_viewer) { + furi_assert(hex_viewer); + furi_assert(hex_viewer->model->stream); + furi_assert(hex_viewer->model->file_offset % HEX_VIEWER_BYTES_PER_LINE == 0); + + memset(hex_viewer->model->file_bytes, 0x0, HEX_VIEWER_BUF_SIZE); + bool isOk = true; + + do { + uint32_t offset = hex_viewer->model->file_offset; + if(!stream_seek(hex_viewer->model->stream, offset, true)) { + FURI_LOG_E(TAG, "Unable to seek stream"); + isOk = false; + break; + } + + hex_viewer->model->file_read_bytes = stream_read( + hex_viewer->model->stream, + (uint8_t*)hex_viewer->model->file_bytes, + HEX_VIEWER_BUF_SIZE); + } while(false); + + return isOk; +} + +int32_t hex_viewer_app(void* p) { + HexViewer* hex_viewer = hex_viewer_alloc(); + + FuriString* file_path; + file_path = furi_string_alloc(); + + do { + if(p && strlen(p)) { + furi_string_set(file_path, (const char*)p); + } else { + furi_string_set(file_path, HEX_VIEWER_APP_PATH_FOLDER); + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options( + &browser_options, HEX_VIEWER_APP_EXTENSION, &I_hex_10px); + browser_options.hide_ext = false; + + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + bool res = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options); + + furi_record_close(RECORD_DIALOGS); + if(!res) { + FURI_LOG_I(TAG, "No file selected"); + break; + } + } + + FURI_LOG_I(TAG, "File selected: %s", furi_string_get_cstr(file_path)); + + if(!hex_viewer_open_file(hex_viewer, furi_string_get_cstr(file_path))) break; + hex_viewer_read_file(hex_viewer); + + InputEvent input; + while(furi_message_queue_get(hex_viewer->input_queue, &input, FuriWaitForever) == + FuriStatusOk) { + if(input.key == InputKeyBack) { + break; + } else if(input.key == InputKeyUp) { + furi_check(furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk); + if(hex_viewer->model->file_offset > 0) { + hex_viewer->model->file_offset -= HEX_VIEWER_BYTES_PER_LINE; + if(!hex_viewer_read_file(hex_viewer)) break; + } + furi_mutex_release(hex_viewer->mutex); + } else if(input.key == InputKeyDown) { + furi_check(furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk); + uint32_t last_byte_on_screen = + hex_viewer->model->file_offset + hex_viewer->model->file_read_bytes; + + if(hex_viewer->model->file_size > last_byte_on_screen) { + hex_viewer->model->file_offset += HEX_VIEWER_BYTES_PER_LINE; + if(!hex_viewer_read_file(hex_viewer)) break; + } + furi_mutex_release(hex_viewer->mutex); + } else if(input.key == InputKeyLeft) { + furi_check(furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk); + hex_viewer->model->mode = !hex_viewer->model->mode; + furi_mutex_release(hex_viewer->mutex); + } else if(input.key == InputKeyRight) { + FuriString* buffer; + buffer = furi_string_alloc(); + furi_string_printf( + buffer, + "File path: %s\nFile size: %lu (0x%lX)", + furi_string_get_cstr(file_path), + hex_viewer->model->file_size, + hex_viewer->model->file_size); + + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_header(message, "Hex Viewer v1.1", 16, 2, AlignLeft, AlignTop); + dialog_message_set_icon(message, &I_hex_10px, 3, 2); + dialog_message_set_text( + message, furi_string_get_cstr(buffer), 3, 16, AlignLeft, AlignTop); + dialog_message_set_buttons(message, NULL, NULL, "Back"); + dialog_message_show(dialogs, message); + + furi_string_free(buffer); + dialog_message_free(message); + furi_record_close(RECORD_DIALOGS); + } + + view_port_update(hex_viewer->view_port); + } + } while(false); + + furi_string_free(file_path); + hex_viewer_free(hex_viewer); + + return 0; +} diff --git a/Applications/Official/DEV_FW/source/hex_viewer/icons/hex_10px.png b/Applications/Official/DEV_FW/source/hex_viewer/icons/hex_10px.png new file mode 100644 index 000000000..582e288c6 Binary files /dev/null and b/Applications/Official/DEV_FW/source/hex_viewer/icons/hex_10px.png differ diff --git a/Applications/Official/DEV_FW/source/lightmeter/.clang-format b/Applications/Official/DEV_FW/source/lightmeter/.clang-format new file mode 100644 index 000000000..4b76f7fa4 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/lightmeter/LICENSE b/Applications/Official/DEV_FW/source/lightmeter/LICENSE new file mode 100644 index 000000000..cb2f65db5 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/lightmeter/README.md b/Applications/Official/DEV_FW/source/lightmeter/README.md new file mode 100644 index 000000000..dc6c6ffd5 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/lightmeter/application.fam b/Applications/Official/DEV_FW/source/lightmeter/application.fam new file mode 100644 index 000000000..b1de62641 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/lightmeter/gui/scenes/config/lightmeter_scene.c b/Applications/Official/DEV_FW/source/lightmeter/gui/scenes/config/lightmeter_scene.c new file mode 100644 index 000000000..2487d5817 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/lightmeter/gui/scenes/config/lightmeter_scene.h b/Applications/Official/DEV_FW/source/lightmeter/gui/scenes/config/lightmeter_scene.h new file mode 100644 index 000000000..9d5931384 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/lightmeter/gui/scenes/config/lightmeter_scene_config.h b/Applications/Official/DEV_FW/source/lightmeter/gui/scenes/config/lightmeter_scene_config.h new file mode 100644 index 000000000..c72a7713e --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/lightmeter/gui/scenes/lightmeter_scene_about.c b/Applications/Official/DEV_FW/source/lightmeter/gui/scenes/lightmeter_scene_about.c new file mode 100644 index 000000000..1508b4c00 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/lightmeter/gui/scenes/lightmeter_scene_config.c b/Applications/Official/DEV_FW/source/lightmeter/gui/scenes/lightmeter_scene_config.c new file mode 100644 index 000000000..3d6bd5803 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/lightmeter/gui/scenes/lightmeter_scene_help.c b/Applications/Official/DEV_FW/source/lightmeter/gui/scenes/lightmeter_scene_help.c new file mode 100644 index 000000000..0441f0925 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/lightmeter/gui/scenes/lightmeter_scene_main.c b/Applications/Official/DEV_FW/source/lightmeter/gui/scenes/lightmeter_scene_main.c new file mode 100644 index 000000000..8ba474461 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/lightmeter/gui/views/main_view.c b/Applications/Official/DEV_FW/source/lightmeter/gui/views/main_view.c new file mode 100644 index 000000000..fcbafbff4 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/lightmeter/gui/views/main_view.h b/Applications/Official/DEV_FW/source/lightmeter/gui/views/main_view.h new file mode 100644 index 000000000..42d2d1877 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/lightmeter/icons/T_10x14.png b/Applications/Official/DEV_FW/source/lightmeter/icons/T_10x14.png new file mode 100644 index 000000000..d81c2c424 Binary files /dev/null and b/Applications/Official/DEV_FW/source/lightmeter/icons/T_10x14.png differ diff --git a/Applications/Official/DEV_FW/source/lightmeter/icons/f_10x14.png b/Applications/Official/DEV_FW/source/lightmeter/icons/f_10x14.png new file mode 100644 index 000000000..c3e85c0ec Binary files /dev/null and b/Applications/Official/DEV_FW/source/lightmeter/icons/f_10x14.png differ diff --git a/Applications/Official/DEV_FW/source/lightmeter/images/framed_gui_config.png b/Applications/Official/DEV_FW/source/lightmeter/images/framed_gui_config.png new file mode 100644 index 000000000..b87c3bd5c Binary files /dev/null and b/Applications/Official/DEV_FW/source/lightmeter/images/framed_gui_config.png differ diff --git a/Applications/Official/DEV_FW/source/lightmeter/images/framed_gui_lux_meter.png b/Applications/Official/DEV_FW/source/lightmeter/images/framed_gui_lux_meter.png new file mode 100644 index 000000000..6ab0cf191 Binary files /dev/null and b/Applications/Official/DEV_FW/source/lightmeter/images/framed_gui_lux_meter.png differ diff --git a/Applications/Official/DEV_FW/source/lightmeter/images/framed_gui_main.png b/Applications/Official/DEV_FW/source/lightmeter/images/framed_gui_main.png new file mode 100644 index 000000000..23dc4a2cc Binary files /dev/null and b/Applications/Official/DEV_FW/source/lightmeter/images/framed_gui_main.png differ diff --git a/Applications/Official/DEV_FW/source/lightmeter/images/gui_config.png b/Applications/Official/DEV_FW/source/lightmeter/images/gui_config.png new file mode 100644 index 000000000..95fdcb167 Binary files /dev/null and b/Applications/Official/DEV_FW/source/lightmeter/images/gui_config.png differ diff --git a/Applications/Official/DEV_FW/source/lightmeter/images/gui_lux_meter.png b/Applications/Official/DEV_FW/source/lightmeter/images/gui_lux_meter.png new file mode 100644 index 000000000..f7159e671 Binary files /dev/null and b/Applications/Official/DEV_FW/source/lightmeter/images/gui_lux_meter.png differ diff --git a/Applications/Official/DEV_FW/source/lightmeter/images/gui_main.png b/Applications/Official/DEV_FW/source/lightmeter/images/gui_main.png new file mode 100644 index 000000000..ba4c3f75f Binary files /dev/null and b/Applications/Official/DEV_FW/source/lightmeter/images/gui_main.png differ diff --git a/Applications/Official/DEV_FW/source/lightmeter/lib/BH1750/BH1750.c b/Applications/Official/DEV_FW/source/lightmeter/lib/BH1750/BH1750.c new file mode 100644 index 000000000..28616e040 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/lightmeter/lib/BH1750/BH1750.h b/Applications/Official/DEV_FW/source/lightmeter/lib/BH1750/BH1750.h new file mode 100644 index 000000000..991350f7f --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/lightmeter/lib/BH1750/LICENSE b/Applications/Official/DEV_FW/source/lightmeter/lib/BH1750/LICENSE new file mode 100644 index 000000000..cb2f65db5 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/lightmeter/lib/BH1750/README.md b/Applications/Official/DEV_FW/source/lightmeter/lib/BH1750/README.md new file mode 100644 index 000000000..b1338d4ab --- /dev/null +++ b/Applications/Official/DEV_FW/source/lightmeter/lib/BH1750/README.md @@ -0,0 +1,2 @@ +# flipperzero-BH1750 +BH1750 light sensor library for Flipper Zero diff --git a/Applications/Official/DEV_FW/source/lightmeter/lib/BH1750/docs/BH1750.pdf b/Applications/Official/DEV_FW/source/lightmeter/lib/BH1750/docs/BH1750.pdf new file mode 100644 index 000000000..267efddc6 Binary files /dev/null and b/Applications/Official/DEV_FW/source/lightmeter/lib/BH1750/docs/BH1750.pdf differ diff --git a/Applications/Official/DEV_FW/source/lightmeter/lightmeter.c b/Applications/Official/DEV_FW/source/lightmeter/lightmeter.c new file mode 100644 index 000000000..07661d2d4 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/lightmeter/lightmeter.h b/Applications/Official/DEV_FW/source/lightmeter/lightmeter.h new file mode 100644 index 000000000..2558be3c5 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/lightmeter/lightmeter.png b/Applications/Official/DEV_FW/source/lightmeter/lightmeter.png new file mode 100644 index 000000000..cacd2276f Binary files /dev/null and b/Applications/Official/DEV_FW/source/lightmeter/lightmeter.png differ diff --git a/Applications/Official/DEV_FW/source/lightmeter/lightmeter_config.h b/Applications/Official/DEV_FW/source/lightmeter/lightmeter_config.h new file mode 100644 index 000000000..4f7e11e0d --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/lightmeter/lightmeter_helper.c b/Applications/Official/DEV_FW/source/lightmeter/lightmeter_helper.c new file mode 100644 index 000000000..465ccbce1 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/lightmeter/lightmeter_helper.h b/Applications/Official/DEV_FW/source/lightmeter/lightmeter_helper.h new file mode 100644 index 000000000..78ea6a8d8 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/lightmeter/module/back.jpg b/Applications/Official/DEV_FW/source/lightmeter/module/back.jpg new file mode 100644 index 000000000..366d7a852 Binary files /dev/null and b/Applications/Official/DEV_FW/source/lightmeter/module/back.jpg differ diff --git a/Applications/Official/DEV_FW/source/lightmeter/module/front.jpg b/Applications/Official/DEV_FW/source/lightmeter/module/front.jpg new file mode 100644 index 000000000..b38fd4150 Binary files /dev/null and b/Applications/Official/DEV_FW/source/lightmeter/module/front.jpg differ diff --git a/Applications/Official/DEV_FW/source/lightmeter/module/module.jpg b/Applications/Official/DEV_FW/source/lightmeter/module/module.jpg new file mode 100644 index 000000000..53f481338 Binary files /dev/null and b/Applications/Official/DEV_FW/source/lightmeter/module/module.jpg differ diff --git a/Applications/Official/DEV_FW/source/lightmeter/module/module_v2_enclosure.stl b/Applications/Official/DEV_FW/source/lightmeter/module/module_v2_enclosure.stl new file mode 100644 index 000000000..ee2be93fd Binary files /dev/null and b/Applications/Official/DEV_FW/source/lightmeter/module/module_v2_enclosure.stl differ diff --git a/Applications/Official/DEV_FW/source/lightmeter/module/module_v2_gerber.zip b/Applications/Official/DEV_FW/source/lightmeter/module/module_v2_gerber.zip new file mode 100644 index 000000000..02887f4bf Binary files /dev/null and b/Applications/Official/DEV_FW/source/lightmeter/module/module_v2_gerber.zip differ diff --git a/Applications/Official/DEV_FW/source/mandelbrot/Mandelbrot.png b/Applications/Official/DEV_FW/source/mandelbrot/Mandelbrot.png new file mode 100644 index 000000000..485f70e5c Binary files /dev/null and b/Applications/Official/DEV_FW/source/mandelbrot/Mandelbrot.png differ diff --git a/Applications/Official/DEV_FW/source/mandelbrot/application.fam b/Applications/Official/DEV_FW/source/mandelbrot/application.fam new file mode 100644 index 000000000..773835fe5 --- /dev/null +++ b/Applications/Official/DEV_FW/source/mandelbrot/application.fam @@ -0,0 +1,12 @@ +App( + appid="MandelbrotSet", + name="Mandelbrot Set", + apptype=FlipperAppType.EXTERNAL, + entry_point="mandelbrot_app", + cdefines=["APP_MANDELBROT_GAME"], + requires=["gui"], + stack_size=1 * 1024, + order=130, + fap_icon="Mandelbrot.png", + fap_category="Games", +) diff --git a/Applications/Official/DEV_FW/source/mandelbrot/mandelbrot.c b/Applications/Official/DEV_FW/source/mandelbrot/mandelbrot.c new file mode 100644 index 000000000..bfddc6a97 --- /dev/null +++ b/Applications/Official/DEV_FW/source/mandelbrot/mandelbrot.c @@ -0,0 +1,172 @@ +#include +#include +#include +#include +#include + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} PluginEvent; + +typedef struct { + float xZoom; + float yZoom; + float xOffset; + float yOffset; + float zoom; +} PluginState; + +bool mandelbrot_pixel(int x, int y, float xZoom, float yZoom, float xOffset, float yOffset) { + float ratio = 128.0 / 64.0; + //x0 := scaled x coordinate of pixel (scaled to lie in the Mandelbrot X scale (-2.00, 0.47)) + float x0 = (((x / 128.0) * ratio * xZoom)) - xOffset; + //y0 := scaled y coordinate of pixel (scaled to lie in the Mandelbrot Y scale (-1.12, 1.12)) + float y0 = ((y / 64.0) * yZoom) - yOffset; + float x1 = 0.0; + float y1 = 0.0; + float x2 = 0.0; + float y2 = 0.0; + + int iteration = 0; + int max_iteration = 50; + + while(x2 + y2 <= 4.0 && iteration < max_iteration) { + y1 = 2.0 * x1 * y1 + y0; + x1 = x2 - y2 + x0; + x2 = x1 * x1; + y2 = y1 * y1; + iteration++; + } + + if(iteration > 49) { + return true; + } + + return false; +} + +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); + + for(int y = 0; y < 64; y++) { + for(int x = 0; x < 128; x++) { + // did you know if you just pass the indivdiual bits of plugin_state instead of plugin_state + // you dont get any compiler warnings :) + if(mandelbrot_pixel( + x, + y, + plugin_state->xZoom, + plugin_state->yZoom, + plugin_state->xOffset, + plugin_state->yOffset)) { + canvas_draw_dot(canvas, x, y); + } + } + } + + 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 mandelbrot_state_init(PluginState* const plugin_state) { + plugin_state->xOffset = 3.0; + plugin_state->yOffset = 1.12; + plugin_state->xZoom = 2.47; + plugin_state->yZoom = 2.24; + plugin_state->zoom = 1; // this controls the camera when +} + +int32_t mandelbrot_app(void* p) { + UNUSED(p); + + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); + + PluginState* plugin_state = malloc(sizeof(PluginState)); + mandelbrot_state_init(plugin_state); + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, plugin_state, sizeof(PluginState))) { + FURI_LOG_E("mandelbrot", "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: + plugin_state->yOffset += 0.1 / plugin_state->zoom; + break; + case InputKeyDown: + plugin_state->yOffset += -0.1 / plugin_state->zoom; + break; + case InputKeyRight: + plugin_state->xOffset += -0.1 / plugin_state->zoom; + break; + case InputKeyLeft: + plugin_state->xOffset += 0.1 / plugin_state->zoom; + break; + case InputKeyOk: + plugin_state->xZoom -= (2.47 / 10) / plugin_state->zoom; + plugin_state->yZoom -= (2.24 / 10) / plugin_state->zoom; + // used to make camera control finer the more zoomed you are + // this needs to be some sort of curve + plugin_state->zoom += 0.15; + break; + case InputKeyBack: + processing = false; + break; + default: + break; + } + } + } + } else { + FURI_LOG_D("mandelbrot", "osMessageQueue: event timeout"); + // event timeout + } + 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); + + return 0; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/minesweeper/LICENSE b/Applications/Official/DEV_FW/source/minesweeper/LICENSE new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/Applications/Official/DEV_FW/source/minesweeper/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/Official/DEV_FW/source/minesweeper/README.md b/Applications/Official/DEV_FW/source/minesweeper/README.md new file mode 100644 index 000000000..28176bd5e --- /dev/null +++ b/Applications/Official/DEV_FW/source/minesweeper/README.md @@ -0,0 +1,20 @@ +# Minesweeper + +[Original Link](https://github.com/panki27/minesweeper) + +This is a Minesweeper implementation for the Flipper Zero device. + +![screenshot](img/screenshot.png) + +## Controls + +- Arrow buttons to move +- Push center button to open field +- Hold center button to toggle flag +- Push center button on an already open field that has the correct amount of flags surrounding it to auto-open the remaining ones (thanks @gelin!) + +## Compiling + +``` +./fbt firmware_minesweeper +``` diff --git a/Applications/Official/DEV_FW/source/minesweeper/application.fam b/Applications/Official/DEV_FW/source/minesweeper/application.fam new file mode 100644 index 000000000..405cb0d1c --- /dev/null +++ b/Applications/Official/DEV_FW/source/minesweeper/application.fam @@ -0,0 +1,12 @@ +App( + appid="Minesweeper", + name="Minesweeper", + apptype=FlipperAppType.EXTERNAL, + entry_point="minesweeper_app", + cdefines=["APP_MINESWEEPER"], + requires=["gui"], + stack_size=8 * 1024, + fap_category="Games", + fap_icon="minesweeper_icon.png", + order=35, +) diff --git a/Applications/Official/DEV_FW/source/minesweeper/assets.h b/Applications/Official/DEV_FW/source/minesweeper/assets.h new file mode 100644 index 000000000..b734f240f --- /dev/null +++ b/Applications/Official/DEV_FW/source/minesweeper/assets.h @@ -0,0 +1,144 @@ +#define tile_0_width 8 +#define tile_0_height 8 +static uint8_t tile_0_bits[] = { + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, +}; +#define tile_1_width 8 +#define tile_1_height 8 +static uint8_t tile_1_bits[] = { + 0x00, + 0x10, + 0x18, + 0x10, + 0x10, + 0x10, + 0x10, + 0x00, +}; +#define tile_2_width 8 +#define tile_2_height 8 +static uint8_t tile_2_bits[] = { + 0x00, + 0x1C, + 0x20, + 0x20, + 0x18, + 0x04, + 0x3C, + 0x00, +}; +#define tile_3_width 8 +#define tile_3_height 8 +static uint8_t tile_3_bits[] = { + 0x00, + 0x1C, + 0x20, + 0x20, + 0x18, + 0x20, + 0x1C, + 0x00, +}; +#define tile_4_width 8 +#define tile_4_height 8 +static uint8_t tile_4_bits[] = { + 0x00, + 0x04, + 0x14, + 0x14, + 0x3C, + 0x10, + 0x10, + 0x00, +}; +#define tile_5_width 8 +#define tile_5_height 8 +static uint8_t tile_5_bits[] = { + 0x00, + 0x3C, + 0x04, + 0x1C, + 0x20, + 0x20, + 0x1C, + 0x00, +}; +#define tile_6_width 8 +#define tile_6_height 8 +static uint8_t tile_6_bits[] = { + 0x00, + 0x18, + 0x24, + 0x04, + 0x1C, + 0x24, + 0x18, + 0x00, +}; +#define tile_7_width 8 +#define tile_7_height 8 +static uint8_t tile_7_bits[] = { + 0x00, + 0x3C, + 0x20, + 0x20, + 0x10, + 0x08, + 0x08, + 0x00, +}; +#define tile_8_width 8 +#define tile_8_height 8 +static uint8_t tile_8_bits[] = { + 0x00, + 0x18, + 0x24, + 0x18, + 0x24, + 0x24, + 0x18, + 0x00, +}; +#define tile_flag_width 8 +#define tile_flag_height 8 +static uint8_t tile_flag_bits[] = { + 0xFF, + 0x81, + 0xB9, + 0x89, + 0x89, + 0x9D, + 0x81, + 0xFF, +}; +#define tile_mine_width 8 +#define tile_mine_height 8 +static uint8_t tile_mine_bits[] = { + 0x55, + 0xAA, + 0x55, + 0xAA, + 0x55, + 0xAA, + 0x55, + 0xAA, +}; +#define tile_uncleared_width 8 +#define tile_uncleared_height 8 +static uint8_t tile_uncleared_bits[] = { + 0xFF, + 0x81, + 0x81, + 0x81, + 0x81, + 0x81, + 0x81, + 0xFF, +}; diff --git a/Applications/Official/DEV_FW/source/minesweeper/assets/asset b/Applications/Official/DEV_FW/source/minesweeper/assets/asset new file mode 100644 index 000000000..80a6c89f5 --- /dev/null +++ b/Applications/Official/DEV_FW/source/minesweeper/assets/asset @@ -0,0 +1,48 @@ +#define tile_0_width 8 +#define tile_0_height 8 +static uint8_t tile_0_bits[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, }; +#define tile_1_width 8 +#define tile_1_height 8 +static uint8_t tile_1_bits[] = { + 0x00, 0x10, 0x18, 0x10, 0x10, 0x10, 0x10, 0x00, }; +#define tile_2_width 8 +#define tile_2_height 8 +static uint8_t tile_2_bits[] = { + 0x00, 0x1C, 0x20, 0x20, 0x18, 0x04, 0x3C, 0x00, }; +#define tile_3_width 8 +#define tile_3_height 8 +static uint8_t tile_3_bits[] = { + 0x00, 0x1C, 0x20, 0x20, 0x18, 0x20, 0x1C, 0x00, }; +#define tile_4_width 8 +#define tile_4_height 8 +static uint8_t tile_4_bits[] = { + 0x00, 0x04, 0x14, 0x14, 0x3C, 0x10, 0x10, 0x00, }; +#define tile_5_width 8 +#define tile_5_height 8 +static uint8_t tile_5_bits[] = { + 0x00, 0x3C, 0x04, 0x1C, 0x20, 0x20, 0x1C, 0x00, }; +#define tile_6_width 8 +#define tile_6_height 8 +static uint8_t tile_6_bits[] = { + 0x00, 0x18, 0x24, 0x04, 0x1C, 0x24, 0x18, 0x00, }; +#define tile_7_width 8 +#define tile_7_height 8 +static uint8_t tile_7_bits[] = { + 0x00, 0x3C, 0x20, 0x20, 0x10, 0x08, 0x08, 0x00, }; +#define tile_8_width 8 +#define tile_8_height 8 +static uint8_t tile_8_bits[] = { + 0x00, 0x18, 0x24, 0x18, 0x24, 0x24, 0x18, 0x00, }; +#define tile_flag_width 8 +#define tile_flag_height 8 +static uint8_t tile_flag_bits[] = { + 0xFF, 0x81, 0xB9, 0x89, 0x89, 0x9D, 0x81, 0xFF, }; +#define tile_mine_width 8 +#define tile_mine_height 8 +static uint8_t tile_mine_bits[] = { + 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, }; +#define tile_uncleared_width 8 +#define tile_uncleared_height 8 +static uint8_t tile_uncleared_bits[] = { + 0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xFF, }; diff --git a/Applications/Official/DEV_FW/source/minesweeper/assets/mockup.png b/Applications/Official/DEV_FW/source/minesweeper/assets/mockup.png new file mode 100644 index 000000000..00d99b1f2 Binary files /dev/null and b/Applications/Official/DEV_FW/source/minesweeper/assets/mockup.png differ diff --git a/Applications/Official/DEV_FW/source/minesweeper/assets/tile_0.png b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_0.png new file mode 100644 index 000000000..c7fec6cca Binary files /dev/null and b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_0.png differ diff --git a/Applications/Official/DEV_FW/source/minesweeper/assets/tile_0.xbm b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_0.xbm new file mode 100644 index 000000000..3da1fa7d7 --- /dev/null +++ b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_0.xbm @@ -0,0 +1,4 @@ +#define tile_0_width 8 +#define tile_0_height 8 +static char tile_0_bits[] = { + 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, }; diff --git a/Applications/Official/DEV_FW/source/minesweeper/assets/tile_1.png b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_1.png new file mode 100644 index 000000000..588d77c2a Binary files /dev/null and b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_1.png differ diff --git a/Applications/Official/DEV_FW/source/minesweeper/assets/tile_1.xbm b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_1.xbm new file mode 100644 index 000000000..568c72155 --- /dev/null +++ b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_1.xbm @@ -0,0 +1,4 @@ +#define tile_1_width 8 +#define tile_1_height 8 +static uint8_t tile_1_bits[] = { + 0x00, 0x10, 0x18, 0x10, 0x10, 0x10, 0x10, 0x00, }; diff --git a/Applications/Official/DEV_FW/source/minesweeper/assets/tile_2.png b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_2.png new file mode 100644 index 000000000..9c1c59013 Binary files /dev/null and b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_2.png differ diff --git a/Applications/Official/DEV_FW/source/minesweeper/assets/tile_2.xbm b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_2.xbm new file mode 100644 index 000000000..198ae0879 --- /dev/null +++ b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_2.xbm @@ -0,0 +1,4 @@ +#define tile_2_width 8 +#define tile_2_height 8 +static uint8_t tile_2_bits[] = { + 0x00, 0x1C, 0x20, 0x20, 0x18, 0x04, 0x3C, 0x00, }; diff --git a/Applications/Official/DEV_FW/source/minesweeper/assets/tile_3.png b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_3.png new file mode 100644 index 000000000..a229d01f5 Binary files /dev/null and b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_3.png differ diff --git a/Applications/Official/DEV_FW/source/minesweeper/assets/tile_3.xbm b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_3.xbm new file mode 100644 index 000000000..022bd3c6b --- /dev/null +++ b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_3.xbm @@ -0,0 +1,4 @@ +#define tile_3_width 8 +#define tile_3_height 8 +static uint8_t tile_3_bits[] = { + 0x00, 0x1C, 0x20, 0x20, 0x18, 0x20, 0x1C, 0x00, }; diff --git a/Applications/Official/DEV_FW/source/minesweeper/assets/tile_4.png b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_4.png new file mode 100644 index 000000000..dfaab7b27 Binary files /dev/null and b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_4.png differ diff --git a/Applications/Official/DEV_FW/source/minesweeper/assets/tile_4.xbm b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_4.xbm new file mode 100644 index 000000000..58287176c --- /dev/null +++ b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_4.xbm @@ -0,0 +1,4 @@ +#define tile_4_width 8 +#define tile_4_height 8 +static uint8_t tile_4_bits[] = { + 0x00, 0x04, 0x14, 0x14, 0x3C, 0x10, 0x10, 0x00, }; diff --git a/Applications/Official/DEV_FW/source/minesweeper/assets/tile_5.png b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_5.png new file mode 100644 index 000000000..2c8f03c3d Binary files /dev/null and b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_5.png differ diff --git a/Applications/Official/DEV_FW/source/minesweeper/assets/tile_5.xbm b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_5.xbm new file mode 100644 index 000000000..e36f51a36 --- /dev/null +++ b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_5.xbm @@ -0,0 +1,4 @@ +#define tile_5_width 8 +#define tile_5_height 8 +static uint8_t tile_5_bits[] = { + 0x00, 0x3C, 0x04, 0x1C, 0x20, 0x20, 0x1C, 0x00, }; diff --git a/Applications/Official/DEV_FW/source/minesweeper/assets/tile_6.png b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_6.png new file mode 100644 index 000000000..2af1b6511 Binary files /dev/null and b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_6.png differ diff --git a/Applications/Official/DEV_FW/source/minesweeper/assets/tile_6.xbm b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_6.xbm new file mode 100644 index 000000000..74b290e07 --- /dev/null +++ b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_6.xbm @@ -0,0 +1,4 @@ +#define tile_6_width 8 +#define tile_6_height 8 +static uint8_t tile_6_bits[] = { + 0x00, 0x18, 0x24, 0x04, 0x1C, 0x24, 0x18, 0x00, }; diff --git a/Applications/Official/DEV_FW/source/minesweeper/assets/tile_7.png b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_7.png new file mode 100644 index 000000000..c6a4e204b Binary files /dev/null and b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_7.png differ diff --git a/Applications/Official/DEV_FW/source/minesweeper/assets/tile_7.xbm b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_7.xbm new file mode 100644 index 000000000..6aafa6ad0 --- /dev/null +++ b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_7.xbm @@ -0,0 +1,4 @@ +#define tile_7_width 8 +#define tile_7_height 8 +static uint8_t tile_7_bits[] = { + 0x00, 0x3C, 0x20, 0x20, 0x10, 0x08, 0x08, 0x00, }; diff --git a/Applications/Official/DEV_FW/source/minesweeper/assets/tile_8.png b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_8.png new file mode 100644 index 000000000..cebbe5389 Binary files /dev/null and b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_8.png differ diff --git a/Applications/Official/DEV_FW/source/minesweeper/assets/tile_8.xbm b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_8.xbm new file mode 100644 index 000000000..05ab21045 --- /dev/null +++ b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_8.xbm @@ -0,0 +1,4 @@ +#define tile_8_width 8 +#define tile_8_height 8 +static uint8_t tile_8_bits[] = { + 0x00, 0x18, 0x24, 0x18, 0x24, 0x24, 0x18, 0x00, }; diff --git a/Applications/Official/DEV_FW/source/minesweeper/assets/tile_empty.png b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_empty.png new file mode 100644 index 000000000..1f48880fb Binary files /dev/null and b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_empty.png differ diff --git a/Applications/Official/DEV_FW/source/minesweeper/assets/tile_flag.png b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_flag.png new file mode 100644 index 000000000..6ec2a71f8 Binary files /dev/null and b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_flag.png differ diff --git a/Applications/Official/DEV_FW/source/minesweeper/assets/tile_flag.xbm b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_flag.xbm new file mode 100644 index 000000000..ed4b9c0aa --- /dev/null +++ b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_flag.xbm @@ -0,0 +1,4 @@ +#define tile_flag_width 8 +#define tile_flag_height 8 +static uint8_t tile_flag_bits[] = { + 0xFF, 0x81, 0xB9, 0x89, 0x89, 0x9D, 0x81, 0xFF, }; diff --git a/Applications/Official/DEV_FW/source/minesweeper/assets/tile_mine.png b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_mine.png new file mode 100644 index 000000000..3c4a38e6e Binary files /dev/null and b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_mine.png differ diff --git a/Applications/Official/DEV_FW/source/minesweeper/assets/tile_mine.xbm b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_mine.xbm new file mode 100644 index 000000000..6e05347ae --- /dev/null +++ b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_mine.xbm @@ -0,0 +1,4 @@ +#define tile_mine_width 8 +#define tile_mine_height 8 +static uint8_t tile_mine_bits[] = { + 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, }; diff --git a/Applications/Official/DEV_FW/source/minesweeper/assets/tile_uncleared.png b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_uncleared.png new file mode 100644 index 000000000..b98de1116 Binary files /dev/null and b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_uncleared.png differ diff --git a/Applications/Official/DEV_FW/source/minesweeper/assets/tile_uncleared.xbm b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_uncleared.xbm new file mode 100644 index 000000000..e8a8ef610 --- /dev/null +++ b/Applications/Official/DEV_FW/source/minesweeper/assets/tile_uncleared.xbm @@ -0,0 +1,4 @@ +#define tile_uncleared_width 8 +#define tile_uncleared_height 8 +static uint8_t tile_uncleared_bits[] = { + 0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xFF, }; diff --git a/Applications/Official/DEV_FW/source/minesweeper/img/screenshot.png b/Applications/Official/DEV_FW/source/minesweeper/img/screenshot.png new file mode 100644 index 000000000..65b307c55 Binary files /dev/null and b/Applications/Official/DEV_FW/source/minesweeper/img/screenshot.png differ diff --git a/Applications/Official/DEV_FW/source/minesweeper/minesweeper.c b/Applications/Official/DEV_FW/source/minesweeper/minesweeper.c new file mode 100644 index 000000000..fa9fdcc78 --- /dev/null +++ b/Applications/Official/DEV_FW/source/minesweeper/minesweeper.c @@ -0,0 +1,520 @@ +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "assets.h" + +#define PLAYFIELD_WIDTH 16 +#define PLAYFIELD_HEIGHT 7 +#define TILE_WIDTH 8 +#define TILE_HEIGHT 8 + +#define MINECOUNT 20 + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} PluginEvent; + +typedef enum { + TileType0, // this HAS to be in order, for hint assignment to be ez pz + TileType1, + TileType2, + TileType3, + TileType4, + TileType5, + TileType6, + TileType7, + TileType8, + TileTypeUncleared, + TileTypeFlag, + TileTypeMine +} TileType; + +typedef enum { FieldEmpty, FieldMine } Field; + +typedef struct { + Field minefield[PLAYFIELD_WIDTH][PLAYFIELD_HEIGHT]; + TileType playfield[PLAYFIELD_WIDTH][PLAYFIELD_HEIGHT]; + FuriTimer* timer; + int cursor_x; + int cursor_y; + int mines_left; + int fields_cleared; + int flags_set; + bool game_started; + uint32_t game_started_tick; +} Minesweeper; + +static void timer_callback(void* ctx) { + UNUSED(ctx); + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + notification_message(notification, &sequence_reset_vibro); + furi_record_close(RECORD_NOTIFICATION); +} + +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) { + const Minesweeper* minesweeper_state = acquire_mutex((ValueMutex*)ctx, 25); + if(minesweeper_state == NULL) { + return; + } + FuriString* mineStr; + FuriString* timeStr; + mineStr = furi_string_alloc(); + timeStr = furi_string_alloc(); + + furi_string_printf(mineStr, "Mines: %d", MINECOUNT - minesweeper_state->flags_set); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(mineStr)); + + int seconds = 0; + int minutes = 0; + if(minesweeper_state->game_started) { + uint32_t ticks_elapsed = furi_get_tick() - minesweeper_state->game_started_tick; + seconds = (int)ticks_elapsed / furi_kernel_get_tick_frequency(); + minutes = (int)seconds / 60; + seconds = seconds % 60; + } + furi_string_printf(timeStr, "%01d:%02d", minutes, seconds); + canvas_draw_str_aligned(canvas, 128, 0, AlignRight, AlignTop, furi_string_get_cstr(timeStr)); + + uint8_t* tile_to_draw; + + for(int y = 0; y < PLAYFIELD_HEIGHT; y++) { + for(int x = 0; x < PLAYFIELD_WIDTH; x++) { + if(x == minesweeper_state->cursor_x && y == minesweeper_state->cursor_y) { + canvas_invert_color(canvas); + } + switch(minesweeper_state->playfield[x][y]) { + case TileType0: + tile_to_draw = tile_0_bits; + break; + case TileType1: + tile_to_draw = tile_1_bits; + break; + case TileType2: + tile_to_draw = tile_2_bits; + break; + case TileType3: + tile_to_draw = tile_3_bits; + break; + case TileType4: + tile_to_draw = tile_4_bits; + break; + case TileType5: + tile_to_draw = tile_5_bits; + break; + case TileType6: + tile_to_draw = tile_6_bits; + break; + case TileType7: + tile_to_draw = tile_7_bits; + break; + case TileType8: + tile_to_draw = tile_8_bits; + break; + case TileTypeFlag: + tile_to_draw = tile_flag_bits; + break; + case TileTypeUncleared: + tile_to_draw = tile_uncleared_bits; + break; + case TileTypeMine: + tile_to_draw = tile_mine_bits; + break; + default: + // this should never happen + tile_to_draw = tile_mine_bits; + break; + } + canvas_draw_xbm( + canvas, + x * TILE_HEIGHT, // x + 8 + (y * TILE_WIDTH), // y + TILE_WIDTH, + TILE_HEIGHT, + tile_to_draw); + if(x == minesweeper_state->cursor_x && y == minesweeper_state->cursor_y) { + canvas_invert_color(canvas); + } + } + } + + furi_string_free(mineStr); + furi_string_free(timeStr); + release_mutex((ValueMutex*)ctx, minesweeper_state); +} + +static void setup_playfield(Minesweeper* minesweeper_state) { + int mines_left = MINECOUNT; + for(int y = 0; y < PLAYFIELD_HEIGHT; y++) { + for(int x = 0; x < PLAYFIELD_WIDTH; x++) { + minesweeper_state->minefield[x][y] = FieldEmpty; + minesweeper_state->playfield[x][y] = TileTypeUncleared; + } + } + while(mines_left > 0) { + int rand_x = rand() % PLAYFIELD_WIDTH; + int rand_y = rand() % PLAYFIELD_HEIGHT; + // make sure first guess isn't a mine + if(minesweeper_state->minefield[rand_x][rand_y] == FieldEmpty && + (minesweeper_state->cursor_x != rand_x && minesweeper_state->cursor_y != rand_y)) { + minesweeper_state->minefield[rand_x][rand_y] = FieldMine; + mines_left--; + } + } + minesweeper_state->mines_left = MINECOUNT; + minesweeper_state->fields_cleared = 0; + minesweeper_state->flags_set = 0; + minesweeper_state->game_started_tick = furi_get_tick(); + minesweeper_state->game_started = false; +} + +static void place_flag(Minesweeper* minesweeper_state) { + if(minesweeper_state->playfield[minesweeper_state->cursor_x][minesweeper_state->cursor_y] == + TileTypeUncleared) { + minesweeper_state->playfield[minesweeper_state->cursor_x][minesweeper_state->cursor_y] = + TileTypeFlag; + minesweeper_state->flags_set++; + } else if( + minesweeper_state->playfield[minesweeper_state->cursor_x][minesweeper_state->cursor_y] == + TileTypeFlag) { + minesweeper_state->playfield[minesweeper_state->cursor_x][minesweeper_state->cursor_y] = + TileTypeUncleared; + minesweeper_state->flags_set--; + } +} + +static bool game_lost(Minesweeper* minesweeper_state) { + // returns true if the player wants to restart, otherwise false + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + + DialogMessage* message = dialog_message_alloc(); + const char* header_text = "Game Over"; + const char* message_text = "You hit a mine!"; + + dialog_message_set_header(message, header_text, 64, 3, AlignCenter, AlignTop); + dialog_message_set_text(message, message_text, 64, 32, AlignCenter, AlignCenter); + dialog_message_set_buttons(message, NULL, "Play again", NULL); + + dialog_message_set_icon(message, NULL, 0, 10); + + // Set cursor to initial position + minesweeper_state->cursor_x = 0; + minesweeper_state->cursor_y = 0; + + NotificationApp* notifications = furi_record_open(RECORD_NOTIFICATION); + notification_message(notifications, &sequence_set_vibro_on); + furi_record_close(RECORD_NOTIFICATION); + furi_timer_start(minesweeper_state->timer, (uint32_t)furi_kernel_get_tick_frequency() * 0.2); + + DialogMessageButton choice = dialog_message_show(dialogs, message); + dialog_message_free(message); + furi_record_close(RECORD_DIALOGS); + + return choice == DialogMessageButtonCenter; +} + +static bool game_won(Minesweeper* minesweeper_state) { + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + + FuriString* tempStr; + tempStr = furi_string_alloc(); + + int seconds = 0; + int minutes = 0; + uint32_t ticks_elapsed = furi_get_tick() - minesweeper_state->game_started_tick; + seconds = (int)ticks_elapsed / furi_kernel_get_tick_frequency(); + minutes = (int)seconds / 60; + seconds = seconds % 60; + + DialogMessage* message = dialog_message_alloc(); + const char* header_text = "Game won!"; + furi_string_cat_printf(tempStr, "Minefield cleared in %01d:%02d", minutes, seconds); + dialog_message_set_header(message, header_text, 64, 3, AlignCenter, AlignTop); + dialog_message_set_text( + message, furi_string_get_cstr(tempStr), 64, 32, AlignCenter, AlignCenter); + dialog_message_set_buttons(message, NULL, "Play again", NULL); + dialog_message_set_icon(message, NULL, 72, 17); + + DialogMessageButton choice = dialog_message_show(dialogs, message); + dialog_message_free(message); + furi_string_free(tempStr); + furi_record_close(RECORD_DIALOGS); + return choice == DialogMessageButtonCenter; +} + +// returns false if the move loses the game - otherwise true +static bool play_move(Minesweeper* minesweeper_state, int cursor_x, int cursor_y) { + if(minesweeper_state->playfield[cursor_x][cursor_y] == TileTypeFlag) { + // we're on a flagged field, do nothing + return true; + } + if(minesweeper_state->minefield[cursor_x][cursor_y] == FieldMine) { + // player loses - draw mine + minesweeper_state->playfield[cursor_x][cursor_y] = TileTypeMine; + return false; + } + + if(minesweeper_state->playfield[cursor_x][cursor_y] >= TileType1 && + minesweeper_state->playfield[cursor_x][cursor_y] <= TileType8) { + // click on a cleared cell with a number + // count the flags around + int flags = 0; + for(int y = cursor_y - 1; y <= cursor_y + 1; y++) { + for(int x = cursor_x - 1; x <= cursor_x + 1; x++) { + if(x == cursor_x && y == cursor_y) { + // we're on the cell the user selected, so ignore. + continue; + } + // make sure we don't go OOB + if(x >= 0 && x < PLAYFIELD_WIDTH && y >= 0 && y < PLAYFIELD_HEIGHT) { + if(minesweeper_state->playfield[x][y] == TileTypeFlag) { + flags++; + } + } + } + } + int mines = minesweeper_state->playfield[cursor_x][cursor_y]; // ¯\_(ツ)_/¯ + if(flags == mines) { + // auto uncover all non-flags around (to win faster ;) + for(int auto_y = cursor_y - 1; auto_y <= cursor_y + 1; auto_y++) { + for(int auto_x = cursor_x - 1; auto_x <= cursor_x + 1; auto_x++) { + if(auto_x == cursor_x && auto_y == cursor_y) { + continue; + } + if(auto_x >= 0 && auto_x < PLAYFIELD_WIDTH && auto_y >= 0 && + auto_y < PLAYFIELD_HEIGHT) { + if(minesweeper_state->playfield[auto_x][auto_y] == TileTypeUncleared) { + if(!play_move(minesweeper_state, auto_x, auto_y)) { + // flags were wrong, we got a mine! + return false; + } + } + } + } + } + // we're done without hitting a mine - so return + return true; + } + } + + // calculate number of surrounding mines. + int hint = 0; + for(int y = cursor_y - 1; y <= cursor_y + 1; y++) { + for(int x = cursor_x - 1; x <= cursor_x + 1; x++) { + if(x == cursor_x && y == cursor_y) { + // we're on the cell the user selected, so ignore. + continue; + } + // make sure we don't go OOB + if(x >= 0 && x < PLAYFIELD_WIDTH && y >= 0 && y < PLAYFIELD_HEIGHT) { + if(minesweeper_state->minefield[x][y] == FieldMine) { + hint++; + } + } + } + } + // 〜( ̄▽ ̄〜) don't judge me (〜 ̄▽ ̄)〜 + minesweeper_state->playfield[cursor_x][cursor_y] = hint; + minesweeper_state->fields_cleared++; + FURI_LOG_D("Minesweeper", "Setting %d,%d to %d", cursor_x, cursor_y, hint); + if(hint == 0) { + // the field is "empty" + // auto open surrounding fields. + for(int auto_y = cursor_y - 1; auto_y <= cursor_y + 1; auto_y++) { + for(int auto_x = cursor_x - 1; auto_x <= cursor_x + 1; auto_x++) { + if(auto_x == cursor_x && auto_y == cursor_y) { + continue; + } + if(auto_x >= 0 && auto_x < PLAYFIELD_WIDTH && auto_y >= 0 && + auto_y < PLAYFIELD_HEIGHT) { + if(minesweeper_state->playfield[auto_x][auto_y] == TileTypeUncleared) { + play_move(minesweeper_state, auto_x, auto_y); + } + } + } + } + } + return true; +} + +static void minesweeper_state_init(Minesweeper* const minesweeper_state) { + minesweeper_state->cursor_x = minesweeper_state->cursor_y = 0; + minesweeper_state->game_started = false; + for(int y = 0; y < PLAYFIELD_HEIGHT; y++) { + for(int x = 0; x < PLAYFIELD_WIDTH; x++) { + minesweeper_state->playfield[x][y] = TileTypeUncleared; + } + } +} + +int32_t minesweeper_app(void* p) { + UNUSED(p); + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + + DialogMessage* message = dialog_message_alloc(); + const char* header_text = "Minesweeper"; + const char* message_text = "Hold OK pressed to toggle flags.\ngithub.com/panki27"; + + dialog_message_set_header(message, header_text, 64, 3, AlignCenter, AlignTop); + dialog_message_set_text(message, message_text, 64, 32, AlignCenter, AlignCenter); + dialog_message_set_buttons(message, NULL, "Play", NULL); + + dialog_message_set_icon(message, NULL, 0, 10); + + dialog_message_show(dialogs, message); + dialog_message_free(message); + furi_record_close(RECORD_DIALOGS); + + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); + + Minesweeper* minesweeper_state = malloc(sizeof(Minesweeper)); + // setup + minesweeper_state_init(minesweeper_state); + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, minesweeper_state, sizeof(minesweeper_state))) { + FURI_LOG_E("Minesweeper", "cannot create mutex\r\n"); + free(minesweeper_state); + return 255; + } + // 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); + minesweeper_state->timer = furi_timer_alloc(timer_callback, FuriTimerTypeOnce, &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); + Minesweeper* minesweeper_state = (Minesweeper*)acquire_mutex_block(&state_mutex); + if(event_status == FuriStatusOk) { + // press events + if(event.type == EventTypeKey) { + if(event.input.type == InputTypeShort) { + switch(event.input.key) { + case InputKeyUp: + minesweeper_state->cursor_y--; + if(minesweeper_state->cursor_y < 0) { + minesweeper_state->cursor_y = PLAYFIELD_HEIGHT - 1; + } + break; + case InputKeyDown: + minesweeper_state->cursor_y++; + if(minesweeper_state->cursor_y >= PLAYFIELD_HEIGHT) { + minesweeper_state->cursor_y = 0; + } + break; + case InputKeyRight: + minesweeper_state->cursor_x++; + if(minesweeper_state->cursor_x >= PLAYFIELD_WIDTH) { + minesweeper_state->cursor_x = 0; + } + break; + case InputKeyLeft: + minesweeper_state->cursor_x--; + if(minesweeper_state->cursor_x < 0) { + minesweeper_state->cursor_x = PLAYFIELD_WIDTH - 1; + } + break; + case InputKeyOk: + if(!minesweeper_state->game_started) { + setup_playfield(minesweeper_state); + minesweeper_state->game_started = true; + } + if(!play_move( + minesweeper_state, + minesweeper_state->cursor_x, + minesweeper_state->cursor_y)) { + // ooops. looks like we hit a mine! + if(game_lost(minesweeper_state)) { + // player wants to restart. + setup_playfield(minesweeper_state); + } else { + // player wants to exit :( + processing = false; + } + } else { + // check win condition. + if(minesweeper_state->fields_cleared == + (PLAYFIELD_HEIGHT * PLAYFIELD_WIDTH) - MINECOUNT) { + if(game_won(minesweeper_state)) { + //player wants to restart + setup_playfield(minesweeper_state); + } else { + processing = false; + } + } + } + break; + case InputKeyBack: + // Exit the plugin + processing = false; + break; + default: + break; + } + } else if(event.input.type == InputTypeLong) { + // hold events + FURI_LOG_D("Minesweeper", "Got a long press!"); + switch(event.input.key) { + case InputKeyUp: + case InputKeyDown: + case InputKeyRight: + case InputKeyLeft: + break; + case InputKeyOk: + FURI_LOG_D("Minesweeper", "Toggling flag"); + place_flag(minesweeper_state); + break; + case InputKeyBack: + processing = false; + break; + default: + break; + } + } + } + } else { + // event timeout + ; + } + view_port_update(view_port); + release_mutex(&state_mutex, minesweeper_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(minesweeper_state->timer); + free(minesweeper_state); + + return 0; +} diff --git a/Applications/Official/DEV_FW/source/minesweeper/minesweeper_icon.png b/Applications/Official/DEV_FW/source/minesweeper/minesweeper_icon.png new file mode 100644 index 000000000..8a7cd9468 Binary files /dev/null and b/Applications/Official/DEV_FW/source/minesweeper/minesweeper_icon.png differ diff --git a/Applications/Official/DEV_FW/source/montyhall/Monty.png b/Applications/Official/DEV_FW/source/montyhall/Monty.png new file mode 100644 index 000000000..1f4c14cd6 Binary files /dev/null and b/Applications/Official/DEV_FW/source/montyhall/Monty.png differ diff --git a/Applications/Official/DEV_FW/source/montyhall/application.fam b/Applications/Official/DEV_FW/source/montyhall/application.fam new file mode 100644 index 000000000..66d082853 --- /dev/null +++ b/Applications/Official/DEV_FW/source/montyhall/application.fam @@ -0,0 +1,12 @@ +App( + appid="MontyHall", + name="Monty Hall", + apptype=FlipperAppType.EXTERNAL, + entry_point="montyhall_game_app", + cdefines=["APP_MONTYHALL_GAME"], + requires=["gui"], + stack_size=1 * 1024, + order=185, + fap_icon="Monty.png", + fap_category="Games", +) diff --git a/Applications/Official/DEV_FW/source/montyhall/monteyhall.c b/Applications/Official/DEV_FW/source/montyhall/monteyhall.c new file mode 100644 index 000000000..024d539ec --- /dev/null +++ b/Applications/Official/DEV_FW/source/montyhall/monteyhall.c @@ -0,0 +1,450 @@ +#include +#include +#include +#include + +#include +#include + +#define SCREEN_WIDTH 128 +#define SCREEN_HEIGHT 64 + +//AUTHOR: https://github.com/DevMilanIan +//I_DoorClosed_22x35 sourced from VideoPoker/poker.c -> I_CardBack22x35 +//PRs for syntax, formatting, etc can get you listed as a contributor :) + +// CONCEPT: one of three doors will have a car while the other two have only a goat +// randomize a winning door each round, let the player choose a first selection +// reveal a goat door and allow the player to keep or switch their selection +// based on the Monty Hall problem from Let's Make a Deal + +//void draw_goat(Canvas* canvas, int x, int y) { TODO } + +void draw_car(Canvas* canvas, int x, int y) { + // x -> leftmost pixel, y -> topmost pixel + // could be in another file or a pixel array but idk how to so feel free to PR + + canvas_draw_dot(canvas, x + 1, y + 4); + canvas_draw_dot(canvas, x + 1, y + 5); + canvas_draw_dot(canvas, x + 2, y + 3); + canvas_draw_dot(canvas, x + 2, y + 6); + canvas_draw_dot(canvas, x + 3, y + 3); + canvas_draw_dot(canvas, x + 3, y + 6); + + canvas_draw_dot(canvas, x + 4, y + 2); + canvas_draw_dot(canvas, x + 4, y + 3); + canvas_draw_dot(canvas, x + 4, y + 6); + canvas_draw_dot(canvas, x + 4, y + 7); + + canvas_draw_dot(canvas, x + 5, y + 1); + canvas_draw_dot(canvas, x + 5, y + 2); + canvas_draw_dot(canvas, x + 5, y + 3); + canvas_draw_dot(canvas, x + 5, y + 5); + canvas_draw_dot(canvas, x + 5, y + 8); + + canvas_draw_dot(canvas, x + 6, y); + canvas_draw_dot(canvas, x + 6, y + 1); + canvas_draw_dot(canvas, x + 6, y + 3); + canvas_draw_dot(canvas, x + 6, y + 5); + canvas_draw_dot(canvas, x + 6, y + 8); + + canvas_draw_dot(canvas, x + 7, y); + canvas_draw_dot(canvas, x + 7, y + 3); + canvas_draw_dot(canvas, x + 7, y + 6); + canvas_draw_dot(canvas, x + 7, y + 7); + + canvas_draw_dot(canvas, x + 8, y); + canvas_draw_dot(canvas, x + 8, y + 3); + canvas_draw_dot(canvas, x + 8, y + 6); + + canvas_draw_dot(canvas, x + 9, y); + canvas_draw_dot(canvas, x + 9, y + 3); + canvas_draw_dot(canvas, x + 9, y + 6); + + canvas_draw_dot(canvas, x + 10, y); + canvas_draw_dot(canvas, x + 10, y + 3); + canvas_draw_dot(canvas, x + 10, y + 6); + + canvas_draw_dot(canvas, x + 11, y); + canvas_draw_dot(canvas, x + 11, y + 1); + canvas_draw_dot(canvas, x + 11, y + 3); + canvas_draw_dot(canvas, x + 11, y + 6); + + canvas_draw_dot(canvas, x + 12, y + 1); + canvas_draw_dot(canvas, x + 12, y + 2); + canvas_draw_dot(canvas, x + 12, y + 3); + canvas_draw_dot(canvas, x + 12, y + 6); + canvas_draw_dot(canvas, x + 12, y + 7); + + canvas_draw_dot(canvas, x + 13, y + 2); + canvas_draw_dot(canvas, x + 13, y + 3); + canvas_draw_dot(canvas, x + 13, y + 5); + canvas_draw_dot(canvas, x + 13, y + 8); + + canvas_draw_dot(canvas, x + 14, y + 1); + canvas_draw_dot(canvas, x + 14, y + 2); + canvas_draw_dot(canvas, x + 14, y + 5); + canvas_draw_dot(canvas, x + 14, y + 8); + + canvas_draw_dot(canvas, x + 15, y); + canvas_draw_dot(canvas, x + 15, y + 1); + canvas_draw_dot(canvas, x + 15, y + 6); + canvas_draw_dot(canvas, x + 15, y + 7); + + canvas_draw_dot(canvas, x + 16, y); + canvas_draw_dot(canvas, x + 16, y + 1); + canvas_draw_dot(canvas, x + 16, y + 2); + canvas_draw_dot(canvas, x + 16, y + 3); + canvas_draw_dot(canvas, x + 16, y + 4); + canvas_draw_dot(canvas, x + 16, y + 5); +} + +const uint8_t _I_DoorClosed_22x35_0[] = { + 0x01, 0x00, 0x23, 0x00, 0xfe, 0x7f, 0xe1, 0xf0, 0x28, 0x04, 0x43, 0xe3, 0xff, + 0x91, 0xea, 0x75, 0x52, 0x6a, 0xad, 0x56, 0x5b, 0xad, 0xd5, 0x4a, 0x80, 0xbe, + 0x05, 0xf0, 0x2f, 0x81, 0x7c, 0x0b, 0x45, 0x32, 0x2c, 0x91, 0x7c, 0x8c, 0xa4, +}; +const uint8_t* _I_DoorClosed_22x35[] = {_I_DoorClosed_22x35_0}; +const Icon I_DoorClosed_22x35 = + {.width = 22, .height = 35, .frame_count = 1, .frame_rate = 0, .frames = _I_DoorClosed_22x35}; + +typedef struct { + bool isOpen; + bool isSelected; // picked in RoundOne, RoundThree + bool isWinningDoor; // randomized in RoundOne +} Door; + +typedef struct { + Door doors[3]; + bool didSelect; // false in RoundOne -> RoundTwo when true + bool didSwitch; // determined in RoundFour +} DoorState; + +typedef enum { + RoundOne, // all doors closed, player selects a door when ready + RoundTwo, // door selected, reveal one of the remaining two (can go straight to GameOver) + RoundThree, // player can keep or switch their selection + RoundFour, // reveal all doors + GameOver // score has been updated, allow restart +} GameState; + +typedef struct { + GameState game_state; + DoorState door_state; + uint16_t score; +} MontyState; + +static void montyhall_game_init_state(MontyState* monty_state) { + if(!monty_state->score) { + monty_state->score = 0; + } + monty_state->door_state.didSelect = false; + + for(int i = 0; i < 3; i++) { + monty_state->door_state.doors[i].isOpen = false; + monty_state->door_state.doors[i].isSelected = false; + monty_state->door_state.doors[i].isWinningDoor = false; + } + + monty_state->game_state = RoundOne; + int doorIndex = random() % 3; + monty_state->door_state.doors[doorIndex].isWinningDoor = true; +} + +void selectDoor(MontyState* monty_state, int doorIndex) { + if(monty_state->game_state == RoundOne) { + monty_state->door_state.doors[doorIndex].isSelected = true; + if(monty_state->door_state.doors[doorIndex].isSelected) { + monty_state->door_state.didSelect = true; + monty_state->game_state = RoundTwo; + } + } else if(monty_state->game_state == RoundThree) { + for(int i = 0; i < 3; i++) { + monty_state->door_state.doors[i].isSelected = false; + } + + monty_state->door_state.doors[doorIndex].isSelected = true; + } +} + +int getRandomDoorIndex() { + int randomDoorIndex = random() % 3; + return randomDoorIndex; +} + +void revealBadDoor(MontyState* monty_state) { + int doorToReveal = getRandomDoorIndex(); + while(!monty_state->door_state.doors[doorToReveal].isOpen) { + if(!(monty_state->door_state.doors[doorToReveal].isSelected || + monty_state->door_state.doors[doorToReveal].isWinningDoor)) { + monty_state->door_state.doors[doorToReveal].isOpen = true; + } else { + doorToReveal = getRandomDoorIndex(); + } + } +} + +void revealDoors_updateScore(MontyState* monty_state) { + for(int i = 0; i < 3; i++) { + monty_state->door_state.doors[i].isOpen = true; + + if(monty_state->door_state.doors[i].isWinningDoor && + monty_state->door_state.doors[i].isSelected) { + monty_state->score++; + } + } +} + +static void draw_top(Canvas* canvas, const MontyState* monty_state) { + char buffer[16]; + snprintf(buffer, sizeof(buffer), "Cars: %u", monty_state->score); + canvas_draw_str_aligned(canvas, 2, 8, AlignLeft, AlignBottom, buffer); + + if(monty_state->game_state == RoundThree) { + canvas_draw_str_aligned( + canvas, SCREEN_WIDTH - 5, 8, AlignRight, AlignBottom, "Opened a decoy door"); + } +} + +static void draw_doors(Canvas* canvas, const MontyState* monty_state) { + // {| 16 | <22> | 15 | <22> | 15 | <22> | 16 |} = SCREEN_WIDTH + if(monty_state->door_state.doors[0].isOpen) { + if(monty_state->door_state.doors[0].isWinningDoor) { + canvas_draw_frame(canvas, 16, 12, 22, 35); + draw_car(canvas, 18, 26); + } else { + canvas_draw_frame(canvas, 16, 12, 22, 35); + canvas_draw_str(canvas, 18, 34, "Goat"); + } + } else { + canvas_draw_icon(canvas, 16, 12, &I_DoorClosed_22x35); + } + + if(monty_state->door_state.doors[1].isOpen) { + if(monty_state->door_state.doors[1].isWinningDoor) { + canvas_draw_frame(canvas, 53, 12, 22, 35); + draw_car(canvas, 55, 26); + } else { + canvas_draw_frame(canvas, 53, 12, 22, 35); + canvas_draw_str(canvas, 55, 34, "Goat"); + } + } else { + canvas_draw_icon(canvas, 53, 12, &I_DoorClosed_22x35); + } + + if(monty_state->door_state.doors[2].isOpen) { + if(monty_state->door_state.doors[2].isWinningDoor) { + canvas_draw_frame(canvas, 90, 12, 22, 35); + draw_car(canvas, 92, 26); + } else { + canvas_draw_frame(canvas, 90, 12, 22, 35); + canvas_draw_str(canvas, 92, 34, "Goat"); + } + } else { + canvas_draw_icon(canvas, 90, 12, &I_DoorClosed_22x35); + } +} + +static void draw_bottom(Canvas* canvas, const MontyState* monty_state) { + if(monty_state->game_state == RoundOne) { + elements_button_left(canvas, "Left"); + elements_button_center(canvas, "Center"); + elements_button_right(canvas, "Right"); + } + + if(monty_state->game_state == RoundThree) { + if(monty_state->door_state.doors[0].isSelected) { + elements_button_left(canvas, "Keep"); + if(!monty_state->door_state.doors[1].isOpen) { + elements_button_center(canvas, "Switch"); + } else { + elements_button_right(canvas, "Switch"); + } + } else if(monty_state->door_state.doors[1].isSelected) { + elements_button_center(canvas, "Keep"); + if(!monty_state->door_state.doors[0].isOpen) { + elements_button_left(canvas, "Switch"); + } else { + elements_button_right(canvas, "Switch"); + } + } else if(monty_state->door_state.doors[2].isSelected) { + elements_button_right(canvas, "Keep"); + if(!monty_state->door_state.doors[0].isOpen) { + elements_button_left(canvas, "Switch"); + } else { + elements_button_center(canvas, "Switch"); + } + } + } + + if(monty_state->game_state == RoundFour) { + elements_button_center(canvas, "Reveal"); + } + + if(monty_state->game_state == GameOver) { + canvas_draw_str(canvas, 16, SCREEN_HEIGHT - 5, "Hold center to restart"); + } +} + +static void montyhall_render_callback(Canvas* const canvas, void* ctx) { + const MontyState* monty_state = acquire_mutex((ValueMutex*)ctx, 25); + if(monty_state == NULL) { + return; + } + + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + draw_top(canvas, monty_state); + draw_doors(canvas, monty_state); + draw_bottom(canvas, monty_state); + + release_mutex((ValueMutex*)ctx, monty_state); +} + +static void montyhall_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + furi_message_queue_put(event_queue, input_event, FuriWaitForever); +} + +int32_t montyhall_game_app(void* p) { + UNUSED(p); + int32_t return_code = 0; + + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + + MontyState* monty_state = malloc(sizeof(MontyState)); + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, monty_state, sizeof(MontyState))) { + return_code = 255; + goto free_and_exit; + } + + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, montyhall_render_callback, &state_mutex); + view_port_input_callback_set(view_port, montyhall_input_callback, event_queue); + + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + // Start the game + montyhall_game_init_state(monty_state); + + InputEvent event; + for(bool loop = true; loop;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + MontyState* monty_state = (MontyState*)acquire_mutex_block(&state_mutex); + + if(event_status == FuriStatusOk) { + if(event.type == InputTypeShort) { + switch(event.key) { + case InputKeyUp: /* + if(monty_state->game_state == RoundOne) { + monty_state->score++; + } else if(monty_state->game_state == RoundTwo) { + monty_state->score += 2; + } else if(monty_state->game_state == RoundThree) { + monty_state->score += 3; + } else if(monty_state->game_state == RoundFour) { + monty_state->score += 4; + } else if(monty_state->game_state == GameOver) { + monty_state->score += 5; + } */ + break; + case InputKeyDown: /* + if(monty_state->game_state == RoundOne) { + monty_state->score--; + } else if(monty_state->game_state == RoundTwo) { + monty_state->score -= 2; + } else if(monty_state->game_state == RoundThree) { + monty_state->score -= 3; + } else if(monty_state->game_state == RoundFour) { + monty_state->score -= 4; + } else if(monty_state->game_state == GameOver) { + monty_state->score -= 5; + } */ + break; + case InputKeyLeft: + if(monty_state->game_state == RoundOne) { + selectDoor(monty_state, 0); + if(monty_state->game_state == RoundTwo) { + revealBadDoor(monty_state); + monty_state->game_state = RoundThree; + } + } else if(monty_state->game_state == RoundThree) { + if(monty_state->door_state.doors[0].isSelected) { + monty_state->door_state.didSwitch = false; + } else if(!monty_state->door_state.doors[0].isOpen) { + monty_state->door_state.didSwitch = true; + selectDoor(monty_state, 0); + } + monty_state->game_state = RoundFour; + } + break; + case InputKeyOk: + if(monty_state->game_state == RoundOne) { + selectDoor(monty_state, 1); + if(monty_state->game_state == RoundTwo) { + revealBadDoor(monty_state); + monty_state->game_state = RoundThree; + } + } else if(monty_state->game_state == RoundThree) { + if(monty_state->door_state.doors[1].isSelected) { + monty_state->door_state.didSwitch = false; + } else if(!monty_state->door_state.doors[1].isOpen) { + monty_state->door_state.didSwitch = true; + selectDoor(monty_state, 1); + } + monty_state->game_state = RoundFour; + } else if(monty_state->game_state == RoundFour) { + revealDoors_updateScore(monty_state); + monty_state->game_state = GameOver; + } + break; + case InputKeyRight: + if(monty_state->game_state == RoundOne) { + selectDoor(monty_state, 2); + if(monty_state->game_state == RoundTwo) { + revealBadDoor(monty_state); + monty_state->game_state = RoundThree; + } + } else if(monty_state->game_state == RoundThree) { + if(monty_state->door_state.doors[2].isSelected) { + monty_state->door_state.didSwitch = false; + } else if(!monty_state->door_state.doors[2].isOpen) { + monty_state->door_state.didSwitch = true; + selectDoor(monty_state, 2); + } + monty_state->game_state = RoundFour; + } + break; + case InputKeyBack: + loop = false; + break; + default: + break; + } + } + } else if(event.type == InputTypeLong) { + if(event.key == InputKeyOk && monty_state->game_state == GameOver) { + montyhall_game_init_state(monty_state); + } + } + + view_port_update(view_port); + release_mutex(&state_mutex, monty_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); + delete_mutex(&state_mutex); + +free_and_exit: + free(monty_state); + furi_message_queue_free(event_queue); + + return return_code; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/morse_code/application.fam b/Applications/Official/DEV_FW/source/morse_code/application.fam new file mode 100644 index 000000000..1cc7bdaa1 --- /dev/null +++ b/Applications/Official/DEV_FW/source/morse_code/application.fam @@ -0,0 +1,15 @@ +App( + appid="Morse_Code", + name="Morse Code", + apptype=FlipperAppType.EXTERNAL, + entry_point="morse_code_app", + cdefines=["APP_MORSE_CODE"], + requires=[ + "gui", + ], + stack_size=1 * 1024, + order=20, + fap_icon="morse_code_10px.png", + fap_category="Music" + +) \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/morse_code/morse_code.c b/Applications/Official/DEV_FW/source/morse_code/morse_code.c new file mode 100644 index 000000000..beb661222 --- /dev/null +++ b/Applications/Official/DEV_FW/source/morse_code/morse_code.c @@ -0,0 +1,166 @@ +#include "morse_code_worker.h" +#include +#include +#include +#include +#include +#include +#include + +static const float MORSE_CODE_VOLUMES[] = {0, .25, .5, .75, 1}; + +typedef struct { + FuriString* words; + uint8_t volume; + uint32_t dit_delta; +} MorseCodeModel; + +typedef struct { + MorseCodeModel* model; + FuriMutex** model_mutex; + + FuriMessageQueue* input_queue; + + ViewPort* view_port; + Gui* gui; + + MorseCodeWorker* worker; +} MorseCode; + +static void render_callback(Canvas* const canvas, void* ctx) { + MorseCode* morse_code = ctx; + furi_check(furi_mutex_acquire(morse_code->model_mutex, FuriWaitForever) == FuriStatusOk); + // border around the edge of the screen + canvas_set_font(canvas, FontPrimary); + + //write words + elements_multiline_text_aligned( + canvas, 64, 30, AlignCenter, AlignCenter, furi_string_get_cstr(morse_code->model->words)); + + // volume view_port + uint8_t vol_bar_x_pos = 124; + uint8_t vol_bar_y_pos = 0; + const uint8_t volume_h = (64 / (COUNT_OF(MORSE_CODE_VOLUMES) - 1)) * morse_code->model->volume; + canvas_draw_frame(canvas, vol_bar_x_pos, vol_bar_y_pos, 4, 64); + canvas_draw_box(canvas, vol_bar_x_pos, vol_bar_y_pos + (64 - volume_h), 4, volume_h); + + //dit bpm + canvas_draw_str_aligned( + canvas, + 0, + 10, + AlignLeft, + AlignCenter, + furi_string_get_cstr( + furi_string_alloc_printf("Dit: %ld ms", morse_code->model->dit_delta))); + + //button info + elements_button_center(canvas, "Press/Hold"); + furi_mutex_release(morse_code->model_mutex); +} + +static void input_callback(InputEvent* input_event, void* ctx) { + MorseCode* morse_code = ctx; + furi_message_queue_put(morse_code->input_queue, input_event, FuriWaitForever); +} + +static void morse_code_worker_callback(FuriString* words, void* context) { + MorseCode* morse_code = context; + furi_check(furi_mutex_acquire(morse_code->model_mutex, FuriWaitForever) == FuriStatusOk); + morse_code->model->words = words; + furi_mutex_release(morse_code->model_mutex); + view_port_update(morse_code->view_port); +} + +MorseCode* morse_code_alloc() { + MorseCode* instance = malloc(sizeof(MorseCode)); + + instance->model = malloc(sizeof(MorseCodeModel)); + instance->model->words = furi_string_alloc_set_str(""); + instance->model->volume = 3; + instance->model->dit_delta = 150; + instance->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal); + + instance->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + + instance->worker = morse_code_worker_alloc(); + + morse_code_worker_set_callback(instance->worker, morse_code_worker_callback, instance); + + instance->view_port = view_port_alloc(); + view_port_draw_callback_set(instance->view_port, render_callback, instance); + view_port_input_callback_set(instance->view_port, input_callback, instance); + + // Open GUI and register view_port + instance->gui = furi_record_open(RECORD_GUI); + gui_add_view_port(instance->gui, instance->view_port, GuiLayerFullscreen); + + return instance; +} + +void morse_code_free(MorseCode* instance) { + gui_remove_view_port(instance->gui, instance->view_port); + furi_record_close(RECORD_GUI); + view_port_free(instance->view_port); + + morse_code_worker_free(instance->worker); + + furi_message_queue_free(instance->input_queue); + + furi_mutex_free(instance->model_mutex); + + free(instance->model); + free(instance); +} + +int32_t morse_code_app() { + MorseCode* morse_code = morse_code_alloc(); + InputEvent input; + morse_code_worker_start(morse_code->worker); + morse_code_worker_set_volume( + morse_code->worker, MORSE_CODE_VOLUMES[morse_code->model->volume]); + morse_code_worker_set_dit_delta(morse_code->worker, morse_code->model->dit_delta); + while(furi_message_queue_get(morse_code->input_queue, &input, FuriWaitForever) == + FuriStatusOk) { + furi_check(furi_mutex_acquire(morse_code->model_mutex, FuriWaitForever) == FuriStatusOk); + if(input.key == InputKeyBack && input.type == InputTypeLong) { + furi_mutex_release(morse_code->model_mutex); + break; + } else if(input.key == InputKeyBack && input.type == InputTypeShort) { + morse_code_worker_reset_text(morse_code->worker); + } else if(input.key == InputKeyOk) { + if(input.type == InputTypePress) + morse_code_worker_play(morse_code->worker, true); + else if(input.type == InputTypeRelease) + morse_code_worker_play(morse_code->worker, false); + } else if(input.key == InputKeyUp && input.type == InputTypePress) { + if(morse_code->model->volume < COUNT_OF(MORSE_CODE_VOLUMES) - 1) + morse_code->model->volume++; + morse_code_worker_set_volume( + morse_code->worker, MORSE_CODE_VOLUMES[morse_code->model->volume]); + } else if(input.key == InputKeyDown && input.type == InputTypePress) { + if(morse_code->model->volume > 0) morse_code->model->volume--; + morse_code_worker_set_volume( + morse_code->worker, MORSE_CODE_VOLUMES[morse_code->model->volume]); + } else if(input.key == InputKeyLeft && input.type == InputTypePress) { + if(morse_code->model->dit_delta > 10) morse_code->model->dit_delta -= 10; + morse_code_worker_set_dit_delta(morse_code->worker, morse_code->model->dit_delta); + } else if(input.key == InputKeyRight && input.type == InputTypePress) { + if(morse_code->model->dit_delta >= 10) morse_code->model->dit_delta += 10; + morse_code_worker_set_dit_delta(morse_code->worker, morse_code->model->dit_delta); + } + + FURI_LOG_D( + "Input", + "%s %s %ld", + input_get_key_name(input.key), + input_get_type_name(input.type), + input.sequence); + + furi_mutex_release(morse_code->model_mutex); + view_port_update(morse_code->view_port); + } + morse_code_worker_stop(morse_code->worker); + morse_code_free(morse_code); + return 0; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/morse_code/morse_code_10px.png b/Applications/Official/DEV_FW/source/morse_code/morse_code_10px.png new file mode 100644 index 000000000..087c5b239 Binary files /dev/null and b/Applications/Official/DEV_FW/source/morse_code/morse_code_10px.png differ diff --git a/Applications/Official/DEV_FW/source/morse_code/morse_code_worker.c b/Applications/Official/DEV_FW/source/morse_code/morse_code_worker.c new file mode 100644 index 000000000..142b427b6 --- /dev/null +++ b/Applications/Official/DEV_FW/source/morse_code/morse_code_worker.c @@ -0,0 +1,176 @@ +#include "morse_code_worker.h" +#include +#include + +#define TAG "MorseCodeWorker" + +#define MORSE_CODE_VERSION 0 + +//A-Z0-1 +const char morse_array[36][6] = {".-", "-...", "-.-.", "-..", ".", "..-.", + "--.", "....", "..", ".---", "-.-", ".-..", + "--", "-.", "---", ".--.", "--.-", ".-.", + "...", "-", "..-", "...-", ".--", "-..-", + "-.--", "--..", ".----", "..---", "...--", "....-", + ".....", "-....", "--...", "---..", "----.", "-----"}; +const char symbol_array[36] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', + 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}; + +struct MorseCodeWorker { + FuriThread* thread; + MorseCodeWorkerCallback callback; + void* callback_context; + bool is_running; + bool play; + float volume; + uint32_t dit_delta; + FuriString* buffer; + FuriString* words; +}; + +void morse_code_worker_fill_buffer(MorseCodeWorker* instance, uint32_t duration) { + FURI_LOG_D("MorseCode: Duration", "%ld", duration); + if(duration <= instance->dit_delta) + furi_string_push_back(instance->buffer, *DOT); + else if(duration <= (instance->dit_delta * 3)) + furi_string_push_back(instance->buffer, *LINE); + if(furi_string_size(instance->buffer) > 5) furi_string_reset(instance->buffer); + FURI_LOG_D("MorseCode: Buffer", "%s", furi_string_get_cstr(instance->buffer)); +} + +void morse_code_worker_fill_letter(MorseCodeWorker* instance) { + if(furi_string_size(instance->words) > 63) furi_string_reset(instance->words); + for(size_t i = 0; i < sizeof(morse_array); i++) { + if(furi_string_cmp_str(instance->buffer, morse_array[i]) == 0) { + furi_string_push_back(instance->words, symbol_array[i]); + break; + } + } + furi_string_reset(instance->buffer); + FURI_LOG_D("MorseCode: Words", "%s", furi_string_get_cstr(instance->words)); +} + +static int32_t morse_code_worker_thread_callback(void* context) { + furi_assert(context); + MorseCodeWorker* instance = context; + bool was_playing = false; + uint32_t start_tick = 0; + uint32_t end_tick = 0; + bool pushed = true; + bool spaced = true; + while(instance->is_running) { + furi_delay_ms(SLEEP); + + if(instance->play) { + if(!was_playing) { + start_tick = furi_get_tick(); + if(furi_hal_speaker_acquire(1000)) { + furi_hal_speaker_start(FREQUENCY, instance->volume); + } + was_playing = true; + } + } else { + if(was_playing) { + pushed = false; + spaced = false; + if(furi_hal_speaker_is_mine()) { + furi_hal_speaker_stop(); + furi_hal_speaker_release(); + } + end_tick = furi_get_tick(); + was_playing = false; + morse_code_worker_fill_buffer(instance, end_tick - start_tick); + start_tick = 0; + } + } + if(!pushed) { + if(end_tick + (instance->dit_delta * 3) < furi_get_tick()) { + //NEW LETTER + morse_code_worker_fill_letter(instance); + if(instance->callback) + instance->callback(instance->words, instance->callback_context); + pushed = true; + } + } + if(!spaced) { + if(end_tick + (instance->dit_delta * 7) < furi_get_tick()) { + //NEW WORD + furi_string_push_back(instance->words, *SPACE); + if(instance->callback) + instance->callback(instance->words, instance->callback_context); + spaced = true; + } + } + } + return 0; +} + +MorseCodeWorker* morse_code_worker_alloc() { + MorseCodeWorker* instance = malloc(sizeof(MorseCodeWorker)); + instance->thread = furi_thread_alloc(); + furi_thread_set_name(instance->thread, "MorseCodeWorker"); + furi_thread_set_stack_size(instance->thread, 1024); + furi_thread_set_context(instance->thread, instance); + furi_thread_set_callback(instance->thread, morse_code_worker_thread_callback); + instance->play = false; + instance->volume = 1.0f; + instance->dit_delta = 150; + instance->buffer = furi_string_alloc_set_str(""); + instance->words = furi_string_alloc_set_str(""); + return instance; +} + +void morse_code_worker_free(MorseCodeWorker* instance) { + furi_assert(instance); + furi_string_free(instance->buffer); + furi_string_free(instance->words); + furi_thread_free(instance->thread); + free(instance); +} + +void morse_code_worker_set_callback( + MorseCodeWorker* instance, + MorseCodeWorkerCallback callback, + void* context) { + furi_assert(instance); + instance->callback = callback; + instance->callback_context = context; +} + +void morse_code_worker_play(MorseCodeWorker* instance, bool play) { + furi_assert(instance); + instance->play = play; +} + +void morse_code_worker_set_volume(MorseCodeWorker* instance, float level) { + furi_assert(instance); + instance->volume = level; +} + +void morse_code_worker_set_dit_delta(MorseCodeWorker* instance, uint32_t delta) { + furi_assert(instance); + instance->dit_delta = delta; +} + +void morse_code_worker_reset_text(MorseCodeWorker* instance) { + furi_assert(instance); + furi_string_reset(instance->buffer); + furi_string_reset(instance->words); +} + +void morse_code_worker_start(MorseCodeWorker* instance) { + furi_assert(instance); + furi_assert(instance->is_running == false); + instance->is_running = true; + furi_thread_start(instance->thread); + FURI_LOG_D("MorseCode: Start", "is Running"); +} + +void morse_code_worker_stop(MorseCodeWorker* instance) { + furi_assert(instance); + furi_assert(instance->is_running == true); + instance->is_running = false; + furi_thread_join(instance->thread); + FURI_LOG_D("MorseCode: Stop", "Stop"); +} diff --git a/Applications/Official/DEV_FW/source/morse_code/morse_code_worker.h b/Applications/Official/DEV_FW/source/morse_code/morse_code_worker.h new file mode 100644 index 000000000..e880c9579 --- /dev/null +++ b/Applications/Official/DEV_FW/source/morse_code/morse_code_worker.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include + +#define FREQUENCY 261.63f +#define SLEEP 10 +#define DOT "." +#define LINE "-" +#define SPACE " " + +typedef void (*MorseCodeWorkerCallback)(FuriString* buffer, void* context); + +typedef struct MorseCodeWorker MorseCodeWorker; + +MorseCodeWorker* morse_code_worker_alloc(); + +void morse_code_worker_free(MorseCodeWorker* instance); + +void morse_code_worker_set_callback( + MorseCodeWorker* instance, + MorseCodeWorkerCallback callback, + void* context); + +void morse_code_worker_start(MorseCodeWorker* instance); + +void morse_code_worker_stop(MorseCodeWorker* instance); + +void morse_code_worker_play(MorseCodeWorker* instance, bool play); + +void morse_code_worker_reset_text(MorseCodeWorker* instance); + +void morse_code_worker_set_volume(MorseCodeWorker* instance, float level); + +void morse_code_worker_set_dit_delta(MorseCodeWorker* instance, uint32_t delta); diff --git a/Applications/Official/DEV_FW/source/mouse_jiggler/application.fam b/Applications/Official/DEV_FW/source/mouse_jiggler/application.fam new file mode 100644 index 000000000..6c529a883 --- /dev/null +++ b/Applications/Official/DEV_FW/source/mouse_jiggler/application.fam @@ -0,0 +1,12 @@ +App( + appid="MouseJiggler", + name="Mouse Jiggler", + apptype=FlipperAppType.EXTERNAL, + entry_point="mouse_jiggler_app", + cdefines=["APP_MOUSE_JIGGLER"], + requires=["gui"], + stack_size=1 * 1024, + order=150, + fap_icon="mouse_10px.png", + fap_category="Misc", +) \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/mouse_jiggler/mouse_10px.png b/Applications/Official/DEV_FW/source/mouse_jiggler/mouse_10px.png new file mode 100644 index 000000000..94c3a7a14 Binary files /dev/null and b/Applications/Official/DEV_FW/source/mouse_jiggler/mouse_10px.png differ diff --git a/Applications/Official/DEV_FW/source/mouse_jiggler/mouse_jiggler.c b/Applications/Official/DEV_FW/source/mouse_jiggler/mouse_jiggler.c new file mode 100644 index 000000000..868082eea --- /dev/null +++ b/Applications/Official/DEV_FW/source/mouse_jiggler/mouse_jiggler.c @@ -0,0 +1,141 @@ +#include +#include +#include +#include + +#define MOUSE_MOVE_SHORT 5 +#define MOUSE_MOVE_LONG 20 + +typedef enum { + EventTypeInput, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} UsbMouseEvent; + +typedef struct { + bool running; +} MouseJigglerState; + +static void mouse_jiggler_render_callback(Canvas* canvas, void* ctx) { + const MouseJigglerState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25); + if(plugin_state == NULL) { + return; + } + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 2, 12, "USB Mouse Jiggler"); + if(!plugin_state->running) { + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 2, 27, " -> STOPPED"); + canvas_draw_str(canvas, 2, 51, "Press [ok] to start"); + canvas_draw_str(canvas, 2, 63, "Press [back] to exit"); + } else { + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 2, 27, " -> RUNNING"); + canvas_draw_str(canvas, 2, 51, "Press [back] to stop"); + } + + release_mutex((ValueMutex*)ctx, plugin_state); +} + +static void mouse_jiggler_input_callback(InputEvent* input_event, void* ctx) { + FuriMessageQueue* event_queue = ctx; + furi_assert(event_queue); + + UsbMouseEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void mouse_jiggler_state_init(MouseJigglerState* const plugin_state) { + plugin_state->running = false; +} + +int32_t mouse_jiggler_app(void* p) { + UNUSED(p); + + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(UsbMouseEvent)); + + MouseJigglerState* plugin_state = malloc(sizeof(MouseJigglerState)); + if(plugin_state == NULL) { + FURI_LOG_E("MouseJiggler", "MouseJigglerState: malloc error\r\n"); + return 255; + } + mouse_jiggler_state_init(plugin_state); + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, plugin_state, sizeof(MouseJigglerState))) { + FURI_LOG_E("MouseJiggler", "cannot create mutex\r\n"); + furi_message_queue_free(event_queue); + free(plugin_state); + return 255; + } + + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, mouse_jiggler_render_callback, &state_mutex); + view_port_input_callback_set(view_port, mouse_jiggler_input_callback, event_queue); + + FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config(); + furi_hal_usb_set_config(&usb_hid, NULL); + + // Open GUI and register view_port + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + UsbMouseEvent event; + //bool status = 0; + + for(bool processing = true; processing;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + + MouseJigglerState* plugin_state = (MouseJigglerState*)acquire_mutex_block(&state_mutex); + + if(event_status == FuriStatusOk) { + if(event.type == EventTypeKey) { + if(event.input.type == InputTypePress) { + switch(event.input.key) { + case InputKeyOk: + if(!plugin_state->running) { + plugin_state->running = true; + } + break; + case InputKeyBack: + if(!plugin_state->running) { + processing = false; + } else { + plugin_state->running = false; + } + break; + default: + break; + } + } + } + } + + if(plugin_state->running) { + furi_hal_hid_mouse_move(MOUSE_MOVE_SHORT, 0); + furi_delay_ms(500); + furi_hal_hid_mouse_move(-MOUSE_MOVE_SHORT, 0); + furi_delay_ms(500); + } + + view_port_update(view_port); + release_mutex(&state_mutex, plugin_state); + } + + furi_hal_usb_set_config(usb_mode_prev, NULL); + + // remove & free all stuff created by app + 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; +} diff --git a/Applications/Official/DEV_FW/source/multi_converter/application.fam b/Applications/Official/DEV_FW/source/multi_converter/application.fam new file mode 100644 index 000000000..e0abe3ed4 --- /dev/null +++ b/Applications/Official/DEV_FW/source/multi_converter/application.fam @@ -0,0 +1,12 @@ +App( + appid="Multi_Converter", + name="Multi Converter", + apptype=FlipperAppType.EXTERNAL, + entry_point="multi_converter_app", + cdefines=["APP_DEC_HEX_CONVERTER"], + requires=["gui"], + stack_size=1 * 1024, + order=160, + fap_icon="converter_10px.png", + fap_category="Misc", +) diff --git a/Applications/Official/DEV_FW/source/multi_converter/converter_10px.png b/Applications/Official/DEV_FW/source/multi_converter/converter_10px.png new file mode 100644 index 000000000..820b10639 Binary files /dev/null and b/Applications/Official/DEV_FW/source/multi_converter/converter_10px.png differ diff --git a/Applications/Official/DEV_FW/source/multi_converter/multi_converter.c b/Applications/Official/DEV_FW/source/multi_converter/multi_converter.c new file mode 100644 index 000000000..590730357 --- /dev/null +++ b/Applications/Official/DEV_FW/source/multi_converter/multi_converter.c @@ -0,0 +1,168 @@ +#include +#include +#include +#include + +#include "multi_converter_definitions.h" +#include "multi_converter_mode_display.h" +#include "multi_converter_mode_select.h" + +static void multi_converter_render_callback(Canvas* const canvas, void* ctx) { + const MultiConverterState* multi_converter_state = acquire_mutex((ValueMutex*)ctx, 25); + if(multi_converter_state == NULL) { + return; + } + + if(multi_converter_state->mode == ModeDisplay) { + multi_converter_mode_display_draw(canvas, multi_converter_state); + } else { + multi_converter_mode_select_draw(canvas, multi_converter_state); + } + + release_mutex((ValueMutex*)ctx, multi_converter_state); +} + +static void + multi_converter_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + MultiConverterEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void multi_converter_init(MultiConverterState* const multi_converter_state) { + // initial default values + + multi_converter_state->buffer_orig[MULTI_CONVERTER_NUMBER_DIGITS] = '\0'; + multi_converter_state->buffer_dest[MULTI_CONVERTER_NUMBER_DIGITS] = '\0'; // null terminators + + multi_converter_state->unit_type_orig = UnitTypeDec; + multi_converter_state->unit_type_dest = UnitTypeHex; + + multi_converter_state->keyboard_lock = 0; + + // init the display view + multi_converter_mode_display_reset(multi_converter_state); + + // init the select view + multi_converter_mode_select_reset(multi_converter_state); + + // set ModeDisplay as the current mode + multi_converter_state->mode = ModeDisplay; +} + +// main entry point +int32_t multi_converter_app(void* p) { + UNUSED(p); + + // get event queue + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(MultiConverterEvent)); + + // allocate state + MultiConverterState* multi_converter_state = malloc(sizeof(MultiConverterState)); + + // set mutex for plugin state (different threads can access it) + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, multi_converter_state, sizeof(multi_converter_state))) { + FURI_LOG_E("MultiConverter", "cannot create mutex\r\n"); + furi_message_queue_free(event_queue); + free(multi_converter_state); + return 255; + } + + // register callbacks for drawing and input processing + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, multi_converter_render_callback, &state_mutex); + view_port_input_callback_set(view_port, multi_converter_input_callback, event_queue); + + // open GUI and register view_port + Gui* gui = furi_record_open("gui"); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + multi_converter_init(multi_converter_state); + + // main loop + MultiConverterEvent event; + for(bool processing = true; processing;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + MultiConverterState* multi_converter_state = + (MultiConverterState*)acquire_mutex_block(&state_mutex); + + if(event_status == FuriStatusOk) { + // press events + if(event.type == EventTypeKey && !multi_converter_state->keyboard_lock) { + if(multi_converter_state->mode == ModeDisplay) { + if(event.input.key == InputKeyBack) { + if(event.input.type == InputTypePress) processing = false; + } else if(event.input.key == InputKeyOk) { // the "ok" press can be short or long + MultiConverterModeTrigger t = None; + + if(event.input.type == InputTypeLong) + t = multi_converter_mode_display_ok(1, multi_converter_state); + else if(event.input.type == InputTypeShort) + t = multi_converter_mode_display_ok(0, multi_converter_state); + + if(t == Reset) { + multi_converter_mode_select_reset(multi_converter_state); + multi_converter_state->mode = ModeSelector; + } + } else { + if(event.input.type == InputTypePress) + multi_converter_mode_display_navigation( + event.input.key, multi_converter_state); + } + + } else { // ModeSelect + if(event.input.type == InputTypePress) { + switch(event.input.key) { + default: + break; + case InputKeyBack: + case InputKeyOk: { + MultiConverterModeTrigger t = multi_converter_mode_select_exit( + event.input.key == InputKeyOk ? 1 : 0, multi_converter_state); + + if(t == Reset) { + multi_converter_mode_display_reset(multi_converter_state); + } else if(t == Convert) { + multi_converter_mode_display_convert(multi_converter_state); + } + + multi_converter_state->keyboard_lock = 1; + multi_converter_state->mode = ModeDisplay; + break; + } + case InputKeyLeft: + case InputKeyRight: + multi_converter_mode_select_switch(multi_converter_state); + break; + case InputKeyUp: + multi_converter_mode_select_change_unit(-1, multi_converter_state); + break; + case InputKeyDown: + multi_converter_mode_select_change_unit(1, multi_converter_state); + break; + } + } + } + } else if(multi_converter_state->keyboard_lock) { + multi_converter_state->keyboard_lock = 0; + } + } else { + // event timeout + } + + view_port_update(view_port); + release_mutex(&state_mutex, multi_converter_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); + free(multi_converter_state); + + return 0; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/multi_converter/multi_converter_definitions.h b/Applications/Official/DEV_FW/source/multi_converter/multi_converter_definitions.h new file mode 100644 index 000000000..3bed192a0 --- /dev/null +++ b/Applications/Official/DEV_FW/source/multi_converter/multi_converter_definitions.h @@ -0,0 +1,82 @@ +#pragma once + +#define MULTI_CONVERTER_NUMBER_DIGITS 9 + +typedef enum { + EventTypeKey, +} EventType; + +typedef struct { + InputEvent input; + EventType type; +} MultiConverterEvent; + +typedef enum { + ModeDisplay, + ModeSelector, +} MultiConverterMode; + +typedef enum { + None, + Reset, + Convert, +} MultiConverterModeTrigger; + +// new units goes here, used as index to the main multi_converter_available_units array (multi_converter_units.h) +typedef enum { + UnitTypeDec, + UnitTypeHex, + UnitTypeBin, + + UnitTypeCelsius, + UnitTypeFahernheit, + UnitTypeKelvin, + + UnitTypeKilometers, + UnitTypeMeters, + UnitTypeCentimeters, + UnitTypeMiles, + UnitTypeFeet, + UnitTypeInches, + + UnitTypeDegree, + UnitTypeRadian, +} MultiConverterUnitType; + +typedef struct { + MultiConverterUnitType selected_unit_type_orig; + MultiConverterUnitType selected_unit_type_dest; + uint8_t select_orig; +} MultiConverterModeSelect; + +typedef struct { + uint8_t cursor; // cursor position when typing + int8_t key; // hover key + uint8_t comma; // comma already added? (only one comma allowed) + uint8_t negative; // is negative? +} MultiConverterModeDisplay; + +typedef struct MultiConverterUnit MultiConverterUnit; +typedef struct MultiConverterState MultiConverterState; + +struct MultiConverterUnit { + uint8_t allow_comma; + uint8_t allow_negative; + uint8_t max_number_keys; + char mini_name[4]; + char name[12]; + void (*convert_function)(MultiConverterState* const); + uint8_t (*allowed_function)(MultiConverterUnitType); +}; + +struct MultiConverterState { + char buffer_orig[MULTI_CONVERTER_NUMBER_DIGITS + 1]; + char buffer_dest[MULTI_CONVERTER_NUMBER_DIGITS + 1]; + MultiConverterUnitType unit_type_orig; + MultiConverterUnitType unit_type_dest; + MultiConverterMode mode; + MultiConverterModeDisplay display; + MultiConverterModeSelect select; + uint8_t keyboard_lock; // used to create a small lock when switching from SELECT to DISPLAY modes + // (debouncing, basically; otherwise it switch modes twice 'cause it's too fast!) +}; diff --git a/Applications/Official/DEV_FW/source/multi_converter/multi_converter_mode_display.c b/Applications/Official/DEV_FW/source/multi_converter/multi_converter_mode_display.c new file mode 100644 index 000000000..c72a954f7 --- /dev/null +++ b/Applications/Official/DEV_FW/source/multi_converter/multi_converter_mode_display.c @@ -0,0 +1,326 @@ +#include "multi_converter_mode_display.h" + +#define MULTI_CONVERTER_DISPLAY_KEYS 18 // [0] to [F] + [BACK] + [SELECT] + +#define MULTI_CONVERTER_DISPLAY_KEY_NEGATIVE 0 // long press +#define MULTI_CONVERTER_DISPLAY_KEY_COMMA 1 // long press +#define MULTI_CONVERTER_DISPLAY_KEY_DEL 16 +#define MULTI_CONVERTER_DISPLAY_KEY_SELECT 17 + +#define MULTI_CONVERTER_DISPLAY_CHAR_COMMA '.' +#define MULTI_CONVERTER_DISPLAY_CHAR_NEGATIVE '-' +#define MULTI_CONVERTER_DISPLAY_CHAR_DEL '<' +#define MULTI_CONVERTER_DISPLAY_CHAR_SELECT '#' +#define MULTI_CONVERTER_DISPLAY_CHAR_BLANK ' ' + +#define MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN 3 +#define MULTI_CONVERTER_DISPLAY_KEY_CHAR_HEIGHT 8 + +void multi_converter_mode_display_convert(MultiConverterState* const multi_converter_state) { + // 1.- if origin == destination (in theory user won't be allowed to choose the same options, but it's kinda "valid"...) + // just copy buffer_orig to buffer_dest and that's it + + if(multi_converter_state->unit_type_orig == multi_converter_state->unit_type_dest) { + memcpy( + multi_converter_state->buffer_dest, + multi_converter_state->buffer_orig, + MULTI_CONVERTER_NUMBER_DIGITS); + return; + } + + // 2.- origin_buffer has not null functions + if(multi_converter_get_unit(multi_converter_state->unit_type_orig).convert_function == NULL || + multi_converter_get_unit(multi_converter_state->unit_type_orig).allowed_function == NULL) + return; + + // 3.- valid destination type (using allowed_destinations function) + if(!multi_converter_get_unit(multi_converter_state->unit_type_orig) + .allowed_function(multi_converter_state->unit_type_dest)) + return; + + multi_converter_get_unit(multi_converter_state->unit_type_orig) + .convert_function(multi_converter_state); +} + +void multi_converter_mode_display_draw( + Canvas* const canvas, + const MultiConverterState* multi_converter_state) { + canvas_set_color(canvas, ColorBlack); + + // ORIGIN + canvas_set_font(canvas, FontPrimary); + canvas_draw_str( + canvas, 2, 10, multi_converter_get_unit(multi_converter_state->unit_type_orig).mini_name); + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 2 + 30, 10, multi_converter_state->buffer_orig); + + // DESTINATION + canvas_set_font(canvas, FontPrimary); + canvas_draw_str( + canvas, + 2, + 10 + 12, + multi_converter_get_unit(multi_converter_state->unit_type_dest).mini_name); + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 2 + 30, 10 + 12, multi_converter_state->buffer_dest); + + // SEPARATOR_LINE + canvas_draw_line(canvas, 2, 25, 128 - 3, 25); + + // KEYBOARD + uint8_t _x = 5; + uint8_t _y = 25 + 15; // line + 10 + + for(int i = 0; i < MULTI_CONVERTER_DISPLAY_KEYS; i++) { + char g; + if(i < 10) + g = (i + '0'); + else if(i < 16) + g = ((i - 10) + 'A'); + else if(i == MULTI_CONVERTER_DISPLAY_KEY_DEL) + g = MULTI_CONVERTER_DISPLAY_CHAR_DEL; + else + g = MULTI_CONVERTER_DISPLAY_CHAR_SELECT; + + uint8_t g_w = canvas_glyph_width(canvas, g); + + if(i < 16 && + i > multi_converter_get_unit(multi_converter_state->unit_type_orig).max_number_keys - + 1) { + // some units won't use the full [0] - [F] keyboard, in those situations just hide the char + // (won't be selectable anyway, so no worries here; this is just about drawing stuff) + g = MULTI_CONVERTER_DISPLAY_CHAR_BLANK; + } + + // currently hover key is highlighted + if((multi_converter_state->display).key == i) { + canvas_draw_box( + canvas, + _x - MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN, + _y - (MULTI_CONVERTER_DISPLAY_KEY_CHAR_HEIGHT + + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN), + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN + g_w + + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN, + MULTI_CONVERTER_DISPLAY_KEY_CHAR_HEIGHT + + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN * 2); + canvas_set_color(canvas, ColorWhite); + } else { + canvas_draw_frame( + canvas, + _x - MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN, + _y - (MULTI_CONVERTER_DISPLAY_KEY_CHAR_HEIGHT + + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN), + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN + g_w + + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN, + MULTI_CONVERTER_DISPLAY_KEY_CHAR_HEIGHT + + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN * 2); + } + + // draw key + canvas_draw_glyph(canvas, _x, _y, g); + + // certain keys have long_press features, draw whatever they're using there too + if(i == MULTI_CONVERTER_DISPLAY_KEY_NEGATIVE) { + canvas_draw_box( + canvas, + _x + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN + g_w - 4, + _y + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN - 2, + 4, + 2); + } else if(i == MULTI_CONVERTER_DISPLAY_KEY_COMMA) { + canvas_draw_box( + canvas, + _x + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN + g_w - 2, + _y + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN - 2, + 2, + 2); + } + + // back to black + canvas_set_color(canvas, ColorBlack); + + if(i < 8) { + _x += g_w + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN * 2 + 2; + } else if(i == 8) { + _y += (MULTI_CONVERTER_DISPLAY_KEY_CHAR_HEIGHT + + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN * 2) + + 3; + _x = 8; // some padding at the beginning on second line + } else { + _x += g_w + MULTI_CONVERTER_DISPLAY_KEY_FRAME_MARGIN * 2 + 1; + } + } +} + +void multi_converter_mode_display_navigation( + InputKey key, + MultiConverterState* const multi_converter_state) { + // first move to keyboard position, then check if the ORIGIN allows that specific key, if not jump to the "closest one" + switch(key) { + default: + break; + + case InputKeyUp: + case InputKeyDown: + if((multi_converter_state->display).key >= 9) + (multi_converter_state->display).key -= 9; + else + (multi_converter_state->display).key += 9; + break; + + case InputKeyLeft: + case InputKeyRight: + + (multi_converter_state->display).key += (key == InputKeyLeft ? -1 : 1); + + if((multi_converter_state->display).key > MULTI_CONVERTER_DISPLAY_KEYS - 1) + (multi_converter_state->display).key = 0; + else if((multi_converter_state->display).key < 0) + (multi_converter_state->display).key = MULTI_CONVERTER_DISPLAY_KEYS - 1; + break; + } + + // if destination key is disabled by max_number_keys, move to the closest one + // (this could be improved with more accurate keys movements, probably...) + if(multi_converter_get_unit(multi_converter_state->unit_type_orig).max_number_keys >= 16) + return; // weird, since this means "do not show any number on the keyboard, but just in case..." + + int8_t i = -1; + if(key == InputKeyRight || key == InputKeyDown) i = 1; + + while((multi_converter_state->display).key < 16 && + (multi_converter_state->display).key > + multi_converter_get_unit(multi_converter_state->unit_type_orig).max_number_keys - + 1) { + (multi_converter_state->display).key += i; + if((multi_converter_state->display).key > MULTI_CONVERTER_DISPLAY_KEYS - 1) + (multi_converter_state->display).key = 0; + else if((multi_converter_state->display).key < 0) + (multi_converter_state->display).key = MULTI_CONVERTER_DISPLAY_KEYS - 1; + } +} + +void multi_converter_mode_display_reset(MultiConverterState* const multi_converter_state) { + // clean the buffers + for(int i = 0; i < MULTI_CONVERTER_NUMBER_DIGITS; i++) { + multi_converter_state->buffer_orig[i] = MULTI_CONVERTER_DISPLAY_CHAR_BLANK; + multi_converter_state->buffer_dest[i] = MULTI_CONVERTER_DISPLAY_CHAR_BLANK; + } + + // reset the display flags and index + multi_converter_state->display.cursor = 0; + multi_converter_state->display.key = 0; + multi_converter_state->display.comma = 0; + multi_converter_state->display.negative = 0; +} + +void multi_converter_mode_display_toggle_negative( + MultiConverterState* const multi_converter_state) { + if(multi_converter_get_unit(multi_converter_state->unit_type_orig).allow_negative) { + if(!(multi_converter_state->display).negative) { + // shift origin buffer one to right + add the "-" sign (last digit will be lost) + for(int i = MULTI_CONVERTER_NUMBER_DIGITS - 1; i > 0; i--) { + // we could avoid the blanks, but nevermind + multi_converter_state->buffer_orig[i] = multi_converter_state->buffer_orig[i - 1]; + } + multi_converter_state->buffer_orig[0] = MULTI_CONVERTER_DISPLAY_CHAR_NEGATIVE; + + // only increment cursor if we're not out of bound + if((multi_converter_state->display).cursor < MULTI_CONVERTER_NUMBER_DIGITS) + (multi_converter_state->display).cursor++; + } else { + // shift origin buffer one to left, append ' ' on the end + for(int i = 0; i < MULTI_CONVERTER_NUMBER_DIGITS - 1; i++) { + if(multi_converter_state->buffer_orig[i] == MULTI_CONVERTER_DISPLAY_CHAR_BLANK) + break; + + multi_converter_state->buffer_orig[i] = multi_converter_state->buffer_orig[i + 1]; + } + multi_converter_state->buffer_orig[MULTI_CONVERTER_NUMBER_DIGITS - 1] = + MULTI_CONVERTER_DISPLAY_CHAR_BLANK; + + (multi_converter_state->display).cursor--; + } + + // toggle flag + (multi_converter_state->display).negative ^= 1; + } +} + +void multi_converter_mode_display_add_comma(MultiConverterState* const multi_converter_state) { + if(!multi_converter_get_unit(multi_converter_state->unit_type_orig).allow_comma || + (multi_converter_state->display).comma || !(multi_converter_state->display).cursor || + ((multi_converter_state->display).cursor == (MULTI_CONVERTER_NUMBER_DIGITS - 1))) + return; // maybe not allowerd; or one comma already in place; also cannot add commas as first or last chars + + // set flag to one + (multi_converter_state->display).comma = 1; + + multi_converter_state->buffer_orig[(multi_converter_state->display).cursor] = + MULTI_CONVERTER_DISPLAY_CHAR_COMMA; + (multi_converter_state->display).cursor++; +} + +void multi_converter_mode_display_add_number(MultiConverterState* const multi_converter_state) { + if((multi_converter_state->display).key > + multi_converter_get_unit(multi_converter_state->unit_type_orig).max_number_keys - 1) + return; + + if((multi_converter_state->display).key < 10) { + multi_converter_state->buffer_orig[(multi_converter_state->display).cursor] = + (multi_converter_state->display).key + '0'; + } else { + multi_converter_state->buffer_orig[(multi_converter_state->display).cursor] = + ((multi_converter_state->display).key - 10) + 'A'; + } + + (multi_converter_state->display).cursor++; +} + +MultiConverterModeTrigger multi_converter_mode_display_ok( + uint8_t long_press, + MultiConverterState* const multi_converter_state) { + if((multi_converter_state->display).key < MULTI_CONVERTER_DISPLAY_KEY_DEL) { + if((multi_converter_state->display).cursor >= MULTI_CONVERTER_NUMBER_DIGITS) + return None; // limit reached, ignore + + // long press on 0 toggle NEGATIVE if allowed, on 1 adds COMMA if allowed + if(long_press) { + if((multi_converter_state->display).key == MULTI_CONVERTER_DISPLAY_KEY_NEGATIVE) { + // toggle negative + multi_converter_mode_display_toggle_negative(multi_converter_state); + } else if((multi_converter_state->display).key == MULTI_CONVERTER_DISPLAY_KEY_COMMA) { + // add comma + multi_converter_mode_display_add_comma(multi_converter_state); + } + + } else { + // regular keys + multi_converter_mode_display_add_number(multi_converter_state); + } + + multi_converter_mode_display_convert(multi_converter_state); + + } else if((multi_converter_state->display).key == MULTI_CONVERTER_DISPLAY_KEY_DEL) { + if((multi_converter_state->display).cursor > 0) (multi_converter_state->display).cursor--; + + if(multi_converter_state->buffer_orig[(multi_converter_state->display).cursor] == + MULTI_CONVERTER_DISPLAY_CHAR_COMMA) + (multi_converter_state->display).comma = 0; + if(multi_converter_state->buffer_orig[(multi_converter_state->display).cursor] == + MULTI_CONVERTER_DISPLAY_CHAR_NEGATIVE) + (multi_converter_state->display).negative = 0; + + multi_converter_state->buffer_orig[(multi_converter_state->display).cursor] = + MULTI_CONVERTER_DISPLAY_CHAR_BLANK; + + multi_converter_mode_display_convert(multi_converter_state); + + } else { // MULTI_CONVERTER_DISPLAY_KEY_SELECT + return Reset; + } + + return None; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/multi_converter/multi_converter_mode_display.h b/Applications/Official/DEV_FW/source/multi_converter/multi_converter_mode_display.h new file mode 100644 index 000000000..cae929d35 --- /dev/null +++ b/Applications/Official/DEV_FW/source/multi_converter/multi_converter_mode_display.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include + +#include "multi_converter_definitions.h" +#include "multi_converter_units.h" + +// +// performs a unit conversion from origin to source buffers, if there's any error, overflow or +// non-compatible format (which shouldn't happen, but just in case) abort conversion and outputs +// some "?" strings on the buffer or something similar +// +void multi_converter_mode_display_convert(MultiConverterState* const multi_converter_state); + +// +// draw the main DISPLAY view with the current multi_converter_state values +// +void multi_converter_mode_display_draw( + Canvas* const canvas, + const MultiConverterState* multi_converter_state); + +// +// keyboard navigation on DISPLAY mode (NAVIGATION only, no BACK nor OK - InputKey guaranteed to be left/right/up/down) +// +void multi_converter_mode_display_navigation( + InputKey key, + MultiConverterState* const multi_converter_state); + +// +// reset the DISPLAY mode with the current units, cleaning the buffers and different flags; +// call this when exiting the SELECT mode / changing the units +// +void multi_converter_mode_display_reset(MultiConverterState* const multi_converter_state); + +// +// toggle the negative flag on current selected buffer ONLY if the unit allows negative numbers +// (adding negative number may crop the last char on the buffer; it cannot be recovered) +// +void multi_converter_mode_display_toggle_negative(MultiConverterState* const multi_converter_state); + +// +// add a comma/dot/decimal separator/whatever on current selected buffer ONLY if the unit allows it +// (only ONE comma allowed, not in the beginning nor end) +// +void multi_converter_mode_display_add_comma(MultiConverterState* const multi_converter_state); + +// +// add a regular number to the buffer if it's <= the max_number_keys from the unit (not necessary +// since the draw and navigation functions won't allow a trigger for an invalid number, but still +// to keep the "checks" policy on each "add key" function...) +// +void multi_converter_mode_display_add_number(MultiConverterState* const multi_converter_state); + +// +// handle the OK action when selecting a specific key on the keyboard (add a number, a symbol, change mode...) +// returns a ModeTrigger enum value: may or may not let to a mode change on the main loop (WON'T change the mode here) +// +MultiConverterModeTrigger multi_converter_mode_display_ok( + uint8_t long_press, + MultiConverterState* const multi_converter_state); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/multi_converter/multi_converter_mode_select.c b/Applications/Official/DEV_FW/source/multi_converter/multi_converter_mode_select.c new file mode 100644 index 000000000..a56ccd58c --- /dev/null +++ b/Applications/Official/DEV_FW/source/multi_converter/multi_converter_mode_select.c @@ -0,0 +1,210 @@ +#include "multi_converter_mode_select.h" + +#define MULTI_CONVERTER_LIST_ENTRIES_COUNT 3 + +#define MULTI_CONVERTER_INFO_STRING_FROM "FROM:" +#define MULTI_CONVERTER_INFO_STRING_TO "TO:" +#define MULTI_CONVERTER_INFO_STRING_OK "OK: Change" +#define MULTI_CONVERTER_INFO_STRING_BACK "BACK: Cancel" + +void multi_converter_mode_select_draw_destination_offset( + uint8_t x, + uint8_t y, + int8_t d, + Canvas* const canvas, + const MultiConverterState* multi_converter_state) { + int i = 1; + while( + i < + MULTI_CONVERTER_AVAILABLE_UNITS) { // in case there's no match, to avoid an endless loop (in theory shouldn't happen, but...) + int ut = multi_converter_get_unit_type_offset( + (multi_converter_state->select).selected_unit_type_dest, i * d); + if(multi_converter_available_units[(multi_converter_state->select).selected_unit_type_orig] + .allowed_function(ut) && + (multi_converter_state->select).selected_unit_type_orig != ut) { + canvas_draw_str(canvas, x, y, multi_converter_available_units[ut].name); + break; + } + i++; + } +} + +void multi_converter_mode_select_draw_selected_unit( + uint8_t x, + uint8_t y, + MultiConverterUnitType unit_type, + Canvas* const canvas) { + canvas_draw_box( + canvas, + x - 2, + y - 10, + canvas_string_width(canvas, multi_converter_available_units[unit_type].name) + 4, + 13); + canvas_set_color(canvas, ColorWhite); + canvas_draw_str(canvas, x, y, multi_converter_available_units[unit_type].name); + canvas_set_color(canvas, ColorBlack); +} + +void multi_converter_mode_select_draw( + Canvas* const canvas, + const MultiConverterState* multi_converter_state) { + int y = 10; + int x = 10; + + canvas_set_color(canvas, ColorBlack); + + // FROM + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, x, y, MULTI_CONVERTER_INFO_STRING_FROM); + + canvas_set_font(canvas, FontSecondary); + + // offset -1 + y += 12; + + canvas_draw_str( + canvas, + x, + y, + multi_converter_available_units[multi_converter_get_unit_type_offset( + (multi_converter_state->select).selected_unit_type_orig, + -1)] + .name); + + // current selected element + y += 12; + + multi_converter_mode_select_draw_selected_unit( + x, y, (multi_converter_state->select).selected_unit_type_orig, canvas); + + if((multi_converter_state->select).select_orig) canvas_draw_str(canvas, x - 6, y, ">"); + + // offset +1 + y += 12; + + canvas_draw_str( + canvas, + x, + y, + multi_converter_available_units[multi_converter_get_unit_type_offset( + (multi_converter_state->select).selected_unit_type_orig, + 1)] + .name); + + // TO + y = 10; + x = 70; + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, x, y, MULTI_CONVERTER_INFO_STRING_TO); + + canvas_set_font(canvas, FontSecondary); + + // offset -1: go back from current selected destination and find the first one valid (even if it's itself) + y += 12; + + multi_converter_mode_select_draw_destination_offset(x, y, -1, canvas, multi_converter_state); + + // current selected element + y += 12; + + multi_converter_mode_select_draw_selected_unit( + x, y, (multi_converter_state->select).selected_unit_type_dest, canvas); + + if(!(multi_converter_state->select).select_orig) canvas_draw_str(canvas, x - 6, y, ">"); + + // offset +1: same but on the opposite direction + y += 12; + + multi_converter_mode_select_draw_destination_offset(x, y, 1, canvas, multi_converter_state); + + // OK / CANCEL + + canvas_set_color(canvas, ColorBlack); + canvas_draw_box( + canvas, 0, 64 - 12, canvas_string_width(canvas, MULTI_CONVERTER_INFO_STRING_OK) + 4, 12); + canvas_draw_box( + canvas, + 128 - 4 - canvas_string_width(canvas, MULTI_CONVERTER_INFO_STRING_BACK), + 64 - 12, + canvas_string_width(canvas, "BACK: Cancel") + 4, + 12); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_str(canvas, 2, 64 - 3, MULTI_CONVERTER_INFO_STRING_OK); + canvas_draw_str( + canvas, + 128 - 2 - canvas_string_width(canvas, MULTI_CONVERTER_INFO_STRING_BACK), + 64 - 3, + MULTI_CONVERTER_INFO_STRING_BACK); +} + +void multi_converter_mode_select_reset(MultiConverterState* const multi_converter_state) { + // initial pre-selected values are equal to the current selected values + (multi_converter_state->select).selected_unit_type_orig = + multi_converter_state->unit_type_orig; + (multi_converter_state->select).selected_unit_type_dest = + multi_converter_state->unit_type_dest; + + (multi_converter_state->select).select_orig = 1; +} + +MultiConverterModeTrigger multi_converter_mode_select_exit( + uint8_t save_changes, + MultiConverterState* const multi_converter_state) { + if(save_changes) { + multi_converter_state->unit_type_dest = + (multi_converter_state->select).selected_unit_type_dest; + + if(multi_converter_state->unit_type_orig == + (multi_converter_state->select).selected_unit_type_orig) { + // if the ORIGIN unit didn't changed, just trigger the convert + + return Convert; + } else { + multi_converter_state->unit_type_orig = + (multi_converter_state->select).selected_unit_type_orig; + multi_converter_state->unit_type_dest = + (multi_converter_state->select).selected_unit_type_dest; + + return Reset; + } + } + + return None; +} + +void multi_converter_mode_select_switch(MultiConverterState* const multi_converter_state) { + (multi_converter_state->select).select_orig ^= 1; +} + +void multi_converter_mode_select_change_unit( + int8_t direction, + MultiConverterState* const multi_converter_state) { + MultiConverterUnitType d; + if((multi_converter_state->select).select_orig) { + (multi_converter_state->select).selected_unit_type_orig = + multi_converter_get_unit_type_offset( + (multi_converter_state->select).selected_unit_type_orig, direction); + d = (multi_converter_state->select).selected_unit_type_dest; + } else { + d = ((multi_converter_state->select).selected_unit_type_dest + direction) % + MULTI_CONVERTER_AVAILABLE_UNITS; + } + + // check each unit with the ORIGIN allowed_function() to make sure we're selecting a valid DESTINATION + // (when changing the ORIGIN unit the DIRECTION in which we'll switch the DESTINATION will be the SAME); + // also notice that ORIGIN must be DIFFERENT than DESTINATION + int i = 0; + while(i < MULTI_CONVERTER_AVAILABLE_UNITS) { + if(multi_converter_available_units[(multi_converter_state->select).selected_unit_type_orig] + .allowed_function(d) && + (multi_converter_state->select).selected_unit_type_orig != d) { + (multi_converter_state->select).selected_unit_type_dest = d; + break; + } + + d = multi_converter_get_unit_type_offset(d, direction); + i++; + } +} diff --git a/Applications/Official/DEV_FW/source/multi_converter/multi_converter_mode_select.h b/Applications/Official/DEV_FW/source/multi_converter/multi_converter_mode_select.h new file mode 100644 index 000000000..c10ab8e01 --- /dev/null +++ b/Applications/Official/DEV_FW/source/multi_converter/multi_converter_mode_select.h @@ -0,0 +1,73 @@ +#pragma once + +#include +#include +#include + +#include "multi_converter_definitions.h" +#include "multi_converter_units.h" + +// +// aux draw function for units offsets and draw stuff +// +void multi_converter_mode_select_draw_destination_offset( + uint8_t x, + uint8_t y, + int8_t d, + Canvas* const canvas, + const MultiConverterState* multi_converter_state); + +void multi_converter_mode_select_draw_selected_unit( + uint8_t x, + uint8_t y, + MultiConverterUnitType unit_type, + Canvas* const canvas); + +// +// draw the main SELECT view with the current multi_converter_state values +// +void multi_converter_mode_select_draw( + Canvas* const canvas, + const MultiConverterState* multi_converter_state); + +// +// reset the SELECT mode view, showing as "pre-selected" the current working units +// +void multi_converter_mode_select_reset(MultiConverterState* const multi_converter_state); + +// +// exit from SELECT mode and go back to display view, if save_changes == 1 use the current SELECT view info +// to modify the current selected units and reset the views properly (usually if the ORIGIN unit has been +// changed, reset everything; otherwise just trigger the convert function with a new DESTINATION) +// +// currently this function DON'T CHECK invalid unit relations (the navigation and display functions will +// prevent weird behaviours, so for now we're trusting the selected_unit_orig/dest_type values) +// +// returns an enum code MultiConverterDisplayTrigger based on doing nothing (cancel), triggering the display +// convert method or reseting the whole display mode (when fully changing the units) +// +// notice the MODE CHANGE itself is not done here but in the main loop (outside the call) via the ModeTrigger enum element +// +MultiConverterModeTrigger multi_converter_mode_select_exit( + uint8_t save_changes, + MultiConverterState* const multi_converter_state); + +// +// switch between selecting the ORIGIN or the DESTINATION unit on DISPLAY mode (since there're only +// two options, both left/right arrow keys acts as toggles, no "direction" required) +// +void multi_converter_mode_select_switch(MultiConverterState* const multi_converter_state); + +// +// change the selected unit on SELECTED mode, using the select_orig flag to check if we're switching the +// ORIGIN or the DESTINATION unit; the DIRECTION (up or down to travel the array) is set as a param +// +// when switching the ORIGIN one, reset the DESTINATION to the first valid unit (if the current one is not +// valid anymore); when switching the DESTINATION one, an allowed_function() check is performed in order to +// properly set a valid destination unit. +// +// (notice the draw step also perform which units are valid to display, so no worries about that here) +// +void multi_converter_mode_select_change_unit( + int8_t direction, + MultiConverterState* const multi_converter_state); diff --git a/Applications/Official/DEV_FW/source/multi_converter/multi_converter_units.c b/Applications/Official/DEV_FW/source/multi_converter/multi_converter_units.c new file mode 100644 index 000000000..4381f0e6e --- /dev/null +++ b/Applications/Official/DEV_FW/source/multi_converter/multi_converter_units.c @@ -0,0 +1,261 @@ +#include "multi_converter_units.h" + +#define MULTI_CONVERTER_CHAR_OVERFLOW '#' +#define MULTI_CONVERTER_MAX_SUPORTED_INT 999999999 + +#define multi_converter_unit_set_overflow(b) \ + for(int _i = 0; _i < MULTI_CONVERTER_NUMBER_DIGITS; _i++) \ + b[_i] = MULTI_CONVERTER_CHAR_OVERFLOW; + +// +// DEC / HEX / BIN conversion +// +void multi_converter_unit_dec_hex_bin_convert(MultiConverterState* const multi_converter_state) { + char dest[MULTI_CONVERTER_NUMBER_DIGITS]; + + int i = 0; + uint8_t overflow = 0; + + int a = 0; + int r = 0; + uint8_t f = 1; + + switch(multi_converter_state->unit_type_orig) { + default: + break; + case UnitTypeDec: { + a = atoi(multi_converter_state->buffer_orig); + f = (multi_converter_state->unit_type_dest == UnitTypeHex ? 16 : 2); + + break; + } + case UnitTypeHex: + a = strtol(multi_converter_state->buffer_orig, NULL, 16); + f = (multi_converter_state->unit_type_dest == UnitTypeDec ? 10 : 2); + + break; + case UnitTypeBin: + a = strtol(multi_converter_state->buffer_orig, NULL, 2); + f = (multi_converter_state->unit_type_dest == UnitTypeDec ? 10 : 16); + + break; + } + + while(a > 0) { + r = a % f; + dest[i] = r + (r < 10 ? '0' : ('A' - 10)); + a /= f; + if(i++ >= MULTI_CONVERTER_NUMBER_DIGITS) { + overflow = 1; + break; + } + } + + if(overflow) { + multi_converter_unit_set_overflow(multi_converter_state->buffer_dest); + } else { + // copy DEST (reversed) to destination and append empty chars at the end + for(int j = 0; j < MULTI_CONVERTER_NUMBER_DIGITS; j++) { + if(i >= 1) + multi_converter_state->buffer_dest[j] = dest[--i]; + else + multi_converter_state->buffer_dest[j] = ' '; + } + } +} + +uint8_t multi_converter_unit_dec_hex_bin_allowed(MultiConverterUnitType unit_type) { + return (unit_type == UnitTypeDec || unit_type == UnitTypeHex || unit_type == UnitTypeBin); +} + +// +// CEL / FAR / KEL +// +void multi_converter_unit_temperature_convert(MultiConverterState* const multi_converter_state) { + double a = strtof(multi_converter_state->buffer_orig, NULL); + uint8_t overflow = 0; + + switch(multi_converter_state->unit_type_orig) { + default: + break; + case UnitTypeCelsius: + if(multi_converter_state->unit_type_dest == UnitTypeFahernheit) { + // celsius to fahrenheit + a = (a * ((double)1.8)) + 32; + } else { // UnitTypeKelvin + a += ((double)273.15); + } + + break; + case UnitTypeFahernheit: + // fahrenheit to celsius, always + a = (a - 32) / ((double)1.8); + if(multi_converter_state->unit_type_dest == UnitTypeKelvin) { + // if kelvin, add + a += ((double)273.15); + } + + break; + case UnitTypeKelvin: + // kelvin to celsius, always + a -= ((double)273.15); + if(multi_converter_state->unit_type_dest == UnitTypeFahernheit) { + // if fahernheit, convert + a = (a * ((double)1.8)) + 32; + } + + break; + } + + if(overflow) { + multi_converter_unit_set_overflow(multi_converter_state->buffer_dest); + } else { + int ret = snprintf( + multi_converter_state->buffer_dest, MULTI_CONVERTER_NUMBER_DIGITS + 1, "%.3lf", a); + + if(ret < 0) multi_converter_unit_set_overflow(multi_converter_state->buffer_dest); + } +} + +uint8_t multi_converter_unit_temperature_allowed(MultiConverterUnitType unit_type) { + return ( + unit_type == UnitTypeCelsius || unit_type == UnitTypeFahernheit || + unit_type == UnitTypeKelvin); +} + +// +// KM / M / CM / MILES / FEET / INCHES +// + +void multi_converter_unit_distance_convert(MultiConverterState* const multi_converter_state) { + double a = strtof(multi_converter_state->buffer_orig, NULL); + uint8_t overflow = 0; + + switch(multi_converter_state->unit_type_orig) { + default: + break; + case UnitTypeKilometers: + if(multi_converter_state->unit_type_dest == UnitTypeMeters) + a *= ((double)1000); + else if(multi_converter_state->unit_type_dest == UnitTypeCentimeters) + a *= ((double)100000); + else if(multi_converter_state->unit_type_dest == UnitTypeMiles) + a *= ((double)0.6213711); + else if(multi_converter_state->unit_type_dest == UnitTypeFeet) + a *= ((double)3280.839895013); + else if(multi_converter_state->unit_type_dest == UnitTypeInches) + a *= ((double)39370.078740157); + break; + case UnitTypeMeters: + if(multi_converter_state->unit_type_dest == UnitTypeKilometers) + a /= ((double)1000); + else if(multi_converter_state->unit_type_dest == UnitTypeCentimeters) + a *= ((double)100); + else if(multi_converter_state->unit_type_dest == UnitTypeMiles) + a *= ((double)0.0006213711); + else if(multi_converter_state->unit_type_dest == UnitTypeFeet) + a *= ((double)3.280839895013); + else if(multi_converter_state->unit_type_dest == UnitTypeInches) + a *= ((double)39.370078740157); + break; + case UnitTypeCentimeters: + if(multi_converter_state->unit_type_dest == UnitTypeKilometers) + a /= ((double)100000); + else if(multi_converter_state->unit_type_dest == UnitTypeMeters) + a /= ((double)100); + else if(multi_converter_state->unit_type_dest == UnitTypeMiles) + a *= ((double)0.000006213711); + else if(multi_converter_state->unit_type_dest == UnitTypeFeet) + a *= ((double)0.03280839895013); + else if(multi_converter_state->unit_type_dest == UnitTypeInches) + a *= ((double)0.39370078740157); + break; + + case UnitTypeMiles: + if(multi_converter_state->unit_type_dest == UnitTypeKilometers) + a *= ((double)1.609344); + else if(multi_converter_state->unit_type_dest == UnitTypeMeters) + a *= ((double)1609.344); + else if(multi_converter_state->unit_type_dest == UnitTypeCentimeters) + a *= ((double)160934.4); + else if(multi_converter_state->unit_type_dest == UnitTypeFeet) + a *= ((double)5280); + else if(multi_converter_state->unit_type_dest == UnitTypeInches) + a *= ((double)63360); + break; + case UnitTypeFeet: + if(multi_converter_state->unit_type_dest == UnitTypeKilometers) + a *= ((double)0.0003048); + else if(multi_converter_state->unit_type_dest == UnitTypeMeters) + a *= ((double)0.3048); + else if(multi_converter_state->unit_type_dest == UnitTypeCentimeters) + a *= ((double)30.48); + else if(multi_converter_state->unit_type_dest == UnitTypeMiles) + a *= ((double)0.000189393939394); + else if(multi_converter_state->unit_type_dest == UnitTypeInches) + a *= ((double)12); + break; + case UnitTypeInches: + if(multi_converter_state->unit_type_dest == UnitTypeKilometers) + a *= ((double)0.0000254); + else if(multi_converter_state->unit_type_dest == UnitTypeMeters) + a *= ((double)0.0254); + else if(multi_converter_state->unit_type_dest == UnitTypeCentimeters) + a *= ((double)2.54); + else if(multi_converter_state->unit_type_dest == UnitTypeMiles) + a *= ((double)0.0000157828282828); + else if(multi_converter_state->unit_type_dest == UnitTypeFeet) + a *= ((double)0.0833333333333); + break; + } + + if(overflow) { + multi_converter_unit_set_overflow(multi_converter_state->buffer_dest); + } else { + int ret = snprintf( + multi_converter_state->buffer_dest, MULTI_CONVERTER_NUMBER_DIGITS + 1, "%lf", a); + + if(ret < 0) multi_converter_unit_set_overflow(multi_converter_state->buffer_dest); + } +} + +uint8_t multi_converter_unit_distance_allowed(MultiConverterUnitType unit_type) { + return ( + unit_type == UnitTypeKilometers || unit_type == UnitTypeMeters || + unit_type == UnitTypeCentimeters || unit_type == UnitTypeMiles || + unit_type == UnitTypeFeet || unit_type == UnitTypeInches); +} + +// +// DEG / RAD +// + +void multi_converter_unit_angle_convert(MultiConverterState* const multi_converter_state) { + double a = strtof(multi_converter_state->buffer_orig, NULL); + uint8_t overflow = 0; + + switch(multi_converter_state->unit_type_orig) { + default: + break; + case UnitTypeDegree: + if(multi_converter_state->unit_type_dest == UnitTypeRadian) a *= ((double)0.0174532925199); + break; + + case UnitTypeRadian: + if(multi_converter_state->unit_type_dest == UnitTypeDegree) a *= ((double)57.2957795131); + break; + } + + if(overflow) { + multi_converter_unit_set_overflow(multi_converter_state->buffer_dest); + } else { + int ret = snprintf( + multi_converter_state->buffer_dest, MULTI_CONVERTER_NUMBER_DIGITS + 1, "%lf", a); + + if(ret < 0) multi_converter_unit_set_overflow(multi_converter_state->buffer_dest); + } +} + +uint8_t multi_converter_unit_angle_allowed(MultiConverterUnitType unit_type) { + return (unit_type == UnitTypeDegree || unit_type == UnitTypeRadian); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/multi_converter/multi_converter_units.h b/Applications/Official/DEV_FW/source/multi_converter/multi_converter_units.h new file mode 100644 index 000000000..6d3b477bb --- /dev/null +++ b/Applications/Official/DEV_FW/source/multi_converter/multi_converter_units.h @@ -0,0 +1,171 @@ +#pragma once + +#include +#include + +#include "multi_converter_definitions.h" + +#define MULTI_CONVERTER_AVAILABLE_UNITS 14 + +#define multi_converter_get_unit(unit_type) multi_converter_available_units[unit_type] +#define multi_converter_get_unit_type_offset(unit_type, offset) \ + (((unit_type + offset) % MULTI_CONVERTER_AVAILABLE_UNITS + MULTI_CONVERTER_AVAILABLE_UNITS) % \ + MULTI_CONVERTER_AVAILABLE_UNITS) +// the modulo operation will fail with extremely large values on the units array + +// DEC / HEX / BIN +void multi_converter_unit_dec_hex_bin_convert(MultiConverterState* const multi_converter_state); +uint8_t multi_converter_unit_dec_hex_bin_allowed(MultiConverterUnitType); + +// CEL / FAR / KEL +void multi_converter_unit_temperature_convert(MultiConverterState* const multi_converter_state); +uint8_t multi_converter_unit_temperature_allowed(MultiConverterUnitType); + +// KM / M / CM / MILES / FEET / INCHES +void multi_converter_unit_distance_convert(MultiConverterState* const multi_converter_state); +uint8_t multi_converter_unit_distance_allowed(MultiConverterUnitType); + +// DEG / RAD +void multi_converter_unit_angle_convert(MultiConverterState* const multi_converter_state); +uint8_t multi_converter_unit_angle_allowed(MultiConverterUnitType unit_type); + +// +// each unit is made of comma? + negative? + keyboard_length + mini_name + name + convert function + allowed function +// (setting functions as NULL will cause convert / select options to be ignored) +// +static const MultiConverterUnit multi_converter_unit_dec = { + 0, + 0, + 10, + "DEC\0", + "Decimal\0", + multi_converter_unit_dec_hex_bin_convert, + multi_converter_unit_dec_hex_bin_allowed}; +static const MultiConverterUnit multi_converter_unit_hex = { + 0, + 0, + 16, + "HEX\0", + "Hexadecimal\0", + multi_converter_unit_dec_hex_bin_convert, + multi_converter_unit_dec_hex_bin_allowed}; +static const MultiConverterUnit multi_converter_unit_bin = { + 0, + 0, + 2, + "BIN\0", + "Binary\0", + multi_converter_unit_dec_hex_bin_convert, + multi_converter_unit_dec_hex_bin_allowed}; + +static const MultiConverterUnit multi_converter_unit_cel = { + 1, + 1, + 10, + "CEL\0", + "Celsius\0", + multi_converter_unit_temperature_convert, + multi_converter_unit_temperature_allowed}; +static const MultiConverterUnit multi_converter_unit_far = { + 1, + 1, + 10, + "FAR\0", + "Fahernheit\0", + multi_converter_unit_temperature_convert, + multi_converter_unit_temperature_allowed}; +static const MultiConverterUnit multi_converter_unit_kel = { + 1, + 1, + 10, + "KEL\0", + "Kelvin\0", + multi_converter_unit_temperature_convert, + multi_converter_unit_temperature_allowed}; + +static const MultiConverterUnit multi_converter_unit_km = { + 1, + 0, + 10, + "KM\0", + "Kilometers\0", + multi_converter_unit_distance_convert, + multi_converter_unit_distance_allowed}; +static const MultiConverterUnit multi_converter_unit_m = { + 1, + 0, + 10, + "M\0", + "Meters\0", + multi_converter_unit_distance_convert, + multi_converter_unit_distance_allowed}; +static const MultiConverterUnit multi_converter_unit_cm = { + 1, + 0, + 10, + "CM\0", + "Centimeters\0", + multi_converter_unit_distance_convert, + multi_converter_unit_distance_allowed}; +static const MultiConverterUnit multi_converter_unit_mi = { + 1, + 0, + 10, + "MI\0", + "Miles\0", + multi_converter_unit_distance_convert, + multi_converter_unit_distance_allowed}; +static const MultiConverterUnit multi_converter_unit_ft = { + 1, + 0, + 10, + "FT\0", + "Feet\0", + multi_converter_unit_distance_convert, + multi_converter_unit_distance_allowed}; +static const MultiConverterUnit multi_converter_unit_in = { + 1, + 0, + 10, + " \"\0", + "Inches\0", + multi_converter_unit_distance_convert, + multi_converter_unit_distance_allowed}; + +static const MultiConverterUnit multi_converter_unit_deg = { + 1, + 0, + 10, + "DEG\0", + "Degree\0", + multi_converter_unit_angle_convert, + multi_converter_unit_angle_allowed}; +static const MultiConverterUnit multi_converter_unit_rad = { + 1, + 0, + 10, + "RAD\0", + "Radian\0", + multi_converter_unit_angle_convert, + multi_converter_unit_angle_allowed}; + +// index order set by the MultiConverterUnitType enum element (multi_converter_definitions.h) +static const MultiConverterUnit multi_converter_available_units[MULTI_CONVERTER_AVAILABLE_UNITS] = { + [UnitTypeDec] = multi_converter_unit_dec, + [UnitTypeHex] = multi_converter_unit_hex, + [UnitTypeBin] = multi_converter_unit_bin, + + [UnitTypeCelsius] = multi_converter_unit_cel, + [UnitTypeFahernheit] = multi_converter_unit_far, + [UnitTypeKelvin] = multi_converter_unit_kel, + + [UnitTypeKilometers] = multi_converter_unit_km, + [UnitTypeMeters] = multi_converter_unit_m, + [UnitTypeCentimeters] = multi_converter_unit_cm, + [UnitTypeMiles] = multi_converter_unit_mi, + [UnitTypeFeet] = multi_converter_unit_ft, + [UnitTypeInches] = multi_converter_unit_in, + + [UnitTypeDegree] = multi_converter_unit_deg, + [UnitTypeRadian] = multi_converter_unit_rad, +}; \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/music_beeper/application.fam b/Applications/Official/DEV_FW/source/music_beeper/application.fam new file mode 100644 index 000000000..2a83867bf --- /dev/null +++ b/Applications/Official/DEV_FW/source/music_beeper/application.fam @@ -0,0 +1,25 @@ +App( + appid="Music_Beeper", + name="Music Beeper", + apptype=FlipperAppType.EXTERNAL, + entry_point="music_beeper_app", + cdefines=["APP_MUSIC_BEEPER"], + requires=[ + "gui", + "dialogs", + ], + provides=["music_beeper_start"], + stack_size=2 * 1024, + order=45, + fap_icon="icons/music_10px.png", + fap_category="Music", + fap_icon_assets="icons", +) + +App( + appid="music_beeper_start", + apptype=FlipperAppType.STARTUP, + entry_point="music_beeper_on_system_start", + requires=["music_beeper"], + order=30, +) diff --git a/Applications/Official/DEV_FW/source/music_beeper/icons/music_10px.png b/Applications/Official/DEV_FW/source/music_beeper/icons/music_10px.png new file mode 100644 index 000000000..d41eb0db8 Binary files /dev/null and b/Applications/Official/DEV_FW/source/music_beeper/icons/music_10px.png differ diff --git a/Applications/Official/DEV_FW/source/music_beeper/music_beeper.c b/Applications/Official/DEV_FW/source/music_beeper/music_beeper.c new file mode 100644 index 000000000..edebbc597 --- /dev/null +++ b/Applications/Official/DEV_FW/source/music_beeper/music_beeper.c @@ -0,0 +1,367 @@ +#include "music_beeper_worker.h" + +#include +#include + +#include +#include +#include +#include + +#define TAG "MusicBeeper" + +#define MUSIC_BEEPER_APP_PATH_FOLDER ANY_PATH("music_player") +#define MUSIC_BEEPER_APP_EXTENSION "*" + +#define MUSIC_BEEPER_SEMITONE_HISTORY_SIZE 4 + +typedef struct { + uint8_t semitone_history[MUSIC_BEEPER_SEMITONE_HISTORY_SIZE]; + uint8_t duration_history[MUSIC_BEEPER_SEMITONE_HISTORY_SIZE]; + + uint8_t volume; + uint8_t semitone; + uint8_t dots; + uint8_t duration; + float position; +} MusicBeeperModel; + +typedef struct { + MusicBeeperModel* model; + FuriMutex** model_mutex; + + FuriMessageQueue* input_queue; + + ViewPort* view_port; + Gui* gui; + + MusicBeeperWorker* worker; +} MusicBeeper; + +static const float MUSIC_BEEPER_VOLUMES[] = {0, .25, .5, .75, 1}; + +static const char* semitone_to_note(int8_t semitone) { + switch(semitone) { + case 0: + return "C"; + case 1: + return "C#"; + case 2: + return "D"; + case 3: + return "D#"; + case 4: + return "E"; + case 5: + return "F"; + case 6: + return "F#"; + case 7: + return "G"; + case 8: + return "G#"; + case 9: + return "A"; + case 10: + return "A#"; + case 11: + return "B"; + default: + return "--"; + } +} + +static bool is_white_note(uint8_t semitone, uint8_t id) { + switch(semitone) { + case 0: + if(id == 0) return true; + break; + case 2: + if(id == 1) return true; + break; + case 4: + if(id == 2) return true; + break; + case 5: + if(id == 3) return true; + break; + case 7: + if(id == 4) return true; + break; + case 9: + if(id == 5) return true; + break; + case 11: + if(id == 6) return true; + break; + default: + break; + } + + return false; +} + +static bool is_black_note(uint8_t semitone, uint8_t id) { + switch(semitone) { + case 1: + if(id == 0) return true; + break; + case 3: + if(id == 1) return true; + break; + case 6: + if(id == 3) return true; + break; + case 8: + if(id == 4) return true; + break; + case 10: + if(id == 5) return true; + break; + default: + break; + } + + return false; +} + +static void render_callback(Canvas* canvas, void* ctx) { + MusicBeeper* music_beeper = ctx; + furi_check(furi_mutex_acquire(music_beeper->model_mutex, FuriWaitForever) == FuriStatusOk); + + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 0, 12, "MusicBeeper"); + + uint8_t x_pos = 0; + uint8_t y_pos = 24; + const uint8_t white_w = 10; + const uint8_t white_h = 40; + + const int8_t black_x = 6; + const int8_t black_y = -5; + const uint8_t black_w = 8; + const uint8_t black_h = 32; + + // white keys + for(size_t i = 0; i < 7; i++) { + if(is_white_note(music_beeper->model->semitone, i)) { + canvas_draw_box(canvas, x_pos + white_w * i, y_pos, white_w + 1, white_h); + } else { + canvas_draw_frame(canvas, x_pos + white_w * i, y_pos, white_w + 1, white_h); + } + } + + // black keys + for(size_t i = 0; i < 7; i++) { + if(i != 2 && i != 6) { + canvas_set_color(canvas, ColorWhite); + canvas_draw_box( + canvas, x_pos + white_w * i + black_x, y_pos + black_y, black_w + 1, black_h); + canvas_set_color(canvas, ColorBlack); + if(is_black_note(music_beeper->model->semitone, i)) { + canvas_draw_box( + canvas, x_pos + white_w * i + black_x, y_pos + black_y, black_w + 1, black_h); + } else { + canvas_draw_frame( + canvas, x_pos + white_w * i + black_x, y_pos + black_y, black_w + 1, black_h); + } + } + } + + // volume view_port + x_pos = 124; + y_pos = 0; + const uint8_t volume_h = + (64 / (COUNT_OF(MUSIC_BEEPER_VOLUMES) - 1)) * music_beeper->model->volume; + canvas_draw_frame(canvas, x_pos, y_pos, 4, 64); + canvas_draw_box(canvas, x_pos, y_pos + (64 - volume_h), 4, volume_h); + + // note stack view_port + x_pos = 73; + y_pos = 0; + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontPrimary); + canvas_draw_frame(canvas, x_pos, y_pos, 49, 64); + canvas_draw_line(canvas, x_pos + 28, 0, x_pos + 28, 64); + + char duration_text[16]; + for(uint8_t i = 0; i < MUSIC_BEEPER_SEMITONE_HISTORY_SIZE; i++) { + if(music_beeper->model->duration_history[i] == 0xFF) { + snprintf(duration_text, 15, "--"); + } else { + snprintf(duration_text, 15, "%d", music_beeper->model->duration_history[i]); + } + + if(i == 0) { + canvas_draw_box(canvas, x_pos, y_pos + 48, 49, 16); + canvas_set_color(canvas, ColorWhite); + } else { + canvas_set_color(canvas, ColorBlack); + } + canvas_draw_str( + canvas, + x_pos + 4, + 64 - 16 * i - 3, + semitone_to_note(music_beeper->model->semitone_history[i])); + canvas_draw_str(canvas, x_pos + 31, 64 - 16 * i - 3, duration_text); + canvas_draw_line(canvas, x_pos, 64 - 16 * i, x_pos + 48, 64 - 16 * i); + } + + furi_mutex_release(music_beeper->model_mutex); +} + +static void input_callback(InputEvent* input_event, void* ctx) { + MusicBeeper* music_beeper = ctx; + if(input_event->type == InputTypeShort) { + furi_message_queue_put(music_beeper->input_queue, input_event, 0); + } +} + +static void music_beeper_worker_callback( + uint8_t semitone, + uint8_t dots, + uint8_t duration, + float position, + void* context) { + MusicBeeper* music_beeper = context; + furi_check(furi_mutex_acquire(music_beeper->model_mutex, FuriWaitForever) == FuriStatusOk); + + for(size_t i = 0; i < MUSIC_BEEPER_SEMITONE_HISTORY_SIZE - 1; i++) { + size_t r = MUSIC_BEEPER_SEMITONE_HISTORY_SIZE - 1 - i; + music_beeper->model->duration_history[r] = music_beeper->model->duration_history[r - 1]; + music_beeper->model->semitone_history[r] = music_beeper->model->semitone_history[r - 1]; + } + + semitone = (semitone == 0xFF) ? 0xFF : semitone % 12; + + music_beeper->model->semitone = semitone; + music_beeper->model->dots = dots; + music_beeper->model->duration = duration; + music_beeper->model->position = position; + + music_beeper->model->semitone_history[0] = semitone; + music_beeper->model->duration_history[0] = duration; + + furi_mutex_release(music_beeper->model_mutex); + view_port_update(music_beeper->view_port); +} + +void music_beeper_clear(MusicBeeper* instance) { + memset(instance->model->duration_history, 0xff, MUSIC_BEEPER_SEMITONE_HISTORY_SIZE); + memset(instance->model->semitone_history, 0xff, MUSIC_BEEPER_SEMITONE_HISTORY_SIZE); + music_beeper_worker_clear(instance->worker); +} + +MusicBeeper* music_beeper_alloc() { + MusicBeeper* instance = malloc(sizeof(MusicBeeper)); + + instance->model = malloc(sizeof(MusicBeeperModel)); + instance->model->volume = 4; + + instance->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal); + + instance->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + + instance->worker = music_beeper_worker_alloc(); + music_beeper_worker_set_volume( + instance->worker, MUSIC_BEEPER_VOLUMES[instance->model->volume]); + music_beeper_worker_set_callback(instance->worker, music_beeper_worker_callback, instance); + + music_beeper_clear(instance); + + instance->view_port = view_port_alloc(); + view_port_draw_callback_set(instance->view_port, render_callback, instance); + view_port_input_callback_set(instance->view_port, input_callback, instance); + + // Open GUI and register view_port + instance->gui = furi_record_open(RECORD_GUI); + gui_add_view_port(instance->gui, instance->view_port, GuiLayerFullscreen); + + return instance; +} + +void music_beeper_free(MusicBeeper* instance) { + gui_remove_view_port(instance->gui, instance->view_port); + furi_record_close(RECORD_GUI); + view_port_free(instance->view_port); + + music_beeper_worker_free(instance->worker); + + furi_message_queue_free(instance->input_queue); + + furi_mutex_free(instance->model_mutex); + + free(instance->model); + free(instance); +} + +int32_t music_beeper_app(void* p) { + MusicBeeper* music_beeper = music_beeper_alloc(); + + FuriString* file_path; + file_path = furi_string_alloc(); + + do { + if(p && strlen(p)) { + furi_string_set(file_path, (const char*)p); + } else { + furi_string_set(file_path, MUSIC_BEEPER_APP_PATH_FOLDER); + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options( + &browser_options, MUSIC_BEEPER_APP_EXTENSION, &I_music_10px); + browser_options.hide_ext = false; + + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + bool res = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options); + + furi_record_close(RECORD_DIALOGS); + if(!res) { + FURI_LOG_E(TAG, "No file selected"); + break; + } + } + + if(!music_beeper_worker_load(music_beeper->worker, furi_string_get_cstr(file_path))) { + FURI_LOG_E(TAG, "Unable to load file"); + break; + } + + music_beeper_worker_start(music_beeper->worker); + + InputEvent input; + while(furi_message_queue_get(music_beeper->input_queue, &input, FuriWaitForever) == + FuriStatusOk) { + furi_check( + furi_mutex_acquire(music_beeper->model_mutex, FuriWaitForever) == FuriStatusOk); + + if(input.key == InputKeyBack) { + furi_mutex_release(music_beeper->model_mutex); + break; + } else if(input.key == InputKeyUp) { + if(music_beeper->model->volume < COUNT_OF(MUSIC_BEEPER_VOLUMES) - 1) + music_beeper->model->volume++; + music_beeper_worker_set_volume( + music_beeper->worker, MUSIC_BEEPER_VOLUMES[music_beeper->model->volume]); + } else if(input.key == InputKeyDown) { + if(music_beeper->model->volume > 0) music_beeper->model->volume--; + music_beeper_worker_set_volume( + music_beeper->worker, MUSIC_BEEPER_VOLUMES[music_beeper->model->volume]); + } + + furi_mutex_release(music_beeper->model_mutex); + view_port_update(music_beeper->view_port); + } + + music_beeper_worker_stop(music_beeper->worker); + if(p && strlen(p)) break; // Exit instead of going to browser if launched with arg + music_beeper_clear(music_beeper); + } while(1); + + furi_string_free(file_path); + music_beeper_free(music_beeper); + + return 0; +} diff --git a/Applications/Official/DEV_FW/source/music_beeper/music_beeper_cli.c b/Applications/Official/DEV_FW/source/music_beeper/music_beeper_cli.c new file mode 100644 index 000000000..26299fa64 --- /dev/null +++ b/Applications/Official/DEV_FW/source/music_beeper/music_beeper_cli.c @@ -0,0 +1,48 @@ +#include +#include +#include +#include "music_beeper_worker.h" + +static void music_beeper_cli(Cli* cli, FuriString* args, void* context) { + UNUSED(context); + MusicBeeperWorker* music_beeper_worker = music_beeper_worker_alloc(); + Storage* storage = furi_record_open(RECORD_STORAGE); + + do { + if(storage_common_stat(storage, furi_string_get_cstr(args), NULL) == FSE_OK) { + if(!music_beeper_worker_load(music_beeper_worker, furi_string_get_cstr(args))) { + printf("Failed to open file %s\r\n", furi_string_get_cstr(args)); + break; + } + } else { + if(!music_beeper_worker_load_rtttl_from_string( + music_beeper_worker, furi_string_get_cstr(args))) { + printf("Argument is not a file or RTTTL\r\n"); + break; + } + } + + printf("Press CTRL+C to stop\r\n"); + music_beeper_worker_set_volume(music_beeper_worker, 1.0f); + music_beeper_worker_start(music_beeper_worker); + while(!cli_cmd_interrupt_received(cli)) { + furi_delay_ms(50); + } + music_beeper_worker_stop(music_beeper_worker); + } while(0); + + furi_record_close(RECORD_STORAGE); + music_beeper_worker_free(music_beeper_worker); +} + +void music_beeper_on_system_start() { +#ifdef SRV_CLI + Cli* cli = furi_record_open(RECORD_CLI); + + cli_add_command(cli, "music_beeper", CliCommandFlagDefault, music_beeper_cli, NULL); + + furi_record_close(RECORD_CLI); +#else + UNUSED(music_beeper_cli); +#endif +} diff --git a/Applications/Official/DEV_FW/source/music_beeper/music_beeper_worker.c b/Applications/Official/DEV_FW/source/music_beeper/music_beeper_worker.c new file mode 100644 index 000000000..4523b806e --- /dev/null +++ b/Applications/Official/DEV_FW/source/music_beeper/music_beeper_worker.c @@ -0,0 +1,507 @@ +#include "music_beeper_worker.h" + +#include +#include + +#include +#include + +#include + +#define TAG "MusicBeeperWorker" + +#define MUSIC_BEEPER_FILETYPE "Flipper Music Format" +#define MUSIC_BEEPER_VERSION 0 + +#define SEMITONE_PAUSE 0xFF + +#define NOTE_C4 261.63f +#define NOTE_C4_SEMITONE (4.0f * 12.0f) +#define TWO_POW_TWELTH_ROOT 1.059463094359f + +typedef struct { + uint8_t semitone; + uint8_t duration; + uint8_t dots; +} NoteBlock; + +ARRAY_DEF(NoteBlockArray, NoteBlock, M_POD_OPLIST); + +struct MusicBeeperWorker { + FuriThread* thread; + bool should_work; + + MusicBeeperWorkerCallback callback; + void* callback_context; + + float volume; + uint32_t bpm; + uint32_t duration; + uint32_t octave; + NoteBlockArray_t notes; +}; + +static int32_t music_beeper_worker_thread_callback(void* context) { + furi_assert(context); + MusicBeeperWorker* instance = context; + + NoteBlockArray_it_t it; + NoteBlockArray_it(it, instance->notes); + if(furi_hal_speaker_acquire(1000)) { + while(instance->should_work) { + if(NoteBlockArray_end_p(it)) { + NoteBlockArray_it(it, instance->notes); + furi_delay_ms(10); + } else { + NoteBlock* note_block = NoteBlockArray_ref(it); + + float note_from_a4 = (float)note_block->semitone - NOTE_C4_SEMITONE; + float frequency = NOTE_C4 * powf(TWO_POW_TWELTH_ROOT, note_from_a4); + float duration = 60.0 * furi_kernel_get_tick_frequency() * 4 / instance->bpm / + note_block->duration; + uint32_t dots = note_block->dots; + while(dots > 0) { + duration += duration / 2; + dots--; + } + uint32_t next_tick = furi_get_tick() + duration; + float volume = instance->volume; + + if(instance->callback) { + instance->callback( + note_block->semitone, + note_block->dots, + note_block->duration, + 0.0, + instance->callback_context); + } + + furi_hal_speaker_stop(); + furi_hal_speaker_start(frequency, volume); + while(instance->should_work && furi_get_tick() < next_tick) { + volume *= 1; + furi_hal_speaker_set_volume(volume); + furi_delay_ms(2); + } + NoteBlockArray_next(it); + } + } + + furi_hal_speaker_stop(); + furi_hal_speaker_release(); + } else { + FURI_LOG_E(TAG, "Speaker system is busy with another process."); + } + + return 0; +} + +MusicBeeperWorker* music_beeper_worker_alloc() { + MusicBeeperWorker* instance = malloc(sizeof(MusicBeeperWorker)); + + NoteBlockArray_init(instance->notes); + + instance->thread = furi_thread_alloc_ex( + "MusicBeeperWorker", 1024, music_beeper_worker_thread_callback, instance); + + instance->volume = 1.0f; + + return instance; +} + +void music_beeper_worker_clear(MusicBeeperWorker* instance) { + NoteBlockArray_reset(instance->notes); +} + +void music_beeper_worker_free(MusicBeeperWorker* instance) { + furi_assert(instance); + furi_thread_free(instance->thread); + NoteBlockArray_clear(instance->notes); + free(instance); +} + +static bool is_digit(const char c) { + return isdigit(c) != 0; +} + +static bool is_letter(const char c) { + return islower(c) != 0 || isupper(c) != 0; +} + +static bool is_space(const char c) { + return c == ' ' || c == '\t'; +} + +static size_t extract_number(const char* string, uint32_t* number) { + size_t ret = 0; + *number = 0; + while(is_digit(*string)) { + *number *= 10; + *number += (*string - '0'); + string++; + ret++; + } + return ret; +} + +static size_t extract_dots(const char* string, uint32_t* number) { + size_t ret = 0; + *number = 0; + while(*string == '.') { + *number += 1; + string++; + ret++; + } + return ret; +} + +static size_t extract_char(const char* string, char* symbol) { + if(is_letter(*string)) { + *symbol = *string; + return 1; + } else { + return 0; + } +} + +static size_t extract_sharp(const char* string, char* symbol) { + if(*string == '#' || *string == '_') { + *symbol = '#'; + return 1; + } else { + return 0; + } +} + +static size_t skip_till(const char* string, const char symbol) { + size_t ret = 0; + while(*string != '\0' && *string != symbol) { + string++; + ret++; + } + if(*string != symbol) { + ret = 0; + } + return ret; +} + +static bool music_beeper_worker_add_note( + MusicBeeperWorker* instance, + uint8_t semitone, + uint8_t duration, + uint8_t dots) { + NoteBlock note_block; + + note_block.semitone = semitone; + note_block.duration = duration; + note_block.dots = dots; + + NoteBlockArray_push_back(instance->notes, note_block); + + return true; +} + +static int8_t note_to_semitone(const char note) { + switch(note) { + case 'C': + return 0; + // C# + case 'D': + return 2; + // D# + case 'E': + return 4; + case 'F': + return 5; + // F# + case 'G': + return 7; + // G# + case 'A': + return 9; + // A# + case 'B': + return 11; + default: + return 0; + } +} + +static bool music_beeper_worker_parse_notes(MusicBeeperWorker* instance, const char* string) { + const char* cursor = string; + bool result = true; + + while(*cursor != '\0') { + if(!is_space(*cursor)) { + uint32_t duration = 0; + char note_char = '\0'; + char sharp_char = '\0'; + uint32_t octave = 0; + uint32_t dots = 0; + + // Parsing + cursor += extract_number(cursor, &duration); + cursor += extract_char(cursor, ¬e_char); + cursor += extract_sharp(cursor, &sharp_char); + cursor += extract_number(cursor, &octave); + cursor += extract_dots(cursor, &dots); + + // Post processing + note_char = toupper(note_char); + if(!duration) { + duration = instance->duration; + } + if(!octave) { + octave = instance->octave; + } + + // Validation + bool is_valid = true; + is_valid &= (duration >= 1 && duration <= 128); + is_valid &= ((note_char >= 'A' && note_char <= 'G') || note_char == 'P'); + is_valid &= (sharp_char == '#' || sharp_char == '\0'); + is_valid &= (octave <= 16); + is_valid &= (dots <= 16); + if(!is_valid) { + FURI_LOG_E( + TAG, + "Invalid note: %lu%c%c%lu.%lu", + duration, + note_char == '\0' ? '_' : note_char, + sharp_char == '\0' ? '_' : sharp_char, + octave, + dots); + result = false; + break; + } + + // Note to semitones + uint8_t semitone = 0; + if(note_char == 'P') { + semitone = SEMITONE_PAUSE; + } else { + semitone += octave * 12; + semitone += note_to_semitone(note_char); + semitone += sharp_char == '#' ? 1 : 0; + } + + if(music_beeper_worker_add_note(instance, semitone, duration, dots)) { + FURI_LOG_D( + TAG, + "Added note: %c%c%lu.%lu = %u %lu", + note_char == '\0' ? '_' : note_char, + sharp_char == '\0' ? '_' : sharp_char, + octave, + dots, + semitone, + duration); + } else { + FURI_LOG_E( + TAG, + "Invalid note: %c%c%lu.%lu = %u %lu", + note_char == '\0' ? '_' : note_char, + sharp_char == '\0' ? '_' : sharp_char, + octave, + dots, + semitone, + duration); + } + cursor += skip_till(cursor, ','); + } + + if(*cursor != '\0') cursor++; + } + + return result; +} + +bool music_beeper_worker_load(MusicBeeperWorker* instance, const char* file_path) { + furi_assert(instance); + furi_assert(file_path); + + bool ret = false; + if(strcasestr(file_path, ".fmf")) { + ret = music_beeper_worker_load_fmf_from_file(instance, file_path); + } else { + ret = music_beeper_worker_load_rtttl_from_file(instance, file_path); + } + return ret; +} + +bool music_beeper_worker_load_fmf_from_file(MusicBeeperWorker* instance, const char* file_path) { + furi_assert(instance); + furi_assert(file_path); + + bool result = false; + FuriString* temp_str; + temp_str = furi_string_alloc(); + + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* file = flipper_format_file_alloc(storage); + + do { + if(!flipper_format_file_open_existing(file, file_path)) break; + + uint32_t version = 0; + if(!flipper_format_read_header(file, temp_str, &version)) break; + if(furi_string_cmp_str(temp_str, MUSIC_BEEPER_FILETYPE) || + (version != MUSIC_BEEPER_VERSION)) { + FURI_LOG_E(TAG, "Incorrect file format or version"); + break; + } + + if(!flipper_format_read_uint32(file, "BPM", &instance->bpm, 1)) { + FURI_LOG_E(TAG, "BPM is missing"); + break; + } + if(!flipper_format_read_uint32(file, "Duration", &instance->duration, 1)) { + FURI_LOG_E(TAG, "Duration is missing"); + break; + } + if(!flipper_format_read_uint32(file, "Octave", &instance->octave, 1)) { + FURI_LOG_E(TAG, "Octave is missing"); + break; + } + + if(!flipper_format_read_string(file, "Notes", temp_str)) { + FURI_LOG_E(TAG, "Notes is missing"); + break; + } + + if(!music_beeper_worker_parse_notes(instance, furi_string_get_cstr(temp_str))) { + break; + } + + result = true; + } while(false); + + furi_record_close(RECORD_STORAGE); + flipper_format_free(file); + furi_string_free(temp_str); + + return result; +} + +bool music_beeper_worker_load_rtttl_from_file(MusicBeeperWorker* instance, const char* file_path) { + furi_assert(instance); + furi_assert(file_path); + + bool result = false; + FuriString* content; + content = furi_string_alloc(); + Storage* storage = furi_record_open(RECORD_STORAGE); + File* file = storage_file_alloc(storage); + + do { + if(!storage_file_open(file, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) { + FURI_LOG_E(TAG, "Unable to open file"); + break; + }; + + uint16_t ret = 0; + do { + uint8_t buffer[65] = {0}; + ret = storage_file_read(file, buffer, sizeof(buffer) - 1); + for(size_t i = 0; i < ret; i++) { + furi_string_push_back(content, buffer[i]); + } + } while(ret > 0); + + furi_string_trim(content); + if(!furi_string_size(content)) { + FURI_LOG_E(TAG, "Empty file"); + break; + } + + if(!music_beeper_worker_load_rtttl_from_string(instance, furi_string_get_cstr(content))) { + FURI_LOG_E(TAG, "Invalid file content"); + break; + } + + result = true; + } while(0); + + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + furi_string_free(content); + + return result; +} + +bool music_beeper_worker_load_rtttl_from_string(MusicBeeperWorker* instance, const char* string) { + furi_assert(instance); + + const char* cursor = string; + + // Skip name + cursor += skip_till(cursor, ':'); + if(*cursor != ':') { + return false; + } + + // Duration + cursor += skip_till(cursor, '='); + if(*cursor != '=') { + return false; + } + cursor++; + cursor += extract_number(cursor, &instance->duration); + + // Octave + cursor += skip_till(cursor, '='); + if(*cursor != '=') { + return false; + } + cursor++; + cursor += extract_number(cursor, &instance->octave); + + // BPM + cursor += skip_till(cursor, '='); + if(*cursor != '=') { + return false; + } + cursor++; + cursor += extract_number(cursor, &instance->bpm); + + // Notes + cursor += skip_till(cursor, ':'); + if(*cursor != ':') { + return false; + } + cursor++; + if(!music_beeper_worker_parse_notes(instance, cursor)) { + return false; + } + + return true; +} + +void music_beeper_worker_set_callback( + MusicBeeperWorker* instance, + MusicBeeperWorkerCallback callback, + void* context) { + furi_assert(instance); + instance->callback = callback; + instance->callback_context = context; +} + +void music_beeper_worker_set_volume(MusicBeeperWorker* instance, float volume) { + furi_assert(instance); + instance->volume = volume; +} + +void music_beeper_worker_start(MusicBeeperWorker* instance) { + furi_assert(instance); + furi_assert(instance->should_work == false); + + instance->should_work = true; + furi_thread_start(instance->thread); +} + +void music_beeper_worker_stop(MusicBeeperWorker* instance) { + furi_assert(instance); + furi_assert(instance->should_work == true); + + instance->should_work = false; + furi_thread_join(instance->thread); +} diff --git a/Applications/Official/DEV_FW/source/music_beeper/music_beeper_worker.h b/Applications/Official/DEV_FW/source/music_beeper/music_beeper_worker.h new file mode 100644 index 000000000..bc30abf81 --- /dev/null +++ b/Applications/Official/DEV_FW/source/music_beeper/music_beeper_worker.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*MusicBeeperWorkerCallback)( + uint8_t semitone, + uint8_t dots, + uint8_t duration, + float position, + void* context); + +typedef struct MusicBeeperWorker MusicBeeperWorker; + +MusicBeeperWorker* music_beeper_worker_alloc(); + +void music_beeper_worker_clear(MusicBeeperWorker* instance); + +void music_beeper_worker_free(MusicBeeperWorker* instance); + +bool music_beeper_worker_load(MusicBeeperWorker* instance, const char* file_path); + +bool music_beeper_worker_load_fmf_from_file(MusicBeeperWorker* instance, const char* file_path); + +bool music_beeper_worker_load_rtttl_from_file(MusicBeeperWorker* instance, const char* file_path); + +bool music_beeper_worker_load_rtttl_from_string(MusicBeeperWorker* instance, const char* string); + +void music_beeper_worker_set_callback( + MusicBeeperWorker* instance, + MusicBeeperWorkerCallback callback, + void* context); + +void music_beeper_worker_set_volume(MusicBeeperWorker* instance, float volume); + +void music_beeper_worker_start(MusicBeeperWorker* instance); + +void music_beeper_worker_stop(MusicBeeperWorker* instance); + +#ifdef __cplusplus +} +#endif diff --git a/Applications/Official/DEV_FW/source/musictracker/.github/workflows/build_dev.yml b/Applications/Official/DEV_FW/source/musictracker/.github/workflows/build_dev.yml new file mode 100644 index 000000000..4d3da2331 --- /dev/null +++ b/Applications/Official/DEV_FW/source/musictracker/.github/workflows/build_dev.yml @@ -0,0 +1,19 @@ +name: Build dev + +on: + push: + branches: + - master + +jobs: + build_dev: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Build + uses: oleksiikutuzov/flipperzero-ufbt-action@v1 + with: + channel: dev \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/musictracker/README.md b/Applications/Official/DEV_FW/source/musictracker/README.md new file mode 100644 index 000000000..584c2ff86 --- /dev/null +++ b/Applications/Official/DEV_FW/source/musictracker/README.md @@ -0,0 +1,4 @@ +# Flipper Zero music tracker +-=-=- MVP Stage: minimum viable player -=-=- + +[>Get latest build<](https://nightly.link/DrZlo13/flipper-zero-music-tracker/workflows/build_dev/master/zero_tracker.fap.zip) diff --git a/Applications/Official/DEV_FW/source/musictracker/application.fam b/Applications/Official/DEV_FW/source/musictracker/application.fam new file mode 100644 index 000000000..51a0e839d --- /dev/null +++ b/Applications/Official/DEV_FW/source/musictracker/application.fam @@ -0,0 +1,14 @@ +App( + appid="Zero_Tracker", + name="Zero Tracker", + apptype=FlipperAppType.EXTERNAL, + entry_point="zero_tracker_app", + requires=[ + "gui", + ], + stack_size=4 * 1024, + order=20, + fap_icon="zero_tracker.png", + fap_category="Music", + fap_icon_assets="icons", +) diff --git a/Applications/Official/DEV_FW/source/musictracker/icons/.gitignore b/Applications/Official/DEV_FW/source/musictracker/icons/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/Applications/Official/DEV_FW/source/musictracker/tracker_engine/speaker_hal.c b/Applications/Official/DEV_FW/source/musictracker/tracker_engine/speaker_hal.c new file mode 100644 index 000000000..14a9c4f85 --- /dev/null +++ b/Applications/Official/DEV_FW/source/musictracker/tracker_engine/speaker_hal.c @@ -0,0 +1,107 @@ +#include "speaker_hal.h" + +#define FURI_HAL_SPEAKER_TIMER TIM16 +#define FURI_HAL_SPEAKER_CHANNEL LL_TIM_CHANNEL_CH1 +#define FURI_HAL_SPEAKER_PRESCALER 500 + +void tracker_speaker_play(float frequency, float pwm) { + uint32_t autoreload = (SystemCoreClock / FURI_HAL_SPEAKER_PRESCALER / frequency) - 1; + if(autoreload < 2) { + autoreload = 2; + } else if(autoreload > UINT16_MAX) { + autoreload = UINT16_MAX; + } + + if(pwm < 0) pwm = 0; + if(pwm > 1) pwm = 1; + + uint32_t compare_value = pwm * autoreload; + + if(compare_value == 0) { + compare_value = 1; + } + + if(LL_TIM_OC_GetCompareCH1(FURI_HAL_SPEAKER_TIMER) != compare_value) { + LL_TIM_OC_SetCompareCH1(FURI_HAL_SPEAKER_TIMER, compare_value); + } + + if(LL_TIM_GetAutoReload(FURI_HAL_SPEAKER_TIMER) != autoreload) { + LL_TIM_SetAutoReload(FURI_HAL_SPEAKER_TIMER, autoreload); + if(LL_TIM_GetCounter(FURI_HAL_SPEAKER_TIMER) > autoreload) { + LL_TIM_SetCounter(FURI_HAL_SPEAKER_TIMER, 0); + } + } + + LL_TIM_EnableAllOutputs(FURI_HAL_SPEAKER_TIMER); +} + +void tracker_speaker_stop() { + LL_TIM_DisableAllOutputs(FURI_HAL_SPEAKER_TIMER); +} + +void tracker_speaker_init() { + if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) { + furi_hal_speaker_start(200.0f, 0.01f); + tracker_speaker_stop(); + } +} + +void tracker_speaker_deinit() { + if(furi_hal_speaker_is_mine()) { + furi_hal_speaker_stop(); + furi_hal_speaker_release(); + } +} + +static FuriHalInterruptISR tracker_isr; +static void* tracker_isr_context; +static void tracker_interrupt_cb(void* context) { + UNUSED(context); + + if(LL_TIM_IsActiveFlag_UPDATE(TIM2)) { + LL_TIM_ClearFlag_UPDATE(TIM2); + + if(tracker_isr) { + tracker_isr(tracker_isr_context); + } + } +} + +void tracker_interrupt_init(float freq, FuriHalInterruptISR isr, void* context) { + tracker_isr = isr; + tracker_isr_context = context; + + furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, tracker_interrupt_cb, NULL); + + LL_TIM_InitTypeDef TIM_InitStruct = {0}; + // Prescaler to get 1kHz clock + TIM_InitStruct.Prescaler = SystemCoreClock / 1000000 - 1; + TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP; + // Auto reload to get freq Hz interrupt + TIM_InitStruct.Autoreload = (1000000 / freq) - 1; + TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1; + LL_TIM_Init(TIM2, &TIM_InitStruct); + LL_TIM_EnableIT_UPDATE(TIM2); + LL_TIM_EnableAllOutputs(TIM2); + LL_TIM_EnableCounter(TIM2); +} + +void tracker_interrupt_deinit() { + FURI_CRITICAL_ENTER(); + LL_TIM_DeInit(TIM2); + FURI_CRITICAL_EXIT(); + + furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, NULL, NULL); +} + +void tracker_debug_init() { + furi_hal_gpio_init(&gpio_ext_pc3, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); +} + +void tracker_debug_set(bool value) { + furi_hal_gpio_write(&gpio_ext_pc3, value); +} + +void tracker_debug_deinit() { + furi_hal_gpio_init(&gpio_ext_pc3, GpioModeAnalog, GpioPullNo, GpioSpeedLow); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/musictracker/tracker_engine/speaker_hal.h b/Applications/Official/DEV_FW/source/musictracker/tracker_engine/speaker_hal.h new file mode 100644 index 000000000..7867fe93f --- /dev/null +++ b/Applications/Official/DEV_FW/source/musictracker/tracker_engine/speaker_hal.h @@ -0,0 +1,19 @@ +#include + +void tracker_speaker_init(); + +void tracker_speaker_deinit(); + +void tracker_speaker_play(float frequency, float pwm); + +void tracker_speaker_stop(); + +void tracker_interrupt_init(float freq, FuriHalInterruptISR isr, void* context); + +void tracker_interrupt_deinit(); + +void tracker_debug_init(); + +void tracker_debug_set(bool value); + +void tracker_debug_deinit(); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/musictracker/tracker_engine/tracker.c b/Applications/Official/DEV_FW/source/musictracker/tracker_engine/tracker.c new file mode 100644 index 000000000..e5efcea17 --- /dev/null +++ b/Applications/Official/DEV_FW/source/musictracker/tracker_engine/tracker.c @@ -0,0 +1,441 @@ +#include "tracker.h" +#include +#include "speaker_hal.h" + +// SongState song_state = { +// .tick = 0, +// .tick_limit = 2, +// .row = 0, +// }; + +typedef struct { + uint8_t speed; + uint8_t depth; + int8_t direction; + int8_t value; +} IntegerOscillator; + +typedef struct { + float frequency; + float frequency_target; + float pwm; + bool play; + IntegerOscillator vibrato; +} ChannelState; + +typedef struct { + ChannelState* channels; + uint8_t tick; + uint8_t tick_limit; + + uint8_t pattern_index; + uint8_t row_index; + uint8_t order_list_index; +} SongState; + +typedef struct { + uint8_t note; + uint8_t effect; + uint8_t data; +} UnpackedRow; + +struct Tracker { + const Song* song; + bool playing; + TrackerMessageCallback callback; + void* context; + SongState song_state; +}; + +static void channels_state_init(ChannelState* channel) { + channel->frequency = 0; + channel->frequency_target = FREQUENCY_UNSET; + channel->pwm = PWM_DEFAULT; + channel->play = false; + channel->vibrato.speed = 0; + channel->vibrato.depth = 0; + channel->vibrato.direction = 0; + channel->vibrato.value = 0; +} + +static void tracker_song_state_init(Tracker* tracker) { + tracker->song_state.tick = 0; + tracker->song_state.tick_limit = 2; + tracker->song_state.row_index = 0; + tracker->song_state.order_list_index = 0; + tracker->song_state.pattern_index = tracker->song->order_list[0]; + + if(tracker->song_state.channels != NULL) { + free(tracker->song_state.channels); + } + + tracker->song_state.channels = malloc(sizeof(ChannelState) * tracker->song->channels_count); + for(uint8_t i = 0; i < tracker->song->channels_count; i++) { + channels_state_init(&tracker->song_state.channels[i]); + } +} + +static void tracker_song_state_clear(Tracker* tracker) { + if(tracker->song_state.channels != NULL) { + free(tracker->song_state.channels); + tracker->song_state.channels = NULL; + } +} + +static uint8_t record_get_note(Row note) { + return note & ROW_NOTE_MASK; +} + +static uint8_t record_get_effect(Row note) { + return (note >> 6) & ROW_EFFECT_MASK; +} + +static uint8_t record_get_effect_data(Row note) { + return (note >> 10) & ROW_EFFECT_DATA_MASK; +} + +#define NOTES_PER_OCT 12 +const float notes_oct[NOTES_PER_OCT] = { + 130.813f, + 138.591f, + 146.832f, + 155.563f, + 164.814f, + 174.614f, + 184.997f, + 195.998f, + 207.652f, + 220.00f, + 233.082f, + 246.942f, +}; + +static float note_to_freq(uint8_t note) { + if(note == NOTE_NONE) return 0.0f; + note = note - NOTE_C2; + uint8_t octave = note / NOTES_PER_OCT; + uint8_t note_in_oct = note % NOTES_PER_OCT; + return notes_oct[note_in_oct] * (1 << octave); +} + +static float frequency_offset_semitones(float frequency, uint8_t semitones) { + return frequency * (1.0f + ((1.0f / 12.0f) * semitones)); +} + +static float frequency_get_seventh_of_a_semitone(float frequency) { + return frequency * ((1.0f / 12.0f) / 7.0f); +} + +static UnpackedRow get_current_row(const Song* song, SongState* song_state, uint8_t channel) { + const Pattern* pattern = &song->patterns[song_state->pattern_index]; + const Row row = pattern->channels[channel].rows[song_state->row_index]; + return (UnpackedRow){ + .note = record_get_note(row), + .effect = record_get_effect(row), + .data = record_get_effect_data(row), + }; +} + +static int16_t advance_order_and_get_next_pattern_index(const Song* song, SongState* song_state) { + song_state->order_list_index++; + if(song_state->order_list_index >= song->order_list_size) { + return -1; + } else { + return song->order_list[song_state->order_list_index]; + } +} + +typedef struct { + int16_t pattern; + int16_t row; + bool change_pattern; + bool change_row; +} Location; + +static void tracker_send_position_message(Tracker* tracker) { + if(tracker->callback != NULL) { + tracker->callback( + (TrackerMessage){ + .type = TrackerPositionChanged, + .data = + { + .position = + { + .order_list_index = tracker->song_state.order_list_index, + .row = tracker->song_state.row_index, + }, + }, + }, + tracker->context); + } +} + +static void tracker_send_end_message(Tracker* tracker) { + if(tracker->callback != NULL) { + tracker->callback((TrackerMessage){.type = TrackerEndOfSong}, tracker->context); + } +} + +static void advance_to_pattern(Tracker* tracker, Location advance) { + if(advance.change_pattern) { + if(advance.pattern < 0 || advance.pattern >= tracker->song->patterns_count) { + tracker->playing = false; + tracker_send_end_message(tracker); + } else { + tracker->song_state.pattern_index = advance.pattern; + tracker->song_state.row_index = 0; + } + } + + if(advance.change_row) { + if(advance.row < 0) advance.row = 0; + if(advance.row >= PATTERN_SIZE) advance.row = PATTERN_SIZE - 1; + tracker->song_state.row_index = advance.row; + } + + tracker_send_position_message(tracker); +} + +static void tracker_interrupt_body(Tracker* tracker) { + if(!tracker->playing) { + tracker_speaker_stop(); + return; + } + + const uint8_t channel_index = 0; + SongState* song_state = &tracker->song_state; + ChannelState* channel_state = &song_state->channels[channel_index]; + const Song* song = tracker->song; + UnpackedRow row = get_current_row(song, song_state, channel_index); + + // load frequency from note at tick 0 + if(song_state->tick == 0) { + bool invalidate_row = false; + // handle "on first tick" effects + if(row.effect == EffectBreakPattern) { + int16_t next_row_index = row.data; + int16_t next_pattern_index = + advance_order_and_get_next_pattern_index(song, song_state); + advance_to_pattern( + tracker, + (Location){ + .pattern = next_pattern_index, + .row = next_row_index, + .change_pattern = true, + .change_row = true, + }); + + invalidate_row = true; + } + + if(row.effect == EffectJumpToOrder) { + song_state->order_list_index = row.data; + int16_t next_pattern_index = song->order_list[song_state->order_list_index]; + + advance_to_pattern( + tracker, + (Location){ + .pattern = next_pattern_index, + .change_pattern = true, + }); + + invalidate_row = true; + } + + // tracker state can be affected by effects + if(!tracker->playing) { + tracker_speaker_stop(); + return; + } + + if(invalidate_row) { + row = get_current_row(song, song_state, channel_index); + + if(row.effect == EffectSetSpeed) { + song_state->tick_limit = row.data; + } + } + + // handle note effects + if(row.note == NOTE_OFF) { + channel_state->play = false; + } else if((row.note > NOTE_NONE) && (row.note < NOTE_OFF)) { + channel_state->play = true; + + // reset vibrato + channel_state->vibrato.speed = 0; + channel_state->vibrato.depth = 0; + channel_state->vibrato.value = 0; + channel_state->vibrato.direction = 0; + + // reset pwm + channel_state->pwm = PWM_DEFAULT; + + if(row.effect == EffectSlideToNote) { + channel_state->frequency_target = note_to_freq(row.note); + } else { + channel_state->frequency = note_to_freq(row.note); + channel_state->frequency_target = FREQUENCY_UNSET; + } + } + } + + if(channel_state->play) { + float frequency, pwm; + + if((row.effect == EffectSlideUp || row.effect == EffectSlideDown) && + row.data != EFFECT_DATA_NONE) { + // apply slide effect + channel_state->frequency += (row.effect == EffectSlideUp ? 1 : -1) * row.data; + } else if(row.effect == EffectSlideToNote) { + // apply slide to note effect, if target frequency is set + if(channel_state->frequency_target > 0) { + if(channel_state->frequency_target > channel_state->frequency) { + channel_state->frequency += row.data; + if(channel_state->frequency > channel_state->frequency_target) { + channel_state->frequency = channel_state->frequency_target; + channel_state->frequency_target = FREQUENCY_UNSET; + } + } else if(channel_state->frequency_target < channel_state->frequency) { + channel_state->frequency -= row.data; + if(channel_state->frequency < channel_state->frequency_target) { + channel_state->frequency = channel_state->frequency_target; + channel_state->frequency_target = FREQUENCY_UNSET; + } + } + } + } + + frequency = channel_state->frequency; + pwm = channel_state->pwm; + + // apply arpeggio effect + if(row.effect == EffectArpeggio) { + if(row.data != EFFECT_DATA_NONE) { + if((song_state->tick % 3) == 1) { + uint8_t note_offset = EFFECT_DATA_GET_X(row.data); + frequency = frequency_offset_semitones(frequency, note_offset); + } else if((song_state->tick % 3) == 2) { + uint8_t note_offset = EFFECT_DATA_GET_Y(row.data); + frequency = frequency_offset_semitones(frequency, note_offset); + } + } + } else if(row.effect == EffectVibrato) { + // apply vibrato effect, data = speed, depth + uint8_t vibrato_speed = EFFECT_DATA_GET_X(row.data); + uint8_t vibrato_depth = EFFECT_DATA_GET_Y(row.data); + + // update vibrato parameters if speed or depth is non-zero + if(vibrato_speed != 0) channel_state->vibrato.speed = vibrato_speed; + if(vibrato_depth != 0) channel_state->vibrato.depth = vibrato_depth; + + // update vibrato value + channel_state->vibrato.value += + channel_state->vibrato.direction * channel_state->vibrato.speed; + + // change direction if value is at the limit + if(channel_state->vibrato.value > channel_state->vibrato.depth) { + channel_state->vibrato.direction = -1; + } else if(channel_state->vibrato.value < -channel_state->vibrato.depth) { + channel_state->vibrato.direction = 1; + } else if(channel_state->vibrato.direction == 0) { + // set initial direction, if it is not set + channel_state->vibrato.direction = 1; + } + + frequency += + (frequency_get_seventh_of_a_semitone(frequency) * channel_state->vibrato.value); + } else if(row.effect == EffectPWM) { + pwm = (pwm - PWM_MIN) / EFFECT_DATA_1_MAX * row.data + PWM_MIN; + } + + tracker_speaker_play(frequency, pwm); + } else { + tracker_speaker_stop(); + } + + song_state->tick++; + if(song_state->tick >= song_state->tick_limit) { + song_state->tick = 0; + + // next note + song_state->row_index = (song_state->row_index + 1); + + if(song_state->row_index >= PATTERN_SIZE) { + int16_t next_pattern_index = + advance_order_and_get_next_pattern_index(song, song_state); + advance_to_pattern( + tracker, + (Location){ + .pattern = next_pattern_index, + .change_pattern = true, + }); + } else { + tracker_send_position_message(tracker); + } + } +} + +static void tracker_interrupt_cb(void* context) { + Tracker* tracker = (Tracker*)context; + tracker_debug_set(true); + tracker_interrupt_body(tracker); + tracker_debug_set(false); +} + +/********************************************************************* + * Tracker Interface + *********************************************************************/ + +Tracker* tracker_alloc() { + Tracker* tracker = malloc(sizeof(Tracker)); + return tracker; +} + +void tracker_free(Tracker* tracker) { + tracker_song_state_clear(tracker); + free(tracker); +} + +void tracker_set_message_callback(Tracker* tracker, TrackerMessageCallback callback, void* context) { + furi_check(tracker->playing == false); + tracker->callback = callback; + tracker->context = context; +} + +void tracker_set_song(Tracker* tracker, const Song* song) { + furi_check(tracker->playing == false); + tracker->song = song; + tracker_song_state_init(tracker); +} + +void tracker_set_order_index(Tracker* tracker, uint8_t order_index) { + furi_check(tracker->playing == false); + furi_check(order_index < tracker->song->order_list_size); + tracker->song_state.order_list_index = order_index; + tracker->song_state.pattern_index = tracker->song->order_list[order_index]; +} + +void tracker_set_row(Tracker* tracker, uint8_t row) { + furi_check(tracker->playing == false); + furi_check(row < PATTERN_SIZE); + tracker->song_state.row_index = row; +} + +void tracker_start(Tracker* tracker) { + furi_check(tracker->song != NULL); + + tracker->playing = true; + tracker_send_position_message(tracker); + tracker_debug_init(); + tracker_speaker_init(); + tracker_interrupt_init(tracker->song->ticks_per_second, tracker_interrupt_cb, tracker); +} + +void tracker_stop(Tracker* tracker) { + tracker_interrupt_deinit(); + tracker_speaker_deinit(); + tracker_debug_deinit(); + + tracker->playing = false; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/musictracker/tracker_engine/tracker.h b/Applications/Official/DEV_FW/source/musictracker/tracker_engine/tracker.h new file mode 100644 index 000000000..70bf4bd6b --- /dev/null +++ b/Applications/Official/DEV_FW/source/musictracker/tracker_engine/tracker.h @@ -0,0 +1,38 @@ +#pragma once +#include "tracker_notes.h" +#include "tracker_song.h" + +typedef enum { + TrackerPositionChanged, + TrackerEndOfSong, +} TrackerMessageType; + +typedef struct { + TrackerMessageType type; + union tracker_message_data { + struct { + uint8_t order_list_index; + uint8_t row; + } position; + } data; +} TrackerMessage; + +typedef void (*TrackerMessageCallback)(TrackerMessage message, void* context); + +typedef struct Tracker Tracker; + +Tracker* tracker_alloc(); + +void tracker_free(Tracker* tracker); + +void tracker_set_message_callback(Tracker* tracker, TrackerMessageCallback callback, void* context); + +void tracker_set_song(Tracker* tracker, const Song* song); + +void tracker_set_order_index(Tracker* tracker, uint8_t order_index); + +void tracker_set_row(Tracker* tracker, uint8_t row); + +void tracker_start(Tracker* tracker); + +void tracker_stop(Tracker* tracker); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/musictracker/tracker_engine/tracker_notes.h b/Applications/Official/DEV_FW/source/musictracker/tracker_engine/tracker_notes.h new file mode 100644 index 000000000..22ab3590f --- /dev/null +++ b/Applications/Official/DEV_FW/source/musictracker/tracker_engine/tracker_notes.h @@ -0,0 +1,64 @@ +#pragma once + +#define NOTE_NONE 0 +#define NOTE_C2 1 +#define NOTE_Cs2 2 +#define NOTE_D2 3 +#define NOTE_Ds2 4 +#define NOTE_E2 5 +#define NOTE_F2 6 +#define NOTE_Fs2 7 +#define NOTE_G2 8 +#define NOTE_Gs2 9 +#define NOTE_A2 10 +#define NOTE_As2 11 +#define NOTE_B2 12 +#define NOTE_C3 13 +#define NOTE_Cs3 14 +#define NOTE_D3 15 +#define NOTE_Ds3 16 +#define NOTE_E3 17 +#define NOTE_F3 18 +#define NOTE_Fs3 19 +#define NOTE_G3 20 +#define NOTE_Gs3 21 +#define NOTE_A3 22 +#define NOTE_As3 23 +#define NOTE_B3 24 +#define NOTE_C4 25 +#define NOTE_Cs4 26 +#define NOTE_D4 27 +#define NOTE_Ds4 28 +#define NOTE_E4 29 +#define NOTE_F4 30 +#define NOTE_Fs4 31 +#define NOTE_G4 32 +#define NOTE_Gs4 33 +#define NOTE_A4 34 +#define NOTE_As4 35 +#define NOTE_B4 36 +#define NOTE_C5 37 +#define NOTE_Cs5 38 +#define NOTE_D5 39 +#define NOTE_Ds5 40 +#define NOTE_E5 41 +#define NOTE_F5 42 +#define NOTE_Fs5 43 +#define NOTE_G5 44 +#define NOTE_Gs5 45 +#define NOTE_A5 46 +#define NOTE_As5 47 +#define NOTE_B5 48 +#define NOTE_C6 49 +#define NOTE_Cs6 50 +#define NOTE_D6 51 +#define NOTE_Ds6 52 +#define NOTE_E6 53 +#define NOTE_F6 54 +#define NOTE_Fs6 55 +#define NOTE_G6 56 +#define NOTE_Gs6 57 +#define NOTE_A6 58 +#define NOTE_As6 59 +#define NOTE_B6 60 +#define NOTE_OFF 63 \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/musictracker/tracker_engine/tracker_song.h b/Applications/Official/DEV_FW/source/musictracker/tracker_engine/tracker_song.h new file mode 100644 index 000000000..7a054f7b1 --- /dev/null +++ b/Applications/Official/DEV_FW/source/musictracker/tracker_engine/tracker_song.h @@ -0,0 +1,109 @@ +#pragma once +#include + +/** + * @brief Row + * + * AH AL + * FEDCBA98 76543210 + * nnnnnnee eedddddd + * -------- -------- + * nnnnnn = [0] do nothing, [1..60] note number, [61] note off, [62..63] not used + * ee ee = [0..F] effect + * 111222 = [0..63] or [0..7, 0..7] effect data + */ +typedef uint16_t Row; + +#define ROW_NOTE_MASK 0x3F +#define ROW_EFFECT_MASK 0x0F +#define ROW_EFFECT_DATA_MASK 0x3F + +typedef enum { + // 0xy, x - first semitones offset, y - second semitones offset. 0 - no offset .. 7 - +7 semitones... + // Play the arpeggio chord with three notes. The first note is the base note, the second and third are offset by x and y. + // Each note plays one tick. + EffectArpeggio = 0x00, + + // 1xx, xx - effect speed, 0 - no effect, 1 - slowest, 0x3F - fastest. + // Slide the note pitch up by xx Hz every tick. + EffectSlideUp = 0x01, + + // 2xx, xx - effect speed, 0 - no effect, 1 - slowest, 0x3F - fastest. + // Slide the note pitch down by xx Hz every tick. + EffectSlideDown = 0x02, + + // 3xx, xx - effect speed, 0 - no effect, 1 - slowest, 0x3F - fastest. + // Slide the already playing note pitch towards another one by xx Hz every tick. + // The note value is saved until the note is playing, so you don't have to repeat the note value to continue sliding. + EffectSlideToNote = 0x03, + + // 4xy, x - vibrato speed (0..7), y - vibrato depth (0..7). + // Vibrato effect. The pitch of the note increases by x Hz each tick to a positive vibrato depth, then decreases to a negative depth. + // Value 1 of depth means 1/7 of a semitone (about 14.28 ct), so value 7 means full semitone. + // Note will play without vibrato on the first tick at the beginning of the effect. + // Vibrato speed and depth are saved until the note is playing, and will be updated only if they are not zero, so you doesn't have to repeat them every tick. + EffectVibrato = 0x04, + + // Effect05 = 0x05, + // Effect06 = 0x06, + // Effect07 = 0x07, + // Effect08 = 0x08, + // Effect09 = 0x09, + // Effect0A = 0x0A, + + // Bxx, xx - pattern number + // Jump to the order xx in the pattern order table at first tick of current row. + // So if you want to jump to the pattern after note 4, you should put this effect on the 5th note. + EffectJumpToOrder = 0x0B, + + // Cxx, xx - pwm value + // Set the PWM value to xx for current row. + EffectPWM = 0x0C, + + // Bxx, xx - row number + // Jump to the row xx in next pattern at first tick of current row. + // So if you want to jump to the pattern after note 4, you should put this effect on the 5th note. + EffectBreakPattern = 0x0D, + + // Effect0E = 0x0E, + + // Fxx, xx - song speed, 0 - 1 tick per note, 1 - 2 ticks per note, 0x3F - 64 ticks per note. + // Set the speed of the song in terms of ticks per note. + // Will be applied at the first tick of current row. + EffectSetSpeed = 0x0F, +} Effect; + +#define EFFECT_DATA_2(x, y) ((x) | ((y) << 3)) +#define EFFECT_DATA_GET_X(data) ((data)&0x07) +#define EFFECT_DATA_GET_Y(data) (((data) >> 3) & 0x07) +#define EFFECT_DATA_NONE 0 +#define EFFECT_DATA_1_MAX 0x3F +#define EFFECT_DATA_2_MAX 0x07 + +#define FREQUENCY_UNSET -1.0f + +#define PWM_MIN 0.01f +#define PWM_MAX 0.5f +#define PWM_DEFAULT PWM_MAX + +#define PATTERN_SIZE 64 + +#define ROW_MAKE(note, effect, data) \ + ((Row)(((note)&0x3F) | (((effect)&0xF) << 6) | (((data)&0x3F) << 10))) + +typedef struct { + Row rows[PATTERN_SIZE]; +} Channel; + +typedef struct { + Channel* channels; +} Pattern; + +typedef struct { + uint8_t channels_count; + uint8_t patterns_count; + Pattern* patterns; + uint8_t order_list_size; + uint8_t* order_list; + uint16_t ticks_per_second; +} Song; \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/musictracker/view/tracker_view.c b/Applications/Official/DEV_FW/source/musictracker/view/tracker_view.c new file mode 100644 index 000000000..87e6b0fcf --- /dev/null +++ b/Applications/Official/DEV_FW/source/musictracker/view/tracker_view.c @@ -0,0 +1,182 @@ +#include "tracker_view.h" +#include +#include + +typedef struct { + const Song* song; + uint8_t order_list_index; + uint8_t row; +} TrackerViewModel; + +struct TrackerView { + View* view; + void* back_context; + TrackerViewCallback back_callback; +}; + +static Channel* get_current_channel(TrackerViewModel* model) { + uint8_t channel_id = 0; + uint8_t pattern_id = model->song->order_list[model->order_list_index]; + Pattern* pattern = &model->song->patterns[pattern_id]; + return &pattern->channels[channel_id]; +} + +static const char* get_note_from_id(uint8_t note) { +#define NOTE_COUNT 12 + const char* notes[NOTE_COUNT] = { + "C ", + "C#", + "D ", + "D#", + "E ", + "F ", + "F#", + "G ", + "G#", + "A ", + "A#", + "B ", + }; + return notes[(note) % NOTE_COUNT]; +#undef NOTE_COUNT +} + +static uint8_t get_octave_from_id(uint8_t note) { + return ((note) / 12) + 2; +} + +static uint8_t get_first_row_id(uint8_t row) { + return (row / 10) * 10; +} + +static void + draw_row(Canvas* canvas, uint8_t i, Channel* channel, uint8_t row, FuriString* buffer) { + uint8_t x = 12 * (i + 1); + uint8_t first_row_id = get_first_row_id(row); + uint8_t current_row_id = first_row_id + i; + + if((current_row_id) >= 64) { + return; + } + + Row current_row = channel->rows[current_row_id]; + uint8_t note = current_row & ROW_NOTE_MASK; + uint8_t effect = (current_row >> 6) & ROW_EFFECT_MASK; + uint8_t data = (current_row >> 10) & ROW_EFFECT_DATA_MASK; + + if(current_row_id == row) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_line(canvas, x - 9, 1, x - 9, 62); + canvas_draw_box(canvas, x - 8, 0, 9, 64); + canvas_draw_line(canvas, x + 1, 1, x + 1, 62); + canvas_set_color(canvas, ColorWhite); + } + + furi_string_printf(buffer, "%02X", current_row_id); + canvas_draw_str(canvas, x, 61, furi_string_get_cstr(buffer)); + + if(note > 0 && note < NOTE_OFF) { + furi_string_printf( + buffer, "%s%d", get_note_from_id(note - 1), get_octave_from_id(note - 1)); + canvas_draw_str(canvas, x, 44, furi_string_get_cstr(buffer)); + } else if(note == NOTE_OFF) { + canvas_draw_str(canvas, x, 44, "OFF"); + } else { + canvas_draw_str(canvas, x, 44, "---"); + } + + if(effect == 0 && data == 0) { + canvas_draw_str(canvas, x, 21, "-"); + canvas_draw_str(canvas, x, 12, "--"); + } else { + furi_string_printf(buffer, "%X", effect); + canvas_draw_str(canvas, x, 21, furi_string_get_cstr(buffer)); + + if(effect == EffectArpeggio || effect == EffectVibrato) { + uint8_t data_x = EFFECT_DATA_GET_X(data); + uint8_t data_y = EFFECT_DATA_GET_Y(data); + furi_string_printf(buffer, "%d%d", data_x, data_y); + canvas_draw_str(canvas, x, 12, furi_string_get_cstr(buffer)); + } else { + furi_string_printf(buffer, "%02X", data); + canvas_draw_str(canvas, x, 12, furi_string_get_cstr(buffer)); + } + } + + if(current_row_id == row) { + canvas_set_color(canvas, ColorBlack); + } +} + +static void tracker_view_draw_callback(Canvas* canvas, void* _model) { + TrackerViewModel* model = _model; + if(model->song == NULL) { + return; + } + + canvas_set_font_direction(canvas, CanvasDirectionBottomToTop); + canvas_set_font(canvas, FontKeyboard); + + Channel* channel = get_current_channel(model); + FuriString* buffer = furi_string_alloc(); + + for(uint8_t i = 0; i < 10; i++) { + draw_row(canvas, i, channel, model->row, buffer); + } + furi_string_free(buffer); +} + +static bool tracker_view_input_callback(InputEvent* event, void* context) { + TrackerView* tracker_view = context; + + if(tracker_view->back_callback) { + if(event->type == InputTypeShort && event->key == InputKeyBack) { + tracker_view->back_callback(tracker_view->back_context); + return true; + } + } + return false; +} + +TrackerView* tracker_view_alloc() { + TrackerView* tracker_view = malloc(sizeof(TrackerView)); + tracker_view->view = view_alloc(); + view_allocate_model(tracker_view->view, ViewModelTypeLocking, sizeof(TrackerViewModel)); + view_set_context(tracker_view->view, tracker_view); + view_set_draw_callback(tracker_view->view, (ViewDrawCallback)tracker_view_draw_callback); + view_set_input_callback(tracker_view->view, (ViewInputCallback)tracker_view_input_callback); + return tracker_view; +} + +void tracker_view_free(TrackerView* tracker_view) { + view_free(tracker_view->view); + free(tracker_view); +} + +View* tracker_view_get_view(TrackerView* tracker_view) { + return tracker_view->view; +} + +void tracker_view_set_back_callback( + TrackerView* tracker_view, + TrackerViewCallback callback, + void* context) { + tracker_view->back_callback = callback; + tracker_view->back_context = context; +} + +void tracker_view_set_song(TrackerView* tracker_view, const Song* song) { + with_view_model( + tracker_view->view, TrackerViewModel * model, { model->song = song; }, true); +} + +void tracker_view_set_position(TrackerView* tracker_view, uint8_t order_list_index, uint8_t row) { + with_view_model( + tracker_view->view, + TrackerViewModel * model, + { + model->order_list_index = order_list_index; + model->row = row; + }, + true); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/musictracker/view/tracker_view.h b/Applications/Official/DEV_FW/source/musictracker/view/tracker_view.h new file mode 100644 index 000000000..6c2e69ba4 --- /dev/null +++ b/Applications/Official/DEV_FW/source/musictracker/view/tracker_view.h @@ -0,0 +1,29 @@ +#include +#include "../tracker_engine/tracker.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct TrackerView TrackerView; + +TrackerView* tracker_view_alloc(); + +void tracker_view_free(TrackerView* tracker_view); + +View* tracker_view_get_view(TrackerView* tracker_view); + +typedef void (*TrackerViewCallback)(void* context); + +void tracker_view_set_back_callback( + TrackerView* tracker_view, + TrackerViewCallback callback, + void* context); + +void tracker_view_set_song(TrackerView* tracker_view, const Song* song); + +void tracker_view_set_position(TrackerView* tracker_view, uint8_t order_list_index, uint8_t row); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/musictracker/zero_tracker.c b/Applications/Official/DEV_FW/source/musictracker/zero_tracker.c new file mode 100644 index 000000000..f4c10d9ef --- /dev/null +++ b/Applications/Official/DEV_FW/source/musictracker/zero_tracker.c @@ -0,0 +1,536 @@ +#include +#include +#include +#include +#include "zero_tracker.h" +#include "tracker_engine/tracker.h" +#include "view/tracker_view.h" + +// Channel p_0_channels[] = { +// { +// .rows = +// { +// // 1/4 +// ROW_MAKE(NOTE_C3, EffectArpeggio, EFFECT_DATA_2(4, 7)), +// ROW_MAKE(0, EffectArpeggio, EFFECT_DATA_2(4, 7)), +// ROW_MAKE(NOTE_C4, EffectSlideToNote, 0x20), +// ROW_MAKE(0, EffectSlideToNote, 0x20), +// // +// ROW_MAKE(0, EffectSlideToNote, 0x20), +// ROW_MAKE(0, EffectSlideToNote, 0x20), +// ROW_MAKE(0, EffectSlideToNote, 0x20), +// ROW_MAKE(0, EffectSlideToNote, 0x20), +// // +// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)), +// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)), +// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)), +// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)), +// // +// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)), +// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)), +// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)), +// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)), +// // 2/4 +// ROW_MAKE(NOTE_C3, EffectSlideDown, 0x20), +// ROW_MAKE(0, EffectSlideDown, 0x20), +// ROW_MAKE(NOTE_C4, EffectVibrato, EFFECT_DATA_2(3, 3)), +// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)), +// // +// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)), +// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)), +// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)), +// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)), +// // +// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)), +// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)), +// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)), +// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)), +// // +// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)), +// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)), +// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)), +// ROW_MAKE(NOTE_OFF, EffectVibrato, EFFECT_DATA_2(3, 3)), +// // 3/4 +// ROW_MAKE(NOTE_C3, EffectArpeggio, EFFECT_DATA_2(4, 7)), +// ROW_MAKE(0, EffectArpeggio, EFFECT_DATA_2(4, 7)), +// ROW_MAKE(NOTE_OFF, 0, 0), +// ROW_MAKE(0, 0, 0), +// // +// ROW_MAKE(0, 0, 0), +// ROW_MAKE(0, 0, 0), +// ROW_MAKE(0, 0, 0), +// ROW_MAKE(0, 0, 0), +// // +// ROW_MAKE(NOTE_C2, EffectPWM, 60), +// ROW_MAKE(0, EffectPWM, 32), +// ROW_MAKE(0, EffectPWM, 12), +// ROW_MAKE(NOTE_OFF, 0, 0), +// // +// ROW_MAKE(0, 0, 0), +// ROW_MAKE(0, 0, 0), +// ROW_MAKE(0, 0, 0), +// ROW_MAKE(0, 0, 0), +// // 4/4 +// ROW_MAKE(NOTE_C3, EffectSlideDown, 0x20), +// ROW_MAKE(0, EffectSlideDown, 0x20), +// ROW_MAKE(0, EffectSlideDown, 0x20), +// ROW_MAKE(NOTE_OFF, 0, 0), +// // +// ROW_MAKE(0, 0, 0), +// ROW_MAKE(0, 0, 0), +// ROW_MAKE(0, 0, 0), +// ROW_MAKE(0, 0, 0), +// // +// ROW_MAKE(NOTE_C2, EffectPWM, 60), +// ROW_MAKE(0, EffectPWM, 32), +// ROW_MAKE(0, EffectPWM, 12), +// ROW_MAKE(NOTE_OFF, 0, 0), +// // +// ROW_MAKE(0, 0, 0), +// ROW_MAKE(0, 0, 0), +// ROW_MAKE(0, 0, 0), +// ROW_MAKE(0, 0, 0), +// }, +// }, +// }; + +Channel p_0_channels[] = { + { + .rows = + { + // + ROW_MAKE(NOTE_A4, EffectArpeggio, EFFECT_DATA_2(4, 7)), + ROW_MAKE(NOTE_C3, 0, 0), + ROW_MAKE(NOTE_F2, 0, 0), + ROW_MAKE(NOTE_C3, 0, 0), + // + ROW_MAKE(NOTE_E4, 0, 0), + ROW_MAKE(NOTE_C3, 0, 0), + ROW_MAKE(NOTE_E4, EffectPWM, 50), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_A4, 0, 0), + ROW_MAKE(0, EffectPWM, 55), + ROW_MAKE(0, EffectPWM, 45), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_E5, 0, 0), + ROW_MAKE(0, EffectPWM, 55), + ROW_MAKE(0, EffectPWM, 45), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_D5, 0, 0), + ROW_MAKE(NOTE_C3, EffectSlideDown, 0x30), + ROW_MAKE(NOTE_F2, 0, 0), + ROW_MAKE(NOTE_C3, 0, 0), + // + ROW_MAKE(NOTE_C5, 0, 0), + ROW_MAKE(NOTE_C3, 0, 0), + ROW_MAKE(NOTE_C5, 0, 0), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_A4, 0, 0), + ROW_MAKE(0, 0, 0), + ROW_MAKE(0, 0, 0), + ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)), + // + ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)), + ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)), + ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_B4, EffectArpeggio, EFFECT_DATA_2(4, 7)), + ROW_MAKE(NOTE_D3, 0, 0), + ROW_MAKE(NOTE_G2, 0, 0), + ROW_MAKE(NOTE_D3, 0, 0), + // + ROW_MAKE(NOTE_E4, 0, 0), + ROW_MAKE(NOTE_D3, 0, 0), + ROW_MAKE(NOTE_E4, EffectPWM, 50), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_A4, 0, 0), + ROW_MAKE(0, EffectPWM, 55), + ROW_MAKE(0, EffectPWM, 45), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_E5, 0, 0), + ROW_MAKE(0, EffectPWM, 55), + ROW_MAKE(0, EffectPWM, 45), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_D5, 0, 0), + ROW_MAKE(NOTE_D3, EffectSlideDown, 0x3F), + ROW_MAKE(NOTE_G2, 0, 0), + ROW_MAKE(NOTE_D3, 0, 0), + // + ROW_MAKE(NOTE_C5, 0, 0), + ROW_MAKE(NOTE_D3, 0, 0), + ROW_MAKE(NOTE_C5, 0, 0), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_A4, 0, 0), + ROW_MAKE(0, 0, 0), + ROW_MAKE(0, 0, 0), + ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)), + // + ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)), + ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)), + ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)), + ROW_MAKE(NOTE_OFF, 0, 0), + }, + }, +}; + +Channel p_1_channels[] = { + { + .rows = + { + // + ROW_MAKE(NOTE_C5, EffectArpeggio, EFFECT_DATA_2(4, 7)), + ROW_MAKE(NOTE_E3, 0, 0), + ROW_MAKE(NOTE_A2, 0, 0), + ROW_MAKE(NOTE_E3, 0, 0), + // + ROW_MAKE(NOTE_B4, 0, 0), + ROW_MAKE(NOTE_E3, 0, 0), + ROW_MAKE(NOTE_B4, EffectPWM, 50), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_G4, 0, 0), + ROW_MAKE(0, EffectPWM, 55), + ROW_MAKE(0, EffectPWM, 45), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_C5, 0, 0), + ROW_MAKE(0, EffectPWM, 55), + ROW_MAKE(0, EffectPWM, 45), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_C6, 0, 0), + ROW_MAKE(NOTE_E3, EffectSlideDown, 0x30), + ROW_MAKE(NOTE_A2, 0, 0), + ROW_MAKE(NOTE_E3, 0, 0), + // + ROW_MAKE(NOTE_B4, 0, 0), + ROW_MAKE(NOTE_E3, 0, 0), + ROW_MAKE(NOTE_B4, EffectPWM, 50), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_G4, 0, 0), + ROW_MAKE(0, 0, 0), + ROW_MAKE(0, 0, 0), + ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)), + // + ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)), + ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)), + ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_C5, EffectArpeggio, EFFECT_DATA_2(4, 7)), + ROW_MAKE(NOTE_E3, 0, 0), + ROW_MAKE(NOTE_A2, 0, 0), + ROW_MAKE(NOTE_E3, 0, 0), + // + ROW_MAKE(NOTE_B4, 0, 0), + ROW_MAKE(NOTE_E3, 0, 0), + ROW_MAKE(NOTE_B4, EffectPWM, 50), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_G4, 0, 0), + ROW_MAKE(0, EffectPWM, 55), + ROW_MAKE(0, EffectPWM, 45), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_D5, 0, 0), + ROW_MAKE(0, EffectPWM, 55), + ROW_MAKE(0, EffectPWM, 45), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_C6, 0, 0), + ROW_MAKE(NOTE_E3, EffectSlideDown, 0x30), + ROW_MAKE(NOTE_A2, 0, 0), + ROW_MAKE(NOTE_E3, 0, 0), + // + ROW_MAKE(NOTE_B4, 0, 0), + ROW_MAKE(NOTE_E3, 0, 0), + ROW_MAKE(NOTE_B4, EffectPWM, 50), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_G4, 0, 0), + ROW_MAKE(0, 0, 0), + ROW_MAKE(0, 0, 0), + ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)), + // + ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)), + ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)), + ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)), + ROW_MAKE(NOTE_OFF, 0, 0), + }, + }, +}; + +Channel p_2_channels[] = { + { + .rows = + { + // + ROW_MAKE(NOTE_C5, EffectArpeggio, EFFECT_DATA_2(4, 7)), + ROW_MAKE(NOTE_E3, 0, 0), + ROW_MAKE(NOTE_A2, 0, 0), + ROW_MAKE(NOTE_E3, 0, 0), + // + ROW_MAKE(NOTE_C5, 0, 0), + ROW_MAKE(NOTE_A4, 0, 0), + ROW_MAKE(NOTE_C5, 0, 0), + ROW_MAKE(NOTE_A4, 0, 0), + // + ROW_MAKE(NOTE_C5, EffectPWM, 55), + ROW_MAKE(NOTE_A4, EffectPWM, 45), + ROW_MAKE(NOTE_C5, EffectPWM, 35), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_C5, 0, 0), + ROW_MAKE(NOTE_A4, 0, 0), + ROW_MAKE(NOTE_C5, EffectPWM, 55), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_D5, 0, 0), + ROW_MAKE(NOTE_E3, EffectSlideDown, 0x30), + ROW_MAKE(NOTE_A2, 0, 0), + ROW_MAKE(NOTE_E3, 0, 0), + // + ROW_MAKE(NOTE_OFF, 0, 0), + ROW_MAKE(NOTE_E3, 0, 0), + ROW_MAKE(NOTE_B4, EffectPWM, 55), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_D5, 0, 0), + ROW_MAKE(NOTE_B4, 0, 0), + ROW_MAKE(NOTE_D5, EffectPWM, 55), + ROW_MAKE(NOTE_B4, EffectPWM, 55), + // + ROW_MAKE(NOTE_D5, EffectPWM, 45), + ROW_MAKE(NOTE_B4, EffectPWM, 45), + ROW_MAKE(NOTE_D5, EffectPWM, 35), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_D5, EffectArpeggio, EFFECT_DATA_2(4, 7)), + ROW_MAKE(NOTE_E3, 0, 0), + ROW_MAKE(NOTE_A2, 0, 0), + ROW_MAKE(NOTE_E3, 0, 0), + // + ROW_MAKE(NOTE_E5, 0, 0), + ROW_MAKE(NOTE_C5, 0, 0), + ROW_MAKE(NOTE_E5, 0, 0), + ROW_MAKE(NOTE_C5, 0, 0), + // + ROW_MAKE(NOTE_E5, EffectPWM, 55), + ROW_MAKE(NOTE_C5, EffectPWM, 45), + ROW_MAKE(NOTE_E5, EffectPWM, 35), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_E5, 0, 0), + ROW_MAKE(NOTE_C5, 0, 0), + ROW_MAKE(NOTE_E5, EffectPWM, 55), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_D5, 0, 0), + ROW_MAKE(NOTE_E3, EffectSlideDown, 0x30), + ROW_MAKE(NOTE_A2, 0, 0), + ROW_MAKE(NOTE_E3, 0, 0), + // + ROW_MAKE(NOTE_OFF, 0, 0), + ROW_MAKE(NOTE_E3, 0, 0), + ROW_MAKE(NOTE_B4, EffectPWM, 55), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_D5, 0, 0), + ROW_MAKE(NOTE_B4, 0, 0), + ROW_MAKE(NOTE_D5, EffectPWM, 55), + ROW_MAKE(NOTE_B4, EffectPWM, 55), + // + ROW_MAKE(NOTE_D5, EffectPWM, 45), + ROW_MAKE(NOTE_B4, EffectPWM, 45), + ROW_MAKE(NOTE_D5, EffectPWM, 35), + ROW_MAKE(NOTE_OFF, 0, 0), + }, + }, +}; + +Channel p_3_channels[] = { + { + .rows = + { + // + ROW_MAKE(NOTE_Ds5, EffectArpeggio, EFFECT_DATA_2(4, 6)), + ROW_MAKE(NOTE_C5, 0, 0), + ROW_MAKE(NOTE_Ds5, 0, 0), + ROW_MAKE(NOTE_C5, EffectPWM, 55), + // + ROW_MAKE(NOTE_Ds5, EffectPWM, 45), + ROW_MAKE(NOTE_C5, EffectPWM, 35), + ROW_MAKE(NOTE_Ds5, EffectPWM, 30), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_D5, 0, 0), + ROW_MAKE(NOTE_B4, 0, 0), + ROW_MAKE(NOTE_D5, 0, 0), + ROW_MAKE(NOTE_B4, EffectPWM, 55), + // + ROW_MAKE(NOTE_D5, EffectPWM, 45), + ROW_MAKE(NOTE_B4, EffectPWM, 35), + ROW_MAKE(NOTE_D5, EffectPWM, 30), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_Cs5, EffectArpeggio, EFFECT_DATA_2(4, 6)), + ROW_MAKE(NOTE_As4, 0, 0), + ROW_MAKE(NOTE_Cs5, 0, 0), + ROW_MAKE(NOTE_As4, EffectPWM, 55), + // + ROW_MAKE(NOTE_Cs5, EffectPWM, 45), + ROW_MAKE(NOTE_As4, EffectPWM, 35), + ROW_MAKE(NOTE_Cs5, EffectPWM, 30), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_C5, 0, 0), + ROW_MAKE(NOTE_A4, 0, 0), + ROW_MAKE(NOTE_C5, 0, 0), + ROW_MAKE(NOTE_A4, EffectPWM, 55), + // + ROW_MAKE(NOTE_C5, EffectPWM, 45), + ROW_MAKE(NOTE_A4, EffectPWM, 35), + ROW_MAKE(NOTE_C5, EffectPWM, 30), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_B4, EffectArpeggio, EFFECT_DATA_2(4, 6)), + ROW_MAKE(NOTE_Gs4, 0, 0), + ROW_MAKE(NOTE_B4, 0, 0), + ROW_MAKE(NOTE_Gs4, EffectPWM, 55), + // + ROW_MAKE(NOTE_B4, EffectPWM, 45), + ROW_MAKE(NOTE_Gs4, EffectPWM, 35), + ROW_MAKE(NOTE_B4, EffectPWM, 30), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_C5, 0, 0), + ROW_MAKE(NOTE_A4, 0, 0), + ROW_MAKE(NOTE_C5, 0, 0), + ROW_MAKE(NOTE_A4, EffectPWM, 55), + // + ROW_MAKE(NOTE_C5, EffectPWM, 45), + ROW_MAKE(NOTE_A4, EffectPWM, 35), + ROW_MAKE(NOTE_C5, EffectPWM, 30), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_Cs5, EffectArpeggio, EFFECT_DATA_2(4, 6)), + ROW_MAKE(NOTE_As4, 0, 0), + ROW_MAKE(NOTE_Cs5, 0, 0), + ROW_MAKE(NOTE_As4, EffectPWM, 55), + // + ROW_MAKE(NOTE_Cs5, EffectPWM, 45), + ROW_MAKE(NOTE_As4, EffectPWM, 35), + ROW_MAKE(NOTE_Cs5, EffectPWM, 30), + ROW_MAKE(NOTE_OFF, 0, 0), + // + ROW_MAKE(NOTE_D5, 0, 0), + ROW_MAKE(NOTE_B4, 0, 0), + ROW_MAKE(NOTE_D5, 0, 0), + ROW_MAKE(NOTE_B4, EffectPWM, 55), + // + ROW_MAKE(NOTE_D5, EffectPWM, 45), + ROW_MAKE(NOTE_B4, EffectPWM, 35), + ROW_MAKE(NOTE_D5, EffectPWM, 30), + ROW_MAKE(NOTE_OFF, 0, 0), + }, + }, +}; +Pattern patterns[] = { + {.channels = p_0_channels}, + {.channels = p_1_channels}, + {.channels = p_2_channels}, + {.channels = p_3_channels}, +}; + +uint8_t order_list[] = { + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, +}; + +Song song = { + .channels_count = 1, + .patterns_count = sizeof(patterns) / sizeof(patterns[0]), + .patterns = patterns, + + .order_list_size = sizeof(order_list) / sizeof(order_list[0]), + .order_list = order_list, + + .ticks_per_second = 60, +}; + +void tracker_message(TrackerMessage message, void* context) { + FuriMessageQueue* queue = context; + furi_assert(queue); + furi_message_queue_put(queue, &message, 0); +} + +int32_t zero_tracker_app(void* p) { + UNUSED(p); + + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + notification_message(notification, &sequence_display_backlight_enforce_on); + + Gui* gui = furi_record_open(RECORD_GUI); + ViewDispatcher* view_dispatcher = view_dispatcher_alloc(); + TrackerView* tracker_view = tracker_view_alloc(); + tracker_view_set_song(tracker_view, &song); + view_dispatcher_add_view(view_dispatcher, 0, tracker_view_get_view(tracker_view)); + view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen); + view_dispatcher_switch_to_view(view_dispatcher, 0); + + FuriMessageQueue* queue = furi_message_queue_alloc(8, sizeof(TrackerMessage)); + Tracker* tracker = tracker_alloc(); + tracker_set_message_callback(tracker, tracker_message, queue); + tracker_set_song(tracker, &song); + tracker_start(tracker); + + while(1) { + TrackerMessage message; + FuriStatus status = furi_message_queue_get(queue, &message, portMAX_DELAY); + if(status == FuriStatusOk) { + if(message.type == TrackerPositionChanged) { + uint8_t order_list_index = message.data.position.order_list_index; + uint8_t row = message.data.position.row; + uint8_t pattern = song.order_list[order_list_index]; + tracker_view_set_position(tracker_view, order_list_index, row); + FURI_LOG_I("Tracker", "O:%d P:%d R:%d", order_list_index, pattern, row); + } else if(message.type == TrackerEndOfSong) { + FURI_LOG_I("Tracker", "End of song"); + break; + } + } + } + + tracker_stop(tracker); + tracker_free(tracker); + furi_message_queue_free(queue); + + furi_delay_ms(500); + + view_dispatcher_remove_view(view_dispatcher, 0); + tracker_view_free(tracker_view); + view_dispatcher_free(view_dispatcher); + + notification_message(notification, &sequence_display_backlight_enforce_auto); + + furi_record_close(RECORD_NOTIFICATION); + furi_record_close(RECORD_GUI); + + return 0; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/musictracker/zero_tracker.h b/Applications/Official/DEV_FW/source/musictracker/zero_tracker.h new file mode 100644 index 000000000..e69de29bb diff --git a/Applications/Official/DEV_FW/source/musictracker/zero_tracker.png b/Applications/Official/DEV_FW/source/musictracker/zero_tracker.png new file mode 100644 index 000000000..61488d153 Binary files /dev/null and b/Applications/Official/DEV_FW/source/musictracker/zero_tracker.png differ diff --git a/Applications/Official/DEV_FW/source/namechanger/application.fam b/Applications/Official/DEV_FW/source/namechanger/application.fam new file mode 100644 index 000000000..545f0b905 --- /dev/null +++ b/Applications/Official/DEV_FW/source/namechanger/application.fam @@ -0,0 +1,13 @@ +App( + appid="NameChanger", + name="Name Changer", + apptype=FlipperAppType.EXTERNAL, + entry_point="namechanger_app", + cdefines=["APP_NAMECHANGER"], + requires=["gui","storage"], + stack_size=2 * 1024, + order=90, + fap_icon="namechanger_10px.png", + fap_category="Tools", + fap_icon_assets="icons", +) \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/namechanger/icons/Cry_dolph_55x52.png b/Applications/Official/DEV_FW/source/namechanger/icons/Cry_dolph_55x52.png new file mode 100644 index 000000000..86d9db1b4 Binary files /dev/null and b/Applications/Official/DEV_FW/source/namechanger/icons/Cry_dolph_55x52.png differ diff --git a/Applications/Official/DEV_FW/source/namechanger/icons/DolphinMafia_115x62.png b/Applications/Official/DEV_FW/source/namechanger/icons/DolphinMafia_115x62.png new file mode 100644 index 000000000..66fdb40ff Binary files /dev/null and b/Applications/Official/DEV_FW/source/namechanger/icons/DolphinMafia_115x62.png differ diff --git a/Applications/Official/DEV_FW/source/namechanger/icons/DolphinNice_96x59.png b/Applications/Official/DEV_FW/source/namechanger/icons/DolphinNice_96x59.png new file mode 100644 index 000000000..a299d3630 Binary files /dev/null and b/Applications/Official/DEV_FW/source/namechanger/icons/DolphinNice_96x59.png differ diff --git a/Applications/Official/DEV_FW/source/namechanger/icons/MarioBlock.png b/Applications/Official/DEV_FW/source/namechanger/icons/MarioBlock.png new file mode 100644 index 000000000..86f159966 Binary files /dev/null and b/Applications/Official/DEV_FW/source/namechanger/icons/MarioBlock.png differ diff --git a/Applications/Official/DEV_FW/source/namechanger/icons/namechanger_10px.png b/Applications/Official/DEV_FW/source/namechanger/icons/namechanger_10px.png new file mode 100644 index 000000000..60facf25e Binary files /dev/null and b/Applications/Official/DEV_FW/source/namechanger/icons/namechanger_10px.png differ diff --git a/Applications/Official/DEV_FW/source/namechanger/namechanger.c b/Applications/Official/DEV_FW/source/namechanger/namechanger.c new file mode 100644 index 000000000..284fd2eb5 --- /dev/null +++ b/Applications/Official/DEV_FW/source/namechanger/namechanger.c @@ -0,0 +1,275 @@ +#include "namechanger.h" +#include "scenes/namechanger_scene.h" + +#include +#include + +bool namechanger_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + NameChanger* namechanger = context; + return scene_manager_handle_custom_event(namechanger->scene_manager, event); +} + +bool namechanger_back_event_callback(void* context) { + furi_assert(context); + NameChanger* namechanger = context; + return scene_manager_handle_back_event(namechanger->scene_manager); +} + +bool namechanger_make_app_folder(NameChanger* namechanger) { + bool created = false; + FURI_LOG_I(TAG, "folder1"); + + FuriString* folderpath = furi_string_alloc(); + furi_string_set(folderpath, "/ext/dolphin"); + FURI_LOG_I(TAG, "folder2"); + + //Make dir if doesn't exist + if(!storage_simply_mkdir(namechanger->storage, furi_string_get_cstr(folderpath))) { + FURI_LOG_I(TAG, "folder3"); + furi_string_set_str(namechanger->error, "Cannot create\napp folder."); + } else { + FURI_LOG_I(TAG, "folder4"); + created = true; + } + FURI_LOG_I(TAG, "folder5"); + furi_string_free(folderpath); + FURI_LOG_I(TAG, "folder6"); + return created; +} + +bool namechanger_name_read_write(NameChanger* namechanger, char* name, uint8_t mode) { + FuriString* file_path = furi_string_alloc(); + furi_string_set(file_path, "/ext/dolphin/name.txt"); + FURI_LOG_I(TAG, "name1"); + + bool result = false; + + if(mode == 2) { + FURI_LOG_I(TAG, "name2"); + UNUSED(name); + FlipperFormat* file = flipper_format_file_alloc(namechanger->storage); + //read + FuriString* data = furi_string_alloc(); + FURI_LOG_I(TAG, "name3"); + + do { + FURI_LOG_I(TAG, "name4"); + if(!flipper_format_file_open_existing(file, furi_string_get_cstr(file_path))) { + break; + } + FURI_LOG_I(TAG, "name4a"); + + // header + uint32_t version; + + if(!flipper_format_read_header(file, data, &version)) { + break; + } + FURI_LOG_I(TAG, "name4b"); + + if(furi_string_cmp_str(data, NAMECHANGER_HEADER) != 0) { + break; + } + FURI_LOG_I(TAG, "name4c"); + + if(version != 1) { + break; + } + FURI_LOG_I(TAG, "name4d"); + + // get Name + if(!flipper_format_read_string(file, "Name", data)) { + break; + } + FURI_LOG_I(TAG, "name4e"); + + result = true; + FURI_LOG_I(TAG, "name5"); + } while(false); + + flipper_format_free(file); + FURI_LOG_I(TAG, "name6"); + + if(!result) { + FURI_LOG_I(TAG, "name7"); + FURI_LOG_E(TAG, "Cannot load data from file."); + namechanger_text_store_set(namechanger, "%s", furi_hal_version_get_name_ptr()); + } else { + FURI_LOG_I(TAG, "name8"); + furi_string_trim(data); + + if(!furi_string_size(data)) { + FURI_LOG_I(TAG, "name9"); + namechanger_text_store_set(namechanger, "%s", furi_hal_version_get_name_ptr()); + } else { + FURI_LOG_I(TAG, "name10"); + char newname[8]; + snprintf(newname, 8, "%s", furi_string_get_cstr(data)); + namechanger_text_store_set(namechanger, "%s", newname); + } + } + FURI_LOG_I(TAG, "name11"); + + furi_string_free(data); + } else if(mode == 3) { + FURI_LOG_I(TAG, "name12"); + //save + FlipperFormat* file = flipper_format_file_alloc(namechanger->storage); + + do { + FURI_LOG_I(TAG, "name13"); + // Open file for write + if(!flipper_format_file_open_always(file, furi_string_get_cstr(file_path))) { + break; + } + + // Write header + if(!flipper_format_write_header_cstr(file, NAMECHANGER_HEADER, 1)) { + break; + } + + // Write comments + if(!flipper_format_write_comment_cstr( + file, "Changing the value below will change your FlipperZero device name.")) { + break; + } + + if(!flipper_format_write_comment_cstr( + file, + "Note: This is limited to 8 characters using the following: a-z, A-Z, 0-9, and _")) { + break; + } + + if(!flipper_format_write_comment_cstr( + file, "It cannot contain any other characters.")) { + break; + } + + //If name is eraseerase (set by Revert) - then don't write any name + //otherwise, write name as set in the variable + if(strcmp(name, "eraseerase") == 0) { + if(!flipper_format_write_string_cstr(file, "Name", "")) { + break; + } + } else { + if(!flipper_format_write_string_cstr(file, "Name", name)) { + break; + } + } + + FURI_LOG_I(TAG, "name14"); + result = true; + } while(false); + + flipper_format_free(file); + FURI_LOG_I(TAG, "name15"); + + if(!result) { + FURI_LOG_I(TAG, "name16"); + FURI_LOG_E(TAG, "Cannot save name file."); + furi_string_set_str(namechanger->error, "Cannot save\nname file."); + } + } else { + FURI_LOG_I(TAG, "name17"); + FURI_LOG_E(TAG, "Something broke."); + furi_string_set_str(namechanger->error, "Something broke."); + } + FURI_LOG_I(TAG, "name18"); + + return result; +} + +NameChanger* namechanger_alloc() { + NameChanger* namechanger = malloc(sizeof(namechanger)); + + namechanger->scene_manager = scene_manager_alloc(&namechanger_scene_handlers, namechanger); + + namechanger->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(namechanger->view_dispatcher); + view_dispatcher_set_event_callback_context(namechanger->view_dispatcher, namechanger); + view_dispatcher_set_custom_event_callback( + namechanger->view_dispatcher, namechanger_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + namechanger->view_dispatcher, namechanger_back_event_callback); + + namechanger->gui = furi_record_open(RECORD_GUI); + namechanger->storage = furi_record_open(RECORD_STORAGE); + + namechanger->submenu = submenu_alloc(); + view_dispatcher_add_view( + namechanger->view_dispatcher, + NameChangerViewSubmenu, + submenu_get_view(namechanger->submenu)); + + namechanger->text_input = text_input_alloc(); + view_dispatcher_add_view( + namechanger->view_dispatcher, + NameChangerViewTextInput, + text_input_get_view(namechanger->text_input)); + + namechanger->popup = popup_alloc(); + view_dispatcher_add_view( + namechanger->view_dispatcher, NameChangerViewPopup, popup_get_view(namechanger->popup)); + + namechanger->widget = widget_alloc(); + view_dispatcher_add_view( + namechanger->view_dispatcher, NameChangerViewWidget, widget_get_view(namechanger->widget)); + + return namechanger; +} + +void namechanger_free(NameChanger* namechanger) { + furi_assert(namechanger); + + view_dispatcher_remove_view(namechanger->view_dispatcher, NameChangerViewWidget); + widget_free(namechanger->widget); + view_dispatcher_remove_view(namechanger->view_dispatcher, NameChangerViewPopup); + popup_free(namechanger->popup); + + view_dispatcher_remove_view(namechanger->view_dispatcher, NameChangerViewTextInput); + text_input_free(namechanger->text_input); + + view_dispatcher_remove_view(namechanger->view_dispatcher, NameChangerViewSubmenu); + submenu_free(namechanger->submenu); + + view_dispatcher_free(namechanger->view_dispatcher); + scene_manager_free(namechanger->scene_manager); + + furi_string_free(namechanger->error); + + furi_record_close(RECORD_STORAGE); + + furi_record_close(RECORD_GUI); + + free(namechanger); +} + +void namechanger_text_store_set(NameChanger* namechanger, const char* text, ...) { + va_list args; + va_start(args, text); + + vsnprintf(namechanger->text_store, NAMECHANGER_TEXT_STORE_SIZE, text, args); + + va_end(args); +} + +void namechanger_text_store_clear(NameChanger* namechanger) { + memset(namechanger->text_store, 0, NAMECHANGER_TEXT_STORE_SIZE); +} + +int32_t namechanger_app() { + NameChanger* namechanger = namechanger_alloc(); + + namechanger->error = furi_string_alloc(); + furi_string_set(namechanger->error, "Default"); + + view_dispatcher_attach_to_gui( + namechanger->view_dispatcher, namechanger->gui, ViewDispatcherTypeFullscreen); + scene_manager_next_scene(namechanger->scene_manager, NameChangerSceneStart); + + view_dispatcher_run(namechanger->view_dispatcher); + + namechanger_free(namechanger); + return 0; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/namechanger/namechanger.h b/Applications/Official/DEV_FW/source/namechanger/namechanger.h new file mode 100644 index 000000000..9500c1c17 --- /dev/null +++ b/Applications/Official/DEV_FW/source/namechanger/namechanger.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include "namechanger_custom_event.h" +#include "scenes/namechanger_scene.h" + +#define NAMECHANGER_TEXT_STORE_SIZE 9 +#define NAMECHANGER_HEADER "Flipper Name File" + +#define TAG "NameChanger" + +typedef struct { + SceneManager* scene_manager; + ViewDispatcher* view_dispatcher; + + Gui* gui; + Storage* storage; + + char text_store[NAMECHANGER_TEXT_STORE_SIZE + 1]; + FuriString* error; + + Submenu* submenu; + TextInput* text_input; + Popup* popup; + Widget* widget; +} NameChanger; + +typedef enum { + NameChangerViewSubmenu, + NameChangerViewTextInput, + NameChangerViewPopup, + NameChangerViewWidget, +} NameChangerView; + +bool namechanger_make_app_folder(NameChanger* namechanger); +bool namechanger_name_read_write(NameChanger* namechanger, char* name, uint8_t mode); +void namechanger_text_store_set(NameChanger* namechanger, const char* text, ...); +void namechanger_text_store_clear(NameChanger* namechanger); diff --git a/Applications/Official/DEV_FW/source/namechanger/namechanger_10px.png b/Applications/Official/DEV_FW/source/namechanger/namechanger_10px.png new file mode 100644 index 000000000..60facf25e Binary files /dev/null and b/Applications/Official/DEV_FW/source/namechanger/namechanger_10px.png differ diff --git a/Applications/Official/DEV_FW/source/namechanger/namechanger_custom_event.h b/Applications/Official/DEV_FW/source/namechanger/namechanger_custom_event.h new file mode 100644 index 000000000..61418642f --- /dev/null +++ b/Applications/Official/DEV_FW/source/namechanger/namechanger_custom_event.h @@ -0,0 +1,7 @@ +#pragma once + +enum NameChangerCustomEvent { + NameChangerCustomEventBack, + NameChangerCustomEventTextEditResult, + NameChangerCustomEventError, +}; diff --git a/Applications/Official/DEV_FW/source/namechanger/scenes/namechanger_scene.c b/Applications/Official/DEV_FW/source/namechanger/scenes/namechanger_scene.c new file mode 100644 index 000000000..82f96e466 --- /dev/null +++ b/Applications/Official/DEV_FW/source/namechanger/scenes/namechanger_scene.c @@ -0,0 +1,30 @@ +#include "namechanger_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const namechanger_on_enter_handlers[])(void*) = { +#include "namechanger_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 namechanger_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "namechanger_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 namechanger_on_exit_handlers[])(void* context) = { +#include "namechanger_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers namechanger_scene_handlers = { + .on_enter_handlers = namechanger_on_enter_handlers, + .on_event_handlers = namechanger_on_event_handlers, + .on_exit_handlers = namechanger_on_exit_handlers, + .scene_num = NameChangerSceneNum, +}; diff --git a/Applications/Official/DEV_FW/source/namechanger/scenes/namechanger_scene.h b/Applications/Official/DEV_FW/source/namechanger/scenes/namechanger_scene.h new file mode 100644 index 000000000..42071ab98 --- /dev/null +++ b/Applications/Official/DEV_FW/source/namechanger/scenes/namechanger_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) NameChangerScene##id, +typedef enum { +#include "namechanger_scene_config.h" + NameChangerSceneNum, +} NameChangerScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers namechanger_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "namechanger_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 "namechanger_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 "namechanger_scene_config.h" +#undef ADD_SCENE diff --git a/Applications/Official/DEV_FW/source/namechanger/scenes/namechanger_scene_change.c b/Applications/Official/DEV_FW/source/namechanger/scenes/namechanger_scene_change.c new file mode 100644 index 000000000..08150a1bc --- /dev/null +++ b/Applications/Official/DEV_FW/source/namechanger/scenes/namechanger_scene_change.c @@ -0,0 +1,64 @@ +#include "../namechanger.h" + +static void namechanger_scene_change_text_input_callback(void* context) { + NameChanger* namechanger = context; + + view_dispatcher_send_custom_event( + namechanger->view_dispatcher, NameChangerCustomEventTextEditResult); +} + +void namechanger_scene_change_on_enter(void* context) { + NameChanger* namechanger = context; + TextInput* text_input = namechanger->text_input; + + if(namechanger_name_read_write(namechanger, NULL, 2)) { + text_input_set_header_text(text_input, "Set Flipper Name"); + + text_input_set_result_callback( + text_input, + namechanger_scene_change_text_input_callback, + namechanger, + namechanger->text_store, + NAMECHANGER_TEXT_STORE_SIZE, + true); + + view_dispatcher_switch_to_view(namechanger->view_dispatcher, NameChangerViewTextInput); + } else { + view_dispatcher_send_custom_event( + namechanger->view_dispatcher, NameChangerCustomEventError); + } +} + +bool namechanger_scene_change_on_event(void* context, SceneManagerEvent event) { + NameChanger* namechanger = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == NameChangerCustomEventTextEditResult) { + if(namechanger_make_app_folder(namechanger)) { + if(namechanger_name_read_write(namechanger, namechanger->text_store, 3)) { + scene_manager_next_scene( + namechanger->scene_manager, NameChangerSceneChangeSuccess); + } else { + scene_manager_search_and_switch_to_previous_scene( + namechanger->scene_manager, NameChangerSceneError); + } + } else { + scene_manager_search_and_switch_to_previous_scene( + namechanger->scene_manager, NameChangerSceneError); + } + } else if(event.event == NameChangerCustomEventError) { + scene_manager_search_and_switch_to_previous_scene( + namechanger->scene_manager, NameChangerSceneError); + } + } + return consumed; +} + +void namechanger_scene_change_on_exit(void* context) { + NameChanger* namechanger = context; + TextInput* text_input = namechanger->text_input; + + text_input_reset(text_input); +} diff --git a/Applications/Official/DEV_FW/source/namechanger/scenes/namechanger_scene_change_success.c b/Applications/Official/DEV_FW/source/namechanger/scenes/namechanger_scene_change_success.c new file mode 100644 index 000000000..1e0c3c2bb --- /dev/null +++ b/Applications/Official/DEV_FW/source/namechanger/scenes/namechanger_scene_change_success.c @@ -0,0 +1,51 @@ +#include "../namechanger.h" + +static void namechanger_scene_change_success_popup_callback(void* context) { + NameChanger* namechanger = context; + view_dispatcher_send_custom_event(namechanger->view_dispatcher, NameChangerCustomEventBack); +} + +void namechanger_scene_change_success_on_enter(void* context) { + NameChanger* namechanger = context; + Popup* popup = namechanger->popup; + + popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); + popup_set_header(popup, "Saved!", 5, 5, AlignLeft, AlignTop); + popup_set_text(popup, "Rebooting...", 5, 17, AlignLeft, AlignTop); + + popup_set_callback(popup, namechanger_scene_change_success_popup_callback); + popup_set_context(popup, namechanger); + popup_set_timeout(popup, 5000); + popup_enable_timeout(popup); + + view_dispatcher_switch_to_view(namechanger->view_dispatcher, NameChangerViewPopup); +} + +bool namechanger_scene_change_success_on_event(void* context, SceneManagerEvent event) { + NameChanger* namechanger = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == NameChangerCustomEventBack) { + scene_manager_search_and_switch_to_previous_scene( + namechanger->scene_manager, NameChangerSceneChange); + } + } + + return consumed; +} + +void namechanger_scene_change_success_on_exit(void* context) { + NameChanger* namechanger = context; + Popup* popup = namechanger->popup; + + popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop); + popup_set_icon(popup, 0, 0, NULL); + + popup_disable_timeout(popup); + popup_set_context(popup, NULL); + popup_set_callback(popup, NULL); + + furi_hal_power_reset(); +} diff --git a/Applications/Official/DEV_FW/source/namechanger/scenes/namechanger_scene_config.h b/Applications/Official/DEV_FW/source/namechanger/scenes/namechanger_scene_config.h new file mode 100644 index 000000000..26236a057 --- /dev/null +++ b/Applications/Official/DEV_FW/source/namechanger/scenes/namechanger_scene_config.h @@ -0,0 +1,6 @@ +ADD_SCENE(namechanger, start, Start) +ADD_SCENE(namechanger, change, Change) +ADD_SCENE(namechanger, change_success, ChangeSuccess) +ADD_SCENE(namechanger, revert, Revert) +ADD_SCENE(namechanger, revert_success, RevertSuccess) +ADD_SCENE(namechanger, error, Error) \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/namechanger/scenes/namechanger_scene_error.c b/Applications/Official/DEV_FW/source/namechanger/scenes/namechanger_scene_error.c new file mode 100644 index 000000000..3fe2e7276 --- /dev/null +++ b/Applications/Official/DEV_FW/source/namechanger/scenes/namechanger_scene_error.c @@ -0,0 +1,49 @@ +#include "../namechanger.h" + +static void namechanger_scene_error_popup_callback(void* context) { + NameChanger* namechanger = context; + view_dispatcher_send_custom_event(namechanger->view_dispatcher, NameChangerCustomEventBack); +} + +void namechanger_scene_error_on_enter(void* context) { + NameChanger* namechanger = context; + Popup* popup = namechanger->popup; + + popup_set_icon(popup, 60, 12, &I_Cry_dolph_55x52); + popup_set_header(popup, "Error", 5, 7, AlignLeft, AlignTop); + + popup_set_text(popup, furi_string_get_cstr(namechanger->error), 5, 20, AlignLeft, AlignTop); + + popup_set_callback(popup, namechanger_scene_error_popup_callback); + popup_set_context(popup, namechanger); + popup_set_timeout(popup, 10000); + popup_enable_timeout(popup); + + view_dispatcher_switch_to_view(namechanger->view_dispatcher, NameChangerViewPopup); +} + +bool namechanger_scene_error_on_event(void* context, SceneManagerEvent event) { + NameChanger* namechanger = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == NameChangerCustomEventBack) { + view_dispatcher_stop(namechanger->view_dispatcher); + } + } + + return consumed; +} + +void namechanger_scene_error_on_exit(void* context) { + NameChanger* namechanger = context; + Popup* popup = namechanger->popup; + + popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop); + popup_set_icon(popup, 0, 0, NULL); + + popup_disable_timeout(popup); + popup_set_context(popup, NULL); + popup_set_callback(popup, NULL); +} diff --git a/Applications/Official/DEV_FW/source/namechanger/scenes/namechanger_scene_revert.c b/Applications/Official/DEV_FW/source/namechanger/scenes/namechanger_scene_revert.c new file mode 100644 index 000000000..b1ab3d75a --- /dev/null +++ b/Applications/Official/DEV_FW/source/namechanger/scenes/namechanger_scene_revert.c @@ -0,0 +1,68 @@ +#include "../namechanger.h" + +static void + namechanger_scene_revert_widget_callback(GuiButtonType result, InputType type, void* context) { + NameChanger* namechanger = context; + FURI_LOG_I(TAG, "revert1"); + if(type == InputTypeShort) { + FURI_LOG_I(TAG, "revert2"); + view_dispatcher_send_custom_event(namechanger->view_dispatcher, result); + } +} + +void namechanger_scene_revert_on_enter(void* context) { + NameChanger* namechanger = context; + Widget* widget = namechanger->widget; + FURI_LOG_I(TAG, "revert3"); + widget_add_text_box_element( + widget, 0, 0, 128, 25, AlignCenter, AlignCenter, "\e#Revert Name?\e#", false); + widget_add_icon_element(widget, 48, 20, &I_MarioBlock); + widget_add_button_element( + widget, GuiButtonTypeLeft, "Cancel", namechanger_scene_revert_widget_callback, namechanger); + FURI_LOG_I(TAG, "revert4"); + widget_add_button_element( + widget, + GuiButtonTypeRight, + "Revert", + namechanger_scene_revert_widget_callback, + namechanger); + FURI_LOG_I(TAG, "revert5"); + view_dispatcher_switch_to_view(namechanger->view_dispatcher, NameChangerViewWidget); +} + +bool namechanger_scene_revert_on_event(void* context, SceneManagerEvent event) { + NameChanger* namechanger = context; + bool consumed = false; + FURI_LOG_I(TAG, "revert6"); + if(event.type == SceneManagerEventTypeBack) { + consumed = true; + FURI_LOG_I(TAG, "revert7"); + } else if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + FURI_LOG_I(TAG, "revert8"); + if(event.event == GuiButtonTypeRight) { + FURI_LOG_I(TAG, "revert9"); + if(namechanger_name_read_write(namechanger, "eraseerase", 3)) { + FURI_LOG_I(TAG, "revert10"); + scene_manager_next_scene( + namechanger->scene_manager, NameChangerSceneRevertSuccess); + } else { + FURI_LOG_I(TAG, "revert11"); + scene_manager_search_and_switch_to_previous_scene( + namechanger->scene_manager, NameChangerSceneError); + } + } else if(event.event == GuiButtonTypeLeft) { + FURI_LOG_I(TAG, "revert12"); + scene_manager_search_and_switch_to_previous_scene( + namechanger->scene_manager, NameChangerSceneStart); + } + } + FURI_LOG_I(TAG, "revert13"); + return consumed; +} + +void namechanger_scene_revert_on_exit(void* context) { + NameChanger* namechanger = context; + FURI_LOG_I(TAG, "revert14"); + widget_reset(namechanger->widget); +} diff --git a/Applications/Official/DEV_FW/source/namechanger/scenes/namechanger_scene_revert_success.c b/Applications/Official/DEV_FW/source/namechanger/scenes/namechanger_scene_revert_success.c new file mode 100644 index 000000000..0be0a40ef --- /dev/null +++ b/Applications/Official/DEV_FW/source/namechanger/scenes/namechanger_scene_revert_success.c @@ -0,0 +1,55 @@ +#include "../namechanger.h" + +static void namechanger_scene_revert_success_popup_callback(void* context) { + NameChanger* namechanger = context; + view_dispatcher_send_custom_event(namechanger->view_dispatcher, NameChangerCustomEventBack); +} + +void namechanger_scene_revert_success_on_enter(void* context) { + NameChanger* namechanger = context; + Popup* popup = namechanger->popup; + + popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); + popup_set_header(popup, "Reverted!", 70, 5, AlignLeft, AlignTop); + popup_set_text(popup, "Rebooting...", 70, 16, AlignLeft, AlignTop); + + popup_set_callback(popup, namechanger_scene_revert_success_popup_callback); + popup_set_context(popup, namechanger); + popup_set_timeout(popup, 5000); + popup_enable_timeout(popup); + + view_dispatcher_switch_to_view(namechanger->view_dispatcher, NameChangerViewPopup); +} + +bool namechanger_scene_revert_success_on_event(void* context, SceneManagerEvent event) { + NameChanger* namechanger = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeBack) { + consumed = true; + scene_manager_search_and_switch_to_previous_scene( + namechanger->scene_manager, NameChangerSceneStart); + } else if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == NameChangerCustomEventBack) { + scene_manager_search_and_switch_to_previous_scene( + namechanger->scene_manager, NameChangerSceneStart); + } + } + + return consumed; +} + +void namechanger_scene_revert_success_on_exit(void* context) { + NameChanger* namechanger = context; + Popup* popup = namechanger->popup; + + popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop); + popup_set_icon(popup, 0, 0, NULL); + + popup_disable_timeout(popup); + popup_set_context(popup, NULL); + popup_set_callback(popup, NULL); + + furi_hal_power_reset(); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/namechanger/scenes/namechanger_scene_start.c b/Applications/Official/DEV_FW/source/namechanger/scenes/namechanger_scene_start.c new file mode 100644 index 000000000..3fe93f5a2 --- /dev/null +++ b/Applications/Official/DEV_FW/source/namechanger/scenes/namechanger_scene_start.c @@ -0,0 +1,58 @@ +#include "../namechanger.h" + +enum SubmenuIndex { + SubmenuIndexChange, + SubmenuIndexRevert, +}; + +void namechanger_scene_start_submenu_callback(void* context, uint32_t index) { + NameChanger* namechanger = context; + view_dispatcher_send_custom_event(namechanger->view_dispatcher, index); +} + +void namechanger_scene_start_on_enter(void* context) { + NameChanger* namechanger = context; + Submenu* submenu = namechanger->submenu; + + submenu_add_item( + submenu, + "Change", + SubmenuIndexChange, + namechanger_scene_start_submenu_callback, + namechanger); + + submenu_add_item( + submenu, + "Revert", + SubmenuIndexRevert, + namechanger_scene_start_submenu_callback, + namechanger); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(namechanger->scene_manager, NameChangerSceneStart)); + + view_dispatcher_switch_to_view(namechanger->view_dispatcher, NameChangerViewSubmenu); +} + +bool namechanger_scene_start_on_event(void* context, SceneManagerEvent event) { + NameChanger* namechanger = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state( + namechanger->scene_manager, NameChangerSceneStart, event.event); + consumed = true; + if(event.event == SubmenuIndexChange) { + scene_manager_next_scene(namechanger->scene_manager, NameChangerSceneChange); + } + if(event.event == SubmenuIndexRevert) { + scene_manager_next_scene(namechanger->scene_manager, NameChangerSceneRevert); + } + } + return consumed; +} + +void namechanger_scene_start_on_exit(void* context) { + NameChanger* namechanger = context; + submenu_reset(namechanger->submenu); +} diff --git a/Applications/Official/DEV_FW/source/ocarina/README.md b/Applications/Official/DEV_FW/source/ocarina/README.md new file mode 100644 index 000000000..1fcfd00fa --- /dev/null +++ b/Applications/Official/DEV_FW/source/ocarina/README.md @@ -0,0 +1,4 @@ +# flipperzero-ocarina +A basic Ocarina (of Time) for the Flipper Zero. + +Controls are the same as the N64 version of the Ocarina of Time, the Ok button takes the place of the A button diff --git a/Applications/Official/DEV_FW/source/ocarina/application.fam b/Applications/Official/DEV_FW/source/ocarina/application.fam new file mode 100644 index 000000000..192cb2f16 --- /dev/null +++ b/Applications/Official/DEV_FW/source/ocarina/application.fam @@ -0,0 +1,13 @@ +App( + appid="Ocarina", + name="Ocarina", + apptype=FlipperAppType.EXTERNAL, + entry_point="ocarina_app", + cdefines=["APP_OCARINA"], + requires=["gui"], + stack_size=1 * 1024, + order=30, + fap_icon="icons/music_10px.png", + fap_category="Music", + fap_icon_assets="icons", +) diff --git a/Applications/Official/DEV_FW/source/ocarina/icons/music_10px.png b/Applications/Official/DEV_FW/source/ocarina/icons/music_10px.png new file mode 100644 index 000000000..d41eb0db8 Binary files /dev/null and b/Applications/Official/DEV_FW/source/ocarina/icons/music_10px.png differ diff --git a/Applications/Official/DEV_FW/source/ocarina/ocarina.c b/Applications/Official/DEV_FW/source/ocarina/ocarina.c new file mode 100644 index 000000000..013f81ab8 --- /dev/null +++ b/Applications/Official/DEV_FW/source/ocarina/ocarina.c @@ -0,0 +1,118 @@ +#include +#include +#include +#include +#include + +#define NOTE_UP 587.33f +#define NOTE_LEFT 493.88f +#define NOTE_RIGHT 440.00f +#define NOTE_DOWN 349.23 +#define NOTE_OK 293.66f + +typedef struct { + FuriMutex* model_mutex; + + FuriMessageQueue* event_queue; + + ViewPort* view_port; + Gui* gui; +} Ocarina; + +void draw_callback(Canvas* canvas, void* ctx) { + Ocarina* ocarina = ctx; + furi_check(furi_mutex_acquire(ocarina->model_mutex, FuriWaitForever) == FuriStatusOk); + + //canvas_draw_box(canvas, ocarina->model->x, ocarina->model->y, 4, 4); + canvas_draw_frame(canvas, 0, 0, 128, 64); + canvas_draw_str(canvas, 50, 10, "Ocarina"); + canvas_draw_str(canvas, 30, 20, "OK button for A"); + + furi_mutex_release(ocarina->model_mutex); +} + +void input_callback(InputEvent* input, void* ctx) { + Ocarina* ocarina = ctx; + // Puts input onto event queue with priority 0, and waits until completion. + furi_message_queue_put(ocarina->event_queue, input, FuriWaitForever); +} + +Ocarina* ocarina_alloc() { + Ocarina* instance = malloc(sizeof(Ocarina)); + + instance->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal); + + instance->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + + instance->view_port = view_port_alloc(); + view_port_draw_callback_set(instance->view_port, draw_callback, instance); + view_port_input_callback_set(instance->view_port, input_callback, instance); + + instance->gui = furi_record_open("gui"); + gui_add_view_port(instance->gui, instance->view_port, GuiLayerFullscreen); + + return instance; +} + +void ocarina_free(Ocarina* instance) { + view_port_enabled_set(instance->view_port, false); // Disabsles our ViewPort + gui_remove_view_port(instance->gui, instance->view_port); // Removes our ViewPort from the Gui + furi_record_close("gui"); // Closes the gui record + view_port_free(instance->view_port); // Frees memory allocated by view_port_alloc + furi_message_queue_free(instance->event_queue); + + furi_mutex_free(instance->model_mutex); + + furi_hal_speaker_stop(); + + free(instance); +} + +int32_t ocarina_app(void* p) { + UNUSED(p); + + Ocarina* ocarina = ocarina_alloc(); + + InputEvent event; + for(bool processing = true; processing;) { + // Pops a message off the queue and stores it in `event`. + // No message priority denoted by NULL, and 100 ticks of timeout. + FuriStatus status = furi_message_queue_get(ocarina->event_queue, &event, 100); + furi_check(furi_mutex_acquire(ocarina->model_mutex, FuriWaitForever) == FuriStatusOk); + + float volume = 1.0f; + if(status == FuriStatusOk) { + if(event.type == InputTypePress) { + switch(event.key) { + case InputKeyUp: + furi_hal_speaker_start(NOTE_UP, volume); + break; + case InputKeyDown: + furi_hal_speaker_start(NOTE_DOWN, volume); + break; + case InputKeyLeft: + furi_hal_speaker_start(NOTE_LEFT, volume); + break; + case InputKeyRight: + furi_hal_speaker_start(NOTE_RIGHT, volume); + break; + case InputKeyOk: + furi_hal_speaker_start(NOTE_OK, volume); + break; + case InputKeyBack: + processing = false; + break; + default: + break; + } + } else if(event.type == InputTypeRelease) { + furi_hal_speaker_stop(); + } + } + + furi_mutex_release(ocarina->model_mutex); + view_port_update(ocarina->view_port); // signals our draw callback + } + ocarina_free(ocarina); + return 0; +} diff --git a/Applications/Official/DEV_FW/source/orgasmotron/application.fam b/Applications/Official/DEV_FW/source/orgasmotron/application.fam new file mode 100644 index 000000000..2fd456339 --- /dev/null +++ b/Applications/Official/DEV_FW/source/orgasmotron/application.fam @@ -0,0 +1,12 @@ +App( + appid="Orgasmotron", + name="Orgasmotron", + apptype=FlipperAppType.EXTERNAL, + entry_point="orgasmotron_app", + cdefines=["ORGASMOTRON"], + requires=["gui"], + stack_size=1 * 1024, + order=20, + fap_icon="orgasmotron_10px.png", + fap_category="Misc", +) diff --git a/Applications/Official/DEV_FW/source/orgasmotron/orgasmotron.c b/Applications/Official/DEV_FW/source/orgasmotron/orgasmotron.c new file mode 100644 index 000000000..814a9e26d --- /dev/null +++ b/Applications/Official/DEV_FW/source/orgasmotron/orgasmotron.c @@ -0,0 +1,75 @@ +#include +#include + +#include +#include +#include + +void vibro_test_draw_callback(Canvas* canvas, void* ctx) { + UNUSED(ctx); + canvas_clear(canvas); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 2, 10, "Vibro application"); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 2, 22, "Press OK turns on vibro"); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 2, 34, "Press LEFT turns off vibro"); +} + +void vibro_test_input_callback(InputEvent* input_event, void* ctx) { + furi_assert(ctx); + FuriMessageQueue* event_queue = ctx; + furi_message_queue_put(event_queue, input_event, FuriWaitForever); +} + +int32_t orgasmotron_app(void* p) { + UNUSED(p); + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + + // Configure view port + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, vibro_test_draw_callback, NULL); + view_port_input_callback_set(view_port, vibro_test_input_callback, event_queue); + + // Register view port in GUI + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + + InputEvent event; + + while(furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk) { + if(event.type == InputTypeShort && event.key == InputKeyBack) { + notification_message(notification, &sequence_reset_vibro); + notification_message(notification, &sequence_reset_green); + break; + } + if(event.key == InputKeyOk) { + if(event.type == InputTypePress) { + notification_message(notification, &sequence_set_vibro_on); + notification_message(notification, &sequence_set_green_255); + } else if(event.type == InputTypeRelease) { + notification_message(notification, &sequence_set_vibro_on); + notification_message(notification, &sequence_set_green_255); + } + } + if(event.key == InputKeyLeft) { + if(event.type == InputTypePress) { + notification_message(notification, &sequence_reset_vibro); + notification_message(notification, &sequence_reset_green); + } else if(event.type == InputTypeRelease) { + notification_message(notification, &sequence_reset_vibro); + notification_message(notification, &sequence_reset_green); + } + } + } + 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/Official/DEV_FW/source/orgasmotron/orgasmotron.fap b/Applications/Official/DEV_FW/source/orgasmotron/orgasmotron.fap new file mode 100644 index 000000000..23d7bebb3 Binary files /dev/null and b/Applications/Official/DEV_FW/source/orgasmotron/orgasmotron.fap differ diff --git a/Applications/Official/DEV_FW/source/orgasmotron/orgasmotron_10px.png b/Applications/Official/DEV_FW/source/orgasmotron/orgasmotron_10px.png new file mode 100644 index 000000000..b6c93c9f9 Binary files /dev/null and b/Applications/Official/DEV_FW/source/orgasmotron/orgasmotron_10px.png differ diff --git a/Applications/Official/DEV_FW/source/paint/application.fam b/Applications/Official/DEV_FW/source/paint/application.fam new file mode 100644 index 000000000..0630abbce --- /dev/null +++ b/Applications/Official/DEV_FW/source/paint/application.fam @@ -0,0 +1,12 @@ +App( + appid="Paint", + name="Paint", + apptype=FlipperAppType.EXTERNAL, + entry_point="paint_app", + cdefines=["APP_PAINT"], + requires=["gui"], + stack_size=2 * 1024, + order=175, + fap_icon="paintIcon.png", + fap_category="Misc", +) \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/paint/paint.c b/Applications/Official/DEV_FW/source/paint/paint.c new file mode 100644 index 000000000..5cfe85155 --- /dev/null +++ b/Applications/Official/DEV_FW/source/paint/paint.c @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include +#include +#include // Header-file for boolean data-type. + +typedef struct selected_position { + int x; + int y; +} selected_position; + +typedef struct { + selected_position selected; + bool board[32][16]; + bool isDrawing; +} PaintData; + +void paint_draw_callback(Canvas* canvas, void* ctx) { + const PaintData* paint_state = acquire_mutex((ValueMutex*)ctx, 25); + UNUSED(ctx); + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + //draw the canvas(32x16) on screen(144x64) using 4x4 tiles + for(int y = 0; y < 16; y++) { + for(int x = 0; x < 32; x++) { + if(paint_state->board[x][y]) { + canvas_draw_box(canvas, x * 4, y * 4, 4, 4); + } + } + } + + //draw cursor as a 4x4 black box with a 2x2 white box inside + canvas_set_color(canvas, ColorBlack); + canvas_draw_box(canvas, paint_state->selected.x * 4, paint_state->selected.y * 4, 4, 4); + canvas_set_color(canvas, ColorWhite); + canvas_draw_box( + canvas, paint_state->selected.x * 4 + 1, paint_state->selected.y * 4 + 1, 2, 2); + + //release the mutex + release_mutex((ValueMutex*)ctx, paint_state); +} + +void paint_input_callback(InputEvent* input_event, void* ctx) { + furi_assert(ctx); + FuriMessageQueue* event_queue = ctx; + furi_message_queue_put(event_queue, input_event, FuriWaitForever); +} + +int32_t paint_app(void* p) { + UNUSED(p); + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + + PaintData* paint_state = malloc(sizeof(PaintData)); + ValueMutex paint_state_mutex; + if(!init_mutex(&paint_state_mutex, paint_state, sizeof(PaintData))) { + FURI_LOG_E("paint", "cannot create mutex\r\n"); + free(paint_state); + return -1; + } + + // Configure view port + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, paint_draw_callback, &paint_state_mutex); + view_port_input_callback_set(view_port, paint_input_callback, event_queue); + + // Register view port in GUI + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + //NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + + InputEvent event; + + while(furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk) { + //break out of the loop if the back key is pressed + if(event.type == InputTypeShort && event.key == InputKeyBack) { + break; + } + + //check the key pressed and change x and y accordingly + if(event.type == InputTypeShort) { + switch(event.key) { + case InputKeyUp: + paint_state->selected.y -= 1; + break; + case InputKeyDown: + paint_state->selected.y += 1; + break; + case InputKeyLeft: + paint_state->selected.x -= 1; + break; + case InputKeyRight: + paint_state->selected.x += 1; + break; + case InputKeyOk: + paint_state->board[paint_state->selected.x][paint_state->selected.y] = + !paint_state->board[paint_state->selected.x][paint_state->selected.y]; + break; + + default: + break; + } + + //check if cursor position is out of bounds and reset it to the closest position + if(paint_state->selected.x < 0) { + paint_state->selected.x = 0; + } + if(paint_state->selected.x > 31) { + paint_state->selected.x = 31; + } + if(paint_state->selected.y < 0) { + paint_state->selected.y = 0; + } + if(paint_state->selected.y > 15) { + paint_state->selected.y = 15; + } + if(paint_state->isDrawing == true) { + paint_state->board[paint_state->selected.x][paint_state->selected.y] = true; + } + view_port_update(view_port); + } + if(event.key == InputKeyBack && event.type == InputTypeLong) { + paint_state->board[1][1] = true; + for(int y = 0; y < 16; y++) { + for(int x = 0; x < 32; x++) { + paint_state->board[x][y] = false; + } + } + view_port_update(view_port); + } + if(event.key == InputKeyOk && event.type == InputTypeLong) { + paint_state->isDrawing = !paint_state->isDrawing; + paint_state->board[paint_state->selected.x][paint_state->selected.y] = true; + view_port_update(view_port); + } + } + + gui_remove_view_port(gui, view_port); + view_port_free(view_port); + furi_message_queue_free(event_queue); + free(paint_state); + furi_record_close(RECORD_NOTIFICATION); + furi_record_close(RECORD_GUI); + + return 0; +} diff --git a/Applications/Official/DEV_FW/source/paint/paintIcon.png b/Applications/Official/DEV_FW/source/paint/paintIcon.png new file mode 100644 index 000000000..cc0a8b7d8 Binary files /dev/null and b/Applications/Official/DEV_FW/source/paint/paintIcon.png differ diff --git a/Applications/Official/DEV_FW/source/passgen/LICENSE b/Applications/Official/DEV_FW/source/passgen/LICENSE new file mode 100644 index 000000000..85e363072 --- /dev/null +++ b/Applications/Official/DEV_FW/source/passgen/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Skurydin Alexey + +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/Official/DEV_FW/source/passgen/README.md b/Applications/Official/DEV_FW/source/passgen/README.md new file mode 100644 index 000000000..fbeb41680 --- /dev/null +++ b/Applications/Official/DEV_FW/source/passgen/README.md @@ -0,0 +1,4 @@ +# flipper_passgen +This is a simple Password Generator plugin (**fap**) for the [Flipper Zero](https://www.flipperzero.one). + +![preview](images/preview.png) \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/passgen/application.fam b/Applications/Official/DEV_FW/source/passgen/application.fam new file mode 100644 index 000000000..78d810a1d --- /dev/null +++ b/Applications/Official/DEV_FW/source/passgen/application.fam @@ -0,0 +1,12 @@ +App( + appid="Password_Generator", + name="Password Generator", + apptype=FlipperAppType.EXTERNAL, + entry_point="passgenapp", + requires=[ + "gui", + ], + fap_category="Tools", + fap_icon="icons/passgen_icon.png", + fap_icon_assets="icons", +) \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/passgen/icons/Horizontal_arrow_9x7.png b/Applications/Official/DEV_FW/source/passgen/icons/Horizontal_arrow_9x7.png new file mode 100644 index 000000000..caca88718 Binary files /dev/null and b/Applications/Official/DEV_FW/source/passgen/icons/Horizontal_arrow_9x7.png differ diff --git a/Applications/Official/DEV_FW/source/passgen/icons/Ok_btn_9x9.png b/Applications/Official/DEV_FW/source/passgen/icons/Ok_btn_9x9.png new file mode 100644 index 000000000..9a1539da2 Binary files /dev/null and b/Applications/Official/DEV_FW/source/passgen/icons/Ok_btn_9x9.png differ diff --git a/Applications/Official/DEV_FW/source/passgen/icons/Pin_back_arrow_10x8.png b/Applications/Official/DEV_FW/source/passgen/icons/Pin_back_arrow_10x8.png new file mode 100644 index 000000000..3bafabd14 Binary files /dev/null and b/Applications/Official/DEV_FW/source/passgen/icons/Pin_back_arrow_10x8.png differ diff --git a/Applications/Official/DEV_FW/source/passgen/icons/Vertical_arrow_7x9.png b/Applications/Official/DEV_FW/source/passgen/icons/Vertical_arrow_7x9.png new file mode 100644 index 000000000..b889fc8f3 Binary files /dev/null and b/Applications/Official/DEV_FW/source/passgen/icons/Vertical_arrow_7x9.png differ diff --git a/Applications/Official/DEV_FW/source/passgen/icons/passgen_icon.png b/Applications/Official/DEV_FW/source/passgen/icons/passgen_icon.png new file mode 100644 index 000000000..1ed4f77fc Binary files /dev/null and b/Applications/Official/DEV_FW/source/passgen/icons/passgen_icon.png differ diff --git a/Applications/Official/DEV_FW/source/passgen/images/preview.png b/Applications/Official/DEV_FW/source/passgen/images/preview.png new file mode 100644 index 000000000..3224b0a8d Binary files /dev/null and b/Applications/Official/DEV_FW/source/passgen/images/preview.png differ diff --git a/Applications/Official/DEV_FW/source/passgen/passgen.c b/Applications/Official/DEV_FW/source/passgen/passgen.c new file mode 100644 index 000000000..c0f9c6e59 --- /dev/null +++ b/Applications/Official/DEV_FW/source/passgen/passgen.c @@ -0,0 +1,202 @@ +#include +#include +#include +#include +#include +#include +#include + +#define PASSGEN_MAX_LENGTH 16 +#define PASSGEN_CHARACTERS_LENGTH (26 * 4) + +#define PASSGEN_DIGITS "0123456789" +#define PASSGEN_LETTERS_LOW "abcdefghijklmnopqrstuvwxyz" +#define PASSGEN_LETTERS_UP "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +#define PASSGEN_SPECIAL "!#$%^&*.-_" + +typedef enum PassGen_Alphabet { + Digits = 1, + Lowercase = 2, + + Uppercase = 4, + Special = 8, + + DigitsLower = Digits | Lowercase, + DigitsAllLetters = Digits | Lowercase | Uppercase, + Mixed = DigitsAllLetters | Special +} PassGen_Alphabet; + +const int AlphabetLevels[] = {Digits, Lowercase, DigitsLower, DigitsAllLetters, Mixed}; +const char* AlphabetLevelNames[] = {"1234", "abcd", "ab12", "Ab12", "Ab1#"}; +const int AlphabetLevelsCount = sizeof(AlphabetLevels) / sizeof(int); + +const NotificationSequence PassGen_Alert_vibro = { + &message_vibro_on, + &message_blue_255, + &message_delay_50, + &message_vibro_off, + NULL, +}; + +typedef struct { + FuriMessageQueue* input_queue; + ViewPort* view_port; + Gui* gui; + FuriMutex** mutex; + NotificationApp* notify; + char password[PASSGEN_MAX_LENGTH + 1]; + char alphabet[PASSGEN_CHARACTERS_LENGTH + 1]; + int length; + int level; +} PassGen; + +void state_free(PassGen* app) { + gui_remove_view_port(app->gui, app->view_port); + furi_record_close(RECORD_GUI); + view_port_free(app->view_port); + furi_message_queue_free(app->input_queue); + furi_mutex_free(app->mutex); + furi_record_close(RECORD_NOTIFICATION); + free(app); +} + +static void input_callback(InputEvent* input_event, void* ctx) { + PassGen* app = ctx; + if(input_event->type == InputTypeShort) { + furi_message_queue_put(app->input_queue, input_event, 0); + } +} + +static void render_callback(Canvas* canvas, void* ctx) { + char str_length[8]; + PassGen* app = ctx; + furi_check(furi_mutex_acquire(app->mutex, FuriWaitForever) == FuriStatusOk); + + canvas_clear(canvas); + canvas_draw_box(canvas, 0, 0, 128, 14); + canvas_set_color(canvas, ColorWhite); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 2, 11, "Password Generator"); + + canvas_set_color(canvas, ColorBlack); + canvas_draw_str_aligned(canvas, 64, 35, AlignCenter, AlignCenter, app->password); + + // Navigation menu: + canvas_set_font(canvas, FontSecondary); + canvas_draw_icon(canvas, 96, 52, &I_Pin_back_arrow_10x8); + canvas_draw_str(canvas, 108, 60, "Exit"); + + canvas_draw_icon(canvas, 54, 52, &I_Vertical_arrow_7x9); + canvas_draw_str(canvas, 64, 60, AlphabetLevelNames[app->level]); + + snprintf(str_length, sizeof(str_length), "Len: %d", app->length); + canvas_draw_icon(canvas, 4, 53, &I_Horizontal_arrow_9x7); + canvas_draw_str(canvas, 15, 60, str_length); + + furi_mutex_release(app->mutex); +} + +void build_alphabet(PassGen* app) { + PassGen_Alphabet mode = AlphabetLevels[app->level]; + app->alphabet[0] = '\0'; + if((mode & Digits) != 0) strcat(app->alphabet, PASSGEN_DIGITS); + if((mode & Lowercase) != 0) strcat(app->alphabet, PASSGEN_LETTERS_LOW); + if((mode & Uppercase) != 0) strcat(app->alphabet, PASSGEN_LETTERS_UP); + if((mode & Special) != 0) strcat(app->alphabet, PASSGEN_SPECIAL); +} + +PassGen* state_init() { + PassGen* app = malloc(sizeof(PassGen)); + app->length = 8; + app->level = 2; + build_alphabet(app); + app->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + app->view_port = view_port_alloc(); + app->gui = furi_record_open(RECORD_GUI); + app->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + view_port_input_callback_set(app->view_port, input_callback, app); + view_port_draw_callback_set(app->view_port, render_callback, app); + gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen); + + app->notify = furi_record_open(RECORD_NOTIFICATION); + + return app; +} + +void generate(PassGen* app) { + int hi = strlen(app->alphabet); + for(int i = 0; i < app->length; i++) { + int x = rand() % hi; + app->password[i] = app->alphabet[x]; + } + app->password[app->length] = '\0'; +} + +void update_password(PassGen* app, bool vibro) { + generate(app); + + if(vibro) + notification_message(app->notify, &PassGen_Alert_vibro); + else + notification_message(app->notify, &sequence_blink_blue_100); + view_port_update(app->view_port); +} + +int32_t passgenapp(void) { + PassGen* app = state_init(); + generate(app); + + while(1) { + InputEvent input; + while(furi_message_queue_get(app->input_queue, &input, FuriWaitForever) == FuriStatusOk) { + furi_check(furi_mutex_acquire(app->mutex, FuriWaitForever) == FuriStatusOk); + + if(input.type == InputTypeShort) { + switch(input.key) { + case InputKeyBack: + furi_mutex_release(app->mutex); + state_free(app); + return 0; + case InputKeyDown: + if(app->level > 0) { + app->level--; + build_alphabet(app); + update_password(app, false); + } else + notification_message(app->notify, &sequence_blink_red_100); + break; + case InputKeyUp: + if(app->level < AlphabetLevelsCount - 1) { + app->level++; + build_alphabet(app); + update_password(app, false); + } else + notification_message(app->notify, &sequence_blink_red_100); + break; + case InputKeyLeft: + if(app->length > 1) { + app->length--; + update_password(app, false); + } else + notification_message(app->notify, &sequence_blink_red_100); + break; + case InputKeyRight: + if(app->length < PASSGEN_MAX_LENGTH) { + app->length++; + update_password(app, false); + } else + notification_message(app->notify, &sequence_blink_red_100); + break; + case InputKeyOk: + update_password(app, true); + break; + default: + break; + } + } + furi_mutex_release(app->mutex); + } + } + state_free(app); + return 0; +} diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/.gitignore b/Applications/Official/DEV_FW/source/pocsag_pager/.gitignore new file mode 100644 index 000000000..ca91939c9 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pocsag_pager/.gitignore @@ -0,0 +1,4 @@ +.idea +.vscode +CMakeLists.txt +dist \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/application.fam b/Applications/Official/DEV_FW/source/pocsag_pager/application.fam new file mode 100644 index 000000000..aafb6a5a3 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pocsag_pager/application.fam @@ -0,0 +1,13 @@ +App( + appid="pocsag_pager", + name="POCSAG Pager", + apptype=FlipperAppType.PLUGIN, + entry_point="pocsag_pager_app", + cdefines=["APP_POCSAG_PAGER"], + requires=["gui"], + stack_size=4 * 1024, + order=50, + fap_icon="pocsag_pager_10px.png", + fap_category="Tools", + fap_icon_assets="images", +) diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/helpers/pocsag_pager_event.h b/Applications/Official/DEV_FW/source/pocsag_pager/helpers/pocsag_pager_event.h new file mode 100644 index 000000000..8bcf64a30 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pocsag_pager/helpers/pocsag_pager_event.h @@ -0,0 +1,14 @@ +#pragma once + +typedef enum { + //PCSGCustomEvent + PCSGCustomEventStartId = 100, + + PCSGCustomEventSceneSettingLock, + + PCSGCustomEventViewReceiverOK, + PCSGCustomEventViewReceiverConfig, + PCSGCustomEventViewReceiverBack, + PCSGCustomEventViewReceiverOffDisplay, + PCSGCustomEventViewReceiverUnlock, +} PCSGCustomEvent; diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/helpers/pocsag_pager_types.h b/Applications/Official/DEV_FW/source/pocsag_pager/helpers/pocsag_pager_types.h new file mode 100644 index 000000000..fabd7f321 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pocsag_pager/helpers/pocsag_pager_types.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +#define PCSG_VERSION_APP "0.1" +#define PCSG_DEVELOPED "@xMasterX & @Shmuma" +#define PCSG_GITHUB "https://github.com/xMasterX/flipper-pager" + +#define PCSG_KEY_FILE_VERSION 1 +#define PCSG_KEY_FILE_TYPE "Flipper POCSAG Pager Key File" + +/** PCSGRxKeyState state */ +typedef enum { + PCSGRxKeyStateIDLE, + PCSGRxKeyStateBack, + PCSGRxKeyStateStart, + PCSGRxKeyStateAddKey, +} PCSGRxKeyState; + +/** PCSGHopperState state */ +typedef enum { + PCSGHopperStateOFF, + PCSGHopperStateRunnig, + PCSGHopperStatePause, + PCSGHopperStateRSSITimeOut, +} PCSGHopperState; + +/** PCSGLock */ +typedef enum { + PCSGLockOff, + PCSGLockOn, +} PCSGLock; + +typedef enum { + POCSAGPagerViewVariableItemList, + POCSAGPagerViewSubmenu, + POCSAGPagerViewReceiver, + POCSAGPagerViewReceiverInfo, + POCSAGPagerViewWidget, +} POCSAGPagerView; + +/** POCSAGPagerTxRx state */ +typedef enum { + PCSGTxRxStateIDLE, + PCSGTxRxStateRx, + PCSGTxRxStateTx, + PCSGTxRxStateSleep, +} PCSGTxRxState; diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/images/Lock_7x8.png b/Applications/Official/DEV_FW/source/pocsag_pager/images/Lock_7x8.png new file mode 100644 index 000000000..f7c9ca2c7 Binary files /dev/null and b/Applications/Official/DEV_FW/source/pocsag_pager/images/Lock_7x8.png differ diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/images/Message_8x7.png b/Applications/Official/DEV_FW/source/pocsag_pager/images/Message_8x7.png new file mode 100644 index 000000000..642688cd5 Binary files /dev/null and b/Applications/Official/DEV_FW/source/pocsag_pager/images/Message_8x7.png differ diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/images/Pin_back_arrow_10x8.png b/Applications/Official/DEV_FW/source/pocsag_pager/images/Pin_back_arrow_10x8.png new file mode 100644 index 000000000..3bafabd14 Binary files /dev/null and b/Applications/Official/DEV_FW/source/pocsag_pager/images/Pin_back_arrow_10x8.png differ diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/images/Quest_7x8.png b/Applications/Official/DEV_FW/source/pocsag_pager/images/Quest_7x8.png new file mode 100644 index 000000000..6825247fb Binary files /dev/null and b/Applications/Official/DEV_FW/source/pocsag_pager/images/Quest_7x8.png differ diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/images/Scanning_123x52.png b/Applications/Official/DEV_FW/source/pocsag_pager/images/Scanning_123x52.png new file mode 100644 index 000000000..ec785948d Binary files /dev/null and b/Applications/Official/DEV_FW/source/pocsag_pager/images/Scanning_123x52.png differ diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/images/Unlock_7x8.png b/Applications/Official/DEV_FW/source/pocsag_pager/images/Unlock_7x8.png new file mode 100644 index 000000000..9d82b4daf Binary files /dev/null and b/Applications/Official/DEV_FW/source/pocsag_pager/images/Unlock_7x8.png differ diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/images/WarningDolphin_45x42.png b/Applications/Official/DEV_FW/source/pocsag_pager/images/WarningDolphin_45x42.png new file mode 100644 index 000000000..d766ffbb4 Binary files /dev/null and b/Applications/Official/DEV_FW/source/pocsag_pager/images/WarningDolphin_45x42.png differ diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/pocsag_pager_10px.png b/Applications/Official/DEV_FW/source/pocsag_pager/pocsag_pager_10px.png new file mode 100644 index 000000000..a5686c1c0 Binary files /dev/null and b/Applications/Official/DEV_FW/source/pocsag_pager/pocsag_pager_10px.png differ diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/pocsag_pager_app.c b/Applications/Official/DEV_FW/source/pocsag_pager/pocsag_pager_app.c new file mode 100644 index 000000000..3ac242304 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pocsag_pager/pocsag_pager_app.c @@ -0,0 +1,195 @@ +#include "pocsag_pager_app_i.h" + +#include +#include +#include +#include "protocols/protocol_items.h" + +static bool pocsag_pager_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + POCSAGPagerApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool pocsag_pager_app_back_event_callback(void* context) { + furi_assert(context); + POCSAGPagerApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void pocsag_pager_app_tick_event_callback(void* context) { + furi_assert(context); + POCSAGPagerApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +POCSAGPagerApp* pocsag_pager_app_alloc() { + POCSAGPagerApp* app = malloc(sizeof(POCSAGPagerApp)); + + // GUI + app->gui = furi_record_open(RECORD_GUI); + + // View Dispatcher + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&pocsag_pager_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, pocsag_pager_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, pocsag_pager_app_back_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, pocsag_pager_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, + POCSAGPagerViewVariableItemList, + variable_item_list_get_view(app->variable_item_list)); + + // SubMenu + app->submenu = submenu_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, POCSAGPagerViewSubmenu, submenu_get_view(app->submenu)); + + // Widget + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, POCSAGPagerViewWidget, widget_get_view(app->widget)); + + // Receiver + app->pcsg_receiver = pcsg_view_receiver_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + POCSAGPagerViewReceiver, + pcsg_view_receiver_get_view(app->pcsg_receiver)); + + // Receiver Info + app->pcsg_receiver_info = pcsg_view_receiver_info_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + POCSAGPagerViewReceiverInfo, + pcsg_view_receiver_info_get_view(app->pcsg_receiver_info)); + + //init setting + app->setting = subghz_setting_alloc(); + + //ToDo FIX file name setting + + subghz_setting_load(app->setting, EXT_PATH("pocsag/settings.txt")); + + //init Worker & Protocol & History + app->lock = PCSGLockOff; + app->txrx = malloc(sizeof(POCSAGPagerTxRx)); + app->txrx->preset = malloc(sizeof(SubGhzRadioPreset)); + app->txrx->preset->name = furi_string_alloc(); + + // Custom Presets load without using config file + + FlipperFormat* temp_fm_preset = flipper_format_string_alloc(); + flipper_format_write_string_cstr( + temp_fm_preset, + (const char*)"Custom_preset_data", + (const char*)"02 0D 0B 06 08 32 07 04 14 00 13 02 12 04 11 83 10 67 15 24 18 18 19 16 1D 91 1C 00 1B 07 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00"); + flipper_format_rewind(temp_fm_preset); + subghz_setting_load_custom_preset(app->setting, (const char*)"FM95", temp_fm_preset); + + flipper_format_free(temp_fm_preset); + + // custom presets loading - end + + pcsg_preset_init(app, "FM95", 439987500, NULL, 0); + + app->txrx->hopper_state = PCSGHopperStateOFF; + app->txrx->history = pcsg_history_alloc(); + app->txrx->worker = subghz_worker_alloc(); + app->txrx->environment = subghz_environment_alloc(); + subghz_environment_set_protocol_registry( + app->txrx->environment, (void*)&pocsag_pager_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, POCSAGPagerSceneStart); + + return app; +} + +void pocsag_pager_app_free(POCSAGPagerApp* app) { + furi_assert(app); + + //CC1101 off + pcsg_sleep(app); + + // Submenu + view_dispatcher_remove_view(app->view_dispatcher, POCSAGPagerViewSubmenu); + submenu_free(app->submenu); + + // Variable Item List + view_dispatcher_remove_view(app->view_dispatcher, POCSAGPagerViewVariableItemList); + variable_item_list_free(app->variable_item_list); + + // Widget + view_dispatcher_remove_view(app->view_dispatcher, POCSAGPagerViewWidget); + widget_free(app->widget); + + // Receiver + view_dispatcher_remove_view(app->view_dispatcher, POCSAGPagerViewReceiver); + pcsg_view_receiver_free(app->pcsg_receiver); + + // Receiver Info + view_dispatcher_remove_view(app->view_dispatcher, POCSAGPagerViewReceiverInfo); + pcsg_view_receiver_info_free(app->pcsg_receiver_info); + + //setting + subghz_setting_free(app->setting); + + //Worker & Protocol & History + subghz_receiver_free(app->txrx->receiver); + subghz_environment_free(app->txrx->environment); + pcsg_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 pocsag_pager_app(void* p) { + UNUSED(p); + POCSAGPagerApp* pocsag_pager_app = pocsag_pager_app_alloc(); + + view_dispatcher_run(pocsag_pager_app->view_dispatcher); + + pocsag_pager_app_free(pocsag_pager_app); + + return 0; +} diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/pocsag_pager_app_i.c b/Applications/Official/DEV_FW/source/pocsag_pager/pocsag_pager_app_i.c new file mode 100644 index 000000000..ba6e87c28 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pocsag_pager/pocsag_pager_app_i.c @@ -0,0 +1,141 @@ +#include "pocsag_pager_app_i.h" + +#define TAG "POCSAGPager" +#include + +void pcsg_preset_init( + void* context, + const char* preset_name, + uint32_t frequency, + uint8_t* preset_data, + size_t preset_data_size) { + furi_assert(context); + POCSAGPagerApp* 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; +} + +void pcsg_get_frequency_modulation( + POCSAGPagerApp* 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 pcsg_begin(POCSAGPagerApp* 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 = PCSGTxRxStateIDLE; +} + +uint32_t pcsg_rx(POCSAGPagerApp* app, uint32_t frequency) { + furi_assert(app); + if(!furi_hal_subghz_is_frequency_valid(frequency)) { + furi_crash("POCSAGPager: Incorrect RX frequency."); + } + furi_assert( + app->txrx->txrx_state != PCSGTxRxStateRx && app->txrx->txrx_state != PCSGTxRxStateSleep); + + 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 = PCSGTxRxStateRx; + return value; +} + +void pcsg_idle(POCSAGPagerApp* app) { + furi_assert(app); + furi_assert(app->txrx->txrx_state != PCSGTxRxStateSleep); + furi_hal_subghz_idle(); + app->txrx->txrx_state = PCSGTxRxStateIDLE; +} + +void pcsg_rx_end(POCSAGPagerApp* app) { + furi_assert(app); + furi_assert(app->txrx->txrx_state == PCSGTxRxStateRx); + 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 = PCSGTxRxStateIDLE; +} + +void pcsg_sleep(POCSAGPagerApp* app) { + furi_assert(app); + furi_hal_subghz_sleep(); + app->txrx->txrx_state = PCSGTxRxStateSleep; +} + +void pcsg_hopper_update(POCSAGPagerApp* app) { + furi_assert(app); + + switch(app->txrx->hopper_state) { + case PCSGHopperStateOFF: + return; + break; + case PCSGHopperStatePause: + return; + break; + case PCSGHopperStateRSSITimeOut: + if(app->txrx->hopper_timeout != 0) { + app->txrx->hopper_timeout--; + return; + } + break; + default: + break; + } + float rssi = -127.0f; + if(app->txrx->hopper_state != PCSGHopperStateRSSITimeOut) { + // 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 = PCSGHopperStateRSSITimeOut; + return; + } + } else { + app->txrx->hopper_state = PCSGHopperStateRunnig; + } + // 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 == PCSGTxRxStateRx) { + pcsg_rx_end(app); + }; + if(app->txrx->txrx_state == PCSGTxRxStateIDLE) { + subghz_receiver_reset(app->txrx->receiver); + app->txrx->preset->frequency = + subghz_setting_get_hopper_frequency(app->setting, app->txrx->hopper_idx_frequency); + pcsg_rx(app, app->txrx->preset->frequency); + } +} diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/pocsag_pager_app_i.h b/Applications/Official/DEV_FW/source/pocsag_pager/pocsag_pager_app_i.h new file mode 100644 index 000000000..8a0426dc5 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pocsag_pager/pocsag_pager_app_i.h @@ -0,0 +1,72 @@ +#pragma once + +#include "helpers/pocsag_pager_types.h" + +#include "scenes/pocsag_pager_scene.h" +#include +#include +#include +#include +#include +#include +#include +#include "views/pocsag_pager_receiver.h" +#include "views/pocsag_pager_receiver_info.h" +#include "pocsag_pager_history.h" + +#include +#include +#include +#include +#include + +typedef struct POCSAGPagerApp POCSAGPagerApp; + +struct POCSAGPagerTxRx { + SubGhzWorker* worker; + + SubGhzEnvironment* environment; + SubGhzReceiver* receiver; + SubGhzRadioPreset* preset; + PCSGHistory* history; + uint16_t idx_menu_chosen; + PCSGTxRxState txrx_state; + PCSGHopperState hopper_state; + uint8_t hopper_timeout; + uint8_t hopper_idx_frequency; + PCSGRxKeyState rx_key_state; +}; + +typedef struct POCSAGPagerTxRx POCSAGPagerTxRx; + +struct POCSAGPagerApp { + Gui* gui; + ViewDispatcher* view_dispatcher; + POCSAGPagerTxRx* txrx; + SceneManager* scene_manager; + NotificationApp* notifications; + VariableItemList* variable_item_list; + Submenu* submenu; + Widget* widget; + PCSGReceiver* pcsg_receiver; + PCSGReceiverInfo* pcsg_receiver_info; + PCSGLock lock; + SubGhzSetting* setting; +}; + +void pcsg_preset_init( + void* context, + const char* preset_name, + uint32_t frequency, + uint8_t* preset_data, + size_t preset_data_size); +void pcsg_get_frequency_modulation( + POCSAGPagerApp* app, + FuriString* frequency, + FuriString* modulation); +void pcsg_begin(POCSAGPagerApp* app, uint8_t* preset_data); +uint32_t pcsg_rx(POCSAGPagerApp* app, uint32_t frequency); +void pcsg_idle(POCSAGPagerApp* app); +void pcsg_rx_end(POCSAGPagerApp* app); +void pcsg_sleep(POCSAGPagerApp* app); +void pcsg_hopper_update(POCSAGPagerApp* app); diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/pocsag_pager_history.c b/Applications/Official/DEV_FW/source/pocsag_pager/pocsag_pager_history.c new file mode 100644 index 000000000..d5f97b571 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pocsag_pager/pocsag_pager_history.c @@ -0,0 +1,223 @@ +#include "pocsag_pager_history.h" +#include +#include +#include +#include "protocols/pcsg_generic.h" + +#include + +#define PCSG_HISTORY_MAX 50 +#define TAG "PCSGHistory" + +typedef struct { + FuriString* item_str; + FlipperFormat* flipper_string; + uint8_t type; + SubGhzRadioPreset* preset; +} PCSGHistoryItem; + +ARRAY_DEF(PCSGHistoryItemArray, PCSGHistoryItem, M_POD_OPLIST) + +#define M_OPL_PCSGHistoryItemArray_t() ARRAY_OPLIST(PCSGHistoryItemArray, M_POD_OPLIST) + +typedef struct { + PCSGHistoryItemArray_t data; +} PCSGHistoryStruct; + +struct PCSGHistory { + uint32_t last_update_timestamp; + uint16_t last_index_write; + uint8_t code_last_hash_data; + FuriString* tmp_string; + PCSGHistoryStruct* history; +}; + +PCSGHistory* pcsg_history_alloc(void) { + PCSGHistory* instance = malloc(sizeof(PCSGHistory)); + instance->tmp_string = furi_string_alloc(); + instance->history = malloc(sizeof(PCSGHistoryStruct)); + PCSGHistoryItemArray_init(instance->history->data); + return instance; +} + +void pcsg_history_free(PCSGHistory* instance) { + furi_assert(instance); + furi_string_free(instance->tmp_string); + for + M_EACH(item, instance->history->data, PCSGHistoryItemArray_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; + } + PCSGHistoryItemArray_clear(instance->history->data); + free(instance->history); + free(instance); +} + +uint32_t pcsg_history_get_frequency(PCSGHistory* instance, uint16_t idx) { + furi_assert(instance); + PCSGHistoryItem* item = PCSGHistoryItemArray_get(instance->history->data, idx); + return item->preset->frequency; +} + +SubGhzRadioPreset* pcsg_history_get_radio_preset(PCSGHistory* instance, uint16_t idx) { + furi_assert(instance); + PCSGHistoryItem* item = PCSGHistoryItemArray_get(instance->history->data, idx); + return item->preset; +} + +const char* pcsg_history_get_preset(PCSGHistory* instance, uint16_t idx) { + furi_assert(instance); + PCSGHistoryItem* item = PCSGHistoryItemArray_get(instance->history->data, idx); + return furi_string_get_cstr(item->preset->name); +} + +void pcsg_history_reset(PCSGHistory* instance) { + furi_assert(instance); + furi_string_reset(instance->tmp_string); + for + M_EACH(item, instance->history->data, PCSGHistoryItemArray_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; + } + PCSGHistoryItemArray_reset(instance->history->data); + instance->last_index_write = 0; + instance->code_last_hash_data = 0; +} + +uint16_t pcsg_history_get_item(PCSGHistory* instance) { + furi_assert(instance); + return instance->last_index_write; +} + +uint8_t pcsg_history_get_type_protocol(PCSGHistory* instance, uint16_t idx) { + furi_assert(instance); + PCSGHistoryItem* item = PCSGHistoryItemArray_get(instance->history->data, idx); + return item->type; +} + +const char* pcsg_history_get_protocol_name(PCSGHistory* instance, uint16_t idx) { + furi_assert(instance); + PCSGHistoryItem* item = PCSGHistoryItemArray_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* pcsg_history_get_raw_data(PCSGHistory* instance, uint16_t idx) { + furi_assert(instance); + PCSGHistoryItem* item = PCSGHistoryItemArray_get(instance->history->data, idx); + if(item->flipper_string) { + return item->flipper_string; + } else { + return NULL; + } +} +bool pcsg_history_get_text_space_left(PCSGHistory* instance, FuriString* output) { + furi_assert(instance); + if(instance->last_index_write == PCSG_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, PCSG_HISTORY_MAX); + return false; +} + +void pcsg_history_get_text_item_menu(PCSGHistory* instance, FuriString* output, uint16_t idx) { + PCSGHistoryItem* item = PCSGHistoryItemArray_get(instance->history->data, idx); + furi_string_set(output, item->item_str); +} + +PCSGHistoryStateAddKey + pcsg_history_add_to_history(PCSGHistory* instance, void* context, SubGhzRadioPreset* preset) { + furi_assert(instance); + furi_assert(context); + + if(instance->last_index_write >= PCSG_HISTORY_MAX) return PCSGHistoryStateAddKeyOverflow; + + 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 PCSGHistoryStateAddKeyTimeOut; + } + + 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(); + subghz_protocol_decoder_base_serialize(decoder_base, fff, preset); + + do { + if(!flipper_format_rewind(fff)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + } while(false); + flipper_format_free(fff); + + PCSGHistoryItem* item = PCSGHistoryItemArray_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->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; + } + FuriString* temp_ric = furi_string_alloc(); + if(!flipper_format_read_string(item->flipper_string, "Ric", temp_ric)) { + FURI_LOG_E(TAG, "Missing Ric"); + break; + } + + FuriString* temp_message = furi_string_alloc(); + if(!flipper_format_read_string(item->flipper_string, "Message", temp_message)) { + FURI_LOG_E(TAG, "Missing Message"); + break; + } + + furi_string_printf( + item->item_str, + "%s%s", + furi_string_get_cstr(temp_ric), + furi_string_get_cstr(temp_message)); + + furi_string_free(temp_message); + furi_string_free(temp_ric); + + } while(false); + instance->last_index_write++; + return PCSGHistoryStateAddKeyNewDada; + + return PCSGHistoryStateAddKeyUnknown; +} diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/pocsag_pager_history.h b/Applications/Official/DEV_FW/source/pocsag_pager/pocsag_pager_history.h new file mode 100644 index 000000000..7528fcc29 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pocsag_pager/pocsag_pager_history.h @@ -0,0 +1,112 @@ + +#pragma once + +#include +#include +#include +#include +#include + +typedef struct PCSGHistory PCSGHistory; + +/** History state add key */ +typedef enum { + PCSGHistoryStateAddKeyUnknown, + PCSGHistoryStateAddKeyTimeOut, + PCSGHistoryStateAddKeyNewDada, + PCSGHistoryStateAddKeyUpdateData, + PCSGHistoryStateAddKeyOverflow, +} PCSGHistoryStateAddKey; + +/** Allocate PCSGHistory + * + * @return PCSGHistory* + */ +PCSGHistory* pcsg_history_alloc(void); + +/** Free PCSGHistory + * + * @param instance - PCSGHistory instance + */ +void pcsg_history_free(PCSGHistory* instance); + +/** Clear history + * + * @param instance - PCSGHistory instance + */ +void pcsg_history_reset(PCSGHistory* instance); + +/** Get frequency to history[idx] + * + * @param instance - PCSGHistory instance + * @param idx - record index + * @return frequency - frequency Hz + */ +uint32_t pcsg_history_get_frequency(PCSGHistory* instance, uint16_t idx); + +SubGhzRadioPreset* pcsg_history_get_radio_preset(PCSGHistory* instance, uint16_t idx); + +/** Get preset to history[idx] + * + * @param instance - PCSGHistory instance + * @param idx - record index + * @return preset - preset name + */ +const char* pcsg_history_get_preset(PCSGHistory* instance, uint16_t idx); + +/** Get history index write + * + * @param instance - PCSGHistory instance + * @return idx - current record index + */ +uint16_t pcsg_history_get_item(PCSGHistory* instance); + +/** Get type protocol to history[idx] + * + * @param instance - PCSGHistory instance + * @param idx - record index + * @return type - type protocol + */ +uint8_t pcsg_history_get_type_protocol(PCSGHistory* instance, uint16_t idx); + +/** Get name protocol to history[idx] + * + * @param instance - PCSGHistory instance + * @param idx - record index + * @return name - const char* name protocol + */ +const char* pcsg_history_get_protocol_name(PCSGHistory* instance, uint16_t idx); + +/** Get string item menu to history[idx] + * + * @param instance - PCSGHistory instance + * @param output - FuriString* output + * @param idx - record index + */ +void pcsg_history_get_text_item_menu(PCSGHistory* instance, FuriString* output, uint16_t idx); + +/** Get string the remaining number of records to history + * + * @param instance - PCSGHistory instance + * @param output - FuriString* output + * @return bool - is FUUL + */ +bool pcsg_history_get_text_space_left(PCSGHistory* instance, FuriString* output); + +/** Add protocol to history + * + * @param instance - PCSGHistory instance + * @param context - SubGhzProtocolCommon context + * @param preset - SubGhzRadioPreset preset + * @return PCSGHistoryStateAddKey; + */ +PCSGHistoryStateAddKey + pcsg_history_add_to_history(PCSGHistory* instance, void* context, SubGhzRadioPreset* preset); + +/** Get SubGhzProtocolCommonLoad to load into the protocol decoder bin data + * + * @param instance - PCSGHistory instance + * @param idx - record index + * @return SubGhzProtocolCommonLoad* + */ +FlipperFormat* pcsg_history_get_raw_data(PCSGHistory* instance, uint16_t idx); diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/protocols/pcsg_generic.c b/Applications/Official/DEV_FW/source/pocsag_pager/protocols/pcsg_generic.c new file mode 100644 index 000000000..890ed43d7 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pocsag_pager/protocols/pcsg_generic.c @@ -0,0 +1,123 @@ +#include "pcsg_generic.h" +#include +#include +#include "../helpers/pocsag_pager_types.h" + +#define TAG "PCSGBlockGeneric" + +void pcsg_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 pcsg_block_generic_serialize( + PCSGBlockGeneric* 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, PCSG_KEY_FILE_TYPE, PCSG_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; + } + + pcsg_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; + } + + if(!flipper_format_write_string(flipper_format, "Ric", instance->result_ric)) { + FURI_LOG_E(TAG, "Unable to add Ric"); + break; + } + + if(!flipper_format_write_string(flipper_format, "Message", instance->result_msg)) { + FURI_LOG_E(TAG, "Unable to add Message"); + break; + } + + res = true; + } while(false); + furi_string_free(temp_str); + return res; +} + +bool pcsg_block_generic_deserialize(PCSGBlockGeneric* instance, FlipperFormat* flipper_format) { + furi_assert(instance); + bool res = false; + FuriString* temp_data = furi_string_alloc(); + FuriString* temp_data2 = furi_string_alloc(); + + do { + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + if(!flipper_format_read_string(flipper_format, "Ric", temp_data2)) { + FURI_LOG_E(TAG, "Missing Ric"); + break; + } + if(instance->result_ric != NULL) { + furi_string_set(instance->result_ric, temp_data2); + } else { + instance->result_ric = furi_string_alloc_set(temp_data2); + } + + if(!flipper_format_read_string(flipper_format, "Message", temp_data)) { + FURI_LOG_E(TAG, "Missing Message"); + break; + } + if(instance->result_msg != NULL) { + furi_string_set(instance->result_msg, temp_data); + } else { + instance->result_msg = furi_string_alloc_set(temp_data); + } + + res = true; + } while(0); + + furi_string_free(temp_data); + furi_string_free(temp_data2); + + return res; +} diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/protocols/pcsg_generic.h b/Applications/Official/DEV_FW/source/pocsag_pager/protocols/pcsg_generic.h new file mode 100644 index 000000000..ff925b6c9 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pocsag_pager/protocols/pcsg_generic.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include + +#include +#include "furi.h" +#include "furi_hal.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct PCSGBlockGeneric PCSGBlockGeneric; + +struct PCSGBlockGeneric { + const char* protocol_name; + FuriString* result_ric; + FuriString* result_msg; +}; + +/** + * Get name preset. + * @param preset_name name preset + * @param preset_str Output name preset + */ +void pcsg_block_generic_get_preset_name(const char* preset_name, FuriString* preset_str); + +/** + * Serialize data PCSGBlockGeneric. + * @param instance Pointer to a PCSGBlockGeneric 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 pcsg_block_generic_serialize( + PCSGBlockGeneric* instance, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data PCSGBlockGeneric. + * @param instance Pointer to a PCSGBlockGeneric instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool pcsg_block_generic_deserialize(PCSGBlockGeneric* instance, FlipperFormat* flipper_format); + +float pcsg_block_generic_fahrenheit_to_celsius(float fahrenheit); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/protocols/pocsag.c b/Applications/Official/DEV_FW/source/pocsag_pager/protocols/pocsag.c new file mode 100644 index 000000000..69d09d554 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pocsag_pager/protocols/pocsag.c @@ -0,0 +1,371 @@ +#include "pocsag.h" + +#include +#include +#include + +#define TAG "POCSAG" + +static const SubGhzBlockConst pocsag_const = { + .te_short = 833, + .te_delta = 100, +}; + +// Minimal amount of sync bits (interleaving zeros and ones) +#define POCSAG_MIN_SYNC_BITS 32 +#define POCSAG_CW_BITS 32 +#define POCSAG_CW_MASK 0xFFFFFFFF +#define POCSAG_FRAME_SYNC_CODE 0x7CD215D8 +#define POCSAG_IDLE_CODE_WORD 0x7A89C197 + +#define POCSAG_FUNC_NUM 0 +#define POCSAG_FUNC_ALERT1 1 +#define POCSAG_FUNC_ALERT2 2 +#define POCSAG_FUNC_ALPHANUM 3 + +static const char* func_msg[] = {"\e#Num:\e# ", "\e#Alert\e#", "\e#Alert:\e# ", "\e#Msg:\e# "}; +static const char* bcd_chars = "*U -)("; + +struct SubGhzProtocolDecoderPocsag { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + PCSGBlockGeneric generic; + + uint8_t codeword_idx; + uint32_t ric; + uint8_t func; + + // partially decoded character + uint8_t char_bits; + uint8_t char_data; + + // message being decoded + FuriString* msg; + + // Done messages, ready to be serialized/deserialized + FuriString* done_msg; +}; + +typedef struct SubGhzProtocolDecoderPocsag SubGhzProtocolDecoderPocsag; + +typedef enum { + PocsagDecoderStepReset = 0, + PocsagDecoderStepFoundSync, + PocsagDecoderStepFoundPreamble, + PocsagDecoderStepMessage, +} PocsagDecoderStep; + +void* subghz_protocol_decoder_pocsag_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + + SubGhzProtocolDecoderPocsag* instance = malloc(sizeof(SubGhzProtocolDecoderPocsag)); + instance->base.protocol = &subghz_protocol_pocsag; + instance->generic.protocol_name = instance->base.protocol->name; + instance->msg = furi_string_alloc(); + instance->done_msg = furi_string_alloc(); + if(instance->generic.result_msg == NULL) { + instance->generic.result_msg = furi_string_alloc(); + } + if(instance->generic.result_ric == NULL) { + instance->generic.result_ric = furi_string_alloc(); + } + + return instance; +} + +void subghz_protocol_decoder_pocsag_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderPocsag* instance = context; + furi_string_free(instance->msg); + furi_string_free(instance->done_msg); + free(instance); +} + +void subghz_protocol_decoder_pocsag_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderPocsag* instance = context; + + instance->decoder.parser_step = PocsagDecoderStepReset; + instance->decoder.decode_data = 0UL; + instance->decoder.decode_count_bit = 0; + instance->codeword_idx = 0; + instance->char_bits = 0; + instance->char_data = 0; + furi_string_reset(instance->msg); + furi_string_reset(instance->done_msg); + furi_string_reset(instance->generic.result_msg); + furi_string_reset(instance->generic.result_ric); +} + +static void pocsag_decode_address_word(SubGhzProtocolDecoderPocsag* instance, uint32_t data) { + instance->ric = (data >> 13); + instance->ric = (instance->ric << 3) | (instance->codeword_idx >> 1); + instance->func = (data >> 11) & 0b11; +} + +static bool decode_message_alphanumeric(SubGhzProtocolDecoderPocsag* instance, uint32_t data) { + for(uint8_t i = 0; i < 20; i++) { + instance->char_data >>= 1; + if(data & (1 << 30)) { + instance->char_data |= 1 << 6; + } + instance->char_bits++; + if(instance->char_bits == 7) { + if(instance->char_data == 0) return false; + furi_string_push_back(instance->msg, instance->char_data); + instance->char_data = 0; + instance->char_bits = 0; + } + data <<= 1; + } + return true; +} + +static void decode_message_numeric(SubGhzProtocolDecoderPocsag* instance, uint32_t data) { + // 5 groups with 4 bits each + uint8_t val; + for(uint8_t i = 0; i < 5; i++) { + val = (data >> (27 - i * 4)) & 0b1111; + // reverse the order of 4 bits + val = (val & 0x5) << 1 | (val & 0xA) >> 1; + val = (val & 0x3) << 2 | (val & 0xC) >> 2; + + if(val <= 9) + val += '0'; + else + val = bcd_chars[val - 10]; + furi_string_push_back(instance->msg, val); + } +} + +// decode message word, maintaining instance state for partial decoding. Return true if more data +// might follow or false if end of message reached. +static bool pocsag_decode_message_word(SubGhzProtocolDecoderPocsag* instance, uint32_t data) { + switch(instance->func) { + case POCSAG_FUNC_ALERT2: + case POCSAG_FUNC_ALPHANUM: + return decode_message_alphanumeric(instance, data); + + case POCSAG_FUNC_NUM: + decode_message_numeric(instance, data); + return true; + } + return false; +} + +// Function called when current message got decoded, but other messages might follow +static void pocsag_message_done(SubGhzProtocolDecoderPocsag* instance) { + // append the message to the long-term storage string + furi_string_cat_printf( + instance->generic.result_ric, "\e#RIC: %" PRIu32 "\e# | ", instance->ric); + furi_string_cat_str(instance->generic.result_ric, func_msg[instance->func]); + if(instance->func != POCSAG_FUNC_ALERT1) { + furi_string_cat(instance->done_msg, instance->msg); + } + furi_string_cat_str(instance->done_msg, " "); + + furi_string_cat(instance->generic.result_msg, instance->done_msg); + + // reset the state + instance->char_bits = 0; + instance->char_data = 0; + furi_string_reset(instance->msg); +} + +void subghz_protocol_decoder_pocsag_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderPocsag* instance = context; + + // reset state - waiting for 32 bits of interleaving 1s and 0s + if(instance->decoder.parser_step == PocsagDecoderStepReset) { + if(DURATION_DIFF(duration, pocsag_const.te_short) < pocsag_const.te_delta) { + // POCSAG signals are inverted + subghz_protocol_blocks_add_bit(&instance->decoder, !level); + + if(instance->decoder.decode_count_bit == POCSAG_MIN_SYNC_BITS) { + instance->decoder.parser_step = PocsagDecoderStepFoundSync; + } + } else if(instance->decoder.decode_count_bit > 0) { + subghz_protocol_decoder_pocsag_reset(context); + } + return; + } + + int bits_count = duration / pocsag_const.te_short; + uint32_t extra = duration - pocsag_const.te_short * bits_count; + + if(DURATION_DIFF(extra, pocsag_const.te_short) < pocsag_const.te_delta) + bits_count++; + else if(extra > pocsag_const.te_delta) { + // in non-reset state we faced the error signal - we reached the end of the packet, flush data + if(furi_string_size(instance->done_msg) > 0) { + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + subghz_protocol_decoder_pocsag_reset(context); + return; + } + + uint32_t codeword; + + // handle state machine for every incoming bit + while(bits_count-- > 0) { + subghz_protocol_blocks_add_bit(&instance->decoder, !level); + + switch(instance->decoder.parser_step) { + case PocsagDecoderStepFoundSync: + if((instance->decoder.decode_data & POCSAG_CW_MASK) == POCSAG_FRAME_SYNC_CODE) { + instance->decoder.parser_step = PocsagDecoderStepFoundPreamble; + instance->decoder.decode_count_bit = 0; + instance->decoder.decode_data = 0UL; + } + break; + case PocsagDecoderStepFoundPreamble: + // handle codewords + if(instance->decoder.decode_count_bit == POCSAG_CW_BITS) { + codeword = (uint32_t)(instance->decoder.decode_data & POCSAG_CW_MASK); + switch(codeword) { + case POCSAG_IDLE_CODE_WORD: + instance->codeword_idx++; + break; + case POCSAG_FRAME_SYNC_CODE: + instance->codeword_idx = 0; + break; + default: + // Here we expect only address messages + if(codeword >> 31 == 0) { + pocsag_decode_address_word(instance, codeword); + instance->decoder.parser_step = PocsagDecoderStepMessage; + } + instance->codeword_idx++; + } + instance->decoder.decode_count_bit = 0; + instance->decoder.decode_data = 0UL; + } + break; + + case PocsagDecoderStepMessage: + if(instance->decoder.decode_count_bit == POCSAG_CW_BITS) { + codeword = (uint32_t)(instance->decoder.decode_data & POCSAG_CW_MASK); + switch(codeword) { + case POCSAG_IDLE_CODE_WORD: + // Idle during the message stops the message + instance->codeword_idx++; + instance->decoder.parser_step = PocsagDecoderStepFoundPreamble; + pocsag_message_done(instance); + break; + case POCSAG_FRAME_SYNC_CODE: + instance->codeword_idx = 0; + break; + default: + // In this state, both address and message words can arrive + if(codeword >> 31 == 0) { + pocsag_message_done(instance); + pocsag_decode_address_word(instance, codeword); + } else { + if(!pocsag_decode_message_word(instance, codeword)) { + instance->decoder.parser_step = PocsagDecoderStepFoundPreamble; + pocsag_message_done(instance); + } + } + instance->codeword_idx++; + } + instance->decoder.decode_count_bit = 0; + instance->decoder.decode_data = 0UL; + } + break; + } + } +} + +uint8_t subghz_protocol_decoder_pocsag_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderPocsag* instance = context; + uint8_t hash = 0; + for(size_t i = 0; i < furi_string_size(instance->done_msg); i++) + hash ^= furi_string_get_char(instance->done_msg, i); + return hash; +} + +bool subghz_protocol_decoder_pocsag_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderPocsag* instance = context; + uint32_t msg_len; + + if(!pcsg_block_generic_serialize(&instance->generic, flipper_format, preset)) return false; + + msg_len = furi_string_size(instance->done_msg); + if(!flipper_format_write_uint32(flipper_format, "MsgLen", &msg_len, 1)) { + FURI_LOG_E(TAG, "Error adding MsgLen"); + return false; + } + + uint8_t* s = (uint8_t*)furi_string_get_cstr(instance->done_msg); + if(!flipper_format_write_hex(flipper_format, "Msg", s, msg_len)) { + FURI_LOG_E(TAG, "Error adding Msg"); + return false; + } + return true; +} + +bool subghz_protocol_decoder_pocsag_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderPocsag* instance = context; + bool ret = false; + uint32_t msg_len; + uint8_t* buf; + + do { + if(!pcsg_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + + if(!flipper_format_read_uint32(flipper_format, "MsgLen", &msg_len, 1)) { + FURI_LOG_E(TAG, "Missing MsgLen"); + break; + } + + buf = malloc(msg_len); + if(!flipper_format_read_hex(flipper_format, "Msg", buf, msg_len)) { + FURI_LOG_E(TAG, "Missing Msg"); + free(buf); + break; + } + furi_string_set_strn(instance->done_msg, (const char*)buf, msg_len); + free(buf); + + ret = true; + } while(false); + return ret; +} + +void subhz_protocol_decoder_pocsag_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderPocsag* instance = context; + furi_string_cat_printf(output, "%s\r\n", instance->generic.protocol_name); + furi_string_cat(output, instance->done_msg); +} + +const SubGhzProtocolDecoder subghz_protocol_pocsag_decoder = { + .alloc = subghz_protocol_decoder_pocsag_alloc, + .free = subghz_protocol_decoder_pocsag_free, + .reset = subghz_protocol_decoder_pocsag_reset, + .feed = subghz_protocol_decoder_pocsag_feed, + .get_hash_data = subghz_protocol_decoder_pocsag_get_hash_data, + .serialize = subghz_protocol_decoder_pocsag_serialize, + .deserialize = subghz_protocol_decoder_pocsag_deserialize, + .get_string = subhz_protocol_decoder_pocsag_get_string, +}; + +const SubGhzProtocol subghz_protocol_pocsag = { + .name = SUBGHZ_PROTOCOL_POCSAG_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Save | + SubGhzProtocolFlag_Load, + + .decoder = &subghz_protocol_pocsag_decoder, +}; diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/protocols/pocsag.h b/Applications/Official/DEV_FW/source/pocsag_pager/protocols/pocsag.h new file mode 100644 index 000000000..559fa3918 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pocsag_pager/protocols/pocsag.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#include +#include +#include +#include "pcsg_generic.h" +#include + +#define SUBGHZ_PROTOCOL_POCSAG_NAME "POCSAG" + +extern const SubGhzProtocol subghz_protocol_pocsag; diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/protocols/protocol_items.c b/Applications/Official/DEV_FW/source/pocsag_pager/protocols/protocol_items.c new file mode 100644 index 000000000..7e6ebebbd --- /dev/null +++ b/Applications/Official/DEV_FW/source/pocsag_pager/protocols/protocol_items.c @@ -0,0 +1,9 @@ +#include "protocol_items.h" + +const SubGhzProtocol* pocsag_pager_protocol_registry_items[] = { + &subghz_protocol_pocsag, +}; + +const SubGhzProtocolRegistry pocsag_pager_protocol_registry = { + .items = pocsag_pager_protocol_registry_items, + .size = COUNT_OF(pocsag_pager_protocol_registry_items)}; \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/protocols/protocol_items.h b/Applications/Official/DEV_FW/source/pocsag_pager/protocols/protocol_items.h new file mode 100644 index 000000000..3981cd2e3 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pocsag_pager/protocols/protocol_items.h @@ -0,0 +1,6 @@ +#pragma once +#include "../pocsag_pager_app_i.h" + +#include "pocsag.h" + +extern const SubGhzProtocolRegistry pocsag_pager_protocol_registry; diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/scenes/pocsag_pager_receiver.c b/Applications/Official/DEV_FW/source/pocsag_pager/scenes/pocsag_pager_receiver.c new file mode 100644 index 000000000..3f41b9b5d --- /dev/null +++ b/Applications/Official/DEV_FW/source/pocsag_pager/scenes/pocsag_pager_receiver.c @@ -0,0 +1,207 @@ +#include "../pocsag_pager_app_i.h" +#include "../views/pocsag_pager_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 pocsag_pager_scene_receiver_update_statusbar(void* context) { + POCSAGPagerApp* app = context; + FuriString* history_stat_str; + history_stat_str = furi_string_alloc(); + if(!pcsg_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(); + + pcsg_get_frequency_modulation(app, frequency_str, modulation_str); + + pcsg_view_receiver_add_data_statusbar( + app->pcsg_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 { + pcsg_view_receiver_add_data_statusbar( + app->pcsg_receiver, furi_string_get_cstr(history_stat_str), "", ""); + } + furi_string_free(history_stat_str); +} + +void pocsag_pager_scene_receiver_callback(PCSGCustomEvent event, void* context) { + furi_assert(context); + POCSAGPagerApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +static void pocsag_pager_scene_receiver_add_to_history_callback( + SubGhzReceiver* receiver, + SubGhzProtocolDecoderBase* decoder_base, + void* context) { + furi_assert(context); + POCSAGPagerApp* app = context; + FuriString* str_buff; + str_buff = furi_string_alloc(); + + if(pcsg_history_add_to_history(app->txrx->history, decoder_base, app->txrx->preset) == + PCSGHistoryStateAddKeyNewDada) { + furi_string_reset(str_buff); + + pcsg_history_get_text_item_menu( + app->txrx->history, str_buff, pcsg_history_get_item(app->txrx->history) - 1); + pcsg_view_receiver_add_item_to_menu( + app->pcsg_receiver, + furi_string_get_cstr(str_buff), + pcsg_history_get_type_protocol( + app->txrx->history, pcsg_history_get_item(app->txrx->history) - 1)); + + pocsag_pager_scene_receiver_update_statusbar(app); + notification_message(app->notifications, &sequence_blink_green_10); + if(app->lock != PCSGLockOn) { + 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 = PCSGRxKeyStateAddKey; +} + +void pocsag_pager_scene_receiver_on_enter(void* context) { + POCSAGPagerApp* app = context; + + FuriString* str_buff; + str_buff = furi_string_alloc(); + + if(app->txrx->rx_key_state == PCSGRxKeyStateIDLE) { + pcsg_preset_init(app, "FM95", 439987500, NULL, 0); + pcsg_history_reset(app->txrx->history); + app->txrx->rx_key_state = PCSGRxKeyStateStart; + } + + pcsg_view_receiver_set_lock(app->pcsg_receiver, app->lock); + + //Load history to receiver + pcsg_view_receiver_exit(app->pcsg_receiver); + for(uint8_t i = 0; i < pcsg_history_get_item(app->txrx->history); i++) { + furi_string_reset(str_buff); + pcsg_history_get_text_item_menu(app->txrx->history, str_buff, i); + pcsg_view_receiver_add_item_to_menu( + app->pcsg_receiver, + furi_string_get_cstr(str_buff), + pcsg_history_get_type_protocol(app->txrx->history, i)); + app->txrx->rx_key_state = PCSGRxKeyStateAddKey; + } + furi_string_free(str_buff); + pocsag_pager_scene_receiver_update_statusbar(app); + + pcsg_view_receiver_set_callback(app->pcsg_receiver, pocsag_pager_scene_receiver_callback, app); + subghz_receiver_set_rx_callback( + app->txrx->receiver, pocsag_pager_scene_receiver_add_to_history_callback, app); + + if(app->txrx->txrx_state == PCSGTxRxStateRx) { + pcsg_rx_end(app); + }; + if((app->txrx->txrx_state == PCSGTxRxStateIDLE) || + (app->txrx->txrx_state == PCSGTxRxStateSleep)) { + pcsg_begin( + app, + subghz_setting_get_preset_data_by_name( + app->setting, furi_string_get_cstr(app->txrx->preset->name))); + + pcsg_rx(app, app->txrx->preset->frequency); + } + + pcsg_view_receiver_set_idx_menu(app->pcsg_receiver, app->txrx->idx_menu_chosen); + view_dispatcher_switch_to_view(app->view_dispatcher, POCSAGPagerViewReceiver); +} + +bool pocsag_pager_scene_receiver_on_event(void* context, SceneManagerEvent event) { + POCSAGPagerApp* app = context; + bool consumed = false; + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case PCSGCustomEventViewReceiverBack: + // Stop CC1101 Rx + if(app->txrx->txrx_state == PCSGTxRxStateRx) { + pcsg_rx_end(app); + pcsg_sleep(app); + }; + app->txrx->hopper_state = PCSGHopperStateOFF; + app->txrx->idx_menu_chosen = 0; + subghz_receiver_set_rx_callback(app->txrx->receiver, NULL, app); + + app->txrx->rx_key_state = PCSGRxKeyStateIDLE; + pcsg_preset_init(app, "FM95", 439987500, NULL, 0); + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, POCSAGPagerSceneStart); + consumed = true; + break; + case PCSGCustomEventViewReceiverOK: + app->txrx->idx_menu_chosen = pcsg_view_receiver_get_idx_menu(app->pcsg_receiver); + scene_manager_next_scene(app->scene_manager, POCSAGPagerSceneReceiverInfo); + consumed = true; + break; + case PCSGCustomEventViewReceiverConfig: + app->txrx->idx_menu_chosen = pcsg_view_receiver_get_idx_menu(app->pcsg_receiver); + scene_manager_next_scene(app->scene_manager, POCSAGPagerSceneReceiverConfig); + consumed = true; + break; + case PCSGCustomEventViewReceiverOffDisplay: + notification_message(app->notifications, &sequence_display_backlight_off); + consumed = true; + break; + case PCSGCustomEventViewReceiverUnlock: + app->lock = PCSGLockOff; + consumed = true; + break; + default: + break; + } + } else if(event.type == SceneManagerEventTypeTick) { + if(app->txrx->hopper_state != PCSGHopperStateOFF) { + pcsg_hopper_update(app); + pocsag_pager_scene_receiver_update_statusbar(app); + } + if(app->txrx->txrx_state == PCSGTxRxStateRx) { + notification_message(app->notifications, &sequence_blink_cyan_10); + } + } + return consumed; +} + +void pocsag_pager_scene_receiver_on_exit(void* context) { + UNUSED(context); +} diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/scenes/pocsag_pager_scene.c b/Applications/Official/DEV_FW/source/pocsag_pager/scenes/pocsag_pager_scene.c new file mode 100644 index 000000000..4644d99c0 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pocsag_pager/scenes/pocsag_pager_scene.c @@ -0,0 +1,30 @@ +#include "../pocsag_pager_app_i.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const pocsag_pager_scene_on_enter_handlers[])(void*) = { +#include "pocsag_pager_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 pocsag_pager_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "pocsag_pager_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 pocsag_pager_scene_on_exit_handlers[])(void* context) = { +#include "pocsag_pager_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers pocsag_pager_scene_handlers = { + .on_enter_handlers = pocsag_pager_scene_on_enter_handlers, + .on_event_handlers = pocsag_pager_scene_on_event_handlers, + .on_exit_handlers = pocsag_pager_scene_on_exit_handlers, + .scene_num = POCSAGPagerSceneNum, +}; diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/scenes/pocsag_pager_scene.h b/Applications/Official/DEV_FW/source/pocsag_pager/scenes/pocsag_pager_scene.h new file mode 100644 index 000000000..d5c64f9d9 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pocsag_pager/scenes/pocsag_pager_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) POCSAGPagerScene##id, +typedef enum { +#include "pocsag_pager_scene_config.h" + POCSAGPagerSceneNum, +} POCSAGPagerScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers pocsag_pager_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "pocsag_pager_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 "pocsag_pager_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 "pocsag_pager_scene_config.h" +#undef ADD_SCENE diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/scenes/pocsag_pager_scene_about.c b/Applications/Official/DEV_FW/source/pocsag_pager/scenes/pocsag_pager_scene_about.c new file mode 100644 index 000000000..2af33c8bf --- /dev/null +++ b/Applications/Official/DEV_FW/source/pocsag_pager/scenes/pocsag_pager_scene_about.c @@ -0,0 +1,74 @@ +#include "../pocsag_pager_app_i.h" +#include "../helpers/pocsag_pager_types.h" + +void pocsag_pager_scene_about_widget_callback(GuiButtonType result, InputType type, void* context) { + POCSAGPagerApp* app = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(app->view_dispatcher, result); + } +} + +void pocsag_pager_scene_about_on_enter(void* context) { + POCSAGPagerApp* 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", PCSG_VERSION_APP); + furi_string_cat_printf(temp_str, "Developed by:\n%s\n\n", PCSG_DEVELOPED); + furi_string_cat_printf(temp_str, "Github: %s\n\n", PCSG_GITHUB); + + furi_string_cat_printf(temp_str, "\e#%s\n", "Description"); + furi_string_cat_printf(temp_str, "Receiving POCSAG Pager \nmessages\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! POCSAG Pager \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, POCSAGPagerViewWidget); +} + +bool pocsag_pager_scene_about_on_event(void* context, SceneManagerEvent event) { + POCSAGPagerApp* app = context; + bool consumed = false; + UNUSED(app); + UNUSED(event); + + return consumed; +} + +void pocsag_pager_scene_about_on_exit(void* context) { + POCSAGPagerApp* app = context; + + // Clear views + widget_reset(app->widget); +} diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/scenes/pocsag_pager_scene_config.h b/Applications/Official/DEV_FW/source/pocsag_pager/scenes/pocsag_pager_scene_config.h new file mode 100644 index 000000000..8136af14f --- /dev/null +++ b/Applications/Official/DEV_FW/source/pocsag_pager/scenes/pocsag_pager_scene_config.h @@ -0,0 +1,5 @@ +ADD_SCENE(pocsag_pager, start, Start) +ADD_SCENE(pocsag_pager, about, About) +ADD_SCENE(pocsag_pager, receiver, Receiver) +ADD_SCENE(pocsag_pager, receiver_config, ReceiverConfig) +ADD_SCENE(pocsag_pager, receiver_info, ReceiverInfo) diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/scenes/pocsag_pager_scene_receiver_config.c b/Applications/Official/DEV_FW/source/pocsag_pager/scenes/pocsag_pager_scene_receiver_config.c new file mode 100644 index 000000000..154e7d270 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pocsag_pager/scenes/pocsag_pager_scene_receiver_config.c @@ -0,0 +1,221 @@ +#include "../pocsag_pager_app_i.h" + +enum PCSGSettingIndex { + PCSGSettingIndexFrequency, + PCSGSettingIndexHopping, + PCSGSettingIndexModulation, + PCSGSettingIndexLock, +}; + +#define HOPPING_COUNT 2 +const char* const hopping_text[HOPPING_COUNT] = { + "OFF", + "ON", +}; +const uint32_t hopping_value[HOPPING_COUNT] = { + PCSGHopperStateOFF, + PCSGHopperStateRunnig, +}; + +uint8_t pocsag_pager_scene_receiver_config_next_frequency(const uint32_t value, void* context) { + furi_assert(context); + POCSAGPagerApp* 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 pocsag_pager_scene_receiver_config_next_preset(const char* preset_name, void* context) { + furi_assert(context); + POCSAGPagerApp* 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 pocsag_pager_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); + POCSAGPagerApp* app = context; + + if(value == values[0]) { + return 0; + } else { + variable_item_set_current_value_text( + (VariableItem*)scene_manager_get_scene_state( + app->scene_manager, POCSAGPagerSceneReceiverConfig), + " -----"); + return 1; + } +} + +static void pocsag_pager_scene_receiver_config_set_frequency(VariableItem* item) { + POCSAGPagerApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + if(app->txrx->hopper_state == PCSGHopperStateOFF) { + 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 pocsag_pager_scene_receiver_config_set_preset(VariableItem* item) { + POCSAGPagerApp* 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)); + pcsg_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 pocsag_pager_scene_receiver_config_set_hopping_running(VariableItem* item) { + POCSAGPagerApp* 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] == PCSGHopperStateOFF) { + 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, POCSAGPagerSceneReceiverConfig), + 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, POCSAGPagerSceneReceiverConfig), + subghz_setting_get_frequency_default_index(app->setting)); + } else { + variable_item_set_current_value_text( + (VariableItem*)scene_manager_get_scene_state( + app->scene_manager, POCSAGPagerSceneReceiverConfig), + " -----"); + variable_item_set_current_value_index( + (VariableItem*)scene_manager_get_scene_state( + app->scene_manager, POCSAGPagerSceneReceiverConfig), + subghz_setting_get_frequency_default_index(app->setting)); + } + + app->txrx->hopper_state = hopping_value[index]; +} + +static void + pocsag_pager_scene_receiver_config_var_list_enter_callback(void* context, uint32_t index) { + furi_assert(context); + POCSAGPagerApp* app = context; + if(index == PCSGSettingIndexLock) { + view_dispatcher_send_custom_event(app->view_dispatcher, PCSGCustomEventSceneSettingLock); + } +} + +void pocsag_pager_scene_receiver_config_on_enter(void* context) { + POCSAGPagerApp* 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), + pocsag_pager_scene_receiver_config_set_frequency, + app); + value_index = + pocsag_pager_scene_receiver_config_next_frequency(app->txrx->preset->frequency, app); + scene_manager_set_scene_state( + app->scene_manager, POCSAGPagerSceneReceiverConfig, (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, + pocsag_pager_scene_receiver_config_set_hopping_running, + app); + value_index = pocsag_pager_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), + pocsag_pager_scene_receiver_config_set_preset, + app); + value_index = pocsag_pager_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, pocsag_pager_scene_receiver_config_var_list_enter_callback, app); + + view_dispatcher_switch_to_view(app->view_dispatcher, POCSAGPagerViewVariableItemList); +} + +bool pocsag_pager_scene_receiver_config_on_event(void* context, SceneManagerEvent event) { + POCSAGPagerApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == PCSGCustomEventSceneSettingLock) { + app->lock = PCSGLockOn; + scene_manager_previous_scene(app->scene_manager); + consumed = true; + } + } + return consumed; +} + +void pocsag_pager_scene_receiver_config_on_exit(void* context) { + POCSAGPagerApp* 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/Official/DEV_FW/source/pocsag_pager/scenes/pocsag_pager_scene_receiver_info.c b/Applications/Official/DEV_FW/source/pocsag_pager/scenes/pocsag_pager_scene_receiver_info.c new file mode 100644 index 000000000..5f17d9fb7 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pocsag_pager/scenes/pocsag_pager_scene_receiver_info.c @@ -0,0 +1,50 @@ +#include "../pocsag_pager_app_i.h" +#include "../views/pocsag_pager_receiver.h" + +void pocsag_pager_scene_receiver_info_callback(PCSGCustomEvent event, void* context) { + furi_assert(context); + POCSAGPagerApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +static void pocsag_pager_scene_receiver_info_add_to_history_callback( + SubGhzReceiver* receiver, + SubGhzProtocolDecoderBase* decoder_base, + void* context) { + furi_assert(context); + POCSAGPagerApp* app = context; + + if(pcsg_history_add_to_history(app->txrx->history, decoder_base, app->txrx->preset) == + PCSGHistoryStateAddKeyUpdateData) { + pcsg_view_receiver_info_update( + app->pcsg_receiver_info, + pcsg_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 = PCSGRxKeyStateAddKey; + } +} + +void pocsag_pager_scene_receiver_info_on_enter(void* context) { + POCSAGPagerApp* app = context; + + subghz_receiver_set_rx_callback( + app->txrx->receiver, pocsag_pager_scene_receiver_info_add_to_history_callback, app); + pcsg_view_receiver_info_update( + app->pcsg_receiver_info, + pcsg_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen)); + view_dispatcher_switch_to_view(app->view_dispatcher, POCSAGPagerViewReceiverInfo); +} + +bool pocsag_pager_scene_receiver_info_on_event(void* context, SceneManagerEvent event) { + POCSAGPagerApp* app = context; + bool consumed = false; + UNUSED(app); + UNUSED(event); + return consumed; +} + +void pocsag_pager_scene_receiver_info_on_exit(void* context) { + UNUSED(context); +} diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/scenes/pocsag_pager_scene_start.c b/Applications/Official/DEV_FW/source/pocsag_pager/scenes/pocsag_pager_scene_start.c new file mode 100644 index 000000000..d2a94facb --- /dev/null +++ b/Applications/Official/DEV_FW/source/pocsag_pager/scenes/pocsag_pager_scene_start.c @@ -0,0 +1,58 @@ +#include "../pocsag_pager_app_i.h" + +typedef enum { + SubmenuIndexPOCSAGPagerReceiver, + SubmenuIndexPOCSAGPagerAbout, +} SubmenuIndex; + +void pocsag_pager_scene_start_submenu_callback(void* context, uint32_t index) { + POCSAGPagerApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void pocsag_pager_scene_start_on_enter(void* context) { + UNUSED(context); + POCSAGPagerApp* app = context; + Submenu* submenu = app->submenu; + + submenu_add_item( + submenu, + "Receive messages", + SubmenuIndexPOCSAGPagerReceiver, + pocsag_pager_scene_start_submenu_callback, + app); + submenu_add_item( + submenu, + "About", + SubmenuIndexPOCSAGPagerAbout, + pocsag_pager_scene_start_submenu_callback, + app); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(app->scene_manager, POCSAGPagerSceneStart)); + + view_dispatcher_switch_to_view(app->view_dispatcher, POCSAGPagerViewSubmenu); +} + +bool pocsag_pager_scene_start_on_event(void* context, SceneManagerEvent event) { + POCSAGPagerApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexPOCSAGPagerAbout) { + scene_manager_next_scene(app->scene_manager, POCSAGPagerSceneAbout); + consumed = true; + } else if(event.event == SubmenuIndexPOCSAGPagerReceiver) { + scene_manager_next_scene(app->scene_manager, POCSAGPagerSceneReceiver); + consumed = true; + } + scene_manager_set_scene_state(app->scene_manager, POCSAGPagerSceneStart, event.event); + } + + return consumed; +} + +void pocsag_pager_scene_start_on_exit(void* context) { + POCSAGPagerApp* app = context; + submenu_reset(app->submenu); +} diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/views/pocsag_pager_receiver.c b/Applications/Official/DEV_FW/source/pocsag_pager/views/pocsag_pager_receiver.c new file mode 100644 index 000000000..d8398cbfe --- /dev/null +++ b/Applications/Official/DEV_FW/source/pocsag_pager/views/pocsag_pager_receiver.c @@ -0,0 +1,440 @@ +#include "pocsag_pager_receiver.h" +#include "../pocsag_pager_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; +} PCSGReceiverMenuItem; + +ARRAY_DEF(PCSGReceiverMenuItemArray, PCSGReceiverMenuItem, M_POD_OPLIST) + +#define M_OPL_PCSGReceiverMenuItemArray_t() ARRAY_OPLIST(PCSGReceiverMenuItemArray, M_POD_OPLIST) + +struct PCSGReceiverHistory { + PCSGReceiverMenuItemArray_t data; +}; + +typedef struct PCSGReceiverHistory PCSGReceiverHistory; + +static const Icon* ReceiverItemIcons[] = { + [SubGhzProtocolTypeUnknown] = &I_Quest_7x8, + [SubGhzProtocolTypeStatic] = &I_Message_8x7, + [SubGhzProtocolTypeDynamic] = &I_Lock_7x8, +}; + +typedef enum { + PCSGReceiverBarShowDefault, + PCSGReceiverBarShowLock, + PCSGReceiverBarShowToUnlockPress, + PCSGReceiverBarShowUnlock, +} PCSGReceiverBarShow; + +struct PCSGReceiver { + PCSGLock lock; + uint8_t lock_count; + FuriTimer* timer; + View* view; + PCSGReceiverCallback callback; + void* context; +}; + +typedef struct { + FuriString* frequency_str; + FuriString* preset_str; + FuriString* history_stat_str; + PCSGReceiverHistory* history; + uint16_t idx; + uint16_t list_offset; + uint16_t history_item; + PCSGReceiverBarShow bar_show; +} PCSGReceiverModel; + +void pcsg_view_receiver_set_lock(PCSGReceiver* pcsg_receiver, PCSGLock lock) { + furi_assert(pcsg_receiver); + pcsg_receiver->lock_count = 0; + if(lock == PCSGLockOn) { + pcsg_receiver->lock = lock; + with_view_model( + pcsg_receiver->view, + PCSGReceiverModel * model, + { model->bar_show = PCSGReceiverBarShowLock; }, + true); + furi_timer_start(pcsg_receiver->timer, pdMS_TO_TICKS(1000)); + } else { + with_view_model( + pcsg_receiver->view, + PCSGReceiverModel * model, + { model->bar_show = PCSGReceiverBarShowDefault; }, + true); + } +} + +void pcsg_view_receiver_set_callback( + PCSGReceiver* pcsg_receiver, + PCSGReceiverCallback callback, + void* context) { + furi_assert(pcsg_receiver); + furi_assert(callback); + pcsg_receiver->callback = callback; + pcsg_receiver->context = context; +} + +static void pcsg_view_receiver_update_offset(PCSGReceiver* pcsg_receiver) { + furi_assert(pcsg_receiver); + + with_view_model( + pcsg_receiver->view, + PCSGReceiverModel * 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 pcsg_view_receiver_add_item_to_menu( + PCSGReceiver* pcsg_receiver, + const char* name, + uint8_t type) { + furi_assert(pcsg_receiver); + with_view_model( + pcsg_receiver->view, + PCSGReceiverModel * model, + { + PCSGReceiverMenuItem* item_menu = + PCSGReceiverMenuItemArray_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); + pcsg_view_receiver_update_offset(pcsg_receiver); +} + +void pcsg_view_receiver_add_data_statusbar( + PCSGReceiver* pcsg_receiver, + const char* frequency_str, + const char* preset_str, + const char* history_stat_str) { + furi_assert(pcsg_receiver); + with_view_model( + pcsg_receiver->view, + PCSGReceiverModel * 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 pcsg_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 pcsg_view_receiver_draw(Canvas* canvas, PCSGReceiverModel* 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(); + + PCSGReceiverMenuItem* 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 = PCSGReceiverMenuItemArray_get(model->history->data, idx); + furi_string_set(str_buff, item_menu->item_str); + furi_string_replace_all(str_buff, "#", ""); + elements_string_fit_width(canvas, str_buff, scrollbar ? MAX_LEN_PX - 7 : MAX_LEN_PX); + if(model->idx == idx) { + pcsg_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, 15, 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 PCSGReceiverBarShowLock: + canvas_draw_icon(canvas, 64, 55, &I_Lock_7x8); + canvas_draw_str(canvas, 74, 62, "Locked"); + break; + case PCSGReceiverBarShowToUnlockPress: + 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 PCSGReceiverBarShowUnlock: + 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 pcsg_view_receiver_timer_callback(void* context) { + furi_assert(context); + PCSGReceiver* pcsg_receiver = context; + with_view_model( + pcsg_receiver->view, + PCSGReceiverModel * model, + { model->bar_show = PCSGReceiverBarShowDefault; }, + true); + if(pcsg_receiver->lock_count < UNLOCK_CNT) { + pcsg_receiver->callback(PCSGCustomEventViewReceiverOffDisplay, pcsg_receiver->context); + } else { + pcsg_receiver->lock = PCSGLockOff; + pcsg_receiver->callback(PCSGCustomEventViewReceiverUnlock, pcsg_receiver->context); + } + pcsg_receiver->lock_count = 0; +} + +bool pcsg_view_receiver_input(InputEvent* event, void* context) { + furi_assert(context); + PCSGReceiver* pcsg_receiver = context; + + if(pcsg_receiver->lock == PCSGLockOn) { + with_view_model( + pcsg_receiver->view, + PCSGReceiverModel * model, + { model->bar_show = PCSGReceiverBarShowToUnlockPress; }, + true); + if(pcsg_receiver->lock_count == 0) { + furi_timer_start(pcsg_receiver->timer, pdMS_TO_TICKS(1000)); + } + if(event->key == InputKeyBack && event->type == InputTypeShort) { + pcsg_receiver->lock_count++; + } + if(pcsg_receiver->lock_count >= UNLOCK_CNT) { + pcsg_receiver->callback(PCSGCustomEventViewReceiverUnlock, pcsg_receiver->context); + with_view_model( + pcsg_receiver->view, + PCSGReceiverModel * model, + { model->bar_show = PCSGReceiverBarShowUnlock; }, + true); + pcsg_receiver->lock = PCSGLockOff; + furi_timer_start(pcsg_receiver->timer, pdMS_TO_TICKS(650)); + } + + return true; + } + + if(event->key == InputKeyBack && event->type == InputTypeShort) { + pcsg_receiver->callback(PCSGCustomEventViewReceiverBack, pcsg_receiver->context); + } else if( + event->key == InputKeyUp && + (event->type == InputTypeShort || event->type == InputTypeRepeat)) { + with_view_model( + pcsg_receiver->view, + PCSGReceiverModel * model, + { + if(model->idx != 0) model->idx--; + }, + true); + } else if( + event->key == InputKeyDown && + (event->type == InputTypeShort || event->type == InputTypeRepeat)) { + with_view_model( + pcsg_receiver->view, + PCSGReceiverModel * model, + { + if(model->idx != model->history_item - 1) model->idx++; + }, + true); + } else if(event->key == InputKeyLeft && event->type == InputTypeShort) { + pcsg_receiver->callback(PCSGCustomEventViewReceiverConfig, pcsg_receiver->context); + } else if(event->key == InputKeyOk && event->type == InputTypeShort) { + with_view_model( + pcsg_receiver->view, + PCSGReceiverModel * model, + { + if(model->history_item != 0) { + pcsg_receiver->callback(PCSGCustomEventViewReceiverOK, pcsg_receiver->context); + } + }, + false); + } + + pcsg_view_receiver_update_offset(pcsg_receiver); + + return true; +} + +void pcsg_view_receiver_enter(void* context) { + furi_assert(context); +} + +void pcsg_view_receiver_exit(void* context) { + furi_assert(context); + PCSGReceiver* pcsg_receiver = context; + with_view_model( + pcsg_receiver->view, + PCSGReceiverModel * 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, PCSGReceiverMenuItemArray_t) { + furi_string_free(item_menu->item_str); + item_menu->type = 0; + } + PCSGReceiverMenuItemArray_reset(model->history->data); + model->idx = 0; + model->list_offset = 0; + model->history_item = 0; + }, + false); + furi_timer_stop(pcsg_receiver->timer); +} + +PCSGReceiver* pcsg_view_receiver_alloc() { + PCSGReceiver* pcsg_receiver = malloc(sizeof(PCSGReceiver)); + + // View allocation and configuration + pcsg_receiver->view = view_alloc(); + + pcsg_receiver->lock = PCSGLockOff; + pcsg_receiver->lock_count = 0; + view_allocate_model(pcsg_receiver->view, ViewModelTypeLocking, sizeof(PCSGReceiverModel)); + view_set_context(pcsg_receiver->view, pcsg_receiver); + view_set_draw_callback(pcsg_receiver->view, (ViewDrawCallback)pcsg_view_receiver_draw); + view_set_input_callback(pcsg_receiver->view, pcsg_view_receiver_input); + view_set_enter_callback(pcsg_receiver->view, pcsg_view_receiver_enter); + view_set_exit_callback(pcsg_receiver->view, pcsg_view_receiver_exit); + + with_view_model( + pcsg_receiver->view, + PCSGReceiverModel * model, + { + model->frequency_str = furi_string_alloc(); + model->preset_str = furi_string_alloc(); + model->history_stat_str = furi_string_alloc(); + model->bar_show = PCSGReceiverBarShowDefault; + model->history = malloc(sizeof(PCSGReceiverHistory)); + PCSGReceiverMenuItemArray_init(model->history->data); + }, + true); + pcsg_receiver->timer = + furi_timer_alloc(pcsg_view_receiver_timer_callback, FuriTimerTypeOnce, pcsg_receiver); + return pcsg_receiver; +} + +void pcsg_view_receiver_free(PCSGReceiver* pcsg_receiver) { + furi_assert(pcsg_receiver); + + with_view_model( + pcsg_receiver->view, + PCSGReceiverModel * 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, PCSGReceiverMenuItemArray_t) { + furi_string_free(item_menu->item_str); + item_menu->type = 0; + } + PCSGReceiverMenuItemArray_clear(model->history->data); + free(model->history); + }, + false); + furi_timer_free(pcsg_receiver->timer); + view_free(pcsg_receiver->view); + free(pcsg_receiver); +} + +View* pcsg_view_receiver_get_view(PCSGReceiver* pcsg_receiver) { + furi_assert(pcsg_receiver); + return pcsg_receiver->view; +} + +uint16_t pcsg_view_receiver_get_idx_menu(PCSGReceiver* pcsg_receiver) { + furi_assert(pcsg_receiver); + uint32_t idx = 0; + with_view_model( + pcsg_receiver->view, PCSGReceiverModel * model, { idx = model->idx; }, false); + return idx; +} + +void pcsg_view_receiver_set_idx_menu(PCSGReceiver* pcsg_receiver, uint16_t idx) { + furi_assert(pcsg_receiver); + with_view_model( + pcsg_receiver->view, + PCSGReceiverModel * model, + { + model->idx = idx; + if(model->idx > 2) model->list_offset = idx - 2; + }, + true); + pcsg_view_receiver_update_offset(pcsg_receiver); +} diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/views/pocsag_pager_receiver.h b/Applications/Official/DEV_FW/source/pocsag_pager/views/pocsag_pager_receiver.h new file mode 100644 index 000000000..5ea2d4859 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pocsag_pager/views/pocsag_pager_receiver.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include "../helpers/pocsag_pager_types.h" +#include "../helpers/pocsag_pager_event.h" + +typedef struct PCSGReceiver PCSGReceiver; + +typedef void (*PCSGReceiverCallback)(PCSGCustomEvent event, void* context); + +void pcsg_view_receiver_set_lock(PCSGReceiver* pcsg_receiver, PCSGLock keyboard); + +void pcsg_view_receiver_set_callback( + PCSGReceiver* pcsg_receiver, + PCSGReceiverCallback callback, + void* context); + +PCSGReceiver* pcsg_view_receiver_alloc(); + +void pcsg_view_receiver_free(PCSGReceiver* pcsg_receiver); + +View* pcsg_view_receiver_get_view(PCSGReceiver* pcsg_receiver); + +void pcsg_view_receiver_add_data_statusbar( + PCSGReceiver* pcsg_receiver, + const char* frequency_str, + const char* preset_str, + const char* history_stat_str); + +void pcsg_view_receiver_add_item_to_menu( + PCSGReceiver* pcsg_receiver, + const char* name, + uint8_t type); + +uint16_t pcsg_view_receiver_get_idx_menu(PCSGReceiver* pcsg_receiver); + +void pcsg_view_receiver_set_idx_menu(PCSGReceiver* pcsg_receiver, uint16_t idx); + +void pcsg_view_receiver_exit(void* context); diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/views/pocsag_pager_receiver_info.c b/Applications/Official/DEV_FW/source/pocsag_pager/views/pocsag_pager_receiver_info.c new file mode 100644 index 000000000..4811f3902 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pocsag_pager/views/pocsag_pager_receiver_info.c @@ -0,0 +1,137 @@ +#include "pocsag_pager_receiver.h" +#include "../pocsag_pager_app_i.h" +#include "pocsag_pager_icons.h" +#include "../protocols/pcsg_generic.h" +#include +#include + +#define abs(x) ((x) > 0 ? (x) : -(x)) + +struct PCSGReceiverInfo { + View* view; +}; + +typedef struct { + FuriString* protocol_name; + PCSGBlockGeneric* generic; +} PCSGReceiverInfoModel; + +void pcsg_view_receiver_info_update(PCSGReceiverInfo* pcsg_receiver_info, FlipperFormat* fff) { + furi_assert(pcsg_receiver_info); + furi_assert(fff); + + with_view_model( + pcsg_receiver_info->view, + PCSGReceiverInfoModel * model, + { + flipper_format_rewind(fff); + flipper_format_read_string(fff, "Protocol", model->protocol_name); + + pcsg_block_generic_deserialize(model->generic, fff); + }, + true); +} + +void pcsg_view_receiver_info_draw(Canvas* canvas, PCSGReceiverInfoModel* model) { + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontSecondary); + if(model->generic->result_ric != NULL) { + elements_text_box( + canvas, + 0, + 0, + 128, + 64, + AlignLeft, + AlignTop, + furi_string_get_cstr(model->generic->result_ric), + false); + } + if(model->generic->result_msg != NULL) { + elements_text_box( + canvas, + 0, + 12, + 128, + 64, + AlignLeft, + AlignTop, + furi_string_get_cstr(model->generic->result_msg), + false); + } +} + +bool pcsg_view_receiver_info_input(InputEvent* event, void* context) { + furi_assert(context); + //PCSGReceiverInfo* pcsg_receiver_info = context; + + if(event->key == InputKeyBack) { + return false; + } + + return true; +} + +void pcsg_view_receiver_info_enter(void* context) { + furi_assert(context); +} + +void pcsg_view_receiver_info_exit(void* context) { + furi_assert(context); + PCSGReceiverInfo* pcsg_receiver_info = context; + + with_view_model( + pcsg_receiver_info->view, + PCSGReceiverInfoModel * model, + { furi_string_reset(model->protocol_name); }, + false); +} + +PCSGReceiverInfo* pcsg_view_receiver_info_alloc() { + PCSGReceiverInfo* pcsg_receiver_info = malloc(sizeof(PCSGReceiverInfo)); + + // View allocation and configuration + pcsg_receiver_info->view = view_alloc(); + + view_allocate_model( + pcsg_receiver_info->view, ViewModelTypeLocking, sizeof(PCSGReceiverInfoModel)); + view_set_context(pcsg_receiver_info->view, pcsg_receiver_info); + view_set_draw_callback( + pcsg_receiver_info->view, (ViewDrawCallback)pcsg_view_receiver_info_draw); + view_set_input_callback(pcsg_receiver_info->view, pcsg_view_receiver_info_input); + view_set_enter_callback(pcsg_receiver_info->view, pcsg_view_receiver_info_enter); + view_set_exit_callback(pcsg_receiver_info->view, pcsg_view_receiver_info_exit); + + with_view_model( + pcsg_receiver_info->view, + PCSGReceiverInfoModel * model, + { + model->generic = malloc(sizeof(PCSGBlockGeneric)); + model->protocol_name = furi_string_alloc(); + }, + true); + + return pcsg_receiver_info; +} + +void pcsg_view_receiver_info_free(PCSGReceiverInfo* pcsg_receiver_info) { + furi_assert(pcsg_receiver_info); + + with_view_model( + pcsg_receiver_info->view, + PCSGReceiverInfoModel * model, + { + furi_string_free(model->protocol_name); + free(model->generic); + }, + false); + + view_free(pcsg_receiver_info->view); + free(pcsg_receiver_info); +} + +View* pcsg_view_receiver_info_get_view(PCSGReceiverInfo* pcsg_receiver_info) { + furi_assert(pcsg_receiver_info); + return pcsg_receiver_info->view; +} diff --git a/Applications/Official/DEV_FW/source/pocsag_pager/views/pocsag_pager_receiver_info.h b/Applications/Official/DEV_FW/source/pocsag_pager/views/pocsag_pager_receiver_info.h new file mode 100644 index 000000000..dfc85ec88 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pocsag_pager/views/pocsag_pager_receiver_info.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include "../helpers/pocsag_pager_types.h" +#include "../helpers/pocsag_pager_event.h" +#include + +typedef struct PCSGReceiverInfo PCSGReceiverInfo; + +void pcsg_view_receiver_info_update(PCSGReceiverInfo* pcsg_receiver_info, FlipperFormat* fff); + +PCSGReceiverInfo* pcsg_view_receiver_info_alloc(); + +void pcsg_view_receiver_info_free(PCSGReceiverInfo* pcsg_receiver_info); + +View* pcsg_view_receiver_info_get_view(PCSGReceiverInfo* pcsg_receiver_info); diff --git a/Applications/Official/DEV_FW/source/pomodoro/LICENSE b/Applications/Official/DEV_FW/source/pomodoro/LICENSE new file mode 100644 index 000000000..0e259d42c --- /dev/null +++ b/Applications/Official/DEV_FW/source/pomodoro/LICENSE @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/Applications/Official/DEV_FW/source/pomodoro/README.md b/Applications/Official/DEV_FW/source/pomodoro/README.md new file mode 100644 index 000000000..e8e7491f5 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pomodoro/README.md @@ -0,0 +1,34 @@ +# flipperzero_pomodoro + +The Pomodoro Technique is a time management method developed by Francesco Cirillo in the late 1980s.[1] It uses a kitchen timer to break work into intervals, typically 25 minutes in length, separated by short breaks. Each interval is known as a pomodoro, from the Italian word for tomato, after the tomato-shaped kitchen timer Cirillo used as a university student. + +Flipper Zero is a portable Tamagotchi-like multi-functional device developed for interaction with access control systems. The device is able to read, copy, and emulate radio-frequency (RFID) tags, radio remotes, and digital access keys. + +## Pomodoro timer application for Flipper Zero + +Three timers available: + +- classic 25 min work, 5 min rest +- long 50 min work, 10 min rest +- sprint 10 min work, 2 min rest + +With tomato counter + +Plays sound alerts + +Has built-in clocks + +Screenshots: + +![](./misc/1.png) + +![](./misc/2.png) + +![](./misc/3.png) + +![](./misc/4.png) + +![](./misc/5.png) + + +Compatible with firmware v. F81999EA from 14 Oct. 2022 diff --git a/Applications/Official/DEV_FW/source/pomodoro/application.fam b/Applications/Official/DEV_FW/source/pomodoro/application.fam new file mode 100644 index 000000000..e73427fc8 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pomodoro/application.fam @@ -0,0 +1,15 @@ +App( + appid="Pomodoro_Timer", + name="Pomodoro Timer", + apptype=FlipperAppType.EXTERNAL, + entry_point="pomodoro_app", + stack_size=1 * 1024, + cdefines=["APP_POMODORO"], + requires=[ + "gui", + ], + order=10, + fap_icon="pomodoro_timer.png", + fap_category="Tools", + fap_icon_assets="icons", +) diff --git a/Applications/Official/DEV_FW/source/pomodoro/icons/ButtonLeft_4x7.png b/Applications/Official/DEV_FW/source/pomodoro/icons/ButtonLeft_4x7.png new file mode 100644 index 000000000..0b4655d43 Binary files /dev/null and b/Applications/Official/DEV_FW/source/pomodoro/icons/ButtonLeft_4x7.png differ diff --git a/Applications/Official/DEV_FW/source/pomodoro/icons/Ok_btn_9x9.png b/Applications/Official/DEV_FW/source/pomodoro/icons/Ok_btn_9x9.png new file mode 100644 index 000000000..9a1539da2 Binary files /dev/null and b/Applications/Official/DEV_FW/source/pomodoro/icons/Ok_btn_9x9.png differ diff --git a/Applications/Official/DEV_FW/source/pomodoro/icons/Pin_back_arrow_10x8.png b/Applications/Official/DEV_FW/source/pomodoro/icons/Pin_back_arrow_10x8.png new file mode 100644 index 000000000..3bafabd14 Binary files /dev/null and b/Applications/Official/DEV_FW/source/pomodoro/icons/Pin_back_arrow_10x8.png differ diff --git a/Applications/Official/DEV_FW/source/pomodoro/icons/Space_65x18.png b/Applications/Official/DEV_FW/source/pomodoro/icons/Space_65x18.png new file mode 100644 index 000000000..b60ae5097 Binary files /dev/null and b/Applications/Official/DEV_FW/source/pomodoro/icons/Space_65x18.png differ diff --git a/Applications/Official/DEV_FW/source/pomodoro/misc/1.png b/Applications/Official/DEV_FW/source/pomodoro/misc/1.png new file mode 100644 index 000000000..e8543a255 Binary files /dev/null and b/Applications/Official/DEV_FW/source/pomodoro/misc/1.png differ diff --git a/Applications/Official/DEV_FW/source/pomodoro/misc/2.png b/Applications/Official/DEV_FW/source/pomodoro/misc/2.png new file mode 100644 index 000000000..8b5f28476 Binary files /dev/null and b/Applications/Official/DEV_FW/source/pomodoro/misc/2.png differ diff --git a/Applications/Official/DEV_FW/source/pomodoro/misc/3.png b/Applications/Official/DEV_FW/source/pomodoro/misc/3.png new file mode 100644 index 000000000..32473be3c Binary files /dev/null and b/Applications/Official/DEV_FW/source/pomodoro/misc/3.png differ diff --git a/Applications/Official/DEV_FW/source/pomodoro/misc/4.png b/Applications/Official/DEV_FW/source/pomodoro/misc/4.png new file mode 100644 index 000000000..0b48b9fdc Binary files /dev/null and b/Applications/Official/DEV_FW/source/pomodoro/misc/4.png differ diff --git a/Applications/Official/DEV_FW/source/pomodoro/misc/5.png b/Applications/Official/DEV_FW/source/pomodoro/misc/5.png new file mode 100644 index 000000000..1a53bc074 Binary files /dev/null and b/Applications/Official/DEV_FW/source/pomodoro/misc/5.png differ diff --git a/Applications/Official/DEV_FW/source/pomodoro/pomodoro.c b/Applications/Official/DEV_FW/source/pomodoro/pomodoro.c new file mode 100644 index 000000000..5b1db1984 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pomodoro/pomodoro.c @@ -0,0 +1,164 @@ +#include "pomodoro.h" +#include + +#define TAG "PomodoroApp" + +enum PomodoroDebugSubmenuIndex { + PomodoroSubmenuIndex10, + PomodoroSubmenuIndex25, + PomodoroSubmenuIndex50, +}; + +void pomodoro_submenu_callback(void* context, uint32_t index) { + furi_assert(context); + Pomodoro* app = context; + if(index == PomodoroSubmenuIndex10) { + app->view_id = PomodoroView10; + view_dispatcher_switch_to_view(app->view_dispatcher, PomodoroView10); + } + if(index == PomodoroSubmenuIndex25) { + app->view_id = PomodoroView25; + view_dispatcher_switch_to_view(app->view_dispatcher, PomodoroView25); + } + if(index == PomodoroSubmenuIndex50) { + app->view_id = PomodoroView50; + view_dispatcher_switch_to_view(app->view_dispatcher, PomodoroView50); + } +} + +void pomodoro_dialog_callback(DialogExResult result, void* context) { + furi_assert(context); + Pomodoro* app = context; + if(result == DialogExResultLeft) { + view_dispatcher_stop(app->view_dispatcher); + } else if(result == DialogExResultRight) { + view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); // Show last view + } else if(result == DialogExResultCenter) { + view_dispatcher_switch_to_view(app->view_dispatcher, PomodoroViewSubmenu); + } +} + +uint32_t pomodoro_exit_confirm_view(void* context) { + UNUSED(context); + return PomodoroViewExitConfirm; +} + +uint32_t pomodoro_exit(void* context) { + UNUSED(context); + return VIEW_NONE; +} + +Pomodoro* pomodoro_app_alloc() { + Pomodoro* app = malloc(sizeof(Pomodoro)); + + // Gui + app->gui = furi_record_open(RECORD_GUI); + + // Notifications + app->notifications = furi_record_open(RECORD_NOTIFICATION); + + // View dispatcher + app->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + // Submenu view + app->submenu = submenu_alloc(); + submenu_add_item( + app->submenu, + "Classic: 25 work 5 rest", + PomodoroSubmenuIndex25, + pomodoro_submenu_callback, + app); + submenu_add_item( + app->submenu, + "Long: 50 work 10 rest", + PomodoroSubmenuIndex50, + pomodoro_submenu_callback, + app); + submenu_add_item( + app->submenu, + "Sprint: 10 work 2 rest", + PomodoroSubmenuIndex10, + pomodoro_submenu_callback, + app); + view_set_previous_callback(submenu_get_view(app->submenu), pomodoro_exit); + view_dispatcher_add_view( + app->view_dispatcher, PomodoroViewSubmenu, submenu_get_view(app->submenu)); + + // Dialog view + app->dialog = dialog_ex_alloc(); + dialog_ex_set_result_callback(app->dialog, pomodoro_dialog_callback); + dialog_ex_set_context(app->dialog, app); + dialog_ex_set_left_button_text(app->dialog, "Exit"); + dialog_ex_set_right_button_text(app->dialog, "Stay"); + dialog_ex_set_center_button_text(app->dialog, "Menu"); + dialog_ex_set_header(app->dialog, "Close Current App?", 16, 12, AlignLeft, AlignTop); + view_dispatcher_add_view( + app->view_dispatcher, PomodoroViewExitConfirm, dialog_ex_get_view(app->dialog)); + + // 25 minutes view + app->pomodoro_25 = pomodoro_25_alloc(); + view_set_previous_callback(pomodoro_25_get_view(app->pomodoro_25), pomodoro_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, PomodoroView25, pomodoro_25_get_view(app->pomodoro_25)); + + // 50 minutes view + app->pomodoro_50 = pomodoro_50_alloc(); + view_set_previous_callback(pomodoro_50_get_view(app->pomodoro_50), pomodoro_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, PomodoroView50, pomodoro_50_get_view(app->pomodoro_50)); + + // 10 minutes view + app->pomodoro_10 = pomodoro_10_alloc(); + view_set_previous_callback(pomodoro_10_get_view(app->pomodoro_10), pomodoro_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, PomodoroView10, pomodoro_10_get_view(app->pomodoro_10)); + + // TODO switch to menu after Media is done + app->view_id = PomodoroViewSubmenu; + view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); + + return app; +} + +void pomodoro_app_free(Pomodoro* app) { + furi_assert(app); + + // Reset notification + notification_internal_message(app->notifications, &sequence_reset_blue); + + // Free views + view_dispatcher_remove_view(app->view_dispatcher, PomodoroViewSubmenu); + submenu_free(app->submenu); + view_dispatcher_remove_view(app->view_dispatcher, PomodoroViewExitConfirm); + dialog_ex_free(app->dialog); + view_dispatcher_remove_view(app->view_dispatcher, PomodoroView25); + pomodoro_25_free(app->pomodoro_25); + view_dispatcher_remove_view(app->view_dispatcher, PomodoroView50); + pomodoro_50_free(app->pomodoro_50); + view_dispatcher_remove_view(app->view_dispatcher, PomodoroView10); + pomodoro_10_free(app->pomodoro_10); + view_dispatcher_free(app->view_dispatcher); + + // Close records + furi_record_close(RECORD_GUI); + app->gui = NULL; + furi_record_close(RECORD_NOTIFICATION); + app->notifications = NULL; + + // Free rest + free(app); +} + +int32_t pomodoro_app(void* p) { + UNUSED(p); + // Switch profile to Hid + Pomodoro* app = pomodoro_app_alloc(); + + view_dispatcher_run(app->view_dispatcher); + + pomodoro_app_free(app); + + return 0; +} diff --git a/Applications/Official/DEV_FW/source/pomodoro/pomodoro.h b/Applications/Official/DEV_FW/source/pomodoro/pomodoro.h new file mode 100644 index 000000000..53dedb8e3 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pomodoro/pomodoro.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include "pomodoro_timer.h" +#include "views/pomodoro_10.h" +#include "views/pomodoro_25.h" +#include "views/pomodoro_50.h" + +typedef struct { + Gui* gui; + NotificationApp* notifications; + ViewDispatcher* view_dispatcher; + Submenu* submenu; + DialogEx* dialog; + PomodoroTimer* pomodoro_10; + PomodoroTimer* pomodoro_25; + PomodoroTimer* pomodoro_50; + uint32_t view_id; +} Pomodoro; + +typedef enum { + PomodoroViewSubmenu, + PomodoroView10, + PomodoroView25, + PomodoroView50, + PomodoroViewExitConfirm, +} PomodoroView; diff --git a/Applications/Official/DEV_FW/source/pomodoro/pomodoro_timer.c b/Applications/Official/DEV_FW/source/pomodoro/pomodoro_timer.c new file mode 100644 index 000000000..0fba5db42 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pomodoro/pomodoro_timer.c @@ -0,0 +1,242 @@ +#include "pomodoro_timer.h" +#include +#include +#include +#include +#include + +const NotificationSequence sequence_finish = { + &message_display_backlight_on, + &message_green_255, + &message_vibro_on, + &message_note_c5, + &message_delay_100, + &message_vibro_off, + &message_vibro_on, + &message_note_e5, + &message_delay_100, + &message_vibro_off, + &message_vibro_on, + &message_note_g5, + &message_delay_100, + &message_vibro_off, + &message_vibro_on, + &message_note_b5, + &message_delay_250, + &message_vibro_off, + &message_vibro_on, + &message_note_c6, + &message_delay_250, + &message_vibro_off, + &message_sound_off, + NULL, +}; + +const NotificationSequence sequence_rest = { + &message_display_backlight_on, + &message_red_255, + &message_vibro_on, + &message_note_c6, + &message_delay_100, + &message_vibro_off, + &message_vibro_on, + &message_note_b5, + &message_delay_100, + &message_vibro_off, + &message_vibro_on, + &message_note_g5, + &message_delay_100, + &message_vibro_off, + &message_vibro_on, + &message_note_e5, + &message_delay_100, + &message_vibro_off, + &message_vibro_on, + &message_note_c5, + &message_delay_250, + &message_vibro_off, + &message_sound_off, + NULL, +}; + +void pomodoro_timer_process(PomodoroTimer* pomodoro_timer, InputEvent* event) { + with_view_model( + pomodoro_timer->view, + PomodoroTimerModel * model, + { + if(event->type == InputTypePress) { + if(event->key == InputKeyOk) { + model->ok_pressed = true; + } else if(event->key == InputKeyLeft) { + model->reset_pressed = true; + } else if(event->key == InputKeyBack) { + model->back_pressed = true; + } + } else if(event->type == InputTypeRelease) { + if(event->key == InputKeyOk) { + model->ok_pressed = false; + + // START/STOP TIMER + FuriHalRtcDateTime curr_dt; + furi_hal_rtc_get_datetime(&curr_dt); + uint32_t current_timestamp = furi_hal_rtc_datetime_to_timestamp(&curr_dt); + + // STARTED -> PAUSED + if(model->timer_running) { + // Update stopped seconds + model->timer_stopped_seconds = + current_timestamp - model->timer_start_timestamp; + } else if(!model->time_passed) { + // INITIAL -> STARTED + model->timer_start_timestamp = current_timestamp; + model->rest_running = false; + } else { + // PAUSED -> STARTED + model->timer_start_timestamp = + current_timestamp - model->timer_stopped_seconds; + } + model->timer_running = !model->timer_running; + } else if(event->key == InputKeyLeft) { + if(!model->timer_running) { + furi_record_close(RECORD_NOTIFICATION); + model->timer_stopped_seconds = 0; + model->timer_start_timestamp = 0; + model->time_passed = 0; + model->timer_running = false; + } + model->reset_pressed = false; + } else if(event->key == InputKeyBack) { + model->back_pressed = false; + } + } + }, + true); +} + +void pomodoro_draw_callback(Canvas* canvas, void* context, int max_seconds, int max_seconds_rest) { + furi_assert(context); + PomodoroTimerModel* model = context; + FuriHalRtcDateTime curr_dt; + furi_hal_rtc_get_datetime(&curr_dt); + uint32_t current_timestamp = furi_hal_rtc_datetime_to_timestamp(&curr_dt); + + // Header + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 0, 0, AlignLeft, AlignTop, "Pomodoro"); + + canvas_draw_icon(canvas, 68, 1, &I_Pin_back_arrow_10x8); + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned(canvas, 127, 1, AlignRight, AlignTop, "Hold to exit"); + + // Start/Pause/Continue + int txt_main_y = 34; + canvas_draw_icon(canvas, 63, 23, &I_Space_65x18); // button + if(model->ok_pressed) { + elements_slightly_rounded_box(canvas, 66, 25, 60, 13); + canvas_set_color(canvas, ColorWhite); + } + if(model->timer_running) { + model->time_passed = current_timestamp - model->timer_start_timestamp; + elements_multiline_text_aligned(canvas, 83, txt_main_y, AlignLeft, AlignBottom, "Pause"); + canvas_draw_box(canvas, 71, 27, 2, 8); + canvas_draw_box(canvas, 75, 27, 2, 8); + } else { + if(model->time_passed) { + elements_multiline_text_aligned( + canvas, 83, txt_main_y, AlignLeft, AlignBottom, "Continue"); + } else { + elements_multiline_text_aligned( + canvas, 83, txt_main_y, AlignLeft, AlignBottom, "Start"); + } + canvas_draw_icon(canvas, 70, 26, &I_Ok_btn_9x9); // OK icon + } + canvas_set_color(canvas, ColorBlack); + + // Reset + if(!model->timer_running && model->time_passed) { + canvas_draw_icon(canvas, 63, 46, &I_Space_65x18); + if(model->reset_pressed) { + elements_slightly_rounded_box(canvas, 66, 48, 60, 13); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 72, 50, &I_ButtonLeft_4x7); + elements_multiline_text_aligned(canvas, 83, 57, AlignLeft, AlignBottom, "Reset"); + canvas_set_color(canvas, ColorBlack); + } + + char buffer[64]; + + // Time to work + int total_time_left = (max_seconds - (uint32_t)model->time_passed); + int minutes_left = total_time_left / 60; + int seconds_left = total_time_left % 60; + canvas_set_font(canvas, FontBigNumbers); + + // Play sound + if(total_time_left == 0 && !model->sound_playing) { + model->sound_playing = true; + notification_message(furi_record_open(RECORD_NOTIFICATION), &sequence_finish); + } + if(total_time_left < 0) { + model->timer_running = false; + model->time_passed = 0; + model->sound_playing = false; + + model->rest_running = true; + model->rest_start_timestamp = current_timestamp; + seconds_left = 0; + model->counter += 1; + } + if(!model->rest_running) { + snprintf(buffer, sizeof(buffer), "%02d:%02d", minutes_left, seconds_left); + canvas_draw_str(canvas, 0, 39, buffer); + } + if(model->timer_running) { + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 0, 50, AlignLeft, AlignTop, "Time to work"); + } + + // Time to rest + if(model->rest_running && !model->timer_running) { + canvas_set_font(canvas, FontBigNumbers); + int rest_passed = current_timestamp - model->rest_start_timestamp; + int rest_total_time_left = (max_seconds_rest - rest_passed); + int rest_minutes_left = rest_total_time_left / 60; + int rest_seconds_left = rest_total_time_left % 60; + + // Play sound + if(rest_total_time_left == 0 && !model->sound_playing) { + model->sound_playing = true; + notification_message(furi_record_open(RECORD_NOTIFICATION), &sequence_rest); + } + if(rest_total_time_left < 0) { + rest_seconds_left = 0; + model->rest_running = false; + model->sound_playing = false; + } + snprintf(buffer, sizeof(buffer), "%02d:%02d", rest_minutes_left, rest_seconds_left); + canvas_draw_str(canvas, 0, 60, buffer); + + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 0, 27, AlignLeft, AlignTop, "Have a rest"); + } + + // Clocks + canvas_set_font(canvas, FontSecondary); + snprintf( + buffer, + sizeof(buffer), + "%02ld:%02ld:%02ld", + ((uint32_t)current_timestamp % (60 * 60 * 24)) / (60 * 60), + ((uint32_t)current_timestamp % (60 * 60)) / 60, + (uint32_t)current_timestamp % 60); + canvas_draw_str(canvas, 0, 20, buffer); + + // Tomato counter + if(model->counter > 5) { + model->counter = 1; + } + for(int i = 0; i < model->counter; ++i) { + canvas_draw_disc(canvas, 122 - i * 10, 15, 4); + } +} diff --git a/Applications/Official/DEV_FW/source/pomodoro/pomodoro_timer.h b/Applications/Official/DEV_FW/source/pomodoro/pomodoro_timer.h new file mode 100644 index 000000000..284f0a6c6 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pomodoro/pomodoro_timer.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include +#include + +typedef struct PomodoroTimer PomodoroTimer; + +struct PomodoroTimer { + View* view; +}; + +typedef struct PomodoroTimerModel PomodoroTimerModel; + +struct PomodoroTimerModel { + bool ok_pressed; + bool reset_pressed; + bool back_pressed; + bool connected; + bool timer_running; + bool rest_running; + bool sound_playing; + uint32_t timer_start_timestamp; + uint32_t timer_stopped_seconds; + uint32_t time_passed; + uint32_t rest_start_timestamp; + int counter; +}; + +void pomodoro_timer_process(PomodoroTimer* pomodoro_timer, InputEvent* event); + +void pomodoro_draw_callback(Canvas* canvas, void* context, int max_seconds, int max_seconds_rest); diff --git a/Applications/Official/DEV_FW/source/pomodoro/pomodoro_timer.png b/Applications/Official/DEV_FW/source/pomodoro/pomodoro_timer.png new file mode 100644 index 000000000..b25c2718e Binary files /dev/null and b/Applications/Official/DEV_FW/source/pomodoro/pomodoro_timer.png differ diff --git a/Applications/Official/DEV_FW/source/pomodoro/views/pomodoro_10.c b/Applications/Official/DEV_FW/source/pomodoro/views/pomodoro_10.c new file mode 100644 index 000000000..f11f96d9f --- /dev/null +++ b/Applications/Official/DEV_FW/source/pomodoro/views/pomodoro_10.c @@ -0,0 +1,46 @@ +#include "../pomodoro_timer.h" +#include "pomodoro_10.h" +#include +#include +#include +#include + +static void pomodoro_10_draw_callback(Canvas* canvas, void* context) { + int max_seconds = 60 * 10; + int max_seconds_rest = 60 * 2; + pomodoro_draw_callback(canvas, context, max_seconds, max_seconds_rest); +} + +static bool pomodoro_10_input_callback(InputEvent* event, void* context) { + furi_assert(context); + PomodoroTimer* pomodoro_10 = context; + + if(event->type == InputTypeLong && event->key == InputKeyBack) { + return false; + } else { + pomodoro_timer_process(pomodoro_10, event); + return true; + } +} + +PomodoroTimer* pomodoro_10_alloc() { + PomodoroTimer* pomodoro_10 = malloc(sizeof(PomodoroTimer)); + pomodoro_10->view = view_alloc(); + view_set_context(pomodoro_10->view, pomodoro_10); + view_allocate_model(pomodoro_10->view, ViewModelTypeLocking, sizeof(PomodoroTimerModel)); + view_set_draw_callback(pomodoro_10->view, pomodoro_10_draw_callback); + view_set_input_callback(pomodoro_10->view, pomodoro_10_input_callback); + + return pomodoro_10; +} + +void pomodoro_10_free(PomodoroTimer* pomodoro_10) { + furi_assert(pomodoro_10); + view_free(pomodoro_10->view); + free(pomodoro_10); +} + +View* pomodoro_10_get_view(PomodoroTimer* pomodoro_10) { + furi_assert(pomodoro_10); + return pomodoro_10->view; +} diff --git a/Applications/Official/DEV_FW/source/pomodoro/views/pomodoro_10.h b/Applications/Official/DEV_FW/source/pomodoro/views/pomodoro_10.h new file mode 100644 index 000000000..8f27e6bd6 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pomodoro/views/pomodoro_10.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include "../pomodoro_timer.h" + +PomodoroTimer* pomodoro_10_alloc(); + +void pomodoro_10_free(PomodoroTimer* pomodoro_10); + +View* pomodoro_10_get_view(PomodoroTimer* pomodoro_10); diff --git a/Applications/Official/DEV_FW/source/pomodoro/views/pomodoro_25.c b/Applications/Official/DEV_FW/source/pomodoro/views/pomodoro_25.c new file mode 100644 index 000000000..01c5a7125 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pomodoro/views/pomodoro_25.c @@ -0,0 +1,46 @@ +#include "../pomodoro_timer.h" +#include "pomodoro_25.h" +#include +#include +#include +#include + +static void pomodoro_25_draw_callback(Canvas* canvas, void* context) { + int max_seconds = 60 * 25; + int max_seconds_rest = 60 * 5; + pomodoro_draw_callback(canvas, context, max_seconds, max_seconds_rest); +} + +static bool pomodoro_25_input_callback(InputEvent* event, void* context) { + furi_assert(context); + PomodoroTimer* pomodoro_25 = context; + + if(event->type == InputTypeLong && event->key == InputKeyBack) { + return false; + } else { + pomodoro_timer_process(pomodoro_25, event); + return true; + } +} + +PomodoroTimer* pomodoro_25_alloc() { + PomodoroTimer* pomodoro_25 = malloc(sizeof(PomodoroTimer)); + pomodoro_25->view = view_alloc(); + view_set_context(pomodoro_25->view, pomodoro_25); + view_allocate_model(pomodoro_25->view, ViewModelTypeLocking, sizeof(PomodoroTimerModel)); + view_set_draw_callback(pomodoro_25->view, pomodoro_25_draw_callback); + view_set_input_callback(pomodoro_25->view, pomodoro_25_input_callback); + + return pomodoro_25; +} + +void pomodoro_25_free(PomodoroTimer* pomodoro_25) { + furi_assert(pomodoro_25); + view_free(pomodoro_25->view); + free(pomodoro_25); +} + +View* pomodoro_25_get_view(PomodoroTimer* pomodoro_25) { + furi_assert(pomodoro_25); + return pomodoro_25->view; +} diff --git a/Applications/Official/DEV_FW/source/pomodoro/views/pomodoro_25.h b/Applications/Official/DEV_FW/source/pomodoro/views/pomodoro_25.h new file mode 100644 index 000000000..c3eb43976 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pomodoro/views/pomodoro_25.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include "../pomodoro_timer.h" + +PomodoroTimer* pomodoro_25_alloc(); + +void pomodoro_25_free(PomodoroTimer* pomodoro_25); + +View* pomodoro_25_get_view(PomodoroTimer* pomodoro_25); diff --git a/Applications/Official/DEV_FW/source/pomodoro/views/pomodoro_50.c b/Applications/Official/DEV_FW/source/pomodoro/views/pomodoro_50.c new file mode 100644 index 000000000..74f89122a --- /dev/null +++ b/Applications/Official/DEV_FW/source/pomodoro/views/pomodoro_50.c @@ -0,0 +1,46 @@ +#include "../pomodoro_timer.h" +#include "pomodoro_50.h" +#include +#include +#include +#include + +static void pomodoro_50_draw_callback(Canvas* canvas, void* context) { + int max_seconds = 60 * 50; + int max_seconds_rest = 60 * 10; + pomodoro_draw_callback(canvas, context, max_seconds, max_seconds_rest); +} + +static bool pomodoro_50_input_callback(InputEvent* event, void* context) { + furi_assert(context); + PomodoroTimer* pomodoro_50 = context; + + if(event->type == InputTypeLong && event->key == InputKeyBack) { + return false; + } else { + pomodoro_timer_process(pomodoro_50, event); + return true; + } +} + +PomodoroTimer* pomodoro_50_alloc() { + PomodoroTimer* pomodoro_50 = malloc(sizeof(PomodoroTimer)); + pomodoro_50->view = view_alloc(); + view_set_context(pomodoro_50->view, pomodoro_50); + view_allocate_model(pomodoro_50->view, ViewModelTypeLocking, sizeof(PomodoroTimerModel)); + view_set_draw_callback(pomodoro_50->view, pomodoro_50_draw_callback); + view_set_input_callback(pomodoro_50->view, pomodoro_50_input_callback); + + return pomodoro_50; +} + +void pomodoro_50_free(PomodoroTimer* pomodoro_50) { + furi_assert(pomodoro_50); + view_free(pomodoro_50->view); + free(pomodoro_50); +} + +View* pomodoro_50_get_view(PomodoroTimer* pomodoro_50) { + furi_assert(pomodoro_50); + return pomodoro_50->view; +} diff --git a/Applications/Official/DEV_FW/source/pomodoro/views/pomodoro_50.h b/Applications/Official/DEV_FW/source/pomodoro/views/pomodoro_50.h new file mode 100644 index 000000000..e0246d2d2 --- /dev/null +++ b/Applications/Official/DEV_FW/source/pomodoro/views/pomodoro_50.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include "../pomodoro_timer.h" + +PomodoroTimer* pomodoro_50_alloc(); + +void pomodoro_50_free(PomodoroTimer* pomodoro_50); + +View* pomodoro_50_get_view(PomodoroTimer* pomodoro_50); diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/LICENSE b/Applications/Official/DEV_FW/source/rc2014_coleco/LICENSE new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/rc2014_coleco/README.md b/Applications/Official/DEV_FW/source/rc2014_coleco/README.md new file mode 100644 index 000000000..0667860e7 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/rc2014_coleco/application.fam b/Applications/Official/DEV_FW/source/rc2014_coleco/application.fam new file mode 100644 index 000000000..2a3900e19 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/rc2014_coleco/coleco.c b/Applications/Official/DEV_FW/source/rc2014_coleco/coleco.c new file mode 100644 index 000000000..6ae050633 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/rc2014_coleco/coleco_10px.png b/Applications/Official/DEV_FW/source/rc2014_coleco/coleco_10px.png new file mode 100644 index 000000000..d51652adc Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/coleco_10px.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco0_17x17.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco0_17x17.png new file mode 100644 index 000000000..b53bc3b5f Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco0_17x17.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco0_hvr_17x17.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco0_hvr_17x17.png new file mode 100644 index 000000000..19627388e Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco0_hvr_17x17.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco1_17x17.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco1_17x17.png new file mode 100644 index 000000000..2c3977967 Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco1_17x17.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco1_hvr_17x17.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco1_hvr_17x17.png new file mode 100644 index 000000000..562c7e8db Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco1_hvr_17x17.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco2_17x17.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco2_17x17.png new file mode 100644 index 000000000..f8f18405f Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco2_17x17.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco2_hvr_17x17.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco2_hvr_17x17.png new file mode 100644 index 000000000..cac468981 Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco2_hvr_17x17.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco3_17x17.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco3_17x17.png new file mode 100644 index 000000000..3f2288392 Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco3_17x17.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco3_hvr_17x17.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco3_hvr_17x17.png new file mode 100644 index 000000000..c0015312a Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco3_hvr_17x17.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco4_17x17.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco4_17x17.png new file mode 100644 index 000000000..b3888910c Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco4_17x17.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco4_hvr_17x17.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco4_hvr_17x17.png new file mode 100644 index 000000000..63e086275 Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco4_hvr_17x17.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco5_17x17.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco5_17x17.png new file mode 100644 index 000000000..42eb3f59d Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco5_17x17.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco5_hvr_17x17.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco5_hvr_17x17.png new file mode 100644 index 000000000..b06fdfaf9 Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco5_hvr_17x17.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco6_17x17.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco6_17x17.png new file mode 100644 index 000000000..6ed3e239c Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco6_17x17.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco6_hvr_17x17.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco6_hvr_17x17.png new file mode 100644 index 000000000..4be93b365 Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco6_hvr_17x17.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco7_17x17.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco7_17x17.png new file mode 100644 index 000000000..2d200d71b Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco7_17x17.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco7_hvr_17x17.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco7_hvr_17x17.png new file mode 100644 index 000000000..8886dffef Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco7_hvr_17x17.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco8_17x17.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco8_17x17.png new file mode 100644 index 000000000..3905ef80d Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco8_17x17.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco8_hvr_17x17.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco8_hvr_17x17.png new file mode 100644 index 000000000..519ac1e97 Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco8_hvr_17x17.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco9_17x17.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco9_17x17.png new file mode 100644 index 000000000..a51739a1b Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco9_17x17.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco9_hvr_17x17.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco9_hvr_17x17.png new file mode 100644 index 000000000..206e0acd9 Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/Coleco9_hvr_17x17.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoAlt_18x9.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoAlt_18x9.png new file mode 100644 index 000000000..7e6853e52 Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoAlt_18x9.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoAlt_hvr_18x9.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoAlt_hvr_18x9.png new file mode 100644 index 000000000..6b15dcf7b Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoAlt_hvr_18x9.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoFire_18x9.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoFire_18x9.png new file mode 100644 index 000000000..8be499c21 Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoFire_18x9.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoFire_hvr_18x9.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoFire_hvr_18x9.png new file mode 100644 index 000000000..2b0d1d72c Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoFire_hvr_18x9.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoFire_sel_18x9.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoFire_sel_18x9.png new file mode 100644 index 000000000..383cd3a2d Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoFire_sel_18x9.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoJoystick_33x33.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoJoystick_33x33.png new file mode 100644 index 000000000..de4c574bc Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoJoystick_33x33.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoJoystick_hvr_33x33.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoJoystick_hvr_33x33.png new file mode 100644 index 000000000..fd653bfaf Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoJoystick_hvr_33x33.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoJoystick_sel_33x33.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoJoystick_sel_33x33.png new file mode 100644 index 000000000..ea01af395 Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoJoystick_sel_33x33.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoPound_17x17.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoPound_17x17.png new file mode 100644 index 000000000..10b46e0ca Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoPound_17x17.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoPound_hvr_17x17.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoPound_hvr_17x17.png new file mode 100644 index 000000000..784f3687c Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoPound_hvr_17x17.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoStar_17x17.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoStar_17x17.png new file mode 100644 index 000000000..3031c0baf Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoStar_17x17.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoStar_hvr_17x17.png b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoStar_hvr_17x17.png new file mode 100644 index 000000000..546922971 Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/icons/ColecoStar_hvr_17x17.png differ diff --git a/Applications/Official/DEV_FW/source/rc2014_coleco/interface/flipper-coleco.brd b/Applications/Official/DEV_FW/source/rc2014_coleco/interface/flipper-coleco.brd new file mode 100644 index 000000000..47ed27322 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/rc2014_coleco/interface/flipper-coleco.sch b/Applications/Official/DEV_FW/source/rc2014_coleco/interface/flipper-coleco.sch new file mode 100644 index 000000000..c29acd315 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/rc2014_coleco/ui.png b/Applications/Official/DEV_FW/source/rc2014_coleco/ui.png new file mode 100644 index 000000000..97c0ddc21 Binary files /dev/null and b/Applications/Official/DEV_FW/source/rc2014_coleco/ui.png differ diff --git a/Applications/Official/DEV_FW/source/scorched_tanks/LICENSE b/Applications/Official/DEV_FW/source/scorched_tanks/LICENSE new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/Applications/Official/DEV_FW/source/scorched_tanks/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/Official/DEV_FW/source/scorched_tanks/README.md b/Applications/Official/DEV_FW/source/scorched_tanks/README.md new file mode 100644 index 000000000..1c1970c19 --- /dev/null +++ b/Applications/Official/DEV_FW/source/scorched_tanks/README.md @@ -0,0 +1,35 @@ +# Scorched tanks - flipper zero game +A flipper zero game inspired by scorched earth. + +Current state is shown below: + +![input](scorched_tanks_v1.gif) + +## How to do: +Do not hesitate to create PRs. If you start working on sth, please start branch name with TODO id (e.g. `feature/2-change-tank-icon`) + +If you see an improvement, modify this readme and add suggestions via PR. Bugs can be reported via Issues + +## What to do next (it's not in the priority order): +5. flatten surface beneath tanks +7. explosion animation +9. sub-ghz multi-player +11. add other types of weapons +12. code AI +13. add terain destruction +14. add terain gravity (fall down after hitting the middle of the mountain) +18. Add menu with settings (vibartion on/off, difficulty) +20. add more ground modifiers (currently there is only one hill in the middle, maybe 2 hills, skew map, etc) + +## What have been done: +1. ~~remove movement~~ +2. ~~change tank icon~~ +3. ~~power as variable not constant~~ +4. ~~better map generation: high, low regions~~ (continuation in point 20.) +6. ~~collision with the enemy~~ +8. ~~local multi-player~~ +10. ~~improve projectile trace draw on angles > 80~~ +15. ~~FIX: firing stops when bullet fly above the screen~~ +16. ~~Slightly randomize player and enemy spawn locations~~ +17. ~~Shooting vibration~~ +19. ~~Add wind~~ diff --git a/Applications/Official/DEV_FW/source/scorched_tanks/application.fam b/Applications/Official/DEV_FW/source/scorched_tanks/application.fam new file mode 100644 index 000000000..fa0e6dd2b --- /dev/null +++ b/Applications/Official/DEV_FW/source/scorched_tanks/application.fam @@ -0,0 +1,12 @@ +App( + appid="Scorched_Tanks", + name="Scorched Tanks", + apptype=FlipperAppType.EXTERNAL, + entry_point="scorched_tanks_game_app", + cdefines=["APP_SCORCHED_TANKS_GAME"], + requires=["gui"], + stack_size=1 * 1024, + order=100, + fap_icon="scorchedTanks_10px.png", + fap_category="Games", +) diff --git a/Applications/Official/DEV_FW/source/scorched_tanks/scorchedTanks_10px.png b/Applications/Official/DEV_FW/source/scorched_tanks/scorchedTanks_10px.png new file mode 100644 index 000000000..6e1ae4c04 Binary files /dev/null and b/Applications/Official/DEV_FW/source/scorched_tanks/scorchedTanks_10px.png differ diff --git a/Applications/Official/DEV_FW/source/scorched_tanks/scorched_tanks_game_app.c b/Applications/Official/DEV_FW/source/scorched_tanks/scorched_tanks_game_app.c new file mode 100644 index 000000000..fd4c73ee7 --- /dev/null +++ b/Applications/Official/DEV_FW/source/scorched_tanks/scorched_tanks_game_app.c @@ -0,0 +1,540 @@ +#include +#include +#include +#include +#include +#include +#include + +#define SCREEN_WIDTH 128 +#define SCREEN_HEIGHT 64 +#define PLAYER_INIT_LOCATION_X 20 +#define PLAYER_INIT_AIM 45 +#define PLAYER_INIT_POWER 50 +#define ENEMY_INIT_LOCATION_X 108 +#define TANK_BARREL_LENGTH 8 +#define GRAVITY_FORCE (double)0.5 +#define MIN_GROUND_HEIGHT 35 +#define MAX_GROUND_HEIGHT 55 +#define MAX_FIRE_POWER 100 +#define MIN_FIRE_POWER 0 +#define TANK_COLLIDER_SIZE 3 +#define MAX_WIND 10 +#define MAX_PLAYER_DIFF_X 20 +#define MAX_ENEMY_DIFF_X 20 + +// That's a filthy workaround but sin(player.aimAngle) breaks it all... If you're able to fix it, please do create a PR! +double scorched_tanks_sin[91] = { + 0.000, -0.017, -0.035, -0.052, -0.070, -0.087, -0.105, -0.122, -0.139, -0.156, -0.174, -0.191, + -0.208, -0.225, -0.242, -0.259, -0.276, -0.292, -0.309, -0.326, -0.342, -0.358, -0.375, -0.391, + -0.407, -0.423, -0.438, -0.454, -0.469, -0.485, -0.500, -0.515, -0.530, -0.545, -0.559, -0.574, + -0.588, -0.602, -0.616, -0.629, -0.643, -0.656, -0.669, -0.682, -0.695, -0.707, -0.719, -0.731, + -0.743, -0.755, -0.766, -0.777, -0.788, -0.799, -0.809, -0.819, -0.829, -0.839, -0.848, -0.857, + -0.866, -0.875, -0.883, -0.891, -0.899, -0.906, -0.914, -0.921, -0.927, -0.934, -0.940, -0.946, + -0.951, -0.956, -0.961, -0.966, -0.970, -0.974, -0.978, -0.982, -0.985, -0.988, -0.990, -0.993, + -0.995, -0.996, -0.998, -0.999, -0.999, -1.000, -1.000}; +double scorched_tanks_cos[91] = { + 1.000, 1.000, 0.999, 0.999, 0.998, 0.996, 0.995, 0.993, 0.990, 0.988, 0.985, 0.982, 0.978, + 0.974, 0.970, 0.966, 0.961, 0.956, 0.951, 0.946, 0.940, 0.934, 0.927, 0.921, 0.914, 0.906, + 0.899, 0.891, 0.883, 0.875, 0.866, 0.857, 0.848, 0.839, 0.829, 0.819, 0.809, 0.799, 0.788, + 0.777, 0.766, 0.755, 0.743, 0.731, 0.719, 0.707, 0.695, 0.682, 0.669, 0.656, 0.643, 0.629, + 0.616, 0.602, 0.588, 0.574, 0.559, 0.545, 0.530, 0.515, 0.500, 0.485, 0.469, 0.454, 0.438, + 0.423, 0.407, 0.391, 0.375, 0.358, 0.342, 0.326, 0.309, 0.292, 0.276, 0.259, 0.242, 0.225, + 0.208, 0.191, 0.174, 0.156, 0.139, 0.122, 0.105, 0.087, 0.070, 0.052, 0.035, 0.017, 0.000}; +double scorched_tanks_tan[91] = { + 0.000, -0.017, -0.035, -0.052, -0.070, -0.087, -0.105, -0.123, -0.141, -0.158, -0.176, + -0.194, -0.213, -0.231, -0.249, -0.268, -0.287, -0.306, -0.325, -0.344, -0.364, -0.384, + -0.404, -0.424, -0.445, -0.466, -0.488, -0.510, -0.532, -0.554, -0.577, -0.601, -0.625, + -0.649, -0.674, -0.700, -0.727, -0.754, -0.781, -0.810, -0.839, -0.869, -0.900, -0.932, + -0.966, -1.000, -1.036, -1.072, -1.111, -1.150, -1.192, -1.235, -1.280, -1.327, -1.376, + -1.428, -1.483, -1.540, -1.600, -1.664, -1.732, -1.804, -1.881, -1.963, -2.050, -2.144, + -2.246, -2.356, -2.475, -2.605, -2.747, -2.904, -3.078, -3.271, -3.487, -3.732, -4.011, + -4.331, -4.704, -5.144, -5.671, -6.313, -7.115, -8.144, -9.513, -11.429, -14.298, -19.077, + -28.627, -57.254, -90747.269}; +unsigned char scorched_tanks_ground_modifiers[SCREEN_WIDTH] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 28, 26, 24, 22, 20, + 18, 16, 14, 12, 10, 8, 6, 4, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +typedef struct { + // +-----x + // | + // | + // y + uint8_t x; + uint8_t y; +} Point; + +typedef struct { + // +-----x + // | + // | + // y + double x; + double y; +} PointDetailed; + +typedef struct { + unsigned char locationX; + unsigned char hp; + int aimAngle; + unsigned char firePower; +} Tank; + +typedef struct { + Point ground[SCREEN_WIDTH]; + Tank player; + Tank enemy; + bool isPlayerTurn; + bool isShooting; + int windSpeed; + Point trajectory[SCREEN_WIDTH]; + unsigned char trajectoryAnimationStep; + PointDetailed bulletPosition; + PointDetailed bulletVector; +} Game; + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} ScorchedTanksEvent; + +int scorched_tanks_random(int min, int max) { + return min + rand() % ((max + 1) - min); +} + +void scorched_tanks_generate_ground(Game* game_state) { + int lastHeight = 45; + + for(unsigned char a = 0; a < SCREEN_WIDTH; a++) { + int diffHeight = scorched_tanks_random(-2, 3); + int changeLength = scorched_tanks_random(1, 6); + + if(diffHeight == 0) { + changeLength = 1; + } + + for(int b = 0; b < changeLength; b++) { + if(a + b < SCREEN_WIDTH) { + int index = a + b; + int newPoint = lastHeight + diffHeight; + newPoint = newPoint < MIN_GROUND_HEIGHT ? MIN_GROUND_HEIGHT : newPoint; + newPoint = newPoint > MAX_GROUND_HEIGHT ? MAX_GROUND_HEIGHT : newPoint; + game_state->ground[index].x = index; + game_state->ground[index].y = newPoint - scorched_tanks_ground_modifiers[a]; + lastHeight = newPoint; + } else { + a += b; + break; + } + } + + a += changeLength - 1; + } +} + +void scorched_tanks_init_game(Game* game_state) { + game_state->player.locationX = PLAYER_INIT_LOCATION_X + + scorched_tanks_random(0, MAX_PLAYER_DIFF_X) - + MAX_PLAYER_DIFF_X / 2; + game_state->player.aimAngle = PLAYER_INIT_AIM; + game_state->player.firePower = PLAYER_INIT_POWER; + game_state->enemy.aimAngle = PLAYER_INIT_AIM; + game_state->enemy.firePower = PLAYER_INIT_POWER; + game_state->enemy.locationX = + ENEMY_INIT_LOCATION_X + scorched_tanks_random(0, MAX_ENEMY_DIFF_X) - MAX_ENEMY_DIFF_X / 2; + game_state->isPlayerTurn = true; + + game_state->windSpeed = scorched_tanks_random(0, MAX_WIND); + + for(int x = 0; x < SCREEN_WIDTH; x++) { + game_state->trajectory[x].x = 0; + game_state->trajectory[x].y = 0; + } + + scorched_tanks_generate_ground(game_state); +} + +void scorched_tanks_calculate_trajectory(Game* game_state) { + if(game_state->isShooting) { + game_state->bulletVector.x += ((double)game_state->windSpeed - MAX_WIND / 2) / 40; + game_state->bulletVector.y += GRAVITY_FORCE; + + game_state->bulletPosition.x += game_state->bulletVector.x; + game_state->bulletPosition.y += game_state->bulletVector.y; + + int totalDistanceToEnemy = 100; + + if(game_state->isPlayerTurn) { + double distanceToEnemyX = game_state->enemy.locationX - game_state->bulletPosition.x; + double distanceToEnemyY = game_state->ground[game_state->enemy.locationX].y - + TANK_COLLIDER_SIZE - game_state->bulletPosition.y; + totalDistanceToEnemy = + sqrt(distanceToEnemyX * distanceToEnemyX + distanceToEnemyY * distanceToEnemyY); + } else { + double distanceToEnemyX = game_state->player.locationX - game_state->bulletPosition.x; + double distanceToEnemyY = game_state->ground[game_state->player.locationX].y - + TANK_COLLIDER_SIZE - game_state->bulletPosition.y; + totalDistanceToEnemy = + sqrt(distanceToEnemyX * distanceToEnemyX + distanceToEnemyY * distanceToEnemyY); + } + + if(totalDistanceToEnemy <= TANK_COLLIDER_SIZE) { + game_state->isShooting = false; + scorched_tanks_init_game(game_state); + game_state->isPlayerTurn = !game_state->isPlayerTurn; + return; + } + + if(game_state->bulletPosition.x > SCREEN_WIDTH || + game_state->bulletPosition.y > + game_state->ground[(int)round(game_state->bulletPosition.x)].y) { + game_state->isShooting = false; + game_state->bulletPosition.x = 0; + game_state->bulletPosition.y = 0; + game_state->windSpeed = scorched_tanks_random(0, MAX_WIND); + game_state->isPlayerTurn = !game_state->isPlayerTurn; + return; + } + + if(game_state->bulletPosition.y > 0) { + game_state->trajectory[game_state->trajectoryAnimationStep].x = + round(game_state->bulletPosition.x); + game_state->trajectory[game_state->trajectoryAnimationStep].y = + round(game_state->bulletPosition.y); + game_state->trajectoryAnimationStep++; + } + } +} + +static void scorched_tanks_draw_tank( + Canvas* const canvas, + unsigned char x, + unsigned char y, + bool isPlayer) { + unsigned char lineIndex = 0; + + if(isPlayer) { + // Draw tank base + canvas_draw_line(canvas, x - 3, y - lineIndex, x + 3, y - lineIndex++); + canvas_draw_line(canvas, x - 4, y - lineIndex, x + 4, y - lineIndex++); + canvas_draw_line(canvas, x - 4, y - lineIndex, x + 4, y - lineIndex++); + + // draw turret + canvas_draw_line(canvas, x - 2, y - lineIndex, x + 1, y - lineIndex++); + canvas_draw_line(canvas, x - 2, y - lineIndex, x, y - lineIndex++); + } else { + // Draw tank base + canvas_draw_line(canvas, x - 3, y - lineIndex, x + 3, y - lineIndex++); + canvas_draw_line(canvas, x - 4, y - lineIndex, x + 4, y - lineIndex++); + canvas_draw_line(canvas, x - 4, y - lineIndex, x + 4, y - lineIndex++); + + // draw turret + canvas_draw_line(canvas, x - 1, y - lineIndex, x + 2, y - lineIndex++); + canvas_draw_line(canvas, x, y - lineIndex, x + 2, y - lineIndex++); + } +} + +static void scorched_tanks_render_callback(Canvas* const canvas, void* ctx) { + const Game* game_state = acquire_mutex((ValueMutex*)ctx, 25); + + if(game_state == NULL) { + return; + } + + canvas_draw_frame(canvas, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + + canvas_set_color(canvas, ColorBlack); + + if(game_state->isShooting) { + canvas_draw_dot(canvas, game_state->bulletPosition.x, game_state->bulletPosition.y); + } + + for(int a = 1; a < SCREEN_WIDTH; a++) { + canvas_draw_line( + canvas, + game_state->ground[a - 1].x, + game_state->ground[a - 1].y, + game_state->ground[a].x, + game_state->ground[a].y); + + if(game_state->trajectory[a].y != 0) { + canvas_draw_dot(canvas, game_state->trajectory[a].x, game_state->trajectory[a].y); + } + } + + scorched_tanks_draw_tank( + canvas, + game_state->enemy.locationX, + game_state->ground[game_state->enemy.locationX].y - TANK_COLLIDER_SIZE, + true); + + scorched_tanks_draw_tank( + canvas, + game_state->player.locationX, + game_state->ground[game_state->player.locationX].y - TANK_COLLIDER_SIZE, + false); + + int aimX1 = 0; + int aimY1 = 0; + int aimX2 = 0; + int aimY2 = 0; + + if(game_state->isPlayerTurn) { + aimX1 = game_state->player.locationX; + aimY1 = game_state->ground[game_state->player.locationX].y - TANK_COLLIDER_SIZE; + + double sinFromAngle = scorched_tanks_sin[game_state->player.aimAngle]; + double cosFromAngle = scorched_tanks_cos[game_state->player.aimAngle]; + aimX2 = aimX1 + TANK_BARREL_LENGTH * cosFromAngle; + aimY2 = aimY1 + TANK_BARREL_LENGTH * sinFromAngle; + + aimX1 += 1; + aimX2 += 1; + } else { + aimX1 = game_state->enemy.locationX; + aimY1 = game_state->ground[game_state->enemy.locationX].y - TANK_COLLIDER_SIZE; + + double sinFromAngle = scorched_tanks_sin[game_state->enemy.aimAngle]; + double cosFromAngle = scorched_tanks_cos[game_state->enemy.aimAngle]; + aimX2 = aimX1 + TANK_BARREL_LENGTH * cosFromAngle; + aimY2 = aimY1 + TANK_BARREL_LENGTH * sinFromAngle; + + aimX2 = aimX1 - (aimX2 - aimX1); + + aimX1 -= 1; + aimX2 -= 1; + } + + canvas_draw_line(canvas, aimX1, aimY1 - 3, aimX2, aimY2 - 3); + + canvas_set_font(canvas, FontSecondary); + + char buffer2[12]; + snprintf(buffer2, sizeof(buffer2), "wind: %i", game_state->windSpeed - MAX_WIND / 2); + canvas_draw_str(canvas, 55, 10, buffer2); + + if(game_state->isPlayerTurn) { + canvas_draw_str(canvas, 93, 10, "player1"); + + char buffer[12]; + snprintf(buffer, sizeof(buffer), "a: %u", game_state->player.aimAngle); + canvas_draw_str(canvas, 2, 10, buffer); + + snprintf(buffer, sizeof(buffer), "p: %u", game_state->player.firePower); + canvas_draw_str(canvas, 27, 10, buffer); + } else { + canvas_draw_str(canvas, 93, 10, "player2"); + + char buffer[12]; + snprintf(buffer, sizeof(buffer), "a: %u", game_state->enemy.aimAngle); + canvas_draw_str(canvas, 2, 10, buffer); + + snprintf(buffer, sizeof(buffer), "p: %u", game_state->enemy.firePower); + canvas_draw_str(canvas, 27, 10, buffer); + } + + release_mutex((ValueMutex*)ctx, game_state); +} + +static void scorched_tanks_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + ScorchedTanksEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void scorched_tanks_update_timer_callback(FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + ScorchedTanksEvent event = {.type = EventTypeTick}; + furi_message_queue_put(event_queue, &event, 0); +} + +static void scorched_tanks_increase_power(Game* game_state) { + if(game_state->player.firePower < MAX_FIRE_POWER && !game_state->isShooting) { + if(game_state->isPlayerTurn && game_state->player.firePower < MAX_FIRE_POWER) { + game_state->player.firePower++; + } + + if(!game_state->isPlayerTurn && game_state->enemy.firePower < MAX_FIRE_POWER) { + game_state->enemy.firePower++; + } + } +} + +static void scorched_tanks_decrease_power(Game* game_state) { + if(game_state->player.firePower > MIN_FIRE_POWER && !game_state->isShooting) { + if(game_state->isPlayerTurn && game_state->player.firePower > MIN_FIRE_POWER) { + game_state->player.firePower--; + } + + if(!game_state->isPlayerTurn && game_state->enemy.firePower > MIN_FIRE_POWER) { + game_state->enemy.firePower--; + } + } +} + +static void scorched_tanks_aim_up(Game* game_state) { + if(!game_state->isShooting) { + if(game_state->isPlayerTurn && game_state->player.aimAngle < 90) { + game_state->player.aimAngle++; + } + + if(!game_state->isPlayerTurn && game_state->enemy.aimAngle < 90) { + game_state->enemy.aimAngle++; + } + } +} + +static void scorched_tanks_aim_down(Game* game_state) { + if(game_state->player.aimAngle > 0 && !game_state->isShooting) { + if(game_state->isPlayerTurn) { + game_state->player.aimAngle--; + } else { + game_state->enemy.aimAngle--; + } + } +} + +const NotificationSequence sequence_long_vibro = { + &message_vibro_on, + &message_delay_500, + &message_vibro_off, + NULL, +}; + +static void scorched_tanks_fire(Game* game_state) { + if(!game_state->isShooting) { + if(game_state->isPlayerTurn) { + double sinFromAngle = scorched_tanks_sin[game_state->player.aimAngle]; + double cosFromAngle = scorched_tanks_cos[game_state->player.aimAngle]; + unsigned char aimX1 = game_state->player.locationX; + unsigned char aimY1 = + game_state->ground[game_state->player.locationX].y - TANK_COLLIDER_SIZE; + int aimX2 = aimX1 + TANK_BARREL_LENGTH * cosFromAngle; + int aimY2 = aimY1 + TANK_BARREL_LENGTH * sinFromAngle; + game_state->bulletPosition.x = aimX2; + game_state->bulletPosition.y = aimY2; + game_state->bulletVector.x = scorched_tanks_cos[game_state->player.aimAngle] * + ((double)game_state->player.firePower / 10); + game_state->bulletVector.y = scorched_tanks_sin[game_state->player.aimAngle] * + ((double)game_state->player.firePower / 10); + } else { + double sinFromAngle = scorched_tanks_sin[game_state->enemy.aimAngle]; + double cosFromAngle = scorched_tanks_cos[game_state->enemy.aimAngle]; + unsigned char aimX1 = game_state->enemy.locationX; + unsigned char aimY1 = + game_state->ground[game_state->enemy.locationX].y - TANK_COLLIDER_SIZE; + int aimX2 = aimX1 + TANK_BARREL_LENGTH * cosFromAngle; + int aimY2 = aimY1 + TANK_BARREL_LENGTH * sinFromAngle; + aimX2 = aimX1 - (aimX2 - aimX1); + + game_state->bulletPosition.x = aimX2; + game_state->bulletPosition.y = aimY2; + game_state->bulletVector.x = -scorched_tanks_cos[game_state->enemy.aimAngle] * + ((double)game_state->enemy.firePower / 10); + game_state->bulletVector.y = scorched_tanks_sin[game_state->enemy.aimAngle] * + ((double)game_state->enemy.firePower / 10); + } + + game_state->trajectoryAnimationStep = 0; + + for(int x = 0; x < SCREEN_WIDTH; x++) { + game_state->trajectory[x].x = 0; + game_state->trajectory[x].y = 0; + } + + game_state->isShooting = true; + + NotificationApp* notification = furi_record_open("notification"); + notification_message(notification, &sequence_long_vibro); + notification_message(notification, &sequence_blink_white_100); + furi_record_close("notification"); + } +} + +int32_t scorched_tanks_game_app(void* p) { + UNUSED(p); + srand(DWT->CYCCNT); + + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(ScorchedTanksEvent)); + + Game* game_state = malloc(sizeof(Game)); + scorched_tanks_init_game(game_state); + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, game_state, sizeof(ScorchedTanksEvent))) { + FURI_LOG_E("ScorchedTanks", "cannot create mutex\r\n"); + free(game_state); + return 255; + } + + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, scorched_tanks_render_callback, &state_mutex); + view_port_input_callback_set(view_port, scorched_tanks_input_callback, event_queue); + + FuriTimer* timer = + furi_timer_alloc(scorched_tanks_update_timer_callback, FuriTimerTypePeriodic, event_queue); + furi_timer_start(timer, 2000); + + // Open GUI and register view_port + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + ScorchedTanksEvent event; + for(bool processing = true; processing;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 50); + + if(event.type == EventTypeKey) { // && game->isPlayerTurn + if(event.input.type == InputTypeRepeat || event.input.type == InputTypeShort) { + switch(event.input.key) { + case InputKeyUp: + scorched_tanks_aim_up(game_state); + break; + case InputKeyDown: + scorched_tanks_aim_down(game_state); + break; + case InputKeyRight: + scorched_tanks_increase_power(game_state); + break; + case InputKeyLeft: + scorched_tanks_decrease_power(game_state); + break; + case InputKeyOk: + scorched_tanks_fire(game_state); + break; + case InputKeyBack: + processing = false; + break; + default: + break; + } + } + } else if(event.type == EventTypeTick) { + scorched_tanks_calculate_trajectory(game_state); + } + + view_port_update(view_port); + release_mutex(&state_mutex, game_state); + } + + furi_timer_free(timer); + 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); + free(game_state); + + return 0; +} diff --git a/Applications/Official/DEV_FW/source/scorched_tanks/scorched_tanks_v1.gif b/Applications/Official/DEV_FW/source/scorched_tanks/scorched_tanks_v1.gif new file mode 100644 index 000000000..45b2ce117 Binary files /dev/null and b/Applications/Official/DEV_FW/source/scorched_tanks/scorched_tanks_v1.gif differ diff --git a/Applications/Official/DEV_FW/source/sentry_safe/LICENSE b/Applications/Official/DEV_FW/source/sentry_safe/LICENSE new file mode 100644 index 000000000..c0cd6b598 --- /dev/null +++ b/Applications/Official/DEV_FW/source/sentry_safe/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Etienne Sellan + +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/Official/DEV_FW/source/sentry_safe/README.md b/Applications/Official/DEV_FW/source/sentry_safe/README.md new file mode 100644 index 000000000..c4b00fefe --- /dev/null +++ b/Applications/Official/DEV_FW/source/sentry_safe/README.md @@ -0,0 +1,26 @@ +# flipperzero-sentry-safe-plugin + +Flipper zero exploiting vulnerability to open any Sentry Safe and Master Lock electronic safe without any pin code. + +[Vulnerability described here](https://github.com/H4ckd4ddy/bypass-sentry-safe) + +### Installation + +- Download [last release fap file](https://github.com/H4ckd4ddy/flipperzero-sentry-safe-plugin/releases/latest) +- Copy fap file to the apps folder of your flipper SD card + +### Usage + +- Start "Sentry Safe" plugin +- Place wires as described on the plugin screen +- Press enter +- Open safe + +### Build + +- Recursively clone your base firmware (official or not) +- Clone this repository in `applications_user` +- Build with `./fbt fap_dist APPSRC=applications_user/flipperzero-sentry-safe-plugin` +- Retreive builed fap in dist subfolders + +(More info about build tool [here](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/documentation/fbt.md)) diff --git a/Applications/Official/DEV_FW/source/sentry_safe/application.fam b/Applications/Official/DEV_FW/source/sentry_safe/application.fam new file mode 100644 index 000000000..2eb43f4aa --- /dev/null +++ b/Applications/Official/DEV_FW/source/sentry_safe/application.fam @@ -0,0 +1,12 @@ +App( + appid="GPIO_Sentry_Safe", + name="[GPIO] Sentry Safe", + apptype=FlipperAppType.EXTERNAL, + entry_point="sentry_safe_app", + cdefines=["APP_SENTRY_SAFE"], + requires=["gui"], + stack_size=1 * 1024, + order=40, + fap_icon="safe_10px.png", + fap_category="GPIO", +) diff --git a/Applications/Official/DEV_FW/source/sentry_safe/safe_10px.png b/Applications/Official/DEV_FW/source/sentry_safe/safe_10px.png new file mode 100644 index 000000000..713d79a8e Binary files /dev/null and b/Applications/Official/DEV_FW/source/sentry_safe/safe_10px.png differ diff --git a/Applications/Official/DEV_FW/source/sentry_safe/sentry_safe.c b/Applications/Official/DEV_FW/source/sentry_safe/sentry_safe.c new file mode 100644 index 000000000..4d9c12fc7 --- /dev/null +++ b/Applications/Official/DEV_FW/source/sentry_safe/sentry_safe.c @@ -0,0 +1,169 @@ +#include +#include +#include +#include + +#include + +typedef struct { + uint8_t status; +} SentryState; + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} Event; + +const char* status_texts[3] = {"[Press OK to open safe]", "Sending...", "Done !"}; + +static void sentry_safe_render_callback(Canvas* const canvas, void* ctx) { + const SentryState* sentry_state = acquire_mutex((ValueMutex*)ctx, 25); + if(sentry_state == NULL) { + return; + } + + // Before the function is called, the state is set with the canvas_reset(canvas) + + // Frame + canvas_draw_frame(canvas, 0, 0, 128, 64); + + // Message + canvas_set_font(canvas, FontPrimary); + + canvas_draw_frame(canvas, 22, 4, 84, 24); + canvas_draw_str_aligned(canvas, 64, 15, AlignCenter, AlignBottom, "BLACK <-> GND"); + canvas_draw_str_aligned(canvas, 64, 25, AlignCenter, AlignBottom, "GREEN <-> C1 "); + canvas_draw_str_aligned( + canvas, 64, 50, AlignCenter, AlignBottom, status_texts[sentry_state->status]); + + release_mutex((ValueMutex*)ctx, sentry_state); +} + +static void sentry_safe_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + Event event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +void send_request(int command, int a, int b, int c, int d, int e) { + int checksum = (command + a + b + c + d + e); + + furi_hal_gpio_init_simple(&gpio_ext_pc1, GpioModeOutputPushPull); + furi_hal_gpio_write(&gpio_ext_pc1, false); + furi_delay_ms(3.4); + furi_hal_gpio_write(&gpio_ext_pc1, true); + + furi_hal_uart_init(FuriHalUartIdLPUART1, 4800); + //furi_hal_uart_set_br(FuriHalUartIdLPUART1, 4800); + //furi_hal_uart_set_irq_cb(FuriHalUartIdLPUART1, usb_uart_on_irq_cb, usb_uart); + + uint8_t data[8] = {0x0, command, a, b, c, d, e, checksum}; + furi_hal_uart_tx(FuriHalUartIdLPUART1, data, 8); + + furi_delay_ms(100); + + furi_hal_uart_set_irq_cb(FuriHalUartIdLPUART1, NULL, NULL); + furi_hal_uart_deinit(FuriHalUartIdLPUART1); +} + +void reset_code(int a, int b, int c, int d, int e) { + send_request(0x75, a, b, c, d, e); +} + +void try_code(int a, int b, int c, int d, int e) { + send_request(0x71, a, b, c, d, e); +} + +int32_t sentry_safe_app(void* p) { + UNUSED(p); + + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(Event)); + + SentryState* sentry_state = malloc(sizeof(SentryState)); + + sentry_state->status = 0; + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, sentry_state, sizeof(SentryState))) { + FURI_LOG_E("SentrySafe", "cannot create mutex\r\n"); + furi_message_queue_free(event_queue); + free(sentry_state); + return 255; + } + + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, sentry_safe_render_callback, &state_mutex); + view_port_input_callback_set(view_port, sentry_safe_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); + + Event event; + for(bool processing = true; processing;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + + SentryState* sentry_state = (SentryState*)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: + break; + case InputKeyDown: + break; + case InputKeyRight: + break; + case InputKeyLeft: + break; + case InputKeyOk: + + if(sentry_state->status == 2) { + sentry_state->status = 0; + + } else if(sentry_state->status == 0) { + sentry_state->status = 1; + + reset_code(1, 2, 3, 4, 5); + furi_delay_ms(500); + try_code(1, 2, 3, 4, 5); + + sentry_state->status = 2; + } + + break; + case InputKeyBack: + processing = false; + break; + default: + break; + } + } + } + } + + view_port_update(view_port); + release_mutex(&state_mutex, sentry_state); + } + + // Reset GPIO pins to default state + furi_hal_gpio_init(&gpio_ext_pc1, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + + 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); + free(sentry_state); + + return 0; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/spectrum_analyzer/application.fam b/Applications/Official/DEV_FW/source/spectrum_analyzer/application.fam new file mode 100644 index 000000000..04bb946ee --- /dev/null +++ b/Applications/Official/DEV_FW/source/spectrum_analyzer/application.fam @@ -0,0 +1,12 @@ +App( + appid="Spectrum_Analyzer", + name="Spectrum Analyzer", + apptype=FlipperAppType.EXTERNAL, + entry_point="spectrum_analyzer_app", + cdefines=["APP_SPECTRUM_ANALYZER"], + requires=["gui"], + stack_size=2 * 1024, + order=12, + fap_icon="spectrum_10px.png", + fap_category="Tools", +) diff --git a/Applications/Official/DEV_FW/source/spectrum_analyzer/spectrum_10px.png b/Applications/Official/DEV_FW/source/spectrum_analyzer/spectrum_10px.png new file mode 100644 index 000000000..743c2460b Binary files /dev/null and b/Applications/Official/DEV_FW/source/spectrum_analyzer/spectrum_10px.png differ diff --git a/Applications/Official/DEV_FW/source/spectrum_analyzer/spectrum_analyzer.c b/Applications/Official/DEV_FW/source/spectrum_analyzer/spectrum_analyzer.c new file mode 100644 index 000000000..8d68c5a1a --- /dev/null +++ b/Applications/Official/DEV_FW/source/spectrum_analyzer/spectrum_analyzer.c @@ -0,0 +1,515 @@ +#include +#include + +#include +#include +#include +#include "spectrum_analyzer.h" + +#include +#include "spectrum_analyzer_worker.h" + +typedef struct { + uint16_t center_freq; + uint8_t width; + uint8_t band; + uint8_t vscroll; + + uint32_t channel0_frequency; + uint32_t spacing; + + bool mode_change; + + float max_rssi; + uint8_t max_rssi_dec; + uint8_t max_rssi_channel; + uint8_t channel_ss[NUM_CHANNELS]; +} SpectrumAnalyzerModel; + +typedef struct { + SpectrumAnalyzerModel* model; + FuriMutex* model_mutex; + + FuriMessageQueue* event_queue; + + ViewPort* view_port; + Gui* gui; + + SpectrumAnalyzerWorker* worker; +} SpectrumAnalyzer; + +void spectrum_analyzer_draw_scale(Canvas* canvas, const SpectrumAnalyzerModel* model) { + // Draw line + canvas_draw_line( + canvas, FREQ_START_X, FREQ_BOTTOM_Y, FREQ_START_X + FREQ_LENGTH_X, FREQ_BOTTOM_Y); + // Draw minor scale + for(int i = FREQ_START_X; i < FREQ_START_X + FREQ_LENGTH_X; i += 5) { + canvas_draw_line(canvas, i, FREQ_BOTTOM_Y, i, FREQ_BOTTOM_Y + 2); + } + // Draw major scale + for(int i = FREQ_START_X; i < FREQ_START_X + FREQ_LENGTH_X; i += 25) { + canvas_draw_line(canvas, i, FREQ_BOTTOM_Y, i, FREQ_BOTTOM_Y + 4); + } + + // Draw scale tags + uint16_t tag_left; + uint16_t tag_center; + uint16_t tag_right; + char temp_str[18]; + + tag_center = model->center_freq; + + switch(model->width) { + case NARROW: + tag_left = model->center_freq - 2; + tag_right = model->center_freq + 2; + break; + case ULTRANARROW: + tag_left = model->center_freq - 1; + tag_right = model->center_freq + 1; + break; + case ULTRAWIDE: + tag_left = model->center_freq - 40; + tag_right = model->center_freq + 40; + break; + default: + tag_left = model->center_freq - 10; + tag_right = model->center_freq + 10; + } + + canvas_set_font(canvas, FontSecondary); + snprintf(temp_str, 18, "%u", tag_left); + canvas_draw_str_aligned(canvas, FREQ_START_X, 63, AlignCenter, AlignBottom, temp_str); + snprintf(temp_str, 18, "%u", tag_center); + canvas_draw_str_aligned(canvas, 128 / 2, 63, AlignCenter, AlignBottom, temp_str); + snprintf(temp_str, 18, "%u", tag_right); + canvas_draw_str_aligned( + canvas, FREQ_START_X + FREQ_LENGTH_X - 1, 63, AlignCenter, AlignBottom, temp_str); +} + +static void spectrum_analyzer_render_callback(Canvas* const canvas, void* ctx) { + SpectrumAnalyzer* spectrum_analyzer = ctx; + //furi_check(furi_mutex_acquire(spectrum_analyzer->model_mutex, FuriWaitForever) == FuriStatusOk); + + SpectrumAnalyzerModel* model = spectrum_analyzer->model; + + spectrum_analyzer_draw_scale(canvas, model); + + for(uint8_t column = 0; column < 128; column++) { + uint8_t ss = model->channel_ss[column + 2]; + // Compress height to max of 64 values (255>>2) + uint8_t s = MAX((ss - model->vscroll) >> 2, 0); + uint8_t y = FREQ_BOTTOM_Y - s; // bar height + + // Draw each bar + canvas_draw_line(canvas, column, FREQ_BOTTOM_Y, column, y); + } + + if(model->mode_change) { + char temp_mode_str[12]; + switch(model->width) { + case NARROW: + strncpy(temp_mode_str, "NARROW", 12); + break; + case ULTRANARROW: + strncpy(temp_mode_str, "ULTRANARROW", 12); + break; + case ULTRAWIDE: + strncpy(temp_mode_str, "ULTRAWIDE", 12); + break; + default: + strncpy(temp_mode_str, "WIDE", 12); + break; + } + + // Current mode label + char tmp_str[21]; + snprintf(tmp_str, 21, "Mode: %s", temp_mode_str); + canvas_draw_str_aligned(canvas, 127, 4, AlignRight, AlignTop, tmp_str); + } + // Draw cross and label + if(model->max_rssi > PEAK_THRESHOLD) { + // Compress height to max of 64 values (255>>2) + uint8_t max_y = MAX((model->max_rssi_dec - model->vscroll) >> 2, 0); + max_y = (FREQ_BOTTOM_Y - max_y); + + // Cross + int16_t x1, x2, y1, y2; + x1 = model->max_rssi_channel - 2 - 2; + if(x1 < 0) x1 = 0; + y1 = max_y - 2; + if(y1 < 0) y1 = 0; + x2 = model->max_rssi_channel - 2 + 2; + if(x2 > 127) x2 = 127; + y2 = max_y + 2; + if(y2 > 63) y2 = 63; // SHOULD NOT HAPPEN CHECK! + canvas_draw_line(canvas, x1, y1, x2, y2); + + x1 = model->max_rssi_channel - 2 + 2; + if(x1 > 127) x1 = 127; + y1 = max_y - 2; + if(y1 < 0) y1 = 0; + x2 = model->max_rssi_channel - 2 - 2; + if(x2 < 0) x2 = 0; + y2 = max_y + 2; + if(y2 > 63) y2 = 63; // SHOULD NOT HAPPEN CHECK! + canvas_draw_line(canvas, (uint8_t)x1, (uint8_t)y1, (uint8_t)x2, (uint8_t)y2); + + // Label + char temp_str[36]; + snprintf( + temp_str, + 36, + "Peak: %3.2f Mhz %3.1f dbm", + ((double)(model->channel0_frequency + (model->max_rssi_channel * model->spacing)) / + 1000000), + (double)model->max_rssi); + canvas_draw_str_aligned(canvas, 127, 0, AlignRight, AlignTop, temp_str); + } + + //furi_mutex_release(spectrum_analyzer->model_mutex); + + // FURI_LOG_D("Spectrum", "model->vscroll %u", model->vscroll); +} + +static void spectrum_analyzer_input_callback(InputEvent* input_event, void* ctx) { + SpectrumAnalyzer* spectrum_analyzer = ctx; + // Only handle short presses + if(input_event->type == InputTypeShort) { + furi_message_queue_put(spectrum_analyzer->event_queue, input_event, FuriWaitForever); + } +} + +static void spectrum_analyzer_worker_callback( + void* channel_ss, + float max_rssi, + uint8_t max_rssi_dec, + uint8_t max_rssi_channel, + void* context) { + SpectrumAnalyzer* spectrum_analyzer = context; + furi_check( + furi_mutex_acquire(spectrum_analyzer->model_mutex, FuriWaitForever) == FuriStatusOk); + + SpectrumAnalyzerModel* model = (SpectrumAnalyzerModel*)spectrum_analyzer->model; + memcpy(model->channel_ss, (uint8_t*)channel_ss, sizeof(uint8_t) * NUM_CHANNELS); + model->max_rssi = max_rssi; + model->max_rssi_dec = max_rssi_dec; + model->max_rssi_channel = max_rssi_channel; + + furi_mutex_release(spectrum_analyzer->model_mutex); + view_port_update(spectrum_analyzer->view_port); +} + +void spectrum_analyzer_calculate_frequencies(SpectrumAnalyzerModel* model) { + // REDO ALL THIS. CALCULATE ONLY WITH SPACING! + + uint8_t new_band; + uint32_t min_hz; + uint32_t max_hz; + uint8_t margin; + uint8_t step; + uint16_t upper_limit; + uint16_t lower_limit; + uint16_t next_up; + uint16_t next_down; + uint8_t next_band_up; + uint8_t next_band_down; + + switch(model->width) { + case NARROW: + margin = NARROW_MARGIN; + step = NARROW_STEP; + model->spacing = NARROW_SPACING; + break; + case ULTRANARROW: + margin = ULTRANARROW_MARGIN; + step = ULTRANARROW_STEP; + model->spacing = ULTRANARROW_SPACING; + break; + case ULTRAWIDE: + margin = ULTRAWIDE_MARGIN; + step = ULTRAWIDE_STEP; + model->spacing = ULTRAWIDE_SPACING; + /* nearest 20 MHz step */ + model->center_freq = ((model->center_freq + 10) / 20) * 20; + break; + default: + margin = WIDE_MARGIN; + step = WIDE_STEP; + model->spacing = WIDE_SPACING; + /* nearest 5 MHz step */ + model->center_freq = ((model->center_freq + 2) / 5) * 5; + break; + } + + /* handle cases near edges of bands */ + if(model->center_freq > EDGE_900) { + new_band = BAND_900; + upper_limit = UPPER(MAX_900, margin, step); + lower_limit = LOWER(MIN_900, margin, step); + next_up = LOWER(MIN_300, margin, step); + next_down = UPPER(MAX_400, margin, step); + next_band_up = BAND_300; + next_band_down = BAND_400; + } else if(model->center_freq > EDGE_400) { + new_band = BAND_400; + upper_limit = UPPER(MAX_400, margin, step); + lower_limit = LOWER(MIN_400, margin, step); + next_up = LOWER(MIN_900, margin, step); + next_down = UPPER(MAX_300, margin, step); + next_band_up = BAND_900; + next_band_down = BAND_300; + } else { + new_band = BAND_300; + upper_limit = UPPER(MAX_300, margin, step); + lower_limit = LOWER(MIN_300, margin, step); + next_up = LOWER(MIN_400, margin, step); + next_down = UPPER(MAX_900, margin, step); + next_band_up = BAND_400; + next_band_down = BAND_900; + } + + if(model->center_freq > upper_limit) { + model->center_freq = upper_limit; + if(new_band == model->band) { + new_band = next_band_up; + model->center_freq = next_up; + } + } else if(model->center_freq < lower_limit) { + model->center_freq = lower_limit; + if(new_band == model->band) { + new_band = next_band_down; + model->center_freq = next_down; + } + } + + model->band = new_band; + /* doing everything in Hz from here on */ + switch(model->band) { + case BAND_400: + min_hz = MIN_400 * 1000000; + max_hz = MAX_400 * 1000000; + break; + case BAND_300: + min_hz = MIN_300 * 1000000; + max_hz = MAX_300 * 1000000; + break; + default: + min_hz = MIN_900 * 1000000; + max_hz = MAX_900 * 1000000; + break; + } + + model->channel0_frequency = + model->center_freq * 1000000 - (model->spacing * ((NUM_CHANNELS / 2) + 1)); + + // /* calibrate upper channels */ + // hz = model->center_freq * 1000000; + // max_chan = NUM_CHANNELS / 2; + // while (hz <= max_hz && max_chan < NUM_CHANNELS) { + // instance->chan_table[max_chan].frequency = hz; + // FURI_LOG_T("Spectrum", "calibrate_freq ch[%u]: %lu", max_chan, hz); + // hz += model->spacing; + // max_chan++; + // } + + // /* calibrate lower channels */ + // hz = instance->freq * 1000000 - model->spacing; + // min_chan = NUM_CHANNELS / 2; + // while (hz >= min_hz && min_chan > 0) { + // min_chan--; + // instance->chan_table[min_chan].frequency = hz; + // FURI_LOG_T("Spectrum", "calibrate_freq ch[%u]: %lu", min_chan, hz); + // hz -= model->spacing; + // } + + model->max_rssi = -200.0; + model->max_rssi_dec = 0; + + FURI_LOG_D("Spectrum", "setup_frequencies - max_hz: %lu - min_hz: %lu", max_hz, min_hz); + FURI_LOG_D("Spectrum", "center_freq: %u", model->center_freq); + FURI_LOG_D( + "Spectrum", + "ch[0]: %lu - ch[%u]: %lu", + model->channel0_frequency, + NUM_CHANNELS - 1, + model->channel0_frequency + ((NUM_CHANNELS - 1) * model->spacing)); +} + +SpectrumAnalyzer* spectrum_analyzer_alloc() { + SpectrumAnalyzer* instance = malloc(sizeof(SpectrumAnalyzer)); + instance->model = malloc(sizeof(SpectrumAnalyzerModel)); + + SpectrumAnalyzerModel* model = instance->model; + + for(uint8_t ch = 0; ch < NUM_CHANNELS - 1; ch++) { + model->channel_ss[ch] = 0; + } + model->max_rssi_dec = 0; + model->max_rssi_channel = 0; + model->max_rssi = PEAK_THRESHOLD - 1; // Should initializar to < PEAK_THRESHOLD + + model->center_freq = DEFAULT_FREQ; + model->width = WIDE; + model->band = BAND_400; + + model->vscroll = DEFAULT_VSCROLL; + + instance->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal); + instance->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + + instance->worker = spectrum_analyzer_worker_alloc(); + + spectrum_analyzer_worker_set_callback( + instance->worker, spectrum_analyzer_worker_callback, instance); + + // Set system callbacks + instance->view_port = view_port_alloc(); + view_port_draw_callback_set(instance->view_port, spectrum_analyzer_render_callback, instance); + view_port_input_callback_set(instance->view_port, spectrum_analyzer_input_callback, instance); + + // Open GUI and register view_port + instance->gui = furi_record_open(RECORD_GUI); + gui_add_view_port(instance->gui, instance->view_port, GuiLayerFullscreen); + + return instance; +} + +void spectrum_analyzer_free(SpectrumAnalyzer* instance) { + // view_port_enabled_set(view_port, false); + gui_remove_view_port(instance->gui, instance->view_port); + furi_record_close(RECORD_GUI); + view_port_free(instance->view_port); + + spectrum_analyzer_worker_free(instance->worker); + + furi_message_queue_free(instance->event_queue); + + furi_mutex_free(instance->model_mutex); + + free(instance->model); + free(instance); + + furi_hal_subghz_idle(); + furi_hal_subghz_sleep(); +} + +int32_t spectrum_analyzer_app(void* p) { + UNUSED(p); + + SpectrumAnalyzer* spectrum_analyzer = spectrum_analyzer_alloc(); + InputEvent input; + + furi_hal_power_suppress_charge_enter(); + + FURI_LOG_D("Spectrum", "Main Loop - Starting worker"); + furi_delay_ms(50); + + spectrum_analyzer_worker_start(spectrum_analyzer->worker); + + FURI_LOG_D("Spectrum", "Main Loop - Wait on queue"); + furi_delay_ms(50); + + while(furi_message_queue_get(spectrum_analyzer->event_queue, &input, FuriWaitForever) == + FuriStatusOk) { + furi_check( + furi_mutex_acquire(spectrum_analyzer->model_mutex, FuriWaitForever) == FuriStatusOk); + + FURI_LOG_D("Spectrum", "Main Loop - Input: %u", input.key); + + SpectrumAnalyzerModel* model = spectrum_analyzer->model; + + uint8_t vstep = VERTICAL_SHORT_STEP; + uint8_t hstep; + + bool exit_loop = false; + + switch(model->width) { + case NARROW: + hstep = NARROW_STEP; + break; + case ULTRANARROW: + hstep = ULTRANARROW_STEP; + break; + case ULTRAWIDE: + hstep = ULTRAWIDE_STEP; + break; + default: + hstep = WIDE_STEP; + break; + } + + switch(input.key) { + case InputKeyUp: + model->vscroll = MAX(model->vscroll - vstep, MIN_VSCROLL); + FURI_LOG_D("Spectrum", "Vscroll: %u", model->vscroll); + break; + case InputKeyDown: + model->vscroll = MIN(model->vscroll + vstep, MAX_VSCROLL); + FURI_LOG_D("Spectrum", "Vscroll: %u", model->vscroll); + break; + case InputKeyRight: + model->center_freq += hstep; + FURI_LOG_D("Spectrum", "center_freq: %u", model->center_freq); + spectrum_analyzer_calculate_frequencies(model); + spectrum_analyzer_worker_set_frequencies( + spectrum_analyzer->worker, model->channel0_frequency, model->spacing, model->width); + break; + case InputKeyLeft: + model->center_freq -= hstep; + spectrum_analyzer_calculate_frequencies(model); + spectrum_analyzer_worker_set_frequencies( + spectrum_analyzer->worker, model->channel0_frequency, model->spacing, model->width); + FURI_LOG_D("Spectrum", "center_freq: %u", model->center_freq); + break; + case InputKeyOk: { + switch(model->width) { + case WIDE: + model->width = NARROW; + break; + case NARROW: + model->width = ULTRANARROW; + break; + case ULTRANARROW: + model->width = ULTRAWIDE; + break; + case ULTRAWIDE: + model->width = WIDE; + break; + default: + model->width = WIDE; + break; + } + } + + model->mode_change = true; + view_port_update(spectrum_analyzer->view_port); + + furi_delay_ms(1000); + + model->mode_change = false; + spectrum_analyzer_calculate_frequencies(model); + spectrum_analyzer_worker_set_frequencies( + spectrum_analyzer->worker, model->channel0_frequency, model->spacing, model->width); + FURI_LOG_D("Spectrum", "Width: %u", model->width); + break; + case InputKeyBack: + exit_loop = true; + break; + default: + break; + } + + furi_mutex_release(spectrum_analyzer->model_mutex); + view_port_update(spectrum_analyzer->view_port); + if(exit_loop == true) break; + } + + spectrum_analyzer_worker_stop(spectrum_analyzer->worker); + + furi_hal_power_suppress_charge_exit(); + + spectrum_analyzer_free(spectrum_analyzer); + + return 0; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/spectrum_analyzer/spectrum_analyzer.h b/Applications/Official/DEV_FW/source/spectrum_analyzer/spectrum_analyzer.h new file mode 100644 index 000000000..b5cd6d8fe --- /dev/null +++ b/Applications/Official/DEV_FW/source/spectrum_analyzer/spectrum_analyzer.h @@ -0,0 +1,73 @@ +#define NUM_CHANNELS 132 +#define NUM_CHUNKS 6 +#define CHUNK_SIZE (NUM_CHANNELS / NUM_CHUNKS) + +// Screen coordinates +#define FREQ_BOTTOM_Y 50 +#define FREQ_START_X 14 +// How many channels displayed on the scale (On screen still 218) +#define FREQ_LENGTH_X 102 +// dBm threshold to show peak value +#define PEAK_THRESHOLD -85 + +/* + * ultrawide mode: 80 MHz on screen, 784 kHz per channel + * wide mode (default): 20 MHz on screen, 196 kHz per channel + * narrow mode: 4 MHz on screen, 39 kHz per channel + * ultranarrow mode: 2 MHz on screen, 19 kHz per channel + */ +#define WIDE 0 +#define NARROW 1 +#define ULTRAWIDE 2 +#define ULTRANARROW 3 + +/* channel spacing in Hz */ +#define WIDE_SPACING 196078 +#define NARROW_SPACING 39215 +#define ULTRAWIDE_SPACING 784313 +#define ULTRANARROW_SPACING 19607 + +/* vertical scrolling */ +#define VERTICAL_SHORT_STEP 16 +#define MAX_VSCROLL 120 +#define MIN_VSCROLL 0 +#define DEFAULT_VSCROLL 48 + +/* frequencies in MHz */ +#define DEFAULT_FREQ 440 +#define WIDE_STEP 5 +#define NARROW_STEP 1 +#define ULTRAWIDE_STEP 20 +#define ULTRANARROW_STEP 1 +#define WIDE_MARGIN 13 +#define NARROW_MARGIN 3 +#define ULTRAWIDE_MARGIN 42 +#define ULTRANARROW_MARGIN 1 + +/* frequency bands supported by device */ +#define BAND_300 0 +#define BAND_400 1 +#define BAND_900 2 + +/* band limits in MHz */ +#define MIN_300 281 +#define CEN_300 315 +#define MAX_300 361 +#define MIN_400 378 +#define CEN_400 435 +#define MAX_400 481 +#define MIN_900 749 +#define CEN_900 855 +#define MAX_900 962 + +/* band transition points in MHz */ +#define EDGE_400 369 +#define EDGE_900 615 + +/* VCO transition points in Hz */ +#define MID_300 318000000 +#define MID_400 424000000 +#define MID_900 848000000 + +#define UPPER(a, b, c) ((((a) - (b) + ((c) / 2)) / (c)) * (c)) +#define LOWER(a, b, c) ((((a) + (b)) / (c)) * (c)) \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/spectrum_analyzer/spectrum_analyzer_worker.c b/Applications/Official/DEV_FW/source/spectrum_analyzer/spectrum_analyzer_worker.c new file mode 100644 index 000000000..e670d2808 --- /dev/null +++ b/Applications/Official/DEV_FW/source/spectrum_analyzer/spectrum_analyzer_worker.c @@ -0,0 +1,198 @@ +#include "spectrum_analyzer.h" +#include "spectrum_analyzer_worker.h" + +#include +#include + +#include + +struct SpectrumAnalyzerWorker { + FuriThread* thread; + bool should_work; + + SpectrumAnalyzerWorkerCallback callback; + void* callback_context; + + uint32_t channel0_frequency; + uint32_t spacing; + uint8_t width; + float max_rssi; + uint8_t max_rssi_dec; + uint8_t max_rssi_channel; + + uint8_t channel_ss[NUM_CHANNELS]; +}; + +/* set the channel bandwidth */ +void spectrum_analyzer_worker_set_filter(SpectrumAnalyzerWorker* instance) { + uint8_t filter_config[2][2] = { + {CC1101_MDMCFG4, 0}, + {0, 0}, + }; + + // FURI_LOG_D("SpectrumWorker", "spectrum_analyzer_worker_set_filter: width = %u", instance->width); + + /* channel spacing should fit within 80% of channel filter bandwidth */ + switch(instance->width) { + case NARROW: + filter_config[0][1] = 0xFC; /* 39.2 kHz / .8 = 49 kHz --> 58 kHz */ + break; + case ULTRAWIDE: + filter_config[0][1] = 0x0C; /* 784 kHz / .8 = 980 kHz --> 812 kHz */ + break; + default: + filter_config[0][1] = 0x6C; /* 196 kHz / .8 = 245 kHz --> 270 kHz */ + break; + } + furi_hal_subghz_load_registers((uint8_t*)filter_config); +} + +static int32_t spectrum_analyzer_worker_thread(void* context) { + furi_assert(context); + SpectrumAnalyzerWorker* instance = context; + + FURI_LOG_D("SpectrumWorker", "spectrum_analyzer_worker_thread: Start"); + + // Start CC1101 + furi_hal_subghz_reset(); + furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); + furi_hal_subghz_set_frequency(433920000); + furi_hal_subghz_flush_rx(); + furi_hal_subghz_rx(); + + static const uint8_t radio_config[][2] = { + {CC1101_FSCTRL1, 0x12}, + {CC1101_FSCTRL0, 0x00}, + + {CC1101_AGCCTRL2, 0xC0}, + + {CC1101_MDMCFG4, 0x6C}, + {CC1101_TEST2, 0x88}, + {CC1101_TEST1, 0x31}, + {CC1101_TEST0, 0x09}, + /* End */ + {0, 0}, + }; + + while(instance->should_work) { + furi_delay_ms(50); + + // FURI_LOG_T("SpectrumWorker", "spectrum_analyzer_worker_thread: Worker Loop"); + furi_hal_subghz_idle(); + furi_hal_subghz_load_registers((uint8_t*)radio_config); + + // TODO: Check filter! + // spectrum_analyzer_worker_set_filter(instance); + + instance->max_rssi_dec = 0; + + // Visit each channel non-consecutively + for(uint8_t ch_offset = 0, chunk = 0; ch_offset < CHUNK_SIZE; + ++chunk >= NUM_CHUNKS && ++ch_offset && (chunk = 0)) { + uint8_t ch = chunk * CHUNK_SIZE + ch_offset; + furi_hal_subghz_set_frequency(instance->channel0_frequency + (ch * instance->spacing)); + + furi_hal_subghz_rx(); + furi_delay_ms(3); + + // dec dBm + //max_ss = 127 -> -10.5 + //max_ss = 0 -> -74.0 + //max_ss = 255 -> -74.5 + //max_ss = 128 -> -138.0 + instance->channel_ss[ch] = (furi_hal_subghz_get_rssi() + 138) * 2; + + if(instance->channel_ss[ch] > instance->max_rssi_dec) { + instance->max_rssi_dec = instance->channel_ss[ch]; + instance->max_rssi = (instance->channel_ss[ch] / 2) - 138; + instance->max_rssi_channel = ch; + } + + furi_hal_subghz_idle(); + } + + // FURI_LOG_T("SpectrumWorker", "channel_ss[0]: %u", instance->channel_ss[0]); + + // Report results back to main thread + if(instance->callback) { + instance->callback( + (void*)&(instance->channel_ss), + instance->max_rssi, + instance->max_rssi_dec, + instance->max_rssi_channel, + instance->callback_context); + } + } + + return 0; +} + +SpectrumAnalyzerWorker* spectrum_analyzer_worker_alloc() { + FURI_LOG_D("Spectrum", "spectrum_analyzer_worker_alloc: Start"); + + SpectrumAnalyzerWorker* instance = malloc(sizeof(SpectrumAnalyzerWorker)); + + instance->thread = furi_thread_alloc(); + furi_thread_set_name(instance->thread, "SpectrumWorker"); + furi_thread_set_stack_size(instance->thread, 2048); + furi_thread_set_context(instance->thread, instance); + furi_thread_set_callback(instance->thread, spectrum_analyzer_worker_thread); + + FURI_LOG_D("Spectrum", "spectrum_analyzer_worker_alloc: End"); + + return instance; +} + +void spectrum_analyzer_worker_free(SpectrumAnalyzerWorker* instance) { + FURI_LOG_D("Spectrum", "spectrum_analyzer_worker_free"); + furi_assert(instance); + furi_thread_free(instance->thread); + free(instance); +} + +void spectrum_analyzer_worker_set_callback( + SpectrumAnalyzerWorker* instance, + SpectrumAnalyzerWorkerCallback callback, + void* context) { + furi_assert(instance); + instance->callback = callback; + instance->callback_context = context; +} + +void spectrum_analyzer_worker_set_frequencies( + SpectrumAnalyzerWorker* instance, + uint32_t channel0_frequency, + uint32_t spacing, + uint8_t width) { + furi_assert(instance); + + FURI_LOG_D( + "SpectrumWorker", + "spectrum_analyzer_worker_set_frequencies - channel0_frequency= %lu - spacing = %lu - width = %u", + channel0_frequency, + spacing, + width); + + instance->channel0_frequency = channel0_frequency; + instance->spacing = spacing; + instance->width = width; +} + +void spectrum_analyzer_worker_start(SpectrumAnalyzerWorker* instance) { + FURI_LOG_D("Spectrum", "spectrum_analyzer_worker_start"); + + furi_assert(instance); + furi_assert(instance->should_work == false); + + instance->should_work = true; + furi_thread_start(instance->thread); +} + +void spectrum_analyzer_worker_stop(SpectrumAnalyzerWorker* instance) { + FURI_LOG_D("Spectrum", "spectrum_analyzer_worker_stop"); + furi_assert(instance); + furi_assert(instance->should_work == true); + + instance->should_work = false; + furi_thread_join(instance->thread); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/spectrum_analyzer/spectrum_analyzer_worker.h b/Applications/Official/DEV_FW/source/spectrum_analyzer/spectrum_analyzer_worker.h new file mode 100644 index 000000000..ca051dacc --- /dev/null +++ b/Applications/Official/DEV_FW/source/spectrum_analyzer/spectrum_analyzer_worker.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +typedef void (*SpectrumAnalyzerWorkerCallback)( + void* chan_table, + float max_rssi, + uint8_t max_rssi_dec, + uint8_t max_rssi_channel, + void* context); + +typedef struct SpectrumAnalyzerWorker SpectrumAnalyzerWorker; + +SpectrumAnalyzerWorker* spectrum_analyzer_worker_alloc(); + +void spectrum_analyzer_worker_free(SpectrumAnalyzerWorker* instance); + +void spectrum_analyzer_worker_set_callback( + SpectrumAnalyzerWorker* instance, + SpectrumAnalyzerWorkerCallback callback, + void* context); + +void spectrum_analyzer_worker_set_filter(SpectrumAnalyzerWorker* instance); + +void spectrum_analyzer_worker_set_frequencies( + SpectrumAnalyzerWorker* instance, + uint32_t channel0_frequency, + uint32_t spacing, + uint8_t width); + +void spectrum_analyzer_worker_start(SpectrumAnalyzerWorker* instance); + +void spectrum_analyzer_worker_stop(SpectrumAnalyzerWorker* instance); diff --git a/Applications/Official/DEV_FW/source/tanksgame/application.fam b/Applications/Official/DEV_FW/source/tanksgame/application.fam new file mode 100644 index 000000000..748988297 --- /dev/null +++ b/Applications/Official/DEV_FW/source/tanksgame/application.fam @@ -0,0 +1,13 @@ +App( + appid="Tanks", + name="Tanks", + apptype=FlipperAppType.EXTERNAL, + entry_point="tanks_game_app", + cdefines=["APP_TANKS_GAME"], + requires=["gui", "subghz"], + stack_size=4 * 1024, + order=730, + fap_icon="tanksIcon.png", + fap_category="Games", + fap_icon_assets="images", +) diff --git a/Applications/Official/DEV_FW/source/tanksgame/constants.h b/Applications/Official/DEV_FW/source/tanksgame/constants.h new file mode 100644 index 000000000..d65dd32ea --- /dev/null +++ b/Applications/Official/DEV_FW/source/tanksgame/constants.h @@ -0,0 +1,19 @@ +#ifndef FLIPPERZERO_FIRMWARE_CONSTANTS_H +#define FLIPPERZERO_FIRMWARE_CONSTANTS_H + +const uint8_t SCREEN_WIDTH_TANKS = 128; +const uint8_t SCREEN_HEIGHT_TANKS = 64; + +const uint8_t FIELD_WIDTH = 16; +const uint8_t FIELD_HEIGHT = 11; + +const uint16_t TURN_LENGTH = 300; +const uint16_t LONG_PRESS_LENGTH = 10; + +const uint8_t SHOT_COOLDOWN = 5; +const uint8_t RESPAWN_COOLDOWN = 8; +const uint8_t PLAYER_RESPAWN_COOLDOWN = 1; + +const uint8_t CELL_LENGTH_PIXELS = 6; + +#endif diff --git a/Applications/Official/DEV_FW/source/tanksgame/images/HappyFlipper_128x64.png b/Applications/Official/DEV_FW/source/tanksgame/images/HappyFlipper_128x64.png new file mode 100644 index 000000000..d95412f3f Binary files /dev/null and b/Applications/Official/DEV_FW/source/tanksgame/images/HappyFlipper_128x64.png differ diff --git a/Applications/Official/DEV_FW/source/tanksgame/images/TanksSplashScreen_128x64.png b/Applications/Official/DEV_FW/source/tanksgame/images/TanksSplashScreen_128x64.png new file mode 100644 index 000000000..a0dc26487 Binary files /dev/null and b/Applications/Official/DEV_FW/source/tanksgame/images/TanksSplashScreen_128x64.png differ diff --git a/Applications/Official/DEV_FW/source/tanksgame/images/enemy_down.png b/Applications/Official/DEV_FW/source/tanksgame/images/enemy_down.png new file mode 100644 index 000000000..84d64ed44 Binary files /dev/null and b/Applications/Official/DEV_FW/source/tanksgame/images/enemy_down.png differ diff --git a/Applications/Official/DEV_FW/source/tanksgame/images/enemy_left.png b/Applications/Official/DEV_FW/source/tanksgame/images/enemy_left.png new file mode 100644 index 000000000..e118d03a8 Binary files /dev/null and b/Applications/Official/DEV_FW/source/tanksgame/images/enemy_left.png differ diff --git a/Applications/Official/DEV_FW/source/tanksgame/images/enemy_right.png b/Applications/Official/DEV_FW/source/tanksgame/images/enemy_right.png new file mode 100644 index 000000000..8c5f0603b Binary files /dev/null and b/Applications/Official/DEV_FW/source/tanksgame/images/enemy_right.png differ diff --git a/Applications/Official/DEV_FW/source/tanksgame/images/enemy_up.png b/Applications/Official/DEV_FW/source/tanksgame/images/enemy_up.png new file mode 100644 index 000000000..8ad01f635 Binary files /dev/null and b/Applications/Official/DEV_FW/source/tanksgame/images/enemy_up.png differ diff --git a/Applications/Official/DEV_FW/source/tanksgame/images/projectile_down.png b/Applications/Official/DEV_FW/source/tanksgame/images/projectile_down.png new file mode 100644 index 000000000..fd3f8c123 Binary files /dev/null and b/Applications/Official/DEV_FW/source/tanksgame/images/projectile_down.png differ diff --git a/Applications/Official/DEV_FW/source/tanksgame/images/projectile_left.png b/Applications/Official/DEV_FW/source/tanksgame/images/projectile_left.png new file mode 100644 index 000000000..c8c54786d Binary files /dev/null and b/Applications/Official/DEV_FW/source/tanksgame/images/projectile_left.png differ diff --git a/Applications/Official/DEV_FW/source/tanksgame/images/projectile_right.png b/Applications/Official/DEV_FW/source/tanksgame/images/projectile_right.png new file mode 100644 index 000000000..fbf5359ad Binary files /dev/null and b/Applications/Official/DEV_FW/source/tanksgame/images/projectile_right.png differ diff --git a/Applications/Official/DEV_FW/source/tanksgame/images/projectile_up.png b/Applications/Official/DEV_FW/source/tanksgame/images/projectile_up.png new file mode 100644 index 000000000..532446657 Binary files /dev/null and b/Applications/Official/DEV_FW/source/tanksgame/images/projectile_up.png differ diff --git a/Applications/Official/DEV_FW/source/tanksgame/images/tank_base.png b/Applications/Official/DEV_FW/source/tanksgame/images/tank_base.png new file mode 100644 index 000000000..11a524ffa Binary files /dev/null and b/Applications/Official/DEV_FW/source/tanksgame/images/tank_base.png differ diff --git a/Applications/Official/DEV_FW/source/tanksgame/images/tank_down.png b/Applications/Official/DEV_FW/source/tanksgame/images/tank_down.png new file mode 100644 index 000000000..d4e2c8786 Binary files /dev/null and b/Applications/Official/DEV_FW/source/tanksgame/images/tank_down.png differ diff --git a/Applications/Official/DEV_FW/source/tanksgame/images/tank_explosion.png b/Applications/Official/DEV_FW/source/tanksgame/images/tank_explosion.png new file mode 100644 index 000000000..8aab727cd Binary files /dev/null and b/Applications/Official/DEV_FW/source/tanksgame/images/tank_explosion.png differ diff --git a/Applications/Official/DEV_FW/source/tanksgame/images/tank_hedgehog.png b/Applications/Official/DEV_FW/source/tanksgame/images/tank_hedgehog.png new file mode 100644 index 000000000..fdd804be9 Binary files /dev/null and b/Applications/Official/DEV_FW/source/tanksgame/images/tank_hedgehog.png differ diff --git a/Applications/Official/DEV_FW/source/tanksgame/images/tank_left.png b/Applications/Official/DEV_FW/source/tanksgame/images/tank_left.png new file mode 100644 index 000000000..2c7fd40e0 Binary files /dev/null and b/Applications/Official/DEV_FW/source/tanksgame/images/tank_left.png differ diff --git a/Applications/Official/DEV_FW/source/tanksgame/images/tank_right.png b/Applications/Official/DEV_FW/source/tanksgame/images/tank_right.png new file mode 100644 index 000000000..436d90a61 Binary files /dev/null and b/Applications/Official/DEV_FW/source/tanksgame/images/tank_right.png differ diff --git a/Applications/Official/DEV_FW/source/tanksgame/images/tank_stone.png b/Applications/Official/DEV_FW/source/tanksgame/images/tank_stone.png new file mode 100644 index 000000000..87f89c8da Binary files /dev/null and b/Applications/Official/DEV_FW/source/tanksgame/images/tank_stone.png differ diff --git a/Applications/Official/DEV_FW/source/tanksgame/images/tank_up.png b/Applications/Official/DEV_FW/source/tanksgame/images/tank_up.png new file mode 100644 index 000000000..3d4e47579 Binary files /dev/null and b/Applications/Official/DEV_FW/source/tanksgame/images/tank_up.png differ diff --git a/Applications/Official/DEV_FW/source/tanksgame/images/tank_wall.png b/Applications/Official/DEV_FW/source/tanksgame/images/tank_wall.png new file mode 100644 index 000000000..88d537cdb Binary files /dev/null and b/Applications/Official/DEV_FW/source/tanksgame/images/tank_wall.png differ diff --git a/Applications/Official/DEV_FW/source/tanksgame/tanksIcon.png b/Applications/Official/DEV_FW/source/tanksgame/tanksIcon.png new file mode 100644 index 000000000..7a2113c0b Binary files /dev/null and b/Applications/Official/DEV_FW/source/tanksgame/tanksIcon.png differ diff --git a/Applications/Official/DEV_FW/source/tanksgame/tanks_game.c b/Applications/Official/DEV_FW/source/tanksgame/tanks_game.c new file mode 100644 index 000000000..3d67dfbb0 --- /dev/null +++ b/Applications/Official/DEV_FW/source/tanksgame/tanks_game.c @@ -0,0 +1,1456 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "constants.h" + +typedef struct { + // +-----x + // | + // | + // y + uint8_t x; + uint8_t y; +} Point; + +typedef enum { + CellEmpty = 1, + CellWall, + CellExplosion, + CellTankUp, + CellTankRight, + CellTankDown, + CellTankLeft, + CellEnemyUp, + CellEnemyRight, + CellEnemyDown, + CellEnemyLeft, + CellProjectileUp, + CellProjectileRight, + CellProjectileDown, + CellProjectileLeft, +} GameCellState; + +typedef enum { + MenuStateSingleMode, + MenuStateCooperativeServerMode, + MenuStateCooperativeClientMode, +} MenuState; + +typedef enum { + GameStateMenu, + GameStateSingle, + GameStateCooperativeServer, + GameStateCooperativeClient, + GameStateGameOver, +} GameState; + +typedef enum { + DirectionUp, + DirectionRight, + DirectionDown, + DirectionLeft, +} Direction; + +typedef enum { + ModeSingle, + ModeCooperative, +} Mode; + +typedef struct { + Point coordinates; + Direction direction; + bool explosion; + bool is_p1; + bool is_p2; +} ProjectileState; + +typedef struct { + Point coordinates; + uint16_t score; + uint8_t lives; + Direction direction; + bool moving; + bool shooting; + bool live; + uint8_t cooldown; + uint8_t respawn_cooldown; +} PlayerState; + +typedef struct { + // char map[FIELD_WIDTH][FIELD_HEIGHT]; + char thisMap[16][11]; + Point team_one_respawn_points[3]; + Point team_two_respawn_points[3]; + Mode mode; + bool server; + GameState state; + MenuState menu_state; + ProjectileState* projectiles[100]; + PlayerState* bots[6]; + uint8_t enemies_left; + uint8_t enemies_live; + uint8_t enemies_respawn_cooldown; + uint8_t received; + uint8_t sent; + PlayerState* p1; + PlayerState* p2; +} TanksState; + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} TanksEvent; + +typedef enum { + GoesUp, + GoesRight, + GoesDown, + GoesLeft, + Shoots, +} ClientAction; + +//char map[FIELD_HEIGHT][FIELD_WIDTH + 1] = { +char thisMap[11][16 + 1] = { + "* - * -", + " - - = ", + " - - 2", + "1 = - -- ", + "-- = - -- ", + "a-1 = - = 2", + "-- = - -- ", + "1 = - -- ", + " - - 2", + " - - = ", + "* - * -", +}; + +static void tanks_game_write_cell(unsigned char* data, int8_t x, int8_t y, GameCellState cell) { + uint8_t index = y * 16 + x; + data[index] = cell; + // if (x % 2) { + // data[index] = (data[index] & 0b00001111) + (cell << 4); + // } else { + // data[index] = (data[index] & 0b0000) + cell; + // } +} + +// Enum with < 16 items => 4 bits in cell, 2 cells in byte +unsigned char* tanks_game_serialize(const TanksState* const tanks_state) { + static unsigned char result[11 * 16 + 1]; + + for(int8_t x = 0; x < FIELD_WIDTH; x++) { + for(int8_t y = 0; y < FIELD_HEIGHT; y++) { + result[(y * FIELD_WIDTH + x)] = 0; + + GameCellState cell = CellEmpty; + + if(tanks_state->thisMap[x][y] == '-') { + cell = CellWall; + + tanks_game_write_cell(result, x, y, cell); + } + } + } + + for(uint8_t i = 0; i < 6; i++) { + if(tanks_state->bots[i] != NULL) { + GameCellState cell = CellEmpty; + + switch(tanks_state->bots[i]->direction) { + case DirectionUp: + cell = CellEnemyUp; + break; + case DirectionDown: + cell = CellEnemyDown; + break; + case DirectionRight: + cell = CellEnemyRight; + break; + case DirectionLeft: + cell = CellEnemyLeft; + break; + default: + break; + } + + tanks_game_write_cell( + result, + tanks_state->bots[i]->coordinates.x, + tanks_state->bots[i]->coordinates.y, + cell); + } + } + + for(int8_t x = 0; x < 100; x++) { + if(tanks_state->projectiles[x] != NULL) { + GameCellState cell = CellEmpty; + + switch(tanks_state->projectiles[x]->direction) { + case DirectionUp: + cell = CellProjectileUp; + break; + case DirectionDown: + cell = CellProjectileDown; + break; + case DirectionRight: + cell = CellProjectileRight; + break; + case DirectionLeft: + cell = CellProjectileLeft; + break; + default: + break; + } + + tanks_game_write_cell( + result, + tanks_state->projectiles[x]->coordinates.x, + tanks_state->projectiles[x]->coordinates.y, + cell); + } + } + + if(tanks_state->p1 != NULL && tanks_state->p1->live) { + GameCellState cell = CellEmpty; + + switch(tanks_state->p1->direction) { + case DirectionUp: + cell = CellTankUp; + break; + case DirectionDown: + cell = CellTankDown; + break; + case DirectionRight: + cell = CellTankRight; + break; + case DirectionLeft: + cell = CellTankLeft; + break; + default: + break; + } + + tanks_game_write_cell( + result, tanks_state->p1->coordinates.x, tanks_state->p1->coordinates.y, cell); + } + + if(tanks_state->p2 != NULL && tanks_state->p2->live) { + GameCellState cell = CellEmpty; + + switch(tanks_state->p2->direction) { + case DirectionUp: + cell = CellTankUp; + break; + case DirectionDown: + cell = CellTankDown; + break; + case DirectionRight: + cell = CellTankRight; + break; + case DirectionLeft: + cell = CellTankLeft; + break; + default: + break; + } + + tanks_game_write_cell( + result, tanks_state->p2->coordinates.x, tanks_state->p2->coordinates.y, cell); + } + + return result; +} + +static void + tanks_game_render_cell(GameCellState cell, uint8_t x, uint8_t y, Canvas* const canvas) { + const Icon* icon; + + if(cell == CellEmpty) { + return; + } + + switch(cell) { + case CellWall: + icon = &I_tank_wall; + break; + case CellExplosion: + icon = &I_tank_explosion; + break; + case CellTankUp: + icon = &I_tank_up; + break; + case CellTankRight: + icon = &I_tank_right; + break; + case CellTankDown: + icon = &I_tank_down; + break; + case CellTankLeft: + icon = &I_tank_left; + break; + case CellEnemyUp: + icon = &I_enemy_up; + break; + case CellEnemyRight: + icon = &I_enemy_right; + break; + case CellEnemyDown: + icon = &I_enemy_down; + break; + case CellEnemyLeft: + icon = &I_enemy_left; + break; + case CellProjectileUp: + icon = &I_projectile_up; + break; + case CellProjectileRight: + icon = &I_projectile_right; + break; + case CellProjectileDown: + icon = &I_projectile_down; + break; + case CellProjectileLeft: + icon = &I_projectile_left; + break; + default: + return; + break; + } + + canvas_draw_icon(canvas, x * CELL_LENGTH_PIXELS, y * CELL_LENGTH_PIXELS - 1, icon); +} + +static void tanks_game_render_constant_cells(Canvas* const canvas) { + for(int8_t x = 0; x < FIELD_WIDTH; x++) { + for(int8_t y = 0; y < FIELD_HEIGHT; y++) { + char cell = thisMap[y][x]; + + if(cell == '=') { + canvas_draw_icon( + canvas, x * CELL_LENGTH_PIXELS, y * CELL_LENGTH_PIXELS - 1, &I_tank_stone); + continue; + } + + if(cell == '*') { + canvas_draw_icon( + canvas, x * CELL_LENGTH_PIXELS, y * CELL_LENGTH_PIXELS - 1, &I_tank_hedgehog); + continue; + } + + if(cell == 'a') { + canvas_draw_icon( + canvas, x * CELL_LENGTH_PIXELS, y * CELL_LENGTH_PIXELS - 1, &I_tank_base); + continue; + } + } + } +} + +void tanks_game_deserialize_and_write_to_state(unsigned char* data, TanksState* const tanks_state) { + for(uint8_t i = 0; i < 11 * 16; i++) { + uint8_t x = i % 16; + uint8_t y = i / 16; + tanks_state->thisMap[x][y] = data[i]; + } +} + +void tanks_game_deserialize_and_render(unsigned char* data, Canvas* const canvas) { + //for (uint8_t i = 0; i < 11 * 16 / 2; i++) { + for(uint8_t i = 0; i < 11 * 16; i++) { + char cell = data[i]; + uint8_t x = i % 16; // One line (16 cells) = 8 bytes + uint8_t y = i / 16; + + // GameCellState first = cell >> 4; + // GameCellState second = cell & 0b00001111; + + tanks_game_render_cell(cell, x, y, canvas); + // tanks_game_render_cell(second, x + 1, y, canvas); + } + + tanks_game_render_constant_cells(canvas); +} + +static void tanks_game_render_callback(Canvas* const canvas, void* ctx) { + const TanksState* tanks_state = acquire_mutex((ValueMutex*)ctx, 25); + if(tanks_state == NULL) { + return; + } + + // Before the function is called, the state is set with the canvas_reset(canvas) + if(tanks_state->state == GameStateMenu) { + canvas_draw_icon(canvas, 0, 0, &I_TanksSplashScreen_128x64); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 124, 10, AlignRight, AlignBottom, "Single"); + canvas_draw_str_aligned(canvas, 124, 25, AlignRight, AlignBottom, "Co-op S"); + canvas_draw_str_aligned(canvas, 124, 40, AlignRight, AlignBottom, "Co-op C"); + + switch(tanks_state->menu_state) { + case MenuStateSingleMode: + canvas_draw_icon(canvas, 74, 3, &I_tank_right); + break; + case MenuStateCooperativeServerMode: + canvas_draw_icon(canvas, 74, 18, &I_tank_right); + break; + case MenuStateCooperativeClientMode: + canvas_draw_icon(canvas, 74, 33, &I_tank_right); + break; + } + + canvas_draw_frame(canvas, 0, 0, 128, 64); + + release_mutex((ValueMutex*)ctx, tanks_state); + return; + } + + // Field right border + canvas_draw_box(canvas, FIELD_WIDTH * CELL_LENGTH_PIXELS, 0, 2, SCREEN_HEIGHT_TANKS); + + // Cooperative client + if(tanks_state->mode == ModeCooperative && !tanks_state->server) { + for(int8_t x = 0; x < FIELD_WIDTH; x++) { + for(int8_t y = 0; y < FIELD_HEIGHT; y++) { + tanks_game_render_cell(tanks_state->thisMap[x][y], x, y, canvas); + } + } + + tanks_game_render_constant_cells(canvas); + + release_mutex((ValueMutex*)ctx, tanks_state); + return; + } + + // Player + // Point coordinates = tanks_state->p1->coordinates; + // const Icon *icon; + // switch (tanks_state->p1->direction) { + // case DirectionUp: + // icon = &I_tank_up; + // break; + // case DirectionDown: + // icon = &I_tank_down; + // break; + // case DirectionRight: + // icon = &I_tank_right; + // break; + // case DirectionLeft: + // icon = &I_tank_left; + // break; + // default: + // icon = &I_tank_explosion; + // } + + // if (tanks_state->p1->live) { + // canvas_draw_icon(canvas, coordinates.x * CELL_LENGTH_PIXELS, coordinates.y * CELL_LENGTH_PIXELS - 1, icon); + // } + // + // for(int8_t x = 0; x < FIELD_WIDTH; x++) { + // for(int8_t y = 0; y < FIELD_HEIGHT; y++) { + // switch (tanks_state->thisMap[x][y]) { + // case '-': + // canvas_draw_icon(canvas, x * CELL_LENGTH_PIXELS, y * CELL_LENGTH_PIXELS - 1, &I_tank_wall); + // break; + // + // case '=': + // canvas_draw_icon(canvas, x * CELL_LENGTH_PIXELS, y * CELL_LENGTH_PIXELS - 1, &I_tank_stone); + // break; + // + // case '*': + // canvas_draw_icon(canvas, x * CELL_LENGTH_PIXELS, y * CELL_LENGTH_PIXELS - 1, &I_tank_hedgehog); + // break; + // + // case 'a': + // canvas_draw_icon(canvas, x * CELL_LENGTH_PIXELS, y * CELL_LENGTH_PIXELS - 1, &I_tank_base); + // break; + // } + // } + // } + + // for ( + // uint8_t i = 0; + // i < 6; + // i++ + // ) { + // if (tanks_state->bots[i] != NULL) { + // const Icon *icon; + // + // switch(tanks_state->bots[i]->direction) { + // case DirectionUp: + // icon = &I_enemy_up; + // break; + // case DirectionDown: + // icon = &I_enemy_down; + // break; + // case DirectionRight: + // icon = &I_enemy_right; + // break; + // case DirectionLeft: + // icon = &I_enemy_left; + // break; + // default: + // icon = &I_tank_explosion; + // } + // + // canvas_draw_icon( + // canvas, + // tanks_state->bots[i]->coordinates.x * CELL_LENGTH_PIXELS, + // tanks_state->bots[i]->coordinates.y * CELL_LENGTH_PIXELS - 1, + // icon); + // } + // } + + // for(int8_t x = 0; x < 100; x++) { + // if (tanks_state->projectiles[x] != NULL) { + // ProjectileState *projectile = tanks_state->projectiles[x]; + // + // if (projectile->explosion) { + // canvas_draw_icon( + // canvas, + // projectile->coordinates.x * CELL_LENGTH_PIXELS, + // projectile->coordinates.y * CELL_LENGTH_PIXELS - 1, + // &I_tank_explosion); + // continue; + // } + // + // const Icon *icon; + // + // switch(projectile->direction) { + // case DirectionUp: + // icon = &I_projectile_up; + // break; + // case DirectionDown: + // icon = &I_projectile_down; + // break; + // case DirectionRight: + // icon = &I_projectile_right; + // break; + // case DirectionLeft: + // icon = &I_projectile_left; + // break; + // default: + // icon = &I_tank_explosion; + // } + // + // canvas_draw_icon( + // canvas, + // projectile->coordinates.x * CELL_LENGTH_PIXELS, + // projectile->coordinates.y * CELL_LENGTH_PIXELS - 1, + // icon); + // } + // } + + // Info + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontSecondary); + char buffer1[13]; + snprintf(buffer1, sizeof(buffer1), "live: %u", tanks_state->enemies_live); + canvas_draw_str_aligned(canvas, 127, 8, AlignRight, AlignBottom, buffer1); + + snprintf(buffer1, sizeof(buffer1), "left: %u", tanks_state->enemies_left); + canvas_draw_str_aligned(canvas, 127, 18, AlignRight, AlignBottom, buffer1); + + snprintf(buffer1, sizeof(buffer1), "p1 l: %u", tanks_state->p1->lives); + canvas_draw_str_aligned(canvas, 127, 28, AlignRight, AlignBottom, buffer1); + + snprintf(buffer1, sizeof(buffer1), "p1 s: %u", tanks_state->p1->score); + canvas_draw_str_aligned(canvas, 127, 38, AlignRight, AlignBottom, buffer1); + + if(tanks_state->state == GameStateCooperativeServer && tanks_state->p2) { + snprintf(buffer1, sizeof(buffer1), "rec: %u", tanks_state->received); + canvas_draw_str_aligned(canvas, 127, 48, AlignRight, AlignBottom, buffer1); + + snprintf(buffer1, sizeof(buffer1), "snt: %u", tanks_state->sent); + canvas_draw_str_aligned(canvas, 127, 58, AlignRight, AlignBottom, buffer1); + // snprintf(buffer1, sizeof(buffer1), "p2 l: %u", tanks_state->p2->lives); + // canvas_draw_str_aligned(canvas, 127, 48, AlignRight, AlignBottom, buffer1); + // + // snprintf(buffer1, sizeof(buffer1), "p2 s: %u", tanks_state->p2->score); + // canvas_draw_str_aligned(canvas, 127, 58, AlignRight, AlignBottom, buffer1); + } + + if(tanks_state->state == GameStateCooperativeClient) { + snprintf(buffer1, sizeof(buffer1), "rec: %u", tanks_state->received); + canvas_draw_str_aligned(canvas, 127, 48, AlignRight, AlignBottom, buffer1); + } + + // Game Over banner + if(tanks_state->state == GameStateGameOver) { + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 34, 20, 62, 24); + + canvas_set_color(canvas, ColorBlack); + canvas_draw_frame(canvas, 34, 20, 62, 24); + canvas_set_font(canvas, FontPrimary); + + if(tanks_state->enemies_left == 0 && tanks_state->enemies_live == 0) { + canvas_draw_str(canvas, 37, 31, "You win!"); + } else { + canvas_draw_str(canvas, 37, 31, "Game Over"); + } + + canvas_set_font(canvas, FontSecondary); + char buffer[13]; + snprintf(buffer, sizeof(buffer), "Score: %u", tanks_state->p1->score); + canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignBottom, buffer); + } + + // TEST start + unsigned char* data = tanks_game_serialize(tanks_state); + tanks_game_deserialize_and_render(data, canvas); + // TEST enf + + release_mutex((ValueMutex*)ctx, tanks_state); +} + +static void tanks_game_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + TanksEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void tanks_game_update_timer_callback(FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + TanksEvent event = {.type = EventTypeTick}; + furi_message_queue_put(event_queue, &event, 0); +} + +static bool tanks_get_cell_is_free(TanksState* const tanks_state, Point point) { + // Tiles + if(tanks_state->thisMap[point.x][point.y] != ' ') { + return false; + } + + // Projectiles + for(int8_t x = 0; x < 100; x++) { + if(tanks_state->projectiles[x] != NULL) { + if(tanks_state->projectiles[x]->coordinates.x == point.x && + tanks_state->projectiles[x]->coordinates.y == point.y) { + return false; + } + } + } + + // Player 1 + if(tanks_state->p1 != NULL) { + if(tanks_state->p1->coordinates.x == point.x && + tanks_state->p1->coordinates.y == point.y) { + return false; + } + } + + // Player 2 + if(tanks_state->p2 != NULL) { + if(tanks_state->p2->coordinates.x == point.x && + tanks_state->p2->coordinates.y == point.y) { + return false; + } + } + + // Bots + for(int8_t x = 0; x < 6; x++) { + if(tanks_state->bots[x] != NULL) { + if(tanks_state->bots[x]->coordinates.x == point.x && + tanks_state->bots[x]->coordinates.y == point.y) { + return false; + } + } + } + + return true; +} + +static uint8_t tanks_get_random_free_respawn_point_index( + TanksState* const tanks_state, + Point respawn_points[3]) { + uint8_t first = rand() % 3; + int8_t add = rand() % 2 ? +1 : -1; + int8_t second = first + add; + uint8_t third; + + if(second == 4) { + second = 0; + } else if(second == -1) { + second = 3; + } + + for(uint8_t i = 0; i < 3; i++) { + if(i != first && i != second) { + third = i; + } + } + + if(tanks_get_cell_is_free(tanks_state, respawn_points[first])) { + return first; + } + + if(tanks_get_cell_is_free(tanks_state, respawn_points[second])) { + return second; + } + + if(tanks_get_cell_is_free(tanks_state, respawn_points[third])) { + return third; + } + + return -1; +} + +static void tanks_game_init_game(TanksState* const tanks_state, GameState type) { + srand(DWT->CYCCNT); + + tanks_state->state = type; + + for(int8_t x = 0; x < 100; x++) { + if(tanks_state->projectiles[x] != NULL) { + free(tanks_state->projectiles[x]); + tanks_state->projectiles[x] = NULL; + } + } + + int8_t team_one_respawn_points_counter = 0; + int8_t team_two_respawn_points_counter = 0; + + for(int8_t x = 0; x < FIELD_WIDTH; x++) { + for(int8_t y = 0; y < FIELD_HEIGHT; y++) { + tanks_state->thisMap[x][y] = ' '; + + if(thisMap[y][x] == '1') { + Point respawn = {x, y}; + tanks_state->team_one_respawn_points[team_one_respawn_points_counter++] = respawn; + } + + if(thisMap[y][x] == '2') { + Point respawn = {x, y}; + tanks_state->team_two_respawn_points[team_two_respawn_points_counter++] = respawn; + } + + if(thisMap[y][x] == '-') { + tanks_state->thisMap[x][y] = '-'; + } + + if(thisMap[y][x] == '=') { + tanks_state->thisMap[x][y] = '='; + } + + if(thisMap[y][x] == '*') { + tanks_state->thisMap[x][y] = '*'; + } + + if(thisMap[y][x] == 'a') { + tanks_state->thisMap[x][y] = 'a'; + } + } + } + + uint8_t index1 = tanks_get_random_free_respawn_point_index( + tanks_state, tanks_state->team_one_respawn_points); + Point c = { + tanks_state->team_one_respawn_points[index1].x, + tanks_state->team_one_respawn_points[index1].y}; + + PlayerState p1 = { + c, + 0, + 4, + DirectionRight, + 0, + 0, + 1, + SHOT_COOLDOWN, + PLAYER_RESPAWN_COOLDOWN, + }; + + PlayerState* p1_state = malloc(sizeof(PlayerState)); + *p1_state = p1; + + tanks_state->p1 = p1_state; + + if(type == GameStateCooperativeServer) { + int8_t index2 = tanks_get_random_free_respawn_point_index( + tanks_state, tanks_state->team_one_respawn_points); + Point c = { + tanks_state->team_one_respawn_points[index2].x, + tanks_state->team_one_respawn_points[index2].y}; + + PlayerState p2 = { + c, + 0, + 4, + DirectionRight, + 0, + 0, + 1, + SHOT_COOLDOWN, + PLAYER_RESPAWN_COOLDOWN, + }; + + PlayerState* p2_state = malloc(sizeof(PlayerState)); + *p2_state = p2; + + tanks_state->p2 = p2_state; + } + + tanks_state->enemies_left = 5; + tanks_state->enemies_live = 0; + tanks_state->enemies_respawn_cooldown = RESPAWN_COOLDOWN; + tanks_state->received = 0; + tanks_state->sent = 0; + + if(type == GameStateCooperativeClient) { + for(int8_t x = 0; x < FIELD_WIDTH; x++) { + for(int8_t y = 0; y < FIELD_HEIGHT; y++) { + tanks_state->thisMap[x][y] = CellEmpty; + } + } + } +} + +static bool + tanks_game_collision(Point const next_step, bool shoot, TanksState const* const tanks_state) { + if((int8_t)next_step.x < 0 || (int8_t)next_step.y < 0) { + return true; + } + + if(next_step.x >= FIELD_WIDTH || next_step.y >= FIELD_HEIGHT) { + return true; + } + + char tile = tanks_state->thisMap[next_step.x][next_step.y]; + + if(tile == '*' && !shoot) { + return true; + } + + if(tile == '-' || tile == '=' || tile == 'a') { + return true; + } + + for(uint8_t i = 0; i < 6; i++) { + if(tanks_state->bots[i] != NULL) { + if(tanks_state->bots[i]->coordinates.x == next_step.x && + tanks_state->bots[i]->coordinates.y == next_step.y) { + return true; + } + } + } + + if(tanks_state->p1 != NULL && tanks_state->p1->live && + tanks_state->p1->coordinates.x == next_step.x && + tanks_state->p1->coordinates.y == next_step.y) { + return true; + } + + if(tanks_state->p2 != NULL && tanks_state->p2->live && + tanks_state->p2->coordinates.x == next_step.x && + tanks_state->p2->coordinates.y == next_step.y) { + return true; + } + + return false; +} + +static Point tanks_game_get_next_step(Point coordinates, Direction direction) { + Point next_step = {coordinates.x, coordinates.y}; + + switch(direction) { + // +-----x + // | + // | + // y + case DirectionUp: + next_step.y--; + break; + case DirectionRight: + next_step.x++; + break; + case DirectionDown: + next_step.y++; + break; + case DirectionLeft: + next_step.x--; + break; + default: + break; + } + return next_step; +} + +static uint8_t tanks_game_get_free_projectile_index(TanksState* const tanks_state) { + uint8_t freeProjectileIndex; + for(freeProjectileIndex = 0; freeProjectileIndex < 100; freeProjectileIndex++) { + if(tanks_state->projectiles[freeProjectileIndex] == NULL) { + return freeProjectileIndex; + } + } + + return 0; +} + +static void tanks_game_shoot( + TanksState* const tanks_state, + PlayerState* tank_state, + bool is_p1, + bool is_p2) { + tank_state->cooldown = SHOT_COOLDOWN; + + uint8_t freeProjectileIndex = tanks_game_get_free_projectile_index(tanks_state); + + ProjectileState* projectile_state = malloc(sizeof(ProjectileState)); + Point next_step = tanks_game_get_next_step(tank_state->coordinates, tank_state->direction); + + projectile_state->direction = tank_state->direction; + projectile_state->coordinates = next_step; + projectile_state->is_p1 = is_p1; + projectile_state->is_p2 = is_p2; + + bool crush = tanks_game_collision(projectile_state->coordinates, true, tanks_state); + projectile_state->explosion = crush; + + tanks_state->projectiles[freeProjectileIndex] = projectile_state; +} + +static void tanks_game_process_game_step(TanksState* const tanks_state) { + if(tanks_state->state == GameStateMenu) { + return; + } + + if(tanks_state->enemies_left == 0 && tanks_state->enemies_live == 0) { + tanks_state->state = GameStateGameOver; + } + + if(!tanks_state->p1->live && tanks_state->p1->lives == 0) { + tanks_state->state = GameStateGameOver; + } + + if(tanks_state->state == GameStateGameOver) { + return; + } + + if(tanks_state->p1 != NULL) { + if(!tanks_state->p1->live && tanks_state->p1->respawn_cooldown > 0) { + tanks_state->p1->respawn_cooldown--; + } + } + + // Player 1 spawn + if(tanks_state->p1 && !tanks_state->p1->live && tanks_state->p1->lives > 0) { + int8_t index = tanks_get_random_free_respawn_point_index( + tanks_state, tanks_state->team_one_respawn_points); + + if(index != -1) { + Point point = tanks_state->team_one_respawn_points[index]; + Point c = {point.x, point.y}; + tanks_state->p1->coordinates = c; + tanks_state->p1->live = true; + tanks_state->p1->direction = DirectionRight; + tanks_state->p1->cooldown = SHOT_COOLDOWN; + tanks_state->p1->respawn_cooldown = SHOT_COOLDOWN; + } + } + + // Player 2 spawn + if(tanks_state->state == GameStateCooperativeServer && tanks_state->p2 && + !tanks_state->p2->live && tanks_state->p2->lives > 0) { + int8_t index = tanks_get_random_free_respawn_point_index( + tanks_state, tanks_state->team_one_respawn_points); + + if(index != -1) { + Point point = tanks_state->team_one_respawn_points[index]; + Point c = {point.x, point.y}; + tanks_state->p2->coordinates = c; + tanks_state->p2->live = true; + tanks_state->p2->direction = DirectionRight; + tanks_state->p2->cooldown = SHOT_COOLDOWN; + tanks_state->p2->respawn_cooldown = SHOT_COOLDOWN; + } + } + + // Bot turn + for(uint8_t i = 0; i < 6; i++) { + if(tanks_state->bots[i] != NULL) { + PlayerState* bot = tanks_state->bots[i]; + if(bot->cooldown) { + bot->cooldown--; + } + + // Rotate + if(rand() % 3 == 0) { + bot->direction = (rand() % 4); + } + + // Move + if(rand() % 2 == 0) { + Point next_step = tanks_game_get_next_step(bot->coordinates, bot->direction); + bool crush = tanks_game_collision(next_step, false, tanks_state); + + if(!crush) { + bot->coordinates = next_step; + } + } + + // Shoot + if(bot->cooldown == 0 && rand() % 3 != 0) { + tanks_game_shoot(tanks_state, bot, false, false); + } + } + } + + // Bot spawn + if(tanks_state->enemies_respawn_cooldown) { + tanks_state->enemies_respawn_cooldown--; + } + + if(tanks_state->enemies_left > 0 && tanks_state->enemies_live <= 4 && + tanks_state->enemies_respawn_cooldown == 0) { + int8_t index = tanks_get_random_free_respawn_point_index( + tanks_state, tanks_state->team_two_respawn_points); + + if(index != -1) { + tanks_state->enemies_left--; + tanks_state->enemies_live++; + tanks_state->enemies_respawn_cooldown = RESPAWN_COOLDOWN; + Point point = tanks_state->team_two_respawn_points[index]; + + Point c = {point.x, point.y}; + + PlayerState bot = { + c, + 0, + 0, + DirectionLeft, + 0, + 0, + 1, + SHOT_COOLDOWN, + PLAYER_RESPAWN_COOLDOWN, + }; + + uint8_t freeEnemyIndex; + for(freeEnemyIndex = 0; freeEnemyIndex < 6; freeEnemyIndex++) { + if(tanks_state->bots[freeEnemyIndex] == NULL) { + break; + } + } + + PlayerState* bot_state = malloc(sizeof(PlayerState)); + *bot_state = bot; + + tanks_state->bots[freeEnemyIndex] = bot_state; + } + } + + if(tanks_state->p1 != NULL && tanks_state->p1->live && tanks_state->p1->moving) { + Point next_step = + tanks_game_get_next_step(tanks_state->p1->coordinates, tanks_state->p1->direction); + bool crush = tanks_game_collision(next_step, false, tanks_state); + + if(!crush) { + tanks_state->p1->coordinates = next_step; + } + } + + // Player 2 spawn + if(tanks_state->state == GameStateCooperativeServer && tanks_state->p2 && + tanks_state->p2->live && tanks_state->p2->moving) { + Point next_step = + tanks_game_get_next_step(tanks_state->p2->coordinates, tanks_state->p2->direction); + bool crush = tanks_game_collision(next_step, false, tanks_state); + + if(!crush) { + tanks_state->p2->coordinates = next_step; + } + } + + for(int8_t x = 0; x < 100; x++) { + if(tanks_state->projectiles[x] != NULL) { + ProjectileState* projectile = tanks_state->projectiles[x]; + Point c = projectile->coordinates; + + if(projectile->explosion) { + // Break a wall + if(tanks_state->thisMap[c.x][c.y] == '-') { + tanks_state->thisMap[c.x][c.y] = ' '; + } + + // Kill a bot + for(uint8_t i = 0; i < 6; i++) { + if(tanks_state->bots[i] != NULL) { + if(tanks_state->bots[i]->coordinates.x == c.x && + tanks_state->bots[i]->coordinates.y == c.y) { + if(projectile->is_p1) { + tanks_state->p1->score++; + } + + if(projectile->is_p2) { + tanks_state->p2->score++; + } + + // No friendly fire + if(projectile->is_p1 || projectile->is_p2) { + tanks_state->enemies_live--; + free(tanks_state->bots[i]); + tanks_state->bots[i] = NULL; + } + } + } + } + + // Destroy the flag + if(tanks_state->thisMap[c.x][c.y] == 'a') { + tanks_state->state = GameStateGameOver; + return; + } + + // Kill a player 1 + if(tanks_state->p1 != NULL) { + if(tanks_state->p1->live && tanks_state->p1->coordinates.x == c.x && + tanks_state->p1->coordinates.y == c.y) { + tanks_state->p1->live = false; + tanks_state->p1->lives--; + tanks_state->p1->respawn_cooldown = PLAYER_RESPAWN_COOLDOWN; + } + } + + // Kill a player 2 + if(tanks_state->p2 != NULL) { + if(tanks_state->p2->live && tanks_state->p2->coordinates.x == c.x && + tanks_state->p2->coordinates.y == c.y) { + tanks_state->p2->live = false; + tanks_state->p2->lives--; + tanks_state->p2->respawn_cooldown = PLAYER_RESPAWN_COOLDOWN; + } + } + + // Delete projectile + free(tanks_state->projectiles[x]); + tanks_state->projectiles[x] = NULL; + continue; + } + + Point next_step = + tanks_game_get_next_step(projectile->coordinates, projectile->direction); + bool crush = tanks_game_collision(next_step, true, tanks_state); + projectile->coordinates = next_step; + + if(crush) { + projectile->explosion = true; + } + } + } + + if(tanks_state->p1->cooldown > 0) { + tanks_state->p1->cooldown--; + } + + if(tanks_state->p2 != NULL && tanks_state->p2->cooldown > 0) { + tanks_state->p2->cooldown--; + } + + if(tanks_state->p1 != NULL && tanks_state->p1->live && tanks_state->p1->shooting && + tanks_state->p1->cooldown == 0) { + tanks_game_shoot(tanks_state, tanks_state->p1, true, false); + } + + tanks_state->p1->moving = false; + tanks_state->p1->shooting = false; + + if(tanks_state->p2 != NULL) { + tanks_state->p2->moving = false; + tanks_state->p2->shooting = false; + } +} + +int32_t tanks_game_app(void* p) { + UNUSED(p); + srand(DWT->CYCCNT); + + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(TanksEvent)); + + TanksState* tanks_state = malloc(sizeof(TanksState)); + + tanks_state->state = GameStateMenu; + tanks_state->menu_state = MenuStateSingleMode; + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, tanks_state, sizeof(TanksState))) { + FURI_LOG_E("Tanks", "cannot create mutex\r\n"); + furi_message_queue_free(event_queue); + free(tanks_state); + return 255; + } + + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, tanks_game_render_callback, &state_mutex); + view_port_input_callback_set(view_port, tanks_game_input_callback, event_queue); + + FuriTimer* timer = + furi_timer_alloc(tanks_game_update_timer_callback, FuriTimerTypePeriodic, event_queue); + furi_timer_start(timer, furi_kernel_get_tick_frequency() / 4); + + // Open GUI and register view_port + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + TanksEvent event; + + // Initialize network thing. + uint32_t frequency = 433920000; + size_t message_max_len = 180; + uint8_t incomingMessage[180] = {0}; + SubGhzTxRxWorker* subghz_txrx = subghz_tx_rx_worker_alloc(); + subghz_tx_rx_worker_start(subghz_txrx, frequency); + furi_hal_power_suppress_charge_enter(); + + for(bool processing = true; processing;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + + TanksState* tanks_state = (TanksState*)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: + if(tanks_state->state == GameStateMenu) { + if(tanks_state->menu_state == MenuStateCooperativeServerMode) { + tanks_state->menu_state = MenuStateSingleMode; + } else if(tanks_state->menu_state == MenuStateCooperativeClientMode) { + tanks_state->menu_state = MenuStateCooperativeServerMode; + } + } else if(tanks_state->state == GameStateCooperativeClient) { + FuriString* goesUp = NULL; + char arr[2]; + arr[0] = GoesUp; + arr[1] = 0; + furi_string_set(goesUp, (char*)&arr); + + subghz_tx_rx_worker_write( + subghz_txrx, + (uint8_t*)furi_string_get_cstr(goesUp), + strlen(furi_string_get_cstr(goesUp))); + + } else { + tanks_state->p1->moving = true; + tanks_state->p1->direction = DirectionUp; + } + break; + case InputKeyDown: + if(tanks_state->state == GameStateMenu) { + if(tanks_state->menu_state == MenuStateSingleMode) { + tanks_state->menu_state = MenuStateCooperativeServerMode; + } else if(tanks_state->menu_state == MenuStateCooperativeServerMode) { + tanks_state->menu_state = MenuStateCooperativeClientMode; + } + } else if(tanks_state->state == GameStateCooperativeClient) { + FuriString* goesDown = NULL; + char arr[2]; + arr[0] = GoesDown; + arr[1] = 0; + furi_string_set(goesDown, (char*)&arr); + + subghz_tx_rx_worker_write( + subghz_txrx, + (uint8_t*)furi_string_get_cstr(goesDown), + strlen(furi_string_get_cstr(goesDown))); + } else { + tanks_state->p1->moving = true; + tanks_state->p1->direction = DirectionDown; + } + break; + case InputKeyRight: + if(tanks_state->state == GameStateCooperativeClient) { + FuriString* goesRight = NULL; + char arr[2]; + arr[0] = GoesRight; + arr[1] = 0; + furi_string_set(goesRight, (char*)&arr); + + subghz_tx_rx_worker_write( + subghz_txrx, + (uint8_t*)furi_string_get_cstr(goesRight), + strlen(furi_string_get_cstr(goesRight))); + } else { + tanks_state->p1->moving = true; + tanks_state->p1->direction = DirectionRight; + } + break; + case InputKeyLeft: + if(tanks_state->state == GameStateCooperativeClient) { + FuriString* goesLeft = NULL; + char arr[2]; + arr[0] = GoesLeft; + arr[1] = 0; + furi_string_set(goesLeft, (char*)&arr); + + subghz_tx_rx_worker_write( + subghz_txrx, + (uint8_t*)furi_string_get_cstr(goesLeft), + strlen(furi_string_get_cstr(goesLeft))); + } else { + tanks_state->p1->moving = true; + tanks_state->p1->direction = DirectionLeft; + } + break; + case InputKeyOk: + if(tanks_state->state == GameStateMenu) { + if(tanks_state->menu_state == MenuStateSingleMode) { + tanks_state->server = true; + tanks_game_init_game(tanks_state, GameStateSingle); + break; + } else if(tanks_state->menu_state == MenuStateCooperativeServerMode) { + tanks_state->server = true; + tanks_game_init_game(tanks_state, GameStateCooperativeServer); + break; + } else if(tanks_state->menu_state == MenuStateCooperativeClientMode) { + tanks_state->server = false; + tanks_game_init_game(tanks_state, GameStateCooperativeClient); + break; + } + } else if(tanks_state->state == GameStateGameOver) { + tanks_game_init_game(tanks_state, tanks_state->state); + } else if(tanks_state->state == GameStateCooperativeClient) { + FuriString* shoots = NULL; + char arr[2]; + arr[0] = Shoots; + arr[1] = 0; + furi_string_set(shoots, (char*)&arr); + + subghz_tx_rx_worker_write( + subghz_txrx, + (uint8_t*)furi_string_get_cstr(shoots), + strlen(furi_string_get_cstr(shoots))); + } else { + tanks_state->p1->shooting = true; + } + break; + case InputKeyBack: + processing = false; + break; + default: + break; + } + } + } else if(event.type == EventTypeTick) { + if(tanks_state->state == GameStateCooperativeServer) { + if(subghz_tx_rx_worker_available(subghz_txrx)) { + memset(incomingMessage, 0x00, message_max_len); + subghz_tx_rx_worker_read(subghz_txrx, incomingMessage, message_max_len); + + if(incomingMessage != NULL) { + tanks_state->received++; + + switch(incomingMessage[0]) { + case GoesUp: + tanks_state->p2->moving = true; + tanks_state->p2->direction = DirectionUp; + break; + case GoesRight: + tanks_state->p2->moving = true; + tanks_state->p2->direction = DirectionRight; + break; + case GoesDown: + tanks_state->p2->moving = true; + tanks_state->p2->direction = DirectionDown; + break; + case GoesLeft: + tanks_state->p2->moving = true; + tanks_state->p2->direction = DirectionLeft; + break; + case Shoots: + tanks_state->p2->shooting = true; + break; + default: + break; + } + } + } + + tanks_game_process_game_step(tanks_state); + + FuriString* serializedData = NULL; + unsigned char* data = tanks_game_serialize(tanks_state); + char arr[11 * 16 + 1]; + + for(uint8_t i = 0; i < 11 * 16; i++) { + arr[i] = data[i]; + } + + arr[11 * 16] = 0; + + furi_string_set(serializedData, (char*)&arr); + + subghz_tx_rx_worker_write( + subghz_txrx, + (uint8_t*)furi_string_get_cstr(serializedData), + strlen(furi_string_get_cstr(serializedData))); + + tanks_state->sent++; + } else if(tanks_state->state == GameStateSingle) { + tanks_game_process_game_step(tanks_state); + } else if(tanks_state->state == GameStateCooperativeClient) { + if(subghz_tx_rx_worker_available(subghz_txrx)) { + memset(incomingMessage, 0x00, message_max_len); + subghz_tx_rx_worker_read(subghz_txrx, incomingMessage, message_max_len); + + tanks_state->received++; + + tanks_game_deserialize_and_write_to_state( + (unsigned char*)incomingMessage, tanks_state); + } + } + } + } else { + // event timeout + } + + view_port_update(view_port); + release_mutex(&state_mutex, tanks_state); + furi_delay_ms(1); + } + + furi_delay_ms(10); + furi_hal_power_suppress_charge_exit(); + + if(subghz_tx_rx_worker_is_running(subghz_txrx)) { + subghz_tx_rx_worker_stop(subghz_txrx); + subghz_tx_rx_worker_free(subghz_txrx); + } + + furi_timer_free(timer); + 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); + + if(tanks_state->p1 != NULL) { + free(tanks_state->p1); + } + + if(tanks_state->p2 != NULL) { + free(tanks_state->p2); + } + + free(tanks_state); + + return 0; +} diff --git a/Applications/Official/DEV_FW/source/tetris_game/.gitignore b/Applications/Official/DEV_FW/source/tetris_game/.gitignore new file mode 100644 index 000000000..c6127b38c --- /dev/null +++ b/Applications/Official/DEV_FW/source/tetris_game/.gitignore @@ -0,0 +1,52 @@ +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf diff --git a/Applications/Official/DEV_FW/source/tetris_game/README.md b/Applications/Official/DEV_FW/source/tetris_game/README.md new file mode 100644 index 000000000..70024b1cd --- /dev/null +++ b/Applications/Official/DEV_FW/source/tetris_game/README.md @@ -0,0 +1,4 @@ +# flipperzero_tetris_game +Tetris game for Flipper Zero + +The original source code is not mine. It comes from: https://github.com/jeffplang/flipperzero-firmware/tree/tetris_game/applications/tetris_game diff --git a/Applications/Official/DEV_FW/source/tetris_game/application.fam b/Applications/Official/DEV_FW/source/tetris_game/application.fam new file mode 100644 index 000000000..69dda3297 --- /dev/null +++ b/Applications/Official/DEV_FW/source/tetris_game/application.fam @@ -0,0 +1,12 @@ +App( + appid="Tetris", + name="Tetris", + apptype=FlipperAppType.EXTERNAL, + entry_point="tetris_game_app", + cdefines=["APP_TETRIS_GAME"], + requires=["gui"], + stack_size=2 * 1024, + order=20, + fap_icon="tetris_10px.png", + fap_category="Games", +) diff --git a/Applications/Official/DEV_FW/source/tetris_game/tetris_10px.png b/Applications/Official/DEV_FW/source/tetris_game/tetris_10px.png new file mode 100644 index 000000000..e4886d835 Binary files /dev/null and b/Applications/Official/DEV_FW/source/tetris_game/tetris_10px.png differ diff --git a/Applications/Official/DEV_FW/source/tetris_game/tetris_game.c b/Applications/Official/DEV_FW/source/tetris_game/tetris_game.c new file mode 100644 index 000000000..c30dfe9c9 --- /dev/null +++ b/Applications/Official/DEV_FW/source/tetris_game/tetris_game.c @@ -0,0 +1,472 @@ +#include +#include +#include +#include +// #include +// #include +// #include +// #include +#include + +#define BORDER_OFFSET 1 +#define MARGIN_OFFSET 3 +#define BLOCK_HEIGHT 6 +#define BLOCK_WIDTH 6 + +#define FIELD_WIDTH 11 +#define FIELD_HEIGHT 24 + +typedef struct Point { + // Also used for offset data, which is sometimes negative + int8_t x, y; +} Point; + +// Rotation logic taken from +// https://www.youtube.com/watch?v=yIpk5TJ_uaI +typedef enum { OffsetTypeCommon, OffsetTypeI, OffsetTypeO } OffsetType; + +// Since we only support rotating clockwise, these are actual translation values, +// not values to be subtracted to get translation values + +static const Point rotOffsetTranslation[3][4][5] = { + {{{0, 0}, {-1, 0}, {-1, -1}, {0, 2}, {-1, 2}}, + {{0, 0}, {1, 0}, {1, 1}, {0, -2}, {1, -2}}, + {{0, 0}, {1, 0}, {1, -1}, {0, 2}, {1, 2}}, + {{0, 0}, {-1, 0}, {-1, 1}, {0, -2}, {-1, -2}}}, + {{{1, 0}, {-1, 0}, {2, 0}, {-1, 1}, {2, -2}}, + {{0, 1}, {-1, 1}, {2, 1}, {-1, -1}, {2, 2}}, + {{-1, 0}, {1, 0}, {-2, 0}, {1, -1}, {-2, 2}}, + {{0, -1}, {1, -1}, {-2, -1}, {1, 1}, {-2, -2}}}, + {{{0, -1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}}, + {{1, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}}, + {{0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}}, + {{-1, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}}}}; + +typedef struct { + Point p[4]; + uint8_t rotIdx; + OffsetType offsetType; +} Piece; + +// Shapes @ spawn locations, rotation point first +static Piece shapes[] = { + {.p = {{5, 1}, {4, 0}, {5, 0}, {6, 1}}, .rotIdx = 0, .offsetType = OffsetTypeCommon}, // Z + {.p = {{5, 1}, {4, 1}, {5, 0}, {6, 0}}, .rotIdx = 0, .offsetType = OffsetTypeCommon}, // S + {.p = {{5, 1}, {4, 1}, {6, 1}, {6, 0}}, .rotIdx = 0, .offsetType = OffsetTypeCommon}, // L + {.p = {{5, 1}, {4, 0}, {4, 1}, {6, 1}}, .rotIdx = 0, .offsetType = OffsetTypeCommon}, // J + {.p = {{5, 1}, {4, 1}, {5, 0}, {6, 1}}, .rotIdx = 0, .offsetType = OffsetTypeCommon}, // T + {.p = {{5, 1}, {4, 1}, {6, 1}, {7, 1}}, .rotIdx = 0, .offsetType = OffsetTypeI}, // I + {.p = {{5, 1}, {5, 0}, {6, 0}, {6, 1}}, .rotIdx = 0, .offsetType = OffsetTypeO} // O +}; + +typedef enum { GameStatePlaying, GameStateGameOver } GameState; + +typedef struct { + bool playField[FIELD_HEIGHT][FIELD_WIDTH]; + Piece currPiece; + uint16_t numLines; + uint16_t fallSpeed; + GameState gameState; + FuriTimer* timer; +} TetrisState; + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} TetrisEvent; + +static void tetris_game_draw_border(Canvas* const canvas) { + canvas_draw_line(canvas, 0, 0, 0, 127); + canvas_draw_line(canvas, 0, 127, 63, 127); + canvas_draw_line(canvas, 63, 127, 63, 0); + + canvas_draw_line(canvas, 2, 0, 2, 125); + canvas_draw_line(canvas, 2, 125, 61, 125); + canvas_draw_line(canvas, 61, 125, 61, 0); +} + +static void tetris_game_draw_playfield(Canvas* const canvas, const TetrisState* tetris_state) { + // Playfield: 11 x 24 + + for(int y = 0; y < FIELD_HEIGHT; y++) { + for(int x = 0; x < FIELD_WIDTH; x++) { + if(tetris_state->playField[y][x]) { + uint16_t xOffset = x * 5; + uint16_t yOffset = y * 5; + + canvas_draw_rframe( + canvas, + BORDER_OFFSET + MARGIN_OFFSET + xOffset, + BORDER_OFFSET + MARGIN_OFFSET + yOffset - 1, + BLOCK_WIDTH, + BLOCK_HEIGHT, + 1); + canvas_draw_dot( + canvas, + BORDER_OFFSET + MARGIN_OFFSET + xOffset + 2, + BORDER_OFFSET + MARGIN_OFFSET + yOffset + 1); + canvas_draw_dot( + canvas, + BORDER_OFFSET + MARGIN_OFFSET + xOffset + 3, + BORDER_OFFSET + MARGIN_OFFSET + yOffset + 1); + canvas_draw_dot( + canvas, + BORDER_OFFSET + MARGIN_OFFSET + xOffset + 2, + BORDER_OFFSET + MARGIN_OFFSET + yOffset + 2); + } + } + } +} + +static void tetris_game_render_callback(Canvas* const canvas, void* ctx) { + const TetrisState* tetris_state = acquire_mutex((ValueMutex*)ctx, 25); + if(tetris_state == NULL) { + FURI_LOG_E("TetrisGame", "it null"); + return; + } + + tetris_game_draw_border(canvas); + tetris_game_draw_playfield(canvas, tetris_state); + + // Show score on the game field + if(tetris_state->gameState == GameStatePlaying) { + char buffer2[6]; + snprintf(buffer2, sizeof(buffer2), "%u", tetris_state->numLines); + canvas_draw_str_aligned(canvas, 58, 10, AlignRight, AlignBottom, buffer2); + } + + if(tetris_state->gameState == GameStateGameOver) { + // 128 x 64 + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 1, 52, 62, 24); + + canvas_set_color(canvas, ColorBlack); + canvas_draw_frame(canvas, 1, 52, 62, 24); + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 4, 63, "Game Over"); + + char buffer[13]; + snprintf(buffer, sizeof(buffer), "Lines: %u", tetris_state->numLines); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 32, 73, AlignCenter, AlignBottom, buffer); + } + release_mutex((ValueMutex*)ctx, tetris_state); +} + +static void tetris_game_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + TetrisEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void tetris_game_init_state(TetrisState* tetris_state) { + tetris_state->gameState = GameStatePlaying; + tetris_state->numLines = 0; + tetris_state->fallSpeed = 500; + memset(tetris_state->playField, 0, sizeof(tetris_state->playField)); + + memcpy(&tetris_state->currPiece, &shapes[rand() % 7], sizeof(tetris_state->currPiece)); + + furi_timer_start(tetris_state->timer, tetris_state->fallSpeed); +} + +static void tetris_game_remove_curr_piece(TetrisState* tetris_state) { + for(int i = 0; i < 4; i++) { + uint8_t x = tetris_state->currPiece.p[i].x; + uint8_t y = tetris_state->currPiece.p[i].y; + + tetris_state->playField[y][x] = false; + } +} + +static void tetris_game_render_curr_piece(TetrisState* tetris_state) { + for(int i = 0; i < 4; i++) { + uint8_t x = tetris_state->currPiece.p[i].x; + uint8_t y = tetris_state->currPiece.p[i].y; + + tetris_state->playField[y][x] = true; + } +} + +static void tetris_game_rotate_shape(Point currShape[], Point newShape[]) { + // Copy shape data + for(int i = 0; i < 4; i++) { + newShape[i] = currShape[i]; + } + + for(int i = 1; i < 4; i++) { + int8_t relX = currShape[i].x - currShape[0].x; + int8_t relY = currShape[i].y - currShape[0].y; + + // Matrix rotation thing + int8_t newRelX = (relX * 0) + (relY * -1); + int8_t newRelY = (relX * 1) + (relY * 0); + + newShape[i].x = currShape[0].x + newRelX; + newShape[i].y = currShape[0].y + newRelY; + } +} + +static void tetris_game_apply_kick(Point points[], Point kick) { + for(int i = 0; i < 4; i++) { + points[i].x += kick.x; + points[i].y += kick.y; + } +} + +static bool tetris_game_is_valid_pos(TetrisState* tetris_state, Point* shape) { + for(int i = 0; i < 4; i++) { + if(shape[i].x < 0 || shape[i].x > (FIELD_WIDTH - 1) || + tetris_state->playField[shape[i].y][shape[i].x] == true) { + return false; + } + } + return true; +} + +static void tetris_game_try_rotation(TetrisState* tetris_state, Piece* newPiece) { + uint8_t currRotIdx = tetris_state->currPiece.rotIdx; + + Point* rotatedShape = malloc(sizeof(Point) * 4); + Point* kickedShape = malloc(sizeof(Point) * 4); + + memcpy(rotatedShape, &tetris_state->currPiece.p, sizeof(tetris_state->currPiece.p)); + + tetris_game_rotate_shape(tetris_state->currPiece.p, rotatedShape); + + for(int i = 0; i < 5; i++) { + memcpy(kickedShape, rotatedShape, (sizeof(Point) * 4)); + tetris_game_apply_kick( + kickedShape, rotOffsetTranslation[newPiece->offsetType][currRotIdx][i]); + + if(tetris_game_is_valid_pos(tetris_state, kickedShape)) { + memcpy(&newPiece->p, kickedShape, sizeof(newPiece->p)); + newPiece->rotIdx = (newPiece->rotIdx + 1) % 4; + break; + } + } + free(rotatedShape); + free(kickedShape); +} + +static bool tetris_game_row_is_line(bool row[]) { + for(int i = 0; i < FIELD_WIDTH; i++) { + if(row[i] == false) return false; + } + return true; +} + +static void + tetris_game_check_for_lines(TetrisState* tetris_state, uint8_t* lines, uint8_t* numLines) { + for(int i = 0; i < FIELD_HEIGHT; i++) { + if(tetris_game_row_is_line(tetris_state->playField[i])) { + *(lines++) = i; + *numLines += 1; + } + } +} + +static bool tetris_game_piece_at_bottom(TetrisState* tetris_state, Piece* newPiece) { + for(int i = 0; i < 4; i++) { + Point* pos = (Point*)&newPiece->p; + if(pos[i].y >= FIELD_HEIGHT || tetris_state->playField[pos[i].y][pos[i].x] == true) { + return true; + } + } + return false; +} + +static void tetris_game_update_timer_callback(FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + TetrisEvent event = {.type = EventTypeTick}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void + tetris_game_process_step(TetrisState* tetris_state, Piece* newPiece, bool wasDownMove) { + if(tetris_state->gameState == GameStateGameOver) return; + + tetris_game_remove_curr_piece(tetris_state); + + if(wasDownMove) { + if(tetris_game_piece_at_bottom(tetris_state, newPiece)) { + furi_timer_stop(tetris_state->timer); + + tetris_game_render_curr_piece(tetris_state); + uint8_t numLines = 0; + uint8_t lines[] = {0, 0, 0, 0}; + + tetris_game_check_for_lines(tetris_state, lines, &numLines); + if(numLines > 0) { + for(int i = 0; i < numLines; i++) { + // zero out row + for(int j = 0; j < FIELD_WIDTH; j++) { + tetris_state->playField[lines[i]][j] = false; + } + // move all above rows down + for(int k = lines[i]; k >= 0; k--) { + for(int m = 0; m < FIELD_WIDTH; m++) { + tetris_state->playField[k][m] = + (k == 0) ? false : tetris_state->playField[k - 1][m]; + } + } + } + + uint16_t oldNumLines = tetris_state->numLines; + tetris_state->numLines += numLines; + if((oldNumLines / 10) % 10 != (tetris_state->numLines / 10) % 10) { + tetris_state->fallSpeed -= 50; + } + } + + // Check for game over + Piece* spawnedPiece = &shapes[rand() % 7]; + if(!tetris_game_is_valid_pos(tetris_state, spawnedPiece->p)) { + tetris_state->gameState = GameStateGameOver; + } else { + memcpy(&tetris_state->currPiece, spawnedPiece, sizeof(tetris_state->currPiece)); + furi_timer_start(tetris_state->timer, tetris_state->fallSpeed); + } + } + } + + if(tetris_game_is_valid_pos(tetris_state, newPiece->p)) { + memcpy(&tetris_state->currPiece, newPiece, sizeof(tetris_state->currPiece)); + } + + tetris_game_render_curr_piece(tetris_state); +} + +int32_t tetris_game_app() { + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(TetrisEvent)); + + TetrisState* tetris_state = malloc(sizeof(TetrisState)); + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, tetris_state, sizeof(TetrisState))) { + FURI_LOG_E("TetrisGame", "cannot create mutex\r\n"); + furi_message_queue_free(event_queue); + free(tetris_state); + return 255; + } + + // Not doing this eventually causes issues with TimerSvc due to not sleeping/yielding enough in this task + TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); + + vTaskPrioritySet(timer_task, configMAX_PRIORITIES + 1); + + ViewPort* view_port = view_port_alloc(); + view_port_set_orientation(view_port, ViewPortOrientationVertical); + view_port_draw_callback_set(view_port, tetris_game_render_callback, &state_mutex); + view_port_input_callback_set(view_port, tetris_game_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); + + tetris_state->timer = + furi_timer_alloc(tetris_game_update_timer_callback, FuriTimerTypePeriodic, event_queue); + tetris_game_init_state(tetris_state); + + TetrisEvent event; + + Piece* newPiece = malloc(sizeof(Piece)); + uint8_t downRepeatCounter = 0; + + DOLPHIN_DEED(DolphinDeedPluginGameStart); + + for(bool processing = true; processing;) { + // This 10U implicitly sets the game loop speed. downRepeatCounter relies on this value + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 10U); + + TetrisState* tetris_state = (TetrisState*)acquire_mutex_block(&state_mutex); + + memcpy(newPiece, &tetris_state->currPiece, sizeof(tetris_state->currPiece)); + bool wasDownMove = false; + + if(!furi_hal_gpio_read(&gpio_button_right)) { + if(downRepeatCounter > 3) { + for(int i = 0; i < 4; i++) { + newPiece->p[i].y += 1; + } + downRepeatCounter = 0; + wasDownMove = true; + } else { + downRepeatCounter++; + } + } + + if(event_status == FuriStatusOk) { + if(event.type == EventTypeKey) { + if(event.input.type == InputTypePress || event.input.type == InputTypeLong || + event.input.type == InputTypeRepeat) { + switch(event.input.key) { + case InputKeyUp: + break; + case InputKeyDown: + break; + case InputKeyRight: + for(int i = 0; i < 4; i++) { + newPiece->p[i].x += 1; + } + break; + case InputKeyLeft: + for(int i = 0; i < 4; i++) { + newPiece->p[i].x -= 1; + } + break; + case InputKeyOk: + if(tetris_state->gameState == GameStatePlaying) { + tetris_game_remove_curr_piece(tetris_state); + tetris_game_try_rotation(tetris_state, newPiece); + tetris_game_render_curr_piece(tetris_state); + } else { + tetris_game_init_state(tetris_state); + } + break; + case InputKeyBack: + processing = false; + break; + default: + break; + } + } + } else if(event.type == EventTypeTick) { + // TODO: This is inverted. it returns true when the button is not pressed. + // see macro in input.c and do that + if(furi_hal_gpio_read(&gpio_button_right)) { + for(int i = 0; i < 4; i++) { + newPiece->p[i].y += 1; + } + wasDownMove = true; + } + } + } + + tetris_game_process_step(tetris_state, newPiece, wasDownMove); + + view_port_update(view_port); + release_mutex(&state_mutex, tetris_state); + } + + furi_timer_free(tetris_state->timer); + 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); + vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY); + free(newPiece); + free(tetris_state); + + return 0; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/text_viewer/.gitignore b/Applications/Official/DEV_FW/source/text_viewer/.gitignore new file mode 100644 index 000000000..c6127b38c --- /dev/null +++ b/Applications/Official/DEV_FW/source/text_viewer/.gitignore @@ -0,0 +1,52 @@ +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf diff --git a/Applications/Official/DEV_FW/source/text_viewer/LICENSE b/Applications/Official/DEV_FW/source/text_viewer/LICENSE new file mode 100644 index 000000000..69004dc62 --- /dev/null +++ b/Applications/Official/DEV_FW/source/text_viewer/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Roman Shchekin + +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/Official/DEV_FW/source/text_viewer/README.md b/Applications/Official/DEV_FW/source/text_viewer/README.md new file mode 100644 index 000000000..35f15a5d5 --- /dev/null +++ b/Applications/Official/DEV_FW/source/text_viewer/README.md @@ -0,0 +1,7 @@ +# flipper-zero-hex-viewer + +Hex Viewer application for Flipper Zero! + +![Hex Viewer app!](https://habrastorage.org/r/w1560/getpro/habr/upload_files/46e/28a/d97/46e28ad973d144b123a4ce513c895d18.png) + +[Link to FAP](https://nightly.link/QtRoS/flipper-zero-hex-viewer/actions/artifacts/448677581.zip) diff --git a/Applications/Official/DEV_FW/source/text_viewer/application.fam b/Applications/Official/DEV_FW/source/text_viewer/application.fam new file mode 100644 index 000000000..8de05ef0b --- /dev/null +++ b/Applications/Official/DEV_FW/source/text_viewer/application.fam @@ -0,0 +1,16 @@ +App( + appid="text_viewer", + name="text Viewer", + apptype=FlipperAppType.EXTERNAL, + entry_point="hex_viewer_app", + cdefines=["APP_TEXT_VIEWER"], + requires=[ + "gui", + "dialogs", + ], + stack_size=2 * 1024, + order=20, + fap_icon="icons/hex_10px.png", + fap_category="Misc", + fap_icon_assets="icons", +) diff --git a/Applications/Official/DEV_FW/source/text_viewer/hex_viewer.c b/Applications/Official/DEV_FW/source/text_viewer/hex_viewer.c new file mode 100644 index 000000000..3830a6602 --- /dev/null +++ b/Applications/Official/DEV_FW/source/text_viewer/hex_viewer.c @@ -0,0 +1,282 @@ +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#define TAG "TextViewer" + +#define HEX_VIEWER_APP_PATH_FOLDER "/any" +#define HEX_VIEWER_APP_EXTENSION "*" + +#define HEX_VIEWER_BYTES_PER_LINE 20u +#define HEX_VIEWER_LINES_ON_SCREEN 5u +#define HEX_VIEWER_BUF_SIZE (HEX_VIEWER_LINES_ON_SCREEN * HEX_VIEWER_BYTES_PER_LINE) + +typedef struct { + uint8_t file_bytes[HEX_VIEWER_LINES_ON_SCREEN][HEX_VIEWER_BYTES_PER_LINE]; + uint32_t file_offset; + uint32_t file_read_bytes; + uint32_t file_size; + Stream* stream; + bool mode; // Print address or content +} HexViewerModel; + +typedef struct { + HexViewerModel* model; + FuriMutex** mutex; + + FuriMessageQueue* input_queue; + + ViewPort* view_port; + Gui* gui; + Storage* storage; +} HexViewer; + +static void render_callback(Canvas* canvas, void* ctx) { + HexViewer* hex_viewer = ctx; + furi_check(furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk); + + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + + //elements_button_left(canvas, hex_viewer->model->mode ? "Addr" : "Text"); + hex_viewer->model->mode = 1; //text mode + //elements_button_right(canvas, "Info"); + + int ROW_HEIGHT = 12; + int TOP_OFFSET = 10; + int LEFT_OFFSET = 3; + + uint32_t line_count = hex_viewer->model->file_size / HEX_VIEWER_BYTES_PER_LINE; + if(hex_viewer->model->file_size % HEX_VIEWER_BYTES_PER_LINE != 0) line_count += 1; + uint32_t first_line_on_screen = hex_viewer->model->file_offset / HEX_VIEWER_BYTES_PER_LINE; + if(line_count > HEX_VIEWER_LINES_ON_SCREEN) { + uint8_t width = canvas_width(canvas); + elements_scrollbar_pos( + canvas, + width, + 0, + ROW_HEIGHT * HEX_VIEWER_LINES_ON_SCREEN, + first_line_on_screen, // TODO + line_count - (HEX_VIEWER_LINES_ON_SCREEN - 1)); + } + + char temp_buf[32]; + uint32_t row_iters = hex_viewer->model->file_read_bytes / HEX_VIEWER_BYTES_PER_LINE; + if(hex_viewer->model->file_read_bytes % HEX_VIEWER_BYTES_PER_LINE != 0) row_iters += 1; + + for(uint32_t i = 0; i < row_iters; ++i) { + uint32_t bytes_left_per_row = + hex_viewer->model->file_read_bytes - i * HEX_VIEWER_BYTES_PER_LINE; + bytes_left_per_row = MIN(bytes_left_per_row, HEX_VIEWER_BYTES_PER_LINE); + + if(hex_viewer->model->mode) { + memcpy(temp_buf, hex_viewer->model->file_bytes[i], bytes_left_per_row); + temp_buf[bytes_left_per_row] = '\0'; + for(uint32_t j = 0; j < bytes_left_per_row; ++j) + if(!isprint((int)temp_buf[j])) temp_buf[j] = '.'; + + canvas_set_font(canvas, FontKeyboard); + canvas_draw_str(canvas, LEFT_OFFSET, TOP_OFFSET + i * ROW_HEIGHT, temp_buf); + } else { + uint32_t addr = hex_viewer->model->file_offset + i * HEX_VIEWER_BYTES_PER_LINE; + snprintf(temp_buf, 32, "%04lX", addr); + + canvas_set_font(canvas, FontKeyboard); + canvas_draw_str(canvas, LEFT_OFFSET, TOP_OFFSET + i * ROW_HEIGHT, temp_buf); + } + } + + furi_mutex_release(hex_viewer->mutex); +} + +static void input_callback(InputEvent* input_event, void* ctx) { + HexViewer* hex_viewer = ctx; + if(input_event->type == InputTypeShort || input_event->type == InputTypeRepeat) { + furi_message_queue_put(hex_viewer->input_queue, input_event, 0); + } +} + +static HexViewer* hex_viewer_alloc() { + HexViewer* instance = malloc(sizeof(HexViewer)); + + instance->model = malloc(sizeof(HexViewerModel)); + memset(instance->model, 0x0, sizeof(HexViewerModel)); + + instance->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + + instance->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + + instance->view_port = view_port_alloc(); + view_port_draw_callback_set(instance->view_port, render_callback, instance); + view_port_input_callback_set(instance->view_port, input_callback, instance); + + instance->gui = furi_record_open(RECORD_GUI); + gui_add_view_port(instance->gui, instance->view_port, GuiLayerFullscreen); + + instance->storage = furi_record_open(RECORD_STORAGE); + + return instance; +} + +static void hex_viewer_free(HexViewer* instance) { + furi_record_close(RECORD_STORAGE); + + gui_remove_view_port(instance->gui, instance->view_port); + furi_record_close(RECORD_GUI); + view_port_free(instance->view_port); + + furi_message_queue_free(instance->input_queue); + + furi_mutex_free(instance->mutex); + + if(instance->model->stream) buffered_file_stream_close(instance->model->stream); + + free(instance->model); + free(instance); +} + +static bool hex_viewer_open_file(HexViewer* hex_viewer, const char* file_path) { + furi_assert(hex_viewer); + furi_assert(file_path); + + hex_viewer->model->stream = buffered_file_stream_alloc(hex_viewer->storage); + bool isOk = true; + + do { + if(!buffered_file_stream_open( + hex_viewer->model->stream, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) { + FURI_LOG_E(TAG, "Unable to open stream: %s", file_path); + isOk = false; + break; + }; + + hex_viewer->model->file_size = stream_size(hex_viewer->model->stream); + } while(false); + + return isOk; +} + +static bool hex_viewer_read_file(HexViewer* hex_viewer) { + furi_assert(hex_viewer); + furi_assert(hex_viewer->model->stream); + furi_assert(hex_viewer->model->file_offset % HEX_VIEWER_BYTES_PER_LINE == 0); + + memset(hex_viewer->model->file_bytes, 0x0, HEX_VIEWER_BUF_SIZE); + bool isOk = true; + + do { + uint32_t offset = hex_viewer->model->file_offset; + if(!stream_seek(hex_viewer->model->stream, offset, true)) { + FURI_LOG_E(TAG, "Unable to seek stream"); + isOk = false; + break; + } + + hex_viewer->model->file_read_bytes = stream_read( + hex_viewer->model->stream, + (uint8_t*)hex_viewer->model->file_bytes, + HEX_VIEWER_BUF_SIZE); + } while(false); + + return isOk; +} + +int32_t hex_viewer_app(void* p) { + HexViewer* hex_viewer = hex_viewer_alloc(); + + FuriString* file_path; + file_path = furi_string_alloc(); + + do { + if(p && strlen(p)) { + furi_string_set(file_path, (const char*)p); + } else { + furi_string_set(file_path, HEX_VIEWER_APP_PATH_FOLDER); + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options( + &browser_options, HEX_VIEWER_APP_EXTENSION, &I_hex_10px); + browser_options.hide_ext = false; + + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + bool res = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options); + + furi_record_close(RECORD_DIALOGS); + if(!res) { + FURI_LOG_I(TAG, "No file selected"); + break; + } + } + + FURI_LOG_I(TAG, "File selected: %s", furi_string_get_cstr(file_path)); + + if(!hex_viewer_open_file(hex_viewer, furi_string_get_cstr(file_path))) break; + hex_viewer_read_file(hex_viewer); + + InputEvent input; + while(furi_message_queue_get(hex_viewer->input_queue, &input, FuriWaitForever) == + FuriStatusOk) { + if(input.key == InputKeyBack) { + break; + } else if(input.key == InputKeyUp) { + furi_check(furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk); + if(hex_viewer->model->file_offset > 0) { + hex_viewer->model->file_offset -= HEX_VIEWER_BYTES_PER_LINE; + if(!hex_viewer_read_file(hex_viewer)) break; + } + furi_mutex_release(hex_viewer->mutex); + } else if(input.key == InputKeyDown) { + furi_check(furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk); + uint32_t last_byte_on_screen = + hex_viewer->model->file_offset + hex_viewer->model->file_read_bytes; + + if(hex_viewer->model->file_size > last_byte_on_screen) { + hex_viewer->model->file_offset += HEX_VIEWER_BYTES_PER_LINE; + if(!hex_viewer_read_file(hex_viewer)) break; + } + furi_mutex_release(hex_viewer->mutex); + } else if(input.key == InputKeyLeft) { + furi_check(furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk); + hex_viewer->model->mode = !hex_viewer->model->mode; + furi_mutex_release(hex_viewer->mutex); + } else if(input.key == InputKeyRight) { + FuriString* buffer; + buffer = furi_string_alloc(); + furi_string_printf( + buffer, + "File path: %s\nFile size: %lu (0x%lX)", + furi_string_get_cstr(file_path), + hex_viewer->model->file_size, + hex_viewer->model->file_size); + + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_header(message, "Text Viewer v1.1", 16, 2, AlignLeft, AlignTop); + dialog_message_set_icon(message, &I_hex_10px, 3, 2); + dialog_message_set_text( + message, furi_string_get_cstr(buffer), 3, 16, AlignLeft, AlignTop); + dialog_message_set_buttons(message, NULL, NULL, "Back"); + dialog_message_show(dialogs, message); + + furi_string_free(buffer); + dialog_message_free(message); + furi_record_close(RECORD_DIALOGS); + } + view_port_update(hex_viewer->view_port); + } + } while(false); + + furi_string_free(file_path); + hex_viewer_free(hex_viewer); + + return 0; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/text_viewer/icons/hex_10px.png b/Applications/Official/DEV_FW/source/text_viewer/icons/hex_10px.png new file mode 100644 index 000000000..582e288c6 Binary files /dev/null and b/Applications/Official/DEV_FW/source/text_viewer/icons/hex_10px.png differ diff --git a/Applications/Official/DEV_FW/source/tictactoe_game/application.fam b/Applications/Official/DEV_FW/source/tictactoe_game/application.fam new file mode 100644 index 000000000..2ad09f11b --- /dev/null +++ b/Applications/Official/DEV_FW/source/tictactoe_game/application.fam @@ -0,0 +1,12 @@ +App( + appid="TicTacToe", + name="Tic Tac Toe", + apptype=FlipperAppType.EXTERNAL, + entry_point="tictactoe_game_app", + cdefines=["APP_TICTACTOE_GAME"], + requires=["gui"], + stack_size=1 * 1024, + order=250, + fap_icon="tictactoe_10px.png", + fap_category="Games", +) diff --git a/Applications/Official/DEV_FW/source/tictactoe_game/tictactoe_10px.png b/Applications/Official/DEV_FW/source/tictactoe_game/tictactoe_10px.png new file mode 100644 index 000000000..41ca1d973 Binary files /dev/null and b/Applications/Official/DEV_FW/source/tictactoe_game/tictactoe_10px.png differ diff --git a/Applications/Official/DEV_FW/source/tictactoe_game/tictactoe_game.c b/Applications/Official/DEV_FW/source/tictactoe_game/tictactoe_game.c new file mode 100644 index 000000000..fe57d7c62 --- /dev/null +++ b/Applications/Official/DEV_FW/source/tictactoe_game/tictactoe_game.c @@ -0,0 +1,382 @@ +#include +#include +#include +#include +#include + +#define TAG "TicTacToe" + +typedef enum { EventTypeTick, EventTypeKey } EventType; + +typedef struct { + FuriTimer* timer; + uint8_t selBoxX; + uint8_t selBoxY; + + uint8_t selX; + uint8_t selY; + + uint16_t scoreX; + uint16_t scoreO; + + char player; + + char field[3][3]; + bool fieldx[3][3]; + + uint8_t coords[3]; + + bool button_state; + +} TicTacToeState; + +typedef struct { + EventType type; + InputEvent input; +} GameEvent; + +void drawCross(Canvas* const canvas, uint8_t x, uint8_t y) { + canvas_draw_line(canvas, x, y, x + 9, y + 9); // top left - bottom right slash + canvas_draw_line(canvas, x + 9, y, x, y + 9); // down left - top right slash +} + +void drawCircle(Canvas* const canvas, uint8_t x, uint8_t y) { + canvas_draw_circle(canvas, x + 4, y + 5, 5); +} + +void player_switch(TicTacToeState* ts) { + if(ts->player == 'O') { + ts->player = 'X'; + } else if(ts->player == 'X') { + ts->player = 'O'; + } +} + +void tictactoe_draw(Canvas* canvas, TicTacToeState* ts) { + // Draws the game field + canvas_draw_frame(canvas, 0, 0, 64, 64); // frame + canvas_draw_line(canvas, 0, 21, 63, 21); // horizontal line + canvas_draw_line(canvas, 0, 42, 63, 42); // horizontal line + canvas_draw_line(canvas, 21, 0, 21, 63); // vertical line + canvas_draw_line(canvas, 42, 0, 42, 63); // vertical line + + // Draws the game field elements (X or O) + for(uint8_t i = 0; i <= 2; i++) { + for(uint8_t j = 0; j <= 2; j++) { + if(ts->field[i][j] == 'O') { + drawCircle(canvas, ts->coords[i], ts->coords[j]); + } else if(ts->field[i][j] == 'X') { + drawCross(canvas, ts->coords[i], ts->coords[j]); + } + } + } + + // Draws the selection box + if(ts->selX == 1) { + ts->selBoxX = 1; + } else if(ts->selX == 2) { + ts->selBoxX = 22; + } else if(ts->selX == 3) { + ts->selBoxX = 43; + } + + if(ts->selY == 1) { + ts->selBoxY = 1; + } else if(ts->selY == 2) { + ts->selBoxY = 22; + } else if(ts->selY == 3) { + ts->selBoxY = 43; + } + + canvas_set_color(canvas, ColorBlack); + canvas_draw_frame(canvas, ts->selBoxX, ts->selBoxY, 20, 20); + canvas_draw_frame(canvas, ts->selBoxX + 1, ts->selBoxY + 1, 18, 18); + + // Draws the sidebar + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 81, 10, "SCORE"); + canvas_draw_str(canvas, 75, 24, "X:"); + + char scoreXBuffer[10]; + snprintf(scoreXBuffer, sizeof(scoreXBuffer), "%d", ts->scoreX); + canvas_draw_str(canvas, 88, 24, scoreXBuffer); + canvas_draw_str(canvas, 75, 35, "O:"); + + char scoreOBuffer[10]; + snprintf(scoreOBuffer, sizeof(scoreOBuffer), "%d", ts->scoreO); + canvas_draw_str(canvas, 88, 35, scoreOBuffer); + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 75, 46, "Player:"); + + if(ts->player == 'X') { + drawCross(canvas, 93, 50); + } else if(ts->player == 'O') { + drawCircle(canvas, 93, 50); + } +} + +void clear_game_field(TicTacToeState* ts) { + // Clears the game field arrays + for(uint8_t i = 0; i <= 2; i++) { + for(uint8_t j = 0; j <= 2; j++) { + ts->field[i][j] = ' '; + ts->fieldx[i][j] = false; + } + } + + ts->selX = 2; // Centers the selection box on X axis + ts->selY = 2; // Centers the selection box on Y axis +} + +void reset_game_data(TicTacToeState* ts) { + ts->scoreO = 0; + ts->scoreX = 0; + ts->player = 'X'; +} + +void draw_win(Canvas* canvas, char player, TicTacToeState* ts) { + // Handles the score table + if(player == 'X') { + ts->scoreX++; + } else if(player == 'O') { + ts->scoreO++; + } + + // Switches the players + player_switch(ts); + + // Draws the board with players switched + tictactoe_draw(canvas, ts); + + // Clear the game field + clear_game_field(ts); + + // Draw the new board + tictactoe_draw(canvas, ts); +} + +static void tictactoe_state_init(TicTacToeState* tictactoe_state) { + // Set the initial game state + tictactoe_state->selX = 2; + tictactoe_state->selY = 2; + tictactoe_state->player = 'X'; + tictactoe_state->coords[0] = 6; + tictactoe_state->coords[1] = 27; + tictactoe_state->coords[2] = 48; + tictactoe_state->button_state = false; + + clear_game_field(tictactoe_state); + + reset_game_data(tictactoe_state); +} + +static void tictactoe_draw_callback(Canvas* const canvas, void* ctx) { + TicTacToeState* ticst = acquire_mutex((ValueMutex*)ctx, 25); + if(ticst == NULL) { + return; + } + + if(ticst->selX > 3) { + ticst->selX = 3; + } else if(ticst->selX < 1) { + ticst->selX = 1; + } + + if(ticst->selY > 3) { + ticst->selY = 3; + } else if(ticst->selY < 1) { + ticst->selY = 1; + } + + // Assigns the game field elements their value (X or O) when the OK button is pressed + if(ticst->button_state) { + ticst->button_state = false; + for(uint8_t i = 0; i <= 2; i++) { + for(uint8_t j = 0; j <= 2; j++) { + if((ticst->selX == i + 1) && (ticst->selY == j + 1) && + (ticst->fieldx[i][j] == false)) { + if(ticst->player == 'X') { + ticst->field[i][j] = 'X'; + ticst->fieldx[i][j] = true; + player_switch(ticst); + } else if(ticst->player == 'O') { + ticst->field[i][j] = 'O'; + ticst->fieldx[i][j] = true; + player_switch(ticst); + } + } + } + } + } + + // Checks the game field for winning combinations + if((ticst->field[0][0] == 'X') && (ticst->field[1][0] == 'X') && (ticst->field[2][0] == 'X')) { + draw_win(canvas, 'X', ticst); + } else if( + (ticst->field[0][1] == 'X') && (ticst->field[1][1] == 'X') && + (ticst->field[2][1] == 'X')) { + draw_win(canvas, 'X', ticst); + } else if( + (ticst->field[0][2] == 'X') && (ticst->field[1][2] == 'X') && + (ticst->field[2][2] == 'X')) { + draw_win(canvas, 'X', ticst); + } else if( + (ticst->field[0][0] == 'X') && (ticst->field[0][1] == 'X') && + (ticst->field[0][2] == 'X')) { + draw_win(canvas, 'X', ticst); + } else if( + (ticst->field[1][0] == 'X') && (ticst->field[1][1] == 'X') && + (ticst->field[1][2] == 'X')) { + draw_win(canvas, 'X', ticst); + } else if( + (ticst->field[2][0] == 'X') && (ticst->field[2][1] == 'X') && + (ticst->field[2][2] == 'X')) { + draw_win(canvas, 'X', ticst); + } else if( + (ticst->field[0][0] == 'X') && (ticst->field[1][1] == 'X') && + (ticst->field[2][2] == 'X')) { + draw_win(canvas, 'X', ticst); + } else if( + (ticst->field[2][0] == 'X') && (ticst->field[1][1] == 'X') && + (ticst->field[0][2] == 'X')) { + draw_win(canvas, 'X', ticst); + } else if( + (ticst->field[0][0] == 'O') && (ticst->field[1][0] == 'O') && + (ticst->field[2][0] == 'O')) { + draw_win(canvas, 'O', ticst); + } else if( + (ticst->field[0][1] == 'O') && (ticst->field[1][1] == 'O') && + (ticst->field[2][1] == 'O')) { + draw_win(canvas, 'O', ticst); + } else if( + (ticst->field[0][2] == 'O') && (ticst->field[1][2] == 'O') && + (ticst->field[2][2] == 'O')) { + draw_win(canvas, 'O', ticst); + } else if( + (ticst->field[0][0] == 'O') && (ticst->field[0][1] == 'O') && + (ticst->field[0][2] == 'O')) { + draw_win(canvas, 'O', ticst); + } else if( + (ticst->field[1][0] == 'O') && (ticst->field[1][1] == 'O') && + (ticst->field[1][2] == 'O')) { + draw_win(canvas, 'O', ticst); + } else if( + (ticst->field[2][0] == 'O') && (ticst->field[2][1] == 'O') && + (ticst->field[2][2] == 'O')) { + draw_win(canvas, 'O', ticst); + } else if( + (ticst->field[0][0] == 'O') && (ticst->field[1][1] == 'O') && + (ticst->field[2][2] == 'O')) { + draw_win(canvas, 'O', ticst); + } else if( + (ticst->field[2][0] == 'O') && (ticst->field[1][1] == 'O') && + (ticst->field[0][2] == 'O')) { + draw_win(canvas, 'O', ticst); + } else if( + (ticst->fieldx[0][0] == true) && (ticst->fieldx[0][1] == true) && + (ticst->fieldx[0][2] == true) && (ticst->fieldx[1][0] == true) && + (ticst->fieldx[1][1] == true) && (ticst->fieldx[1][2] == true) && + (ticst->fieldx[2][0] == true) && (ticst->fieldx[2][1] == true) && + (ticst->fieldx[2][2] == true)) { + draw_win(canvas, 'T', ticst); + } + + tictactoe_draw(canvas, ticst); + + release_mutex((ValueMutex*)ctx, ticst); +} + +static void tictactoe_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + GameEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void tictactoe_update_timer_callback(FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + GameEvent event = {.type = EventTypeTick}; + furi_message_queue_put(event_queue, &event, 0); +} + +int32_t tictactoe_game_app(void* p) { + UNUSED(p); + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(GameEvent)); + + TicTacToeState* tictactoe_state = malloc(sizeof(TicTacToeState)); + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, tictactoe_state, sizeof(TicTacToeState))) { + FURI_LOG_E(TAG, "Cannot create mutex\r\n"); + furi_message_queue_free(event_queue); + free(tictactoe_state); + return 255; + } + + // Set system callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, tictactoe_draw_callback, &state_mutex); + view_port_input_callback_set(view_port, tictactoe_input_callback, event_queue); + + tictactoe_state->timer = + furi_timer_alloc(tictactoe_update_timer_callback, FuriTimerTypePeriodic, event_queue); + furi_timer_start(tictactoe_state->timer, furi_kernel_get_tick_frequency() / 22); + + tictactoe_state_init(tictactoe_state); + + // Open GUI and register view_port + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + GameEvent event; + for(bool processing = true; processing;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + TicTacToeState* tictactoe_state = (TicTacToeState*)acquire_mutex_block(&state_mutex); + + if(event_status == FuriStatusOk) { + // Key events + if(event.type == EventTypeKey) { + if(event.input.type == InputTypePress) { + switch(event.input.key) { + case InputKeyBack: + processing = false; + break; + case InputKeyRight: + tictactoe_state->selX++; + break; + case InputKeyLeft: + tictactoe_state->selX--; + break; + case InputKeyUp: + tictactoe_state->selY--; + break; + case InputKeyDown: + tictactoe_state->selY++; + break; + case InputKeyOk: + tictactoe_state->button_state = true; + break; + default: + break; + } + } + } + } + + view_port_update(view_port); + release_mutex(&state_mutex, tictactoe_state); + } + + furi_timer_free(tictactoe_state->timer); + 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); + free(tictactoe_state); + + return 0; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/timelapse/README.md b/Applications/Official/DEV_FW/source/timelapse/README.md new file mode 100644 index 000000000..fe3ed171a --- /dev/null +++ b/Applications/Official/DEV_FW/source/timelapse/README.md @@ -0,0 +1,72 @@ + +# zeitraffer + +[![Build FAP](https://github.com/theageoflove/flipperzero-zeitraffer/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/theageoflove/flipperzero-zeitraffer/actions/workflows/build.yml) + +english version [below](#eng) + +![zeitraffer for flipper zero](https://theageoflove.ru/uploads/2022/11/photo_2022-11-10_15-54-25.jpg) +Видео работы: https://youtu.be/VPSpRLJXYAc + +Готовый фап под последнюю релизную прошивку [можно скачать здесь](https://nightly.link/theageoflove/flipperzero-zeitraffer/workflows/build/main/zeitraffer.fap.zip). + +Я ненастоящий сварщик, не обессудьте. Делал для своей Sony DSLR A100, подходит для любых камер, поддерживающих проводной пульт с тремя контактами. + +Основано на хелловорлде https://github.com/zmactep/flipperzero-hello-world + +### Управление: + + - **вверх-вниз** - время. + - **влево-вправо** - количество кадров + + 0 кадров - бесконечный режим, -1 кадров - BULB + - **зажатие стрелок** - ±10 кадров/секунд + - **ОК** - пуск/пауза + - Длинное нажатие **ОК** - включить/выключить подсветку + - **назад** - сброс + - длинное нажатие **назад** - выход + +При работающем таймере блокируются все кнопки кроме ОК. + +При запуске даётся три секунды на отскочить. + +## Чо надо + - две оптопары типа EL817C + - кусок гребёнки на три пина + - немного провода + - термоусадка + - разъём пульта от камеры. Где взять или из чего сделать - думайте + +## Как собрать +Берём оптопары, соединяем по схеме. +![](https://theageoflove.ru/uploads/2022/11/camera_cable.jpg) +Где какой пин у камеры, можно узнать например тут: https://www.doc-diy.net/photo/remote_pinout/ + +# English +Simple timelapse app for Flipper Zero. + +[Get latest release](https://nightly.link/theageoflove/flipperzero-zeitraffer/workflows/build/main/zeitraffer.fap.zip) + +based on https://github.com/zmactep/flipperzero-hello-world + +### Control: + - Up and down - time. + - Left and right - number of frames + - Long press arrows - ±10 frames/seconds + - OK - start/pause + - Long press OK - turn on/off the backlight + - Back - reset + - Long press back - exit + +When the timer is running, all buttons are blocked except OK. + +## What you need: + - two EL817C optocouplers + - pin header connector 1x3 2,54mm male + - some wire + - heat shrink + - camera remote connector +## How to assemble +Take optocouplers, connect according to the scheme. +![](https://theageoflove.ru/uploads/2022/11/camera_cable_en.jpg) +Camera pinout can be found here: https://www.doc-diy.net/photo/remote_pinout/ diff --git a/Applications/Official/DEV_FW/source/timelapse/application.fam b/Applications/Official/DEV_FW/source/timelapse/application.fam new file mode 100644 index 000000000..ee725fe52 --- /dev/null +++ b/Applications/Official/DEV_FW/source/timelapse/application.fam @@ -0,0 +1,21 @@ +App( + appid="GPIO_Intervalometer", + name="[GPIO] Intervalometer", + apptype=FlipperAppType.EXTERNAL, + entry_point="zeitraffer_app", + cdefines=["APP_ZEITRAFFER"], + requires=[ + "gui", + "input", + "notification", + "gpio" + ], + stack_size=2 * 1024, + order=90, + fap_icon_assets="icons", + fap_icon="zeitraffer.png", + fap_category="GPIO", + fap_description="Simple intervalometer app", + fap_author="Aurelius Rosenbaum", + fap_weburl="https://github.com/theageoflove/flipperzero-zeitraffer", +) \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/timelapse/gpio_item.c b/Applications/Official/DEV_FW/source/timelapse/gpio_item.c new file mode 100644 index 000000000..2d0f5f676 --- /dev/null +++ b/Applications/Official/DEV_FW/source/timelapse/gpio_item.c @@ -0,0 +1,51 @@ +#include "gpio_item.h" + +#include + +typedef struct { + const char* name; + const GpioPin* pin; +} GpioItem; + +static const GpioItem gpio_item[GPIO_ITEM_COUNT] = { + {"1.2: PA7", &gpio_ext_pa7}, + {"1.3: PA6", &gpio_ext_pa6}, + {"1.4: PA4", &gpio_ext_pa4}, + {"1.5: PB3", &gpio_ext_pb3}, + {"1.6: PB2", &gpio_ext_pb2}, + {"1.7: PC3", &gpio_ext_pc3}, + {"2.7: PC1", &gpio_ext_pc1}, + {"2.8: PC0", &gpio_ext_pc0}, +}; + +void gpio_item_configure_pin(uint8_t index, GpioMode mode) { + furi_assert(index < GPIO_ITEM_COUNT); + furi_hal_gpio_write(gpio_item[index].pin, false); + furi_hal_gpio_init(gpio_item[index].pin, mode, GpioPullNo, GpioSpeedVeryHigh); +} + +void gpio_item_configure_all_pins(GpioMode mode) { + for(uint8_t i = 0; i < GPIO_ITEM_COUNT; i++) { + gpio_item_configure_pin(i, mode); + } +} + +void gpio_item_set_pin(uint8_t index, bool level) { + furi_assert(index < GPIO_ITEM_COUNT); + furi_hal_gpio_write(gpio_item[index].pin, level); +} + +void gpio_item_set_all_pins(bool level) { + for(uint8_t i = 0; i < GPIO_ITEM_COUNT; i++) { + gpio_item_set_pin(i, level); + } +} + +const char* gpio_item_get_pin_name(uint8_t index) { + furi_assert(index < GPIO_ITEM_COUNT + 1); + if(index == GPIO_ITEM_COUNT) { + return "ALL"; + } else { + return gpio_item[index].name; + } +} diff --git a/Applications/Official/DEV_FW/source/timelapse/gpio_item.h b/Applications/Official/DEV_FW/source/timelapse/gpio_item.h new file mode 100644 index 000000000..5cb2b86c1 --- /dev/null +++ b/Applications/Official/DEV_FW/source/timelapse/gpio_item.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#define GPIO_ITEM_COUNT 8 + +void gpio_item_configure_pin(uint8_t index, GpioMode mode); + +void gpio_item_configure_all_pins(GpioMode mode); + +void gpio_item_set_pin(uint8_t index, bool level); + +void gpio_item_set_all_pins(bool level); + +const char* gpio_item_get_pin_name(uint8_t index); diff --git a/Applications/Official/DEV_FW/source/timelapse/icons/ButtonDown_7x4.png b/Applications/Official/DEV_FW/source/timelapse/icons/ButtonDown_7x4.png new file mode 100644 index 000000000..2954bb6a6 Binary files /dev/null and b/Applications/Official/DEV_FW/source/timelapse/icons/ButtonDown_7x4.png differ diff --git a/Applications/Official/DEV_FW/source/timelapse/icons/ButtonLeft_4x7.png b/Applications/Official/DEV_FW/source/timelapse/icons/ButtonLeft_4x7.png new file mode 100644 index 000000000..0b4655d43 Binary files /dev/null and b/Applications/Official/DEV_FW/source/timelapse/icons/ButtonLeft_4x7.png differ diff --git a/Applications/Official/DEV_FW/source/timelapse/icons/ButtonRight_4x7.png b/Applications/Official/DEV_FW/source/timelapse/icons/ButtonRight_4x7.png new file mode 100644 index 000000000..8e1c74c1c Binary files /dev/null and b/Applications/Official/DEV_FW/source/timelapse/icons/ButtonRight_4x7.png differ diff --git a/Applications/Official/DEV_FW/source/timelapse/icons/ButtonUp_7x4.png b/Applications/Official/DEV_FW/source/timelapse/icons/ButtonUp_7x4.png new file mode 100644 index 000000000..1be79328b Binary files /dev/null and b/Applications/Official/DEV_FW/source/timelapse/icons/ButtonUp_7x4.png differ diff --git a/Applications/Official/DEV_FW/source/timelapse/icons/Pin_star_7x7.png b/Applications/Official/DEV_FW/source/timelapse/icons/Pin_star_7x7.png new file mode 100644 index 000000000..42fdea86e Binary files /dev/null and b/Applications/Official/DEV_FW/source/timelapse/icons/Pin_star_7x7.png differ diff --git a/Applications/Official/DEV_FW/source/timelapse/icons/loading_10px.png b/Applications/Official/DEV_FW/source/timelapse/icons/loading_10px.png new file mode 100644 index 000000000..4f626b3d5 Binary files /dev/null and b/Applications/Official/DEV_FW/source/timelapse/icons/loading_10px.png differ diff --git a/Applications/Official/DEV_FW/source/timelapse/zeitraffer.c b/Applications/Official/DEV_FW/source/timelapse/zeitraffer.c new file mode 100644 index 000000000..4e31292c6 --- /dev/null +++ b/Applications/Official/DEV_FW/source/timelapse/zeitraffer.c @@ -0,0 +1,435 @@ +#include +#include +#include +#include +#include +#include +#include "gpio_item.h" +#include "GPIO_Intervalometer_icons.h" + +#define CONFIG_FILE_DIRECTORY_PATH "/ext/apps_data/intravelometer" +#define CONFIG_FILE_PATH CONFIG_FILE_DIRECTORY_PATH "/intravelometer.conf" + +// Часть кода покрадена из https://github.com/zmactep/flipperzero-hello-world + +int32_t Time = 10; // Таймер +int32_t Count = 10; // Количество кадров +int32_t WorkTime = 0; // Счётчик таймера +int32_t WorkCount = 0; // Счётчик кадров +bool InfiniteShot = false; // Бесконечная съёмка +bool Bulb = false; // Режим BULB +int32_t Backlight = 0; // Подсветка: вкл/выкл/авто +int32_t Delay = 3; // Задержка на отскочить +bool Work = false; + +const NotificationSequence sequence_click = { + &message_note_c7, + &message_delay_50, + &message_sound_off, + NULL, +}; + +typedef enum { + EventTypeTick, + EventTypeInput, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} ZeitrafferEvent; + +static void draw_callback(Canvas* canvas, void* ctx) { + UNUSED(ctx); + char temp_str[36]; + canvas_clear(canvas); + canvas_set_font(canvas, FontPrimary); + switch(Count) { + case -1: + snprintf(temp_str, sizeof(temp_str), "Set: BULB %li sec", Time); + break; + case 0: + snprintf(temp_str, sizeof(temp_str), "Set: infinite, %li sec", Time); + break; + default: + snprintf(temp_str, sizeof(temp_str), "Set: %li frames, %li sec", Count, Time); + } + canvas_draw_str(canvas, 3, 15, temp_str); + snprintf(temp_str, sizeof(temp_str), "Left: %li frames, %li sec", WorkCount, WorkTime); + canvas_draw_str(canvas, 3, 35, temp_str); + + switch(Backlight) { + case 1: + canvas_draw_str(canvas, 13, 55, "ON"); + break; + case 2: + canvas_draw_str(canvas, 13, 55, "OFF"); + break; + default: + canvas_draw_str(canvas, 13, 55, "AUTO"); + } + + //canvas_draw_icon(canvas, 90, 17, &I_ButtonUp_7x4); + //canvas_draw_icon(canvas, 100, 17, &I_ButtonDown_7x4); + //canvas_draw_icon(canvas, 27, 17, &I_ButtonLeftSmall_3x5); + //canvas_draw_icon(canvas, 37, 17, &I_ButtonRightSmall_3x5); + //canvas_draw_icon(canvas, 3, 48, &I_Pin_star_7x7); + + canvas_draw_icon(canvas, 85, 41, &I_ButtonUp_7x4); + canvas_draw_icon(canvas, 85, 57, &I_ButtonDown_7x4); + canvas_draw_icon(canvas, 59, 48, &I_ButtonLeft_4x7); + canvas_draw_icon(canvas, 72, 48, &I_ButtonRight_4x7); + canvas_draw_icon(canvas, 3, 48, &I_Pin_star_7x7); + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 65, 55, "F"); + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 85, 55, "S"); + + canvas_draw_icon(canvas, 59, 48, &I_ButtonLeft_4x7); + canvas_draw_icon(canvas, 72, 48, &I_ButtonRight_4x7); + + if(Work) { + canvas_draw_icon(canvas, 106, 46, &I_loading_10px); + } +} + +static void input_callback(InputEvent* input_event, void* ctx) { + // Проверяем, что контекст не нулевой + furi_assert(ctx); + FuriMessageQueue* event_queue = ctx; + + ZeitrafferEvent event = {.type = EventTypeInput, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void timer_callback(FuriMessageQueue* event_queue) { + // Проверяем, что контекст не нулевой + furi_assert(event_queue); + + ZeitrafferEvent event = {.type = EventTypeTick}; + furi_message_queue_put(event_queue, &event, 0); +} + +int32_t zeitraffer_app(void* p) { + UNUSED(p); + + // Текущее событие типа кастомного типа ZeitrafferEvent + ZeitrafferEvent event; + // Очередь событий на 8 элементов размера ZeitrafferEvent + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(ZeitrafferEvent)); + + // Создаем новый view port + ViewPort* view_port = view_port_alloc(); + // Создаем callback отрисовки, без контекста + view_port_draw_callback_set(view_port, draw_callback, NULL); + // Создаем callback нажатий на клавиши, в качестве контекста передаем + // нашу очередь сообщений, чтоб запихивать в неё эти события + view_port_input_callback_set(view_port, input_callback, event_queue); + + // Создаем GUI приложения + Gui* gui = furi_record_open(RECORD_GUI); + // Подключаем view port к GUI в полноэкранном режиме + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + // Конфигурим пины + gpio_item_configure_all_pins(GpioModeOutputPushPull); + + // Создаем периодический таймер с коллбэком, куда в качестве + // контекста будет передаваться наша очередь событий + FuriTimer* timer = furi_timer_alloc(timer_callback, FuriTimerTypePeriodic, event_queue); + // Запускаем таймер + //furi_timer_start(timer, 1500); + + // Включаем нотификации + NotificationApp* notifications = furi_record_open(RECORD_NOTIFICATION); + + Storage* storage = furi_record_open(RECORD_STORAGE); + + // Загружаем настройки + FlipperFormat* load = flipper_format_file_alloc(storage); + + do { + if(!flipper_format_file_open_existing(load, CONFIG_FILE_PATH)) { + notification_message(notifications, &sequence_error); + break; + } + if(!flipper_format_read_int32(load, "Time", &Time, 1)) { + notification_message(notifications, &sequence_error); + break; + } + if(!flipper_format_read_int32(load, "Count", &Count, 1)) { + notification_message(notifications, &sequence_error); + break; + } + if(!flipper_format_read_int32(load, "Backlight", &Backlight, 1)) { + notification_message(notifications, &sequence_error); + break; + } + if(!flipper_format_read_int32(load, "Delay", &Delay, 1)) { + notification_message(notifications, &sequence_error); + break; + } + notification_message(notifications, &sequence_success); + + } while(0); + + flipper_format_free(load); + + // Бесконечный цикл обработки очереди событий + while(1) { + // Выбираем событие из очереди в переменную event (ждем бесконечно долго, если очередь пуста) + // и проверяем, что у нас получилось это сделать + furi_check(furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk); + + // Наше событие — это нажатие кнопки + if(event.type == EventTypeInput) { + if(event.input.type == InputTypeShort) { // Короткие нажатия + + if(event.input.key == InputKeyBack) { + if(Work) { // Если таймер запущен - нефиг мацать кнопки! + notification_message(notifications, &sequence_error); + } else { + WorkCount = Count; + WorkTime = 3; + if(Count == 0) { + InfiniteShot = true; + WorkCount = 1; + } else + InfiniteShot = false; + + notification_message(notifications, &sequence_success); + } + } + if(event.input.key == InputKeyRight) { + if(furi_timer_is_running(timer)) { + notification_message(notifications, &sequence_error); + } else { + Count++; + notification_message(notifications, &sequence_click); + } + } + if(event.input.key == InputKeyLeft) { + if(furi_timer_is_running(timer)) { + notification_message(notifications, &sequence_error); + } else { + Count--; + notification_message(notifications, &sequence_click); + } + } + if(event.input.key == InputKeyUp) { + if(furi_timer_is_running(timer)) { + notification_message(notifications, &sequence_error); + } else { + Time++; + notification_message(notifications, &sequence_click); + } + } + if(event.input.key == InputKeyDown) { + if(furi_timer_is_running(timer)) { + notification_message(notifications, &sequence_error); + } else { + Time--; + notification_message(notifications, &sequence_click); + } + } + if(event.input.key == InputKeyOk) { + if(furi_timer_is_running(timer)) { + notification_message(notifications, &sequence_click); + furi_timer_stop(timer); + Work = false; + } else { + furi_timer_start(timer, 1000); + Work = true; + + if(WorkCount == 0) WorkCount = Count; + + if(WorkTime == 0) WorkTime = Delay; + + if(Count == 0) { + InfiniteShot = true; + WorkCount = 1; + } else + InfiniteShot = false; + + if(Count == -1) { + gpio_item_set_pin(4, true); + gpio_item_set_pin(5, true); + Bulb = true; + WorkCount = 1; + WorkTime = Time; + } else + Bulb = false; + + notification_message(notifications, &sequence_success); + } + } + } + if(event.input.type == InputTypeLong) { // Длинные нажатия + // Если нажата кнопка "назад", то выходим из цикла, а следовательно и из приложения + if(event.input.key == InputKeyBack) { + if(furi_timer_is_running(timer)) { // А если работает таймер - не выходим :D + notification_message(notifications, &sequence_error); + } else { + notification_message(notifications, &sequence_click); + gpio_item_set_all_pins(false); + furi_timer_stop(timer); + notification_message( + notifications, &sequence_display_backlight_enforce_auto); + break; + } + } + if(event.input.key == InputKeyOk) { + // Нам ваша подсветка и нахой не нужна! Или нужна? + Backlight++; + if(Backlight > 2) Backlight = 0; + } + } + + if(event.input.type == InputTypeRepeat) { // Зажатые кнопки + if(event.input.key == InputKeyRight) { + if(furi_timer_is_running(timer)) { + notification_message(notifications, &sequence_error); + } else { + Count = Count + 10; + } + } + if(event.input.key == InputKeyLeft) { + if(furi_timer_is_running(timer)) { + notification_message(notifications, &sequence_error); + } else { + Count = Count - 10; + } + } + if(event.input.key == InputKeyUp) { + if(furi_timer_is_running(timer)) { + notification_message(notifications, &sequence_error); + } else { + Time = Time + 10; + } + } + if(event.input.key == InputKeyDown) { + if(furi_timer_is_running(timer)) { + notification_message(notifications, &sequence_error); + } else { + Time = Time - 10; + } + } + } + } + + // Наше событие — это сработавший таймер + else if(event.type == EventTypeTick) { + WorkTime--; + + if(WorkTime < 1) { // фоткаем + notification_message(notifications, &sequence_blink_white_100); + if(Bulb) { + gpio_item_set_all_pins(false); + WorkCount = 0; + } else { + WorkCount--; + view_port_update(view_port); + notification_message(notifications, &sequence_click); + // Дрыгаем ногами + //gpio_item_set_all_pins(true); + gpio_item_set_pin(4, true); + gpio_item_set_pin(5, true); + furi_delay_ms(400); // На короткие нажатия фотик плохо реагирует + gpio_item_set_pin(4, false); + gpio_item_set_pin(5, false); + //gpio_item_set_all_pins(false); + + if(InfiniteShot) WorkCount++; + + WorkTime = Time; + view_port_update(view_port); + } + } else { + // Отправляем нотификацию мигания синим светодиодом + notification_message(notifications, &sequence_blink_blue_100); + } + + if(WorkCount < 1) { // закончили + Work = false; + gpio_item_set_all_pins(false); + furi_timer_stop(timer); + notification_message(notifications, &sequence_audiovisual_alert); + WorkTime = 3; + WorkCount = 0; + } + + switch(Backlight) { // чо по подсветке? + case 1: + notification_message(notifications, &sequence_display_backlight_on); + break; + case 2: + notification_message(notifications, &sequence_display_backlight_off); + break; + default: + notification_message(notifications, &sequence_display_backlight_enforce_auto); + } + } + if(Time < 1) Time = 1; // Не даём открутить таймер меньше единицы + if(Count < -1) + Count = 0; // А тут даём, бо 0 кадров это бесконечная съёмка, а -1 кадров - BULB + } + + // Схороняем настройки + FlipperFormat* save = flipper_format_file_alloc(storage); + + do { + if(!flipper_format_file_open_always(save, CONFIG_FILE_PATH)) { + notification_message(notifications, &sequence_error); + break; + } + if(!flipper_format_write_header_cstr(save, "Zeitraffer", 1)) { + notification_message(notifications, &sequence_error); + break; + } + if(!flipper_format_write_comment_cstr( + save, + "Zeitraffer app settings: № of frames, interval time, backlight type, Delay")) { + notification_message(notifications, &sequence_error); + break; + } + if(!flipper_format_write_int32(save, "Time", &Time, 1)) { + notification_message(notifications, &sequence_error); + break; + } + if(!flipper_format_write_int32(save, "Count", &Count, 1)) { + notification_message(notifications, &sequence_error); + break; + } + if(!flipper_format_write_int32(save, "Backlight", &Backlight, 1)) { + notification_message(notifications, &sequence_error); + break; + } + if(!flipper_format_write_int32(save, "Delay", &Delay, 1)) { + notification_message(notifications, &sequence_error); + break; + } + + } while(0); + + flipper_format_free(save); + + furi_record_close(RECORD_STORAGE); + + // Очищаем таймер + furi_timer_free(timer); + + // Специальная очистка памяти, занимаемой очередью + furi_message_queue_free(event_queue); + + // Чистим созданные объекты, связанные с интерфейсом + gui_remove_view_port(gui, view_port); + view_port_free(view_port); + furi_record_close(RECORD_GUI); + + // Очищаем нотификации + furi_record_close(RECORD_NOTIFICATION); + + return 0; +} diff --git a/Applications/Official/DEV_FW/source/timelapse/zeitraffer.png b/Applications/Official/DEV_FW/source/timelapse/zeitraffer.png new file mode 100644 index 000000000..3a42e406d Binary files /dev/null and b/Applications/Official/DEV_FW/source/timelapse/zeitraffer.png differ diff --git a/Applications/Official/DEV_FW/source/totp/.clang-format b/Applications/Official/DEV_FW/source/totp/.clang-format new file mode 100644 index 000000000..4b76f7fa4 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/.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/Official/DEV_FW/source/totp/LICENSE b/Applications/Official/DEV_FW/source/totp/LICENSE new file mode 100644 index 000000000..65504e7b1 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Alexander Kopachov + +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/Official/DEV_FW/source/totp/application.fam b/Applications/Official/DEV_FW/source/totp/application.fam new file mode 100644 index 000000000..fbd21ddf7 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/application.fam @@ -0,0 +1,37 @@ +App( + appid="totp", + name="Authenticator", + apptype=FlipperAppType.EXTERNAL, + entry_point="totp_app", + cdefines=["APP_TOTP"], + requires=[ + "gui", + "cli", + "dialogs", + "storage", + "input", + "notification" + ], + stack_size=2 * 1024, + order=20, + fap_category="Misc", + fap_icon_assets="images", + fap_icon="totp_10px.png", + fap_private_libs=[ + Lib( + name="base32", + ), + Lib( + name="list", + ), + Lib( + name="timezone_utils", + ), + Lib( + name="polyfills", + ), + Lib( + name="roll_value", + ), + ], +) diff --git a/Applications/Official/DEV_FW/source/totp/cli/cli.c b/Applications/Official/DEV_FW/source/totp/cli/cli.c new file mode 100644 index 000000000..e61c67206 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/cli/cli.c @@ -0,0 +1,76 @@ +// Original idea: https://github.com/br0ziliy + +#include "cli.h" +#include +#include "cli_helpers.h" +#include "commands/list/list.h" +#include "commands/add/add.h" +#include "commands/delete/delete.h" +#include "commands/timezone/timezone.h" +#include "commands/help/help.h" +#include "commands/move/move.h" +#include "commands/pin/pin.h" +#include "commands/notification/notification.h" + +static void totp_cli_print_unknown_command(const FuriString* unknown_command) { + TOTP_CLI_PRINTF( + "Command \"%s\" is unknown. Use \"" TOTP_CLI_COMMAND_HELP + "\" command to get list of available commands.", + furi_string_get_cstr(unknown_command)); +} + +static void totp_cli_handler(Cli* cli, FuriString* args, void* context) { + PluginState* plugin_state = (PluginState*)context; + + FuriString* cmd = furi_string_alloc(); + + args_read_string_and_trim(args, cmd); + + if(furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_HELP) == 0 || + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_HELP_ALT) == 0 || + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_HELP_ALT2) == 0 || furi_string_empty(cmd)) { + totp_cli_command_help_handle(); + } else if( + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_ADD) == 0 || + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_ADD_ALT) == 0 || + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_ADD_ALT2) == 0) { + totp_cli_command_add_handle(plugin_state, args, cli); + } else if( + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_LIST) == 0 || + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_LIST_ALT) == 0) { + totp_cli_command_list_handle(plugin_state, cli); + } else if( + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_DELETE) == 0 || + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_DELETE_ALT) == 0) { + totp_cli_command_delete_handle(plugin_state, args, cli); + } else if( + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_TIMEZONE) == 0 || + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_TIMEZONE_ALT) == 0) { + totp_cli_command_timezone_handle(plugin_state, args, cli); + } else if( + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_MOVE) == 0 || + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_MOVE_ALT) == 0) { + totp_cli_command_move_handle(plugin_state, args, cli); + } else if(furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_PIN) == 0) { + totp_cli_command_pin_handle(plugin_state, args, cli); + } else if(furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_NOTIFICATION) == 0) { + totp_cli_command_notification_handle(plugin_state, args, cli); + } else { + totp_cli_print_unknown_command(cmd); + } + + furi_string_free(cmd); +} + +void totp_cli_register_command_handler(PluginState* plugin_state) { + Cli* cli = furi_record_open(RECORD_CLI); + cli_add_command( + cli, TOTP_CLI_COMMAND_NAME, CliCommandFlagParallelSafe, totp_cli_handler, plugin_state); + furi_record_close(RECORD_CLI); +} + +void totp_cli_unregister_command_handler() { + Cli* cli = furi_record_open(RECORD_CLI); + cli_delete_command(cli, TOTP_CLI_COMMAND_NAME); + furi_record_close(RECORD_CLI); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/cli/cli.h b/Applications/Official/DEV_FW/source/totp/cli/cli.h new file mode 100644 index 000000000..3eb18172c --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/cli/cli.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include "../types/plugin_state.h" + +void totp_cli_register_command_handler(PluginState* plugin_state); +void totp_cli_unregister_command_handler(); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/cli/cli_helpers.c b/Applications/Official/DEV_FW/source/totp/cli/cli_helpers.c new file mode 100644 index 000000000..0bea6fd90 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/cli/cli_helpers.c @@ -0,0 +1,21 @@ +#include "cli_helpers.h" +#include + +bool totp_cli_ensure_authenticated(const PluginState* plugin_state, Cli* cli) { + if(plugin_state->current_scene == TotpSceneAuthentication) { + TOTP_CLI_PRINTF("Pleases enter PIN on your flipper device\r\n"); + + while(plugin_state->current_scene == TotpSceneAuthentication && + !cli_cmd_interrupt_received(cli)) { + furi_delay_ms(100); + } + + TOTP_CLI_DELETE_LAST_LINE(); + + if(plugin_state->current_scene == TotpSceneAuthentication) { //-V547 + return false; + } + } + + return true; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/cli/cli_helpers.h b/Applications/Official/DEV_FW/source/totp/cli/cli_helpers.h new file mode 100644 index 000000000..ae6fe6e0c --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/cli/cli_helpers.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include "../types/plugin_state.h" + +#define TOTP_CLI_COMMAND_NAME "totp" + +#define DOCOPT_ARGUMENT(arg) "<" arg ">" +#define DOCOPT_MULTIPLE(arg) arg "..." +#define DOCOPT_OPTIONAL(param) "[" param "]" +#define DOCOPT_REQUIRED(param) "(" param ")" +#define DOCOPT_OPTION(option, value) option " " value +#define DOCOPT_SWITCH(option) option +#define DOCOPT_OPTIONS "[options]" +#define DOCOPT_DEFAULT(val) "[default: " val "]" + +#define TOTP_CLI_PRINTF(format, ...) \ + do { \ + _Pragma(STRINGIFY(GCC diagnostic push)) \ + _Pragma(STRINGIFY(GCC diagnostic ignored "-Wdouble-promotion")) \ + printf(format, ##__VA_ARGS__); \ + _Pragma(STRINGIFY(GCC diagnostic pop)) \ + } while(false) + +#define TOTP_CLI_DELETE_LAST_LINE() \ + TOTP_CLI_PRINTF("\033[A\33[2K\r"); \ + fflush(stdout) + +#define TOTP_CLI_DELETE_CURRENT_LINE() \ + TOTP_CLI_PRINTF("\33[2K\r"); \ + fflush(stdout) + +#define TOTP_CLI_DELETE_LAST_CHAR() \ + TOTP_CLI_PRINTF("\b \b"); \ + fflush(stdout) + +#define TOTP_CLI_PRINT_INVALID_ARGUMENTS() \ + TOTP_CLI_PRINTF( \ + "Invalid command arguments. use \"help\" command to get list of available commands") + +#define TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE() \ + TOTP_CLI_PRINTF("An error has occurred during updating config file\r\n") + +/** + * @brief Checks whether user is authenticated and entered correct PIN. + * If user is not authenticated it prompts user to enter correct PIN to authenticate. + * @param plugin_state application state + * @param cli reference to the firmware CLI subsystem + * @return \c true if user is already authenticated or successfully authenticated; \c false otherwise + */ +bool totp_cli_ensure_authenticated(const PluginState* plugin_state, Cli* cli); diff --git a/Applications/Official/DEV_FW/source/totp/cli/commands/add/add.c b/Applications/Official/DEV_FW/source/totp/cli/commands/add/add.c new file mode 100644 index 000000000..e037546e2 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/cli/commands/add/add.c @@ -0,0 +1,218 @@ +#include "add.h" +#include +#include +#include "../../../lib/list/list.h" +#include "../../../types/token_info.h" +#include "../../../services/config/config.h" +#include "../../../services/convert/convert.h" +#include "../../cli_helpers.h" +#include "../../../ui/scene_director.h" + +#define TOTP_CLI_COMMAND_ADD_ARG_NAME "name" +#define TOTP_CLI_COMMAND_ADD_ARG_ALGO "algo" +#define TOTP_CLI_COMMAND_ADD_ARG_ALGO_PREFIX "-a" +#define TOTP_CLI_COMMAND_ADD_ARG_DIGITS "digits" +#define TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX "-d" +#define TOTP_CLI_COMMAND_ADD_ARG_UNSECURE_PREFIX "-u" + +static bool token_info_set_algo_from_str(TokenInfo* token_info, const FuriString* str) { + if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME) == 0) { + token_info->algo = SHA1; + return true; + } + + if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA256_NAME) == 0) { + token_info->algo = SHA256; + return true; + } + + if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME) == 0) { + token_info->algo = SHA512; + return true; + } + + return false; +} + +void totp_cli_command_add_docopt_commands() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_ADD ", " TOTP_CLI_COMMAND_ADD_ALT + ", " TOTP_CLI_COMMAND_ADD_ALT2 " Add new token\r\n"); +} + +void totp_cli_command_add_docopt_usage() { + TOTP_CLI_PRINTF( + " " TOTP_CLI_COMMAND_NAME + " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_ADD " | " TOTP_CLI_COMMAND_ADD_ALT " | " TOTP_CLI_COMMAND_ADD_ALT2) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ADD_ARG_NAME) " " DOCOPT_OPTIONAL( + DOCOPT_OPTION( + TOTP_CLI_COMMAND_ADD_ARG_ALGO_PREFIX, + DOCOPT_ARGUMENT( + TOTP_CLI_COMMAND_ADD_ARG_ALGO))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ADD_ARG_DIGITS))) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_ADD_ARG_UNSECURE_PREFIX)) "\r\n"); +} + +void totp_cli_command_add_docopt_arguments() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_ADD_ARG_NAME " Token name\r\n"); +} + +void totp_cli_command_add_docopt_options() { + TOTP_CLI_PRINTF(" " DOCOPT_OPTION( + TOTP_CLI_COMMAND_ADD_ARG_ALGO_PREFIX, + DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ADD_ARG_ALGO)) " Token hashing algorithm.\r\n"); + TOTP_CLI_PRINTF( + " Could be one of: sha1, sha256, sha512 " DOCOPT_DEFAULT("sha1") "\r\n"); + cli_nl(); + TOTP_CLI_PRINTF(" " DOCOPT_OPTION( + TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX, + DOCOPT_ARGUMENT( + TOTP_CLI_COMMAND_ADD_ARG_DIGITS)) " Number of digits to generate, one of: 6, 8 " DOCOPT_DEFAULT("6") "\r\n"); + TOTP_CLI_PRINTF(" " DOCOPT_SWITCH( + TOTP_CLI_COMMAND_ADD_ARG_UNSECURE_PREFIX) " Show console user input as-is without masking\r\n"); +} + +static void furi_string_secure_free(FuriString* str) { + for(long i = furi_string_size(str) - 1; i >= 0; i--) { + furi_string_set_char(str, i, '\0'); + } + + furi_string_free(str); +} + +static bool totp_cli_read_secret(Cli* cli, FuriString* out_str, bool mask_user_input) { + uint8_t c; + while(cli_read(cli, &c, 1) == 1) { + if(c == CliSymbolAsciiEsc) { + // Some keys generating escape-sequences + // We need to ignore them as we care about alpha-numerics only + uint8_t c2; + cli_read_timeout(cli, &c2, 1, 0); + cli_read_timeout(cli, &c2, 1, 0); + } else if(c == CliSymbolAsciiETX) { + TOTP_CLI_DELETE_CURRENT_LINE(); + TOTP_CLI_PRINTF("Cancelled by user\r\n"); + return false; + } else if((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { + if(mask_user_input) { + putc('*', stdout); + } else { + putc(c, stdout); + } + fflush(stdout); + furi_string_push_back(out_str, c); + } else if(c == CliSymbolAsciiBackspace || c == CliSymbolAsciiDel) { + size_t out_str_size = furi_string_size(out_str); + if(out_str_size > 0) { + TOTP_CLI_DELETE_LAST_CHAR(); + furi_string_left(out_str, out_str_size - 1); + } + } else if(c == CliSymbolAsciiCR) { + cli_nl(); + break; + } + } + + TOTP_CLI_DELETE_LAST_LINE(); + return true; +} + +void totp_cli_command_add_handle(PluginState* plugin_state, FuriString* args, Cli* cli) { + FuriString* temp_str = furi_string_alloc(); + TokenInfo* token_info = token_info_alloc(); + + // Reading token name + if(!args_read_probably_quoted_string_and_trim(args, temp_str)) { + TOTP_CLI_PRINT_INVALID_ARGUMENTS(); + furi_string_free(temp_str); + token_info_free(token_info); + return; + } + + size_t temp_cstr_len = furi_string_size(temp_str); + token_info->name = malloc(temp_cstr_len + 1); + furi_check(token_info->name != NULL); + strlcpy(token_info->name, furi_string_get_cstr(temp_str), temp_cstr_len + 1); + + // Read optional arguments + bool mask_user_input = true; + while(args_read_string_and_trim(args, temp_str)) { + bool parsed = false; + if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_ADD_ARG_ALGO_PREFIX) == 0) { + if(!args_read_string_and_trim(args, temp_str)) { + TOTP_CLI_PRINTF("Missed value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_ALGO_PREFIX + "\"\r\n"); + } else if(!token_info_set_algo_from_str(token_info, temp_str)) { + TOTP_CLI_PRINTF( + "\"%s\" is incorrect value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_ALGO_PREFIX + "\"\r\n", + furi_string_get_cstr(temp_str)); + } else { + parsed = true; + } + } else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX) == 0) { + if(!args_read_string_and_trim(args, temp_str)) { + TOTP_CLI_PRINTF( + "Missed value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX + "\"\r\n"); + } else if(!token_info_set_digits_from_int( + token_info, CONVERT_CHAR_TO_DIGIT(furi_string_get_char(temp_str, 0)))) { + TOTP_CLI_PRINTF( + "\"%s\" is incorrect value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX + "\"\r\n", + furi_string_get_cstr(temp_str)); + } else { + parsed = true; + } + } else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_ADD_ARG_UNSECURE_PREFIX) == 0) { + mask_user_input = false; + parsed = true; + } else { + TOTP_CLI_PRINTF("Unknown argument \"%s\"\r\n", furi_string_get_cstr(temp_str)); + } + + if(!parsed) { + TOTP_CLI_PRINT_INVALID_ARGUMENTS(); + furi_string_free(temp_str); + token_info_free(token_info); + return; + } + } + + // Reading token secret + furi_string_reset(temp_str); + TOTP_CLI_PRINTF("Enter token secret and confirm with [ENTER]\r\n"); + if(!totp_cli_read_secret(cli, temp_str, mask_user_input) || + !totp_cli_ensure_authenticated(plugin_state, cli)) { + furi_string_secure_free(temp_str); + token_info_free(token_info); + return; + } + + if(!token_info_set_secret( + token_info, + furi_string_get_cstr(temp_str), + furi_string_size(temp_str), + plugin_state->iv)) { + TOTP_CLI_PRINTF("Token secret seems to be invalid and can not be parsed\r\n"); + furi_string_secure_free(temp_str); + token_info_free(token_info); + return; + } + + furi_string_secure_free(temp_str); + + bool load_generate_token_scene = false; + if(plugin_state->current_scene == TotpSceneGenerateToken) { + totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL); + load_generate_token_scene = true; + } + + TOTP_LIST_INIT_OR_ADD(plugin_state->tokens_list, token_info, furi_check); + plugin_state->tokens_count++; + if(totp_config_file_save_new_token(token_info) == TotpConfigFileUpdateSuccess) { + TOTP_CLI_PRINTF("Token \"%s\" has been successfully added\r\n", token_info->name); + } else { + TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE(); + } + + if(load_generate_token_scene) { + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL); + } +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/cli/commands/add/add.h b/Applications/Official/DEV_FW/source/totp/cli/commands/add/add.h new file mode 100644 index 000000000..001dc9e80 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/cli/commands/add/add.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include "../../../types/plugin_state.h" + +#define TOTP_CLI_COMMAND_ADD "add" +#define TOTP_CLI_COMMAND_ADD_ALT "mk" +#define TOTP_CLI_COMMAND_ADD_ALT2 "new" + +void totp_cli_command_add_handle(PluginState* plugin_state, FuriString* args, Cli* cli); +void totp_cli_command_add_docopt_commands(); +void totp_cli_command_add_docopt_usage(); +void totp_cli_command_add_docopt_arguments(); +void totp_cli_command_add_docopt_options(); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/cli/commands/delete/delete.c b/Applications/Official/DEV_FW/source/totp/cli/commands/delete/delete.c new file mode 100644 index 000000000..046341693 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/cli/commands/delete/delete.c @@ -0,0 +1,110 @@ +#include "delete.h" + +#include +#include +#include +#include "../../../lib/list/list.h" +#include "../../../services/config/config.h" +#include "../../cli_helpers.h" +#include "../../../ui/scene_director.h" + +#define TOTP_CLI_COMMAND_DELETE_ARG_INDEX "index" +#define TOTP_CLI_COMMAND_DELETE_ARG_FORCE_SUFFIX "-f" + +void totp_cli_command_delete_docopt_commands() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_DELETE ", " TOTP_CLI_COMMAND_DELETE_ALT + " Delete existing token\r\n"); +} + +void totp_cli_command_delete_docopt_usage() { + TOTP_CLI_PRINTF( + " " TOTP_CLI_COMMAND_NAME + " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_DELETE " | " TOTP_CLI_COMMAND_DELETE_ALT) " " DOCOPT_ARGUMENT( + TOTP_CLI_COMMAND_DELETE_ARG_INDEX) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_DELETE_ARG_FORCE_SUFFIX)) "\r\n"); +} + +void totp_cli_command_delete_docopt_arguments() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_DELETE_ARG_INDEX " Token index in the list\r\n"); +} + +void totp_cli_command_delete_docopt_options() { + TOTP_CLI_PRINTF(" " DOCOPT_SWITCH( + TOTP_CLI_COMMAND_DELETE_ARG_FORCE_SUFFIX) " Force command to do not ask user for interactive confirmation\r\n"); +} + +void totp_cli_command_delete_handle(PluginState* plugin_state, FuriString* args, Cli* cli) { + int token_number; + if(!args_read_int_and_trim(args, &token_number) || token_number <= 0 || + token_number > plugin_state->tokens_count) { + TOTP_CLI_PRINT_INVALID_ARGUMENTS(); + return; + } + + FuriString* temp_str = furi_string_alloc(); + bool confirm_needed = true; + if(args_read_string_and_trim(args, temp_str)) { + if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_DELETE_ARG_FORCE_SUFFIX) == 0) { + confirm_needed = false; + } else { + TOTP_CLI_PRINTF("Unknown argument \"%s\"\r\n", furi_string_get_cstr(temp_str)); + TOTP_CLI_PRINT_INVALID_ARGUMENTS(); + furi_string_free(temp_str); + return; + } + } + furi_string_free(temp_str); + + if(!totp_cli_ensure_authenticated(plugin_state, cli)) { + return; + } + + ListNode* list_node = list_element_at(plugin_state->tokens_list, token_number - 1); + + TokenInfo* token_info = list_node->data; + + bool confirmed = !confirm_needed; + if(confirm_needed) { + TOTP_CLI_PRINTF("WARNING!\r\n"); + TOTP_CLI_PRINTF( + "TOKEN \"%s\" WILL BE PERMANENTLY DELETED WITHOUT ABILITY TO RECOVER IT.\r\n", + token_info->name); + TOTP_CLI_PRINTF("Confirm? [y/n]\r\n"); + fflush(stdout); + char user_pick; + do { + user_pick = tolower(cli_getc(cli)); + } while(user_pick != 'y' && user_pick != 'n' && user_pick != CliSymbolAsciiCR && + user_pick != CliSymbolAsciiETX && user_pick != CliSymbolAsciiEsc); + + confirmed = user_pick == 'y' || user_pick == CliSymbolAsciiCR; + } + + if(confirmed) { + if(!totp_cli_ensure_authenticated(plugin_state, cli)) { + return; + } + + bool activate_generate_token_scene = false; + if(plugin_state->current_scene != TotpSceneAuthentication) { + totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL); + activate_generate_token_scene = true; + } + + plugin_state->tokens_list = list_remove(plugin_state->tokens_list, list_node); + plugin_state->tokens_count--; + + if(totp_full_save_config_file(plugin_state) == TotpConfigFileUpdateSuccess) { + TOTP_CLI_PRINTF("Token \"%s\" has been successfully deleted\r\n", token_info->name); + } else { + TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE(); + } + + token_info_free(token_info); + + if(activate_generate_token_scene) { + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL); + } + } else { + TOTP_CLI_PRINTF("User not confirmed\r\n"); + } +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/cli/commands/delete/delete.h b/Applications/Official/DEV_FW/source/totp/cli/commands/delete/delete.h new file mode 100644 index 000000000..deb7f74fe --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/cli/commands/delete/delete.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include "../../../types/plugin_state.h" + +#define TOTP_CLI_COMMAND_DELETE "delete" +#define TOTP_CLI_COMMAND_DELETE_ALT "rm" + +void totp_cli_command_delete_handle(PluginState* plugin_state, FuriString* args, Cli* cli); +void totp_cli_command_delete_docopt_commands(); +void totp_cli_command_delete_docopt_usage(); +void totp_cli_command_delete_docopt_arguments(); +void totp_cli_command_delete_docopt_options(); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/cli/commands/help/help.c b/Applications/Official/DEV_FW/source/totp/cli/commands/help/help.c new file mode 100644 index 000000000..419964880 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/cli/commands/help/help.c @@ -0,0 +1,53 @@ +#include "help.h" +#include "../../cli_helpers.h" +#include "../add/add.h" +#include "../delete/delete.h" +#include "../list/list.h" +#include "../timezone/timezone.h" +#include "../move/move.h" +#include "../pin/pin.h" +#include "../notification/notification.h" + +void totp_cli_command_help_docopt_commands() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_HELP ", " TOTP_CLI_COMMAND_HELP_ALT + ", " TOTP_CLI_COMMAND_HELP_ALT2 " Show command usage help\r\n"); +} + +void totp_cli_command_help_docopt_usage() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_NAME " " DOCOPT_REQUIRED( + TOTP_CLI_COMMAND_HELP " | " TOTP_CLI_COMMAND_HELP_ALT + " | " TOTP_CLI_COMMAND_HELP_ALT2) "\r\n"); +} + +void totp_cli_command_help_handle() { + TOTP_CLI_PRINTF("Usage:\r\n"); + totp_cli_command_help_docopt_usage(); + totp_cli_command_list_docopt_usage(); + totp_cli_command_add_docopt_usage(); + totp_cli_command_delete_docopt_usage(); + totp_cli_command_timezone_docopt_usage(); + totp_cli_command_move_docopt_usage(); + totp_cli_command_pin_docopt_usage(); + totp_cli_command_notification_docopt_usage(); + cli_nl(); + TOTP_CLI_PRINTF("Commands:\r\n"); + totp_cli_command_help_docopt_commands(); + totp_cli_command_list_docopt_commands(); + totp_cli_command_add_docopt_commands(); + totp_cli_command_delete_docopt_commands(); + totp_cli_command_timezone_docopt_commands(); + totp_cli_command_move_docopt_commands(); + totp_cli_command_pin_docopt_commands(); + totp_cli_command_notification_docopt_commands(); + cli_nl(); + TOTP_CLI_PRINTF("Arguments:\r\n"); + totp_cli_command_add_docopt_arguments(); + totp_cli_command_delete_docopt_arguments(); + totp_cli_command_timezone_docopt_arguments(); + totp_cli_command_notification_docopt_arguments(); + cli_nl(); + TOTP_CLI_PRINTF("Options:\r\n"); + totp_cli_command_add_docopt_options(); + totp_cli_command_delete_docopt_options(); + totp_cli_command_move_docopt_options(); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/cli/commands/help/help.h b/Applications/Official/DEV_FW/source/totp/cli/commands/help/help.h new file mode 100644 index 000000000..4268b8bc5 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/cli/commands/help/help.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#define TOTP_CLI_COMMAND_HELP "help" +#define TOTP_CLI_COMMAND_HELP_ALT "h" +#define TOTP_CLI_COMMAND_HELP_ALT2 "?" + +void totp_cli_command_help_handle(); +void totp_cli_command_help_docopt_commands(); +void totp_cli_command_help_docopt_usage(); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/cli/commands/list/list.c b/Applications/Official/DEV_FW/source/totp/cli/commands/list/list.c new file mode 100644 index 000000000..739a0de40 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/cli/commands/list/list.c @@ -0,0 +1,58 @@ +#include "list.h" +#include +#include "../../../lib/list/list.h" +#include "../../../types/token_info.h" +#include "../../../services/config/constants.h" +#include "../../cli_helpers.h" + +static char* get_algo_as_cstr(TokenHashAlgo algo) { + switch(algo) { + case SHA1: + return TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME; + case SHA256: + return TOTP_CONFIG_TOKEN_ALGO_SHA256_NAME; + case SHA512: + return TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME; + default: + break; + } + + return "UNKNOWN"; +} + +void totp_cli_command_list_docopt_commands() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_LIST ", " TOTP_CLI_COMMAND_LIST_ALT + " List all available tokens\r\n"); +} + +void totp_cli_command_list_docopt_usage() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_NAME " " DOCOPT_REQUIRED( + TOTP_CLI_COMMAND_LIST " | " TOTP_CLI_COMMAND_LIST_ALT) "\r\n"); +} + +void totp_cli_command_list_handle(PluginState* plugin_state, Cli* cli) { + if(!totp_cli_ensure_authenticated(plugin_state, cli)) { + return; + } + + if(plugin_state->tokens_list == NULL) { + TOTP_CLI_PRINTF("There are no tokens"); + return; + } + + TOTP_CLI_PRINTF("+-----+-----------------------------+--------+--------+\r\n"); + TOTP_CLI_PRINTF("| %-*s | %-*s | %-*s | %-s |\r\n", 3, "#", 27, "Name", 6, "Algo", "Digits"); + TOTP_CLI_PRINTF("+-----+-----------------------------+--------+--------+\r\n"); + uint16_t index = 1; + TOTP_LIST_FOREACH(plugin_state->tokens_list, node, { + TokenInfo* token_info = (TokenInfo*)node->data; + TOTP_CLI_PRINTF( + "| %-3" PRIu16 " | %-27.27s | %-6s | %-6" PRIu8 " |\r\n", + index, + token_info->name, + get_algo_as_cstr(token_info->algo), + token_info->digits); + index++; + }); + TOTP_CLI_PRINTF("+-----+-----------------------------+--------+--------+\r\n"); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/cli/commands/list/list.h b/Applications/Official/DEV_FW/source/totp/cli/commands/list/list.h new file mode 100644 index 000000000..7d6de5874 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/cli/commands/list/list.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include "../../../types/plugin_state.h" + +#define TOTP_CLI_COMMAND_LIST "list" +#define TOTP_CLI_COMMAND_LIST_ALT "ls" + +void totp_cli_command_list_handle(PluginState* plugin_state, Cli* cli); +void totp_cli_command_list_docopt_commands(); +void totp_cli_command_list_docopt_usage(); diff --git a/Applications/Official/DEV_FW/source/totp/cli/commands/move/move.c b/Applications/Official/DEV_FW/source/totp/cli/commands/move/move.c new file mode 100644 index 000000000..9d47134e5 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/cli/commands/move/move.c @@ -0,0 +1,164 @@ +#include "move.h" + +#include +#include +#include "../../../lib/list/list.h" +#include "../../../types/token_info.h" +#include "../../../services/config/config.h" +#include "../../cli_helpers.h" +#include "../../../ui/scene_director.h" + +#define TOTP_CLI_COMMAND_MOVE_ARG_INDEX "index" + +#define TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME "name" +#define TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME_PREFIX "-n" + +#define TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX "index" +#define TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX_PREFIX "-i" + +void totp_cli_command_move_docopt_commands() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_MOVE ", " TOTP_CLI_COMMAND_MOVE_ALT + " Move\\rename token\r\n"); +} + +void totp_cli_command_move_docopt_usage() { + TOTP_CLI_PRINTF( + " " TOTP_CLI_COMMAND_NAME + " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_MOVE " | " TOTP_CLI_COMMAND_MOVE_ALT) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_MOVE_ARG_INDEX) " " DOCOPT_OPTIONAL( + DOCOPT_OPTION( + TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME_PREFIX, + DOCOPT_ARGUMENT( + TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX))) "\r\n"); +} + +void totp_cli_command_move_docopt_options() { + TOTP_CLI_PRINTF(" " DOCOPT_OPTION( + TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME_PREFIX, + DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME)) " New token name.\r\n"); + TOTP_CLI_PRINTF(" " DOCOPT_OPTION( + TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX_PREFIX, + DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX)) " New token index.\r\n"); +} + +void totp_cli_command_move_handle(PluginState* plugin_state, FuriString* args, Cli* cli) { + int token_index; + if(!args_read_int_and_trim(args, &token_index)) { + TOTP_CLI_PRINT_INVALID_ARGUMENTS(); + return; + } + + if(!totp_cli_ensure_authenticated(plugin_state, cli)) { + return; + } + + if(token_index < 1 || token_index > plugin_state->tokens_count) { + TOTP_CLI_PRINT_INVALID_ARGUMENTS(); + return; + } + + FuriString* temp_str = furi_string_alloc(); + + char* new_token_name = NULL; + int new_token_index = 0; + + while(args_read_string_and_trim(args, temp_str)) { + bool parsed = false; + if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME_PREFIX) == 0) { + if(!args_read_string_and_trim(args, temp_str)) { + TOTP_CLI_PRINTF( + "Missed value for argument \"" TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME_PREFIX + "\"\r\n"); + } else { + if(new_token_name != NULL) { + free(new_token_name); + } + + new_token_name = malloc(furi_string_size(temp_str) + 1); + if(new_token_name == NULL) { + furi_string_free(temp_str); + return; + } + + strlcpy( + new_token_name, + furi_string_get_cstr(temp_str), + furi_string_size(temp_str) + 1); + parsed = true; + } + } else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX_PREFIX) == 0) { + if(!args_read_int_and_trim(args, &new_token_index)) { + TOTP_CLI_PRINTF( + "Missed value for argument \"" TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX_PREFIX + "\"\r\n"); + } else if(new_token_index < 1 || new_token_index > plugin_state->tokens_count) { + TOTP_CLI_PRINTF( + "\"%" PRId16 + "\" is incorrect value for argument \"" TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX_PREFIX + "\"\r\n", + new_token_index); + } else { + parsed = true; + } + } else { + TOTP_CLI_PRINTF("Unknown argument \"%s\"\r\n", furi_string_get_cstr(temp_str)); + } + + if(!parsed) { + TOTP_CLI_PRINT_INVALID_ARGUMENTS(); + furi_string_free(temp_str); + if(new_token_name != NULL) { + free(new_token_name); + } + return; + } + } + + if(!totp_cli_ensure_authenticated(plugin_state, cli)) { + furi_string_free(temp_str); + if(new_token_name != NULL) { + free(new_token_name); + } + return; + } + + bool activate_generate_token_scene = false; + if(plugin_state->current_scene != TotpSceneAuthentication) { + totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL); + activate_generate_token_scene = true; + } + + bool token_updated = false; + TokenInfo* token_info = NULL; + if(new_token_index > 0) { + plugin_state->tokens_list = + list_remove_at(plugin_state->tokens_list, token_index - 1, (void**)&token_info); + furi_check(token_info != NULL); + plugin_state->tokens_list = + list_insert_at(plugin_state->tokens_list, new_token_index - 1, token_info); + token_updated = true; + } else { + token_info = list_element_at(plugin_state->tokens_list, token_index - 1)->data; + } + + if(new_token_name != NULL) { + free(token_info->name); + token_info->name = new_token_name; + token_updated = true; + } + + if(token_updated) { + if(totp_full_save_config_file(plugin_state) == TotpConfigFileUpdateSuccess) { + TOTP_CLI_PRINTF("Token \"%s\" has been successfully updated\r\n", token_info->name); + } else { + TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE(); + } + } else { + TOTP_CLI_PRINT_INVALID_ARGUMENTS(); + } + + if(activate_generate_token_scene) { + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL); + } + + furi_string_free(temp_str); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/cli/commands/move/move.h b/Applications/Official/DEV_FW/source/totp/cli/commands/move/move.h new file mode 100644 index 000000000..9eaad5319 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/cli/commands/move/move.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include "../../../types/plugin_state.h" + +#define TOTP_CLI_COMMAND_MOVE "move" +#define TOTP_CLI_COMMAND_MOVE_ALT "mv" + +void totp_cli_command_move_handle(PluginState* plugin_state, FuriString* args, Cli* cli); +void totp_cli_command_move_docopt_commands(); +void totp_cli_command_move_docopt_usage(); +void totp_cli_command_move_docopt_options(); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/cli/commands/notification/notification.c b/Applications/Official/DEV_FW/source/totp/cli/commands/notification/notification.c new file mode 100644 index 000000000..5b98a857b --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/cli/commands/notification/notification.c @@ -0,0 +1,109 @@ +#include "notification.h" +#include +#include "../../../services/config/config.h" +#include "../../../ui/scene_director.h" +#include "../../cli_helpers.h" + +#define TOTP_CLI_COMMAND_NOTIFICATION_ARG_METHOD "method" +#define TOTP_CLI_COMMAND_NOTIFICATION_METHOD_NONE "none" +#define TOTP_CLI_COMMAND_NOTIFICATION_METHOD_SOUND "sound" +#define TOTP_CLI_COMMAND_NOTIFICATION_METHOD_VIBRO "vibro" + +void totp_cli_command_notification_docopt_commands() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_NOTIFICATION + " Get or set notification method\r\n"); +} + +void totp_cli_command_notification_docopt_usage() { + TOTP_CLI_PRINTF( + " " TOTP_CLI_COMMAND_NAME " " TOTP_CLI_COMMAND_NOTIFICATION " " DOCOPT_OPTIONAL( + DOCOPT_MULTIPLE(DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_NOTIFICATION_ARG_METHOD))) "\r\n"); +} + +void totp_cli_command_notification_docopt_arguments() { + TOTP_CLI_PRINTF( + " " TOTP_CLI_COMMAND_NOTIFICATION_ARG_METHOD + " Notification method to be set. Must be one of [" TOTP_CLI_COMMAND_NOTIFICATION_METHOD_NONE + ", " TOTP_CLI_COMMAND_NOTIFICATION_METHOD_SOUND + ", " TOTP_CLI_COMMAND_NOTIFICATION_METHOD_VIBRO "]\r\n"); +} + +static void totp_cli_command_notification_print_method(NotificationMethod method) { + bool has_previous_method = false; + if(method & NotificationMethodSound) { + TOTP_CLI_PRINTF("\"" TOTP_CLI_COMMAND_NOTIFICATION_METHOD_SOUND "\""); + has_previous_method = true; + } + if(method & NotificationMethodVibro) { + if(has_previous_method) { + TOTP_CLI_PRINTF(" and "); + } + + TOTP_CLI_PRINTF("\"" TOTP_CLI_COMMAND_NOTIFICATION_METHOD_VIBRO "\""); + } + if(method == NotificationMethodNone) { + TOTP_CLI_PRINTF("\"" TOTP_CLI_COMMAND_NOTIFICATION_METHOD_NONE "\""); + } +} + +void totp_cli_command_notification_handle(PluginState* plugin_state, FuriString* args, Cli* cli) { + if(!totp_cli_ensure_authenticated(plugin_state, cli)) { + return; + } + + FuriString* temp_str = furi_string_alloc(); + bool new_method_provided = false; + NotificationMethod new_method = NotificationMethodNone; + bool args_valid = true; + while(args_read_string_and_trim(args, temp_str)) { + if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_NOTIFICATION_METHOD_NONE) == 0) { + new_method_provided = true; + new_method = NotificationMethodNone; + } else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_NOTIFICATION_METHOD_SOUND) == 0) { + new_method_provided = true; + new_method |= NotificationMethodSound; + } else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_NOTIFICATION_METHOD_VIBRO) == 0) { + new_method_provided = true; + new_method |= NotificationMethodVibro; + } else { + args_valid = false; + break; + } + } + + do { + if(!args_valid) { + TOTP_CLI_PRINT_INVALID_ARGUMENTS(); + break; + } + + if(new_method_provided) { + Scene previous_scene = TotpSceneNone; + if(plugin_state->current_scene == TotpSceneGenerateToken || + plugin_state->current_scene == TotpSceneAppSettings) { + previous_scene = plugin_state->current_scene; + totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL); + } + + plugin_state->notification_method = new_method; + if(totp_config_file_update_notification_method(new_method) == + TotpConfigFileUpdateSuccess) { + TOTP_CLI_PRINTF("Notification method is set to "); + totp_cli_command_notification_print_method(new_method); + cli_nl(); + } else { + TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE(); + } + + if(previous_scene != TotpSceneNone) { + totp_scene_director_activate_scene(plugin_state, previous_scene, NULL); + } + } else { + TOTP_CLI_PRINTF("Current notification method is "); + totp_cli_command_notification_print_method(plugin_state->notification_method); + cli_nl(); + } + } while(false); + + furi_string_free(temp_str); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/cli/commands/notification/notification.h b/Applications/Official/DEV_FW/source/totp/cli/commands/notification/notification.h new file mode 100644 index 000000000..c349f9620 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/cli/commands/notification/notification.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include "../../../types/plugin_state.h" + +#define TOTP_CLI_COMMAND_NOTIFICATION "notify" + +void totp_cli_command_notification_handle(PluginState* plugin_state, FuriString* args, Cli* cli); +void totp_cli_command_notification_docopt_commands(); +void totp_cli_command_notification_docopt_usage(); +void totp_cli_command_notification_docopt_arguments(); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/cli/commands/pin/pin.c b/Applications/Official/DEV_FW/source/totp/cli/commands/pin/pin.c new file mode 100644 index 000000000..198324e27 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/cli/commands/pin/pin.c @@ -0,0 +1,181 @@ +#include "pin.h" + +#include +#include +#include "../../../types/token_info.h" +#include "../../../types/user_pin_codes.h" +#include "../../../services/config/config.h" +#include "../../cli_helpers.h" +#include "../../../lib/polyfills/memset_s.h" +#include "../../../services/crypto/crypto.h" +#include "../../../ui/scene_director.h" + +#define TOTP_CLI_COMMAND_PIN_COMMAND_SET "set" +#define TOTP_CLI_COMMAND_PIN_COMMAND_REMOVE "remove" + +void totp_cli_command_pin_docopt_commands() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_PIN " Set\\change\\remove PIN\r\n"); +} + +void totp_cli_command_pin_docopt_usage() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_NAME " " TOTP_CLI_COMMAND_PIN " " DOCOPT_REQUIRED( + TOTP_CLI_COMMAND_PIN_COMMAND_SET " | " TOTP_CLI_COMMAND_PIN_COMMAND_REMOVE) "\r\n"); +} + +static inline uint8_t totp_cli_key_to_pin_code(uint8_t key) { + uint8_t code = 0; + switch(key) { + case 0x44: // left + code = PinCodeArrowLeft; + break; + case 0x41: // up + code = PinCodeArrowUp; + break; + case 0x43: // right + code = PinCodeArrowRight; + break; + case 0x42: // down + code = PinCodeArrowDown; + break; + default: + break; + } + + return code; +} + +static bool totp_cli_read_pin(Cli* cli, uint8_t* pin, uint8_t* pin_length) { + TOTP_CLI_PRINTF("Enter new PIN (use arrow keys on your keyboard): "); + fflush(stdout); + uint8_t c; + *pin_length = 0; + while(cli_read(cli, &c, 1) == 1) { + if(c == CliSymbolAsciiEsc) { + uint8_t c2; + uint8_t c3; + if(cli_read_timeout(cli, &c2, 1, 0) == 1 && cli_read_timeout(cli, &c3, 1, 0) == 1 && + c2 == 0x5b) { + uint8_t code = totp_cli_key_to_pin_code(c3); + if(code > 0) { + pin[*pin_length] = code; + *pin_length = *pin_length + 1; + putc('*', stdout); + fflush(stdout); + } + } + } else if(c == CliSymbolAsciiETX) { + TOTP_CLI_DELETE_CURRENT_LINE(); + TOTP_CLI_PRINTF("Cancelled by user\r\n"); + return false; + } else if(c == CliSymbolAsciiBackspace || c == CliSymbolAsciiDel) { + if(*pin_length > 0) { + *pin_length = *pin_length - 1; + pin[*pin_length] = 0; + TOTP_CLI_DELETE_LAST_CHAR(); + } + } else if(c == CliSymbolAsciiCR) { + cli_nl(); + break; + } + } + + TOTP_CLI_DELETE_LAST_LINE(); + return true; +} + +void totp_cli_command_pin_handle(PluginState* plugin_state, FuriString* args, Cli* cli) { + UNUSED(plugin_state); + FuriString* temp_str = furi_string_alloc(); + + bool do_change = false; + bool do_remove = false; + UNUSED(do_remove); + if(args_read_string_and_trim(args, temp_str)) { + if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_PIN_COMMAND_SET) == 0) { + do_change = true; + } else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_PIN_COMMAND_REMOVE) == 0) { + do_remove = true; + } else { + TOTP_CLI_PRINT_INVALID_ARGUMENTS(); + } + } else { + TOTP_CLI_PRINT_INVALID_ARGUMENTS(); + } + + if((do_change || do_remove) && totp_cli_ensure_authenticated(plugin_state, cli)) { + bool load_generate_token_scene = false; + do { + uint8_t old_iv[TOTP_IV_SIZE]; + memcpy(&old_iv[0], &plugin_state->iv[0], TOTP_IV_SIZE); + uint8_t new_pin[TOTP_IV_SIZE]; + uint8_t new_pin_length = 0; + if(do_change) { + if(!totp_cli_read_pin(cli, &new_pin[0], &new_pin_length) || + !totp_cli_ensure_authenticated(plugin_state, cli)) { + memset_s(&new_pin[0], TOTP_IV_SIZE, 0, TOTP_IV_SIZE); + break; + } + } else if(do_remove) { + new_pin_length = 0; + memset(&new_pin[0], 0, TOTP_IV_SIZE); + } + + if(plugin_state->current_scene == TotpSceneGenerateToken) { + totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL); + load_generate_token_scene = true; + } + + TOTP_CLI_PRINTF("Encrypting, please wait...\r\n"); + + memset(&plugin_state->iv[0], 0, TOTP_IV_SIZE); + memset(&plugin_state->base_iv[0], 0, TOTP_IV_SIZE); + if(plugin_state->crypto_verify_data != NULL) { + free(plugin_state->crypto_verify_data); + plugin_state->crypto_verify_data = NULL; + } + + if(!totp_crypto_seed_iv( + plugin_state, new_pin_length > 0 ? &new_pin[0] : NULL, new_pin_length)) { + memset_s(&new_pin[0], TOTP_IV_SIZE, 0, TOTP_IV_SIZE); + TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE(); + break; + } + + memset_s(&new_pin[0], TOTP_IV_SIZE, 0, TOTP_IV_SIZE); + + TOTP_LIST_FOREACH(plugin_state->tokens_list, node, { + TokenInfo* token_info = node->data; + size_t plain_token_length; + uint8_t* plain_token = totp_crypto_decrypt( + token_info->token, token_info->token_length, &old_iv[0], &plain_token_length); + free(token_info->token); + token_info->token = totp_crypto_encrypt( + plain_token, + plain_token_length, + &plugin_state->iv[0], + &token_info->token_length); + memset_s(plain_token, plain_token_length, 0, plain_token_length); + free(plain_token); + }); + + TOTP_CLI_DELETE_LAST_LINE(); + + if(totp_full_save_config_file(plugin_state) == TotpConfigFileUpdateSuccess) { + if(do_change) { + TOTP_CLI_PRINTF("PIN has been successfully changed\r\n"); + } else if(do_remove) { + TOTP_CLI_PRINTF("PIN has been successfully removed\r\n"); + } + } else { + TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE(); + } + + } while(false); + + if(load_generate_token_scene) { + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL); + } + } + + furi_string_free(temp_str); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/cli/commands/pin/pin.h b/Applications/Official/DEV_FW/source/totp/cli/commands/pin/pin.h new file mode 100644 index 000000000..1308ae736 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/cli/commands/pin/pin.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include "../../../types/plugin_state.h" + +#define TOTP_CLI_COMMAND_PIN "pin" + +void totp_cli_command_pin_handle(PluginState* plugin_state, FuriString* args, Cli* cli); +void totp_cli_command_pin_docopt_commands(); +void totp_cli_command_pin_docopt_usage(); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/cli/commands/timezone/timezone.c b/Applications/Official/DEV_FW/source/totp/cli/commands/timezone/timezone.c new file mode 100644 index 000000000..537cf8a4a --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/cli/commands/timezone/timezone.c @@ -0,0 +1,55 @@ +#include "timezone.h" +#include +#include "../../../services/config/config.h" +#include "../../../ui/scene_director.h" +#include "../../cli_helpers.h" + +#define TOTP_CLI_COMMAND_TIMEZONE_ARG_TIMEZONE "timezone" + +void totp_cli_command_timezone_docopt_commands() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_TIMEZONE ", " TOTP_CLI_COMMAND_TIMEZONE_ALT + " Get or set current timezone\r\n"); +} + +void totp_cli_command_timezone_docopt_usage() { + TOTP_CLI_PRINTF( + " " TOTP_CLI_COMMAND_NAME + " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_TIMEZONE " | " TOTP_CLI_COMMAND_TIMEZONE_ALT) " " DOCOPT_OPTIONAL( + DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_TIMEZONE_ARG_TIMEZONE)) "\r\n"); +} + +void totp_cli_command_timezone_docopt_arguments() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_TIMEZONE_ARG_TIMEZONE + " Timezone offset in hours to be set.\r\n"); +} + +void totp_cli_command_timezone_handle(PluginState* plugin_state, FuriString* args, Cli* cli) { + if(!totp_cli_ensure_authenticated(plugin_state, cli)) { + return; + } + + FuriString* temp_str = furi_string_alloc(); + if(args_read_string_and_trim(args, temp_str)) { + float tz = strtof(furi_string_get_cstr(temp_str), NULL); + if(tz >= -12.75f && tz <= 12.75f) { + plugin_state->timezone_offset = tz; + if(totp_config_file_update_timezone_offset(tz) == TotpConfigFileUpdateSuccess) { + TOTP_CLI_PRINTF("Timezone is set to %f\r\n", tz); + } else { + TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE(); + } + if(plugin_state->current_scene == TotpSceneGenerateToken) { + totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL); + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL); + } else if(plugin_state->current_scene == TotpSceneAppSettings) { + totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL); + totp_scene_director_activate_scene(plugin_state, TotpSceneAppSettings, NULL); + } + } else { + TOTP_CLI_PRINTF("Invalid timezone offset\r\n"); + } + } else { + TOTP_CLI_PRINTF("Current timezone offset is %f\r\n", plugin_state->timezone_offset); + } + furi_string_free(temp_str); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/cli/commands/timezone/timezone.h b/Applications/Official/DEV_FW/source/totp/cli/commands/timezone/timezone.h new file mode 100644 index 000000000..9823cf0ee --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/cli/commands/timezone/timezone.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include "../../../types/plugin_state.h" + +#define TOTP_CLI_COMMAND_TIMEZONE "timezone" +#define TOTP_CLI_COMMAND_TIMEZONE_ALT "tz" + +void totp_cli_command_timezone_handle(PluginState* plugin_state, FuriString* args, Cli* cli); +void totp_cli_command_timezone_docopt_commands(); +void totp_cli_command_timezone_docopt_usage(); +void totp_cli_command_timezone_docopt_arguments(); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/images/DolphinCommon_56x48.png b/Applications/Official/DEV_FW/source/totp/images/DolphinCommon_56x48.png new file mode 100644 index 000000000..089aaed83 Binary files /dev/null and b/Applications/Official/DEV_FW/source/totp/images/DolphinCommon_56x48.png differ diff --git a/Applications/Official/DEV_FW/source/totp/images/totp_arrow_bottom_10x5.png b/Applications/Official/DEV_FW/source/totp/images/totp_arrow_bottom_10x5.png new file mode 100644 index 000000000..54e22f5ef Binary files /dev/null and b/Applications/Official/DEV_FW/source/totp/images/totp_arrow_bottom_10x5.png differ diff --git a/Applications/Official/DEV_FW/source/totp/images/totp_arrow_left_8x9.png b/Applications/Official/DEV_FW/source/totp/images/totp_arrow_left_8x9.png new file mode 100644 index 000000000..3bf9121c0 Binary files /dev/null and b/Applications/Official/DEV_FW/source/totp/images/totp_arrow_left_8x9.png differ diff --git a/Applications/Official/DEV_FW/source/totp/images/totp_arrow_right_8x9.png b/Applications/Official/DEV_FW/source/totp/images/totp_arrow_right_8x9.png new file mode 100644 index 000000000..8c6a8bfeb Binary files /dev/null and b/Applications/Official/DEV_FW/source/totp/images/totp_arrow_right_8x9.png differ diff --git a/Applications/Official/DEV_FW/source/totp/lib/base32/base32.c b/Applications/Official/DEV_FW/source/totp/lib/base32/base32.c new file mode 100644 index 000000000..9781c831f --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/lib/base32/base32.c @@ -0,0 +1,60 @@ +// Base32 implementation +// +// Copyright 2010 Google Inc. +// Author: Markus Gutschke +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "base32.h" + +int base32_decode(const uint8_t* encoded, uint8_t* result, int bufSize) { + int buffer = 0; + int bitsLeft = 0; + int count = 0; + for(const uint8_t* ptr = encoded; count < bufSize && *ptr; ++ptr) { + uint8_t ch = *ptr; + if(ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '-') { + continue; + } + buffer <<= 5; + + // Deal with commonly mistyped characters + if(ch == '0') { + ch = 'O'; + } else if(ch == '1') { + ch = 'L'; + } else if(ch == '8') { + ch = 'B'; + } + + // Look up one base32 digit + if((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) { + ch = (ch & 0x1F) - 1; + } else if(ch >= '2' && ch <= '7') { + ch -= '2' - 26; + } else { + return -1; + } + + buffer |= ch; + bitsLeft += 5; + if(bitsLeft >= 8) { + result[count++] = buffer >> (bitsLeft - 8); + bitsLeft -= 8; + } + } + if(count < bufSize) { + result[count] = '\000'; + } + return count; +} diff --git a/Applications/Official/DEV_FW/source/totp/lib/base32/base32.h b/Applications/Official/DEV_FW/source/totp/lib/base32/base32.h new file mode 100644 index 000000000..dea1a1c81 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/lib/base32/base32.h @@ -0,0 +1,39 @@ +// Base32 implementation +// +// Copyright 2010 Google Inc. +// Author: Markus Gutschke +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Encode and decode from base32 encoding using the following alphabet: +// ABCDEFGHIJKLMNOPQRSTUVWXYZ234567 +// This alphabet is documented in RFC 4648/3548 +// +// We allow white-space and hyphens, but all other characters are considered +// invalid. +// +// All functions return the number of output bytes or -1 on error. If the +// output buffer is too small, the result will silently be truncated. + +#pragma once + +#include + +/** + * @brief Decodes Base-32 encoded bytes into plain bytes. + * @param encoded Base-32 encoded bytes + * @param[out] result result output buffer + * @param bufSize result output buffer size + * @return Decoded result length in bytes if successfully decoded; \c -1 otherwise + */ +int base32_decode(const uint8_t* encoded, uint8_t* result, int bufSize); diff --git a/Applications/Official/DEV_FW/source/totp/lib/list/list.c b/Applications/Official/DEV_FW/source/totp/lib/list/list.c new file mode 100644 index 000000000..f7abb6c8e --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/lib/list/list.c @@ -0,0 +1,136 @@ +#include "list.h" + +ListNode* list_init_head(void* data) { + ListNode* new = malloc(sizeof(ListNode)); + if(new == NULL) return NULL; + new->data = data; + new->next = NULL; + return new; +} + +ListNode* list_add(ListNode* head, void* data) { + ListNode* new = malloc(sizeof(ListNode)); + if(new == NULL) return NULL; + new->data = data; + new->next = NULL; + + if(head == NULL) + head = new; + else { + ListNode* it; + + for(it = head; it->next != NULL; it = it->next) + ; + + it->next = new; + } + + return head; +} + +ListNode* list_find(ListNode* head, const void* data) { + ListNode* it = NULL; + + for(it = head; it != NULL; it = it->next) + if(it->data == data) break; + + return it; +} + +ListNode* list_element_at(ListNode* head, uint16_t index) { + ListNode* it; + uint16_t i; + for(it = head, i = 0; it != NULL && i < index; it = it->next, i++) + ; + return it; +} + +ListNode* list_remove(ListNode* head, ListNode* ep) { + if(head == NULL) { + return NULL; + } + + if(head == ep) { + ListNode* new_head = head->next; + free(head); + return new_head; + } + + ListNode* it; + + for(it = head; it->next != ep; it = it->next) + ; + + it->next = ep->next; + free(ep); + + return head; +} + +ListNode* list_remove_at(ListNode* head, uint16_t index, void** removed_node_data) { + if(head == NULL) { + return NULL; + } + + ListNode* it; + ListNode* prev = NULL; + + uint16_t i; + + for(it = head, i = 0; it != NULL && i < index; prev = it, it = it->next, i++) + ; + + if(it == NULL) return head; + + ListNode* new_head = head; + if(prev == NULL) { + new_head = it->next; + } else { + prev->next = it->next; + } + + if(removed_node_data != NULL) { + *removed_node_data = it->data; + } + + free(it); + + return new_head; +} + +ListNode* list_insert_at(ListNode* head, uint16_t index, void* data) { + if(index == 0 || head == NULL) { + ListNode* new_head = list_init_head(data); + if(new_head != NULL) { + new_head->next = head; + } + return new_head; + } + + ListNode* it; + ListNode* prev = NULL; + + uint16_t i; + + for(it = head, i = 0; it != NULL && i < index; prev = it, it = it->next, i++) + ; + + ListNode* new = malloc(sizeof(ListNode)); + if(new == NULL) return NULL; + new->data = data; + new->next = it; + prev->next = new; + + return head; +} + +void list_free(ListNode* head) { + ListNode* it = head; + ListNode* tmp; + + while(it != NULL) { + tmp = it; + it = it->next; + free(tmp); + } +} diff --git a/Applications/Official/DEV_FW/source/totp/lib/list/list.h b/Applications/Official/DEV_FW/source/totp/lib/list/list.h new file mode 100644 index 000000000..c52d4c25a --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/lib/list/list.h @@ -0,0 +1,102 @@ +#pragma once + +#include +#include + +/** + * @brief Single linked list node + */ +typedef struct ListNode { + /** + * @brief Pointer to the data assigned to the current list node + */ + void* data; + + /** + * @brief Pointer to the next list node + */ + struct ListNode* next; +} ListNode; + +/** + * @brief Initializes a new list node head + * @param data data to be assigned to the head list node + * @return Head list node + */ +ListNode* list_init_head(void* data); + +/** + * @brief Adds new list node to the end of the list + * @param head head list node + * @param data data to be assigned to the newly added list node + * @return Head list node + */ +ListNode* list_add( + ListNode* head, + void* data); /* adds element with specified data to the end of the list and returns new head node. */ + +/** + * @brief Searches list node with the given assigned \p data in the list + * @param head head list node + * @param data data to be searched + * @return List node containing \p data if there is such a node in the list; \c NULL otherwise + */ +ListNode* list_find(ListNode* head, const void* data); + +/** + * @brief Searches list node with the given \p index in the list + * @param head head list node + * @param index desired list node index + * @return List node with the given \p index in the list if there is such a list node; \c NULL otherwise + */ +ListNode* list_element_at(ListNode* head, uint16_t index); + +/** + * @brief Removes list node from the list + * @param head head list node + * @param ep list node to be removed + * @return Head list node + */ +ListNode* list_remove(ListNode* head, ListNode* ep); + +/** + * @brief Removes list node with the given \p index in the list from the list + * @param head head list node + * @param index index of the node to be removed + * @param[out] removed_node_data data which was assigned to the removed list node + * @return Head list node + */ +ListNode* list_remove_at(ListNode* head, uint16_t index, void** removed_node_data); + +/** + * @brief Inserts new list node at the given index + * @param head head list node + * @param index index in the list where the new list node should be inserted + * @param data data to be assgned to the new list node + * @return Head list node + */ +ListNode* list_insert_at(ListNode* head, uint16_t index, void* data); + +/** + * @brief Disposes all the list nodes in the list + * @param head head list node + */ +void list_free(ListNode* head); + +#define TOTP_LIST_INIT_OR_ADD(head, item, assert) \ + do { \ + if(head == NULL) { \ + head = list_init_head(item); \ + assert(head != NULL); \ + } else { \ + assert(list_add(head, item) != NULL); \ + } \ + } while(false) + +#define TOTP_LIST_FOREACH(head, node, action) \ + do { \ + ListNode* node = head; \ + while(node != NULL) { \ + action node = node->next; \ + } \ + } while(false) diff --git a/Applications/Official/DEV_FW/source/totp/lib/polyfills/memset_s.c b/Applications/Official/DEV_FW/source/totp/lib/polyfills/memset_s.c new file mode 100644 index 000000000..81c285c0d --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/lib/polyfills/memset_s.c @@ -0,0 +1,22 @@ +#include "memset_s.h" + +#define RSIZE_MAX 0x7fffffffffffffffUL + +errno_t memset_s(void* s, rsize_t smax, int c, rsize_t n) { + if(!s || smax > RSIZE_MAX) { + return EINVAL; + } + + errno_t violation_present = 0; + if(n > smax) { + n = smax; + violation_present = EINVAL; + } + + volatile unsigned char* v = s; + for(rsize_t i = 0u; i < n; ++i) { + *v++ = (unsigned char)c; + } + + return violation_present; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/lib/polyfills/memset_s.h b/Applications/Official/DEV_FW/source/totp/lib/polyfills/memset_s.h new file mode 100644 index 000000000..54628860d --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/lib/polyfills/memset_s.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +#ifndef _RSIZE_T_DECLARED +typedef uint64_t rsize_t; +#define _RSIZE_T_DECLARED +#endif +#ifndef _ERRNOT_DECLARED +typedef int16_t errno_t; //-V677 +#define _ERRNOT_DECLARED +#endif + +/** + * @brief Copies the value \p c into each of the first \p n characters of the object pointed to by \p s. + * @param s pointer to the object to fill + * @param smax size of the destination object + * @param c fill byte + * @param n number of bytes to fill + * @return \c 0 on success; non-zero otherwise + */ +errno_t memset_s(void* s, rsize_t smax, int c, rsize_t n); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/lib/polyfills/strnlen.c b/Applications/Official/DEV_FW/source/totp/lib/polyfills/strnlen.c new file mode 100644 index 000000000..54d183895 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/lib/polyfills/strnlen.c @@ -0,0 +1,11 @@ +#include "strnlen.h" + +size_t strnlen(const char* s, size_t maxlen) { + size_t len; + + for(len = 0; len < maxlen; len++, s++) { + if(!*s) break; + } + + return len; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/lib/polyfills/strnlen.h b/Applications/Official/DEV_FW/source/totp/lib/polyfills/strnlen.h new file mode 100644 index 000000000..7dcef3a18 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/lib/polyfills/strnlen.h @@ -0,0 +1,3 @@ +#include + +size_t strnlen(const char* s, size_t maxlen); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/lib/roll_value/roll_value.c b/Applications/Official/DEV_FW/source/totp/lib/roll_value/roll_value.c new file mode 100644 index 000000000..b8f30e078 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/lib/roll_value/roll_value.c @@ -0,0 +1,28 @@ +#include "roll_value.h" + +#define TOTP_ROLL_VALUE_FN(type, step_type) \ + TOTP_ROLL_VALUE_FN_HEADER(type, step_type) { \ + type v = *value; \ + if(step > 0 && v > max - step) { \ + if(overflow_behavior == RollOverflowBehaviorRoll) { \ + v = min; \ + } else if(overflow_behavior == RollOverflowBehaviorStop) { \ + v = max; \ + } \ + } else if(step < 0 && v < min - step) { \ + if(overflow_behavior == RollOverflowBehaviorRoll) { \ + v = max; \ + } else if(overflow_behavior == RollOverflowBehaviorStop) { \ + v = min; \ + } \ + } else { \ + v += step; \ + } \ + *value = v; \ + } + +TOTP_ROLL_VALUE_FN(int8_t, int8_t) + +TOTP_ROLL_VALUE_FN(uint8_t, int8_t) + +TOTP_ROLL_VALUE_FN(uint16_t, int16_t); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/lib/roll_value/roll_value.h b/Applications/Official/DEV_FW/source/totp/lib/roll_value/roll_value.h new file mode 100644 index 000000000..3c270be9a --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/lib/roll_value/roll_value.h @@ -0,0 +1,58 @@ +#pragma once + +#include + +typedef uint8_t TotpRollValueOverflowBehavior; + +enum TotpRollValueOverflowBehaviors { + /** + * @brief Do not change value if it reached constraint + */ + RollOverflowBehaviorStop, + + /** + * @brief Set value to opposite constraint value if it reached constraint + */ + RollOverflowBehaviorRoll +}; + +#define TOTP_ROLL_VALUE_FN_HEADER(type, step_type) \ + void totp_roll_value_##type( \ + type* value, \ + step_type step, \ + type min, \ + type max, \ + TotpRollValueOverflowBehavior overflow_behavior) + +/** + * @brief Rolls \c int8_t \p value using \p min and \p max as an value constraints with \p step step. + * When value reaches constraint value \p overflow_behavior defines what to do next. + * @param[in,out] value value to roll + * @param step step to be used to change value + * @param min minimal possible value + * @param max maximum possible value + * @param overflow_behavior defines what to do when value reaches constraint value + */ +TOTP_ROLL_VALUE_FN_HEADER(int8_t, int8_t); + +/** + * @brief Rolls \c uint8_t \p value using \p min and \p max as an value constraints with \p step step. + * When value reaches constraint value \p overflow_behavior defines what to do next. + * @param[in,out] value value to roll + * @param step step to be used to change value + * @param min minimal possible value + * @param max maximum possible value + * @param overflow_behavior defines what to do when value reaches constraint value + */ +TOTP_ROLL_VALUE_FN_HEADER(uint8_t, int8_t); + +/** + * @brief Rolls \c uint16_t \p value using \p min and \p max as an value constraints with \p step step. + * When value reaches constraint value \p overflow_behavior defines what to do next. + * @param[in,out] value value to roll + * @param step step to be used to change value + * @param min minimal possible value + * @param max maximum possible value + * @param overflow_behavior defines what to do when value reaches constraint value + */ +TOTP_ROLL_VALUE_FN_HEADER(uint16_t, int16_t); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/lib/timezone_utils/timezone_utils.c b/Applications/Official/DEV_FW/source/totp/lib/timezone_utils/timezone_utils.c new file mode 100644 index 000000000..31df3bbba --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/lib/timezone_utils/timezone_utils.c @@ -0,0 +1,16 @@ +#include "timezone_utils.h" + +int32_t timezone_offset_from_hours(float hours) { + return hours * 3600.0f; +} + +uint64_t timezone_offset_apply(uint64_t time, int32_t offset) { + uint64_t for_time_adjusted; + if(offset > 0) { + for_time_adjusted = time - offset; + } else { + for_time_adjusted = time + (-offset); + } + + return for_time_adjusted; +} diff --git a/Applications/Official/DEV_FW/source/totp/lib/timezone_utils/timezone_utils.h b/Applications/Official/DEV_FW/source/totp/lib/timezone_utils/timezone_utils.h new file mode 100644 index 000000000..5bb3b8ead --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/lib/timezone_utils/timezone_utils.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +/** + * @brief Calculates timezone offset in seconds given timezone offset in hours. + * @param hours timezone offset in hours + * @return Timezone offset in seconds. + */ +int32_t timezone_offset_from_hours(float hours); + +/** + * @brief Applies timezone offset to a given time. + * @param time time to apply offset to. + * @param offset timezone offset in seconds. + * @return Time with timezone offset applied. + */ +uint64_t timezone_offset_apply(uint64_t time, int32_t offset); diff --git a/Applications/Official/DEV_FW/source/totp/services/config/config.c b/Applications/Official/DEV_FW/source/totp/services/config/config.c new file mode 100644 index 000000000..b9f0e9d50 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/services/config/config.c @@ -0,0 +1,733 @@ +#include "config.h" +#include +#include +#include "../list/list.h" +#include "../../types/common.h" +#include "../../types/token_info.h" +#include "migrations/config_migration_v1_to_v2.h" + +#define CONFIG_FILE_DIRECTORY_PATH EXT_PATH("authenticator") +#define CONFIG_FILE_PATH CONFIG_FILE_DIRECTORY_PATH "/totp.conf" +#define CONFIG_FILE_BACKUP_PATH CONFIG_FILE_PATH ".backup" +#define CONFIG_FILE_TEMP_PATH CONFIG_FILE_PATH ".tmp" +#define CONFIG_FILE_ORIG_PATH CONFIG_FILE_PATH ".orig" +#define CONFIG_FILE_PATH_PREVIOUS EXT_PATH("apps/Misc") "/totp.conf" + +static char* token_info_get_algo_as_cstr(const TokenInfo* token_info) { + switch(token_info->algo) { + case SHA1: + return TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME; + case SHA256: + return TOTP_CONFIG_TOKEN_ALGO_SHA256_NAME; + case SHA512: + return TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME; + default: + break; + } + + return NULL; +} + +static void token_info_set_algo_from_str(TokenInfo* token_info, const FuriString* str) { + if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME) == 0) { + token_info->algo = SHA1; + } else if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA256_NAME) == 0) { + token_info->algo = SHA256; + } else if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME) == 0) { + token_info->algo = SHA512; + } +} + +/** + * @brief Opens storage record + * @return Storage record + */ +static Storage* totp_open_storage() { + return furi_record_open(RECORD_STORAGE); +} + +/** + * @brief Closes storage record + */ +static void totp_close_storage() { + furi_record_close(RECORD_STORAGE); +} + +/** + * @brief Closes config file + * @param file config file reference + */ +static void totp_close_config_file(FlipperFormat* file) { + if(file == NULL) return; + flipper_format_file_close(file); + flipper_format_free(file); +} + +/** + * @brief Opens or creates TOTP application standard config file + * @param storage storage record to use + * @param[out] file opened config file + * @return Config file open result + */ +static TotpConfigFileOpenResult totp_open_config_file(Storage* storage, FlipperFormat** file) { + FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); + + if(storage_common_stat(storage, CONFIG_FILE_PATH, NULL) == FSE_OK) { + FURI_LOG_D(LOGGING_TAG, "Config file %s found", CONFIG_FILE_PATH); + if(!flipper_format_file_open_existing(fff_data_file, CONFIG_FILE_PATH)) { + FURI_LOG_E(LOGGING_TAG, "Error opening existing file %s", CONFIG_FILE_PATH); + totp_close_config_file(fff_data_file); + return TotpConfigFileOpenError; + } + } else if(storage_common_stat(storage, CONFIG_FILE_PATH_PREVIOUS, NULL) == FSE_OK) { + FURI_LOG_D(LOGGING_TAG, "Old config file %s found", CONFIG_FILE_PATH_PREVIOUS); + if(storage_common_stat(storage, CONFIG_FILE_DIRECTORY_PATH, NULL) == FSE_NOT_EXIST) { + FURI_LOG_D( + LOGGING_TAG, + "Directory %s doesn't exist. Will create new.", + CONFIG_FILE_DIRECTORY_PATH); + if(!storage_simply_mkdir(storage, CONFIG_FILE_DIRECTORY_PATH)) { + FURI_LOG_E(LOGGING_TAG, "Error creating directory %s", CONFIG_FILE_DIRECTORY_PATH); + totp_close_config_file(fff_data_file); + return TotpConfigFileOpenError; + } + } + if(storage_common_rename(storage, CONFIG_FILE_PATH_PREVIOUS, CONFIG_FILE_PATH) != FSE_OK) { + FURI_LOG_E(LOGGING_TAG, "Error moving config to %s", CONFIG_FILE_PATH); + totp_close_config_file(fff_data_file); + return TotpConfigFileOpenError; + } + FURI_LOG_I(LOGGING_TAG, "Applied config file path migration"); + return totp_open_config_file(storage, file); + } else { + FURI_LOG_D(LOGGING_TAG, "Config file %s is not found. Will create new.", CONFIG_FILE_PATH); + if(storage_common_stat(storage, CONFIG_FILE_DIRECTORY_PATH, NULL) == FSE_NOT_EXIST) { + FURI_LOG_D( + LOGGING_TAG, + "Directory %s doesn't exist. Will create new.", + CONFIG_FILE_DIRECTORY_PATH); + if(!storage_simply_mkdir(storage, CONFIG_FILE_DIRECTORY_PATH)) { + FURI_LOG_E(LOGGING_TAG, "Error creating directory %s", CONFIG_FILE_DIRECTORY_PATH); + return TotpConfigFileOpenError; + } + } + + if(!flipper_format_file_open_new(fff_data_file, CONFIG_FILE_PATH)) { + totp_close_config_file(fff_data_file); + FURI_LOG_E(LOGGING_TAG, "Error creating new file %s", CONFIG_FILE_PATH); + return TotpConfigFileOpenError; + } + + flipper_format_write_header_cstr( + fff_data_file, CONFIG_FILE_HEADER, CONFIG_FILE_ACTUAL_VERSION); + float tmp_tz = 0; + flipper_format_write_comment_cstr(fff_data_file, " "); + flipper_format_write_comment_cstr( + fff_data_file, + "Timezone offset in hours. Important note: do not put '+' sign for positive values"); + flipper_format_write_float(fff_data_file, TOTP_CONFIG_KEY_TIMEZONE, &tmp_tz, 1); + + uint32_t tmp_uint32 = NotificationMethodSound | NotificationMethodVibro; + flipper_format_write_comment_cstr(fff_data_file, " "); + flipper_format_write_comment_cstr( + fff_data_file, + "How to notify user when new token is generated or badusb mode is activated (possible values: 0 - do not notify, 1 - sound, 2 - vibro, 3 sound and vibro)"); + flipper_format_write_uint32( + fff_data_file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, &tmp_uint32, 1); + + FuriString* temp_str = furi_string_alloc(); + + flipper_format_write_comment_cstr(fff_data_file, " "); + flipper_format_write_comment_cstr(fff_data_file, "=== TOKEN SAMPLE BEGIN ==="); + flipper_format_write_comment_cstr(fff_data_file, " "); + flipper_format_write_comment_cstr( + fff_data_file, "# Token name which will be visible in the UI."); + furi_string_printf(temp_str, "%s: Sample token name", TOTP_CONFIG_KEY_TOKEN_NAME); + flipper_format_write_comment(fff_data_file, temp_str); + flipper_format_write_comment_cstr(fff_data_file, " "); + + flipper_format_write_comment_cstr( + fff_data_file, + "# Plain token secret without spaces, dashes and etc, just pure alpha-numeric characters. Important note: plain token will be encrypted and replaced by TOTP app"); + furi_string_printf(temp_str, "%s: plaintokensecret", TOTP_CONFIG_KEY_TOKEN_SECRET); + flipper_format_write_comment(fff_data_file, temp_str); + flipper_format_write_comment_cstr(fff_data_file, " "); + + furi_string_printf( + temp_str, + " # Token hashing algorithm to use during code generation. Supported options are %s, %s and %s. If you are not use which one to use - use %s", + TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME, + TOTP_CONFIG_TOKEN_ALGO_SHA256_NAME, + TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME, + TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME); + flipper_format_write_comment(fff_data_file, temp_str); + furi_string_printf( + temp_str, "%s: %s", TOTP_CONFIG_KEY_TOKEN_ALGO, TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME); + flipper_format_write_comment(fff_data_file, temp_str); + flipper_format_write_comment_cstr(fff_data_file, " "); + + flipper_format_write_comment_cstr( + fff_data_file, + "# How many digits there should be in generated code. Available options are 6 and 8. Majority websites requires 6 digits code, however some rare websites wants to get 8 digits code. If you are not sure which one to use - use 6"); + furi_string_printf(temp_str, "%s: 6", TOTP_CONFIG_KEY_TOKEN_DIGITS); + flipper_format_write_comment(fff_data_file, temp_str); + flipper_format_write_comment_cstr(fff_data_file, " "); + + flipper_format_write_comment_cstr(fff_data_file, "=== TOKEN SAMPLE END ==="); + flipper_format_write_comment_cstr(fff_data_file, " "); + + furi_string_free(temp_str); + if(!flipper_format_rewind(fff_data_file)) { + totp_close_config_file(fff_data_file); + FURI_LOG_E(LOGGING_TAG, "Rewind error"); + return TotpConfigFileOpenError; + } + } + + *file = fff_data_file; + return TotpConfigFileOpenSuccess; +} + +TotpConfigFileUpdateResult + totp_config_file_save_new_token_i(FlipperFormat* file, const TokenInfo* token_info) { + TotpConfigFileUpdateResult update_result; + do { + if(!flipper_format_seek_to_end(file)) { + update_result = TotpConfigFileUpdateError; + break; + } + + if(!flipper_format_write_string_cstr(file, TOTP_CONFIG_KEY_TOKEN_NAME, token_info->name)) { + update_result = TotpConfigFileUpdateError; + break; + } + + bool token_is_valid = token_info->token != NULL && token_info->token_length > 0; + if(!token_is_valid && + !flipper_format_write_comment_cstr(file, "!!! WARNING BEGIN: INVALID TOKEN !!!")) { + update_result = TotpConfigFileUpdateError; + break; + } + + if(!flipper_format_write_hex( + file, TOTP_CONFIG_KEY_TOKEN_SECRET, token_info->token, token_info->token_length)) { + update_result = TotpConfigFileUpdateError; + break; + } + + if(!token_is_valid && !flipper_format_write_comment_cstr(file, "!!! WARNING END !!!")) { + update_result = TotpConfigFileUpdateError; + break; + } + + if(!flipper_format_write_string_cstr( + file, TOTP_CONFIG_KEY_TOKEN_ALGO, token_info_get_algo_as_cstr(token_info))) { + update_result = TotpConfigFileUpdateError; + break; + } + + uint32_t tmp_uint32 = token_info->digits; + if(!flipper_format_write_uint32(file, TOTP_CONFIG_KEY_TOKEN_DIGITS, &tmp_uint32, 1)) { + update_result = TotpConfigFileUpdateError; + break; + } + + update_result = TotpConfigFileUpdateSuccess; + } while(false); + + return update_result; +} + +TotpConfigFileUpdateResult totp_config_file_save_new_token(const TokenInfo* token_info) { + Storage* cfg_storage = totp_open_storage(); + FlipperFormat* file; + TotpConfigFileUpdateResult update_result; + + if(totp_open_config_file(cfg_storage, &file) == TotpConfigFileOpenSuccess) { + do { + if(totp_config_file_save_new_token_i(file, token_info) != + TotpConfigFileUpdateSuccess) { + update_result = TotpConfigFileUpdateError; + break; + } + + update_result = TotpConfigFileUpdateSuccess; + } while(false); + + totp_close_config_file(file); + } else { + update_result = TotpConfigFileUpdateError; + } + + totp_close_storage(); + return update_result; +} + +TotpConfigFileUpdateResult totp_config_file_update_timezone_offset(float new_timezone_offset) { + Storage* cfg_storage = totp_open_storage(); + FlipperFormat* file; + TotpConfigFileUpdateResult update_result; + + if(totp_open_config_file(cfg_storage, &file) == TotpConfigFileOpenSuccess) { + do { + if(!flipper_format_insert_or_update_float( + file, TOTP_CONFIG_KEY_TIMEZONE, &new_timezone_offset, 1)) { + update_result = TotpConfigFileUpdateError; + break; + } + + update_result = TotpConfigFileUpdateSuccess; + } while(false); + + totp_close_config_file(file); + } else { + update_result = TotpConfigFileUpdateError; + } + + totp_close_storage(); + return update_result; +} + +TotpConfigFileUpdateResult + totp_config_file_update_notification_method(NotificationMethod new_notification_method) { + Storage* cfg_storage = totp_open_storage(); + FlipperFormat* file; + TotpConfigFileUpdateResult update_result; + + if(totp_open_config_file(cfg_storage, &file) == TotpConfigFileOpenSuccess) { + do { + uint32_t tmp_uint32 = new_notification_method; + if(!flipper_format_insert_or_update_uint32( + file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, &tmp_uint32, 1)) { + update_result = TotpConfigFileUpdateError; + break; + } + + update_result = TotpConfigFileUpdateSuccess; + } while(false); + + totp_close_config_file(file); + } else { + update_result = TotpConfigFileUpdateError; + } + + totp_close_storage(); + return update_result; +} + +TotpConfigFileUpdateResult totp_config_file_update_user_settings(const PluginState* plugin_state) { + Storage* cfg_storage = totp_open_storage(); + FlipperFormat* file; + TotpConfigFileUpdateResult update_result; + if(totp_open_config_file(cfg_storage, &file) == TotpConfigFileOpenSuccess) { + do { + if(!flipper_format_insert_or_update_float( + file, TOTP_CONFIG_KEY_TIMEZONE, &plugin_state->timezone_offset, 1)) { + update_result = TotpConfigFileUpdateError; + break; + } + uint32_t tmp_uint32 = plugin_state->notification_method; + if(!flipper_format_insert_or_update_uint32( + file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, &tmp_uint32, 1)) { + update_result = TotpConfigFileUpdateError; + break; + } + + update_result = TotpConfigFileUpdateSuccess; + } while(false); + + totp_close_config_file(file); + } else { + update_result = TotpConfigFileUpdateError; + } + + totp_close_storage(); + return update_result; +} + +TotpConfigFileUpdateResult totp_full_save_config_file(const PluginState* const plugin_state) { + Storage* storage = totp_open_storage(); + FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); + TotpConfigFileUpdateResult result = TotpConfigFileUpdateSuccess; + + do { + if(!flipper_format_file_open_always(fff_data_file, CONFIG_FILE_TEMP_PATH)) { + result = TotpConfigFileUpdateError; + break; + } + + if(!flipper_format_write_header_cstr( + fff_data_file, CONFIG_FILE_HEADER, CONFIG_FILE_ACTUAL_VERSION)) { + result = TotpConfigFileUpdateError; + break; + } + + if(!flipper_format_write_hex( + fff_data_file, TOTP_CONFIG_KEY_BASE_IV, &plugin_state->base_iv[0], TOTP_IV_SIZE)) { + result = TotpConfigFileUpdateError; + break; + } + + if(!flipper_format_write_hex( + fff_data_file, + TOTP_CONFIG_KEY_CRYPTO_VERIFY, + plugin_state->crypto_verify_data, + plugin_state->crypto_verify_data_length)) { + result = TotpConfigFileUpdateError; + break; + } + + if(!flipper_format_write_float( + fff_data_file, TOTP_CONFIG_KEY_TIMEZONE, &plugin_state->timezone_offset, 1)) { + result = TotpConfigFileUpdateError; + break; + } + + if(!flipper_format_write_bool( + fff_data_file, TOTP_CONFIG_KEY_PINSET, &plugin_state->pin_set, 1)) { + result = TotpConfigFileUpdateError; + break; + } + uint32_t tmp_uint32 = plugin_state->notification_method; + if(!flipper_format_write_uint32( + fff_data_file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, &tmp_uint32, 1)) { + result = TotpConfigFileUpdateError; + break; + } + + bool tokens_written = true; + TOTP_LIST_FOREACH(plugin_state->tokens_list, node, { + const TokenInfo* token_info = node->data; + tokens_written = tokens_written && + totp_config_file_save_new_token_i(fff_data_file, token_info) == + TotpConfigFileUpdateSuccess; + }); + + if(!tokens_written) { + result = TotpConfigFileUpdateError; + break; + } + } while(false); + + totp_close_config_file(fff_data_file); + + if(result == TotpConfigFileUpdateSuccess) { + if(storage_file_exists(storage, CONFIG_FILE_ORIG_PATH)) { + storage_simply_remove(storage, CONFIG_FILE_ORIG_PATH); + } + + if(storage_common_rename(storage, CONFIG_FILE_PATH, CONFIG_FILE_ORIG_PATH) != FSE_OK) { + result = TotpConfigFileUpdateError; + } else if(storage_common_rename(storage, CONFIG_FILE_TEMP_PATH, CONFIG_FILE_PATH) != FSE_OK) { + result = TotpConfigFileUpdateError; + } else if(!storage_simply_remove(storage, CONFIG_FILE_ORIG_PATH)) { + result = TotpConfigFileUpdateError; + } + } + + totp_close_storage(); + return result; +} + +TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_state) { + Storage* storage = totp_open_storage(); + FlipperFormat* fff_data_file; + + TotpConfigFileOpenResult result; + if((result = totp_open_config_file(storage, &fff_data_file)) != TotpConfigFileOpenSuccess) { + totp_close_storage(); + return result; + } + + plugin_state->timezone_offset = 0; + + FuriString* temp_str = furi_string_alloc(); + + do { + uint32_t file_version; + if(!flipper_format_read_header(fff_data_file, temp_str, &file_version)) { + FURI_LOG_E(LOGGING_TAG, "Missing or incorrect header"); + result = TotpConfigFileOpenError; + break; + } + + if(file_version < CONFIG_FILE_ACTUAL_VERSION) { + FURI_LOG_I( + LOGGING_TAG, + "Obsolete config file version detected. Current version: %" PRIu32 + "; Actual version: %" PRId16, + file_version, + CONFIG_FILE_ACTUAL_VERSION); + totp_close_config_file(fff_data_file); + + if(storage_common_stat(storage, CONFIG_FILE_BACKUP_PATH, NULL) == FSE_OK) { + storage_simply_remove(storage, CONFIG_FILE_BACKUP_PATH); + } + + if(storage_common_copy(storage, CONFIG_FILE_PATH, CONFIG_FILE_BACKUP_PATH) == FSE_OK) { + FURI_LOG_I(LOGGING_TAG, "Took config file backup to %s", CONFIG_FILE_BACKUP_PATH); + if(totp_open_config_file(storage, &fff_data_file) != TotpConfigFileOpenSuccess) { + result = TotpConfigFileOpenError; + break; + } + + FlipperFormat* fff_backup_data_file = flipper_format_file_alloc(storage); + if(!flipper_format_file_open_existing( + fff_backup_data_file, CONFIG_FILE_BACKUP_PATH)) { + flipper_format_file_close(fff_backup_data_file); + flipper_format_free(fff_backup_data_file); + result = TotpConfigFileOpenError; + break; + } + + if(file_version == 1) { + if(totp_config_migrate_v1_to_v2(fff_data_file, fff_backup_data_file)) { + FURI_LOG_I(LOGGING_TAG, "Applied migration from v1 to v2"); + } else { + FURI_LOG_W( + LOGGING_TAG, "An error occurred during migration from v1 to v2"); + result = TotpConfigFileOpenError; + break; + } + } + + flipper_format_file_close(fff_backup_data_file); + flipper_format_free(fff_backup_data_file); + flipper_format_rewind(fff_data_file); + } else { + FURI_LOG_E( + LOGGING_TAG, + "An error occurred during taking backup of %s into %s before migration", + CONFIG_FILE_PATH, + CONFIG_FILE_BACKUP_PATH); + result = TotpConfigFileOpenError; + break; + } + } + + if(!flipper_format_read_hex( + fff_data_file, TOTP_CONFIG_KEY_BASE_IV, &plugin_state->base_iv[0], TOTP_IV_SIZE)) { + FURI_LOG_D(LOGGING_TAG, "Missing base IV"); + } + + if(!flipper_format_rewind(fff_data_file)) { + result = TotpConfigFileOpenError; + break; + } + + uint32_t crypto_size; + if(flipper_format_get_value_count( + fff_data_file, TOTP_CONFIG_KEY_CRYPTO_VERIFY, &crypto_size) && + crypto_size > 0) { + plugin_state->crypto_verify_data = malloc(sizeof(uint8_t) * crypto_size); + furi_check(plugin_state->crypto_verify_data != NULL); + plugin_state->crypto_verify_data_length = crypto_size; + if(!flipper_format_read_hex( + fff_data_file, + TOTP_CONFIG_KEY_CRYPTO_VERIFY, + plugin_state->crypto_verify_data, + crypto_size)) { + FURI_LOG_D(LOGGING_TAG, "Missing crypto verify token"); + free(plugin_state->crypto_verify_data); + plugin_state->crypto_verify_data = NULL; + plugin_state->crypto_verify_data_length = 0; + } + } else { + plugin_state->crypto_verify_data = NULL; + plugin_state->crypto_verify_data_length = 0; + } + + if(!flipper_format_rewind(fff_data_file)) { + result = TotpConfigFileOpenError; + break; + } + + if(!flipper_format_read_float( + fff_data_file, TOTP_CONFIG_KEY_TIMEZONE, &plugin_state->timezone_offset, 1)) { + plugin_state->timezone_offset = 0; + FURI_LOG_D(LOGGING_TAG, "Missing timezone offset information, defaulting to 0"); + } + + if(!flipper_format_rewind(fff_data_file)) { + result = TotpConfigFileOpenError; + break; + } + + if(!flipper_format_read_bool( + fff_data_file, TOTP_CONFIG_KEY_PINSET, &plugin_state->pin_set, 1)) { + plugin_state->pin_set = true; + } + + flipper_format_rewind(fff_data_file); + + uint32_t tmp_uint32; + if(!flipper_format_read_uint32( + fff_data_file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, &tmp_uint32, 1)) { + tmp_uint32 = NotificationMethodSound | NotificationMethodVibro; + } + + plugin_state->notification_method = tmp_uint32; + } while(false); + + furi_string_free(temp_str); + totp_close_config_file(fff_data_file); + totp_close_storage(); + return result; +} + +TokenLoadingResult totp_config_file_load_tokens(PluginState* const plugin_state) { + Storage* storage = totp_open_storage(); + FlipperFormat* fff_data_file; + if(totp_open_config_file(storage, &fff_data_file) != TotpConfigFileOpenSuccess) { + totp_close_storage(); + return TokenLoadingResultError; + } + + FuriString* temp_str = furi_string_alloc(); + uint32_t temp_data32; + + if(!flipper_format_read_header(fff_data_file, temp_str, &temp_data32)) { + FURI_LOG_E(LOGGING_TAG, "Missing or incorrect header"); + totp_close_storage(); + furi_string_free(temp_str); + return TokenLoadingResultError; + } + + TokenLoadingResult result = TokenLoadingResultSuccess; + uint16_t index = 0; + bool has_any_plain_secret = false; + + while(true) { + if(!flipper_format_read_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_NAME, temp_str)) { + break; + } + + TokenInfo* tokenInfo = token_info_alloc(); + + size_t temp_cstr_len = furi_string_size(temp_str); + tokenInfo->name = malloc(temp_cstr_len + 1); + furi_check(tokenInfo->name != NULL); + strlcpy(tokenInfo->name, furi_string_get_cstr(temp_str), temp_cstr_len + 1); + + uint32_t secret_bytes_count; + if(!flipper_format_get_value_count( + fff_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, &secret_bytes_count)) { + secret_bytes_count = 0; + } + + if(secret_bytes_count == 1) { // Plain secret key + if(flipper_format_read_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str)) { + if(token_info_set_secret( + tokenInfo, + furi_string_get_cstr(temp_str), + furi_string_size(temp_str), + &plugin_state->iv[0])) { + FURI_LOG_W(LOGGING_TAG, "Token \"%s\" has plain secret", tokenInfo->name); + } else { + tokenInfo->token = NULL; + tokenInfo->token_length = 0; + FURI_LOG_W(LOGGING_TAG, "Token \"%s\" has invalid secret", tokenInfo->name); + result = TokenLoadingResultWarning; + } + } else { + tokenInfo->token = NULL; + tokenInfo->token_length = 0; + result = TokenLoadingResultWarning; + } + + has_any_plain_secret = true; + } else { // encrypted + tokenInfo->token_length = secret_bytes_count; + if(secret_bytes_count > 0) { + tokenInfo->token = malloc(tokenInfo->token_length); + furi_check(tokenInfo->token != NULL); + if(!flipper_format_read_hex( + fff_data_file, + TOTP_CONFIG_KEY_TOKEN_SECRET, + tokenInfo->token, + tokenInfo->token_length)) { + free(tokenInfo->token); + tokenInfo->token = NULL; + tokenInfo->token_length = 0; + result = TokenLoadingResultWarning; + } + } else { + tokenInfo->token = NULL; + result = TokenLoadingResultWarning; + } + } + + if(flipper_format_read_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, temp_str)) { + token_info_set_algo_from_str(tokenInfo, temp_str); + } else { + tokenInfo->algo = SHA1; + } + + if(!flipper_format_read_uint32( + fff_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, &temp_data32, 1) || + !token_info_set_digits_from_int(tokenInfo, temp_data32)) { + tokenInfo->digits = TOTP_6_DIGITS; + } + + FURI_LOG_D(LOGGING_TAG, "Found token \"%s\"", tokenInfo->name); + + TOTP_LIST_INIT_OR_ADD(plugin_state->tokens_list, tokenInfo, furi_check); + + index++; + } + + plugin_state->tokens_count = index; + plugin_state->token_list_loaded = true; + + FURI_LOG_D(LOGGING_TAG, "Found %" PRIu16 " tokens", index); + + furi_string_free(temp_str); + totp_close_config_file(fff_data_file); + totp_close_storage(); + + if(has_any_plain_secret) { + totp_full_save_config_file(plugin_state); + } + + return result; +} + +TotpConfigFileUpdateResult + totp_config_file_update_crypto_signatures(const PluginState* plugin_state) { + Storage* storage = totp_open_storage(); + FlipperFormat* config_file; + TotpConfigFileUpdateResult update_result; + if(totp_open_config_file(storage, &config_file) == TotpConfigFileOpenSuccess) { + do { + if(!flipper_format_insert_or_update_hex( + config_file, TOTP_CONFIG_KEY_BASE_IV, plugin_state->base_iv, TOTP_IV_SIZE)) { + update_result = TotpConfigFileUpdateError; + break; + } + + if(!flipper_format_insert_or_update_hex( + config_file, + TOTP_CONFIG_KEY_CRYPTO_VERIFY, + plugin_state->crypto_verify_data, + plugin_state->crypto_verify_data_length)) { + update_result = TotpConfigFileUpdateError; + break; + } + + if(!flipper_format_insert_or_update_bool( + config_file, TOTP_CONFIG_KEY_PINSET, &plugin_state->pin_set, 1)) { + update_result = TotpConfigFileUpdateError; + break; + } + + update_result = TotpConfigFileUpdateSuccess; + } while(false); + + totp_close_config_file(config_file); + } else { + update_result = TotpConfigFileUpdateError; + } + + totp_close_storage(); + return update_result; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/services/config/config.h b/Applications/Official/DEV_FW/source/totp/services/config/config.h new file mode 100644 index 000000000..bb48105f7 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/services/config/config.h @@ -0,0 +1,119 @@ +#pragma once + +#include +#include +#include "../../types/plugin_state.h" +#include "../../types/token_info.h" +#include "constants.h" + +typedef uint8_t TokenLoadingResult; +typedef uint8_t TotpConfigFileOpenResult; +typedef uint8_t TotpConfigFileUpdateResult; + +/** + * @brief Token loading results + */ +enum TokenLoadingResults { + /** + * @brief All the tokens loaded successfully + */ + TokenLoadingResultSuccess, + + /** + * @brief All the tokens loaded, but there are some warnings + */ + TokenLoadingResultWarning, + + /** + * @brief Tokens not loaded because of error(s) + */ + TokenLoadingResultError +}; + +/** + * @brief Config file opening result + */ +enum TotpConfigFileOpenResults { + /** + * @brief Config file opened successfully + */ + TotpConfigFileOpenSuccess = 0, + + /** + * @brief An error has occurred during opening config file + */ + TotpConfigFileOpenError = 1 +}; + +/** + * @brief Config file updating result + */ +enum TotpConfigFileUpdateResults { + /** + * @brief Config file updated successfully + */ + TotpConfigFileUpdateSuccess, + + /** + * @brief An error has occurred during updating config file + */ + TotpConfigFileUpdateError +}; + +/** + * @brief Saves all the settings and tokens to an application config file + * @param plugin_state application state + * @return Config file update result + */ +TotpConfigFileUpdateResult totp_full_save_config_file(const PluginState* const plugin_state); + +/** + * @brief Loads basic information from an application config file into application state without loading all the tokens + * @param plugin_state application state + * @return Config file open result + */ +TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_state); + +/** + * @brief Loads tokens from an application config file into application state + * @param plugin_state application state + * @return Results of the loading + */ +TokenLoadingResult totp_config_file_load_tokens(PluginState* const plugin_state); + +/** + * @brief Add new token to the end of the application config file + * @param token_info token information to be saved + * @return Config file update result + */ +TotpConfigFileUpdateResult totp_config_file_save_new_token(const TokenInfo* token_info); + +/** + * @brief Updates timezone offset in an application config file + * @param new_timezone_offset new timezone offset to be set + * @return Config file update result + */ +TotpConfigFileUpdateResult totp_config_file_update_timezone_offset(float new_timezone_offset); + +/** + * @brief Updates notification method in an application config file + * @param new_notification_method new notification method to be set + * @return Config file update result + */ +TotpConfigFileUpdateResult + totp_config_file_update_notification_method(NotificationMethod new_notification_method); + +/** + * @brief Updates application user settings + * @param plugin_state application state + * @return Config file update result + */ +TotpConfigFileUpdateResult totp_config_file_update_user_settings(const PluginState* plugin_state); + +/** + * @brief Updates crypto signatures information + * @param plugin_state application state + * @return Config file update result + */ +TotpConfigFileUpdateResult + totp_config_file_update_crypto_signatures(const PluginState* plugin_state); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/services/config/constants.h b/Applications/Official/DEV_FW/source/totp/services/config/constants.h new file mode 100644 index 000000000..696ea1593 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/services/config/constants.h @@ -0,0 +1,18 @@ +#pragma once + +#define CONFIG_FILE_HEADER "Flipper TOTP plugin config file" +#define CONFIG_FILE_ACTUAL_VERSION 2 + +#define TOTP_CONFIG_KEY_TIMEZONE "Timezone" +#define TOTP_CONFIG_KEY_TOKEN_NAME "TokenName" +#define TOTP_CONFIG_KEY_TOKEN_SECRET "TokenSecret" +#define TOTP_CONFIG_KEY_TOKEN_ALGO "TokenAlgo" +#define TOTP_CONFIG_KEY_TOKEN_DIGITS "TokenDigits" +#define TOTP_CONFIG_KEY_CRYPTO_VERIFY "Crypto" +#define TOTP_CONFIG_KEY_BASE_IV "BaseIV" +#define TOTP_CONFIG_KEY_PINSET "PinIsSet" +#define TOTP_CONFIG_KEY_NOTIFICATION_METHOD "NotificationMethod" + +#define TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME "sha1" +#define TOTP_CONFIG_TOKEN_ALGO_SHA256_NAME "sha256" +#define TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME "sha512" diff --git a/Applications/Official/DEV_FW/source/totp/services/config/migrations/config_migration_v1_to_v2.c b/Applications/Official/DEV_FW/source/totp/services/config/migrations/config_migration_v1_to_v2.c new file mode 100644 index 000000000..1ed8c37f0 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/services/config/migrations/config_migration_v1_to_v2.c @@ -0,0 +1,46 @@ +#include "config_migration_v1_to_v2.h" +#include +#include "../constants.h" + +#define NEW_VERSION 2 + +bool totp_config_migrate_v1_to_v2( + FlipperFormat* fff_data_file, + FlipperFormat* fff_backup_data_file) { + flipper_format_write_header_cstr(fff_data_file, CONFIG_FILE_HEADER, NEW_VERSION); + + FuriString* temp_str = furi_string_alloc(); + + if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_BASE_IV, temp_str)) { + flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_BASE_IV, temp_str); + } + + if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_CRYPTO_VERIFY, temp_str)) { + flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_CRYPTO_VERIFY, temp_str); + } + + if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_TIMEZONE, temp_str)) { + flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TIMEZONE, temp_str); + } + + while(true) { + if(!flipper_format_read_string( + fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_NAME, temp_str)) { + break; + } + + flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_NAME, temp_str); + + flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str); + flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str); + + flipper_format_write_string_cstr( + fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME); + uint32_t default_digits = 6; + flipper_format_write_uint32( + fff_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, &default_digits, 1); + } + + furi_string_free(temp_str); + return true; +} diff --git a/Applications/Official/DEV_FW/source/totp/services/config/migrations/config_migration_v1_to_v2.h b/Applications/Official/DEV_FW/source/totp/services/config/migrations/config_migration_v1_to_v2.h new file mode 100644 index 000000000..99470f04d --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/services/config/migrations/config_migration_v1_to_v2.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +bool totp_config_migrate_v1_to_v2( + FlipperFormat* fff_data_file, + FlipperFormat* fff_backup_data_file); diff --git a/Applications/Official/DEV_FW/source/totp/services/convert/convert.h b/Applications/Official/DEV_FW/source/totp/services/convert/convert.h new file mode 100644 index 000000000..740d47ace --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/services/convert/convert.h @@ -0,0 +1,4 @@ +#pragma once + +#define CONVERT_DIGIT_TO_CHAR(digit) ((digit) + '0') +#define CONVERT_CHAR_TO_DIGIT(ch) ((ch) - '0') diff --git a/Applications/Official/DEV_FW/source/totp/services/crypto/crypto.c b/Applications/Official/DEV_FW/source/totp/services/crypto/crypto.c new file mode 100644 index 000000000..ed4775dfb --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/services/crypto/crypto.c @@ -0,0 +1,133 @@ +#include "crypto.h" +#include +#include +#include "../config/config.h" +#include "../../types/common.h" +#include "memset_s.h" + +#define CRYPTO_KEY_SLOT 2 +#define CRYPTO_VERIFY_KEY "FFF_Crypto_pass" +#define CRYPTO_VERIFY_KEY_LENGTH 16 +#define CRYPTO_ALIGNMENT_FACTOR 16 + +uint8_t* totp_crypto_encrypt( + const uint8_t* plain_data, + const size_t plain_data_length, + const uint8_t* iv, + size_t* encrypted_data_length) { + uint8_t* encrypted_data; + size_t remain = plain_data_length % CRYPTO_ALIGNMENT_FACTOR; + if(remain) { + size_t plain_data_aligned_length = plain_data_length - remain + CRYPTO_ALIGNMENT_FACTOR; + uint8_t* plain_data_aligned = malloc(plain_data_aligned_length); + furi_check(plain_data_aligned != NULL); + memset(plain_data_aligned, 0, plain_data_aligned_length); + memcpy(plain_data_aligned, plain_data, plain_data_length); + + encrypted_data = malloc(plain_data_aligned_length); + furi_check(encrypted_data != NULL); + *encrypted_data_length = plain_data_aligned_length; + + furi_hal_crypto_store_load_key(CRYPTO_KEY_SLOT, iv); + furi_hal_crypto_encrypt(plain_data_aligned, encrypted_data, plain_data_aligned_length); + furi_hal_crypto_store_unload_key(CRYPTO_KEY_SLOT); + + memset_s(plain_data_aligned, plain_data_aligned_length, 0, plain_data_aligned_length); + free(plain_data_aligned); + } else { + encrypted_data = malloc(plain_data_length); + furi_check(encrypted_data != NULL); + *encrypted_data_length = plain_data_length; + + furi_hal_crypto_store_load_key(CRYPTO_KEY_SLOT, iv); + furi_hal_crypto_encrypt(plain_data, encrypted_data, plain_data_length); + furi_hal_crypto_store_unload_key(CRYPTO_KEY_SLOT); + } + + return encrypted_data; +} + +uint8_t* totp_crypto_decrypt( + const uint8_t* encrypted_data, + const size_t encrypted_data_length, + const uint8_t* iv, + size_t* decrypted_data_length) { + *decrypted_data_length = encrypted_data_length; + uint8_t* decrypted_data = malloc(*decrypted_data_length); + furi_check(decrypted_data != NULL); + furi_hal_crypto_store_load_key(CRYPTO_KEY_SLOT, iv); + furi_hal_crypto_decrypt(encrypted_data, decrypted_data, encrypted_data_length); + furi_hal_crypto_store_unload_key(CRYPTO_KEY_SLOT); + return decrypted_data; +} + +bool totp_crypto_seed_iv(PluginState* plugin_state, const uint8_t* pin, uint8_t pin_length) { + if(plugin_state->crypto_verify_data == NULL) { + FURI_LOG_D(LOGGING_TAG, "Generating new IV"); + furi_hal_random_fill_buf(&plugin_state->base_iv[0], TOTP_IV_SIZE); + } + + memcpy(&plugin_state->iv[0], &plugin_state->base_iv[0], TOTP_IV_SIZE); + if(pin != NULL && pin_length > 0) { + uint8_t max_i; + if(pin_length > TOTP_IV_SIZE) { + max_i = TOTP_IV_SIZE; + } else { + max_i = pin_length; + } + + for(uint8_t i = 0; i < max_i; i++) { + plugin_state->iv[i] = plugin_state->iv[i] ^ (uint8_t)(pin[i] * (i + 1)); + } + } else { + uint8_t max_i; + size_t uid_size = furi_hal_version_uid_size(); + if(uid_size > TOTP_IV_SIZE) { + max_i = TOTP_IV_SIZE; + } else { + max_i = uid_size; + } + + const uint8_t* uid = furi_hal_version_uid(); + for(uint8_t i = 0; i < max_i; i++) { + plugin_state->iv[i] = plugin_state->iv[i] ^ uid[i]; + } + } + + bool result = true; + if(plugin_state->crypto_verify_data == NULL) { + FURI_LOG_D(LOGGING_TAG, "Generating crypto verify data"); + plugin_state->crypto_verify_data = malloc(CRYPTO_VERIFY_KEY_LENGTH); + furi_check(plugin_state->crypto_verify_data != NULL); + plugin_state->crypto_verify_data_length = CRYPTO_VERIFY_KEY_LENGTH; + + plugin_state->crypto_verify_data = totp_crypto_encrypt( + (uint8_t*)CRYPTO_VERIFY_KEY, + CRYPTO_VERIFY_KEY_LENGTH, + &plugin_state->iv[0], + &plugin_state->crypto_verify_data_length); + + plugin_state->pin_set = pin != NULL && pin_length > 0; + + result = totp_config_file_update_crypto_signatures(plugin_state) == + TotpConfigFileUpdateSuccess; + } + + return result; +} + +bool totp_crypto_verify_key(const PluginState* plugin_state) { + size_t decrypted_key_length; + const uint8_t* decrypted_key = totp_crypto_decrypt( + plugin_state->crypto_verify_data, + plugin_state->crypto_verify_data_length, + &plugin_state->iv[0], + &decrypted_key_length); + + bool key_valid = true; + for(uint8_t i = 0; i < CRYPTO_VERIFY_KEY_LENGTH && key_valid; i++) { + if(decrypted_key[i] != CRYPTO_VERIFY_KEY[i]) key_valid = false; + } + + return key_valid; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/services/crypto/crypto.h b/Applications/Official/DEV_FW/source/totp/services/crypto/crypto.h new file mode 100644 index 000000000..3442b9a6e --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/services/crypto/crypto.h @@ -0,0 +1,47 @@ +#pragma once + +#include "../../types/plugin_state.h" + +/** + * @brief Encrypts plain data using built-in certificate and given initialization vector (IV) + * @param plain_data plain data to be encrypted + * @param plain_data_length plain data length + * @param iv initialization vector (IV) to be used to encrypt plain data + * @param[out] encrypted_data_length encrypted data length + * @return Encrypted data + */ +uint8_t* totp_crypto_encrypt( + const uint8_t* plain_data, + const size_t plain_data_length, + const uint8_t* iv, + size_t* encrypted_data_length); + +/** + * @brief Decrypts encrypted data using built-in certificate and given initialization vector (IV) + * @param encrypted_data encrypted data to be decrypted + * @param encrypted_data_length encrypted data length + * @param iv initialization vector (IV) to be used to encrypt plain data + * @param[out] decrypted_data_length decrypted data length + * @return Decrypted data + */ +uint8_t* totp_crypto_decrypt( + const uint8_t* encrypted_data, + const size_t encrypted_data_length, + const uint8_t* iv, + size_t* decrypted_data_length); + +/** + * @brief Seed initialization vector (IV) using user's PIN + * @param plugin_state application state + * @param pin user's PIN + * @param pin_length user's PIN length + * @return \c true on success; \c false otherwise + */ +bool totp_crypto_seed_iv(PluginState* plugin_state, const uint8_t* pin, uint8_t pin_length); + +/** + * @brief Verifies whether cryptographic information (certificate + IV) is valid and can be used for encryption and decryption + * @param plugin_state application state + * @return \c true if cryptographic information is valid; \c false otherwise + */ +bool totp_crypto_verify_key(const PluginState* plugin_state); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/services/hmac/byteswap.c b/Applications/Official/DEV_FW/source/totp/services/hmac/byteswap.c new file mode 100644 index 000000000..e922deec0 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/services/hmac/byteswap.c @@ -0,0 +1,12 @@ +#include "byteswap.h" + +uint32_t swap_uint32(uint32_t val) { + val = ((val << 8) & 0xFF00FF00) | ((val >> 8) & 0xFF00FF); + return (val << 16) | (val >> 16); +} + +uint64_t swap_uint64(uint64_t val) { + val = ((val << 8) & 0xFF00FF00FF00FF00ULL) | ((val >> 8) & 0x00FF00FF00FF00FFULL); + val = ((val << 16) & 0xFFFF0000FFFF0000ULL) | ((val >> 16) & 0x0000FFFF0000FFFFULL); + return (val << 32) | (val >> 32); +} diff --git a/Applications/Official/DEV_FW/source/totp/services/hmac/byteswap.h b/Applications/Official/DEV_FW/source/totp/services/hmac/byteswap.h new file mode 100644 index 000000000..2e3f1743f --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/services/hmac/byteswap.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +/** + * @brief Swap bytes in 32-bit value + * @param val value to swap bytes in + * @return Value with bytes swapped + */ +uint32_t swap_uint32(uint32_t val); + +/** + * @brief Swap bytes in 64-bit value + * @param val value to swap bytes in + * @return Value with bytes swapped + */ +uint64_t swap_uint64(uint64_t val); diff --git a/Applications/Official/DEV_FW/source/totp/services/hmac/hmac_common.h b/Applications/Official/DEV_FW/source/totp/services/hmac/hmac_common.h new file mode 100644 index 000000000..9c5b5828f --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/services/hmac/hmac_common.h @@ -0,0 +1,64 @@ +#include +#include "sha256.h" +#include "memxor.h" + +#define IPAD 0x36 +#define OPAD 0x5c + +/* Concatenate two preprocessor tokens. */ +#define _GLHMAC_CONCAT_(prefix, suffix) prefix##suffix +#define _GLHMAC_CONCAT(prefix, suffix) _GLHMAC_CONCAT_(prefix, suffix) + +#if GL_HMAC_NAME == 5 +#define HMAC_ALG md5 +#else +#define HMAC_ALG _GLHMAC_CONCAT(sha, GL_HMAC_NAME) +#endif + +#define GL_HMAC_CTX _GLHMAC_CONCAT(HMAC_ALG, _ctx) +#define GL_HMAC_FN _GLHMAC_CONCAT(hmac_, HMAC_ALG) +#define GL_HMAC_FN_INIT _GLHMAC_CONCAT(HMAC_ALG, _init_ctx) +#define GL_HMAC_FN_BLOC _GLHMAC_CONCAT(HMAC_ALG, _process_block) +#define GL_HMAC_FN_PROC _GLHMAC_CONCAT(HMAC_ALG, _process_bytes) +#define GL_HMAC_FN_FINI _GLHMAC_CONCAT(HMAC_ALG, _finish_ctx) + +static void + hmac_hash(const void* key, size_t keylen, const void* in, size_t inlen, int pad, void* resbuf) { + struct GL_HMAC_CTX hmac_ctx; + char block[GL_HMAC_BLOCKSIZE]; + + memset(block, pad, sizeof block); + memxor(block, key, keylen); + + GL_HMAC_FN_INIT(&hmac_ctx); + GL_HMAC_FN_BLOC(block, sizeof block, &hmac_ctx); + GL_HMAC_FN_PROC(in, inlen, &hmac_ctx); + GL_HMAC_FN_FINI(&hmac_ctx, resbuf); +} + +int GL_HMAC_FN(const void* key, size_t keylen, const void* in, size_t inlen, void* resbuf) { + char optkeybuf[GL_HMAC_HASHSIZE]; + char innerhash[GL_HMAC_HASHSIZE]; + + /* Ensure key size is <= block size. */ + if(keylen > GL_HMAC_BLOCKSIZE) { + struct GL_HMAC_CTX keyhash; + + GL_HMAC_FN_INIT(&keyhash); + GL_HMAC_FN_PROC(key, keylen, &keyhash); + GL_HMAC_FN_FINI(&keyhash, optkeybuf); + + key = optkeybuf; + /* zero padding of the key to the block size + is implicit in the memxor. */ + keylen = sizeof optkeybuf; + } + + /* Compute INNERHASH from KEY and IN. */ + hmac_hash(key, keylen, in, inlen, IPAD, innerhash); + + /* Compute result from KEY and INNERHASH. */ + hmac_hash(key, keylen, innerhash, sizeof innerhash, OPAD, resbuf); + + return 0; +} diff --git a/Applications/Official/DEV_FW/source/totp/services/hmac/hmac_sha1.c b/Applications/Official/DEV_FW/source/totp/services/hmac/hmac_sha1.c new file mode 100644 index 000000000..0a78d569a --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/services/hmac/hmac_sha1.c @@ -0,0 +1,24 @@ +/* hmac-sha1.c -- hashed message authentication codes + Copyright (C) 2018-2022 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +#include "hmac_sha1.h" + +#include "sha1.h" + +#define GL_HMAC_NAME 1 +#define GL_HMAC_BLOCKSIZE 64 +#define GL_HMAC_HASHSIZE 20 +#include "hmac_common.h" diff --git a/Applications/Official/DEV_FW/source/totp/services/hmac/hmac_sha1.h b/Applications/Official/DEV_FW/source/totp/services/hmac/hmac_sha1.h new file mode 100644 index 000000000..25ff2f648 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/services/hmac/hmac_sha1.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#define HMAC_SHA1_RESULT_SIZE 20 + +/* Compute Hashed Message Authentication Code with SHA-1, over BUFFER + data of BUFLEN bytes using the KEY of KEYLEN bytes, writing the + output to pre-allocated 20 byte minimum RESBUF buffer. Return 0 on + success. */ +int hmac_sha1(const void* key, size_t keylen, const void* in, size_t inlen, void* restrict resbuf); diff --git a/Applications/Official/DEV_FW/source/totp/services/hmac/hmac_sha256.c b/Applications/Official/DEV_FW/source/totp/services/hmac/hmac_sha256.c new file mode 100644 index 000000000..c51f24b4d --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/services/hmac/hmac_sha256.c @@ -0,0 +1,23 @@ +/* hmac-sha256.c -- hashed message authentication codes + Copyright (C) 2018-2022 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +#include "hmac_sha256.h" + +#define GL_HMAC_NAME 256 +#define GL_HMAC_BLOCKSIZE 64 +#define GL_HMAC_HASHSIZE 32 + +#include "hmac_common.h" diff --git a/Applications/Official/DEV_FW/source/totp/services/hmac/hmac_sha256.h b/Applications/Official/DEV_FW/source/totp/services/hmac/hmac_sha256.h new file mode 100644 index 000000000..9aeaf10d6 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/services/hmac/hmac_sha256.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#define HMAC_SHA256_RESULT_SIZE 32 + +/* Compute Hashed Message Authentication Code with SHA-256, over BUFFER + data of BUFLEN bytes using the KEY of KEYLEN bytes, writing the + output to pre-allocated 32 byte minimum RESBUF buffer. Return 0 on + success. */ +int hmac_sha256(const void* key, size_t keylen, const void* in, size_t inlen, void* restrict resbuf); diff --git a/Applications/Official/DEV_FW/source/totp/services/hmac/hmac_sha512.c b/Applications/Official/DEV_FW/source/totp/services/hmac/hmac_sha512.c new file mode 100644 index 000000000..dc9342a91 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/services/hmac/hmac_sha512.c @@ -0,0 +1,24 @@ +/* hmac-sha512.c -- hashed message authentication codes + Copyright (C) 2018-2022 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +#include "hmac_sha512.h" + +#include "sha512.h" + +#define GL_HMAC_NAME 512 +#define GL_HMAC_BLOCKSIZE 128 +#define GL_HMAC_HASHSIZE 64 +#include "hmac_common.h" diff --git a/Applications/Official/DEV_FW/source/totp/services/hmac/hmac_sha512.h b/Applications/Official/DEV_FW/source/totp/services/hmac/hmac_sha512.h new file mode 100644 index 000000000..712b7f4a0 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/services/hmac/hmac_sha512.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#define HMAC_SHA512_RESULT_SIZE 64 + +/* Compute Hashed Message Authentication Code with SHA-512, over BUFFER + data of BUFLEN bytes using the KEY of KEYLEN bytes, writing the + output to pre-allocated 64 byte minimum RESBUF buffer. Return 0 on + success. */ +int hmac_sha512(const void* key, size_t keylen, const void* in, size_t inlen, void* restrict resbuf); diff --git a/Applications/Official/DEV_FW/source/totp/services/hmac/memxor.c b/Applications/Official/DEV_FW/source/totp/services/hmac/memxor.c new file mode 100644 index 000000000..ab6026aa3 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/services/hmac/memxor.c @@ -0,0 +1,30 @@ +/* memxor.c -- perform binary exclusive OR operation of two memory blocks. + Copyright (C) 2005, 2006 Free Software Foundation, Inc. + + 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 2, 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, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Simon Josefsson. The interface was inspired by memxor + in Niels Möller's Nettle. */ + +#include "memxor.h" + +void* memxor(void* /*restrict*/ dest, const void* /*restrict*/ src, size_t n) { + char const* s = (char const*)src; + char* d = (char*)dest; + + for(; n > 0; n--) *d++ ^= *s++; + + return dest; +} diff --git a/Applications/Official/DEV_FW/source/totp/services/hmac/memxor.h b/Applications/Official/DEV_FW/source/totp/services/hmac/memxor.h new file mode 100644 index 000000000..71aa604c8 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/services/hmac/memxor.h @@ -0,0 +1,28 @@ +/* memxor.h -- perform binary exclusive OR operation on memory blocks. + Copyright (C) 2005 Free Software Foundation, Inc. + + 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 2, 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, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Simon Josefsson. The interface was inspired by memxor + in Niels Möller's Nettle. */ + +#pragma once + +#include + +/* Compute binary exclusive OR of memory areas DEST and SRC, putting + the result in DEST, of length N bytes. Returns a pointer to + DEST. */ +void* memxor(void* /*restrict*/ dest, const void* /*restrict*/ src, size_t n); diff --git a/Applications/Official/DEV_FW/source/totp/services/hmac/sha1.c b/Applications/Official/DEV_FW/source/totp/services/hmac/sha1.c new file mode 100644 index 000000000..243a6dde8 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/services/hmac/sha1.c @@ -0,0 +1,269 @@ +/* sha1.c - Functions to compute SHA1 message digest of files or + memory blocks according to the NIST specification FIPS-180-1. + + Copyright (C) 2000-2001, 2003-2006, 2008-2022 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +/* Written by Scott G. Miller + Credits: + Robert Klep -- Expansion function fix +*/ + +/* Specification. */ +#include "sha1.h" + +#include +#include + +#ifdef WORDS_BIGENDIAN +#define SWAP(n) (n) +#else +#include "byteswap.h" +#define SWAP(n) swap_uint32(n) +#endif + +/* This array contains the bytes used to pad the buffer to the next + 64-byte boundary. (RFC 1321, 3.1: Step 1) */ +static const unsigned char fillbuf[64] = {0x80, 0 /* , 0, 0, ... */}; + +/* Take a pointer to a 160 bit block of data (five 32 bit ints) and + initialize it to the start constants of the SHA1 algorithm. This + must be called before using hash in the call to sha1_hash. */ +void sha1_init_ctx(struct sha1_ctx* ctx) { + ctx->A = 0x67452301; + ctx->B = 0xefcdab89; + ctx->C = 0x98badcfe; + ctx->D = 0x10325476; + ctx->E = 0xc3d2e1f0; + + ctx->total[0] = ctx->total[1] = 0; + ctx->buflen = 0; +} + +/* Copy the 4 byte value from v into the memory location pointed to by *cp, + If your architecture allows unaligned access this is equivalent to + * (uint32_t *) cp = v */ +static void set_uint32(char* cp, uint32_t v) { + memcpy(cp, &v, sizeof v); +} + +/* Put result from CTX in first 20 bytes following RESBUF. The result + must be in little endian byte order. */ +void* sha1_read_ctx(const struct sha1_ctx* ctx, void* resbuf) { + char* r = resbuf; + set_uint32(r + 0 * sizeof ctx->A, SWAP(ctx->A)); + set_uint32(r + 1 * sizeof ctx->B, SWAP(ctx->B)); + set_uint32(r + 2 * sizeof ctx->C, SWAP(ctx->C)); + set_uint32(r + 3 * sizeof ctx->D, SWAP(ctx->D)); + set_uint32(r + 4 * sizeof ctx->E, SWAP(ctx->E)); + + return resbuf; +} + +/* Process the remaining bytes in the internal buffer and the usual + prolog according to the standard and write the result to RESBUF. */ +void* sha1_finish_ctx(struct sha1_ctx* ctx, void* resbuf) { + /* Take yet unprocessed bytes into account. */ + uint32_t bytes = ctx->buflen; + size_t size = (bytes < 56) ? 64 / 4 : 64 * 2 / 4; + + /* Now count remaining bytes. */ + ctx->total[0] += bytes; + if(ctx->total[0] < bytes) ++ctx->total[1]; + + /* Put the 64-bit file length in *bits* at the end of the buffer. */ + ctx->buffer[size - 2] = SWAP((ctx->total[1] << 3) | (ctx->total[0] >> 29)); + ctx->buffer[size - 1] = SWAP(ctx->total[0] << 3); + + memcpy(&((char*)ctx->buffer)[bytes], fillbuf, (size - 2) * 4 - bytes); + + /* Process last bytes. */ + sha1_process_block(ctx->buffer, size * 4, ctx); + + return sha1_read_ctx(ctx, resbuf); +} + +/* Compute SHA1 message digest for LEN bytes beginning at BUFFER. The + result is always in little endian byte order, so that a byte-wise + output yields to the wanted ASCII representation of the message + digest. */ +void* sha1_buffer(const char* buffer, size_t len, void* resblock) { + struct sha1_ctx ctx; + + /* Initialize the computation context. */ + sha1_init_ctx(&ctx); + + /* Process whole buffer but last len % 64 bytes. */ + sha1_process_bytes(buffer, len, &ctx); + + /* Put result in desired memory area. */ + return sha1_finish_ctx(&ctx, resblock); +} + +void sha1_process_bytes(const void* buffer, size_t len, struct sha1_ctx* ctx) { + /* When we already have some bits in our internal buffer concatenate + both inputs first. */ + if(ctx->buflen != 0) { + size_t left_over = ctx->buflen; + size_t add = 128 - left_over > len ? len : 128 - left_over; + + memcpy(&((char*)ctx->buffer)[left_over], buffer, add); + ctx->buflen += add; + + if(ctx->buflen > 64) { + sha1_process_block(ctx->buffer, ctx->buflen & ~63, ctx); + + ctx->buflen &= 63; + /* The regions in the following copy operation cannot overlap, + because ctx->buflen < 64 ≤ (left_over + add) & ~63. */ + memcpy(ctx->buffer, &((char*)ctx->buffer)[(left_over + add) & ~63], ctx->buflen); + } + + buffer = (const char*)buffer + add; + len -= add; + } + + /* Process available complete blocks. */ + if(len >= 64) { +#if !(_STRING_ARCH_unaligned || _STRING_INLINE_unaligned) +#define UNALIGNED_P(p) ((uintptr_t)(p) % sizeof(uint32_t) != 0) + if(UNALIGNED_P(buffer)) + while(len > 64) { + sha1_process_block(memcpy(ctx->buffer, buffer, 64), 64, ctx); //-V1086 + buffer = (const char*)buffer + 64; + len -= 64; + } + else +#endif + { + sha1_process_block(buffer, len & ~63, ctx); + buffer = (const char*)buffer + (len & ~63); + len &= 63; + } + } + + /* Move remaining bytes in internal buffer. */ + if(len > 0) { + size_t left_over = ctx->buflen; + + memcpy(&((char*)ctx->buffer)[left_over], buffer, len); + left_over += len; + if(left_over >= 64) { + sha1_process_block(ctx->buffer, 64, ctx); + left_over -= 64; + /* The regions in the following copy operation cannot overlap, + because left_over ≤ 64. */ + memcpy(ctx->buffer, &ctx->buffer[16], left_over); + } + ctx->buflen = left_over; + } +} + +/* --- Code below is the primary difference between md5.c and sha1.c --- */ + +/* SHA1 round constants */ +#define K1 0x5a827999 +#define K2 0x6ed9eba1 +#define K3 0x8f1bbcdc +#define K4 0xca62c1d6 + +/* Round functions. Note that F2 is the same as F4. */ +#define F1(B, C, D) (D ^ (B & (C ^ D))) +#define F2(B, C, D) (B ^ C ^ D) +#define F3(B, C, D) ((B & C) | (D & (B | C))) +#define F4(B, C, D) (B ^ C ^ D) + +/* Process LEN bytes of BUFFER, accumulating context into CTX. + It is assumed that LEN % 64 == 0. + Most of this code comes from GnuPG's cipher/sha1.c. */ + +void sha1_process_block(const void* buffer, size_t len, struct sha1_ctx* ctx) { + const uint32_t* words = buffer; + size_t nwords = len / sizeof(uint32_t); + const uint32_t* endp = words + nwords; + uint32_t x[16]; + uint32_t a = ctx->A; + uint32_t b = ctx->B; + uint32_t c = ctx->C; + uint32_t d = ctx->D; + uint32_t e = ctx->E; + uint32_t lolen = len; + + /* First increment the byte count. RFC 1321 specifies the possible + length of the file up to 2^64 bits. Here we only compute the + number of bytes. Do a double word increment. */ + ctx->total[0] += lolen; + ctx->total[1] += (len >> 31 >> 1) + (ctx->total[0] < lolen); + +#define rol(x, n) (((x) << (n)) | ((uint32_t)(x) >> (32 - (n)))) + +#define M(I) \ + (tm = x[I & 0x0f] ^ x[(I - 14) & 0x0f] ^ x[(I - 8) & 0x0f] ^ x[(I - 3) & 0x0f], \ + (x[I & 0x0f] = rol(tm, 1))) + +#define R(A, B, C, D, E, F, K, M) \ + do { \ + E += rol(A, 5) + F(B, C, D) + K + M; \ + B = rol(B, 30); \ + } while(0) + + while(words < endp) { + uint32_t tm; + int t; + for(t = 0; t < 16; t++) { + x[t] = SWAP(*words); + words++; + } + + for(int i = 0; i < 80; i++) { + uint32_t xx = i < 16 ? x[i] : M(i); + uint32_t ki = i / 20; + switch(ki) { + case 0: + R(a, b, c, d, e, F1, K1, xx); + break; + case 1: + R(a, b, c, d, e, F2, K2, xx); + break; + case 2: + R(a, b, c, d, e, F3, K3, xx); + break; + default: + R(a, b, c, d, e, F4, K4, xx); + break; + } + + uint32_t tt = a; + a = e; + e = d; + d = c; + c = b; + b = tt; + } + + a = ctx->A += a; + b = ctx->B += b; + c = ctx->C += c; + d = ctx->D += d; + e = ctx->E += e; + } +} + +/* + * Hey Emacs! + * Local Variables: + * coding: utf-8 + * End: + */ diff --git a/Applications/Official/DEV_FW/source/totp/services/hmac/sha1.h b/Applications/Official/DEV_FW/source/totp/services/hmac/sha1.h new file mode 100644 index 000000000..e9eb7712a --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/services/hmac/sha1.h @@ -0,0 +1,84 @@ +/* Declarations of functions and data types used for SHA1 sum + library functions. + Copyright (C) 2000-2001, 2003, 2005-2006, 2008-2022 Free Software + Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define SHA1_DIGEST_SIZE 20 + +/* Structure to save state of computation between the single steps. */ +struct sha1_ctx { + uint32_t A; + uint32_t B; + uint32_t C; + uint32_t D; + uint32_t E; + + uint32_t total[2]; + uint32_t buflen; /* ≥ 0, ≤ 128 */ + uint32_t buffer[32]; /* 128 bytes; the first buflen bytes are in use */ +}; + +/* Initialize structure containing state of computation. */ +extern void sha1_init_ctx(struct sha1_ctx* ctx); + +/* Starting with the result of former calls of this function (or the + initialization function update the context for the next LEN bytes + starting at BUFFER. + It is necessary that LEN is a multiple of 64!!! */ +extern void sha1_process_block(const void* buffer, size_t len, struct sha1_ctx* ctx); + +/* Starting with the result of former calls of this function (or the + initialization function update the context for the next LEN bytes + starting at BUFFER. + It is NOT required that LEN is a multiple of 64. */ +extern void sha1_process_bytes(const void* buffer, size_t len, struct sha1_ctx* ctx); + +/* Process the remaining bytes in the buffer and put result from CTX + in first 20 bytes following RESBUF. The result is always in little + endian byte order, so that a byte-wise output yields to the wanted + ASCII representation of the message digest. */ +extern void* sha1_finish_ctx(struct sha1_ctx* ctx, void* restrict resbuf); + +/* Put result from CTX in first 20 bytes following RESBUF. The result is + always in little endian byte order, so that a byte-wise output yields + to the wanted ASCII representation of the message digest. */ +extern void* sha1_read_ctx(const struct sha1_ctx* ctx, void* restrict resbuf); + +/* Compute SHA1 message digest for LEN bytes beginning at BUFFER. The + result is always in little endian byte order, so that a byte-wise + output yields to the wanted ASCII representation of the message + digest. */ +extern void* sha1_buffer(const char* buffer, size_t len, void* restrict resblock); + +#ifdef __cplusplus +} +#endif + +/* + * Hey Emacs! + * Local Variables: + * coding: utf-8 + * End: + */ diff --git a/Applications/Official/DEV_FW/source/totp/services/hmac/sha256.c b/Applications/Official/DEV_FW/source/totp/services/hmac/sha256.c new file mode 100644 index 000000000..89ca67c2b --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/services/hmac/sha256.c @@ -0,0 +1,286 @@ +/* sha256.c - Functions to compute SHA256 message digest of files or + memory blocks according to the NIST specification FIPS-180-2. + + Copyright (C) 2005-2006, 2008-2022 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +/* Written by David Madore, considerably copypasting from + Scott G. Miller's sha1.c +*/ + +/* Specification. */ +#include "sha256.h" + +#include +#include + +#ifdef WORDS_BIGENDIAN +#define SWAP(n) (n) +#else +#include "byteswap.h" +#define SWAP(n) swap_uint32(n) +#endif + +/* This array contains the bytes used to pad the buffer to the next + 64-byte boundary. */ +static const unsigned char fillbuf[64] = {0x80, 0 /* , 0, 0, ... */}; + +/* + Takes a pointer to a 256 bit block of data (eight 32 bit ints) and + initializes it to the start constants of the SHA256 algorithm. This + must be called before using hash in the call to sha256_hash +*/ +void sha256_init_ctx(struct sha256_ctx* ctx) { + ctx->state[0] = 0x6a09e667UL; + ctx->state[1] = 0xbb67ae85UL; + ctx->state[2] = 0x3c6ef372UL; + ctx->state[3] = 0xa54ff53aUL; + ctx->state[4] = 0x510e527fUL; + ctx->state[5] = 0x9b05688cUL; + ctx->state[6] = 0x1f83d9abUL; + ctx->state[7] = 0x5be0cd19UL; + + ctx->total[0] = ctx->total[1] = 0; + ctx->buflen = 0; +} + +/* Copy the value from v into the memory location pointed to by *CP, + If your architecture allows unaligned access, this is equivalent to + * (__typeof__ (v) *) cp = v */ +static void set_uint32(char* cp, uint32_t v) { + memcpy(cp, &v, sizeof v); +} + +/* Put result from CTX in first 32 bytes following RESBUF. + The result must be in little endian byte order. */ +void* sha256_read_ctx(const struct sha256_ctx* ctx, void* resbuf) { + int i; + char* r = resbuf; + + for(i = 0; i < 8; i++) set_uint32(r + i * sizeof ctx->state[0], SWAP(ctx->state[i])); + + return resbuf; +} + +/* Process the remaining bytes in the internal buffer and the usual + prolog according to the standard and write the result to RESBUF. */ +static void sha256_conclude_ctx(struct sha256_ctx* ctx) { + /* Take yet unprocessed bytes into account. */ + size_t bytes = ctx->buflen; + size_t size = (bytes < 56) ? 64 / 4 : 64 * 2 / 4; + + /* Now count remaining bytes. */ + ctx->total[0] += bytes; + if(ctx->total[0] < bytes) ++ctx->total[1]; + + /* Put the 64-bit file length in *bits* at the end of the buffer. + Use set_uint32 rather than a simple assignment, to avoid risk of + unaligned access. */ + set_uint32((char*)&ctx->buffer[size - 2], SWAP((ctx->total[1] << 3) | (ctx->total[0] >> 29))); + set_uint32((char*)&ctx->buffer[size - 1], SWAP(ctx->total[0] << 3)); + + memcpy(&((char*)ctx->buffer)[bytes], fillbuf, (size - 2) * 4 - bytes); + + /* Process last bytes. */ + sha256_process_block(ctx->buffer, size * 4, ctx); +} + +void* sha256_finish_ctx(struct sha256_ctx* ctx, void* resbuf) { + sha256_conclude_ctx(ctx); + return sha256_read_ctx(ctx, resbuf); +} + +/* Compute SHA256 message digest for LEN bytes beginning at BUFFER. The + result is always in little endian byte order, so that a byte-wise + output yields to the wanted ASCII representation of the message + digest. */ +void* sha256_buffer(const char* buffer, size_t len, void* resblock) { + struct sha256_ctx ctx; + + /* Initialize the computation context. */ + sha256_init_ctx(&ctx); + + /* Process whole buffer but last len % 64 bytes. */ + sha256_process_bytes(buffer, len, &ctx); + + /* Put result in desired memory area. */ + return sha256_finish_ctx(&ctx, resblock); +} + +void sha256_process_bytes(const void* buffer, size_t len, struct sha256_ctx* ctx) { + /* When we already have some bits in our internal buffer concatenate + both inputs first. */ + if(ctx->buflen != 0) { + size_t left_over = ctx->buflen; + size_t add = 128 - left_over > len ? len : 128 - left_over; + + memcpy(&((char*)ctx->buffer)[left_over], buffer, add); + ctx->buflen += add; + + if(ctx->buflen > 64) { + sha256_process_block(ctx->buffer, ctx->buflen & ~63, ctx); + + ctx->buflen &= 63; + /* The regions in the following copy operation cannot overlap, + because ctx->buflen < 64 ≤ (left_over + add) & ~63. */ + memcpy(ctx->buffer, &((char*)ctx->buffer)[(left_over + add) & ~63], ctx->buflen); + } + + buffer = (const char*)buffer + add; + len -= add; + } + + /* Process available complete blocks. */ + if(len >= 64) { +#if !(_STRING_ARCH_unaligned || _STRING_INLINE_unaligned) +#define UNALIGNED_P(p) ((uintptr_t)(p) % sizeof(uint32_t) != 0) + if(UNALIGNED_P(buffer)) + while(len > 64) { + sha256_process_block(memcpy(ctx->buffer, buffer, 64), 64, ctx); //-V1086 + buffer = (const char*)buffer + 64; + len -= 64; + } + else +#endif + { + sha256_process_block(buffer, len & ~63, ctx); + buffer = (const char*)buffer + (len & ~63); + len &= 63; + } + } + + /* Move remaining bytes in internal buffer. */ + if(len > 0) { + size_t left_over = ctx->buflen; + + memcpy(&((char*)ctx->buffer)[left_over], buffer, len); + left_over += len; + if(left_over >= 64) { + sha256_process_block(ctx->buffer, 64, ctx); + left_over -= 64; + /* The regions in the following copy operation cannot overlap, + because left_over ≤ 64. */ + memcpy(ctx->buffer, &ctx->buffer[16], left_over); + } + ctx->buflen = left_over; + } +} + +/* --- Code below is the primary difference between sha1.c and sha256.c --- */ + +/* SHA256 round constants */ +#define K(I) sha256_round_constants[I] +static const uint32_t sha256_round_constants[64] = { + 0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, 0x3956c25bUL, 0x59f111f1UL, + 0x923f82a4UL, 0xab1c5ed5UL, 0xd807aa98UL, 0x12835b01UL, 0x243185beUL, 0x550c7dc3UL, + 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL, 0xc19bf174UL, 0xe49b69c1UL, 0xefbe4786UL, + 0x0fc19dc6UL, 0x240ca1ccUL, 0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL, + 0x983e5152UL, 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, 0xc6e00bf3UL, 0xd5a79147UL, + 0x06ca6351UL, 0x14292967UL, 0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL, 0x53380d13UL, + 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL, 0xa2bfe8a1UL, 0xa81a664bUL, + 0xc24b8b70UL, 0xc76c51a3UL, 0xd192e819UL, 0xd6990624UL, 0xf40e3585UL, 0x106aa070UL, + 0x19a4c116UL, 0x1e376c08UL, 0x2748774cUL, 0x34b0bcb5UL, 0x391c0cb3UL, 0x4ed8aa4aUL, + 0x5b9cca4fUL, 0x682e6ff3UL, 0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL, + 0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL, +}; + +/* Round functions. */ +#define F2(A, B, C) ((A & B) | (C & (A | B))) +#define F1(E, F, G) (G ^ (E & (F ^ G))) + +/* Process LEN bytes of BUFFER, accumulating context into CTX. + It is assumed that LEN % 64 == 0. + Most of this code comes from GnuPG's cipher/sha1.c. */ + +void sha256_process_block(const void* buffer, size_t len, struct sha256_ctx* ctx) { + const uint32_t* words = buffer; + size_t nwords = len / sizeof(uint32_t); + const uint32_t* endp = words + nwords; + uint32_t x[16]; + uint32_t a = ctx->state[0]; + uint32_t b = ctx->state[1]; + uint32_t c = ctx->state[2]; + uint32_t d = ctx->state[3]; + uint32_t e = ctx->state[4]; + uint32_t f = ctx->state[5]; + uint32_t g = ctx->state[6]; + uint32_t h = ctx->state[7]; + uint32_t lolen = len; + + /* First increment the byte count. FIPS PUB 180-2 specifies the possible + length of the file up to 2^64 bits. Here we only compute the + number of bytes. Do a double word increment. */ + ctx->total[0] += lolen; + ctx->total[1] += (len >> 31 >> 1) + (ctx->total[0] < lolen); + +#define rol(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) +#define S0(x) (rol(x, 25) ^ rol(x, 14) ^ (x >> 3)) +#define S1(x) (rol(x, 15) ^ rol(x, 13) ^ (x >> 10)) +#define SS0(x) (rol(x, 30) ^ rol(x, 19) ^ rol(x, 10)) +#define SS1(x) (rol(x, 26) ^ rol(x, 21) ^ rol(x, 7)) + +#define M(I) \ + (tm = S1(x[(I - 2) & 0x0f]) + x[(I - 7) & 0x0f] + S0(x[(I - 15) & 0x0f]) + x[I & 0x0f], \ + x[I & 0x0f] = tm) + +#define R(A, B, C, D, E, F, G, H, K, M) \ + do { \ + t0 = SS0(A) + F2(A, B, C); \ + t1 = H + SS1(E) + F1(E, F, G) + K + M; \ + D += t1; \ + H = t0 + t1; \ + } while(0) + + while(words < endp) { + uint32_t tm; + uint32_t t0, t1; + int t; + /* FIXME: see sha1.c for a better implementation. */ + for(t = 0; t < 16; t++) { + x[t] = SWAP(*words); + words++; + } + + for(int i = 0; i < 64; i++) { + uint32_t xx = i < 16 ? x[i] : M(i); + R(a, b, c, d, e, f, g, h, K(i), xx); + uint32_t tt = a; + a = h; + h = g; + g = f; + f = e; + e = d; + d = c; + c = b; + b = tt; + } + + a = ctx->state[0] += a; + b = ctx->state[1] += b; + c = ctx->state[2] += c; + d = ctx->state[3] += d; + e = ctx->state[4] += e; + f = ctx->state[5] += f; + g = ctx->state[6] += g; + h = ctx->state[7] += h; + } +} + +/* + * Hey Emacs! + * Local Variables: + * coding: utf-8 + * End: + */ diff --git a/Applications/Official/DEV_FW/source/totp/services/hmac/sha256.h b/Applications/Official/DEV_FW/source/totp/services/hmac/sha256.h new file mode 100644 index 000000000..964f2eb97 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/services/hmac/sha256.h @@ -0,0 +1,79 @@ +/* Declarations of functions and data types used for SHA256 sum + library functions. + Copyright (C) 2005-2006, 2008-2022 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum { SHA256_DIGEST_SIZE = 256 / 8 }; + +/* Structure to save state of computation between the single steps. */ +struct sha256_ctx { + uint32_t state[8]; + + uint32_t total[2]; + size_t buflen; /* ≥ 0, ≤ 128 */ + uint32_t buffer[32]; /* 128 bytes; the first buflen bytes are in use */ +}; + +/* Initialize structure containing state of computation. */ +extern void sha256_init_ctx(struct sha256_ctx* ctx); + +/* Starting with the result of former calls of this function (or the + initialization function update the context for the next LEN bytes + starting at BUFFER. + It is necessary that LEN is a multiple of 64!!! */ +extern void sha256_process_block(const void* buffer, size_t len, struct sha256_ctx* ctx); + +/* Starting with the result of former calls of this function (or the + initialization function update the context for the next LEN bytes + starting at BUFFER. + It is NOT required that LEN is a multiple of 64. */ +extern void sha256_process_bytes(const void* buffer, size_t len, struct sha256_ctx* ctx); + +/* Process the remaining bytes in the buffer and put result from CTX + in first 32 (28) bytes following RESBUF. The result is always in little + endian byte order, so that a byte-wise output yields to the wanted + ASCII representation of the message digest. */ +extern void* sha256_finish_ctx(struct sha256_ctx* ctx, void* restrict resbuf); + +/* Put result from CTX in first 32 (28) bytes following RESBUF. The result is + always in little endian byte order, so that a byte-wise output yields + to the wanted ASCII representation of the message digest. */ +extern void* sha256_read_ctx(const struct sha256_ctx* ctx, void* restrict resbuf); + +/* Compute SHA256 message digest for LEN bytes beginning at BUFFER. + The result is always in little endian byte order, so that a byte-wise + output yields to the wanted ASCII representation of the message + digest. */ +extern void* sha256_buffer(const char* buffer, size_t len, void* restrict resblock); + +#ifdef __cplusplus +} +#endif + +/* + * Hey Emacs! + * Local Variables: + * coding: utf-8 + * End: + */ diff --git a/Applications/Official/DEV_FW/source/totp/services/hmac/sha512.c b/Applications/Official/DEV_FW/source/totp/services/hmac/sha512.c new file mode 100644 index 000000000..f04c4d593 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/services/hmac/sha512.c @@ -0,0 +1,315 @@ +/* sha512.c - Functions to compute SHA512 message digest of files or + memory blocks according to the NIST specification FIPS-180-2. + + Copyright (C) 2005-2006, 2008-2022 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +/* Written by David Madore, considerably copypasting from + Scott G. Miller's sha1.c +*/ + +/* Specification. */ +#include "sha512.h" + +#include +#include + +#ifdef WORDS_BIGENDIAN +#define SWAP(n) (n) +#else +#include "byteswap.h" +#define SWAP(n) swap_uint64(n) +#endif + +/* This array contains the bytes used to pad the buffer to the next + 128-byte boundary. */ +static const unsigned char fillbuf[128] = {0x80, 0 /* , 0, 0, ... */}; + +/* + Takes a pointer to a 512 bit block of data (eight 64 bit ints) and + initializes it to the start constants of the SHA512 algorithm. This + must be called before using hash in the call to sha512_hash +*/ +void sha512_init_ctx(struct sha512_ctx* ctx) { + ctx->state[0] = u64hilo(0x6a09e667, 0xf3bcc908); + ctx->state[1] = u64hilo(0xbb67ae85, 0x84caa73b); + ctx->state[2] = u64hilo(0x3c6ef372, 0xfe94f82b); + ctx->state[3] = u64hilo(0xa54ff53a, 0x5f1d36f1); + ctx->state[4] = u64hilo(0x510e527f, 0xade682d1); + ctx->state[5] = u64hilo(0x9b05688c, 0x2b3e6c1f); + ctx->state[6] = u64hilo(0x1f83d9ab, 0xfb41bd6b); + ctx->state[7] = u64hilo(0x5be0cd19, 0x137e2179); + + ctx->total[0] = ctx->total[1] = u64lo(0); + ctx->buflen = 0; +} + +/* Copy the value from V into the memory location pointed to by *CP, + If your architecture allows unaligned access, this is equivalent to + * (__typeof__ (v) *) cp = v */ +static void set_uint64(char* cp, u64 v) { + memcpy(cp, &v, sizeof v); +} + +/* Put result from CTX in first 64 bytes following RESBUF. + The result must be in little endian byte order. */ +void* sha512_read_ctx(const struct sha512_ctx* ctx, void* resbuf) { + int i; + char* r = resbuf; + + for(i = 0; i < 8; i++) set_uint64(r + i * sizeof ctx->state[0], SWAP(ctx->state[i])); + + return resbuf; +} + +/* Process the remaining bytes in the internal buffer and the usual + prolog according to the standard and write the result to RESBUF. */ +static void sha512_conclude_ctx(struct sha512_ctx* ctx) { + /* Take yet unprocessed bytes into account. */ + size_t bytes = ctx->buflen; + size_t size = (bytes < 112) ? 128 / 8 : 128 * 2 / 8; + + /* Now count remaining bytes. */ + ctx->total[0] = u64plus(ctx->total[0], u64lo(bytes)); + if(u64lt(ctx->total[0], u64lo(bytes))) ctx->total[1] = u64plus(ctx->total[1], u64lo(1)); + + /* Put the 128-bit file length in *bits* at the end of the buffer. + Use set_uint64 rather than a simple assignment, to avoid risk of + unaligned access. */ + set_uint64( + (char*)&ctx->buffer[size - 2], + SWAP(u64or(u64shl(ctx->total[1], 3), u64shr(ctx->total[0], 61)))); + set_uint64((char*)&ctx->buffer[size - 1], SWAP(u64shl(ctx->total[0], 3))); + + memcpy(&((char*)ctx->buffer)[bytes], fillbuf, (size - 2) * 8 - bytes); + + /* Process last bytes. */ + sha512_process_block(ctx->buffer, size * 8, ctx); +} + +void* sha512_finish_ctx(struct sha512_ctx* ctx, void* resbuf) { + sha512_conclude_ctx(ctx); + return sha512_read_ctx(ctx, resbuf); +} + +/* Compute SHA512 message digest for LEN bytes beginning at BUFFER. The + result is always in little endian byte order, so that a byte-wise + output yields to the wanted ASCII representation of the message + digest. */ +void* sha512_buffer(const char* buffer, size_t len, void* resblock) { + struct sha512_ctx ctx; + + /* Initialize the computation context. */ + sha512_init_ctx(&ctx); + + /* Process whole buffer but last len % 128 bytes. */ + sha512_process_bytes(buffer, len, &ctx); + + /* Put result in desired memory area. */ + return sha512_finish_ctx(&ctx, resblock); +} + +void sha512_process_bytes(const void* buffer, size_t len, struct sha512_ctx* ctx) { + /* When we already have some bits in our internal buffer concatenate + both inputs first. */ + if(ctx->buflen != 0) { + size_t left_over = ctx->buflen; + size_t add = 256 - left_over > len ? len : 256 - left_over; + + memcpy(&((char*)ctx->buffer)[left_over], buffer, add); + ctx->buflen += add; + + if(ctx->buflen > 128) { + sha512_process_block(ctx->buffer, ctx->buflen & ~127, ctx); + + ctx->buflen &= 127; + /* The regions in the following copy operation cannot overlap, + because ctx->buflen < 128 ≤ (left_over + add) & ~127. */ + memcpy(ctx->buffer, &((char*)ctx->buffer)[(left_over + add) & ~127], ctx->buflen); + } + + buffer = (const char*)buffer + add; + len -= add; + } + + /* Process available complete blocks. */ + if(len >= 128) { +#if !(_STRING_ARCH_unaligned || _STRING_INLINE_unaligned) +#define UNALIGNED_P(p) ((uintptr_t)(p) % sizeof(u64) != 0) + if(UNALIGNED_P(buffer)) + while(len > 128) { + sha512_process_block(memcpy(ctx->buffer, buffer, 128), 128, ctx); //-V1086 + buffer = (const char*)buffer + 128; + len -= 128; + } + else +#endif + { + sha512_process_block(buffer, len & ~127, ctx); + buffer = (const char*)buffer + (len & ~127); + len &= 127; + } + } + + /* Move remaining bytes in internal buffer. */ + if(len > 0) { + size_t left_over = ctx->buflen; + + memcpy(&((char*)ctx->buffer)[left_over], buffer, len); + left_over += len; + if(left_over >= 128) { + sha512_process_block(ctx->buffer, 128, ctx); + left_over -= 128; + /* The regions in the following copy operation cannot overlap, + because left_over ≤ 128. */ + memcpy(ctx->buffer, &ctx->buffer[16], left_over); + } + ctx->buflen = left_over; + } +} + +/* --- Code below is the primary difference between sha1.c and sha512.c --- */ + +/* SHA512 round constants */ +#define K(I) sha512_round_constants[I] +static u64 const sha512_round_constants[80] = { + u64init(0x428a2f98, 0xd728ae22), u64init(0x71374491, 0x23ef65cd), + u64init(0xb5c0fbcf, 0xec4d3b2f), u64init(0xe9b5dba5, 0x8189dbbc), + u64init(0x3956c25b, 0xf348b538), u64init(0x59f111f1, 0xb605d019), + u64init(0x923f82a4, 0xaf194f9b), u64init(0xab1c5ed5, 0xda6d8118), + u64init(0xd807aa98, 0xa3030242), u64init(0x12835b01, 0x45706fbe), + u64init(0x243185be, 0x4ee4b28c), u64init(0x550c7dc3, 0xd5ffb4e2), + u64init(0x72be5d74, 0xf27b896f), u64init(0x80deb1fe, 0x3b1696b1), + u64init(0x9bdc06a7, 0x25c71235), u64init(0xc19bf174, 0xcf692694), + u64init(0xe49b69c1, 0x9ef14ad2), u64init(0xefbe4786, 0x384f25e3), + u64init(0x0fc19dc6, 0x8b8cd5b5), u64init(0x240ca1cc, 0x77ac9c65), + u64init(0x2de92c6f, 0x592b0275), u64init(0x4a7484aa, 0x6ea6e483), + u64init(0x5cb0a9dc, 0xbd41fbd4), u64init(0x76f988da, 0x831153b5), + u64init(0x983e5152, 0xee66dfab), u64init(0xa831c66d, 0x2db43210), + u64init(0xb00327c8, 0x98fb213f), u64init(0xbf597fc7, 0xbeef0ee4), + u64init(0xc6e00bf3, 0x3da88fc2), u64init(0xd5a79147, 0x930aa725), + u64init(0x06ca6351, 0xe003826f), u64init(0x14292967, 0x0a0e6e70), + u64init(0x27b70a85, 0x46d22ffc), u64init(0x2e1b2138, 0x5c26c926), + u64init(0x4d2c6dfc, 0x5ac42aed), u64init(0x53380d13, 0x9d95b3df), + u64init(0x650a7354, 0x8baf63de), u64init(0x766a0abb, 0x3c77b2a8), + u64init(0x81c2c92e, 0x47edaee6), u64init(0x92722c85, 0x1482353b), + u64init(0xa2bfe8a1, 0x4cf10364), u64init(0xa81a664b, 0xbc423001), + u64init(0xc24b8b70, 0xd0f89791), u64init(0xc76c51a3, 0x0654be30), + u64init(0xd192e819, 0xd6ef5218), u64init(0xd6990624, 0x5565a910), + u64init(0xf40e3585, 0x5771202a), u64init(0x106aa070, 0x32bbd1b8), + u64init(0x19a4c116, 0xb8d2d0c8), u64init(0x1e376c08, 0x5141ab53), + u64init(0x2748774c, 0xdf8eeb99), u64init(0x34b0bcb5, 0xe19b48a8), + u64init(0x391c0cb3, 0xc5c95a63), u64init(0x4ed8aa4a, 0xe3418acb), + u64init(0x5b9cca4f, 0x7763e373), u64init(0x682e6ff3, 0xd6b2b8a3), + u64init(0x748f82ee, 0x5defb2fc), u64init(0x78a5636f, 0x43172f60), + u64init(0x84c87814, 0xa1f0ab72), u64init(0x8cc70208, 0x1a6439ec), + u64init(0x90befffa, 0x23631e28), u64init(0xa4506ceb, 0xde82bde9), + u64init(0xbef9a3f7, 0xb2c67915), u64init(0xc67178f2, 0xe372532b), + u64init(0xca273ece, 0xea26619c), u64init(0xd186b8c7, 0x21c0c207), + u64init(0xeada7dd6, 0xcde0eb1e), u64init(0xf57d4f7f, 0xee6ed178), + u64init(0x06f067aa, 0x72176fba), u64init(0x0a637dc5, 0xa2c898a6), + u64init(0x113f9804, 0xbef90dae), u64init(0x1b710b35, 0x131c471b), + u64init(0x28db77f5, 0x23047d84), u64init(0x32caab7b, 0x40c72493), + u64init(0x3c9ebe0a, 0x15c9bebc), u64init(0x431d67c4, 0x9c100d4c), + u64init(0x4cc5d4be, 0xcb3e42b6), u64init(0x597f299c, 0xfc657e2a), + u64init(0x5fcb6fab, 0x3ad6faec), u64init(0x6c44198c, 0x4a475817), +}; + +/* Round functions. */ +#define F2(A, B, C) u64or(u64and(A, B), u64and(C, u64or(A, B))) +#define F1(E, F, G) u64xor(G, u64and(E, u64xor(F, G))) + +/* Process LEN bytes of BUFFER, accumulating context into CTX. + It is assumed that LEN % 128 == 0. + Most of this code comes from GnuPG's cipher/sha1.c. */ + +void sha512_process_block(const void* buffer, size_t len, struct sha512_ctx* ctx) { + u64 const* words = buffer; + u64 const* endp = words + len / sizeof(u64); + u64 x[16]; + u64 a = ctx->state[0]; + u64 b = ctx->state[1]; + u64 c = ctx->state[2]; + u64 d = ctx->state[3]; + u64 e = ctx->state[4]; + u64 f = ctx->state[5]; + u64 g = ctx->state[6]; + u64 h = ctx->state[7]; + u64 lolen = u64size(len); + + /* First increment the byte count. FIPS PUB 180-2 specifies the possible + length of the file up to 2^128 bits. Here we only compute the + number of bytes. Do a double word increment. */ + ctx->total[0] = u64plus(ctx->total[0], lolen); + ctx->total[1] = u64plus( + ctx->total[1], u64plus(u64size(len >> 31 >> 31 >> 2), u64lo(u64lt(ctx->total[0], lolen)))); + +#define S0(x) u64xor(u64rol(x, 63), u64xor(u64rol(x, 56), u64shr(x, 7))) +#define S1(x) u64xor(u64rol(x, 45), u64xor(u64rol(x, 3), u64shr(x, 6))) +#define SS0(x) u64xor(u64rol(x, 36), u64xor(u64rol(x, 30), u64rol(x, 25))) +#define SS1(x) u64xor(u64rol(x, 50), u64xor(u64rol(x, 46), u64rol(x, 23))) + +#define M(I) \ + (x[(I)&15] = u64plus( \ + x[(I)&15], \ + u64plus(S1(x[((I)-2) & 15]), u64plus(x[((I)-7) & 15], S0(x[((I)-15) & 15]))))) + +#define R(A, B, C, D, E, F, G, H, K, M) \ + do { \ + u64 t0 = u64plus(SS0(A), F2(A, B, C)); \ + u64 t1 = u64plus(H, u64plus(SS1(E), u64plus(F1(E, F, G), u64plus(K, M)))); \ + D = u64plus(D, t1); \ + H = u64plus(t0, t1); \ + } while(0) + + while(words < endp) { + int t; + /* FIXME: see sha1.c for a better implementation. */ + for(t = 0; t < 16; t++) { + x[t] = SWAP(*words); + words++; + } + + for(int i = 0; i < 80; i++) { + u64 xx = i < 16 ? x[i] : M(i); + R(a, b, c, d, e, f, g, h, K(i), xx); + u64 tt = a; + a = h; + h = g; + g = f; + f = e; + e = d; + d = c; + c = b; + b = tt; + } + + a = ctx->state[0] = u64plus(ctx->state[0], a); + b = ctx->state[1] = u64plus(ctx->state[1], b); + c = ctx->state[2] = u64plus(ctx->state[2], c); + d = ctx->state[3] = u64plus(ctx->state[3], d); + e = ctx->state[4] = u64plus(ctx->state[4], e); + f = ctx->state[5] = u64plus(ctx->state[5], f); + g = ctx->state[6] = u64plus(ctx->state[6], g); + h = ctx->state[7] = u64plus(ctx->state[7], h); + } +} + +/* + * Hey Emacs! + * Local Variables: + * coding: utf-8 + * End: + */ diff --git a/Applications/Official/DEV_FW/source/totp/services/hmac/sha512.h b/Applications/Official/DEV_FW/source/totp/services/hmac/sha512.h new file mode 100644 index 000000000..38590829e --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/services/hmac/sha512.h @@ -0,0 +1,82 @@ +/* Declarations of functions and data types used for SHA512 and SHA384 sum + library functions. + Copyright (C) 2005-2006, 2008-2022 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +#pragma once + +#include +#include "u64.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum { SHA512_DIGEST_SIZE = 512 / 8 }; + +/* Structure to save state of computation between the single steps. */ +struct sha512_ctx { + u64 state[8]; + + u64 total[2]; + size_t buflen; /* ≥ 0, ≤ 256 */ + u64 buffer[32]; /* 256 bytes; the first buflen bytes are in use */ +}; + +/* Initialize structure containing state of computation. */ +extern void sha512_init_ctx(struct sha512_ctx* ctx); + +/* Starting with the result of former calls of this function (or the + initialization function update the context for the next LEN bytes + starting at BUFFER. + It is necessary that LEN is a multiple of 128!!! */ +extern void sha512_process_block(const void* buffer, size_t len, struct sha512_ctx* ctx); + +/* Starting with the result of former calls of this function (or the + initialization function update the context for the next LEN bytes + starting at BUFFER. + It is NOT required that LEN is a multiple of 128. */ +extern void sha512_process_bytes(const void* buffer, size_t len, struct sha512_ctx* ctx); + +/* Process the remaining bytes in the buffer and put result from CTX + in first 64 (48) bytes following RESBUF. The result is always in little + endian byte order, so that a byte-wise output yields to the wanted + ASCII representation of the message digest. */ +extern void* sha512_finish_ctx(struct sha512_ctx* ctx, void* restrict resbuf); + +/* Put result from CTX in first 64 (48) bytes following RESBUF. The result is + always in little endian byte order, so that a byte-wise output yields + to the wanted ASCII representation of the message digest. + + IMPORTANT: On some systems it is required that RESBUF is correctly + aligned for a 32 bits value. */ +extern void* sha512_read_ctx(const struct sha512_ctx* ctx, void* restrict resbuf); + +/* Compute SHA512 message digest for LEN bytes beginning at BUFFER. + The result is always in little endian byte order, so that a byte-wise + output yields to the wanted ASCII representation of the message + digest. */ +extern void* sha512_buffer(const char* buffer, size_t len, void* restrict resblock); + +#ifdef __cplusplus +} +#endif + +/* + * Hey Emacs! + * Local Variables: + * coding: utf-8 + * End: + */ diff --git a/Applications/Official/DEV_FW/source/totp/services/hmac/u64.h b/Applications/Official/DEV_FW/source/totp/services/hmac/u64.h new file mode 100644 index 000000000..91e42e6c9 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/services/hmac/u64.h @@ -0,0 +1,44 @@ +/* uint64_t-like operations that work even on hosts lacking uint64_t + + Copyright (C) 2006, 2009-2022 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +/* Written by Paul Eggert. */ + +#pragma once + +#include + +#ifndef _GL_U64_INLINE +#define _GL_U64_INLINE _GL_INLINE +#endif + +/* Return X rotated left by N bits, where 0 < N < 64. */ +#define u64rol(x, n) u64or(u64shl(x, n), u64shr(x, 64 - (n))) + +/* Native implementations are trivial. See below for comments on what + these operations do. */ +typedef uint64_t u64; +#define u64hilo(hi, lo) ((u64)(((u64)(hi) << 32) + (lo))) +#define u64init(hi, lo) u64hilo(hi, lo) +#define u64lo(x) ((u64)(x)) +#define u64size(x) u64lo(x) +#define u64lt(x, y) ((x) < (y)) +#define u64and(x, y) ((x) & (y)) +#define u64or(x, y) ((x) | (y)) +#define u64xor(x, y) ((x) ^ (y)) +#define u64plus(x, y) ((x) + (y)) +#define u64shl(x, n) ((x) << (n)) +#define u64shr(x, n) ((x) >> (n)) diff --git a/Applications/Official/DEV_FW/source/totp/services/totp/totp.c b/Applications/Official/DEV_FW/source/totp/services/totp/totp.c new file mode 100644 index 000000000..1a20d58c1 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/services/totp/totp.c @@ -0,0 +1,113 @@ +#include "totp.h" + +#include +#include +#include +#include +#include +#include "../hmac/hmac_sha1.h" +#include "../hmac/hmac_sha256.h" +#include "../hmac/hmac_sha512.h" +#include "../hmac/byteswap.h" +#include "../../lib/timezone_utils/timezone_utils.h" + +#define HMAC_MAX_SIZE 64 + +/** + * @brief Generates the timeblock for a time in seconds. + * Timeblocks are the amount of intervals in a given time. For example, + * if 1,000,000 seconds has passed for 30 second intervals, you would get + * 33,333 timeblocks (intervals), where timeblock++ is effectively +30 seconds. + * @param interval in seconds + * @param for_time a time in seconds to get the current timeblocks + * @return Timeblock given \p for_time using \p interval + */ +uint64_t totp_timecode(uint8_t interval, uint64_t for_time) { + return for_time / interval; +} + +/** + * @brief Generates an OTP (One Time Password) + * @param algo hashing algorithm to be used + * @param digits desired TOTP code length + * @param plain_secret plain token secret + * @param plain_secret_length plain token secret length + * @param input input data for OTP code generation + * @return OTP code if code was successfully generated; 0 otherwise + */ +uint32_t otp_generate( + TOTP_ALGO algo, + uint8_t digits, + const uint8_t* plain_secret, + size_t plain_secret_length, + uint64_t input) { + uint8_t hmac[HMAC_MAX_SIZE] = {0}; + + uint64_t input_swapped = swap_uint64(input); + + int hmac_len = + (*algo)(plain_secret, plain_secret_length, (uint8_t*)&input_swapped, 8, &hmac[0]); + if(hmac_len == 0) { + return OTP_ERROR; + } + + uint64_t offset = (hmac[hmac_len - 1] & 0xF); + uint64_t i_code = + ((hmac[offset] & 0x7F) << 24 | (hmac[offset + 1] & 0xFF) << 16 | + (hmac[offset + 2] & 0xFF) << 8 | (hmac[offset + 3] & 0xFF)); + i_code %= (uint64_t)pow(10, digits); + + return i_code; +} + +uint32_t totp_at( + TOTP_ALGO algo, + uint8_t digits, + const uint8_t* plain_secret, + size_t plain_secret_length, + uint64_t for_time, + float timezone, + uint8_t interval) { + uint64_t for_time_adjusted = + timezone_offset_apply(for_time, timezone_offset_from_hours(timezone)); + return otp_generate( + algo, + digits, + plain_secret, + plain_secret_length, + totp_timecode(interval, for_time_adjusted)); +} + +static int totp_algo_sha1( + const uint8_t* key, + size_t key_length, + const uint8_t* input, + size_t input_length, + uint8_t* output) { + hmac_sha1(key, key_length, input, input_length, output); + return HMAC_SHA1_RESULT_SIZE; +} + +static int totp_algo_sha256( + const uint8_t* key, + size_t key_length, + const uint8_t* input, + size_t input_length, + uint8_t* output) { + hmac_sha256(key, key_length, input, input_length, output); + return HMAC_SHA256_RESULT_SIZE; +} + +static int totp_algo_sha512( + const uint8_t* key, + size_t key_length, + const uint8_t* input, + size_t input_length, + uint8_t* output) { + hmac_sha512(key, key_length, input, input_length, output); + return HMAC_SHA512_RESULT_SIZE; +} + +const TOTP_ALGO TOTP_ALGO_SHA1 = (TOTP_ALGO)(&totp_algo_sha1); +const TOTP_ALGO TOTP_ALGO_SHA256 = (TOTP_ALGO)(&totp_algo_sha256); +const TOTP_ALGO TOTP_ALGO_SHA512 = (TOTP_ALGO)(&totp_algo_sha512); diff --git a/Applications/Official/DEV_FW/source/totp/services/totp/totp.h b/Applications/Official/DEV_FW/source/totp/services/totp/totp.h new file mode 100644 index 000000000..3f45a0223 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/services/totp/totp.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include + +#define OTP_ERROR (0) + +/** + * @brief Must compute HMAC using passed arguments, output as char array through output. + * \p key is secret key buffer. + * \p key_length is secret key buffer length. + * \p input is input buffer. + * \p input_length is input buffer length. + * \p output is an output buffer of the resulting HMAC operation. + * Must return 0 if error, or the length in bytes of the HMAC operation. + */ +typedef int (*TOTP_ALGO)( + const uint8_t* key, + size_t key_length, + const uint8_t* input, + size_t input_length, + uint8_t* output); + +/** + * @brief Computes HMAC using SHA1 + */ +extern const TOTP_ALGO TOTP_ALGO_SHA1; + +/** + * @brief Computes HMAC using SHA256 + */ +extern const TOTP_ALGO TOTP_ALGO_SHA256; + +/** + * @brief Computes HMAC using SHA512 + */ +extern const TOTP_ALGO TOTP_ALGO_SHA512; + +/** + * @brief Generates a OTP key using the totp algorithm. + * @param algo hashing algorithm to be used + * @param digits desired TOTP code length + * @param plain_secret plain token secret + * @param plain_secret_length plain token secret length + * @param for_time the time the generated key will be created for + * @param timezone UTC timezone adjustment for the generated key + * @param interval token lifetime in seconds + * @return TOTP code if code was successfully generated; 0 otherwise + */ +uint32_t totp_at( + TOTP_ALGO algo, + uint8_t digits, + const uint8_t* plain_secret, + size_t plain_secret_length, + uint64_t for_time, + float timezone, + uint8_t interval); diff --git a/Applications/Official/DEV_FW/source/totp/totp_10px.png b/Applications/Official/DEV_FW/source/totp/totp_10px.png new file mode 100644 index 000000000..70ed56d98 Binary files /dev/null and b/Applications/Official/DEV_FW/source/totp/totp_10px.png differ diff --git a/Applications/Official/DEV_FW/source/totp/totp_app.c b/Applications/Official/DEV_FW/source/totp/totp_app.c new file mode 100644 index 000000000..93acf8e4d --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/totp_app.c @@ -0,0 +1,197 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "services/config/config.h" +#include "types/plugin_state.h" +#include "types/token_info.h" +#include "types/plugin_event.h" +#include "types/event_type.h" +#include "types/common.h" +#include "ui/scene_director.h" +#include "ui/constants.h" +#include "ui/common_dialogs.h" +#include "services/crypto/crypto.h" +#include "cli/cli.h" + +#define IDLE_TIMEOUT 60000 + +static void render_callback(Canvas* const canvas, void* ctx) { + PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25); + if(plugin_state != NULL) { + totp_scene_director_render(canvas, plugin_state); + } + + 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 bool totp_activate_initial_scene(PluginState* const plugin_state) { + if(plugin_state->crypto_verify_data == NULL) { + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_buttons(message, "No", NULL, "Yes"); + dialog_message_set_text( + message, + "Would you like to setup PIN?", + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER, + AlignCenter, + AlignCenter); + DialogMessageButton dialog_result = + dialog_message_show(plugin_state->dialogs_app, message); + dialog_message_free(message); + if(dialog_result == DialogMessageButtonRight) { + totp_scene_director_activate_scene(plugin_state, TotpSceneAuthentication, NULL); + } else { + if(!totp_crypto_seed_iv(plugin_state, NULL, 0)) { + totp_dialogs_config_loading_error(plugin_state); + return false; + } + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL); + } + } else if(plugin_state->pin_set) { + totp_scene_director_activate_scene(plugin_state, TotpSceneAuthentication, NULL); + } else { + if(!totp_crypto_seed_iv(plugin_state, NULL, 0)) { + totp_dialogs_config_loading_error(plugin_state); + return false; + } + if(totp_crypto_verify_key(plugin_state)) { + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL); + } else { + FURI_LOG_E( + LOGGING_TAG, + "Digital signature verification failed. Looks like conf file was created on another flipper and can't be used on any other"); + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_buttons(message, "Exit", NULL, NULL); + dialog_message_set_text( + message, + "Digital signature verification failed", + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER, + AlignCenter, + AlignCenter); + dialog_message_show(plugin_state->dialogs_app, message); + dialog_message_free(message); + return false; + } + } + + return true; +} + +static bool totp_plugin_state_init(PluginState* const plugin_state) { + plugin_state->gui = furi_record_open(RECORD_GUI); + plugin_state->notification_app = furi_record_open(RECORD_NOTIFICATION); + plugin_state->dialogs_app = furi_record_open(RECORD_DIALOGS); + + if(totp_config_file_load_base(plugin_state) != TotpConfigFileOpenSuccess) { + totp_dialogs_config_loading_error(plugin_state); + return false; + } + + return true; +} + +static void totp_plugin_state_free(PluginState* plugin_state) { + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_NOTIFICATION); + furi_record_close(RECORD_DIALOGS); + + ListNode* node = plugin_state->tokens_list; + ListNode* tmp; + while(node != NULL) { + tmp = node->next; + TokenInfo* tokenInfo = node->data; + token_info_free(tokenInfo); + free(node); + node = tmp; + } + + if(plugin_state->crypto_verify_data != NULL) { + free(plugin_state->crypto_verify_data); + } + free(plugin_state); +} + +int32_t totp_app() { + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); + PluginState* plugin_state = malloc(sizeof(PluginState)); + furi_check(plugin_state != NULL); + + if(!totp_plugin_state_init(plugin_state)) { + FURI_LOG_E(LOGGING_TAG, "App state initialization failed\r\n"); + totp_plugin_state_free(plugin_state); + return 254; + } + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, plugin_state, sizeof(PluginState))) { + FURI_LOG_E(LOGGING_TAG, "Cannot create mutex\r\n"); + totp_plugin_state_free(plugin_state); + return 255; + } + + totp_cli_register_command_handler(plugin_state); + totp_scene_director_init_scenes(plugin_state); + if(!totp_activate_initial_scene(plugin_state)) { + FURI_LOG_E(LOGGING_TAG, "An error ocurred during activating initial scene\r\n"); + totp_plugin_state_free(plugin_state); + return 253; + } + + // 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_add_view_port(plugin_state->gui, view_port, GuiLayerFullscreen); + + PluginEvent event; + bool processing = true; + uint32_t last_user_interaction_time = furi_get_tick(); + while(processing) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + + PluginState* plugin_state_m = acquire_mutex_block(&state_mutex); + + if(event_status == FuriStatusOk) { + if(event.type == EventTypeKey) { + last_user_interaction_time = furi_get_tick(); + } + + processing = totp_scene_director_handle_event(&event, plugin_state_m); + } else if( + plugin_state_m->pin_set && plugin_state_m->current_scene != TotpSceneAuthentication && + furi_get_tick() - last_user_interaction_time > IDLE_TIMEOUT) { + totp_scene_director_activate_scene(plugin_state_m, TotpSceneAuthentication, NULL); + } + + view_port_update(view_port); + release_mutex(&state_mutex, plugin_state_m); + } + + totp_cli_unregister_command_handler(); + totp_scene_director_deactivate_active_scene(plugin_state); + totp_scene_director_dispose(plugin_state); + + view_port_enabled_set(view_port, false); + gui_remove_view_port(plugin_state->gui, view_port); + view_port_free(view_port); + furi_message_queue_free(event_queue); + delete_mutex(&state_mutex); + totp_plugin_state_free(plugin_state); + return 0; +} diff --git a/Applications/Official/DEV_FW/source/totp/types/common.h b/Applications/Official/DEV_FW/source/totp/types/common.h new file mode 100644 index 000000000..2c6d6b293 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/types/common.h @@ -0,0 +1,3 @@ +#pragma once + +#define LOGGING_TAG "TOTP APP" diff --git a/Applications/Official/DEV_FW/source/totp/types/event_type.h b/Applications/Official/DEV_FW/source/totp/types/event_type.h new file mode 100644 index 000000000..4fe916872 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/types/event_type.h @@ -0,0 +1,9 @@ +#pragma once +#include + +typedef uint8_t EventType; + +enum EventTypes { + EventTypeTick, + EventTypeKey, +}; diff --git a/Applications/Official/DEV_FW/source/totp/types/notification_method.h b/Applications/Official/DEV_FW/source/totp/types/notification_method.h new file mode 100644 index 000000000..f86613352 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/types/notification_method.h @@ -0,0 +1,9 @@ +#pragma once + +typedef uint8_t NotificationMethod; + +enum NotificationMethods { + NotificationMethodNone = 0b00, + NotificationMethodSound = 0b01, + NotificationMethodVibro = 0b10, +}; diff --git a/Applications/Official/DEV_FW/source/totp/types/nullable.h b/Applications/Official/DEV_FW/source/totp/types/nullable.h new file mode 100644 index 000000000..4f9b7bc01 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/types/nullable.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +#define TOTP_NULLABLE_STRUCT(value_type) \ + typedef struct TotpNullable_##value_type { \ + bool is_null; \ + value_type value; \ + } TotpNullable_##value_type + +#define TOTP_NULLABLE_NULL(s) s.is_null = true +#define TOTP_NULLABLE_VALUE(s, v) \ + s.is_null = false; \ + s.value = v + +TOTP_NULLABLE_STRUCT(uint16_t); diff --git a/Applications/Official/DEV_FW/source/totp/types/plugin_event.h b/Applications/Official/DEV_FW/source/totp/types/plugin_event.h new file mode 100644 index 000000000..76c22af59 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/types/plugin_event.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include +#include "event_type.h" + +typedef struct { + EventType type; + InputEvent input; +} PluginEvent; diff --git a/Applications/Official/DEV_FW/source/totp/types/plugin_state.h b/Applications/Official/DEV_FW/source/totp/types/plugin_state.h new file mode 100644 index 000000000..0aad2e125 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/types/plugin_state.h @@ -0,0 +1,90 @@ +#pragma once + +#include +#include +#include +#include "../lib/list/list.h" +#include "../ui/totp_scenes_enum.h" +#include "notification_method.h" + +#define TOTP_IV_SIZE 16 + +/** + * @brief Application state structure + */ +typedef struct { + /** + * @brief Application current scene + */ + Scene current_scene; + + /** + * @brief Application current scene state + */ + void* current_scene_state; + + /** + * @brief Reference to the firmware notification subsystem + */ + NotificationApp* notification_app; + + /** + * @brief Reference to the firmware dialogs subsystem + */ + DialogsApp* dialogs_app; + + /** + * @brief Reference to the firmware GUI subsystem + */ + Gui* gui; + + /** + * @brief Timezone UTC offset in hours + */ + float timezone_offset; + + /** + * @brief Token list head node + */ + ListNode* tokens_list; + + /** + * @brief Whether token list is loaded or not + */ + bool token_list_loaded; + + /** + * @brief Tokens list length + */ + uint16_t tokens_count; + + /** + * @brief Encrypted well-known string data + */ + uint8_t* crypto_verify_data; + + /** + * @brief Encrypted well-known string data length + */ + size_t crypto_verify_data_length; + + /** + * @brief Whether PIN is set by user or not + */ + bool pin_set; + + /** + * @brief Initialization vector (IV) to be used for encryption\decryption + */ + uint8_t iv[TOTP_IV_SIZE]; + + /** + * @brief Basic randomly-generated initialization vector (IV) + */ + uint8_t base_iv[TOTP_IV_SIZE]; + + /** + * @brief Notification method + */ + NotificationMethod notification_method; +} PluginState; diff --git a/Applications/Official/DEV_FW/source/totp/types/token_info.c b/Applications/Official/DEV_FW/source/totp/types/token_info.c new file mode 100644 index 000000000..c032c6d3f --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/types/token_info.c @@ -0,0 +1,61 @@ +#include +#include +#include "token_info.h" +#include "stdlib.h" +#include "common.h" +#include "../lib/base32/base32.h" +#include "../services/crypto/crypto.h" +#include "../lib/polyfills/memset_s.h" + +TokenInfo* token_info_alloc() { + TokenInfo* tokenInfo = malloc(sizeof(TokenInfo)); + furi_check(tokenInfo != NULL); + tokenInfo->algo = SHA1; + tokenInfo->digits = TOTP_6_DIGITS; + return tokenInfo; +} + +void token_info_free(TokenInfo* token_info) { + if(token_info == NULL) return; + free(token_info->name); + free(token_info->token); + free(token_info); +} + +bool token_info_set_secret( + TokenInfo* token_info, + const char* base32_token_secret, + size_t token_secret_length, + const uint8_t* iv) { + uint8_t* plain_secret = malloc(token_secret_length); + furi_check(plain_secret != NULL); + int plain_secret_length = + base32_decode((const uint8_t*)base32_token_secret, plain_secret, token_secret_length); + bool result; + if(plain_secret_length >= 0) { + token_info->token = + totp_crypto_encrypt(plain_secret, plain_secret_length, iv, &token_info->token_length); + result = true; + } else { + result = false; + } + + memset_s(plain_secret, token_secret_length, 0, token_secret_length); + free(plain_secret); + return result; +} + +bool token_info_set_digits_from_int(TokenInfo* token_info, uint8_t digits) { + switch(digits) { + case 6: + token_info->digits = TOTP_6_DIGITS; + return true; + case 8: + token_info->digits = TOTP_8_DIGITS; + return true; + default: + break; + } + + return false; +} diff --git a/Applications/Official/DEV_FW/source/totp/types/token_info.h b/Applications/Official/DEV_FW/source/totp/types/token_info.h new file mode 100644 index 000000000..04caa68a8 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/types/token_info.h @@ -0,0 +1,107 @@ +#pragma once + +#include + +typedef uint8_t TokenHashAlgo; +typedef uint8_t TokenDigitsCount; + +/** + * @brief Hashing algorithm to be used to generate token + */ +enum TokenHashAlgos { + /** + * @brief SHA1 hashing algorithm + */ + SHA1, + + /** + * @brief SHA256 hashing algorithm + */ + SHA256, + + /** + * @brief SHA512 hashing algorithm + */ + SHA512 +}; + +/** + * @brief Token digits count to be generated. + */ +enum TokenDigitsCounts { + /** + * @brief 6 digits + */ + TOTP_6_DIGITS = 6, + + /** + * @brief 8 digits + */ + TOTP_8_DIGITS = 8 +}; + +#define TOTP_TOKEN_DIGITS_MAX_COUNT 8 + +/** + * @brief TOTP token information + */ +typedef struct { + /** + * @brief Encrypted token secret + */ + uint8_t* token; + + /** + * @brief Encrypted token secret length + */ + size_t token_length; + + /** + * @brief User-friendly token name + */ + char* name; + + /** + * @brief Hashing algorithm + */ + TokenHashAlgo algo; + + /** + * @brief Desired TOTP token length + */ + TokenDigitsCount digits; +} TokenInfo; + +/** + * @brief Allocates a new instance of \c TokenInfo + * @return + */ +TokenInfo* token_info_alloc(); + +/** + * @brief Disposes all the resources allocated by the given \c TokenInfo instance + * @param token_info instance to be disposed + */ +void token_info_free(TokenInfo* token_info); + +/** + * @brief Encrypts & sets plain token secret to the given instance of \c TokenInfo + * @param token_info instance where secret should be updated + * @param base32_token_secret plain token secret in Base32 format + * @param token_secret_length plain token secret length + * @param iv initialization vecor (IV) to be used for encryption + * @return \c true if token successfully set; \c false otherwise + */ +bool token_info_set_secret( + TokenInfo* token_info, + const char* base32_token_secret, + size_t token_secret_length, + const uint8_t* iv); + +/** + * @brief Sets token digits count from \c uint8_t value + * @param token_info instance whichs token digits count length should be updated + * @param digits desired token digits count length + * @return \c true if token digits count length has been updated; \c false p + */ +bool token_info_set_digits_from_int(TokenInfo* token_info, uint8_t digits); diff --git a/Applications/Official/DEV_FW/source/totp/types/user_pin_codes.h b/Applications/Official/DEV_FW/source/totp/types/user_pin_codes.h new file mode 100644 index 000000000..b5d72d398 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/types/user_pin_codes.h @@ -0,0 +1,10 @@ +#pragma once + +typedef uint8_t TotpUserPinCode; + +enum TotpUserPinCodes { + PinCodeArrowUp = 2, + PinCodeArrowRight = 8, + PinCodeArrowDown = 11, + PinCodeArrowLeft = 5 +}; \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/ui/common_dialogs.c b/Applications/Official/DEV_FW/source/totp/ui/common_dialogs.c new file mode 100644 index 000000000..0a10389e1 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/ui/common_dialogs.c @@ -0,0 +1,20 @@ +#include "common_dialogs.h" +#include "constants.h" + +static DialogMessageButton totp_dialogs_common(PluginState* plugin_state, const char* text) { + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_buttons(message, "Exit", NULL, NULL); + dialog_message_set_text( + message, text, SCREEN_WIDTH_CENTER, SCREEN_HEIGHT_CENTER, AlignCenter, AlignCenter); + DialogMessageButton result = dialog_message_show(plugin_state->dialogs_app, message); + dialog_message_free(message); + return result; +} + +DialogMessageButton totp_dialogs_config_loading_error(PluginState* plugin_state) { + return totp_dialogs_common(plugin_state, "An error has occurred\nduring loading config file"); +} + +DialogMessageButton totp_dialogs_config_updating_error(PluginState* plugin_state) { + return totp_dialogs_common(plugin_state, "An error has occurred\nduring updating config file"); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/ui/common_dialogs.h b/Applications/Official/DEV_FW/source/totp/ui/common_dialogs.h new file mode 100644 index 000000000..187d0e95c --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/ui/common_dialogs.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include "../types/plugin_state.h" + +DialogMessageButton totp_dialogs_config_loading_error(PluginState* plugin_state); +DialogMessageButton totp_dialogs_config_updating_error(PluginState* plugin_state); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/ui/constants.h b/Applications/Official/DEV_FW/source/totp/ui/constants.h new file mode 100644 index 000000000..9caf90c4e --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/ui/constants.h @@ -0,0 +1,6 @@ +#pragma once + +#define SCREEN_WIDTH 128 +#define SCREEN_HEIGHT 64 +#define SCREEN_WIDTH_CENTER (SCREEN_WIDTH >> 1) +#define SCREEN_HEIGHT_CENTER (SCREEN_HEIGHT >> 1) diff --git a/Applications/Official/DEV_FW/source/totp/ui/scene_director.c b/Applications/Official/DEV_FW/source/totp/ui/scene_director.c new file mode 100644 index 000000000..fcc9f37d8 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/ui/scene_director.c @@ -0,0 +1,128 @@ +#include "../types/common.h" +#include "scene_director.h" +#include "scenes/authenticate/totp_scene_authenticate.h" +#include "scenes/generate_token/totp_scene_generate_token.h" +#include "scenes/add_new_token/totp_scene_add_new_token.h" +#include "scenes/token_menu/totp_scene_token_menu.h" +#include "scenes/app_settings/totp_app_settings.h" + +void totp_scene_director_activate_scene( + PluginState* const plugin_state, + Scene scene, + const void* context) { + totp_scene_director_deactivate_active_scene(plugin_state); + switch(scene) { + case TotpSceneGenerateToken: + totp_scene_generate_token_activate(plugin_state, context); + break; + case TotpSceneAuthentication: + totp_scene_authenticate_activate(plugin_state); + break; + case TotpSceneAddNewToken: + totp_scene_add_new_token_activate(plugin_state, context); + break; + case TotpSceneTokenMenu: + totp_scene_token_menu_activate(plugin_state, context); + break; + case TotpSceneAppSettings: + totp_scene_app_settings_activate(plugin_state, context); + break; + case TotpSceneNone: + break; + default: + break; + } + + plugin_state->current_scene = scene; +} + +void totp_scene_director_deactivate_active_scene(PluginState* const plugin_state) { + switch(plugin_state->current_scene) { + case TotpSceneGenerateToken: + totp_scene_generate_token_deactivate(plugin_state); + break; + case TotpSceneAuthentication: + totp_scene_authenticate_deactivate(plugin_state); + break; + case TotpSceneAddNewToken: + totp_scene_add_new_token_deactivate(plugin_state); + break; + case TotpSceneTokenMenu: + totp_scene_token_menu_deactivate(plugin_state); + break; + case TotpSceneAppSettings: + totp_scene_app_settings_deactivate(plugin_state); + break; + case TotpSceneNone: + break; + default: + break; + } +} + +void totp_scene_director_init_scenes(PluginState* const plugin_state) { + totp_scene_authenticate_init(plugin_state); + totp_scene_generate_token_init(plugin_state); + totp_scene_add_new_token_init(plugin_state); + totp_scene_token_menu_init(plugin_state); + totp_scene_app_settings_init(plugin_state); +} + +void totp_scene_director_render(Canvas* const canvas, PluginState* const plugin_state) { + switch(plugin_state->current_scene) { + case TotpSceneGenerateToken: + totp_scene_generate_token_render(canvas, plugin_state); + break; + case TotpSceneAuthentication: + totp_scene_authenticate_render(canvas, plugin_state); + break; + case TotpSceneAddNewToken: + totp_scene_add_new_token_render(canvas, plugin_state); + break; + case TotpSceneTokenMenu: + totp_scene_token_menu_render(canvas, plugin_state); + break; + case TotpSceneAppSettings: + totp_scene_app_settings_render(canvas, plugin_state); + break; + case TotpSceneNone: + break; + default: + break; + } +} + +void totp_scene_director_dispose(const PluginState* const plugin_state) { + totp_scene_generate_token_free(plugin_state); + totp_scene_authenticate_free(plugin_state); + totp_scene_add_new_token_free(plugin_state); + totp_scene_token_menu_free(plugin_state); + totp_scene_app_settings_free(plugin_state); +} + +bool totp_scene_director_handle_event(PluginEvent* const event, PluginState* const plugin_state) { + bool processing = true; + switch(plugin_state->current_scene) { + case TotpSceneGenerateToken: + processing = totp_scene_generate_token_handle_event(event, plugin_state); + break; + case TotpSceneAuthentication: + processing = totp_scene_authenticate_handle_event(event, plugin_state); + break; + case TotpSceneAddNewToken: + processing = totp_scene_add_new_token_handle_event(event, plugin_state); + break; + case TotpSceneTokenMenu: + processing = totp_scene_token_menu_handle_event(event, plugin_state); + break; + case TotpSceneAppSettings: + processing = totp_scene_app_settings_handle_event(event, plugin_state); + break; + case TotpSceneNone: + break; + default: + break; + } + + return processing; +} diff --git a/Applications/Official/DEV_FW/source/totp/ui/scene_director.h b/Applications/Official/DEV_FW/source/totp/ui/scene_director.h new file mode 100644 index 000000000..541a63f1c --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/ui/scene_director.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include "../types/plugin_state.h" +#include "../types/plugin_event.h" +#include "totp_scenes_enum.h" + +/** + * @brief Activates scene + * @param plugin_state application state + * @param scene scene to be activated + * @param context scene context to be passed to the scene activation method + */ +void totp_scene_director_activate_scene( + PluginState* const plugin_state, + Scene scene, + const void* context); + +/** + * @brief Deactivate current scene + * @param plugin_state application state + */ +void totp_scene_director_deactivate_active_scene(PluginState* const plugin_state); + +/** + * @brief Initializes all the available scenes + * @param plugin_state application state + */ +void totp_scene_director_init_scenes(PluginState* const plugin_state); + +/** + * @brief Renders current scene + * @param canvas canvas to render at + * @param plugin_state application state + */ +void totp_scene_director_render(Canvas* const canvas, PluginState* const plugin_state); + +/** + * @brief Disposes all the available scenes + * @param plugin_state application state + */ +void totp_scene_director_dispose(const PluginState* const plugin_state); + +/** + * @brief Handles application event for the current scene + * @param event event to be handled + * @param plugin_state application state + * @return \c true if event handled and applilcation should continue; \c false if application should be closed + */ +bool totp_scene_director_handle_event(PluginEvent* const event, PluginState* const plugin_state); diff --git a/Applications/Official/DEV_FW/source/totp/ui/scenes/add_new_token/totp_input_text.c b/Applications/Official/DEV_FW/source/totp/ui/scenes/add_new_token/totp_input_text.c new file mode 100644 index 000000000..6956ec1ad --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/ui/scenes/add_new_token/totp_input_text.c @@ -0,0 +1,82 @@ +#include "totp_input_text.h" +#include +#include "../../../lib/polyfills/strnlen.h" + +void view_draw(View* view, Canvas* canvas) { + furi_assert(view); + if(view->draw_callback) { + void* data = view_get_model(view); + view->draw_callback(canvas, data); + view_unlock_model(view); + } +} + +bool view_input(View* view, InputEvent* event) { + furi_assert(view); + if(view->input_callback) { + return view->input_callback(event, view->context); + } else { + return false; + } +} + +void view_unlock_model(View* view) { + furi_assert(view); + if(view->model_type == ViewModelTypeLocking) { + ViewModelLocking* model = (ViewModelLocking*)(view->model); + furi_check(furi_mutex_release(model->mutex) == FuriStatusOk); + } +} + +static void commit_text_input_callback(void* context) { + InputTextSceneState* text_input_state = (InputTextSceneState*)context; + if(text_input_state->callback != 0) { + InputTextSceneCallbackResult* result = malloc(sizeof(InputTextSceneCallbackResult)); + furi_check(result != NULL); + result->user_input_length = + strnlen(text_input_state->text_input_buffer, INPUT_BUFFER_SIZE); + result->user_input = malloc(result->user_input_length + 1); + furi_check(result->user_input != NULL); + result->callback_data = text_input_state->callback_data; + strlcpy( + result->user_input, + text_input_state->text_input_buffer, + result->user_input_length + 1); + text_input_state->callback(result); + } +} + +InputTextSceneState* totp_input_text_activate(InputTextSceneContext* context) { + InputTextSceneState* text_input_state = malloc(sizeof(InputTextSceneState)); + furi_check(text_input_state != NULL); + text_input_state->text_input = text_input_alloc(); + text_input_state->text_input_view = text_input_get_view(text_input_state->text_input); + text_input_state->callback = context->callback; + text_input_state->callback_data = context->callback_data; + text_input_set_header_text(text_input_state->text_input, context->header_text); + text_input_set_result_callback( + text_input_state->text_input, + commit_text_input_callback, + text_input_state, + &text_input_state->text_input_buffer[0], + INPUT_BUFFER_SIZE, + true); + return text_input_state; +} + +void totp_input_text_render(Canvas* const canvas, InputTextSceneState* text_input_state) { + view_draw(text_input_state->text_input_view, canvas); +} + +bool totp_input_text_handle_event(PluginEvent* const event, InputTextSceneState* text_input_state) { + if(event->type == EventTypeKey) { + view_input(text_input_state->text_input_view, &event->input); + } + + return true; +} + +void totp_input_text_free(InputTextSceneState* state) { + text_input_free(state->text_input); + free(state); +} diff --git a/Applications/Official/DEV_FW/source/totp/ui/scenes/add_new_token/totp_input_text.h b/Applications/Official/DEV_FW/source/totp/ui/scenes/add_new_token/totp_input_text.h new file mode 100644 index 000000000..145e8904d --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/ui/scenes/add_new_token/totp_input_text.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include +#include "../../../types/plugin_state.h" +#include "../../../types/plugin_event.h" + +typedef struct { + char* user_input; + size_t user_input_length; + void* callback_data; +} InputTextSceneCallbackResult; + +typedef void (*InputTextSceneCallback)(InputTextSceneCallbackResult* result); + +typedef struct { + InputTextSceneCallback callback; + char* header_text; + void* callback_data; +} InputTextSceneContext; + +#define INPUT_BUFFER_SIZE 255 + +typedef struct { + TextInput* text_input; + View* text_input_view; + char text_input_buffer[INPUT_BUFFER_SIZE]; + InputTextSceneCallback callback; + void* callback_data; +} InputTextSceneState; + +InputTextSceneState* totp_input_text_activate(InputTextSceneContext* context); +void totp_input_text_render(Canvas* const canvas, InputTextSceneState* text_input_state); +bool totp_input_text_handle_event(PluginEvent* const event, InputTextSceneState* text_input_state); +void totp_input_text_free(InputTextSceneState* state); diff --git a/Applications/Official/DEV_FW/source/totp/ui/scenes/add_new_token/totp_scene_add_new_token.c b/Applications/Official/DEV_FW/source/totp/ui/scenes/add_new_token/totp_scene_add_new_token.c new file mode 100644 index 000000000..592a12d0f --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/ui/scenes/add_new_token/totp_scene_add_new_token.c @@ -0,0 +1,323 @@ +#include "totp_scene_add_new_token.h" +#include "../../../types/common.h" +#include "../../constants.h" +#include "../../scene_director.h" +#include "totp_input_text.h" +#include "../../../types/token_info.h" +#include "../../../lib/list/list.h" +#include "../../../services/config/config.h" +#include "../../ui_controls.h" +#include "../../common_dialogs.h" +#include "../../../lib/roll_value/roll_value.h" +#include "../../../types/nullable.h" +#include "../generate_token/totp_scene_generate_token.h" + +char* TOKEN_ALGO_LIST[] = {"SHA1", "SHA256", "SHA512"}; +char* TOKEN_DIGITS_TEXT_LIST[] = {"6 digits", "8 digits"}; +TokenDigitsCount TOKEN_DIGITS_VALUE_LIST[] = {TOTP_6_DIGITS, TOTP_8_DIGITS}; + +typedef enum { + TokenNameTextBox, + TokenSecretTextBox, + TokenAlgoSelect, + TokenLengthSelect, + ConfirmButton, +} Control; + +typedef struct { + char* token_name; + size_t token_name_length; + char* token_secret; + size_t token_secret_length; + bool saved; + Control selected_control; + InputTextSceneContext* token_name_input_context; + InputTextSceneContext* token_secret_input_context; + InputTextSceneState* input_state; + uint32_t input_started_at; + TotpNullable_uint16_t current_token_index; + int16_t screen_y_offset; + TokenHashAlgo algo; + uint8_t digits_count_index; +} SceneState; + +void totp_scene_add_new_token_init(const PluginState* plugin_state) { + UNUSED(plugin_state); +} + +static void on_token_name_user_comitted(InputTextSceneCallbackResult* result) { + SceneState* scene_state = result->callback_data; + free(scene_state->token_name); + scene_state->token_name = result->user_input; + scene_state->token_name_length = result->user_input_length; + scene_state->input_started_at = 0; + free(result); +} + +static void on_token_secret_user_comitted(InputTextSceneCallbackResult* result) { + SceneState* scene_state = result->callback_data; + free(scene_state->token_secret); + scene_state->token_secret = result->user_input; + scene_state->token_secret_length = result->user_input_length; + scene_state->input_started_at = 0; + free(result); +} + +void totp_scene_add_new_token_activate( + PluginState* plugin_state, + const TokenAddEditSceneContext* context) { + SceneState* scene_state = malloc(sizeof(SceneState)); + furi_check(scene_state != NULL); + plugin_state->current_scene_state = scene_state; + scene_state->token_name = "Name"; + scene_state->token_name_length = strlen(scene_state->token_name); + scene_state->token_secret = "Secret"; + scene_state->token_secret_length = strlen(scene_state->token_secret); + + scene_state->token_name_input_context = malloc(sizeof(InputTextSceneContext)); + furi_check(scene_state->token_name_input_context != NULL); + scene_state->token_name_input_context->header_text = "Enter token name"; + scene_state->token_name_input_context->callback_data = scene_state; + scene_state->token_name_input_context->callback = on_token_name_user_comitted; + + scene_state->token_secret_input_context = malloc(sizeof(InputTextSceneContext)); + furi_check(scene_state->token_secret_input_context != NULL); + scene_state->token_secret_input_context->header_text = "Enter token secret"; + scene_state->token_secret_input_context->callback_data = scene_state; + scene_state->token_secret_input_context->callback = on_token_secret_user_comitted; + + scene_state->screen_y_offset = 0; + + scene_state->input_state = NULL; + + if(context == NULL) { + TOTP_NULLABLE_NULL(scene_state->current_token_index); + } else { + TOTP_NULLABLE_VALUE(scene_state->current_token_index, context->current_token_index); + } +} + +void totp_scene_add_new_token_render(Canvas* const canvas, PluginState* plugin_state) { + SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; + if(scene_state->input_started_at > 0) { + totp_input_text_render(canvas, scene_state->input_state); + return; + } + + ui_control_text_box_render( + canvas, + 10 - scene_state->screen_y_offset, + scene_state->token_name, + scene_state->selected_control == TokenNameTextBox); + ui_control_text_box_render( + canvas, + 27 - scene_state->screen_y_offset, + scene_state->token_secret, + scene_state->selected_control == TokenSecretTextBox); + ui_control_select_render( + canvas, + 0, + 44 - scene_state->screen_y_offset, + SCREEN_WIDTH, + TOKEN_ALGO_LIST[scene_state->algo], + scene_state->selected_control == TokenAlgoSelect); + ui_control_select_render( + canvas, + 0, + 63 - scene_state->screen_y_offset, + SCREEN_WIDTH, + TOKEN_DIGITS_TEXT_LIST[scene_state->digits_count_index], + scene_state->selected_control == TokenLengthSelect); + ui_control_button_render( + canvas, + SCREEN_WIDTH_CENTER - 24, + 85 - scene_state->screen_y_offset, + 48, + 13, + "Confirm", + scene_state->selected_control == ConfirmButton); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 0, 0, SCREEN_WIDTH, 10); + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 0, 0, AlignLeft, AlignTop, "Add new token"); + canvas_set_font(canvas, FontSecondary); +} + +void update_screen_y_offset(SceneState* scene_state) { + if(scene_state->selected_control > TokenAlgoSelect) { + scene_state->screen_y_offset = 35; + } else { + scene_state->screen_y_offset = 0; + } +} + +bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState* plugin_state) { + if(event->type != EventTypeKey) { + return true; + } + + SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; + if(scene_state->input_started_at > 0 && + furi_get_tick() - scene_state->input_started_at > 300) { + return totp_input_text_handle_event(event, scene_state->input_state); + } + + if(event->input.type == InputTypeLong && event->input.key == InputKeyBack) { + return false; + } + + if(event->input.type != InputTypePress) { + return true; + } + + switch(event->input.key) { + case InputKeyUp: + totp_roll_value_uint8_t( + &scene_state->selected_control, + -1, + TokenNameTextBox, + ConfirmButton, + RollOverflowBehaviorStop); + update_screen_y_offset(scene_state); + break; + case InputKeyDown: + totp_roll_value_uint8_t( + &scene_state->selected_control, + 1, + TokenNameTextBox, + ConfirmButton, + RollOverflowBehaviorStop); + update_screen_y_offset(scene_state); + break; + case InputKeyRight: + if(scene_state->selected_control == TokenAlgoSelect) { + totp_roll_value_uint8_t(&scene_state->algo, 1, SHA1, SHA512, RollOverflowBehaviorRoll); + } else if(scene_state->selected_control == TokenLengthSelect) { + totp_roll_value_uint8_t( + &scene_state->digits_count_index, 1, 0, 1, RollOverflowBehaviorRoll); + } + break; + case InputKeyLeft: + if(scene_state->selected_control == TokenAlgoSelect) { + totp_roll_value_uint8_t( + &scene_state->algo, -1, SHA1, SHA512, RollOverflowBehaviorRoll); + } else if(scene_state->selected_control == TokenLengthSelect) { + totp_roll_value_uint8_t( + &scene_state->digits_count_index, -1, 0, 1, RollOverflowBehaviorRoll); + } + break; + case InputKeyOk: + switch(scene_state->selected_control) { + case TokenNameTextBox: + if(scene_state->input_state != NULL) { + totp_input_text_free(scene_state->input_state); + } + scene_state->input_state = + totp_input_text_activate(scene_state->token_name_input_context); + scene_state->input_started_at = furi_get_tick(); + break; + case TokenSecretTextBox: + if(scene_state->input_state != NULL) { + totp_input_text_free(scene_state->input_state); + } + scene_state->input_state = + totp_input_text_activate(scene_state->token_secret_input_context); + scene_state->input_started_at = furi_get_tick(); + break; + case TokenAlgoSelect: + break; + case TokenLengthSelect: + break; + case ConfirmButton: { + TokenInfo* tokenInfo = token_info_alloc(); + bool token_secret_set = token_info_set_secret( + tokenInfo, + scene_state->token_secret, + scene_state->token_secret_length, + &plugin_state->iv[0]); + + if(token_secret_set) { + tokenInfo->name = malloc(scene_state->token_name_length + 1); + furi_check(tokenInfo->name != NULL); + strlcpy( + tokenInfo->name, scene_state->token_name, scene_state->token_name_length + 1); + tokenInfo->algo = scene_state->algo; + tokenInfo->digits = TOKEN_DIGITS_VALUE_LIST[scene_state->digits_count_index]; + + TOTP_LIST_INIT_OR_ADD(plugin_state->tokens_list, tokenInfo, furi_check); + plugin_state->tokens_count++; + + if(totp_config_file_save_new_token(tokenInfo) != TotpConfigFileUpdateSuccess) { + token_info_free(tokenInfo); + totp_dialogs_config_updating_error(plugin_state); + return false; + } + + GenerateTokenSceneContext generate_scene_context = { + .current_token_index = plugin_state->tokens_count - 1}; + totp_scene_director_activate_scene( + plugin_state, TotpSceneGenerateToken, &generate_scene_context); + } else { + token_info_free(tokenInfo); + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_buttons(message, "Back", NULL, NULL); + dialog_message_set_text( + message, + "Token secret is invalid", + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER, + AlignCenter, + AlignCenter); + dialog_message_show(plugin_state->dialogs_app, message); + dialog_message_free(message); + scene_state->selected_control = TokenSecretTextBox; + update_screen_y_offset(scene_state); + } + break; + } + default: + break; + } + break; + case InputKeyBack: + if(!scene_state->current_token_index.is_null) { + GenerateTokenSceneContext generate_scene_context = { + .current_token_index = scene_state->current_token_index.value}; + totp_scene_director_activate_scene( + plugin_state, TotpSceneGenerateToken, &generate_scene_context); + } else { + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL); + } + break; + default: + break; + } + + return true; +} + +void totp_scene_add_new_token_deactivate(PluginState* plugin_state) { + if(plugin_state->current_scene_state == NULL) return; + SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; + free(scene_state->token_name); + free(scene_state->token_secret); + + free(scene_state->token_name_input_context->header_text); + free(scene_state->token_name_input_context); + + free(scene_state->token_secret_input_context->header_text); + free(scene_state->token_secret_input_context); + + if(scene_state->input_state != NULL) { + totp_input_text_free(scene_state->input_state); + } + + free(plugin_state->current_scene_state); + plugin_state->current_scene_state = NULL; +} + +void totp_scene_add_new_token_free(const PluginState* plugin_state) { + UNUSED(plugin_state); +} diff --git a/Applications/Official/DEV_FW/source/totp/ui/scenes/add_new_token/totp_scene_add_new_token.h b/Applications/Official/DEV_FW/source/totp/ui/scenes/add_new_token/totp_scene_add_new_token.h new file mode 100644 index 000000000..c412e5f0f --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/ui/scenes/add_new_token/totp_scene_add_new_token.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include +#include "../../../types/plugin_state.h" +#include "../../../types/plugin_event.h" + +typedef struct { + uint16_t current_token_index; +} TokenAddEditSceneContext; + +void totp_scene_add_new_token_init(const PluginState* plugin_state); +void totp_scene_add_new_token_activate( + PluginState* plugin_state, + const TokenAddEditSceneContext* context); +void totp_scene_add_new_token_render(Canvas* const canvas, PluginState* plugin_state); +bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState* plugin_state); +void totp_scene_add_new_token_deactivate(PluginState* plugin_state); +void totp_scene_add_new_token_free(const PluginState* plugin_state); diff --git a/Applications/Official/DEV_FW/source/totp/ui/scenes/app_settings/totp_app_settings.c b/Applications/Official/DEV_FW/source/totp/ui/scenes/app_settings/totp_app_settings.c new file mode 100644 index 000000000..b8936f395 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/ui/scenes/app_settings/totp_app_settings.c @@ -0,0 +1,250 @@ +#include "totp_app_settings.h" +#include +#include +#include "../../ui_controls.h" +#include "../../common_dialogs.h" +#include "../../scene_director.h" +#include "../token_menu/totp_scene_token_menu.h" +#include "../../constants.h" +#include "../../../services/config/config.h" +#include "../../../services/convert/convert.h" +#include "../../../lib/roll_value/roll_value.h" +#include "../../../types/nullable.h" + +char* YES_NO_LIST[] = {"NO", "YES"}; + +typedef enum { HoursInput, MinutesInput, Sound, Vibro, ConfirmButton } Control; + +typedef struct { + int8_t tz_offset_hours; + uint8_t tz_offset_minutes; + bool notification_sound; + bool notification_vibro; + uint8_t y_offset; + TotpNullable_uint16_t current_token_index; + Control selected_control; +} SceneState; + +void totp_scene_app_settings_init(const PluginState* plugin_state) { + UNUSED(plugin_state); +} + +void totp_scene_app_settings_activate( + PluginState* plugin_state, + const AppSettingsSceneContext* context) { + SceneState* scene_state = malloc(sizeof(SceneState)); + furi_check(scene_state != NULL); + plugin_state->current_scene_state = scene_state; + if(context != NULL) { + TOTP_NULLABLE_VALUE(scene_state->current_token_index, context->current_token_index); + } else { + TOTP_NULLABLE_NULL(scene_state->current_token_index); + } + + float off_int; + float off_dec = modff(plugin_state->timezone_offset, &off_int); + scene_state->tz_offset_hours = off_int; + scene_state->tz_offset_minutes = 60.0f * off_dec; + scene_state->notification_sound = plugin_state->notification_method & NotificationMethodSound; + scene_state->notification_vibro = plugin_state->notification_method & NotificationMethodVibro; +} + +static void two_digit_to_str(int8_t num, char* str) { + uint8_t index = 0; + if(num < 0) { + str[index++] = '-'; + num = -num; + } + + uint8_t d1 = (num / 10) % 10; + uint8_t d2 = num % 10; + str[index++] = CONVERT_DIGIT_TO_CHAR(d1); + str[index++] = CONVERT_DIGIT_TO_CHAR(d2); + str[index++] = '\0'; +} + +void totp_scene_app_settings_render(Canvas* const canvas, const PluginState* plugin_state) { + const SceneState* scene_state = plugin_state->current_scene_state; + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned( + canvas, 0, 0 - scene_state->y_offset, AlignLeft, AlignTop, "Timezone offset"); + canvas_set_font(canvas, FontSecondary); + + char tmp_str[4]; + two_digit_to_str(scene_state->tz_offset_hours, &tmp_str[0]); + canvas_draw_str_aligned(canvas, 0, 16 - scene_state->y_offset, AlignLeft, AlignTop, "Hours:"); + ui_control_select_render( + canvas, + 36, + 10 - scene_state->y_offset, + SCREEN_WIDTH - 36, + &tmp_str[0], + scene_state->selected_control == HoursInput); + + two_digit_to_str(scene_state->tz_offset_minutes, &tmp_str[0]); + canvas_draw_str_aligned( + canvas, 0, 34 - scene_state->y_offset, AlignLeft, AlignTop, "Minutes:"); + ui_control_select_render( + canvas, + 36, + 28 - scene_state->y_offset, + SCREEN_WIDTH - 36, + &tmp_str[0], + scene_state->selected_control == MinutesInput); + + canvas_draw_icon( + canvas, + SCREEN_WIDTH_CENTER - 5, + SCREEN_HEIGHT - 5 - scene_state->y_offset, + &I_totp_arrow_bottom_10x5); + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned( + canvas, 0, 64 - scene_state->y_offset, AlignLeft, AlignTop, "Notifications"); + canvas_set_font(canvas, FontSecondary); + + canvas_draw_str_aligned(canvas, 0, 80 - scene_state->y_offset, AlignLeft, AlignTop, "Sound:"); + ui_control_select_render( + canvas, + 36, + 74 - scene_state->y_offset, + SCREEN_WIDTH - 36, + YES_NO_LIST[scene_state->notification_sound], + scene_state->selected_control == Sound); + + canvas_draw_str_aligned(canvas, 0, 98 - scene_state->y_offset, AlignLeft, AlignTop, "Vibro:"); + ui_control_select_render( + canvas, + 36, + 92 - scene_state->y_offset, + SCREEN_WIDTH - 36, + YES_NO_LIST[scene_state->notification_vibro], + scene_state->selected_control == Vibro); + + ui_control_button_render( + canvas, + SCREEN_WIDTH_CENTER - 24, + 115 - scene_state->y_offset, + 48, + 13, + "Confirm", + scene_state->selected_control == ConfirmButton); +} + +bool totp_scene_app_settings_handle_event( + const PluginEvent* const event, + PluginState* plugin_state) { + if(event->type != EventTypeKey) { + return true; + } + + SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; + if(event->input.type != InputTypePress && event->input.type != InputTypeRepeat) { + return true; + } + + switch(event->input.key) { + case InputKeyUp: + totp_roll_value_uint8_t( + &scene_state->selected_control, + -1, + HoursInput, + ConfirmButton, + RollOverflowBehaviorStop); + if(scene_state->selected_control > MinutesInput) { + scene_state->y_offset = 64; + } else { + scene_state->y_offset = 0; + } + break; + case InputKeyDown: + totp_roll_value_uint8_t( + &scene_state->selected_control, 1, HoursInput, ConfirmButton, RollOverflowBehaviorStop); + if(scene_state->selected_control > MinutesInput) { + scene_state->y_offset = 64; + } else { + scene_state->y_offset = 0; + } + break; + case InputKeyRight: + if(scene_state->selected_control == HoursInput) { + totp_roll_value_int8_t( + &scene_state->tz_offset_hours, 1, -12, 12, RollOverflowBehaviorStop); + } else if(scene_state->selected_control == MinutesInput) { + totp_roll_value_uint8_t( + &scene_state->tz_offset_minutes, 15, 0, 45, RollOverflowBehaviorRoll); + } else if(scene_state->selected_control == Sound) { + scene_state->notification_sound = !scene_state->notification_sound; + } else if(scene_state->selected_control == Vibro) { + scene_state->notification_vibro = !scene_state->notification_vibro; + } + break; + case InputKeyLeft: + if(scene_state->selected_control == HoursInput) { + totp_roll_value_int8_t( + &scene_state->tz_offset_hours, -1, -12, 12, RollOverflowBehaviorStop); + } else if(scene_state->selected_control == MinutesInput) { + totp_roll_value_uint8_t( + &scene_state->tz_offset_minutes, -15, 0, 45, RollOverflowBehaviorRoll); + } else if(scene_state->selected_control == Sound) { + scene_state->notification_sound = !scene_state->notification_sound; + } else if(scene_state->selected_control == Vibro) { + scene_state->notification_vibro = !scene_state->notification_vibro; + } + break; + case InputKeyOk: + if(scene_state->selected_control == ConfirmButton) { + plugin_state->timezone_offset = (float)scene_state->tz_offset_hours + + (float)scene_state->tz_offset_minutes / 60.0f; + + plugin_state->notification_method = + (scene_state->notification_sound ? NotificationMethodSound : + NotificationMethodNone) | + (scene_state->notification_vibro ? NotificationMethodVibro : + NotificationMethodNone); + + if(totp_config_file_update_user_settings(plugin_state) != + TotpConfigFileUpdateSuccess) { + totp_dialogs_config_updating_error(plugin_state); + return false; + } + + if(!scene_state->current_token_index.is_null) { + TokenMenuSceneContext generate_scene_context = { + .current_token_index = scene_state->current_token_index.value}; + totp_scene_director_activate_scene( + plugin_state, TotpSceneTokenMenu, &generate_scene_context); + } else { + totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu, NULL); + } + } + break; + case InputKeyBack: { + if(!scene_state->current_token_index.is_null) { + TokenMenuSceneContext generate_scene_context = { + .current_token_index = scene_state->current_token_index.value}; + totp_scene_director_activate_scene( + plugin_state, TotpSceneTokenMenu, &generate_scene_context); + } else { + totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu, NULL); + } + break; + } + default: + break; + } + + return true; +} + +void totp_scene_app_settings_deactivate(PluginState* plugin_state) { + if(plugin_state->current_scene_state == NULL) return; + + free(plugin_state->current_scene_state); + plugin_state->current_scene_state = NULL; +} + +void totp_scene_app_settings_free(const PluginState* plugin_state) { + UNUSED(plugin_state); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/ui/scenes/app_settings/totp_app_settings.h b/Applications/Official/DEV_FW/source/totp/ui/scenes/app_settings/totp_app_settings.h new file mode 100644 index 000000000..1721186ed --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/ui/scenes/app_settings/totp_app_settings.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include "../../../types/plugin_state.h" +#include "../../../types/plugin_event.h" + +typedef struct { + uint16_t current_token_index; +} AppSettingsSceneContext; + +void totp_scene_app_settings_init(const PluginState* plugin_state); +void totp_scene_app_settings_activate( + PluginState* plugin_state, + const AppSettingsSceneContext* context); +void totp_scene_app_settings_render(Canvas* const canvas, const PluginState* plugin_state); +bool totp_scene_app_settings_handle_event( + const PluginEvent* const event, + PluginState* plugin_state); +void totp_scene_app_settings_deactivate(PluginState* plugin_state); +void totp_scene_app_settings_free(const PluginState* plugin_state); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/ui/scenes/authenticate/totp_scene_authenticate.c b/Applications/Official/DEV_FW/source/totp/ui/scenes/authenticate/totp_scene_authenticate.c new file mode 100644 index 000000000..c595b5bd0 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/ui/scenes/authenticate/totp_scene_authenticate.c @@ -0,0 +1,167 @@ +#include "totp_scene_authenticate.h" +#include +#include +#include "../../../types/common.h" +#include "../../constants.h" +#include "../../../services/config/config.h" +#include "../../scene_director.h" +#include "../../totp_scenes_enum.h" +#include "../../../services/crypto/crypto.h" +#include "../../../types/user_pin_codes.h" + +#define MAX_CODE_LENGTH TOTP_IV_SIZE + +typedef struct { + TotpUserPinCode code_input[MAX_CODE_LENGTH]; + uint8_t code_length; +} SceneState; + +void totp_scene_authenticate_init(PluginState* plugin_state) { + memset(&plugin_state->iv[0], 0, TOTP_IV_SIZE); +} + +void totp_scene_authenticate_activate(PluginState* plugin_state) { + SceneState* scene_state = malloc(sizeof(SceneState)); + furi_check(scene_state != NULL); + scene_state->code_length = 0; + memset(&scene_state->code_input[0], 0, MAX_CODE_LENGTH); + plugin_state->current_scene_state = scene_state; + memset(&plugin_state->iv[0], 0, TOTP_IV_SIZE); +} + +void totp_scene_authenticate_render(Canvas* const canvas, PluginState* plugin_state) { + const SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; + + int v_shift = 0; + if(scene_state->code_length > 0) { + v_shift = -10; + } + + if(plugin_state->crypto_verify_data == NULL) { + canvas_draw_str_aligned( + canvas, + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER - 10 + v_shift, + AlignCenter, + AlignCenter, + "Use arrow keys"); + canvas_draw_str_aligned( + canvas, + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER + 5 + v_shift, + AlignCenter, + AlignCenter, + "to setup new PIN"); + } else { + canvas_draw_str_aligned( + canvas, + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER + v_shift, + AlignCenter, + AlignCenter, + "Use arrow keys to enter PIN"); + } + const uint8_t PIN_ASTERISK_RADIUS = 3; + const uint8_t PIN_ASTERISK_STEP = (PIN_ASTERISK_RADIUS << 1) + 2; + if(scene_state->code_length > 0) { + uint8_t left_start_x = ((scene_state->code_length - 1) * PIN_ASTERISK_STEP) >> 1; + for(uint8_t i = 0; i < scene_state->code_length; i++) { + canvas_draw_disc( + canvas, + SCREEN_WIDTH_CENTER - left_start_x + i * PIN_ASTERISK_STEP, + SCREEN_HEIGHT_CENTER + 10, + PIN_ASTERISK_RADIUS); + } + } +} + +bool totp_scene_authenticate_handle_event( + const PluginEvent* const event, + PluginState* plugin_state) { + if(event->type != EventTypeKey) { + return true; + } + + if(event->input.type == InputTypeLong && event->input.key == InputKeyBack) { + return false; + } + + if(event->input.type != InputTypePress) { + return true; + } + + SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; + + switch(event->input.key) { + case InputKeyUp: + if(scene_state->code_length < MAX_CODE_LENGTH) { + scene_state->code_input[scene_state->code_length] = PinCodeArrowUp; + scene_state->code_length++; + } + break; + case InputKeyDown: + if(scene_state->code_length < MAX_CODE_LENGTH) { + scene_state->code_input[scene_state->code_length] = PinCodeArrowDown; + scene_state->code_length++; + } + break; + case InputKeyRight: + if(scene_state->code_length < MAX_CODE_LENGTH) { + scene_state->code_input[scene_state->code_length] = PinCodeArrowRight; + scene_state->code_length++; + } + break; + case InputKeyLeft: + if(scene_state->code_length < MAX_CODE_LENGTH) { + scene_state->code_input[scene_state->code_length] = PinCodeArrowLeft; + scene_state->code_length++; + } + break; + case InputKeyOk: + totp_crypto_seed_iv(plugin_state, &scene_state->code_input[0], scene_state->code_length); + + if(totp_crypto_verify_key(plugin_state)) { + FURI_LOG_D(LOGGING_TAG, "PIN is valid"); + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL); + } else { + FURI_LOG_D(LOGGING_TAG, "PIN is NOT valid"); + memset(&scene_state->code_input[0], 0, MAX_CODE_LENGTH); + memset(&plugin_state->iv[0], 0, TOTP_IV_SIZE); + scene_state->code_length = 0; + + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_buttons(message, "Try again", NULL, NULL); + dialog_message_set_header( + message, + "You entered\ninvalid PIN", + SCREEN_WIDTH_CENTER - 25, + SCREEN_HEIGHT_CENTER - 5, + AlignCenter, + AlignCenter); + dialog_message_set_icon(message, &I_DolphinCommon_56x48, 72, 17); + dialog_message_show(plugin_state->dialogs_app, message); + dialog_message_free(message); + } + break; + case InputKeyBack: + if(scene_state->code_length > 0) { + scene_state->code_input[scene_state->code_length - 1] = 0; + scene_state->code_length--; + } + break; + default: + break; + } + + return true; +} + +void totp_scene_authenticate_deactivate(PluginState* plugin_state) { + if(plugin_state->current_scene_state == NULL) return; + free(plugin_state->current_scene_state); + plugin_state->current_scene_state = NULL; +} + +void totp_scene_authenticate_free(const PluginState* plugin_state) { + UNUSED(plugin_state); +} diff --git a/Applications/Official/DEV_FW/source/totp/ui/scenes/authenticate/totp_scene_authenticate.h b/Applications/Official/DEV_FW/source/totp/ui/scenes/authenticate/totp_scene_authenticate.h new file mode 100644 index 000000000..b8fe174ae --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/ui/scenes/authenticate/totp_scene_authenticate.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include "../../../types/plugin_state.h" +#include "../../../types/plugin_event.h" + +void totp_scene_authenticate_init(PluginState* plugin_state); +void totp_scene_authenticate_activate(PluginState* plugin_state); +void totp_scene_authenticate_render(Canvas* const canvas, PluginState* plugin_state); +bool totp_scene_authenticate_handle_event( + const PluginEvent* const event, + PluginState* plugin_state); +void totp_scene_authenticate_deactivate(PluginState* plugin_state); +void totp_scene_authenticate_free(const PluginState* plugin_state); diff --git a/Applications/Official/DEV_FW/source/totp/ui/scenes/generate_token/totp_scene_generate_token.c b/Applications/Official/DEV_FW/source/totp/ui/scenes/generate_token/totp_scene_generate_token.c new file mode 100644 index 000000000..157a7192f --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/ui/scenes/generate_token/totp_scene_generate_token.c @@ -0,0 +1,416 @@ +#include +#include +#include +#include +#include "totp_scene_generate_token.h" +#include "../../../types/token_info.h" +#include "../../../types/common.h" +#include "../../constants.h" +#include "../../../services/totp/totp.h" +#include "../../../services/config/config.h" +#include "../../../services/crypto/crypto.h" +#include "../../../services/convert/convert.h" +#include "../../../lib/polyfills/memset_s.h" +#include "../../../lib/roll_value/roll_value.h" +#include "../../scene_director.h" +#include "../token_menu/totp_scene_token_menu.h" +#include "../../../workers/type_code/type_code.h" + +#define TOKEN_LIFETIME 30 + +typedef struct { + uint16_t current_token_index; + char last_code[TOTP_TOKEN_DIGITS_MAX_COUNT + 1]; + char* last_code_name; + bool need_token_update; + uint32_t last_token_gen_time; + TotpTypeCodeWorkerContext* type_code_worker_context; + NotificationMessage const** notification_sequence_new_token; + NotificationMessage const** notification_sequence_badusb; +} SceneState; + +static const NotificationSequence* + get_notification_sequence_new_token(const PluginState* plugin_state, SceneState* scene_state) { + if(scene_state->notification_sequence_new_token == NULL) { + uint8_t i = 0; + uint8_t length = 4; + if(plugin_state->notification_method & NotificationMethodVibro) { + length += 2; + } + + if(plugin_state->notification_method & NotificationMethodSound) { + length += 2; + } + + scene_state->notification_sequence_new_token = malloc(sizeof(void*) * length); + furi_check(scene_state->notification_sequence_new_token != NULL); + scene_state->notification_sequence_new_token[i++] = &message_display_backlight_on; + scene_state->notification_sequence_new_token[i++] = &message_green_255; + if(plugin_state->notification_method & NotificationMethodVibro) { + scene_state->notification_sequence_new_token[i++] = &message_vibro_on; + } + + if(plugin_state->notification_method & NotificationMethodSound) { + scene_state->notification_sequence_new_token[i++] = &message_note_c5; + } + + scene_state->notification_sequence_new_token[i++] = &message_delay_50; + + if(plugin_state->notification_method & NotificationMethodVibro) { + scene_state->notification_sequence_new_token[i++] = &message_vibro_off; + } + + if(plugin_state->notification_method & NotificationMethodSound) { + scene_state->notification_sequence_new_token[i++] = &message_sound_off; + } + + scene_state->notification_sequence_new_token[i++] = NULL; + } + + return (NotificationSequence*)scene_state->notification_sequence_new_token; +} + +static const NotificationSequence* + get_notification_sequence_badusb(const PluginState* plugin_state, SceneState* scene_state) { + if(scene_state->notification_sequence_badusb == NULL) { + uint8_t i = 0; + uint8_t length = 3; + if(plugin_state->notification_method & NotificationMethodVibro) { + length += 2; + } + + if(plugin_state->notification_method & NotificationMethodSound) { + length += 6; + } + + scene_state->notification_sequence_badusb = malloc(sizeof(void*) * length); + furi_check(scene_state->notification_sequence_badusb != NULL); + + scene_state->notification_sequence_badusb[i++] = &message_blue_255; + if(plugin_state->notification_method & NotificationMethodVibro) { + scene_state->notification_sequence_badusb[i++] = &message_vibro_on; + } + + if(plugin_state->notification_method & NotificationMethodSound) { + scene_state->notification_sequence_badusb[i++] = &message_note_d5; //-V525 + scene_state->notification_sequence_badusb[i++] = &message_delay_50; + scene_state->notification_sequence_badusb[i++] = &message_note_e4; + scene_state->notification_sequence_badusb[i++] = &message_delay_50; + scene_state->notification_sequence_badusb[i++] = &message_note_f3; + } + + scene_state->notification_sequence_badusb[i++] = &message_delay_50; + + if(plugin_state->notification_method & NotificationMethodVibro) { + scene_state->notification_sequence_badusb[i++] = &message_vibro_off; + } + + if(plugin_state->notification_method & NotificationMethodSound) { + scene_state->notification_sequence_badusb[i++] = &message_sound_off; + } + + scene_state->notification_sequence_badusb[i++] = NULL; + } + + return (NotificationSequence*)scene_state->notification_sequence_badusb; +} + +static void int_token_to_str(uint32_t i_token_code, char* str, TokenDigitsCount len) { + if(i_token_code == OTP_ERROR) { + memset(&str[0], '-', len); + } else { + for(int i = len - 1; i >= 0; i--) { + str[i] = CONVERT_DIGIT_TO_CHAR(i_token_code % 10); + i_token_code = i_token_code / 10; + } + } + + str[len] = '\0'; +} + +static TOTP_ALGO get_totp_algo_impl(TokenHashAlgo algo) { + switch(algo) { + case SHA1: + return TOTP_ALGO_SHA1; + case SHA256: + return TOTP_ALGO_SHA256; + case SHA512: + return TOTP_ALGO_SHA512; + default: + break; + } + + return NULL; +} + +static void update_totp_params(PluginState* const plugin_state) { + SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; + + if(scene_state->current_token_index < plugin_state->tokens_count) { + TokenInfo* tokenInfo = + list_element_at(plugin_state->tokens_list, scene_state->current_token_index)->data; + + scene_state->need_token_update = true; + scene_state->last_code_name = tokenInfo->name; + } +} + +void totp_scene_generate_token_init(const PluginState* plugin_state) { + UNUSED(plugin_state); +} + +void totp_scene_generate_token_activate( + PluginState* plugin_state, + const GenerateTokenSceneContext* context) { + if(!plugin_state->token_list_loaded) { + TokenLoadingResult token_load_result = totp_config_file_load_tokens(plugin_state); + if(token_load_result != TokenLoadingResultSuccess) { + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_buttons(message, NULL, "Okay", NULL); + if(token_load_result == TokenLoadingResultWarning) { + dialog_message_set_text( + message, + "Unable to load some tokens\nPlease review conf file", + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER, + AlignCenter, + AlignCenter); + } else if(token_load_result == TokenLoadingResultError) { + dialog_message_set_text( + message, + "Unable to load tokens\nPlease review conf file", + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER, + AlignCenter, + AlignCenter); + } + + dialog_message_show(plugin_state->dialogs_app, message); + dialog_message_free(message); + } + } + SceneState* scene_state = malloc(sizeof(SceneState)); + furi_check(scene_state != NULL); + if(context == NULL || context->current_token_index > plugin_state->tokens_count) { + scene_state->current_token_index = 0; + } else { + scene_state->current_token_index = context->current_token_index; + } + scene_state->need_token_update = true; + plugin_state->current_scene_state = scene_state; + FURI_LOG_D(LOGGING_TAG, "Timezone set to: %f", (double)plugin_state->timezone_offset); + update_totp_params(plugin_state); + scene_state->type_code_worker_context = totp_type_code_worker_start(); + scene_state->type_code_worker_context->string = &scene_state->last_code[0]; + scene_state->type_code_worker_context->string_length = TOTP_TOKEN_DIGITS_MAX_COUNT + 1; +} + +void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_state) { + if(plugin_state->tokens_count == 0) { + canvas_draw_str_aligned( + canvas, + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER - 10, + AlignCenter, + AlignCenter, + "Token list is empty"); + canvas_draw_str_aligned( + canvas, + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER + 10, + AlignCenter, + AlignCenter, + "Press OK button to add"); + return; + } + + SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; + FuriHalRtcDateTime curr_dt; + furi_hal_rtc_get_datetime(&curr_dt); + uint32_t curr_ts = furi_hal_rtc_datetime_to_timestamp(&curr_dt); + + bool is_new_token_time = curr_ts % TOKEN_LIFETIME == 0; + if(is_new_token_time && scene_state->last_token_gen_time != curr_ts) { + scene_state->need_token_update = true; + } + + if(scene_state->need_token_update) { + scene_state->need_token_update = false; + scene_state->last_token_gen_time = curr_ts; + + const TokenInfo* tokenInfo = + (TokenInfo*)(list_element_at( + plugin_state->tokens_list, scene_state->current_token_index) + ->data); + + if(tokenInfo->token != NULL && tokenInfo->token_length > 0) { + furi_mutex_acquire( + scene_state->type_code_worker_context->string_sync, FuriWaitForever); + size_t key_length; + uint8_t* key = totp_crypto_decrypt( + tokenInfo->token, tokenInfo->token_length, &plugin_state->iv[0], &key_length); + + int_token_to_str( + totp_at( + get_totp_algo_impl(tokenInfo->algo), + tokenInfo->digits, + key, + key_length, + curr_ts, + plugin_state->timezone_offset, + TOKEN_LIFETIME), + scene_state->last_code, + tokenInfo->digits); + memset_s(key, key_length, 0, key_length); + free(key); + } else { + furi_mutex_acquire( + scene_state->type_code_worker_context->string_sync, FuriWaitForever); + int_token_to_str(0, scene_state->last_code, tokenInfo->digits); + } + + furi_mutex_release(scene_state->type_code_worker_context->string_sync); + + if(is_new_token_time) { + notification_message( + plugin_state->notification_app, + get_notification_sequence_new_token(plugin_state, scene_state)); + } + } + + canvas_set_font(canvas, FontPrimary); + uint16_t token_name_width = canvas_string_width(canvas, scene_state->last_code_name); + if(SCREEN_WIDTH - token_name_width > 18) { + canvas_draw_str_aligned( + canvas, + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER - 20, + AlignCenter, + AlignCenter, + scene_state->last_code_name); + } else { + canvas_draw_str_aligned( + canvas, + 9, + SCREEN_HEIGHT_CENTER - 20, + AlignLeft, + AlignCenter, + scene_state->last_code_name); + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 0, SCREEN_HEIGHT_CENTER - 24, 9, 9); + canvas_draw_box(canvas, SCREEN_WIDTH - 10, SCREEN_HEIGHT_CENTER - 24, 9, 9); + canvas_set_color(canvas, ColorBlack); + } + + canvas_set_font(canvas, FontBigNumbers); + canvas_draw_str_aligned( + canvas, + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER, + AlignCenter, + AlignCenter, + scene_state->last_code); + + const uint8_t BAR_MARGIN = 3; + const uint8_t BAR_HEIGHT = 4; + float percentDone = (float)(TOKEN_LIFETIME - curr_ts % TOKEN_LIFETIME) / (float)TOKEN_LIFETIME; + uint8_t barWidth = (uint8_t)((float)(SCREEN_WIDTH - (BAR_MARGIN << 1)) * percentDone); + uint8_t barX = ((SCREEN_WIDTH - (BAR_MARGIN << 1) - barWidth) >> 1) + BAR_MARGIN; + + canvas_draw_box(canvas, barX, SCREEN_HEIGHT - BAR_MARGIN - BAR_HEIGHT, barWidth, BAR_HEIGHT); + + if(plugin_state->tokens_count > 1) { + canvas_draw_icon(canvas, 0, SCREEN_HEIGHT_CENTER - 24, &I_totp_arrow_left_8x9); + canvas_draw_icon( + canvas, SCREEN_WIDTH - 9, SCREEN_HEIGHT_CENTER - 24, &I_totp_arrow_right_8x9); + } +} + +bool totp_scene_generate_token_handle_event( + const PluginEvent* const event, + PluginState* plugin_state) { + if(event->type != EventTypeKey) { + return true; + } + + if(event->input.type == InputTypeLong && event->input.key == InputKeyBack) { + return false; + } + + SceneState* scene_state; + if(event->input.type == InputTypeLong && event->input.key == InputKeyDown) { + scene_state = (SceneState*)plugin_state->current_scene_state; + totp_type_code_worker_notify( + scene_state->type_code_worker_context, TotpTypeCodeWorkerEventType); + notification_message( + plugin_state->notification_app, + get_notification_sequence_badusb(plugin_state, scene_state)); + return true; + } + + if(event->input.type != InputTypePress && event->input.type != InputTypeRepeat) { + return true; + } + + scene_state = (SceneState*)plugin_state->current_scene_state; + switch(event->input.key) { + case InputKeyUp: + break; + case InputKeyDown: + break; + case InputKeyRight: + totp_roll_value_uint16_t( + &scene_state->current_token_index, + 1, + 0, + plugin_state->tokens_count - 1, + RollOverflowBehaviorRoll); + update_totp_params(plugin_state); + break; + case InputKeyLeft: + totp_roll_value_uint16_t( + &scene_state->current_token_index, + -1, + 0, + plugin_state->tokens_count - 1, + RollOverflowBehaviorRoll); + update_totp_params(plugin_state); + break; + case InputKeyOk: + if(plugin_state->tokens_count == 0) { + totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu, NULL); + } else { + TokenMenuSceneContext ctx = {.current_token_index = scene_state->current_token_index}; + totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu, &ctx); + } + break; + case InputKeyBack: + break; + default: + break; + } + + return true; +} + +void totp_scene_generate_token_deactivate(PluginState* plugin_state) { + if(plugin_state->current_scene_state == NULL) return; + SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; + + totp_type_code_worker_stop(scene_state->type_code_worker_context); + + if(scene_state->notification_sequence_new_token != NULL) { + free(scene_state->notification_sequence_new_token); + } + + if(scene_state->notification_sequence_badusb != NULL) { + free(scene_state->notification_sequence_badusb); + } + + free(scene_state); + plugin_state->current_scene_state = NULL; +} + +void totp_scene_generate_token_free(const PluginState* plugin_state) { + UNUSED(plugin_state); +} diff --git a/Applications/Official/DEV_FW/source/totp/ui/scenes/generate_token/totp_scene_generate_token.h b/Applications/Official/DEV_FW/source/totp/ui/scenes/generate_token/totp_scene_generate_token.h new file mode 100644 index 000000000..44a3b1c0f --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/ui/scenes/generate_token/totp_scene_generate_token.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include "../../../types/plugin_state.h" +#include "../../../types/plugin_event.h" + +typedef struct { + uint16_t current_token_index; +} GenerateTokenSceneContext; + +void totp_scene_generate_token_init(const PluginState* plugin_state); +void totp_scene_generate_token_activate( + PluginState* plugin_state, + const GenerateTokenSceneContext* context); +void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_state); +bool totp_scene_generate_token_handle_event( + const PluginEvent* const event, + PluginState* plugin_state); +void totp_scene_generate_token_deactivate(PluginState* plugin_state); +void totp_scene_generate_token_free(const PluginState* plugin_state); diff --git a/Applications/Official/DEV_FW/source/totp/ui/scenes/token_menu/totp_scene_token_menu.c b/Applications/Official/DEV_FW/source/totp/ui/scenes/token_menu/totp_scene_token_menu.c new file mode 100644 index 000000000..167762602 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/ui/scenes/token_menu/totp_scene_token_menu.c @@ -0,0 +1,210 @@ +#include "totp_scene_token_menu.h" +#include +#include +#include "../../ui_controls.h" +#include "../../common_dialogs.h" +#include "../../constants.h" +#include "../../scene_director.h" +#include "../../../services/config/config.h" +#include "../../../lib/list/list.h" +#include "../../../types/token_info.h" +#include "../generate_token/totp_scene_generate_token.h" +#include "../add_new_token/totp_scene_add_new_token.h" +#include "../app_settings/totp_app_settings.h" +#include "../../../types/nullable.h" +#include "../../../lib/roll_value/roll_value.h" + +#define SCREEN_HEIGHT_THIRD (SCREEN_HEIGHT / 3) +#define SCREEN_HEIGHT_THIRD_CENTER (SCREEN_HEIGHT_THIRD >> 1) + +typedef enum { AddNewToken, DeleteToken, AppSettings } Control; + +typedef struct { + Control selected_control; + TotpNullable_uint16_t current_token_index; +} SceneState; + +void totp_scene_token_menu_init(const PluginState* plugin_state) { + UNUSED(plugin_state); +} + +void totp_scene_token_menu_activate( + PluginState* plugin_state, + const TokenMenuSceneContext* context) { + SceneState* scene_state = malloc(sizeof(SceneState)); + furi_check(scene_state != NULL); + plugin_state->current_scene_state = scene_state; + if(context != NULL) { + TOTP_NULLABLE_VALUE(scene_state->current_token_index, context->current_token_index); + } else { + TOTP_NULLABLE_NULL(scene_state->current_token_index); + } +} + +void totp_scene_token_menu_render(Canvas* const canvas, PluginState* plugin_state) { + const SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; + if(scene_state->current_token_index.is_null) { + ui_control_button_render( + canvas, + SCREEN_WIDTH_CENTER - 36, + 5, + 72, + 21, + "Add new token", + scene_state->selected_control == AddNewToken); + ui_control_button_render( + canvas, + SCREEN_WIDTH_CENTER - 36, + 39, + 72, + 21, + "Settings", + scene_state->selected_control == AppSettings); + } else { + ui_control_button_render( + canvas, + SCREEN_WIDTH_CENTER - 36, + SCREEN_HEIGHT_THIRD_CENTER - 8, + 72, + 16, + "Add new token", + scene_state->selected_control == AddNewToken); + ui_control_button_render( + canvas, + SCREEN_WIDTH_CENTER - 36, + SCREEN_HEIGHT_THIRD + SCREEN_HEIGHT_THIRD_CENTER - 8, + 72, + 16, + "Delete token", + scene_state->selected_control == DeleteToken); + ui_control_button_render( + canvas, + SCREEN_WIDTH_CENTER - 36, + SCREEN_HEIGHT_THIRD + SCREEN_HEIGHT_THIRD + SCREEN_HEIGHT_THIRD_CENTER - 8, + 72, + 16, + "Settings", + scene_state->selected_control == AppSettings); + } +} + +bool totp_scene_token_menu_handle_event(const PluginEvent* const event, PluginState* plugin_state) { + if(event->type != EventTypeKey) { + return true; + } + + SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; + if(event->input.type != InputTypePress) { + return true; + } + + switch(event->input.key) { + case InputKeyUp: + totp_roll_value_uint8_t( + &scene_state->selected_control, -1, AddNewToken, AppSettings, RollOverflowBehaviorRoll); + if(scene_state->selected_control == DeleteToken && + scene_state->current_token_index.is_null) { + scene_state->selected_control--; + } + break; + case InputKeyDown: + totp_roll_value_uint8_t( + &scene_state->selected_control, 1, AddNewToken, AppSettings, RollOverflowBehaviorRoll); + if(scene_state->selected_control == DeleteToken && + scene_state->current_token_index.is_null) { + scene_state->selected_control++; + } + break; + case InputKeyRight: + break; + case InputKeyLeft: + break; + case InputKeyOk: + switch(scene_state->selected_control) { + case AddNewToken: { + if(scene_state->current_token_index.is_null) { + totp_scene_director_activate_scene(plugin_state, TotpSceneAddNewToken, NULL); + } else { + TokenAddEditSceneContext add_new_token_scene_context = { + .current_token_index = scene_state->current_token_index.value}; + totp_scene_director_activate_scene( + plugin_state, TotpSceneAddNewToken, &add_new_token_scene_context); + } + break; + } + case DeleteToken: { + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_buttons(message, "No", NULL, "Yes"); + dialog_message_set_header(message, "Confirmation", 0, 0, AlignLeft, AlignTop); + dialog_message_set_text( + message, + "Are you sure want to delete?", + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER, + AlignCenter, + AlignCenter); + DialogMessageButton dialog_result = + dialog_message_show(plugin_state->dialogs_app, message); + dialog_message_free(message); + if(dialog_result == DialogMessageButtonRight && + !scene_state->current_token_index.is_null) { + TokenInfo* tokenInfo = NULL; + plugin_state->tokens_list = list_remove_at( + plugin_state->tokens_list, + scene_state->current_token_index.value, + (void**)&tokenInfo); + plugin_state->tokens_count--; + furi_check(tokenInfo != NULL); + token_info_free(tokenInfo); + + if(totp_full_save_config_file(plugin_state) != TotpConfigFileUpdateSuccess) { + totp_dialogs_config_updating_error(plugin_state); + return false; + } + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL); + } + break; + } + case AppSettings: { + if(!scene_state->current_token_index.is_null) { + AppSettingsSceneContext app_settings_context = { + .current_token_index = scene_state->current_token_index.value}; + totp_scene_director_activate_scene( + plugin_state, TotpSceneAppSettings, &app_settings_context); + } else { + totp_scene_director_activate_scene(plugin_state, TotpSceneAppSettings, NULL); + } + break; + } + default: + break; + } + break; + case InputKeyBack: { + if(!scene_state->current_token_index.is_null) { + GenerateTokenSceneContext generate_scene_context = { + .current_token_index = scene_state->current_token_index.value}; + totp_scene_director_activate_scene( + plugin_state, TotpSceneGenerateToken, &generate_scene_context); + } else { + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL); + } + break; + } + default: + break; + } + + return true; +} + +void totp_scene_token_menu_deactivate(PluginState* plugin_state) { + if(plugin_state->current_scene_state == NULL) return; + + free(plugin_state->current_scene_state); + plugin_state->current_scene_state = NULL; +} + +void totp_scene_token_menu_free(const PluginState* plugin_state) { + UNUSED(plugin_state); +} diff --git a/Applications/Official/DEV_FW/source/totp/ui/scenes/token_menu/totp_scene_token_menu.h b/Applications/Official/DEV_FW/source/totp/ui/scenes/token_menu/totp_scene_token_menu.h new file mode 100644 index 000000000..059b8e571 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/ui/scenes/token_menu/totp_scene_token_menu.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include "../../../types/plugin_state.h" +#include "../../../types/plugin_event.h" + +typedef struct { + uint16_t current_token_index; +} TokenMenuSceneContext; + +void totp_scene_token_menu_init(const PluginState* plugin_state); +void totp_scene_token_menu_activate( + PluginState* plugin_state, + const TokenMenuSceneContext* context); +void totp_scene_token_menu_render(Canvas* const canvas, PluginState* plugin_state); +bool totp_scene_token_menu_handle_event(const PluginEvent* const event, PluginState* plugin_state); +void totp_scene_token_menu_deactivate(PluginState* plugin_state); +void totp_scene_token_menu_free(const PluginState* plugin_state); diff --git a/Applications/Official/DEV_FW/source/totp/ui/totp_scenes_enum.h b/Applications/Official/DEV_FW/source/totp/ui/totp_scenes_enum.h new file mode 100644 index 000000000..0c73af772 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/ui/totp_scenes_enum.h @@ -0,0 +1,38 @@ +#pragma once + +typedef uint8_t Scene; + +/** + * @brief TOTP application scenes + */ +enum Scenes { + /** + * @brief Empty scene which does nothing + */ + TotpSceneNone, + + /** + * @brief Scene where user have to enter PIN to authenticate + */ + TotpSceneAuthentication, + + /** + * @brief Scene where actual TOTP token is getting generated and displayed to the user + */ + TotpSceneGenerateToken, + + /** + * @brief Scene where user can add new token + */ + TotpSceneAddNewToken, + + /** + * @brief Scene with a menu for given token, allowing user to do multiple actions + */ + TotpSceneTokenMenu, + + /** + * @brief Scene where user can change application settings + */ + TotpSceneAppSettings +}; diff --git a/Applications/Official/DEV_FW/source/totp/ui/ui_controls.c b/Applications/Official/DEV_FW/source/totp/ui/ui_controls.c new file mode 100644 index 000000000..af029dd9f --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/ui/ui_controls.c @@ -0,0 +1,114 @@ +#include "ui_controls.h" +#include +#include "constants.h" + +#define TEXT_BOX_HEIGHT 13 +#define TEXT_BOX_MARGIN 4 + +void ui_control_text_box_render( + Canvas* const canvas, + int16_t y, + const char* text, + bool is_selected) { + if(y < -TEXT_BOX_HEIGHT) { + return; + } + + if(is_selected) { + canvas_draw_rframe( + canvas, + TEXT_BOX_MARGIN, + TEXT_BOX_MARGIN + y, + SCREEN_WIDTH - TEXT_BOX_MARGIN - TEXT_BOX_MARGIN, + TEXT_BOX_HEIGHT, + 0); + canvas_draw_rframe( + canvas, + TEXT_BOX_MARGIN - 1, + TEXT_BOX_MARGIN + y - 1, + SCREEN_WIDTH - TEXT_BOX_MARGIN - TEXT_BOX_MARGIN + 2, + TEXT_BOX_HEIGHT + 2, + 1); + } else { + canvas_draw_rframe( + canvas, + TEXT_BOX_MARGIN, + TEXT_BOX_MARGIN + y, + SCREEN_WIDTH - TEXT_BOX_MARGIN - TEXT_BOX_MARGIN, + TEXT_BOX_HEIGHT, + 1); + } + + canvas_draw_str_aligned( + canvas, TEXT_BOX_MARGIN + 2, TEXT_BOX_MARGIN + 3 + y, AlignLeft, AlignTop, text); +} + +void ui_control_select_render( + Canvas* const canvas, + int16_t x, + int16_t y, + uint8_t width, + const char* text, + bool is_selected) { + if(y < -TEXT_BOX_HEIGHT) { + return; + } + + if(is_selected) { + canvas_draw_rframe( + canvas, + x + TEXT_BOX_MARGIN, + TEXT_BOX_MARGIN + y, + width - TEXT_BOX_MARGIN - TEXT_BOX_MARGIN, + TEXT_BOX_HEIGHT, + 0); + canvas_draw_rframe( + canvas, + x + TEXT_BOX_MARGIN - 1, + TEXT_BOX_MARGIN + y - 1, + width - TEXT_BOX_MARGIN - TEXT_BOX_MARGIN + 2, + TEXT_BOX_HEIGHT + 2, + 1); + } else { + canvas_draw_rframe( + canvas, + x + TEXT_BOX_MARGIN, + TEXT_BOX_MARGIN + y, + width - TEXT_BOX_MARGIN - TEXT_BOX_MARGIN, + TEXT_BOX_HEIGHT, + 1); + } + + canvas_draw_str_aligned( + canvas, x + (width >> 1), TEXT_BOX_MARGIN + 3 + y, AlignCenter, AlignTop, text); + canvas_draw_icon( + canvas, x + TEXT_BOX_MARGIN + 2, TEXT_BOX_MARGIN + 2 + y, &I_totp_arrow_left_8x9); + canvas_draw_icon( + canvas, x + width - TEXT_BOX_MARGIN - 10, TEXT_BOX_MARGIN + 2 + y, &I_totp_arrow_right_8x9); +} + +void ui_control_button_render( + Canvas* const canvas, + int16_t x, + int16_t y, + uint8_t width, + uint8_t height, + const char* text, + bool is_selected) { + if(y < -height) { + return; + } + + if(is_selected) { + canvas_draw_rbox(canvas, x, y, width, height, 1); + canvas_set_color(canvas, ColorWhite); + } else { + canvas_draw_rframe(canvas, x, y, width, height, 1); + } + + canvas_draw_str_aligned( + canvas, x + (width >> 1), y + (height >> 1) + 1, AlignCenter, AlignCenter, text); + if(is_selected) { + canvas_set_color(canvas, ColorBlack); + } +} diff --git a/Applications/Official/DEV_FW/source/totp/ui/ui_controls.h b/Applications/Official/DEV_FW/source/totp/ui/ui_controls.h new file mode 100644 index 000000000..b97006a03 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/ui/ui_controls.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include + +/** + * @brief Renders TextBox control + * @param canvas canvas to render control at + * @param y vertical position of a control to be rendered at + * @param text text to be rendered inside control + * @param is_selected whether control should be rendered as focused or not + */ +void ui_control_text_box_render( + Canvas* const canvas, + int16_t y, + const char* text, + bool is_selected); + +/** + * @brief Renders Button control + * @param canvas canvas to render control at + * @param x horizontal position of a control to be rendered at + * @param y vertical position of a control to be rendered at + * @param width control width + * @param height control height + * @param text text to be rendered inside control + * @param is_selected whether control should be rendered as focused or not + */ +void ui_control_button_render( + Canvas* const canvas, + int16_t x, + int16_t y, + uint8_t width, + uint8_t height, + const char* text, + bool is_selected); + +/** + * @brief Renders Select control + * @param canvas canvas to render control at + * @param x horizontal position of a control to be rendered at + * @param y vertical position of a control to be rendered at + * @param width control width + * @param text text to be rendered inside control + * @param is_selected whether control should be rendered as focused or not + */ +void ui_control_select_render( + Canvas* const canvas, + int16_t x, + int16_t y, + uint8_t width, + const char* text, + bool is_selected); diff --git a/Applications/Official/DEV_FW/source/totp/workers/type_code/type_code.c b/Applications/Official/DEV_FW/source/totp/workers/type_code/type_code.c new file mode 100644 index 000000000..3eb59047a --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/workers/type_code/type_code.c @@ -0,0 +1,115 @@ +#include "type_code.h" +#include "../../services/convert/convert.h" + +static const uint8_t hid_number_keys[10] = { + HID_KEYBOARD_0, + HID_KEYBOARD_1, + HID_KEYBOARD_2, + HID_KEYBOARD_3, + HID_KEYBOARD_4, + HID_KEYBOARD_5, + HID_KEYBOARD_6, + HID_KEYBOARD_7, + HID_KEYBOARD_8, + HID_KEYBOARD_9}; + +static void totp_type_code_worker_restore_usb_mode(TotpTypeCodeWorkerContext* context) { + if(context->usb_mode_prev != NULL) { + furi_hal_usb_set_config(context->usb_mode_prev, NULL); + context->usb_mode_prev = NULL; + } +} + +static inline bool totp_type_code_worker_stop_requested() { + return furi_thread_flags_get() & TotpTypeCodeWorkerEventStop; +} + +static void totp_type_code_worker_type_code(TotpTypeCodeWorkerContext* context) { + context->usb_mode_prev = furi_hal_usb_get_config(); + furi_hal_usb_unlock(); + furi_check(furi_hal_usb_set_config(&usb_hid, NULL) == true); + uint8_t i = 0; + do { + furi_delay_ms(500); + i++; + } while(!furi_hal_hid_is_connected() && i < 100 && !totp_type_code_worker_stop_requested()); + + if(furi_hal_hid_is_connected() && + furi_mutex_acquire(context->string_sync, 500) == FuriStatusOk) { + furi_delay_ms(500); + i = 0; + while(i < context->string_length && context->string[i] != 0) { + uint8_t digit = CONVERT_CHAR_TO_DIGIT(context->string[i]); + if(digit > 9) break; + uint8_t hid_kb_key = hid_number_keys[digit]; + furi_hal_hid_kb_press(hid_kb_key); + furi_delay_ms(30); + furi_hal_hid_kb_release(hid_kb_key); + i++; + } + + furi_mutex_release(context->string_sync); + + furi_delay_ms(100); + } + + totp_type_code_worker_restore_usb_mode(context); +} + +static int32_t totp_type_code_worker_callback(void* context) { + ValueMutex context_mutex; + if(!init_mutex(&context_mutex, context, sizeof(TotpTypeCodeWorkerContext))) { + return 251; + } + + while(true) { + uint32_t flags = furi_thread_flags_wait( + TotpTypeCodeWorkerEventStop | TotpTypeCodeWorkerEventType, + FuriFlagWaitAny, + FuriWaitForever); + furi_check((flags & FuriFlagError) == 0); //-V562 + if(flags & TotpTypeCodeWorkerEventStop) break; + + TotpTypeCodeWorkerContext* h_context = acquire_mutex_block(&context_mutex); + if(flags & TotpTypeCodeWorkerEventType) { + totp_type_code_worker_type_code(h_context); + } + + release_mutex(&context_mutex, h_context); + } + + delete_mutex(&context_mutex); + + return 0; +} + +TotpTypeCodeWorkerContext* totp_type_code_worker_start() { + TotpTypeCodeWorkerContext* context = malloc(sizeof(TotpTypeCodeWorkerContext)); + furi_check(context != NULL); + context->string_sync = furi_mutex_alloc(FuriMutexTypeNormal); + context->thread = furi_thread_alloc(); + context->usb_mode_prev = NULL; + furi_thread_set_name(context->thread, "TOTPHidWorker"); + furi_thread_set_stack_size(context->thread, 1024); + furi_thread_set_context(context->thread, context); + furi_thread_set_callback(context->thread, totp_type_code_worker_callback); + furi_thread_start(context->thread); + return context; +} + +void totp_type_code_worker_stop(TotpTypeCodeWorkerContext* context) { + furi_assert(context != NULL); + furi_thread_flags_set(furi_thread_get_id(context->thread), TotpTypeCodeWorkerEventStop); + furi_thread_join(context->thread); + furi_thread_free(context->thread); + furi_mutex_free(context->string_sync); + totp_type_code_worker_restore_usb_mode(context); + free(context); +} + +void totp_type_code_worker_notify( + TotpTypeCodeWorkerContext* context, + TotpTypeCodeWorkerEvent event) { + furi_assert(context != NULL); + furi_thread_flags_set(furi_thread_get_id(context->thread), event); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/totp/workers/type_code/type_code.h b/Applications/Official/DEV_FW/source/totp/workers/type_code/type_code.h new file mode 100644 index 000000000..27f2e02d4 --- /dev/null +++ b/Applications/Official/DEV_FW/source/totp/workers/type_code/type_code.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include + +typedef uint8_t TotpTypeCodeWorkerEvent; + +typedef struct { + char* string; + uint8_t string_length; + FuriThread* thread; + FuriMutex* string_sync; + FuriHalUsbInterface* usb_mode_prev; +} TotpTypeCodeWorkerContext; + +enum TotpTypeCodeWorkerEvents { + TotpTypeCodeWorkerEventReserved = (1 << 0), + TotpTypeCodeWorkerEventStop = (1 << 1), + TotpTypeCodeWorkerEventType = (1 << 2) +}; + +TotpTypeCodeWorkerContext* totp_type_code_worker_start(); +void totp_type_code_worker_stop(TotpTypeCodeWorkerContext* context); +void totp_type_code_worker_notify( + TotpTypeCodeWorkerContext* context, + TotpTypeCodeWorkerEvent event); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/tuning_fork/LICENSE b/Applications/Official/DEV_FW/source/tuning_fork/LICENSE new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/tuning_fork/README.md b/Applications/Official/DEV_FW/source/tuning_fork/README.md new file mode 100644 index 000000000..5524eba3e --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/tuning_fork/application.fam b/Applications/Official/DEV_FW/source/tuning_fork/application.fam new file mode 100644 index 000000000..47cef5364 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/tuning_fork/img/screenshot_1.png b/Applications/Official/DEV_FW/source/tuning_fork/img/screenshot_1.png new file mode 100644 index 000000000..047279889 Binary files /dev/null and b/Applications/Official/DEV_FW/source/tuning_fork/img/screenshot_1.png differ diff --git a/Applications/Official/DEV_FW/source/tuning_fork/img/screenshot_2.png b/Applications/Official/DEV_FW/source/tuning_fork/img/screenshot_2.png new file mode 100644 index 000000000..c31f37744 Binary files /dev/null and b/Applications/Official/DEV_FW/source/tuning_fork/img/screenshot_2.png differ diff --git a/Applications/Official/DEV_FW/source/tuning_fork/img/tuning_fork.gif b/Applications/Official/DEV_FW/source/tuning_fork/img/tuning_fork.gif new file mode 100644 index 000000000..27bfe8cbe Binary files /dev/null and b/Applications/Official/DEV_FW/source/tuning_fork/img/tuning_fork.gif differ diff --git a/Applications/Official/DEV_FW/source/tuning_fork/notes.h b/Applications/Official/DEV_FW/source/tuning_fork/notes.h new file mode 100644 index 000000000..c00b4f8ed --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/tuning_fork/tuning_fork.c b/Applications/Official/DEV_FW/source/tuning_fork/tuning_fork.c new file mode 100644 index 000000000..69a76029f --- /dev/null +++ b/Applications/Official/DEV_FW/source/tuning_fork/tuning_fork.c @@ -0,0 +1,408 @@ +#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) { + if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) { + furi_hal_speaker_start( + current_tuning_note_freq(tuning_fork_state), tuning_fork_state->volume); + } +} + +static void stop() { + if(furi_hal_speaker_is_mine()) { + furi_hal_speaker_stop(); + furi_hal_speaker_release(); + } +} + +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/Official/DEV_FW/source/tuning_fork/tuning_fork_icon.png b/Applications/Official/DEV_FW/source/tuning_fork/tuning_fork_icon.png new file mode 100644 index 000000000..074d9d590 Binary files /dev/null and b/Applications/Official/DEV_FW/source/tuning_fork/tuning_fork_icon.png differ diff --git a/Applications/Official/DEV_FW/source/tuning_fork/tunings.h b/Applications/Official/DEV_FW/source/tuning_fork/tunings.h new file mode 100644 index 000000000..14bf469fe --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/unitemp/LICENSE.md b/Applications/Official/DEV_FW/source/unitemp/LICENSE.md new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/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/Official/DEV_FW/source/unitemp/README.md b/Applications/Official/DEV_FW/source/unitemp/README.md new file mode 100644 index 000000000..c40ed520a --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/README.md @@ -0,0 +1,9 @@ +![Flipper usage](https://user-images.githubusercontent.com/10090793/206618263-c1e212e4-58dc-432e-87a8-5c19fd835b35.png) +# Unitemp - Universal temperature sensor reader +[![GitHub release](https://img.shields.io/github/release/quen0n/unitemp-flipperzero?include_prereleases=&sort=semver&color=blue)](https://github.com/quen0n/unitemp-flipperzero/releases/) +[![GitHub](https://img.shields.io/github/license/quen0n/unitemp-flipperzero)](https://github.com/quen0n/unitemp-flipperzero/blob/dev/LICENSE.md) +[Flipper Zero](https://flipperzero.one/) application for reading temperature, humidity and pressure sensors using Onewire, Singlewire, I2C protocols. +## List of supported sensors (supplemented) +![image](https://user-images.githubusercontent.com/10090793/208763931-d15e9883-1016-4add-bd00-14d7842fd82d.png) +## Installation +Copy the contents of the repository to the `applications/plugins/unitemp` folder and build the project. Flash FZ along with resources. [More...](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/documentation/fbt.md) diff --git a/Applications/Official/DEV_FW/source/unitemp/Sensors.c b/Applications/Official/DEV_FW/source/unitemp/Sensors.c new file mode 100644 index 000000000..f81daa827 --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/Sensors.c @@ -0,0 +1,656 @@ +/* + Unitemp - Universal temperature reader + Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n) + + 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 . +*/ +#include "Sensors.h" +#include +#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(GPIOList) / sizeof(GPIO)) +//Количество интерфейсов +#define INTERFACES_TYPES_COUNT (int)(sizeof(interfaces) / sizeof(const Interface*)) +//Количество типов датчиков +#define SENSOR_TYPES_COUNT (int)(sizeof(sensorTypes) / sizeof(const SensorType*)) + +//Перечень достуных портов ввода/вывода +static const GPIO GPIOList[] = { + {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}}; + +//Список интерфейсов, которые прикреплены к GPIO (определяется индексом) +//NULL - порт свободен, указатель на интерфейс - порт занят этим интерфейсом +static const Interface* gpio_interfaces_list[GPIO_ITEMS] = {0}; + +const Interface SINGLE_WIRE = { + .name = "Single wire", + .allocator = unitemp_singlewire_alloc, + .mem_releaser = unitemp_singlewire_free, + .updater = unitemp_singlewire_update}; +const Interface I2C = { + .name = "I2C", + .allocator = unitemp_I2C_sensor_alloc, + .mem_releaser = unitemp_I2C_sensor_free, + .updater = unitemp_I2C_sensor_update}; +const Interface ONE_WIRE = { + .name = "One wire", + .allocator = unitemp_onewire_sensor_alloc, + .mem_releaser = unitemp_onewire_sensor_free, + .updater = unitemp_onewire_sensor_update}; + +//Перечень интерфейсов подключения +//static const Interface* interfaces[] = {&SINGLE_WIRE, &I2C, &ONE_WIRE}; +//Перечень датчиков +static const SensorType* sensorTypes[] = { + &DHT11, + &DHT12_SW, + &DHT20, + &DHT21, + &DHT22, + &Dallas, + &AM2320_SW, + &AM2320_I2C, + &AHT10, + &LM75, + &BMP280, + &BME280}; + +const SensorType* unitemp_sensors_getTypeFromInt(uint8_t index) { + if(index > SENSOR_TYPES_COUNT) return NULL; + return sensorTypes[index]; +} + +const SensorType* unitemp_sensors_getTypeFromStr(char* str) { + UNUSED(str); + if(str == NULL) return NULL; + for(uint8_t i = 0; i < unitemp_sensors_getTypesCount(); i++) { + if(!strcmp(str, sensorTypes[i]->typename)) { + return sensorTypes[i]; + } + } + return NULL; +} + +uint8_t unitemp_sensors_getTypesCount(void) { + return SENSOR_TYPES_COUNT; +} +const SensorType** unitemp_sensors_getTypes(void) { + return sensorTypes; +} + +int unitemp_getIntFromType(const SensorType* type) { + for(int i = 0; i < SENSOR_TYPES_COUNT; i++) { + if(!strcmp(type->typename, sensorTypes[i]->typename)) { + return i; + } + } + return 255; +} +const GPIO* unitemp_gpio_getFromInt(uint8_t name) { + for(uint8_t i = 0; i < GPIO_ITEMS; i++) { + if(GPIOList[i].num == name) { + return &GPIOList[i]; + } + } + return NULL; +} + +const GPIO* unitemp_gpio_getFromIndex(uint8_t index) { + return &GPIOList[index]; +} + +uint8_t unitemp_gpio_toInt(const GPIO* gpio) { + if(gpio == NULL) return 255; + for(uint8_t i = 0; i < GPIO_ITEMS; i++) { + if(GPIOList[i].pin->pin == gpio->pin->pin && GPIOList[i].pin->port == gpio->pin->port) { + return GPIOList[i].num; + } + } + return 255; +} + +uint8_t unitemp_gpio_to_index(const GpioPin* gpio) { + if(gpio == NULL) return 255; + for(uint8_t i = 0; i < GPIO_ITEMS; i++) { + if(GPIOList[i].pin->pin == gpio->pin && GPIOList[i].pin->port == gpio->port) { + return i; + } + } + return 255; +} + +uint8_t unitemp_gpio_getAviablePortsCount(const Interface* interface, const GPIO* extraport) { + uint8_t aviable_ports_count = 0; + for(uint8_t i = 0; i < GPIO_ITEMS; i++) { + //Проверка для one wire + if(interface == &ONE_WIRE) { + if(((gpio_interfaces_list[i] == NULL || gpio_interfaces_list[i] == &ONE_WIRE) && + (i != 12)) || //Почему-то не работает на 17 порте + (unitemp_gpio_getFromIndex(i) == extraport)) { + aviable_ports_count++; + } + } + + //Проверка для single wire + if(interface == &SINGLE_WIRE) { + if(gpio_interfaces_list[i] == NULL || (unitemp_gpio_getFromIndex(i) == extraport)) { + aviable_ports_count++; + } + } + + if(interface == &I2C) { + //У I2C два фиксированых порта + return 0; + } + } + return aviable_ports_count; +} + +void unitemp_gpio_lock(const GPIO* gpio, const Interface* interface) { + uint8_t i = unitemp_gpio_to_index(gpio->pin); + if(i == 255) return; + gpio_interfaces_list[i] = interface; +} + +void unitemp_gpio_unlock(const GPIO* gpio) { + uint8_t i = unitemp_gpio_to_index(gpio->pin); + if(i == 255) return; + gpio_interfaces_list[i] = NULL; +} + +const GPIO* + unitemp_gpio_getAviablePort(const Interface* interface, uint8_t index, const GPIO* extraport) { + //Проверка для I2C + if(interface == &I2C) { + if((gpio_interfaces_list[10] == NULL || gpio_interfaces_list[10] == &I2C) && + (gpio_interfaces_list[11] == NULL || gpio_interfaces_list[11] == &I2C)) { + //Возврат истины + return unitemp_gpio_getFromIndex(0); + } else { + //Возврат лжи + return NULL; + } + } + + uint8_t aviable_index = 0; + for(uint8_t i = 0; i < GPIO_ITEMS; i++) { + //Проверка для one wire + if(interface == &ONE_WIRE) { + //Почему-то не работает на 17 порте + if(((gpio_interfaces_list[i] == NULL || gpio_interfaces_list[i] == &ONE_WIRE) && + (i != 12)) || //Почему-то не работает на 17 порте + (unitemp_gpio_getFromIndex(i) == extraport)) { + if(aviable_index == index) { + return unitemp_gpio_getFromIndex(i); + } else { + aviable_index++; + } + } + } + //Проверка для single wire + if(interface == &SINGLE_WIRE) { + if(gpio_interfaces_list[i] == NULL || unitemp_gpio_getFromIndex(i) == extraport) { + if(aviable_index == index) { + return unitemp_gpio_getFromIndex(i); + } else { + aviable_index++; + } + } + } + } + + return NULL; +} + +void unitemp_sensor_delete(Sensor* sensor) { + for(uint8_t i = 0; i < app->sensors_count; i++) { + if(app->sensors[i] == sensor) { + app->sensors[i]->status = UT_SENSORSTATUS_INACTIVE; + unitemp_sensors_save(); + unitemp_sensors_reload(); + return; + } + } +} + +Sensor* unitemp_sensor_getActive(uint8_t index) { + uint8_t aviable_index = 0; + for(uint8_t i = 0; i < app->sensors_count; i++) { + if(app->sensors[i]->status != UT_SENSORSTATUS_INACTIVE) { + if(aviable_index == index) { + return app->sensors[i]; + } else { + aviable_index++; + } + } + } + return NULL; +} + +uint8_t unitemp_sensors_getCount(void) { + if(app->sensors == NULL) return 0; + return app->sensors_count; +} + +uint8_t unitemp_sensors_getActiveCount(void) { + if(app->sensors == NULL) return 0; + uint8_t counter = 0; + for(uint8_t i = 0; i < unitemp_sensors_getCount(); i++) { + if(app->sensors[i]->status != UT_SENSORSTATUS_INACTIVE) counter++; + } + return counter; +} + +void unitemp_sensors_add(Sensor* sensor) { + app->sensors = + (Sensor**)realloc(app->sensors, (unitemp_sensors_getCount() + 1) * sizeof(Sensor*)); + app->sensors[unitemp_sensors_getCount()] = sensor; + app->sensors_count++; +} + +bool unitemp_sensors_load(void) { +#ifdef UNITEMP_DEBUG + FURI_LOG_D(APP_NAME, "Loading sensors..."); +#endif + + //Выделение памяти на поток + app->file_stream = file_stream_alloc(app->storage); + + //Переменная пути к файлу + FuriString* filepath = furi_string_alloc(); + //Составление пути к файлу + furi_string_printf(filepath, "%s/%s", APP_PATH_FOLDER, APP_FILENAME_SENSORS); + + //Открытие потока к файлу с датчиками + if(!file_stream_open( + app->file_stream, furi_string_get_cstr(filepath), FSAM_READ_WRITE, FSOM_OPEN_EXISTING)) { + if(file_stream_get_error(app->file_stream) == FSE_NOT_EXIST) { + FURI_LOG_W(APP_NAME, "Missing sensors file"); + //Закрытие потока и освобождение памяти + file_stream_close(app->file_stream); + stream_free(app->file_stream); + return false; + } else { + FURI_LOG_E( + APP_NAME, + "An error occurred while loading the sensors file: %d", + file_stream_get_error(app->file_stream)); + //Закрытие потока и освобождение памяти + file_stream_close(app->file_stream); + stream_free(app->file_stream); + return false; + } + } + + //Вычисление размера файла + uint16_t file_size = stream_size(app->file_stream); + //Если файл пустой, то: + if(file_size == (uint8_t)0) { + FURI_LOG_W(APP_NAME, "Sensors file is empty"); + //Закрытие потока и освобождение памяти + file_stream_close(app->file_stream); + 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 sensors file"); + //Закрытие потока и освобождение памяти + file_stream_close(app->file_stream); + stream_free(app->file_stream); + free(file_buf); + return false; + } + + //Указатель на начало строки + FuriString* file = furi_string_alloc_set_str((char*)file_buf); + //Сколько байт до конца строки + size_t line_end = 0; + + while(line_end != STRING_FAILURE && line_end != (size_t)(file_size - 1)) { + //Имя датчика + char name[11] = {0}; + //Тип датчика + char type[11] = {0}; + //Смещение по температуре + int temp_offset = 0; + //Смещение по строке для отделения аргументов + int offset = 0; + //Чтение из строки + sscanf(((char*)(file_buf + line_end)), "%s %s %d %n", name, type, &temp_offset, &offset); + //Ограничение длины имени + name[10] = '\0'; + + //Замена ? на пробел + for(uint8_t i = 0; i < 10; i++) { + if(name[i] == '?') name[i] = ' '; + } + + char* args = ((char*)(file_buf + line_end + offset)); + const SensorType* stype = unitemp_sensors_getTypeFromStr(type); + + //Проверка типа датчика + if(stype != NULL && sizeof(name) > 0 && sizeof(name) <= 11) { + Sensor* sensor = + unitemp_sensor_alloc(name, unitemp_sensors_getTypeFromStr(type), args); + if(sensor != NULL) { + sensor->temp_offset = temp_offset; + unitemp_sensors_add(sensor); + } else { + FURI_LOG_E(APP_NAME, "Failed sensor (%s:%s) mem allocation", name, type); + } + } else { + FURI_LOG_E(APP_NAME, "Unsupported sensor name (%s) or sensor type (%s)", name, type); + } + //Вычисление конца строки + line_end = furi_string_search_char(file, '\n', line_end + 1); + } + + free(file_buf); + file_stream_close(app->file_stream); + stream_free(app->file_stream); + + FURI_LOG_I(APP_NAME, "Sensors have been successfully loaded"); + return true; +} + +bool unitemp_sensors_save(void) { +#ifdef UNITEMP_DEBUG + FURI_LOG_D(APP_NAME, "Saving sensors..."); +#endif + + //Выделение памяти для потока + app->file_stream = file_stream_alloc(app->storage); + + //Переменная пути к файлу + FuriString* filepath = furi_string_alloc(); + //Составление пути к файлу + furi_string_printf(filepath, "%s/%s", APP_PATH_FOLDER, APP_FILENAME_SENSORS); + //Создание папки плагина + storage_common_mkdir(app->storage, APP_PATH_FOLDER); + //Открытие потока + if(!file_stream_open( + app->file_stream, furi_string_get_cstr(filepath), FSAM_READ_WRITE, FSOM_CREATE_ALWAYS)) { + FURI_LOG_E( + APP_NAME, + "An error occurred while saving the sensors file: %d", + file_stream_get_error(app->file_stream)); + //Закрытие потока и освобождение памяти + file_stream_close(app->file_stream); + stream_free(app->file_stream); + return false; + } + + //Сохранение датчиков + for(uint8_t i = 0; i < unitemp_sensors_getActiveCount(); i++) { + Sensor* sensor = unitemp_sensor_getActive(i); + //Замена пробела на ? + for(uint8_t i = 0; i < 10; i++) { + if(sensor->name[i] == ' ') sensor->name[i] = '?'; + } + + stream_write_format( + app->file_stream, + "%s %s %d ", + sensor->name, + sensor->type->typename, + sensor->temp_offset); + + if(sensor->type->interface == &SINGLE_WIRE) { + stream_write_format( + app->file_stream, "%d\n", unitemp_singlewire_sensorGetGPIO(sensor)->num); + } + if(sensor->type->interface == &I2C) { + stream_write_format( + app->file_stream, "%X\n", ((I2CSensor*)sensor->instance)->currentI2CAdr); + } + if(sensor->type->interface == &ONE_WIRE) { + stream_write_format( + app->file_stream, + "%d %02X%02X%02X%02X%02X%02X%02X%02X\n", + ((OneWireSensor*)sensor->instance)->bus->gpio->num, + ((OneWireSensor*)sensor->instance)->deviceID[0], + ((OneWireSensor*)sensor->instance)->deviceID[1], + ((OneWireSensor*)sensor->instance)->deviceID[2], + ((OneWireSensor*)sensor->instance)->deviceID[3], + ((OneWireSensor*)sensor->instance)->deviceID[4], + ((OneWireSensor*)sensor->instance)->deviceID[5], + ((OneWireSensor*)sensor->instance)->deviceID[6], + ((OneWireSensor*)sensor->instance)->deviceID[7]); + } + } + + //Закрытие потока и освобождение памяти + file_stream_close(app->file_stream); + stream_free(app->file_stream); + + FURI_LOG_I(APP_NAME, "Sensors have been successfully saved"); + return true; +} +void unitemp_sensors_reload(void) { + unitemp_sensors_deInit(); + unitemp_sensors_free(); + + unitemp_sensors_load(); + unitemp_sensors_init(); +} + +bool unitemp_sensor_isContains(Sensor* sensor) { + for(uint8_t i = 0; i < unitemp_sensors_getCount(); i++) { + if(app->sensors[i] == sensor) return true; + } + return false; +} + +Sensor* unitemp_sensor_alloc(char* name, const SensorType* type, char* args) { + if(name == NULL || type == NULL) return NULL; + bool status = false; + //Выделение памяти под датчик + Sensor* sensor = malloc(sizeof(Sensor)); + if(sensor == NULL) { + FURI_LOG_E(APP_NAME, "Sensor %s allocation error", name); + return false; + } + + //Выделение памяти под имя + sensor->name = malloc(11); + if(sensor->name == NULL) { + FURI_LOG_E(APP_NAME, "Sensor %s name allocation error", name); + return false; + } + //Запись имени датчка + strcpy(sensor->name, name); + //Тип датчика + sensor->type = type; + //Статус датчика по умолчанию - ошибка + sensor->status = UT_SENSORSTATUS_ERROR; + //Время последнего опроса + sensor->lastPollingTime = + furi_get_tick() - 10000; //чтобы первый опрос произошёл как можно раньше + + sensor->temp = -128.0f; + sensor->hum = -128.0f; + sensor->pressure = -128.0f; + sensor->temp_offset = 0; + //Выделение памяти под инстанс датчика в зависимости от его интерфейса + status = sensor->type->interface->allocator(sensor, args); + + //Выход если датчик успешно развёрнут + if(status) { + FURI_LOG_I(APP_NAME, "Sensor %s allocated", name); + return sensor; + } + //Выход с очисткой если память для датчика не была выделена + free(sensor->name); + free(sensor); + FURI_LOG_E(APP_NAME, "Sensor %s(%s) allocation error", name, type->typename); + return NULL; +} + +void unitemp_sensor_free(Sensor* sensor) { + if(sensor == NULL) { + FURI_LOG_E(APP_NAME, "Null pointer sensor releasing"); + return; + } + if(sensor->type == NULL) { + FURI_LOG_E(APP_NAME, "Sensor type is null"); + return; + } + if(sensor->type->mem_releaser == NULL) { + FURI_LOG_E(APP_NAME, "Sensor releaser is null"); + return; + } + bool status = false; + //Высвобождение памяти под инстанс + status = sensor->type->interface->mem_releaser(sensor); + UNUSED(status); +#ifdef UNITEMP_DEBUG + + if(status) { + FURI_LOG_D(APP_NAME, "Sensor %s memory successfully released", sensor->name); + } else { + FURI_LOG_E(APP_NAME, "Sensor %s memory is not released", sensor->name); + } +#endif + free(sensor->name); + //free(sensor); +} + +void unitemp_sensors_free(void) { + for(uint8_t i = 0; i < unitemp_sensors_getCount(); i++) { + unitemp_sensor_free(app->sensors[i]); + } + app->sensors_count = 0; +} + +bool unitemp_sensors_init(void) { + bool result = true; + + //Перебор датчиков из списка + for(uint8_t i = 0; i < unitemp_sensors_getCount(); i++) { + //Включение 5V если на порту 1 FZ его нет + //Может пропасть при отключении USB + if(furi_hal_power_is_otg_enabled() != true) { + furi_hal_power_enable_otg(); +#ifdef UNITEMP_DEBUG + FURI_LOG_D(APP_NAME, "OTG enabled"); +#endif + } + if(!(*app->sensors[i]->type->initializer)(app->sensors[i])) { + FURI_LOG_E( + APP_NAME, + "An error occurred during sensor initialization %s", + app->sensors[i]->name); + result = false; + } +#ifdef UNITEMP_DEBUG + FURI_LOG_D(APP_NAME, "Sensor %s successfully initialized", app->sensors[i]->name); +#endif + } + app->sensors_ready = true; + return result; +} + +bool unitemp_sensors_deInit(void) { + bool result = true; + //Выключение 5 В если до этого оно не было включено + if(app->settings.lastOTGState != true) { + furi_hal_power_disable_otg(); +#ifdef UNITEMP_DEBUG + FURI_LOG_D(APP_NAME, "OTG disabled"); +#endif + } + + //Перебор датчиков из списка + for(uint8_t i = 0; i < unitemp_sensors_getCount(); i++) { + if(!(*app->sensors[i]->type->deinitializer)(app->sensors[i])) { + FURI_LOG_E( + APP_NAME, + "An error occurred during sensor deinitialization %s", + app->sensors[i]->name); + result = false; + } + } + return result; +} + +UnitempStatus unitemp_sensor_updateData(Sensor* sensor) { + if(sensor == NULL) return UT_SENSORSTATUS_ERROR; + + //Проверка на допустимость опроса датчика + if(furi_get_tick() - sensor->lastPollingTime < sensor->type->pollingInterval) { + //Возврат ошибки если последний опрос датчика был неудачным + if(sensor->status == UT_SENSORSTATUS_TIMEOUT) { + return UT_SENSORSTATUS_TIMEOUT; + } + return UT_SENSORSTATUS_EARLYPOOL; + } + + sensor->lastPollingTime = furi_get_tick(); + + if(!furi_hal_power_is_otg_enabled()) { + furi_hal_power_enable_otg(); + } + + sensor->status = sensor->type->interface->updater(sensor); + +#ifdef UNITEMP_DEBUG + if(sensor->status != UT_SENSORSTATUS_OK && sensor->status != UT_SENSORSTATUS_POLLING) + FURI_LOG_D(APP_NAME, "Sensor %s update status %d", sensor->name, sensor->status); +#endif + + if(app->settings.temp_unit == UT_TEMP_FAHRENHEIT && sensor->status == UT_SENSORSTATUS_OK) + uintemp_celsiumToFarengate(sensor); + if(sensor->status == UT_SENSORSTATUS_OK) { + sensor->temp += sensor->temp_offset / 10.f; + if(app->settings.pressure_unit == UT_PRESSURE_MM_HG) { + unitemp_pascalToMmHg(sensor); + } else if(app->settings.pressure_unit == UT_PRESSURE_IN_HG) { + unitemp_pascalToInHg(sensor); + } else if(app->settings.pressure_unit == UT_PRESSURE_KPA) { + unitemp_pascalToKPa(sensor); + } + } + return sensor->status; +} + +void unitemp_sensors_updateValues(void) { + for(uint8_t i = 0; i < unitemp_sensors_getCount(); i++) { + unitemp_sensor_updateData(unitemp_sensor_getActive(i)); + } +} diff --git a/Applications/Official/DEV_FW/source/unitemp/Sensors.h b/Applications/Official/DEV_FW/source/unitemp/Sensors.h new file mode 100644 index 000000000..cb98e1783 --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/Sensors.h @@ -0,0 +1,325 @@ +/* + Unitemp - Universal temperature reader + Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef UNITEMP_SENSORS +#define UNITEMP_SENSORS +#include +#include + +//Маски бит для определения типов возвращаемых значений +#define UT_TEMPERATURE 0b00000001 +#define UT_HUMIDITY 0b00000010 +#define UT_PRESSURE 0b00000100 + +//Статусы опроса датчика +typedef enum { + UT_DATA_TYPE_TEMP = UT_TEMPERATURE, + UT_DATA_TYPE_TEMP_HUM = UT_TEMPERATURE | UT_HUMIDITY, + UT_DATA_TYPE_TEMP_PRESS = UT_TEMPERATURE | UT_PRESSURE, + UT_DATA_TYPE_TEMP_HUM_PRESS = UT_TEMPERATURE | UT_HUMIDITY | UT_PRESSURE, +} SensorDataType; + +//Типы возвращаемых данных +typedef enum { + UT_SENSORSTATUS_OK, //Всё хорошо, опрос успешен + UT_SENSORSTATUS_TIMEOUT, //Датчик не отозвался + UT_SENSORSTATUS_EARLYPOOL, //Опрос раньше положенной задержки + UT_SENSORSTATUS_BADCRC, //Неверная контрольная сумма + UT_SENSORSTATUS_ERROR, //Прочие ошибки + UT_SENSORSTATUS_POLLING, //В датчике происходит преобразование + UT_SENSORSTATUS_INACTIVE, //Датчик на редактировании или удалён + +} UnitempStatus; + +//Порт ввода/вывода Flipper Zero +typedef struct GPIO { + const uint8_t num; + const char* name; + const GpioPin* pin; +} GPIO; + +typedef struct Sensor Sensor; + +/** + * @brief Указатель функции выделения памяти и подготовки экземпляра датчика + */ +typedef bool(SensorAllocator)(Sensor* sensor, char* args); +/** + * @brief Указатель на функцию высвобождении памяти датчика + */ +typedef bool(SensorFree)(Sensor* sensor); +/** + * @brief Указатель функции инициализации датчика + */ +typedef bool(SensorInitializer)(Sensor* sensor); +/** + * @brief Указатель функции деинициализации датчика + */ +typedef bool(SensorDeinitializer)(Sensor* sensor); +/** + * @brief Указатель функции обновления значения датчика + */ +typedef UnitempStatus(SensorUpdater)(Sensor* sensor); + +//Типы подключения датчиков +typedef struct Interface { + //Имя интерфейса + const char* name; + //Функция выделения памяти интерфейса + SensorAllocator* allocator; + //Функция высвыбождения памяти интерфейса + SensorFree* mem_releaser; + //Функция обновления значения датчика по интерфейсу + SensorUpdater* updater; +} Interface; + +//Типы датчиков +typedef struct { + //Модель датчика + const char* typename; + //Полное имя с аналогами + const char* altname; + //Тип возвращаемых данных + SensorDataType datatype; + //Интерфейс подключения + const Interface* interface; + //Интервал опроса датчика + uint16_t pollingInterval; + //Функция выделения памяти для датчика + SensorAllocator* allocator; + //Функция высвыбождения памяти для датчика + SensorFree* mem_releaser; + //Функция инициализации датчика + SensorInitializer* initializer; + //Функция деинициализация датчика + SensorDeinitializer* deinitializer; + //Функция обновления значения датчка + SensorUpdater* updater; +} SensorType; + +//Датчик +typedef struct Sensor { + //Имя датчика + char* name; + //Температура + float temp; + //Относительная влажность + float hum; + //Атмосферное давление + float pressure; + //Тип датчика + const SensorType* type; + //Статус последнего опроса датчика + UnitempStatus status; + //Время последнего опроса датчика + uint32_t lastPollingTime; + //Смещение по температуре (x10) + int8_t temp_offset; + //Экземпляр датчика + void* instance; +} Sensor; + +extern const Interface SINGLE_WIRE; //Собственный однопроводной протокол датчиков DHTXX и AM23XX +extern const Interface ONE_WIRE; //Однопроводной протокол Dallas +extern const Interface I2C; //I2C_2 (PC0, PC1) +//extern const Interface SPI; + +/* ============================= Датчик(и) ============================= */ +/** + * @brief Выделение памяти под датчик + * + * @param name Имя датчика + * @param type Тип датчика + * @param args Указатель на строку с парамерами датчика + * @return Указатель на датчик в случае успешного выделения памяти, NULL при ошибке + */ +Sensor* unitemp_sensor_alloc(char* name, const SensorType* type, char* args); + +/** + * @brief Высвыбождение памяти конкретного датчка + * @param sensor Указатель на датчик + */ +void unitemp_sensor_free(Sensor* sensor); + +/** + * @brief Обновление данных указанного датчика + * @param sensor Указатель на датчик + * @return Статус опроса датчика + */ +UnitempStatus unitemp_sensor_updateData(Sensor* sensor); + +/** + * @brief Проверка наличия датчика в памяти + * + * @param sensor Указатель на датчик + * @return Истина если этот датчик уже загружен, ложь если это новый датчик + */ +bool unitemp_sensor_isContains(Sensor* sensor); + +/** + * @brief Получить датчик из списка по индексу + * + * @param index Индекс датчика (0 - unitemp_sensors_getCount()) + * @return Указатель на датчик при успехе, NULL при неудаче + */ +Sensor* unitemp_sensor_getActive(uint8_t index); + +/** + * @brief Загрузка датчиков с SD-карты + * @return Истина если загрузка прошла успешно + */ +bool unitemp_sensors_load(); + +/** + * @brief Функция перезагрузки датчиков с SD-карты +*/ +void unitemp_sensors_reload(void); + +/** + * @brief Сохранение датчиков на SD-карту + * @return Истина если сохранение прошло успешно + */ +bool unitemp_sensors_save(void); + +/** + * @brief Удаление датчика + * + * @param sensor Указатель на датчик + */ +void unitemp_sensor_delete(Sensor* sensor); + +/** + * @brief Инициализация загруженных датчиков + * @return Истина если всё прошло успешно + */ +bool unitemp_sensors_init(void); + +/** + * @brief Деинициализация загруженных датчиков + * @return Истина если всё прошло успешно + */ +bool unitemp_sensors_deInit(void); + +/** + * @brief Высвыбождение памяти всех датчиков + */ +void unitemp_sensors_free(void); + +/** + * @brief Обновить данные всех датчиков + */ +void unitemp_sensors_updateValues(void); + +/** + * @brief Получить количество загруженных датчиков + * @return Количество датчиков + */ +uint8_t unitemp_sensors_getCount(void); + +/** + * @brief Добавить датчик в общий список + * @param sensor Указатель на датчик + */ +void unitemp_sensors_add(Sensor* sensor); + +/** +* @brief Получить списк доступных типов датчиков +* @return Указатель на список датчиков +*/ +const SensorType** unitemp_sensors_getTypes(void); + +/** +* @brief Получить количество доступных типов датчиков +* @return Количество доступных типов датчиков +*/ +uint8_t unitemp_sensors_getTypesCount(void); + +/** + * @brief Получить тип сенсора по его индексу + * @param index Индекс типа датчика (от 0 до SENSOR_TYPES_COUNT) + * @return const SensorType* + */ +const SensorType* unitemp_sensors_getTypeFromInt(uint8_t index); + +/** + * @brief Преобразовать строчное название датчка в указатель + * + * @param str Имя датчика в виде строки + * @return Указатель на тип датчика при успехе, иначе NULL + */ +const SensorType* unitemp_sensors_getTypeFromStr(char* str); + +/** + * @brief Получить количество активных датчиков + * + * @return Количество активных датчиков + */ +uint8_t unitemp_sensors_getActiveCount(void); + +/* ============================= GPIO ============================= */ +/** + * @brief Конвертация номера порта на корпусе FZ в GPIO + * @param name Номер порта на корпусе FZ + * @return Указатель на GPIO при успехе, NULL при ошибке + */ +const GPIO* unitemp_gpio_getFromInt(uint8_t name); +/** + * @brief Конвертация GPIO в номер на корпусе FZ + * @param gpio Указатель на порт + * @return Номер порта на корпусе FZ + */ +uint8_t unitemp_gpio_toInt(const GPIO* gpio); + +/** + * @brief Блокировка GPIO указанным интерфейсом + * @param gpio Указатель на порт + * @param interface Указатель на интерфейс, которым порт будет занят + */ +void unitemp_gpio_lock(const GPIO* gpio, const Interface* interface); + +/** + * @brief Разблокировка порта + * @param gpio Указатель на порт + */ +void unitemp_gpio_unlock(const GPIO* gpio); +/** + * @brief Получить количество доступных портов для указанного интерфейса + * @param interface Указатель на интерфейс + * @return Количество доступных портов + */ +uint8_t unitemp_gpio_getAviablePortsCount(const Interface* interface, const GPIO* extraport); +/** + * @brief Получить указатель на доступный для интерфейса порт по индексу + * @param interface Указатель на интерфейс + * @param index Номер порта (от 0 до unitemp_gpio_getAviablePortsCount()) + * @param extraport Указатель на дополнительный порт, который будет принудительно считаться доступным. Можно указать NULL если не требуется + * @return Указатель на доступный порт + */ +const GPIO* + unitemp_gpio_getAviablePort(const Interface* interface, uint8_t index, const GPIO* extraport); + +/* Датчики */ +//DHTxx и их производные +#include "./interfaces/SingleWireSensor.h" +//DS18x2x +#include "./interfaces/OneWireSensor.h" +#include "./sensors/LM75.h" +//BMP280, BME280 +#include "./sensors/BMx280.h" +#include "./sensors/AM2320.h" +#include "./sensors/DHT20.h" +#endif diff --git a/Applications/Official/DEV_FW/source/unitemp/application.fam b/Applications/Official/DEV_FW/source/unitemp/application.fam new file mode 100644 index 000000000..da4316b62 --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/application.fam @@ -0,0 +1,20 @@ +App( + appid="Temp_Sensors_Reader", + name="Temp Sensors Reader", + apptype=FlipperAppType.EXTERNAL, + entry_point="unitemp_app", + cdefines=["UNITEMP_APP"], + requires=[ + "gui", + ], + stack_size=2 * 1024, + order=100, + fap_description = "Universal temperature sensors reader", + fap_author = "Quenon", + fap_weburl = "https://github.com/quen0n/Unitemp-Flipper-Zero-Plugin", + fap_category="GPIO", + fap_icon="icon.png", + fap_icon_assets="assets", + fap_libs=["assets"], + fap_icon_assets_symbol="unitemp", +) \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/unitemp/assets/README.MD b/Applications/Official/DEV_FW/source/unitemp/assets/README.MD new file mode 100644 index 000000000..7c359a052 --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/assets/README.MD @@ -0,0 +1,3 @@ +# Unitemp assets + +Created by [@Svaarich](https://github.com/Svaarich) diff --git a/Applications/Official/DEV_FW/source/unitemp/assets/flipper_happy_2_60x38.png b/Applications/Official/DEV_FW/source/unitemp/assets/flipper_happy_2_60x38.png new file mode 100644 index 000000000..c4322c237 Binary files /dev/null and b/Applications/Official/DEV_FW/source/unitemp/assets/flipper_happy_2_60x38.png differ diff --git a/Applications/Official/DEV_FW/source/unitemp/assets/flipper_happy_60x38.png b/Applications/Official/DEV_FW/source/unitemp/assets/flipper_happy_60x38.png new file mode 100644 index 000000000..db57e12c2 Binary files /dev/null and b/Applications/Official/DEV_FW/source/unitemp/assets/flipper_happy_60x38.png differ diff --git a/Applications/Official/DEV_FW/source/unitemp/assets/flipper_sad_60x38.png b/Applications/Official/DEV_FW/source/unitemp/assets/flipper_sad_60x38.png new file mode 100644 index 000000000..994cf4bde Binary files /dev/null and b/Applications/Official/DEV_FW/source/unitemp/assets/flipper_sad_60x38.png differ diff --git a/Applications/Official/DEV_FW/source/unitemp/assets/hum_9x15.png b/Applications/Official/DEV_FW/source/unitemp/assets/hum_9x15.png new file mode 100644 index 000000000..70cedb8f5 Binary files /dev/null and b/Applications/Official/DEV_FW/source/unitemp/assets/hum_9x15.png differ diff --git a/Applications/Official/DEV_FW/source/unitemp/assets/in_hg_15x15.png b/Applications/Official/DEV_FW/source/unitemp/assets/in_hg_15x15.png new file mode 100644 index 000000000..a27782482 Binary files /dev/null and b/Applications/Official/DEV_FW/source/unitemp/assets/in_hg_15x15.png differ diff --git a/Applications/Official/DEV_FW/source/unitemp/assets/mm_hg_15x15.png b/Applications/Official/DEV_FW/source/unitemp/assets/mm_hg_15x15.png new file mode 100644 index 000000000..66b5dc6ea Binary files /dev/null and b/Applications/Official/DEV_FW/source/unitemp/assets/mm_hg_15x15.png differ diff --git a/Applications/Official/DEV_FW/source/unitemp/assets/pressure_7x13.png b/Applications/Official/DEV_FW/source/unitemp/assets/pressure_7x13.png new file mode 100644 index 000000000..43aaa7bbc Binary files /dev/null and b/Applications/Official/DEV_FW/source/unitemp/assets/pressure_7x13.png differ diff --git a/Applications/Official/DEV_FW/source/unitemp/assets/repo_qr_50x50.png b/Applications/Official/DEV_FW/source/unitemp/assets/repo_qr_50x50.png new file mode 100644 index 000000000..5bf668497 Binary files /dev/null and b/Applications/Official/DEV_FW/source/unitemp/assets/repo_qr_50x50.png differ diff --git a/Applications/Official/DEV_FW/source/unitemp/assets/sherlok_53x45.png b/Applications/Official/DEV_FW/source/unitemp/assets/sherlok_53x45.png new file mode 100644 index 000000000..1f258737e Binary files /dev/null and b/Applications/Official/DEV_FW/source/unitemp/assets/sherlok_53x45.png differ diff --git a/Applications/Official/DEV_FW/source/unitemp/assets/temp_C_11x14.png b/Applications/Official/DEV_FW/source/unitemp/assets/temp_C_11x14.png new file mode 100644 index 000000000..19cf423b4 Binary files /dev/null and b/Applications/Official/DEV_FW/source/unitemp/assets/temp_C_11x14.png differ diff --git a/Applications/Official/DEV_FW/source/unitemp/assets/temp_F_11x14.png b/Applications/Official/DEV_FW/source/unitemp/assets/temp_F_11x14.png new file mode 100644 index 000000000..5ed469337 Binary files /dev/null and b/Applications/Official/DEV_FW/source/unitemp/assets/temp_F_11x14.png differ diff --git a/Applications/Official/DEV_FW/source/unitemp/icon.png b/Applications/Official/DEV_FW/source/unitemp/icon.png new file mode 100644 index 000000000..f0958ecdc Binary files /dev/null and b/Applications/Official/DEV_FW/source/unitemp/icon.png differ diff --git a/Applications/Official/DEV_FW/source/unitemp/interfaces/I2CSensor.c b/Applications/Official/DEV_FW/source/unitemp/interfaces/I2CSensor.c new file mode 100644 index 000000000..72d959e27 --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/interfaces/I2CSensor.c @@ -0,0 +1,131 @@ +/* + Unitemp - Universal temperature reader + Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n) + + 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 . +*/ +#include "I2CSensor.h" + +static uint8_t sensors_count = 0; + +void unitemp_i2c_acquire(FuriHalI2cBusHandle* handle) { + furi_hal_i2c_acquire(handle); + LL_GPIO_SetPinPull(gpio_ext_pc1.port, gpio_ext_pc1.pin, LL_GPIO_PULL_UP); + LL_GPIO_SetPinPull(gpio_ext_pc0.port, gpio_ext_pc0.pin, LL_GPIO_PULL_UP); +} + +bool unitemp_i2c_isDeviceReady(I2CSensor* i2c_sensor) { + unitemp_i2c_acquire(i2c_sensor->i2c); + bool status = furi_hal_i2c_is_device_ready(i2c_sensor->i2c, i2c_sensor->currentI2CAdr, 10); + furi_hal_i2c_release(i2c_sensor->i2c); + return status; +} + +uint8_t unitemp_i2c_readReg(I2CSensor* i2c_sensor, uint8_t reg) { + //Блокировка шины + unitemp_i2c_acquire(i2c_sensor->i2c); + uint8_t buff[1] = {0}; + furi_hal_i2c_read_mem(i2c_sensor->i2c, i2c_sensor->currentI2CAdr, reg, buff, 1, 10); + furi_hal_i2c_release(i2c_sensor->i2c); + return buff[0]; +} + +bool unitemp_i2c_readArray(I2CSensor* i2c_sensor, uint8_t len, uint8_t* data) { + unitemp_i2c_acquire(i2c_sensor->i2c); + bool status = furi_hal_i2c_rx(i2c_sensor->i2c, i2c_sensor->currentI2CAdr, data, len, 10); + furi_hal_i2c_release(i2c_sensor->i2c); + return status; +} + +bool unitemp_i2c_readRegArray(I2CSensor* i2c_sensor, uint8_t startReg, uint8_t len, uint8_t* data) { + unitemp_i2c_acquire(i2c_sensor->i2c); + bool status = + furi_hal_i2c_read_mem(i2c_sensor->i2c, i2c_sensor->currentI2CAdr, startReg, data, len, 10); + furi_hal_i2c_release(i2c_sensor->i2c); + return status; +} + +bool unitemp_i2c_writeReg(I2CSensor* i2c_sensor, uint8_t reg, uint8_t value) { + //Блокировка шины + unitemp_i2c_acquire(i2c_sensor->i2c); + uint8_t buff[1] = {value}; + bool status = + furi_hal_i2c_write_mem(i2c_sensor->i2c, i2c_sensor->currentI2CAdr, reg, buff, 1, 10); + furi_hal_i2c_release(i2c_sensor->i2c); + return status; +} + +bool unitemp_i2c_writeArray(I2CSensor* i2c_sensor, uint8_t len, uint8_t* data) { + unitemp_i2c_acquire(i2c_sensor->i2c); + bool status = furi_hal_i2c_tx(i2c_sensor->i2c, i2c_sensor->currentI2CAdr, data, len, 10); + furi_hal_i2c_release(i2c_sensor->i2c); + return status; +} + +bool unitemp_i2c_writeRegArray(I2CSensor* i2c_sensor, uint8_t startReg, uint8_t len, uint8_t* data) { + //Блокировка шины + unitemp_i2c_acquire(i2c_sensor->i2c); + bool status = furi_hal_i2c_write_mem( + i2c_sensor->i2c, i2c_sensor->currentI2CAdr, startReg, data, len, 10); + furi_hal_i2c_release(i2c_sensor->i2c); + return status; +} + +bool unitemp_I2C_sensor_alloc(Sensor* sensor, char* args) { + bool status = false; + I2CSensor* instance = malloc(sizeof(I2CSensor)); + if(instance == NULL) { + FURI_LOG_E(APP_NAME, "Sensor %s instance allocation error", sensor->name); + return false; + } + instance->i2c = &furi_hal_i2c_handle_external; + sensor->instance = instance; + + //Указание функций инициализации, деинициализации и обновления данных, а так же адреса на шине I2C + status = sensor->type->allocator(sensor, args); + int i2c_addr; + sscanf(args, "%X", &i2c_addr); + + //Установка адреса шины I2C + if(i2c_addr >= instance->minI2CAdr && i2c_addr <= instance->maxI2CAdr) { + instance->currentI2CAdr = i2c_addr; + } else { + instance->currentI2CAdr = instance->minI2CAdr; + } + + //Блокировка портов GPIO + sensors_count++; + unitemp_gpio_lock(unitemp_gpio_getFromInt(15), &I2C); + unitemp_gpio_lock(unitemp_gpio_getFromInt(16), &I2C); + + return status; +} + +bool unitemp_I2C_sensor_free(Sensor* sensor) { + bool status = sensor->type->mem_releaser(sensor); + free(sensor->instance); + if(--sensors_count == 0) { + unitemp_gpio_unlock(unitemp_gpio_getFromInt(15)); + unitemp_gpio_unlock(unitemp_gpio_getFromInt(16)); + } + + return status; +} + +UnitempStatus unitemp_I2C_sensor_update(Sensor* sensor) { + if(sensor->status != UT_SENSORSTATUS_OK) { + sensor->type->initializer(sensor); + } + return sensor->type->updater(sensor); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/unitemp/interfaces/I2CSensor.h b/Applications/Official/DEV_FW/source/unitemp/interfaces/I2CSensor.h new file mode 100644 index 000000000..3df709d9a --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/interfaces/I2CSensor.h @@ -0,0 +1,128 @@ +/* + Unitemp - Universal temperature reader + Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef UNITEMP_I2C +#define UNITEMP_I2C + +#include "../unitemp.h" + +#include + +//Структура I2C датчика +typedef struct I2CSensor { + //Указатель на интерфейс I2C + FuriHalI2cBusHandle* i2c; + //Минимальный адрес устройства на шине I2C + uint8_t minI2CAdr; + //Максимальный адрес устройства на шине I2C + uint8_t maxI2CAdr; + //Текущий адрес устройства на шине I2C + uint8_t currentI2CAdr; + //Указатель на собственный экземпляр датчика + void* sensorInstance; +} I2CSensor; + +/** + * @brief Заблокировать шину I2C + * + * @param handle Указатель на шину + */ +void unitemp_i2c_acquire(FuriHalI2cBusHandle* handle); + +/** + * @brief Проверить наличие датчика на шине + * + * @param i2c_sensor Указатель на датчик + * @return Истина если устройство отозвалось + */ +bool unitemp_i2c_isDeviceReady(I2CSensor* i2c_sensor); + +/** + * @brief Выделение памяти для датчика на шине I2C + * @param sensor Указатель на датчик + * @param st Тип датчика + * @return Истина если всё ок + */ +bool unitemp_I2C_sensor_alloc(Sensor* sensor, char* args); + +/** + * @brief Высвобождение памяти инстанса датчика + * @param sensor Указатель на датчик + */ +bool unitemp_I2C_sensor_free(Sensor* sensor); + +/** + * @brief Обновить значение с датчка + * @param sensor Указатель на датчик + * @return Статус обновления + */ +UnitempStatus unitemp_I2C_sensor_update(Sensor* sensor); +/** + * @brief Прочитать значение регистра reg + * @param i2c_sensor Указатель на инстанс датчика + * @param reg Номер регистра + * @return Значение регистра + */ +uint8_t unitemp_i2c_readReg(I2CSensor* i2c_sensor, uint8_t reg); + +/** + * @brief Прочитать масссив значений из памяти + * @param i2c_sensor Указатель на инстанс датчика + * @param startReg Адрес регистра с которого начнётся чтение + * @param len Количество байт для считывания из регистра + * @param data Указатель на массив куда будут считаны данные + * @return Истина если устройство вернуло данные + */ +bool unitemp_i2c_readRegArray(I2CSensor* i2c_sensor, uint8_t startReg, uint8_t len, uint8_t* data); + +/** + * @brief Записать значение в регистр + * @param i2c_sensor Указатель на инстанс датчика + * @param reg Номер регистра + * @param value Значение для записи + * @return Истина если значение записано + */ +bool unitemp_i2c_writeReg(I2CSensor* i2c_sensor, uint8_t reg, uint8_t value); + +/** + * @brief Записать масссив значений в память + * @param i2c_sensor Указатель на инстанс датчика + * @param startReg Адрес регистра с которого начнётся запись + * @param len Количество байт для считывания из регистра + * @param data Указатель на массив откуда будут записаны данные + * @return Истина если устройство вернуло данные + */ +bool unitemp_i2c_writeRegArray(I2CSensor* i2c_sensor, uint8_t startReg, uint8_t len, uint8_t* data); + +/** + * @brief Прочитать массив данных по шине I2C + * @param i2c_sensor Указатель на инстанс датчика + * @param startReg Адрес регистра с которого начнётся чтение + * @param data Указатель на массив куда будут считаны данные + * @return Истина если устройство вернуло данные + */ +bool unitemp_i2c_readArray(I2CSensor* i2c_sensor, uint8_t len, uint8_t* data); + +/** + * @brief Записать масссив данных по шине I2C + * @param i2c_sensor Указатель на инстанс датчика + * @param len Количество байт для считывания из регистра + * @param data Указатель на массив откуда будут записаны данные + * @return Истина если устройство вернуло данные + */ +bool unitemp_i2c_writeArray(I2CSensor* i2c_sensor, uint8_t len, uint8_t* data); +#endif \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/unitemp/interfaces/OneWireSensor.c b/Applications/Official/DEV_FW/source/unitemp/interfaces/OneWireSensor.c new file mode 100644 index 000000000..740ba3365 --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/interfaces/OneWireSensor.c @@ -0,0 +1,490 @@ +/* + Unitemp - Universal temperature reader + Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n) + + 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 . +*/ +//Использован код Дмитрия Погребняка: https://aterlux.ru/article/1wire + +#include "OneWireSensor.h" +#include +#include +#include + +const SensorType Dallas = { + .typename = "Dallas", + .altname = "Dallas (DS18x2x)", + .interface = &ONE_WIRE, + .datatype = UT_DATA_TYPE_TEMP, + .pollingInterval = 1000, + .allocator = unitemp_onewire_sensor_alloc, + .mem_releaser = unitemp_onewire_sensor_free, + .initializer = unitemp_onewire_sensor_init, + .deinitializer = unitemp_onewire_sensor_deinit, + .updater = unitemp_onewire_sensor_update}; + +// Переменные для хранения промежуточного результата сканирования шины +// найденный восьмибайтовый адрес +static uint8_t onewire_enum[8] = {0}; +// последний нулевой бит, где была неоднозначность (нумеруя с единицы) +static uint8_t onewire_enum_fork_bit = 65; + +OneWireBus* uintemp_onewire_bus_alloc(const GPIO* gpio) { + if(gpio == NULL) { + return NULL; + } + + //Проверка на наличие шины на этом порте + for(uint8_t i = 0; i < unitemp_sensors_getActiveCount(); i++) { + if(unitemp_sensor_getActive(i)->type->interface == &ONE_WIRE && + ((OneWireSensor*)unitemp_sensor_getActive(i)->instance)->bus->gpio->num == gpio->num) { + //Если шина на этом порту уже есть, то возврат указателя на шину + return ((OneWireSensor*)unitemp_sensor_getActive(i)->instance)->bus; + } + } + + OneWireBus* bus = malloc(sizeof(OneWireBus)); + bus->device_count = 0; + bus->gpio = gpio; + bus->powerMode = PWR_PASSIVE; +#ifdef UNITEMP_DEBUG + FURI_LOG_D(APP_NAME, "one wire bus (port %d) allocated", gpio->num); +#endif + + return bus; +} + +bool unitemp_onewire_bus_init(OneWireBus* bus) { + if(bus == NULL) return false; + bus->device_count++; + //Выход если шина уже была инициализирована + if(bus->device_count > 1) return true; + + unitemp_gpio_lock(bus->gpio, &ONE_WIRE); + //Высокий уровень по умолчанию + furi_hal_gpio_write(bus->gpio->pin, true); + //Режим работы - OpenDrain, подтяжка включается на всякий случай + furi_hal_gpio_init( + bus->gpio->pin, //Порт FZ + GpioModeOutputOpenDrain, //Режим работы - открытый сток + GpioPullUp, //Принудительная подтяжка линии данных к питанию + GpioSpeedVeryHigh); //Скорость работы - максимальная + + return true; +} +bool unitemp_onewire_bus_deinit(OneWireBus* bus) { +#ifdef UNITEMP_DEBUG + FURI_LOG_D(APP_NAME, "devices on wire %d: %d", bus->gpio->num, bus->device_count); +#endif + bus->device_count--; + if(bus->device_count <= 0) { + bus->device_count = 0; + unitemp_gpio_unlock(bus->gpio); + //Режим работы - аналог, подтяжка выключена + furi_hal_gpio_init( + bus->gpio->pin, //Порт FZ + GpioModeAnalog, //Режим работы - аналог + GpioPullNo, //Подтяжка выключена + GpioSpeedLow); //Скорость работы - минимальная + //Низкий уровень по умолчанию + furi_hal_gpio_write(bus->gpio->pin, false); + return true; + } else { + return false; + } +} +bool unitemp_onewire_bus_start(OneWireBus* bus) { + furi_hal_gpio_write(bus->gpio->pin, false); + furi_delay_us(500); + + furi_hal_gpio_write(bus->gpio->pin, true); + + //Ожидание подъёма шины + uint32_t t = furi_get_tick(); + while(!furi_hal_gpio_read(bus->gpio->pin)) { + //Выход если шина не поднялась + if(furi_get_tick() - t > 10) return false; + } + + furi_delay_us(100); + bool status = !furi_hal_gpio_read(bus->gpio->pin); + furi_delay_us(400); + return status; +} + +void unitemp_onewire_bus_send_bit(OneWireBus* bus, bool state) { + //Необходимо для стабильной работы при пассивном питании + if(bus->powerMode == PWR_PASSIVE) furi_delay_us(100); + + if(state) { + // write 1 + furi_hal_gpio_write(bus->gpio->pin, false); + furi_delay_us(1); + furi_hal_gpio_write(bus->gpio->pin, true); + furi_delay_us(90); + } else { + furi_hal_gpio_write(bus->gpio->pin, false); + furi_delay_us(90); + furi_hal_gpio_write(bus->gpio->pin, true); + //Ожидание подъёма шины + uint32_t t = furi_get_tick(); + while(!furi_hal_gpio_read(bus->gpio->pin)) { + //Выход если шина не поднялась + if(furi_get_tick() - t > 10) return; + } + } +} + +void unitemp_onewire_bus_send_byte(OneWireBus* bus, uint8_t data) { + for(int i = 0; i < 8; i++) { + unitemp_onewire_bus_send_bit(bus, (data & (1 << i)) != 0); + } +} + +void unitemp_onewire_bus_send_byteArray(OneWireBus* bus, uint8_t* data, uint8_t len) { + for(uint8_t i = 0; i < len; i++) { + unitemp_onewire_bus_send_byte(bus, data[i]); + } +} + +bool unitemp_onewire_bus_read_bit(OneWireBus* bus) { + furi_delay_ms(1); + furi_hal_gpio_write(bus->gpio->pin, false); + furi_delay_us(2); // Длительность низкого уровня, минимум 1 мкс + furi_hal_gpio_write(bus->gpio->pin, true); + furi_delay_us(8); // Пауза до момента сэмплирования, всего не более 15 мкс + bool r = furi_hal_gpio_read(bus->gpio->pin); + furi_delay_us(80); // Ожидание до следующего тайм-слота, минимум 60 мкс с начала низкого уровня + return r; +} + +uint8_t unitemp_onewire_bus_read_byte(OneWireBus* bus) { + uint8_t r = 0; + for(uint8_t p = 8; p; p--) { + r >>= 1; + if(unitemp_onewire_bus_read_bit(bus)) r |= 0x80; + } + return r; +} + +void unitemp_onewire_bus_read_byteArray(OneWireBus* bus, uint8_t* data, uint8_t len) { + for(uint8_t i = 0; i < len; i++) { + data[i] = unitemp_onewire_bus_read_byte(bus); + } +} + +static uint8_t onewire_CRC_update(uint8_t crc, uint8_t b) { + for(uint8_t p = 8; p; p--) { + crc = ((crc ^ b) & 1) ? (crc >> 1) ^ 0b10001100 : (crc >> 1); + b >>= 1; + } + return crc; +} + +bool unitemp_onewire_CRC_check(uint8_t* data, uint8_t len) { + uint8_t crc = 0; + for(uint8_t i = 0; i < len; i++) { + crc = onewire_CRC_update(crc, data[i]); + } + return !crc; +} + +char* unitemp_onewire_sensor_getModel(Sensor* sensor) { + OneWireSensor* ow_sensor = sensor->instance; + switch(ow_sensor->deviceID[0]) { + case FC_DS18B20: + return "DS18B20"; + case FC_DS18S20: + return "DS18S20"; + case FC_DS1822: + return "DS1822"; + default: + return "unknown"; + } +} + +bool unitemp_onewire_sensor_readID(OneWireSensor* instance) { + if(!unitemp_onewire_bus_start(instance->bus)) return false; + unitemp_onewire_bus_send_byte(instance->bus, 0x33); // Чтение ПЗУ + unitemp_onewire_bus_read_byteArray(instance->bus, instance->deviceID, 8); + if(!unitemp_onewire_CRC_check(instance->deviceID, 8)) { + memset(instance->deviceID, 0, 8); + return false; + } + instance->familyCode = instance->deviceID[0]; + return true; +} + +void unitemp_onewire_bus_enum_init(void) { + for(uint8_t p = 0; p < 8; p++) { + onewire_enum[p] = 0; + } + onewire_enum_fork_bit = 65; // правее правого +} + +uint8_t* unitemp_onewire_bus_enum_next(OneWireBus* bus) { + furi_delay_ms(10); + if(!onewire_enum_fork_bit) { // Если на предыдущем шаге уже не было разногласий +#ifdef UNITEMP_DEBUG + FURI_LOG_D(APP_NAME, "All devices on wire %d is found", unitemp_gpio_toInt(bus->gpio)); +#endif + return 0; // то просто выходим ничего не возвращая + } + if(!unitemp_onewire_bus_start(bus)) { +#ifdef UNITEMP_DEBUG + FURI_LOG_D(APP_NAME, "Wire %d is empty", unitemp_gpio_toInt(bus->gpio)); +#endif + return 0; + } + uint8_t bp = 8; + uint8_t* pprev = &onewire_enum[0]; + uint8_t prev = *pprev; + uint8_t next = 0; + + uint8_t p = 1; + unitemp_onewire_bus_send_byte(bus, 0xF0); + uint8_t newfork = 0; + for(;;) { + uint8_t not0 = unitemp_onewire_bus_read_bit(bus); + uint8_t not1 = unitemp_onewire_bus_read_bit(bus); + if(!not0) { // Если присутствует в адресах бит ноль + if(!not1) { // Но также присустствует бит 1 (вилка) + if(p < + onewire_enum_fork_bit) { // Если мы левее прошлого правого конфликтного бита, + if(prev & 1) { + next |= 0x80; // то копируем значение бита из прошлого прохода + } else { + newfork = p; // если ноль, то запомним конфликтное место + } + } else if(p == onewire_enum_fork_bit) { + next |= + 0x80; // если на этом месте в прошлый раз был правый конфликт с нулём, выведем 1 + } else { + newfork = p; // правее - передаём ноль и запоминаем конфликтное место + } + } // в противном случае идём, выбирая ноль в адресе + } else { + if(!not1) { // Присутствует единица + next |= 0x80; + } else { // Нет ни нулей ни единиц - ошибочная ситуация +#ifdef UNITEMP_DEBUG + FURI_LOG_D(APP_NAME, "Wrong wire %d situation", unitemp_gpio_toInt(bus->gpio)); +#endif + return 0; + } + } + unitemp_onewire_bus_send_bit(bus, next & 0x80); + bp--; + if(!bp) { + *pprev = next; + if(p >= 64) break; + next = 0; + pprev++; + prev = *pprev; + bp = 8; + } else { + if(p >= 64) break; + prev >>= 1; + next >>= 1; + } + p++; + } + onewire_enum_fork_bit = newfork; + return &onewire_enum[0]; +} + +void unitemp_onewire_bus_select_sensor(OneWireSensor* instance) { + unitemp_onewire_bus_send_byte(instance->bus, 0x55); + unitemp_onewire_bus_send_byteArray(instance->bus, instance->deviceID, 8); +} + +bool unitemp_onewire_sensor_alloc(Sensor* sensor, char* args) { + OneWireSensor* instance = malloc(sizeof(OneWireSensor)); + if(instance == NULL) { + FURI_LOG_E(APP_NAME, "Sensor %s instance allocation error", sensor->name); + return false; + } + sensor->instance = instance; + //Очистка адреса + memset(instance->deviceID, 0, 8); + + int gpio, addr_0, addr_1, addr_2, addr_3, addr_4, addr_5, addr_6, addr_7; + sscanf( + args, + "%d %2X%2X%2X%2X%2X%2X%2X%2X", + &gpio, + &addr_0, + &addr_1, + &addr_2, + &addr_3, + &addr_4, + &addr_5, + &addr_6, + &addr_7); + instance->deviceID[0] = addr_0; + instance->deviceID[1] = addr_1; + instance->deviceID[2] = addr_2; + instance->deviceID[3] = addr_3; + instance->deviceID[4] = addr_4; + instance->deviceID[5] = addr_5; + instance->deviceID[6] = addr_6; + instance->deviceID[7] = addr_7; + + instance->familyCode = instance->deviceID[0]; + + instance->bus = uintemp_onewire_bus_alloc(unitemp_gpio_getFromInt(gpio)); + + if(instance != NULL) { + return true; + } + FURI_LOG_E(APP_NAME, "Sensor %s bus allocation error", sensor->name); + free(instance); + return false; +} + +bool unitemp_onewire_sensor_free(Sensor* sensor) { + if(((OneWireSensor*)sensor->instance)->bus != NULL) { + if(((OneWireSensor*)sensor->instance)->bus->device_count == 0) { + free(((OneWireSensor*)sensor->instance)->bus); + } + } + + free(sensor->instance); + + return true; +} + +bool unitemp_onewire_sensor_init(Sensor* sensor) { + OneWireSensor* instance = sensor->instance; + if(instance == NULL || instance->bus == NULL) { + FURI_LOG_E(APP_NAME, "Sensor pointer is null!"); + return false; + } + + unitemp_onewire_bus_init(instance->bus); + furi_delay_ms(1); + + if(instance->familyCode == FC_DS18B20 || instance->familyCode == FC_DS1822) { + //Установка разрядности в 10 бит + if(!unitemp_onewire_bus_start(instance->bus)) return false; + unitemp_onewire_bus_select_sensor(instance); + unitemp_onewire_bus_send_byte(instance->bus, 0x4E); // Запись в память + uint8_t buff[3]; + //Значения тревоги + buff[0] = 0x4B; //Значение нижнего предела температуры + buff[1] = 0x46; //Значение верхнего предела температуры + //Конфигурация + buff[2] = 0b01111111; //12 бит разрядность преобразования + unitemp_onewire_bus_send_byteArray(instance->bus, buff, 3); + + //Сохранение значений в EEPROM для автоматического восстановления после сбоев питания + if(!unitemp_onewire_bus_start(instance->bus)) return false; + unitemp_onewire_bus_select_sensor(instance); + unitemp_onewire_bus_send_byte(instance->bus, 0x48); // Запись в EEPROM + } + + return true; +} + +bool unitemp_onewire_sensor_deinit(Sensor* sensor) { + OneWireSensor* instance = sensor->instance; + if(instance == NULL || instance->bus == NULL) return false; + unitemp_onewire_bus_deinit(instance->bus); + + return true; +} + +UnitempStatus unitemp_onewire_sensor_update(Sensor* sensor) { + //Снятие особого статуса с датчика при пассивном режиме питания + if(sensor->status == UT_SENSORSTATUS_EARLYPOOL) { + return UT_SENSORSTATUS_POLLING; + } + + OneWireSensor* instance = sensor->instance; + uint8_t buff[9] = {0}; + if(sensor->status != UT_SENSORSTATUS_POLLING) { + //Если датчик в прошлый раз не отозвался, проверка его наличия на шине + if(sensor->status == UT_SENSORSTATUS_TIMEOUT || sensor->status == UT_SENSORSTATUS_BADCRC) { + if(!unitemp_onewire_bus_start(instance->bus)) return UT_SENSORSTATUS_TIMEOUT; + unitemp_onewire_bus_select_sensor(instance); + unitemp_onewire_bus_send_byte(instance->bus, 0xBE); // Read Scratch-pad + unitemp_onewire_bus_read_byteArray(instance->bus, buff, 9); + if(!unitemp_onewire_CRC_check(buff, 9)) { +#ifdef UNITEMP_DEBUG + FURI_LOG_D(APP_NAME, "Sensor %s is not found", sensor->name); +#endif + return UT_SENSORSTATUS_TIMEOUT; + } + } + + if(!unitemp_onewire_bus_start(instance->bus)) return UT_SENSORSTATUS_TIMEOUT; + //Запуск преобразования на всех датчиках в режиме пассивного питания + if(instance->bus->powerMode == PWR_PASSIVE) { + unitemp_onewire_bus_send_byte(instance->bus, 0xCC); // skip addr + //Установка на всех датчиках этой шины особого статуса, чтобы не запускать преобразование ещё раз + for(uint8_t i = 0; i < unitemp_sensors_getActiveCount(); i++) { + if(unitemp_sensor_getActive(i)->type->interface == &ONE_WIRE && + ((OneWireSensor*)unitemp_sensor_getActive(i)->instance)->bus == instance->bus) { + unitemp_sensor_getActive(i)->status = UT_SENSORSTATUS_EARLYPOOL; + } + } + + } else { + unitemp_onewire_bus_select_sensor(instance); + } + + unitemp_onewire_bus_send_byte(instance->bus, 0x44); // convert t + if(instance->bus->powerMode == PWR_PASSIVE) { + furi_hal_gpio_write(instance->bus->gpio->pin, true); + furi_hal_gpio_init( + instance->bus->gpio->pin, GpioModeOutputPushPull, GpioPullUp, GpioSpeedVeryHigh); + } + return UT_SENSORSTATUS_POLLING; + } else { + if(instance->bus->powerMode == PWR_PASSIVE) { + furi_hal_gpio_write(instance->bus->gpio->pin, true); + furi_hal_gpio_init( + instance->bus->gpio->pin, GpioModeOutputOpenDrain, GpioPullUp, GpioSpeedVeryHigh); + } + if(!unitemp_onewire_bus_start(instance->bus)) return UT_SENSORSTATUS_TIMEOUT; + unitemp_onewire_bus_select_sensor(instance); + unitemp_onewire_bus_send_byte(instance->bus, 0xBE); // Read Scratch-pad + unitemp_onewire_bus_read_byteArray(instance->bus, buff, 9); + if(!unitemp_onewire_CRC_check(buff, 9)) { +#ifdef UNITEMP_DEBUG + FURI_LOG_D(APP_NAME, "Failed CRC check: %s", sensor->name); +#endif + return UT_SENSORSTATUS_BADCRC; + } + int16_t raw = buff[0] | ((int16_t)buff[1] << 8); + if(instance->familyCode == FC_DS18S20) { + //Песевдо-12-бит. Отключено из-за неестественности и нестабильности показаний по сравнению с DS18B20 + //sensor->temp = ((float)raw / 2.0f) - 0.25f + (16.0f - buff[6]) / 16.0f; + //Честные 9 бит + sensor->temp = ((float)raw / 2.0f); + } else { + sensor->temp = (float)raw / 16.0f; + } + } + + return UT_SENSORSTATUS_OK; +} + +bool unitemp_onewire_id_compare(uint8_t* id1, uint8_t* id2) { + if(id1 == NULL || id2 == NULL) return false; + for(uint8_t i = 0; i < 8; i++) { + if(id1[i] != id2[i]) return false; + } + return true; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/unitemp/interfaces/OneWireSensor.h b/Applications/Official/DEV_FW/source/unitemp/interfaces/OneWireSensor.h new file mode 100644 index 000000000..f2b148535 --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/interfaces/OneWireSensor.h @@ -0,0 +1,223 @@ +/* + Unitemp - Universal temperature reader + Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef UNITEMP_OneWire +#define UNITEMP_OneWire + +#include "../unitemp.h" + +//Коды семейства устройств +typedef enum DallasFamilyCode { + FC_DS18S20 = 0x10, + FC_DS1822 = 0x22, + FC_DS18B20 = 0x28, +} DallasFamilyCode; + +//Режим питания датчка +typedef enum PowerMode { + PWR_PASSIVE, //Питание от линии данных + PWR_ACTIVE //Питание от источника питания +} PowerMode; + +//Инстанс шины one wire +typedef struct { + //Порт подключения датчика + const GPIO* gpio; + //Количество устройств на шине + //Обновляется при ручном добавлении датчика на эту шину + int8_t device_count; + //Режим питания датчиков на шине + PowerMode powerMode; +} OneWireBus; + +//Инстанс датчика one wire +typedef struct OneWireSensor { + //Указатель на шину OneWire + OneWireBus* bus; + //Текущий адрес устройства на шине OneWire + uint8_t deviceID[8]; + //Код семейства устройств + DallasFamilyCode familyCode; +} OneWireSensor; + +/** + * @brief Выделение памяти для датчика на шине OneWire + * @param sensor Указатель на датчик + * @param args Указатель на массив аргументов с параметрами датчика + * @return Истина если всё ок + */ +bool unitemp_onewire_sensor_alloc(Sensor* sensor, char* args); + +/** + * @brief Высвобождение памяти инстанса датчика + * @param sensor Указатель на датчик + */ +bool unitemp_onewire_sensor_free(Sensor* sensor); + +/** + * @brief Инициализации датчика на шине one wire + * @param sensor Указатель на датчик + * @return Истина если инициализация упспешная + */ +bool unitemp_onewire_sensor_init(Sensor* sensor); + +/** + * @brief Деинициализация датчика + * @param sensor Указатель на датчик + */ +bool unitemp_onewire_sensor_deinit(Sensor* sensor); + +/** + * @brief Обновить значение с датчка + * @param sensor Указатель на датчик + * @return Статус обновления + */ +UnitempStatus unitemp_onewire_sensor_update(Sensor* sensor); + +/** + * @brief Выделение памяти для шины one wire и её инициализация + * @param gpio Порт на котором необходимо создать шину + * @return При успехе возвращает указатель на шину one wire + */ +OneWireBus* uintemp_onewire_bus_alloc(const GPIO* gpio); + +/** + * @brief Инициализация шины one wire + * + * @param bus Указатель на шину + * @return Истина если инициализация успешна + */ +bool unitemp_onewire_bus_init(OneWireBus* bus); + +/** + * @brief Деинициализация шины one wire + * + * @param bus Указатель на шину + * @return Истина если шина была деинициализирована, ложь если на шине остались устройства + */ +bool unitemp_onewire_bus_deinit(OneWireBus* bus); + +/** + * @brief Запуск общения с датчиками на шине one wire + * @param bus Указатель на шину + * @return Истина если хотя бы одно устройство отозвалось + */ +bool unitemp_onewire_bus_start(OneWireBus* bus); + +/** + * @brief Отправить 1 бит данных на шину one wire + * @param bus Указатель на шину + * @param state Логический уровень + */ +void unitemp_onewire_bus_send_bit(OneWireBus* bus, bool state); + +/** + * @brief Запись байта на шину one wire + * + * @param bus Указатель на шину one wire + * @param data Записываемый байт + */ +void unitemp_onewire_bus_send_byte(OneWireBus* bus, uint8_t data); + +/** + * @brief Запись массива байт на шину one wire + * + * @param bus Указатель на шину one wire + * @param data Указатель на массив, откуда будут записаны данные + * @param len Количество байт + */ +void unitemp_onewire_bus_send_byteArray(OneWireBus* bus, uint8_t* data, uint8_t len); + +/** + * @brief Чтение бита на шине one wire + * + * @param bus Указатель на шину one wire + * @return Логический уровень бита + */ +bool unitemp_onewire_bus_read_bit(OneWireBus* bus); + +/** + * @brief Чтение байта с шины One Wire + * + * @param bus Указатель на шину one wire + * @return Байт информации + */ +uint8_t unitemp_onewire_bus_read_byte(OneWireBus* bus); + +/** + * @brief Чтение массива байт с шины One Wire + * + * @param bus Указатель на шину one wire + * @param data Указатель на массив, куда будут записаны данные + * @param len Количество байт + */ +void unitemp_onewire_bus_read_byteArray(OneWireBus* bus, uint8_t* data, uint8_t len); + +/** + * @brief Проверить контрольную сумму массива данных + * + * @param data Указатель на массив данных + * @param len Длина массива (включая байт CRC) + * @return Истина если контрольная сумма корректная + */ +bool unitemp_onewire_CRC_check(uint8_t* data, uint8_t len); + +/** + * @brief Получить имя модели датчика на шине One Wire + * + * @param sensor Указатель на датчик + * @return Указатель на строку с названием + */ +char* unitemp_onewire_sensor_getModel(Sensor* sensor); + +/** + * @brief Чтение индификатора единственного датчика. ID запишется в инстанс датчика + * + * @param instance Указатель на инстанс датчика + * @return Истина, если код успешно прочитан, ложь если устройство отсутствует или устройств на шине больше одного + */ +bool unitemp_oneWire_sensor_readID(OneWireSensor* instance); + +/** + * @brief Команда выбора определённого датчка по его ID + * @param instance Указатель на датчик one wire + */ +void unitemp_onewire_bus_select_sensor(OneWireSensor* instance); + +/** + * @brief Инициализация процесса поиска адресов на шине one wire + */ +void unitemp_onewire_bus_enum_init(void); + +/** + * @brief Перечисляет устройства на шине one wire и получает очередной адрес + * @param bus Указатель на шину one wire + * @return Возвращает указатель на буфер, содержащий восьмибайтовое значение адреса, либо NULL, если поиск завешён + */ +uint8_t* unitemp_onewire_bus_enum_next(OneWireBus* bus); + +/** + * @brief Сравнить ID датчиков + * + * @param id1 Указатель на адрес первого датчика + * @param id2 Указатель на адрес второго датчика + * @return Истина если ID индентичны + */ +bool unitemp_onewire_id_compare(uint8_t* id1, uint8_t* id2); + +extern const SensorType Dallas; +#endif \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/unitemp/interfaces/SingleWireSensor.c b/Applications/Official/DEV_FW/source/unitemp/interfaces/SingleWireSensor.c new file mode 100644 index 000000000..e601e086e --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/interfaces/SingleWireSensor.c @@ -0,0 +1,279 @@ +/* + Unitemp - Universal temperature reader + Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n) + + 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 . +*/ +#include "SingleWireSensor.h" + +//Максимальное количество попугаев ожидания датчика +#define POLLING_TIMEOUT_TICKS 500 + +/* Типы датчиков и их параметры */ +const SensorType DHT11 = { + .typename = "DHT11", + .interface = &SINGLE_WIRE, + .datatype = UT_DATA_TYPE_TEMP_HUM, + .pollingInterval = 2000, + .allocator = unitemp_singlewire_alloc, + .mem_releaser = unitemp_singlewire_free, + .initializer = unitemp_singlewire_init, + .deinitializer = unitemp_singlewire_deinit, + .updater = unitemp_singlewire_update}; +const SensorType DHT12_SW = { + .typename = "DHT12", + .interface = &SINGLE_WIRE, + .datatype = UT_DATA_TYPE_TEMP_HUM, + .pollingInterval = 2000, + .allocator = unitemp_singlewire_alloc, + .mem_releaser = unitemp_singlewire_free, + .initializer = unitemp_singlewire_init, + .deinitializer = unitemp_singlewire_deinit, + .updater = unitemp_singlewire_update}; +const SensorType DHT21 = { + .typename = "DHT21", + .altname = "DHT21/AM2301", + .interface = &SINGLE_WIRE, + .datatype = UT_DATA_TYPE_TEMP_HUM, + .pollingInterval = 1000, + .allocator = unitemp_singlewire_alloc, + .mem_releaser = unitemp_singlewire_free, + .initializer = unitemp_singlewire_init, + .deinitializer = unitemp_singlewire_deinit, + .updater = unitemp_singlewire_update}; +const SensorType DHT22 = { + .typename = "DHT22", + .altname = "DHT22/AM2302", + .interface = &SINGLE_WIRE, + .datatype = UT_DATA_TYPE_TEMP_HUM, + .pollingInterval = 2000, + .allocator = unitemp_singlewire_alloc, + .mem_releaser = unitemp_singlewire_free, + .initializer = unitemp_singlewire_init, + .deinitializer = unitemp_singlewire_deinit, + .updater = unitemp_singlewire_update}; +const SensorType AM2320_SW = { + .typename = "AM2320", + .altname = "AM2320 (single wire)", + .interface = &SINGLE_WIRE, + .datatype = UT_DATA_TYPE_TEMP_HUM, + .pollingInterval = 2000, + .allocator = unitemp_singlewire_alloc, + .mem_releaser = unitemp_singlewire_free, + .initializer = unitemp_singlewire_init, + .deinitializer = unitemp_singlewire_deinit, + .updater = unitemp_singlewire_update}; + +bool unitemp_singlewire_alloc(Sensor* sensor, char* args) { + if(args == NULL) return false; + SingleWireSensor* instance = malloc(sizeof(SingleWireSensor)); + if(instance == NULL) { + FURI_LOG_E(APP_NAME, "Sensor %s instance allocation error", sensor->name); + return false; + } + sensor->instance = instance; + + int gpio = 255; + sscanf(args, "%d", &gpio); + + if(unitemp_singlewire_sensorSetGPIO(sensor, unitemp_gpio_getFromInt(gpio))) { + return true; + } + FURI_LOG_E(APP_NAME, "Sensor %s GPIO setting error", sensor->name); + free(instance); + return false; +} +bool unitemp_singlewire_free(Sensor* sensor) { + free(sensor->instance); + + return true; +} + +bool unitemp_singlewire_init(Sensor* sensor) { + SingleWireSensor* instance = ((Sensor*)sensor)->instance; + if(instance == NULL || instance->gpio == NULL) { + FURI_LOG_E(APP_NAME, "Sensor pointer is null!"); + return false; + } + unitemp_gpio_lock(instance->gpio, &SINGLE_WIRE); + //Высокий уровень по умолчанию + furi_hal_gpio_write(instance->gpio->pin, true); + //Режим работы - OpenDrain, подтяжка включается на всякий случай + furi_hal_gpio_init( + instance->gpio->pin, //Порт FZ + GpioModeOutputOpenDrain, //Режим работы - открытый сток + GpioPullUp, //Принудительная подтяжка линии данных к питанию + GpioSpeedVeryHigh); //Скорость работы - максимальная + return true; +} + +bool unitemp_singlewire_deinit(Sensor* sensor) { + SingleWireSensor* instance = ((Sensor*)sensor)->instance; + if(instance == NULL || instance->gpio == NULL) return false; + unitemp_gpio_unlock(instance->gpio); + //Низкий уровень по умолчанию + furi_hal_gpio_write(instance->gpio->pin, false); + //Режим работы - аналог, подтяжка выключена + furi_hal_gpio_init( + instance->gpio->pin, //Порт FZ + GpioModeAnalog, //Режим работы - аналог + GpioPullNo, //Подтяжка выключена + GpioSpeedLow); //Скорость работы - минимальная + return true; +} + +bool unitemp_singlewire_sensorSetGPIO(Sensor* sensor, const GPIO* gpio) { + if(sensor == NULL || gpio == NULL) return false; + SingleWireSensor* instance = sensor->instance; + instance->gpio = gpio; + return true; +} +const GPIO* unitemp_singlewire_sensorGetGPIO(Sensor* sensor) { + if(sensor == NULL) return NULL; + SingleWireSensor* instance = sensor->instance; + return instance->gpio; +} + +UnitempStatus unitemp_singlewire_update(Sensor* sensor) { + SingleWireSensor* instance = sensor->instance; + + //Массив для приёма данных + uint8_t data[5] = {0}; + + /* Запрос */ + //Опускание линии + furi_hal_gpio_write(instance->gpio->pin, false); + //Ожидание более 18 мс + furi_delay_ms(19); + //Выключение прерываний, чтобы ничто не мешало обработке данных + __disable_irq(); + //Подъём линии + furi_hal_gpio_write(instance->gpio->pin, true); + + /* Ответ датчика */ + //Переменная-счётчик + uint16_t timeout = 0; + + //Ожидание подъёма линии + while(!furi_hal_gpio_read(instance->gpio->pin)) { + timeout++; + if(timeout > POLLING_TIMEOUT_TICKS) { + //Включение прерываний + __enable_irq(); + //Возврат признака отсутствующего датчика + return UT_SENSORSTATUS_TIMEOUT; + } + } + timeout = 0; + + //Ожидание спада линии + while(furi_hal_gpio_read(instance->gpio->pin)) { + timeout++; + if(timeout > POLLING_TIMEOUT_TICKS) { + //Включение прерываний + __enable_irq(); + //Возврат признака отсутствующего датчика + return UT_SENSORSTATUS_TIMEOUT; + } + } + + //Ожидание подъёма линии + while(!furi_hal_gpio_read(instance->gpio->pin)) { + timeout++; + if(timeout > POLLING_TIMEOUT_TICKS) { + //Включение прерываний + __enable_irq(); + //Возврат признака отсутствующего датчика + return UT_SENSORSTATUS_TIMEOUT; + } + } + timeout = 0; + + //Ожидание спада линии + while(furi_hal_gpio_read(instance->gpio->pin)) { + timeout++; + if(timeout > POLLING_TIMEOUT_TICKS) { + //Включение прерываний + __enable_irq(); + //Возврат признака отсутствующего датчика + return UT_SENSORSTATUS_TIMEOUT; + } + } + + /* Чтение данных с датчика*/ + //Приём 5 байт + for(uint8_t a = 0; a < 5; a++) { + for(uint8_t b = 7; b != 255; b--) { + uint16_t hT = 0, lT = 0; + //Пока линия в низком уровне, инкремент переменной lT + while(!furi_hal_gpio_read(instance->gpio->pin) && lT != 65535) lT++; + //Пока линия в высоком уровне, инкремент переменной hT + while(furi_hal_gpio_read(instance->gpio->pin) && hT != 65535) hT++; + //Если hT больше lT, то пришла единица + if(hT > lT) data[a] |= (1 << b); + } + } + //Включение прерываний + __enable_irq(); + + //Проверка контрольной суммы + if((uint8_t)(data[0] + data[1] + data[2] + data[3]) != data[4]) { + //Если контрольная сумма не совпала, возврат ошибки + return UT_SENSORSTATUS_BADCRC; + } + + /* Преобразование данных в явный вид */ + //DHT11 и DHT12 + if(sensor->type == &DHT11 || sensor->type == &DHT12_SW) { + sensor->hum = (float)data[0]; + sensor->temp = (float)data[2]; + + //Проверка на отрицательность температуры + if(data[3] != 0) { + //Проверка знака + if(!(data[3] & (1 << 7))) { + //Добавление положительной дробной части + sensor->temp += data[3] * 0.1f; + } else { + //А тут делаем отрицательное значение + data[3] &= ~(1 << 7); + sensor->temp += data[3] * 0.1f; + sensor->temp *= -1; + } + } + } + + //DHT21, DHT22, AM2320 + if(sensor->type == &DHT21 || sensor->type == &DHT22 || sensor->type == &AM2320_SW) { + sensor->hum = (float)(((uint16_t)data[0] << 8) | data[1]) / 10; + + uint16_t raw = (((uint16_t)data[2] << 8) | data[3]); + //Проверка на отрицательность температуры + if(READ_BIT(raw, 1 << 15)) { + //Проверка на способ кодирования данных + if(READ_BIT(raw, 0x60)) { + //Не оригинал + sensor->temp = (float)((int16_t)raw) / 10; + } else { + //Оригинальный датчик + CLEAR_BIT(raw, 1 << 15); + sensor->temp = (float)(raw) / -10; + } + } else { + sensor->temp = (float)(raw) / 10; + } + } + //Возврат признака успешного опроса + return UT_SENSORSTATUS_OK; +} diff --git a/Applications/Official/DEV_FW/source/unitemp/interfaces/SingleWireSensor.h b/Applications/Official/DEV_FW/source/unitemp/interfaces/SingleWireSensor.h new file mode 100644 index 000000000..f5bc74734 --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/interfaces/SingleWireSensor.h @@ -0,0 +1,92 @@ +/* + Unitemp - Universal temperature reader + Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef UNITEMP_SINGLE_WIRE +#define UNITEMP_SINGLE_WIRE + +#include "../unitemp.h" +#include "../Sensors.h" + +//Интерфейс Single Wire +typedef struct { + //Порт подключения датчика + const GPIO* gpio; +} SingleWireSensor; + +/* Датчики */ +extern const SensorType DHT11; +extern const SensorType DHT12_SW; +extern const SensorType DHT21; +extern const SensorType DHT22; +extern const SensorType AM2320_SW; + +/** + * @brief Инициализация датчика + * + * @param sensor Указатель на инициализируемый датчик + * @return Истина если всё прошло успешно + */ +bool unitemp_singlewire_init(Sensor* sensor); + +/** + * @brief Деинициализация датчика + * + * @param sensor Указатель на инициализируемый датчик + * @return Истина если всё прошло успешно + */ +bool unitemp_singlewire_deinit(Sensor* sensor); + +/** + * @brief Получение данных с датчика по однопроводному интерфейсу DHTxx и AM2xxx + * + * @param sensor Указатель на датчик + * @return Статус опроса + */ +UnitempStatus unitemp_singlewire_update(Sensor* sensor); + +/** + * @brief Установить порт датчика + * + * @param sensor Указатель на датчик + * @param gpio Устанавливаемый порт + * @return Истина если всё ок + */ +bool unitemp_singlewire_sensorSetGPIO(Sensor* sensor, const GPIO* gpio); + +/** + * @brief Получить порт датчика + * + * @param sensor Указатель на датчик + * @return Указатель на GPIO + */ +const GPIO* unitemp_singlewire_sensorGetGPIO(Sensor* sensor); + +/** + * @brief Выделение памяти под датчик на линии One Wire + * + * @param sensor Указатель на датчик + * @param args Указатель на массив с аргументами параметров датчка + */ +bool unitemp_singlewire_alloc(Sensor* sensor, char* args); + +/** + * @brief Высвобождение памяти инстанса датчика + * + * @param sensor Указатель на датчик + */ +bool unitemp_singlewire_free(Sensor* sensor); +#endif \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/unitemp/sensors/AM2320.c b/Applications/Official/DEV_FW/source/unitemp/sensors/AM2320.c new file mode 100644 index 000000000..e77707005 --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/sensors/AM2320.c @@ -0,0 +1,106 @@ +/* + Unitemp - Universal temperature reader + Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n) + + 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 . +*/ +#include "AM2320.h" +#include "../interfaces/I2CSensor.h" + +const SensorType AM2320_I2C = { + .typename = "AM2320_I2C", + .altname = "AM2320 (I2C)", + .interface = &I2C, + .datatype = UT_TEMPERATURE | UT_HUMIDITY, + .pollingInterval = 2000, + .allocator = unitemp_AM2320_I2C_alloc, + .mem_releaser = unitemp_AM2320_I2C_free, + .initializer = unitemp_AM2320_init, + .deinitializer = unitemp_AM2320_I2C_deinit, + .updater = unitemp_AM2320_I2C_update}; + +static uint16_t AM2320_calc_CRC(uint8_t* ptr, uint8_t len) { + uint16_t crc = 0xFFFF; + uint8_t i; + while(len--) { + crc ^= *ptr++; + for(i = 0; i < 8; i++) { + if(crc & 0x01) { + crc >>= 1; + crc ^= 0xA001; + } else { + crc >>= 1; + } + } + } + return crc; +} + +bool unitemp_AM2320_I2C_alloc(Sensor* sensor, char* args) { + UNUSED(args); + I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance; + + //Адреса на шине I2C (7 бит) + i2c_sensor->minI2CAdr = 0x5C << 1; + i2c_sensor->maxI2CAdr = 0x5C << 1; + return true; +} + +bool unitemp_AM2320_I2C_free(Sensor* sensor) { + //Нечего высвобождать, так как ничего не было выделено + UNUSED(sensor); + return true; +} + +bool unitemp_AM2320_init(Sensor* sensor) { + //Нечего инициализировать + UNUSED(sensor); + return true; +} + +bool unitemp_AM2320_I2C_deinit(Sensor* sensor) { + //Нечего деинициализировать + UNUSED(sensor); + return true; +} + +UnitempStatus unitemp_AM2320_I2C_update(Sensor* sensor) { + I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance; + + uint8_t data[8] = {0x03, 0x00, 0x04}; + + //Wake up + unitemp_i2c_isDeviceReady(i2c_sensor); + furi_delay_ms(1); + + //Запрос + if(!unitemp_i2c_writeArray(i2c_sensor, 3, data)) return UT_SENSORSTATUS_TIMEOUT; + furi_delay_ms(2); + //Ответ + if(!unitemp_i2c_readArray(i2c_sensor, 8, data)) return UT_SENSORSTATUS_TIMEOUT; + + if(AM2320_calc_CRC(data, 6) != ((data[7] << 8) | data[6])) { + return UT_SENSORSTATUS_BADCRC; + } + + sensor->hum = (float)(((uint16_t)data[2] << 8) | data[3]) / 10; + //Проверка на отрицательность температуры + if(!(data[4] & (1 << 7))) { + sensor->temp = (float)(((uint16_t)data[4] << 8) | data[5]) / 10; + } else { + data[4] &= ~(1 << 7); + sensor->temp = (float)(((uint16_t)data[4] << 8) | data[5]) / -10; + } + return UT_SENSORSTATUS_OK; +} diff --git a/Applications/Official/DEV_FW/source/unitemp/sensors/AM2320.h b/Applications/Official/DEV_FW/source/unitemp/sensors/AM2320.h new file mode 100644 index 000000000..f13105470 --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/sensors/AM2320.h @@ -0,0 +1,62 @@ +/* + Unitemp - Universal temperature reader + Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef UNITEMP_AM2320 +#define UNITEMP_AM2320 + +#include "../unitemp.h" +#include "../Sensors.h" +extern const SensorType AM2320_I2C; +/** + * @brief Выделение памяти и установка начальных значений датчика AM2320 + * + * @param sensor Указатель на создаваемый датчик + * @return Истина при успехе + */ +bool unitemp_AM2320_I2C_alloc(Sensor* sensor, char* args); + +/** + * @brief Инициализации датчика AM2320 + * + * @param sensor Указатель на датчик + * @return Истина если инициализация упспешная + */ +bool unitemp_AM2320_init(Sensor* sensor); + +/** + * @brief Деинициализация датчика + * + * @param sensor Указатель на датчик + */ +bool unitemp_AM2320_I2C_deinit(Sensor* sensor); + +/** + * @brief Обновление значений из датчика + * + * @param sensor Указатель на датчик + * @return Статус обновления + */ +UnitempStatus unitemp_AM2320_I2C_update(Sensor* sensor); + +/** + * @brief Высвободить память датчика + * + * @param sensor Указатель на датчик + */ +bool unitemp_AM2320_I2C_free(Sensor* sensor); + +#endif \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/unitemp/sensors/BMx280.c b/Applications/Official/DEV_FW/source/unitemp/sensors/BMx280.c new file mode 100644 index 000000000..a64daaa1d --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/sensors/BMx280.c @@ -0,0 +1,352 @@ +/* + Unitemp - Universal temperature reader + Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n) + + 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 . +*/ +#include "BMx280.h" + +const SensorType BMP280 = { + .typename = "BMP280", + .interface = &I2C, + .datatype = UT_TEMPERATURE | UT_PRESSURE, + .pollingInterval = 500, + .allocator = unitemp_BMx280_alloc, + .mem_releaser = unitemp_BMx280_free, + .initializer = unitemp_BMx280_init, + .deinitializer = unitemp_BMx280_deinit, + .updater = unitemp_BMx280_update}; +const SensorType BME280 = { + .typename = "BME280", + .interface = &I2C, + .datatype = UT_TEMPERATURE | UT_HUMIDITY | UT_PRESSURE, + + .pollingInterval = 500, + .allocator = unitemp_BMx280_alloc, + .mem_releaser = unitemp_BMx280_free, + .initializer = unitemp_BMx280_init, + .deinitializer = unitemp_BMx280_deinit, + .updater = unitemp_BMx280_update}; + +//Интервал обновления калибровочных значений +#define BOSCH_CAL_UPDATE_INTERVAL 60000 + +#define TEMP_CAL_START_ADDR 0x88 +#define PRESS_CAL_START_ADDR 0x8E +#define HUM_CAL_H1_ADDR 0xA1 +#define HUM_CAL_H2_ADDR 0xE1 + +#define BMP280_ID 0x58 +#define BME280_ID 0x60 + +#define BMx280_I2C_ADDR_MIN (0x76 << 1) +#define BMx280_I2C_ADDR_MAX (0x77 << 1) + +#define BMx280_REG_STATUS 0xF3 +#define BMx280_REG_CTRL_MEAS 0xF4 +#define BMx280_REG_CONFIG 0xF5 +#define BME280_REG_CTRL_HUM 0xF2 +//Преддескретизация температуры +#define BMx280_TEMP_OVERSAMPLING_SKIP 0b00000000 +#define BMx280_TEMP_OVERSAMPLING_1 0b00100000 +#define BMx280_TEMP_OVERSAMPLING_2 0b01000000 +#define BMx280_TEMP_OVERSAMPLING_4 0b01100000 +#define BMx280_TEMP_OVERSAMPLING_8 0b10000000 +#define BMx280_TEMP_OVERSAMPLING_16 0b10100000 +//Преддескретизация давления +#define BMx280_PRESS_OVERSAMPLING_SKIP 0b00000000 +#define BMx280_PRESS_OVERSAMPLING_1 0b00000100 +#define BMx280_PRESS_OVERSAMPLING_2 0b00001000 +#define BMx280_PRESS_OVERSAMPLING_4 0b00001100 +#define BMx280_PRESS_OVERSAMPLING_8 0b00010000 +#define BMx280_PRESS_OVERSAMPLING_16 0b00010100 +//Преддескретизация влажности +#define BME280_HUM_OVERSAMPLING_SKIP 0b00000000 +#define BME280_HUM_OVERSAMPLING_1 0b00000001 +#define BME280_HUM_OVERSAMPLING_2 0b00000010 +#define BME280_HUM_OVERSAMPLING_4 0b00000011 +#define BME280_HUM_OVERSAMPLING_8 0b00000100 +#define BME280_HUM_OVERSAMPLING_16 0b00000101u +//Режимы работы датчика +#define BMx280_MODE_SLEEP 0b00000000 //Наелся и спит +#define BMx280_MODE_FORCED 0b00000001 //Обновляет значения 1 раз, после чего уходит в сон +#define BMx280_MODE_NORMAL 0b00000011 //Регулярно обновляет значения +//Период обновления в нормальном режиме +#define BMx280_STANDBY_TIME_0_5 0b00000000 +#define BMx280_STANDBY_TIME_62_5 0b00100000 +#define BMx280_STANDBY_TIME_125 0b01000000 +#define BMx280_STANDBY_TIME_250 0b01100000 +#define BMx280_STANDBY_TIME_500 0b10000000 +#define BMx280_STANDBY_TIME_1000 0b10100000 +#define BMx280_STANDBY_TIME_2000 0b11000000 +#define BMx280_STANDBY_TIME_4000 0b11100000 +//Коэффициент фильтрации значений +#define BMx280_FILTER_COEFF_1 0b00000000 +#define BMx280_FILTER_COEFF_2 0b00000100 +#define BMx280_FILTER_COEFF_4 0b00001000 +#define BMx280_FILTER_COEFF_8 0b00001100 +#define BMx280_FILTER_COEFF_16 0b00010000 +//Разрешить работу по SPI +#define BMx280_SPI_3W_ENABLE 0b00000001 +#define BMx280_SPI_3W_DISABLE 0b00000000 + +static float BMx280_compensate_temperature(I2CSensor* i2c_sensor, int32_t adc_T) { + BMx280_instance* bmx280_instance = (BMx280_instance*)i2c_sensor->sensorInstance; + int32_t var1, var2; + var1 = ((((adc_T >> 3) - ((int32_t)bmx280_instance->temp_cal.dig_T1 << 1))) * + ((int32_t)bmx280_instance->temp_cal.dig_T2)) >> + 11; + var2 = (((((adc_T >> 4) - ((int32_t)bmx280_instance->temp_cal.dig_T1)) * + ((adc_T >> 4) - ((int32_t)bmx280_instance->temp_cal.dig_T1))) >> + 12) * + ((int32_t)bmx280_instance->temp_cal.dig_T3)) >> + 14; + bmx280_instance->t_fine = var1 + var2; + return ((bmx280_instance->t_fine * 5 + 128) >> 8) / 100.0f; +} + +static float BMx280_compensate_pressure(I2CSensor* i2c_sensor, int32_t adc_P) { + BMx280_instance* bmx280_instance = (BMx280_instance*)i2c_sensor->sensorInstance; + + int32_t var1, var2; + uint32_t p; + var1 = (((int32_t)bmx280_instance->t_fine) >> 1) - (int32_t)64000; + var2 = (((var1 >> 2) * (var1 >> 2)) >> 11) * ((int32_t)bmx280_instance->press_cal.dig_P6); + var2 = var2 + ((var1 * ((int32_t)bmx280_instance->press_cal.dig_P5)) << 1); + var2 = (var2 >> 2) + (((int32_t)bmx280_instance->press_cal.dig_P4) << 16); + var1 = (((bmx280_instance->press_cal.dig_P3 * (((var1 >> 2) * (var1 >> 2)) >> 13)) >> 3) + + ((((int32_t)bmx280_instance->press_cal.dig_P2) * var1) >> 1)) >> + 18; + var1 = ((((32768 + var1)) * ((int32_t)bmx280_instance->press_cal.dig_P1)) >> 15); + if(var1 == 0) { + return 0; // avoid exception caused by division by zero + } + p = (((uint32_t)(((int32_t)1048576) - adc_P) - (var2 >> 12))) * 3125; + if(p < 0x80000000) { + p = (p << 1) / ((uint32_t)var1); + } else { + p = (p / (uint32_t)var1) * 2; + } + var1 = (((int32_t)bmx280_instance->press_cal.dig_P9) * + ((int32_t)(((p >> 3) * (p >> 3)) >> 13))) >> + 12; + var2 = (((int32_t)(p >> 2)) * ((int32_t)bmx280_instance->press_cal.dig_P8)) >> 13; + p = (uint32_t)((int32_t)p + ((var1 + var2 + bmx280_instance->press_cal.dig_P7) >> 4)); + return p; +} + +static float BMx280_compensate_humidity(I2CSensor* i2c_sensor, int32_t adc_H) { + BMx280_instance* bmx280_instance = (BMx280_instance*)i2c_sensor->sensorInstance; + int32_t v_x1_u32r; + v_x1_u32r = (bmx280_instance->t_fine - ((int32_t)76800)); + + v_x1_u32r = + (((((adc_H << 14) - (((int32_t)bmx280_instance->hum_cal.dig_H4) << 20) - + (((int32_t)bmx280_instance->hum_cal.dig_H5) * v_x1_u32r)) + + ((int32_t)16384)) >> + 15) * + (((((((v_x1_u32r * ((int32_t)bmx280_instance->hum_cal.dig_H6)) >> 10) * + (((v_x1_u32r * ((int32_t)bmx280_instance->hum_cal.dig_H3)) >> 11) + + ((int32_t)32768))) >> + 10) + + ((int32_t)2097152)) * + ((int32_t)bmx280_instance->hum_cal.dig_H2) + + 8192) >> + 14)); + + v_x1_u32r = + (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * + ((int32_t)bmx280_instance->hum_cal.dig_H1)) >> + 4)); + + v_x1_u32r = (v_x1_u32r < 0 ? 0 : v_x1_u32r); + v_x1_u32r = (v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r); + return ((uint32_t)(v_x1_u32r >> 12)) / 1024.0f; +} + +static bool bmx280_readCalValues(I2CSensor* i2c_sensor) { + BMx280_instance* bmx280_instance = (BMx280_instance*)i2c_sensor->sensorInstance; + if(!unitemp_i2c_readRegArray( + i2c_sensor, TEMP_CAL_START_ADDR, 6, (uint8_t*)&bmx280_instance->temp_cal)) + return false; +#ifdef UNITEMP_DEBUG + FURI_LOG_D( + APP_NAME, + "Sensor BMx280 (0x%02X) T1-T3: %d, %d, %d", + i2c_sensor->currentI2CAdr, + bmx280_instance->temp_cal.dig_T1, + bmx280_instance->temp_cal.dig_T2, + bmx280_instance->temp_cal.dig_T3); +#endif + + if(!unitemp_i2c_readRegArray( + i2c_sensor, PRESS_CAL_START_ADDR, 18, (uint8_t*)&bmx280_instance->press_cal)) + return false; +#ifdef UNITEMP_DEBUG + FURI_LOG_D( + APP_NAME, + "Sensor BMx280 (0x%02X): P1-P9: %d, %d, %d, %d, %d, %d, %d, %d, %d", + i2c_sensor->currentI2CAdr, + bmx280_instance->press_cal.dig_P1, + bmx280_instance->press_cal.dig_P2, + bmx280_instance->press_cal.dig_P3, + bmx280_instance->press_cal.dig_P4, + bmx280_instance->press_cal.dig_P5, + bmx280_instance->press_cal.dig_P6, + bmx280_instance->press_cal.dig_P7, + bmx280_instance->press_cal.dig_P8, + bmx280_instance->press_cal.dig_P9); +#endif + + if(bmx280_instance->chip_id == BME280_ID) { + uint8_t buff[7] = {0}; + if(!unitemp_i2c_readRegArray(i2c_sensor, HUM_CAL_H1_ADDR, 1, buff)) return false; + bmx280_instance->hum_cal.dig_H1 = buff[0]; + + if(!unitemp_i2c_readRegArray(i2c_sensor, HUM_CAL_H2_ADDR, 7, buff)) return false; + bmx280_instance->hum_cal.dig_H2 = (uint16_t)(buff[0] | ((uint16_t)buff[1] << 8)); + bmx280_instance->hum_cal.dig_H3 = buff[2]; + bmx280_instance->hum_cal.dig_H4 = ((int16_t)buff[3] << 4) | (buff[4] & 0x0F); + bmx280_instance->hum_cal.dig_H5 = (buff[4] & 0x0F) | ((int16_t)buff[5] << 4); + bmx280_instance->hum_cal.dig_H6 = buff[6]; + +#ifdef UNITEMP_DEBUG + FURI_LOG_D( + APP_NAME, + "Sensor BMx280 (0x%02X): H1-H6: %d, %d, %d, %d, %d, %d", + i2c_sensor->currentI2CAdr, + bmx280_instance->hum_cal.dig_H1, + bmx280_instance->hum_cal.dig_H2, + bmx280_instance->hum_cal.dig_H3, + bmx280_instance->hum_cal.dig_H4, + bmx280_instance->hum_cal.dig_H5, + bmx280_instance->hum_cal.dig_H6); +#endif + } + + bmx280_instance->last_cal_update_time = furi_get_tick(); + return true; +} +static bool bmp280_isMeasuring(Sensor* sensor) { + I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance; + return (bool)((unitemp_i2c_readReg(i2c_sensor, BMx280_REG_STATUS) & 0x08) >> 3); +} + +bool unitemp_BMx280_alloc(Sensor* sensor, char* args) { + UNUSED(args); + I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance; + BMx280_instance* bmx280_instance = malloc(sizeof(BMx280_instance)); + if(bmx280_instance == NULL) { + FURI_LOG_E(APP_NAME, "Failed to allocation sensor %s instance", sensor->name); + return false; + } + + if(sensor->type == &BMP280) bmx280_instance->chip_id = BMP280_ID; + if(sensor->type == &BME280) bmx280_instance->chip_id = BME280_ID; + + i2c_sensor->sensorInstance = bmx280_instance; + + i2c_sensor->minI2CAdr = BMx280_I2C_ADDR_MIN; + i2c_sensor->maxI2CAdr = BMx280_I2C_ADDR_MAX; + return true; +} + +bool unitemp_BMx280_init(Sensor* sensor) { + I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance; + //Перезагрузка + unitemp_i2c_writeReg(i2c_sensor, 0xE0, 0xB6); + //Чтение ID датчика + uint8_t id = unitemp_i2c_readReg(i2c_sensor, 0xD0); + if(id != BMP280_ID && id != BME280_ID) { + FURI_LOG_E( + APP_NAME, + "Sensor %s returned wrong ID 0x%02X, expected 0x%02X or 0x%02X", + sensor->name, + id, + BMP280_ID, + BME280_ID); + return false; + } + + //Настройка режимов работы + if(id == BME280_ID) { + unitemp_i2c_writeReg(i2c_sensor, BME280_REG_CTRL_HUM, BME280_HUM_OVERSAMPLING_1); + unitemp_i2c_writeReg( + i2c_sensor, BME280_REG_CTRL_HUM, unitemp_i2c_readReg(i2c_sensor, BME280_REG_CTRL_HUM)); + } + unitemp_i2c_writeReg( + i2c_sensor, + BMx280_REG_CTRL_MEAS, + BMx280_TEMP_OVERSAMPLING_2 | BMx280_PRESS_OVERSAMPLING_4 | BMx280_MODE_NORMAL); + //Настройка периода опроса и фильтрации значений + unitemp_i2c_writeReg( + i2c_sensor, + BMx280_REG_CONFIG, + BMx280_STANDBY_TIME_500 | BMx280_FILTER_COEFF_16 | BMx280_SPI_3W_DISABLE); + //Чтение калибровочных значений + if(!bmx280_readCalValues(i2c_sensor)) { + FURI_LOG_E(APP_NAME, "Failed to read calibration values sensor %s", sensor->name); + return false; + } + return true; +} + +bool unitemp_BMx280_deinit(Sensor* sensor) { + I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance; + //Перевод в сон + unitemp_i2c_writeReg(i2c_sensor, BMx280_REG_CTRL_MEAS, BMx280_MODE_SLEEP); + return true; +} + +UnitempStatus unitemp_BMx280_update(Sensor* sensor) { + I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance; + BMx280_instance* instance = i2c_sensor->sensorInstance; + + uint32_t t = furi_get_tick(); + + uint8_t buff[3]; + //Проверка инициализированности датчика + unitemp_i2c_readRegArray(i2c_sensor, 0xF4, 2, buff); + if(buff[0] == 0) { + FURI_LOG_W(APP_NAME, "Sensor %s is not initialized!", sensor->name); + return UT_SENSORSTATUS_ERROR; + } + + while(bmp280_isMeasuring(sensor)) { + if(furi_get_tick() - t > 100) { + return UT_SENSORSTATUS_TIMEOUT; + } + } + + if(furi_get_tick() - instance->last_cal_update_time > BOSCH_CAL_UPDATE_INTERVAL) { + bmx280_readCalValues(i2c_sensor); + } + + if(!unitemp_i2c_readRegArray(i2c_sensor, 0xFA, 3, buff)) return UT_SENSORSTATUS_TIMEOUT; + int32_t adc_T = ((int32_t)buff[0] << 12) | ((int32_t)buff[1] << 4) | ((int32_t)buff[2] >> 4); + if(!unitemp_i2c_readRegArray(i2c_sensor, 0xF7, 3, buff)) return UT_SENSORSTATUS_TIMEOUT; + int32_t adc_P = ((int32_t)buff[0] << 12) | ((int32_t)buff[1] << 4) | ((int32_t)buff[2] >> 4); + if(!unitemp_i2c_readRegArray(i2c_sensor, 0xFD, 2, buff)) return UT_SENSORSTATUS_TIMEOUT; + int32_t adc_H = ((uint16_t)buff[0] << 8) | buff[1]; + sensor->temp = BMx280_compensate_temperature(i2c_sensor, adc_T); + sensor->pressure = BMx280_compensate_pressure(i2c_sensor, adc_P); + sensor->hum = BMx280_compensate_humidity(i2c_sensor, adc_H); + return UT_SENSORSTATUS_OK; +} + +bool unitemp_BMx280_free(Sensor* sensor) { + I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance; + free(i2c_sensor->sensorInstance); + return true; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/unitemp/sensors/BMx280.h b/Applications/Official/DEV_FW/source/unitemp/sensors/BMx280.h new file mode 100644 index 000000000..fe52a364e --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/sensors/BMx280.h @@ -0,0 +1,102 @@ +/* + Unitemp - Universal temperature reader + Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef UNITEMP_BMx280 +#define UNITEMP_BMx280 + +#include "../unitemp.h" +#include "../Sensors.h" +#include "../interfaces/I2CSensor.h" + +typedef struct { + uint16_t dig_T1; + int16_t dig_T2; + int16_t dig_T3; +} BMx280_temp_cal; + +typedef struct { + uint16_t dig_P1; + int16_t dig_P2; + int16_t dig_P3; + int16_t dig_P4; + int16_t dig_P5; + int16_t dig_P6; + int16_t dig_P7; + int16_t dig_P8; + int16_t dig_P9; +} BMx280_press_cal; + +typedef struct { + uint8_t dig_H1; + int16_t dig_H2; + uint8_t dig_H3; + int16_t dig_H4; + int16_t dig_H5; + int8_t dig_H6; +} BMx280_hum_cal; + +typedef struct { + //Калибровочные значения температуры + BMx280_temp_cal temp_cal; + //Калибровочные значения давления + BMx280_press_cal press_cal; + //Калибровочные значения влажности воздуха + BMx280_hum_cal hum_cal; + //Время последнего обновления калибровочных значений + uint32_t last_cal_update_time; + //Индификатор датчика + uint8_t chip_id; + //Корректировочное значение температуры + int32_t t_fine; +} BMx280_instance; + +extern const SensorType BMP280; +extern const SensorType BME280; +/** + * @brief Выделение памяти и установка начальных значений датчика BMP280 + * @param sensor Указатель на создаваемый датчик + * @return Истина при успехе + */ +bool unitemp_BMx280_alloc(Sensor* sensor, char* args); + +/** + * @brief Инициализации датчика BMP280 + * @param sensor Указатель на датчик + * @return Истина если инициализация упспешная + */ +bool unitemp_BMx280_init(Sensor* sensor); + +/** + * @brief Деинициализация датчика + * @param sensor Указатель на датчик + */ +bool unitemp_BMx280_deinit(Sensor* sensor); + +/** + * @brief Обновление значений из датчика + * @param sensor Указатель на датчик + * @return Статус опроса датчика + */ +UnitempStatus unitemp_BMx280_update(Sensor* sensor); + +/** + * @brief Высвободить память датчика + * @param sensor Указатель на датчик + */ +bool unitemp_BMx280_free(Sensor* sensor); + +#endif \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/unitemp/sensors/DHT20.c b/Applications/Official/DEV_FW/source/unitemp/sensors/DHT20.c new file mode 100644 index 000000000..f16e8dfc1 --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/sensors/DHT20.c @@ -0,0 +1,154 @@ +/* + Unitemp - Universal temperature reader + Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n) + + 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 . +*/ +#include "DHT20.h" +#include "../interfaces/I2CSensor.h" + +const SensorType DHT20 = { + .typename = "DHT20", + .altname = "DHT20/AM2108/AHT20", + .interface = &I2C, + .datatype = UT_TEMPERATURE | UT_HUMIDITY, + .pollingInterval = 1000, + .allocator = unitemp_DHT20_I2C_alloc, + .mem_releaser = unitemp_DHT20_I2C_free, + .initializer = unitemp_DHT20_init, + .deinitializer = unitemp_DHT20_I2C_deinit, + .updater = unitemp_DHT20_I2C_update}; +const SensorType AHT10 = { + .typename = "AHT10", + .interface = &I2C, + .datatype = UT_TEMPERATURE | UT_HUMIDITY, + .pollingInterval = 1000, + .allocator = unitemp_DHT20_I2C_alloc, + .mem_releaser = unitemp_DHT20_I2C_free, + .initializer = unitemp_DHT20_init, + .deinitializer = unitemp_DHT20_I2C_deinit, + .updater = unitemp_DHT20_I2C_update}; + +static uint8_t DHT20_get_status(I2CSensor* i2c_sensor) { + uint8_t status[1] = {0}; + unitemp_i2c_readArray(i2c_sensor, 1, status); + return status[0]; +} + +static uint8_t DHT20_calc_CRC8(uint8_t* message, uint8_t Num) { + uint8_t i; + uint8_t byte; + uint8_t crc = 0xFF; + for(byte = 0; byte < Num; byte++) { + crc ^= (message[byte]); + for(i = 8; i > 0; --i) { + if(crc & 0x80) + crc = (crc << 1) ^ 0x31; + else + crc = (crc << 1); + } + } + return crc; +} + +static void DHT20_reset_reg(I2CSensor* i2c_sensor, uint8_t addr) { + uint8_t data[3] = {addr, 0x00, 0x00}; + + unitemp_i2c_writeArray(i2c_sensor, 3, data); + + furi_delay_ms(5); + + unitemp_i2c_readArray(i2c_sensor, 3, data); + + furi_delay_ms(10); + + data[0] = 0xB0 | addr; + unitemp_i2c_writeArray(i2c_sensor, 3, data); +} + +bool unitemp_DHT20_I2C_alloc(Sensor* sensor, char* args) { + UNUSED(args); + I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance; + + //Адреса на шине I2C (7 бит) + i2c_sensor->minI2CAdr = 0x38 << 1; + i2c_sensor->maxI2CAdr = (sensor->type == &DHT20) ? (0x38 << 1) : (0x39 << 1); + return true; +} + +bool unitemp_DHT20_I2C_free(Sensor* sensor) { + //Нечего высвобождать, так как ничего не было выделено + UNUSED(sensor); + return true; +} + +bool unitemp_DHT20_init(Sensor* sensor) { + I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance; + + uint8_t data[3] = {0xA8, 0x00, 0x00}; + if(!unitemp_i2c_writeArray(i2c_sensor, 3, data)) return false; + furi_delay_ms(10); + data[0] = (sensor->type == &DHT20) ? 0xBE : 0xE1; + data[1] = 0x08; + if(!unitemp_i2c_writeArray(i2c_sensor, 3, data)) return false; + furi_delay_ms(10); + + return true; +} + +bool unitemp_DHT20_I2C_deinit(Sensor* sensor) { + //Нечего деинициализировать + UNUSED(sensor); + return true; +} + +UnitempStatus unitemp_DHT20_I2C_update(Sensor* sensor) { + I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance; + + if(DHT20_get_status(i2c_sensor) != 0x18) { + DHT20_reset_reg(i2c_sensor, 0x1B); + DHT20_reset_reg(i2c_sensor, 0x1C); + DHT20_reset_reg(i2c_sensor, 0x1E); + } + furi_delay_ms(10); + + uint8_t data[7] = {0xAC, 0x33, 0x00}; + if(!unitemp_i2c_writeArray(i2c_sensor, 3, data)) return UT_SENSORSTATUS_TIMEOUT; + furi_delay_ms(80); + uint32_t t = furi_get_tick(); + while(DHT20_get_status(i2c_sensor) == 0x80) { + if(furi_get_tick() - t > 10) return UT_SENSORSTATUS_TIMEOUT; + } + + if(!unitemp_i2c_readArray(i2c_sensor, 7, data)) return UT_SENSORSTATUS_TIMEOUT; + + if(DHT20_calc_CRC8(data, 6) != data[6]) { + return UT_SENSORSTATUS_BADCRC; + } + uint32_t RetuData = 0; + RetuData = (RetuData | data[1]) << 8; + RetuData = (RetuData | data[2]) << 8; + RetuData = (RetuData | data[3]); + RetuData = RetuData >> 4; + sensor->hum = RetuData * 100 * 10 / 1024.0f / 1024.0f / 10.0f; + + RetuData = 0; + RetuData = (RetuData | data[3]) << 8; + RetuData = (RetuData | data[4]) << 8; + RetuData = (RetuData | data[5]); + RetuData = RetuData & 0xfffff; + sensor->temp = (RetuData * 200 * 10.0f / 1024.0f / 1024.0f - 500) / 10.0f; + + return UT_SENSORSTATUS_OK; +} diff --git a/Applications/Official/DEV_FW/source/unitemp/sensors/DHT20.h b/Applications/Official/DEV_FW/source/unitemp/sensors/DHT20.h new file mode 100644 index 000000000..db49495dc --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/sensors/DHT20.h @@ -0,0 +1,63 @@ +/* + Unitemp - Universal temperature reader + Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef UNITEMP_DHT20 +#define UNITEMP_DHT20 + +#include "../unitemp.h" +#include "../Sensors.h" +extern const SensorType DHT20; +extern const SensorType AHT10; +/** + * @brief Выделение памяти и установка начальных значений датчика DHT20 + * + * @param sensor Указатель на создаваемый датчик + * @return Истина при успехе + */ +bool unitemp_DHT20_I2C_alloc(Sensor* sensor, char* args); + +/** + * @brief Инициализации датчика DHT20 + * + * @param sensor Указатель на датчик + * @return Истина если инициализация упспешная + */ +bool unitemp_DHT20_init(Sensor* sensor); + +/** + * @brief Деинициализация датчика + * + * @param sensor Указатель на датчик + */ +bool unitemp_DHT20_I2C_deinit(Sensor* sensor); + +/** + * @brief Обновление значений из датчика + * + * @param sensor Указатель на датчик + * @return Статус обновления + */ +UnitempStatus unitemp_DHT20_I2C_update(Sensor* sensor); + +/** + * @brief Высвободить память датчика + * + * @param sensor Указатель на датчик + */ +bool unitemp_DHT20_I2C_free(Sensor* sensor); + +#endif \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/unitemp/sensors/LM75.c b/Applications/Official/DEV_FW/source/unitemp/sensors/LM75.c new file mode 100644 index 000000000..e71376404 --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/sensors/LM75.c @@ -0,0 +1,87 @@ +/* + Unitemp - Universal temperature reader + Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n) + + 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 . +*/ +#include "LM75.h" +#include "../interfaces/I2CSensor.h" + +#define LM75_REG_TEMP 0x00 +#define LM75_REG_CONFIG 0x01 +#define LM75_REG_THYST 0x02 +#define LM75_REG_TOS 0x03 + +#define LM75_CONFIG_SHUTDOWN 0b00000001 +#define LM75_CONFIG_INTERRUPT 0b00000010 +#define LM75_CONFIG_OSPOLARITY_HIGH 0b00000100 +#define LM75_CONFIG_FAULTQUEUE_1 0b00000000 +#define LM75_CONFIG_FAULTQUEUE_2 0b00001000 +#define LM75_CONFIG_FAULTQUEUE_4 0b00010000 +#define LM75_CONFIG_FAULTQUEUE_6 0b00011000 + +const SensorType LM75 = { + .typename = "LM75", + .interface = &I2C, + .datatype = UT_DATA_TYPE_TEMP, + .pollingInterval = 500, + .allocator = unitemp_LM75_alloc, + .mem_releaser = unitemp_LM75_free, + .initializer = unitemp_LM75_init, + .deinitializer = unitemp_LM75_deinit, + .updater = unitemp_LM75_update}; + +bool unitemp_LM75_alloc(Sensor* sensor, char* args) { + UNUSED(args); + I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance; + + //Адреса на шине I2C (7 бит) + i2c_sensor->minI2CAdr = 0b1001000 << 1; + i2c_sensor->maxI2CAdr = 0b1001111 << 1; + return true; +} + +bool unitemp_LM75_free(Sensor* sensor) { + //Нечего высвобождать, так как ничего не было выделено + UNUSED(sensor); + return true; +} + +bool unitemp_LM75_init(Sensor* sensor) { + I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance; + + //Выход если не удалось записать значение в датчик + if(!unitemp_i2c_writeReg(i2c_sensor, LM75_REG_CONFIG, LM75_CONFIG_FAULTQUEUE_1)) return false; + + return true; +} + +bool unitemp_LM75_deinit(Sensor* sensor) { + I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance; + if(!unitemp_i2c_writeReg( + i2c_sensor, LM75_REG_CONFIG, LM75_CONFIG_FAULTQUEUE_1 | LM75_CONFIG_SHUTDOWN)) + return false; + return true; +} + +UnitempStatus unitemp_LM75_update(Sensor* sensor) { + I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance; + + uint8_t buff[2]; + if(!unitemp_i2c_readRegArray(i2c_sensor, LM75_REG_TEMP, 2, buff)) + return UT_SENSORSTATUS_TIMEOUT; + int16_t raw = (((uint16_t)buff[0] << 8) | buff[1]); + sensor->temp = raw / 32 * 0.125; + return UT_SENSORSTATUS_OK; +} diff --git a/Applications/Official/DEV_FW/source/unitemp/sensors/LM75.h b/Applications/Official/DEV_FW/source/unitemp/sensors/LM75.h new file mode 100644 index 000000000..dc1fb791c --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/sensors/LM75.h @@ -0,0 +1,62 @@ +/* + Unitemp - Universal temperature reader + Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef UNITEMP_LM75 +#define UNITEMP_LM75 + +#include "../unitemp.h" +#include "../Sensors.h" +extern const SensorType LM75; +/** + * @brief Выделение памяти и установка начальных значений датчика LM75 + * + * @param sensor Указатель на создаваемый датчик + * @return Истина при успехе + */ +bool unitemp_LM75_alloc(Sensor* sensor, char* args); + +/** + * @brief Инициализации датчика LM75 + * + * @param sensor Указатель на датчик + * @return Истина если инициализация упспешная + */ +bool unitemp_LM75_init(Sensor* sensor); + +/** + * @brief Деинициализация датчика + * + * @param sensor Указатель на датчик + */ +bool unitemp_LM75_deinit(Sensor* sensor); + +/** + * @brief Обновление значений из датчика + * + * @param sensor Указатель на датчик + * @return Статус обновления + */ +UnitempStatus unitemp_LM75_update(Sensor* sensor); + +/** + * @brief Высвободить память датчика + * + * @param sensor Указатель на датчик + */ +bool unitemp_LM75_free(Sensor* sensor); + +#endif \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/unitemp/sensors/Sensors.xlsx b/Applications/Official/DEV_FW/source/unitemp/sensors/Sensors.xlsx new file mode 100644 index 000000000..03522a643 Binary files /dev/null and b/Applications/Official/DEV_FW/source/unitemp/sensors/Sensors.xlsx differ diff --git a/Applications/Official/DEV_FW/source/unitemp/unitemp.c b/Applications/Official/DEV_FW/source/unitemp/unitemp.c new file mode 100644 index 000000000..ce1cd1dc9 --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/unitemp.c @@ -0,0 +1,310 @@ +/* + Unitemp - Universal temperature reader + Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n) + + 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 . +*/ +#include "unitemp.h" +#include "interfaces/SingleWireSensor.h" +#include "Sensors.h" +#include "./views/UnitempViews.h" + +#include +#include + +/* Переменные */ +//Данные приложения +Unitemp* app; + +void uintemp_celsiumToFarengate(Sensor* sensor) { + sensor->temp = sensor->temp * (9.0 / 5.0) + 32; +} + +void unitemp_pascalToMmHg(Sensor* sensor) { + sensor->pressure = sensor->pressure * 0.007500638; +} +void unitemp_pascalToKPa(Sensor* sensor) { + sensor->pressure = sensor->pressure / 1000.0f; +} +void unitemp_pascalToInHg(Sensor* sensor) { + sensor->pressure = sensor->pressure * 0.0002953007; +} + +bool unitemp_saveSettings(void) { + //Выделение памяти для потока + app->file_stream = file_stream_alloc(app->storage); + + //Переменная пути к файлу + FuriString* filepath = furi_string_alloc(); + //Составление пути к файлу + furi_string_printf(filepath, "%s/%s", APP_PATH_FOLDER, APP_FILENAME_SETTINGS); + //Создание папки плагина + storage_common_mkdir(app->storage, APP_PATH_FOLDER); + //Открытие потока + if(!file_stream_open( + app->file_stream, furi_string_get_cstr(filepath), FSAM_READ_WRITE, FSOM_CREATE_ALWAYS)) { + FURI_LOG_E( + APP_NAME, + "An error occurred while saving the settings file: %d", + file_stream_get_error(app->file_stream)); + //Закрытие потока и освобождение памяти + file_stream_close(app->file_stream); + stream_free(app->file_stream); + return false; + } + + //Сохранение настроек + stream_write_format( + app->file_stream, "INFINITY_BACKLIGHT %d\n", app->settings.infinityBacklight); + stream_write_format(app->file_stream, "TEMP_UNIT %d\n", app->settings.temp_unit); + stream_write_format(app->file_stream, "PRESSURE_UNIT %d\n", app->settings.pressure_unit); + + //Закрытие потока и освобождение памяти + file_stream_close(app->file_stream); + stream_free(app->file_stream); + + FURI_LOG_I(APP_NAME, "Settings have been successfully saved"); + return true; +} + +bool unitemp_loadSettings(void) { +#ifdef UNITEMP_DEBUG + FURI_LOG_D(APP_NAME, "Loading settings..."); +#endif + + //Выделение памяти на поток + app->file_stream = file_stream_alloc(app->storage); + + //Переменная пути к файлу + FuriString* filepath = furi_string_alloc(); + //Составление пути к файлу + furi_string_printf(filepath, "%s/%s", APP_PATH_FOLDER, APP_FILENAME_SETTINGS); + + //Открытие потока к файлу настроек + if(!file_stream_open( + app->file_stream, furi_string_get_cstr(filepath), FSAM_READ_WRITE, FSOM_OPEN_EXISTING)) { + //Сохранение настроек по умолчанию в случае отсутствия файла + if(file_stream_get_error(app->file_stream) == FSE_NOT_EXIST) { + FURI_LOG_W(APP_NAME, "Missing settings file. Setting defaults and saving..."); + //Закрытие потока и освобождение памяти + file_stream_close(app->file_stream); + stream_free(app->file_stream); + //Сохранение стандартного конфига + unitemp_saveSettings(); + return false; + } else { + FURI_LOG_E( + APP_NAME, + "An error occurred while loading the settings file: %d. Standard values have been applied", + file_stream_get_error(app->file_stream)); + //Закрытие потока и освобождение памяти + file_stream_close(app->file_stream); + stream_free(app->file_stream); + return false; + } + } + + //Вычисление размера файла + uint8_t file_size = stream_size(app->file_stream); + //Если файл пустой, то: + if(file_size == (uint8_t)0) { + FURI_LOG_W(APP_NAME, "Settings file is empty"); + //Закрытие потока и освобождение памяти + file_stream_close(app->file_stream); + stream_free(app->file_stream); + //Сохранение стандартного конфига + unitemp_saveSettings(); + 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 settings file"); + //Закрытие потока и освобождение памяти + file_stream_close(app->file_stream); + stream_free(app->file_stream); + free(file_buf); + return false; + } + //Построчное чтение файла + //Указатель на начало строки + FuriString* file = furi_string_alloc_set_str((char*)file_buf); + //Сколько байт до конца строки + size_t line_end = 0; + + while(line_end != STRING_FAILURE && line_end != (size_t)(file_size - 1)) { + char buff[20] = {0}; + sscanf(((char*)(file_buf + line_end)), "%s", buff); + + if(!strcmp(buff, "INFINITY_BACKLIGHT")) { + //Чтение значения параметра + int p = 0; + sscanf(((char*)(file_buf + line_end)), "INFINITY_BACKLIGHT %d", &p); + app->settings.infinityBacklight = p; + } else if(!strcmp(buff, "TEMP_UNIT")) { + //Чтение значения параметра + int p = 0; + sscanf(((char*)(file_buf + line_end)), "\nTEMP_UNIT %d", &p); + app->settings.temp_unit = p; + } else if(!strcmp(buff, "PRESSURE_UNIT")) { + //Чтение значения параметра + int p = 0; + sscanf(((char*)(file_buf + line_end)), "\nPRESSURE_UNIT %d", &p); + app->settings.pressure_unit = p; + } else { + FURI_LOG_W(APP_NAME, "Unknown settings parameter: %s", buff); + } + + //Вычисление конца строки + line_end = furi_string_search_char(file, '\n', line_end + 1); + } + free(file_buf); + file_stream_close(app->file_stream); + stream_free(app->file_stream); + + FURI_LOG_I(APP_NAME, "Settings have been successfully loaded"); + return true; +} + +/** + * @brief Выделение места под переменные плагина + * + * @return true Если всё прошло успешно + * @return false Если в процессе загрузки произошла ошибка + */ +static bool unitemp_alloc(void) { + //Выделение памяти под данные приложения + app = malloc(sizeof(Unitemp)); + //Разрешение работы приложения + app->processing = true; + + //Открытие хранилища (?) + app->storage = furi_record_open(RECORD_STORAGE); + + //Уведомления + app->notifications = furi_record_open(RECORD_NOTIFICATION); + + //Установка значений по умолчанию + app->settings.infinityBacklight = true; //Подсветка горит всегда + app->settings.temp_unit = UT_TEMP_CELSIUS; //Единица измерения температуры - градусы Цельсия + app->settings.pressure_unit = UT_PRESSURE_MM_HG; //Единица измерения давления - мм рт. ст. + + app->gui = furi_record_open(RECORD_GUI); + //Диспетчер окон + app->view_dispatcher = view_dispatcher_alloc(); + + app->sensors = NULL; + + app->buff = malloc(BUFF_SIZE); + + unitemp_General_alloc(); + + unitemp_MainMenu_alloc(); + unitemp_Settings_alloc(); + unitemp_SensorsList_alloc(); + unitemp_SensorEdit_alloc(); + unitemp_SensorNameEdit_alloc(); + unitemp_SensorActions_alloc(); + unitemp_widgets_alloc(); + + //Всплывающее окно + app->popup = popup_alloc(); + view_dispatcher_add_view(app->view_dispatcher, UnitempViewPopup, popup_get_view(app->popup)); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + return true; +} + +/** + * @brief Освыбождение памяти после работы приложения + */ +static void unitemp_free(void) { + popup_free(app->popup); + unitemp_widgets_free(); + + unitemp_SensorActions_free(); + unitemp_SensorNameEdit_free(); + unitemp_SensorEdit_free(); + unitemp_SensorsList_free(); + unitemp_Settings_free(); + unitemp_MainMenu_free(); + unitemp_General_free(); + + free(app->buff); + + view_dispatcher_free(app->view_dispatcher); + furi_record_close(RECORD_GUI); + //Очистка датчиков + //Высвыбождение данных датчиков + unitemp_sensors_free(); + free(app->sensors); + + //Закрытие уведомлений + furi_record_close(RECORD_NOTIFICATION); + //Закрытие хранилища + furi_record_close(RECORD_STORAGE); + //Удаление в самую последнюю очередь + free(app); +} + +/** + * @brief Точка входа в приложение + * + * @return Код ошибки + */ +int32_t unitemp_app() { + //Выделение памяти под переменные + //Выход если произошла ошибка + if(unitemp_alloc() == false) { + //Освобождение памяти + unitemp_free(); + //Выход + return 0; + } + + //Загрузка настроек из SD-карты + unitemp_loadSettings(); + //Применение настроек + if(app->settings.infinityBacklight == true) { + //Постоянное свечение подсветки + notification_message(app->notifications, &sequence_display_backlight_enforce_on); + } + app->settings.lastOTGState = furi_hal_power_is_otg_enabled(); + //Загрузка датчиков из SD-карты + unitemp_sensors_load(); + //Инициализация датчиков + unitemp_sensors_init(); + + unitemp_General_switch(); + + while(app->processing) { + if(app->sensors_ready) unitemp_sensors_updateValues(); + furi_delay_ms(100); + } + + //Деинициализация датчиков + unitemp_sensors_deInit(); + //Автоматическое управление подсветкой + if(app->settings.infinityBacklight == true) + notification_message(app->notifications, &sequence_display_backlight_enforce_auto); + //Освобождение памяти + unitemp_free(); + //Выход + return 0; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/unitemp/unitemp.h b/Applications/Official/DEV_FW/source/unitemp/unitemp.h new file mode 100644 index 000000000..144780968 --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/unitemp.h @@ -0,0 +1,148 @@ +/* + Unitemp - Universal temperature reader + Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef UNITEMP +#define UNITEMP + +/* Подключение стандартных библиотек */ + +/* Подключение API Flipper Zero */ +//Файловый поток +#include +//Экран +#include +#include +#include +#include +//Уведомления +#include +#include + +/* Внутренние библиотеки */ +//Интерфейсы подключения датчиков +#include "Sensors.h" + +/* Объявление макроподстановок */ +//Имя приложения +#define APP_NAME "Unitemp" +//Путь хранения файлов плагина +#define APP_PATH_FOLDER "/ext/unitemp" +//Имя файла с настройками +#define APP_FILENAME_SETTINGS "settings.cfg" +//Имя файла с датчиками +#define APP_FILENAME_SENSORS "sensors.cfg" +//Версия приложения +#define UNITEMP_APP_VER "1.0" + +//Размер буффера текста +#define BUFF_SIZE 32 + +#define UNITEMP_DEBUG + +/* Объявление перечислений */ +//Единицы измерения температуры +typedef enum { UT_TEMP_CELSIUS, UT_TEMP_FAHRENHEIT, UT_TEMP_COUNT } tempMeasureUnit; +//Единицы измерения давления +typedef enum { + UT_PRESSURE_MM_HG, + UT_PRESSURE_IN_HG, + UT_PRESSURE_KPA, + + UT_PRESSURE_COUNT +} pressureMeasureUnit; +/* Объявление структур */ +//Настройки плагина +typedef struct { + //Бесконечная работа подсветки + bool infinityBacklight; + //Единица измерения температуры + tempMeasureUnit temp_unit; + //Единица измерения давления + pressureMeasureUnit pressure_unit; + //Последнее состояние OTG + bool lastOTGState; +} UnitempSettings; + +//Основная структура плагина +typedef struct { + //Система + bool processing; //Флаг работы приложения. При ложном значении приложение закрывается + bool sensors_ready; //Флаг готовности датчиков к опросу + //Основные настройки + UnitempSettings settings; + //Массив указателей на датчики + Sensor** sensors; + //Количество загруженных датчиков + uint8_t sensors_count; + //SD-карта + Storage* storage; //Хранилище + Stream* file_stream; //Файловый поток + + //Экран + Gui* gui; + ViewDispatcher* view_dispatcher; + NotificationApp* notifications; + Widget* widget; + Popup* popup; + //Буффер для различного текста + char* buff; +} Unitemp; + +/* Объявление прототипов функций */ + +/** + * @brief Перевод значения температуры датчика из Цельсия в Фаренгейты + * + * @param sensor Указатель на датчик + */ +void uintemp_celsiumToFarengate(Sensor* sensor); + +/** + * @brief Конвертация давления из паскалей в мм рт.ст. + * + * @param sensor Указатель на датчик + */ +void unitemp_pascalToMmHg(Sensor* sensor); + +/** + * @brief Конвертация давления из паскалей в килопаскали + * + * @param sensor Указатель на датчик + */ +void unitemp_pascalToKPa(Sensor* sensor); +/** + * @brief Конвертация давления из паскалей в дюйм рт.ст. + * + * @param sensor Указатель на датчик + */ +void unitemp_pascalToInHg(Sensor* sensor); + +/** + * @brief Сохранение настроек на SD-карту + * + * @return Истина если сохранение успешное + */ +bool unitemp_saveSettings(void); +/** + * @brief Загрузка настроек с SD-карты + * + * @return Истина если загрузка успешная + */ +bool unitemp_loadSettings(void); + +extern Unitemp* app; +#endif \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/unitemp/views/General_view.c b/Applications/Official/DEV_FW/source/unitemp/views/General_view.c new file mode 100644 index 000000000..fc1408b1a --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/views/General_view.c @@ -0,0 +1,563 @@ +/* + Unitemp - Universal temperature reader + Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n) + + 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 . +*/ +#include "UnitempViews.h" +#include "unitemp_icons.h" + +#include + +static View* view; + +typedef enum general_views { + G_NO_SENSORS_VIEW, //Нет датчиков + G_LIST_VIEW, //Вид в ввиде списка + G_CAROUSEL_VIEW, //Карусель +} general_view; + +typedef enum carousel_info { + CAROUSEL_VALUES, //Отображение значений датчиков + CAROUSEL_INFO, //Отображение информации о датчике +} carousel_info; + +static general_view current_view; + +carousel_info carousel_info_selector = CAROUSEL_VALUES; +uint8_t generalview_sensor_index = 0; + +static void _draw_temperature(Canvas* canvas, Sensor* sensor, uint8_t x, uint8_t y, Color color) { + //Рисование рамки + canvas_draw_rframe(canvas, x, y, 54, 20, 3); + + if(color == ColorBlack) { + canvas_draw_rbox(canvas, x, y, 54, 19, 3); + canvas_invert_color(canvas); + } else { + canvas_draw_rframe(canvas, x, y, 54, 19, 3); + } + + int8_t temp_dec = abs((int16_t)(sensor->temp * 10) % 10); + + //Рисование иконки + canvas_draw_icon( + canvas, + x + 3, + y + 3, + (app->settings.temp_unit == UT_TEMP_CELSIUS ? &I_temp_C_11x14 : &I_temp_F_11x14)); + + if((int16_t)sensor->temp == -128 || sensor->status == UT_SENSORSTATUS_TIMEOUT) { + canvas_set_font(canvas, FontBigNumbers); + canvas_draw_str_aligned(canvas, x + 27, y + 10, AlignCenter, AlignCenter, "--"); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, x + 50, y + 10 + 3, AlignRight, AlignCenter, ". -"); + if(color == ColorBlack) canvas_invert_color(canvas); + return; + } + + //Целая часть температуры + //Костыль для отображения знака числа меньше 0 + uint8_t offset = 0; + if(sensor->temp < 0 && sensor->temp > -1) { + app->buff[0] = '-'; + offset = 1; + } + snprintf((char*)(app->buff + offset), BUFF_SIZE, "%d", (int8_t)sensor->temp); + canvas_set_font(canvas, FontBigNumbers); + canvas_draw_str_aligned( + canvas, + x + 27 + ((sensor->temp <= -10 || sensor->temp > 99) ? 5 : 0), + y + 10, + AlignCenter, + AlignCenter, + app->buff); + //Печать дробной части температуры в диапазоне от -9 до 99 (когда два знака в числе) + if(sensor->temp > -10 && sensor->temp <= 99) { + uint8_t int_len = canvas_string_width(canvas, app->buff); + snprintf(app->buff, BUFF_SIZE, ".%d", temp_dec); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, x + 27 + int_len / 2 + 2, y + 10 + 7, app->buff); + } + if(color == ColorBlack) canvas_invert_color(canvas); +} + +static void _draw_humidity(Canvas* canvas, Sensor* sensor, const uint8_t pos[2]) { + //Рисование рамки + canvas_draw_rframe(canvas, pos[0], pos[1], 54, 20, 3); + canvas_draw_rframe(canvas, pos[0], pos[1], 54, 19, 3); + + //Рисование иконки + canvas_draw_icon(canvas, pos[0] + 3, pos[1] + 2, &I_hum_9x15); + + //Целая часть влажности + snprintf(app->buff, BUFF_SIZE, "%d", (uint8_t)sensor->hum); + canvas_set_font(canvas, FontBigNumbers); + canvas_draw_str_aligned(canvas, pos[0] + 27, pos[1] + 10, AlignCenter, AlignCenter, app->buff); + uint8_t int_len = canvas_string_width(canvas, app->buff); + //Единица измерения + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, pos[0] + 27 + int_len / 2 + 4, pos[1] + 10 + 7, "%"); +} + +static void _draw_pressure(Canvas* canvas, Sensor* sensor) { + const uint8_t x = 29, y = 39; + //Рисование рамки + canvas_draw_rframe(canvas, x, y, 69, 20, 3); + canvas_draw_rframe(canvas, x, y, 69, 19, 3); + + //Рисование иконки + canvas_draw_icon(canvas, x + 3, y + 4, &I_pressure_7x13); + + int16_t press_int = sensor->pressure; + int8_t press_dec = (int16_t)(sensor->temp * 10) % 10; + + //Целая часть давления + snprintf(app->buff, BUFF_SIZE, "%d", press_int); + canvas_set_font(canvas, FontBigNumbers); + canvas_draw_str_aligned( + canvas, x + 27 + ((press_int > 99) ? 5 : 0), y + 10, AlignCenter, AlignCenter, app->buff); + //Печать дробной части давления в диапазоне от 0 до 99 (когда два знака в числе) + if(press_int <= 99) { + uint8_t int_len = canvas_string_width(canvas, app->buff); + snprintf(app->buff, BUFF_SIZE, ".%d", press_dec); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, x + 27 + int_len / 2 + 2, y + 10 + 7, app->buff); + } + canvas_set_font(canvas, FontSecondary); + //Единица измерения + if(app->settings.pressure_unit == UT_PRESSURE_MM_HG) { + canvas_draw_icon(canvas, x + 50, y + 2, &I_mm_hg_15x15); + } else if(app->settings.pressure_unit == UT_PRESSURE_IN_HG) { + canvas_draw_icon(canvas, x + 50, y + 2, &I_in_hg_15x15); + } else if(app->settings.pressure_unit == UT_PRESSURE_KPA) { + canvas_draw_str(canvas, x + 52, y + 13, "kPa"); + } +} + +static void _draw_singleSensor(Canvas* canvas, Sensor* sensor, const uint8_t pos[2], Color color) { + canvas_set_font(canvas, FontPrimary); + + const uint8_t max_width = 56; + + char sensor_name[12] = {0}; + memcpy(sensor_name, sensor->name, 10); + + if(canvas_string_width(canvas, sensor_name) > max_width) { + uint8_t i = 10; + while((canvas_string_width(canvas, sensor_name) > max_width - 6) && (i != 0)) { + sensor_name[i--] = '\0'; + } + sensor_name[++i] = '.'; + sensor_name[++i] = '.'; + } + + canvas_draw_str_aligned( + canvas, pos[0] + 27, pos[1] + 3, AlignCenter, AlignCenter, sensor_name); + _draw_temperature(canvas, sensor, pos[0], pos[1] + 8, color); +} + +static void _draw_view_noSensors(Canvas* canvas) { + canvas_draw_icon(canvas, 7, 17, &I_sherlok_53x45); + //Рисование рамки + canvas_draw_rframe(canvas, 0, 0, 128, 63, 7); + canvas_draw_rframe(canvas, 0, 0, 128, 64, 7); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 63, 10, AlignCenter, AlignCenter, "No sensors found"); + canvas_set_font(canvas, FontSecondary); + const uint8_t x = 65, y = 32; + canvas_draw_rframe(canvas, x - 4, y - 11, 54, 33, 3); + canvas_draw_rframe(canvas, x - 4, y - 11, 54, 34, 3); + canvas_draw_str(canvas, x, y, "To add the"); + canvas_draw_str(canvas, x, y + 9, "new sensor"); + canvas_draw_str(canvas, x, y + 18, "press OK"); + + canvas_draw_icon(canvas, x + 37, y + 10, &I_Ok_btn_9x9); +} + +static void _draw_view_sensorsList(Canvas* canvas) { + //Текущая страница + uint8_t page = generalview_sensor_index / 4; + //Количество датчиков, которые будут отображаться на странице + uint8_t page_sensors_count; + if((unitemp_sensors_getActiveCount() - page * 4) / 4) { + page_sensors_count = 4; + } else { + page_sensors_count = (unitemp_sensors_getActiveCount() - page * 4) % 4; + } + + //Количество страниц + uint8_t pages = + unitemp_sensors_getActiveCount() / 4 + (unitemp_sensors_getActiveCount() % 4 ? 1 : 0); + + //Стрелка влево + if(page > 0) { + canvas_draw_icon(canvas, 2, 32, &I_ButtonLeft_4x7); + } + //Стрелка вправо + if(pages > 0 && page < pages - 1) { + canvas_draw_icon(canvas, 122, 32, &I_ButtonRight_4x7); + } + + const uint8_t value_positions[][4][2] = { + {{36, 18}}, //1 датчик + {{7, 18}, {67, 18}}, //2 датчика + {{7, 3}, {67, 3}, {37, 33}}, //3 датчика + {{7, 3}, {67, 3}, {7, 33}, {67, 33}}}; //4 датчика + //Рисование рамки + canvas_draw_rframe(canvas, 0, 0, 128, 63, 7); + canvas_draw_rframe(canvas, 0, 0, 128, 64, 7); + for(uint8_t i = 0; i < page_sensors_count; i++) { + _draw_singleSensor( + canvas, + unitemp_sensor_getActive(page * 4 + i), + value_positions[page_sensors_count - 1][i], + ColorWhite); + } +} + +static void _draw_carousel_values(Canvas* canvas) { + UnitempStatus sensor_status = unitemp_sensor_getActive(generalview_sensor_index)->status; + if(sensor_status == UT_SENSORSTATUS_ERROR || sensor_status == UT_SENSORSTATUS_TIMEOUT) { + const Icon* frames[] = { + &I_flipper_happy_60x38, &I_flipper_happy_2_60x38, &I_flipper_sad_60x38}; + canvas_draw_icon(canvas, 34, 23, frames[furi_get_tick() % 2250 / 750]); + + canvas_set_font(canvas, FontSecondary); + if(unitemp_sensor_getActive(generalview_sensor_index)->type->interface == &SINGLE_WIRE) { + snprintf( + app->buff, + BUFF_SIZE, + "Waiting for module on pin %d", + ((SingleWireSensor*)unitemp_sensor_getActive(generalview_sensor_index)->instance) + ->gpio->num); + } + if(unitemp_sensor_getActive(generalview_sensor_index)->type->interface == &ONE_WIRE) { + snprintf( + app->buff, + BUFF_SIZE, + "Waiting for module on pin %d", + ((OneWireSensor*)unitemp_sensor_getActive(generalview_sensor_index)->instance) + ->bus->gpio->num); + } + if(unitemp_sensor_getActive(generalview_sensor_index)->type->interface == &I2C) { + snprintf(app->buff, BUFF_SIZE, "Waiting for module on I2C pins"); + } + canvas_draw_str_aligned(canvas, 64, 19, AlignCenter, AlignCenter, app->buff); + return; + } + + static const uint8_t temp_positions[3][2] = {{37, 23}, {37, 16}, {9, 16}}; + static const uint8_t hum_positions[2][2] = {{37, 38}, {65, 16}}; + //Селектор значений для отображения + switch(unitemp_sensor_getActive(generalview_sensor_index)->type->datatype) { + case UT_DATA_TYPE_TEMP: + _draw_temperature( + canvas, + unitemp_sensor_getActive(generalview_sensor_index), + temp_positions[0][0], + temp_positions[0][1], + ColorWhite); + break; + case UT_DATA_TYPE_TEMP_HUM: + _draw_temperature( + canvas, + unitemp_sensor_getActive(generalview_sensor_index), + temp_positions[1][0], + temp_positions[1][1], + ColorWhite); + _draw_humidity( + canvas, unitemp_sensor_getActive(generalview_sensor_index), hum_positions[0]); + break; + case UT_DATA_TYPE_TEMP_PRESS: + _draw_temperature( + canvas, + unitemp_sensor_getActive(generalview_sensor_index), + temp_positions[1][0], + temp_positions[1][1], + ColorWhite); + _draw_pressure(canvas, unitemp_sensor_getActive(generalview_sensor_index)); + break; + case UT_DATA_TYPE_TEMP_HUM_PRESS: + _draw_temperature( + canvas, + unitemp_sensor_getActive(generalview_sensor_index), + temp_positions[2][0], + temp_positions[2][1], + ColorWhite); + _draw_humidity( + canvas, unitemp_sensor_getActive(generalview_sensor_index), hum_positions[1]); + _draw_pressure(canvas, unitemp_sensor_getActive(generalview_sensor_index)); + break; + } +} +static void _draw_carousel_info(Canvas* canvas) { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 10, 23, "Type:"); + + if(unitemp_sensor_getActive(generalview_sensor_index)->type->interface == &ONE_WIRE) { + OneWireSensor* s = unitemp_sensor_getActive(generalview_sensor_index)->instance; + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 10, 35, "GPIO:"); + canvas_draw_str(canvas, 10, 47, "ID:"); + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str( + canvas, + 41, + 23, + unitemp_onewire_sensor_getModel(unitemp_sensor_getActive(generalview_sensor_index))); + canvas_draw_str(canvas, 41, 35, s->bus->gpio->name); + snprintf( + app->buff, + BUFF_SIZE, + "%02X%02X%02X%02X%02X%02X%02X%02X", + s->deviceID[0], + s->deviceID[1], + s->deviceID[2], + s->deviceID[3], + s->deviceID[4], + s->deviceID[5], + s->deviceID[6], + s->deviceID[7]); + canvas_draw_str(canvas, 24, 47, app->buff); + } + + if(unitemp_sensor_getActive(generalview_sensor_index)->type->interface == &SINGLE_WIRE) { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 10, 35, "GPIO:"); + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str( + canvas, 41, 23, unitemp_sensor_getActive(generalview_sensor_index)->type->typename); + canvas_draw_str( + canvas, + 41, + 35, + ((SingleWireSensor*)unitemp_sensor_getActive(generalview_sensor_index)->instance) + ->gpio->name); + } + + if(unitemp_sensor_getActive(generalview_sensor_index)->type->interface == &I2C) { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 10, 35, "I2C addr:"); + canvas_draw_str(canvas, 10, 46, "SDA pin:"); + canvas_draw_str(canvas, 10, 58, "SCL pin:"); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str( + canvas, 41, 23, unitemp_sensor_getActive(generalview_sensor_index)->type->typename); + snprintf( + app->buff, + BUFF_SIZE, + "0x%02X", + ((I2CSensor*)unitemp_sensor_getActive(generalview_sensor_index)->instance) + ->currentI2CAdr >> + 1); + canvas_draw_str(canvas, 57, 35, app->buff); + canvas_draw_str(canvas, 54, 46, "15 (C0)"); + canvas_draw_str(canvas, 54, 58, "16 (C1)"); + } +} +static void _draw_view_sensorsCarousel(Canvas* canvas) { + //Рисование рамки + canvas_draw_rframe(canvas, 0, 0, 128, 63, 7); + canvas_draw_rframe(canvas, 0, 0, 128, 64, 7); + + //Печать имени + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned( + canvas, + 64, + 7, + AlignCenter, + AlignCenter, + unitemp_sensor_getActive(generalview_sensor_index)->name); + //Подчёркивание + uint8_t line_len = + canvas_string_width(canvas, unitemp_sensor_getActive(generalview_sensor_index)->name) + 2; + canvas_draw_line(canvas, 64 - line_len / 2, 12, 64 + line_len / 2, 12); + + //Стрелка вправо + if(unitemp_sensors_getTypesCount() > 0 && + generalview_sensor_index < unitemp_sensors_getActiveCount() - 1) { + canvas_draw_icon(canvas, 122, 29, &I_ButtonRight_4x7); + } + //Стрелка влево + if(generalview_sensor_index > 0) { + canvas_draw_icon(canvas, 2, 29, &I_ButtonLeft_4x7); + } + + switch(carousel_info_selector) { + case CAROUSEL_VALUES: + _draw_carousel_values(canvas); + break; + case CAROUSEL_INFO: + _draw_carousel_info(canvas); + break; + } +} + +static void _draw_callback(Canvas* canvas, void* _model) { + UNUSED(_model); + + app->sensors_ready = true; + + uint8_t sensors_count = unitemp_sensors_getActiveCount(); + + if(generalview_sensor_index + 1 > sensors_count) generalview_sensor_index = 0; + + if(sensors_count == 0) { + current_view = G_NO_SENSORS_VIEW; + _draw_view_noSensors(canvas); + } else { + if(sensors_count == 1) current_view = G_CAROUSEL_VIEW; + if(current_view == G_NO_SENSORS_VIEW) current_view = G_CAROUSEL_VIEW; + if(current_view == G_LIST_VIEW) _draw_view_sensorsList(canvas); + if(current_view == G_CAROUSEL_VIEW) _draw_view_sensorsCarousel(canvas); + } +} + +static bool _input_callback(InputEvent* event, void* context) { + UNUSED(context); + + //Обработка короткого нажатия "ок" + if(event->key == InputKeyOk && event->type == InputTypeShort) { + //Меню добавления датчика при их отсутствии + if(current_view == G_NO_SENSORS_VIEW) { + app->sensors_ready = false; + unitemp_SensorsList_switch(); + } else if(current_view == G_LIST_VIEW) { + //Переход в главное меню при выключенном селекторе + app->sensors_ready = false; + unitemp_MainMenu_switch(); + } else if(current_view == G_CAROUSEL_VIEW) { + app->sensors_ready = false; + unitemp_SensorActions_switch(unitemp_sensor_getActive(generalview_sensor_index)); + } + } + + //Обработка короткого нажатия "вниз" + if(event->key == InputKeyDown && event->type == InputTypeShort) { + //Переход из значений в информацию в карусели + if(current_view == G_CAROUSEL_VIEW && carousel_info_selector == CAROUSEL_VALUES) { + carousel_info_selector = CAROUSEL_INFO; + return true; + } + //Переход в карусель из списка + if(current_view == G_LIST_VIEW) { + current_view = G_CAROUSEL_VIEW; + return true; + } + } + + //Обработка короткого нажатия "вверх" + if(event->key == InputKeyUp && event->type == InputTypeShort) { + //Переход из информации в значения в карусели + if(current_view == G_CAROUSEL_VIEW && carousel_info_selector == CAROUSEL_INFO) { + carousel_info_selector = CAROUSEL_VALUES; + return true; + } + //Переход в список из карусели + if(current_view == G_CAROUSEL_VIEW && carousel_info_selector == CAROUSEL_VALUES && + unitemp_sensors_getActiveCount() > 1) { + current_view = G_LIST_VIEW; + return true; + } + } + + //Обработка короткого нажатия "вправо" + if(event->key == InputKeyRight && event->type == InputTypeShort) { + //Пролистывание карусели вперёд + if(current_view == G_CAROUSEL_VIEW) { + if(++generalview_sensor_index >= unitemp_sensors_getActiveCount()) { + generalview_sensor_index = 0; + if(carousel_info_selector == CAROUSEL_VALUES) current_view = G_LIST_VIEW; + } + + return true; + } + //Пролистывание списка вперёд + if(current_view == G_LIST_VIEW) { + generalview_sensor_index += 4; + if(generalview_sensor_index >= unitemp_sensors_getActiveCount()) { + generalview_sensor_index = 0; + current_view = G_CAROUSEL_VIEW; + } + return true; + } + } + + //Обработка короткого нажатия "влево" + if(event->key == InputKeyLeft && event->type == InputTypeShort) { + //Пролистывание карусели назад + if(current_view == G_CAROUSEL_VIEW) { + if(--generalview_sensor_index >= unitemp_sensors_getActiveCount()) { + generalview_sensor_index = unitemp_sensors_getActiveCount() - 1; + if(carousel_info_selector == CAROUSEL_VALUES) current_view = G_LIST_VIEW; + } + + return true; + } + //Пролистывание списка назад + if(current_view == G_LIST_VIEW) { + generalview_sensor_index -= 4; + if(generalview_sensor_index >= unitemp_sensors_getActiveCount()) { + generalview_sensor_index = unitemp_sensors_getActiveCount() - 1; + current_view = G_CAROUSEL_VIEW; + } + + return true; + } + } + + //Обработка короткого нажатия "назад" + if(event->key == InputKeyBack && event->type == InputTypeShort) { + //Выход из приложения при карусели или отсутствии датчиков + if(current_view == G_NO_SENSORS_VIEW || + ((current_view == G_CAROUSEL_VIEW) && (carousel_info_selector == CAROUSEL_VALUES))) { + app->processing = false; + return true; + } + //Переключение селектора вида карусели + if((current_view == G_CAROUSEL_VIEW) && (carousel_info_selector != CAROUSEL_VALUES)) { + carousel_info_selector = CAROUSEL_VALUES; + return true; + } + //Переход в карусель из списка + if(current_view == G_LIST_VIEW) { + current_view = G_CAROUSEL_VIEW; + return true; + } + } + + return true; +} + +void unitemp_General_alloc(void) { + view = view_alloc(); + view_set_context(view, app); + view_set_draw_callback(view, _draw_callback); + view_set_input_callback(view, _input_callback); + + view_dispatcher_add_view(app->view_dispatcher, UnitempViewGeneral, view); +} + +void unitemp_General_switch(void) { + app->sensors_ready = true; + view_dispatcher_switch_to_view(app->view_dispatcher, UnitempViewGeneral); +} + +void unitemp_General_free(void) { + view_free(view); +} diff --git a/Applications/Official/DEV_FW/source/unitemp/views/MainMenu_view.c b/Applications/Official/DEV_FW/source/unitemp/views/MainMenu_view.c new file mode 100644 index 000000000..4b2820eee --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/views/MainMenu_view.c @@ -0,0 +1,99 @@ +/* + Unitemp - Universal temperature reader + Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n) + + 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 . +*/ +#include "UnitempViews.h" +#include + +//Текущий вид +static View* view; +//Список +static VariableItemList* variable_item_list; + +#define VIEW_ID UnitempViewMainMenu + +/** + * @brief Функция обработки нажатия кнопки "Назад" + * + * @param context Указатель на данные приложения + * @return ID вида в который нужно переключиться + */ +static uint32_t _exit_callback(void* context) { + UNUSED(context); + //Возврат в общий вид + return UnitempViewGeneral; +} +/** + * @brief Функция обработки нажатия средней кнопки + * + * @param context Указатель на данные приложения + * @param index На каком элементе списка была нажата кнопка + */ +static void _enter_callback(void* context, uint32_t index) { + UNUSED(context); + if(index == 0) { //Add new sensor + unitemp_SensorsList_switch(); + } + if(index == 1) { //Settings + unitemp_Settings_switch(); + } + if(index == 2) { + unitemp_widget_help_switch(); + } + if(index == 3) { + unitemp_widget_about_switch(); + } +} + +/** + * @brief Создание списка действий с указанным датчиком + */ +void unitemp_MainMenu_alloc(void) { + variable_item_list = variable_item_list_alloc(); + //Сброс всех элементов меню + variable_item_list_reset(variable_item_list); + + variable_item_list_add(variable_item_list, "Add new sensor", 1, NULL, NULL); + variable_item_list_add(variable_item_list, "Settings", 1, NULL, NULL); + variable_item_list_add(variable_item_list, "Help", 1, NULL, NULL); + variable_item_list_add(variable_item_list, "About", 1, NULL, NULL); + + //Добавление колбека на нажатие средней кнопки + variable_item_list_set_enter_callback(variable_item_list, _enter_callback, app); + + //Создание вида из списка + view = variable_item_list_get_view(variable_item_list); + //Добавление колбека на нажатие кнопки "Назад" + view_set_previous_callback(view, _exit_callback); + //Добавление вида в диспетчер + view_dispatcher_add_view(app->view_dispatcher, VIEW_ID, view); +} + +void unitemp_MainMenu_switch(void) { + //Обнуление последнего выбранного пункта + variable_item_list_set_selected_item(variable_item_list, 0); + //Переключение в вид + view_dispatcher_switch_to_view(app->view_dispatcher, VIEW_ID); +} + +void unitemp_MainMenu_free(void) { + //Очистка списка элементов + variable_item_list_free(variable_item_list); + //Очистка вида + view_free(view); + //Удаление вида после обработки + view_dispatcher_remove_view(app->view_dispatcher, VIEW_ID); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/unitemp/views/Popup_view.c b/Applications/Official/DEV_FW/source/unitemp/views/Popup_view.c new file mode 100644 index 000000000..d495462b2 --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/views/Popup_view.c @@ -0,0 +1,50 @@ +/* + Unitemp - Universal temperature reader + Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n) + + 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 . +*/ +#include "UnitempViews.h" +#include +#include +#include + +uint32_t _prev_view_id; + +#define VIEW_ID UnitempViewPopup + +static void _popup_callback(void* context) { + UNUSED(context); + view_dispatcher_switch_to_view(app->view_dispatcher, _prev_view_id); +} + +void unitemp_popup(const Icon* icon, char* header, char* message, uint32_t prev_view_id) { + _prev_view_id = prev_view_id; + popup_reset(app->popup); + popup_set_icon(app->popup, 0, 64 - icon_get_height(icon), icon); + popup_set_header(app->popup, header, 64, 6, AlignCenter, AlignCenter); + popup_set_text( + app->popup, + message, + (128 - icon_get_width(icon)) / 2 + icon_get_width(icon), + 32, + AlignCenter, + AlignCenter); + + popup_set_timeout(app->popup, 5000); + popup_set_callback(app->popup, _popup_callback); + popup_enable_timeout(app->popup); + + view_dispatcher_switch_to_view(app->view_dispatcher, VIEW_ID); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/unitemp/views/SensorActions_view.c b/Applications/Official/DEV_FW/source/unitemp/views/SensorActions_view.c new file mode 100644 index 000000000..6f375a50a --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/views/SensorActions_view.c @@ -0,0 +1,125 @@ +/* + Unitemp - Universal temperature reader + Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n) + + 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 . +*/ +#include "UnitempViews.h" +#include +#include + +//Текущий вид +static View* view; +//Список +static VariableItemList* variable_item_list; +//Текущий датчик +static Sensor* current_sensor; + +typedef enum carousel_info { + CAROUSEL_VALUES, //Отображение значений датчиков + CAROUSEL_INFO, //Отображение информации о датчике +} carousel_info; +extern carousel_info carousel_info_selector; + +#define VIEW_ID UnitempViewSensorActions + +/** + * @brief Функция обработки нажатия кнопки "Назад" + * + * @param context Указатель на данные приложения + * @return ID вида в который нужно переключиться + */ +static uint32_t _exit_callback(void* context) { + UNUSED(context); + + //Возврат предыдущий вид + return UnitempViewGeneral; +} +/** + * @brief Функция обработки нажатия средней кнопки + * + * @param context Указатель на данные приложения + * @param index На каком элементе списка была нажата кнопка + */ +static void _enter_callback(void* context, uint32_t index) { + UNUSED(context); + switch(index) { + case 0: + carousel_info_selector = CAROUSEL_INFO; + unitemp_General_switch(); + return; + case 1: + unitemp_SensorEdit_switch(current_sensor); + break; + case 2: + unitemp_widget_delete_switch(current_sensor); + break; + case 3: + unitemp_SensorsList_switch(); + break; + case 4: + unitemp_Settings_switch(); + break; + case 5: + unitemp_widget_help_switch(); + break; + case 6: + unitemp_widget_about_switch(); + break; + } +} + +/** + * @brief Создание меню действий с датчиком + */ +void unitemp_SensorActions_alloc(void) { + variable_item_list = variable_item_list_alloc(); + //Сброс всех элементов меню + variable_item_list_reset(variable_item_list); + + variable_item_list_add(variable_item_list, "Info", 1, NULL, NULL); + variable_item_list_add(variable_item_list, "Edit", 1, NULL, NULL); + variable_item_list_add(variable_item_list, "Delete", 1, NULL, NULL); + + variable_item_list_add(variable_item_list, "Add new sensor", 1, NULL, NULL); + variable_item_list_add(variable_item_list, "Settings", 1, NULL, NULL); + variable_item_list_add(variable_item_list, "Help", 1, NULL, NULL); + variable_item_list_add(variable_item_list, "About", 1, NULL, NULL); + + //Добавление колбека на нажатие средней кнопки + variable_item_list_set_enter_callback(variable_item_list, _enter_callback, app); + //Создание вида из списка + view = variable_item_list_get_view(variable_item_list); + //Добавление колбека на нажатие кнопки "Назад" + view_set_previous_callback(view, _exit_callback); + //Добавление вида в диспетчер + view_dispatcher_add_view(app->view_dispatcher, VIEW_ID, view); +} + +void unitemp_SensorActions_switch(Sensor* sensor) { + current_sensor = sensor; + //Обнуление последнего выбранного пункта + variable_item_list_set_selected_item(variable_item_list, 0); + + view_dispatcher_switch_to_view(app->view_dispatcher, VIEW_ID); +} + +void unitemp_SensorActions_free(void) { + //Очистка списка элементов + variable_item_list_free(variable_item_list); + //Очистка вида + view_free(view); + //Удаление вида после обработки + view_dispatcher_remove_view(app->view_dispatcher, VIEW_ID); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/unitemp/views/SensorEdit_view.c b/Applications/Official/DEV_FW/source/unitemp/views/SensorEdit_view.c new file mode 100644 index 000000000..d1660bc2e --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/views/SensorEdit_view.c @@ -0,0 +1,380 @@ +/* + Unitemp - Universal temperature reader + Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n) + + 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 . +*/ +#include "UnitempViews.h" +#include + +#include "../interfaces/SingleWireSensor.h" +#include "../interfaces/OneWireSensor.h" +#include "../interfaces/I2CSensor.h" + +//Текущий вид +static View* view; +//Список +static VariableItemList* variable_item_list; +//Текущий редактируемый датчик +static Sensor* editable_sensor; +//Изначальный GPIO датчика +static const GPIO* initial_gpio = NULL; + +//Элемент списка - имя датчика +static VariableItem* sensor_name_item; +//Элемент списка - адрес датчика one wire +static VariableItem* onewire_addr_item; +//Элемент списка - адрес датчика one wire +static VariableItem* onewire_type_item; +//Элемент списка - смещение температуры +VariableItem* temp_offset_item; + +#define OFFSET_BUFF_SIZE 5 +//Буффер для текста смещения +static char* offset_buff; + +extern uint8_t generalview_sensor_index; + +#define VIEW_ID UnitempViewSensorEdit + +bool _onewire_id_exist(uint8_t* id) { + if(id == NULL) return false; + for(uint8_t i = 0; i < unitemp_sensors_getActiveCount(); i++) { + if(unitemp_sensor_getActive(i)->type == &Dallas) { + if(unitemp_onewire_id_compare( + id, ((OneWireSensor*)(unitemp_sensor_getActive(i)->instance))->deviceID)) { + return true; + } + } + } + return false; +} + +static void _onewire_scan(void) { + OneWireSensor* ow_sensor = editable_sensor->instance; +#ifdef UNITEMP_DEBUG + FURI_LOG_D( + APP_NAME, + "devices on wire %d: %d", + ow_sensor->bus->gpio->num, + ow_sensor->bus->device_count); +#endif + + //Сканирование шины one wire + unitemp_onewire_bus_init(ow_sensor->bus); + uint8_t* id = NULL; + do { + id = unitemp_onewire_bus_enum_next(ow_sensor->bus); + } while(_onewire_id_exist(id)); + + if(id == NULL) { + unitemp_onewire_bus_enum_init(); + id = unitemp_onewire_bus_enum_next(ow_sensor->bus); + if(_onewire_id_exist(id)) { + do { + id = unitemp_onewire_bus_enum_next(ow_sensor->bus); + } while(_onewire_id_exist(id) && id != NULL); + } + if(id == NULL) { + memset(ow_sensor->deviceID, 0, 8); + ow_sensor->familyCode = 0; + unitemp_onewire_bus_deinit(ow_sensor->bus); + variable_item_set_current_value_text(onewire_addr_item, "empty"); + variable_item_set_current_value_text( + onewire_type_item, unitemp_onewire_sensor_getModel(editable_sensor)); + return; + } + } + + unitemp_onewire_bus_deinit(ow_sensor->bus); + + memcpy(ow_sensor->deviceID, id, 8); + ow_sensor->familyCode = id[0]; +#ifdef UNITEMP_DEBUG + FURI_LOG_D( + APP_NAME, + "Found sensor's ID: %02X%02X%02X%02X%02X%02X%02X%02X", + id[0], + id[1], + id[2], + id[3], + id[4], + id[5], + id[6], + id[7]); +#endif + + if(ow_sensor->familyCode != 0) { + char id_buff[10]; + snprintf( + id_buff, + 10, + "%02X%02X%02X", + ow_sensor->deviceID[1], + ow_sensor->deviceID[2], + ow_sensor->deviceID[3]); + //А больше не лезет( + variable_item_set_current_value_text(onewire_addr_item, id_buff); + } else { + variable_item_set_current_value_text(onewire_addr_item, "empty"); + } + variable_item_set_current_value_text( + onewire_type_item, unitemp_onewire_sensor_getModel(editable_sensor)); +} + +/** + * @brief Функция обработки нажатия кнопки "Назад" + * + * @param context Указатель на данные приложения + * @return ID вида в который нужно переключиться + */ +static uint32_t _exit_callback(void* context) { + UNUSED(context); + editable_sensor->status = UT_SENSORSTATUS_TIMEOUT; + if(!unitemp_sensor_isContains(editable_sensor)) unitemp_sensor_free(editable_sensor); + unitemp_sensors_reload(); + //Возврат предыдущий вид + return UnitempViewGeneral; +} +/** + * @brief Функция обработки нажатия средней кнопки + * + * @param context Указатель на данные приложения + * @param index На каком элементе списка была нажата кнопка + */ +static void _enter_callback(void* context, uint32_t index) { + UNUSED(context); + //Смена имени + if(index == 0) { + unitemp_SensorNameEdit_switch(editable_sensor); + } + //Сохранение + if((index == 4 && editable_sensor->type->interface != &ONE_WIRE) || + (index == 5 && editable_sensor->type->interface == &ONE_WIRE)) { + //Выход если датчик one wire не имеет ID + if(editable_sensor->type->interface == &ONE_WIRE && + ((OneWireSensor*)(editable_sensor->instance))->familyCode == 0) { + return; + } + if(initial_gpio != NULL) { + unitemp_gpio_unlock(initial_gpio); + initial_gpio = NULL; + } + editable_sensor->status = UT_SENSORSTATUS_TIMEOUT; + if(!unitemp_sensor_isContains(editable_sensor)) unitemp_sensors_add(editable_sensor); + unitemp_sensors_save(); + unitemp_sensors_reload(); + + generalview_sensor_index = unitemp_sensors_getActiveCount() - 1; + unitemp_General_switch(); + } + + //Адрес устройства на шине one wire + if(index == 4 && editable_sensor->type->interface == &ONE_WIRE) { + _onewire_scan(); + } +} + +/** + * @brief Функция обработки изменения значения GPIO + * + * @param item Указатель на элемент списка + */ +static void _gpio_change_callback(VariableItem* item) { + uint8_t index = variable_item_get_current_value_index(item); + if(editable_sensor->type->interface == &SINGLE_WIRE) { + SingleWireSensor* instance = editable_sensor->instance; + instance->gpio = + unitemp_gpio_getAviablePort(editable_sensor->type->interface, index, initial_gpio); + variable_item_set_current_value_text(item, instance->gpio->name); + } + if(editable_sensor->type->interface == &ONE_WIRE) { + OneWireSensor* instance = editable_sensor->instance; + instance->bus->gpio = + unitemp_gpio_getAviablePort(editable_sensor->type->interface, index, NULL); + variable_item_set_current_value_text(item, instance->bus->gpio->name); + } +} +/** + * @brief Функция обработки изменения значения GPIO + * + * @param item Указатель на элемент списка + */ +static void _i2caddr_change_callback(VariableItem* item) { + uint8_t index = variable_item_get_current_value_index(item); + ((I2CSensor*)editable_sensor->instance)->currentI2CAdr = + ((I2CSensor*)editable_sensor->instance)->minI2CAdr + index * 2; + char buff[5]; + snprintf(buff, 5, "0x%2X", ((I2CSensor*)editable_sensor->instance)->currentI2CAdr >> 1); + variable_item_set_current_value_text(item, buff); +} +/** + * @brief Функция обработки изменения значения имени датчика + * + * @param item Указатель на элемент списка + */ +static void _name_change_callback(VariableItem* item) { + variable_item_set_current_value_index(item, 0); + unitemp_SensorNameEdit_switch(editable_sensor); +} +/** + * @brief Функция обработки изменения значения адреса датчика one wire + * + * @param item Указатель на элемент списка + */ +static void _onwire_addr_change_callback(VariableItem* item) { + variable_item_set_current_value_index(item, 0); + _onewire_scan(); +} + +/** + * @brief Функция обработки изменения значения смещения температуры + * + * @param item Указатель на элемент списка + */ +static void _offset_change_callback(VariableItem* item) { + editable_sensor->temp_offset = variable_item_get_current_value_index(item) - 20; + snprintf( + offset_buff, OFFSET_BUFF_SIZE, "%+1.1f", (double)(editable_sensor->temp_offset / 10.0)); + variable_item_set_current_value_text(item, offset_buff); +} + +/** + * @brief Создание меню редактирования датчка + */ +void unitemp_SensorEdit_alloc(void) { + variable_item_list = variable_item_list_alloc(); + //Сброс всех элементов меню + variable_item_list_reset(variable_item_list); + + //Добавление колбека на нажатие средней кнопки + variable_item_list_set_enter_callback(variable_item_list, _enter_callback, app); + + //Создание вида из списка + view = variable_item_list_get_view(variable_item_list); + //Добавление колбека на нажатие кнопки "Назад" + view_set_previous_callback(view, _exit_callback); + //Добавление вида в диспетчер + view_dispatcher_add_view(app->view_dispatcher, VIEW_ID, view); + + offset_buff = malloc(OFFSET_BUFF_SIZE); +} + +void unitemp_SensorEdit_switch(Sensor* sensor) { + editable_sensor = sensor; + + editable_sensor->status = UT_SENSORSTATUS_INACTIVE; + + //Сброс всех элементов меню + variable_item_list_reset(variable_item_list); + //Обнуление последнего выбранного пункта + variable_item_list_set_selected_item(variable_item_list, 0); + + //Имя датчика + sensor_name_item = variable_item_list_add( + variable_item_list, "Name", strlen(sensor->name) > 7 ? 1 : 2, _name_change_callback, NULL); + variable_item_set_current_value_index(sensor_name_item, 0); + variable_item_set_current_value_text(sensor_name_item, sensor->name); + + //Тип датчика (не редактируется) + onewire_type_item = variable_item_list_add(variable_item_list, "Type", 1, NULL, NULL); + variable_item_set_current_value_index(onewire_type_item, 0); + variable_item_set_current_value_text( + onewire_type_item, + (sensor->type->interface == &ONE_WIRE ? unitemp_onewire_sensor_getModel(editable_sensor) : + sensor->type->typename)); + //Смещение температуры + temp_offset_item = variable_item_list_add( + variable_item_list, "Temp. offset", 41, _offset_change_callback, NULL); + variable_item_set_current_value_index(temp_offset_item, sensor->temp_offset + 20); + snprintf( + offset_buff, OFFSET_BUFF_SIZE, "%+1.1f", (double)(editable_sensor->temp_offset / 10.0)); + variable_item_set_current_value_text(temp_offset_item, offset_buff); + + //Порт подключения датчка (для one wire и single wire) + if(sensor->type->interface == &ONE_WIRE || sensor->type->interface == &SINGLE_WIRE) { + if(sensor->type->interface == &ONE_WIRE) { + initial_gpio = ((OneWireSensor*)editable_sensor->instance)->bus->gpio; + } else { + initial_gpio = ((SingleWireSensor*)editable_sensor->instance)->gpio; + } + + uint8_t aviable_gpio_count = + unitemp_gpio_getAviablePortsCount(sensor->type->interface, initial_gpio); + VariableItem* item = variable_item_list_add( + variable_item_list, "GPIO", aviable_gpio_count, _gpio_change_callback, app); + + uint8_t gpio_index = 0; + if(unitemp_sensor_isContains(editable_sensor)) { + for(uint8_t i = 0; i < aviable_gpio_count; i++) { + if(unitemp_gpio_getAviablePort(sensor->type->interface, i, initial_gpio) == + initial_gpio) { + gpio_index = i; + break; + } + } + } + variable_item_set_current_value_index(item, gpio_index); + variable_item_set_current_value_text( + item, + unitemp_gpio_getAviablePort(sensor->type->interface, gpio_index, initial_gpio)->name); + } + //Адрес устройства на шине I2C (для датчиков I2C) + if(sensor->type->interface == &I2C) { + VariableItem* item = variable_item_list_add( + variable_item_list, + "I2C address", + (((I2CSensor*)sensor->instance)->maxI2CAdr >> 1) - + (((I2CSensor*)sensor->instance)->minI2CAdr >> 1) + 1, + _i2caddr_change_callback, + app); + snprintf(app->buff, 5, "0x%2X", ((I2CSensor*)sensor->instance)->currentI2CAdr >> 1); + variable_item_set_current_value_index( + item, + (((I2CSensor*)sensor->instance)->currentI2CAdr >> 1) - + (((I2CSensor*)sensor->instance)->minI2CAdr >> 1)); + variable_item_set_current_value_text(item, app->buff); + } + + //Адрес устройства на шине one wire (для датчиков one wire) + if(sensor->type->interface == &ONE_WIRE) { + onewire_addr_item = variable_item_list_add( + variable_item_list, "Address", 2, _onwire_addr_change_callback, NULL); + OneWireSensor* ow_sensor = sensor->instance; + if(ow_sensor->familyCode == 0) { + variable_item_set_current_value_text(onewire_addr_item, "Scan"); + } else { + snprintf( + app->buff, + 10, + "%02X%02X%02X", + ow_sensor->deviceID[1], + ow_sensor->deviceID[2], + ow_sensor->deviceID[3]); + variable_item_set_current_value_text(onewire_addr_item, app->buff); + } + } + variable_item_list_add(variable_item_list, "Save", 1, NULL, NULL); + view_dispatcher_switch_to_view(app->view_dispatcher, VIEW_ID); +} + +void unitemp_SensorEdit_free(void) { + //Очистка списка элементов + variable_item_list_free(variable_item_list); + //Очистка вида + view_free(view); + //Удаление вида после обработки + view_dispatcher_remove_view(app->view_dispatcher, VIEW_ID); + free(offset_buff); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/unitemp/views/SensorNameEdit_view.c b/Applications/Official/DEV_FW/source/unitemp/views/SensorNameEdit_view.c new file mode 100644 index 000000000..1d36ac142 --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/views/SensorNameEdit_view.c @@ -0,0 +1,46 @@ +/* + Unitemp - Universal temperature reader + Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n) + + 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 . +*/ +#include "UnitempViews.h" +#include + +//Окно ввода текста +static TextInput* text_input; +//Текущий редактируемый датчик +static Sensor* editable_sensor; + +#define VIEW_ID UnitempViewSensorNameEdit + +static void _sensor_name_changed_callback(void* context) { + UNUSED(context); + unitemp_SensorEdit_switch(editable_sensor); +} + +void unitemp_SensorNameEdit_alloc(void) { + text_input = text_input_alloc(); + view_dispatcher_add_view(app->view_dispatcher, VIEW_ID, text_input_get_view(text_input)); + text_input_set_header_text(text_input, "Sensor name"); +} +void unitemp_SensorNameEdit_switch(Sensor* sensor) { + editable_sensor = sensor; + text_input_set_result_callback( + text_input, _sensor_name_changed_callback, app, sensor->name, 11, true); + view_dispatcher_switch_to_view(app->view_dispatcher, VIEW_ID); +} +void unitemp_SensorNameEdit_free(void) { + text_input_free(text_input); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/unitemp/views/SensorsList_view.c b/Applications/Official/DEV_FW/source/unitemp/views/SensorsList_view.c new file mode 100644 index 000000000..a7d3d5556 --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/views/SensorsList_view.c @@ -0,0 +1,162 @@ +/* + Unitemp - Universal temperature reader + Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n) + + 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 . +*/ +#include "UnitempViews.h" +#include +#include +#include + +//Текущий вид +static View* view; +//Список +static VariableItemList* variable_item_list; + +#define VIEW_ID UnitempViewSensorsList + +/** + * @brief Функция обработки нажатия кнопки "Назад" + * + * @param context Указатель на данные приложения + * @return ID вида в который нужно переключиться + */ +static uint32_t _exit_callback(void* context) { + UNUSED(context); + + //Возврат предыдущий вид + return UnitempViewGeneral; +} +/** + * @brief Функция обработки нажатия средней кнопки + * + * @param context Указатель на данные приложения + * @param index На каком элементе списка была нажата кнопка + */ +static void _enter_callback(void* context, uint32_t index) { + UNUSED(context); + if(index == unitemp_sensors_getTypesCount()) { + unitemp_widget_help_switch(); + return; + } + + const SensorType* type = unitemp_sensors_getTypes()[index]; + uint8_t sensor_type_count = 0; + + //Подсчёт имеющихся датчиков данного типа + for(uint8_t i = 0; i < unitemp_sensors_getActiveCount(); i++) { + if(unitemp_sensor_getActive(i)->type == type) { + sensor_type_count++; + } + } + + //Имя датчка + char sensor_name[11]; + //Добавление счётчика к имени если такой датчик имеется + if(sensor_type_count == 0) + snprintf(sensor_name, 11, "%s", type->typename); + else + snprintf(sensor_name, 11, "%s_%d", type->typename, sensor_type_count); + + char args[22] = {0}; + + //Проверка доступности датчика + if(unitemp_gpio_getAviablePort(type->interface, 0, NULL) == NULL) { + if(type->interface == &SINGLE_WIRE || type->interface == &ONE_WIRE) { + unitemp_popup( + &I_Cry_dolph_55x52, "Sensor is unavailable", "All GPIOs\nare busy", VIEW_ID); + } + if(type->interface == &I2C) { + unitemp_popup( + &I_Cry_dolph_55x52, "Sensor is unavailable", "GPIOs 15 or 16\nare busy", VIEW_ID); + } + return; + } + + //Выбор первого доступного порта для датчика single wire + if(type->interface == &SINGLE_WIRE) { + snprintf( + args, + 4, + "%d", + unitemp_gpio_toInt(unitemp_gpio_getAviablePort(type->interface, 0, NULL))); + } + //Выбор первого доступного порта для датчика one wire и запись нулевого ID + if(type->interface == &ONE_WIRE) { + snprintf( + args, + 21, + "%d %02X%02X%02X%02X%02X%02X%02X%02X", + unitemp_gpio_toInt(unitemp_gpio_getAviablePort(type->interface, 0, NULL)), + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0); + } + //Для I2C адрес выберется автоматически + + unitemp_SensorEdit_switch(unitemp_sensor_alloc(sensor_name, type, args)); +} + +/** + * @brief Создание меню редактирования настроек + */ +void unitemp_SensorsList_alloc(void) { + variable_item_list = variable_item_list_alloc(); + //Сброс всех элементов меню + variable_item_list_reset(variable_item_list); + + //Добавление в список доступных датчиков + for(uint8_t i = 0; i < unitemp_sensors_getTypesCount(); i++) { + if(unitemp_sensors_getTypes()[i]->altname == NULL) { + variable_item_list_add( + variable_item_list, unitemp_sensors_getTypes()[i]->typename, 1, NULL, app); + } else { + variable_item_list_add( + variable_item_list, unitemp_sensors_getTypes()[i]->altname, 1, NULL, app); + } + } + variable_item_list_add(variable_item_list, "I don't know what to choose", 1, NULL, app); + + //Добавление колбека на нажатие средней кнопки + variable_item_list_set_enter_callback(variable_item_list, _enter_callback, app); + + //Создание вида из списка + view = variable_item_list_get_view(variable_item_list); + //Добавление колбека на нажатие кнопки "Назад" + view_set_previous_callback(view, _exit_callback); + //Добавление вида в диспетчер + view_dispatcher_add_view(app->view_dispatcher, VIEW_ID, view); +} + +void unitemp_SensorsList_switch(void) { + //Обнуление последнего выбранного пункта + variable_item_list_set_selected_item(variable_item_list, 0); + + view_dispatcher_switch_to_view(app->view_dispatcher, VIEW_ID); +} + +void unitemp_SensorsList_free(void) { + //Очистка списка элементов + variable_item_list_free(variable_item_list); + //Очистка вида + view_free(view); + //Удаление вида после обработки + view_dispatcher_remove_view(app->view_dispatcher, VIEW_ID); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/unitemp/views/Settings_view.c b/Applications/Official/DEV_FW/source/unitemp/views/Settings_view.c new file mode 100644 index 000000000..c3f82c14e --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/views/Settings_view.c @@ -0,0 +1,152 @@ +/* + Unitemp - Universal temperature reader + Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n) + + 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 . +*/ +#include "UnitempViews.h" +#include + +//Текущий вид +static View* view; +//Список +static VariableItemList* variable_item_list; + +static const char states[2][9] = {"Auto", "Infinity"}; +static const char temp_units[UT_TEMP_COUNT][3] = {"*C", "*F"}; +static const char pressure_units[UT_PRESSURE_COUNT][6] = {"mm Hg", "in Hg", "kPa"}; + +//Элемент списка - бесконечная подсветка +VariableItem* infinity_backlight_item; +//Единица измерения температуры +VariableItem* temperature_unit_item; +//Единица измерения давления +VariableItem* pressure_unit_item; +#define VIEW_ID UnitempViewSettings + +/** + * @brief Функция обработки нажатия кнопки "Назад" + * + * @param context Указатель на данные приложения + * @return ID вида в который нужно переключиться + */ +static uint32_t _exit_callback(void* context) { + UNUSED(context); + //Костыль с зависающей подсветкой + if((bool)variable_item_get_current_value_index(infinity_backlight_item) != + app->settings.infinityBacklight) { + if((bool)variable_item_get_current_value_index(infinity_backlight_item)) { + notification_message(app->notifications, &sequence_display_backlight_enforce_on); + } else { + notification_message(app->notifications, &sequence_display_backlight_enforce_auto); + } + } + + app->settings.infinityBacklight = + (bool)variable_item_get_current_value_index(infinity_backlight_item); + app->settings.temp_unit = variable_item_get_current_value_index(temperature_unit_item); + app->settings.pressure_unit = variable_item_get_current_value_index(pressure_unit_item); + unitemp_saveSettings(); + unitemp_loadSettings(); + + //Возврат предыдущий вид + return UnitempViewMainMenu; +} +/** + * @brief Функция обработки нажатия средней кнопки + * + * @param context Указатель на данные приложения + * @param index На каком элементе списка была нажата кнопка + */ +static void _enter_callback(void* context, uint32_t index) { + UNUSED(context); + UNUSED(index); +} + +static void _setting_change_callback(VariableItem* item) { + if(item == infinity_backlight_item) { + variable_item_set_current_value_text( + infinity_backlight_item, + states[variable_item_get_current_value_index(infinity_backlight_item)]); + } + if(item == temperature_unit_item) { + variable_item_set_current_value_text( + temperature_unit_item, + temp_units[variable_item_get_current_value_index(temperature_unit_item)]); + } + if(item == pressure_unit_item) { + variable_item_set_current_value_text( + pressure_unit_item, + pressure_units[variable_item_get_current_value_index(pressure_unit_item)]); + } +} + +/** + * @brief Создание меню редактирования настроек + */ +void unitemp_Settings_alloc(void) { + variable_item_list = variable_item_list_alloc(); + //Сброс всех элементов меню + variable_item_list_reset(variable_item_list); + + infinity_backlight_item = variable_item_list_add( + variable_item_list, "Backlight time", UT_TEMP_COUNT, _setting_change_callback, app); + temperature_unit_item = + variable_item_list_add(variable_item_list, "Temp. unit", 2, _setting_change_callback, app); + pressure_unit_item = variable_item_list_add( + variable_item_list, "Press. unit", UT_PRESSURE_COUNT, _setting_change_callback, app); + + //Добавление колбека на нажатие средней кнопки + variable_item_list_set_enter_callback(variable_item_list, _enter_callback, app); + + //Создание вида из списка + view = variable_item_list_get_view(variable_item_list); + //Добавление колбека на нажатие кнопки "Назад" + view_set_previous_callback(view, _exit_callback); + //Добавление вида в диспетчер + view_dispatcher_add_view(app->view_dispatcher, VIEW_ID, view); +} + +void unitemp_Settings_switch(void) { + //Обнуление последнего выбранного пункта + variable_item_list_set_selected_item(variable_item_list, 0); + + variable_item_set_current_value_index( + infinity_backlight_item, (uint8_t)app->settings.infinityBacklight); + variable_item_set_current_value_text( + infinity_backlight_item, + states[variable_item_get_current_value_index(infinity_backlight_item)]); + + variable_item_set_current_value_index(temperature_unit_item, (uint8_t)app->settings.temp_unit); + variable_item_set_current_value_text( + temperature_unit_item, + temp_units[variable_item_get_current_value_index(temperature_unit_item)]); + + variable_item_set_current_value_index( + pressure_unit_item, (uint8_t)app->settings.pressure_unit); + variable_item_set_current_value_text( + pressure_unit_item, + pressure_units[variable_item_get_current_value_index(pressure_unit_item)]); + + view_dispatcher_switch_to_view(app->view_dispatcher, VIEW_ID); +} + +void unitemp_Settings_free(void) { + //Очистка списка элементов + variable_item_list_free(variable_item_list); + //Очистка вида + view_free(view); + //Удаление вида после обработки + view_dispatcher_remove_view(app->view_dispatcher, VIEW_ID); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/unitemp/views/UnitempViews.h b/Applications/Official/DEV_FW/source/unitemp/views/UnitempViews.h new file mode 100644 index 000000000..b7c2467e0 --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/views/UnitempViews.h @@ -0,0 +1,94 @@ +/* + Unitemp - Universal temperature reader + Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef UNITEMP_SCENES +#define UNITEMP_SCENES + +#include "../unitemp.h" + +//Виды менюшек +typedef enum UnitempViews { + UnitempViewGeneral, + UnitempViewMainMenu, + UnitempViewSettings, + UnitempViewSensorsList, + UnitempViewSensorEdit, + UnitempViewSensorNameEdit, + UnitempViewSensorActions, + UnitempViewWidget, + UnitempViewPopup, + + UnitempViewsCount +} UnitempViews; + +/** + * @brief Вывести всплывающее окно + * + * @param icon Указатель на иконку + * @param header Заголовок + * @param message Сообщение + * @param prev_view_id ID вида куда в который нужно вернуться + */ +void unitemp_popup(const Icon* icon, char* header, char* message, uint32_t prev_view_id); + +/* Общий вид на датчики */ +void unitemp_General_alloc(void); +void unitemp_General_switch(void); +void unitemp_General_free(void); + +/* Главное меню */ +void unitemp_MainMenu_alloc(void); +void unitemp_MainMenu_switch(void); +void unitemp_MainMenu_free(void); + +/* Настройки */ +void unitemp_Settings_alloc(void); +void unitemp_Settings_switch(void); +void unitemp_Settings_free(void); + +/* Список датчиков */ +void unitemp_SensorsList_alloc(void); +void unitemp_SensorsList_switch(void); +void unitemp_SensorsList_free(void); + +/* Редактор датчка */ +void unitemp_SensorEdit_alloc(void); +//sensor - указатель на редактируемый датчик +void unitemp_SensorEdit_switch(Sensor* sensor); +void unitemp_SensorEdit_free(void); + +/* Редактор имени датчика */ +void unitemp_SensorNameEdit_alloc(void); +void unitemp_SensorNameEdit_switch(Sensor* sensor); +void unitemp_SensorNameEdit_free(void); + +/* Список действий с датчиком */ +void unitemp_SensorActions_alloc(void); +void unitemp_SensorActions_switch(Sensor* sensor); +void unitemp_SensorActions_free(void); + +/* Виджеты */ +void unitemp_widgets_alloc(void); +void unitemp_widgets_free(void); + +/* Подтверждение удаления */ +void unitemp_widget_delete_switch(Sensor* sensor); +/* Помощь */ +void unitemp_widget_help_switch(void); +/* О приложении */ +void unitemp_widget_about_switch(void); +#endif \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/unitemp/views/Widgets_view.c b/Applications/Official/DEV_FW/source/unitemp/views/Widgets_view.c new file mode 100644 index 000000000..6d8702ca1 --- /dev/null +++ b/Applications/Official/DEV_FW/source/unitemp/views/Widgets_view.c @@ -0,0 +1,204 @@ +/* + Unitemp - Universal temperature reader + Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n) + + 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 . +*/ +#include "UnitempViews.h" +#include "unitemp_icons.h" + +#include + +void unitemp_widgets_alloc(void) { + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, UnitempViewWidget, widget_get_view(app->widget)); +} + +void unitemp_widgets_free(void) { + widget_free(app->widget); +} + +/* ================== Подтверждение удаления ================== */ +Sensor* current_sensor; +/** + * @brief Функция обработки нажатия кнопки "Назад" + * + * @param context Указатель на данные приложения + * @return ID вида в который нужно переключиться + */ +static uint32_t _delete_exit_callback(void* context) { + UNUSED(context); + //Возвращаем ID вида, в который нужно вернуться + return UnitempViewSensorActions; +} +/** + * @brief Обработчик нажатий на кнопку в виджете + * + * @param result Какая из кнопок была нажата + * @param type Тип нажатия + * @param context Указатель на данные плагина + */ +static void _delete_click_callback(GuiButtonType result, InputType type, void* context) { + UNUSED(context); + //Коротко нажата левая кнопка (Cancel) + if(result == GuiButtonTypeLeft && type == InputTypeShort) { + unitemp_SensorActions_switch(current_sensor); + } + //Коротко нажата правая кнопка (Delete) + if(result == GuiButtonTypeRight && type == InputTypeShort) { + //Удаление датчика + unitemp_sensor_delete(current_sensor); + //Выход из меню + unitemp_General_switch(); + } +} +/** + * @brief Переключение в виджет удаления датчика + */ +void unitemp_widget_delete_switch(Sensor* sensor) { + current_sensor = sensor; + //Очистка виджета + widget_reset(app->widget); + //Добавление кнопок + widget_add_button_element( + app->widget, GuiButtonTypeLeft, "Cancel", _delete_click_callback, app); + widget_add_button_element( + app->widget, GuiButtonTypeRight, "Delete", _delete_click_callback, app); + + snprintf(app->buff, BUFF_SIZE, "\e#Delete %s?\e#", current_sensor->name); + widget_add_text_box_element( + app->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, app->buff, false); + + if(current_sensor->type->interface == &ONE_WIRE) { + OneWireSensor* s = current_sensor->instance; + + snprintf( + app->buff, + BUFF_SIZE, + "\e#Type:\e# %s", + unitemp_onewire_sensor_getModel(current_sensor)); + widget_add_text_box_element( + app->widget, 0, 16, 128, 23, AlignLeft, AlignTop, app->buff, false); + snprintf(app->buff, BUFF_SIZE, "\e#GPIO:\e# %s", s->bus->gpio->name); + widget_add_text_box_element( + app->widget, 0, 28, 128, 23, AlignLeft, AlignTop, app->buff, false); + + snprintf( + app->buff, + BUFF_SIZE, + "\e#ID:\e# %02X%02X%02X%02X%02X%02X%02X%02X", + s->deviceID[0], + s->deviceID[1], + s->deviceID[2], + s->deviceID[3], + s->deviceID[4], + s->deviceID[5], + s->deviceID[6], + s->deviceID[7]); + widget_add_text_box_element( + app->widget, 0, 40, 128, 23, AlignLeft, AlignTop, app->buff, false); + } + + if(current_sensor->type->interface == &SINGLE_WIRE) { + snprintf(app->buff, BUFF_SIZE, "\e#Type:\e# %s", current_sensor->type->typename); + widget_add_text_box_element( + app->widget, 0, 16, 128, 23, AlignLeft, AlignTop, app->buff, false); + snprintf( + app->buff, + BUFF_SIZE, + "\e#GPIO:\e# %s", + ((SingleWireSensor*)current_sensor->instance)->gpio->name); + widget_add_text_box_element( + app->widget, 0, 28, 128, 23, AlignLeft, AlignTop, app->buff, false); + } + + if(current_sensor->type->interface == &I2C) { + snprintf(app->buff, BUFF_SIZE, "\e#Type:\e# %s", current_sensor->type->typename); + widget_add_text_box_element( + app->widget, 0, 16, 128, 23, AlignLeft, AlignTop, app->buff, false); + snprintf( + app->buff, + BUFF_SIZE, + "\e#I2C addr:\e# 0x%02X", + ((I2CSensor*)current_sensor->instance)->currentI2CAdr); + widget_add_text_box_element( + app->widget, 0, 28, 128, 23, AlignLeft, AlignTop, app->buff, false); + } + + view_set_previous_callback(widget_get_view(app->widget), _delete_exit_callback); + view_dispatcher_switch_to_view(app->view_dispatcher, UnitempViewWidget); +} + +/* ========================== Помощь ========================== */ + +/** + * @brief Функция обработки нажатия кнопки "Назад" + * + * @param context Указатель на данные приложения + * @return ID вида в который нужно переключиться + */ +static uint32_t _help_exit_callback(void* context) { + UNUSED(context); + //Возвращаем ID вида, в который нужно вернуться + return UnitempViewGeneral; +} + +/** + * @brief Переключение в виджет помощи + */ +void unitemp_widget_help_switch(void) { + //Очистка виджета + widget_reset(app->widget); + + widget_add_icon_element(app->widget, 3, 7, &I_repo_qr_50x50); + widget_add_icon_element(app->widget, 71, 15, &I_DolphinCommon_56x48); + + widget_add_string_multiline_element( + app->widget, 55, 5, AlignLeft, AlignTop, FontSecondary, "You can find help\nthere"); + + widget_add_frame_element(app->widget, 0, 0, 128, 63, 7); + widget_add_frame_element(app->widget, 0, 0, 128, 64, 7); + + view_set_previous_callback(widget_get_view(app->widget), _help_exit_callback); + view_dispatcher_switch_to_view(app->view_dispatcher, UnitempViewWidget); +} + +/* ========================== О приложении ========================== */ + +/** + * @brief Переключение в виджет о приложении + */ +void unitemp_widget_about_switch(void) { + //Очистка виджета + widget_reset(app->widget); + + widget_add_frame_element(app->widget, 0, 0, 128, 63, 7); + widget_add_frame_element(app->widget, 0, 0, 128, 64, 7); + + snprintf(app->buff, BUFF_SIZE, "#Unitemp %s#", UNITEMP_APP_VER); + widget_add_text_box_element( + app->widget, 0, 4, 128, 12, AlignCenter, AlignCenter, app->buff, false); + + widget_add_text_scroll_element( + app->widget, + 4, + 16, + 121, + 44, + "Universal plugin for viewing the values of temperature\nsensors\n\e#Author: Quenon\ngithub.com/quen0n\n\e#Designer: Svaarich\ngithub.com/Svaarich\n\e#Issues & suggestions\ntiny.one/unitemp"); + + view_set_previous_callback(widget_get_view(app->widget), _help_exit_callback); + view_dispatcher_switch_to_view(app->view_dispatcher, UnitempViewWidget); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/usb_hid_autofire.c b/Applications/Official/DEV_FW/source/usb_hid_autofire.c new file mode 100644 index 000000000..7540b0dae --- /dev/null +++ b/Applications/Official/DEV_FW/source/usb_hid_autofire.c @@ -0,0 +1,176 @@ +#include +#include +#include +#include +#include +#include "version.h" + +// Uncomment to be able to make a screenshot +//#define USB_HID_AUTOFIRE_SCREENSHOT + +typedef enum { + EventTypeInput, +} EventType; + +typedef struct { + union { + InputEvent input; + }; + EventType type; +} UsbMouseEvent; + +bool btn_left_autofire = false; +uint32_t autofire_delay = 10; +#define SWAP(a, b) \ + do { \ + typeof(a) temp = a; \ + a = b; \ + b = temp; \ + } while(0) + +void reverse(char str[], int length) { + int start = 0; + int end = length - 1; + while(start < end) { + SWAP(*(str + start), *(str + end)); + start++; + end--; + } +} +// Implementation of itoa() +char* itoa(int num, char* str, int base) { + int i = 0; + bool isNegative = false; + + /* Handle 0 explicitly, otherwise empty string is printed for 0 */ + if(num == 0) { + str[i++] = '0'; + str[i] = '\0'; + return str; + } + + // In standard itoa(), negative numbers are handled only with + // base 10. Otherwise numbers are considered unsigned. + if(num < 0 && base == 10) { + isNegative = true; + num = -num; + } + + // Process individual digits + while(num != 0) { + int rem = num % base; + str[i++] = (rem > 9) ? (rem - 10) + 'a' : rem + '0'; + num = num / base; + } + + // If number is negative, append '-' + if(isNegative) str[i++] = '-'; + + str[i] = '\0'; // Append string terminator + + // Reverse the string + reverse(str, i); + + return str; +} + +static void usb_hid_autofire_render_callback(Canvas* canvas, void* ctx) { + UNUSED(ctx); + char autofire_delay_str[12]; + itoa(autofire_delay, autofire_delay_str, 10); + + canvas_clear(canvas); + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 0, 10, "USB HID Autofire"); + canvas_draw_str(canvas, 0, 34, btn_left_autofire ? "" : ""); + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 90, 10, "v"); + canvas_draw_str(canvas, 96, 10, VERSION); + canvas_draw_str(canvas, 0, 22, "Press [ok] for auto left clicking"); + canvas_draw_str(canvas, 0, 46, "delay [ms]:"); + canvas_draw_str(canvas, 50, 46, autofire_delay_str); // I can't even + canvas_draw_str(canvas, 0, 63, "Press [back] to exit"); +} + +static void usb_hid_autofire_input_callback(InputEvent* input_event, void* ctx) { + FuriMessageQueue* event_queue = ctx; + + UsbMouseEvent event; + event.type = EventTypeInput; + event.input = *input_event; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +int32_t usb_hid_autofire_app(void* p) { + UNUSED(p); + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(UsbMouseEvent)); + furi_check(event_queue); + ViewPort* view_port = view_port_alloc(); + + FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config(); +#ifndef USB_HID_AUTOFIRE_SCREENSHOT + furi_hal_usb_unlock(); + furi_check(furi_hal_usb_set_config(&usb_hid, NULL) == true); +#endif + + view_port_draw_callback_set(view_port, usb_hid_autofire_render_callback, NULL); + view_port_input_callback_set(view_port, usb_hid_autofire_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); + + UsbMouseEvent event; + while(1) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 50); + + if(event_status == FuriStatusOk) { + if(event.type == EventTypeInput) { + if(event.input.key == InputKeyBack) { + break; + } + + if(event.input.type != InputTypeRelease) { + continue; + } + + switch(event.input.key) { + case InputKeyOk: + btn_left_autofire = !btn_left_autofire; + break; + case InputKeyLeft: + if(autofire_delay > 0) { + autofire_delay -= 10; + } + break; + case InputKeyRight: + autofire_delay += 10; + break; + default: + break; + } + } + } + + if(btn_left_autofire) { + furi_hal_hid_mouse_press(HID_MOUSE_BTN_LEFT); + // TODO: Don't wait, but use the timer directly to just don't send the release event (see furi_hal_cortex_delay_us) + furi_delay_us(autofire_delay * 500); + furi_hal_hid_mouse_release(HID_MOUSE_BTN_LEFT); + furi_delay_us(autofire_delay * 500); + } + + view_port_update(view_port); + } + + furi_hal_usb_set_config(usb_mode_prev, NULL); + + // remove & free all stuff created by app + gui_remove_view_port(gui, view_port); + view_port_free(view_port); + furi_message_queue_free(event_queue); + + return 0; +} diff --git a/Applications/Official/DEV_FW/source/usb_midi/.gitattributes b/Applications/Official/DEV_FW/source/usb_midi/.gitattributes new file mode 100644 index 000000000..dfe077042 --- /dev/null +++ b/Applications/Official/DEV_FW/source/usb_midi/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/Applications/Official/DEV_FW/source/usb_midi/.gitignore b/Applications/Official/DEV_FW/source/usb_midi/.gitignore new file mode 100644 index 000000000..c6127b38c --- /dev/null +++ b/Applications/Official/DEV_FW/source/usb_midi/.gitignore @@ -0,0 +1,52 @@ +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf diff --git a/Applications/Official/DEV_FW/source/usb_midi/application.fam b/Applications/Official/DEV_FW/source/usb_midi/application.fam new file mode 100644 index 000000000..d44153fd7 --- /dev/null +++ b/Applications/Official/DEV_FW/source/usb_midi/application.fam @@ -0,0 +1,14 @@ +App( + appid="USB_Midi", + name="USB Midi", + apptype=FlipperAppType.EXTERNAL, + entry_point="usb_midi_app", + requires=[ + "gui", + ], + stack_size=4 * 1024, + order=20, + fap_icon="usb_midi.png", + fap_category="Music", + # fap_icon_assets="icons", +) diff --git a/Applications/Official/DEV_FW/source/usb_midi/midi/config.h b/Applications/Official/DEV_FW/source/usb_midi/midi/config.h new file mode 100644 index 000000000..c62c1b1ef --- /dev/null +++ b/Applications/Official/DEV_FW/source/usb_midi/midi/config.h @@ -0,0 +1,3 @@ +#include + +#define SYSEX_BUFFER_LEN 16 \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/usb_midi/midi/message.c b/Applications/Official/DEV_FW/source/usb_midi/midi/message.c new file mode 100644 index 000000000..7bee9816a --- /dev/null +++ b/Applications/Official/DEV_FW/source/usb_midi/midi/message.c @@ -0,0 +1,144 @@ +#include "message.h" + +/** Returns the data within the MidiEvent as a NoteOffEvent struct */ +NoteOffEvent AsNoteOff(MidiEvent* event) { + NoteOffEvent m; + m.channel = event->channel; + m.note = event->data[0]; + m.velocity = event->data[1]; + return m; +} + +/** Returns the data within the MidiEvent as a NoteOnEvent struct */ +NoteOnEvent AsNoteOn(MidiEvent* event) { + NoteOnEvent m; + m.channel = event->channel; + m.note = event->data[0]; + m.velocity = event->data[1]; + return m; +} + +/** Returns the data within the MidiEvent as a PolyphonicKeyPressureEvent struct */ +PolyphonicKeyPressureEvent AsPolyphonicKeyPressure(MidiEvent* event) { + PolyphonicKeyPressureEvent m; + m.channel = event->channel; + m.note = event->data[0]; + m.pressure = event->data[1]; + return m; +} + +/** Returns the data within the MidiEvent as a ControlChangeEvent struct.*/ +ControlChangeEvent AsControlChange(MidiEvent* event) { + ControlChangeEvent m; + m.channel = event->channel; + m.control_number = event->data[0]; + m.value = event->data[1]; + return m; +} + +/** Returns the data within the MidiEvent as a ProgramChangeEvent struct.*/ +ProgramChangeEvent AsProgramChange(MidiEvent* event) { + ProgramChangeEvent m; + m.channel = event->channel; + m.program = event->data[0]; + return m; +} + +/** Returns the data within the MidiEvent as a ProgramChangeEvent struct.*/ +ChannelPressureEvent AsChannelPressure(MidiEvent* event) { + ChannelPressureEvent m; + m.channel = event->channel; + m.pressure = event->data[0]; + return m; +} + +/** Returns the data within the MidiEvent as a PitchBendEvent struct.*/ +PitchBendEvent AsPitchBend(MidiEvent* event) { + PitchBendEvent m; + m.channel = event->channel; + m.value = ((uint16_t)(event->data[1]) << 7) + (event->data[0] - 8192); + return m; +} + +SystemExclusiveEvent AsSystemExclusive(MidiEvent* event) { + SystemExclusiveEvent m; + m.length = event->sysex_message_len; + for(int i = 0; i < SYSEX_BUFFER_LEN; i++) { + m.data[i] = 0; + if(i < m.length) { + m.data[i] = event->sysex_data[i]; + } + } + return m; +} + +MTCQuarterFrameEvent AsMTCQuarterFrame(MidiEvent* event) { + MTCQuarterFrameEvent m; + m.message_type = (event->data[0] & 0x70) >> 4; + m.value = (event->data[0]) & 0x0f; + return m; +} + +SongPositionPointerEvent AsSongPositionPointer(MidiEvent* event) { + SongPositionPointerEvent m; + m.position = ((uint16_t)(event->data[1]) << 7) | (event->data[0]); + return m; +} + +SongSelectEvent AsSongSelect(MidiEvent* event) { + SongSelectEvent m; + m.song = event->data[0]; + return m; +} + +AllSoundOffEvent AsAllSoundOff(MidiEvent* event) { + AllSoundOffEvent m; + m.channel = event->channel; + return m; +} + +ResetAllControllersEvent AsResetAllControllers(MidiEvent* event) { + ResetAllControllersEvent m; + m.channel = event->channel; + m.value = event->data[1]; + return m; +} + +LocalControlEvent AsLocalControl(MidiEvent* event) { + LocalControlEvent m; + m.channel = event->channel; + m.local_control_off = (event->data[1] == 0); + m.local_control_on = (event->data[1] == 127); + return m; +} + +AllNotesOffEvent AsAllNotesOff(MidiEvent* event) { + AllNotesOffEvent m; + m.channel = event->channel; + return m; +} + +OmniModeOffEvent AsOmniModeOff(MidiEvent* event) { + OmniModeOffEvent m; + m.channel = event->channel; + return m; +} + +OmniModeOnEvent AsOmniModeOn(MidiEvent* event) { + OmniModeOnEvent m; + m.channel = event->channel; + return m; +} + +MonoModeOnEvent AsMonoModeOn(MidiEvent* event) { + MonoModeOnEvent m; + m.channel = event->channel; + m.num_channels = event->data[1]; + return m; +} + +PolyModeOnEvent AsPolyModeOn(MidiEvent* event) { + PolyModeOnEvent m; + m.channel = event->channel; + return m; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/usb_midi/midi/message.h b/Applications/Official/DEV_FW/source/usb_midi/midi/message.h new file mode 100644 index 000000000..88402c4a4 --- /dev/null +++ b/Applications/Official/DEV_FW/source/usb_midi/midi/message.h @@ -0,0 +1,251 @@ +#pragma once +#include +#include +#include "config.h" + +typedef enum { + NoteOff, /**< & */ + NoteOn, /**< & */ + PolyphonicKeyPressure, /**< & */ + ControlChange, /**< & */ + ProgramChange, /**< & */ + ChannelPressure, /**< & */ + PitchBend, /**< & */ + SystemCommon, /**< & */ + SystemRealTime, /**< & */ + ChannelMode, /**< & */ + MessageLast, /**< & */ +} MidiMessageType; + +typedef enum { + SystemExclusive, /**< & */ + MTCQuarterFrame, /**< & */ + SongPositionPointer, /**< & */ + SongSelect, /**< & */ + SCUndefined0, /**< & */ + SCUndefined1, /**< & */ + TuneRequest, /**< & */ + SysExEnd, /**< & */ + SystemCommonLast, /**< & */ +} SystemCommonType; + +typedef enum { + TimingClock, /**< & */ + SRTUndefined0, /**< & */ + Start, /**< & */ + Continue, /**< & */ + Stop, /**< & */ + SRTUndefined1, /**< & */ + ActiveSensing, /**< & */ + Reset, /**< & */ + SystemRealTimeLast, /**< & */ +} SystemRealTimeType; + +typedef enum { + AllSoundOff, /**< & */ + ResetAllControllers, /**< & */ + LocalControl, /**< & */ + AllNotesOff, /**< & */ + OmniModeOff, /**< & */ + OmniModeOn, /**< & */ + MonoModeOn, /**< & */ + PolyModeOn, /**< & */ + ChannelModeLast, /**< & */ +} ChannelModeType; + +/** Struct containing note, and velocity data for a given channel. +Can be made from MidiEvent +*/ +typedef struct { + int channel; /**< & */ + uint8_t note; /**< & */ + uint8_t velocity; /**< & */ +} NoteOffEvent; + +/** Struct containing note, and velocity data for a given channel. +Can be made from MidiEvent +*/ +typedef struct { + int channel; /**< & */ + uint8_t note; /**< & */ + uint8_t velocity; /**< & */ +} NoteOnEvent; + +/** Struct containing note, and pressure data for a given channel. +Can be made from MidiEvent +*/ +typedef struct { + int channel; + uint8_t note; + uint8_t pressure; +} PolyphonicKeyPressureEvent; + +/** Struct containing control number, and value for a given channel. +Can be made from MidiEvent +*/ +typedef struct { + int channel; /**< & */ + uint8_t control_number; /**< & */ + uint8_t value; /**< & */ +} ControlChangeEvent; + +/** Struct containing new program number, for a given channel. +Can be made from MidiEvent +*/ +typedef struct { + int channel; /**< & */ + uint8_t program; /**< & */ +} ProgramChangeEvent; + +/** Struct containing pressure (aftertouch), for a given channel. +Can be made from MidiEvent +*/ +typedef struct { + int channel; /**< & */ + uint8_t pressure; /**< & */ +} ChannelPressureEvent; + +/** Struct containing pitch bend value for a given channel. +Can be made from MidiEvent +*/ +typedef struct { + int channel; /**< & */ + int16_t value; /**< & */ +} PitchBendEvent; + +/** Struct containing sysex data. +Can be made from MidiEvent +*/ +typedef struct { + int length; + uint8_t data[SYSEX_BUFFER_LEN]; /**< & */ +} SystemExclusiveEvent; + +/** Struct containing QuarterFrame data. +Can be made from MidiEvent +*/ +typedef struct { + uint8_t message_type; /**< & */ + uint8_t value; /**< & */ +} MTCQuarterFrameEvent; + +/** Struct containing song position data. +Can be made from MidiEvent +*/ +typedef struct { + uint16_t position; /**< & */ +} SongPositionPointerEvent; + +/** Struct containing song select data. +Can be made from MidiEvent +*/ +typedef struct { + uint8_t song; /**< & */ +} SongSelectEvent; + +/** Struct containing sound off data. +Can be made from MidiEvent +*/ +typedef struct { + int channel; /**< & */ +} AllSoundOffEvent; + +/** Struct containing ResetAllControllersEvent data. +Can be made from MidiEvent +*/ +typedef struct { + int channel; /**< & */ + uint8_t value; /**< & */ +} ResetAllControllersEvent; + +/** Struct containing LocalControlEvent data. +Can be made from MidiEvent +*/ +typedef struct { + int channel; /**< & */ + bool local_control_off; /**< & */ + bool local_control_on; /**< & */ +} LocalControlEvent; + +/** Struct containing AllNotesOffEvent data. +Can be made from MidiEvent +*/ +typedef struct { + int channel; /**< & */ +} AllNotesOffEvent; + +/** Struct containing OmniModeOffEvent data. + * Can be made from MidiEvent +*/ +typedef struct { + int channel; /**< & */ +} OmniModeOffEvent; + +/** Struct containing OmniModeOnEvent data. +Can be made from MidiEvent +*/ +typedef struct { + int channel; /**< & */ +} OmniModeOnEvent; + +/** Struct containing MonoModeOnEvent data. +Can be made from MidiEvent +*/ +typedef struct { + int channel; /**< & */ + uint8_t num_channels; /**< & */ +} MonoModeOnEvent; + +/** Struct containing PolyModeOnEvent data. +Can be made from MidiEvent +*/ +typedef struct { + int channel; /**< & */ +} PolyModeOnEvent; + +/** Simple MidiEvent with message type, channel, and data[2] members. +*/ +typedef struct { + MidiMessageType type; + int channel; + uint8_t data[2]; + uint8_t sysex_data[SYSEX_BUFFER_LEN]; + uint8_t sysex_message_len; + SystemCommonType sc_type; + SystemRealTimeType srt_type; + ChannelModeType cm_type; +} MidiEvent; + +/** Returns the data within the MidiEvent as a NoteOffEvent struct */ +NoteOffEvent AsNoteOff(MidiEvent* event); + +/** Returns the data within the MidiEvent as a NoteOnEvent struct */ +NoteOnEvent AsNoteOn(MidiEvent* event); + +/** Returns the data within the MidiEvent as a PolyphonicKeyPressureEvent struct */ +PolyphonicKeyPressureEvent AsPolyphonicKeyPressure(MidiEvent* event); + +/** Returns the data within the MidiEvent as a ControlChangeEvent struct.*/ +ControlChangeEvent AsControlChange(MidiEvent* event); + +/** Returns the data within the MidiEvent as a ProgramChangeEvent struct.*/ +ProgramChangeEvent AsProgramChange(MidiEvent* event); + +/** Returns the data within the MidiEvent as a ProgramChangeEvent struct.*/ +ChannelPressureEvent AsChannelPressure(MidiEvent* event); + +/** Returns the data within the MidiEvent as a PitchBendEvent struct.*/ +PitchBendEvent AsPitchBend(MidiEvent* event); + +SystemExclusiveEvent AsSystemExclusive(MidiEvent* event); +MTCQuarterFrameEvent AsMTCQuarterFrame(MidiEvent* event); +SongPositionPointerEvent AsSongPositionPointer(MidiEvent* event); +SongSelectEvent AsSongSelect(MidiEvent* event); +AllSoundOffEvent AsAllSoundOff(MidiEvent* event); +ResetAllControllersEvent AsResetAllControllers(MidiEvent* event); +LocalControlEvent AsLocalControl(MidiEvent* event); +AllNotesOffEvent AsAllNotesOff(MidiEvent* event); +OmniModeOffEvent AsOmniModeOff(MidiEvent* event); +OmniModeOnEvent AsOmniModeOn(MidiEvent* event); +MonoModeOnEvent AsMonoModeOn(MidiEvent* event); +PolyModeOnEvent AsPolyModeOn(MidiEvent* event); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/usb_midi/midi/parser.c b/Applications/Official/DEV_FW/source/usb_midi/midi/parser.c new file mode 100644 index 000000000..86120e007 --- /dev/null +++ b/Applications/Official/DEV_FW/source/usb_midi/midi/parser.c @@ -0,0 +1,149 @@ +#include +#include "parser.h" + +typedef enum { + ParserEmpty, + ParserHasStatus, + ParserHasData0, + ParserSysEx, +} ParserState; + +const uint8_t kStatusByteMask = 0x80; +const uint8_t kMessageMask = 0x70; +const uint8_t kDataByteMask = 0x7F; +const uint8_t kSystemCommonMask = 0xF0; +const uint8_t kChannelMask = 0x0F; +const uint8_t kRealTimeMask = 0xF8; +const uint8_t kSystemRealTimeMask = 0x07; + +struct MidiParser { + MidiMessageType status; + ParserState state; + MidiEvent incoming_message; +}; + +MidiParser* midi_parser_alloc(void) { + MidiParser* parser = malloc(sizeof(MidiParser)); + parser->incoming_message.type = MessageLast; + parser->state = ParserEmpty; + return parser; +} + +void midi_parser_free(MidiParser* parser) { + free(parser); +} + +bool midi_parser_parse(MidiParser* parser, uint8_t byte) { + bool parsed = false; + MidiEvent* event = &parser->incoming_message; + + switch(parser->state) { + case ParserEmpty: + // check byte for valid Status Byte + if(byte & kStatusByteMask) { + // Get MessageType, and Channel + event->channel = byte & kChannelMask; + event->type = (MidiMessageType)((byte & kMessageMask) >> 4); + + // Validate, and move on. + if(event->type < MessageLast) { + parser->state = ParserHasStatus; + // Mark this status byte as running_status + parser->status = event->type; + + if(parser->status == SystemCommon) { + event->channel = 0; + //system real time = 1111 1xxx + if(byte & 0x08) { + event->type = SystemRealTime; + parser->status = SystemRealTime; + event->srt_type = (SystemRealTimeType)(byte & kSystemRealTimeMask); + + //short circuit to start + parser->state = ParserEmpty; + //queue_.push(incoming_message_); + parsed = true; + } + //system common + else { + event->sc_type = (SystemCommonType)(byte & 0x07); + //sysex + if(event->sc_type == SystemExclusive) { + parser->state = ParserSysEx; + event->sysex_message_len = 0; + } + //short circuit + else if(event->sc_type > SongSelect) { + parser->state = ParserEmpty; + //queue_.push(incoming_message_); + parsed = true; + } + } + } + } + // Else we'll keep waiting for a valid incoming status byte + } else { + // Handle as running status + event->type = parser->status; + event->data[0] = byte & kDataByteMask; + parser->state = ParserHasData0; + } + break; + case ParserHasStatus: + if((byte & kStatusByteMask) == 0) { + event->data[0] = byte & kDataByteMask; + if(parser->status == ChannelPressure || parser->status == ProgramChange || + event->sc_type == MTCQuarterFrame || event->sc_type == SongSelect) { + //these are just one data byte, so we short circuit back to start + parser->state = ParserEmpty; + //queue_.push(incoming_message_); + parsed = true; + } else { + parser->state = ParserHasData0; + } + + //ChannelModeMessages (reserved Control Changes) + if(parser->status == ControlChange && event->data[0] > 119) { + event->type = ChannelMode; + parser->status = ChannelMode; + event->cm_type = (ChannelModeType)(event->data[0] - 120); + } + } else { + // invalid message go back to start ;p + parser->state = ParserEmpty; + } + break; + case ParserHasData0: + if((byte & kStatusByteMask) == 0) { + event->data[1] = byte & kDataByteMask; + // At this point the message is valid, and we can add this MidiEvent to the queue + //queue_.push(incoming_message_); + parsed = true; + } + // Regardless, of whether the data was valid or not we go back to empty + // because either the message is queued for handling or its not. + parser->state = ParserEmpty; + break; + case ParserSysEx: + // end of sysex + if(byte == 0xf7) { + parser->state = ParserEmpty; + //queue_.push(incoming_message_); + parsed = true; + } else { + if(event->sysex_message_len < SYSEX_BUFFER_LEN) { + event->sysex_data[event->sysex_message_len] = byte; + event->sysex_message_len++; + } + } + break; + default: + break; + } + + return parsed; +} + +MidiEvent* midi_parser_get_message(MidiParser* parser) { + return &parser->incoming_message; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/usb_midi/midi/parser.h b/Applications/Official/DEV_FW/source/usb_midi/midi/parser.h new file mode 100644 index 000000000..93630f026 --- /dev/null +++ b/Applications/Official/DEV_FW/source/usb_midi/midi/parser.h @@ -0,0 +1,14 @@ +#pragma once +#include +#include +#include "message.h" + +typedef struct MidiParser MidiParser; + +MidiParser* midi_parser_alloc(void); + +void midi_parser_free(MidiParser* parser); + +bool midi_parser_parse(MidiParser* parser, uint8_t data); + +MidiEvent* midi_parser_get_message(MidiParser* parser); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/usb_midi/midi/usb_message.c b/Applications/Official/DEV_FW/source/usb_midi/midi/usb_message.c new file mode 100644 index 000000000..b6844c5f4 --- /dev/null +++ b/Applications/Official/DEV_FW/source/usb_midi/midi/usb_message.c @@ -0,0 +1,40 @@ +#include "usb_message.h" + +CodeIndex code_index_from_data(uint8_t data) { + return (CodeIndex)(data & 0b00001111); +} + +uint8_t cable_id_from_data(uint8_t data) { + return (data >> 4) & 0b00001111; +} + +uint8_t usb_message_data_size(CodeIndex code_index) { + uint8_t data_size = 0; + switch(code_index) { + case CodeIndexCommon1Byte: + /* case CodeIndexSysExEnd1Byte: */ + case CodeIndexSingleByte: + data_size = 1; + break; + case CodeIndexSysEx2Byte: + case CodeIndexSysExEnd2Byte: + case CodeIndexProgramChange: + case CodeIndexChannelPressure: + data_size = 2; + break; + case CodeIndexSysEx3Byte: + case CodeIndexSysExStart: + case CodeIndexSysExEnd3Byte: + case CodeIndexNoteOff: + case CodeIndexNoteOn: + case CodeIndexPolyKeyPress: + case CodeIndexControlChange: + case CodeIndexPitchBendChange: + data_size = 3; + break; + default: + break; + } + + return data_size; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/usb_midi/midi/usb_message.h b/Applications/Official/DEV_FW/source/usb_midi/midi/usb_message.h new file mode 100644 index 000000000..852e9cb4f --- /dev/null +++ b/Applications/Official/DEV_FW/source/usb_midi/midi/usb_message.h @@ -0,0 +1,28 @@ +#pragma once +#include + +typedef enum { + CodeIndexMisc = 0x0, /**< Reserved, MIDI Size: 1, 2, 3 */ + CodeIndexCableEvent = 0x1, /**< Reserved, MIDI Size: 1, 2, 3 */ + CodeIndexSysEx2Byte = 0x2, /**< MIDI Size: 2 */ + CodeIndexSysEx3Byte = 0x3, /**< MIDI Size: 3 */ + CodeIndexSysExStart = 0x4, /**< MIDI Size: 3 */ + CodeIndexCommon1Byte = 0x5, /**< MIDI Size: 1 */ + CodeIndexSysExEnd1Byte = 0x5, /**< MIDI Size: 1 */ + CodeIndexSysExEnd2Byte = 0x6, /**< MIDI Size: 2 */ + CodeIndexSysExEnd3Byte = 0x7, /**< MIDI Size: 3 */ + CodeIndexNoteOff = 0x8, /**< MIDI Size: 3 */ + CodeIndexNoteOn = 0x9, /**< MIDI Size: 3 */ + CodeIndexPolyKeyPress = 0xA, /**< MIDI Size: 3 */ + CodeIndexControlChange = 0xB, /**< MIDI Size: 3 */ + CodeIndexProgramChange = 0xC, /**< MIDI Size: 2 */ + CodeIndexChannelPressure = 0xD, /**< MIDI Size: 2 */ + CodeIndexPitchBendChange = 0xE, /**< MIDI Size: 3 */ + CodeIndexSingleByte = 0xF, /**< MIDI Size: 1 */ +} CodeIndex; + +CodeIndex code_index_from_data(uint8_t data); + +uint8_t cable_id_from_data(uint8_t data); + +uint8_t usb_message_data_size(CodeIndex code_index); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/usb_midi/usb/cm3_usb_audio.h b/Applications/Official/DEV_FW/source/usb_midi/usb/cm3_usb_audio.h new file mode 100644 index 000000000..3c767f929 --- /dev/null +++ b/Applications/Official/DEV_FW/source/usb_midi/usb/cm3_usb_audio.h @@ -0,0 +1,234 @@ +/** @defgroup usb_audio_defines USB Audio Type Definitions + +@brief Defined Constants and Types for the USB Audio Type Definitions + +@ingroup USB_defines + +@version 1.0.0 + +@author @htmlonly © @endhtmlonly 2014 +Daniel Thompson +Seb Holzapfel + +@date 19 April 2014 + +LGPL License Terms @ref lgpl_license +*/ + +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2014 Daniel Thompson + * Copyright (C) 2018 Seb Holzapfel + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +/**@{*/ + +#ifndef LIBOPENCM3_USB_AUDIO_H +#define LIBOPENCM3_USB_AUDIO_H + +#include + +/* + * Definitions from the USB_AUDIO_ or usb_audio_ namespace come from: + * "Universal Serial Bus Class Definitions for Audio Devices, Revision 1.0" + */ + +/* Table A-1: Audio Interface Class Code */ +#define USB_CLASS_AUDIO 0x01 + +/* Table A-2: Audio Interface Subclass Codes */ +#define USB_AUDIO_SUBCLASS_UNDEFINED 0x00 +#define USB_AUDIO_SUBCLASS_CONTROL 0x01 +#define USB_AUDIO_SUBCLASS_AUDIOSTREAMING 0x02 +#define USB_AUDIO_SUBCLASS_MIDISTREAMING 0x03 + +/* Table A-4: Audio Class-specific Descriptor Types */ +#define USB_AUDIO_DT_CS_UNDEFINED 0x20 +#define USB_AUDIO_DT_CS_DEVICE 0x21 +#define USB_AUDIO_DT_CS_CONFIGURATION 0x22 +#define USB_AUDIO_DT_CS_STRING 0x23 +#define USB_AUDIO_DT_CS_INTERFACE 0x24 +#define USB_AUDIO_DT_CS_ENDPOINT 0x25 + +/* Table A-5: Audio Class-Specific AC Interface Descriptor Subtypes */ +#define USB_AUDIO_TYPE_AC_DESCRIPTOR_UNDEFINED 0x00 +#define USB_AUDIO_TYPE_HEADER 0x01 +#define USB_AUDIO_TYPE_INPUT_TERMINAL 0x02 +#define USB_AUDIO_TYPE_OUTPUT_TERMINAL 0x03 +#define USB_AUDIO_TYPE_MIXER_UNIT 0x04 +#define USB_AUDIO_TYPE_SELECTOR_UNIT 0x05 +#define USB_AUDIO_TYPE_FEATURE_UNIT 0x06 +#define USB_AUDIO_TYPE_PROCESSING_UNIT 0x07 +#define USB_AUDIO_TYPE_EXTENSION_UNIT 0x08 + +/* Table 4-2: Class-Specific AC Interface Header Descriptor (head) */ +struct usb_audio_header_descriptor_head { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint16_t bcdADC; + uint16_t wTotalLength; + uint8_t bInCollection; + /* ... */ +} __attribute__((packed)); + +/* Table 4-2: Class-Specific AC Interface Header Descriptor (body) */ +struct usb_audio_header_descriptor_body { + /* ... */ + uint8_t baInterfaceNr; +} __attribute__((packed)); + +/* Table 4-3: Input Terminal Descriptor */ +struct usb_audio_input_terminal_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bTerminalID; + uint16_t wTerminalType; + uint8_t bAssocTerminal; + uint8_t bNrChannels; + uint16_t wChannelConfig; + uint8_t iChannelNames; + uint8_t iTerminal; +} __attribute__((packed)); + +/* Table 4-3: Output Terminal Descriptor */ +struct usb_audio_output_terminal_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bTerminalID; + uint16_t wTerminalType; + uint8_t bAssocTerminal; + uint8_t bSourceID; + uint8_t iTerminal; +} __attribute__((packed)); + +/* Table 4-7: Feature Unit Descriptor (head) */ +struct usb_audio_feature_unit_descriptor_head { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bUnitID; + uint8_t bSourceID; + uint8_t bControlSize; + uint16_t bmaControlMaster; /* device can assume 16-bit, given highest + * defined bit in spec is bit #9. + * (it is thus required bControlSize=2) */ + /* ... */ +} __attribute__((packed)); + +/* Table 4-7: Feature Unit Descriptor (body) */ +struct usb_audio_feature_unit_descriptor_body { + /* ... */ + uint16_t bmaControl; + /* ... */ +} __attribute__((packed)); + +/* Table 4-7: Feature Unit Descriptor (tail) */ +struct usb_audio_feature_unit_descriptor_tail { + /* ... */ + uint8_t iFeature; +} __attribute__((packed)); + +/* Table 4-7: Feature Unit Descriptor (2-channel) + * + * This structure is a convenience covering the (common) case where + * there are 2 channels associated with the feature unit + */ +struct usb_audio_feature_unit_descriptor_2ch { + struct usb_audio_feature_unit_descriptor_head head; + struct usb_audio_feature_unit_descriptor_body channel_control[2]; + struct usb_audio_feature_unit_descriptor_tail tail; +} __attribute__((packed)); + +/* Table 4-19: Class-Specific AS Interface Descriptor */ +struct usb_audio_stream_interface_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bTerminalLink; + uint8_t bDelay; + uint16_t wFormatTag; +} __attribute__((packed)); + +/* Table 4-20: Standard AS Isochronous Audio Data Endpoint Descriptor */ +struct usb_audio_stream_endpoint_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bEndpointAddress; + uint8_t bmAttributes; + uint16_t wMaxPacketSize; + uint8_t bInterval; + uint8_t bRefresh; + uint8_t bSynchAddress; +} __attribute__((packed)); + +/* Table 4-21: Class-Specific AS Isochronous Audio Data Endpoint Descriptor */ +struct usb_audio_stream_audio_endpoint_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bmAttributes; + uint8_t bLockDelayUnits; + uint16_t wLockDelay; +} __attribute__((packed)); + +/* + * Definitions from the USB_AUDIO_FORMAT_ or usb_audio_format_ namespace come from: + * "Universal Serial Bus Device Class Definition for Audio Data Formats, Revision 1.0" + */ + +/* Table 2-1: Type I Format Type Descriptor (head) */ +struct usb_audio_format_type1_descriptor_head { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bFormatType; + uint8_t bNrChannels; + uint8_t bSubFrameSize; + uint8_t bBitResolution; + uint8_t bSamFreqType; + /* ... */ +} __attribute__((packed)); + +/* Table 2-2: Continuous Sampling Frequency */ +struct usb_audio_format_continuous_sampling_frequency { + /* ... */ + uint32_t tLowerSamFreq : 24; + uint32_t tUpperSamFreq : 24; +} __attribute__((packed)); + +/* Table 2-3: Discrete Number of Sampling Frequencies */ +struct usb_audio_format_discrete_sampling_frequency { + /* ... */ + uint32_t tSamFreq : 24; +} __attribute__((packed)); + +/* Table 2-1: Type I Format Type Descriptor (1 sampling frequency) + * + * This structure is a convenience covering the (common) case where + * only 1 discrete sampling frequency is used + */ +struct usb_audio_format_type1_descriptor_1freq { + struct usb_audio_format_type1_descriptor_head head; + struct usb_audio_format_discrete_sampling_frequency freqs[1]; +} __attribute__((packed)); + +#endif + +/**@}*/ diff --git a/Applications/Official/DEV_FW/source/usb_midi/usb/cm3_usb_midi.h b/Applications/Official/DEV_FW/source/usb_midi/usb/cm3_usb_midi.h new file mode 100644 index 000000000..8435c883e --- /dev/null +++ b/Applications/Official/DEV_FW/source/usb_midi/usb/cm3_usb_midi.h @@ -0,0 +1,190 @@ +/** @defgroup usb_audio_defines USB MIDI Type Definitions + +@brief Defined Constants and Types for the USB MIDI Type Definitions + +@ingroup USB_defines + +@version 1.0.0 + +@author @htmlonly © @endhtmlonly 2014 +Daniel Thompson + +@date 19 April 2014 + +LGPL License Terms @ref lgpl_license +*/ + +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2014 Daniel Thompson + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +/**@{*/ + +#ifndef LIBOPENCM3_USB_MIDI_H +#define LIBOPENCM3_USB_MIDI_H + +#include + +/* + * Definitions from the USB_MIDI_ or usb_midi_ namespace come from: + * "Universal Serial Bus Class Definitions for MIDI Devices, Revision 1.0" + */ + +/* Appendix A.1: MS Class-Specific Interface Descriptor Subtypes */ +#define USB_MIDI_SUBTYPE_MS_DESCRIPTOR_UNDEFINED 0x00 +#define USB_MIDI_SUBTYPE_MS_HEADER 0x01 +#define USB_MIDI_SUBTYPE_MIDI_IN_JACK 0x02 +#define USB_MIDI_SUBTYPE_MIDI_OUT_JACK 0x03 +#define USB_MIDI_SUBTYPE_MIDI_ELEMENT 0x04 + +/* Appendix A.2: MS Class-Specific Endpoint Descriptor Subtypes */ +#define USB_MIDI_SUBTYPE_DESCRIPTOR_UNDEFINED 0x00 +#define USB_MIDI_SUBTYPE_MS_GENERAL 0x01 + +/* Appendix A.3: MS MIDI IN and OUT Jack types */ +#define USB_MIDI_JACK_TYPE_UNDEFINED 0x00 +#define USB_MIDI_JACK_TYPE_EMBEDDED 0x01 +#define USB_MIDI_JACK_TYPE_EXTERNAL 0x02 + +/* Appendix A.5.1 Endpoint Control Selectors */ +#define USB_MIDI_EP_CONTROL_UNDEFINED 0x00 +#define USB_MIDI_ASSOCIATION_CONTROL 0x01 + +/* Table 6-2: Class-Specific MS Interface Header Descriptor */ +struct usb_midi_header_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint16_t bcdMSC; + uint16_t wTotalLength; +} __attribute__((packed)); + +/* Table 6-3: MIDI IN Jack Descriptor */ +struct usb_midi_in_jack_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bJackType; + uint8_t bJackID; + uint8_t iJack; +} __attribute__((packed)); + +/* Table 6-4: MIDI OUT Jack Descriptor (head) */ +struct usb_midi_out_jack_descriptor_head { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bJackType; + uint8_t bJackID; + uint8_t bNrInputPins; + /* ... */ +} __attribute__((packed)); + +/* Table 6.4: MIDI OUT Jack Descriptor (body) */ +struct usb_midi_out_jack_descriptor_body { + /* ... */ + uint8_t baSourceID; + uint8_t baSourcePin; + /* ... */ +} __attribute__((packed)); + +/* Table 6.4: MIDI OUT Jack Descriptor (tail) */ +struct usb_midi_out_jack_descriptor_tail { + /* ... */ + uint8_t iJack; +} __attribute__((packed)); + +/* Table 6.4: MIDI OUT Jack Descriptor (single) + * + * This structure is a convenience covering the (normal) case where + * there is only one input pin. + */ +struct usb_midi_out_jack_descriptor { + struct usb_midi_out_jack_descriptor_head head; + struct usb_midi_out_jack_descriptor_body source[1]; + struct usb_midi_out_jack_descriptor_tail tail; +} __attribute__((packed)); + +/* Table 6-5: MIDI Element Descriptor (head) */ +struct usb_midi_element_descriptor_head { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bElementID; + uint8_t bNrInputPins; + /* ... */ +} __attribute__((packed)); + +/* Table 6-5: MIDI Element Descriptor (body) */ +struct usb_midi_element_descriptor_body { + /* ... */ + uint8_t baSourceID; + uint8_t baSourcePin; + /* ... */ +} __attribute__((packed)); + +/* Table 6-5: MIDI Element Descriptor (tail) */ +struct usb_midi_element_descriptor_tail { + /* ... */ + uint8_t bNrOutputPins; + uint8_t bInTerminalLink; + uint8_t bOutTerminalLink; + uint8_t bElCapsSize; + uint16_t bmElementCaps; /* host cannot assume this is 16-bit but device + can (since highest defined bitmap value in + v1.0 is bit 11) */ + uint8_t iElement; +} __attribute__((packed)); + +/* Table 6-5: MIDI Element Descriptor (single) + * + * This structure is a convenience covering the (common) case where + * there is only one input pin. + */ +struct usb_midi_element_descriptor { + struct usb_midi_element_descriptor_head head; + struct usb_midi_element_descriptor_body source[1]; + struct usb_midi_element_descriptor_tail tail; +} __attribute__((packed)); + +/* Table 6-7: Class-specific MS Bulk Data Endpoint Descriptor (head) */ +struct usb_midi_endpoint_descriptor_head { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bNumEmbMIDIJack; +} __attribute__((packed)); + +/* Table 6-7: Class-specific MS Bulk Data Endpoint Descriptor (body) */ +struct usb_midi_endpoint_descriptor_body { + uint8_t baAssocJackID; +} __attribute__((packed)); + +/* Table 6.7: Class-specific MS Bulk Data Endpoint Descriptor (single) + * + * This structure is a convenience covering the (normal) case where + * there is only one input pin. + */ +struct usb_midi_endpoint_descriptor { + struct usb_midi_endpoint_descriptor_head head; + struct usb_midi_endpoint_descriptor_body jack[1]; +} __attribute__((packed)); + +#endif + +/**@}*/ diff --git a/Applications/Official/DEV_FW/source/usb_midi/usb/usb_midi_driver.c b/Applications/Official/DEV_FW/source/usb_midi/usb/usb_midi_driver.c new file mode 100644 index 000000000..9abf77d12 --- /dev/null +++ b/Applications/Official/DEV_FW/source/usb_midi/usb/usb_midi_driver.c @@ -0,0 +1,428 @@ +#include +#include +#include +#include + +#include "usb_midi_driver.h" +#include "cm3_usb_audio.h" +#include "cm3_usb_midi.h" + +// Appendix B. "Example: Simple MIDI Adapter" from "Universal Serial Bus Device Class Definition for MIDI Devices", Revision 1.0 + +#define USB_VID 0x6666 +#define USB_PID 0x5119 + +#define USB_EP0_SIZE 8 + +#define USB_MIDI_EP_SIZE 64 +#define USB_MIDI_EP_IN 0x81 +#define USB_MIDI_EP_OUT 0x01 + +#define EP_CFG_DECONFIGURE 0 +#define EP_CFG_CONFIGURE 1 + +enum { + USB_STR_ZERO, + USB_STR_MANUFACTURER, + USB_STR_PRODUCT, + USB_STR_SERIAL_NUMBER, +}; + +/* + B.1 Device Descriptor +*/ +static const struct usb_device_descriptor device_descriptor = { + .bLength = sizeof(struct usb_device_descriptor), + .bDescriptorType = USB_DTYPE_DEVICE, + .bcdUSB = VERSION_BCD(2, 0, 0), // was 0x0110, 1.10 - current revision of USBspecification. + .bDeviceClass = USB_CLASS_PER_INTERFACE, + .bDeviceSubClass = USB_SUBCLASS_NONE, + .bDeviceProtocol = USB_PROTO_NONE, + .bMaxPacketSize0 = USB_EP0_SIZE, + .idVendor = USB_VID, + .idProduct = USB_PID, + .bcdDevice = VERSION_BCD(1, 0, 0), + .iManufacturer = USB_STR_MANUFACTURER, + .iProduct = USB_STR_PRODUCT, + .iSerialNumber = USB_STR_SERIAL_NUMBER, + .bNumConfigurations = 1, +}; + +struct usb_audio_header_descriptor { + struct usb_audio_header_descriptor_head head; + struct usb_audio_header_descriptor_body body; +} __attribute__((packed)); + +struct usb_midi_jacks_descriptor { + struct usb_midi_header_descriptor header; + struct usb_midi_in_jack_descriptor in_embedded; + struct usb_midi_in_jack_descriptor in_external; + struct usb_midi_out_jack_descriptor out_embedded; + struct usb_midi_out_jack_descriptor out_external; +} __attribute__((packed)); + +struct MidiConfigDescriptor { + /* + B.2 Configuration Descriptor + */ + struct usb_config_descriptor config; + + /* + B.3 AudioControl Interface Descriptors + + The AudioControl interface describes the device structure (audio function topology) + and is used to manipulate the Audio Controls. This device has no audio function incorporated. + However, the AudioControl interface is mandatory and therefore both the standard AC interface + descriptor and the classspecific AC interface descriptor must be present. + The class-specific AC interface descriptor only contains the header descriptor. + */ + // B.3.1 Standard AC Interface Descriptor + struct usb_interface_descriptor audio_control_iface; + // B.3.2 Class-specific AC Interface Descriptor + struct usb_audio_header_descriptor audio_control_header; + + /* + B.4 MIDIStreaming Interface Descriptors + */ + // B.4.1 Standard MS Interface Descriptor + struct usb_interface_descriptor midi_streaming_iface; + // B.4.2 Class-specific MS Interface Descriptor + // B.4.3 MIDI IN Jack Descriptor + // B.4.4 MIDI OUT Jack Descriptor + struct usb_midi_jacks_descriptor midi_jacks; + + /* + B.5 Bulk OUT Endpoint Descriptors + */ + // B.5.1 Standard Bulk OUT Endpoint Descriptor + struct usb_endpoint_descriptor bulk_out; + // B.5.2 Class-specific MS Bulk OUT Endpoint Descriptor + struct usb_midi_endpoint_descriptor midi_bulk_out; + + /* + B.6 Bulk IN Endpoint Descriptors + */ + // B.6.1 Standard Bulk IN Endpoint Descriptor + struct usb_endpoint_descriptor bulk_in; + // B.6.2 Class-specific MS Bulk IN Endpoint Descriptor + struct usb_midi_endpoint_descriptor midi_bulk_in; +} __attribute__((packed)); + +static const struct MidiConfigDescriptor config_descriptor = { + .config = + { + .bLength = sizeof(struct usb_config_descriptor), + .bDescriptorType = USB_DTYPE_CONFIGURATION, + .wTotalLength = sizeof(struct MidiConfigDescriptor), + .bNumInterfaces = 2, /* control and data */ + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = USB_CFG_ATTR_RESERVED, + .bMaxPower = USB_CFG_POWER_MA(100), + }, + .audio_control_iface = + { + .bLength = sizeof(struct usb_interface_descriptor), + .bDescriptorType = USB_DTYPE_INTERFACE, + .bInterfaceNumber = 0, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_AUDIO_SUBCLASS_CONTROL, + .bInterfaceProtocol = USB_PROTO_NONE, + .iInterface = 0, + }, + .audio_control_header = + { + .head = + { + .bLength = sizeof(struct usb_audio_header_descriptor), + .bDescriptorType = USB_AUDIO_DT_CS_INTERFACE, + .bDescriptorSubtype = USB_AUDIO_TYPE_HEADER, + .bcdADC = VERSION_BCD(1, 0, 0), + .wTotalLength = sizeof(struct usb_audio_header_descriptor), + .bInCollection = 1, + }, + .body = + { + .baInterfaceNr = 1, + }, + }, + .midi_streaming_iface = + { + .bLength = sizeof(struct usb_interface_descriptor), + .bDescriptorType = USB_DTYPE_INTERFACE, + .bInterfaceNumber = 1, + .bAlternateSetting = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_AUDIO_SUBCLASS_MIDISTREAMING, + .bInterfaceProtocol = USB_PROTO_NONE, + .iInterface = 0, + }, + .midi_jacks = + { + .header = + { + .bLength = sizeof(struct usb_midi_header_descriptor), + .bDescriptorType = USB_AUDIO_DT_CS_INTERFACE, + .bDescriptorSubtype = USB_MIDI_SUBTYPE_MS_HEADER, + .bcdMSC = VERSION_BCD(1, 0, 0), + .wTotalLength = sizeof(struct usb_midi_jacks_descriptor), + }, + .in_embedded = + { + .bLength = sizeof(struct usb_midi_in_jack_descriptor), + .bDescriptorType = USB_AUDIO_DT_CS_INTERFACE, + .bDescriptorSubtype = USB_MIDI_SUBTYPE_MIDI_IN_JACK, + .bJackType = USB_MIDI_JACK_TYPE_EMBEDDED, + .bJackID = 0x01, + .iJack = 0x00, + }, + .in_external = + { + .bLength = sizeof(struct usb_midi_in_jack_descriptor), + .bDescriptorType = USB_AUDIO_DT_CS_INTERFACE, + .bDescriptorSubtype = USB_MIDI_SUBTYPE_MIDI_IN_JACK, + .bJackType = USB_MIDI_JACK_TYPE_EXTERNAL, + .bJackID = 0x02, + .iJack = 0x00, + }, + .out_embedded = + { + .head = + { + .bLength = sizeof(struct usb_midi_out_jack_descriptor), + .bDescriptorType = USB_AUDIO_DT_CS_INTERFACE, + .bDescriptorSubtype = USB_MIDI_SUBTYPE_MIDI_OUT_JACK, + .bJackType = USB_MIDI_JACK_TYPE_EMBEDDED, + .bJackID = 0x03, + .bNrInputPins = 1, + }, + .source[0] = + { + .baSourceID = 0x02, + .baSourcePin = 0x01, + }, + .tail = + { + .iJack = 0x00, + }, + }, + .out_external = + { + .head = + { + .bLength = sizeof(struct usb_midi_out_jack_descriptor), + .bDescriptorType = USB_AUDIO_DT_CS_INTERFACE, + .bDescriptorSubtype = USB_MIDI_SUBTYPE_MIDI_OUT_JACK, + .bJackType = USB_MIDI_JACK_TYPE_EXTERNAL, + .bJackID = 0x04, + .bNrInputPins = 1, + }, + .source[0] = + { + .baSourceID = 0x01, + .baSourcePin = 0x01, + }, + .tail = + { + .iJack = 0x00, + }, + }, + }, + .bulk_out = + { + .bLength = sizeof(struct usb_endpoint_descriptor), + .bDescriptorType = USB_DTYPE_ENDPOINT, + .bEndpointAddress = USB_MIDI_EP_OUT, + .bmAttributes = USB_EPTYPE_BULK, + .wMaxPacketSize = USB_MIDI_EP_SIZE, + .bInterval = 0, + }, + .midi_bulk_out = + { + .head = + { + .bLength = sizeof(struct usb_midi_endpoint_descriptor), + .bDescriptorType = USB_AUDIO_DT_CS_ENDPOINT, + .bDescriptorSubType = USB_MIDI_SUBTYPE_MS_GENERAL, + .bNumEmbMIDIJack = 1, + }, + .jack[0] = + { + .baAssocJackID = 0x01, + }, + }, + .bulk_in = + { + .bLength = sizeof(struct usb_endpoint_descriptor), + .bDescriptorType = USB_DTYPE_ENDPOINT, + .bEndpointAddress = USB_MIDI_EP_IN, + .bmAttributes = USB_EPTYPE_BULK, + .wMaxPacketSize = USB_MIDI_EP_SIZE, + .bInterval = 0, + }, + .midi_bulk_in = + { + .head = + { + .bLength = sizeof(struct usb_midi_endpoint_descriptor), + .bDescriptorType = USB_AUDIO_DT_CS_ENDPOINT, + .bDescriptorSubType = USB_MIDI_SUBTYPE_MS_GENERAL, + .bNumEmbMIDIJack = 1, + }, + .jack[0] = + { + .baAssocJackID = 0x03, + }, + }, +}; + +static const struct usb_string_descriptor dev_manufacturer_string = + USB_STRING_DESC("Flipper Devices Inc."); + +static const struct usb_string_descriptor dev_product_string = + USB_STRING_DESC("Flipper MIDI Device"); + +static const struct usb_string_descriptor dev_serial_number_string = + USB_STRING_DESC("Serial Number"); + +static void midi_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx); +static void midi_deinit(usbd_device* dev); +static void midi_on_wakeup(usbd_device* dev); +static void midi_on_suspend(usbd_device* dev); +static usbd_respond midi_ep_config(usbd_device* dev, uint8_t cfg); +static usbd_respond midi_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback); + +FuriHalUsbInterface midi_usb_interface = { + .init = midi_init, + .deinit = midi_deinit, + .wakeup = midi_on_wakeup, + .suspend = midi_on_suspend, + .dev_descr = (struct usb_device_descriptor*)&device_descriptor, + .cfg_descr = (void*)&config_descriptor, +}; + +typedef struct { + usbd_device* dev; + MidiRxCallback rx_callback; + void* context; + FuriSemaphore* semaphore_tx; + bool connected; +} MidiUsb; + +static MidiUsb midi_usb; + +void midi_usb_set_context(void* context) { + midi_usb.context = context; +} + +void midi_usb_set_rx_callback(MidiRxCallback callback) { + midi_usb.rx_callback = callback; +} + +size_t midi_usb_rx(uint8_t* buffer, size_t size) { + size_t len = usbd_ep_read(midi_usb.dev, USB_MIDI_EP_OUT, buffer, size); + return len; +} + +size_t midi_usb_tx(uint8_t* buffer, uint8_t size) { + if((midi_usb.semaphore_tx == NULL) || (midi_usb.connected == false)) return 0; + + furi_check(furi_semaphore_acquire(midi_usb.semaphore_tx, FuriWaitForever) == FuriStatusOk); + + if(midi_usb.connected) { + int32_t len = usbd_ep_write(midi_usb.dev, USB_MIDI_EP_IN, buffer, size); + return len; + } else { + return 0; + } +} + +static void midi_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx) { + UNUSED(intf); + UNUSED(ctx); + + midi_usb_interface.str_manuf_descr = (void*)&dev_manufacturer_string; + midi_usb_interface.str_prod_descr = (void*)&dev_product_string; + midi_usb_interface.str_serial_descr = (void*)&dev_serial_number_string; + midi_usb_interface.dev_descr->idVendor = USB_VID; + midi_usb_interface.dev_descr->idProduct = USB_PID; + + midi_usb.dev = dev; + if(midi_usb.semaphore_tx == NULL) midi_usb.semaphore_tx = furi_semaphore_alloc(1, 1); + + usbd_reg_config(dev, midi_ep_config); + usbd_reg_control(dev, midi_control); + + usbd_connect(dev, true); +} + +static void midi_deinit(usbd_device* dev) { + midi_usb.connected = false; + midi_usb.dev = NULL; + furi_semaphore_free(midi_usb.semaphore_tx); + + usbd_reg_config(dev, NULL); + usbd_reg_control(dev, NULL); +} + +static void midi_on_wakeup(usbd_device* dev) { + UNUSED(dev); + if(!midi_usb.connected) { + midi_usb.connected = true; + } +} + +static void midi_on_suspend(usbd_device* dev) { + UNUSED(dev); + if(midi_usb.connected) { + midi_usb.connected = false; + } +} + +static void midi_tx_rx(usbd_device* dev, uint8_t event, uint8_t ep) { + UNUSED(dev); + UNUSED(ep); + + switch(event) { + case usbd_evt_eptx: + furi_semaphore_release(midi_usb.semaphore_tx); + break; + case usbd_evt_eprx: + if(midi_usb.rx_callback != NULL) { + midi_usb.rx_callback(midi_usb.context); + } + break; + default: + break; + } +} + +static usbd_respond midi_ep_config(usbd_device* dev, uint8_t cfg) { + switch(cfg) { + case EP_CFG_DECONFIGURE: + usbd_ep_deconfig(dev, USB_MIDI_EP_OUT); + usbd_ep_deconfig(dev, USB_MIDI_EP_IN); + usbd_reg_endpoint(dev, USB_MIDI_EP_OUT, NULL); + usbd_reg_endpoint(dev, USB_MIDI_EP_IN, NULL); + return usbd_ack; + case EP_CFG_CONFIGURE: + usbd_ep_config(dev, USB_MIDI_EP_OUT, USB_EPTYPE_BULK, USB_MIDI_EP_SIZE); + usbd_ep_config(dev, USB_MIDI_EP_IN, USB_EPTYPE_BULK, USB_MIDI_EP_SIZE); + usbd_reg_endpoint(dev, USB_MIDI_EP_OUT, midi_tx_rx); + usbd_reg_endpoint(dev, USB_MIDI_EP_IN, midi_tx_rx); + return usbd_ack; + default: + return usbd_fail; + } +} + +static usbd_respond midi_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback) { + UNUSED(dev); + UNUSED(req); + UNUSED(callback); + + return usbd_fail; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/usb_midi/usb/usb_midi_driver.h b/Applications/Official/DEV_FW/source/usb_midi/usb/usb_midi_driver.h new file mode 100644 index 000000000..d385efcb5 --- /dev/null +++ b/Applications/Official/DEV_FW/source/usb_midi/usb/usb_midi_driver.h @@ -0,0 +1,14 @@ +#pragma once +#include + +extern FuriHalUsbInterface midi_usb_interface; + +typedef void (*MidiRxCallback)(void* context); + +void midi_usb_set_context(void* context); + +void midi_usb_set_rx_callback(MidiRxCallback callback); + +size_t midi_usb_rx(uint8_t* buffer, size_t size); + +size_t midi_usb_tx(uint8_t* buffer, uint8_t size); \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/usb_midi/usb_midi.c b/Applications/Official/DEV_FW/source/usb_midi/usb_midi.c new file mode 100644 index 000000000..d82fb74d7 --- /dev/null +++ b/Applications/Official/DEV_FW/source/usb_midi/usb_midi.c @@ -0,0 +1,84 @@ +#include +#include +#include "usb/usb_midi_driver.h" +#include "midi/parser.h" +#include "midi/usb_message.h" +#include + +float note_to_frequency(int note) { + float a = 440; + return (a / 32) * powf(2, ((note - 9) / 12.0)); +} + +typedef enum { + MidiThreadEventStop = (1 << 0), + MidiThreadEventRx = (1 << 1), + MidiThreadEventAll = MidiThreadEventStop | MidiThreadEventRx, +} MidiThreadEvent; + +static void midi_rx_callback(void* context) { + furi_assert(context); + FuriThreadId thread_id = (FuriThreadId)context; + furi_thread_flags_set(thread_id, MidiThreadEventRx); +} + +int32_t usb_midi_app(void* p) { + UNUSED(p); + + FuriHalUsbInterface* usb_config_prev; + usb_config_prev = furi_hal_usb_get_config(); + midi_usb_set_context(furi_thread_get_id(furi_thread_get_current())); + midi_usb_set_rx_callback(midi_rx_callback); + furi_hal_usb_set_config(&midi_usb_interface, NULL); + + MidiParser* parser = midi_parser_alloc(); + uint32_t events; + uint8_t current_note = 255; + + while(1) { + events = furi_thread_flags_wait(MidiThreadEventAll, FuriFlagWaitAny, FuriWaitForever); + + if(!(events & FuriFlagError)) { + if(events & MidiThreadEventRx) { + uint8_t buffer[64]; + size_t size = midi_usb_rx(buffer, sizeof(buffer)); + // loopback + // midi_usb_tx(buffer, size); + size_t start = 0; + while(start < size) { + CodeIndex code_index = code_index_from_data(buffer[start]); + uint8_t data_size = usb_message_data_size(code_index); + if(data_size == 0) break; + + start += 1; + for(size_t j = 0; j < data_size; j++) { + if(midi_parser_parse(parser, buffer[start + j])) { + MidiEvent* event = midi_parser_get_message(parser); + if(event->type == NoteOn) { + NoteOnEvent note_on = AsNoteOn(event); + current_note = note_on.note; + if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) { + furi_hal_speaker_start( + note_to_frequency(note_on.note), + note_on.velocity / 127.0f); + } + } else if(event->type == NoteOff) { + NoteOffEvent note_off = AsNoteOff(event); + if(note_off.note == current_note && furi_hal_speaker_is_mine()) { + furi_hal_speaker_stop(); + furi_hal_speaker_release(); + } + } + } + } + start += data_size; + } + } + } + } + + midi_parser_free(parser); + furi_hal_usb_set_config(usb_config_prev, NULL); + + return 0; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/usb_midi/usb_midi.png b/Applications/Official/DEV_FW/source/usb_midi/usb_midi.png new file mode 100644 index 000000000..6d0ac6fed Binary files /dev/null and b/Applications/Official/DEV_FW/source/usb_midi/usb_midi.png differ diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/LICENSE.md b/Applications/Official/DEV_FW/source/usbkeyboard/LICENSE.md new file mode 100644 index 000000000..c616efe70 --- /dev/null +++ b/Applications/Official/DEV_FW/source/usbkeyboard/LICENSE.md @@ -0,0 +1,24 @@ +BSD 2-Clause License + +Copyright (c) 2022, Gabriel Cirlig + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/README.md b/Applications/Official/DEV_FW/source/usbkeyboard/README.md new file mode 100644 index 000000000..b2e31f9f4 --- /dev/null +++ b/Applications/Official/DEV_FW/source/usbkeyboard/README.md @@ -0,0 +1,8 @@ +# FlipperZeroUSBKeyboard +Turn your Flipper Zero into an USB keyboard. Works with the [latest Unleashed firmware](https://github.com/Eng1n33r/flipperzero-firmware). See the release on how to install the external app on the sdcard. For building instructions please refer the [official FAP guide](https://github.com/Eng1n33r/flipperzero-firmware/blob/dev/documentation/AppsOnSDCard.md). + +How it works: https://twitter.com/hookgab/status/1572537933210718211 + +Credits: + +All the good people that made the [BT HID remote](https://github.com/flipperdevices/flipperzero-firmware/tree/873e1f114b7ca55a72dc68bf1b1fa6d169e7c17e/applications/plugins/bt_hid_app) and the [USB Mouse](https://github.com/flipperdevices/flipperzero-firmware/tree/873e1f114b7ca55a72dc68bf1b1fa6d169e7c17e/applications/debug/usb_mouse). diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/application.fam b/Applications/Official/DEV_FW/source/usbkeyboard/application.fam new file mode 100644 index 000000000..1e419f3fe --- /dev/null +++ b/Applications/Official/DEV_FW/source/usbkeyboard/application.fam @@ -0,0 +1,15 @@ +App( + appid="USB_Keyboard", + name="USB Keyboard & Mouse", + apptype=FlipperAppType.EXTERNAL, + entry_point="usb_hid_app", + stack_size=1 * 1024, + cdefines=["APP_USB_KEYBOARD"], + requires=[ + "gui", + ], + order=60, + fap_icon="usb_keyboard_10px.png", + fap_category="Misc", + fap_icon_assets="assets", +) diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/assets/Arr_dwn_7x9.png b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Arr_dwn_7x9.png new file mode 100644 index 000000000..d4034efc4 Binary files /dev/null and b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Arr_dwn_7x9.png differ diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/assets/Arr_up_7x9.png b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Arr_up_7x9.png new file mode 100644 index 000000000..28b4236a2 Binary files /dev/null and b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Arr_up_7x9.png differ diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/assets/ButtonDown_7x4.png b/Applications/Official/DEV_FW/source/usbkeyboard/assets/ButtonDown_7x4.png new file mode 100644 index 000000000..2954bb6a6 Binary files /dev/null and b/Applications/Official/DEV_FW/source/usbkeyboard/assets/ButtonDown_7x4.png differ diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/assets/ButtonLeft_4x7.png b/Applications/Official/DEV_FW/source/usbkeyboard/assets/ButtonLeft_4x7.png new file mode 100644 index 000000000..0b4655d43 Binary files /dev/null and b/Applications/Official/DEV_FW/source/usbkeyboard/assets/ButtonLeft_4x7.png differ diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/assets/ButtonRight_4x7.png b/Applications/Official/DEV_FW/source/usbkeyboard/assets/ButtonRight_4x7.png new file mode 100644 index 000000000..8e1c74c1c Binary files /dev/null and b/Applications/Official/DEV_FW/source/usbkeyboard/assets/ButtonRight_4x7.png differ diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/assets/ButtonUp_7x4.png b/Applications/Official/DEV_FW/source/usbkeyboard/assets/ButtonUp_7x4.png new file mode 100644 index 000000000..1be79328b Binary files /dev/null and b/Applications/Official/DEV_FW/source/usbkeyboard/assets/ButtonUp_7x4.png differ diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/assets/Button_18x18.png b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Button_18x18.png new file mode 100644 index 000000000..30a5b4fab Binary files /dev/null and b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Button_18x18.png differ diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/assets/Circles_47x47.png b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Circles_47x47.png new file mode 100644 index 000000000..6a16ebf7b Binary files /dev/null and b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Circles_47x47.png differ diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/assets/Left_mouse_icon_9x9.png b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Left_mouse_icon_9x9.png new file mode 100644 index 000000000..c533d8572 Binary files /dev/null and b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Left_mouse_icon_9x9.png differ diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/assets/Like_def_11x9.png b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Like_def_11x9.png new file mode 100644 index 000000000..555bea3d4 Binary files /dev/null and b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Like_def_11x9.png differ diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/assets/Like_pressed_17x17.png b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Like_pressed_17x17.png new file mode 100644 index 000000000..f5bf276f3 Binary files /dev/null and b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Like_pressed_17x17.png differ diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/assets/Ok_btn_9x9.png b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Ok_btn_9x9.png new file mode 100644 index 000000000..9a1539da2 Binary files /dev/null and b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Ok_btn_9x9.png differ diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/assets/Ok_btn_pressed_13x13.png b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Ok_btn_pressed_13x13.png new file mode 100644 index 000000000..6b46ba3a8 Binary files /dev/null and b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Ok_btn_pressed_13x13.png differ diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/assets/Pin_arrow_down_7x9.png b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Pin_arrow_down_7x9.png new file mode 100644 index 000000000..9687397af Binary files /dev/null and b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Pin_arrow_down_7x9.png differ diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/assets/Pin_arrow_left_9x7.png b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Pin_arrow_left_9x7.png new file mode 100644 index 000000000..fb4ded78f Binary files /dev/null and b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Pin_arrow_left_9x7.png differ diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/assets/Pin_arrow_right_9x7.png b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Pin_arrow_right_9x7.png new file mode 100644 index 000000000..97648d176 Binary files /dev/null and b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Pin_arrow_right_9x7.png differ diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/assets/Pin_arrow_up_7x9.png b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Pin_arrow_up_7x9.png new file mode 100644 index 000000000..a91a6fd5e Binary files /dev/null and b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Pin_arrow_up_7x9.png differ diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/assets/Pin_back_arrow_10x8.png b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Pin_back_arrow_10x8.png new file mode 100644 index 000000000..3bafabd14 Binary files /dev/null and b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Pin_back_arrow_10x8.png differ diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/assets/Pressed_Button_13x13.png b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Pressed_Button_13x13.png new file mode 100644 index 000000000..823926b84 Binary files /dev/null and b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Pressed_Button_13x13.png differ diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/assets/Right_mouse_icon_9x9.png b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Right_mouse_icon_9x9.png new file mode 100644 index 000000000..446d7176c Binary files /dev/null and b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Right_mouse_icon_9x9.png differ diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/assets/Space_65x18.png b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Space_65x18.png new file mode 100644 index 000000000..b60ae5097 Binary files /dev/null and b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Space_65x18.png differ diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/assets/Voldwn_6x6.png b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Voldwn_6x6.png new file mode 100644 index 000000000..d7a82a2df Binary files /dev/null and b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Voldwn_6x6.png differ diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/assets/Volup_8x6.png b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Volup_8x6.png new file mode 100644 index 000000000..4b7ec66d6 Binary files /dev/null and b/Applications/Official/DEV_FW/source/usbkeyboard/assets/Volup_8x6.png differ diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/usb_hid.c b/Applications/Official/DEV_FW/source/usbkeyboard/usb_hid.c new file mode 100644 index 000000000..744d6463e --- /dev/null +++ b/Applications/Official/DEV_FW/source/usbkeyboard/usb_hid.c @@ -0,0 +1,198 @@ +#include "usb_hid.h" +#include +#include +#include + +#define TAG "UsbHidApp" + +enum UsbDebugSubmenuIndex { + UsbHidSubmenuIndexDirpad, + UsbHidSubmenuIndexKeyboard, + UsbHidSubmenuIndexMedia, + UsbHidSubmenuIndexMouse, + UsbHidSubmenuIndexMouseJiggler, +}; + +void usb_hid_submenu_callback(void* context, uint32_t index) { + furi_assert(context); + UsbHid* app = context; + if(index == UsbHidSubmenuIndexDirpad) { + app->view_id = UsbHidViewDirpad; + view_dispatcher_switch_to_view(app->view_dispatcher, UsbHidViewDirpad); + } else if(index == UsbHidSubmenuIndexKeyboard) { + app->view_id = UsbHidViewKeyboard; + view_dispatcher_switch_to_view(app->view_dispatcher, UsbHidViewKeyboard); + } else if(index == UsbHidSubmenuIndexMedia) { + app->view_id = UsbHidViewMedia; + view_dispatcher_switch_to_view(app->view_dispatcher, UsbHidViewMedia); + } else if(index == UsbHidSubmenuIndexMouse) { + app->view_id = UsbHidViewMouse; + view_dispatcher_switch_to_view(app->view_dispatcher, UsbHidViewMouse); + } else if(index == UsbHidSubmenuIndexMouseJiggler) { + app->view_id = UsbHidViewMouseJiggler; + view_dispatcher_switch_to_view(app->view_dispatcher, UsbHidViewMouseJiggler); + } +} + +void usb_hid_dialog_callback(DialogExResult result, void* context) { + furi_assert(context); + UsbHid* app = context; + if(result == DialogExResultLeft) { + view_dispatcher_stop(app->view_dispatcher); + } else if(result == DialogExResultRight) { + view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); // Show last view + } else if(result == DialogExResultCenter) { + view_dispatcher_switch_to_view(app->view_dispatcher, UsbHidViewSubmenu); + } +} + +uint32_t usb_hid_exit_confirm_view(void* context) { + UNUSED(context); + return UsbHidViewExitConfirm; +} + +uint32_t usb_hid_exit(void* context) { + UNUSED(context); + return VIEW_NONE; +} + +UsbHid* usb_hid_app_alloc() { + UsbHid* app = malloc(sizeof(UsbHid)); + + // Gui + app->gui = furi_record_open(RECORD_GUI); + + // Notifications + app->notifications = furi_record_open(RECORD_NOTIFICATION); + + // View dispatcher + app->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + // Submenu view + app->submenu = submenu_alloc(); + submenu_add_item( + app->submenu, "Dirpad", UsbHidSubmenuIndexDirpad, usb_hid_submenu_callback, app); + submenu_add_item( + app->submenu, "Keyboard", UsbHidSubmenuIndexKeyboard, usb_hid_submenu_callback, app); + submenu_add_item( + app->submenu, "Media", UsbHidSubmenuIndexMedia, usb_hid_submenu_callback, app); + submenu_add_item( + app->submenu, "Mouse", UsbHidSubmenuIndexMouse, usb_hid_submenu_callback, app); + submenu_add_item( + app->submenu, + "Mouse Jiggler", + UsbHidSubmenuIndexMouseJiggler, + usb_hid_submenu_callback, + app); + view_set_previous_callback(submenu_get_view(app->submenu), usb_hid_exit); + view_dispatcher_add_view( + app->view_dispatcher, UsbHidViewSubmenu, submenu_get_view(app->submenu)); + + // Dialog view + app->dialog = dialog_ex_alloc(); + dialog_ex_set_result_callback(app->dialog, usb_hid_dialog_callback); + dialog_ex_set_context(app->dialog, app); + dialog_ex_set_left_button_text(app->dialog, "Exit"); + dialog_ex_set_right_button_text(app->dialog, "Stay"); + dialog_ex_set_center_button_text(app->dialog, "Menu"); + dialog_ex_set_header(app->dialog, "Close Current App?", 16, 12, AlignLeft, AlignTop); + view_dispatcher_add_view( + app->view_dispatcher, UsbHidViewExitConfirm, dialog_ex_get_view(app->dialog)); + + // Dirpad view + app->usb_hid_dirpad = usb_hid_dirpad_alloc(); + view_set_previous_callback( + usb_hid_dirpad_get_view(app->usb_hid_dirpad), usb_hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, UsbHidViewDirpad, usb_hid_dirpad_get_view(app->usb_hid_dirpad)); + + // Keyboard view + app->usb_hid_keyboard = usb_hid_keyboard_alloc(); + view_set_previous_callback( + usb_hid_keyboard_get_view(app->usb_hid_keyboard), usb_hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, + UsbHidViewKeyboard, + usb_hid_keyboard_get_view(app->usb_hid_keyboard)); + + // Media view + app->usb_hid_media = usb_hid_media_alloc(); + view_set_previous_callback( + usb_hid_media_get_view(app->usb_hid_media), usb_hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, UsbHidViewMedia, usb_hid_media_get_view(app->usb_hid_media)); + + // Mouse view + app->usb_hid_mouse = usb_hid_mouse_alloc(); + view_set_previous_callback( + usb_hid_mouse_get_view(app->usb_hid_mouse), usb_hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, UsbHidViewMouse, usb_hid_mouse_get_view(app->usb_hid_mouse)); + + // Mouse jiggler view + app->hid_mouse_jiggler = hid_mouse_jiggler_alloc(app); + view_set_previous_callback( + hid_mouse_jiggler_get_view(app->hid_mouse_jiggler), usb_hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, + UsbHidViewMouseJiggler, + hid_mouse_jiggler_get_view(app->hid_mouse_jiggler)); + + // TODO switch to menu after Media is done + app->view_id = UsbHidViewSubmenu; + view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); + + return app; +} + +void usb_hid_app_free(UsbHid* app) { + furi_assert(app); + + // Reset notification + notification_internal_message(app->notifications, &sequence_reset_blue); + + // Free views + view_dispatcher_remove_view(app->view_dispatcher, UsbHidViewSubmenu); + submenu_free(app->submenu); + view_dispatcher_remove_view(app->view_dispatcher, UsbHidViewExitConfirm); + dialog_ex_free(app->dialog); + view_dispatcher_remove_view(app->view_dispatcher, UsbHidViewDirpad); + usb_hid_dirpad_free(app->usb_hid_dirpad); + view_dispatcher_remove_view(app->view_dispatcher, UsbHidViewKeyboard); + usb_hid_keyboard_free(app->usb_hid_keyboard); + view_dispatcher_remove_view(app->view_dispatcher, UsbHidViewMedia); + usb_hid_media_free(app->usb_hid_media); + view_dispatcher_remove_view(app->view_dispatcher, UsbHidViewMouse); + usb_hid_mouse_free(app->usb_hid_mouse); + view_dispatcher_remove_view(app->view_dispatcher, UsbHidViewMouseJiggler); + hid_mouse_jiggler_free(app->hid_mouse_jiggler); + view_dispatcher_free(app->view_dispatcher); + // Close records + furi_record_close(RECORD_GUI); + app->gui = NULL; + furi_record_close(RECORD_NOTIFICATION); + app->notifications = NULL; + + // Free rest + free(app); +} + +int32_t usb_hid_app(void* p) { + UNUSED(p); + // Switch profile to Hid + UsbHid* app = usb_hid_app_alloc(); + + FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config(); + furi_hal_usb_unlock(); + furi_check(furi_hal_usb_set_config(&usb_hid, NULL) == true); + + view_dispatcher_run(app->view_dispatcher); + + // Change back profile + furi_hal_usb_set_config(usb_mode_prev, NULL); + usb_hid_app_free(app); + + return 0; +} diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/usb_hid.h b/Applications/Official/DEV_FW/source/usbkeyboard/usb_hid.h new file mode 100644 index 000000000..77b70e59f --- /dev/null +++ b/Applications/Official/DEV_FW/source/usbkeyboard/usb_hid.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include "views/usb_hid_dirpad.h" +#include "views/usb_hid_keyboard.h" +#include "views/usb_hid_media.h" +#include "views/usb_hid_mouse.h" +#include "views/usb_hid_mouse_jiggler.h" + +typedef struct { + Gui* gui; + NotificationApp* notifications; + ViewDispatcher* view_dispatcher; + Submenu* submenu; + DialogEx* dialog; + UsbHidDirpad* usb_hid_dirpad; + UsbHidKeyboard* usb_hid_keyboard; + UsbHidMedia* usb_hid_media; + UsbHidMouse* usb_hid_mouse; + HidMouseJiggler* hid_mouse_jiggler; + uint32_t view_id; +} UsbHid; + +typedef enum { + UsbHidViewSubmenu, + UsbHidViewDirpad, + UsbHidViewKeyboard, + UsbHidViewMedia, + UsbHidViewMouse, + UsbHidViewMouseJiggler, + UsbHidViewExitConfirm, +} UsbHidView; diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/usb_keyboard_10px.png b/Applications/Official/DEV_FW/source/usbkeyboard/usb_keyboard_10px.png new file mode 100644 index 000000000..7649138eb Binary files /dev/null and b/Applications/Official/DEV_FW/source/usbkeyboard/usb_keyboard_10px.png differ diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/views/usb_hid_dirpad.c b/Applications/Official/DEV_FW/source/usbkeyboard/views/usb_hid_dirpad.c new file mode 100644 index 000000000..d6aec9ef2 --- /dev/null +++ b/Applications/Official/DEV_FW/source/usbkeyboard/views/usb_hid_dirpad.c @@ -0,0 +1,197 @@ +#include "usb_hid_dirpad.h" +#include +#include +#include +#include + +struct UsbHidDirpad { + View* view; +}; + +typedef struct { + bool left_pressed; + bool up_pressed; + bool right_pressed; + bool down_pressed; + bool ok_pressed; + bool back_pressed; + bool connected; +} UsbHidDirpadModel; + +static void usb_hid_dirpad_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) { + canvas_draw_triangle(canvas, x, y, 5, 3, dir); + if(dir == CanvasDirectionBottomToTop) { + canvas_draw_line(canvas, x, y + 6, x, y - 1); + } else if(dir == CanvasDirectionTopToBottom) { + canvas_draw_line(canvas, x, y - 6, x, y + 1); + } else if(dir == CanvasDirectionRightToLeft) { + canvas_draw_line(canvas, x + 6, y, x - 1, y); + } else if(dir == CanvasDirectionLeftToRight) { + canvas_draw_line(canvas, x - 6, y, x + 1, y); + } +} + +static void usb_hid_dirpad_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + UsbHidDirpadModel* model = context; + + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Dirpad"); + + canvas_draw_icon(canvas, 68, 2, &I_Pin_back_arrow_10x8); + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned(canvas, 127, 3, AlignRight, AlignTop, "Hold to exit"); + + // Up + canvas_draw_icon(canvas, 21, 24, &I_Button_18x18); + if(model->up_pressed) { + elements_slightly_rounded_box(canvas, 24, 26, 13, 13); + canvas_set_color(canvas, ColorWhite); + } + usb_hid_dirpad_draw_arrow(canvas, 30, 30, CanvasDirectionBottomToTop); + canvas_set_color(canvas, ColorBlack); + + // Down + canvas_draw_icon(canvas, 21, 45, &I_Button_18x18); + if(model->down_pressed) { + elements_slightly_rounded_box(canvas, 24, 47, 13, 13); + canvas_set_color(canvas, ColorWhite); + } + usb_hid_dirpad_draw_arrow(canvas, 30, 55, CanvasDirectionTopToBottom); + canvas_set_color(canvas, ColorBlack); + + // Left + canvas_draw_icon(canvas, 0, 45, &I_Button_18x18); + if(model->left_pressed) { + elements_slightly_rounded_box(canvas, 3, 47, 13, 13); + canvas_set_color(canvas, ColorWhite); + } + usb_hid_dirpad_draw_arrow(canvas, 7, 53, CanvasDirectionRightToLeft); + canvas_set_color(canvas, ColorBlack); + + // Right + canvas_draw_icon(canvas, 42, 45, &I_Button_18x18); + if(model->right_pressed) { + elements_slightly_rounded_box(canvas, 45, 47, 13, 13); + canvas_set_color(canvas, ColorWhite); + } + usb_hid_dirpad_draw_arrow(canvas, 53, 53, CanvasDirectionLeftToRight); + canvas_set_color(canvas, ColorBlack); + + // Ok + canvas_draw_icon(canvas, 63, 25, &I_Space_65x18); + if(model->ok_pressed) { + elements_slightly_rounded_box(canvas, 66, 27, 60, 13); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9); + elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Space"); + canvas_set_color(canvas, ColorBlack); + + // Back + canvas_draw_icon(canvas, 63, 45, &I_Space_65x18); + if(model->back_pressed) { + elements_slightly_rounded_box(canvas, 66, 47, 60, 13); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 74, 49, &I_Pin_back_arrow_10x8); + elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Back"); +} + +static void usb_hid_dirpad_process(UsbHidDirpad* usb_hid_dirpad, InputEvent* event) { + with_view_model( + usb_hid_dirpad->view, + UsbHidDirpadModel * model, + { + if(event->type == InputTypePress) { + if(event->key == InputKeyUp) { + model->up_pressed = true; + furi_hal_hid_kb_press(HID_KEYBOARD_UP_ARROW); + } else if(event->key == InputKeyDown) { + model->down_pressed = true; + furi_hal_hid_kb_press(HID_KEYBOARD_DOWN_ARROW); + } else if(event->key == InputKeyLeft) { + model->left_pressed = true; + furi_hal_hid_kb_press(HID_KEYBOARD_LEFT_ARROW); + } else if(event->key == InputKeyRight) { + model->right_pressed = true; + furi_hal_hid_kb_press(HID_KEYBOARD_RIGHT_ARROW); + } else if(event->key == InputKeyOk) { + model->ok_pressed = true; + furi_hal_hid_kb_press(HID_KEYBOARD_SPACEBAR); + } else if(event->key == InputKeyBack) { + model->back_pressed = true; + } + } else if(event->type == InputTypeRelease) { + if(event->key == InputKeyUp) { + model->up_pressed = false; + furi_hal_hid_kb_release(HID_KEYBOARD_UP_ARROW); + } else if(event->key == InputKeyDown) { + model->down_pressed = false; + furi_hal_hid_kb_release(HID_KEYBOARD_DOWN_ARROW); + } else if(event->key == InputKeyLeft) { + model->left_pressed = false; + furi_hal_hid_kb_release(HID_KEYBOARD_LEFT_ARROW); + } else if(event->key == InputKeyRight) { + model->right_pressed = false; + furi_hal_hid_kb_release(HID_KEYBOARD_RIGHT_ARROW); + } else if(event->key == InputKeyOk) { + model->ok_pressed = false; + furi_hal_hid_kb_release(HID_KEYBOARD_SPACEBAR); + } else if(event->key == InputKeyBack) { + model->back_pressed = false; + } + } else if(event->type == InputTypeShort) { + if(event->key == InputKeyBack) { + furi_hal_hid_kb_press(HID_KEYBOARD_DELETE); + furi_hal_hid_kb_release(HID_KEYBOARD_DELETE); + furi_hal_hid_consumer_key_press(HID_CONSUMER_AC_BACK); + furi_hal_hid_consumer_key_release(HID_CONSUMER_AC_BACK); + } + } + }, + true); +} + +static bool usb_hid_dirpad_input_callback(InputEvent* event, void* context) { + furi_assert(context); + UsbHidDirpad* usb_hid_dirpad = context; + bool consumed = false; + + if(event->type == InputTypeLong && event->key == InputKeyBack) { + furi_hal_hid_kb_release_all(); + } else { + usb_hid_dirpad_process(usb_hid_dirpad, event); + consumed = true; + } + + return consumed; +} + +UsbHidDirpad* usb_hid_dirpad_alloc() { + UsbHidDirpad* usb_hid_dirpad = malloc(sizeof(UsbHidDirpad)); + usb_hid_dirpad->view = view_alloc(); + view_set_context(usb_hid_dirpad->view, usb_hid_dirpad); + view_allocate_model(usb_hid_dirpad->view, ViewModelTypeLocking, sizeof(UsbHidDirpadModel)); + view_set_draw_callback(usb_hid_dirpad->view, usb_hid_dirpad_draw_callback); + view_set_input_callback(usb_hid_dirpad->view, usb_hid_dirpad_input_callback); + + return usb_hid_dirpad; +} + +void usb_hid_dirpad_free(UsbHidDirpad* usb_hid_dirpad) { + furi_assert(usb_hid_dirpad); + view_free(usb_hid_dirpad->view); + free(usb_hid_dirpad); +} + +View* usb_hid_dirpad_get_view(UsbHidDirpad* usb_hid_dirpad) { + furi_assert(usb_hid_dirpad); + return usb_hid_dirpad->view; +} + +void usb_hid_dirpad_set_connected_status(UsbHidDirpad* usb_hid_dirpad, bool connected) { + furi_assert(usb_hid_dirpad); + with_view_model( + usb_hid_dirpad->view, UsbHidDirpadModel * model, { model->connected = connected; }, true); +} diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/views/usb_hid_dirpad.h b/Applications/Official/DEV_FW/source/usbkeyboard/views/usb_hid_dirpad.h new file mode 100644 index 000000000..305e051b4 --- /dev/null +++ b/Applications/Official/DEV_FW/source/usbkeyboard/views/usb_hid_dirpad.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +typedef struct UsbHidDirpad UsbHidDirpad; + +UsbHidDirpad* usb_hid_dirpad_alloc(); + +void usb_hid_dirpad_free(UsbHidDirpad* usb_hid_dirpad); + +View* usb_hid_dirpad_get_view(UsbHidDirpad* usb_hid_dirpad); + +void usb_hid_dirpad_set_connected_status(UsbHidDirpad* usb_hid_dirpad, bool connected); diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/views/usb_hid_keyboard.c b/Applications/Official/DEV_FW/source/usbkeyboard/views/usb_hid_keyboard.c new file mode 100644 index 000000000..71438e1c9 --- /dev/null +++ b/Applications/Official/DEV_FW/source/usbkeyboard/views/usb_hid_keyboard.c @@ -0,0 +1,372 @@ +#include "usb_hid_keyboard.h" +#include +#include +#include +#include +#include + +struct UsbHidKeyboard { + View* view; +}; + +typedef struct { + bool shift; + bool alt; + bool ctrl; + bool gui; + uint8_t x; + uint8_t y; + uint8_t last_key_code; + uint16_t modifier_code; + bool ok_pressed; + bool back_pressed; + bool connected; + char key_string[5]; +} UsbHidKeyboardModel; + +typedef struct { + uint8_t width; + char* key; + const Icon* icon; + char* shift_key; + uint8_t value; +} UsbHidKeyboardKey; + +typedef struct { + int8_t x; + int8_t y; +} UsbHidKeyboardPoint; + +// 4 BY 12 +#define MARGIN_TOP 0 +#define MARGIN_LEFT 4 +#define KEY_WIDTH 9 +#define KEY_HEIGHT 12 +#define KEY_PADDING 1 +#define ROW_COUNT 6 +#define COLUMN_COUNT 12 + +// 0 width items are not drawn, but there value is used +const UsbHidKeyboardKey usb_hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = { + { + {.width = 1, .icon = NULL, .key = "1", .shift_key = "!", .value = HID_KEYBOARD_1}, + {.width = 1, .icon = NULL, .key = "2", .shift_key = "@", .value = HID_KEYBOARD_2}, + {.width = 1, .icon = NULL, .key = "3", .shift_key = "#", .value = HID_KEYBOARD_3}, + {.width = 1, .icon = NULL, .key = "4", .shift_key = "$", .value = HID_KEYBOARD_4}, + {.width = 1, .icon = NULL, .key = "5", .shift_key = "%", .value = HID_KEYBOARD_5}, + {.width = 1, .icon = NULL, .key = "6", .shift_key = "^", .value = HID_KEYBOARD_6}, + {.width = 1, .icon = NULL, .key = "7", .shift_key = "&", .value = HID_KEYBOARD_7}, + {.width = 1, .icon = NULL, .key = "8", .shift_key = "*", .value = HID_KEYBOARD_8}, + {.width = 1, .icon = NULL, .key = "9", .shift_key = "(", .value = HID_KEYBOARD_9}, + {.width = 1, .icon = NULL, .key = "0", .shift_key = ")", .value = HID_KEYBOARD_0}, + {.width = 2, .icon = &I_Pin_arrow_left_9x7, .value = HID_KEYBOARD_DELETE}, + {.width = 0, .value = HID_KEYBOARD_DELETE}, + }, + { + {.width = 1, .icon = NULL, .key = "q", .shift_key = "Q", .value = HID_KEYBOARD_Q}, + {.width = 1, .icon = NULL, .key = "w", .shift_key = "W", .value = HID_KEYBOARD_W}, + {.width = 1, .icon = NULL, .key = "e", .shift_key = "E", .value = HID_KEYBOARD_E}, + {.width = 1, .icon = NULL, .key = "r", .shift_key = "R", .value = HID_KEYBOARD_R}, + {.width = 1, .icon = NULL, .key = "t", .shift_key = "T", .value = HID_KEYBOARD_T}, + {.width = 1, .icon = NULL, .key = "y", .shift_key = "Y", .value = HID_KEYBOARD_Y}, + {.width = 1, .icon = NULL, .key = "u", .shift_key = "U", .value = HID_KEYBOARD_U}, + {.width = 1, .icon = NULL, .key = "i", .shift_key = "I", .value = HID_KEYBOARD_I}, + {.width = 1, .icon = NULL, .key = "o", .shift_key = "O", .value = HID_KEYBOARD_O}, + {.width = 1, .icon = NULL, .key = "p", .shift_key = "P", .value = HID_KEYBOARD_P}, + {.width = 1, .icon = NULL, .key = "[", .shift_key = "{", .value = HID_KEYBOARD_OPEN_BRACKET}, + {.width = 1, + .icon = NULL, + .key = "]", + .shift_key = "}", + .value = HID_KEYBOARD_CLOSE_BRACKET}, + }, + { + {.width = 1, .icon = NULL, .key = "a", .shift_key = "A", .value = HID_KEYBOARD_A}, + {.width = 1, .icon = NULL, .key = "s", .shift_key = "S", .value = HID_KEYBOARD_S}, + {.width = 1, .icon = NULL, .key = "d", .shift_key = "D", .value = HID_KEYBOARD_D}, + {.width = 1, .icon = NULL, .key = "f", .shift_key = "F", .value = HID_KEYBOARD_F}, + {.width = 1, .icon = NULL, .key = "g", .shift_key = "G", .value = HID_KEYBOARD_G}, + {.width = 1, .icon = NULL, .key = "h", .shift_key = "H", .value = HID_KEYBOARD_H}, + {.width = 1, .icon = NULL, .key = "j", .shift_key = "J", .value = HID_KEYBOARD_J}, + {.width = 1, .icon = NULL, .key = "k", .shift_key = "K", .value = HID_KEYBOARD_K}, + {.width = 1, .icon = NULL, .key = "l", .shift_key = "L", .value = HID_KEYBOARD_L}, + {.width = 1, .icon = NULL, .key = ";", .shift_key = ":", .value = HID_KEYBOARD_SEMICOLON}, + {.width = 2, .icon = &I_Pin_arrow_right_9x7, .value = HID_KEYBOARD_RETURN}, + {.width = 0, .value = HID_KEYBOARD_RETURN}, + }, + { + {.width = 1, .icon = NULL, .key = "z", .shift_key = "Z", .value = HID_KEYBOARD_Z}, + {.width = 1, .icon = NULL, .key = "x", .shift_key = "X", .value = HID_KEYBOARD_X}, + {.width = 1, .icon = NULL, .key = "c", .shift_key = "C", .value = HID_KEYBOARD_C}, + {.width = 1, .icon = NULL, .key = "v", .shift_key = "V", .value = HID_KEYBOARD_V}, + {.width = 1, .icon = NULL, .key = "b", .shift_key = "B", .value = HID_KEYBOARD_B}, + {.width = 1, .icon = NULL, .key = "n", .shift_key = "N", .value = HID_KEYBOARD_N}, + {.width = 1, .icon = NULL, .key = "m", .shift_key = "M", .value = HID_KEYBOARD_M}, + {.width = 1, .icon = NULL, .key = "/", .shift_key = "?", .value = HID_KEYBOARD_SLASH}, + {.width = 1, .icon = NULL, .key = "\\", .shift_key = "|", .value = HID_KEYBOARD_BACKSLASH}, + {.width = 1, .icon = NULL, .key = "`", .shift_key = "~", .value = HID_KEYBOARD_GRAVE_ACCENT}, + {.width = 1, .icon = &I_ButtonUp_7x4, .value = HID_KEYBOARD_UP_ARROW}, + {.width = 1, .icon = NULL, .key = "-", .shift_key = "_", .value = HID_KEYBOARD_MINUS}, + }, + { + {.width = 1, .icon = &I_Pin_arrow_up_7x9, .value = HID_KEYBOARD_L_SHIFT}, + {.width = 1, .icon = NULL, .key = ",", .shift_key = "<", .value = HID_KEYPAD_COMMA}, + {.width = 1, .icon = NULL, .key = ".", .shift_key = ">", .value = HID_KEYBOARD_DOT}, + {.width = 4, .icon = NULL, .key = " ", .value = HID_KEYBOARD_SPACEBAR}, + {.width = 0, .value = HID_KEYBOARD_SPACEBAR}, + {.width = 0, .value = HID_KEYBOARD_SPACEBAR}, + {.width = 0, .value = HID_KEYBOARD_SPACEBAR}, + {.width = 1, .icon = NULL, .key = "'", .shift_key = "\"", .value = HID_KEYBOARD_APOSTROPHE}, + {.width = 1, .icon = NULL, .key = "=", .shift_key = "+", .value = HID_KEYBOARD_EQUAL_SIGN}, + {.width = 1, .icon = &I_ButtonLeft_4x7, .value = HID_KEYBOARD_LEFT_ARROW}, + {.width = 1, .icon = &I_ButtonDown_7x4, .value = HID_KEYBOARD_DOWN_ARROW}, + {.width = 1, .icon = &I_ButtonRight_4x7, .value = HID_KEYBOARD_RIGHT_ARROW}, + }, + { + {.width = 3, .icon = NULL, .key = "Ctrl", .value = HID_KEYBOARD_L_CTRL}, + {.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_CTRL}, + {.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_CTRL}, + {.width = 3, .icon = NULL, .key = "Alt", .value = HID_KEYBOARD_L_ALT}, + {.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_ALT}, + {.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_ALT}, + {.width = 3, .icon = NULL, .key = "Cmd", .value = HID_KEYBOARD_L_GUI}, + {.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_GUI}, + {.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_GUI}, + {.width = 3, .icon = NULL, .key = "Tab", .value = HID_KEYBOARD_TAB}, + {.width = 0, .icon = NULL, .value = HID_KEYBOARD_TAB}, + {.width = 0, .icon = NULL, .value = HID_KEYBOARD_TAB}, + }, +}; + +static void usb_hid_keyboard_to_upper(char* str) { + while(*str) { + *str = toupper((unsigned char)*str); + str++; + } +} + +static void usb_hid_keyboard_draw_key( + Canvas* canvas, + UsbHidKeyboardModel* model, + uint8_t x, + uint8_t y, + UsbHidKeyboardKey key, + bool selected) { + if(!key.width) return; + + canvas_set_color(canvas, ColorBlack); + uint8_t keyWidth = KEY_WIDTH * key.width + KEY_PADDING * (key.width - 1); + if(selected) { + // Draw a filled box + elements_slightly_rounded_box( + canvas, + MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING), + MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING), + keyWidth, + KEY_HEIGHT); + canvas_set_color(canvas, ColorWhite); + } else { + // Draw a framed box + elements_slightly_rounded_frame( + canvas, + MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING), + MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING), + keyWidth, + KEY_HEIGHT); + } + if(key.icon != NULL) { + // Draw the icon centered on the button + canvas_draw_icon( + canvas, + MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 - key.icon->width / 2, + MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2 - key.icon->height / 2, + key.icon); + } else { + // If shift is toggled use the shift key when available + strcpy(model->key_string, (model->shift && key.shift_key != 0) ? key.shift_key : key.key); + // Upper case if ctrl or alt was toggled true + if((model->ctrl && key.value == HID_KEYBOARD_L_CTRL) || + (model->alt && key.value == HID_KEYBOARD_L_ALT) || + (model->gui && key.value == HID_KEYBOARD_L_GUI)) { + usb_hid_keyboard_to_upper(model->key_string); + } + canvas_draw_str_aligned( + canvas, + MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 + 1, + MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2, + AlignCenter, + AlignCenter, + model->key_string); + } +} + +static void usb_hid_keyboard_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + UsbHidKeyboardModel* model = context; + + canvas_set_font(canvas, FontKeyboard); + // Start shifting the all keys up if on the next row (Scrolling) + uint8_t initY = model->y - 4 > 0 ? model->y - 4 : 0; + for(uint8_t y = initY; y < ROW_COUNT; y++) { + const UsbHidKeyboardKey* keyboardKeyRow = usb_hid_keyboard_keyset[y]; + uint8_t x = 0; + for(uint8_t i = 0; i < COLUMN_COUNT; i++) { + UsbHidKeyboardKey key = keyboardKeyRow[i]; + // Select when the button is hovered + // Select if the button is hovered within its width + // Select if back is clicked and its the backspace key + // Deselect when the button clicked or not hovered + bool keySelected = (x <= model->x && model->x < (x + key.width)) && y == model->y; + bool backSelected = model->back_pressed && key.value == HID_KEYBOARD_DELETE; + usb_hid_keyboard_draw_key( + canvas, + model, + x, + y - initY, + key, + (!model->ok_pressed && keySelected) || backSelected); + x += key.width; + } + } +} + +static uint8_t usb_hid_keyboard_get_selected_key(UsbHidKeyboardModel* model) { + UsbHidKeyboardKey key = usb_hid_keyboard_keyset[model->y][model->x]; + // Use upper case if shift is toggled + bool useUppercase = model->shift; + // Check if the key has an upper case version + bool hasUppercase = key.shift_key != 0; + if(useUppercase && hasUppercase) + return key.value; + else + return key.value; +} + +static void + usb_hid_keyboard_get_select_key(UsbHidKeyboardModel* model, UsbHidKeyboardPoint delta) { + // Keep going until a valid spot is found, this allows for nulls and zero width keys in the map + do { + if(((int8_t)model->y) + delta.y < 0) + model->y = ROW_COUNT - 1; + else + model->y = (model->y + delta.y) % ROW_COUNT; + } while(delta.y != 0 && usb_hid_keyboard_keyset[model->y][model->x].value == 0); + + do { + if(((int8_t)model->x) + delta.x < 0) + model->x = COLUMN_COUNT - 1; + else + model->x = (model->x + delta.x) % COLUMN_COUNT; + } while(delta.x != 0 && usb_hid_keyboard_keyset[model->y][model->x].width == + 0); // Skip zero width keys, pretend they are one key +} + +static void usb_hid_keyboard_process(UsbHidKeyboard* usb_hid_keyboard, InputEvent* event) { + with_view_model( + usb_hid_keyboard->view, + UsbHidKeyboardModel * model, + { + if(event->key == InputKeyOk) { + if(event->type == InputTypePress) { + model->ok_pressed = true; + } else if(event->type == InputTypeLong || event->type == InputTypeShort) { + model->last_key_code = usb_hid_keyboard_get_selected_key(model); + + // Toggle the modifier key when clicked, and click the key + if(model->last_key_code == HID_KEYBOARD_L_SHIFT) { + model->shift = !model->shift; + if(model->shift) + model->modifier_code |= KEY_MOD_LEFT_SHIFT; + else + model->modifier_code &= ~KEY_MOD_LEFT_SHIFT; + } else if(model->last_key_code == HID_KEYBOARD_L_ALT) { + model->alt = !model->alt; + if(model->alt) + model->modifier_code |= KEY_MOD_LEFT_ALT; + else + model->modifier_code &= ~KEY_MOD_LEFT_ALT; + } else if(model->last_key_code == HID_KEYBOARD_L_CTRL) { + model->ctrl = !model->ctrl; + if(model->ctrl) + model->modifier_code |= KEY_MOD_LEFT_CTRL; + else + model->modifier_code &= ~KEY_MOD_LEFT_CTRL; + } else if(model->last_key_code == HID_KEYBOARD_L_GUI) { + model->gui = !model->gui; + if(model->gui) + model->modifier_code |= KEY_MOD_LEFT_GUI; + else + model->modifier_code &= ~KEY_MOD_LEFT_GUI; + } + furi_hal_hid_kb_press(model->modifier_code | model->last_key_code); + } else if(event->type == InputTypeRelease) { + // Release happens after short and long presses + furi_hal_hid_kb_release(model->modifier_code | model->last_key_code); + model->ok_pressed = false; + } + } else if(event->key == InputKeyBack) { + // If back is pressed for a short time, backspace + if(event->type == InputTypePress) { + model->back_pressed = true; + } else if(event->type == InputTypeShort) { + furi_hal_hid_kb_press(HID_KEYBOARD_DELETE); + furi_hal_hid_kb_release(HID_KEYBOARD_DELETE); + } else if(event->type == InputTypeRelease) { + model->back_pressed = false; + } + } else if(event->type == InputTypePress || event->type == InputTypeRepeat) { + // Cycle the selected keys + if(event->key == InputKeyUp) { + usb_hid_keyboard_get_select_key(model, (UsbHidKeyboardPoint){.x = 0, .y = -1}); + } else if(event->key == InputKeyDown) { + usb_hid_keyboard_get_select_key(model, (UsbHidKeyboardPoint){.x = 0, .y = 1}); + } else if(event->key == InputKeyLeft) { + usb_hid_keyboard_get_select_key(model, (UsbHidKeyboardPoint){.x = -1, .y = 0}); + } else if(event->key == InputKeyRight) { + usb_hid_keyboard_get_select_key(model, (UsbHidKeyboardPoint){.x = 1, .y = 0}); + } + } + }, + true); +} + +static bool usb_hid_keyboard_input_callback(InputEvent* event, void* context) { + furi_assert(context); + UsbHidKeyboard* usb_hid_keyboard = context; + bool consumed = false; + + if(event->type == InputTypeLong && event->key == InputKeyBack) { + furi_hal_hid_kb_release_all(); + } else { + usb_hid_keyboard_process(usb_hid_keyboard, event); + consumed = true; + } + + return consumed; +} + +UsbHidKeyboard* usb_hid_keyboard_alloc() { + UsbHidKeyboard* usb_hid_keyboard = malloc(sizeof(UsbHidKeyboard)); + + usb_hid_keyboard->view = view_alloc(); + view_set_context(usb_hid_keyboard->view, usb_hid_keyboard); + view_allocate_model(usb_hid_keyboard->view, ViewModelTypeLocking, sizeof(UsbHidKeyboardModel)); + view_set_draw_callback(usb_hid_keyboard->view, usb_hid_keyboard_draw_callback); + view_set_input_callback(usb_hid_keyboard->view, usb_hid_keyboard_input_callback); + + with_view_model( + usb_hid_keyboard->view, UsbHidKeyboardModel * model, { model->connected = true; }, true); + + return usb_hid_keyboard; +} + +void usb_hid_keyboard_free(UsbHidKeyboard* usb_hid_keyboard) { + furi_assert(usb_hid_keyboard); + view_free(usb_hid_keyboard->view); + free(usb_hid_keyboard); +} + +View* usb_hid_keyboard_get_view(UsbHidKeyboard* usb_hid_keyboard) { + furi_assert(usb_hid_keyboard); + return usb_hid_keyboard->view; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/views/usb_hid_keyboard.h b/Applications/Official/DEV_FW/source/usbkeyboard/views/usb_hid_keyboard.h new file mode 100644 index 000000000..4dee5fbee --- /dev/null +++ b/Applications/Official/DEV_FW/source/usbkeyboard/views/usb_hid_keyboard.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +typedef struct UsbHidKeyboard UsbHidKeyboard; + +UsbHidKeyboard* usb_hid_keyboard_alloc(); + +void usb_hid_keyboard_free(UsbHidKeyboard* usb_hid_keyboard); + +View* usb_hid_keyboard_get_view(UsbHidKeyboard* usb_hid_keyboard); + +void usb_hid_keyboard_set_connected_status(UsbHidKeyboard* usb_hid_keyboard, bool connected); diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/views/usb_hid_media.c b/Applications/Official/DEV_FW/source/usbkeyboard/views/usb_hid_media.c new file mode 100644 index 000000000..8d2188434 --- /dev/null +++ b/Applications/Official/DEV_FW/source/usbkeyboard/views/usb_hid_media.c @@ -0,0 +1,201 @@ +#include "usb_hid_media.h" +#include +#include +#include +#include + +struct UsbHidMedia { + View* view; +}; + +typedef struct { + bool left_pressed; + bool up_pressed; + bool right_pressed; + bool down_pressed; + bool ok_pressed; + bool connected; +} UsbHidMediaModel; + +static void usb_hid_media_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) { + canvas_draw_triangle(canvas, x, y, 5, 3, dir); + if(dir == CanvasDirectionBottomToTop) { + canvas_draw_dot(canvas, x, y - 1); + } else if(dir == CanvasDirectionTopToBottom) { + canvas_draw_dot(canvas, x, y + 1); + } else if(dir == CanvasDirectionRightToLeft) { + canvas_draw_dot(canvas, x - 1, y); + } else if(dir == CanvasDirectionLeftToRight) { + canvas_draw_dot(canvas, x + 1, y); + } +} + +static void usb_hid_media_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + UsbHidMediaModel* model = context; + + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Media"); + canvas_set_font(canvas, FontSecondary); + + // Keypad circles + canvas_draw_icon(canvas, 76, 8, &I_Circles_47x47); + + // Up + if(model->up_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 93, 9, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 96, 12, &I_Volup_8x6); + canvas_set_color(canvas, ColorBlack); + + // Down + if(model->down_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 93, 41, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 96, 45, &I_Voldwn_6x6); + canvas_set_color(canvas, ColorBlack); + + // Left + if(model->left_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 77, 25, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + usb_hid_media_draw_arrow(canvas, 82, 31, CanvasDirectionRightToLeft); + usb_hid_media_draw_arrow(canvas, 86, 31, CanvasDirectionRightToLeft); + canvas_set_color(canvas, ColorBlack); + + // Right + if(model->right_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 109, 25, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + usb_hid_media_draw_arrow(canvas, 112, 31, CanvasDirectionLeftToRight); + usb_hid_media_draw_arrow(canvas, 116, 31, CanvasDirectionLeftToRight); + canvas_set_color(canvas, ColorBlack); + + // Ok + if(model->ok_pressed) { + canvas_draw_icon(canvas, 93, 25, &I_Pressed_Button_13x13); + canvas_set_color(canvas, ColorWhite); + } + usb_hid_media_draw_arrow(canvas, 96, 31, CanvasDirectionLeftToRight); + canvas_draw_line(canvas, 100, 29, 100, 33); + canvas_draw_line(canvas, 102, 29, 102, 33); + canvas_set_color(canvas, ColorBlack); + + // Exit + canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8); + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit"); +} + +static void usb_hid_media_process_press(UsbHidMedia* usb_hid_media, InputEvent* event) { + with_view_model( + usb_hid_media->view, + UsbHidMediaModel * model, + { + if(event->key == InputKeyUp) { + model->up_pressed = true; + furi_hal_hid_consumer_key_press(HID_CONSUMER_VOLUME_INCREMENT); + } else if(event->key == InputKeyDown) { + model->down_pressed = true; + furi_hal_hid_consumer_key_press(HID_CONSUMER_VOLUME_DECREMENT); + } else if(event->key == InputKeyLeft) { + model->left_pressed = true; + furi_hal_hid_consumer_key_press(HID_CONSUMER_SCAN_PREVIOUS_TRACK); + } else if(event->key == InputKeyRight) { + model->right_pressed = true; + furi_hal_hid_consumer_key_press(HID_CONSUMER_SCAN_NEXT_TRACK); + } else if(event->key == InputKeyOk) { + model->ok_pressed = true; + furi_hal_hid_consumer_key_press(HID_CONSUMER_PLAY_PAUSE); + } + }, + true); +} + +static void hid_media_process_release(UsbHidMedia* usb_hid_media, InputEvent* event) { + with_view_model( + usb_hid_media->view, + UsbHidMediaModel * model, + { + if(event->key == InputKeyUp) { + model->up_pressed = false; + furi_hal_hid_consumer_key_release(HID_CONSUMER_VOLUME_INCREMENT); + } else if(event->key == InputKeyDown) { + model->down_pressed = false; + furi_hal_hid_consumer_key_release(HID_CONSUMER_VOLUME_DECREMENT); + } else if(event->key == InputKeyLeft) { + model->left_pressed = false; + furi_hal_hid_consumer_key_release(HID_CONSUMER_SCAN_PREVIOUS_TRACK); + } else if(event->key == InputKeyRight) { + model->right_pressed = false; + furi_hal_hid_consumer_key_release(HID_CONSUMER_SCAN_NEXT_TRACK); + } else if(event->key == InputKeyOk) { + model->ok_pressed = false; + furi_hal_hid_consumer_key_release(HID_CONSUMER_PLAY_PAUSE); + } + }, + true); +} + +static bool usb_hid_media_input_callback(InputEvent* event, void* context) { + furi_assert(context); + UsbHidMedia* usb_hid_media = context; + bool consumed = false; + + if(event->type == InputTypePress) { + usb_hid_media_process_press(usb_hid_media, event); + consumed = true; + } else if(event->type == InputTypeRelease) { + hid_media_process_release(usb_hid_media, event); + consumed = true; + } else if(event->type == InputTypeShort) { + if(event->key == InputKeyBack) { + furi_hal_hid_kb_release_all(); + } + } + + return consumed; +} + +UsbHidMedia* usb_hid_media_alloc() { + UsbHidMedia* usb_hid_media = malloc(sizeof(UsbHidMedia)); + usb_hid_media->view = view_alloc(); + view_set_context(usb_hid_media->view, usb_hid_media); + view_allocate_model(usb_hid_media->view, ViewModelTypeLocking, sizeof(UsbHidMediaModel)); + view_set_draw_callback(usb_hid_media->view, usb_hid_media_draw_callback); + view_set_input_callback(usb_hid_media->view, usb_hid_media_input_callback); + + with_view_model( + usb_hid_media->view, UsbHidMediaModel * model, { model->connected = true; }, true); + + return usb_hid_media; +} + +void usb_hid_media_free(UsbHidMedia* usb_hid_media) { + furi_assert(usb_hid_media); + view_free(usb_hid_media->view); + free(usb_hid_media); +} + +View* usb_hid_media_get_view(UsbHidMedia* usb_hid_media) { + furi_assert(usb_hid_media); + return usb_hid_media->view; +} + +void usb_hid_media_set_connected_status(UsbHidMedia* usb_hid_media, bool connected) { + furi_assert(usb_hid_media); + with_view_model( + usb_hid_media->view, UsbHidMediaModel * model, { model->connected = connected; }, true); +} diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/views/usb_hid_media.h b/Applications/Official/DEV_FW/source/usbkeyboard/views/usb_hid_media.h new file mode 100644 index 000000000..441e2bd6d --- /dev/null +++ b/Applications/Official/DEV_FW/source/usbkeyboard/views/usb_hid_media.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +typedef struct UsbHidMedia UsbHidMedia; + +UsbHidMedia* usb_hid_media_alloc(); + +void usb_hid_media_free(UsbHidMedia* usb_hid_media); + +View* usb_hid_media_get_view(UsbHidMedia* usb_hid_media); + +void usb_hid_media_set_connected_status(UsbHidMedia* usb_hid_media, bool connected); diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/views/usb_hid_mouse.c b/Applications/Official/DEV_FW/source/usbkeyboard/views/usb_hid_mouse.c new file mode 100644 index 000000000..27f2ac105 --- /dev/null +++ b/Applications/Official/DEV_FW/source/usbkeyboard/views/usb_hid_mouse.c @@ -0,0 +1,215 @@ +#include "usb_hid_mouse.h" +#include +#include +#include +#include + +struct UsbHidMouse { + View* view; +}; +#define MOUSE_MOVE_SHORT 5 +#define MOUSE_MOVE_LONG 20 + +typedef struct { + bool left_pressed; + bool up_pressed; + bool right_pressed; + bool down_pressed; + bool left_mouse_pressed; + bool left_mouse_held; + bool right_mouse_pressed; + bool connected; +} UsbHidMouseModel; + +static void usb_hid_mouse_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + UsbHidMouseModel* model = context; + + // Header + /*if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + }*/ + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse"); + canvas_set_font(canvas, FontSecondary); + + if(model->left_mouse_held == true) { + elements_multiline_text_aligned(canvas, 0, 62, AlignLeft, AlignBottom, "Selecting..."); + } else { + canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8); + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit"); + } + + // Keypad circles + canvas_draw_icon(canvas, 64, 8, &I_Circles_47x47); + + // Up + if(model->up_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 81, 9, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 84, 10, &I_Pin_arrow_up_7x9); + canvas_set_color(canvas, ColorBlack); + + // Down + if(model->down_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 81, 41, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 84, 43, &I_Pin_arrow_down_7x9); + canvas_set_color(canvas, ColorBlack); + + // Left + if(model->left_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 65, 25, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 67, 28, &I_Pin_arrow_left_9x7); + canvas_set_color(canvas, ColorBlack); + + // Right + if(model->right_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 97, 25, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 99, 28, &I_Pin_arrow_right_9x7); + canvas_set_color(canvas, ColorBlack); + + // Ok + if(model->left_mouse_pressed) { + canvas_draw_icon(canvas, 81, 25, &I_Ok_btn_pressed_13x13); + } else { + canvas_draw_icon(canvas, 83, 27, &I_Left_mouse_icon_9x9); + } + + // Back + if(model->right_mouse_pressed) { + canvas_draw_icon(canvas, 108, 48, &I_Ok_btn_pressed_13x13); + } else { + canvas_draw_icon(canvas, 110, 50, &I_Right_mouse_icon_9x9); + } +} + +static void usb_hid_mouse_process(UsbHidMouse* usb_hid_mouse, InputEvent* event) { + with_view_model( + usb_hid_mouse->view, + UsbHidMouseModel * model, + { + if(event->key == InputKeyBack) { + if(event->type == InputTypeShort) { + furi_hal_hid_mouse_press(HID_MOUSE_BTN_RIGHT); + furi_hal_hid_mouse_release(HID_MOUSE_BTN_RIGHT); + } else if(event->type == InputTypePress) { + model->right_mouse_pressed = true; + } else if(event->type == InputTypeRelease) { + model->right_mouse_pressed = false; + } + } else if(event->key == InputKeyOk) { + if(event->type == InputTypeShort) { + // Just release if it was being held before + if(!model->left_mouse_held) furi_hal_hid_mouse_press(HID_MOUSE_BTN_LEFT); + furi_hal_hid_mouse_release(HID_MOUSE_BTN_LEFT); + model->left_mouse_held = false; + } else if(event->type == InputTypeLong) { + furi_hal_hid_mouse_press(HID_MOUSE_BTN_LEFT); + model->left_mouse_held = true; + model->left_mouse_pressed = true; + } else if(event->type == InputTypePress) { + model->left_mouse_pressed = true; + } else if(event->type == InputTypeRelease) { + // Only release if it wasn't a long press + if(!model->left_mouse_held) model->left_mouse_pressed = false; + } + + } else if(event->key == InputKeyRight) { + if(event->type == InputTypePress) { + model->right_pressed = true; + furi_hal_hid_mouse_move(MOUSE_MOVE_SHORT, 0); + } else if(event->type == InputTypeRepeat) { + furi_hal_hid_mouse_move(MOUSE_MOVE_LONG, 0); + } else if(event->type == InputTypeRelease) { + model->right_pressed = false; + } + } else if(event->key == InputKeyLeft) { + if(event->type == InputTypePress) { + model->left_pressed = true; + furi_hal_hid_mouse_move(-MOUSE_MOVE_SHORT, 0); + } else if(event->type == InputTypeRepeat) { + furi_hal_hid_mouse_move(-MOUSE_MOVE_LONG, 0); + } else if(event->type == InputTypeRelease) { + model->left_pressed = false; + } + } else if(event->key == InputKeyDown) { + if(event->type == InputTypePress) { + model->down_pressed = true; + furi_hal_hid_mouse_move(0, MOUSE_MOVE_SHORT); + } else if(event->type == InputTypeRepeat) { + furi_hal_hid_mouse_move(0, MOUSE_MOVE_LONG); + } else if(event->type == InputTypeRelease) { + model->down_pressed = false; + } + } else if(event->key == InputKeyUp) { + if(event->type == InputTypePress) { + model->up_pressed = true; + furi_hal_hid_mouse_move(0, -MOUSE_MOVE_SHORT); + } else if(event->type == InputTypeRepeat) { + furi_hal_hid_mouse_move(0, -MOUSE_MOVE_LONG); + } else if(event->type == InputTypeRelease) { + model->up_pressed = false; + } + } + }, + true); +} + +static bool usb_hid_mouse_input_callback(InputEvent* event, void* context) { + furi_assert(context); + UsbHidMouse* usb_hid_mouse = context; + bool consumed = false; + + if(event->type == InputTypeLong && event->key == InputKeyBack) { + furi_hal_hid_mouse_release(HID_MOUSE_BTN_LEFT); + furi_hal_hid_mouse_release(HID_MOUSE_BTN_RIGHT); + } else { + usb_hid_mouse_process(usb_hid_mouse, event); + consumed = true; + } + + return consumed; +} + +UsbHidMouse* usb_hid_mouse_alloc() { + UsbHidMouse* usb_hid_mouse = malloc(sizeof(UsbHidMouse)); + usb_hid_mouse->view = view_alloc(); + view_set_context(usb_hid_mouse->view, usb_hid_mouse); + view_allocate_model(usb_hid_mouse->view, ViewModelTypeLocking, sizeof(UsbHidMouseModel)); + view_set_draw_callback(usb_hid_mouse->view, usb_hid_mouse_draw_callback); + view_set_input_callback(usb_hid_mouse->view, usb_hid_mouse_input_callback); + + with_view_model( + usb_hid_mouse->view, UsbHidMouseModel * model, { model->connected = true; }, true); + + return usb_hid_mouse; +} + +void usb_hid_mouse_free(UsbHidMouse* usb_hid_mouse) { + furi_assert(usb_hid_mouse); + view_free(usb_hid_mouse->view); + free(usb_hid_mouse); +} + +View* usb_hid_mouse_get_view(UsbHidMouse* usb_hid_mouse) { + furi_assert(usb_hid_mouse); + return usb_hid_mouse->view; +} diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/views/usb_hid_mouse.h b/Applications/Official/DEV_FW/source/usbkeyboard/views/usb_hid_mouse.h new file mode 100644 index 000000000..e7eec9224 --- /dev/null +++ b/Applications/Official/DEV_FW/source/usbkeyboard/views/usb_hid_mouse.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +typedef struct UsbHidMouse UsbHidMouse; + +UsbHidMouse* usb_hid_mouse_alloc(); + +void usb_hid_mouse_free(UsbHidMouse* usb_hid_mouse); + +View* usb_hid_mouse_get_view(UsbHidMouse* usb_hid_mouse); + +void usb_hid_mouse_set_connected_status(UsbHidMouse* usb_hid_mouse, bool connected); diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/views/usb_hid_mouse_jiggler.c b/Applications/Official/DEV_FW/source/usbkeyboard/views/usb_hid_mouse_jiggler.c new file mode 100644 index 000000000..fe0c618d2 --- /dev/null +++ b/Applications/Official/DEV_FW/source/usbkeyboard/views/usb_hid_mouse_jiggler.c @@ -0,0 +1,130 @@ +#include "usb_hid_mouse_jiggler.h" +#include +#include +#include + +#include + +#define TAG "HidMouseJiggler" + +struct HidMouseJiggler { + View* view; + FuriTimer* timer; +}; + +typedef struct { + bool running; + uint8_t counter; +} HidMouseJigglerModel; + +static void hid_mouse_jiggler_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + HidMouseJigglerModel* model = context; + + // Header + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse Jiggler"); + + canvas_set_font(canvas, FontPrimary); + elements_multiline_text(canvas, AlignLeft, 35, "Press Start\nto jiggle"); + canvas_set_font(canvas, FontSecondary); + + // Ok + canvas_draw_icon(canvas, 63, 25, &I_Space_65x18); + if(model->running) { + elements_slightly_rounded_box(canvas, 66, 27, 60, 13); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9); + if(model->running) { + elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Stop"); + } else { + elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Start"); + } + canvas_set_color(canvas, ColorBlack); + + // Back + canvas_draw_icon(canvas, 74, 49, &I_Pin_back_arrow_10x8); + elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Quit"); +} + +static void hid_mouse_jiggler_timer_callback(void* context) { + furi_assert(context); + HidMouseJiggler* hid_mouse_jiggler = context; + with_view_model( + hid_mouse_jiggler->view, + HidMouseJigglerModel * model, + { + if(model->running) { + model->counter++; + furi_hal_hid_mouse_move( + (model->counter % 2 == 0) ? MOUSE_MOVE_SHORT : -MOUSE_MOVE_SHORT, 0); + } + }, + false); +} + +static void hid_mouse_jiggler_enter_callback(void* context) { + furi_assert(context); + HidMouseJiggler* hid_mouse_jiggler = context; + + furi_timer_start(hid_mouse_jiggler->timer, 500); +} + +static void hid_mouse_jiggler_exit_callback(void* context) { + furi_assert(context); + HidMouseJiggler* hid_mouse_jiggler = context; + furi_timer_stop(hid_mouse_jiggler->timer); +} + +static bool hid_mouse_jiggler_input_callback(InputEvent* event, void* context) { + furi_assert(context); + HidMouseJiggler* hid_mouse_jiggler = context; + + bool consumed = false; + + if(event->key == InputKeyOk) { + with_view_model( + hid_mouse_jiggler->view, + HidMouseJigglerModel * model, + { model->running = !model->running; }, + true); + consumed = true; + } + + return consumed; +} + +HidMouseJiggler* hid_mouse_jiggler_alloc() { + HidMouseJiggler* hid_mouse_jiggler = malloc(sizeof(HidMouseJiggler)); + + hid_mouse_jiggler->view = view_alloc(); + view_set_context(hid_mouse_jiggler->view, hid_mouse_jiggler); + view_allocate_model( + hid_mouse_jiggler->view, ViewModelTypeLocking, sizeof(HidMouseJigglerModel)); + view_set_draw_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_draw_callback); + view_set_input_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_input_callback); + view_set_enter_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_enter_callback); + view_set_exit_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_exit_callback); + + hid_mouse_jiggler->timer = furi_timer_alloc( + hid_mouse_jiggler_timer_callback, FuriTimerTypePeriodic, hid_mouse_jiggler); + + return hid_mouse_jiggler; +} + +void hid_mouse_jiggler_free(HidMouseJiggler* hid_mouse_jiggler) { + furi_assert(hid_mouse_jiggler); + + furi_timer_stop(hid_mouse_jiggler->timer); + furi_timer_free(hid_mouse_jiggler->timer); + + view_free(hid_mouse_jiggler->view); + + free(hid_mouse_jiggler); +} + +View* hid_mouse_jiggler_get_view(HidMouseJiggler* hid_mouse_jiggler) { + furi_assert(hid_mouse_jiggler); + return hid_mouse_jiggler->view; +} diff --git a/Applications/Official/DEV_FW/source/usbkeyboard/views/usb_hid_mouse_jiggler.h b/Applications/Official/DEV_FW/source/usbkeyboard/views/usb_hid_mouse_jiggler.h new file mode 100644 index 000000000..24cf877a7 --- /dev/null +++ b/Applications/Official/DEV_FW/source/usbkeyboard/views/usb_hid_mouse_jiggler.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#define MOUSE_MOVE_SHORT 5 +#define MOUSE_MOVE_LONG 20 + +typedef struct HidMouseJiggler HidMouseJiggler; + +HidMouseJiggler* hid_mouse_jiggler_alloc(); + +void hid_mouse_jiggler_free(HidMouseJiggler* hid_mouse_jiggler); + +View* hid_mouse_jiggler_get_view(HidMouseJiggler* hid_mouse_jiggler); diff --git a/Applications/Official/DEV_FW/source/wav_player/application.fam b/Applications/Official/DEV_FW/source/wav_player/application.fam new file mode 100644 index 000000000..ec0c76291 --- /dev/null +++ b/Applications/Official/DEV_FW/source/wav_player/application.fam @@ -0,0 +1,12 @@ +App( + appid="WAV_Player", + name="WAV Player", + apptype=FlipperAppType.EXTERNAL, + entry_point="wav_player_app", + cdefines=["APP_WAV_PLAYER"], + stack_size=4 * 1024, + order=60, + fap_icon="wav_10px.png", + fap_category="Music", + fap_icon_assets="images", +) diff --git a/Applications/Official/DEV_FW/source/wav_player/images/music_10px.png b/Applications/Official/DEV_FW/source/wav_player/images/music_10px.png new file mode 100644 index 000000000..d41eb0db8 Binary files /dev/null and b/Applications/Official/DEV_FW/source/wav_player/images/music_10px.png differ diff --git a/Applications/Official/DEV_FW/source/wav_player/wav_10px.png b/Applications/Official/DEV_FW/source/wav_player/wav_10px.png new file mode 100644 index 000000000..54ff554a1 Binary files /dev/null and b/Applications/Official/DEV_FW/source/wav_player/wav_10px.png differ diff --git a/Applications/Official/DEV_FW/source/wav_player/wav_parser.c b/Applications/Official/DEV_FW/source/wav_player/wav_parser.c new file mode 100644 index 000000000..c2897706c --- /dev/null +++ b/Applications/Official/DEV_FW/source/wav_player/wav_parser.c @@ -0,0 +1,84 @@ +#include "wav_parser.h" + +#define TAG "WavParser" + +const char* format_text(FormatTag tag) { + switch(tag) { + case FormatTagPCM: + return "PCM"; + case FormatTagIEEE_FLOAT: + return "IEEE FLOAT"; + default: + return "Unknown"; + } +}; + +struct WavParser { + WavHeaderChunk header; + WavFormatChunk format; + WavDataChunk data; + size_t wav_data_start; + size_t wav_data_end; +}; + +WavParser* wav_parser_alloc() { + return malloc(sizeof(WavParser)); +} + +void wav_parser_free(WavParser* parser) { + free(parser); +} + +bool wav_parser_parse(WavParser* parser, Stream* stream) { + stream_read(stream, (uint8_t*)&parser->header, sizeof(WavHeaderChunk)); + stream_read(stream, (uint8_t*)&parser->format, sizeof(WavFormatChunk)); + stream_read(stream, (uint8_t*)&parser->data, sizeof(WavDataChunk)); + + if(memcmp(parser->header.riff, "RIFF", 4) != 0 || + memcmp(parser->header.wave, "WAVE", 4) != 0) { + FURI_LOG_E(TAG, "WAV: wrong header"); + return false; + } + + if(memcmp(parser->format.fmt, "fmt ", 4) != 0) { + FURI_LOG_E(TAG, "WAV: wrong format"); + return false; + } + + if(parser->format.tag != FormatTagPCM || memcmp(parser->data.data, "data", 4) != 0) { + FURI_LOG_E( + TAG, + "WAV: non-PCM format %u, next '%lu'", + parser->format.tag, + (uint32_t)parser->data.data); + return false; + } + + FURI_LOG_I( + TAG, + "Format tag: %s, ch: %u, smplrate: %lu, bps: %lu, bits: %u", + format_text(parser->format.tag), + parser->format.channels, + parser->format.sample_rate, + parser->format.byte_per_sec, + parser->format.bits_per_sample); + + parser->wav_data_start = stream_tell(stream); + parser->wav_data_end = parser->wav_data_start + parser->data.size; + + FURI_LOG_I(TAG, "data: %u - %u", parser->wav_data_start, parser->wav_data_end); + + return true; +} + +size_t wav_parser_get_data_start(WavParser* parser) { + return parser->wav_data_start; +} + +size_t wav_parser_get_data_end(WavParser* parser) { + return parser->wav_data_end; +} + +size_t wav_parser_get_data_len(WavParser* parser) { + return parser->wav_data_end - parser->wav_data_start; +} diff --git a/Applications/Official/DEV_FW/source/wav_player/wav_parser.h b/Applications/Official/DEV_FW/source/wav_player/wav_parser.h new file mode 100644 index 000000000..f50c48b3f --- /dev/null +++ b/Applications/Official/DEV_FW/source/wav_player/wav_parser.h @@ -0,0 +1,51 @@ +#pragma once +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + FormatTagPCM = 0x0001, + FormatTagIEEE_FLOAT = 0x0003, +} FormatTag; + +typedef struct { + uint8_t riff[4]; + uint32_t size; + uint8_t wave[4]; +} WavHeaderChunk; + +typedef struct { + uint8_t fmt[4]; + uint32_t size; + uint16_t tag; + uint16_t channels; + uint32_t sample_rate; + uint32_t byte_per_sec; + uint16_t block_align; + uint16_t bits_per_sample; +} WavFormatChunk; + +typedef struct { + uint8_t data[4]; + uint32_t size; +} WavDataChunk; + +typedef struct WavParser WavParser; + +WavParser* wav_parser_alloc(); + +void wav_parser_free(WavParser* parser); + +bool wav_parser_parse(WavParser* parser, Stream* stream); + +size_t wav_parser_get_data_start(WavParser* parser); + +size_t wav_parser_get_data_end(WavParser* parser); + +size_t wav_parser_get_data_len(WavParser* parser); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/wav_player/wav_player.c b/Applications/Official/DEV_FW/source/wav_player/wav_player.c new file mode 100644 index 000000000..0a57162ff --- /dev/null +++ b/Applications/Official/DEV_FW/source/wav_player/wav_player.c @@ -0,0 +1,323 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "wav_player_hal.h" +#include "wav_parser.h" +#include "wav_player_view.h" +#include +#include + +#include + +#define TAG "WavPlayer" + +#define WAVPLAYER_FOLDER "/ext/wav_player" + +static bool open_wav_stream(Stream* stream) { + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + bool result = false; + FuriString* path; + path = furi_string_alloc(); + furi_string_set(path, WAVPLAYER_FOLDER); + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options(&browser_options, ".wav", &I_music_10px); + browser_options.base_path = WAVPLAYER_FOLDER; + browser_options.hide_ext = false; + + bool ret = dialog_file_browser_show(dialogs, path, path, &browser_options); + + furi_record_close(RECORD_DIALOGS); + if(ret) { + if(!file_stream_open(stream, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) { + FURI_LOG_E(TAG, "Cannot open file \"%s\"", furi_string_get_cstr(path)); + } else { + result = true; + } + } + furi_string_free(path); + return result; +} + +typedef enum { + WavPlayerEventHalfTransfer, + WavPlayerEventFullTransfer, + WavPlayerEventCtrlVolUp, + WavPlayerEventCtrlVolDn, + WavPlayerEventCtrlMoveL, + WavPlayerEventCtrlMoveR, + WavPlayerEventCtrlOk, + WavPlayerEventCtrlBack, +} WavPlayerEventType; + +typedef struct { + WavPlayerEventType type; +} WavPlayerEvent; + +static void wav_player_dma_isr(void* ctx) { + FuriMessageQueue* event_queue = ctx; + + // half of transfer + if(LL_DMA_IsActiveFlag_HT1(DMA1)) { + LL_DMA_ClearFlag_HT1(DMA1); + // fill first half of buffer + WavPlayerEvent event = {.type = WavPlayerEventHalfTransfer}; + furi_message_queue_put(event_queue, &event, 0); + } + + // transfer complete + if(LL_DMA_IsActiveFlag_TC1(DMA1)) { + LL_DMA_ClearFlag_TC1(DMA1); + // fill second half of buffer + WavPlayerEvent event = {.type = WavPlayerEventFullTransfer}; + furi_message_queue_put(event_queue, &event, 0); + } +} + +typedef struct { + Storage* storage; + Stream* stream; + WavParser* parser; + uint16_t* sample_buffer; + uint8_t* tmp_buffer; + + size_t samples_count_half; + size_t samples_count; + + FuriMessageQueue* queue; + + float volume; + bool play; + + WavPlayerView* view; + ViewDispatcher* view_dispatcher; + Gui* gui; + NotificationApp* notification; +} WavPlayerApp; + +static WavPlayerApp* app_alloc() { + WavPlayerApp* app = malloc(sizeof(WavPlayerApp)); + app->samples_count_half = 1024 * 4; + app->samples_count = app->samples_count_half * 2; + app->storage = furi_record_open(RECORD_STORAGE); + app->stream = file_stream_alloc(app->storage); + app->parser = wav_parser_alloc(); + app->sample_buffer = malloc(sizeof(uint16_t) * app->samples_count); + app->tmp_buffer = malloc(sizeof(uint8_t) * app->samples_count); + app->queue = furi_message_queue_alloc(10, sizeof(WavPlayerEvent)); + + app->volume = 10.0f; + app->play = true; + + app->gui = furi_record_open(RECORD_GUI); + app->view_dispatcher = view_dispatcher_alloc(); + app->view = wav_player_view_alloc(); + + view_dispatcher_add_view(app->view_dispatcher, 0, wav_player_view_get_view(app->view)); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + view_dispatcher_switch_to_view(app->view_dispatcher, 0); + + app->notification = furi_record_open(RECORD_NOTIFICATION); + notification_message(app->notification, &sequence_display_backlight_enforce_on); + + return app; +} + +static void app_free(WavPlayerApp* app) { + view_dispatcher_remove_view(app->view_dispatcher, 0); + view_dispatcher_free(app->view_dispatcher); + wav_player_view_free(app->view); + furi_record_close(RECORD_GUI); + + furi_message_queue_free(app->queue); + free(app->tmp_buffer); + free(app->sample_buffer); + wav_parser_free(app->parser); + stream_free(app->stream); + furi_record_close(RECORD_STORAGE); + + notification_message(app->notification, &sequence_display_backlight_enforce_auto); + furi_record_close(RECORD_NOTIFICATION); + free(app); +} + +// TODO: that works only with 8-bit 2ch audio +static bool fill_data(WavPlayerApp* app, size_t index) { + uint16_t* sample_buffer_start = &app->sample_buffer[index]; + size_t count = stream_read(app->stream, app->tmp_buffer, app->samples_count); + + for(size_t i = count; i < app->samples_count; i++) { + app->tmp_buffer[i] = 0; + } + + for(size_t i = 0; i < app->samples_count; i += 2) { + float data = app->tmp_buffer[i]; + data -= UINT8_MAX / 2; // to signed + data /= UINT8_MAX / 2; // scale -1..1 + + data *= app->volume; // volume + data = tanhf(data); // hyperbolic tangent limiter + + 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 / 2] = data; + } + + wav_player_view_set_data(app->view, sample_buffer_start, app->samples_count_half); + + return count != app->samples_count; +} + +static void ctrl_callback(WavPlayerCtrl ctrl, void* ctx) { + FuriMessageQueue* event_queue = ctx; + WavPlayerEvent event; + + switch(ctrl) { + case WavPlayerCtrlVolUp: + event.type = WavPlayerEventCtrlVolUp; + furi_message_queue_put(event_queue, &event, 0); + break; + case WavPlayerCtrlVolDn: + event.type = WavPlayerEventCtrlVolDn; + furi_message_queue_put(event_queue, &event, 0); + break; + case WavPlayerCtrlMoveL: + event.type = WavPlayerEventCtrlMoveL; + furi_message_queue_put(event_queue, &event, 0); + break; + case WavPlayerCtrlMoveR: + event.type = WavPlayerEventCtrlMoveR; + furi_message_queue_put(event_queue, &event, 0); + break; + case WavPlayerCtrlOk: + event.type = WavPlayerEventCtrlOk; + furi_message_queue_put(event_queue, &event, 0); + break; + case WavPlayerCtrlBack: + event.type = WavPlayerEventCtrlBack; + furi_message_queue_put(event_queue, &event, 0); + break; + default: + break; + } +} + +static void app_run(WavPlayerApp* app) { + if(!open_wav_stream(app->stream)) return; + if(!wav_parser_parse(app->parser, app->stream)) return; + + wav_player_view_set_volume(app->view, app->volume); + wav_player_view_set_start(app->view, wav_parser_get_data_start(app->parser)); + wav_player_view_set_current(app->view, stream_tell(app->stream)); + wav_player_view_set_end(app->view, wav_parser_get_data_end(app->parser)); + wav_player_view_set_play(app->view, app->play); + + wav_player_view_set_context(app->view, app->queue); + wav_player_view_set_ctrl_callback(app->view, ctrl_callback); + + bool eof = fill_data(app, 0); + eof = fill_data(app, app->samples_count_half); + + wav_player_speaker_init(); + wav_player_dma_init((uint32_t)app->sample_buffer, app->samples_count); + + furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, wav_player_dma_isr, app->queue); + + if(furi_hal_speaker_acquire(1000)) { + wav_player_dma_start(); + wav_player_speaker_start(); + + WavPlayerEvent event; + + while(1) { + if(furi_message_queue_get(app->queue, &event, FuriWaitForever) == FuriStatusOk) { + if(event.type == WavPlayerEventHalfTransfer) { + eof = fill_data(app, 0); + wav_player_view_set_current(app->view, stream_tell(app->stream)); + if(eof) { + stream_seek( + app->stream, + wav_parser_get_data_start(app->parser), + StreamOffsetFromStart); + } + + } else if(event.type == WavPlayerEventFullTransfer) { + eof = fill_data(app, app->samples_count_half); + wav_player_view_set_current(app->view, stream_tell(app->stream)); + if(eof) { + stream_seek( + app->stream, + wav_parser_get_data_start(app->parser), + StreamOffsetFromStart); + } + } else if(event.type == WavPlayerEventCtrlVolUp) { + if(app->volume < 9.9) app->volume += 0.4; + wav_player_view_set_volume(app->view, app->volume); + } else if(event.type == WavPlayerEventCtrlVolDn) { + if(app->volume > 0.01) app->volume -= 0.4; + wav_player_view_set_volume(app->view, app->volume); + } else if(event.type == WavPlayerEventCtrlMoveL) { + int32_t seek = + stream_tell(app->stream) - wav_parser_get_data_start(app->parser); + seek = + MIN(seek, (int32_t)(wav_parser_get_data_len(app->parser) / (size_t)100)); + stream_seek(app->stream, -seek, StreamOffsetFromCurrent); + wav_player_view_set_current(app->view, stream_tell(app->stream)); + } else if(event.type == WavPlayerEventCtrlMoveR) { + int32_t seek = wav_parser_get_data_end(app->parser) - stream_tell(app->stream); + seek = + MIN(seek, (int32_t)(wav_parser_get_data_len(app->parser) / (size_t)100)); + stream_seek(app->stream, seek, StreamOffsetFromCurrent); + wav_player_view_set_current(app->view, stream_tell(app->stream)); + } else if(event.type == WavPlayerEventCtrlOk) { + app->play = !app->play; + wav_player_view_set_play(app->view, app->play); + + if(!app->play) { + wav_player_speaker_stop(); + } else { + wav_player_speaker_start(); + } + } else if(event.type == WavPlayerEventCtrlBack) { + break; + } + } + } + + wav_player_speaker_stop(); + wav_player_dma_stop(); + furi_hal_speaker_release(); + } + + furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, NULL, NULL); +} + +int32_t wav_player_app(void* p) { + UNUSED(p); + WavPlayerApp* app = app_alloc(); + + Storage* storage = furi_record_open(RECORD_STORAGE); + if(!storage_simply_mkdir(storage, WAVPLAYER_FOLDER)) { + FURI_LOG_E(TAG, "Could not create folder %s", WAVPLAYER_FOLDER); + } + furi_record_close(RECORD_STORAGE); + + app_run(app); + app_free(app); + return 0; +} diff --git a/Applications/Official/DEV_FW/source/wav_player/wav_player_hal.c b/Applications/Official/DEV_FW/source/wav_player/wav_player_hal.c new file mode 100644 index 000000000..ad0c019be --- /dev/null +++ b/Applications/Official/DEV_FW/source/wav_player/wav_player_hal.c @@ -0,0 +1,58 @@ +#include "wav_player_hal.h" +#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 + +void wav_player_speaker_init() { + LL_TIM_InitTypeDef TIM_InitStruct = {0}; + TIM_InitStruct.Prescaler = 4; + TIM_InitStruct.Autoreload = 255; + 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 wav_player_speaker_start() { + LL_TIM_EnableAllOutputs(FURI_HAL_SPEAKER_TIMER); + LL_TIM_EnableCounter(FURI_HAL_SPEAKER_TIMER); +} + +void wav_player_speaker_stop() { + LL_TIM_DisableAllOutputs(FURI_HAL_SPEAKER_TIMER); + LL_TIM_DisableCounter(FURI_HAL_SPEAKER_TIMER); +} + +void wav_player_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 wav_player_dma_start() { + LL_DMA_EnableChannel(DMA_INSTANCE); + LL_TIM_EnableDMAReq_UPDATE(FURI_HAL_SPEAKER_TIMER); +} + +void wav_player_dma_stop() { + LL_DMA_DisableChannel(DMA_INSTANCE); +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/wav_player/wav_player_hal.h b/Applications/Official/DEV_FW/source/wav_player/wav_player_hal.h new file mode 100644 index 000000000..124f51406 --- /dev/null +++ b/Applications/Official/DEV_FW/source/wav_player/wav_player_hal.h @@ -0,0 +1,23 @@ +#pragma once +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void wav_player_speaker_init(); + +void wav_player_speaker_start(); + +void wav_player_speaker_stop(); + +void wav_player_dma_init(uint32_t address, size_t size); + +void wav_player_dma_start(); + +void wav_player_dma_stop(); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/wav_player/wav_player_view.c b/Applications/Official/DEV_FW/source/wav_player/wav_player_view.c new file mode 100644 index 000000000..fdf08cb21 --- /dev/null +++ b/Applications/Official/DEV_FW/source/wav_player/wav_player_view.c @@ -0,0 +1,201 @@ +#include "wav_player_view.h" + +#define DATA_COUNT 116 + +struct WavPlayerView { + View* view; + WavPlayerCtrlCallback callback; + void* context; +}; + +typedef struct { + bool play; + float volume; + size_t start; + size_t end; + size_t current; + uint8_t data[DATA_COUNT]; +} WavPlayerViewModel; + +float map(float x, float in_min, float in_max, float out_min, float out_max) { + return (x - in_min) * (out_max - out_min + 1) / (in_max - in_min + 1) + out_min; +} + +static void wav_player_view_draw_callback(Canvas* canvas, void* _model) { + WavPlayerViewModel* model = _model; + + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + uint8_t x_pos = 0; + uint8_t y_pos = 0; + + // volume + x_pos = 124; + y_pos = 0; + const float volume = (64 / 10.0f) * model->volume; + canvas_draw_frame(canvas, x_pos, y_pos, 4, 64); + canvas_draw_box(canvas, x_pos, y_pos + (64 - volume), 4, volume); + + // play / pause + x_pos = 58; + y_pos = 55; + if(!model->play) { + canvas_draw_line(canvas, x_pos, y_pos, x_pos + 8, y_pos + 4); + canvas_draw_line(canvas, x_pos, y_pos + 8, x_pos + 8, y_pos + 4); + canvas_draw_line(canvas, x_pos, y_pos + 8, x_pos, y_pos); + } else { + canvas_draw_box(canvas, x_pos, y_pos, 3, 9); + canvas_draw_box(canvas, x_pos + 4, y_pos, 3, 9); + } + + x_pos = 78; + y_pos = 55; + canvas_draw_line(canvas, x_pos, y_pos, x_pos + 4, y_pos + 4); + canvas_draw_line(canvas, x_pos, y_pos + 8, x_pos + 4, y_pos + 4); + canvas_draw_line(canvas, x_pos, y_pos + 8, x_pos, y_pos); + + x_pos = 82; + y_pos = 55; + canvas_draw_line(canvas, x_pos, y_pos, x_pos + 4, y_pos + 4); + canvas_draw_line(canvas, x_pos, y_pos + 8, x_pos + 4, y_pos + 4); + canvas_draw_line(canvas, x_pos, y_pos + 8, x_pos, y_pos); + + x_pos = 40; + y_pos = 55; + canvas_draw_line(canvas, x_pos, y_pos, x_pos - 4, y_pos + 4); + canvas_draw_line(canvas, x_pos, y_pos + 8, x_pos - 4, y_pos + 4); + canvas_draw_line(canvas, x_pos, y_pos + 8, x_pos, y_pos); + + x_pos = 44; + y_pos = 55; + canvas_draw_line(canvas, x_pos, y_pos, x_pos - 4, y_pos + 4); + canvas_draw_line(canvas, x_pos, y_pos + 8, x_pos - 4, y_pos + 4); + canvas_draw_line(canvas, x_pos, y_pos + 8, x_pos, y_pos); + + // len + x_pos = 4; + y_pos = 47; + const uint8_t play_len = 116; + uint8_t play_pos = map(model->current, model->start, model->end, 0, play_len - 4); + + canvas_draw_frame(canvas, x_pos, y_pos, play_len, 4); + canvas_draw_box(canvas, x_pos + play_pos, y_pos - 2, 4, 8); + canvas_draw_box(canvas, x_pos, y_pos, play_pos, 4); + + // osc + x_pos = 4; + y_pos = 0; + for(size_t i = 1; i < DATA_COUNT; i++) { + canvas_draw_line(canvas, x_pos + i - 1, model->data[i - 1], x_pos + i, model->data[i]); + } +} + +static bool wav_player_view_input_callback(InputEvent* event, void* context) { + WavPlayerView* wav_player_view = context; + bool consumed = false; + + if(wav_player_view->callback) { + if(event->type == InputTypeShort || event->type == InputTypeRepeat) { + if(event->key == InputKeyUp) { + wav_player_view->callback(WavPlayerCtrlVolUp, wav_player_view->context); + consumed = true; + } else if(event->key == InputKeyDown) { + wav_player_view->callback(WavPlayerCtrlVolDn, wav_player_view->context); + consumed = true; + } else if(event->key == InputKeyLeft) { + wav_player_view->callback(WavPlayerCtrlMoveL, wav_player_view->context); + consumed = true; + } else if(event->key == InputKeyRight) { + wav_player_view->callback(WavPlayerCtrlMoveR, wav_player_view->context); + consumed = true; + } else if(event->key == InputKeyOk) { + wav_player_view->callback(WavPlayerCtrlOk, wav_player_view->context); + consumed = true; + } else if(event->key == InputKeyBack) { + wav_player_view->callback(WavPlayerCtrlBack, wav_player_view->context); + consumed = true; + } + } + } + + return consumed; +} + +WavPlayerView* wav_player_view_alloc() { + WavPlayerView* wav_view = malloc(sizeof(WavPlayerView)); + wav_view->view = view_alloc(); + view_set_context(wav_view->view, wav_view); + view_allocate_model(wav_view->view, ViewModelTypeLocking, sizeof(WavPlayerViewModel)); + view_set_draw_callback(wav_view->view, wav_player_view_draw_callback); + view_set_input_callback(wav_view->view, wav_player_view_input_callback); + + return wav_view; +} + +void wav_player_view_free(WavPlayerView* wav_view) { + furi_assert(wav_view); + view_free(wav_view->view); + free(wav_view); +} + +View* wav_player_view_get_view(WavPlayerView* wav_view) { + furi_assert(wav_view); + return wav_view->view; +} + +void wav_player_view_set_volume(WavPlayerView* wav_view, float volume) { + furi_assert(wav_view); + with_view_model( + wav_view->view, WavPlayerViewModel * model, { model->volume = volume; }, true); +} + +void wav_player_view_set_start(WavPlayerView* wav_view, size_t start) { + furi_assert(wav_view); + with_view_model( + wav_view->view, WavPlayerViewModel * model, { model->start = start; }, true); +} + +void wav_player_view_set_end(WavPlayerView* wav_view, size_t end) { + furi_assert(wav_view); + with_view_model( + wav_view->view, WavPlayerViewModel * model, { model->end = end; }, true); +} + +void wav_player_view_set_current(WavPlayerView* wav_view, size_t current) { + furi_assert(wav_view); + with_view_model( + wav_view->view, WavPlayerViewModel * model, { model->current = current; }, true); +} + +void wav_player_view_set_play(WavPlayerView* wav_view, bool play) { + furi_assert(wav_view); + with_view_model( + wav_view->view, WavPlayerViewModel * model, { model->play = play; }, true); +} + +void wav_player_view_set_data(WavPlayerView* wav_view, uint16_t* data, size_t data_count) { + furi_assert(wav_view); + with_view_model( + wav_view->view, + WavPlayerViewModel * model, + { + size_t inc = (data_count / DATA_COUNT) - 1; + + for(size_t i = 0; i < DATA_COUNT; i++) { + model->data[i] = *data / 6; + if(model->data[i] > 42) model->data[i] = 42; + data += inc; + } + }, + true); +} + +void wav_player_view_set_ctrl_callback(WavPlayerView* wav_view, WavPlayerCtrlCallback callback) { + furi_assert(wav_view); + wav_view->callback = callback; +} + +void wav_player_view_set_context(WavPlayerView* wav_view, void* context) { + furi_assert(wav_view); + wav_view->context = context; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/wav_player/wav_player_view.h b/Applications/Official/DEV_FW/source/wav_player/wav_player_view.h new file mode 100644 index 000000000..246aeaf3e --- /dev/null +++ b/Applications/Official/DEV_FW/source/wav_player/wav_player_view.h @@ -0,0 +1,45 @@ +#pragma once +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct WavPlayerView WavPlayerView; + +typedef enum { + WavPlayerCtrlVolUp, + WavPlayerCtrlVolDn, + WavPlayerCtrlMoveL, + WavPlayerCtrlMoveR, + WavPlayerCtrlOk, + WavPlayerCtrlBack, +} WavPlayerCtrl; + +typedef void (*WavPlayerCtrlCallback)(WavPlayerCtrl ctrl, void* context); + +WavPlayerView* wav_player_view_alloc(); + +void wav_player_view_free(WavPlayerView* wav_view); + +View* wav_player_view_get_view(WavPlayerView* wav_view); + +void wav_player_view_set_volume(WavPlayerView* wav_view, float volume); + +void wav_player_view_set_start(WavPlayerView* wav_view, size_t start); + +void wav_player_view_set_end(WavPlayerView* wav_view, size_t end); + +void wav_player_view_set_current(WavPlayerView* wav_view, size_t current); + +void wav_player_view_set_play(WavPlayerView* wav_view, bool play); + +void wav_player_view_set_data(WavPlayerView* wav_view, uint16_t* data, size_t data_count); + +void wav_player_view_set_ctrl_callback(WavPlayerView* wav_view, WavPlayerCtrlCallback callback); + +void wav_player_view_set_context(WavPlayerView* wav_view, void* context); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/wifi_scanner/FlipperZeroWiFiModuleDefines.h b/Applications/Official/DEV_FW/source/wifi_scanner/FlipperZeroWiFiModuleDefines.h new file mode 100644 index 000000000..7b28ebd64 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wifi_scanner/application.fam b/Applications/Official/DEV_FW/source/wifi_scanner/application.fam new file mode 100644 index 000000000..bc7d352eb --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wifi_scanner/wifi_10px.png b/Applications/Official/DEV_FW/source/wifi_scanner/wifi_10px.png new file mode 100644 index 000000000..c13534660 Binary files /dev/null and b/Applications/Official/DEV_FW/source/wifi_scanner/wifi_10px.png differ diff --git a/Applications/Official/DEV_FW/source/wifi_scanner/wifi_scanner.c b/Applications/Official/DEV_FW/source/wifi_scanner/wifi_scanner.c new file mode 100644 index 000000000..826048e26 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/LICENSE b/Applications/Official/DEV_FW/source/wii_ec_anal/LICENSE new file mode 100644 index 000000000..95e544a06 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/README.md b/Applications/Official/DEV_FW/source/wii_ec_anal/README.md new file mode 100644 index 000000000..8d439c7e0 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/README.txt b/Applications/Official/DEV_FW/source/wii_ec_anal/README.txt new file mode 100644 index 000000000..e7ebe7a4c --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/WiiEC.png b/Applications/Official/DEV_FW/source/wii_ec_anal/WiiEC.png new file mode 100644 index 000000000..6e1afcb0c Binary files /dev/null and b/Applications/Official/DEV_FW/source/wii_ec_anal/WiiEC.png differ diff --git a/Applications/Official/DEV_FW/source/wii_ec_anal/_image_tool/LICENSE b/Applications/Official/DEV_FW/source/wii_ec_anal/_image_tool/LICENSE new file mode 100644 index 000000000..95e544a06 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/_image_tool/README b/Applications/Official/DEV_FW/source/wii_ec_anal/_image_tool/README new file mode 100644 index 000000000..979605a08 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/_image_tool/_convert.c b/Applications/Official/DEV_FW/source/wii_ec_anal/_image_tool/_convert.c new file mode 100644 index 000000000..57deeb083 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/_image_tool/_convert.sh b/Applications/Official/DEV_FW/source/wii_ec_anal/_image_tool/_convert.sh new file mode 100644 index 000000000..aaa7977b5 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/_image_tool/_convert_images.c b/Applications/Official/DEV_FW/source/wii_ec_anal/_image_tool/_convert_images.c new file mode 100644 index 000000000..e8ab899f7 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/_image_tool/_convert_images.h b/Applications/Official/DEV_FW/source/wii_ec_anal/_image_tool/_convert_images.h new file mode 100644 index 000000000..1743cb409 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/_image_tool/_convert_test.c b/Applications/Official/DEV_FW/source/wii_ec_anal/_image_tool/_convert_test.c new file mode 100644 index 000000000..fdc2ee946 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/_images/CLASSIC.png b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/CLASSIC.png new file mode 100644 index 000000000..aa5318b33 Binary files /dev/null and b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/CLASSIC.png differ diff --git a/Applications/Official/DEV_FW/source/wii_ec_anal/_images/CLASSIC_N.png b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/CLASSIC_N.png new file mode 100644 index 000000000..24f4ac225 Binary files /dev/null and b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/CLASSIC_N.png differ diff --git a/Applications/Official/DEV_FW/source/wii_ec_anal/_images/DEBUG.png b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/DEBUG.png new file mode 100644 index 000000000..bca35c693 Binary files /dev/null and b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/DEBUG.png differ diff --git a/Applications/Official/DEV_FW/source/wii_ec_anal/_images/DUMP.png b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/DUMP.png new file mode 100644 index 000000000..dc9328aab Binary files /dev/null and b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/DUMP.png differ diff --git a/Applications/Official/DEV_FW/source/wii_ec_anal/_images/GIMP/Nunchuck_acc.xcf b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/GIMP/Nunchuck_acc.xcf new file mode 100644 index 000000000..67f70139e Binary files /dev/null and b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/GIMP/Nunchuck_acc.xcf differ diff --git a/Applications/Official/DEV_FW/source/wii_ec_anal/_images/GIMP/RIP.xcf b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/GIMP/RIP.xcf new file mode 100644 index 000000000..0058fe9c8 Binary files /dev/null and b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/GIMP/RIP.xcf differ diff --git a/Applications/Official/DEV_FW/source/wii_ec_anal/_images/GIMP/Wiring.xcf b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/GIMP/Wiring.xcf new file mode 100644 index 000000000..aa8078db8 Binary files /dev/null and b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/GIMP/Wiring.xcf differ diff --git a/Applications/Official/DEV_FW/source/wii_ec_anal/_images/GIMP/classic.xcf b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/GIMP/classic.xcf new file mode 100644 index 000000000..6fd152675 Binary files /dev/null and b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/GIMP/classic.xcf differ diff --git a/Applications/Official/DEV_FW/source/wii_ec_anal/_images/GIMP/csLogo.xcf b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/GIMP/csLogo.xcf new file mode 100644 index 000000000..f4e33844a Binary files /dev/null and b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/GIMP/csLogo.xcf differ diff --git a/Applications/Official/DEV_FW/source/wii_ec_anal/_images/GIMP/fonts.xcf b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/GIMP/fonts.xcf new file mode 100644 index 000000000..d05d03fc7 Binary files /dev/null and b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/GIMP/fonts.xcf differ diff --git a/Applications/Official/DEV_FW/source/wii_ec_anal/_images/GIMP/frame.xcf b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/GIMP/frame.xcf new file mode 100644 index 000000000..31705cf72 Binary files /dev/null and b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/GIMP/frame.xcf differ diff --git a/Applications/Official/DEV_FW/source/wii_ec_anal/_images/GIMP/port.xcf b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/GIMP/port.xcf new file mode 100644 index 000000000..10fcd2de2 Binary files /dev/null and b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/GIMP/port.xcf differ diff --git a/Applications/Official/DEV_FW/source/wii_ec_anal/_images/GIMP/social.xcf b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/GIMP/social.xcf new file mode 100644 index 000000000..377eaa63b Binary files /dev/null and b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/GIMP/social.xcf differ diff --git a/Applications/Official/DEV_FW/source/wii_ec_anal/_images/NUNCHUCK.png b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/NUNCHUCK.png new file mode 100644 index 000000000..bc31ae386 Binary files /dev/null and b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/NUNCHUCK.png differ diff --git a/Applications/Official/DEV_FW/source/wii_ec_anal/_images/NUNCHUCK_acc.png b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/NUNCHUCK_acc.png new file mode 100644 index 000000000..895c85e4c Binary files /dev/null and b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/NUNCHUCK_acc.png differ diff --git a/Applications/Official/DEV_FW/source/wii_ec_anal/_images/NUNCHUCK_anal.png b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/NUNCHUCK_anal.png new file mode 100644 index 000000000..e821d7ee2 Binary files /dev/null and b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/NUNCHUCK_anal.png differ diff --git a/Applications/Official/DEV_FW/source/wii_ec_anal/_images/NUNCHUCK_cal.gif b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/NUNCHUCK_cal.gif new file mode 100644 index 000000000..72d807a54 Binary files /dev/null and b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/NUNCHUCK_cal.gif differ diff --git a/Applications/Official/DEV_FW/source/wii_ec_anal/_images/NUNCHUCK_cal.png b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/NUNCHUCK_cal.png new file mode 100644 index 000000000..f9d34bb93 Binary files /dev/null and b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/NUNCHUCK_cal.png differ diff --git a/Applications/Official/DEV_FW/source/wii_ec_anal/_images/Nunchucky.png b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/Nunchucky.png new file mode 100644 index 000000000..3af395da6 Binary files /dev/null and b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/Nunchucky.png differ diff --git a/Applications/Official/DEV_FW/source/wii_ec_anal/_images/RIP.png b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/RIP.png new file mode 100644 index 000000000..0acfe0c00 Binary files /dev/null and b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/RIP.png differ diff --git a/Applications/Official/DEV_FW/source/wii_ec_anal/_images/SPLASH.png b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/SPLASH.png new file mode 100644 index 000000000..a5c3f093a Binary files /dev/null and b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/SPLASH.png differ diff --git a/Applications/Official/DEV_FW/source/wii_ec_anal/_images/WAIT.png b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/WAIT.png new file mode 100644 index 000000000..776edc3f1 Binary files /dev/null and b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/WAIT.png differ diff --git a/Applications/Official/DEV_FW/source/wii_ec_anal/_images/WiiChuck.png b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/WiiChuck.png new file mode 100644 index 000000000..532ce3096 Binary files /dev/null and b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/WiiChuck.png differ diff --git a/Applications/Official/DEV_FW/source/wii_ec_anal/_images/Wiring.png b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/Wiring.png new file mode 100644 index 000000000..300c07ee4 Binary files /dev/null and b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/Wiring.png differ diff --git a/Applications/Official/DEV_FW/source/wii_ec_anal/_images/plug.png b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/plug.png new file mode 100644 index 000000000..c418f43b1 Binary files /dev/null and b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/plug.png differ diff --git a/Applications/Official/DEV_FW/source/wii_ec_anal/_images/social.png b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/social.png new file mode 100644 index 000000000..1d3eddcc5 Binary files /dev/null and b/Applications/Official/DEV_FW/source/wii_ec_anal/_images/social.png differ diff --git a/Applications/Official/DEV_FW/source/wii_ec_anal/application.fam b/Applications/Official/DEV_FW/source/wii_ec_anal/application.fam new file mode 100644 index 000000000..42ed7e979 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/bc_logging.h b/Applications/Official/DEV_FW/source/wii_ec_anal/bc_logging.h new file mode 100644 index 000000000..73dda80bd --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/err.h b/Applications/Official/DEV_FW/source/wii_ec_anal/err.h new file mode 100644 index 000000000..5a25c93f8 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/images.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/images.c new file mode 100644 index 000000000..e8ab899f7 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/images.h b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/images.h new file mode 100644 index 000000000..d21909176 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_3x5_0.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_3x5_0.c new file mode 100644 index 000000000..8fc8e0e14 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_3x5_1.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_3x5_1.c new file mode 100644 index 000000000..8b7d4cf80 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_3x5_2.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_3x5_2.c new file mode 100644 index 000000000..89a81c75e --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_3x5_3.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_3x5_3.c new file mode 100644 index 000000000..97ff0478a --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_3x5_4.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_3x5_4.c new file mode 100644 index 000000000..2bbd9ef42 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_3x5_5.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_3x5_5.c new file mode 100644 index 000000000..e0466f37a --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_3x5_6.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_3x5_6.c new file mode 100644 index 000000000..1b62caf72 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_3x5_7.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_3x5_7.c new file mode 100644 index 000000000..acfe57cf8 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_3x5_8.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_3x5_8.c new file mode 100644 index 000000000..31f32af52 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_3x5_9.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_3x5_9.c new file mode 100644 index 000000000..4b1ba1e09 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_3x5_v.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_3x5_v.c new file mode 100644 index 000000000..2282e1697 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_0.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_0.c new file mode 100644 index 000000000..7ae2186b3 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_1.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_1.c new file mode 100644 index 000000000..c1a9cec74 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_2.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_2.c new file mode 100644 index 000000000..7fab90010 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_3.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_3.c new file mode 100644 index 000000000..2099bf795 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_4.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_4.c new file mode 100644 index 000000000..1eee4f07d --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_5.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_5.c new file mode 100644 index 000000000..be1e54681 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_6.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_6.c new file mode 100644 index 000000000..da155c1b5 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_7.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_7.c new file mode 100644 index 000000000..fde7e8ea2 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_8.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_8.c new file mode 100644 index 000000000..aff178282 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_9.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_9.c new file mode 100644 index 000000000..2417c57e8 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_A.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_A.c new file mode 100644 index 000000000..910c034a2 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_B.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_B.c new file mode 100644 index 000000000..93808fee2 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_C.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_C.c new file mode 100644 index 000000000..1438eaf44 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_D.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_D.c new file mode 100644 index 000000000..9c6b590ee --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_E.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_E.c new file mode 100644 index 000000000..bc15fb240 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_F.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_5x7_F.c new file mode 100644 index 000000000..e4ad0db69 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_0.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_0.c new file mode 100644 index 000000000..952cf34d8 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_1.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_1.c new file mode 100644 index 000000000..846a6876c --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_2.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_2.c new file mode 100644 index 000000000..4534bb67c --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_3.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_3.c new file mode 100644 index 000000000..7e79eb03a --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_4.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_4.c new file mode 100644 index 000000000..324b036ce --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_5.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_5.c new file mode 100644 index 000000000..cdfda5f2b --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_6.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_6.c new file mode 100644 index 000000000..781a060f1 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_7.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_7.c new file mode 100644 index 000000000..fec5f4bf4 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_8.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_8.c new file mode 100644 index 000000000..a5b21c375 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_9.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_9.c new file mode 100644 index 000000000..f7707c0df --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_A.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_A.c new file mode 100644 index 000000000..1bb65c902 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_B.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_B.c new file mode 100644 index 000000000..00e012d53 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_C.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_C.c new file mode 100644 index 000000000..694901009 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_D.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_D.c new file mode 100644 index 000000000..a95e760eb --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_E.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_E.c new file mode 100644 index 000000000..f49503f00 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_F.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_F.c new file mode 100644 index 000000000..0037b2544 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_G.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_G.c new file mode 100644 index 000000000..f30bc9952 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_X.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_X.c new file mode 100644 index 000000000..4735e82a1 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_Y.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_Y.c new file mode 100644 index 000000000..508e786bd --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_Z.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_Z.c new file mode 100644 index 000000000..c42d560ac --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_d_.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_d_.c new file mode 100644 index 000000000..1f8123a6c --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_n_.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_n_.c new file mode 100644 index 000000000..15d403d28 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_v_.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_6x8_v_.c new file mode 100644 index 000000000..1229701a1 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_RIP.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_RIP.c new file mode 100644 index 000000000..c20877ef0 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_Cable.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_Cable.c new file mode 100644 index 000000000..f4ac26173 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_Joy.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_Joy.c new file mode 100644 index 000000000..5054103b3 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_Main.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_Main.c new file mode 100644 index 000000000..b29a9ab57 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_btn_A1.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_btn_A1.c new file mode 100644 index 000000000..0889b2a08 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_btn_B1.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_btn_B1.c new file mode 100644 index 000000000..bbf5fba1a --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_btn_X1.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_btn_X1.c new file mode 100644 index 000000000..2352ba695 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_btn_Y1.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_btn_Y1.c new file mode 100644 index 000000000..d7192e3e7 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_pad_LR1.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_pad_LR1.c new file mode 100644 index 000000000..300ed5eee --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_pad_UD1.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_pad_UD1.c new file mode 100644 index 000000000..feb32d283 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_trg_L1.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_trg_L1.c new file mode 100644 index 000000000..c70e35334 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_trg_L2.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_trg_L2.c new file mode 100644 index 000000000..47561ab98 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_trg_L3.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_trg_L3.c new file mode 100644 index 000000000..0b51bed35 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_trg_L4.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_trg_L4.c new file mode 100644 index 000000000..062caca77 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_trg_R1.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_trg_R1.c new file mode 100644 index 000000000..6f08886d3 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_trg_R2.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_trg_R2.c new file mode 100644 index 000000000..d85e45761 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_trg_R3.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_trg_R3.c new file mode 100644 index 000000000..082d160e2 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_trg_R4.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_cc_trg_R4.c new file mode 100644 index 000000000..0395058b8 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_csLogo_FULL.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_csLogo_FULL.c new file mode 100644 index 000000000..a8c030fa2 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_csLogo_Small.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_csLogo_Small.c new file mode 100644 index 000000000..71debc2ff --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_ecp_SCL.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_ecp_SCL.c new file mode 100644 index 000000000..e3622a626 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_ecp_SDA.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_ecp_SDA.c new file mode 100644 index 000000000..5ce0cbec4 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_ecp_port.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_ecp_port.c new file mode 100644 index 000000000..60f535458 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_key_Back.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_key_Back.c new file mode 100644 index 000000000..23c17fe2b --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_key_D.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_key_D.c new file mode 100644 index 000000000..689b9148c --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_key_L.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_key_L.c new file mode 100644 index 000000000..a5fca1a21 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_key_OK.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_key_OK.c new file mode 100644 index 000000000..926d91c2e --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_key_OKi.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_key_OKi.c new file mode 100644 index 000000000..aa6f9e692 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_key_R.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_key_R.c new file mode 100644 index 000000000..8b97c7b48 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_key_U.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_key_U.c new file mode 100644 index 000000000..65f4cd9e0 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/gfx/img_key_Ui.c b/Applications/Official/DEV_FW/source/wii_ec_anal/gfx/img_key_Ui.c new file mode 100644 index 000000000..30c60c66e --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/i2c_workaround.h b/Applications/Official/DEV_FW/source/wii_ec_anal/i2c_workaround.h new file mode 100644 index 000000000..b24efaf48 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/info.sh b/Applications/Official/DEV_FW/source/wii_ec_anal/info.sh new file mode 100644 index 000000000..e009eb118 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/notes.txt b/Applications/Official/DEV_FW/source/wii_ec_anal/notes.txt new file mode 100644 index 000000000..61b6e29af --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/wii_anal.c b/Applications/Official/DEV_FW/source/wii_ec_anal/wii_anal.c new file mode 100644 index 000000000..f0af1c9c5 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/wii_anal.h b/Applications/Official/DEV_FW/source/wii_ec_anal/wii_anal.h new file mode 100644 index 000000000..3aae61fdc --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/wii_anal_ec.c b/Applications/Official/DEV_FW/source/wii_ec_anal/wii_anal_ec.c new file mode 100644 index 000000000..dab167bc0 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/wii_anal_ec.h b/Applications/Official/DEV_FW/source/wii_ec_anal/wii_anal_ec.h new file mode 100644 index 000000000..eec6b523c --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/wii_anal_keys.c b/Applications/Official/DEV_FW/source/wii_ec_anal/wii_anal_keys.c new file mode 100644 index 000000000..8c5c99b4e --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/wii_anal_keys.h b/Applications/Official/DEV_FW/source/wii_ec_anal/wii_anal_keys.h new file mode 100644 index 000000000..c10fcd1ef --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/wii_anal_lcd.c b/Applications/Official/DEV_FW/source/wii_ec_anal/wii_anal_lcd.c new file mode 100644 index 000000000..921a3708e --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/wii_anal_lcd.h b/Applications/Official/DEV_FW/source/wii_ec_anal/wii_anal_lcd.h new file mode 100644 index 000000000..e52a3adc6 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/wii_anal_ver.h b/Applications/Official/DEV_FW/source/wii_ec_anal/wii_anal_ver.h new file mode 100644 index 000000000..3f2c8c0e6 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/wii_ec.c b/Applications/Official/DEV_FW/source/wii_ec_anal/wii_ec.c new file mode 100644 index 000000000..00dcbf922 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/wii_ec.h b/Applications/Official/DEV_FW/source/wii_ec_anal/wii_ec.h new file mode 100644 index 000000000..a28453740 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/wii_ec_classic.c b/Applications/Official/DEV_FW/source/wii_ec_anal/wii_ec_classic.c new file mode 100644 index 000000000..5bd3398ca --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/wii_ec_macros.h b/Applications/Official/DEV_FW/source/wii_ec_anal/wii_ec_macros.h new file mode 100644 index 000000000..00ab9825b --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/wii_ec_nunchuck.c b/Applications/Official/DEV_FW/source/wii_ec_anal/wii_ec_nunchuck.c new file mode 100644 index 000000000..d88d535b6 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/wii_ec_udraw.c b/Applications/Official/DEV_FW/source/wii_ec_anal/wii_ec_udraw.c new file mode 100644 index 000000000..82987b205 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/wii_ec_udraw.h b/Applications/Official/DEV_FW/source/wii_ec_anal/wii_ec_udraw.h new file mode 100644 index 000000000..9283fd95d --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/wii_i2c.c b/Applications/Official/DEV_FW/source/wii_ec_anal/wii_i2c.c new file mode 100644 index 000000000..f5d6840d9 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Official/DEV_FW/source/wii_ec_anal/wii_i2c.h b/Applications/Official/DEV_FW/source/wii_ec_anal/wii_i2c.h new file mode 100644 index 000000000..efebefcf9 --- /dev/null +++ b/Applications/Official/DEV_FW/source/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/Applications/Official/DEV_FW/source/zombiez/application.fam b/Applications/Official/DEV_FW/source/zombiez/application.fam new file mode 100644 index 000000000..3245187d2 --- /dev/null +++ b/Applications/Official/DEV_FW/source/zombiez/application.fam @@ -0,0 +1,12 @@ +App( + appid="Zombiez", + name="Zombiez", + apptype=FlipperAppType.EXTERNAL, + entry_point="zombiez_game_app", + cdefines=["APP_ZOMBIEZ_GAME"], + requires=["gui"], + stack_size=2 * 1024, + order=280, + fap_icon="zombie_10px.png", + fap_category="Games", +) diff --git a/Applications/Official/DEV_FW/source/zombiez/zombie_10px.png b/Applications/Official/DEV_FW/source/zombiez/zombie_10px.png new file mode 100644 index 000000000..37363ec04 Binary files /dev/null and b/Applications/Official/DEV_FW/source/zombiez/zombie_10px.png differ diff --git a/Applications/Official/DEV_FW/source/zombiez/zombiez.c b/Applications/Official/DEV_FW/source/zombiez/zombiez.c new file mode 100644 index 000000000..bd2a5c97d --- /dev/null +++ b/Applications/Official/DEV_FW/source/zombiez/zombiez.c @@ -0,0 +1,400 @@ +#include +#include +#include +#include + +//ORIGINAL REPO: https://github.com/Dooskington/flipperzero-zombiez +//AUTHORS: https://github.com/Dooskington | https://github.com/DevMilanIan + +#include "zombiez.h" + +#define ZOMBIES_MAX 3 +#define ZOMBIES_WIDTH 5 +#define ZOMBIES_HEIGHT 8 +#define PROJECTILES_MAX 10 + +#define MIN_Y 5 +#define MAX_Y 58 +#define WALL_X 16 +#define PLAYER_START_X 8 +#define PLAYER_START_Y (MAX_Y - MIN_Y) / 2 + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} PluginEvent; + +typedef enum { GameStatePlaying, GameStateGameOver } GameState; + +typedef struct { + int x; + int y; +} Point; + +typedef struct { + Point position; + int hp; +} Player; + +typedef struct { + Point position; + int hp; +} Zombie; + +typedef struct { + Point position; +} Projectile; + +typedef struct { + GameState game_state; + Player player; + + size_t zombies_count; + Zombie* zombies[ZOMBIES_MAX]; + + size_t projectiles_count; + Projectile* projectiles[PROJECTILES_MAX]; + + uint16_t score; + bool input_shoot; +} 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_draw_frame(canvas, 0, 0, 128, 64); + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned( + canvas, + plugin_state->player.position.x, + plugin_state->player.position.y, + AlignCenter, + AlignCenter, + "@"); + + canvas_draw_line(canvas, WALL_X, 0, WALL_X, 64); + canvas_draw_line(canvas, WALL_X + 2, 4, WALL_X + 2, 59); + + for(int i = 0; i < PROJECTILES_MAX; ++i) { + Projectile* p = plugin_state->projectiles[i]; + if(p != NULL) { + canvas_draw_disc(canvas, p->position.x, p->position.y, 3); + } + } + + for(int i = 0; i < ZOMBIES_MAX; ++i) { + Zombie* z = plugin_state->zombies[i]; + if(z != NULL) { + for(int h = 0; h < ZOMBIES_HEIGHT; h++) { + for(int w = 0; w < ZOMBIES_WIDTH; w++) { + // Switch animation + int zIdx = 0; + if(z->position.x % 2 == 0) { + zIdx = 1; + } + + // Draw zombie pixels + if(zombie_array[zIdx][h][w] == 1) { + int x = z->position.x + w; + int y = z->position.y + h; + + canvas_draw_dot(canvas, x, y); + } + } + } + } + } + + int heart; + if((plugin_state->player.hp - 10) > 5) { // 16, 17, 18, 19, 20 + heart = 0; + } else if((plugin_state->player.hp - 5) > 5) { // 11, 12, 13, 14, 15 + heart = 1; + } else if((plugin_state->player.hp - 3) > 2) { // 6, 7, 8, 9, 10 + heart = 2; + } else if(plugin_state->player.hp > 0) { // 1, 2, 3, 4, 5 + heart = 3; + } else { // 0 + heart = 4; + } + // visual representation of health + for(int h = 0; h < 5; h++) { + for(int w = 0; w < 5; w++) { + if(heart_array[heart][h][w] == 1) { + int x = 124 - w; + int y = 56 + h; + + canvas_draw_dot(canvas, x, y); + } + } + } + + // buffer hp + score + char hpBuffer[8]; + char scoreBuffer[14]; + + if(plugin_state->game_state == GameStatePlaying) { + // display ammo / reload + if(plugin_state->projectiles_count >= PROJECTILES_MAX) { + canvas_draw_str_aligned(canvas, 24, 10, AlignLeft, AlignCenter, "RELOAD"); + } else { + for(uint8_t i = 0; i < (PROJECTILES_MAX - plugin_state->projectiles_count); i++) { + canvas_draw_box(canvas, 24 + (4 * i), 6, 2, 4); + } + } + // display hp + score + snprintf(hpBuffer, sizeof(hpBuffer), "%u", plugin_state->player.hp); + canvas_draw_str_aligned(canvas, 118, 62, AlignRight, AlignBottom, hpBuffer); + + snprintf(scoreBuffer, sizeof(scoreBuffer), "%u", plugin_state->score); + canvas_draw_str_aligned(canvas, 126, 10, AlignRight, AlignBottom, scoreBuffer); + } + // Game Over banner + if(plugin_state->game_state == GameStateGameOver) { + // Screen is 128x64 px + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 34, 20, 62, 24); + + canvas_set_color(canvas, ColorBlack); + canvas_draw_frame(canvas, 34, 20, 62, 24); + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 37, 31, "Game Over"); + + canvas_set_font(canvas, FontSecondary); + snprintf(scoreBuffer, sizeof(scoreBuffer), "Score: %u", plugin_state->score); + canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignBottom, scoreBuffer); + } + + //char* info = (char*)malloc(16 * sizeof(char)); + //asprintf(&info, "%d, %d", plugin_state->x, plugin_state->y); + //canvas_draw_str_aligned(canvas, 32, 16, AlignLeft, AlignBottom, info); + //free(info); + + 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 tick(PluginState* const plugin_state) { + if(plugin_state->input_shoot && (plugin_state->projectiles_count < PROJECTILES_MAX)) { + Projectile* p = (Projectile*)malloc(sizeof(Projectile)); + p->position.x = plugin_state->player.position.x; + p->position.y = plugin_state->player.position.y; + + size_t idx = plugin_state->projectiles_count; + plugin_state->projectiles[idx] = p; + plugin_state->projectiles_count += 1; + } + + for(int i = 0; i < ZOMBIES_MAX; ++i) { + if(!plugin_state->zombies[i]) { + Zombie* z = (Zombie*)malloc(sizeof(Zombie)); + //z->hp = 20; + z->position.x = 126; + z->position.y = MIN_Y + (rand() % (MAX_Y - MIN_Y)); + + plugin_state->zombies[i] = z; + plugin_state->zombies_count += 1; + } + } + + for(int i = 0; i < PROJECTILES_MAX; ++i) { + Projectile* p = plugin_state->projectiles[i]; + if(p != NULL) { + p->position.x += 2; + + for(int i = 0; i < ZOMBIES_MAX; ++i) { + Zombie* z = plugin_state->zombies[i]; + if(z != NULL) { + if( // projectile close enough to zombie + (((z->position.x - p->position.x) <= 2) && + ((z->position.y - p->position.y) <= 4)) && + (((p->position.x - z->position.x) <= 2) && + ((p->position.y - z->position.y) <= 6))) { + //z->hp -= 5; + //if(z->hp <= 0) { + plugin_state->zombies_count -= 1; + free(z); + plugin_state->zombies[i] = NULL; + plugin_state->score++; + //if(plugin_state->score % 15 == 0) DOLPHIN_DEED(getRandomDeed()); + //} + } else if(z->position.x <= WALL_X && z->position.x > 0) { // zombie got to the wall + plugin_state->zombies_count -= 1; + free(z); + plugin_state->zombies[i] = NULL; + if(plugin_state->player.hp > 0) { + plugin_state->player.hp--; + } else { + plugin_state->game_state = GameStateGameOver; + } + } else { + if(furi_get_tick() % 2 == 0) z->position.x--; + } + } + } + + if(p->position.x >= 128) { + free(p); + plugin_state->projectiles[i] = NULL; + } + } + } + + plugin_state->input_shoot = false; +} + +static void timer_callback(void* ctx) { + furi_assert(ctx); + FuriMessageQueue* event_queue = ctx; + PluginEvent event = {.type = EventTypeTick}; + furi_message_queue_put(event_queue, &event, 0); +} + +static void zombiez_state_init(PluginState* const plugin_state) { + plugin_state->player.position.x = PLAYER_START_X; + plugin_state->player.position.y = PLAYER_START_Y; + plugin_state->player.hp = 20; + + plugin_state->projectiles_count = 0; + plugin_state->zombies_count = 0; + plugin_state->score = 0; + + for(int i = 0; i < PROJECTILES_MAX; i++) { + plugin_state->projectiles[i] = NULL; + } + + for(int i = 0; i < ZOMBIES_MAX; i++) { + plugin_state->zombies[i] = NULL; + } + + plugin_state->game_state = GameStatePlaying; + plugin_state->input_shoot = false; +} + +int32_t zombiez_game_app(void* p) { + UNUSED(p); + uint32_t return_code = 0; + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); + + PluginState* plugin_state = malloc(sizeof(PluginState)); + zombiez_state_init(plugin_state); + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, plugin_state, sizeof(PluginState))) { + FURI_LOG_E("Zombiez", "cannot create mutex\r\n"); + return_code = 255; + goto free_and_exit; + } + + // 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); + + FuriTimer* timer = furi_timer_alloc(timer_callback, FuriTimerTypePeriodic, event_queue); + furi_timer_start(timer, furi_kernel_get_tick_frequency() / 22); + + // Open GUI and register view_port + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + PluginEvent event; + bool isRunning = true; + while(isRunning) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + PluginState* plugin_state = (PluginState*)acquire_mutex_block(&state_mutex); + if(event_status == FuriStatusOk) { + if(event.type == EventTypeKey) { + if(event.input.type == InputTypePress) { + switch(event.input.key) { + case InputKeyUp: + if(plugin_state->player.position.y > MIN_Y && + plugin_state->game_state == GameStatePlaying) { + plugin_state->player.position.y--; + } + break; + case InputKeyDown: + if(plugin_state->player.position.y < MAX_Y && + plugin_state->game_state == GameStatePlaying) { + plugin_state->player.position.y++; + } + break; + case InputKeyOk: + if(plugin_state->projectiles_count < PROJECTILES_MAX && + plugin_state->game_state == GameStatePlaying) { + plugin_state->input_shoot = true; + } + break; + case InputKeyBack: + break; + default: + break; + } + } else if( + event.input.type == InputTypeRepeat && + plugin_state->game_state == GameStatePlaying) { + switch(event.input.key) { + case InputKeyUp: + if(plugin_state->player.position.y > (MIN_Y + 1)) { + plugin_state->player.position.y -= 4; + } + break; + case InputKeyDown: + if(plugin_state->player.position.y < (MAX_Y - 1)) { + plugin_state->player.position.y += 4; + } + break; + default: + break; + } + } else if(event.input.type == InputTypeLong) { + if(event.input.key == InputKeyOk) { + if(plugin_state->game_state == GameStateGameOver) { + zombiez_state_init(plugin_state); + } else if(plugin_state->projectiles_count >= PROJECTILES_MAX) { + plugin_state->projectiles_count = 0; + plugin_state->player.hp++; + } + } else if(event.input.key == InputKeyBack) { + isRunning = false; + } + } + } else if(event.type == EventTypeTick) { + tick(plugin_state); + } + } else { + // event timeout + } + + view_port_update(view_port); + release_mutex(&state_mutex, plugin_state); + } + + furi_timer_free(timer); + 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); + +free_and_exit: + free(plugin_state); + furi_message_queue_free(event_queue); + + return return_code; +} \ No newline at end of file diff --git a/Applications/Official/DEV_FW/source/zombiez/zombiez.h b/Applications/Official/DEV_FW/source/zombiez/zombiez.h new file mode 100644 index 000000000..eea71d707 --- /dev/null +++ b/Applications/Official/DEV_FW/source/zombiez/zombiez.h @@ -0,0 +1,62 @@ +#include + +uint8_t zombie_array[2][8][5] = { + { + {0, 0, 1, 1, 1}, + {0, 0, 1, 1, 1}, + {1, 1, 1, 1, 1}, + {0, 0, 1, 1, 1}, + {0, 0, 1, 1, 1}, + {0, 0, 1, 1, 1}, + {0, 0, 1, 0, 0}, + {0, 0, 1, 0, 0}, + }, + { + {0, 0, 1, 1, 1}, + {0, 0, 1, 1, 1}, + {1, 1, 1, 1, 1}, + {0, 0, 1, 1, 1}, + {0, 0, 1, 1, 1}, + {0, 0, 1, 1, 1}, + {0, 0, 0, 0, 1}, + {0, 0, 0, 0, 1}, + }, +}; + +uint8_t heart_array[5][5][5] = { + { + {0, 1, 0, 1, 0}, + {1, 1, 1, 1, 1}, + {1, 1, 1, 1, 1}, + {0, 1, 1, 1, 0}, + {0, 0, 1, 0, 0}, + }, + { + {0, 0, 0, 0, 0}, + {1, 1, 1, 1, 1}, + {1, 1, 1, 1, 1}, + {0, 1, 1, 1, 0}, + {0, 0, 1, 0, 0}, + }, + { + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {1, 1, 1, 1, 1}, + {0, 1, 1, 1, 0}, + {0, 0, 1, 0, 0}, + }, + { + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 1, 1, 1, 0}, + {0, 0, 1, 0, 0}, + }, + { + {1, 0, 0, 0, 1}, + {0, 1, 0, 1, 0}, + {0, 0, 1, 0, 0}, + {0, 1, 0, 1, 0}, + {1, 0, 0, 0, 1}, + }, +}; \ No newline at end of file