diff --git a/.github/workflows/disable-auto-reopen-after-second-close.yml b/.github/workflows/disable-auto-reopen-after-second-close.yml new file mode 100644 index 000000000000..13bcb232ded4 --- /dev/null +++ b/.github/workflows/disable-auto-reopen-after-second-close.yml @@ -0,0 +1,45 @@ +name: Disable auto-reopen after second close + +on: + pull_request: + types: [closed] + +permissions: + issues: write + pull-requests: write + +jobs: + disable: + runs-on: ubuntu-latest + steps: + - name: Disable automatic reopen + uses: actions/github-script@v7 + with: + script: | + const pr = context.payload.pull_request; + + // Ignore merged PRs + if (pr.merged) return; + + const labels = pr.labels.map(l => l.name); + + // Only act if it was previously reopened once + if (!labels.includes('reopen-used')) return; + + // Mark auto-reopen as permanently disabled + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + labels: ['reopen-locked'] + }); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + body: + "This PR was reopened once and then closed again.\n\n" + + "Automatic reopening via `/not-a-mistake` is now disabled.\n" + + "Maintainers may still reopen this PR manually if needed." + }); diff --git a/.github/workflows/mistake-pr.yml b/.github/workflows/mistake-pr.yml new file mode 100644 index 000000000000..b398dd7467c7 --- /dev/null +++ b/.github/workflows/mistake-pr.yml @@ -0,0 +1,67 @@ +name: Auto-close mistake PRs + +on: + pull_request: + types: [opened, synchronize, reopened] + +permissions: + issues: write + pull-requests: write + +jobs: + detect: + runs-on: ubuntu-latest + steps: + - name: Detect mistake PR + uses: actions/github-script@v7 + with: + script: | + const pr = context.payload.pull_request; + const labels = pr.labels.map(l => l.name); + + // Skip if auto-reopen is disabled or already used + if (labels.includes('reopen-used') || labels.includes('reopen-locked')) { + return; + } + + // Get changed files + const files = await github.paginate( + github.rest.pulls.listFiles, + { + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pr.number, + } + ); + + // Detect newly added OR copied files + const hasNewFiles = files.some( + f => f.status === 'added' || f.status === 'copied' + ); + + if (!hasNewFiles) return; + + // Label as mistake PR + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + labels: ['mistake-pr'] + }); + + // Explain and close + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + body: + "This PR was automatically closed because it adds new files.\n\n" + + "If this is intentional, comment `/not-a-mistake` **once** to reopen it." + }); + + await github.rest.pulls.update({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pr.number, + state: 'closed' + }); diff --git a/.github/workflows/reopen-once.yml b/.github/workflows/reopen-once.yml new file mode 100644 index 000000000000..637869768784 --- /dev/null +++ b/.github/workflows/reopen-once.yml @@ -0,0 +1,84 @@ +name: Reopen mistake PR once + +on: + issue_comment: + types: [created] + +permissions: + issues: write + pull-requests: write + +jobs: + reopen: + runs-on: ubuntu-latest + steps: + - name: Handle /not-a-mistake + uses: actions/github-script@v7 + with: + script: | + const comment = context.payload.comment; + const issue = context.payload.issue; + + if (!issue.pull_request) return; + if (comment.body.trim() !== '/not-a-mistake') return; + + const pr = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: issue.number + }); + + const labels = pr.data.labels.map(l => l.name); + + // Only PR author may use this + if (comment.user.login !== pr.data.user.login) { + return; + } + + // Must be closed and labeled mistake-pr + if (pr.data.state !== 'closed') return; + if (!labels.includes('mistake-pr')) return; + + // Auto-reopen already disabled + if (labels.includes('reopen-used') || labels.includes('reopen-locked')) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: + "Automatic reopening is disabled for this PR.\n\n" + + "A maintainer may still reopen it manually if needed." + }); + return; + } + + // Mark reopen as used FIRST (prevents race) + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + labels: ['reopen-used'] + }); + + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + name: 'mistake-pr' + }); + + await github.rest.pulls.update({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: issue.number, + state: 'open' + }); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: + "PR reopened.\n\n" + + "If this PR is closed again, automatic reopening will be disabled." + }); diff --git a/Diagrams/.obsidian/app.json b/Diagrams/.obsidian/app.json new file mode 100644 index 000000000000..9e26dfeeb6e6 --- /dev/null +++ b/Diagrams/.obsidian/app.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/Diagrams/.obsidian/appearance.json b/Diagrams/.obsidian/appearance.json new file mode 100644 index 000000000000..9e26dfeeb6e6 --- /dev/null +++ b/Diagrams/.obsidian/appearance.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/Diagrams/.obsidian/core-plugins.json b/Diagrams/.obsidian/core-plugins.json new file mode 100644 index 000000000000..639b90da7172 --- /dev/null +++ b/Diagrams/.obsidian/core-plugins.json @@ -0,0 +1,33 @@ +{ + "file-explorer": true, + "global-search": true, + "switcher": true, + "graph": true, + "backlink": true, + "canvas": true, + "outgoing-link": true, + "tag-pane": true, + "footnotes": false, + "properties": true, + "page-preview": true, + "daily-notes": true, + "templates": true, + "note-composer": true, + "command-palette": true, + "slash-command": false, + "editor-status": true, + "bookmarks": true, + "markdown-importer": false, + "zk-prefixer": false, + "random-note": false, + "outline": true, + "word-count": true, + "slides": false, + "audio-recorder": false, + "workspaces": false, + "file-recovery": true, + "publish": false, + "sync": true, + "bases": true, + "webviewer": false +} \ No newline at end of file diff --git a/Diagrams/.obsidian/workspace.json b/Diagrams/.obsidian/workspace.json new file mode 100644 index 000000000000..b1ee9a62a17d --- /dev/null +++ b/Diagrams/.obsidian/workspace.json @@ -0,0 +1,181 @@ +{ + "main": { + "id": "d1bcf6a441f1ffa0", + "type": "split", + "children": [ + { + "id": "93d2aa7072c111d2", + "type": "tabs", + "children": [ + { + "id": "d3b4cee07f48b93b", + "type": "leaf", + "state": { + "type": "empty", + "state": {}, + "icon": "lucide-file", + "title": "New tab" + } + } + ] + } + ], + "direction": "vertical" + }, + "left": { + "id": "4ccc105dc4fded54", + "type": "split", + "children": [ + { + "id": "675dba12eead9ff0", + "type": "tabs", + "children": [ + { + "id": "a02f912852c9ffc1", + "type": "leaf", + "state": { + "type": "file-explorer", + "state": { + "sortOrder": "alphabetical", + "autoReveal": false + }, + "icon": "lucide-folder-closed", + "title": "Files" + } + }, + { + "id": "12123966e35c994f", + "type": "leaf", + "state": { + "type": "search", + "state": { + "query": "", + "matchingCase": false, + "explainSearch": false, + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical" + }, + "icon": "lucide-search", + "title": "Search" + } + }, + { + "id": "a919e2ce8b41c36e", + "type": "leaf", + "state": { + "type": "bookmarks", + "state": {}, + "icon": "lucide-bookmark", + "title": "Bookmarks" + } + } + ] + } + ], + "direction": "horizontal", + "width": 300 + }, + "right": { + "id": "de8ad46ed00c92ec", + "type": "split", + "children": [ + { + "id": "33d21acae245f288", + "type": "tabs", + "children": [ + { + "id": "e24d0618d6282398", + "type": "leaf", + "state": { + "type": "backlink", + "state": { + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical", + "showSearch": false, + "searchQuery": "", + "backlinkCollapsed": false, + "unlinkedCollapsed": true + }, + "icon": "links-coming-in", + "title": "Backlinks" + } + }, + { + "id": "b1433e36081cfd0c", + "type": "leaf", + "state": { + "type": "outgoing-link", + "state": { + "linksCollapsed": false, + "unlinkedCollapsed": true + }, + "icon": "links-going-out", + "title": "Outgoing links" + } + }, + { + "id": "23f001e7431040ef", + "type": "leaf", + "state": { + "type": "tag", + "state": { + "sortOrder": "frequency", + "useHierarchy": true, + "showSearch": false, + "searchQuery": "" + }, + "icon": "lucide-tags", + "title": "Tags" + } + }, + { + "id": "9cc1240dcfab34d9", + "type": "leaf", + "state": { + "type": "all-properties", + "state": { + "sortOrder": "frequency", + "showSearch": false, + "searchQuery": "" + }, + "icon": "lucide-archive", + "title": "All properties" + } + }, + { + "id": "61df8a47ce9eeec7", + "type": "leaf", + "state": { + "type": "outline", + "state": { + "followCursor": false, + "showSearch": false, + "searchQuery": "" + }, + "icon": "lucide-list", + "title": "Outline" + } + } + ] + } + ], + "direction": "horizontal", + "width": 300, + "collapsed": true + }, + "left-ribbon": { + "hiddenItems": { + "switcher:Open quick switcher": false, + "graph:Open graph view": false, + "canvas:Create new canvas": false, + "daily-notes:Open today's daily note": false, + "templates:Insert template": false, + "command-palette:Open command palette": false, + "bases:Create new base": false + } + }, + "active": "d3b4cee07f48b93b", + "lastOpenFiles": [] +} \ No newline at end of file diff --git a/Diagrams/File Structure.canvas b/Diagrams/File Structure.canvas new file mode 100644 index 000000000000..815461432420 --- /dev/null +++ b/Diagrams/File Structure.canvas @@ -0,0 +1,652 @@ +{ + "nodes":[ + { + "id":"24bf9c010517c043", + "type":"group", + "styleAttributes":{ + "border":null + }, + "x":-480, + "y":-740, + "width":460, + "height":760, + "label":"Actuator Subsystems" + }, + {"id":"10d2e8f294485406","type":"group","x":40,"y":-740,"width":360,"height":520,"label":"Generic Devices"}, + {"id":"b257e56e060bcf72","type":"group","x":-1060,"y":-1080,"width":360,"height":300,"label":"Legend"}, + {"id":"c387321b908f5578","type":"group","x":-480,"y":-1160,"width":240,"height":360,"label":"Drive Subsystem"}, + { + "id":"6cb595b6641c5429", + "type":"text", + "text":"Turret", + "styleAttributes":{ + "textAlign":"center", + "shape":null + }, + "x":-440, + "y":-560, + "width":160, + "height":60, + "color":"5" + }, + { + "id":"7c67cdb9e592c4a8", + "type":"text", + "text":"Actuators_", + "styleAttributes":{"textAlign":"center","shape":"database"}, + "x":-440, + "y":-680, + "width":160, + "height":60 + }, + { + "id":"c60fab6b02e73c2d", + "type":"text", + "text":"Flywheel Motor", + "styleAttributes":{"textAlign":"center","shape":"pill"}, + "x":-220, + "y":-560, + "width":160, + "height":60, + "color":"1" + }, + { + "id":"4b4b925b55e20a4b", + "type":"text", + "text":"Rotation Motor", + "styleAttributes":{"textAlign":"center","shape":"pill"}, + "x":-220, + "y":-480, + "width":160, + "height":60, + "color":"1" + }, + { + "id":"b6ca95068fb87cd1", + "type":"text", + "text":"Specific\nDevices", + "styleAttributes":{"textAlign":"center","shape":"pill"}, + "x":-860, + "y":-1060, + "width":140, + "height":60, + "color":"1" + }, + { + "id":"1e2cad7776a30e09", + "type":"text", + "text":"Folders", + "styleAttributes":{"textAlign":"center","shape":"database"}, + "x":-860, + "y":-960, + "width":140, + "height":60 + }, + { + "id":"fea165c26dfac7b4", + "type":"text", + "text":"Primary Classes", + "styleAttributes":{"textAlign":"center","shape":"predefined-process"}, + "x":-1040, + "y":-1060, + "width":140, + "height":80, + "color":"6" + }, + { + "id":"e39752447e25fe9b", + "type":"text", + "text":"Generic Device Classes", + "styleAttributes":{"textAlign":"center","shape":"circle"}, + "x":-1040, + "y":-880, + "width":140, + "height":80, + "color":"3" + }, + { + "id":"b3d29ccbc0207fba", + "type":"text", + "text":"Subsystem Classes", + "styleAttributes":{ + "textAlign":"center", + "shape":null + }, + "x":-1040, + "y":-960, + "width":140, + "height":60, + "color":"5" + }, + { + "id":"36c91406159c570c", + "type":"text", + "text":"Lift", + "styleAttributes":{ + "textAlign":"center", + "shape":null + }, + "x":-440, + "y":-80, + "width":160, + "height":60, + "color":"5" + }, + { + "id":"46567539d2a5035d", + "type":"text", + "text":"Lift Motor?", + "styleAttributes":{"textAlign":"center","shape":"pill"}, + "x":-220, + "y":-80, + "width":160, + "height":60, + "color":"1" + }, + { + "id":"38a0d38b66a11a14", + "type":"text", + "text":"Intake Motor", + "styleAttributes":{"textAlign":"center","shape":"pill"}, + "x":-220, + "y":-320, + "width":160, + "height":60, + "color":"1" + }, + { + "id":"4d43db858659fbed", + "type":"text", + "text":"Belt Servo", + "styleAttributes":{"textAlign":"center","shape":"pill"}, + "x":-220, + "y":-240, + "width":160, + "height":60, + "color":"1" + }, + { + "id":"29fb84e8134463b3", + "type":"text", + "text":"Transfer Servo", + "styleAttributes":{"textAlign":"center","shape":"pill"}, + "x":-220, + "y":-160, + "width":160, + "height":60, + "color":"1" + }, + { + "id":"d8d4dacf5920c2cb", + "type":"text", + "text":"Hood Servo", + "styleAttributes":{"textAlign":"center","shape":"pill"}, + "x":-220, + "y":-400, + "width":160, + "height":60, + "color":"1" + }, + { + "id":"3fe96a913c4c7045", + "type":"text", + "text":"Hardware_", + "styleAttributes":{"textAlign":"center","shape":"database"}, + "x":-1020, + "y":-680, + "width":140, + "height":60 + }, + { + "id":"b0917a192e867cbe", + "type":"text", + "text":"Teleop_", + "styleAttributes":{"textAlign":"center","shape":"database"}, + "x":-1020, + "y":-540, + "width":140, + "height":60 + }, + { + "id":"10a4fa2477f2c9da", + "type":"text", + "text":"Main_", + "styleAttributes":{"textAlign":"center","shape":"database"}, + "x":-1220, + "y":-540, + "width":140, + "height":60 + }, + { + "id":"781182d601f21fe3", + "type":"text", + "text":"Intake", + "styleAttributes":{ + "textAlign":"center", + "shape":null + }, + "x":-440, + "y":-320, + "width":160, + "height":60, + "color":"5" + }, + { + "id":"b681d02738b88d07", + "type":"text", + "text":"TeleOp Control", + "styleAttributes":{"textAlign":"center"}, + "x":-440, + "y":-980, + "width":160, + "height":60, + "color":"5" + }, + { + "id":"3cd44b1464241515", + "type":"text", + "text":"Processor Classes", + "styleAttributes":{"textAlign":"center","shape":"parallelogram"}, + "x":-860, + "y":-860, + "width":140, + "height":60, + "color":"#6efe6c" + }, + { + "id":"d728e0a74da6c0fd", + "type":"text", + "text":"Auto_", + "styleAttributes":{"textAlign":"center","shape":"database"}, + "x":-1020, + "y":-400, + "width":140, + "height":60 + }, + { + "id":"c950be02cfc42181", + "type":"text", + "text":"Drivetrain_", + "styleAttributes":{"textAlign":"center","shape":"database"}, + "x":-440, + "y":-1100, + "width":160, + "height":60 + }, + { + "id":"20c263df8c9982a4", + "type":"text", + "text":"Auto Pathing", + "styleAttributes":{"textAlign":"center"}, + "x":-440, + "y":-900, + "width":160, + "height":60, + "color":"5" + }, + { + "id":"f8f6dfb640cbfd1d", + "type":"text", + "text":"Devices", + "styleAttributes":{ + "textAlign":"center", + "shape":"circle", + "border":null + }, + "x":120, + "y":-700, + "width":200, + "height":100, + "color":"3" + }, + { + "id":"ce3a4399dc294b02", + "type":"text", + "text":"Servo", + "styleAttributes":{"textAlign":"center","shape":"circle"}, + "x":160, + "y":-400, + "width":120, + "height":60, + "color":"3" + }, + { + "id":"b31e377ce216f382", + "type":"text", + "text":"DcMotorEx", + "styleAttributes":{"textAlign":"center","shape":"circle"}, + "x":80, + "y":-480, + "width":120, + "height":60, + "color":"3" + }, + { + "id":"64070cd56adeb48a", + "type":"text", + "text":"CRServo", + "styleAttributes":{"textAlign":"center","shape":"circle"}, + "x":240, + "y":-320, + "width":120, + "height":60, + "color":"3" + }, + { + "id":"cb4e9f8e5320c422", + "type":"text", + "text":"Robot (LOAD_Hardware_Class)", + "styleAttributes":{"textAlign":"center","shape":"predefined-process"}, + "x":-820, + "y":-620, + "width":260, + "height":60, + "color":"6" + } + ], + "edges":[ + { + "id":"a754ca4983fc848a", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromNode":"781182d601f21fe3", + "fromSide":"right", + "toNode":"38a0d38b66a11a14", + "toSide":"left" + }, + { + "id":"1ff69a492a801c93", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromNode":"781182d601f21fe3", + "fromSide":"right", + "toNode":"4d43db858659fbed", + "toSide":"left" + }, + { + "id":"63cb6bd74dfe514d", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromNode":"781182d601f21fe3", + "fromSide":"right", + "toNode":"29fb84e8134463b3", + "toSide":"left" + }, + { + "id":"f39b6af2b8e48183", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromNode":"6cb595b6641c5429", + "fromSide":"right", + "toNode":"c60fab6b02e73c2d", + "toSide":"left" + }, + { + "id":"67ba26aea9f89a01", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromNode":"6cb595b6641c5429", + "fromSide":"right", + "toNode":"4b4b925b55e20a4b", + "toSide":"left" + }, + { + "id":"e4ac46a6a34dfc96", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromFloating":false, + "fromNode":"6cb595b6641c5429", + "fromSide":"right", + "toNode":"d8d4dacf5920c2cb", + "toSide":"left" + }, + { + "id":"1a3c223f60e7960e", + "styleAttributes":{}, + "toFloating":false, + "fromNode":"36c91406159c570c", + "fromSide":"right", + "toNode":"46567539d2a5035d", + "toSide":"left" + }, + { + "id":"75fb7926f00bf1f2", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromFloating":false, + "fromNode":"b31e377ce216f382", + "fromSide":"left", + "toNode":"38a0d38b66a11a14", + "toSide":"right" + }, + { + "id":"e502478432f11ab2", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromFloating":false, + "fromNode":"b31e377ce216f382", + "fromSide":"left", + "toNode":"c60fab6b02e73c2d", + "toSide":"right" + }, + { + "id":"3d6234ec9ba8b80a", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromFloating":false, + "fromNode":"b31e377ce216f382", + "fromSide":"left", + "toNode":"4b4b925b55e20a4b", + "toSide":"right" + }, + { + "id":"ecf36fe8628d23d4", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromFloating":false, + "fromNode":"b31e377ce216f382", + "fromSide":"left", + "toNode":"46567539d2a5035d", + "toSide":"right" + }, + { + "id":"e520deaeb33f18ea", + "styleAttributes":{"pathfindingMethod":"square","path":"dotted"}, + "toFloating":false, + "fromFloating":false, + "fromNode":"10a4fa2477f2c9da", + "fromSide":"right", + "toNode":"3fe96a913c4c7045", + "toSide":"left" + }, + { + "id":"a490a71e7c0889c4", + "styleAttributes":{"pathfindingMethod":"square","path":"dotted"}, + "toFloating":false, + "fromFloating":false, + "fromNode":"10a4fa2477f2c9da", + "fromSide":"right", + "toNode":"b0917a192e867cbe", + "toSide":"left" + }, + { + "id":"fee5e7b645c72d49", + "styleAttributes":{"pathfindingMethod":"square","path":"dotted"}, + "toFloating":false, + "fromFloating":false, + "fromNode":"10a4fa2477f2c9da", + "fromSide":"right", + "toNode":"d728e0a74da6c0fd", + "toSide":"left" + }, + { + "id":"c27b4cd5b53a39a1", + "styleAttributes":{"pathfindingMethod":"square","path":"dotted"}, + "toFloating":false, + "fromFloating":false, + "fromNode":"3fe96a913c4c7045", + "fromSide":"right", + "toNode":"cb4e9f8e5320c422", + "toSide":"left" + }, + { + "id":"cb54500a03c6dae8", + "styleAttributes":{"pathfindingMethod":"square","path":"dotted"}, + "toFloating":false, + "fromNode":"3fe96a913c4c7045", + "fromSide":"right", + "toNode":"7c67cdb9e592c4a8", + "toSide":"left" + }, + { + "id":"5a67ba206c21c5d5", + "styleAttributes":{"pathfindingMethod":"square","path":"dotted"}, + "toFloating":false, + "fromFloating":false, + "fromNode":"3fe96a913c4c7045", + "fromSide":"right", + "toNode":"c950be02cfc42181", + "toSide":"left" + }, + { + "id":"730fdab1703e2384", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromNode":"cb4e9f8e5320c422", + "fromSide":"right", + "toNode":"36c91406159c570c", + "toSide":"left" + }, + { + "id":"227ee64c216dd8a2", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromNode":"cb4e9f8e5320c422", + "fromSide":"right", + "toNode":"6cb595b6641c5429", + "toSide":"left" + }, + { + "id":"da5b6b4b304e2a2a", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromNode":"cb4e9f8e5320c422", + "fromSide":"right", + "toNode":"781182d601f21fe3", + "toSide":"left" + }, + { + "id":"600959ea0be584d1", + "styleAttributes":{"pathfindingMethod":"square","path":"short-dashed"}, + "toFloating":false, + "fromFloating":false, + "fromNode":"ce3a4399dc294b02", + "fromSide":"left", + "toNode":"29fb84e8134463b3", + "toSide":"right" + }, + { + "id":"59e449d6190f7c62", + "styleAttributes":{"pathfindingMethod":"square","path":"short-dashed"}, + "toFloating":false, + "fromFloating":false, + "fromNode":"ce3a4399dc294b02", + "fromSide":"left", + "toNode":"d8d4dacf5920c2cb", + "toSide":"right" + }, + { + "id":"b4c105fc0c9f9a0b", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromNode":"cb4e9f8e5320c422", + "fromSide":"right", + "toNode":"20c263df8c9982a4", + "toSide":"left" + }, + { + "id":"6c06e6161a19b57b", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromFloating":false, + "fromNode":"cb4e9f8e5320c422", + "fromSide":"right", + "toNode":"b681d02738b88d07", + "toSide":"left" + }, + { + "id":"c3e114e590ec207f", + "styleAttributes":{ + "path":"dotted", + "arrow":null + }, + "toFloating":false, + "fromNode":"7c67cdb9e592c4a8", + "fromSide":"bottom", + "toNode":"36c91406159c570c", + "toSide":"top" + }, + { + "id":"b0839d6e6e68d597", + "styleAttributes":{}, + "toFloating":false, + "fromNode":"c950be02cfc42181", + "fromSide":"bottom", + "toNode":"20c263df8c9982a4", + "toSide":"top" + }, + { + "id":"3c47645c9d187d74", + "styleAttributes":{"path":"long-dashed","pathfindingMethod":"square"}, + "toFloating":false, + "fromFloating":false, + "fromNode":"64070cd56adeb48a", + "fromSide":"left", + "toNode":"4d43db858659fbed", + "toSide":"right" + }, + { + "id":"7e25a3f58e5e8145", + "styleAttributes":{"path":"dotted"}, + "toFloating":false, + "fromNode":"7c67cdb9e592c4a8", + "fromSide":"right", + "toNode":"f8f6dfb640cbfd1d", + "toSide":"left" + }, + { + "id":"6befddbfcd1ec457", + "styleAttributes":{"path":"dotted"}, + "toFloating":false, + "fromNode":"f8f6dfb640cbfd1d", + "fromSide":"bottom", + "toNode":"ce3a4399dc294b02", + "toSide":"top" + }, + { + "id":"bb0e476818e13bff", + "styleAttributes":{"pathfindingMethod":"square","path":"dotted"}, + "toFloating":false, + "fromFloating":false, + "fromNode":"f8f6dfb640cbfd1d", + "fromSide":"bottom", + "toNode":"b31e377ce216f382", + "toSide":"top" + }, + { + "id":"450188924918c729", + "styleAttributes":{"pathfindingMethod":"square","path":"dotted"}, + "toFloating":false, + "fromFloating":false, + "fromNode":"f8f6dfb640cbfd1d", + "fromSide":"bottom", + "toNode":"64070cd56adeb48a", + "toSide":"top" + } + ], + "metadata":{ + "version":"1.0-1.0", + "frontmatter":{}, + "startNode":"0b1473b84ec5c5d3" + } +} \ No newline at end of file diff --git a/Diagrams/File_Structure.png b/Diagrams/File_Structure.png new file mode 100644 index 000000000000..166df00c2742 Binary files /dev/null and b/Diagrams/File_Structure.png differ diff --git a/Diagrams/Robot_Controls.png b/Diagrams/Robot_Controls.png new file mode 100644 index 000000000000..cedc746d8180 Binary files /dev/null and b/Diagrams/Robot_Controls.png differ diff --git a/Diagrams/Robot_Wiring.png b/Diagrams/Robot_Wiring.png new file mode 100644 index 000000000000..8b4808bb1128 Binary files /dev/null and b/Diagrams/Robot_Wiring.png differ diff --git a/Diagrams/Wiring.canvas b/Diagrams/Wiring.canvas new file mode 100644 index 000000000000..ea4cd43f646f --- /dev/null +++ b/Diagrams/Wiring.canvas @@ -0,0 +1,485 @@ +{ + "nodes":[ + {"id":"85dd3f12ac72d900","type":"group","x":-360,"y":-440,"width":380,"height":260,"label":"Legend"}, + { + "id":"c2eee2656a3ddfa1", + "type":"text", + "text":"Pinpoint Computer\nI2C Port 1", + "styleAttributes":{"textAlign":"center"}, + "x":80, + "y":-260, + "width":160, + "height":60, + "color":"6" + }, + { + "id":"e20a52ef21d57dcf", + "type":"text", + "text":"LED Controller\nI2C Port 2", + "styleAttributes":{"textAlign":"center"}, + "x":80, + "y":-340, + "width":160, + "height":60, + "color":"6" + }, + { + "id":"f9a634b23b5064c5", + "type":"text", + "text":"Servos", + "styleAttributes":{"textAlign":"center"}, + "x":-340, + "y":-340, + "width":160, + "height":60, + "color":"4" + }, + { + "id":"48faa728dd92705d", + "type":"text", + "text":"Motors", + "styleAttributes":{"textAlign":"center"}, + "x":-160, + "y":-340, + "width":160, + "height":60, + "color":"5" + }, + { + "id":"26e535ad3414017c", + "type":"text", + "text":"Control System", + "styleAttributes":{"textAlign":"center"}, + "x":-340, + "y":-260, + "width":160, + "height":60, + "color":"2" + }, + { + "id":"2cad408dc2aae848", + "type":"text", + "text":"I2C Devices", + "styleAttributes":{"textAlign":"center"}, + "x":-160, + "y":-260, + "width":160, + "height":60, + "color":"6" + }, + { + "id":"a0abe0901b65ac56", + "type":"text", + "text":"Upper Color Sensors\nI2C Ports 0-1", + "styleAttributes":{"textAlign":"center"}, + "x":300, + "y":-340, + "width":160, + "height":60, + "color":"6" + }, + { + "id":"e3fdefe17fe2310d", + "type":"text", + "text":"Analog Devices", + "styleAttributes":{"textAlign":"center"}, + "x":-160, + "y":-420, + "width":160, + "height":60, + "color":"3" + }, + { + "id":"7a1b2108fa3bc30e", + "type":"text", + "text":"Hall Effect Sensor\nPort 0", + "styleAttributes":{"textAlign":"center"}, + "x":80, + "y":-420, + "width":160, + "height":60, + "color":"1" + }, + { + "id":"81a20594db9883e0", + "type":"text", + "text":"Control Hub", + "styleAttributes":{"textAlign":"center"}, + "x":-80, + "y":-160, + "width":260, + "height":60, + "color":"2" + }, + { + "id":"d9c005396002882a", + "type":"text", + "text":"Expansion Hub", + "styleAttributes":{"textAlign":"center"}, + "x":360, + "y":-160, + "width":260, + "height":60, + "color":"2" + }, + { + "id":"f667221e45b8fc86", + "type":"text", + "text":"Intake Motor\nPort 0 - No Encoder", + "styleAttributes":{"textAlign":"center"}, + "x":520, + "y":-60, + "width":260, + "height":60, + "color":"5" + }, + { + "id":"0075a4079a1a472d", + "type":"text", + "text":"Front Right Drive Motor\nPort 1 - No Encoder", + "styleAttributes":{"textAlign":"center"}, + "x":520, + "y":20, + "width":260, + "height":60, + "color":"5" + }, + { + "id":"5587942ea5903dbe", + "type":"text", + "text":"Back Right Drive Motor\nPort 2 - No Encoder", + "styleAttributes":{"textAlign":"center"}, + "x":520, + "y":100, + "width":260, + "height":60, + "color":"5" + }, + { + "id":"c6d790ec667c829f", + "type":"text", + "text":"Turret Rotation Motor\nPort 3 - With Encoder", + "styleAttributes":{"textAlign":"center"}, + "x":520, + "y":180, + "width":260, + "height":60, + "color":"5" + }, + { + "id":"d97803361edf47ba", + "type":"text", + "text":"Lower Color Sensors\nI2C Ports 2-3", + "styleAttributes":{"textAlign":"center"}, + "x":300, + "y":-260, + "width":160, + "height":60, + "color":"6" + }, + { + "id":"86792695c78b2d59", + "type":"text", + "text":"Servo Power Injector", + "styleAttributes":{"textAlign":"center"}, + "x":-440, + "y":-160, + "width":180, + "height":60, + "color":"2" + }, + { + "id":"88b48890e8f34fc1", + "type":"text", + "text":"The Servo Power Injector ports correspond to the Control Hub Ports.\nChub P0 -> SPI P1, Chub P1 -> SPI P2, etc. The given ports are for the SPI, not the control hub.", + "styleAttributes":{ + "textAlign":"center", + "shape":null, + "border":null + }, + "x":-540, + "y":-40, + "width":160, + "height":260 + }, + { + "id":"bfb02b2a92b84ad2", + "type":"text", + "text":"Belt Servo - Port 1", + "styleAttributes":{"textAlign":"center"}, + "x":-300, + "y":-60, + "width":200, + "height":60, + "color":"4" + }, + { + "id":"440a120e5375c485", + "type":"text", + "text":"Front Left Drive Motor\nPort 0 - No Encoder", + "styleAttributes":{"textAlign":"center"}, + "x":80, + "y":-60, + "width":260, + "height":60, + "color":"5" + }, + { + "id":"76edd4abcf05a2a3", + "type":"text", + "text":"Back Left Drive Motor\nPort 1 - No Encoder", + "styleAttributes":{"textAlign":"center"}, + "x":80, + "y":20, + "width":260, + "height":60, + "color":"5" + }, + { + "id":"336d50b5d4cb968f", + "type":"text", + "text":"Flywheel Motor 1\nPort 2 - With Encoder", + "styleAttributes":{"textAlign":"center"}, + "x":80, + "y":100, + "width":260, + "height":60, + "color":"5" + }, + { + "id":"f8b39583a022e492", + "type":"text", + "text":"Flywheel Motor 2\nPort 3 - No Encoder", + "styleAttributes":{"textAlign":"center"}, + "x":80, + "y":180, + "width":260, + "height":60, + "color":"5" + }, + { + "id":"41c5560b3c8edc13", + "type":"text", + "text":"Hood Servo - Port 2", + "styleAttributes":{"textAlign":"center"}, + "x":-300, + "y":20, + "width":200, + "height":60, + "color":"4" + }, + { + "id":"b2918b70ae0b4303", + "type":"text", + "text":"Gate Servo - Port 3", + "styleAttributes":{"textAlign":"center"}, + "x":-300, + "y":100, + "width":200, + "height":60, + "color":"4" + }, + { + "id":"8c94376760075734", + "type":"text", + "text":"Transfer Servo - Port 4", + "styleAttributes":{"textAlign":"center"}, + "x":-300, + "y":180, + "width":200, + "height":60, + "color":"4" + }, + { + "id":"b7514b6ae54e27cf", + "type":"text", + "text":"Digital Devices", + "styleAttributes":{"textAlign":"center"}, + "x":-340, + "y":-420, + "width":160, + "height":60, + "color":"1" + } + ], + "edges":[ + { + "id":"b19d432c983955cf", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromNode":"81a20594db9883e0", + "fromSide":"bottom", + "toNode":"440a120e5375c485", + "toSide":"left" + }, + { + "id":"d76f3cbbdeee44de", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromNode":"81a20594db9883e0", + "fromSide":"bottom", + "toNode":"76edd4abcf05a2a3", + "toSide":"left" + }, + { + "id":"f3883787e1cf36ff", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromNode":"81a20594db9883e0", + "fromSide":"bottom", + "toNode":"336d50b5d4cb968f", + "toSide":"left" + }, + { + "id":"5b0001153bf55d91", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromFloating":false, + "fromNode":"81a20594db9883e0", + "fromSide":"bottom", + "toNode":"f8b39583a022e492", + "toSide":"left" + }, + { + "id":"2ef75997821cccc9", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromNode":"d9c005396002882a", + "fromSide":"bottom", + "toNode":"f667221e45b8fc86", + "toSide":"left" + }, + { + "id":"6eca4d1d11415dae", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromNode":"d9c005396002882a", + "fromSide":"bottom", + "toNode":"0075a4079a1a472d", + "toSide":"left" + }, + { + "id":"d70db43e95185aa4", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromNode":"d9c005396002882a", + "fromSide":"bottom", + "toNode":"5587942ea5903dbe", + "toSide":"left" + }, + { + "id":"94d25591b9c935b0", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromFloating":false, + "fromNode":"d9c005396002882a", + "fromSide":"bottom", + "toNode":"c6d790ec667c829f", + "toSide":"left" + }, + { + "id":"4a02d336a8851951", + "styleAttributes":{}, + "toFloating":false, + "fromNode":"81a20594db9883e0", + "fromSide":"right", + "toNode":"d9c005396002882a", + "toSide":"left" + }, + { + "id":"50becea15ce0c423", + "styleAttributes":{}, + "toFloating":false, + "fromFloating":false, + "fromNode":"81a20594db9883e0", + "fromSide":"left", + "toNode":"86792695c78b2d59", + "toSide":"right" + }, + { + "id":"2994660c601b60c7", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromNode":"86792695c78b2d59", + "fromSide":"bottom", + "toNode":"bfb02b2a92b84ad2", + "toSide":"left" + }, + { + "id":"489912cd2dc5605d", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromNode":"86792695c78b2d59", + "fromSide":"bottom", + "toNode":"41c5560b3c8edc13", + "toSide":"left" + }, + { + "id":"6e6343e8a6c4769c", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromNode":"86792695c78b2d59", + "fromSide":"bottom", + "toNode":"b2918b70ae0b4303", + "toSide":"left" + }, + { + "id":"18ec8f8b3be09fca", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromNode":"86792695c78b2d59", + "fromSide":"bottom", + "toNode":"8c94376760075734", + "toSide":"left" + }, + { + "id":"811cf14f46b67298", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromNode":"81a20594db9883e0", + "fromSide":"top", + "toNode":"c2eee2656a3ddfa1", + "toSide":"left" + }, + { + "id":"c6a6b035bf824dfe", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromNode":"81a20594db9883e0", + "fromSide":"top", + "toNode":"e20a52ef21d57dcf", + "toSide":"left" + }, + { + "id":"5c79a21468a43736", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromNode":"d9c005396002882a", + "fromSide":"top", + "toNode":"a0abe0901b65ac56", + "toSide":"right" + }, + { + "id":"a6ee77457b2001f7", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromNode":"d9c005396002882a", + "fromSide":"top", + "toNode":"d97803361edf47ba", + "toSide":"right" + }, + { + "id":"21b147d9374eee41", + "styleAttributes":{"pathfindingMethod":"square"}, + "toFloating":false, + "fromNode":"81a20594db9883e0", + "fromSide":"top", + "toNode":"7a1b2108fa3bc30e", + "toSide":"left" + } + ], + "metadata":{ + "version":"1.0-1.0", + "frontmatter":{} + } +} \ No newline at end of file diff --git a/FilesystemFlowchart.png b/FilesystemFlowchart.png deleted file mode 100644 index 2437ada57afc..000000000000 Binary files a/FilesystemFlowchart.png and /dev/null differ diff --git a/README.md b/README.md index 66bb095445bc..4a2abcefd3a3 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,19 @@ # LOAD Robotics DECODE Robot Code This repository contains LOAD Robotics' robot code for the 2025-2026 FTC season DECODE. -## Robot Hardware File Hierarchy -![Filesystem Flowchart](FilesystemFlowchart.png "Robot Hardware File Hierarchy") -This chart describes hierarchy of our hardware manager classes. - ## NOTICE This repository contains the public FTC SDK for the DECODE (2025-2026) competition season. + +## LOAD Charts & Diagrams +### Our Filestructure +![File Structure Flowchart](Diagrams/File_Structure.png) +### Robot Control Scheme (Pre December 6th) +![Robot Gamepad 1 & 2 Control Charts](Diagrams/Robot_Controls.png) +### Robot Wiring Diagram +![Wiring Paths and Labels](Diagrams/Robot_Wiring.png) + ## Welcome! This GitHub repository contains the source code that is used to build an Android app to control a *FIRST* Tech Challenge competition robot. To use this SDK, download/clone the entire project to your local computer. diff --git a/TeamCode/build.gradle b/TeamCode/build.gradle index dd6160908114..ae2a311874eb 100644 --- a/TeamCode/build.gradle +++ b/TeamCode/build.gradle @@ -25,8 +25,9 @@ android { dependencies { implementation project(':FtcRobotController') - implementation 'io.github.skeleton-army.marrow:core:0.1.2' + implementation 'com.skeletonarmyftc.marrow:core:1.0.0' implementation 'dev.nextftc:ftc:1.0.1' implementation 'dev.nextftc.extensions:pedro:1.0.0' implementation "com.bylazar:graph:1.0.4" + //implementation 'com.github.goBILDA-Official:FtcRobotController-Add-Prism:goBILDA-Prism-Driver' // For Light Strips } diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Auto_/Auto_Dec6th.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Auto_/Auto_Dec6th.java index c1fae0517a70..fc0786fae05d 100644 --- a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Auto_/Auto_Dec6th.java +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Auto_/Auto_Dec6th.java @@ -1,10 +1,17 @@ package org.firstinspires.ftc.teamcode.LOADCode.Main_.Auto_; // make sure this aligns with class location +import static org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_.LoadHardwareClass.selectedAlliance; + import com.qualcomm.robotcore.eventloop.opmode.Autonomous; +import com.qualcomm.robotcore.eventloop.opmode.Disabled; import com.qualcomm.robotcore.eventloop.opmode.OpMode; import com.qualcomm.robotcore.hardware.DcMotorEx; import com.qualcomm.robotcore.hardware.DcMotorSimple; +import com.skeletonarmy.marrow.prompts.OptionPrompt; +import com.skeletonarmy.marrow.prompts.Prompter; +import org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_.LoadHardwareClass; +@Disabled @Autonomous(name = "Auto_Dec6th", group = "TestAuto", preselectTeleOp="Teleop_Main_") public class Auto_Dec6th extends OpMode { @@ -13,6 +20,9 @@ public class Auto_Dec6th extends OpMode { DcMotorEx BL; DcMotorEx BR; + // Create the prompter object for selecting Alliance and Auto + Prompter prompter = null; + /** * Copied over from LinearOpMode. * @param milliseconds The number of milliseconds to sleep. @@ -35,12 +45,26 @@ public void init() { FL.setDirection(DcMotorSimple.Direction.REVERSE); BL.setDirection(DcMotorSimple.Direction.REVERSE); + + prompter = new Prompter(this); + prompter.prompt("alliance", + new OptionPrompt<>("Select Alliance", + LoadHardwareClass.Alliance.RED, + LoadHardwareClass.Alliance.BLUE + )); + prompter.onComplete(() -> { + selectedAlliance = prompter.get("alliance"); + telemetry.addData("Selection", "Complete"); + telemetry.addData("Alliance", selectedAlliance); + telemetry.update(); + } + ); } /** This method is called continuously after Init while waiting for "play". **/ @Override public void init_loop() { - + prompter.run(); } /** This method is called once at the start of the OpMode. diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Auto_/Auto_Main_.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Auto_/Auto_Main_.java index 45ccb6d84d3f..978d0c6fa1e8 100644 --- a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Auto_/Auto_Main_.java +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Auto_/Auto_Main_.java @@ -1,58 +1,55 @@ package org.firstinspires.ftc.teamcode.LOADCode.Main_.Auto_; import static org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_.LoadHardwareClass.selectedAlliance; +import static dev.nextftc.extensions.pedro.PedroComponent.follower; + +import androidx.annotation.NonNull; import com.pedropathing.geometry.Pose; import com.qualcomm.robotcore.eventloop.opmode.Autonomous; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; import com.skeletonarmy.marrow.prompts.OptionPrompt; import com.skeletonarmy.marrow.prompts.Prompter; import org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_.Actuators_.Intake; import org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_.Actuators_.Turret; import org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_.Commands; +import org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_.Drivetrain_.MecanumDrivetrainClass; +import org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_.Drivetrain_.Pedro_Paths; import org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_.LoadHardwareClass; import org.firstinspires.ftc.teamcode.pedroPathing.Constants; -import dev.nextftc.core.commands.Command; import dev.nextftc.core.commands.delays.Delay; -import dev.nextftc.core.commands.delays.WaitUntil; -import dev.nextftc.core.commands.groups.ParallelGroup; import dev.nextftc.core.commands.groups.SequentialGroup; +import dev.nextftc.core.commands.utility.InstantCommand; import dev.nextftc.extensions.pedro.PedroComponent; import dev.nextftc.ftc.NextFTCOpMode; -@Disabled @Autonomous(name = "Auto_Main_", group = "Main", preselectTeleOp="Teleop_Main_") public class Auto_Main_ extends NextFTCOpMode { - // Define other PedroPathing constants - private final Pose startPose = new Pose(87, 8.8, Math.toRadians(90)); // Start Pose of our robot. - - private enum Auto { - LEAVE_NEAR_LAUNCH, - LEAVE_FAR_HP, - MOVEMENT_RP_DEC6 - } - - private Auto selectedAuto = null; - private boolean turretOn = false; - + // Variable to store the selected auto program + Auto selectedAuto = null; // Create the prompter object for selecting Alliance and Auto Prompter prompter = null; - // Create a new instance of our Robot class LoadHardwareClass Robot = new LoadHardwareClass(this); + // Create a Paths object for accessing modular auto paths + Pedro_Paths paths = new Pedro_Paths(); + // Create a Commands object for auto creation + Commands Commands = new Commands(Robot); - @Override - public void onInit() { - // Initialize all hardware of the robot + // Auto parameter variables + private boolean turretOn = true; + + @SuppressWarnings("unused") + public Auto_Main_() { addComponents( new PedroComponent(Constants::createFollower) ); - Robot.init(startPose); - Robot.drivetrain.follower = PedroComponent.follower(); + } + @Override + public void onInit() { prompter = new Prompter(this); prompter.prompt("alliance", new OptionPrompt<>("Select Alliance", @@ -61,17 +58,27 @@ public void onInit() { )); prompter.prompt("auto", new OptionPrompt<>("Select Auto", - Auto.LEAVE_NEAR_LAUNCH, - Auto.LEAVE_FAR_HP, - Auto.MOVEMENT_RP_DEC6 + new Near_12Ball(), + new Near_9Ball(), + new Leave_Far_Generic() + //new test_Auto(), + //new Complex_Test_Auto() )); prompter.onComplete(() -> { selectedAlliance = prompter.get("alliance"); selectedAuto = prompter.get("auto"); telemetry.addData("Selection", "Complete"); - } - ); - Robot.drivetrain.paths.buildPaths(selectedAlliance); + telemetry.addData("Alliance", selectedAlliance.toString()); + telemetry.addData("Auto", selectedAuto); + telemetry.update(); + // Build paths + paths.buildPaths(selectedAlliance, follower()); + // Initialize all hardware of the robot + Robot.init(paths.autoMirror(selectedAuto.getStartPose(), selectedAlliance), follower()); + while (opModeInInit() && Robot.turret.zeroTurret()){ + sleep(0); + } + }); } @Override @@ -82,87 +89,252 @@ public void onWaitForStart() { @Override public void onStartButtonPressed() { // Schedule the proper auto - switch (selectedAuto) { - case LEAVE_NEAR_LAUNCH: - Leave_Near_Launch().invoke(); - return; - case LEAVE_FAR_HP: - Leave_Far_HP().invoke(); - return; - } + selectedAuto.runAuto(); + turretOn = selectedAuto.getTurretEnabled(); + + // Indicate that initialization is done + telemetry.addLine("Initialized"); + telemetry.update(); } @Override public void onUpdate() { + telemetry.addData("Running Auto", selectedAuto.toString()); + telemetry.addLine(); if (turretOn){ - switch (selectedAlliance) { - case RED: - Robot.turret.updateAimbot(Robot.drivetrain.follower.getPose(), true); - return; - case BLUE: - Robot.turret.updateAimbot(Robot.drivetrain.follower.getPose(), false); - return; - } - } - - telemetry.addData("selectedAuto", selectedAuto); - telemetry.addData("currentPose", Robot.drivetrain.follower.getPose()); + telemetry.addData("Aimbot Target", selectedAlliance); + }else{ + telemetry.addLine("Aimbot Off"); + } + Robot.turret.updateAimbot(turretOn, true, 0); + Robot.turret.updateFlywheel(); + + MecanumDrivetrainClass.robotPose = Robot.drivetrain.follower.getPose(); + + telemetry.addLine(); + telemetry.addData("FarStart", paths.farStart); + telemetry.addData("FarShoot", paths.noTurretFarShoot); telemetry.update(); } + @Override + public void onStop(){ + Robot.drivetrain.follower.holdPoint(Robot.drivetrain.follower.getPose()); + MecanumDrivetrainClass.robotPose = Robot.drivetrain.follower.getPose(); + } + /** - * This auto starts at the far zone, - * shoots it's preloads after 5 seconds, - * and goes to the leave zone next to the human player zone. + * This class serves as a template for all auto programs.
+ * The methods runAuto() and ToString() must be overridden for each auto. */ - private Command Leave_Far_HP() { - turretOn = true; - return new SequentialGroup( - Commands.setFlywheelState(Robot, Turret.flywheelstate.ON), - new ParallelGroup( - new WaitUntil(() -> Robot.turret.getFlywheelRPM() > 5900), - new Delay(5) - ), - Commands.setIntakeMode(Robot, Intake.Mode.SHOOTING), - new Delay(2), - Commands.setIntakeMode(Robot, Intake.Mode.INTAKING), - new Delay(1), - Commands.setIntakeMode(Robot, Intake.Mode.SHOOTING), - new Delay(2), - Commands.setTransferState(Robot, Intake.transferState.UP), - new Delay(1), - Commands.setTransferState(Robot, Intake.transferState.DOWN), - Commands.setIntakeMode(Robot, Intake.Mode.OFF), - Commands.setFlywheelState(Robot, Turret.flywheelstate.OFF), - Commands.runPath(Robot.drivetrain.paths.start2_to_leave3, true, 0.6) - ); + abstract static class Auto{ +// /** +// * This constructor must be called from the child class using super() +// * @param startingPose Indicates the starting pose of the robot +// * @param runTurret Indicates whether to run the turret auto aim functions +// */ +// Auto(Pose startingPose, Boolean runTurret){ +// turretOn = runTurret; +// startPose = startingPose; +// } +// Auto(Pose startingPose){ +// turretOn = true; +// startPose = startingPose; +// } + + abstract Pose getStartPose(); + abstract boolean getTurretEnabled(); + + /** Override this to schedule the auto command*/ + abstract void runAuto(); + /** Override this to rename the auto*/ + @NonNull + @Override + public abstract String toString(); + } + + private class Leave_Far_Generic extends Auto{ + public Pose startPose = paths.farStart; + public boolean turretEnabled = false; + + @Override + public Pose getStartPose(){ + return startPose; + } + @Override + public boolean getTurretEnabled(){ + return turretEnabled; + } + + @Override + public void runAuto(){ + new SequentialGroup( + Commands.runPath(paths.farStart_to_NoTurret_FarShoot, true, 1), + Commands.shootBalls(), + Commands.setIntakeMode(Intake.intakeMode.INTAKING), + Commands.runPath(paths.farShoot_noTurret_to_farPreload, true, 1), + Commands.setIntakeMode(Intake.intakeMode.OFF) + ).schedule(); + } + + @NonNull + @Override + public String toString(){return "Far Zone No Turret Generic";} + } + + private class Near_9Ball extends Auto{ + public Pose startPose = paths.nearStart; + public boolean turretEnabled = true; + + @Override + public Pose getStartPose(){ + return startPose; + } + @Override + public boolean getTurretEnabled(){ + return turretEnabled; + } + + @Override + public void runAuto(){ + new SequentialGroup( + Commands.setIntakeMode(Intake.intakeMode.INTAKING), + new InstantCommand(Commands.setFlywheelState(Turret.flywheelState.ON)), + Commands.runPath(paths.nearStart_to_midShoot, true, 1), + Commands.shootBalls(), + Commands.setFlywheelState(Turret.flywheelState.ON), + Commands.setIntakeMode(Intake.intakeMode.INTAKING), + Commands.runPath(paths.midShoot_to_nearPreload, true, 1), + Commands.runPath(paths.nearPreload_to_midShoot, true, 1), + Commands.shootBalls(), + Commands.setFlywheelState(Turret.flywheelState.ON), + Commands.setIntakeMode(Intake.intakeMode.INTAKING), + Commands.runPath(paths.midShoot_to_midPreload, true, 1), + Commands.runPath(paths.midPreload_to_midShoot, true, 1), + Commands.shootBalls(), + Commands.runPath(paths.midShoot_to_nearLeave, true, 1) + ).schedule(); + } + + @NonNull + @Override + public String toString(){return "Near Zone 9 Ball Auto";} + } + + private class Near_12Ball extends Auto{ + public Pose startPose = paths.nearStart; + public boolean turretEnabled = true; + + @Override + public Pose getStartPose(){ + return startPose; + } + @Override + public boolean getTurretEnabled(){ + return turretEnabled; + } + + @Override + public void runAuto(){ + new SequentialGroup( + Commands.setIntakeMode(Intake.intakeMode.INTAKING), + new InstantCommand(Commands.setFlywheelState(Turret.flywheelState.ON)), + Commands.runPath(paths.nearStart_to_midShoot, true, 1), + Commands.shootBalls(), + Commands.setFlywheelState(Turret.flywheelState.ON), + Commands.setIntakeMode(Intake.intakeMode.INTAKING), + Commands.runPath(paths.midShoot_to_nearPreload, true, 1), + Commands.runPath(paths.nearPreload_to_midShoot, true, 1), + Commands.shootBalls(), + Commands.setFlywheelState(Turret.flywheelState.ON), + Commands.setIntakeMode(Intake.intakeMode.INTAKING), + Commands.runPath(paths.midShoot_to_midPreload, true, 1), + Commands.runPath(paths.midPreload_to_midShoot, true, 1), + Commands.shootBalls(), + Commands.setFlywheelState(Turret.flywheelState.ON), + Commands.setIntakeMode(Intake.intakeMode.INTAKING), + Commands.runPath(paths.midShoot_to_farPreload, true, 1), + Commands.runPath(paths.farPreload_to_midShoot, true, 1), + Commands.shootBalls(), + Commands.runPath(paths.midShoot_to_nearLeave, true, 1) + ).schedule(); + } + + @NonNull + @Override + public String toString(){return "Near Zone 12 Ball Auto";} } /** - * This auto starts at the near zone, - * shoots it's preloads after 5 seconds, - * and goes to the leave pose that is in the launch zone. + * This auto starts at the far zone */ - private Command Leave_Near_Launch() { - turretOn = true; - return new SequentialGroup( - Commands.setFlywheelState(Robot, Turret.flywheelstate.ON), - new ParallelGroup( - new WaitUntil(() -> Robot.turret.getFlywheelRPM() > 5900), - new Delay(5) - ), - Commands.setIntakeMode(Robot, Intake.Mode.SHOOTING), - new Delay(2), - Commands.setIntakeMode(Robot, Intake.Mode.INTAKING), - new Delay(1), - Commands.setIntakeMode(Robot, Intake.Mode.SHOOTING), - new Delay(2), - Commands.setTransferState(Robot, Intake.transferState.UP), - new Delay(1), - Commands.setTransferState(Robot, Intake.transferState.DOWN), - Commands.setIntakeMode(Robot, Intake.Mode.OFF), - Commands.setFlywheelState(Robot, Turret.flywheelstate.OFF), - Commands.runPath(Robot.drivetrain.paths.start1_to_leave1, true, 0.6) - ); + private class Complex_Test_Auto extends Auto{ + public Pose startPose = paths.farStart; + public boolean turretEnabled = true; + + @Override + public Pose getStartPose(){ + return startPose; + } + @Override + public boolean getTurretEnabled(){ + return turretEnabled; + } + + @Override + void runAuto() { + new SequentialGroup( + new Delay(1), + //Commands.shootBalls(), + Commands.setIntakeMode(Intake.intakeMode.INTAKING), + Commands.runPath(paths.farStart_to_farPreload, true, 1), + Commands.setIntakeMode(Intake.intakeMode.OFF), + Commands.runPath(paths.farPreload_to_farShoot, true, 1), + //Commands.shootBalls(), + Commands.setIntakeMode(Intake.intakeMode.INTAKING), + Commands.runPath(paths.farStart_to_midPreload, true, 1), + Commands.setIntakeMode(Intake.intakeMode.OFF), + Commands.runPath(paths.midPreload_to_farShoot, true, 1), + //Commands.shootBalls(), + Commands.runPath(paths.farShoot_to_farLeave, true, 1) + ).schedule(); + } + + @NonNull + @Override + public String toString() { + return "Complex Test Auto"; + } + } + + private class test_Auto extends Auto{ + public Pose startPose = paths.farStart; + public boolean turretEnabled = false; + + @Override + public Pose getStartPose(){ + return startPose; + } + @Override + public boolean getTurretEnabled(){ + return turretEnabled; + } + + @Override + public void runAuto(){ + double tempSpeed = 1; + new SequentialGroup( + Commands.runPath(paths.farStart_to_farPreload,true,tempSpeed), + Commands.runPath(paths.farPreload_to_farShoot,true,tempSpeed), + Commands.runPath(paths.farShoot_to_midPreload, true, tempSpeed), + Commands.runPath(paths.midPreload_to_midShoot, true, tempSpeed), + Commands.runPath(paths.midShoot_to_nearPreload, true, tempSpeed), + Commands.runPath(paths.nearPreload_to_nearShoot, true, tempSpeed) + ).schedule(); + } + + @NonNull + @Override + public String toString(){return "Test Auto";} } } diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Auto_/NextFTC_Pedro_Example_Auto.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Auto_/NextFTC_Pedro_Example_Auto.java new file mode 100644 index 000000000000..bcbe78d74df4 --- /dev/null +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Auto_/NextFTC_Pedro_Example_Auto.java @@ -0,0 +1,54 @@ +//package org.firstinspires.ftc.teamcode.LOADCode.Main_.Auto_; +// +//import com.acmerobotics.roadrunner.path.BezierLine; +//import com.pedropathing.geometry.Pose; +//import com.pedropathing.paths.PathChain; +//import com.qualcomm.robotcore.eventloop.opmode.Autonomous; +// +//import org.firstinspires.ftc.teamcode.pedroPathing.Constants; +// +//import dev.nextftc.core.commands.groups.SequentialGroup; +//import dev.nextftc.core.components.SubsystemComponent; +//import dev.nextftc.extensions.pedro.FollowPath; +//import dev.nextftc.extensions.pedro.PedroComponent; +//import dev.nextftc.ftc.NextFTCOpMode; +// +//@Autonomous(name = "blueBottom") +//public class NextFTC_Pedro_Example_Auto extends NextFTCOpMode { +// private final Pose startPose = new Pose(85.5, 8.3, Math.toRadians(90.0)); +// private final Pose depositPose = new Pose(84.3, 61.9, Math.toRadians(0.0)); +// private final Pose curvePoint = new Pose(138.2, 48.1); +// +// private PathChain skib; +// +// public NextFTC_Pedro_Example_Auto() { +// addComponents( +// new PedroComponent(Constants::createFollower) +// ); +// } +// +// private void buildPaths() { +// skib = follower.pathBuilder() +// .addPath(new BezierLine(startPose, depositPose)) +// .setLinearHeadingInterpolation(startPose.heading, depositPose.heading) +// .build(); +// } +// +// public Command getSecondRoutine() { +// return new SequentialGroup( +// new FollowPath(skib) +// ); +// } +// +// @Override +// public void onInit() { +// follower.setMaxPower(0.7); +// follower.setStartingPose(startPose); +// buildPaths(); +// } +// +// @Override +// public void onStartButtonPressed() { +// getSecondRoutine().run(); +// } +//} \ No newline at end of file diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Hardware_/Actuators_/Devices.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Hardware_/Actuators_/Devices.java index f664d27fd09d..1464fc9d6324 100644 --- a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Hardware_/Actuators_/Devices.java +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Hardware_/Actuators_/Devices.java @@ -7,8 +7,14 @@ import com.qualcomm.robotcore.hardware.DcMotor; import com.qualcomm.robotcore.hardware.DcMotorEx; import com.qualcomm.robotcore.hardware.DcMotorSimple; +import com.qualcomm.robotcore.hardware.DigitalChannel; +import com.qualcomm.robotcore.hardware.DistanceSensor; +import com.qualcomm.robotcore.hardware.NormalizedColorSensor; +import com.qualcomm.robotcore.hardware.NormalizedRGBA; import com.qualcomm.robotcore.hardware.Servo; +import org.firstinspires.ftc.robotcore.external.navigation.DistanceUnit; + import dev.nextftc.control.ControlSystem; import dev.nextftc.control.KineticState; import dev.nextftc.control.feedback.PIDCoefficients; @@ -46,14 +52,25 @@ public double getPower() { } public static class DcMotorExClass { - // PID pidCoefficients + + // Old PID Coefficients + PIDCoefficients old_pidCoefficients = new PIDCoefficients(0, 0, 0); + BasicFeedforwardParameters old_ffCoefficients = new BasicFeedforwardParameters(0,0,0); + + // PID Coefficients PIDCoefficients pidCoefficients = new PIDCoefficients(0, 0, 0); BasicFeedforwardParameters ffCoefficients = new BasicFeedforwardParameters(0,0,0); - // Encoder ticks/rotation - // 1620rpm Gobilda - 103.8 ticks at the motor shaft + /** + *

Encoder ticks/rotation:


+ * 1620rpm Gobilda - 103.8
+ * 1150rpm Gobilda - 145.1
+ * 223rpm Gobilda - 751.8
+ */ public double ticksPerRotation = 103.8; // Target position/velocity of the motor public double target = 0; + // Offset position of the motor + public double offset = 0; // Motor object private DcMotorEx motorObject = null; @@ -80,6 +97,21 @@ public void init (@NonNull OpMode opmode, String motorName){ motorObject = opmode.hardwareMap.get(DcMotorEx.class, motorName); } + ControlSystem velPID = null; + ControlSystem posPID = null; + + public void buildPIDs(){ + if (old_pidCoefficients != pidCoefficients || old_ffCoefficients != ffCoefficients){ + posPID = ControlSystem.builder().posPid(pidCoefficients).build(); + velPID = ControlSystem.builder() + .velPid(pidCoefficients) + .basicFF(ffCoefficients) + .build(); + } + old_pidCoefficients = pidCoefficients; + old_ffCoefficients = ffCoefficients; + } + /** * Sets the value of the PID coefficients of the motor. * @param coefficients The values to set the coefficients to. @@ -101,6 +133,15 @@ public void resetEncoder(){ motorObject.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER); motorObject.setMode(DcMotor.RunMode.RUN_USING_ENCODER); } + /** + * Sets a position offset on the motor in encoder ticks. + */ + public void setOffsetTicks(double ticks){ + offset = ticks; + } + public void setOffsetDegrees(double degrees){ + setOffsetTicks(degrees * (ticksPerRotation/360)); + } /** * Sets the runMode of the motor. * @param runMode The mode to set the motor to. @@ -123,12 +164,13 @@ public void setDirection(DcMotorSimple.Direction direction){ } public void setEncoderTicks(int ticks){ motorObject.setTargetPosition(ticks); + setRunMode(DcMotor.RunMode.RUN_TO_POSITION); } /** * @return The current position of the turret motor in encoder ticks. Can be any value. */ public double getEncoderTicks(){ - return motorObject.getCurrentPosition(); + return motorObject.getCurrentPosition() + offset; } /** * @param power A value between -1 and 1 that the turret motor's power will be set to. @@ -175,15 +217,25 @@ public double getPower(){ /** * Uses a PID controller to move the motor to the desired position. * Must be called every loop to function properly. - * @param angle The angle in degrees to move the motor to. Can be any number. + * @param angle The angle in degrees to move the motor to. Can be any number.
*/ public void setAngle(double angle){ + setAngle(angle, 0); + } + /** + * Uses a PID controller to move the motor to the desired position. + * Must be called every loop to function properly. + * @param angle The angle in degrees to move the motor to. Can be any number. + * @param velocity The velocity in degrees/sec that the motor should be spinning at when it reaches the target point. + */ + public void setAngle(double angle, double velocity){ target = angle; - ControlSystem turretPID = ControlSystem.builder().posPid(pidCoefficients).build(); + buildPIDs(); KineticState currentKineticState = new KineticState(getAngleAbsolute(), getDegreesPerSecond()); - turretPID.setGoal(new KineticState(target)); - setPower(turretPID.calculate(currentKineticState)); + posPID.setGoal(new KineticState(target, velocity)); + setPower(posPID.calculate(currentKineticState)); } + /** * Uses a PID controller to accelerate the motor to the desired RPM. * Must be called every loop to function properly. @@ -191,14 +243,11 @@ public void setAngle(double angle){ */ public void setRPM(double rpm){ target = rpm; + buildPIDs(); double degreesPerSecond = target*6; - ControlSystem PID = ControlSystem.builder() - .velPid(pidCoefficients) - .basicFF(ffCoefficients) - .build(); KineticState currentKineticState = new KineticState(getAngleAbsolute(), getDegreesPerSecond()); - PID.setGoal(new KineticState(0, degreesPerSecond)); - setPower(PID.calculate(currentKineticState)); + velPID.setGoal(new KineticState(0, degreesPerSecond)); + setPower(velPID.calculate(currentKineticState)); } } @@ -231,4 +280,67 @@ public double getAngle(){ return servo.getPosition(); } } + + public static class REVColorSensorV3Class { + private NormalizedColorSensor sensor; + + public void init(@NonNull OpMode opmode, String sensorName){ + sensor = opmode.hardwareMap.get(NormalizedColorSensor.class, sensorName); + } + + public NormalizedRGBA getNormalizedColors(){ + return sensor.getNormalizedColors(); + } + + public double getGain(){ + return sensor.getGain(); + } + + public void setGain(double gain){ + sensor.setGain((float) gain); + } + + public double getDistance(DistanceUnit units){ + return ((DistanceSensor) sensor).getDistance(units); + } + } + + public static class DualProximitySensorClass { + private final REVColorSensorV3Class sensor1 = new REVColorSensorV3Class(); + private final REVColorSensorV3Class sensor2 = new REVColorSensorV3Class(); + + public double threshold = 2; + public DistanceUnit units = DistanceUnit.CM; + + public void init(@NonNull OpMode opmode, String sensor1Name, String sensor2Name){ + sensor1.init(opmode, sensor1Name); + sensor2.init(opmode, sensor2Name); + } + + public void setGain(double gain){ + sensor1.setGain(gain); + sensor2.setGain(gain); + } + + public boolean objectDetected(){ + return (sensor1.getDistance(units) < threshold || sensor2.getDistance(units) < threshold); + } + + public double[] getDistances(){ + return new double[]{sensor1.getDistance(units), sensor2.getDistance(units)}; + } + } + + public static class REVHallEffectSensorClass { + private DigitalChannel sensor; + + public void init(@NonNull OpMode opMode, String sensorName){ + sensor = opMode.hardwareMap.get(DigitalChannel.class, sensorName); + sensor.setMode(DigitalChannel.Mode.INPUT); + } + + public Boolean getTriggered(){ + return !sensor.getState(); + } + } } \ No newline at end of file diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Hardware_/Actuators_/Intake.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Hardware_/Actuators_/Intake.java index 30d827000a53..5d02127bde55 100644 --- a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Hardware_/Actuators_/Intake.java +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Hardware_/Actuators_/Intake.java @@ -1,19 +1,25 @@ package org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_.Actuators_; +import com.bylazar.configurables.annotations.Configurable; import com.qualcomm.robotcore.eventloop.opmode.OpMode; import com.qualcomm.robotcore.hardware.DcMotor; import com.qualcomm.robotcore.hardware.DcMotorSimple; +import org.firstinspires.ftc.robotcore.external.navigation.DistanceUnit; +@Configurable public class Intake { // RESET THESE TO PRIVATE AFTER DECEMBER 6TH! - public final Devices.DcMotorExClass intake = new Devices.DcMotorExClass(); - public final Devices.CRServoClass belt = new Devices.CRServoClass(); - public final Devices.ServoClass transfer = new Devices.ServoClass(); + private final Devices.DcMotorExClass intake = new Devices.DcMotorExClass(); + private final Devices.CRServoClass belt = new Devices.CRServoClass(); + private final Devices.ServoClass transfer = new Devices.ServoClass(); + public final Devices.DualProximitySensorClass topSensor = new Devices.DualProximitySensorClass(); + public final Devices.DualProximitySensorClass bottomSensor = new Devices.DualProximitySensorClass(); - public enum Mode { + public enum intakeMode { INTAKING, SHOOTING, + NO_BELT, REVERSING, OFF } @@ -23,37 +29,54 @@ public enum transferState { DOWN } + public static double proximitySensorThreshold = 20; + public void init(OpMode opmode){ intake.init(opmode, "intake"); belt.init(opmode, "belt"); transfer.init(opmode, "transfer"); + topSensor.init(opmode, "color1", "color2"); + bottomSensor.init(opmode, "color3", "color4"); + intake.setZeroPowerBehaviour(DcMotor.ZeroPowerBehavior.BRAKE); intake.setDirection(DcMotorSimple.Direction.REVERSE); belt.setDirection(DcMotorSimple.Direction.REVERSE); + + topSensor.setGain(2); + topSensor.units = DistanceUnit.MM; + topSensor.threshold = proximitySensorThreshold; + bottomSensor.setGain(2); + bottomSensor.units = DistanceUnit.MM; + bottomSensor.threshold = proximitySensorThreshold; } /** * @param direction * Takes the following inputs * */ - public void setMode(Mode direction){ - if (direction == Mode.INTAKING){ + public void setMode(intakeMode direction){ + if (direction == intakeMode.INTAKING){ + //if (!(getTopSensorState() && getBottomSensorState())){ + belt.setPower(1); + //} intake.setPower(1); - belt.setPower(1); - }else if (direction == Mode.SHOOTING){ + }else if (direction == intakeMode.SHOOTING){ intake.setPower(0); belt.setPower(1); - }else if (direction == Mode.REVERSING){ + }else if (direction == intakeMode.REVERSING){ intake.setPower(-1); belt.setPower(-1); + }else if (direction == intakeMode.NO_BELT){ + intake.setPower(1); + belt.setPower(0); }else{ intake.setPower(0); belt.setPower(0); @@ -63,23 +86,23 @@ public void setMode(Mode direction){ /** * Outputs one of the following modes * */ - public Mode getMode(){ + public intakeMode getMode(){ double intakePower = intake.getPower(); double beltPower = belt.getPower(); if (intakePower == 1 && beltPower == 1){ - return Mode.INTAKING; + return intakeMode.INTAKING; }else if (intakePower == 0 && beltPower == 1){ - return Mode.SHOOTING; + return intakeMode.SHOOTING; }else if (intakePower == -1 && beltPower == -1){ - return Mode.REVERSING; + return intakeMode.REVERSING; }else{ - return Mode.OFF; + return intakeMode.OFF; } } @@ -92,4 +115,13 @@ public void setTransfer(transferState state) { transfer.setAngle(.05); } } + + public boolean getTopSensorState(){ + topSensor.threshold = proximitySensorThreshold; + return topSensor.objectDetected(); + } + public boolean getBottomSensorState(){ + bottomSensor.threshold = proximitySensorThreshold; + return bottomSensor.objectDetected(); + } } diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Hardware_/Actuators_/Turret.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Hardware_/Actuators_/Turret.java index 9579dfb1bcfc..e2682fb4795e 100644 --- a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Hardware_/Actuators_/Turret.java +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Hardware_/Actuators_/Turret.java @@ -4,8 +4,12 @@ import com.pedropathing.geometry.Pose; import com.qualcomm.robotcore.eventloop.opmode.OpMode; import com.qualcomm.robotcore.hardware.DcMotor; +import com.qualcomm.robotcore.hardware.DcMotorSimple; +import com.qualcomm.robotcore.hardware.Servo; +import com.skeletonarmy.marrow.zones.PolygonZone; - +import org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_.LoadHardwareClass; +import org.firstinspires.ftc.teamcode.LOADCode.Main_.Utils_; import dev.nextftc.control.feedback.PIDCoefficients; import dev.nextftc.control.feedforward.BasicFeedforwardParameters; @@ -13,124 +17,315 @@ @Configurable public class Turret { - + // Hardware definitions public final Devices.DcMotorExClass rotation = new Devices.DcMotorExClass(); public final Devices.DcMotorExClass flywheel = new Devices.DcMotorExClass(); - public final Devices.ServoClass hood = new Devices.ServoClass(); - public final Devices.ServoClass gate = new Devices.ServoClass(); + private final Devices.DcMotorExClass flywheel2 = new Devices.DcMotorExClass(); + private final Devices.ServoClass hood = new Devices.ServoClass(); + private final Devices.ServoClass gate = new Devices.ServoClass(); + public final Devices.REVHallEffectSensorClass hall = new Devices.REVHallEffectSensorClass(); // Offset 20 degrees from directly forwards + + // Turret PID coefficients + public static PIDCoefficients turretCoefficients = new PIDCoefficients(0.02, 0.00000000005, 0.004); // 223RPM Motor + + // Flywheel PID coefficients for various speeds + //public static PIDCoefficients flywheelCoefficients = new PIDCoefficients(0.0002, 0, 0); // 4500 RPM + public static PIDCoefficients flywheelCoefficients4200 = new PIDCoefficients(0.0004, 0, 0); // 4200 RPM + public static PIDCoefficients flywheelCoefficients3500 = new PIDCoefficients(0.00025, 0, 0); // 3500 RPM + //public static PIDCoefficients flywheelCoefficients = new PIDCoefficients(0.00025, 0, 0); // 3000 RPM - public static PIDCoefficients turretCoefficients = new PIDCoefficients(0.002, 0, 0); - public static PIDCoefficients flywheelCoefficients = new PIDCoefficients(0.0003, 0.0001, 0.0001); - public static BasicFeedforwardParameters flywheelFFCoefficients = new BasicFeedforwardParameters(0.00002899,0,0); + // Flywheel FF coefficients for various speeds + //public static BasicFeedforwardParameters flywheelFFCoefficients = new BasicFeedforwardParameters(0.000026,0,0); // 4500 RPM + public static BasicFeedforwardParameters flywheelFFCoefficients4200 = new BasicFeedforwardParameters(0.000032,0,0); // 4200 RPM + public static BasicFeedforwardParameters flywheelFFCoefficients3500 = new BasicFeedforwardParameters(0.000032,0,0); // 3500 RPM + //public static BasicFeedforwardParameters flywheelFFCoefficients = new BasicFeedforwardParameters(0.000031,0,0); // 3000 RPM + // Actual Flywheel Coefficients + private PIDCoefficients actualFlywheelCoefficients = flywheelCoefficients3500; + private BasicFeedforwardParameters actualFlywheelFFCoefficients = flywheelFFCoefficients3500; + + // Define any Enums here public enum gatestate { OPEN, CLOSED, } - - public enum flywheelstate { - ON, - OFF + public enum flywheelState { + OFF, + ON } - public flywheelstate flywheelState = flywheelstate.OFF; - public void init(OpMode opmode){ - rotation.init(opmode, "turret", 145.1); //Previously 103.8 - flywheel.init(opmode, "flywheel"); + // Define any state variables or important parameters here + /** Stores the current state of the flywheel.*/ + public flywheelState flywheelMode = flywheelState.OFF; + double targetRPM = 0; + /** Controls the target speed of the flywheel when it is on.*/ + public static double flywheelNearSpeed = 3500; + public static double flywheelFarSpeed = 4200; + /** Controls the upper software limit of the hood.*/ + public static double upperHoodLimit = 260; + /** + * Stores the offset of the turret's rotation + */ + public static double turretOffset = 116; + /** + * Stores the zeroing state of the turret + */ + public static boolean zeroed = false; + + // Stores important objects for later access + OpMode opMode = null; + LoadHardwareClass Robot = null; + PolygonZone robotZone = new PolygonZone(15, 15); + + // The variable to store the InterpLUT table for turret hood aimbot + public Utils_.InterpLUT hoodLUT = new Utils_.InterpLUT(); + + public void init(OpMode opmode, LoadHardwareClass robot){ + // Store important objects in their respective variables + opMode = opmode; + Robot = robot; + + // Initialize hardware objects + rotation.init(opmode, "turret", 751.8 * ((double) 131 / 36)); + flywheel.init(opmode, "flywheel", 28); + flywheel2.init(opmode, "flywheel2", 28); hood.init(opmode, "hood"); gate.init(opmode, "gate"); + hall.init(opmode, "hall"); - rotation.setZeroPowerBehaviour(DcMotor.ZeroPowerBehavior.BRAKE); + // Set servos to initial positions + setGateState(gatestate.CLOSED); + // Set servo directions + hood.setDirection(Servo.Direction.REVERSE); - flywheel.ticksPerRotation = 28; + // Flywheel Motor Settings + flywheel2.setDirection(DcMotorSimple.Direction.REVERSE); + flywheel.setRunMode(DcMotor.RunMode.RUN_USING_ENCODER); + flywheel2.setRunMode(DcMotor.RunMode.RUN_USING_ENCODER); + flywheel.setZeroPowerBehaviour(DcMotor.ZeroPowerBehavior.FLOAT); + flywheel2.setZeroPowerBehaviour(DcMotor.ZeroPowerBehavior.FLOAT); - gate.setAngle(0.5); + // Rotation Motor Settings + rotation.setZeroPowerBehaviour(DcMotor.ZeroPowerBehavior.BRAKE); + rotation.setDirection(DcMotorSimple.Direction.REVERSE); + rotation.setOffsetDegrees(turretOffset); // Pass PID pidCoefficients to motor classes rotation.setPidCoefficients(turretCoefficients); - flywheel.setPidCoefficients(flywheelCoefficients); - flywheel.setFFCoefficients(flywheelFFCoefficients); + flywheel.setPidCoefficients(actualFlywheelCoefficients); + flywheel.setFFCoefficients(actualFlywheelFFCoefficients); + flywheel2.setPidCoefficients(actualFlywheelCoefficients); + flywheel2.setFFCoefficients(actualFlywheelFFCoefficients); + + // TODO Build hood InterpLUT for autoaim + // Near zone measurements + hoodLUT.add(0, 0); + hoodLUT.add(53.5,108); + hoodLUT.add(71,168); + hoodLUT.add(77, 181); + hoodLUT.add(88,190); + hoodLUT.add(94.5,190); + // Far zone measurements + hoodLUT.add(103, 200); + hoodLUT.add(204, 200); + + // Generate Lookup Table & Initialize servo position + hoodLUT.createLUT(); + Pose goalPose = new Pose(0,144,0); + if (LoadHardwareClass.selectedAlliance == LoadHardwareClass.Alliance.RED) {goalPose = new Pose(144, 144, 0);} + //setHood(hoodLUT.get(Robot.drivetrain.follower.getPose().distanceFrom(goalPose))); + setHood(0); } + /** Sets the value of the internal motor PID coefficients */ public void updatePIDs(){ // Pass PID pidCoefficients to motor classes rotation.setPidCoefficients(turretCoefficients); - flywheel.setPidCoefficients(flywheelCoefficients); - flywheel.setFFCoefficients(flywheelFFCoefficients); + flywheel.setPidCoefficients(actualFlywheelCoefficients); + flywheel.setFFCoefficients(actualFlywheelFFCoefficients); + flywheel2.setPidCoefficients(actualFlywheelCoefficients); + flywheel2.setFFCoefficients(actualFlywheelFFCoefficients); + rotation.setOffsetDegrees(turretOffset); } + double redOffset = -2; + double blueOffset = 2; + double posOffset = 4; + /** - * @param robotPose The pose of the robot, gotten from PedroPathing's localization - * @param targetRedGoal Set this to true to target the red goal, otherwise targets the blue goal. + * Runs the aimbot program to control the turret rotation and hood angle.
+ * Must be called every loop to function properly. + * @param turret If TRUE, enables the rotation autoaim. + * Otherwise, sets the turret to face forwards. + * @param hood If TRUE, enables the hood autoaim. + * Otherwise, sets the hood to the highest launch angle. */ - public void updateAimbot(Pose robotPose, boolean targetRedGoal){ - rotation.setAngle(calcLocalizer(robotPose, targetRedGoal)); + public void updateAimbot(boolean turret, boolean hood, double hoodOffset){ + if (turret){ + // Set the turret rotation + if (LoadHardwareClass.selectedAlliance == LoadHardwareClass.Alliance.RED){ + rotation.setAngle(Math.min(Math.max(0, calcLocalizer()+redOffset), 360)); + }else{ + rotation.setAngle(Math.min(Math.max(0, calcLocalizer()+blueOffset), 360)); + } + }else{ + rotation.setAngle(90); + } + if (hood){ + // Set the hood angle + Pose goalPose = new Pose(0,144,0); + if (LoadHardwareClass.selectedAlliance == LoadHardwareClass.Alliance.RED) {goalPose = new Pose(144, 144, 0);} + setHood(hoodLUT.get(Robot.drivetrain.follower.getPose().distanceFrom(goalPose)) + hoodOffset); + }else{ + setHood(0); + } + } + // FIXME does not work currently + public void updateAimbotWithVelocity(){ + // Set the turret rotation + if (LoadHardwareClass.selectedAlliance == LoadHardwareClass.Alliance.RED){ + rotation.setAngle(Math.max(0, calcLocalizer()+redOffset)%360, -Math.toDegrees(Robot.drivetrain.follower.getAngularVelocity())); + }else{ + rotation.setAngle(Math.max(0, calcLocalizer()+blueOffset)%360, -Math.toDegrees(Robot.drivetrain.follower.getAngularVelocity())); + } + // Set the hood angle + Pose goalPose = new Pose(0-posOffset,144+posOffset,0); + if (LoadHardwareClass.selectedAlliance == LoadHardwareClass.Alliance.RED) {goalPose = new Pose(144+posOffset, 144+posOffset, 0);} + //setHood(hoodLUT.get(Robot.drivetrain.follower.getPose().distanceFrom(goalPose))); } - - public void setGate(gatestate state){ - if (state == gatestate.OPEN){ + /** + * Sets the state of the turret gate. + */ + public void setGateState(gatestate state){ + if (state == gatestate.CLOSED){ + gate.setAngle(0.47); + }else if (state == gatestate.OPEN){ gate.setAngle(0.5); - }else if (state == gatestate.CLOSED){ - gate.setAngle(0.25); } } /** - * Outputs one of the following modes + * Sets the angle of the hood. + * @param angle An angle in degrees that is constrained to between 0 and the upper hood limit. + */ + public void setHood(double angle){ + hood.setAngle(Math.min(Math.max(angle, 0), upperHoodLimit)/(360*5)); + } + + /** + * Gets the last set position of the turret hood. + * @return The angle of the hood in degrees. + */ + public double getHood(){ + return hood.getAngle() * 360 * 5; + } + + /** + * Gets the current state of the turret gate. + * Outputs one of the following modes. * */ public gatestate getGate(){ - if (gate.getAngle() == 0.5){ + if (gate.getAngle() == 0.47){ return gatestate.OPEN; } else { return gatestate.CLOSED; } } - public double calcLocalizer (Pose robotPose, boolean targetRedGoal){ - Pose goalPose = new Pose(0,144,0); - if (targetRedGoal) {goalPose = new Pose(144, 144, 0);} + /** + * Calculates the target angle to rotate the turret to in order to aim at the correct goal.
+ * Currently uses Pinpoint Odometry and trigonometry to get the angle. + */ + public double calcLocalizer (){ + Pose goalPose = new Pose(0-posOffset,144+posOffset,0); + if (LoadHardwareClass.selectedAlliance == LoadHardwareClass.Alliance.RED) {goalPose = new Pose(144+posOffset, 144+posOffset, 0);} - return Math.toDegrees(Math.atan2( - goalPose.getY()-robotPose.getY(), - goalPose.getX()-robotPose.getX()) - ) - Math.toDegrees(robotPose.getHeading()) + 180; + return (Math.toDegrees(Math.atan2( + goalPose.getY()-Robot.drivetrain.follower.getPose().getY(), + goalPose.getX()-Robot.drivetrain.follower.getPose().getX()) + ) - Math.toDegrees(Robot.drivetrain.follower.getPose().getHeading()) + 90)%360; } /** + * Sets the RPM of the flywheel. * @param rpm - * RPM Range [0,6000] + * Range [0,6000] */ - public void setFlywheelRPM(double rpm){ - flywheel.setRPM(rpm); + private void setFlywheelRPM(double rpm){ + if (rpm == 0){ + flywheel.target = 0; + flywheel.setPower(0); + flywheel2.setPower(0); + }else{ + flywheel.setRPM(rpm); + flywheel2.setPower(flywheel.getPower()); + } } /** - * @return double flywheel.getRPM(); - RPM Range [0,6000] + * Gets the current RPM of the flywheel. */ public double getFlywheelRPM(){ return flywheel.getRPM(); } /** - * Sets the target state of the Flywheel + * Sets the target state of the Flywheel.
+ * updateFlywheel() must be called every loop for this to be effective. * @param state The state to set the flywheel to (ON/OFF) */ - public void setFlywheel(flywheelstate state){ - flywheelState = state; + public void setFlywheelState(flywheelState state){ + flywheelMode = state; + } + + public double getFlywheelCurrentMaxSpeed(){ + return targetRPM; + } + + + public boolean zeroTurret(){ + if (!zeroed){ + rotation.setPower(1); + if (hall.getTriggered()){ + rotation.setPower(0); + rotation.resetEncoder(); + zeroed = true; + } + } + opMode.telemetry.addLine("ZEROING TURRET"); + opMode.telemetry.addData("Turret Power", rotation.getPower()); + opMode.telemetry.update(); + return !zeroed; } /** * Updates the flywheel PID. Must be called every loop. */ public void updateFlywheel(){ - if (flywheelState == flywheelstate.ON){ - setFlywheelRPM(5485.714285714286); - } else if (flywheelState == flywheelstate.OFF){ + robotZone.setPosition(Robot.drivetrain.follower.getPose().getX(), Robot.drivetrain.follower.getPose().getY()); + robotZone.setRotation(Robot.drivetrain.follower.getPose().getHeading()); + + opMode.telemetry.addData("In Far Zone", robotZone.isInside(LoadHardwareClass.FarLaunchZone)); + opMode.telemetry.addData("In Near Zone", robotZone.isInside(LoadHardwareClass.ReallyNearLaunchZoneRed)); + + if (robotZone.isInside(LoadHardwareClass.FarLaunchZone)){ + targetRPM = flywheelFarSpeed; + actualFlywheelCoefficients = flywheelCoefficients4200; + actualFlywheelFFCoefficients = flywheelFFCoefficients4200; + }else{ + targetRPM = flywheelNearSpeed; + actualFlywheelCoefficients = flywheelCoefficients3500; + actualFlywheelFFCoefficients = flywheelFFCoefficients3500; + } + + if (flywheelMode == flywheelState.ON){ + setFlywheelRPM(targetRPM); + }else if (flywheelMode == flywheelState.OFF){ setFlywheelRPM(0); } } diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Hardware_/Commands.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Hardware_/Commands.java index 49131376c7c5..248a3e8939e8 100644 --- a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Hardware_/Commands.java +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Hardware_/Commands.java @@ -1,51 +1,95 @@ package org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_; +import androidx.annotation.NonNull; + import com.pedropathing.paths.PathChain; +import com.skeletonarmy.marrow.TimerEx; import org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_.Actuators_.Intake; import org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_.Actuators_.Turret; import dev.nextftc.core.commands.Command; +import dev.nextftc.core.commands.delays.WaitUntil; +import dev.nextftc.core.commands.groups.SequentialGroup; import dev.nextftc.core.commands.utility.InstantCommand; import dev.nextftc.core.commands.utility.LambdaCommand; import dev.nextftc.extensions.pedro.FollowPath; public class Commands { - public static Object driveSystem = null; - public static Object turretSystem = null; - public static Object intakeSystem = null; + // Robot Object for command access + public LoadHardwareClass Robot; + public Commands(@NonNull LoadHardwareClass robot){ + Robot = robot; + } - public static Command runPath(PathChain path, boolean holdEnd) { - return new FollowPath(path, holdEnd).requires(driveSystem); + // Delay timer for shooting sequence + private static final TimerEx shootingTimer = new TimerEx(1); + private static Command resetShootingTimer() { + return new LambdaCommand("resetShootingTimer").setStart(shootingTimer::restart); } - public static Command runPath(PathChain path, boolean holdEnd, double maxPower) { - return new FollowPath(path, holdEnd, maxPower).requires(driveSystem); + + public Command runPath(PathChain path, boolean holdEnd, double maxPower) { + return new FollowPath(path, holdEnd, maxPower); } - public static Command setFlywheelState(LoadHardwareClass Robot, Turret.flywheelstate state) { + public Command setFlywheelState(Turret.flywheelState state) { return new LambdaCommand("setFlywheelState()") .setInterruptible(false) - .setStart(() -> Robot.turret.setFlywheel(state)) + .setStart(() -> Robot.turret.setFlywheelState(state)) .setIsDone(() -> { - if (state == Turret.flywheelstate.ON){ - return Robot.turret.getFlywheelRPM() > 5900; + if (state == Turret.flywheelState.ON){ + return Robot.turret.getFlywheelRPM() > Robot.turret.getFlywheelCurrentMaxSpeed() - 100; }else{ - return Robot.turret.getFlywheelRPM() < 100; + return true; } }) - .requires(turretSystem); + ; } - public static Command setIntakeMode(LoadHardwareClass Robot, Intake.Mode state) { + private Command setGateState(Turret.gatestate state){ + return new InstantCommand(new LambdaCommand("setGateState") + .setStart(() -> Robot.turret.setGateState(state)) + ); + } + + public Command setIntakeMode(Intake.intakeMode state) { return new InstantCommand(new LambdaCommand("setIntakeMode()") .setStart(() -> Robot.intake.setMode(state)) - .requires(intakeSystem)); + .setIsDone(() -> true) + ); } - public static Command setTransferState(LoadHardwareClass Robot, Intake.transferState state) { + public Command setTransferState(Intake.transferState state) { return new InstantCommand(new LambdaCommand("setIntakeMode()") .setStart(() -> Robot.intake.setTransfer(state)) - .requires(intakeSystem)); + .setIsDone(() -> true) + ); } + + public Command shootBalls(){ + return new SequentialGroup( + // Ensure the flywheel is up to speed, if not, spin up first + setFlywheelState(Turret.flywheelState.ON), + + // Shoot the first two balls + setIntakeMode(Intake.intakeMode.INTAKING), + setGateState(Turret.gatestate.OPEN), + resetShootingTimer(), + new WaitUntil(() -> Robot.intake.getTopSensorState() && !Robot.intake.getBottomSensorState() && shootingTimer.isDone()), + + // Shoot the last ball + setIntakeMode(Intake.intakeMode.SHOOTING), + setTransferState(Intake.transferState.UP), + resetShootingTimer(), + new WaitUntil(shootingTimer::isDone), + + // Reset the systems + setIntakeMode(Intake.intakeMode.OFF), + setGateState(Turret.gatestate.CLOSED), + setTransferState(Intake.transferState.DOWN), + setFlywheelState(Turret.flywheelState.OFF) + ); + } + } diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Hardware_/Drivetrain_/MecanumDrivetrainClass.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Hardware_/Drivetrain_/MecanumDrivetrainClass.java index 89044cb1c545..6f40ac67922b 100644 --- a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Hardware_/Drivetrain_/MecanumDrivetrainClass.java +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Hardware_/Drivetrain_/MecanumDrivetrainClass.java @@ -7,6 +7,7 @@ import com.pedropathing.paths.PathChain; import com.qualcomm.robotcore.eventloop.opmode.OpMode; +import org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_.LoadHardwareClass; import org.firstinspires.ftc.teamcode.pedroPathing.Constants; public class MecanumDrivetrainClass { @@ -15,7 +16,8 @@ public class MecanumDrivetrainClass { // Misc Constants public Follower follower = null; - public Pedro_Paths paths = null; + + public static Pose robotPose = null; /** * Initializes the PedroPathing follower. @@ -26,12 +28,8 @@ public class MecanumDrivetrainClass { public void init (@NonNull OpMode myOpMode, Pose initialPose){ // PedroPathing initialization follower = Constants.createFollower(myOpMode.hardwareMap); // Initializes the PedroPathing path follower - paths = new Pedro_Paths(follower); follower.setStartingPose(initialPose); // Sets the initial position of the robot on the field follower.update(); // Applies the initialization - - follower.startTeleopDrive(); - follower.update(); } /** @@ -39,15 +37,16 @@ public void init (@NonNull OpMode myOpMode, Pose initialPose){ * Needs to be run once after all hardware is initialized. * @param myOpMode Allows the follower access to the robot hardware. * @param initialPose The starting pose of the robot. - * @param follow The follower object. + * @param follow The PedroPathing follower object */ public void init (@NonNull OpMode myOpMode, Pose initialPose, Follower follow){ // PedroPathing initialization - follower = follow; - paths = new Pedro_Paths(follower); - follower.setStartingPose(initialPose); // Sets the initial position of the robot on the field + follower = follow; // Initializes the PedroPathing path follower + follower.setPose(initialPose); // Sets the initial position of the robot on the field follower.update(); // Applies the initialization + } + public void startTeleOpDrive(){ follower.startTeleopDrive(); follower.update(); } @@ -62,9 +61,9 @@ public void init (@NonNull OpMode myOpMode, Pose initialPose, Follower follow){ */ public void pedroMecanumDrive(double forward, double strafe, double rotate, boolean robotCentric){ follower.setTeleOpDrive( - -forward, - -strafe, - -rotate, + -forward * speedMultiplier, + -strafe * speedMultiplier, + -rotate * speedMultiplier, robotCentric); follower.update(); } @@ -74,6 +73,12 @@ public void runPath(PathChain path, boolean holdEndpoint){ follower.update(); } + public double distanceFromGoal(){ + Pose goalPose = new Pose(0,144,0); + if (LoadHardwareClass.selectedAlliance == LoadHardwareClass.Alliance.RED) {goalPose = new Pose(144, 144, 0);} + return follower.getPose().distanceFrom(goalPose); + } + public boolean pathComplete(){ return !follower.isBusy(); } diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Hardware_/Drivetrain_/Pedro_Paths.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Hardware_/Drivetrain_/Pedro_Paths.java index d45fa0c3d763..5a18905eb4d1 100644 --- a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Hardware_/Drivetrain_/Pedro_Paths.java +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Hardware_/Drivetrain_/Pedro_Paths.java @@ -10,432 +10,532 @@ public class Pedro_Paths { // The variable to store PedroPathing's follower object for path building - public Follower follower; + private Follower follower; /** * Define primary poses to be used in paths */ - // Dummy Stand-In Poses - public Pose dummyStartPose = new Pose(0,0,0); - public Pose dummyMoveRPPose = new Pose(0,28,0); // Start Poses - public Pose startPose1 = new Pose(112, 136.6, Math.toRadians(270)); - public Pose startPose2 = new Pose(88, 7.4, Math.toRadians(90)); + public Pose nearStart = new Pose(118, 132, Math.toRadians(306)); + public Pose farStart = new Pose(88, 7.4, Math.toRadians(90)); // Preload Poses - public Pose preloadPose1 = new Pose(130.000, 83.500, Math.toRadians(90)); - public Pose preloadPose2 = new Pose(132.000, 59.500, Math.toRadians(90)); - public Pose preloadPose3 = new Pose(132.000, 35.500, Math.toRadians(90)); + public Pose nearPreload = new Pose(127.000, 83.500, Math.toRadians(0)); + public Pose midPreload = new Pose(132.000, 59.500, Math.toRadians(0)); + public Pose farPreload = new Pose(132.000, 35.500, Math.toRadians(0)); // Shooting Poses - public Pose shootingPose1 = new Pose(115, 120, Math.toRadians(-35)); - public Pose shootingPose2 = new Pose(85, 85, Math.toRadians(-15)); - public Pose shootingPose3 = new Pose(85, 15, Math.toRadians(60)); + public Pose nearShoot = new Pose(115, 120, Math.toRadians(-35)); + public Pose midShoot = new Pose(85, 85, Math.toRadians(-15)); + public Pose farShoot = new Pose(85, 15, Math.toRadians(60)); + public Pose noTurretMidShoot = new Pose(85, 85, Math.toRadians(45)); + public Pose noTurretFarShoot = new Pose(85, 15, Math.toRadians(67.3)); // Leave Poses - public Pose leavePose1 = new Pose(90,120); - public Pose leavePose2 = new Pose(95,55); - public Pose leavePose3 = new Pose(115,20); + public Pose nearLeave = new Pose(90,120, Math.toRadians(90)); + public Pose midLeave = new Pose(95,55, Math.toRadians(90)); + public Pose farLeave = new Pose(115,20, Math.toRadians(90)); - // Define all path variables - // Dummy Paths - public PathChain basicMoveRPPath; + /** + *

Define all path variables

+ */ // Start Poses to Preloads - public PathChain startPose1_to_preload1, startPose1_to_preload2, startPose1_to_preload3; - public PathChain startPose2_to_preload1, startPose2_to_preload2, startPose2_to_preload3; + public PathChain nearStart_to_nearPreload, nearStart_to_midPreload, nearStart_to_farPreload; + public PathChain farStart_to_nearPreload, farStart_to_midPreload, farStart_to_farPreload; + // Start Poses to Shooting Poses + public PathChain nearStart_to_midShoot; + //public PathChain nearStart_to_nearShoot, nearStart_to_midShoot, nearStart_to_farShoot; + //public PathChain farStart_to_nearShoot, farStart_to_midShoot, farStart_to_farShoot; // Preloads to Shooting Positions - public PathChain preload1_to_shooting1, preload1_to_shooting2, preload1_to_shooting3; - public PathChain preload2_to_shooting1, preload2_to_shooting2, preload2_to_shooting3; - public PathChain preload3_to_shooting1, preload3_to_shooting2, preload3_to_shooting3; + public PathChain nearPreload_to_nearShoot, nearPreload_to_midShoot, nearPreload_to_farShoot; + public PathChain midPreload_to_nearShoot, midPreload_to_midShoot, midPreload_to_farShoot; + public PathChain farPreload_to_nearShoot, farPreload_to_midShoot, farPreload_to_farShoot; // Shooting Positions to Preloads - public PathChain shooting1_to_preload1, shooting1_to_preload2, shooting1_to_preload3; - public PathChain shooting2_to_preload1, shooting2_to_preload2, shooting2_to_preload3; - public PathChain shooting3_to_preload1, shooting3_to_preload2, shooting3_to_preload3; + public PathChain nearShoot_to_nearPreload, nearShoot_to_midPreload, nearShoot_to_farPreload; + public PathChain midShoot_to_nearPreload, midShoot_to_midPreload, midShoot_to_farPreload; + public PathChain farShoot_to_nearPreload, farShoot_to_midPreload, farShoot_to_farPreload; + // No-turret Shooting Positions to Preloads + public PathChain midShoot_noTurret_to_nearPreload, midShoot_noTurret_to_midPreload, midShoot_noTurret_to_farPreload; + public PathChain farShoot_noTurret_to_nearPreload, farShoot_noTurret_to_midPreload, farShoot_noTurret_to_farPreload; // Shooting Positions to Leave Positions - public PathChain shooting1_to_leave1, shooting1_to_leave2; - public PathChain shooting2_to_leave1, shooting2_to_leave2; - public PathChain shooting3_to_leave2, shooting3_to_leave3; + public PathChain nearShoot_to_nearLeave, nearShoot_to_midLeave; + public PathChain midShoot_to_nearLeave, midShoot_to_midLeave; + public PathChain farShoot_to_midLeave, farShoot_to_farLeave; // Start Positions to Leave Positions - public PathChain start1_to_leave1, start1_to_leave2; - public PathChain start2_to_leave2, start2_to_leave3; + public PathChain nearStart_to_nearLeave, nearStart_to_midLeave; + public PathChain farStart_to_midLeave, farStart_to_farLeave; + // Start Positions to No-Turret shoot Positions + public PathChain nearStart_to_NoTurret_MidShoot, farStart_to_NoTurret_FarShoot; public Pose autoMirror(Pose pose, LoadHardwareClass.Alliance alliance){ - int mult = 1; if (alliance == LoadHardwareClass.Alliance.BLUE){ - mult = -1; + return new Pose( + 144 - pose.getX(), + pose.getY(), + mirrorHeading(pose.getHeading(), alliance) + ); + }else{ + return pose; } - return new Pose( - 144 - pose.getX(), - 144 - pose.getY(), - Math.atan2(Math.sin(pose.getHeading()), mult * Math.cos(pose.getHeading())) - ); } - - /** - *

DON'T EVER USE THIS PATH!!!

- * This path is ONLY for the December 6th scrimmage, for movement RP bonus only! - */ - public void buildMoveRPPath(){ - basicMoveRPPath = follower.pathBuilder() - .addPath(new BezierCurve(dummyStartPose,dummyMoveRPPose)) - .setLinearHeadingInterpolation(dummyStartPose.getHeading(), dummyMoveRPPose.getHeading()) - .build(); + private double mirrorHeading(double heading, LoadHardwareClass.Alliance alliance){ + if (alliance == LoadHardwareClass.Alliance.BLUE){ + final double v = Math.toDegrees(Math.atan2(Math.sin(heading), Math.cos(heading))); + if (Math.cos(heading) >= 0){ + return Math.toRadians((180 - v)); + }else{ + return Math.toRadians((360 - v)%360); + } + }else{ + return heading; + } } - public void buildStart1ToPreloads(LoadHardwareClass.Alliance alliance) { - startPose1_to_preload1 = follower.pathBuilder() + + public void buildStart1ToPreloads(){ + nearStart_to_nearPreload = follower.pathBuilder() .addPath(new BezierCurve( - autoMirror(startPose1, alliance), - autoMirror(new Pose(89.000, 136.600), alliance), - autoMirror(new Pose(89.000, 78.000), alliance), - autoMirror(new Pose(95.000, 83.500), alliance), - autoMirror(new Pose(110.000, 83.500), alliance) + nearStart, + new Pose(89.000, 136.600), + new Pose(89.000, 78.000), + new Pose(95.000, 83.500), + new Pose(110.000, 83.500) )) - .setLinearHeadingInterpolation(startPose1.getHeading(), Math.toRadians(0)) + .setLinearHeadingInterpolation(nearStart.getHeading(), Math.toRadians(0)) .addPath(new BezierLine( - autoMirror(new Pose(110.000, 83.500), alliance), - autoMirror(preloadPose1, alliance) + new Pose(110.000, 83.500), + nearPreload )) - .setTangentHeadingInterpolation() + .setConstantHeadingInterpolation(Math.toRadians(0)) .build(); - startPose1_to_preload2 = follower.pathBuilder() + nearStart_to_midPreload = follower.pathBuilder() .addPath(new BezierCurve( - autoMirror(startPose1, alliance), - autoMirror(new Pose(89.000, 136.600), alliance), - autoMirror(new Pose(89.000, 54.000), alliance), - autoMirror(new Pose(95.000, 59.500), alliance), - autoMirror(new Pose(110.000, 59.500), alliance) + nearStart, + new Pose(89.000, 136.600), + new Pose(89.000, 54.000), + new Pose(95.000, 59.500), + new Pose(110.000, 59.500) )) - .setLinearHeadingInterpolation(startPose1.getHeading(), Math.toRadians(0)) + .setLinearHeadingInterpolation(nearStart.getHeading(), Math.toRadians(0)) .addPath(new BezierLine( - autoMirror(new Pose(110.000, 59.500), alliance), - autoMirror(preloadPose2, alliance) + new Pose(110.000, 59.500), + midPreload )) - .setTangentHeadingInterpolation() + .setConstantHeadingInterpolation(Math.toRadians(0)) .build(); - startPose1_to_preload3 = follower.pathBuilder() + nearStart_to_farPreload = follower.pathBuilder() .addPath(new BezierCurve( - autoMirror(startPose1, alliance), - autoMirror(new Pose(89.000, 136.600), alliance), - autoMirror(new Pose(89.000, 30.000), alliance), - autoMirror(new Pose(95.000, 35.500), alliance), - autoMirror(new Pose(110.000, 35.500), alliance) + nearStart, + new Pose(89.000, 136.600), + new Pose(89.000, 30.000), + new Pose(95.000, 35.500), + new Pose(110.000, 35.500) )) - .setLinearHeadingInterpolation(startPose1.getHeading(), Math.toRadians(0)) + .setLinearHeadingInterpolation(nearStart.getHeading(), Math.toRadians(0)) .addPath(new BezierLine( - autoMirror(new Pose(110.000, 35.500), alliance), - autoMirror(preloadPose3, alliance) + new Pose(110.000, 35.500), + farPreload )) - .setTangentHeadingInterpolation() + .setConstantHeadingInterpolation(Math.toRadians(0)) .build(); } - public void buildStart2ToPreloads(LoadHardwareClass.Alliance alliance){ - startPose2_to_preload1 = follower.pathBuilder() + public void buildStart2ToPreloads(){ + farStart_to_nearPreload = follower.pathBuilder() .addPath(new BezierCurve( - autoMirror(startPose2, alliance), - autoMirror(new Pose(89.000, 89.000), alliance), - autoMirror(new Pose(95.000, 83.500), alliance), - autoMirror(new Pose(110.000, 83.500), alliance) + farStart, + new Pose(89.000, 79.000), + new Pose(95.000, 83.500), + new Pose(110.000, 83.500) )) - .setLinearHeadingInterpolation(startPose2.getHeading(), Math.toRadians(0)) + .setLinearHeadingInterpolation(farStart.getHeading(), Math.toRadians(0)) .addPath(new BezierLine( - autoMirror(new Pose(110.000, 83.500), alliance), - autoMirror(preloadPose1, alliance) + new Pose(110.000, 83.500), + nearPreload )) - .setTangentHeadingInterpolation() + .setConstantHeadingInterpolation(Math.toRadians(0)) .build(); - startPose2_to_preload2 = follower.pathBuilder() + farStart_to_midPreload = follower.pathBuilder() .addPath(new BezierCurve( - autoMirror(startPose2, alliance), - autoMirror(new Pose(89.000, 136.600), alliance), - autoMirror(new Pose(89.000, 65.000), alliance), - autoMirror(new Pose(95.000, 59.500), alliance), - autoMirror(new Pose(110.000, 59.500), alliance) + farStart, + new Pose(89.000, 55.000), + new Pose(95.000, 59.500), + new Pose(110.000, 59.500) )) - .setLinearHeadingInterpolation(startPose1.getHeading(), Math.toRadians(0)) + .setLinearHeadingInterpolation(nearStart.getHeading(), Math.toRadians(0)) .addPath(new BezierLine( - autoMirror(new Pose(110.000, 59.500), alliance), - autoMirror(preloadPose2, alliance) + new Pose(110.000, 59.500), + midPreload )) - .setTangentHeadingInterpolation() + .setConstantHeadingInterpolation(Math.toRadians(0)) .build(); - startPose2_to_preload3 = follower.pathBuilder() + farStart_to_farPreload = follower.pathBuilder() .addPath(new BezierCurve( - autoMirror(startPose2, alliance), - autoMirror(new Pose(89.000, 136.600), alliance), - autoMirror(new Pose(89.000, 41.000), alliance), - autoMirror(new Pose(95.000, 35.500), alliance), - autoMirror(new Pose(110.000, 35.500), alliance) + farStart, + new Pose(89.000, 25), + new Pose(95.000, 35.500), + new Pose(110.000, 35.500) )) - .setLinearHeadingInterpolation(startPose2.getHeading(), Math.toRadians(0)) + .setLinearHeadingInterpolation(farStart.getHeading(), Math.toRadians(0)) .addPath(new BezierLine( - autoMirror(new Pose(110.000, 35.500), alliance), - autoMirror(preloadPose3, alliance) + new Pose(110.000, 35.500), + farPreload )) - .setTangentHeadingInterpolation() + .setConstantHeadingInterpolation(Math.toRadians(0)) .build(); } - public void buildPreload1ToShootings(LoadHardwareClass.Alliance alliance){ - preload1_to_shooting1 = follower.pathBuilder() - .addPath(new BezierCurve( - autoMirror(preloadPose1, alliance), - autoMirror(shootingPose1, alliance) + public void buildStart1ToShootings(){ + nearStart_to_midShoot = follower.pathBuilder() + .addPath(new BezierLine( + nearStart, + midShoot )) - .setLinearHeadingInterpolation(preloadPose1.getHeading(), shootingPose1.getHeading()) + .setLinearHeadingInterpolation(nearStart.getHeading(), midShoot.getHeading()) .build(); - preload1_to_shooting2 = follower.pathBuilder() - .addPath(new BezierCurve( - autoMirror(preloadPose1, alliance), - autoMirror(shootingPose2, alliance) + } + public void buildPreload1ToShootings(){ + nearPreload_to_nearShoot = follower.pathBuilder() + .addPath(new BezierLine( + nearPreload, + nearShoot )) - .setLinearHeadingInterpolation(preloadPose1.getHeading(), shootingPose2.getHeading()) + .setLinearHeadingInterpolation(nearPreload.getHeading(), nearShoot.getHeading()) .build(); - preload1_to_shooting3 = follower.pathBuilder() + nearPreload_to_midShoot = follower.pathBuilder() + .addPath(new BezierLine( + nearPreload, + midShoot + )) + .setLinearHeadingInterpolation(nearPreload.getHeading(), midShoot.getHeading()) + .build(); + nearPreload_to_farShoot = follower.pathBuilder() .addPath(new BezierCurve( - autoMirror(preloadPose1, alliance), - autoMirror(new Pose(80.000, 83.500), alliance), - autoMirror(shootingPose3, alliance) + nearPreload, + new Pose(80.000, 83.500), + farShoot )) - .setLinearHeadingInterpolation(preloadPose1.getHeading(), shootingPose3.getHeading()) + .setLinearHeadingInterpolation(nearPreload.getHeading(), farShoot.getHeading()) .build(); } - public void buildPreload2ToShootings(LoadHardwareClass.Alliance alliance){ - preload2_to_shooting1 = follower.pathBuilder() + public void buildPreload2ToShootings(){ + midPreload_to_nearShoot = follower.pathBuilder() .addPath(new BezierCurve( - autoMirror(preloadPose2, alliance), - autoMirror(new Pose(65,59.5), alliance), - autoMirror(shootingPose1, alliance) + midPreload, + new Pose(65,59.5), + nearShoot )) - .setLinearHeadingInterpolation(preloadPose2.getHeading(), shootingPose1.getHeading()) + .setLinearHeadingInterpolation(midPreload.getHeading(), nearShoot.getHeading()) .build(); - preload2_to_shooting2 = follower.pathBuilder() + midPreload_to_midShoot = follower.pathBuilder() .addPath(new BezierCurve( - autoMirror(preloadPose2, alliance), - autoMirror(new Pose(65,59.5), alliance), - autoMirror(shootingPose2, alliance) + midPreload, + new Pose(65,59.5), + midShoot )) - .setLinearHeadingInterpolation(preloadPose2.getHeading(), shootingPose2.getHeading()) + .setLinearHeadingInterpolation(midPreload.getHeading(), midShoot.getHeading()) .build(); - preload2_to_shooting3 = follower.pathBuilder() + midPreload_to_farShoot = follower.pathBuilder() .addPath(new BezierCurve( - autoMirror(preloadPose2, alliance), - autoMirror(new Pose(90.000, 59.500), alliance), - autoMirror(shootingPose3, alliance) + midPreload, + new Pose(90.000, 59.500), + farShoot )) - .setLinearHeadingInterpolation(preloadPose2.getHeading(), shootingPose3.getHeading()) + .setLinearHeadingInterpolation(midPreload.getHeading(), farShoot.getHeading()) .build(); } - public void buildPreload3ToShootings(LoadHardwareClass.Alliance alliance){ - preload3_to_shooting1 = follower.pathBuilder() + public void buildPreload3ToShootings(){ + farPreload_to_nearShoot = follower.pathBuilder() .addPath(new BezierCurve( - autoMirror(preloadPose2, alliance), - autoMirror(new Pose(75,30), alliance), - autoMirror(new Pose(80,100), alliance), - autoMirror(shootingPose1, alliance) + farPreload, + new Pose(75,30), + new Pose(80,100), + nearShoot )) - .setLinearHeadingInterpolation(preloadPose2.getHeading(), shootingPose1.getHeading()) + .setLinearHeadingInterpolation(midPreload.getHeading(), nearShoot.getHeading()) .build(); - preload3_to_shooting2 = follower.pathBuilder() + farPreload_to_midShoot = follower.pathBuilder() .addPath(new BezierCurve( - autoMirror(preloadPose2, alliance), - autoMirror(new Pose(85,32), alliance), - autoMirror(shootingPose2, alliance) + farPreload, + new Pose(85,32), + midShoot )) - .setLinearHeadingInterpolation(preloadPose2.getHeading(), shootingPose2.getHeading()) + .setLinearHeadingInterpolation(midPreload.getHeading(), midShoot.getHeading()) .build(); - preload3_to_shooting3 = follower.pathBuilder() - .addPath(new BezierCurve( - autoMirror(preloadPose2, alliance), - autoMirror(shootingPose3, alliance) + farPreload_to_farShoot = follower.pathBuilder() + .addPath(new BezierLine( + farPreload, + farShoot )) - .setLinearHeadingInterpolation(preloadPose2.getHeading(), shootingPose3.getHeading()) + .setLinearHeadingInterpolation(midPreload.getHeading(), farShoot.getHeading()) .build(); } - public void buildShooting1ToPreloads(LoadHardwareClass.Alliance alliance){ - shooting1_to_preload1 = follower.pathBuilder() + public void buildShooting1ToPreloads(){ + nearShoot_to_nearPreload = follower.pathBuilder() .addPath(new BezierCurve( - autoMirror(shootingPose1, alliance), - autoMirror(new Pose(60, 80), alliance), - autoMirror(preloadPose1, alliance) + nearShoot, + new Pose(60, 80), + nearPreload )) - .setLinearHeadingInterpolation(shootingPose1.getHeading(), preloadPose1.getHeading()) + .setLinearHeadingInterpolation(nearShoot.getHeading(), nearPreload.getHeading()) .build(); - shooting1_to_preload2 = follower.pathBuilder() + nearShoot_to_midPreload = follower.pathBuilder() .addPath(new BezierCurve( - autoMirror(shootingPose1, alliance), - autoMirror(new Pose(60, 55), alliance), - autoMirror(preloadPose2, alliance) + nearShoot, + new Pose(60, 55), + midPreload )) - .setLinearHeadingInterpolation(shootingPose1.getHeading(), preloadPose2.getHeading()) + .setLinearHeadingInterpolation(nearShoot.getHeading(), midPreload.getHeading()) .build(); - shooting1_to_preload3 = follower.pathBuilder() + nearShoot_to_farPreload = follower.pathBuilder() .addPath(new BezierCurve( - autoMirror(shootingPose1, alliance), - autoMirror(new Pose(60, 27), alliance), - autoMirror(preloadPose3, alliance) + nearShoot, + new Pose(60, 27), + farPreload )) - .setLinearHeadingInterpolation(shootingPose1.getHeading(), preloadPose3.getHeading()) + .setLinearHeadingInterpolation(nearShoot.getHeading(), farPreload.getHeading()) .build(); } - public void buildShooting2ToPreloads(LoadHardwareClass.Alliance alliance){ - shooting2_to_preload1 = follower.pathBuilder() - .addPath(new BezierCurve( - autoMirror(shootingPose2, alliance), - autoMirror(preloadPose1, alliance) + public void buildShooting2ToPreloads(){ + midShoot_to_nearPreload = follower.pathBuilder() + .addPath(new BezierLine( + midShoot, + nearPreload )) - .setLinearHeadingInterpolation(shootingPose2.getHeading(), preloadPose1.getHeading()) + .setLinearHeadingInterpolation(midShoot.getHeading(), nearPreload.getHeading()) .build(); - shooting2_to_preload2 = follower.pathBuilder() + midShoot_to_midPreload = follower.pathBuilder() .addPath(new BezierCurve( - autoMirror(shootingPose2, alliance), - autoMirror(new Pose(75, 56), alliance), - autoMirror(preloadPose2, alliance) + midShoot, + new Pose(75, 56), + midPreload )) - .setLinearHeadingInterpolation(shootingPose2.getHeading(), preloadPose2.getHeading()) + .setLinearHeadingInterpolation(midShoot.getHeading(), midPreload.getHeading()) .build(); - shooting2_to_preload3 = follower.pathBuilder() + midShoot_to_farPreload = follower.pathBuilder() .addPath(new BezierCurve( - autoMirror(shootingPose2, alliance), - autoMirror(new Pose(68, 30), alliance), - autoMirror(preloadPose3, alliance) + midShoot, + new Pose(68, 30), + farPreload )) - .setLinearHeadingInterpolation(shootingPose2.getHeading(), preloadPose3.getHeading()) + .setLinearHeadingInterpolation(midShoot.getHeading(), farPreload.getHeading()) .build(); } - public void buildShooting3ToPreloads(LoadHardwareClass.Alliance alliance){ - shooting3_to_preload1 = follower.pathBuilder() + public void buildShooting3ToPreloads(){ + farShoot_to_nearPreload = follower.pathBuilder() .addPath(new BezierCurve( - autoMirror(shootingPose3, alliance), - autoMirror(new Pose(73, 88), alliance), - autoMirror(preloadPose1, alliance) + farShoot, + new Pose(73, 88), + nearPreload )) - .setLinearHeadingInterpolation(shootingPose3.getHeading(), preloadPose1.getHeading()) + .setLinearHeadingInterpolation(farShoot.getHeading(), nearPreload.getHeading()) .build(); - shooting3_to_preload2 = follower.pathBuilder() + farShoot_to_midPreload = follower.pathBuilder() .addPath(new BezierCurve( - autoMirror(shootingPose3, alliance), - autoMirror(new Pose(78, 62), alliance), - autoMirror(preloadPose2, alliance) + farShoot, + new Pose(78, 62), + midPreload )) - .setLinearHeadingInterpolation(shootingPose3.getHeading(), preloadPose2.getHeading()) + .setLinearHeadingInterpolation(farShoot.getHeading(), midPreload.getHeading()) .build(); - shooting3_to_preload3 = follower.pathBuilder() + farShoot_to_farPreload = follower.pathBuilder() .addPath(new BezierCurve( - autoMirror(shootingPose3, alliance), - autoMirror(new Pose(82.5, 35), alliance), - autoMirror(preloadPose3, alliance) + farShoot, + new Pose(82.5, 35), + farPreload )) - .setLinearHeadingInterpolation(shootingPose3.getHeading(), preloadPose3.getHeading()) + .setLinearHeadingInterpolation(farShoot.getHeading(), farPreload.getHeading()) .build(); } - public void buildShooting1ToLeaves(LoadHardwareClass.Alliance alliance){ - shooting1_to_leave1 = follower.pathBuilder() + public void buildShooting2NoTurretToPreloads(){ + midShoot_noTurret_to_nearPreload = follower.pathBuilder() + .addPath(new BezierLine( + noTurretMidShoot, + nearPreload + )) + .setLinearHeadingInterpolation(noTurretMidShoot.getHeading(), nearPreload.getHeading()) + .build(); + midShoot_noTurret_to_midPreload = follower.pathBuilder() .addPath(new BezierCurve( - autoMirror(shootingPose1, alliance), - autoMirror(leavePose1, alliance) + noTurretMidShoot, + new Pose(75, 56), + midPreload )) - .setConstantHeadingInterpolation(shootingPose1.getHeading()) + .setLinearHeadingInterpolation(noTurretMidShoot.getHeading(), midPreload.getHeading()) .build(); - shooting1_to_leave2 = follower.pathBuilder() + midShoot_noTurret_to_farPreload = follower.pathBuilder() .addPath(new BezierCurve( - autoMirror(shootingPose1, alliance), - autoMirror(leavePose2, alliance) + noTurretMidShoot, + new Pose(68, 30), + farPreload )) - .setConstantHeadingInterpolation(shootingPose1.getHeading()) + .setLinearHeadingInterpolation(noTurretMidShoot.getHeading(), farPreload.getHeading()) .build(); } - public void buildShooting2ToLeaves(LoadHardwareClass.Alliance alliance){ - shooting2_to_leave1 = follower.pathBuilder() + public void buildShooting3NoTurretToPreloads(){ + farShoot_noTurret_to_nearPreload = follower.pathBuilder() .addPath(new BezierCurve( - autoMirror(shootingPose2, alliance), - autoMirror(leavePose1, alliance) + noTurretFarShoot, + new Pose(73, 88), + nearPreload )) - .setConstantHeadingInterpolation(shootingPose2.getHeading()) + .setLinearHeadingInterpolation(noTurretFarShoot.getHeading(), nearPreload.getHeading()) .build(); - shooting2_to_leave2 = follower.pathBuilder() + farShoot_noTurret_to_midPreload = follower.pathBuilder() .addPath(new BezierCurve( - autoMirror(shootingPose2, alliance), - autoMirror(leavePose2, alliance) + noTurretFarShoot, + new Pose(78, 62), + midPreload )) - .setConstantHeadingInterpolation(shootingPose2.getHeading()) + .setLinearHeadingInterpolation(noTurretFarShoot.getHeading(), midPreload.getHeading()) .build(); - } - public void buildShooting3ToLeaves(LoadHardwareClass.Alliance alliance){ - shooting3_to_leave2 = follower.pathBuilder() + farShoot_noTurret_to_farPreload = follower.pathBuilder() .addPath(new BezierCurve( - autoMirror(shootingPose3, alliance), - autoMirror(leavePose2, alliance) + noTurretFarShoot, + new Pose(82.5, 35), + farPreload )) - .setConstantHeadingInterpolation(shootingPose3.getHeading()) + .setLinearHeadingInterpolation(noTurretFarShoot.getHeading(), farPreload.getHeading()) .build(); - shooting3_to_leave3 = follower.pathBuilder() - .addPath(new BezierCurve( - autoMirror(shootingPose3, alliance), - autoMirror(leavePose3, alliance) + } + public void buildShooting1ToLeaves(){ + nearShoot_to_nearLeave = follower.pathBuilder() + .addPath(new BezierLine( + nearShoot, + nearLeave + )) + .setConstantHeadingInterpolation(nearShoot.getHeading()) + .build(); + nearShoot_to_midLeave = follower.pathBuilder() + .addPath(new BezierLine( + nearShoot, + midLeave )) - .setConstantHeadingInterpolation(shootingPose3.getHeading()) + .setConstantHeadingInterpolation(nearShoot.getHeading()) .build(); } - public void buildStart1ToLeaves(LoadHardwareClass.Alliance alliance){ - start1_to_leave1 = follower.pathBuilder() - .addPath(new BezierCurve( - autoMirror(startPose1, alliance), - autoMirror(leavePose1, alliance) + public void buildShooting2ToLeaves(){ + midShoot_to_nearLeave = follower.pathBuilder() + .addPath(new BezierLine( + midShoot, + nearLeave )) - .setConstantHeadingInterpolation(startPose1.getHeading()) + .setConstantHeadingInterpolation(midShoot.getHeading()) .build(); - start1_to_leave2 = follower.pathBuilder() - .addPath(new BezierCurve( - autoMirror(startPose1, alliance), - autoMirror(leavePose3, alliance) + midShoot_to_midLeave = follower.pathBuilder() + .addPath(new BezierLine( + midShoot, + midLeave )) - .setConstantHeadingInterpolation(startPose1.getHeading()) + .setConstantHeadingInterpolation(midShoot.getHeading()) .build(); } - public void buildStart2ToLeaves(LoadHardwareClass.Alliance alliance){ - start2_to_leave2 = follower.pathBuilder() - .addPath(new BezierCurve( - autoMirror(startPose2, alliance), - autoMirror(leavePose1, alliance) + public void buildShooting3ToLeaves(){ + farShoot_to_midLeave = follower.pathBuilder() + .addPath(new BezierLine( + farShoot, + midLeave )) - .setConstantHeadingInterpolation(startPose2.getHeading()) + .setConstantHeadingInterpolation(farShoot.getHeading()) .build(); - start2_to_leave3 = follower.pathBuilder() - .addPath(new BezierCurve( - autoMirror(startPose2, alliance), - autoMirror(leavePose3, alliance) + farShoot_to_farLeave = follower.pathBuilder() + .addPath(new BezierLine( + farShoot, + farLeave + )) + .setConstantHeadingInterpolation(farShoot.getHeading()) + .build(); + } + public void buildStart1ToLeaves(){ + nearStart_to_nearLeave = follower.pathBuilder() + .addPath(new BezierLine( + nearStart, + nearLeave + )) + .setConstantHeadingInterpolation(nearStart.getHeading()) + .build(); + nearStart_to_midLeave = follower.pathBuilder() + .addPath(new BezierLine( + nearStart, + farLeave + )) + .setConstantHeadingInterpolation(nearStart.getHeading()) + .build(); + } + public void buildStart2ToLeaves(){ + farStart_to_midLeave = follower.pathBuilder() + .addPath(new BezierLine( + farStart, + nearLeave + )) + .setConstantHeadingInterpolation(farStart.getHeading()) + .build(); + farStart_to_farLeave = follower.pathBuilder() + .addPath(new BezierLine( + farStart, + farLeave )) - .setConstantHeadingInterpolation(startPose2.getHeading()) + .setConstantHeadingInterpolation(farStart.getHeading()) + .build(); + } + public void buildStartsToNoTurretShoots(){ + nearStart_to_NoTurret_MidShoot = follower.pathBuilder() + .addPath(new BezierLine( + nearStart, + noTurretMidShoot + )) + .setLinearHeadingInterpolation(nearStart.getHeading(), noTurretMidShoot.getHeading()) + .build(); + farStart_to_NoTurret_FarShoot = follower.pathBuilder() + .addPath(new BezierLine( + farStart, + noTurretFarShoot + )) + .setLinearHeadingInterpolation(farStart.getHeading(), noTurretFarShoot.getHeading()) .build(); } + /** * Builds all the paths, mirroring them to the other side of the field if necessary */ - public void buildPaths(LoadHardwareClass.Alliance alliance){ + public void buildPaths(LoadHardwareClass.Alliance Alliance, Follower follow){ + follower = follow; + + nearStart = autoMirror(nearStart, Alliance); + farStart = autoMirror(farStart, Alliance); + nearPreload = autoMirror(nearPreload, Alliance); + midPreload = autoMirror(midPreload, Alliance); + farPreload = autoMirror(farPreload, Alliance); + nearShoot = autoMirror(nearShoot, Alliance); + midShoot = autoMirror(midShoot, Alliance); + farShoot = autoMirror(farShoot, Alliance); + noTurretMidShoot = autoMirror(noTurretMidShoot, Alliance); + noTurretFarShoot = autoMirror(noTurretFarShoot, Alliance); + nearLeave = autoMirror(nearLeave, Alliance); + midLeave = autoMirror(midLeave, Alliance); + farLeave = autoMirror(farLeave, Alliance); + + /// All paths are for the RED side of the field. they will be mirrored if necessary. - buildMoveRPPath(); // Paths going from each start position to each of the preloads. - buildStart1ToPreloads(alliance); - buildStart2ToPreloads(alliance); + buildStart1ToPreloads(); + buildStart2ToPreloads(); + // Paths going from each start position to each of the shooting positions. + buildStart1ToShootings(); // Paths going from each preload to each shooting position - buildPreload1ToShootings(alliance); - buildPreload2ToShootings(alliance); - buildPreload3ToShootings(alliance); + buildPreload1ToShootings(); + buildPreload2ToShootings(); + buildPreload3ToShootings(); // Paths going from each shooting position to each preload - buildShooting1ToPreloads(alliance); - buildShooting2ToPreloads(alliance); - buildShooting3ToPreloads(alliance); + buildShooting1ToPreloads(); + buildShooting2ToPreloads(); + buildShooting3ToPreloads(); + // Paths going from each no-turret shooting position to each preload + buildShooting2NoTurretToPreloads(); + buildShooting3NoTurretToPreloads(); // Paths going from each shooting position to the leave positions. - buildShooting1ToLeaves(alliance); - buildShooting2ToLeaves(alliance); - buildShooting3ToLeaves(alliance); + buildShooting1ToLeaves(); + buildShooting2ToLeaves(); + buildShooting3ToLeaves(); // Paths going from the start positions to the leave positions - buildStart1ToLeaves(alliance); - buildStart2ToLeaves(alliance); - } - - /** - * Must be called after MecanumDrivetrainClass is initialized. - * @param follow PedroPathing's follower object, gotten from MecanumDrivetrainClass - */ - public Pedro_Paths(Follower follow){ - follower = follow; + buildStart1ToLeaves(); + buildStart2ToLeaves(); + // Paths going from the start positions to the no-turret shooting positions + buildStartsToNoTurretShoots(); } } diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Hardware_/LoadHardwareClass.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Hardware_/LoadHardwareClass.java index b0943a033cc5..2e023e363459 100644 --- a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Hardware_/LoadHardwareClass.java +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Hardware_/LoadHardwareClass.java @@ -22,7 +22,7 @@ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * CAUSED AND NEAR ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ @@ -33,6 +33,8 @@ import com.pedropathing.follower.Follower; import com.pedropathing.geometry.Pose; import com.qualcomm.robotcore.eventloop.opmode.OpMode; +import com.skeletonarmy.marrow.zones.Point; +import com.skeletonarmy.marrow.zones.PolygonZone; import org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_.Actuators_.Intake; import org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_.Actuators_.Turret; @@ -55,11 +57,24 @@ public class LoadHardwareClass { // Declare various enums & other variables that are useful across files public static Alliance selectedAlliance = null; + public Pose goalPose = new Pose(144, 144); public enum Alliance { RED, BLUE } + // Declare zones on the field for logic purposes + public static PolygonZone FarLaunchZone = new PolygonZone( + new Point(96,0), + new Point(48, 0), + new Point(72, 24) + ); + public static PolygonZone ReallyNearLaunchZoneRed = new PolygonZone( + new Point(72, 72), + new Point(144, 144), + new Point(0, 144) + ); + /** * Copied from LinearOpMode. * @param milliseconds The number of milliseconds to sleep. @@ -90,7 +105,7 @@ public LoadHardwareClass(OpMode opmode) { public void init(Pose initialPose) { // Initialize all subclasses drivetrain.init(myOpMode, initialPose); - turret.init(myOpMode); + turret.init(myOpMode, this); intake.init(myOpMode); // Misc telemetry @@ -105,7 +120,7 @@ public void init(Pose initialPose) { public void init(Pose initialPose, Follower follower) { // Initialize all subclasses drivetrain.init(myOpMode, initialPose, follower); - turret.init(myOpMode); + turret.init(myOpMode, this); intake.init(myOpMode); // Misc telemetry diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Teleop_/Teleop_Main_.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Teleop_/Teleop_Main_.java index c886ce94dec0..7f80337d4b8f 100644 --- a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Teleop_/Teleop_Main_.java +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Teleop_/Teleop_Main_.java @@ -22,13 +22,15 @@ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * CAUSED AND NEAR ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.firstinspires.ftc.teamcode.LOADCode.Main_.Teleop_; +import static org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_.LoadHardwareClass.selectedAlliance; + import com.bylazar.configurables.annotations.Configurable; import com.bylazar.telemetry.PanelsTelemetry; import com.bylazar.telemetry.TelemetryManager; @@ -41,97 +43,166 @@ import com.skeletonarmy.marrow.prompts.Prompter; import org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_.Actuators_.Intake; +import org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_.Actuators_.Intake.intakeMode; +import org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_.Actuators_.Intake.transferState; import org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_.Actuators_.Turret; +import org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_.Actuators_.Turret.flywheelState; +import org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_.Actuators_.Turret.gatestate; +import org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_.Drivetrain_.MecanumDrivetrainClass; +import org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_.Drivetrain_.Pedro_Paths; import org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_.LoadHardwareClass; +// FIRST COMMENT FROM MY NEW COMPUTER =D +// - ARI + @Configurable @TeleOp(name="Teleop_Main_", group="TeleOp") public class Teleop_Main_ extends LinearOpMode { // Declare OpMode members. - private ElapsedTime runtime = new ElapsedTime(); - private TelemetryManager panelsTelemetry = PanelsTelemetry.INSTANCE.getTelemetry(); + private final ElapsedTime runtime = new ElapsedTime(); + private final TelemetryManager panelsTelemetry = PanelsTelemetry.INSTANCE.getTelemetry(); // Variables for storing data for the second gamepad controls public static double DylanStickDeadzones = 0.2; + public int shootingState = 0; - public TimerEx shootingDelay = new TimerEx(1); + public boolean turretOn = true; + public TimerEx stateTimer = new TimerEx(1); + public double hoodOffset = 0; + // Create a new instance of our Robot class + LoadHardwareClass Robot = new LoadHardwareClass(this); + // Create a new Paths instance + Pedro_Paths Paths = new Pedro_Paths(); + // Create a new instance of Prompter for selecting the alliance Prompter prompter = null; + enum startPoses { + FAR, + NEAR + } // Contains the start Pose of our robot. This can be changed or saved from the autonomous period. - private final Pose startPose = new Pose(135.6,9.8, Math.toRadians(90)); - - // Create a new instance of our Robot class - LoadHardwareClass Robot = new LoadHardwareClass(this); + private Pose startPose = Paths.farStart; @Override public void runOpMode() { - telemetry.addData("Status", "Initialized"); - telemetry.update(); - // Create a new prompter for selecting alliance prompter = new Prompter(this); - prompter.prompt("alliance", new OptionPrompt<>("Select Alliance", LoadHardwareClass.Alliance.RED, LoadHardwareClass.Alliance.BLUE)); + prompter.prompt("alliance", () -> { + if (selectedAlliance == null){ + return new OptionPrompt<>("Select Alliance", LoadHardwareClass.Alliance.RED, LoadHardwareClass.Alliance.BLUE); + }else{ + return null; + } + }); + prompter.prompt("startPose", () -> { + if (MecanumDrivetrainClass.robotPose == null){ + return new OptionPrompt<>("Select Start Pose", + startPoses.FAR, + startPoses.NEAR + ); + }else{ + startPose = MecanumDrivetrainClass.robotPose; + return null; + } + }); prompter.onComplete(() -> { - LoadHardwareClass.selectedAlliance = prompter.get("alliance"); - telemetry.addData("Selection", "Complete"); + if (selectedAlliance == null){ + selectedAlliance = prompter.get("alliance"); + } + telemetry.addData("Selection", "Complete"); + telemetry.addData("Alliance", selectedAlliance); + if (MecanumDrivetrainClass.robotPose == null){ + startPoses pose = prompter.get("startPose"); + if (pose.equals(startPoses.FAR)) { + startPose = Paths.autoMirror(Paths.farStart, selectedAlliance); + telemetry.addData("Start Pose", "Far Start Pose"); + } else if (pose.equals(startPoses.NEAR)) { + startPose = Paths.autoMirror(Paths.nearStart, selectedAlliance); + telemetry.addData("Start Pose", "Near Start Pose"); } - ); + }else{ + startPose = MecanumDrivetrainClass.robotPose; + telemetry.addData("Start Pose", "Ending Pose of Auto"); + } + telemetry.update(); + }); // Runs repeatedly while in init - while (opModeInInit()){ - // If an auto was not run, run the prompter to select the correct alliance - if (LoadHardwareClass.selectedAlliance == null){ - prompter.run(); - } + while (opModeInInit()) { + prompter.run(); } // Wait for the game to start (driver presses START) waitForStart(); - runtime.reset(); - // Initialize all hardware of the robot Robot.init(startPose); + runtime.reset(); + Paths.buildPaths(selectedAlliance, Robot.drivetrain.follower); + Robot.drivetrain.startTeleOpDrive(); // run until the end of the match (driver presses STOP) while (opModeIsActive()) { + if (!Turret.zeroed){ + while (!isStopRequested() && Robot.turret.zeroTurret()){ + sleep(0); + } + } Gamepad1(); - Gamepad2Dec6(); + Gamepad2(); Robot.turret.updatePIDs(); + double flywheelPercentage = (int) Math.round(Robot.turret.getFlywheelRPM()/Robot.turret.getFlywheelCurrentMaxSpeed() *100); + telemetry.addData("Flywheel Percentage", flywheelPercentage+"%"); + panelsTelemetry.addData("Flywheel Percentage", flywheelPercentage+"%"); + telemetry.addData("SpeedMult", Robot.drivetrain.speedMultiplier); + telemetry.addLine(); + //positional telemetry + telemetry.addData("X Position", Robot.drivetrain.follower.getPose().getX()); + telemetry.addData("Y Position", Robot.drivetrain.follower.getPose().getY()); + telemetry.addData("Heading", Math.toDegrees(Robot.drivetrain.follower.getPose().getHeading())); + telemetry.addData("Distance From Goal", Robot.drivetrain.distanceFromGoal()); + telemetry.addLine(); // Turret-related Telemetry - telemetry.addData("Turret Target Angle:", Robot.turret.rotation.target); + panelsTelemetry.addData("Turret Target Angle", Robot.turret.rotation.target); + panelsTelemetry.addData("Turret Actual Angle", Robot.turret.rotation.getAngleAbsolute()); + telemetry.addData("Turret Target Angle", Robot.turret.rotation.target); telemetry.addData("Turret Actual Angle", Robot.turret.rotation.getAngleAbsolute()); + telemetry.addData("Turret Hood Angle", Robot.turret.getHood()); telemetry.addLine(); panelsTelemetry.addData("Flywheel Target Speed", Robot.turret.flywheel.target); panelsTelemetry.addData("Flywheel Actual Speed", Robot.turret.getFlywheelRPM()); + panelsTelemetry.addData("Flywheel Power", Robot.turret.flywheel.getPower()); telemetry.addData("Flywheel Target Speed", Robot.turret.flywheel.target); telemetry.addData("Flywheel Actual Speed", Robot.turret.getFlywheelRPM()); + telemetry.addData("Flywheel State", Robot.turret.getFlywheelRPM()); + // Intake-related Telemetry telemetry.addLine(); - telemetry.addData("Belt Power", Robot.intake.belt.getPower()); - + telemetry.addData("Intake Mode", Robot.intake.getMode()); - // Intake-related Telemetry + // Color Sensor Telemetry telemetry.addLine(); - telemetry.addData("Intake Status", Robot.intake.intake.getPower()); + telemetry.addData("Upper Sensor", Robot.intake.getTopSensorState()); + telemetry.addData("Lower Sensor", Robot.intake.getBottomSensorState()); // System-related Telemetry telemetry.addLine(); - telemetry.addData("Status", "Run Time: " + runtime.toString()); - telemetry.addData("Version: ", "12/5/25"); + telemetry.addData("Status", "Run Time: " + runtime); + telemetry.addData("Version: ", "12/12/25"); telemetry.update(); - + panelsTelemetry.update(); } } + /** *

Gamepad 1 Controls (Ari's Pick V1)

* * */ - public void Gamepad1(){ - double speedmult = 0.66; - if (gamepad1.left_trigger >= 0.5) { - speedmult -= 0.33; + public void Gamepad1() { + + if (gamepad1.left_trigger >= 0.5 && gamepad1.right_trigger >= 0.5) { + Robot.drivetrain.speedMultiplier = 0.66; + } else if (gamepad1.left_trigger >= 0.5) { + Robot.drivetrain.speedMultiplier = 0.33; + } else if (gamepad1.right_trigger >= 0.5) { + Robot.drivetrain.speedMultiplier = 1; + } else { + Robot.drivetrain.speedMultiplier = 0.66; } - if (gamepad1.right_trigger >= 0.5){ - speedmult += 0.33; + + if (gamepad1.bWasPressed()){ + if (selectedAlliance == LoadHardwareClass.Alliance.RED){ + Robot.drivetrain.follower.setPose(new Pose(8, 8, Math.toRadians(90))); + }else if (selectedAlliance == LoadHardwareClass.Alliance.BLUE){ + Robot.drivetrain.follower.setPose(new Pose(136, 8, Math.toRadians(90))); + } } double turnMult = 2; - if (false){ // I specifically asked to not have this! - turnMult = 1; - } +// if (gamepad1.left_stick_y == 0 && gamepad1.left_stick_x == 0){ +// turnMult = 1; +// } Robot.drivetrain.pedroMecanumDrive( - gamepad1.left_stick_y*speedmult, - gamepad1.left_stick_x*speedmult, - gamepad1.right_stick_x*speedmult, + gamepad1.left_stick_y, + gamepad1.left_stick_x, + gamepad1.right_stick_x / turnMult, true ); } @@ -199,10 +281,10 @@ public void Gamepad1(){ *
  • Analog Inputs */ - public void Gamepad2(){ + public void Gamepad2() { - //Intake Controls (Left Stick Y) - if (Math.abs(gamepad2.left_stick_y) >= DylanStickDeadzones){ - if (gamepad2.left_stick_y > 0){ // OUT (Digital) - Robot.intake.setMode(Intake.Mode.REVERSING); - } else { // IN (Digital) - Robot.intake.setMode(Intake.Mode.INTAKING); - } - } else { // OFF - Robot.intake.setMode(Intake.Mode.OFF); + // Turret Aimbot + if (gamepad2.aWasPressed()){ + turretOn = !turretOn; } + Robot.turret.updateAimbot(turretOn, true, hoodOffset); - //Turret Angle Controls (Right Stick X) - //To be added after manual control is finished + //Intake Controls (Left Stick Y) + if (shootingState == 0) { + if (Math.abs(gamepad2.left_stick_y) >= DylanStickDeadzones && + Math.abs(gamepad2.right_stick_y) >= DylanStickDeadzones) { + Robot.intake.setMode(intakeMode.INTAKING); + }else if (Math.abs(gamepad2.left_stick_y) >= DylanStickDeadzones && + Math.abs(gamepad2.right_stick_y) < DylanStickDeadzones) { + Robot.intake.setMode(intakeMode.NO_BELT); + }else if (Math.abs(gamepad2.left_stick_y) < DylanStickDeadzones && + Math.abs(gamepad2.right_stick_y) >= DylanStickDeadzones) { + Robot.intake.setMode(intakeMode.SHOOTING); + }else if (gamepad2.back){ + Robot.intake.setMode(intakeMode.REVERSING); + }else{ // OFF + Robot.intake.setMode(intakeMode.OFF); + } - //Flywheel Toggle Control (Y Button) - if (gamepad2.yWasPressed()){ - if (Robot.turret.flywheelState == Turret.flywheelstate.OFF){ - Robot.turret.setFlywheel(Turret.flywheelstate.ON); - } else { - Robot.turret.setFlywheel(Turret.flywheelstate.OFF); + /* TODO Uncomment once autobelt control is finished + if (Math.abs(gamepad2.left_stick_y) >= DylanStickDeadzones) { + Robot.intake.setMode(intakeMode.INTAKING); + }else{ // OFF + Robot.intake.setMode(intakeMode.OFF); + } + */ + + //Flywheel Toggle Control (Y Button) + if (gamepad2.yWasPressed()) { + if (Robot.turret.flywheelMode == flywheelState.OFF) { + Robot.turret.setFlywheelState(flywheelState.ON); + } else { + Robot.turret.setFlywheelState(flywheelState.OFF); + } } } + Robot.turret.updateFlywheel(); + + // Hood Controls + if (gamepad2.dpadUpWasPressed()){ + hoodOffset += 10; + }else if (gamepad2.dpadDownWasPressed()){ + hoodOffset -= 10; + } + //Shoot (B Button Press) // Increment the shooting state - if (gamepad2.bWasPressed() && shootingState < 2 && Robot.turret.getFlywheelRPM() > 5900){shootingState++;} - switch (shootingState){ + if (gamepad2.bWasPressed() && shootingState < 1 && Robot.turret.getFlywheelRPM() > Robot.turret.getFlywheelCurrentMaxSpeed()-100) { + shootingState++; + } + switch (shootingState) { case 0: telemetry.addData("Shooting State", "OFF"); return; case 1: - Robot.intake.setMode(Intake.Mode.INTAKING); - Robot.turret.setGate(Turret.gatestate.OPEN); + if (Robot.intake.getMode() == intakeMode.OFF){ + stateTimer.restart(); + stateTimer.start(); + } + Robot.intake.setMode(intakeMode.INTAKING); + Robot.turret.setGateState(gatestate.OPEN); telemetry.addData("Shooting State", "STARTED"); + if (stateTimer.isDone() && Robot.intake.getTopSensorState() && !Robot.intake.getBottomSensorState()){ + shootingState++; + } return; case 2: - Robot.intake.setMode(Intake.Mode.SHOOTING); - Robot.intake.setTransfer(Intake.transferState.UP); - shootingDelay.restart(); - shootingState++; + if (Robot.intake.getMode() == intakeMode.INTAKING){ + stateTimer.restart(); + stateTimer.start(); + } + Robot.intake.setMode(Intake.intakeMode.SHOOTING); + Robot.intake.setTransfer(transferState.UP); telemetry.addData("Shooting State", "TRANSFERRED"); + if (stateTimer.isDone()) { + shootingState++; + } return; case 3: - if (shootingDelay.isDone()){shootingState++;} - telemetry.addData("Shooting State", "DELAY"); - return; - case 4: - Robot.turret.setFlywheelRPM(0); - Robot.turret.setGate(Turret.gatestate.CLOSED); - Robot.intake.setMode(Intake.Mode.OFF); - Robot.intake.setTransfer(Intake.transferState.DOWN); + Robot.turret.setFlywheelState(flywheelState.OFF); + Robot.turret.setGateState(gatestate.CLOSED); + Robot.intake.setMode(intakeMode.OFF); + Robot.intake.setTransfer(transferState.DOWN); telemetry.addData("Shooting State", "RESET"); shootingState = 0; } - - - } - - /** - *

    Gamepad 2 Controls (December 6th Scrimmage)

    - * - */ - public void Gamepad2Dec6(){ - - //Intake Controls (Left Stick Y) - if (Math.abs(gamepad2.left_stick_y) >= DylanStickDeadzones){ - Robot.intake.intake.setPower(gamepad2.left_stick_y); - } else { // OFF - Robot.intake.intake.setPower(0); - } - - //Transfer Belt (ABS Right Stick Y) - if (Math.abs(gamepad2.right_stick_y) >= DylanStickDeadzones) { - Robot.intake.belt.setPower(Math.abs(gamepad2.right_stick_y)); - } else { // OFF - Robot.intake.belt.setPower(0); - } - - // Gate control - if (Robot.turret.getFlywheelRPM() > 3000){ - Robot.turret.setGate(Turret.gatestate.OPEN); - }else{ - Robot.turret.setGate(Turret.gatestate.CLOSED); - } - - // Transfer control - if (gamepad2.b){ - Robot.intake.setTransfer(Intake.transferState.UP); - }else{ - Robot.intake.setTransfer(Intake.transferState.DOWN); - } - - //Flywheel Toggle Control (Y Button) - if (gamepad2.yWasPressed()){ - if (Robot.turret.flywheelState == Turret.flywheelstate.ON){ - Robot.turret.setFlywheel(Turret.flywheelstate.OFF); - }else if (Robot.turret.flywheelState == Turret.flywheelstate.OFF){ - Robot.turret.setFlywheel(Turret.flywheelstate.ON); - } - } - Robot.turret.updateFlywheel(); } -} +} \ No newline at end of file diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Teleop_/Teleop_Outreach_.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Teleop_/Teleop_Outreach_.java index ebf6e3cb7833..67b8e596a563 100644 --- a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Teleop_/Teleop_Outreach_.java +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Teleop_/Teleop_Outreach_.java @@ -22,7 +22,7 @@ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * CAUSED AND NEAR ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ @@ -38,8 +38,6 @@ import org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_.LoadHardwareClass; -//TODO, implement all our external libraries and functionality. - @TeleOp(name="Teleop_Outreach_", group="TeleOp") public class Teleop_Outreach_ extends LinearOpMode { @@ -48,7 +46,7 @@ public class Teleop_Outreach_ extends LinearOpMode { private TelemetryManager panelsTelemetry = PanelsTelemetry.INSTANCE.getTelemetry(); // Contains the start Pose of our robot. This can be changed or saved from the autonomous period. - private final Pose startPose = new Pose(135.6,9.8, Math.toRadians(90)); + private final Pose startPose = new Pose(88.5,7.8, Math.toRadians(90)); @Override public void runOpMode() { @@ -57,13 +55,15 @@ public void runOpMode() { // Create a new instance of our Robot class LoadHardwareClass Robot = new LoadHardwareClass(this); + // Initialize all hardware of the robot + Robot.init(startPose); + // TODO uncomment this once the function has been verified + //Robot.turret.zeroTurret(isStopRequested()); // Wait for the game to start (driver presses START) waitForStart(); runtime.reset(); - - // Initialize all hardware of the robot - Robot.init(startPose); + Robot.drivetrain.startTeleOpDrive(); // run until the end of the match (driver presses STOP) while (opModeIsActive()) { @@ -75,19 +75,18 @@ public void runOpMode() { gamepad1.right_stick_x/2, true ); - if (gamepad1.x){ - Robot.turret.setFlywheelRPM(5485.714285714286); - panelsTelemetry.addData("SetRPM", 5485.714285714286); - }else{ - Robot.turret.setFlywheelRPM(0); - panelsTelemetry.addData("SetRPM", 0); - } - - Robot.turret.updatePIDs(); - telemetry.addData("FlywheelState", Robot.turret.flywheelState); - panelsTelemetry.addData("FlywheelRPM", Robot.turret.getFlywheelRPM()); + if (gamepad1.dpad_up){ + Robot.turret.setHood(Robot.turret.getHood() + 2); + }else if (gamepad1.dpad_down){ + Robot.turret.setHood(Robot.turret.getHood() + 2); + } + telemetry.addData("Hood Angle", Robot.turret.getHood()); + telemetry.addLine(); + telemetry.addData("Robot Pose X", Math.round(Robot.drivetrain.follower.getPose().getX())); + telemetry.addData("Robot Pose Y", Math.round(Robot.drivetrain.follower.getPose().getY())); + telemetry.addData("Robot Pose H", Math.round(Math.toDegrees(Robot.drivetrain.follower.getPose().getHeading()))); // System-related Telemetry telemetry.addLine(); @@ -95,7 +94,6 @@ public void runOpMode() { telemetry.addData("Version: ", "11/4/25"); telemetry.update(); panelsTelemetry.update(telemetry); - //TODO, Add a more advanced telemetry handler for better organization, readability, and debugging } } } diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Teleop_/Teleop_Tuning_.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Teleop_/Teleop_Tuning_.java new file mode 100644 index 000000000000..ea765929be47 --- /dev/null +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Teleop_/Teleop_Tuning_.java @@ -0,0 +1,180 @@ +/* Copyright (c) 2017 FIRST. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted (subject to the limitations in the disclaimer below) provided that + * the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * Neither the name of FIRST nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS + * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND NEAR ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.firstinspires.ftc.teamcode.LOADCode.Main_.Teleop_; + +import com.bylazar.configurables.annotations.Configurable; +import com.bylazar.telemetry.PanelsTelemetry; +import com.bylazar.telemetry.TelemetryManager; +import com.pedropathing.geometry.Pose; +import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; +import com.qualcomm.robotcore.eventloop.opmode.TeleOp; +import com.qualcomm.robotcore.util.ElapsedTime; + +import org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_.Actuators_.Intake; +import org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_.Actuators_.Turret; +import org.firstinspires.ftc.teamcode.LOADCode.Main_.Hardware_.LoadHardwareClass; + +@Configurable +@TeleOp(name="Teleop_Tuning_", group="TeleOp") +public class Teleop_Tuning_ extends LinearOpMode { + + // Declare OpMode members. + private ElapsedTime runtime = new ElapsedTime(); + private ElapsedTime loopTimer = new ElapsedTime(); + private TelemetryManager panelsTelemetry = PanelsTelemetry.INSTANCE.getTelemetry(); + + // Panels variables + public static double hoodTargetPos = 0; + public static int turretTurnIncrement = 2; + int turretTurnMultiplier = 1; + int turretTurnAngle = 0; + + // Contains the start Pose of our robot. This can be changed or saved from the autonomous period. + private final Pose startPose = new Pose(88.5,7.8, Math.toRadians(90)); + + @Override + public void runOpMode() { + + // Create a new instance of our Robot class + LoadHardwareClass Robot = new LoadHardwareClass(this); + // Initialize all hardware of the robot + Robot.init(startPose); + + while (opModeInInit() && Robot.turret.zeroTurret()){ + sleep(0); + } + telemetry.addData("Status", "Initialized"); + telemetry.update(); + + // Wait for the game to start (driver presses START) + waitForStart(); + runtime.reset(); + + // Begin TeleOp driving + Robot.drivetrain.startTeleOpDrive(); + Robot.turret.setGateState(Turret.gatestate.OPEN); + + // run until the end of the match (driver presses STOP) + while (opModeIsActive()) { + + // Pass the joystick positions to our mecanum drive controller + Robot.drivetrain.pedroMecanumDrive( + gamepad1.left_stick_y/2, + gamepad1.left_stick_x/2, + gamepad1.right_stick_x/2, + true + ); + + // Color sensor telemetry + telemetry.addLine("COLOR SENSOR DATA"); + telemetry.addData("Color Sensor Threshold", Intake.proximitySensorThreshold); + telemetry.addData("Upper Sensor Array Triggered", Robot.intake.getTopSensorState()); + telemetry.addData("Lower Sensor Array Triggered", Robot.intake.getBottomSensorState()); + + // Controls for turret rotation testing + if (gamepad1.x){ + Robot.turret.rotation.setAngle(turretTurnAngle); + turretTurnAngle += turretTurnIncrement * turretTurnMultiplier; + if (turretTurnAngle > 360){ + turretTurnAngle = 360; + turretTurnMultiplier *= -1; + }else if (turretTurnAngle < 0){ + turretTurnAngle = 0; + turretTurnMultiplier *= -1; + } + + }else if (gamepad1.b){ + Robot.turret.updateAimbotWithVelocity(); + }else if (gamepad1.a){ + Robot.turret.rotation.setAngle(0); + }else{ + Robot.turret.rotation.setAngle(90); + } + Robot.turret.updatePIDs(); + telemetry.addLine(); + telemetry.addLine("TURRET DATA"); + telemetry.addData("Turret Target Angle", Robot.turret.rotation.target); + telemetry.addData("Turret Actual Angle", Robot.turret.rotation.getAngleAbsolute()); + telemetry.addData("Hall Effect Detected", Robot.turret.hall.getTriggered()); + panelsTelemetry.addData("Turret Target Angle", Robot.turret.rotation.target); + panelsTelemetry.addData("Turret Actual Angle", Robot.turret.rotation.getAngleAbsolute()); + + + // Controls for hood testing +// if (gamepad1.dpad_up){ +// Robot.turret.setHood(Robot.turret.getHood() + 2); +// }else if (gamepad1.dpad_down){ +// Robot.turret.setHood(Robot.turret.getHood() - 2); +// }else if (gamepad1.dpadLeftWasPressed()){ +// Robot.turret.setHood(hoodTargetPos); +// } + telemetry.addLine(); + telemetry.addLine("HOOD DATA"); + telemetry.addData("Hood Angle", Robot.turret.getHood()); + + // Controls for flywheel testing + if (gamepad1.backWasPressed()){ + if (Robot.turret.flywheelMode == Turret.flywheelState.OFF){ + Robot.turret.setFlywheelState(Turret.flywheelState.ON); + }else{ + Robot.turret.setFlywheelState(Turret.flywheelState.OFF); + } + } + Robot.turret.updateFlywheel(); + telemetry.addLine(); + telemetry.addLine("FLYWHEEL DATA"); + telemetry.addData("Flywheel Target Velocity", Robot.turret.flywheel.target); + telemetry.addData("Flywheel Actual Velocity", Robot.turret.getFlywheelRPM()); + telemetry.addData("Flywheel Motor Power", Robot.turret.flywheel.getPower()); + panelsTelemetry.addData("Flywheel Target Velocity", Robot.turret.flywheel.target); + panelsTelemetry.addData("Flywheel Actual Velocity", Robot.turret.getFlywheelRPM()); + panelsTelemetry.addData("Flywheel Motor Power", Robot.turret.flywheel.getPower()); + + if (gamepad1.aWasPressed()){ + if (Robot.intake.getMode() == Intake.intakeMode.INTAKING){ + Robot.intake.setMode(Intake.intakeMode.OFF); + }else{ + Robot.intake.setMode(Intake.intakeMode.INTAKING); + } + } + + // System-related Telemetry + telemetry.addLine(); + telemetry.addLine("SYSTEM DATA"); + telemetry.addData("Status", "Run Time: " + runtime.toString()); + telemetry.addData("Loop Time", loopTimer.toString()); + panelsTelemetry.addData("Loop Time", loopTimer.toString()); + telemetry.addData("Version: ", "12/26/25"); + telemetry.update(); + panelsTelemetry.update(); + loopTimer.reset(); + } + } +} diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Utils_.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Utils_.java new file mode 100644 index 000000000000..14c2b3a1f60b --- /dev/null +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Main_/Utils_.java @@ -0,0 +1,232 @@ +package org.firstinspires.ftc.teamcode.LOADCode.Main_; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + + +public class Utils_ { + + /** + * Performs spline interpolation given a set of control points. + */ + @SuppressWarnings("all") + public static class InterpLUT { + + private List mX = new ArrayList<>(); + private List mY = new ArrayList<>(); + private List mM = new ArrayList<>(); + + private InterpLUT(List x, List y, List m) { + mX = x; + mY = y; + mM = m; + } + + public InterpLUT() { + } + + /** + * Adds a control point to the LUT + */ + public void add(double input, double output) { + mX.add(input); + mY.add(output); + } + + /** + * Creates a monotone cubic spline from a given set of control points. + * + *

    + * The spline is guaranteed to pass through each control point exactly. Moreover, assuming the control points are + * monotonic (Y is non-decreasing or non-increasing) then the interpolated values will also be monotonic. + * + * @throws IllegalArgumentException if the X or Y arrays are null, have different lengths or have fewer than 2 values. + */ + //public static LUTWithInterpolator createLUT(List x, List y) { + public void createLUT() { + List x = this.mX; + List y = this.mY; + + if (x == null || y == null || x.size() != y.size() || x.size() < 2) { + throw new IllegalArgumentException("There must be at least two control " + + "points and the arrays must be of equal length."); + } + + final int n = x.size(); + Double[] d = new Double[n - 1]; // could optimize this out + Double[] m = new Double[n]; + + // Compute slopes of secant lines between successive points. + for (int i = 0; i < n - 1; i++) { + Double h = x.get(i + 1) - x.get(i); + if (h <= 0f) { + throw new IllegalArgumentException("The control points must all " + + "have strictly increasing X values."); + } + d[i] = (y.get(i + 1) - y.get(i)) / h; + } + + // Initialize the tangents as the average of the secants. + m[0] = d[0]; + for (int i = 1; i < n - 1; i++) { + m[i] = (d[i - 1] + d[i]) * 0.5f; + } + m[n - 1] = d[n - 2]; + + // Update the tangents to preserve monotonicity. + for (int i = 0; i < n - 1; i++) { + if (d[i] == 0f) { // successive Y values are equal + m[i] = Double.valueOf(0f); + m[i + 1] = Double.valueOf(0f); + } else { + double a = m[i] / d[i]; + double b = m[i + 1] / d[i]; + double h = Math.hypot(a, b); + if (h > 9f) { + double t = 3f / h; + m[i] = t * a * d[i]; + m[i + 1] = t * b * d[i]; + } + } + } + mX = x; + mY = y; + mM = Arrays.asList(m); + } + + /** + * Interpolates the value of Y = f(X) for given X. Clamps X to the domain of the spline. + * + * @param input The X value. + * @return The interpolated Y = f(X) value. + */ + public double get(double input) { + // Handle the boundary cases. + final int n = mX.size(); + if (Double.isNaN(input)) { + return input; + } + if (input <= mX.get(0)) { + throw new IllegalArgumentException("User requested value outside of bounds of LUT. Bounds are: " + mX.get(0).toString() + " to " + mX.get(n - 1).toString() + ". Value provided was: " + input); + } + if (input >= mX.get(n - 1)) { + throw new IllegalArgumentException("User requested value outside of bounds of LUT. Bounds are: " + mX.get(0).toString() + " to " + mX.get(n - 1).toString() + ". Value provided was: " + input); + } + + // Find the index 'i' of the last point with smaller X. + // We know this will be within the spline due to the boundary tests. + int i = 0; + while (input >= mX.get(i + 1)) { + i += 1; + if (input == mX.get(i)) { + return mY.get(i); + } + } + + // Perform cubic Hermite spline interpolation. + double h = mX.get(i + 1) - mX.get(i); + double t = (input - mX.get(i)) / h; + return (mY.get(i) * (1 + 2 * t) + h * mM.get(i) * t) * (1 - t) * (1 - t) + + (mY.get(i + 1) * (3 - 2 * t) + h * mM.get(i + 1) * (t - 1)) * t * t; + } + + // For debugging. + @Override + public String toString() { + StringBuilder str = new StringBuilder(); + final int n = mX.size(); + str.append("["); + for (int i = 0; i < n; i++) { + if (i != 0) { + str.append(", "); + } + str.append("(").append(mX.get(i)); + str.append(", ").append(mY.get(i)); + str.append(": ").append(mM.get(i)).append(")"); + } + str.append("]"); + return str.toString(); + } + + } + + public class InterpLUTPoint{ + public InterpLUTPoint( + double X, + double Y, + String Name + ){ + + } + } + + public enum telemetrySortCategory { + DRIVETRAIN, + INTAKE, + TRANSFER, + TURRET, + TESTINGVALUES, + OTHER, + } + + /** + * This class is for the runtime per-loop sorting and displaying of every single TelemetryObject. + * There should only be one of these per program. + */ + public class TelemetryManager{ + List telemtryList = new ArrayList<>(); + List sortedTelemetryList = new ArrayList<>(); + + public void addLine(TelemetryObject telemetryline){ + telemtryList.add(telemetryline); + } + + public void updateTelemetry(){ + //sortedTelemetryList = telemtryList.sort(Comparator.comparing(TelemetryObject::getName)); + //for(TelemetryObject telemObj : telemtryList){ + + //} + } + } + + /** + * This class represents a single line of telemetry, which will be sorted and managed by a TelemetryManager. + * It will contain all the relevant data for lookup and sorting, as well as functionality for live runtime editing of keys and values + */ + public class TelemetryObject{ + + Object name; + Object key; + Object value; + telemetrySortCategory category; + + public TelemetryObject(Object initialName, + Object initialKey, + Object initialValue, + telemetrySortCategory telemCategory, + TelemetryManager telemetryManagerParent){ + name = initialName; + key = initialKey; + value = initialValue; + category = telemCategory; + } + + public Object getName(){ + return name; + } + public Object getKey(){ + return key; + } + public Object getValue(){ + return value; + } + public telemetrySortCategory getTelemCategory(){ + return category; + } + + } + + +} diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Notes and TODOs b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Notes and TODOs index 8dae2b096875..d5b4ec13edc1 100644 --- a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Notes and TODOs +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Notes and TODOs @@ -1,7 +1,5 @@ //TODO, implement all our external libraries and functionality. -//TODO, add 2 generic autos for preloads and shooting and stuff - //DONE, Implement controls graph to TeleOp //DONE, Consolidate the generic actuator functionalities folder into one file @@ -10,7 +8,7 @@ //TODO, Add a more advanced telemetry handler for better organization, readability, and debugging -//TODO, Add proper functionality for manual turret control to Turret.java +//DONE, Add proper functionality for manual turret control to Turret.java //DONE, Shoot mode state machine Off->On->LastShot->Delay->ResetActuators->Off etc. cycled with B button on Gamepad2 @@ -20,4 +18,18 @@ //DONE, Finish translating Dylan's December 6th controls into the program. -//TODO, Figure out how to use NextFTC's Pedropathing extension for auto. \ No newline at end of file +//DONE, Figure out how to use NextFTC's Pedropathing extension for auto. + + + +//1. TODO, test the auto paths used in the generic autos - ARI WORKING ON IT! +//2. TODO, create two generic autos for testing and competition +//3. TODO, test the turret rotation autoaim with shooting +//4. TODO, establish static (non-changing) flywheel speeds for near/far zones +//5. TODO, make hood autoaim using InterpLUT +//6. TODO, test turret hood and rotation autoaim together +//7. TODO, test turret autoaim with the generic autos - ADD THE TURRET CONTROL FUNCTIONALITY TO COMMANDS + +// TODO: create paths to pick up human player zone Artifacts and to open the gate + +//TODO Link the PID update to the flywheel state so it can float instead of break at zero power \ No newline at end of file diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Testing_/PedroDriveToAprilTag.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Testing_/PedroDriveToAprilTag.java index 8f3c7cb0f91c..e61e6cbb6d4d 100644 --- a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Testing_/PedroDriveToAprilTag.java +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Testing_/PedroDriveToAprilTag.java @@ -22,7 +22,7 @@ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * CAUSED AND NEAR ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Testing_/TurretAprilTagTracking.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Testing_/TurretAprilTagTracking.java index 0b39aaea7655..4bf67c5e0d6e 100644 --- a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Testing_/TurretAprilTagTracking.java +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/LOADCode/Testing_/TurretAprilTagTracking.java @@ -22,7 +22,7 @@ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * CAUSED AND NEAR ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/Prism/Color.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/Prism/Color.java new file mode 100644 index 000000000000..afcc91b81cc0 --- /dev/null +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/Prism/Color.java @@ -0,0 +1,56 @@ +/* MIT License + * Copyright (c) [2025] [Base 10 Assets, LLC] + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.firstinspires.ftc.teamcode.Prism; + +public class Color { + public int red; + public int green; + public int blue; + + public Color(int red, int green, int blue) + { + this.red = Math.min(red, 255); + this.green = Math.min(green, 255); + this.blue = Math.min(blue, 255); + } + + @Override + public String toString() + { + return String.format("%d, %d, %d", red, green, blue); + } + + public static final Color RED = new Color(255, 0, 0); + public static final Color ORANGE = new Color(255, 165, 0); + public static final Color YELLOW = new Color(255, 255, 0); + public static final Color OLIVE = new Color(128, 128, 0); + public static final Color GREEN = new Color(0, 255, 0); + public static final Color CYAN = new Color(0, 255, 255); + public static final Color BLUE = new Color(0, 0, 255); + public static final Color TEAL = new Color(0, 128, 128); + public static final Color MAGENTA = new Color(255, 0, 255); + public static final Color PURPLE = new Color(128, 0, 128); + public static final Color PINK = new Color(255, 20, 128); + public static final Color WHITE = new Color(255, 255, 255); + public static final Color TRANSPARENT = new Color(0, 0, 0); +} diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/Prism/Direction.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/Prism/Direction.java new file mode 100644 index 000000000000..44205f3150df --- /dev/null +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/Prism/Direction.java @@ -0,0 +1,28 @@ +/* MIT License + * Copyright (c) [2025] [Base 10 Assets, LLC] + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.firstinspires.ftc.teamcode.Prism; + +public enum Direction { + Forward, + Backward +} diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/Prism/GoBildaPrismConfigurator.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/Prism/GoBildaPrismConfigurator.java new file mode 100644 index 000000000000..c2d1b25c5da7 --- /dev/null +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/Prism/GoBildaPrismConfigurator.java @@ -0,0 +1,1200 @@ +/* MIT License + * Copyright (c) [2025] [Base 10 Assets, LLC] + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.firstinspires.ftc.teamcode; + +import static org.firstinspires.ftc.teamcode.Prism.GoBildaPrismDriver.LayerHeight; + +import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; +import com.qualcomm.robotcore.eventloop.opmode.TeleOp; + +import org.firstinspires.ftc.teamcode.Prism.Color; +import org.firstinspires.ftc.teamcode.Prism.GoBildaPrismDriver; +import org.firstinspires.ftc.teamcode.Prism.GoBildaPrismDriver.Artboard; +import org.firstinspires.ftc.teamcode.Prism.PrismAnimations; +import org.firstinspires.ftc.teamcode.Prism.PrismAnimations.AnimationType; +import org.firstinspires.ftc.teamcode.Prism.PrismAnimations.PoliceLights; + +import java.util.concurrent.TimeUnit; + +/* + * This code creates a "Configurator" UI which exposes a somewhat limited amount of the functionality + * available to create animations and artboards to users without needing to work with very much code. + * + * This file is not meant to serve as example code - unless you're trying to create a telemetry-based + * UI for the drivers station. + * For example code that your team can leverage in your autonomous/teleop code to recall artboards + * check out the GoBildaPrismArtboardExample. Run this code on your robot and use it to create + * the artboards you'd like to be able to switch between. This means that switching between animations + * can be very fast and easy, and the work of creating animations (and sending this information over + * I²C) only has to happen once! + * + */ + +@TeleOp(name="Prism Configurator", group="Linear OpMode") +//@Disabled + +public class GoBildaPrismConfigurator extends LinearOpMode { + + GoBildaPrismDriver prism; + + // an enum which is used in a mini state machine to allow the user to set different colors. + public enum AnimationColor{ + PRIMARY_COLOR, + SECONDARY_COLOR, + TERTIARY_COLOR, + } + AnimationColor animationColor = AnimationColor.PRIMARY_COLOR; + + // Set a default style for the Police Lights Animation. + PoliceLights.PoliceLightsStyle policeLightsStyle = PoliceLights.PoliceLightsStyle.Style1; + + int startPoint = 0; // the start LED for any configured animation + int endPoint = 12; // the end LED for a configured animation + int brightness = 50; // the brightness of configured animation + int period = 1000; // the period of a configured animation + float speed = 0.5F; // the speed of a configured animation + + int layerSelector = 0; // an integer used to create a cursor to select a layer + int animationSelector = 1; // animation cursor + int artboardSelector = 0; // artboard cursor + + String hsbTelemetry; // string meant to send over telemetry that gets updated by hsbViaJoystick() + String hueTelemetry; // updated by hueViaJoystick() + + /* + * An array of colors passed to the SingleFill animation. + */ + Color[] singleFillColors = { + Color.RED, Color.WHITE, Color.BLUE + }; + + AnimationType selectedAnimation = AnimationType.SOLID; // store the animation that is being selected. + + /* + * the enum powering the main state machine that this code moves through, each state represents + * a page the user can see displayed via telemetry on the driver's station. + */ + public enum ConfigState{ + WELCOME_SCREEN, + SELECT_LAYER, + SET_ENDPOINTS, + SELECT_ANIMATION, + CONFIGURE_ANIMATION, + SET_BRIGHTNESS, + SET_SPEED, + FORK_IN_THE_ROAD, + SAVE_TO_ARTBOARD, + COMPLETE; + } + + ConfigState configState = ConfigState.WELCOME_SCREEN; + + /* + * This is actually a bit of a duplicate of the LayerHeight found in PrismAnimations. This adds + * an animation slot which can be stored at each position in the enum. This is not required for + * the Prism side, but I want to be able to show a user what animation is stored at what layer + * after they've created their first animation. + */ + public enum Layers{ + LAYER_0 (AnimationType.NONE,0,LayerHeight.LAYER_0), + LAYER_1 (AnimationType.NONE,1,LayerHeight.LAYER_1), + LAYER_2 (AnimationType.NONE,2,LayerHeight.LAYER_2), + LAYER_3 (AnimationType.NONE,3,LayerHeight.LAYER_3), + LAYER_4 (AnimationType.NONE,4,LayerHeight.LAYER_4), + LAYER_5 (AnimationType.NONE,5,LayerHeight.LAYER_5), + LAYER_6 (AnimationType.NONE,6,LayerHeight.LAYER_6), + LAYER_7 (AnimationType.NONE,7,LayerHeight.LAYER_7), + LAYER_8 (AnimationType.NONE,8,LayerHeight.LAYER_8), + LAYER_9 (AnimationType.NONE,9,LayerHeight.LAYER_9); + + private AnimationType animationType; + private final int index; + private final LayerHeight layerHeight; + + Layers(AnimationType animationType, int index, LayerHeight layerHeight){ + this.animationType = animationType; + this.index = index; + this.layerHeight = layerHeight; + } + } + + /* + * This enum captures the kind of speed we can control on the animation. + */ + public enum SpeedType{ + NO_SPEED, + PERIOD_ONLY, + SPEED_ONLY, + PERIOD_AND_SPEED, + } + + Layers selectedLayer = Layers.LAYER_0; + + Artboard selectedArtboard = Artboard.ARTBOARD_0; + + /* + * Create each Prism Animation which can be customized by the user. + */ + PrismAnimations.Solid solid = new PrismAnimations.Solid(); + PrismAnimations.Solid endpointsAnimation = new PrismAnimations.Solid(); + PrismAnimations.Blink blink = new PrismAnimations.Blink(); + PrismAnimations.Pulse pulse = new PrismAnimations.Pulse(); + PrismAnimations.SineWave sineWave = new PrismAnimations.SineWave(); + PrismAnimations.DroidScan droidScan = new PrismAnimations.DroidScan(); + PrismAnimations.Rainbow rainbow = new PrismAnimations.Rainbow(); + PrismAnimations.Snakes snakes = new PrismAnimations.Snakes(); + PrismAnimations.Random random = new PrismAnimations.Random(); + PrismAnimations.Sparkle sparkle = new PrismAnimations.Sparkle(); + PrismAnimations.SingleFill singleFill = new PrismAnimations.SingleFill(); + PrismAnimations.RainbowSnakes rainbowSnakes = new PrismAnimations.RainbowSnakes(); + PoliceLights policeLights = new PoliceLights(); + + + @Override + public void runOpMode() { + prism = hardwareMap.get(GoBildaPrismDriver.class,"prism"); + + telemetry.addLine("Welcome to the Prism Configurator, enjoy these fun stats " + + "and click the 'Play' button to continue"); + telemetry.addLine(""); + + telemetry.addData("Device ID", prism.getDeviceID()); + telemetry.addLine(prism.getFirmwareVersionString()); + telemetry.addLine(prism.getHardwareVersionString()); + telemetry.addData("Power Cycle Count", prism.getPowerCycleCount()); + telemetry.addData("Run Time (Minutes)",prism.getRunTime(TimeUnit.MINUTES)); + telemetry.addData("Run Time (Hours)",prism.getRunTime(TimeUnit.HOURS)); + telemetry.update(); + + // Wait for the game to start (driver presses START) + waitForStart(); + resetRuntime(); + + // run until the end of the match (driver presses STOP) + while (opModeIsActive()) { + + switch (configState){ + case WELCOME_SCREEN: + welcomeScreen(); + if(gamepad1.aWasPressed()){ + //if the user presses "a" we move onto the next state. + prism.clearAllAnimations(); + configState = ConfigState.SELECT_LAYER; + } + break; + case SELECT_LAYER: + /* + * You can't convince me that this is the correct way to implement a cursor, + * but you also can't tell me this one doesn't work. + * We allow the user to increment the layerSelecter variable, before constraining + * it to within a valid range. We then pass that into our "selectLayer()" + * function which displays the UI elements via telemetry and saves the selected + * layer every loop. + */ + if(gamepad1.dpadUpWasPressed()){ + layerSelector -=1; + } + if(gamepad1.dpadDownWasPressed()){ + layerSelector +=1; + } + layerSelector = Math.min(9, Math.max(0,layerSelector)); + selectLayer(layerSelector); + if(gamepad1.aWasPressed()){ + configureEndpointsAnimation(true); + configState = ConfigState.SET_ENDPOINTS; + } + break; + case SET_ENDPOINTS: + configureEndPoints(); + if(gamepad1.aWasPressed()){ + telemetry.clear(); + selectAnimation(animationSelector); + configState = ConfigState.SELECT_ANIMATION; + } + if(gamepad1.bWasPressed()){ + configState = ConfigState.SELECT_LAYER; + } + break; + case SELECT_ANIMATION: + if(gamepad1.dpadUpWasPressed()){ + animationSelector -=1; + animationSelector = Math.min(12, Math.max(1,animationSelector)); + selectAnimation(animationSelector); + } + if(gamepad1.dpadDownWasPressed()){ + animationSelector +=1; + animationSelector = Math.min(12, Math.max(1,animationSelector)); + selectAnimation(animationSelector); + } + if(gamepad1.aWasPressed()){ + selectedLayer.animationType = selectedAnimation; + configState = ConfigState.CONFIGURE_ANIMATION; + } + if(gamepad1.bWasPressed()){ + configureEndpointsAnimation(true); + configState = ConfigState.SET_ENDPOINTS; + } + break; + case CONFIGURE_ANIMATION: + configureAnimation(true,false); + if(gamepad1.aWasPressed()){ + configState = ConfigState.SET_BRIGHTNESS; + } + if(gamepad1.bWasPressed()){ + telemetry.clear(); + selectAnimation(animationSelector); + configState = ConfigState.SELECT_ANIMATION; + } + break; + case SET_BRIGHTNESS: + configureBrightness(); + if(gamepad1.aWasPressed()){ + configState = ConfigState.SET_SPEED; + } + if(gamepad1.bWasPressed()){ + configState = ConfigState.CONFIGURE_ANIMATION; + } + break; + case SET_SPEED: + if(configureSpeed()){ + configState = ConfigState.FORK_IN_THE_ROAD; + } + if(gamepad1.aWasPressed()){ + configState = ConfigState.FORK_IN_THE_ROAD; + } + if(gamepad1.bWasPressed()){ + configState = ConfigState.SET_BRIGHTNESS; + } + break; + case FORK_IN_THE_ROAD: + forkInTheRoad(); + if(gamepad1.aWasPressed()){ + telemetry.clear(); + configState = ConfigState.SAVE_TO_ARTBOARD; + selectArtboard(artboardSelector); + } + if(gamepad1.bWasPressed()){ + if(configureSpeed()){ + configState = ConfigState.SET_BRIGHTNESS; + } else configState = ConfigState.SET_SPEED; + } + if(gamepad1.yWasPressed()){ + configState = ConfigState.SELECT_LAYER; + } + if(gamepad1.xWasPressed()){ + configState = ConfigState.WELCOME_SCREEN; + } + break; + case SAVE_TO_ARTBOARD: + if(gamepad1.dpadUpWasPressed()){ + artboardSelector -=1; + artboardSelector = Math.min(7, Math.max(0,artboardSelector)); + selectArtboard(artboardSelector); + } + if(gamepad1.dpadDownWasPressed()){ + artboardSelector +=1; + artboardSelector = Math.min(7, Math.max(0,artboardSelector)); + selectArtboard(artboardSelector); + } + if(gamepad1.aWasPressed()){ + prism.saveCurrentAnimationsToArtboard(selectedArtboard); + prism.enableDefaultBootArtboard(true); + configState = ConfigState.COMPLETE; + } + if(gamepad1.bWasPressed()){ + configState = ConfigState.FORK_IN_THE_ROAD; + } + break; + case COMPLETE: + telemetry.addLine("Artboard saved!"); + telemetry.addLine(""); + telemetry.addLine("Press X to return to the beginning."); + if(gamepad1.xWasPressed()){ + configState = ConfigState.WELCOME_SCREEN; + } + break; + } + telemetry.update(); + sleep(20); + } + } + + /* + * through this code I try and keep as much code as possible inside of functions, with how + * complex our main state machine is, stuff like telemetry is is nice to keep only where we need it. + */ + public void welcomeScreen(){ + telemetry.addLine("Welcome to the goBILDA Prism Configurator!"); + telemetry.addLine("Hold on tight - we were cooking when we made this."); + telemetry.addLine(""); + telemetry.addLine("Core to understanding how to use this product is knowing these three terms:"); + telemetry.addLine("Animations: (Like RAINBOW or BLINK) - These have properties you can configure, " + + "like their color. And they can have unique start and end points."); + telemetry.addLine(""); + telemetry.addLine("Layers: There are 10 layers, each of which can store an animation. " + + "These are hierarchical. So an Animation on layer 5 will cover an animation on layer 2 " + + "if they overlap. You can use start and end points to have layers overlap to create new patterns!" + + "Or show multiple animations at once on different LEDs."); + telemetry.addLine(""); + telemetry.addLine("Artboards: An Artboard is a set of 10 layers which is stored on the Prism. " + + "you can have up to 8 unique Artboards. Artboards are easy and computationally fast to switch between. " + + "We recommend that you configure your Artboards for your robot once and then switch between them."); + telemetry.addLine(""); + telemetry.addLine("Press A to continue"); + resetStoredAnimations(); + } + + public void selectLayer(int selector){ + telemetry.addLine("Select the Layer that you wish to save an Animation to."); + telemetry.addLine("Use D-Pad up and D-Pad down to navigate through the layers."); + telemetry.addLine(""); + telemetry.addData("LAYER_0 Animation", Layers.LAYER_0.animationType+layerCursor(Layers.LAYER_0,selector)); + telemetry.addData("LAYER_1 Animation", Layers.LAYER_1.animationType+layerCursor(Layers.LAYER_1,selector)); + telemetry.addData("LAYER_2 Animation", Layers.LAYER_2.animationType+layerCursor(Layers.LAYER_2,selector)); + telemetry.addData("LAYER_3 Animation", Layers.LAYER_3.animationType+layerCursor(Layers.LAYER_3,selector)); + telemetry.addData("LAYER_4 Animation", Layers.LAYER_4.animationType+layerCursor(Layers.LAYER_4,selector)); + telemetry.addData("LAYER_5 Animation", Layers.LAYER_5.animationType+layerCursor(Layers.LAYER_5,selector)); + telemetry.addData("LAYER_6 Animation", Layers.LAYER_6.animationType+layerCursor(Layers.LAYER_6,selector)); + telemetry.addData("LAYER_7 Animation", Layers.LAYER_7.animationType+layerCursor(Layers.LAYER_7,selector)); + telemetry.addData("LAYER_8 Animation", Layers.LAYER_8.animationType+layerCursor(Layers.LAYER_8,selector)); + telemetry.addData("LAYER_9 Animation", Layers.LAYER_9.animationType+layerCursor(Layers.LAYER_9,selector)); + telemetry.addLine(""); + telemetry.addLine("Press A to continue"); + } + + public String layerCursor(Layers layer, int selector){ + if(layer.index == selector){ + selectedLayer = layer; + return "<--"; + }else return ""; + } + + public void selectAnimation(int animationSelector){ + telemetry.addLine("Select the Animation that you wish to use"); + telemetry.addLine("Use D-Pad up and D-Pad down to navigate through the Animations."); + telemetry.addLine(""); + telemetry.addData("Solid",animationCursor(AnimationType.SOLID,animationSelector)); + telemetry.addData("Blink",animationCursor(AnimationType.BLINK,animationSelector)); + telemetry.addData("Pulse",animationCursor(AnimationType.PULSE,animationSelector)); + telemetry.addData("Sine Wave",animationCursor(AnimationType.SINE_WAVE,animationSelector)); + telemetry.addData("Droid Scan",animationCursor(AnimationType.DROID_SCAN,animationSelector)); + telemetry.addData("Rainbow",animationCursor(AnimationType.RAINBOW,animationSelector)); + telemetry.addData("Snakes",animationCursor(AnimationType.SNAKES,animationSelector)); + telemetry.addData("Random",animationCursor(AnimationType.RANDOM,animationSelector)); + telemetry.addData("Sparkle",animationCursor(AnimationType.SPARKLE,animationSelector)); + telemetry.addData("Single Fill",animationCursor(AnimationType.SINGLE_FILL,animationSelector)); + telemetry.addData("Rainbow Snakes",animationCursor(AnimationType.RAINBOW_SNAKES,animationSelector)); + telemetry.addData("Police Lights",animationCursor(AnimationType.POLICE_LIGHTS,animationSelector)); + telemetry.addLine(""); + telemetry.addLine("Press A to continue"); + telemetry.addLine("Press B to go back"); + } + + public String animationCursor(AnimationType animationType, int selector){ + if(animationType.AnimationTypeIndex == selector){ + selectedAnimation = animationType; + configureAnimation(false,true); + return "<--"; + }else return ""; + } + + public void configureEndPoints(){ + if(gamepad1.dpadLeftWasPressed()){ + startPoint -= 1; + startPoint = Math.min(Math.min(255,Math.max(0,startPoint)),endPoint-1); + configureEndpointsAnimation(false); + } + if(gamepad1.dpadRightWasPressed()){ + startPoint += 1; + startPoint = Math.min(Math.min(255,Math.max(0,startPoint)),endPoint-1); + configureEndpointsAnimation(false); + } + if(gamepad1.leftBumperWasPressed()){ + endPoint -= 1; + endPoint = Math.max(Math.min(255,Math.max(0,endPoint)),startPoint+1); + configureEndpointsAnimation(false); + } + if(gamepad1.rightBumperWasPressed()){ + endPoint += 1; + endPoint = Math.max(Math.min(255,Math.max(0,endPoint)),startPoint+1); + configureEndpointsAnimation(false); + } + + telemetry.addLine("Set the start and stop point for each LED"); + telemetry.addLine(""); + telemetry.addLine("Use the d-pad to set the start point, d-pad right moves it further from the Prism. " + + "d-pad left moves it closer."); + telemetry.addLine("Bumpers move the endpoint, The left bumper moves it closer to the Prism, right moves it further."); + telemetry.addData("Start Point", startPoint); + telemetry.addData("End Point", endPoint); + telemetry.addLine(""); + telemetry.addLine("Press A to Continue"); + telemetry.addLine("Press B to go back"); + } + + public void configureBrightness(){ + if(gamepad1.dpadDownWasPressed()){ + brightness -= 10; + brightness = Math.min(100,Math.max(0,brightness)); + configureAnimation(false,false); + } + if(gamepad1.dpadUpWasPressed()){ + brightness += 10; + brightness = Math.min(100,Math.max(0,brightness)); + configureAnimation(false,false); + } + + telemetry.addLine("Set the brightness for this animation"); + telemetry.addLine(""); + telemetry.addLine("Use the d-pad to adjust the brightness, up increases, down decreases."); + telemetry.addLine(""); + telemetry.addData("Brightness", brightness); + telemetry.addLine(""); + telemetry.addLine("Press A to Continue"); + telemetry.addLine("Press B to go back"); + } + + /** + * Returns: True if we should skip this animation type. + */ + public boolean configureSpeed(){ + switch (speedFromAnimation(selectedAnimation)){ + case NO_SPEED: return true; + case SPEED_ONLY: + if(gamepad1.dpadUpWasPressed()){ + speed += 0.1f; + speed = Math.min(1.0f, Math.max(0,speed)); + configureAnimation(false,false); + } + if(gamepad1.dpadDownWasPressed()){ + speed -= 0.1f; + speed = Math.min(1.0f, Math.max(0,speed)); + configureAnimation(false,false); + } + telemetry.addLine("Set the speed for this animation from 0 to 1"); + telemetry.addLine(""); + telemetry.addLine("Use the d-pad to adjust the speed, up increases, down decreases."); + telemetry.addLine(""); + telemetry.addData("Speed", speed); + telemetry.addLine(""); + telemetry.addLine("Press A to Continue"); + telemetry.addLine("Press B to go back"); + break; + case PERIOD_ONLY: + if(gamepad1.dpadUpWasPressed()){ + if(period < 401){ + period += 100; + } else period += 500; + period = Math.min(300000, Math.max(0,period)); + configureAnimation(false,false); + } + if(gamepad1.dpadDownWasPressed()){ + if(period < 501){ + period -= 100; + } else period -= 500; + period = Math.min(300000, Math.max(0,period)); + configureAnimation(false,false); + } + telemetry.addLine("Set the period for this animation in Milliseconds"); + telemetry.addLine(""); + telemetry.addLine("Use the d-pad to adjust the period, up increases, down decreases."); + telemetry.addLine(""); + telemetry.addData("Period", period); + telemetry.addLine(""); + telemetry.addLine("Press A to Continue"); + telemetry.addLine("Press B to go back"); + break; + case PERIOD_AND_SPEED: + if(gamepad1.dpadUpWasPressed()){ + if(period < 401){ + period += 100; + } else period += 500; + period = Math.min(300000, Math.max(0,period)); + configureAnimation(false,false); + } + if(gamepad1.dpadDownWasPressed()){ + if(period < 501){ + period -= 100; + } else period -= 500; + period = Math.min(300000, Math.max(0,period)); + configureAnimation(false,false); + } + if(gamepad1.dpadRightWasPressed()){ + speed += 0.1f; + speed = Math.min(1.0f, Math.max(0,speed)); + configureAnimation(false,false); + } + if(gamepad1.dpadLeftWasPressed()){ + speed -= 0.1f; + speed = Math.min(1.0f, Math.max(0,speed)); + configureAnimation(false,false); + } + telemetry.addLine("Set the period for this animation in Milliseconds"); + telemetry.addLine(""); + telemetry.addLine("Set the speed for this animation from 0-1"); + telemetry.addLine(""); + telemetry.addLine("Use the d-pad to adjust the period, up increases, down decreases."); + telemetry.addLine(""); + telemetry.addData("Period", period); + telemetry.addLine(""); + telemetry.addLine("Use the d-pad to adjust the speed, right increases, left decreases."); + telemetry.addLine(""); + telemetry.addData("Speed", speed); + telemetry.addLine(""); + telemetry.addLine("Press A to Continue"); + telemetry.addLine("Press B to go back"); + break; + } + return false; + } + + public SpeedType speedFromAnimation(AnimationType animationType){ + if(animationType == AnimationType.BLINK || animationType == AnimationType.PULSE || + animationType == AnimationType.SPARKLE || animationType == AnimationType.POLICE_LIGHTS){ + return SpeedType.PERIOD_ONLY; + } + if (animationType == AnimationType.DROID_SCAN || animationType == AnimationType.RAINBOW || + animationType == AnimationType.SNAKES || animationType == AnimationType.RANDOM || + animationType == AnimationType.RAINBOW_SNAKES){ + return SpeedType.SPEED_ONLY; + } + if(animationType == AnimationType.SINE_WAVE || animationType == AnimationType.SINGLE_FILL){ + return SpeedType.PERIOD_AND_SPEED; + } + else return SpeedType.NO_SPEED; + } + + public void forkInTheRoad(){ + telemetry.addLine("If you are done with your creation, press A to save it to an Artboard."); + telemetry.addLine(""); + telemetry.addLine("To go back, press B."); + telemetry.addLine(""); + telemetry.addLine("If you'd instead like to layer another animation on top of this " + + "one, press Y."); + telemetry.addLine(""); + telemetry.addLine("Press X to return to the start and clear currently set animations."); + } + + public void selectArtboard(int artboardSelector){ + telemetry.addLine("Select the Artboard that you wish to save to"); + telemetry.addLine("Use D-Pad up and D-Pad down to navigate through the Artboards."); + telemetry.addLine(""); + telemetry.addData("Artboard 0",artboardCursor(Artboard.ARTBOARD_0,artboardSelector)); + telemetry.addData("Artboard 1",artboardCursor(Artboard.ARTBOARD_1,artboardSelector)); + telemetry.addData("Artboard 2",artboardCursor(Artboard.ARTBOARD_2,artboardSelector)); + telemetry.addData("Artboard 3",artboardCursor(Artboard.ARTBOARD_3,artboardSelector)); + telemetry.addData("Artboard 4",artboardCursor(Artboard.ARTBOARD_4,artboardSelector)); + telemetry.addData("Artboard 5",artboardCursor(Artboard.ARTBOARD_5,artboardSelector)); + telemetry.addData("Artboard 6",artboardCursor(Artboard.ARTBOARD_6,artboardSelector)); + telemetry.addData("Artboard 7",artboardCursor(Artboard.ARTBOARD_7,artboardSelector)); + telemetry.addLine(""); + telemetry.addLine("Press A to save"); + telemetry.addLine("Press B to go back"); + } + + public String artboardCursor(Artboard artboard, int selector){ + if(artboard.index == selector){ + selectedArtboard = artboard; + return "<--"; + }else return ""; + } + + public void configureAnimation(boolean showTelemetry, boolean isBeingInserted){ + switch(selectedAnimation){ + case SOLID: + configureSolid(showTelemetry, isBeingInserted); + break; + case BLINK: + configureBlink(showTelemetry, isBeingInserted); + break; + case PULSE: + configurePulse(showTelemetry, isBeingInserted); + break; + case SINE_WAVE: + configureSineWave(showTelemetry, isBeingInserted); + break; + case DROID_SCAN: + configureDroidScan(showTelemetry, isBeingInserted); + break; + case RAINBOW: + configureRainbow(showTelemetry, isBeingInserted); + break; + case SNAKES: + configureSnakes(showTelemetry, isBeingInserted); + break; + case RANDOM: + configureRandom(showTelemetry, isBeingInserted); + break; + case SPARKLE: + configureSparkle(showTelemetry, isBeingInserted); + break; + case SINGLE_FILL: + configureSingleFill(showTelemetry, isBeingInserted); + break; + case RAINBOW_SNAKES: + configureRainbowSnakes(showTelemetry, isBeingInserted); + break; + case POLICE_LIGHTS: + configurePoliceLights(showTelemetry, isBeingInserted); + break; + } + } + + public void configureEndpointsAnimation(boolean isBeingInserted){ + endpointsAnimation.setStartIndex(startPoint); + endpointsAnimation.setStopIndex(endPoint); + if(isBeingInserted){ + prism.insertAndUpdateAnimation(selectedLayer.layerHeight, endpointsAnimation); + } else { + prism.updateAnimationFromIndex(selectedLayer.layerHeight); + } + } + + /* + * Past this point, we have a function to create each type of animation and configure the + * specifics to it. As often as I could, you'll find that actions that multiple animations + * share (like setPrimaryColor) get abstracted into their own functions (here we have + * "hsbViaJoystick) to do the work. This avoids copy and pasting code. + */ + + public void configureSolid(boolean showTelemetry, boolean isBeingInserted){ + solid.setPrimaryColor(hsbViaJoystick(solid.getPrimaryColor())); + solid.setStartIndex(startPoint); + solid.setStopIndex(endPoint); + solid.setBrightness(brightness); + + if(isBeingInserted){ + prism.insertAndUpdateAnimation(selectedLayer.layerHeight, solid); + } else { + prism.updateAnimationFromIndex(selectedLayer.layerHeight); + } + + if(showTelemetry){ + telemetry.addLine("Selected Animation: Solid"); + showHsbTelemetry(); + telemetry.addLine(hsbTelemetry); + telemetry.addLine("Press A to Continue"); + telemetry.addLine("Press B to go back"); + } + } + + public void configureBlink(boolean showTelemetry, boolean isBeingInserted){ + switch (animationColor){ + case PRIMARY_COLOR: + blink.setPrimaryColor(hsbViaJoystick(blink.getPrimaryColor())); + break; + case SECONDARY_COLOR: + blink.setSecondaryColor(hsbViaJoystick(blink.getSecondaryColor())); + break; + } + toggleThroughColors(gamepad1.yWasPressed(),false); + + blink.setStartIndex(startPoint); + blink.setStopIndex(endPoint); + blink.setBrightness(brightness); + blink.setPeriod(period); + blink.setPrimaryColorPeriod(period/2); + + if(isBeingInserted){ + prism.insertAndUpdateAnimation(selectedLayer.layerHeight, blink); + } else { + prism.updateAnimationFromIndex(selectedLayer.layerHeight); + } + + if(showTelemetry){ + telemetry.addLine("Selected Animation: Blink"); + showHsbTelemetry(); + telemetry.addLine(hsbTelemetry); + telemetry.addLine(""); + telemetry.addLine("Click the Y button to switch between setting the primary and secondary color"); + telemetry.addLine(animationColor.toString()); + telemetry.addLine(""); + telemetry.addLine("Press A to Continue"); + telemetry.addLine("Press B to go back"); + } + } + + public void configurePulse(boolean showTelemetry, boolean isBeingInserted){ + switch (animationColor){ + case PRIMARY_COLOR: + pulse.setPrimaryColor(hsbViaJoystick(pulse.getPrimaryColor())); + break; + case SECONDARY_COLOR: + pulse.setSecondaryColor(hsbViaJoystick(pulse.getSecondaryColor())); + break; + } + + toggleThroughColors(gamepad1.yWasPressed(),false); + + pulse.setStartIndex(startPoint); + pulse.setStopIndex(endPoint); + pulse.setBrightness(brightness); + pulse.setPeriod(period); + + if(isBeingInserted){ + prism.insertAndUpdateAnimation(selectedLayer.layerHeight, pulse); + } else { + prism.updateAnimationFromIndex(selectedLayer.layerHeight); + } + + if(showTelemetry){ + telemetry.addLine("Selected Animation: Pulse"); + showHsbTelemetry(); + telemetry.addLine(hsbTelemetry); + telemetry.addLine(""); + telemetry.addLine("Click the Y button to switch between setting the primary and secondary color"); + telemetry.addLine(animationColor.toString()); + telemetry.addLine(""); + telemetry.addLine("Press A to Continue"); + telemetry.addLine("Press B to go back"); + } + } + + public void configureSineWave(boolean showTelemetry, boolean isBeingInserted){ + switch (animationColor){ + case PRIMARY_COLOR: + sineWave.setPrimaryColor(hsbViaJoystick(sineWave.getPrimaryColor())); + break; + case SECONDARY_COLOR: + sineWave.setSecondaryColor(hsbViaJoystick(sineWave.getSecondaryColor())); + break; + } + + toggleThroughColors(gamepad1.yWasPressed(),false); + + sineWave.setStartIndex(startPoint); + sineWave.setStopIndex(endPoint); + sineWave.setBrightness(brightness); + sineWave.setPeriod(period); + sineWave.setSpeed(speed); + + if(isBeingInserted){ + prism.insertAndUpdateAnimation(selectedLayer.layerHeight, sineWave); + } else { + prism.updateAnimationFromIndex(selectedLayer.layerHeight); + } + + if(showTelemetry){ + telemetry.addLine("Selected Animation: Sine Wave"); + showHsbTelemetry(); + telemetry.addLine(hsbTelemetry); + telemetry.addLine(""); + telemetry.addLine("Click the Y button to switch between setting the primary and secondary color"); + telemetry.addLine(animationColor.toString()); + telemetry.addLine(""); + telemetry.addLine("Press A to Continue"); + telemetry.addLine("Press B to go back"); + } + } + + public void configureDroidScan(boolean showTelemetry, boolean isBeingInserted){ + switch (animationColor){ + case PRIMARY_COLOR: + droidScan.setPrimaryColor(hsbViaJoystick(droidScan.getPrimaryColor())); + break; + case SECONDARY_COLOR: + droidScan.setSecondaryColor(hsbViaJoystick(droidScan.getSecondaryColor())); + break; + } + + toggleThroughColors(gamepad1.yWasPressed(),false); + + droidScan.setStartIndex(startPoint); + droidScan.setStopIndex(endPoint); + droidScan.setBrightness(brightness); + droidScan.setSpeed(speed); + + if(isBeingInserted){ + prism.insertAndUpdateAnimation(selectedLayer.layerHeight, droidScan); + } else { + prism.updateAnimationFromIndex(selectedLayer.layerHeight); + } + + if(showTelemetry){ + telemetry.addLine("Selected Animation: Droid Scan"); + showHsbTelemetry(); + telemetry.addLine(hsbTelemetry); + telemetry.addLine(""); + telemetry.addLine("Click the Y button to switch between setting the primary and secondary color"); + telemetry.addLine(animationColor.toString()); + telemetry.addLine(""); + telemetry.addLine("Press A to Continue"); + telemetry.addLine("Press B to go back"); + } + } + + public void configureRainbow(boolean showTelemetry, boolean isBeingInserted){ + float[] hues = hueViaJoystick(rainbow.getStartHue(),rainbow.getStopHue()); + rainbow.setStartHue(hues[0]); + rainbow.setStopHue(hues[1]); + + rainbow.setStartIndex(startPoint); + rainbow.setStopIndex(endPoint); + rainbow.setBrightness(brightness); + rainbow.setSpeed(speed); + + if(isBeingInserted){ + prism.insertAndUpdateAnimation(selectedLayer.layerHeight, rainbow); + } else { + prism.updateAnimationFromIndex(selectedLayer.layerHeight); + } + if(showTelemetry){ + telemetry.addLine("Selected Animation: Rainbow"); + telemetry.addLine("Move the joysticks to configure the range of the rainbow"); + telemetry.addLine("Left Joystick X (side-to-side) changes the start hue"); + telemetry.addLine("Right Joystick x (side-to-side) changes the end hue"); + telemetry.addLine(""); + telemetry.addLine(hueTelemetry); + telemetry.addLine(""); + telemetry.addLine("Press A to Continue"); + telemetry.addLine("Press B to go back"); + } + } + + public void configureSnakes(boolean showTelemetry, boolean isBeingInserted){ + snakes.setColors(hsbViaJoystick(snakes.getColors()[0])); + + snakes.setStartIndex(startPoint); + snakes.setStopIndex(endPoint); + snakes.setBrightness(brightness); + snakes.setSpeed(speed); + + if(isBeingInserted){ + prism.insertAndUpdateAnimation(selectedLayer.layerHeight, snakes); + } else { + prism.updateAnimationFromIndex(selectedLayer.layerHeight); + } + + if(showTelemetry) { + telemetry.addLine("Selected Animation: Snakes"); + showHsbTelemetry(); + telemetry.addLine(hsbTelemetry); + telemetry.addLine(""); + telemetry.addLine("Press A to Continue"); + telemetry.addLine("Press B to go back"); + } + } + + public void configureRandom(boolean showTelemetry, boolean isBeingInserted){ + float[] hues = hueViaJoystick(random.getStartHue(), random.getStopHue()); + random.setStartHue(hues[0]); + random.setStopHue(hues[1]); + + random.setStartIndex(startPoint); + random.setStopIndex(endPoint); + random.setBrightness(brightness); + random.setSpeed(speed); + + if(isBeingInserted){ + prism.insertAndUpdateAnimation(selectedLayer.layerHeight, random); + } else { + prism.updateAnimationFromIndex(selectedLayer.layerHeight); + } + + if(showTelemetry){ + telemetry.addLine("Selected Animation: Random"); + telemetry.addLine("Move the joysticks to configure the range of the random colors"); + telemetry.addLine("Left Joystick X (side-to-side) changes the start hue"); + telemetry.addLine("Right Joystick x (side-to-side) changes the end hue"); + telemetry.addLine(""); + telemetry.addLine(hueTelemetry); + telemetry.addLine(""); + telemetry.addLine("Press A to Continue"); + telemetry.addLine("Press B to go back"); + } + } + + public void configureSparkle(boolean showTelemetry, boolean isBeingInserted){ + switch (animationColor){ + case PRIMARY_COLOR: + sparkle.setPrimaryColor(hsbViaJoystick(sparkle.getPrimaryColor())); + break; + case SECONDARY_COLOR: + sparkle.setSecondaryColor(hsbViaJoystick(sparkle.getSecondaryColor())); + break; + } + + toggleThroughColors(gamepad1.yWasPressed(),false); + + sparkle.setStartIndex(startPoint); + sparkle.setStopIndex(endPoint); + sparkle.setBrightness(brightness); + sparkle.setPeriod(period); + + if(isBeingInserted){ + prism.insertAndUpdateAnimation(selectedLayer.layerHeight, sparkle); + } else { + prism.updateAnimationFromIndex(selectedLayer.layerHeight); + } + + if(showTelemetry){ + telemetry.addLine("Selected Animation: Sparkle"); + showHsbTelemetry(); + telemetry.addLine(hsbTelemetry); + telemetry.addLine(""); + telemetry.addLine("Click the Y button to switch between setting the primary and secondary color"); + telemetry.addLine(animationColor.toString()); + telemetry.addLine(""); + telemetry.addLine("Press A to Continue"); + telemetry.addLine("Press B to go back"); + } + } + + public void configureSingleFill(boolean showTelemetry, boolean isBeingInserted){ + switch (animationColor){ + case PRIMARY_COLOR: + singleFillColors[0] = hsbViaJoystick(singleFill.getColors()[0]); + break; + case SECONDARY_COLOR: + singleFillColors[1] = hsbViaJoystick(singleFill.getColors()[1]); + break; + case TERTIARY_COLOR: + singleFillColors[2] = hsbViaJoystick(singleFill.getColors()[2]); + break; + } + + toggleThroughColors(gamepad1.yWasPressed(),true); + + singleFill.setColors(singleFillColors); + + singleFill.setStartIndex(startPoint); + singleFill.setStopIndex(endPoint); + singleFill.setBrightness(brightness); + singleFill.setSpeed(speed); + singleFill.setPeriod(period); + + if(isBeingInserted){ + prism.insertAndUpdateAnimation(selectedLayer.layerHeight, singleFill); + } else { + prism.updateAnimationFromIndex(selectedLayer.layerHeight); + } + + if(showTelemetry){ + telemetry.addLine("Selected Animation: Single Fill"); + telemetry.addLine(""); + showHsbTelemetry(); + telemetry.addLine(hsbTelemetry); + telemetry.addLine(""); + telemetry.addLine("Click the Y button to switch between setting the primary, secondary, and tertiary color"); + telemetry.addLine(animationColor.toString()); + telemetry.addLine(""); + telemetry.addLine("Press A to Continue"); + telemetry.addLine("Press B to go back"); + } + } + + public void configureRainbowSnakes(boolean showTelemetry, boolean isBeingInserted){ + float[] hues = hueViaJoystick(rainbowSnakes.getStartHue(),rainbowSnakes.getStopHue()); + rainbowSnakes.setStartHue(hues[0]); + rainbowSnakes.setStopHue(hues[1]); + + rainbowSnakes.setStartIndex(startPoint); + rainbowSnakes.setStopIndex(endPoint); + rainbowSnakes.setBrightness(brightness); + rainbowSnakes.setSpeed(speed); + + if(isBeingInserted){ + prism.insertAndUpdateAnimation(selectedLayer.layerHeight, rainbowSnakes); + } else { + prism.updateAnimationFromIndex(selectedLayer.layerHeight); + } + + if(showTelemetry){ + telemetry.addLine("Selected Animation: Rainbow Snakes"); + telemetry.addLine("Move the joysticks to configure the range of the rainbow"); + telemetry.addLine("Left Joystick X (side-to-side) changes the start hue"); + telemetry.addLine("Right Joystick x (side-to-side) changes the end hue"); + telemetry.addLine(""); + telemetry.addLine(hueTelemetry); + telemetry.addLine(""); + telemetry.addLine("Press A to Continue"); + telemetry.addLine("Press B to go back"); + } + } + + public void configurePoliceLights(boolean showTelemetry, boolean isBeingInserted){ + switch (animationColor){ + case PRIMARY_COLOR: + policeLights.setPrimaryColor(hsbViaJoystick(policeLights.getPrimaryColor())); + break; + case SECONDARY_COLOR: + policeLights.setSecondaryColor(hsbViaJoystick(policeLights.getSecondaryColor())); + break; + case TERTIARY_COLOR: + policeLights.setTertiaryColor(hsbViaJoystick(policeLights.getTertiaryColor())); + break; + } + + toggleThroughColors(gamepad1.yWasPressed(),true); + + switch (policeLightsStyle){ + case Style1: + if(gamepad1.rightStickButtonWasPressed()){ + policeLightsStyle = PoliceLights.PoliceLightsStyle.Style2; + } + break; + case Style2: + if(gamepad1.rightStickButtonWasPressed()){ + policeLightsStyle = PoliceLights.PoliceLightsStyle.Style3; + } + break; + case Style3: + if(gamepad1.rightStickButtonWasPressed()){ + policeLightsStyle = PoliceLights.PoliceLightsStyle.Style1; + } + break; + } + + policeLights.setStartIndex(startPoint); + policeLights.setStopIndex(endPoint); + policeLights.setBrightness(brightness); + policeLights.setPoliceLightsStyle(policeLightsStyle); + policeLights.setPeriod(period); + + if(isBeingInserted){ + prism.insertAndUpdateAnimation(selectedLayer.layerHeight, policeLights); + } else { + prism.updateAnimationFromIndex(selectedLayer.layerHeight); + } + + if(showTelemetry){ + telemetry.addLine("Selected Animation: Police Lights"); + telemetry.addLine(""); + telemetry.addLine("Toggle between styles by clicking the right joystick"); + telemetry.addData("Police Lights Style: ",policeLightsStyle); + telemetry.addLine(""); + showHsbTelemetry(); + telemetry.addLine(hsbTelemetry); + telemetry.addLine(""); + telemetry.addLine("Click the Y button to switch between setting the primary, secondary, and tertiary color"); + telemetry.addLine(animationColor.toString()); + telemetry.addLine(""); + telemetry.addLine("Press A to Continue"); + telemetry.addLine("Press B to go back"); + } + } + + public void resetStoredAnimations(){ + Layers.LAYER_0.animationType = AnimationType.NONE; + Layers.LAYER_1.animationType = AnimationType.NONE; + Layers.LAYER_2.animationType = AnimationType.NONE; + Layers.LAYER_3.animationType = AnimationType.NONE; + Layers.LAYER_4.animationType = AnimationType.NONE; + Layers.LAYER_5.animationType = AnimationType.NONE; + Layers.LAYER_6.animationType = AnimationType.NONE; + Layers.LAYER_7.animationType = AnimationType.NONE; + Layers.LAYER_8.animationType = AnimationType.NONE; + Layers.LAYER_9.animationType = AnimationType.NONE; + } + + /* + * Reasonably, you might be wondering why I'm configuring the colors in HSB/HSL instead + * of RGB - The sane choice. And it's all to create a more intuitive user experience. + * Having the user create a color by combining sliders of red, green, and blue is very true + * to the colorspace we are actually working in, but folks I tried this on found it very + * difficult. Hue/Saturation/Brightness allows us to create one slider for each three intuitive + * variables. Hue changes the color, saturation changes the intensity of the color, and brightness + * changes, well the brightness. + * Actually implementing this isn't very clean, but the result for the user is a better experience. + */ + public Color hsbViaJoystick(Color previousColor){ + final float HUE_JOYSTICK_SCALAR = 5; + final float SATURATION_JOYSTICK_SCALAR = 0.05F; + final float BRIGHTNESS_JOYSTICK_SCALAR = 0.05f; + + float[] hsb = new float[3]; // Android graphics library wants an array containing RGB values. + android.graphics.Color.RGBToHSV(previousColor.red,previousColor.green,previousColor.blue,hsb); + + /* + * Here we let the user increase or decrease H, S, or B with the joystick. + * Pushing the stick more moves the value more. + */ + hsb[0] = Math.max(Math.min(hsb[0] +(gamepad1.left_stick_x*HUE_JOYSTICK_SCALAR), 360),0); + hsb[1] = Math.max(Math.min(hsb[1] +(gamepad1.right_stick_x*SATURATION_JOYSTICK_SCALAR), 1),0); + hsb[2] = Math.max(Math.min(hsb[2] +(-gamepad1.right_stick_y*BRIGHTNESS_JOYSTICK_SCALAR), 0.98f),0); + + /* + * Here we create an integer where the Android graphics library will store each component of + * our RGB color one-after-another. I hope we can agree that this is cursed. + */ + int colorInt = android.graphics.Color.HSVToColor(hsb); + Color color = new Color(0,0,0); // Create a new color to return. + + /* + * One of the big downsides in this multi-color model is that some behavior isn't very + * intuitive. In this case as you approach a saturation of 1, the number of hues you can + * display are limited to very pure versions of red, green, and blue. To avoid this we + * limit the maximum saturation to 0.98 (this limit happens above) and to reduce confusion, + * we display this to the user over telemetry as 1.0. + */ + if(hsb[2] > 0.97){ + hsb[2] = 1.0f; + } + hsbTelemetry = String.format("Hue/Saturation/Brightness: %4.2f %4.2f %4.2f", hsb[0], hsb[1], hsb[2]); + + color.red = android.graphics.Color.red(colorInt); + color.green = android.graphics.Color.green(colorInt); + color.blue = android.graphics.Color.blue(colorInt); + return color; + } + + public float[] hueViaJoystick(float previousStartHue, float previousEndHue){ + final float HUE_JOYSTICK_SCALAR = 5; + float[] hues = new float[2]; + + hues[0] = Math.max(Math.min(previousStartHue +(gamepad1.left_stick_x*HUE_JOYSTICK_SCALAR), 360),0); + hues[1] = Math.max(Math.min(previousEndHue +(gamepad1.right_stick_x*HUE_JOYSTICK_SCALAR),360),0); + + hueTelemetry = String.format("Start hue/end hue: %4.2f %4.2f", hues[0], hues[1]); + + return hues; + } + + public void showHsbTelemetry(){ + telemetry.addLine("Move the joysticks to configure the color of the LEDs"); + telemetry.addLine("Left Joystick X (side-to-side) Changes the Hue of a color"); + telemetry.addLine("Right Joystick X (side-to-side) changes the Saturation"); + telemetry.addLine("Right Joystick Y (up-and-down) changes the Brightness"); + telemetry.addLine(""); + } + + public void toggleThroughColors(boolean button, boolean thirdColor){ + if(button){ + switch (animationColor){ + case PRIMARY_COLOR: + animationColor = AnimationColor.SECONDARY_COLOR; + break; + case SECONDARY_COLOR: + if(thirdColor){ + animationColor = AnimationColor.TERTIARY_COLOR; + } else{ + animationColor = AnimationColor.PRIMARY_COLOR; + } + break; + case TERTIARY_COLOR: + animationColor = AnimationColor.PRIMARY_COLOR; + break; + } + } + } +} diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/Prism/GoBildaPrismDriver.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/Prism/GoBildaPrismDriver.java new file mode 100644 index 000000000000..974a208502fa --- /dev/null +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/Prism/GoBildaPrismDriver.java @@ -0,0 +1,440 @@ +/* MIT License + * Copyright (c) [2025] [Base 10 Assets, LLC] + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.firstinspires.ftc.teamcode.Prism; + +import static com.qualcomm.robotcore.util.TypeConversion.byteArrayToInt; +import static com.qualcomm.robotcore.util.TypeConversion.unsignedByteToInt; + +import com.qualcomm.hardware.lynx.LynxI2cDeviceSynch; +import com.qualcomm.robotcore.hardware.I2cAddr; +import com.qualcomm.robotcore.hardware.I2cDeviceSynchDevice; +import com.qualcomm.robotcore.hardware.I2cDeviceSynchSimple; +import com.qualcomm.robotcore.hardware.configuration.annotations.DeviceProperties; +import com.qualcomm.robotcore.hardware.configuration.annotations.I2cDeviceType; +import com.qualcomm.robotcore.util.TypeConversion; + +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +@I2cDeviceType +@DeviceProperties( + name = "goBILDA® Prism RGB LED Driver", + xmlTag = "goBILDAPrism", + description ="Prism RGB LED Driver (6-30V Input, I²C / PWM Control)" +) +public class GoBildaPrismDriver extends I2cDeviceSynchDevice { + private static final byte DEFAULT_ADDRESS = 0x38; + private final int MAXIMUM_NUMBER_OF_ANIMATIONS = 10; + private final int MAXIMUM_NUMBER_OF_ANIMATION_GROUPS = 8; + private PrismAnimations.AnimationBase[] animations = new PrismAnimations.AnimationBase[MAXIMUM_NUMBER_OF_ANIMATIONS]; + + //#region Public Types + public enum LayerHeight + { + LAYER_0 (Register.ANIMATION_SLOT_0), + LAYER_1 (Register.ANIMATION_SLOT_1), + LAYER_2 (Register.ANIMATION_SLOT_2), + LAYER_3 (Register.ANIMATION_SLOT_3), + LAYER_4 (Register.ANIMATION_SLOT_4), + LAYER_5 (Register.ANIMATION_SLOT_5), + LAYER_6 (Register.ANIMATION_SLOT_6), + LAYER_7 (Register.ANIMATION_SLOT_7), + LAYER_8 (Register.ANIMATION_SLOT_8), + LAYER_9 (Register.ANIMATION_SLOT_9), + DISABLED(Register.NULL); + + /* Package Private */ final Register register; + /* Package Private */ final int index; + + LayerHeight(Register register){ + this.register = register; + if(register == Register.NULL) + this.index = -1; + else + this.index = register.address - Register.ANIMATION_SLOT_0.address; + } + } + + public enum Artboard + { + ARTBOARD_0 (0,0), + ARTBOARD_1 (1,1), + ARTBOARD_2 (2,2), + ARTBOARD_3 (3,3), + ARTBOARD_4 (4,4), + ARTBOARD_5 (5,5), + ARTBOARD_6 (6,6), + ARTBOARD_7 (7,7); + + /* Package Private */ final byte bitmask; + public final int index; + + Artboard(int val,int index){ + this.bitmask = (byte)(1 << val); + this.index = index; + } + } + //#endregion + + //#region Package-Private types + /** + * Captures the length of each type of register used on the device. + */ + /* Package Private */ enum RegisterType + { + INT8(1,255), + INT16(2, 65535), + INT24(3, 16777215), + INT32(4, 2147483647); + + /* Package Private */ final int lengthBytes; + /* Package Private */ final int maxValue; + + RegisterType(int lengthBytes, int maxValue){ + this.lengthBytes = lengthBytes; + this.maxValue = maxValue; + } + } + + /* Package Private */ enum RegisterAccess + { + READ_ONLY, + WRITE_ONLY, + READ_AND_WRITE; + } + + /** + * Register map, including register address and register type + */ + /* Package Private */ enum Register + { + DEVICE_ID (0 , RegisterType.INT8 , RegisterAccess.READ_ONLY), + FIRMWARE_VERSION (1 , RegisterType.INT24, RegisterAccess.READ_ONLY), + HARDWARE_VERSION (2 , RegisterType.INT16, RegisterAccess.READ_ONLY), + POWER_CYCLE_COUNT (3 , RegisterType.INT32, RegisterAccess.READ_ONLY), + RUNTIME_IN_MINUTES(4 , RegisterType.INT32, RegisterAccess.READ_ONLY), + STATUS (5 , RegisterType.INT32, RegisterAccess.READ_ONLY), + CONTROL (6 , RegisterType.INT32, RegisterAccess.WRITE_ONLY), + ARTBOARD_CONTROL (7 , RegisterType.INT32, RegisterAccess.WRITE_ONLY), + ANIMATION_SLOT_0 (8 , RegisterType.INT32, RegisterAccess.READ_AND_WRITE), + ANIMATION_SLOT_1 (9 , RegisterType.INT32, RegisterAccess.READ_AND_WRITE), + ANIMATION_SLOT_2 (10, RegisterType.INT32, RegisterAccess.READ_AND_WRITE), + ANIMATION_SLOT_3 (11, RegisterType.INT32, RegisterAccess.READ_AND_WRITE), + ANIMATION_SLOT_4 (12, RegisterType.INT32, RegisterAccess.READ_AND_WRITE), + ANIMATION_SLOT_5 (13, RegisterType.INT32, RegisterAccess.READ_AND_WRITE), + ANIMATION_SLOT_6 (14, RegisterType.INT32, RegisterAccess.READ_AND_WRITE), + ANIMATION_SLOT_7 (15, RegisterType.INT32, RegisterAccess.READ_AND_WRITE), + ANIMATION_SLOT_8 (16, RegisterType.INT32, RegisterAccess.READ_AND_WRITE), + ANIMATION_SLOT_9 (17, RegisterType.INT32, RegisterAccess.READ_AND_WRITE), + NULL (18, RegisterType.INT8 , RegisterAccess.READ_ONLY); + + /* Package Private */ final int address; + /* Package Private */ final RegisterType registerType; + /* Package Private */ final RegisterAccess registerAccess; + + Register(int address, RegisterType registerType, RegisterAccess registerAccess){ + this.address = address; + this.registerType = registerType; + this.registerAccess = registerAccess; + } + } + + private int readInt(Register register){ + return byteArrayToInt(deviceClient.read(register.address,register.registerType.lengthBytes),ByteOrder.LITTLE_ENDIAN); + } + + //#endregion + + public GoBildaPrismDriver(I2cDeviceSynchSimple deviceClient, boolean deviceClientIsOwned) + { + super(deviceClient, deviceClientIsOwned); + + this.deviceClient.setI2cAddress(I2cAddr.create7bit(DEFAULT_ADDRESS)); + super.registerArmingStateCallback(false); + } + + @Override + public Manufacturer getManufacturer() + { + return Manufacturer.GoBilda; + } + + @Override + protected synchronized boolean doInitialize() + { + ((LynxI2cDeviceSynch)(deviceClient)).setBusSpeed(LynxI2cDeviceSynch.BusSpeed.FAST_400K); + return true; + } + + @Override + public String getDeviceName() + { + return "goBILDA® Prism RGB LED Driver"; + } + + /** + * @return 3 if device is functional. + */ + public int getDeviceID(){ + //return readInt(Register.DEVICE_ID); + byte[] packet = deviceClient.read(Register.DEVICE_ID.address, Register.DEVICE_ID.registerType.lengthBytes); + return packet[0]; + } + + /** + * @return Firmware Version of device. Array[0] is the major version, Array[1] is the minor version + * Array[2] is the patch version. + */ + public int[] getFirmwareVersion(){ + byte[] packet = deviceClient.read(Register.FIRMWARE_VERSION.address,Register.FIRMWARE_VERSION.registerType.lengthBytes); + int[] output = new int[3]; + output[0] = packet[2]; + output[1] = packet[1]; + output[2] = packet[0]; + return output; + } + + /** + * @return Hardware version of the device as a string. + */ + public String getFirmwareVersionString(){ + byte[] packet = deviceClient.read(Register.FIRMWARE_VERSION.address,Register.FIRMWARE_VERSION.registerType.lengthBytes); + int[] output = new int[3]; + output[0] = packet[2]; + output[1] = packet[1]; + output[2] = packet[0]; + return String.format("%d.%d.%d", output[0], output[1], output[2]); + } + + /** + * @return Hardware version of device. Array[0] is the major version, Array[1] is the minor version. + */ + public int[] getHardwareVersion(){ + byte[] packet = deviceClient.read(Register.HARDWARE_VERSION.address,Register.HARDWARE_VERSION.registerType.lengthBytes); + int[] output = new int[2]; + output[0] = packet[1]; + output[1] = packet[0]; + return output; + } + public String getHardwareVersionString(){ + byte[] packet = deviceClient.read(Register.HARDWARE_VERSION.address,Register.HARDWARE_VERSION.registerType.lengthBytes); + int[] output = new int[2]; + output[0] = packet[1]; + output[1] = packet[0]; + return String.format("%d.%d", output[0], output[1]); + } + + /** + * @return The number of times the device has power cycled in its lifetime. + */ + public int getPowerCycleCount(){ + return readInt(Register.POWER_CYCLE_COUNT); + } + + /** + * @return Total device runtime in minutes + */ + public long getRunTime(TimeUnit timeUnit){ + return timeUnit.convert(readInt(Register.RUNTIME_IN_MINUTES),TimeUnit.MINUTES); + } + + /** + * @return total LEDs in strip + */ + public int getNumberOfLEDs(){ + byte[] packet = deviceClient.read(Register.STATUS.address,Register.STATUS.registerType.lengthBytes); + return unsignedByteToInt(packet[0]); + } + + /** + * @return Current Animation frames per second + */ + public int getCurrentFPS(){ + byte[] inputPacket = deviceClient.read(Register.STATUS.address,Register.STATUS.registerType.lengthBytes); + byte[] outputPacket = new byte[4]; + outputPacket[0] = inputPacket[1]; + outputPacket[1] = inputPacket[2]; + + return byteArrayToInt(outputPacket,ByteOrder.LITTLE_ENDIAN); + } + + /** + * @return + */ + public boolean fpsLimited(){ + byte[] packet = deviceClient.read(Register.STATUS.address, Register.STATUS.registerType.lengthBytes); + //return packet[3] >> 7; + return false; + } + + /** + * return What artboard is set as default boot animation? + */ + public int getBootAnimationArtboard(){ + byte[] packet = deviceClient.read(Register.STATUS.address, Register.STATUS.registerType.lengthBytes); + return 0; + } + + + /** + * Inserts an animation into the specified slot in the animations array. + * + * @param height the height where the animation should be inserted (0-9) + * @param animation the AnimationBase object to insert into the array + * @return true if the animation was successfully inserted, false if the index + * is out of bounds or the animation is null + */ + public boolean insertAnimation(LayerHeight height, PrismAnimations.AnimationBase animation) + { + if(height == LayerHeight.DISABLED || animation == null) + return false; + + animations[height.index] = animation; + animations[height.index].layerHeight = height; + return true; + } + + /** + * Inserts an animation at the specified index and immediately updates it over I2C. + * + * @param height the height where the animation should be inserted and updated (0-9) + * @param animation the AnimationBase object to insert and update + * @return true if both insertion and update operations were successful, + * false if either operation failed + */ + public boolean insertAndUpdateAnimation(LayerHeight height, PrismAnimations.AnimationBase animation) + { + if(insertAnimation(height, animation)) + return updateAnimationFromIndex(height, true); + return false; + } + + /** + * Updates all animations in the manager by sending their data over I2C. + * Iterates through all animation slots and updates any non-null animations. + * + * @return true if the update process completed (currently always returns true) + */ + public boolean updateAllAnimations() + { + for(int i = 0; i < MAXIMUM_NUMBER_OF_ANIMATIONS; i++){ + if(animations[i] != null && animations[i].layerHeight != LayerHeight.DISABLED) + animations[i].updateAnimationOverI2C(this.deviceClient, false); + } + return true; + } + + /** + * Updates a specific animation at the given Layer Height by sending its data over I2C. + * + * @param height the height of the animation to update (0-9) + * @return true if the animation was successfully updated, false if the index + * is out of bounds or if no animation exists at the specified index + */ + public boolean updateAnimationFromIndex(LayerHeight height) + { + return updateAnimationFromIndex(height, false); + } + + public void clearAllAnimations() + { + byte[] packet = TypeConversion.intToByteArray(1 << 25, ByteOrder.LITTLE_ENDIAN); + + deviceClient.write(Register.CONTROL.address, packet); + Arrays.fill(animations, null); + } + + public void setTargetFPS(int targetFPS) + { + final int boundedTargetFps = Math.max(0, Math.min(targetFPS, 0x7FFF)); + final int ChangeTargetFpsBit = 1 << 15; + final int ChangeTargetFpsCommand = ChangeTargetFpsBit | boundedTargetFps; + byte[] packet = TypeConversion.intToByteArray(ChangeTargetFpsCommand, ByteOrder.LITTLE_ENDIAN); + deviceClient.write(Register.CONTROL.address, packet); + } + + public void setStripLength(int stripLength) + { + final int boundedStripLength = Math.max(0, Math.min(stripLength, 0xFF)); + final int ChangeStripLengthBit = 1 << 24; + final int ChangeStripLengthCommand = ChangeStripLengthBit | (boundedStripLength << 16); + byte[] packet = TypeConversion.intToByteArray(ChangeStripLengthCommand, ByteOrder.LITTLE_ENDIAN); + deviceClient.write(Register.CONTROL.address, packet); + } + + public void saveCurrentAnimationsToArtboard(Artboard artboard) + { + byte[] data = { + artboard.bitmask, + 0, + 0, + 0 + }; + deviceClient.write(Register.ARTBOARD_CONTROL.address, data); + } + + public void loadAnimationsFromArtboard(Artboard artboard) + { + byte[] data = { + 0, + artboard.bitmask, + 0, + 0 + }; + deviceClient.write(Register.ARTBOARD_CONTROL.address, data); + } + + public void setDefaultBootArtboard(Artboard artboard) + { + byte[] data = { + 0, + 0, + artboard.bitmask, + 0 + }; + deviceClient.write(Register.ARTBOARD_CONTROL.address, data); + } + + public void enableDefaultBootArtboard(boolean enable) + { + byte[] data = { + 0, + 0, + 0, + enable ? (byte)0b00000001 : (byte)0b00000010 + }; + deviceClient.write(Register.ARTBOARD_CONTROL.address, data); + } + + private boolean updateAnimationFromIndex(LayerHeight height, boolean isBeingInserted) + { + if(height == LayerHeight.DISABLED || animations[height.index] == null) + return false; + + boolean animationEnabled = animations[height.index].layerHeight != LayerHeight.DISABLED; + if(animationEnabled) + animations[height.index].updateAnimationOverI2C(this.deviceClient, isBeingInserted); + return animationEnabled; + } +} diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/Prism/GoBildaPrismExample.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/Prism/GoBildaPrismExample.java new file mode 100644 index 000000000000..c844fe416215 --- /dev/null +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/Prism/GoBildaPrismExample.java @@ -0,0 +1,154 @@ +/* MIT License + * Copyright (c) [2025] [Base 10 Assets, LLC] + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.firstinspires.ftc.teamcode; + + +import static org.firstinspires.ftc.teamcode.Prism.GoBildaPrismDriver.LayerHeight; + +import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; +import com.qualcomm.robotcore.eventloop.opmode.TeleOp; + +import org.firstinspires.ftc.teamcode.Prism.Color; +import org.firstinspires.ftc.teamcode.Prism.GoBildaPrismDriver; +import org.firstinspires.ftc.teamcode.Prism.PrismAnimations; + +import java.util.concurrent.TimeUnit; + +/* + * This example file shows how to create a couple of different Animations on the Prism, and save + * them to an Artboard on the device. + * The example file called "GoBildaPrismConfigurator" is designed to create a user-interface + * through your driver's station. This is a great way to create Artboards from Animations, but + * doesn't let you control quite everything about the available Animations. If you'd like to + * do something more complex, or would just prefer to create your Artboard in Java, this + * program shows you how. + * The example file called "GoBildaPrismArtboardExample" shows how to recall different Artboards + * that you have already saved to the device. That file shows the code you should consider adding + * to your OpMode if you would like to dynamically change the Artboard shown by your LEDs during + * the match. + * + * Core to understanding how to use this product is knowing these three terms: + * Animations: (Like RAINBOW or BLINK) - These have properties you can configure, like their color. + * They can have unique start and end points! + * Layers: There are 10 layers, each of which can store an animation. These are hierarchical. + * So an Animation on layer 5 will cover an animation on layer 2 if they overlap. + * You can use start and end points to have layers overlap to create new patterns! Or show multiple + * animations at once on different LEDs. + * Artboards: An Artboard is a set of 10 layers which is stored on the Prism. + * you can have up to 8 unique Artboards. Artboards are easy and computationally fast to switch between. + */ + +@TeleOp(name="Prism Animations Example", group="Linear OpMode") +//@Disabled + +public class GoBildaPrismExample extends LinearOpMode { + + GoBildaPrismDriver prism; + + PrismAnimations.Solid solid = new PrismAnimations.Solid(Color.BLUE); + PrismAnimations.RainbowSnakes rainbowSnakes = new PrismAnimations.RainbowSnakes(); + + @Override + public void runOpMode() { + /* + * Initialize the hardware variables. Note that the strings used here must correspond + * to the names assigned during the robot configuration step on the driver's station. + */ + prism = hardwareMap.get(GoBildaPrismDriver.class,"prism"); + + /* + * Set the number of LEDs (starting at 0) that are in your strip. This can be longer + * than the actual length of the strip, but some animations won't look quite right. + */ + prism.setStripLength(32); + + /* + * Here you can customize the specifics of different animations. Each animation has it's + * own set of parameters that you can customize to create something unique! Each animation + * has carefully selected default parameters. So you do not need to set each parameter + * for every animation! + */ + solid.setBrightness(50); + solid.setStartIndex(0); + solid.setStopIndex(12); + + rainbowSnakes.setNumberOfSnakes(2); + rainbowSnakes.setSnakeLength(3); + rainbowSnakes.setSpacingBetween(6); + rainbowSnakes.setSpeed(0.5f); + + telemetry.addData("Device ID: ", prism.getDeviceID()); + telemetry.addData("Firmware Version: ", prism.getFirmwareVersionString()); + telemetry.addData("Hardware Version: ", prism.getHardwareVersionString()); + telemetry.addData("Power Cycle Count: ", prism.getPowerCycleCount()); + telemetry.update(); + + // Wait for the game to start (driver presses START) + waitForStart(); + resetRuntime(); + + // run until the end of the match (driver presses STOP) + while (opModeIsActive()) { + + + if(gamepad1.aWasPressed()){ + /* + * Here we insert and update the animation to the Prism, this by default does not + * save it to an Artboard, it just starts the Animation playing. If you have + * already inserted an animation at a layer height, you can instead call + * .updateAnimationFromIndex(LayerHeight.LAYER_0) to update an animation at a + * specific layer height without overwriting it completely. + */ + prism.insertAndUpdateAnimation(LayerHeight.LAYER_0, solid); + prism.insertAndUpdateAnimation(LayerHeight.LAYER_1,rainbowSnakes); + } + + if(gamepad1.xWasPressed()){ + /* + * Clearing the animation doesn't erase any saved Artboards, but it removes all the + * currently displayed animations. + */ + prism.clearAllAnimations(); + } + + if(gamepad1.dpadDownWasPressed()){ + /* + * Here we save the animation we are currently displaying to Artboard 0. + */ + prism.saveCurrentAnimationsToArtboard(GoBildaPrismDriver.Artboard.ARTBOARD_0); + } + + telemetry.addLine("Press A to insert and update the created animations."); + telemetry.addLine("Press X to clear current animations."); + telemetry.addLine("Press D-Pad Down to save current animations to Artboard #0"); + telemetry.addLine(); + telemetry.addData("Run Time (Hours): ",prism.getRunTime(TimeUnit.HOURS)); + telemetry.addData("Run Time (Minutes): ",prism.getRunTime(TimeUnit.MINUTES)); + telemetry.addData("Number of LEDS: ", prism.getNumberOfLEDs()); + telemetry.addData("Current FPS: ", prism.getCurrentFPS()); + telemetry.update(); + sleep(50); + } + } + +} diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/Prism/PrismAnimations.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/Prism/PrismAnimations.java new file mode 100644 index 000000000000..6474bc42f2fe --- /dev/null +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/Prism/PrismAnimations.java @@ -0,0 +1,1069 @@ +/* MIT License + * Copyright (c) [2025] [Base 10 Assets, LLC] + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.firstinspires.ftc.teamcode.Prism; + +import static org.firstinspires.ftc.teamcode.Prism.GoBildaPrismDriver.LayerHeight; + +import com.qualcomm.robotcore.hardware.I2cDeviceSynchSimple; +import com.qualcomm.robotcore.util.TypeConversion; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.concurrent.TimeUnit; + +public class PrismAnimations { + public enum AnimationType{ + NONE(0), + SOLID(1), + BLINK(2), + PULSE(3), + SINE_WAVE(4), + DROID_SCAN(5), + RAINBOW(6), + SNAKES(7), + RANDOM(8), + SPARKLE(9), + SINGLE_FILL(10), + RAINBOW_SNAKES(11), + POLICE_LIGHTS(12); + + public final int AnimationTypeIndex; + AnimationType(int animationType){ + this.AnimationTypeIndex = animationType; + } + } + + public static abstract class AnimationBase { + protected final AnimationType animationType; + protected int brightness = 100; + protected int startIndex = 0; + protected int stopIndex = 255; + protected LayerHeight layerHeight; + + //region Constructors + protected AnimationBase(AnimationType type){ + this.animationType = type; + } + + /** + * @param brightness from 0 to 100. + */ + protected AnimationBase(AnimationType type, int brightness) { + this(type); + this.brightness = Math.min(brightness, 100); + } + + /** + * Set both the first, and last LED in a string that you'd like this animation to show on. + * @param startIndex from 0 to 255. + * @param stopIndex from 0 to 255. + */ + protected AnimationBase(AnimationType type, int startIndex, int stopIndex) { + this(type); + this.startIndex = Math.min(startIndex, 255); + this.stopIndex = Math.min(stopIndex, 255); + } + + /** + * @param brightness from 0 to 100. + * @param startIndex from 0 to 255. + * @param stopIndex from 0 to 255. + */ + protected AnimationBase(AnimationType type, int brightness, int startIndex, int stopIndex){ + this(type, brightness); + this.startIndex = Math.min(startIndex, 255); + this.stopIndex = Math.min(stopIndex, 255); + } + + /** + * @param brightness from 0 to 100. + * @param startIndex from 0 to 255. + * @param stopIndex from 0 to 255. + * @param layerHeight the height that this animation should sit at from 0 to 9. + */ + protected AnimationBase(AnimationType type, int brightness, int startIndex, int stopIndex, LayerHeight layerHeight){ + this(type, brightness, startIndex, stopIndex); + this.layerHeight = layerHeight; + } + //endregion + + //region Setters / Getters + public int getBrightness() { return brightness; } + public int getStartIndex() { return startIndex; } + public int getStopIndex() { return stopIndex; } + + /** + * Brightness of selected animation. + * @param brightness From 0 to 100. + */ + public void setBrightness(int brightness) { this.brightness = Math.min(brightness, 100); } + + /** + * The first LED in the string that you'd like this animation to display on. + * @param startIndex from 0 to 255. + */ + public void setStartIndex(int startIndex) { this.startIndex = Math.min(startIndex, 255); } + + /** + * The last LED in the string that you'd like this animation to display on. + * @param stopIndex from 0 to 255. + */ + public void setStopIndex(int stopIndex) { this.stopIndex = Math.min(stopIndex, 255); } + + /** + * Set both the first, and last LED in a string that you'd like this animation to show on. + * @param startIndex from 0 to 255. + * @param stopIndex from 0 to 255. + */ + public void setIndexes(int startIndex, int stopIndex){ + this.startIndex = (byte)startIndex; + this.stopIndex = (byte)stopIndex; + } + //endregion + + protected abstract void updateAnimationSpecificValuesOverI2C(I2cDeviceSynchSimple deviceClient); + + protected void updateAnimationOverI2C(I2cDeviceSynchSimple deviceClient, boolean isInsertingAnimation) + { + if(isInsertingAnimation) + deviceClient.write(layerHeight.register.address, GetByteArray(0, animationType.AnimationTypeIndex)); + deviceClient.write(layerHeight.register.address, GetByteArray(1, brightness)); + deviceClient.write(layerHeight.register.address, GetByteArray(2, startIndex)); + deviceClient.write(layerHeight.register.address, GetByteArray(3, stopIndex)); + updateAnimationSpecificValuesOverI2C(deviceClient); + } + + protected byte[] floatToByteArray (float value, ByteOrder byteOrder) + { + return ByteBuffer.allocate(4).order(byteOrder).putFloat(value).array(); + } + + private byte unsignedIntToByte(int data) + { + int boundedData = Math.min(Math.max(data, 0), 255); + ByteBuffer buffer = ByteBuffer.allocate(4).putInt(boundedData); + + return buffer.array()[3]; + } + + protected byte[] GetByteArray(int subRegister, Color color) + { + return new byte[] { + (byte)Math.max(0, Math.min(subRegister, 12)), + (byte)color.red, + (byte)color.green, + (byte)color.blue + }; + } + + protected byte[] GetByteArray(int subRegister, int data) + { + byte[] packet = TypeConversion.intToByteArray(data, ByteOrder.LITTLE_ENDIAN); + return new byte[] { + (byte)Math.max(0, Math.min(subRegister, 12)), + packet[0], + packet[1], + packet[2], + packet[3] + }; + } + + protected byte[] GetByteArray(int subRegister, float data) + { + byte[] packet = floatToByteArray(data, ByteOrder.LITTLE_ENDIAN); + return new byte[] { + (byte)Math.max(0, Math.min(subRegister, 12)), + packet[0], + packet[1], + packet[2], + packet[3] + }; + } + + protected byte[] GetByteArray(int subRegister, byte data) + { + return new byte[]{ + (byte)subRegister, + data + }; + } + + protected byte[] GetByteArray(int subRegister, Direction direction) + { + boolean bool = direction == Direction.Forward; + return new byte[]{ + (byte)Math.max(0, Math.min(subRegister, 12)), + (byte) (bool ? 1 : 0) + }; + } + + protected byte[] GetByteArray(int subRegister, Color... colors) + { + byte[] array = new byte[1+(colors.length*3)]; + + array[0] = (byte)Math.max(0, Math.min(subRegister, 12)); + + for(int i = 0; i < colors.length; i++){ + array[1+(i*3)] = unsignedIntToByte(colors[i].red); + array[1+(i*3+1)] = unsignedIntToByte(colors[i].green); + array[1+(i*3+2)] = unsignedIntToByte(colors[i].blue); + } + + return array; + } + } + + public static class Blink extends AnimationBase{ + private Color primaryColor = Color.BLUE; + private Color secondaryColor = Color.RED; + private int period = 2000; + private int primaryColorPeriod = 1000; + + public Blink(){ super(AnimationType.BLINK); } + public Blink(Color primaryColor) { + this(); + this.primaryColor = primaryColor; + } + public Blink(Color primaryColor, Color secondaryColor) { + this(primaryColor); + this.secondaryColor = secondaryColor; + } + public Blink(Color primaryColor, Color secondaryColor, int period) { + this(primaryColor, secondaryColor); + this.period = period; + } + public Blink(Color primaryColor, Color secondaryColor, int period, int primaryColorPeriod) { + this(primaryColor, secondaryColor, period); + this.primaryColorPeriod = primaryColorPeriod; + } + + /** + * set the length of one loop of an animation in milliseconds. + * @param period from 0 - 4,294,697,295. Larger is longer. + */ + public void setPeriod(int period) { this.period = period; } + + /** + * set the length of one loop of an animation in specified unit. + * @param duration duration of loop. + * @param timeUnit unit to use. + */ + public void setPeriod(int duration, TimeUnit timeUnit){ + this.period = Math.toIntExact(timeUnit.toMillis(duration)); + } + public void setPrimaryColor(Color color) { primaryColor = color; } + public void setSecondaryColor(Color color) { secondaryColor = color; } + public void setPrimaryColor(int red, int green, int blue) { primaryColor = new Color(red, green, blue); } + public void setSecondaryColor(int red, int green, int blue) { secondaryColor = new Color(red, green, blue); } + /** + * set the duration of the primary color in a blink. + * @param primaryColorPeriod from 0 - 4,294,697,295. Larger is longer. + */ + public void setPrimaryColorPeriod(int primaryColorPeriod) { this.primaryColorPeriod = primaryColorPeriod; } + + /** + * set the duration of the primary color in a blink. + * @param duration duration of loop. + * @param timeUnit unit to use. + */ + public void setPrimaryColorPeriod(int duration, TimeUnit timeUnit){ + this.primaryColorPeriod = Math.toIntExact(timeUnit.toMillis(duration)); + } + + public int getPeriod() { return period; } + public Color getPrimaryColor() { return primaryColor; } + public Color getSecondaryColor() { return secondaryColor; } + public int getPrimaryColorPeriod() { return primaryColorPeriod; } + + @Override + protected void updateAnimationSpecificValuesOverI2C(I2cDeviceSynchSimple deviceClient) + { + deviceClient.write(layerHeight.register.address, GetByteArray(4, primaryColor)); + deviceClient.write(layerHeight.register.address, GetByteArray(5, secondaryColor)); + deviceClient.write(layerHeight.register.address, GetByteArray(6, period)); + deviceClient.write(layerHeight.register.address, GetByteArray(7, primaryColorPeriod)); + } + } + + public static class DroidScan extends AnimationBase{ + private Color primaryColor = Color.RED; + private Color secondaryColor = Color.TRANSPARENT; + private DroidScanStyle style = DroidScanStyle.BOTH_TAIL; + private float speed = .4f; + private int eyeWidth = 3; + private int trailWidth = 3; + + public enum DroidScanStyle{ + NO_TAIL(0), + FRONT_TAIL(1), + BACK_TAIL(2), + BOTH_TAIL(3); + + /* Package Private */ final byte styleValue; + DroidScanStyle(int value){ + styleValue = (byte)value; + } + } + + public DroidScan(){ super(AnimationType.DROID_SCAN); } + public DroidScan(Color primaryColor) { + this(); + this.primaryColor = primaryColor; + } + public DroidScan(Color primaryColor, Color secondaryColor) { + this(primaryColor); + this.secondaryColor = secondaryColor; + } + + public void setSecondaryColor(Color color) { this.secondaryColor = color; } + public void setPrimaryColor(Color color) { this.primaryColor = color; } + + /** + * Sets the speed of the animation + * @param speed from 0 to 1. + */ + public void setSpeed(float speed) { this.speed = speed; } + + /** + * Sets the width of the "eye" in the animation in pixels + * @param eyeWidth from 0 to 255. + */ + public void setEyeWidth(int eyeWidth) { this.eyeWidth = eyeWidth; } + + /** + * Sets the width of the trail of the eye in the animation in pixels + * @param trailWidth from 0 to 255. + */ + public void setTrailWidth(int trailWidth) { this.trailWidth = trailWidth; } + public void setDroidScanStyle(DroidScanStyle style) { this.style = style; } + + public Color getSecondaryColor() { return secondaryColor; } + public Color getPrimaryColor() { return primaryColor; } + public float getSpeed() { return speed; } + public int getEyeWidth() { return eyeWidth; } + public int getTrailWidth() { return trailWidth; } + public DroidScanStyle getDroidScanStyle() { return style; } + + @Override + protected void updateAnimationSpecificValuesOverI2C(I2cDeviceSynchSimple deviceClient) + { + deviceClient.write(layerHeight.register.address, GetByteArray(4 , primaryColor)); + deviceClient.write(layerHeight.register.address, GetByteArray(5 , secondaryColor)); + deviceClient.write(layerHeight.register.address, GetByteArray(6 , speed)); + deviceClient.write(layerHeight.register.address, GetByteArray(7 , (byte)eyeWidth)); + deviceClient.write(layerHeight.register.address, GetByteArray(8 , (byte)trailWidth)); + deviceClient.write(layerHeight.register.address, GetByteArray(11, style.styleValue)); + } + } + + public static class PoliceLights extends AnimationBase { + private Color red = Color.RED; + private Color white = Color.WHITE; + private Color blue = Color.BLUE; + private int period = 1000; + private PoliceLightsStyle style = PoliceLightsStyle.Style1; + + public enum PoliceLightsStyle{ + Style1(0), + Style2(1), + Style3(2); + + public byte styleValue; + PoliceLightsStyle(int value){ + styleValue = (byte)value; + } + } + + public PoliceLights(){ super(AnimationType.POLICE_LIGHTS); } + + /** + * set the length of one loop of an animation in milliseconds. + * @param period from 0 - 4,294,697,295. Larger is longer. + */ + public void setPeriod(int period) { this.period = period; } + + /** + * set the length of one loop of an animation in specified unit. + * @param duration duration of loop. + * @param timeUnit unit to use. + */ + public void setPeriod(int duration, TimeUnit timeUnit){ + this.period = Math.toIntExact(timeUnit.toMillis(duration)); + } + public void setPoliceLightsStyle(PoliceLightsStyle style){this.style = style;} + public void setPrimaryColor(Color color){this.red = color;} + public void setSecondaryColor(Color color){this.white = color;} + public void setTertiaryColor(Color color){this.blue = color;} + + public PoliceLightsStyle getPoliceLightsStyle(){return style;} + public Color getPrimaryColor(){return this.red;} + public Color getSecondaryColor(){return this.white;} + public Color getTertiaryColor(){return this.blue;} + + @Override + protected void updateAnimationSpecificValuesOverI2C(I2cDeviceSynchSimple deviceClient) + { + deviceClient.write(layerHeight.register.address, GetByteArray(6 , period)); + deviceClient.write(layerHeight.register.address, GetByteArray(7 , red)); + deviceClient.write(layerHeight.register.address, GetByteArray(8 , white)); + deviceClient.write(layerHeight.register.address, GetByteArray(9 , blue)); + deviceClient.write(layerHeight.register.address, GetByteArray(11, style.styleValue)); + } + } + + public static class Pulse extends AnimationBase { + private Color primaryColor = Color.GREEN; + private Color secondaryColor = Color.RED; + private int period = 1000; + + public Pulse(){ super(AnimationType.PULSE); } + public Pulse(Color primaryColor) { + this(); + this.primaryColor = primaryColor; + } + public Pulse(Color primaryColor, Color secondaryColor) { + this(primaryColor); + this.secondaryColor = secondaryColor; + } + public Pulse(Color primaryColor, Color secondaryColor, int period) { + this(primaryColor, secondaryColor); + this.period = period; + } + + /** + * set the period of the pulse in milliseconds. + * @param period from 0 - 4,294,697,295. Larger is longer. + */ + public void setPeriod(int period) { this.period = period; } + + /** + * set the period of the pulse in specified unit. + * @param duration duration of loop. + * @param timeUnit unit to use. + */ + public void setPeriod(int duration, TimeUnit timeUnit){ + this.period = Math.toIntExact(timeUnit.toMillis(duration)); + } + public void setPrimaryColor(Color color) { primaryColor = color; } + public void setSecondaryColor(Color color) { secondaryColor = color; } + public void setPrimaryColor(int red, int green, int blue) { primaryColor = new Color((byte)red, (byte)green, (byte)blue); } + public void setSecondaryColor(int red, int green, int blue) { secondaryColor = new Color((byte)red, (byte)green, (byte)blue); } + + public int getPeriod() { return period; } + public Color getPrimaryColor() { return primaryColor; } + public Color getSecondaryColor() { return secondaryColor; } + + @Override + protected void updateAnimationSpecificValuesOverI2C(I2cDeviceSynchSimple deviceClient) + { + deviceClient.write(layerHeight.register.address, GetByteArray(4, primaryColor)); + deviceClient.write(layerHeight.register.address, GetByteArray(5, secondaryColor)); + deviceClient.write(layerHeight.register.address, GetByteArray(6, period)); + } + } + + public static class Rainbow extends AnimationBase { + private float startHue = 0.0f; + private float stopHue = 360.0f; + private float speed = 0.50f; // 0.00 - 1.00 + private int repeatAfter = 25; + private Direction direction = Direction.Forward; + + public Rainbow(){ super(AnimationType.RAINBOW); } + public Rainbow(float startHue) { + this(); + this.startHue = startHue; + } + public Rainbow(float startHue, float stopHue){ + this(startHue); + this.stopHue = stopHue; + } + public Rainbow(float startHue, float stopHue, float speed){ + this(startHue, stopHue); + this.speed = speed; + } + public Rainbow(float startHue, float stopHue, float speed, Direction direction){ + this(startHue, stopHue, speed); + this.direction = direction; + } + public Rainbow(float startHue, float stopHue, float speed, Direction direction, int repeatAfter){ + this(startHue, stopHue, speed, direction); + this.repeatAfter = repeatAfter; + } + + //region Getters/Setters + public float getSpeed() { return speed;} + public float getStopHue() { return stopHue; } + public float getStartHue() { return startHue; } + public Direction getDirection() { return direction; } + public int getRepeatAfter() { return repeatAfter; } + + /** + * Sets the speed of the animation + * @param speed from 0 to 1. + */ + public void setSpeed(float speed) { this.speed = speed; } + + /** + * Can be used to limit the colors shown in the rainbow. This is a hue so it reflects + * the position as an angle on a color wheel. Red is 0, Green is 120, Blue is 240, etc. + * @param startHue from 0 to 360. + */ + public void setStartHue(float startHue){ this.startHue = startHue; } + + /** + * Can be used to limit the colors shown in the rainbow. This is a hue so it reflects + * the position as an angle on a color wheel. Red is 0, Green is 120, Blue is 240, etc. + * @param stopHue from 0 to 360. + */ + public void setStopHue(float stopHue) { this.stopHue = stopHue; } + public void setDirection(Direction direction) { this.direction = direction; } + + /** + * The number of pixels before the rainbow repeats. Default is 20. + * @param repeatAfter From 0 to 255. + */ + public void setRepeatAfter(int repeatAfter) { this.repeatAfter = repeatAfter; } + public void setHues(float startHue, float stopHue) { + this.startHue = startHue; + this.stopHue = stopHue; + } + //endregion + @Override + protected void updateAnimationSpecificValuesOverI2C(I2cDeviceSynchSimple deviceClient) + { + deviceClient.write(layerHeight.register.address, GetByteArray(4, startHue)); + deviceClient.write(layerHeight.register.address, GetByteArray(5, stopHue)); + deviceClient.write(layerHeight.register.address, GetByteArray(6, speed)); + deviceClient.write(layerHeight.register.address, GetByteArray(7, direction)); + deviceClient.write(layerHeight.register.address, GetByteArray(9, (byte)repeatAfter)); + } + } + + public static class RainbowSnakes extends AnimationBase { + private int numberOfSnakes = 3; + private int snakeLength = 5; + private int spacingBetween = 2; + private int repeatAfter = 15; + private float startHue = 0.0f; + private float stopHue = 360.0f; + private Color backgroundColor = Color.TRANSPARENT; + private float speed = 0.50f; + private Direction direction = Direction.Backward; + + //region Constructors + public RainbowSnakes(){ super(AnimationType.RAINBOW_SNAKES); } + public RainbowSnakes(float startHue, float stopHue) { + this(); + this.startHue = startHue; + this.stopHue = stopHue; + } + public RainbowSnakes(float startHue, float stopHue, int numberOfSnakes){ + this(startHue, stopHue); + this.numberOfSnakes = (byte)numberOfSnakes; + } + public RainbowSnakes(float startHue, float stopHue, int numberOfSnakes, int snakeLength){ + this(startHue, stopHue, numberOfSnakes); + this.snakeLength = (byte)snakeLength; + } + public RainbowSnakes(float startHue, float stopHue, int numberOfSnakes, int snakeLength, int spacingBetween){ + this(startHue, stopHue, numberOfSnakes, snakeLength); + this.spacingBetween = (byte)spacingBetween; + } + public RainbowSnakes(float startHue, float stopHue, int numberOfSnakes, int snakeLength, int spacingBetween, int repeatAfter){ + this(startHue, stopHue, numberOfSnakes, snakeLength, spacingBetween); + this.repeatAfter = (byte)repeatAfter; + } + public RainbowSnakes(float startHue, float stopHue, int numberOfSnakes, int snakeLength, int spacingBetween, int repeatAfter, Color backgroundColor){ + this(startHue, stopHue, numberOfSnakes, snakeLength, spacingBetween, repeatAfter); + this.backgroundColor = backgroundColor; + } + public RainbowSnakes(float startHue, float stopHue, int numberOfSnakes, int snakeLength, int spacingBetween, int repeatAfter, Color backgroundColor, float speed){ + this(startHue, stopHue, numberOfSnakes, snakeLength, spacingBetween, repeatAfter, backgroundColor); + this.speed = speed; + } + public RainbowSnakes(float startHue, float stopHue, int numberOfSnakes, int snakeLength, int spacingBetween, int repeatAfter, Color backgroundColor, float speed, Direction direction){ + this(startHue, stopHue, numberOfSnakes, snakeLength, spacingBetween, repeatAfter, backgroundColor, speed); + this.direction = direction; + } + //endregion + + //region Getters/Setters + public float getSpeed() { return speed; } + public float getStartHue() { return startHue;} + public float getStopHue() { return stopHue; } + public int getSnakeLength() { return snakeLength; } + public int getRepeatAfter() { return repeatAfter; } + public Direction getDirection() { return direction; } + public int getSpacingBetween() { return spacingBetween; } + public Color getBackgroundColor() { return backgroundColor; } + public int getNumberOfSnakes() { return numberOfSnakes; } + + /** + * Sets the speed of the animation + * @param speed from 0 to 1. + */ + public void setSpeed(float speed) { this.speed = speed; } + + /** + * Can be used to limit the colors shown in the rainbow. This is a hue so it reflects + * the position as an angle on a color wheel. Red is 0, Green is 120, Blue is 240, etc. + * @param startHue from 0 to 360. + */ + public void setStartHue(float startHue) { this.startHue = startHue; } + + /** + * Can be used to limit the colors shown in the rainbow. This is a hue so it reflects + * the position as an angle on a color wheel. Red is 0, Green is 120, Blue is 240, etc. + * @param stopHue from 0 to 360. + */ + public void setStopHue(float stopHue) { this.stopHue = stopHue; } + public void setHues(float startHue, float stopHue) { + this.startHue = startHue; + this.stopHue = stopHue; + } + public void setDirection(Direction direction) { this.direction = direction; } + + /** + * Length in pixels of each snake. + * @param snakeLength from 0 to 255. + */ + public void setSnakeLength(int snakeLength) { this.snakeLength = snakeLength; } + + /** + * The number of pixels between the last snake and the first snake of a repeating animation. + * @param repeatAfter from 0 to 255. + */ + public void setRepeatAfter(int repeatAfter) { this.repeatAfter = repeatAfter; } + + /** + * The number of pixels between consecutive snakes. + * @param spacingBetween from 0 to 255. + */ + public void setSpacingBetween(int spacingBetween) { this.spacingBetween = spacingBetween; } + public void setBackgroundColor(Color backgroundColor) { this.backgroundColor = backgroundColor; } + public void setNumberOfSnakes(int numberOfSnakes) { this.numberOfSnakes = numberOfSnakes; } + //endregion + + @Override + protected void updateAnimationSpecificValuesOverI2C(I2cDeviceSynchSimple deviceClient) + { + deviceClient.write(layerHeight.register.address, GetByteArray(4 , startHue)); + deviceClient.write(layerHeight.register.address, GetByteArray(5 , stopHue)); + deviceClient.write(layerHeight.register.address, GetByteArray(6 , speed)); + deviceClient.write(layerHeight.register.address, GetByteArray(7 , direction)); + deviceClient.write(layerHeight.register.address, GetByteArray(8 , (byte)spacingBetween)); + deviceClient.write(layerHeight.register.address, GetByteArray(9 , (byte)repeatAfter)); + deviceClient.write(layerHeight.register.address, GetByteArray(10, backgroundColor)); + deviceClient.write(layerHeight.register.address, GetByteArray(11, (byte)snakeLength)); + deviceClient.write(layerHeight.register.address, GetByteArray(12, (byte)numberOfSnakes)); + } + } + + public static class Random extends AnimationBase { + private float startHue = 0f; + private float stopHue = 360f; + private float speed = 0.1f; + + //region Constructors + public Random(){ super(AnimationType.RANDOM); } + public Random(float startHue, float stopHue){ + this(); + setHues(startHue, stopHue); + } + public Random(float startHue, float stopHue, float speed){ + this(startHue, stopHue); + this.speed = speed; + } + //endregion + + //region Getters/Setters + public float getSpeed() { return speed; } + public float getStopHue() { return stopHue; } + public float getStartHue() { return startHue; } + + /** + * Sets the speed of the animation + * @param speed from 0 to 1. + */ + public void setSpeed(float speed) { this.speed = speed; } + + /** + * Can be used to limit the colors shown in the rainbow. This is a hue so it reflects + * the position as an angle on a color wheel. Red is 0, Green is 120, Blue is 240, etc. + * @param startHue from 0 to 360. + */ + public void setStartHue(float startHue) { this.startHue = startHue; } + + /** + * Can be used to limit the colors shown in the rainbow. This is a hue so it reflects + * the position as an angle on a color wheel. Red is 0, Green is 120, Blue is 240, etc. + * @param stopHue from 0 to 360. + */ + public void setStopHue(float stopHue) { this.stopHue = stopHue; } + public void setHues(float startHue, float stopHue) { + this.startHue = startHue; + this.stopHue = stopHue; + } + //endregion + @Override + protected void updateAnimationSpecificValuesOverI2C(I2cDeviceSynchSimple deviceClient) + { + deviceClient.write(layerHeight.register.address, GetByteArray(4, startHue)); + deviceClient.write(layerHeight.register.address, GetByteArray(5, stopHue)); + deviceClient.write(layerHeight.register.address, GetByteArray(6, speed)); + } + } + + public static class SineWave extends AnimationBase { + private Color secondaryColor = Color.BLUE; + private Color primaryColor = Color.RED; + private Direction direction = Direction.Forward; + private float offset = 0.5f; + private float speed = 0.5f; + private int period = 1000; + + public SineWave(){ super(AnimationType.SINE_WAVE); } + public SineWave(Color primaryColor) { + this(); + this.primaryColor = primaryColor; + } + public SineWave(Color primaryColor, Color secondaryColor) { + this(primaryColor); + this.secondaryColor = secondaryColor; + } + public SineWave(Color primaryColor, Color secondaryColor, int period) { + this(primaryColor, secondaryColor); + this.period = period; + } + public SineWave(Color primaryColor, Color secondaryColor, int period, float speed) { + this(primaryColor, secondaryColor, period); + this.speed = speed; + } + public SineWave(Color primaryColor, Color secondaryColor, int period, float speed, float offset) { + this(primaryColor, secondaryColor, period, speed); + this.offset = offset; + } + public SineWave(Color primaryColor, Color secondaryColor, int period, float speed, float offset, Direction direction) { + this(primaryColor, secondaryColor, period, speed, offset); + this.direction = direction; + } + + /** + * set the period of the sine wave in milliseconds. + * @param period from 0 - 4,294,697,295. Larger is longer. + */ + public void setPeriod(int period) { this.period = period; } + + /** + * set the period of the sine wave in specified unit. + * @param duration duration of loop. + * @param timeUnit unit to use. + */ + public void setPeriod(int duration, TimeUnit timeUnit){ + this.period = Math.toIntExact(timeUnit.toMillis(duration)); + } + + /** + * Sets the speed of the animation + * @param speed from 0 to 1. + */ + public void setSpeed(float speed) { this.speed = speed; } + + /** + * Sets the vertical height of the sine wave, this will increase the contrast between the + * two shown colors. + * @param offset from 0 to 1. + */ + public void setOffset(float offset) { this.offset = offset; } + public void setPrimaryColor(Color color) { primaryColor = color; } + public void setSecondaryColor(Color color) { secondaryColor = color; } + public void setDirection(Direction direction) { this.direction = direction; } + public void setPrimaryColor(int red, int green, int blue) { primaryColor = new Color((byte)red, (byte)green, (byte)blue); } + public void setSecondaryColor(int red, int green, int blue) { secondaryColor = new Color((byte)red, (byte)green, (byte)blue); } + + public int getPeriod() { return period; } + public float getSpeed() { return speed; } + public float getOffset(){ return offset; } + public Direction getDirection() { return direction; } + public Color getPrimaryColor() { return primaryColor; } + public Color getSecondaryColor() { return secondaryColor; } + + @Override + protected void updateAnimationSpecificValuesOverI2C(I2cDeviceSynchSimple deviceClient) + { + deviceClient.write(layerHeight.register.address, GetByteArray(4, primaryColor)); + deviceClient.write(layerHeight.register.address, GetByteArray(5, secondaryColor)); + deviceClient.write(layerHeight.register.address, GetByteArray(6, period)); + deviceClient.write(layerHeight.register.address, GetByteArray(7, direction)); + deviceClient.write(layerHeight.register.address, GetByteArray(8, offset)); + deviceClient.write(layerHeight.register.address, GetByteArray(9, speed)); + } + } + + public static class SingleFill extends AnimationBase { + private Color[] colors = {Color.WHITE, Color.GREEN, Color.BLUE}; + private int period = 750; + private float speed = 0.5f; + private int pixelLength = 3; + private Direction direction = Direction.Backward; + private SingleFillStyle style = SingleFillStyle.FillIn; + + public enum SingleFillStyle{ + FillIn(0), + FillOut(1); + + final byte styleValue; + SingleFillStyle(int styleValue){ this.styleValue = (byte)styleValue; } + } + + public SingleFill(){ super(AnimationType.SINGLE_FILL); } + public SingleFill(Color... colors) { + this(); + this.colors = colors; + } + + public int getPeriod() { return period; } + public float getSpeed() { return speed; } + public Color[] getColors() { return colors; } + public int getPixelLength() { return pixelLength; } + public Direction getDirection() { return direction; } + public SingleFillStyle getStyle() { return style; } + + /** + * set the period of the single fill in milliseconds. + * @param period from 0 - 4,294,697,295. Larger is longer. + */ + public void setPeriod(int period) { this.period = period; } + + /** + * set the period of the single fill in specified unit. + * @param duration duration of loop. + * @param timeUnit unit to use. + */ + public void setPeriod(int duration, TimeUnit timeUnit){ + this.period = Math.toIntExact(timeUnit.toMillis(duration)); + } + + /** + * Sets the speed of the animation + * @param speed from 0 to 1. + */ + public void setSpeed(float speed) { this.speed = speed; } + public void setColors(Color... colors) { + for(int i = 0; i < Math.min(colors.length,10); i++){ + this.colors[i] = colors[i]; + } + } + public void setPixelLength(int pixelLength) { this.pixelLength = pixelLength; } + public void setStyle(SingleFillStyle style) { this.style = style; } + public void setDirection(Direction direction) { this.direction = direction; } + + @Override + protected void updateAnimationSpecificValuesOverI2C(I2cDeviceSynchSimple deviceClient) + { + deviceClient.write(layerHeight.register.address, GetByteArray(4, (byte)colors.length)); + deviceClient.write(layerHeight.register.address, GetByteArray(5, colors)); + deviceClient.write(layerHeight.register.address, GetByteArray(6, period)); + deviceClient.write(layerHeight.register.address, GetByteArray(7, direction)); + deviceClient.write(layerHeight.register.address, GetByteArray(8, (byte)pixelLength)); + deviceClient.write(layerHeight.register.address, GetByteArray(9, speed)); + deviceClient.write(layerHeight.register.address, GetByteArray(11, style.styleValue)); + } + } + + public static class Snakes extends AnimationBase { + private Color[] colors = {Color.RED, Color.WHITE, Color.BLUE}; + private int snakeLength = 5; + private int spacingBetween = 2; + private int repeatAfter = 15; + private Color backgroundColor = Color.TRANSPARENT; + private float speed = 0.50f; + private Direction direction = Direction.Backward; + + //region Constructors + public Snakes(){ super(AnimationType.SNAKES); } + public Snakes(Color... colors) { + this(); + this.colors = colors; + } + public Snakes(int snakeLength, Color... colors){ + this(colors); + this.snakeLength = (byte)snakeLength; + } + public Snakes(int snakeLength, int spacingBetween, Color... colors){ + this(snakeLength, colors); + this.spacingBetween = (byte)spacingBetween; + } + public Snakes(int snakeLength, int spacingBetween, int repeatAfter, Color... colors){ + this(snakeLength, spacingBetween, colors); + this.repeatAfter = (byte)repeatAfter; + } + public Snakes(int snakeLength, int spacingBetween, int repeatAfter, Color backgroundColor, Color... colors){ + this(snakeLength, spacingBetween, repeatAfter, colors); + this.backgroundColor = backgroundColor; + } + public Snakes(int snakeLength, int spacingBetween, int repeatAfter, Color backgroundColor, float speed, Color... colors){ + this(snakeLength, spacingBetween, repeatAfter, backgroundColor, colors); + this.speed = speed; + } + public Snakes(int snakeLength, int spacingBetween, int repeatAfter, Color backgroundColor, float speed, Direction direction, Color... colors){ + this(snakeLength, spacingBetween, repeatAfter, backgroundColor, speed, colors); + this.direction = direction; + } + //endregion + + //region Getters/Setters + public float getSpeed() { return speed; } + public Color[] getColors() { return colors; } + public int getSnakeLength() { return snakeLength; } + public int getRepeatAfter() { return repeatAfter; } + public Direction getDirection() { return direction; } + public int getSpacingBetween() { return spacingBetween; } + public Color getBackgroundColor() { return backgroundColor; } + + /** + * Sets the speed of the animation. + * @param speed from 0 to 1. + */ + public void setSpeed(float speed) { this.speed = speed; } + public void setColors(Color... colors) { this.colors = colors;} + public void setDirection(Direction direction) { this.direction = direction; } + + /** + * Length in pixels of each snake. + * @param snakeLength from 0 to 255. + */ + public void setSnakeLength(int snakeLength) { this.snakeLength = snakeLength; } + + /** + * The number of pixels between the last snake and the first snake of a repeating animation. + * @param repeatAfter from 0 to 255. + */ + public void setRepeatAfter(int repeatAfter) { this.repeatAfter = repeatAfter; } + + /** + * The number of pixels between consecutive snakes. + * @param spacingBetween from 0 to 255. + */ + public void setSpacingBetween(int spacingBetween) { this.spacingBetween = spacingBetween; } + public void setBackgroundColor(Color backgroundColor) { this.backgroundColor = backgroundColor; } + //endregion + @Override + protected void updateAnimationSpecificValuesOverI2C(I2cDeviceSynchSimple deviceClient) + { + deviceClient.write(layerHeight.register.address, GetByteArray(4, (byte)colors.length)); + deviceClient.write(layerHeight.register.address, GetByteArray(5, colors)); + deviceClient.write(layerHeight.register.address, GetByteArray(6, speed)); + deviceClient.write(layerHeight.register.address, GetByteArray(7, direction)); + deviceClient.write(layerHeight.register.address, GetByteArray(8, (byte)spacingBetween)); + deviceClient.write(layerHeight.register.address, GetByteArray(9, (byte)repeatAfter)); + deviceClient.write(layerHeight.register.address, GetByteArray(10, backgroundColor)); + deviceClient.write(layerHeight.register.address, GetByteArray(11, (byte)snakeLength)); + } + } + + public static class Solid extends AnimationBase { + private Color primaryColor = Color.RED; + + public Solid(){super(AnimationType.SOLID);} + public Solid(Color primaryColor) { + super(AnimationType.SOLID); + this.primaryColor = primaryColor; + } + public Solid(Color primaryColor, int brightness) { + super(AnimationType.SOLID, brightness); + this.primaryColor = primaryColor; + } + public Solid(Color primaryColor, int startIndex, int stopIndex){ + super(AnimationType.SOLID, startIndex, stopIndex); + this.primaryColor = primaryColor; + } + public Solid(Color primaryColor, int brightness, int startIndex, int stopIndex){ + super(AnimationType.SOLID, brightness, startIndex, stopIndex); + this.primaryColor = primaryColor; + } + + public void setPrimaryColor(int red, int green, int blue) { primaryColor = new Color((byte)red, (byte)green, (byte)blue); } + public void setPrimaryColor(Color color) { primaryColor = color; } + public Color getPrimaryColor() { return primaryColor; } + + @Override + protected void updateAnimationSpecificValuesOverI2C(I2cDeviceSynchSimple deviceClient) + { + deviceClient.write(layerHeight.register.address, GetByteArray(4, primaryColor)); + } + } + + public static class Sparkle extends AnimationBase { + private Color primaryColor = Color.WHITE; + private Color secondaryColor = Color.TRANSPARENT; + private int sparkleProbability = 16; + private int period = 100; + + //region Constructors + public Sparkle() { super(AnimationType.SPARKLE); } + public Sparkle(Color primaryColor){ + this(); + this.primaryColor = primaryColor; + } + public Sparkle(Color primaryColor, Color secondaryColor){ + this(primaryColor); + this.secondaryColor = secondaryColor; + } + public Sparkle(Color primaryColor, Color secondaryColor, int period){ + this(primaryColor, secondaryColor); + this.period = period; + } + //endregion + + //region Getters/Setters + public int getPeriod() { return period; } + public Color getPrimaryColor() { return primaryColor; } + public Color getSecondaryColor() { return secondaryColor; } + public int getSparkleProbability() { return sparkleProbability; } + + /** + * sets the time between updating the sparkles. + * @param period from 0 - 4,294,697,295. Larger is longer. + */ + public void setPeriod(int period) { this.period = period; } + public void setPrimaryColor(Color primaryColor) { this.primaryColor = primaryColor; } + public void setSecondaryColor(Color secondaryColor) { this.secondaryColor = secondaryColor; } + + /** + * Sets the probability/density of sparkles. Lower is denser. Default is 16. + * @param sparkleProbability from 0 to 255. + */ + public void setSparkleProbability(int sparkleProbability) { this.sparkleProbability = sparkleProbability; }; + //endregion + @Override + protected void updateAnimationSpecificValuesOverI2C(I2cDeviceSynchSimple deviceClient) + { + deviceClient.write(layerHeight.register.address, GetByteArray(4, primaryColor)); + deviceClient.write(layerHeight.register.address, GetByteArray(5, secondaryColor)); + deviceClient.write(layerHeight.register.address, GetByteArray(6, period)); + deviceClient.write(layerHeight.register.address, GetByteArray(7, (byte)sparkleProbability)); + } + } +} diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/pedroPathing/Constants.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/pedroPathing/Constants.java index 6b78bdee8f54..d4338b8b7e58 100644 --- a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/pedroPathing/Constants.java +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/pedroPathing/Constants.java @@ -16,16 +16,17 @@ public class Constants { public static FollowerConstants followerConstants = new FollowerConstants() - .mass(5) // TODO: Change this to the actual weight of the robot - .forwardZeroPowerAcceleration(-45.7735) - .lateralZeroPowerAcceleration(-53.7000) + .mass(13.6) // TODO: Change this to the actual weight of the robot + .forwardZeroPowerAcceleration(-42.5878) + .lateralZeroPowerAcceleration(-68.2022) // Set following parameters to true to enable dual PID .useSecondaryTranslationalPIDF(false) .useSecondaryHeadingPIDF(false) .useSecondaryDrivePIDF(false) - .translationalPIDFCoefficients(new PIDFCoefficients(.1, 0, 0.01, 0.05)) - .headingPIDFCoefficients(new PIDFCoefficients(3, 0.3, 0.2, 0.05)) - .drivePIDFCoefficients(new FilteredPIDFCoefficients(0.3, 0.1, 0.01, 0.6, 0.6)); + .centripetalScaling(0.0002) + .translationalPIDFCoefficients(new PIDFCoefficients(0.15, 0, 0.01, 0.05)) + .headingPIDFCoefficients(new PIDFCoefficients(2, 0, 0.1, 0.026)) + .drivePIDFCoefficients(new FilteredPIDFCoefficients(0.02, 0, 0.001, 0.6, 0.02)); public static PathConstraints pathConstraints = new PathConstraints( 0.99, @@ -48,16 +49,16 @@ public class Constants { .leftRearMotorDirection(DcMotorSimple.Direction.REVERSE) .rightFrontMotorDirection(DcMotorSimple.Direction.FORWARD) .rightRearMotorDirection(DcMotorSimple.Direction.FORWARD) - .xVelocity(59.3402) - .yVelocity(46.4157); + .xVelocity(65.1356) + .yVelocity(39.2622); public static PinpointConstants localizerConstants = new PinpointConstants() - .forwardPodY(-2) - .strafePodX(4.5) + .forwardPodY(-3.25) + .strafePodX(-6.25) .distanceUnit(DistanceUnit.INCH) .hardwareMapName("pinpoint") .encoderResolution(GoBildaPinpointDriver.GoBildaOdometryPods.goBILDA_4_BAR_POD) - .forwardEncoderDirection(GoBildaPinpointDriver.EncoderDirection.REVERSED) + .forwardEncoderDirection(GoBildaPinpointDriver.EncoderDirection.FORWARD) .strafeEncoderDirection(GoBildaPinpointDriver.EncoderDirection.FORWARD); public static Follower createFollower(HardwareMap hardwareMap) { diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/pedroPathing/Tuning.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/pedroPathing/Tuning.java index 9744592b7994..01693c124f90 100644 --- a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/pedroPathing/Tuning.java +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/pedroPathing/Tuning.java @@ -1,8 +1,8 @@ package org.firstinspires.ftc.teamcode.pedroPathing; import static org.firstinspires.ftc.teamcode.pedroPathing.Tuning.changes; -import static org.firstinspires.ftc.teamcode.pedroPathing.Tuning.drawOnlyCurrent; import static org.firstinspires.ftc.teamcode.pedroPathing.Tuning.draw; +import static org.firstinspires.ftc.teamcode.pedroPathing.Tuning.drawOnlyCurrent; import static org.firstinspires.ftc.teamcode.pedroPathing.Tuning.follower; import static org.firstinspires.ftc.teamcode.pedroPathing.Tuning.stopRobot; import static org.firstinspires.ftc.teamcode.pedroPathing.Tuning.telemetryM; @@ -16,11 +16,15 @@ import com.bylazar.telemetry.PanelsTelemetry; import com.bylazar.telemetry.TelemetryManager; import com.pedropathing.follower.Follower; -import com.pedropathing.geometry.*; -import com.pedropathing.math.*; -import com.pedropathing.paths.*; +import com.pedropathing.geometry.BezierCurve; +import com.pedropathing.geometry.BezierLine; +import com.pedropathing.geometry.Pose; +import com.pedropathing.math.Vector; +import com.pedropathing.paths.HeadingInterpolator; +import com.pedropathing.paths.Path; +import com.pedropathing.paths.PathChain; import com.pedropathing.telemetry.SelectableOpMode; -import com.pedropathing.util.*; +import com.pedropathing.util.PoseHistory; import com.qualcomm.robotcore.eventloop.opmode.OpMode; import com.qualcomm.robotcore.eventloop.opmode.TeleOp; @@ -206,7 +210,7 @@ public void init_loop() { public void loop() { follower.update(); - telemetryM.debug("Distance Moved: " + follower.getPose().getX()); + telemetryM.debug("Distance Moved: " + (follower.getPose().getX() - 72)); telemetryM.debug("The multiplier will display what your forward ticks to inches should be to scale your current distance to " + DISTANCE + " inches."); telemetryM.debug("Multiplier: " + (DISTANCE / ((follower.getPose().getX() - 72) / follower.getPoseTracker().getLocalizer().getForwardMultiplier()))); telemetryM.update(telemetry); @@ -254,7 +258,7 @@ public void init_loop() { public void loop() { follower.update(); - telemetryM.debug("Distance Moved: " + follower.getPose().getY()); + telemetryM.debug("Distance Moved: " + (follower.getPose().getY() - 72)); telemetryM.debug("The multiplier will display what your strafe ticks to inches should be to scale your current distance to " + DISTANCE + " inches."); telemetryM.debug("Multiplier: " + (DISTANCE / ((follower.getPose().getY() - 72) / follower.getPoseTracker().getLocalizer().getLateralMultiplier()))); telemetryM.update(telemetry); @@ -536,7 +540,7 @@ public void loop() { */ class ForwardZeroPowerAccelerationTuner extends OpMode { private final ArrayList accelerations = new ArrayList<>(); - public static double VELOCITY = 30; + public static double VELOCITY = 60; private double previousVelocity; private long previousTimeNano; @@ -642,7 +646,7 @@ public void loop() { */ class LateralZeroPowerAccelerationTuner extends OpMode { private final ArrayList accelerations = new ArrayList<>(); - public static double VELOCITY = 30; + public static double VELOCITY = 35; private double previousVelocity; private long previousTimeNano; private boolean stopping; @@ -790,6 +794,9 @@ public void loop() { } telemetryM.debug("Push the robot laterally to test the Translational PIDF(s)."); + telemetryM.addData("Zero Line", 0); + telemetryM.addData("Error X", follower.errorCalculator.getTranslationalError().getXComponent()); + telemetryM.addData("Error Y", follower.errorCalculator.getTranslationalError().getYComponent()); telemetryM.update(telemetry); } } @@ -862,6 +869,8 @@ public void loop() { } telemetryM.debug("Turn the robot manually to test the Heading PIDF(s)."); + telemetryM.addData("Zero Line", 0); + telemetryM.addData("Error", follower.errorCalculator.getHeadingError()); telemetryM.update(telemetry); } } @@ -905,7 +914,7 @@ public void init_loop() { public void start() { follower.deactivateAllPIDFs(); follower.activateDrive(); - + forwards = follower.pathBuilder() .setGlobalDeceleration() .addPath(new BezierLine(new Pose(72,72), new Pose(DISTANCE + 72,72))) @@ -941,6 +950,8 @@ public void loop() { } telemetryM.debug("Driving forward?: " + forward); + telemetryM.addData("Zero Line", 0); + telemetryM.addData("Error", follower.errorCalculator.getDriveErrors()[1]); telemetryM.update(telemetry); } } diff --git a/build.dependencies.gradle b/build.dependencies.gradle index f72bad961c24..52bde17c270f 100644 --- a/build.dependencies.gradle +++ b/build.dependencies.gradle @@ -15,7 +15,7 @@ dependencies { implementation 'org.firstinspires.ftc:Vision:11.0.0' implementation 'androidx.appcompat:appcompat:1.2.0' - implementation 'com.pedropathing:ftc:2.0.4' + implementation 'com.pedropathing:ftc:2.0.5' implementation 'com.pedropathing:telemetry:1.0.0' implementation 'com.bylazar:fullpanels:1.0.9'