krux icon indicating copy to clipboard operation
krux copied to clipboard

[Bug] Generating qr.nc fail : Error: IndexError('bytes index out of range',)

Open spatcho opened this issue 9 months ago • 67 comments

Describe the bug

Printer driver is set to cnc.

It doesn't matter if you load the wallet from a new mnemonic or from a qr scan.

After wallet is loaded, I do : Backup Mnemonic > Encrypted > Encrypted QR Code > Type key and proceed without custom id

When presented with QR code on device screen, I do : Print as QR > yes to "cnc/file" > and I quickly get a screen showing in red "Error: IndexError('bytes index out of range',)"

The sd card is recognized, I could save qr as images, and I see an empty "qr.nc" file with updated timestamp but 0 bytes size.

I tried to modify the cnc driver setup, without success, and I retested with default cnc driver setup.

Issue happen on my wonder mv device. Issue happen on the simulator for wonder my and also for amigo.

I am now trying to build a firmware with MICROPY_ENABLE_COMPILER to see more infos. I have a cnc on grbl, that would be another step.

Device(s) affected

  • [ ] Amigo
  • [ ] M5stickV
  • [ ] Cube
  • [ ] Dock
  • [ ] Bit
  • [ ] Yahboom
  • [X] WonderMV

Version affected

  • [X] Official release (selfcustody/krux): v25.03.0
  • [ ] Beta release (odudex/krux_binaries): vXX.YY.Z-betaWW

spatcho avatar Mar 28 '25 14:03 spatcho

Export to CNC feature is "abandoned", as non of devs have one to test, and, until now, we didn't know any user making use of it. I was even suggesting to remove it. But since we have you now, and it seems you're motivated to make it work, we can try to help! I'll try to track and solve the error on my side too

odudex avatar Mar 28 '25 14:03 odudex

I don't think you need to enable compiler, just comment these lines of the try/except and build the firmware using the script /.krux build maixpy_yahboom (or other device name) and flash the firmware onto device using instructions here. Then follow these instructions to use MaixPy IDE and see the exception on terminal.

tadeubas avatar Mar 28 '25 14:03 tadeubas

I am on silicon Mac but I could compile and build a new firmware using the linux how to : krux build maixpy_wonder_mv . To flash I didn't find out how to pass the -p and -B params to krux flash so I used odudex ktool-mac, replacing the contents of maixpy_wonder_mv with my binaries : ktool-mac -p /dev/cu.usbserial-xxxxxx -B dan -b 1500000 maixpy_wonder_mv/kboot.kfpkg . Seems it went through, I see my updated version in the about screen on device.

In this section, I removed line "self.ctx.display.clear()", but it doesn't help having more details on the issue.

About MaixPy IDE, I tried, seems it connect as the device is rebooting, but it never complete and the device stay frozen. As I see no "wonder mv" in the supported devices list, could I do something ?

spatcho avatar Mar 28 '25 17:03 spatcho

Change this to see the error in the terminal -> remove the try / except block.

From this:

try:
    self.ctx.display.clear()
    status = item[1]()
    if status != MENU_CONTINUE:
        return status
except Exception as e:
    self.ctx.display.to_portrait()
    self.ctx.display.clear()
    self.ctx.display.draw_centered_text(
        t("Error:") + "\n%s" % repr(e), theme.error_color
    )
    self.ctx.input.wait_for_button()

To this:

self.ctx.display.clear()
    status = item[1]()
    if status != MENU_CONTINUE:
        return status

Then to connect to the device and see the output in terminal copy the below file (kboot.py.txt rename to remove the .txt at the end) to the project root dir and execute (maybe you will need to install dependencies, use poetry install --all-extras to install everything including dependencies needed to run our simulator): poetry python ktool.py -t if you don't see nothing changing in the terminal try to reset the device while connected or to use the cmd with this param: poetry python ktool.py -t -B dan

ktool.py.txt

