Skip to content

Feature/no external#306

Merged
meganetaaan merged 10 commits intodev/v1.0from
feature/no_external
Nov 25, 2024
Merged

Feature/no external#306
meganetaaan merged 10 commits intodev/v1.0from
feature/no_external

Conversation

@meganetaaan
Copy link
Copy Markdown
Collaborator

@meganetaaan meganetaaan commented Nov 16, 2024

#94 の対応です。submoduleを一旦廃止して、サーボのドライバ等の関連するモジュールをリポジトリに集約しました。

Summary by CodeRabbit

  • New Features

    • Introduced drivers for RS30X and SCServo motors, enabling enhanced control and communication.
  • Documentation

    • Updated README files to clarify project structure and provide better guidance for users.
    • Simplified instructions for cloning the repository by removing the --recursive option.
    • Removed references to external modules in documentation, streamlining the user experience.
  • Bug Fixes

    • Adjusted build workflows to improve efficiency by eliminating unnecessary submodule checks during the build process.
    • Enhanced workflow conditions to ensure deployments occur only when actual changes are detected.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Nov 16, 2024

Walkthrough

The changes in this pull request involve the complete removal of three submodules from the .gitmodules file, along with corresponding deletions of related files and references throughout the documentation and configuration files. The modifications simplify the project's structure by eliminating references to external modules and adjusting the documentation to reflect these changes. Additionally, new driver implementations for RS30X and SCServo motors have been introduced, enhancing the firmware's capabilities.

Changes

File Change Summary
.gitmodules Removed entries for submodules: firmware/extern/avatar, firmware/extern/rs30x, firmware/extern/scservo.
firmware/README.md Removed reference to external modules from the directory structure section.
firmware/README_ja.md Updated introduction and directory structure sections; added links and clarifications regarding ongoing development.
firmware/docs/getting-started.md Removed --recursive option from git clone command; commented out Docker-related setup instructions.
firmware/docs/getting-started_ja.md Removed --recursive option from git clone command; commented out Docker-related setup instructions.
firmware/extern/avatar Deleted file containing a subproject commit reference.
firmware/extern/manifest_extern.json Deleted file specifying included manifest files.
firmware/extern/rs30x Deleted file containing a subproject commit reference.
firmware/extern/scservo Deleted file containing a subproject commit reference.
firmware/stackchan/drivers/manifest_driver.json Removed entries for scservo and rs30x manifests in the "include" section.
firmware/stackchan/drivers/rs30x.ts Introduced driver for RS30X servo motor with various methods and error handling.
firmware/stackchan/drivers/scservo.ts Introduced driver for SCServo motors with comprehensive control methods and error handling.
firmware/tsconfig.json Removed paths for scservo and rs30x from the compilerOptions.paths section.
.github/workflows/build.yml Removed submodules: recursive from the checkout step in the GitHub Actions workflow.
.github/workflows/bundle.yml Removed submodules: recursive from the checkout step; updated conditional execution based on changes detected.

Possibly related PRs

🐇 In the fields where we used to roam,
External modules have found no home.
With RS30X and SCServo in sight,
Our firmware's future is looking bright!
A simpler path, we now embrace,
A joyful leap in our coding space! 🌱


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ 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.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • 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 generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

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 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.

@meganetaaan
Copy link
Copy Markdown
Collaborator Author

リポジトリ側に持ってきたドライバのソースがlintに引っかかっているので、要修正

@meganetaaan meganetaaan marked this pull request as ready for review November 17, 2024 14:36
@meganetaaan meganetaaan requested a review from stc1988 November 17, 2024 14:36
@meganetaaan
Copy link
Copy Markdown
Collaborator Author

@stc1988 レビューをお願いできますでしょうか 🙏

Copy link
Copy Markdown
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: 9

🧹 Outside diff range and nitpick comments (7)
firmware/docs/getting-started_ja.md (1)

Line range hint 33-34: Update the introduction text to match available setup methods

The text mentions "次の3通りの方法" (following 3 methods) but Docker setup is commented out. This should be updated to "次の2通りの方法" (following 2 methods) to match the available options.

-次の3通りの方法があります。
+次の2通りの方法があります。
🧰 Tools
🪛 Markdownlint

20-20: null
Dollar signs used before commands without showing output

(MD014, commands-show-output)


21-21: null
Dollar signs used before commands without showing output

