Skip to content

Conversation

@laomaiweng
Copy link
Contributor

@laomaiweng laomaiweng commented Jul 24, 2025

The device type (RM1, RM2, RMPP) is determined in multiple places across the code based on either the contents of the
/sys/devices/soc0/machine file, or from the user-provided --hardware argument. This makes it error-prone, and new commands or features also need to come with their own logic for device type determination.

This PR introduces the HardwareType enum, and its associated parse function, so that the logic to determine the device type lives in a single location.

It also adds a --hardware option to codexctl list, to only list images for the specified hardware type.

Summary by CodeRabbit

  • New Features

    • The CLI now supports an optional argument to filter version listings by hardware type.
  • Improvements

    • Hardware type handling is unified and more robust using a dedicated hardware type enum instead of string checks.
    • Version listing and download functions validate and filter results based on the selected hardware type.
    • Device update commands and compatibility checks utilize explicit hardware type properties for clarity and correctness.
  • Bug Fixes

    • Enhanced error handling for invalid or unsupported hardware types.
  • Tests

    • Updated tests to use the new hardware type enum for greater accuracy and consistency.
    • Added tests to verify error handling for unsupported hardware types.

The device type (RM1, RM2, RMPP) was determined in multiple places
across the code based on either the contents of the
/sys/devices/soc0/machine file, or from the user-provided --hardware
argument. This made it error-prone, and new commands or features also
needed to come with their own logic for device type determination.

This commit introduces the HardwareType enum, and its associated `parse`
function, so that the logic to determine the device type lives in a
single location.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 24, 2025

Walkthrough

The changes replace string-based hardware type handling with a strongly typed HardwareType enum throughout the codebase. Device identification, conditional logic, command construction, and version listing now use this enum. The CLI is updated to accept a hardware argument for listing versions, and tests are updated to use the enum. All relevant logic is refactored to use enum-based dispatch and properties.

Changes

File(s) Change Summary
codexctl/device.py
codexctl/device.py
Introduced HardwareType enum for hardware types; refactored device detection and related logic to use enum and its properties.
codexctl/init.py
codexctl/__init__.py
Updated to use HardwareType for device parsing, filtering, and validation; CLI supports hardware argument for listing.
codexctl/updates.py
codexctl/updates.py
Refactored UpdateManager methods to accept and use HardwareType enum instead of strings, updating all related logic.
tests/test.py
tests/test.py
Updated imports and test calls to use HardwareType enum members instead of device type strings; added test for exception on unsupported hardware; added assert_raises context manager.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI
    participant HardwareType
    participant DeviceManager
    participant UpdateManager

    User->>CLI: list --hardware <type>
    CLI->>HardwareType: parse(<type>)
    HardwareType-->>CLI: HardwareType.<value> or None
    CLI->>UpdateManager: get_latest_version(HardwareType)
    UpdateManager-->>CLI: version info (filtered by hardware)
    CLI->>User: display filtered version list
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~15–25 minutes

Suggested reviewers

  • Jayy001

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a0f05bb and e1d2347.

