|
| 1 | +# Arduino Presence Detection and Input Handling Fix |
| 2 | + |
| 3 | +## Issues Fixed |
| 4 | + |
| 5 | +### 1. Arduino Presence Detection Failure |
| 6 | +**Problem:** The `check_arduino_presence()` function was failing to detect Arduino even when "Y,Y" response was sent by the Jumperless. The function would always return `(False, False)`, causing unnecessary prompts. |
| 7 | + |
| 8 | +**Root Cause:** The `serial_term_in()` thread continuously reads from the serial port. When `check_arduino_presence()` sent "A?" and waited for response, the `serial_term_in()` thread would consume the "Y,Y" response before `check_arduino_presence()` could read it. |
| 9 | + |
| 10 | +**Solution:** Wrapped the serial communication in `check_arduino_presence()` with `serial_lock`: |
| 11 | +```python |
| 12 | +with serial_lock: |
| 13 | + # Flush, write, wait, and read - all protected from other threads |
| 14 | + ser.write(b"A?") |
| 15 | + time.sleep(0.4) |
| 16 | + response = ser.read(ser.in_waiting or 100).decode('utf-8', errors='ignore') |
| 17 | +``` |
| 18 | + |
| 19 | +This ensures `serial_term_in()` cannot read the response while `check_arduino_presence()` is waiting for it. |
| 20 | + |
| 21 | +### 2. Input Prompt Issues |
| 22 | +**Problem:** |
| 23 | +- User had to press Enter twice to submit input |
| 24 | +- Input "N" was interpreted as "Y" (wrong response) |
| 25 | + |
| 26 | +**Root Cause:** The `handle_interactive_input_simple()` function (called by `serial_term_out()` thread) was running with terminal in raw mode when `interactive_mode = True`. This caused: |
| 27 | +1. The interactive handler to consume the first Enter keypress |
| 28 | +2. The `input()` call to get an empty string (from buffered data or partial read) |
| 29 | +3. Empty string being treated as "Y" by the logic `if user_input == 'Y' or user_input == ''` |
| 30 | + |
| 31 | +**Solution:** Before calling `input()`, temporarily disable interactive mode and restore terminal settings: |
| 32 | +```python |
| 33 | +old_interactive_state = interactive_mode |
| 34 | +if interactive_mode: |
| 35 | + interactive_mode = False |
| 36 | + time.sleep(0.1) # Let serial_term_out exit interactive handler |
| 37 | + |
| 38 | +# Restore normal terminal settings |
| 39 | +if sys.platform != "win32" and 'original_terminal_settings' in globals(): |
| 40 | + termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, original_terminal_settings) |
| 41 | + |
| 42 | +# Flush input buffer |
| 43 | +sys.stdin.flush() |
| 44 | + |
| 45 | +# Now input() works correctly |
| 46 | +user_input = input().strip().upper() |
| 47 | + |
| 48 | +# Restore states |
| 49 | +interactive_mode = old_interactive_state |
| 50 | +``` |
| 51 | + |
| 52 | +### 3. Debug Output Added |
| 53 | +Added clear status messages so users can see what's happening: |
| 54 | +```python |
| 55 | +safe_print(f"Arduino detection: connected={is_connected}, present={is_present}", Fore.CYAN) |
| 56 | +``` |
| 57 | + |
| 58 | +When Arduino is detected, it now clearly states: |
| 59 | +``` |
| 60 | +Arduino detection: connected=True, present=True |
| 61 | +✓ Arduino detected and ready - proceeding with flash |
| 62 | +``` |
| 63 | + |
| 64 | +## Testing |
| 65 | +Test the fixes by: |
| 66 | +1. Loading a Wokwi project with Arduino sketch |
| 67 | +2. Verify you see "Arduino detection: connected=True, present=True" |
| 68 | +3. Verify flash proceeds without prompt when Arduino is present |
| 69 | +4. Disconnect Arduino and try again |
| 70 | +5. Verify prompt appears and accepts input correctly (single Enter press) |
| 71 | +6. Verify "N" actually skips the flash |
| 72 | + |
| 73 | +## Technical Details |
| 74 | + |
| 75 | +### Serial Lock Usage |
| 76 | +The `serial_lock` is a threading.Lock that prevents concurrent access to the serial port. It's used throughout the codebase but was missing from `check_arduino_presence()`. |
| 77 | + |
| 78 | +### Interactive Mode |
| 79 | +Interactive mode is used for character-by-character input (like a terminal emulator). When enabled: |
| 80 | +- Terminal is set to raw mode (no line buffering) |
| 81 | +- `serial_term_out()` calls `handle_interactive_input()` which reads stdin character-by-character |
| 82 | +- Must be disabled for `input()` to work correctly |
| 83 | + |
| 84 | +### Thread Safety |
| 85 | +Multiple threads interact with serial and stdin: |
| 86 | +- `serial_term_in()` - reads from serial port |
| 87 | +- `serial_term_out()` - reads from stdin, writes to serial |
| 88 | +- `handle_interactive_input_simple()` - character-by-character stdin reading |
| 89 | +- Main thread - calls `input()` for prompts |
| 90 | + |
| 91 | +Proper coordination using locks and state flags is essential. |
| 92 | + |
| 93 | +### 4. Empty Sketch Detection |
| 94 | +**Problem:** The system would attempt to flash empty Arduino sketches (containing only empty `setup()` and `loop()` functions), wasting time and resources. |
| 95 | + |
| 96 | +**Solution:** Added validation to detect empty sketches by: |
| 97 | +1. Removing comments and whitespace from the sketch |
| 98 | +2. Checking if only `void setup(){}` and `void loop(){}` remain |
| 99 | +3. Returning success without flashing if sketch is empty |
| 100 | + |
| 101 | +```python |
| 102 | +# Strip comments and whitespace |
| 103 | +stripped_sketch = re.sub(r'//.*?$|/\*.*?\*/', '', sketch_content, flags=re.MULTILINE | re.DOTALL) |
| 104 | +stripped_sketch = re.sub(r'\s+', '', stripped_sketch) |
| 105 | + |
| 106 | +# Check for empty pattern |
| 107 | +if 'voidsetup(){}' in stripped_sketch and 'voidloop(){}' in stripped_sketch: |
| 108 | + if stripped_sketch.count('{') <= 2: # Only setup and loop braces |
| 109 | + return True # Skip flash, not an error |
| 110 | +``` |
| 111 | + |
| 112 | +### 5. Variable Scope Bug Fix |
| 113 | +**Problem:** Exception handler referenced `old_menu_state` and `old_interactive_state` before they were assigned, causing "local variable referenced before assignment" error. |
| 114 | + |
| 115 | +**Solution:** Initialize these variables to `None` before the try block: |
| 116 | +```python |
| 117 | +old_menu_state = None |
| 118 | +old_interactive_state = None |
| 119 | + |
| 120 | +try: |
| 121 | + old_menu_state = menuEntered |
| 122 | + old_interactive_state = interactive_mode |
| 123 | + # ... rest of code |
| 124 | +except Exception as e: |
| 125 | + # Safe to reference old_menu_state and old_interactive_state here |
| 126 | + if old_menu_state is not None: |
| 127 | + menuEntered = old_menu_state |
| 128 | +``` |
| 129 | + |
| 130 | +## Files Modified |
| 131 | +- `JumperlessWokwiBridge.py` - check_arduino_presence() and flash_arduino_sketch() |
| 132 | + |
| 133 | +## Related Issues |
| 134 | +This fix resolves the issues where: |
| 135 | +- Arduino flash would prompt even when Arduino was connected |
| 136 | +- User input wasn't properly received during flash prompts |
| 137 | +- Double Enter key press was needed for input confirmation |
| 138 | +- "local variable referenced before assignment" error in exception handler |
| 139 | +- Empty Arduino sketches would trigger unnecessary flash operations |
| 140 | + |
0 commit comments