(MD014, commands-show-output)


22-22: null
Dollar signs used before commands without showing output

(MD014, commands-show-output)

firmware/docs/getting-started.md (3)

17-23: Add context about repository structure changes

Since this is a significant change from using submodules to having all code in the main repository, consider adding a brief note explaining this architectural change. Also, showing command output would help users verify successful execution.

 Clone this repository.
+
+> Note: Previously, this project used Git submodules which required the `--recursive` flag.
+> These have now been consolidated into the main repository for simplicity.
 
 ```console
 $ git clone https://github.com/stack-chan/stack-chan.git
 $ cd stack-chan/firmware
 $ npm i
+
+# Expected output:
+added XX packages in Ys

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Markdownlint</summary>

20-20: null
Dollar signs used before commands without showing output

(MD014, commands-show-output)

---

21-21: null
Dollar signs used before commands without showing output

(MD014, commands-show-output)

---

22-22: null
Dollar signs used before commands without showing output

(MD014, commands-show-output)

</details>

</details>

---

Line range hint `39-83`: **Remove commented-out Docker documentation**

Instead of keeping the Docker-related documentation commented out, it should be properly removed. If you want to preserve this information for historical purposes, consider:
1. Moving it to a separate "legacy" or "alternative-setups" document
2. Keeping it in Git history
3. Adding a note about why Docker support was removed

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Markdownlint</summary>

20-20: null
Dollar signs used before commands without showing output

(MD014, commands-show-output)

---

21-21: null
Dollar signs used before commands without showing output

(MD014, commands-show-output)

---

22-22: null
Dollar signs used before commands without showing output

(MD014, commands-show-output)

</details>

</details>

---

Line range hint `1-24`: **Document the consolidated driver modules**

Since this PR moves servo drivers (RS30X and SCServo) into the main repository, consider adding information about:
1. Available driver modules and their purposes
2. How to use these drivers in the firmware
3. Any configuration needed for different servo types

This would help users understand the available options after the removal of external dependencies.

Would you like me to help draft this additional documentation section?

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Markdownlint</summary>

20-20: null
Dollar signs used before commands without showing output

(MD014, commands-show-output)

---

21-21: null
Dollar signs used before commands without showing output

(MD014, commands-show-output)

---

22-22: null
Dollar signs used before commands without showing output

(MD014, commands-show-output)

</details>

</details>

</blockquote></details>
<details>
<summary>firmware/stackchan/drivers/scservo.ts (3)</summary><blockquote>

`22-24`: **Simplify `el` function logic using bitwise operations**

The bitmask `& 0xff00` is unnecessary after shifting `h` by 8 bits. Using bitwise OR improves clarity and efficiency.



Apply this diff to simplify the function:

```diff
22 function el(h: number, l: number) {
23-  return ((h << 8) & 0xff00) + (l & 0xff)
23+  return (h << 8) | (l & 0xff)
24 }

19-21: Simplify le function logic by removing redundant bitmask

The bitmask & 0xff00 is redundant when shifting right by 8 bits. Removing it simplifies the code without affecting functionality.

Apply this diff to simplify the function:

19 function le(v: number): [number, number] {
20-  return [(v & 0xff00) >> 8, v & 0xff]
20+  return [v >> 8, v & 0xff]
21 }

195-221: Handle promise rejections on timeouts in #sendCommand

Currently, the promise resolves with undefined on a timeout, which might not be expected by the caller. Rejecting the promise on errors provides clearer error handling and aligns with common async/await practices.

Apply this diff to reject the promise on timeout:

213       return new Promise((resolve, reject) => {
214         const id = Timer.set(() => {
215           this.#promises.shift()
216           trace(\`timeout. \${this.#promises.length}\n\`)
217-          resolve(undefined)
217+          reject(new Error('Command timeout'))
218         }, 40)
219         this.#promises.push([resolve, id])
220       })

Ensure that calling methods handle promise rejections appropriately.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between ce5f72e and 2fce255.

📒 Files selected for processing (13)
  • .gitmodules (0 hunks)
  • firmware/README.md (0 hunks)
  • firmware/README_ja.md (0 hunks)
  • firmware/docs/getting-started.md (1 hunks)
  • firmware/docs/getting-started_ja.md (1 hunks)
  • firmware/extern/avatar (0 hunks)
  • firmware/extern/manifest_extern.json (0 hunks)
  • firmware/extern/rs30x (0 hunks)
  • firmware/extern/scservo (0 hunks)
  • firmware/stackchan/drivers/manifest_driver.json (0 hunks)
  • firmware/stackchan/drivers/rs30x.ts (1 hunks)
  • firmware/stackchan/drivers/scservo.ts (1 hunks)
  • firmware/tsconfig.json (0 hunks)
💤 Files with no reviewable changes (9)
  • .gitmodules
  • firmware/README.md
  • firmware/README_ja.md
  • firmware/extern/avatar
  • firmware/extern/manifest_extern.json
  • firmware/extern/rs30x
  • firmware/extern/scservo
  • firmware/stackchan/drivers/manifest_driver.json
  • firmware/tsconfig.json
🧰 Additional context used
🪛 Markdownlint
firmware/docs/getting-started.md

20-20: null
Dollar signs used before commands without showing output

(MD014, commands-show-output)

firmware/docs/getting-started_ja.md

20-20: null
Dollar signs used before commands without showing output

(MD014, commands-show-output)

🪛 Biome
firmware/stackchan/drivers/rs30x.ts

[error] 147-147: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

firmware/stackchan/drivers/scservo.ts

[error] 111-111: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

🔇 Additional comments (1)
firmware/docs/getting-started_ja.md (1)

17-20: Verify documentation consistency

The simplified git clone command correctly reflects the removal of submodules. However, let's ensure this matches the English version.

✅ Verification successful

Documentation is consistent across language versions

The git clone command is identical in both Japanese (getting-started_ja.md) and English (getting-started.md) versions, correctly showing the simplified command without the --recursive flag.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if the English version has matching changes
# Expected: No --recursive flag in git clone command

rg --type md "git clone.*stack-chan\.git" firmware/docs/getting-started.md

Length of output: 133

🧰 Tools
🪛 Markdownlint

20-20: null
Dollar signs used before commands without showing output

(MD014, commands-show-output)

Comment thread firmware/stackchan/drivers/rs30x.ts Outdated
Comment on lines +19 to +23
export const TorqeMode: { [key: string]: TorqueMode } = Object.freeze({
OFF: 0,
ON: 1,
BREAK: 2,
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Typo in 'TorqeMode' constant name

The constant TorqeMode appears to be misspelled. It should be TorqueMode.

Apply this diff to correct the typo:

-export const TorqeMode: { [key: string]: TorqueMode } = Object.freeze({
+export const TorqueMode: { [key: string]: TorqueMode } = Object.freeze({
  OFF: 0,
  ON: 1,
  BREAK: 2,
})
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const TorqeMode: { [key: string]: TorqueMode } = Object.freeze({
OFF: 0,
ON: 1,
BREAK: 2,
})
export const TorqueMode: { [key: string]: TorqueMode } = Object.freeze({
OFF: 0,
ON: 1,
BREAK: 2,
})

Comment on lines +287 to +289
async reboot(): Promise<void> {
this.#sendCommand(...COMMANDS.REBOOT)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Missing 'await' for asynchronous method call

In the reboot method, the call to this.#sendCommand is asynchronous and should be awaited.

Apply this diff:

async reboot(): Promise<void> {
-  this.#sendCommand(...COMMANDS.REBOOT)
+  await this.#sendCommand(...COMMANDS.REBOOT)
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async reboot(): Promise<void> {
this.#sendCommand(...COMMANDS.REBOOT)
}
async reboot(): Promise<void> {
await this.#sendCommand(...COMMANDS.REBOOT)
}

Comment thread firmware/stackchan/drivers/rs30x.ts Outdated
* @returns TBD
*/
async setTorque(enable: boolean): Promise<unknown> {
const mode = enable ? TorqeMode.ON : TorqeMode.OFF
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Update 'TorqeMode' to 'TorqueMode'

Ensure consistency in the variable name by correcting TorqeMode to TorqueMode.

Apply this diff:

-const mode = enable ? TorqeMode.ON : TorqeMode.OFF
+const mode = enable ? TorqueMode.ON : TorqueMode.OFF
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const mode = enable ? TorqeMode.ON : TorqeMode.OFF
const mode = enable ? TorqueMode.ON : TorqueMode.OFF

Comment on lines +282 to +285
async setComplianceSlope(rotation: Rotation, angle: number): Promise<void> {
const command = rotation === Rotation.CW ? COMMANDS.SET_COMPLIANCE_SLOPE_CW : COMMANDS.SET_COMPLIANCE_SLOPE_CCW
this.#sendCommand(...command, angle)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Missing 'await' for asynchronous method call

In the setComplianceSlope method, the call to this.#sendCommand is asynchronous and should be awaited to ensure it completes before the method returns.

Apply this diff:

async setComplianceSlope(rotation: Rotation, angle: number): Promise<void> {
  const command = rotation === Rotation.CW ? COMMANDS.SET_COMPLIANCE_SLOPE_CW : COMMANDS.SET_COMPLIANCE_SLOPE_CCW
-  this.#sendCommand(...command, angle)
+  await this.#sendCommand(...command, angle)
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async setComplianceSlope(rotation: Rotation, angle: number): Promise<void> {
const command = rotation === Rotation.CW ? COMMANDS.SET_COMPLIANCE_SLOPE_CW : COMMANDS.SET_COMPLIANCE_SLOPE_CCW
this.#sendCommand(...command, angle)
}
async setComplianceSlope(rotation: Rotation, angle: number): Promise<void> {
const command = rotation === Rotation.CW ? COMMANDS.SET_COMPLIANCE_SLOPE_CW : COMMANDS.SET_COMPLIANCE_SLOPE_CCW
await this.#sendCommand(...command, angle)
}

Comment thread firmware/stackchan/drivers/rs30x.ts
Comment thread firmware/stackchan/drivers/scservo.ts
Comment thread firmware/stackchan/drivers/scservo.ts Outdated
Comment on lines +109 to +112
default:
// @ts-ignore 6113
let _state: never
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid variable declarations in switch case statements to prevent scope leakage

Variables declared inside a case clause can leak their scope to subsequent cases. To prevent this and ensure maintainability, wrap the default case body in a block.

Apply this diff to fix the scope leakage:

109             default:
110+            {
111               // @ts-ignore 6113
112               let _state: never
113+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
default:
// @ts-ignore 6113
let _state: never
}
default:
{
// @ts-ignore 6113
let _state: never
}
🧰 Tools
🪛 Biome

[error] 111-111: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

const rxBuf = this.#rxBuffer
for (let b = 0; b < byte; b++) {
// NOTE: We can safely read a number
rxBuf[this.#idx++] = this.read() as number
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add bounds checking to prevent buffer overflow in onReadable function

Incrementing this.#idx without checking can lead to buffer overflow if incoming data exceeds this.#rxBuffer's capacity. Implement a check to ensure safe indexing.

Apply this diff to add bounds checking:

69-            rxBuf[this.#idx++] = this.read() as number
69+            if (this.#idx < rxBuf.length) {
69+              rxBuf[this.#idx++] = this.read() as number
69+            } else {
69+              // Buffer overflow prevention
69+              this.#idx = 0
69+              this.#state = RX_STATE.SEEK
69+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
rxBuf[this.#idx++] = this.read() as number
if (this.#idx < rxBuf.length) {
rxBuf[this.#idx++] = this.read() as number
} else {
// Buffer overflow prevention
this.#idx = 0
this.#state = RX_STATE.SEEK
}

Copy link
Copy Markdown
Contributor

@stc1988 stc1988 left a comment

Choose a reason for hiding this comment

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

actions内での以下も不要になるかと思います

with:
  submodules: recursive

https://github.com/stack-chan/stack-chan/blob/ce5f72e490410d5cb7a83d00d16322c03c4db24f/.github/workflows/build.yml#L6C1-L9C32

- uses: actions/checkout@v4
with:
submodules: recursive

Copy link
Copy Markdown
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: 6

🧹 Outside diff range and nitpick comments (6)
firmware/stackchan/drivers/scservo.ts (4)

58-136: Consider implementing packet validation

The packet handler could benefit from additional validation:

  1. Maximum packet size validation
  2. Command type validation before processing
 class PacketHandler extends Serial {
+  static readonly MAX_PACKET_SIZE = 64;
   constructor(option) {
     // ...
-    this.#rxBuffer = new Uint8Array(64)
+    this.#rxBuffer = new Uint8Array(PacketHandler.MAX_PACKET_SIZE)
     // ...
   }
   
   // Add validation method
+  private isValidCommand(command: number): command is Command {
+    return Object.values(COMMAND).includes(command as Command);
+  }
 }

143-151: Optimize checksum calculation

The current implementation creates a new array slice for each checksum calculation. Consider using array indices directly for better performance.

-function checksum(arr: number[] | Uint8Array): number {
+function checksum(arr: number[] | Uint8Array, length: number): number {
   let sum = 0
-  for (const n of arr.slice(2)) {
+  for (let i = 2; i < length; i++) {
-    sum += n
+    sum += arr[i]
   }
   const cs = ~(sum & 0xff)
   return cs
 }

309-310: Extract angle calculation constants

The angle calculations use magic numbers. Consider extracting these into named constants for better maintainability.

+  private static readonly ANGLE_SCALE = 1024;
+  private static readonly ANGLE_RANGE = 200;
+  private static readonly MAX_POSITION = 0x03ff;
+
   async setAngle(angle: number): Promise<unknown> {
-    const a = Math.floor(clamp(((angle + this.#offset) * 1024) / 200, 0, 0x03ff))
+    const a = Math.floor(clamp(
+      ((angle + this.#offset) * SCServo.ANGLE_SCALE) / SCServo.ANGLE_RANGE,
+      0,
+      SCServo.MAX_POSITION
+    ))
     return this.#sendCommand(COMMAND.WRITE, ADDRESS.GOAL_POSITION, ...le(a))
   }

Also applies to: 321-322


339-352: Add retry mechanism for readStatus

The readStatus method could benefit from a retry mechanism when receiving corrupted responses, improving reliability.

+  private static readonly MAX_RETRIES = 3;
+  private static readonly RETRY_DELAY = 20;
+
   async readStatus(): Promise<Maybe<{ angle: number }>> {
+    for (let attempt = 0; attempt < SCServo.MAX_RETRIES; attempt++) {
       const values = await this.#sendCommand(COMMAND.READ, ADDRESS.PRESENT_POSITION, 15)
       if (values == null || values.length < 15) {
+        if (attempt < SCServo.MAX_RETRIES - 1) {
+          await new Promise(resolve => Timer.set(resolve, SCServo.RETRY_DELAY));
+          continue;
+        }
         return {
           success: false,
-          reason: 'response corrupted.',
+          reason: `Response corrupted after ${SCServo.MAX_RETRIES} attempts.`,
         }
       }
       const angle = (el(values[0], values[1]) * 200) / 1024
       return {
         success: true,
         value: { angle },
       }
+    }
   }
firmware/stackchan/drivers/rs30x.ts (2)

28-28: Simplify the Rotation type declaration

The Rotation type can be simplified for better readability by directly using keyof typeof Rotation.

Apply this diff:

-export type Rotation = (typeof Rotation)[keyof typeof Rotation]
+export type Rotation = keyof typeof Rotation

56-58: Use the 'clamp' utility function for consistency

In the setAngle and setAngleInTime methods, you're clamping the angle values manually. Consider using the existing clamp function for consistency and maintainability.

Apply this diff to use the clamp function:

-const a = Math.max(-150, Math.min(150, angle)) * 10
+const a = clamp(angle, -150, 150) * 10

This change can be applied to both the setAngle and setAngleInTime methods.

Also applies to: 267-267, 278-278

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 88aa492 and 2c7ac78.

📒 Files selected for processing (2)
  • firmware/stackchan/drivers/rs30x.ts (1 hunks)
  • firmware/stackchan/drivers/scservo.ts (1 hunks)
🧰 Additional context used
📓 Learnings (1)
firmware/stackchan/drivers/scservo.ts (1)
Learnt from: meganetaaan
PR: stack-chan/stack-chan#306
File: firmware/stackchan/drivers/scservo.ts:161-161
Timestamp: 2024-11-23T07:45:05.883Z
Learning: Moddableの型定義では、`Timer.set()`関数は`Timer`型を返すため、`Timer`型の変数宣言は正しい。
🔇 Additional comments (3)
firmware/stackchan/drivers/scservo.ts (1)

5-14: Well-implemented Result type pattern!

The Maybe<T> type effectively implements the Result pattern, providing clear error handling with optional error reasons.

firmware/stackchan/drivers/rs30x.ts (2)

306-316: Handle potential errors when reading servo status

In the readStatus method, ensure that all possible error cases are properly handled. Specifically, consider what happens if the angle calculation results in unexpected values.

Consider adding additional validation:

if (isNaN(angle)) {
  throw new Error('Invalid angle received from servo')
}

298-298: Ensure consistent usage of 'TorqueMode'

Confirm that TorqueMode is correctly defined and used consistently throughout the code. In previous comments, there was a typo TorqeMode. Ensure that all references have been updated.

Run the following script to check for any occurrences of TorqeMode:

✅ Verification successful

TorqueMode is correctly defined and consistently used

The type TorqueMode is properly defined and used throughout the codebase. There are no instances of the typo TorqeMode. The implementation shows:

  • A correct type definition: type TorqueMode = TORQUE_OFF | TORQUE_ON | TORQUE_BREAK
  • A proper constant object with frozen values: TorqueMode.OFF, TorqueMode.ON, TorqueMode.BREAK
  • Consistent usage in the code: TorqueMode.ON : TorqueMode.OFF
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Search for any incorrect references to 'TorqeMode'

# Test: Find all instances of 'TorqeMode' in the codebase.
# Expect: No results if all typos are corrected.
rg 'TorqeMode'

Length of output: 14


Script:

#!/bin/bash
# Let's verify both the correct and incorrect spellings, and also check the definition
# Search for both TorqueMode and TorqeMode
rg -i "torq[ue]*mode"

# Also check the enum/type definition
ast-grep --pattern 'enum TorqueMode {
  $$$
}'

Length of output: 366


Script:

#!/bin/bash
# Let's check the complete definition and usage context in rs30x.ts
rg -A 3 "type TorqueMode|export const TorqueMode" firmware/stackchan/drivers/rs30x.ts

Length of output: 248

Comment on lines +214 to +222
return new Promise((resolve, _reject) => {
const id = Timer.set(() => {
this.#promises.shift()
trace(`timeout. ${this.#promises.length}\n`)
resolve(undefined)
}, 40)
this.#promises.push([resolve, id])
})
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance promise timeout handling

The current promise timeout implementation could leak memory if multiple timeouts occur. Consider implementing a more robust timeout handling mechanism.

-    return new Promise((resolve, _reject) => {
+    return new Promise((resolve, reject) => {
       const id = Timer.set(() => {
         this.#promises.shift()
-        trace(`timeout. ${this.#promises.length}\n`)
-        resolve(undefined)
+        reject(new Error('Command timeout'))
       }, 40)
       this.#promises.push([resolve, id])
     })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return new Promise((resolve, _reject) => {
const id = Timer.set(() => {
this.#promises.shift()
trace(`timeout. ${this.#promises.length}\n`)
resolve(undefined)
}, 40)
this.#promises.push([resolve, id])
})
}
return new Promise((resolve, reject) => {
const id = Timer.set(() => {
this.#promises.shift()
reject(new Error('Command timeout'))
}, 40)
this.#promises.push([resolve, id])
})
}

Comment on lines +176 to +183
if (packetHandler == null) {
packetHandler = new PacketHandler({
receive: config.serial?.receive ?? 16,
transmit: config.serial?.transmit ?? 17,
baud: 1_000_000,
port: 2,
})
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve singleton pattern implementation

The current singleton implementation of packetHandler could lead to race conditions. Consider using a proper singleton pattern.

-let packetHandler: PacketHandler = null
+class PacketHandlerSingleton {
+  private static instance: PacketHandler | null = null;
+  
+  static getInstance(config?: any): PacketHandler {
+    if (!this.instance) {
+      this.instance = new PacketHandler({
+        receive: config?.serial?.receive ?? 16,
+        transmit: config?.serial?.transmit ?? 17,
+        baud: 1_000_000,
+        port: 2,
+      });
+    }
+    return this.instance;
+  }
+}

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +14 to +17
type TORQUE_OFF = 0
type TORQUE_ON = 1
type TORQUE_BREAK = 2
type TorqueMode = TORQUE_OFF | TORQUE_ON | TORQUE_BREAK
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Replace 'type' with 'const' for numeric constants

The declarations for TORQUE_OFF, TORQUE_ON, and TORQUE_BREAK should use const instead of type since they represent numeric constants. Using const ensures that the values are available at runtime.

Apply this diff to correct the declarations:

-type TORQUE_OFF = 0
-type TORQUE_ON = 1
-type TORQUE_BREAK = 2
-type TorqueMode = TORQUE_OFF | TORQUE_ON | TORQUE_BREAK
+const TORQUE_OFF = 0
+const TORQUE_ON = 1
+const TORQUE_BREAK = 2
+type TorqueMode = typeof TORQUE_OFF | typeof TORQUE_ON | typeof TORQUE_BREAK

Alternatively, consider using an enum for better clarity:

enum TorqueMode {
  OFF = 0,
  ON = 1,
  BREAK = 2,
}

Comment on lines +197 to +208
if (packetHandler == null) {
packetHandler = new PacketHandler({
receive: config.serial?.receive ?? 16,
transmit: config.serial?.transmit ?? 17,
baud: 115_200,
port: 2,
})
}
if (packetHandler.hasCallbackOf(id)) {
throw new Error('This id is already instantiated')
}
packetHandler.registerCallback(this.#id, this.#onCommandRead)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Prevent multiple instances of PacketHandler

The singleton pattern for packetHandler is not thread-safe and could lead to issues if multiple instances are created in concurrent environments. Consider implementing a proper singleton pattern or enforcing instance control to ensure only one PacketHandler exists.

Apply this diff to enforce a singleton pattern:

- let packetHandler: PacketHandler = null
+ let packetHandler: PacketHandler | null = null

if (packetHandler == null) {
  packetHandler = new PacketHandler({ /* options */ })
}

Alternatively, you can move packetHandler inside the RS30X class as a static property:

class RS30X {
  static #packetHandler: PacketHandler | null = null;
  // inside the constructor
  if (RS30X.#packetHandler == null) {
    RS30X.#packetHandler = new PacketHandler({ /* options */ })
  }
  // use RS30X.#packetHandler instead of packetHandler
}

#id: number
#onCommandRead: (values: number[]) => void
#txBuf: Uint8Array
#promises: Array<[(values: number[]) => void, Timer]>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Correct the type of the Timer in promises

The Timer in the #promises array should be of type number, representing the timer ID returned by Timer.set, not the Timer class itself.

Apply this diff to correct the type:

-#promises: Array<[(values: number[]) => void, Timer]>
+#promises: Array<[(values: number[]) => void, number]>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#promises: Array<[(values: number[]) => void, Timer]>
#promises: Array<[(values: number[]) => void, number]>

Comment on lines +241 to +248
return new Promise((resolve, _reject) => {
const id = Timer.set(() => {
this.#promises.shift()
trace(`timeout. ${this.#promises.length}\n`)
resolve(undefined)
}, 100)
this.#promises.push([resolve, id])
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle promise rejections and potential timeouts

Currently, the _reject parameter in the Promise is unused, and timeouts resolve the promise with undefined. This could lead to unhandled cases where the awaited result is undefined. Consider rejecting the promise on timeout to handle errors appropriately.

Apply this diff to handle rejections:

return new Promise((resolve, reject) => {
  const id = Timer.set(() => {
    this.#promises.shift()
    trace(`timeout. ${this.#promises.length}\n`)
-   resolve(undefined)
+   reject(new Error('Command timed out'))
  }, 100)
  this.#promises.push([resolve, id])
})

Additionally, update the methods that call #sendCommand to handle rejections:

async readStatus(): Promise<number> {
  try {
    const values = await this.#sendCommand(...COMMANDS.REQUEST_STATUS)
    // existing code
  } catch (error) {
    throw new Error(`Failed to read status: ${error.message}`)
  }
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return new Promise((resolve, _reject) => {
const id = Timer.set(() => {
this.#promises.shift()
trace(`timeout. ${this.#promises.length}\n`)
resolve(undefined)
}, 100)
this.#promises.push([resolve, id])
})
return new Promise((resolve, reject) => {
const id = Timer.set(() => {
this.#promises.shift()
trace(`timeout. ${this.#promises.length}\n`)
reject(new Error('Command timed out'))
}, 100)
this.#promises.push([resolve, id])
})

@meganetaaan
Copy link
Copy Markdown
Collaborator Author

@stc1988 修正しました。ついでに🐰さんの指摘も、目に付くものは対応しました。

Copy link
Copy Markdown
Contributor

@stc1988 stc1988 left a comment

Choose a reason for hiding this comment

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

対応ありがとうございます。LGTMです 🙆‍♂️

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.

2 participants