If you want to test faster, use our simulator with sd card enabled (a folder sd will be created on the project's simulator folder): poetry run poe simulator --sd

I've tested on the simulator and can confirm that this error appears: Image

The stacktrace below appears on terminal (with the change mentioned above) :

Exception in thread Thread-1 (run_krux):
Traceback (most recent call last):
  File "/usr/lib/python3.12/threading.py", line 1073, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.12/threading.py", line 1010, in run
    self._target(*self._args, **self._kwargs)
  File "/krux/simulator/simulator.py", line 130, in run_krux
    exec(boot_file.read())
  File "<string>", line 159, in <module>
  File "<string>", line 133, in home
  File "/krux/src/krux/pages/__init__.py", line 512, in run
    _, status = self.menu.run_loop(start_from_index)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/krux/src/krux/pages/__init__.py", line 686, in run_loop
    status = self._clicked_item(selected_item_index)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/krux/src/krux/pages/__init__.py", line 718, in _clicked_item
    status = item[1]()
             ^^^^^^^^^
  File "/krux/src/krux/pages/home_pages/home.py", line 71, in backup_mnemonic
    return mnemonics_viewer.mnemonic()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/krux/src/krux/pages/home_pages/mnemonic_backup.py", line 46, in mnemonic
    submenu.run_loop()
  File "/krux/src/krux/pages/__init__.py", line 686, in run_loop
    status = self._clicked_item(selected_item_index)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/krux/src/krux/pages/__init__.py", line 718, in _clicked_item
    status = item[1]()
             ^^^^^^^^^
  File "/krux/src/krux/pages/home_pages/mnemonic_backup.py", line 87, in encrypt_mnemonic_menu
    return encrypt_mnemonic_menu.encrypt_menu()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/krux/src/krux/pages/encryption_ui.py", line 176, in encrypt_menu
    _, _ = submenu.run_loop()
           ^^^^^^^^^^^^^^^^^^
  File "/krux/src/krux/pages/__init__.py", line 686, in run_loop
    status = self._clicked_item(selected_item_index)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/krux/src/krux/pages/__init__.py", line 718, in _clicked_item
    status = item[1]()
             ^^^^^^^^^
  File "/krux/src/krux/pages/encryption_ui.py", line 280, in encrypted_qr_code
    seed_qr_view.display_qr(allow_export=True)
  File "/krux/src/krux/pages/qr_view.py", line 505, in display_qr
    _, status = submenu.run_loop()
                ^^^^^^^^^^^^^^^^^^
  File "/krux/src/krux/pages/__init__.py", line 686, in run_loop
    status = self._clicked_item(selected_item_index)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/krux/src/krux/pages/__init__.py", line 718, in _clicked_item
    status = item[1]()
             ^^^^^^^^^
  File "/krux/src/krux/pages/qr_view.py", line 435, in print_qr
    utils.print_standard_qr(self.code, title=title, is_qr=True)
  File "/krux/src/krux/pages/utils.py", line 50, in print_standard_qr
    print_page.print_qr(data, qr_format, title, width, is_qr)
  File "/krux/src/krux/pages/print_page.py", line 62, in print_qr
    self._send_qr_to_printer(data)
  File "/krux/src/krux/pages/print_page.py", line 48, in _send_qr_to_printer
    self.printer.print_qr_code(qr_code)
  File "/krux/simulator/kruxsim/mocks/machine.py", line 40, in new_print_qr_code
    return old_print_qr_code(qr_code)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/krux/src/krux/printers/cnc.py", line 255, in print_qr_code
    super().print_qr_code(qr_code)
  File "/krux/src/krux/printers/cnc.py", line 72, in print_qr_code
    while qr_code[size] != "\n":
          ~~~~~~~^^^^^^
IndexError: bytearray index out of range

Conclusion

For me it appears that CNC driver was built to deal with TEXT QRCodes only, but the Encrypted one is in binary form.

tadeubas avatar Mar 29 '25 05:03 tadeubas

Thanks for help, with your code modification suggestion and that command "poetry run python ktool.py -t -p /dev/cu.usbserial-xxxx -B dan" I could see output clearly.

For some reason in my initial testing I saw the cnc option only for encrypted, but now I see the option for any kind of qr.

The error is also with plain text qr, seed qr, compact qr.

spatcho avatar Mar 29 '25 08:03 spatcho

The simulator, can it keep the printer driver settings (default to cnc, etc) and load a qr from an existing source ? Right now to use it I have to set the driver every time and fastest I found is to create a mnemonic with 12 words.

spatcho avatar Mar 29 '25 09:03 spatcho

I placed some debug infos in that block and checked for end of qr_code array loop by size instead of '\n' char, to go further without exception. That way I get a qr.nc generated but only with modal setting gcodes, nothing else.

Did the qr_code array structure changed since the last cnc tested version, around mid June 2023 as of this issue ? No matter the type of qr (plain or compact tested), the array is filled with numbers. I am no python expert but the code look for a '\n' whithout (I think) converting to char, and to decide whether to cut or not, it look for 0 or 1, which would I guess mean it's a hole in the qr, but there is very few 0 or 1 on the qr_code array.

Can you explain me how the qr_code array work ?

spatcho avatar Mar 29 '25 10:03 spatcho

Sorry I didn't look at this issue yet, but I see you are digging deep. Yes, the QR structure was optimized, from strings of zeros, ones and \n, to binary format, where each QR "pixel", or block, is represented by 1 bit. This is likely the cause of the issue.

odudex avatar Mar 29 '25 14:03 odudex

Yes, if you run the simulator with --sd. This will create an sd folder inside simulator and enable access to files there. In settings you can change the Persist -> Location to SD card and this will save any changes you made to defaults.

To test this faster at "Home" screen, I like to change boot.py and "jump" Login by commenting its line here, then create a new Wallet in ctx (context object):

# login(ctx)
# gc.collect()
from krux.wallet import Wallet
from krux.key import Key
ctx.wallet = Wallet(Key("action action action action action action action action action action action action", False))
home(ctx)

As you have already identified, you can use an older version in order to test CNC for now. I think we can convert the QR code back to the non "optimized structure" before sending it to the CNC printer. This would solve the issue.

tadeubas avatar Mar 30 '25 01:03 tadeubas

SD card and new wallet in simulator, great tip, thanks 👍

Does it make sense if I inspire from the thermal printer code to understand the optimized qr_code array and adapt the cnc code ? I don't have a thermal printer, I don't know if that part is up to date.

spatcho avatar Mar 30 '25 17:03 spatcho

Does it make sense if I inspire from the thermal printer code to understand the optimized qr_code array and adapt the cnc code ? I don't have a thermal printer, I don't know if that part is up to date.

It does, shouldn't be complicated. Thermal printer is working. Do you have grbl gcode simulator/visualizer to inspect the results?

odudex avatar Mar 30 '25 17:03 odudex

I have an openbuilds cnc that I want to test with, and I can inspect first with open builds control software.

spatcho avatar Mar 30 '25 17:03 spatcho

Ok, did some progress here and here Image

It should reproduce the "action action ..." wallet in compact format image

It miss / mix / mirror some data. For now if the bit is more than 0 I consider it as a dot. Will see.

The code so far is

    def print_qr_code(self, qr_code):
        """Prints a QR code, scaling it up as large as possible"""
        from ..qr import get_size

        size = 0

        size = get_size(qr_code)
        print("size:",size)

        print("self.part_size",self.part_size," / self.border_padding:",self.border_padding," / size:",size)

        cell_size = (self.part_size - (self.border_padding * 2)) / size

        print("cell_size:",cell_size)

        # Modal settings
        self.on_gcode("G17")  # x/y plane
        self.on_gcode("G20" if self.unit == "in" else "G21")  # units
        self.on_gcode("G40")  # cancel diameter compensation
        self.on_gcode("G49")  # cancel length offset
        self.on_gcode("G54")  # coord system 1
        self.on_gcode("G90")  # non-incremental motion
        self.on_gcode("G94")  # feed/minute mode

        print("self.cut_depth:",self.cut_depth," / self.pass_depth:",self.pass_depth)

        scale = Settings().hardware.printer.thermal.adafruit.paper_width // size
        scale *= Settings().hardware.printer.thermal.adafruit.scale  # Scale in %
        scale //= 200  # 100% * 2 because printer will scale 2X later to save data
        # Being at full size sometimes makes prints more faded (can't apply too much heat?)
        print("scale:",scale)

        line_bytes_size = (size * scale + 7) // 8  # amount of bytes per line
        print("line_bytes_size:",line_bytes_size)

        #num_passes = math.ceil(self.cut_depth / self.pass_depth)
        num_passes = 1
        for p in range(num_passes):
            print("pass #",p)
            for row in range(size):
                print("row #",row)
                byte = 0
                line_bytes = bytearray()
                for col in range(size):
                    print("col #",col)
                    bit_index = row * size + col
                    print("bit_index #",bit_index)
                    bit = qr_code[bit_index >> 3] & (1 << (bit_index % 8))
                    cut = 0
                    if bit > 0:
                        cut = 1
                    print("bit:",bit," / cut:",cut)
                    if cut:
                        # Flip the y coord
                        y = row
                        x = col
                        x_index = x
                        if y % 2 == 0:
                            x_index = size - 1 - x
                        plunge_depth = min((p + 1) * self.pass_depth, self.cut_depth)
                        self.cut_cell(x_index, size - 1 - y, cell_size, plunge_depth)

spatcho avatar Mar 30 '25 18:03 spatcho

Wow, didn't know that this exists https://software.openbuilds.com/ very nice!

tadeubas avatar Mar 30 '25 18:03 tadeubas

Ok, did some progress here and here

You're close!

odudex avatar Mar 30 '25 18:03 odudex

Ok, did some progress here and here

You're close!

Indeed . Now the dots are correctly read.

Image

    def print_qr_code(self, qr_code):
        """Prints a QR code, scaling it up as large as possible"""
        from ..qr import get_size

        size = 0

        size = get_size(qr_code)
        print("size:",size)

        print("self.part_size",self.part_size," / self.border_padding:",self.border_padding," / size:",size)

        cell_size = (self.part_size - (self.border_padding * 2)) / size

        print("cell_size:",cell_size)

        # Modal settings
        self.on_gcode("G17")  # x/y plane
        self.on_gcode("G20" if self.unit == "in" else "G21")  # units
        self.on_gcode("G40")  # cancel diameter compensation
        self.on_gcode("G49")  # cancel length offset
        self.on_gcode("G54")  # coord system 1
        self.on_gcode("G90")  # non-incremental motion
        self.on_gcode("G94")  # feed/minute mode

        print("self.cut_depth:",self.cut_depth," / self.pass_depth:",self.pass_depth)

        #num_passes = math.ceil(self.cut_depth / self.pass_depth)
        num_passes = 1
        for p in range(num_passes):
            print("pass #",p)
            for row in range(size):
                print("row #",row)
                byte = 0
                line_bytes = bytearray()
                for col in range(size):
                    print("col #",col)
                    bit_index = row * size + col
                    print("bit_index #",bit_index)
                    bit = qr_code[bit_index >> 3] & (1 << (bit_index % 8))
                    cut = 0
                    if bit > 0:
                        cut = 1
                    print("bit:",bit," / cut:",cut)
                    if cut:
                        # Flip the y coord
                        y = row
                        x = col
                        x_index = x
                        plunge_depth = min((p + 1) * self.pass_depth, self.cut_depth)
                        self.cut_cell(x_index, size - 1 - y, cell_size, plunge_depth)

spatcho avatar Mar 31 '25 07:03 spatcho

I added an "SVG" option to the "Save QR Image to SD card", this output an .svg, it use lib https://github.com/orsinium-labs/svg.py . That was useful for me to play with 3d printing, and real quick to do now that I got the qr_code format understood. Would it be interesting to add it to the trunk ?

Here the menu Image and the output Image

Here the encrypted ( password abc ) qr as svg Image

And an easy slice with prusa slicer, which is i.e. recognized as additional software in tails Image and the output, which was scanned ok Image

Am I right that it is only possible to export encrypted qr images and not compact, plain, etc ?

spatcho avatar Mar 31 '25 09:03 spatcho

I think the nc code generation Is good now. It will take me some time to test on the machine, I need to do some additional work on it first.

spatcho avatar Mar 31 '25 10:03 spatcho

Am I right that it is only possible to export encrypted qr images and not compact, plain, etc ?

Yes, for now we don't allow saving unencrypted secrets, even in image formats.

I'm impressed on how fast you got familiarized with the code, fixed bugs and implemented new features! We'll want a Pull Requests!

Also, please consider joining our Telegram group. People will love to follow your hacking progress with 3D printer and CNC!

odudex avatar Mar 31 '25 13:03 odudex

Nice addition! But I think this SVG lib will not work on device, need to test... did you find a micropython implementation alternative for SVG?

tadeubas avatar Mar 31 '25 13:03 tadeubas

Nice addition! But I think this SVG lib will not work on device, need to test... did you find a micropython implementation alternative for SVG?

Ah! Indeed you are right, it fail to fin the svg lib ... the output svg is very simple, I will see to code it.

spatcho avatar Mar 31 '25 13:03 spatcho

Am I right that it is only possible to export encrypted qr images and not compact, plain, etc ?

Yes, for now we don't allow saving unencrypted secrets, even in image formats.

I'm impressed on how fast you got familiarized with the code, fixed bugs and implemented new features! We'll want a Pull Requests!

Also, please consider joining our Telegram group. People will love to follow your hacking progress with 3D printer and CNC!

Thank you, ! will do the pull, it's a very pleasant project to develop for, easy to setup, helper tools, and you helped me a lot.

spatcho avatar Mar 31 '25 14:03 spatcho

I adapted the svg output to micropython. No additional lib. I don't like to write xml without a support lib but didn't find one for micropython, except a parser which I don't need. So hardcoded write of '<svg xmlns...' etc but I guess the structure is simple enough for this to be acceptable.

I tested again cnc and svg to sd card on the device, not only on the simulator.

I would like to make the pull request, could you instruct me a bit : shall I fork and create some specific branch ? naming ?

spatcho avatar Apr 01 '25 08:04 spatcho

Wow, nice to know that! To create a PR:

  1. Fork the project from here (selfcustody).
  2. Clone the project into your machine
  3. On the folder create a new branch using git checkout -b qr_cnc
  4. Make changes to the files you needed
  5. Commit git commit -m and type your message about what is in the commit
  6. Push the changes to your branch git push (for the first time, git will say the branch doesn't exist on your fork yet, only on your machine, so follow the cmd instructions it gives to you to it will be something like git push --set-upstream origin)
  7. Your branch qr_cnc and changes will appear on Github on your fork (your projects).
  8. Navigate to your fork (your project) on github and a button will appear at top suggesting to create a PR on selfcustody based on the branch you recently pushed (created on github), just click on this button and follow instructions selecting the selfcustody develop branch (alternatively you can enter in selfcustody/krux click on Pull request at top and then new and select the selfcustody develop branch and you qr_cnc branch)

You can use VSCode to help with git related commands by using its interface if you prefer

Then we will see your changes on the PR here and comment about it to fix something in order to integrate the code.

tadeubas avatar Apr 01 '25 12:04 tadeubas

Could you help me with the rx tx pins on the wondermv ? I connected the hardware correctly but I don't know if the 27 / 28 pin numbers I see on the app are correct and I didn't find any specific specs on wondermv pins setup. I uncommented on cnc.py as documented, but it always say "not connected" when I choose to print direct to cnc.

spatcho avatar Apr 03 '25 13:04 spatcho

This is something I never tested too, on thermal printer. Pins 27 and 28 are correct for WonderMV

Image

Image

odudex avatar Apr 03 '25 13:04 odudex

Thanks a lot. Where did you find those specs ? I ordered a m5stickv and a maix cube, so I can test other devices.

spatcho avatar Apr 03 '25 13:04 spatcho

On thermal printer we always recommend to use the TX pin and GND only because Krux doesn't need to receive nothing from the printer, and connecting RX sometimes could cause issues.

tadeubas avatar Apr 03 '25 14:04 tadeubas

Where did you find those specs ?

After intensive investigation :) https://drive.google.com/drive/folders/1Gy9MKHwWgql2Bpu28xJtZg6IAgV2TTyR

odudex avatar Apr 03 '25 16:04 odudex

I can happily say that I see now grbl response messages - error messages but at least it communicates

spatcho avatar Apr 04 '25 08:04 spatcho