lykahb / neotools

Command-line tools for AlphaSmart NEO
GNU General Public License v3.0
39 stars 7 forks source link

Command only works on second run, after Ctrl-C interrupt #10

Closed ArenT1981 closed 1 year ago

ArenT1981 commented 1 year ago

On my Fedora 38 system, if I run any command, e.g. neotools info with my Neo connected, nothing happens, with it just appearing to hang.

If I then manually Ctrl-C at the console, I get the following traceback:

    sys.exit(cli())
             ^^^^^
  File "/usr/lib/python3.11/site-packages/click/core.py", line 1130, in __call__
    return self.main(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/click/core.py", line 1055, in main
    rv = self.invoke(ctx)
         ^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/click/core.py", line 1657, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/click/core.py", line 1404, in invoke
    return ctx.invoke(self.callback, **ctx.params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/click/core.py", line 760, in invoke
    return __callback(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/aren/.local/lib/python3.11/site-packages/neotools/cli.py", line 214, in system_info
    info = commands.system_info()
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/home/aren/.local/lib/python3.11/site-packages/neotools/commands.py", line 20, in new_func
    result = f(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^
  File "/home/aren/.local/lib/python3.11/site-packages/neotools/commands.py", line 228, in system_info
    with Device.connect() as device:
  File "/usr/lib64/python3.11/contextlib.py", line 137, in __enter__
    return next(self.gen)
           ^^^^^^^^^^^^^^
  File "/home/aren/.local/lib/python3.11/site-packages/neotools/device.py", line 42, in connect
    device.dispose()
  File "/home/aren/.local/lib/python3.11/site-packages/neotools/device.py", line 88, in dispose
    if self.original_product == HID_PRODUCT_ID and self.dev.idProduct == COM_PRODUCT_ID:
                                                   ^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'idProduct'
Traceback (most recent call last):
  File "/usr/lib64/python3.11/weakref.py", line 666, in _exitfunc
    f()
  File "/usr/lib64/python3.11/weakref.py", line 590, in __call__
    return info.func(*info.args, **(info.kwargs or {}))
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/aren/.local/lib/python3.11/site-packages/usb/_objfinalizer.py", line 106, in _do_finalize_object_ref
    obj._do_finalize_object()
  File "/home/aren/.local/lib/python3.11/site-packages/usb/_objfinalizer.py", line 73, in _do_finalize_object
    self._finalize_object()
  File "/home/aren/.local/lib/python3.11/site-packages/usb/backend/libusb1.py", line 613, in _finalize_object
    _lib.libusb_unref_device(self.devid)
                             ^^^^^^^^^^
AttributeError: '_Device' object has no attribute 'devid'
Exception ignored in: <function _AutoFinalizedObjectBase.__del__ at 0x7f07570a8540>
Traceback (most recent call last):
  File "/home/aren/.local/lib/python3.11/site-packages/usb/_objfinalizer.py", line 86, in __del__
    self.finalize()
  File "/home/aren/.local/lib/python3.11/site-packages/usb/_objfinalizer.py", line 146, in finalize
    self._finalizer()
    ^^^^^^^^^^^^^^^
AttributeError: '_Device' object has no attribute '_finalizer'

The crucial lines appears to be these ones:


    if self.original_product == HID_PRODUCT_ID and self.dev.idProduct == COM_PRODUCT_ID:
                                                   ^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'idProduct'

If I now run the command again, it works perfectly fine.

This has meant that I've had to create the following slightly ugly hack bash script as a workaround to just copy all of the files off the device (which is all I'm really interested in):

#!/bin/bash

# Get timestamp for postfix on directory name
TIMESTAMP="`date +%Y-%m-%d-%H_%M_%S`"
WRITEDIR=writing-${TIMESTAMP}
mkdir $WRITEDIR

neotools files read-all --path $WRITEDIR/ &
PID=$!
sleep 0.2
kill $PID 
sleep 1.0
echo "Copying text files off Neo..."
neotools files read-all --path $WRITEDIR/ 

ls -lh $WRITEDIR
echo "Files copied into $WRITEDIR"

As you can see, I explicity run the command, tiny delay, kill it using its pid, small delay, then run the command again, and this works just fine and copies everything off my Neo...

Any idea what the fix/problem is? In any case, thanks for this project, is super handy being able to just dump the files off the Neo rather than having to send things one-by-one in keyboard mode...

lykahb commented 1 year ago

It looks like the tool is waiting for the Neo to switch into the communication mode. The Ctrl-C signal must come when the logic is in this loop

I haven't encountered this error. Can you run neotools --verbose info and post the result here?

Also, instead of running the files command twice, it is cleaner to make the first command just a mode switch neotools mode --comms.

ArenT1981 commented 1 year ago

@lykahb Thanks for reply. This is the exact result of neotools --verbose info. As you can see, it just freezes/goes no further than the "Connecting to Neo in communication mode"

--> neotools --verbose info
DEBUG:neotools.device:Searching for device
DEBUG:neotools.device:Detaching kernel driver
DEBUG:neotools.device:Switching Neo to communication mode
DEBUG:neotools.device:Connecting to Neo in communication mode

Looking at your indicated code, it looks like is indeed stuck in that infinite holding loop, with the self.dev device descriptor stuck in a None state, or otherwise making it down to line 88, where it seems to freeze, until the Ctrl-C reveals the error message as indicated above:


    if self.original_product == HID_PRODUCT_ID and self.dev.idProduct == COM_PRODUCT_ID:
                                                   ^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'idProduct'

Anyway, your suggested workaround works fine, although perhaps not entirely with the behaviour you expected. I modified the relevant lines of my copy script to this:

echo "* Preparing Neo to copy files off device..."
neotools mode --comms &
sleep 1.0
echo "* Copying text files off Neo..."
neotools files read-all --path $WRITEDIR/ 

And this now works very nicely/cleanly. However, one thing that might be interesting for you to note, is that the initial neotools mode --comms command does not consistently automatically cleanly exit/return to console... It just sits there... Until a further command is run. So by backgrounding the process, sleeping a bit (just to give the Neo to do whatever logic switching it is doing), then calling the files read-all command, all works cleanly and the files are copied and the script nicely exits/completes. So it appears that at the same exact time the files copying process is initiated the mode --comms completes.

So it is almost as if the process is hanging/waiting until something else runs to complete the change of state and end that process, of that it is forcing a buffer/cache to be cleared. Could it be some sort of USB device buffer/caching issue/mode? Weird. In any case, as I say, with my script like the above, it seems be working very nicely, so thank you so much for this tool, it really is a lot handier than having to transfer all of my writing by the traditional "keyboard send" method.

lykahb commented 1 year ago

Yeah, both neotools mode --comms and the command to read files hang while they wait for the Neo to switch to the communication mode. I prefer running the mode command first because it does not touch the files.

I believe that when the sequence to initiate the mode change is sent to Neo, something happens that makes it to switch mode successfully when the sequence comes the second time. I'd like to reproduce this. But if I cannot, I can replace indefinite wait with a second attempt to switch after a short timeout.

ArenT1981 commented 1 year ago

"But if I cannot, I can replace indefinite wait with a second attempt to switch after a short timeout."

Sounds like a good option if you're not able to discover whatever the 'secret' mode switch sequence is that actually occurs. In the meantime, running neotools mode --comms & to background it, with a short sleep command directly after, seems to work extremely well for people wanting to add bash scripts/wrappers to do routine tasks like copying files off the device like I did.

lykahb commented 1 year ago

I published a version that tries flipping the mode twice. Can you upgrade to neotools 0.3.4 and run neotools files read-all --path $WRITEDIR?

ArenT1981 commented 1 year ago

Your latest update/version with this retry logic seems to have indeed fixed the issue entirely, and every command is now working perfectly on my Neo. It takes a couple of seconds to complete, but it is all working exactly as it should.

Commands that I've tested/verified as working straight off, with zero issues:

neotools info
neotools --verbose info
neotools files read foo
neotools files write bar.txt bar
neotools files read-all --path foo/

Meaning my bash script wrapper has now been reduced down to this much nicer script:

 #!/bin/bash

# Get timestamp for postfix on directory name
TIMESTAMP="`date +%Y-%m-%d-%H_%M_%S`"
WRITEDIR=neo-writing-${TIMESTAMP}
mkdir $WRITEDIR

echo "* Copying text files off Neo..."
neotools files read-all --path $WRITEDIR/ 

ls -lh $WRITEDIR
echo "* Files copied into $WRITEDIR"

Thanks. I'll let you close this issue as complete. Your tooling means I now have the ability to seamlessly read/write text files to the Neo as a seamless USB device. Brilliant. This turns into an even more near-perfect minimal distraction writers or note-taking device for 2023. It's a pity Renaissance Learning or whoever can't resume manufacturing them again, pretty much in their existing hardware configuration or equivalent (though they could probably now increase the memory/storage by a factor of 100,000x with no cost penalty), and for a competitive price. There is a lot to be said for using commonplace AA batteries rather than lithion-ion rechargeables that inevitably deteriorate. Anyway, hopefully my device will live for a long time before dying...

I added the following convenience aliases to my .bashrc :smile: :

alias neo-write="neotools files write"
alias neo-read="neotools files read"