📒 Files selected for processing (1)
  • tests/test.py (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test.py
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
codexctl/__init__.py (1)

58-61: Simplify conditional with ternary operator

The static analysis tool correctly suggests simplifying this if-else block.

-            if hw:
-                remarkable_version = HardwareType.parse(hw)
-            else:
-                remarkable_version = None
+            remarkable_version = HardwareType.parse(hw) if hw else None
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d7313b7 and ec5bb12.

📒 Files selected for processing (4)
  • codexctl/__init__.py (5 hunks)
  • codexctl/device.py (5 hunks)
  • codexctl/updates.py (6 hunks)
  • tests/test.py (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
codexctl/updates.py (1)
codexctl/device.py (4)
  • HardwareType (19-73)
  • toltec_type (66-73)
  • old_download_hw (36-43)
  • new_download_hw (46-53)
tests/test.py (2)
codexctl/device.py (1)
  • HardwareType (19-73)
codexctl/updates.py (1)
  • get_toltec_version (137-161)
🪛 Ruff (0.12.2)
codexctl/__init__.py

58-61: Use ternary operator remarkable_version = HardwareType.parse(hw) if hw else None instead of if-else-block

Replace if-else-block with remarkable_version = HardwareType.parse(hw) if hw else None

(SIM108)

🔇 Additional comments (5)
tests/test.py (1)

9-9: LGTM! Correct adoption of HardwareType enum

The test file has been properly updated to use the HardwareType enum instead of string literals for device type specification. This change aligns perfectly with the PR's objective of consolidating device type logic.

Also applies to: 181-183, 187-194

codexctl/__init__.py (1)

26-26: Excellent implementation of hardware type consolidation

The changes successfully integrate the HardwareType enum throughout the file:

  • Proper error handling and fallback logic for hardware type parsing
  • Clean implementation of hardware-specific version filtering in the list command
  • Correct replacement of string-based checks with enum comparisons
  • Addition of the --hardware CLI option with appropriate aliases

These changes significantly improve type safety and maintainability.

Also applies to: 54-57, 79-87, 91-91, 230-230, 495-502

codexctl/device.py (2)

28-28: Verify the "tests" alias for RM2

Including "tests" as an alias for RM2 could be confusing and potentially cause issues if test environments need different handling. Is this intentional for test compatibility?


1-1: Well-structured HardwareType enum implementation

Excellent consolidation of device type logic:

  • The enum provides a single source of truth for hardware types
  • Clear separation of concerns with different properties for various update contexts
  • Proper error handling with descriptive ValueError messages for unsupported operations
  • Clean integration throughout the DeviceManager class

The implementation successfully achieves the PR's goal of centralizing device type determination.

Also applies to: 19-74, 113-113, 444-444, 518-518

codexctl/updates.py (1)

14-14: Clean refactoring to use HardwareType enum

The updates module has been successfully refactored to use the HardwareType enum:

  • All string-based device type checks replaced with type-safe enum matching
  • Proper use of hardware type properties for URL and filename construction
  • Exhaustive match statements ensure all hardware types are handled
  • The explicit string conversion for download_folder improves clarity

These changes align perfectly with the PR's objective and improve maintainability.

Also applies to: 118-135, 164-225

self.hardware = "reMarkable1"
else:
self.hardware = "reMarkable2"
self.hardware = HardwareType.parse(machine_contents)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to have changed handling, previously self.hardware would not be set if the file was not found, now it's defaulting to the rM2.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I follow. I read this logic as:

  • remote device:
    • with ftp.file(...) as file: throws a FileNotFoundError if the file is not found (no change from previous logic)
    • if the file is found, machine_contents contains its contents (no change from previous logic)
  • local device:
    • same as above but if the file is not found the exception is caught and machine_contents will be set to "tests" (no change from previous logic)
  • HardwareType.parse will either set the hardware type, or throw a ValueError if it doesn't understand the machine_contents (changes from previous logic: previously, it would default to "reMarkable2")

In my eyes the only difference is that now the logic will throw a ValueError if it doesn't understand the machine_contents (which will be set as before). Do you want the code to go ahead with self.hardware unset, or set to None, or something else?

Copy link
Collaborator

@Eeems Eeems Aug 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If machine_contents was tests in the previous code, self.hardware would not be set. Now it's being set to HardwareType.RM2.

Likely the way it was previously was to avoid raising an exception when instantiating the DeviceManager instance, but still preserving that we don't know what device type it is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, the way I read it, previously if machine_contents was tests, if would fall under the else: branch of the if "reMarkable Ferrari" in machine_contents test and set self.hardware = "reMarkable2".

But either way, I agree with your other comment that off-device tests should be handled differently than by piggy-backing onto the hardware detection logic. I've pushed a commit that relies on a mock object instead, and no longer needs the "tests" hack.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, I seem to have misread that every single time as another elseif, whoops.

def parse(cls, device_type: str) -> "HardwareType":
if device_type.lower() in ("pp", "pro", "rmpp", "ferrari", "remarkable ferrari"):
return cls.RMPP
elif device_type.lower() in ("2", "rm2", "remarkable 2", "tests"):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
elif device_type.lower() in ("2", "rm2", "remarkable 2", "tests"):
elif device_type.lower() in ("2", "rm2", "remarkable 2"):

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"tests" was in there because the code in DeviceManager.__init__ will set machine_contents to "tests" (and pass it to HardwareType.parse) if /sys/devices/soc0/machine is not found, in the local device case. I assumed this was done to support running tests on a PC rather than on the reMarkable itself.

Removing this value causes tests/test.py to fail with this exception:

❯ python ./tests/test.py 
Traceback (most recent call last):
  File "/home/home/Code/misc/codexctl/./tests/test.py", line 13, in <module>
    set_server_config = DeviceManager().set_server_config
                        ~~~~~~~~~~~~~^^
  File "/home/home/Code/misc/codexctl/codexctl/device.py", line 113, in __init__
    self.hardware = HardwareType.parse(machine_contents)
                    ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
  File "/home/home/Code/misc/codexctl/codexctl/device.py", line 33, in parse
    raise ValueError(f"Unknown hardware version: {device_type} (rm1, rm2, rmpp)")
ValueError: Unknown hardware version: tests (rm1, rm2, rmpp)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are no local tests outside of test.py or basic sanity checks that don't look to replicate running on device. @Jayy001 may have done some local testing by forcing some things, but that would not be good to leave enabled in the main application, and is better handled by either hiding it behind an environment variable to turn on, or build into test.py to trigger the explicit logic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, I've handled this in test.py by using a mock object.

Also add test for RMPP toltec update, which should fail.

Co-authored-by: Nathaniel van Diepen <Eeems@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
tests/test.py (1)

62-77: Fix unused variable in exception handling.

The context manager implementation is correct, but there's an unused variable assignment.

Apply this diff to fix the unused variable:

-    except expected as e:
+    except expected:
         print("pass")
         return
-    except Exception as e:
-        got = e.__class__.__name__
+    except Exception as ex:
+        got = ex.__class__.__name__
codexctl/updates.py (1)

137-164: LGTM! Proper enum-based toltec handling.

The method correctly uses the HardwareType.toltec_type property and handles the ValueError appropriately by converting it to SystemExit. This aligns with the previous review feedback.

Consider using exception chaining for better error traceability:

-        except ValueError as ex:
-            raise SystemExit(*ex.args)
+        except ValueError as ex:
+            raise SystemExit(*ex.args) from ex
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 01b145b and ef08214.

📒 Files selected for processing (2)
  • codexctl/updates.py (6 hunks)
  • tests/test.py (3 hunks)
🧰 Additional context used
🪛 Ruff (0.12.2)
codexctl/updates.py

150-150: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

tests/test.py

69-69: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)

🔇 Additional comments (9)
tests/test.py (4)

9-9: LGTM! Import updated correctly.

The addition of HardwareType to the import statement properly supports the enum-based hardware type handling throughout the test file.


197-198: LGTM! Enum usage correctly implemented.

The replacement of string literals with HardwareType enum members properly aligns with the consolidated device type logic.


203-203: LGTM! Consistent enum usage.

The get_toltec_version calls correctly use HardwareType enum members instead of string literals.

Also applies to: 208-208


211-212: LGTM! Proper exception testing added.

The new test correctly verifies that get_toltec_version raises SystemExit for RMPP hardware, which is expected since toltec is not supported on ReMarkable Paper Pro.

codexctl/updates.py (5)

14-14: LGTM! Import correctly added.

The HardwareType import properly supports the enum-based refactoring throughout the file.


118-135: LGTM! Clean enum-based refactoring.

The method signature and implementation correctly use the HardwareType enum with a clean match statement for version selection. This eliminates string-based checks and improves type safety.


166-167: LGTM! Method signature properly updated.

The parameter type change to HardwareType enum and renaming to hardware_type correctly aligns with the consolidated device type logic.


181-188: LGTM! Explicit string conversion added.

The explicit str() conversion of the Path object ensures compatibility with downstream operations that expect string paths.


199-227: LGTM! Excellent enum-based download logic.

The refactoring successfully replaces string-based conditionals with clean enum dispatch. Using hardware_type.old_download_hw and hardware_type.new_download_hw properties centralizes hardware-specific filename logic and improves maintainability.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
tests/test.py (1)

66-81: Fix unused variable in exception handling.

The context manager is well-implemented, but there's an unused variable e on line 73 that should be addressed.

Apply this diff to fix the unused variable:

-    except expected as e:
+    except expected:
         print("pass")
         return
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ef08214 and a0f05bb.

📒 Files selected for processing (3)
  • codexctl/__init__.py (5 hunks)
  • codexctl/device.py (5 hunks)
  • tests/test.py (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • codexctl/init.py
  • codexctl/device.py
🧰 Additional context used
🪛 Ruff (0.12.2)
tests/test.py

73-73: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)

🔇 Additional comments (5)
tests/test.py (5)

6-6: LGTM! Import changes support the HardwareType refactoring.

The addition of NonCallableMock and HardwareType imports aligns perfectly with the PR's objective to consolidate device type logic using a strongly typed enum.

Also applies to: 10-10


14-17: LGTM! Mock setup properly supports the refactored set_server_config method.

The NonCallableMock with only the logger field and the class method reference correctly accommodate the change from instance method to class method call.


86-86: LGTM! Correctly updated to call set_server_config as a class method.

The addition of device_manager as the first argument properly reflects the refactoring from instance method to class method.


201-202: LGTM! Perfect implementation of HardwareType enum usage.

The replacement of string literals ("reMarkable 1", "reMarkable 2") with HardwareType.RM1 and HardwareType.RM2 perfectly aligns with the PR's objective to consolidate device type logic using a strongly typed enum.

Also applies to: 207-207, 212-212


215-216: LGTM! Excellent addition of error handling test.

This test case properly validates that get_toltec_version raises SystemExit when called with HardwareType.RMPP, ensuring robust error handling for unsupported hardware types.

@Jayy001 Jayy001 merged commit 5055a9d into Jayy001:main Sep 10, 2025
1 check passed
@Jayy001
Copy link
Owner

Jayy001 commented Sep 10, 2025

@laomaiweng Thank you so much ❤️

@laomaiweng laomaiweng deleted the feature/hardware-type-enum branch September 10, 2025 15:07
@Endermanbugzjfc
Copy link

#146

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants