diff --git a/README.md b/README.md index eec5a64a24d..5c4c4dd9caa 100644 --- a/README.md +++ b/README.md @@ -5,24 +5,56 @@ A Flipper Zero app that allows the flipper to communicate with Digimon V-Pets. -Currently tested: -- DM20 -- DMX -- PenZ -- DMC -- PenOG -- PenProg -- PenX +Currently tested devices: +Classic: +- 1997 Digital Monster +- 1998 Digimon Pendulum +- 1999 Digivice +- 2000 D-3 +- 2000 D-Terminal +- 2002 D-Scanner +- 2002 Digimon Pendulum Progress +- 2003 Digimon Pendulum X +- 2005 Digimon Accel +- 2005 Digimon Mini +- 2006 Digivice iC + +Modern: +- 2017 Digital Monster Ver.20th +- 2018 Digimon Pendulum Ver.20th +- 2019 Digital Monster X +- 2020 Digimon Pendulum Z +- 2021 Digivice Ver.Complete + +Color: +- 2023 Digital Monster COLOR + +Currently tested apps: +- W0rld (via Chrome web browser serial) +- Alpha Terminal and Alpha Serial (Android) +- Alpha Terminal (Windows) +- ACom Wiki (Android) + +Unsupported Apps: +- Alpha Serial (Windows) still doesn't seem to detect it Untested: -- All other pets (Pen20, Original Pets, etc) +- PenC +- All other pets - Listen Mode +Thanks to [Joushiikuta](https://www.youtube.com/@joushiikuta) for testing the classic devices which I do not have. + Based on: - The DMComm project by BladeSabre: https://github.com/dmcomm/dmcomm-project +- The updated DMComm Arduino library by BladeSabre: https://github.com/dmcomm/dmcomm-arduino-lib - The Flipper Zero Boilerplate App: https://github.com/leedave/flipper-zero-fap-boilerplate - The Flipper Zero Firmware: https://github.com/flipperdevices/flipperzero-firmware +### Known Issues + +Occasionally when using the USB A-Com mode, the flipper zero USB driver will crash the flipper due to the way I'm overwriting the USB VID/PID. I'm not sure why yet and am still debugging this. Subsequent attempts seem to work and this is uncommon (although not rare). + ### Youtube Example [![Video showing app in action](https://img.youtube.com/vi/pggRzHnXlF4/0.jpg)](https://www.youtube.com/watch?v=pggRzHnXlF4) @@ -33,12 +65,18 @@ You will need to construct a circuit similar to the A-Com circuit described in t ![Schematic](screenshots/flipper_vpet_circuit.png) -Pin C3 - 2k resistor - Pin B2 - 10k resistor - Pin GND +Pin C3 - 4k resistor - Pin B2 - 20k resistor - Pin GND Pin B2 - VPet+ Pin GND - VPet- +The 4k/20k resistor pairing is confirmed to work with the older devices. If you only need modern device support 2k/10k is also compatible, though it will not work with older devices. + +[Joushiikuta](https://www.youtube.com/@joushiikuta) has created an awesome gerber file for a compatible PCB located [here](pcb/20240225_FlipperZero_F-Com_PCB_Thickness_1.6mm_Gerber.zip) (included with permission) + +You will also need compatible right angle header pins and SMD resistors. + ## Installation The F-Com app is compiled and installed with [ufbt](https://github.com/flipperdevices/flipperzero-ufbt) @@ -54,7 +92,7 @@ flipper to make them accessible under the "Saved" menu option. ### Listen -Listen is currently untested, as I have not created a jig to make it function!! +Listen mode is now partially functional. It is difficult to get a good read at the moment although it is possible. I need to re-work the code to use rising/falling edge detection instead of looping though so the flipper GUI does not hang, and we don't have to spend the whole time looping on a GPIO read. Listen allows you to connect 2 vpets to each other and eavesdrop on the data they send. Both codes will show up on the flipper after communication completes, and you can save either code to the flipper for later use. Leaving this screen will pause dmcomm. @@ -76,6 +114,4 @@ The flipper will change the USB port from CLI mode into Serial mode and behave a Dmcomm supports a voltage test. However, the flipper zero firmware does not yet have ADC support implemented. This is still possible as demonstrated by the flipper zero oscilloscope project. At some point I may implement this. A-Com's themselves use analog input in order to support a wider range of devices logic levels. This may limit the flipper app's compatibility for now. -DMC support isn't included in the dmcomm-project ino, but has been added to the updated https://github.com/dmcomm/dmcomm-arduino-lib project. At some point I may update this to port the CPP code onto the flipper. - Debug mode support could be added at some point. diff --git a/application.fam b/application.fam index d71f727f550..a7c7d3dd020 100644 --- a/application.fam +++ b/application.fam @@ -8,7 +8,7 @@ App( stack_size=3 * 1024, fap_category="GPIO", # Optional values - # fap_version="0.1", + fap_version="1.1", fap_icon="template.png", # 10x10 1-bit PNG # fap_description="A simple app", # fap_author="J. Doe", diff --git a/dmcomm-lib/Print.cpp b/dmcomm-lib/Print.cpp index 359ca5282ef..2350fd2dcf5 100644 --- a/dmcomm-lib/Print.cpp +++ b/dmcomm-lib/Print.cpp @@ -11,6 +11,7 @@ void Stream::set_callback(DmcommCallback callback) size_t Stream::write(uint8_t i) { + if(cb == NULL) return 0; char str[10]; snprintf(str, 10, "%c", (char)i); size_t sent = furi_stream_buffer_send( @@ -23,6 +24,7 @@ size_t Stream::write(uint8_t i) size_t Stream::write(const char *buffer, size_t size) { + if(cb == NULL) return 0; char str[10]; size_t sent = 0; for(size_t i = 0; i < size; i++) @@ -39,6 +41,7 @@ size_t Stream::write(const char *buffer, size_t size) size_t Stream::print(const char buffer[]) { + if(cb == NULL) return 0; return furi_stream_buffer_send( app->dmcomm_output_stream, buffer, @@ -48,6 +51,7 @@ size_t Stream::print(const char buffer[]) size_t Stream::print(int i, int fmt) { + if(cb == NULL) return 0; UNUSED(fmt); char str[10]; snprintf(str, 10, "%d", i); @@ -62,6 +66,7 @@ size_t Stream::print(int i, int fmt) size_t Stream::println(void) { + if(cb == NULL) return 0; size_t sent = furi_stream_buffer_send( app->dmcomm_output_stream, "\n", @@ -74,6 +79,7 @@ size_t Stream::println(void) size_t Stream::println(const char buffer[]) { + if(cb == NULL) return 0; size_t sent = furi_stream_buffer_send( app->dmcomm_output_stream, buffer, @@ -91,6 +97,7 @@ size_t Stream::println(const char buffer[]) size_t Stream::println(int i, int fmt) { + if(cb == NULL) return 0; UNUSED(fmt); char str[10]; snprintf(str, 10, "%d\n", i); diff --git a/dmcomm-lib/Print.h b/dmcomm-lib/Print.h index 34145db5762..0ba9b94ca29 100644 --- a/dmcomm-lib/Print.h +++ b/dmcomm-lib/Print.h @@ -1,3 +1,10 @@ +/* +implementation of the Print/Stream classes + +but the underlying I/O is to two flipper stream buffers +instead of an arduino serial interface. + +*/ #include "arduino.h" #define DEC 10 diff --git a/dmcomm-lib/dmcomm_pin_control.h b/dmcomm-lib/dmcomm_pin_control.h index b80b9105b72..6adeffcdc59 100644 --- a/dmcomm-lib/dmcomm_pin_control.h +++ b/dmcomm-lib/dmcomm_pin_control.h @@ -49,7 +49,7 @@ class BaseProngInput { void setActiveLevel(uint8_t level); uint32_t waitForActive(uint32_t timeout); uint32_t waitForIdle(uint32_t timeout); - uint32_t waitFor(bool active, uint32_t timeout); + virtual uint32_t waitFor(bool active, uint32_t timeout); ReceiveOutcome waitFrom(bool active, uint32_t dur_min, uint32_t dur_max, int16_t current_bit); protected: uint8_t idle_level_; diff --git a/dmcomm-lib/dmcomm_serial.cpp b/dmcomm-lib/dmcomm_serial.cpp index 37bb0cef69a..54b53089902 100644 --- a/dmcomm-lib/dmcomm_serial.cpp +++ b/dmcomm-lib/dmcomm_serial.cpp @@ -87,6 +87,7 @@ uint8_t SerialFollower::serialRead() { int16_t incoming_int; uint8_t incoming_byte; uint8_t i = 0; + bool first = true; if (serial_.available() == 0) { return 0; } @@ -94,6 +95,13 @@ uint8_t SerialFollower::serialRead() { do { do { incoming_int = serial_.read(); + // if we have nothing to read, and this is the first character + // then exit immediately, this makes our loop really tight while we are active + if(first && incoming_int == -1) + { + return 0; + } + first = false; time = millis() - time_start; if (time > DMCOMM_SERIAL_TIMEOUT_MILLIS) { serial_.println(F("[No EOL received]")); diff --git a/dmcomm-lib/fcom.cpp b/dmcomm-lib/fcom.cpp index 611867a2f02..aa362ce31fb 100644 --- a/dmcomm-lib/fcom.cpp +++ b/dmcomm-lib/fcom.cpp @@ -28,7 +28,7 @@ static FComState fcom_state; void set_serial_callback(DmcommCallback cb) { - FURI_LOG_I(TAG, "set_serial_callback %p", cb); + //FURI_LOG_I(TAG, "set_serial_callback %p", cb); fcom_state.stream->set_callback(cb); } diff --git a/dmcomm-lib/flipper_pin_control.cpp b/dmcomm-lib/flipper_pin_control.cpp index ef46bea4404..158802d8670 100644 --- a/dmcomm-lib/flipper_pin_control.cpp +++ b/dmcomm-lib/flipper_pin_control.cpp @@ -1,4 +1,5 @@ #include "flipper_pin_control.h" +#include "DMComm.h" namespace DMComm { @@ -52,4 +53,59 @@ bool FComInput::isActive() { return level == active_level_; } +/* +NOTE: We override this because if we loop without delays on the +flipper, we starve the process scheduler thread... which means +the UI locks up. This isn't an issue for _most_ operations because +they are all 13ms or less. But in listen mode or go-second mode +we are waiting for _FIVE SECONDS_ which is really annoying for the +user being unable to back out of the app during that time. + +SO. If the delay is really big, we're going to add a 1ms delay +every loop to allow the UI to process events and stuff so the +user isn't left in the dust. + +For tight timeouts though, we will keep the original code. + +Technically, this should be done using rising/falling edge +interrupts... However, that's pretty complicated to implement +because we need to determine which state is active, what current +state we are in, call into C code and manage waiting on a lock. +And even then lock timeouts aren't microsecond accurate, so it +still wouldn't be something we could use for everything... + +This is "good enough". +*/ +uint32_t FComInput::waitFor(bool active, uint32_t timeout) { + if(timeout < 250000) + { + uint32_t start_time = micros(); + uint32_t duration; + while (true) { + duration = micros() - start_time; + if (duration > timeout) { + return DMCOMM_SIGNAL_TIMED_OUT; + } + if (active == isActive()) { + return duration; + } + } + } + else + { + uint32_t start_time = micros(); + uint32_t duration; + while (true) { + duration = micros() - start_time; + if (duration > timeout) { + return DMCOMM_SIGNAL_TIMED_OUT; + } + if (active == isActive()) { + return duration; + } + delay(1); + } + } +} + } \ No newline at end of file diff --git a/dmcomm-lib/flipper_pin_control.h b/dmcomm-lib/flipper_pin_control.h index 66dd1570be5..f0606313955 100644 --- a/dmcomm-lib/flipper_pin_control.h +++ b/dmcomm-lib/flipper_pin_control.h @@ -26,6 +26,7 @@ class FComInput : public BaseProngInput { bool isActive(); void setThreshold(uint16_t threshold_mV); uint16_t voltage(); + uint32_t waitFor(bool active, uint32_t timeout); private: const GpioPin* pin_in_; }; diff --git a/dmcomm_link.c b/dmcomm_link.c index a657f43d17b..7cf36ff3ac3 100644 --- a/dmcomm_link.c +++ b/dmcomm_link.c @@ -5,19 +5,7 @@ * dmcomm thread, runs the dmcomm loop. call init first. * exits when the app dmcomm_run var is set to false. * App runs this constantly on startup until app close. -* -int32_t dmcomm_reader(void* context) { - FURI_LOG_I(TAG, "dmcomm_reader start"); - App* app = context; - setup(); - while(app->dmcomm_run) { - loop(); - } - - FURI_LOG_I(TAG, "dmcomm_reader end"); - return 0; -}*/ - +*/ int32_t fcom_thread(void* context) { FURI_LOG_I(TAG, "fcom_thread start"); App* app = context; @@ -37,7 +25,7 @@ Used for comms by app (non USB) */ void dmcomm_sendcommand(void* context, const char* cmd) { - FURI_LOG_I(TAG, "dmcomm_sendcommand"); + FURI_LOG_I(TAG, "dmcomm_sendcommand: %s", cmd); App* app = context; size_t sent = furi_stream_buffer_send( diff --git a/pcb/20240225_FlipperZero_F-Com_PCB_Thickness_1.6mm_Gerber.zip b/pcb/20240225_FlipperZero_F-Com_PCB_Thickness_1.6mm_Gerber.zip new file mode 100644 index 00000000000..197bc5da953 Binary files /dev/null and b/pcb/20240225_FlipperZero_F-Com_PCB_Thickness_1.6mm_Gerber.zip differ diff --git a/scene_listen_menu.c b/scene_listen_menu.c index d5b094c68a4..d4947d4bd12 100644 --- a/scene_listen_menu.c +++ b/scene_listen_menu.c @@ -59,21 +59,29 @@ bool fcom_listen_menu_scene_on_event(void* context, SceneManagerEvent event) { switch(event.event) { case ListenMenuSceneSelectionEvent2Prong: strncpy(app->state->current_code, "V0\n", MAX_DIGIROM_LEN); + furi_string_reset(app->state->r_code); + furi_string_reset(app->state->s_code); scene_manager_next_scene(app->scene_manager, FcomReadCodeScene); consumed = true; break; case ListenMenuSceneSelectionEvent3Prong: strncpy(app->state->current_code, "X0\n", MAX_DIGIROM_LEN); + furi_string_reset(app->state->r_code); + furi_string_reset(app->state->s_code); scene_manager_next_scene(app->scene_manager, FcomReadCodeScene); consumed = true; break; case ListenMenuSceneSelectionEventXrosMini: strncpy(app->state->current_code, "Y0\n", MAX_DIGIROM_LEN); + furi_string_reset(app->state->r_code); + furi_string_reset(app->state->s_code); scene_manager_next_scene(app->scene_manager, FcomReadCodeScene); consumed = true; break; case ListenMenuSceneSelectionEventColor: strncpy(app->state->current_code, "C0\n", MAX_DIGIROM_LEN); + furi_string_reset(app->state->r_code); + furi_string_reset(app->state->s_code); scene_manager_next_scene(app->scene_manager, FcomReadCodeScene); consumed = true; break; diff --git a/scene_main_menu.c b/scene_main_menu.c index d83eb2b16f4..8170db578f0 100644 --- a/scene_main_menu.c +++ b/scene_main_menu.c @@ -18,6 +18,7 @@ Setup our scene widgets and create callback hooks */ void fcom_menu_callback(void* context, uint32_t index); void fcom_main_menu_scene_on_enter(void* context) { + FURI_LOG_I(TAG, "fcom_main_menu_scene_on_enter"); App* app = context; submenu_reset(app->submenu); submenu_add_item(app->submenu, "Listen", MainMenuSelectionListen, fcom_menu_callback, app); @@ -52,6 +53,7 @@ void fcom_menu_callback(void* context, uint32_t index) { /** main menu event handler - switches scene based on the event */ bool fcom_main_menu_scene_on_event(void* context, SceneManagerEvent event) { + FURI_LOG_I(TAG, "fcom_main_menu_scene_on_event"); App* app = context; bool consumed = false; switch(event.type) { @@ -83,6 +85,7 @@ bool fcom_main_menu_scene_on_event(void* context, SceneManagerEvent event) { } void fcom_main_menu_scene_on_exit(void* context) { + FURI_LOG_I(TAG, "fcom_main_menu_scene_on_exit"); App* app = context; submenu_reset(app->submenu); } diff --git a/scene_read_code.c b/scene_read_code.c index 3deb3295617..cf7d0c8824b 100644 --- a/scene_read_code.c +++ b/scene_read_code.c @@ -1,15 +1,53 @@ /* Listen to two devices chat and then allow options for saving either code -Left save s code -Right save r code -Back return +The dmcomm code just outputs a string of r codes. So we do some stuff to alternate +between saving to r_code and s_code so we can save each devices' codes separately. +Additionally, this method _always_ terminates via timeout... so we need additional handling for that. -Post save should go back to us +e.g. +r:FC03 r:F30C r:ED12 r:EE11 t -TODO: Need to test this... It may not actually work as-is +will be interpreted as -(It probably won't anymore for sure) +s_code = V1-FC03-ED12 +r_code = V2-F3OC-EE11 + +It's probably more complicated than it needs to be atm :/ + +TODO: +- For some reason... this hangs the UI while DMComm is doing stuff. I remember it happening similar before but don't remember why. +- Refactor the serial reader into a state machine + +something like: + +idle r: + r -> pending r code + t -> save validate (reset for send code) + newline -> save validate + anything else -> reset + +idle s: + r -> pending s code + t -> save validate (reset for send code) + newline -> save validate + anything else -> reset + +pending code: + : -> read code + anything else -> reset + +read code: + A-Z,0-9 -> populate code + space -> end code -> idle + anything else -> reset + +save validate: + if code valid -> done + if not valid -> reset + +reset: + clear code then -> idle s */ #include "flipper.h" #include "app_state.h" @@ -18,99 +56,164 @@ TODO: Need to test this... It may not actually work as-is #include "scene_read_code.h" #include + +#define MAX_DISPLAY_BYTES 36 + +// These need to persist between processInput calls as the serial data comes in at random points +static char curcode = 0; +static bool first = false; +static bool assert_next_colon = false; // Necessary for managing state + +// NOTE: listen mode only returns 'r' codes. but we want to split into "1st" and "2nd" device codes +// So use this to alternate logically between which code slot we save in between data blocks +static char altcode = 's'; + /* Callback from dmcomm thread with serial results */ + void processReadInput(void* context) { furi_assert(context); App* app = context; char out[64]; - size_t recieved = 0; - memset(out, 0, 64); - - recieved = furi_stream_buffer_receive( - app->dmcomm_output_stream, - &out, - 63, - 0); - UNUSED(recieved); - FURI_LOG_I(TAG, "DMComm Data: %s", out); - - if(app->state->waitForCode) + size_t recieved = 1; + + while(recieved > 0) { - FURI_LOG_I(TAG, "reading code"); - furi_string_reset(app->state->r_code); - furi_string_reset(app->state->s_code); - int rpackets = 0; - int spackets = 0; - int l = strlen(out); - int first = true; - for(int i = 0; i < l; i++) + memset(out, 0, 64); + recieved = furi_stream_buffer_receive( + app->dmcomm_output_stream, + &out, + 63, + 0); + + if(app->state->waitForCode && recieved > 0) { - if(out[i] == 's' && i + 5 < l) + FURI_LOG_I(TAG, "DMComm Sent Data: %d <%s>", recieved, out); + //FURI_LOG_I(TAG, "reading code"); + int l = strlen(out); + for(int i = 0; i < l; i++) { - FURI_LOG_I(TAG, "found s"); - if(furi_string_empty(app->state->s_code)) - { - if(first) + if(out[i] == 't' || (assert_next_colon && out[i] != ':')) + { // reset for timeout and continue :( + + if((app->state->rpackets == 0 && app->state->spackets == 0) || (assert_next_colon && out[i] != ':')) + { + //FURI_LOG_I(TAG, "reset codes"); + curcode = 0; + first = true; + app->state->spackets = 0; + app->state->rpackets = 0; + furi_string_reset(app->state->r_code); + furi_string_reset(app->state->s_code); + } // Listen always times out + } + if(assert_next_colon) + assert_next_colon = false; + if(out[i] == 'r' && altcode == 's') + { // Starts an s code block + curcode = 's'; + assert_next_colon = true; + //FURI_LOG_I(TAG, "read s"); + if(furi_string_empty(app->state->s_code)) { - furi_string_cat_printf(app->state->s_code, "V1-"); - first = false; + furi_string_push_back(app->state->s_code, app->state->current_code[0]); + if(first) + { + //FURI_LOG_I(TAG, "s first"); + first = false; + furi_string_push_back(app->state->s_code, '1'); + } + else + { + //FURI_LOG_I(TAG, "s second"); + furi_string_push_back(app->state->s_code, '2'); + } } - else - furi_string_cat_printf(app->state->s_code, "V2-"); + furi_string_push_back(app->state->s_code, '-'); } - else - furi_string_cat_printf(app->state->s_code, "-"); - - i += 2; // : - for(int j = 0; j < 4; j++) - furi_string_push_back(app->state->s_code, out[i++]); // 4 hex - spackets++; - } - - if(out[i] == 'r' && i + 5 < l) - { - FURI_LOG_I(TAG, "found r"); - if(furi_string_empty(app->state->r_code)) - { - if(first) + else if(out[i] == 'r' && altcode == 'r') + { // Starts an r code block + curcode = 'r'; + assert_next_colon = true; + //FURI_LOG_I(TAG, "read r"); + if(furi_string_empty(app->state->r_code)) { - furi_string_cat_printf(app->state->r_code, "V1-"); - first = false; + furi_string_push_back(app->state->r_code, app->state->current_code[0]); + if(first) + { + //FURI_LOG_I(TAG, "r first"); + first = false; + furi_string_push_back(app->state->r_code, '1'); + } + else + { + //FURI_LOG_I(TAG, "r second"); + furi_string_push_back(app->state->r_code, '2'); + } } + furi_string_push_back(app->state->r_code, '-'); + } + else if(curcode != 0 && (('A' <= out[i] && out[i] <= 'Z') || ('0' <= out[i] && out[i] <= '9'))) + { // If we're reading a code, read alphanum into the code + //FURI_LOG_I(TAG, "read char %c", out[i]); + if(curcode == 's') + furi_string_push_back(app->state->s_code, out[i]); + if(curcode == 'r') + furi_string_push_back(app->state->r_code, out[i]); + } + else if(curcode != 0 && (out[i] == ' ' || out[i] == '\n')) + { // If we're reading a code, a space ends it + //FURI_LOG_I(TAG, "code done"); + if(curcode == 's') + app->state->spackets++; + if(curcode == 'r') + app->state->rpackets++; + if(altcode == 's') + altcode = 'r'; else - furi_string_cat_printf(app->state->r_code, "V2-"); + altcode = 's'; + curcode = 0; } - else - furi_string_cat_printf(app->state->r_code, "-"); - i += 2; // : - for(int j = 0; j < 4; j++) - furi_string_push_back(app->state->r_code, out[i++]); // 4 hex - rpackets++; - } - } + if(out[i] == '\n') + { + //FURI_LOG_I(TAG, "data done"); + // If we have a newline, then we've ended input. Since we're not driving + // input, we don't know codeLen ahead of time. So now set it to spackets + // so we can enter the final if and output the codes + app->state->codeLen = app->state->spackets; + } + // if spackets == rpackets and spackets = code packets, then present code for saving + // and stop scanning for new codes. Perhaps shutdown dmcomm at this point? + if(app->state->codeLen > 0 && app->state->rpackets == app->state->codeLen && app->state->spackets == app->state->codeLen) + { + FURI_LOG_I(TAG, "s code %s", furi_string_get_cstr(app->state->s_code)); + FURI_LOG_I(TAG, "r code %s", furi_string_get_cstr(app->state->r_code)); - FURI_LOG_I(TAG, "s code %s", furi_string_get_cstr(app->state->s_code)); - FURI_LOG_I(TAG, "r code %s", furi_string_get_cstr(app->state->r_code)); + furi_string_set_strn(app->dialog_header, furi_string_get_cstr(app->state->s_code), MAX_DISPLAY_BYTES); + if(furi_string_size(app->dialog_header) >= MAX_DISPLAY_BYTES) + furi_string_cat(app->dialog_header, "..."); + dialog_ex_set_header(app->dialog, furi_string_get_cstr(app->dialog_header), 64, 12, AlignCenter, AlignTop); - //if spackets == rpackets and spackets = code packets, then present code for saving - if(rpackets > 0 && spackets > 0 && abs(rpackets-spackets) <= 1) - { - dialog_ex_set_header(app->dialog, furi_string_get_cstr(app->state->s_code), 10, 12, AlignLeft, AlignTop); - dialog_ex_set_text(app->dialog, furi_string_get_cstr(app->state->r_code), 10, 24, AlignLeft, AlignTop); + furi_string_set_strn(app->dialog_text, furi_string_get_cstr(app->state->r_code), MAX_DISPLAY_BYTES); + if(furi_string_size(app->dialog_text) >= MAX_DISPLAY_BYTES) + furi_string_cat(app->dialog_text, "..."); + dialog_ex_set_text(app->dialog, furi_string_get_cstr(app->dialog_text), 10, 24, AlignLeft, AlignTop); - app->state->waitForCode = false; - dialog_ex_set_left_button_text(app->dialog, "Save Top"); - dialog_ex_set_right_button_text(app->dialog, "Save Bot"); + dialog_ex_set_left_button_text(app->dialog, "Save Top"); + dialog_ex_set_right_button_text(app->dialog, "Save Bot"); + app->state->waitForCode = false; + dmcomm_sendcommand(app, "0\n"); // Stop the dmcomm, we don't want to overwrite codes + break; // stop reading data + FURI_LOG_I(TAG, "done"); + } + } } } - - FURI_LOG_I(TAG, "done"); } void read_code_cb(void* context) @@ -124,7 +227,7 @@ void read_code_cb(void* context) void read_code_dialog_callback(DialogExResult result, void* context) { furi_assert(context); App* app = context; - UNUSED(app); + app->state->save_code_return_scene = FcomReadCodeScene; if(result == DialogExResultRight) { FURI_LOG_I(TAG, "DialogExResultRight"); @@ -144,25 +247,54 @@ void fcom_read_code_scene_on_enter(void* context) { FURI_LOG_I(TAG, "fcom_read_code_scene_on_enter"); App* app = context; - // TODO: somehow if we return from save dialog, don't clear and restart the read - // because we will want to allow to save both codes + bool hasData = !furi_string_empty(app->state->r_code) && !furi_string_empty(app->state->s_code); + + if(hasData) + { + furi_string_set_strn(app->dialog_header, furi_string_get_cstr(app->state->s_code), MAX_DISPLAY_BYTES); + if(furi_string_size(app->dialog_header) >= MAX_DISPLAY_BYTES) + furi_string_cat(app->dialog_header, "..."); + dialog_ex_set_header(app->dialog, furi_string_get_cstr(app->dialog_header), 64, 12, AlignCenter, AlignTop); + + furi_string_set_strn(app->dialog_text, furi_string_get_cstr(app->state->r_code), MAX_DISPLAY_BYTES); + if(furi_string_size(app->dialog_text) >= MAX_DISPLAY_BYTES) + furi_string_cat(app->dialog_text, "..."); + dialog_ex_set_text(app->dialog, furi_string_get_cstr(app->dialog_text), 10, 24, AlignLeft, AlignTop); + + dialog_ex_set_left_button_text(app->dialog, "Save Top"); + dialog_ex_set_right_button_text(app->dialog, "Save Bot"); + } + else + { + dialog_ex_set_header(app->dialog, "Waiting For Data", 64, 12, AlignCenter, AlignTop); + dialog_ex_set_text(app->dialog, "Connect to device bus and initiate comms", 10, 24, AlignLeft, AlignTop); + dialog_ex_set_left_button_text(app->dialog, NULL); + dialog_ex_set_right_button_text(app->dialog, NULL); + } - dialog_ex_set_header(app->dialog, "Waiting For Data", 64, 12, AlignCenter, AlignTop); - dialog_ex_set_text(app->dialog, "Connect to device bus and transfer data", 10, 24, AlignLeft, AlignTop); - dialog_ex_set_left_button_text(app->dialog, NULL); - dialog_ex_set_right_button_text(app->dialog, NULL); dialog_ex_set_center_button_text(app->dialog, NULL); // This will eventually be a "resend" button dialog_ex_set_result_callback(app->dialog, read_code_dialog_callback); dialog_ex_set_context(app->dialog, app); view_dispatcher_switch_to_view(app->view_dispatcher, FcomReadCodeView); - app->state->waitForCode = true; + // Initialize everything for capture + curcode = 0; + altcode = 's'; + first = true; + app->state->codeLen = -1; // Set to negative until we know how long the codes are + app->state->rpackets = 0; + app->state->spackets = 0; + app->state->waitForCode = !hasData; set_serial_callback(read_code_cb); - furi_string_reset(app->state->r_code); - furi_string_reset(app->state->s_code); - dmcomm_sendcommand(app, app->state->current_code); + if(hasData) + dmcomm_sendcommand(app, "0\n"); + else + { + dmcomm_sendcommand(app, app->state->current_code); + //dmcomm_sendcommand(app, "\n"); \n is included from listen menu + } } bool fcom_read_code_scene_on_event(void* context, SceneManagerEvent event) { @@ -191,6 +323,7 @@ void fcom_read_code_scene_on_exit(void* context) { App* app = context; UNUSED(app); + dialog_ex_reset(app->dialog); set_serial_callback(NULL); dmcomm_sendcommand(app, "0\n"); app->state->waitForCode = false; diff --git a/scene_save_code.c b/scene_save_code.c index 1116e6dbb20..92f641379ca 100644 --- a/scene_save_code.c +++ b/scene_save_code.c @@ -69,7 +69,7 @@ void save_text_input_callback(void* context) { flipper_format_free(fff_file); // After we save, switch to main menu - scene_manager_search_and_switch_to_previous_scene(app->scene_manager, FcomMainMenuScene); + scene_manager_search_and_switch_to_previous_scene(app->scene_manager, app->state->save_code_return_scene); } diff --git a/scene_send_code.c b/scene_send_code.c index 4c538fe7a0d..b467bfdc101 100644 --- a/scene_send_code.c +++ b/scene_send_code.c @@ -4,6 +4,33 @@ Sends the code to the DMComm and initiates sending Displays current code we are sending in bold Waits for and displays last recieved code in thin text Right will go to the save code dialog for the new code (if there is one) + +TODO: + +Switch to a state machine like: + +idle: + r -> pending r code + s -> pending s code + t -> reset + newline -> save validate + anything else -> reset + +pending code: + : -> read code + anything else -> reset + +read code: + A-Z,0-9 -> populate code + space -> end code -> idle + anything else -> reset + +save validate: + if code valid -> done + if not valid -> reset + +reset: + clear code then -> idle */ #include "flipper.h" @@ -100,8 +127,9 @@ void processInput(void* context) } furi_string_push_back(app->state->r_code, '-'); } - else if(('A' <= out[i] && out[i] <= 'Z') || ('0' <= out[i] && out[i] <= '9')) + else if(curcode != 0 && (('A' <= out[i] && out[i] <= 'Z') || ('0' <= out[i] && out[i] <= '9'))) { // If we're reading a code, read alphanum into the code + //FURI_LOG_I(TAG, "read char %c", out[i]); if(curcode == 's') furi_string_push_back(app->state->s_code, out[i]); if(curcode == 'r') @@ -109,6 +137,7 @@ void processInput(void* context) } else if(curcode != 0 && (out[i] == ' ' || out[i] == '\n')) { // If we're reading a code, a space ends it + //FURI_LOG_I(TAG, "code done"); if(curcode == 's') app->state->spackets++; if(curcode == 'r') diff --git a/usbuart/usb_uart_bridge.c b/usbuart/usb_uart_bridge.c index fb5f39f6052..82ec9919ad3 100644 --- a/usbuart/usb_uart_bridge.c +++ b/usbuart/usb_uart_bridge.c @@ -81,13 +81,50 @@ void usb_uart_send(UsbUartBridge* usb_uart, const uint8_t* data, size_t len) furi_thread_flags_set(furi_thread_get_id(usb_uart->thread), WorkerEvtRxDone); } +/* Copied from furi_hal_usb_cdc.c so we can change our vid/pid for alpha terminal/serial */ + +#define USB_EP0_SIZE 8 + +enum UsbDevDescStr { + UsbDevLang = 0, + UsbDevManuf = 1, + UsbDevProduct = 2, + UsbDevSerial = 3, +}; + +static const struct usb_device_descriptor cdc_device_desc_fcom = { + .bLength = sizeof(struct usb_device_descriptor), + .bDescriptorType = USB_DTYPE_DEVICE, + .bcdUSB = VERSION_BCD(2, 0, 0), + .bDeviceClass = USB_CLASS_IAD, + .bDeviceSubClass = USB_SUBCLASS_IAD, + .bDeviceProtocol = USB_PROTO_IAD, + .bMaxPacketSize0 = USB_EP0_SIZE, + .idVendor = 0x2341, + .idProduct = 0x0000, + .bcdDevice = VERSION_BCD(1, 0, 0), + .iManufacturer = UsbDevManuf, + .iProduct = UsbDevProduct, + .iSerialNumber = UsbDevSerial, + .bNumConfigurations = 1, +}; + +static const struct usb_string_descriptor dev_manuf_desc = USB_STRING_DESC("Flipper Devices Inc."); + +FuriHalUsbInterface usb_cdc_fcom = { + .dev_descr = (struct usb_device_descriptor*)&cdc_device_desc_fcom, + .str_manuf_descr = (void*)&dev_manuf_desc, + .str_prod_descr = NULL, + .str_serial_descr = NULL, +}; + static void usb_uart_vcp_init(UsbUartBridge* usb_uart, uint8_t vcp_ch) { furi_hal_usb_unlock(); if(vcp_ch == 0) { Cli* cli = furi_record_open(RECORD_CLI); cli_session_close(cli); furi_record_close(RECORD_CLI); - furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true); + furi_check(furi_hal_usb_set_config(&usb_cdc_fcom, NULL) == true); } else { furi_check(furi_hal_usb_set_config(&usb_cdc_dual, NULL) == true); Cli* cli = furi_record_open(RECORD_CLI); @@ -164,7 +201,7 @@ static int32_t usb_uart_worker(void* context) { } usb_uart_vcp_deinit(usb_uart, usb_uart->cfg.vcp_ch); - furi_hal_gpio_init(USB_USART_DE_RE_PIN, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + //furi_hal_gpio_init(USB_USART_DE_RE_PIN, GpioModeAnalog, GpioPullNo, GpioSpeedLow); furi_thread_flags_set(furi_thread_get_id(usb_uart->tx_thread), WorkerEvtTxStop); furi_thread_join(usb_uart->tx_thread); @@ -175,7 +212,7 @@ static int32_t usb_uart_worker(void* context) { furi_semaphore_free(usb_uart->tx_sem); furi_hal_usb_unlock(); - furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true); + furi_check(furi_hal_usb_set_config(&usb_cdc_fcom, NULL) == true); Cli* cli = furi_record_open(RECORD_CLI); cli_session_open(cli, &cli_vcp); furi_record_close(RECORD_CLI); @@ -239,6 +276,18 @@ static void vcp_on_line_config(void* context, struct usb_cdc_line_coding* config } UsbUartBridge* usb_uart_enable(UsbUartConfig* cfg) { + /* + NOTE: We need to replace the vid/pid so alpha terminal/serial android + detect this as an arduino... But we can't reference the cdc_init + method 'cause it's in the firmware's private code. So overwrite + our handles here instead. + */ + usb_cdc_fcom.init = usb_cdc_single.init; + usb_cdc_fcom.deinit = usb_cdc_single.deinit; + usb_cdc_fcom.wakeup = usb_cdc_single.wakeup; + usb_cdc_fcom.suspend = usb_cdc_single.suspend; + usb_cdc_fcom.cfg_descr = usb_cdc_single.cfg_descr; + UsbUartBridge* usb_uart = malloc(sizeof(UsbUartBridge)); memcpy(&(usb_uart->cfg_new), cfg, sizeof(UsbUartConfig)); diff --git a/usbuart/usb_uart_bridge.h b/usbuart/usb_uart_bridge.h index b0045743545..a30ed06c561 100644 --- a/usbuart/usb_uart_bridge.h +++ b/usbuart/usb_uart_bridge.h @@ -9,6 +9,10 @@ some buffer streams #include "flipper.h" #include #include +#include +#include + +extern FuriHalUsbInterface usb_cdc_fcom; typedef void (*UsbUartCallback)(); typedef struct UsbUartBridge UsbUartBridge;