From a8dbad7880160c0f93091eadb563f60436f324d7 Mon Sep 17 00:00:00 2001 From: Patrick Creighton Date: Sat, 9 Mar 2024 15:10:00 -0800 Subject: [PATCH 01/24] Miscellaneous infrastructure updates (#299) * Add gitignore to virtual iridium * Update dependabot configuration --- .github/dependabot.yml | 26 +++++++++++++++++---- src/virtual_iridium/.gitignore | 2 ++ src/virtual_iridium/python/imap_stuff.pyc | Bin 1402 -> 0 bytes src/virtual_iridium/python/sbd_packets.pyc | Bin 2688 -> 0 bytes src/virtual_iridium/python/smtp_stuff.pyc | Bin 2282 -> 0 bytes 5 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 src/virtual_iridium/.gitignore delete mode 100644 src/virtual_iridium/python/imap_stuff.pyc delete mode 100644 src/virtual_iridium/python/sbd_packets.pyc delete mode 100644 src/virtual_iridium/python/smtp_stuff.pyc diff --git a/.github/dependabot.yml b/.github/dependabot.yml index afb98ae0c..c591541cf 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,7 +1,23 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + version: 2 updates: -- package-ecosystem: github-actions - directory: "/" - schedule: - interval: daily - open-pull-requests-limit: 10 + - package-ecosystem: "github-actions" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" + # Create a group of dependencies to be updated together in one pull request + groups: + # Specify a name for the group, which will be used in pull request titles + # and branch names + gh-actions: + # Define patterns to include dependencies in the group (based on + # dependency name) + patterns: + # - "rubocop" # A single dependency name + # - "rspec*" # A wildcard string that matches multiple dependency names + - "*" # A wildcard that matches all dependencies in the package + # ecosystem. Note: using "*" may open a large pull request diff --git a/src/virtual_iridium/.gitignore b/src/virtual_iridium/.gitignore new file mode 100644 index 000000000..a1dc806a7 --- /dev/null +++ b/src/virtual_iridium/.gitignore @@ -0,0 +1,2 @@ +# python generated files +*.pyc diff --git a/src/virtual_iridium/python/imap_stuff.pyc b/src/virtual_iridium/python/imap_stuff.pyc deleted file mode 100644 index b73294f84467a910cfe5974b40ff38c68ad48e79..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1402 zcmb_c%Wl&^6usj-n#7EswBmH|W~3X38kL{(H&2`bcpkPyh4+LLq=KZ0jWTgeuS zB33NfvgI@Q2|k4nfOE&;C9x#&jPLWzx%W;|`_^lJ`T9`>H2-*b-^VgHv4wa9MMN)t z$%0;pzwI8qLx9JT!+s4}`a3@RK^@{URs(AX%X}6@mZ+?g6*TdvaOh_ql?^HyG^tS` zsPG6D!FruSD56pt?$8=LUm~MPgiH>@i{%r~*U)+7J0TqoZk<#rb*N>!=jn9n~<#CCqEz*0A5X zbIbAs_mac(#5N&4ONL4%8WPWu=!%=SX3?I%Spv^2b+G^;G6RIE$RsRA+$6!}RMqwGtD z+aC@extJ&&>nhu*NCnmb86~se$nt&MPZQIlsPcV&zmD}R)wv2Sdqld(%bmeCZgBpNI02USTvvJ!hbtB!;NH=zm`X%LXHvYokM zCQqYOh7Rg!mRA|EJn8c<$nvI>AdPXFZ&db1WiB#@Y`MKNY0Pn&Rrxwb7uk9wjUP%Y zUB&6)#J$1C*5j8lp~Iti{_U$EK>5hv$y?Sx6s1W#sEjhdPf3vr~JNCIcQV+M16>8?f{M6&$Hx`nc-4 zVUlJG8;Pb^6)U1A)A5HRDrmIX9EwX zw|`@RQ;ryq@+vu^OGWbv-KbJVQpk7m8aa9S5YV!vrn-k_*Pd6OJ6G-xhsL~uY68)Z%`%&9gwRXbXz_Pog&`&KA# zNx_>E-H>XLrX{}l)+)n%ljd7=vqt$g&GBs@O^e#6-3B&(V)-17hW)_@AKQoh{=g_c zleNNVI`}ns$nYyMzt)MIICPuPu_^evY&LS(1kos*jI4RC#h!&pl*W$JpKHh3&^YV( z_^09M%JCA2m0y!MnQ$i7Vu)d3hlx(Q{IX;glLp5@6sNIG3dym=8;R3kI2(_3=D4y8 z9Y#6}#<9Dt8pLm|W|92dUEsB#@+b>3J$74=t+v*rNnl;*W>)r6q?5f&!)KE;i~`QZ z-V6O!veM+Xc{)p7I!Y(VejOyK+l4K?)>$~21hG92$}rx+G}Fa8T__}jJ%-eKl4e&H zXQpk>hGsHMU9dFuY&Pm$o6OC^NnkP)nOWSMK64jo(zC-T;G@>gz7Nxz$Q4?vUI2%7zDg&euXRa5whx)fdFu7Dk-AXFv06t(zk%`(Z<((PyKvrz zfh+^@+XCY7X9RzrTqWs~q%(Ltc+k1`w9A1$-vL60S;7bl##tKsj1D2{Nt$S%Y3TEU z!k})E8k9X}{kV@C8K?y=na!mGoDj6o>d@+xAj`UR(h6N7udt`=lki0r za1czER>>W*N;jY&*oG1P5DrC1(FTzX7HNEZlYpT3qKTb$88Rp4D~y8MvWpEamUO^O z(XZ|se#?S$^AG+FFdd#@+x-_mUG4?~Z-KM_7OMhfZbv>}d_LclLcXH$xbgz^k6;%d zjnZUnvRHZq*SOHR9Wm?|dU5&tdmP7@98HW)Two%fqogr~GY9lWMUyJh`ivs~kOzkQ zD}pL-eAO*(p*7QXnkH6%2M7BOh61m51+z7^qpGT;L@4>7FvA=3Za`gpm|;xw8~^yhqaS$?A}RMzquoyL3r0gR6~PXGV_ diff --git a/src/virtual_iridium/python/smtp_stuff.pyc b/src/virtual_iridium/python/smtp_stuff.pyc deleted file mode 100644 index a1a49b70c2ecdf6fcdff499d422aa1bc64b9a7a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2282 zcmb_dOK&4Z5Uw8E<42swCM@iNgrb!ed?2xq(4JOkm#~2qN)(O5#ahW4&vcw|=B4TO z!XlfC`3W2l;A=l^+0KRXedPaa`NouCA_ESAEr8`)9lJ`(Honh*lpD|IaYY zub5)|6s1JNJ6h3D-SRyedO~}Y)+npfuuh6~YLqr8^J(b2b)C{CWi1-E+`2(&o3aiK zJ8tb$xy5PKZbpw~fo$-nrV_Y_FGjp^*=*^9GT zYLhaotYf!U>@Py2H#wL3`;|<4CG&h1CxsKbWxdS8$x4qM=sYT7JsQK`Kc8P!u~DG z)@j#quK4mbHX1?Xdi^<~FuOIra(hz5CVI zsNtZ;$2&Lg^TEck(belD($7F1Csh(Bv+Ou3G6Y3*T7h8PTo#s<2D9;0M>hEeJhR6P zo!dj5g-Lp7i^IpJ*99}cAnRiatqr5=fGtQFC)z$2Rt7z>Xfj)-$ymG%&Mq&6(AQ~U zxxU7t+%`3~fkj$OlH9R%9 z35UYY%FvkaiYm6O&2f!5H4xorFLz;|CaJy%?X_5#a2VgRJqD4iE^f`#z{$+SGM-s% zAl7_+*@_u8r>--FtLx4)wI)I-(rB5p4(E0^j7!<>QxXalprFaY}sXjHTi z`~o>QEiy;k2TZrAdcyFBKn}nwn%1}v;u>R;6>GGp6A}(n=miEgKn|G(-FQ^}NT?Su z?{E%Z3!m~|M8Fq;-?)NP;2cfic7KrFrtJPf$p4)>ZW8q6QYkhv5V5w1{Y^;!|BHP> zC^R>%iwzCp)3m!5hEs*Ax;9m8W5@L&#BgPGDBhyDOLmW@_h`Dm%GlCV3MzvhV1{02(JnZUN$>FB4N1CSgYcx;17+`wGC#!%X*BN6?!knYOTJM0_%X(-E(wOU{ej zIWh>}HBM3L+%+>_4md+B0ZRYddtj}?9$n$y7_q;A1q1GcK+Y-H)OJ*cZ_*-+1Mcg9 z`^yC!$D?alMLTgq3L}A(+-wYRzMSO%jM?BJYq7n^9bXc|Vse%9eZUJeUM@SVbDKU6 zN}_uVqKEO;8#3Uge}rK^V)N?0_dvBVcD=5;uexd<;{zVvt~x*~cDzTb#~BPh<%y3K ze2mF*tt7w48&9CSVs|z+`5|;!-sJOh=&H86Auo@j)W&XZ$kQm5vujH)Ybo=Y From 1a16207dfe52d21e3a16607675a9ff233675d505 Mon Sep 17 00:00:00 2001 From: Patrick Creighton Date: Sat, 9 Mar 2024 15:29:09 -0800 Subject: [PATCH 02/24] Comment out grouped dependabot updates (#300) --- .github/dependabot.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c591541cf..94198add2 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,15 +9,15 @@ updates: directory: "/" # Location of package manifests schedule: interval: "weekly" - # Create a group of dependencies to be updated together in one pull request - groups: - # Specify a name for the group, which will be used in pull request titles - # and branch names - gh-actions: - # Define patterns to include dependencies in the group (based on - # dependency name) - patterns: - # - "rubocop" # A single dependency name - # - "rspec*" # A wildcard string that matches multiple dependency names - - "*" # A wildcard that matches all dependencies in the package - # ecosystem. Note: using "*" may open a large pull request + # # Create a group of dependencies to be updated together in one pull request + # groups: + # # Specify a name for the group, which will be used in pull request titles + # # and branch names + # gh-actions: + # # Define patterns to include dependencies in the group (based on + # # dependency name) + # patterns: + # # - "rubocop" # A single dependency name + # # - "rspec*" # A wildcard string that matches multiple dependency names + # - "*" # A wildcard that matches all dependencies in the package + # # ecosystem. Note: using "*" may open a large pull request From e88543efa8c9eb17d433e5b813b5e2f7b4345b7a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 9 Mar 2024 15:31:44 -0800 Subject: [PATCH 03/24] Bump ip from 2.0.0 to 2.0.1 in /src/website/tests (#252) Bumps [ip](https://github.com/indutny/node-ip) from 2.0.0 to 2.0.1. - [Commits](https://github.com/indutny/node-ip/compare/v2.0.0...v2.0.1) --- updated-dependencies: - dependency-name: ip dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/website/tests/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/website/tests/package-lock.json b/src/website/tests/package-lock.json index 194755a28..2ff4da1c7 100644 --- a/src/website/tests/package-lock.json +++ b/src/website/tests/package-lock.json @@ -1010,9 +1010,9 @@ } }, "node_modules/ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", + "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==" }, "node_modules/is-arrayish": { "version": "0.3.2", From aaf99d893bc8b3898c8bc7b8a6f903524a809616 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 9 Mar 2024 15:31:55 -0800 Subject: [PATCH 04/24] Bump follow-redirects from 1.15.3 to 1.15.5 in /src/website/tests (#253) Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.3 to 1.15.5. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.3...v1.15.5) --- updated-dependencies: - dependency-name: follow-redirects dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/website/tests/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/website/tests/package-lock.json b/src/website/tests/package-lock.json index 2ff4da1c7..61016f359 100644 --- a/src/website/tests/package-lock.json +++ b/src/website/tests/package-lock.json @@ -883,9 +883,9 @@ "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", "funding": [ { "type": "individual", From d41dc9a7c4ef6f8fb4bf986298f3055988c32feb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 9 Mar 2024 15:32:08 -0800 Subject: [PATCH 05/24] Bump axios from 0.27.2 to 0.28.0 in /src/website/tests (#254) Bumps [axios](https://github.com/axios/axios) from 0.27.2 to 0.28.0. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v0.28.0/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v0.27.2...v0.28.0) --- updated-dependencies: - dependency-name: axios dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/website/tests/package-lock.json | 18 ++++++++++++------ src/website/tests/package.json | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/website/tests/package-lock.json b/src/website/tests/package-lock.json index 61016f359..6cc3ef659 100644 --- a/src/website/tests/package-lock.json +++ b/src/website/tests/package-lock.json @@ -12,7 +12,7 @@ "@cucumber/messages": "20.0.0", "@cucumber/pretty-formatter": "1.0.0", "amqp-ts": "1.8.0", - "axios": "0.27.2", + "axios": "0.28.0", "chai": "4.3.7", "deep-equal-in-any-order": "1.1.20", "mongoose": "^7.5.3", @@ -504,12 +504,13 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", - "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.28.0.tgz", + "integrity": "sha512-Tu7NYoGY4Yoc7I+Npf9HhUMtEEpV7ZiLH9yndTCoNhcpBH0kwcvFbzYN9/u5QKI5A6uefjsNNWaz5olJVYS62Q==", "dependencies": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "node_modules/balanced-match": { @@ -1419,6 +1420,11 @@ "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.5.tgz", "integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==" }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", diff --git a/src/website/tests/package.json b/src/website/tests/package.json index cd8d42913..646ed49df 100644 --- a/src/website/tests/package.json +++ b/src/website/tests/package.json @@ -11,7 +11,7 @@ "@cucumber/messages": "20.0.0", "@cucumber/pretty-formatter": "1.0.0", "amqp-ts": "1.8.0", - "axios": "0.27.2", + "axios": "0.28.0", "chai": "4.3.7", "deep-equal-in-any-order": "1.1.20", "mongoose": "^7.5.3", From 51b776fc9cfae8653f8b9067db084572e0714230 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 9 Mar 2024 15:32:22 -0800 Subject: [PATCH 06/24] Bump zod and next in /src/website (#257) Removes [zod](https://github.com/colinhacks/zod). It's no longer used after updating ancestor dependency [next](https://github.com/vercel/next.js). These dependencies need to be updated together. Removes `zod` Updates `next` from 13.4.19 to 14.1.3 - [Release notes](https://github.com/vercel/next.js/releases) - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js) - [Commits](https://github.com/vercel/next.js/compare/v13.4.19...v14.1.3) --- updated-dependencies: - dependency-name: zod dependency-type: indirect - dependency-name: next dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/website/package-lock.json | 203 +++++++++++++++------------------- 1 file changed, 87 insertions(+), 116 deletions(-) diff --git a/src/website/package-lock.json b/src/website/package-lock.json index 47bd58f4a..df7dd66da 100644 --- a/src/website/package-lock.json +++ b/src/website/package-lock.json @@ -1334,8 +1334,9 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, "node_modules/@next/env": { - "version": "13.4.19", - "license": "MIT" + "version": "14.1.3", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.1.3.tgz", + "integrity": "sha512-VhgXTvrgeBRxNPjyfBsDIMvgsKDxjlpw4IAUsHCX8Gjl1vtHUYRT3+xfQ/wwvLPDd/6kqfLqk9Pt4+7gysuCKQ==" }, "node_modules/@next/eslint-plugin-next": { "version": "14.0.2", @@ -1347,9 +1348,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.19.tgz", - "integrity": "sha512-vv1qrjXeGbuF2mOkhkdxMDtv9np7W4mcBtaDnHU+yJG+bBwa6rYsYSCI/9Xm5+TuF5SbZbrWO6G1NfTh1TMjvQ==", + "version": "14.1.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.1.3.tgz", + "integrity": "sha512-LALu0yIBPRiG9ANrD5ncB3pjpO0Gli9ZLhxdOu6ZUNf3x1r3ea1rd9Q+4xxUkGrUXLqKVK9/lDkpYIJaCJ6AHQ==", "cpu": [ "arm64" ], @@ -1362,9 +1363,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.19.tgz", - "integrity": "sha512-jyzO6wwYhx6F+7gD8ddZfuqO4TtpJdw3wyOduR4fxTUCm3aLw7YmHGYNjS0xRSYGAkLpBkH1E0RcelyId6lNsw==", + "version": "14.1.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.1.3.tgz", + "integrity": "sha512-E/9WQeXxkqw2dfcn5UcjApFgUq73jqNKaE5bysDm58hEUdUGedVrnRhblhJM7HbCZNhtVl0j+6TXsK0PuzXTCg==", "cpu": [ "x64" ], @@ -1377,9 +1378,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.19.tgz", - "integrity": "sha512-vdlnIlaAEh6H+G6HrKZB9c2zJKnpPVKnA6LBwjwT2BTjxI7e0Hx30+FoWCgi50e+YO49p6oPOtesP9mXDRiiUg==", + "version": "14.1.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.1.3.tgz", + "integrity": "sha512-USArX9B+3rZSXYLFvgy0NVWQgqh6LHWDmMt38O4lmiJNQcwazeI6xRvSsliDLKt+78KChVacNiwvOMbl6g6BBw==", "cpu": [ "arm64" ], @@ -1392,9 +1393,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.19.tgz", - "integrity": "sha512-aU0HkH2XPgxqrbNRBFb3si9Ahu/CpaR5RPmN2s9GiM9qJCiBBlZtRTiEca+DC+xRPyCThTtWYgxjWHgU7ZkyvA==", + "version": "14.1.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.1.3.tgz", + "integrity": "sha512-esk1RkRBLSIEp1qaQXv1+s6ZdYzuVCnDAZySpa62iFTMGTisCyNQmqyCTL9P+cLJ4N9FKCI3ojtSfsyPHJDQNw==", "cpu": [ "arm64" ], @@ -1407,9 +1408,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.19.tgz", - "integrity": "sha512-htwOEagMa/CXNykFFeAHHvMJeqZfNQEoQvHfsA4wgg5QqGNqD5soeCer4oGlCol6NGUxknrQO6VEustcv+Md+g==", + "version": "14.1.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.3.tgz", + "integrity": "sha512-8uOgRlYEYiKo0L8YGeS+3TudHVDWDjPVDUcST+z+dUzgBbTEwSSIaSgF/vkcC1T/iwl4QX9iuUyUdQEl0Kxalg==", "cpu": [ "x64" ], @@ -1422,9 +1423,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.19.tgz", - "integrity": "sha512-4Gj4vvtbK1JH8ApWTT214b3GwUh9EKKQjY41hH/t+u55Knxi/0wesMzwQRhppK6Ddalhu0TEttbiJ+wRcoEj5Q==", + "version": "14.1.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.3.tgz", + "integrity": "sha512-DX2zqz05ziElLoxskgHasaJBREC5Y9TJcbR2LYqu4r7naff25B4iXkfXWfcp69uD75/0URmmoSgT8JclJtrBoQ==", "cpu": [ "x64" ], @@ -1437,9 +1438,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.19.tgz", - "integrity": "sha512-bUfDevQK4NsIAHXs3/JNgnvEY+LRyneDN788W2NYiRIIzmILjba7LaQTfihuFawZDhRtkYCv3JDC3B4TwnmRJw==", + "version": "14.1.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.1.3.tgz", + "integrity": "sha512-HjssFsCdsD4GHstXSQxsi2l70F/5FsRTRQp8xNgmQs15SxUfUJRvSI9qKny/jLkY3gLgiCR3+6A7wzzK0DBlfA==", "cpu": [ "arm64" ], @@ -1452,9 +1453,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.19.tgz", - "integrity": "sha512-Y5kikILFAr81LYIFaw6j/NrOtmiM4Sf3GtOc0pn50ez2GCkr+oejYuKGcwAwq3jiTKuzF6OF4iT2INPoxRycEA==", + "version": "14.1.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.1.3.tgz", + "integrity": "sha512-DRuxD5axfDM1/Ue4VahwSxl1O5rn61hX8/sF0HY8y0iCbpqdxw3rB3QasdHn/LJ6Wb2y5DoWzXcz3L1Cr+Thrw==", "cpu": [ "ia32" ], @@ -1467,9 +1468,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.19.tgz", - "integrity": "sha512-YzA78jBDXMYiINdPdJJwGgPNT3YqBNNGhsthsDoWHL9p24tEJn9ViQf/ZqTbwSpX/RrkPupLfuuTH2sf73JBAw==", + "version": "14.1.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.1.3.tgz", + "integrity": "sha512-uC2DaDoWH7h1P/aJ4Fok3Xiw6P0Lo4ez7NbowW2VGNXw/Xv6tOuLUcxhBYZxsSUJtpeknCi8/fvnSpyCFp4Rcg==", "cpu": [ "x64" ], @@ -2109,8 +2110,9 @@ } }, "node_modules/@swc/helpers": { - "version": "0.5.1", - "license": "Apache-2.0", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz", + "integrity": "sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==", "dependencies": { "tslib": "^2.4.0" } @@ -2723,7 +2725,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, + "devOptional": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -2985,7 +2987,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -3031,7 +3033,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, + "devOptional": true, "dependencies": { "fill-range": "^7.0.1" }, @@ -3150,7 +3152,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001522", + "version": "1.0.30001596", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001596.tgz", + "integrity": "sha512-zpkZ+kEr6We7w63ORkoJ2pOfBwBkY/bJrG/UZ90qNb45Isblu8wzDgevEOrRL1r9dWayHjYiiyCMEXPn4DweGQ==", "funding": [ { "type": "opencollective", @@ -3164,8 +3168,7 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ], - "license": "CC-BY-4.0" + ] }, "node_modules/chalk": { "version": "2.4.2", @@ -3190,7 +3193,7 @@ "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, + "devOptional": true, "funding": [ { "type": "individual", @@ -3217,7 +3220,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, + "devOptional": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -4522,7 +4525,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, + "devOptional": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -4724,10 +4727,6 @@ "node": ">=10.13.0" } }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "license": "BSD-2-Clause" - }, "node_modules/global-modules": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", @@ -5019,7 +5018,7 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==", - "dev": true + "devOptional": true }, "node_modules/import-fresh": { "version": "3.3.0", @@ -5174,7 +5173,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, + "devOptional": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -5240,7 +5239,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -5285,7 +5284,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, + "devOptional": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -5318,7 +5317,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.12.0" } @@ -6069,35 +6068,34 @@ } }, "node_modules/next": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/next/-/next-13.4.19.tgz", - "integrity": "sha512-HuPSzzAbJ1T4BD8e0bs6B9C1kWQ6gv8ykZoRWs5AQoiIuqbGHHdQO7Ljuvg05Q0Z24E2ABozHe6FxDvI6HfyAw==", + "version": "14.1.3", + "resolved": "https://registry.npmjs.org/next/-/next-14.1.3.tgz", + "integrity": "sha512-oexgMV2MapI0UIWiXKkixF8J8ORxpy64OuJ/J9oVUmIthXOUCcuVEZX+dtpgq7wIfIqtBwQsKEDXejcjTsan9g==", "dependencies": { - "@next/env": "13.4.19", - "@swc/helpers": "0.5.1", + "@next/env": "14.1.3", + "@swc/helpers": "0.5.2", "busboy": "1.6.0", - "caniuse-lite": "^1.0.30001406", - "postcss": "8.4.14", - "styled-jsx": "5.1.1", - "watchpack": "2.4.0", - "zod": "3.21.4" + "caniuse-lite": "^1.0.30001579", + "graceful-fs": "^4.2.11", + "postcss": "8.4.31", + "styled-jsx": "5.1.1" }, "bin": { "next": "dist/bin/next" }, "engines": { - "node": ">=16.8.0" + "node": ">=18.17.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "13.4.19", - "@next/swc-darwin-x64": "13.4.19", - "@next/swc-linux-arm64-gnu": "13.4.19", - "@next/swc-linux-arm64-musl": "13.4.19", - "@next/swc-linux-x64-gnu": "13.4.19", - "@next/swc-linux-x64-musl": "13.4.19", - "@next/swc-win32-arm64-msvc": "13.4.19", - "@next/swc-win32-ia32-msvc": "13.4.19", - "@next/swc-win32-x64-msvc": "13.4.19" + "@next/swc-darwin-arm64": "14.1.3", + "@next/swc-darwin-x64": "14.1.3", + "@next/swc-linux-arm64-gnu": "14.1.3", + "@next/swc-linux-arm64-musl": "14.1.3", + "@next/swc-linux-x64-gnu": "14.1.3", + "@next/swc-linux-x64-musl": "14.1.3", + "@next/swc-win32-arm64-msvc": "14.1.3", + "@next/swc-win32-ia32-msvc": "14.1.3", + "@next/swc-win32-x64-msvc": "14.1.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -6114,29 +6112,6 @@ } } }, - "node_modules/next/node_modules/postcss": { - "version": "8.4.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", - "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - } - ], - "dependencies": { - "nanoid": "^3.3.4", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, "node_modules/normalize-package-data": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", @@ -6156,7 +6131,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -6426,7 +6401,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8.6" }, @@ -6438,7 +6413,6 @@ "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -6853,7 +6827,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, + "devOptional": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -7127,7 +7101,7 @@ "version": "1.69.5", "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.5.tgz", "integrity": "sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ==", - "dev": true, + "devOptional": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -7826,6 +7800,21 @@ "node": ">=8" } }, + "node_modules/stylelint/node_modules/typescript": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", + "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", + "dev": true, + "optional": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/stylis": { "version": "4.2.0", "license": "MIT" @@ -7966,7 +7955,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, + "devOptional": true, "dependencies": { "is-number": "^7.0.0" }, @@ -8275,17 +8264,6 @@ "d3-timer": "^3.0.1" } }, - "node_modules/watchpack": { - "version": "2.4.0", - "license": "MIT", - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/webidl-conversions": { "version": "7.0.0", "license": "BSD-2-Clause", @@ -8447,13 +8425,6 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } - }, - "node_modules/zod": { - "version": "3.21.4", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } } } } From c9dd51072210d9bb3aa68769556b70b877ea71ff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 9 Mar 2024 15:32:32 -0800 Subject: [PATCH 07/24] Bump ip from 2.0.0 to 2.0.1 in /src/website (#255) Bumps [ip](https://github.com/indutny/node-ip) from 2.0.0 to 2.0.1. - [Commits](https://github.com/indutny/node-ip/compare/v2.0.0...v2.0.1) --- updated-dependencies: - dependency-name: ip dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/website/package-lock.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/website/package-lock.json b/src/website/package-lock.json index df7dd66da..a33fa035d 100644 --- a/src/website/package-lock.json +++ b/src/website/package-lock.json @@ -17,7 +17,7 @@ "leaflet-geometryutil": "^0.10.2", "mongodb": "^4.8.1", "mongoose": "^7.5.0", - "next": "latest", + "next": "*", "react": "^18.2.0", "react-dom": "^18.2.0", "react-leaflet": "^4.2.1", @@ -5114,8 +5114,9 @@ } }, "node_modules/ip": { - "version": "2.0.0", - "license": "MIT" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", + "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==" }, "node_modules/ipaddr.js": { "version": "1.9.1", From 97f782ce7cc39b360952f529192650009fd50f88 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 9 Mar 2024 15:32:43 -0800 Subject: [PATCH 08/24] Bump postcss and next in /src/website (#258) Bumps [postcss](https://github.com/postcss/postcss) to 8.4.31 and updates ancestor dependency [next](https://github.com/vercel/next.js). These dependencies need to be updated together. Updates `postcss` from 8.4.14 to 8.4.31 - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/postcss/compare/8.4.14...8.4.31) Updates `next` from 13.4.19 to 14.1.3 - [Release notes](https://github.com/vercel/next.js/releases) - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js) - [Commits](https://github.com/vercel/next.js/compare/v13.4.19...v14.1.3) --- updated-dependencies: - dependency-name: postcss dependency-type: indirect - dependency-name: next dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> From 220ea446a6147d3ed05047f432862d872be08b6b Mon Sep 17 00:00:00 2001 From: John Ahn Date: Sun, 10 Mar 2024 10:17:01 -0700 Subject: [PATCH 09/24] Upgrade Node.js version from v18 to v20 (#315) --- .devcontainer/website/website.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/website/website.Dockerfile b/.devcontainer/website/website.Dockerfile index 893136ada..811547bc5 100644 --- a/.devcontainer/website/website.Dockerfile +++ b/.devcontainer/website/website.Dockerfile @@ -1,6 +1,6 @@ # Copied from https://github.com/microsoft/vscode-dev-containers/blob/5a084a93b0736ea86395ac99019a5b72a00b6341/containers/javascript-node-mongo/.devcontainer/Dockerfile # [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster -ARG VARIANT=18 +ARG VARIANT=20 FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT} # [Optional] Uncomment this section to install additional OS packages. From 32eb2b22927fc23db96ada89026940ab19b80d95 Mon Sep 17 00:00:00 2001 From: evannawfal <140864831+evannawfal@users.noreply.github.com> Date: Sun, 10 Mar 2024 10:44:19 -0700 Subject: [PATCH 10/24] LUT mappings added (#234) * LUT mappings added * fixed comment on reynolds number --------- Co-authored-by: Patrick Creighton --- src/global_launch/config/README.md | 18 ++++++++++++++++++ src/global_launch/config/globals.yaml | 6 ++++++ 2 files changed, 24 insertions(+) diff --git a/src/global_launch/config/README.md b/src/global_launch/config/README.md index b2daac721..60065e737 100644 --- a/src/global_launch/config/README.md +++ b/src/global_launch/config/README.md @@ -73,6 +73,24 @@ ROS parameters specific to the nodes in the local_pathfinding package. - _Acceptable Values_: `"bitstar"`, `"bfmtstar"`, `"fmtstar"`, `"informedrrtstar"`, `"lazylbtrrt"`, `"lazyprmstar"`, `"lbtrrt"`, `"prmstar"`, `"rrtconnect"`, `"rrtsharp"`, `"rrtstar"`, `"rrtxstatic"`, `"sorrtstar"` +## Controller Parameters + +ROS parameters specific to the nodes in the Controller. + +### wingsail_ctrl_node + +**`reynolds_number`** + +- _Description_: The Reynolds number of the wind. +- _Datatype_: `double` +- _Range_: `(0.0, MAX_DOUBLE)` + +**`angle_of_attack`** + +- _Description_: The angle of attack of the sail. +- _Datatype_: `double` +- _Range_: `(-180.0, 180.0]` + ## Boat Simulator Parameters ROS parameters specific to the nodes in the boat simulator. diff --git a/src/global_launch/config/globals.yaml b/src/global_launch/config/globals.yaml index b385741dd..7cec8befa 100644 --- a/src/global_launch/config/globals.yaml +++ b/src/global_launch/config/globals.yaml @@ -16,6 +16,12 @@ navigate_main: ros__parameters: path_planner: "rrtstar" +# controller parameters +wingsail_ctrl_node: + ros__parameters: + reynolds_number: [0.0, 1.0, 2.0] + angle_of_attack: [0.0, 1.0, 2.0] + # boat_simulator parameters low_level_control_node: ros__parameters: From 9f496d73ae51ffdb3885a7a5e2aca840affbcf0a Mon Sep 17 00:00:00 2001 From: Patrick Creighton Date: Sun, 10 Mar 2024 10:55:57 -0700 Subject: [PATCH 11/24] Use rrtstar in tests (#316) Co-authored-by: ci-bot --- src/local_pathfinding/test/test_ompl_path.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/local_pathfinding/test/test_ompl_path.py b/src/local_pathfinding/test/test_ompl_path.py index e7662566d..73013c310 100644 --- a/src/local_pathfinding/test/test_ompl_path.py +++ b/src/local_pathfinding/test/test_ompl_path.py @@ -15,7 +15,7 @@ ais_ships=AISShips(), global_path=Path(), filtered_wind_sensor=WindSensor(), - planner="bitstar", + planner="rrtstar", ), ) From 415a30b4df89339362df0dbb2ad52cd59035054b Mon Sep 17 00:00:00 2001 From: Patrick Creighton Date: Sun, 10 Mar 2024 11:26:31 -0700 Subject: [PATCH 12/24] Fix test warnings (#317) --- src/controller/tests/unit/wingsail/common/test_lut.py | 2 +- src/local_pathfinding/test/test_local_path.py | 2 +- src/local_pathfinding/test/test_objectives.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controller/tests/unit/wingsail/common/test_lut.py b/src/controller/tests/unit/wingsail/common/test_lut.py index dc7c1d907..f0c3436e4 100644 --- a/src/controller/tests/unit/wingsail/common/test_lut.py +++ b/src/controller/tests/unit/wingsail/common/test_lut.py @@ -30,7 +30,7 @@ def test_unknown_interpolation_exception(self): [ [[10000, 10000, 10000], [1, 1, 1]], [10000, 10000, 10000], - [[0, 1], 10000, 10000], + np.array([[0, 1], 10000, 10000], dtype=object), np.array([[10000, 10000, 10000], [1, 1, 1]]), np.array([10000, 10000, 10000]), np.array([[[0, 1]], [[0, 1]], [[0, 1]]]), diff --git a/src/local_pathfinding/test/test_local_path.py b/src/local_pathfinding/test/test_local_path.py index 001c45f3c..0d5d2c754 100644 --- a/src/local_pathfinding/test/test_local_path.py +++ b/src/local_pathfinding/test/test_local_path.py @@ -12,7 +12,7 @@ def test_LocalPath_update_if_needed(): ais_ships=AISShips(), global_path=Path(), filtered_wind_sensor=WindSensor(), - planner="bitstar", + planner="rrtstar", ) assert PATH.waypoints is not None, "waypoints is not initialized" assert len(PATH.waypoints) > 1, "waypoints length <= 1" diff --git a/src/local_pathfinding/test/test_objectives.py b/src/local_pathfinding/test/test_objectives.py index 9289b650f..da31d4e7c 100644 --- a/src/local_pathfinding/test/test_objectives.py +++ b/src/local_pathfinding/test/test_objectives.py @@ -22,7 +22,7 @@ ais_ships=AISShips(), global_path=Path(), filtered_wind_sensor=WindSensor(), - planner="bitstar", + planner="rrtstar", ), ) From 0a45f044c6ca709bc438f21c2a8dd957f21cb542 Mon Sep 17 00:00:00 2001 From: "imgbot[bot]" <31301654+imgbot[bot]@users.noreply.github.com> Date: Sun, 10 Mar 2024 15:53:14 -0700 Subject: [PATCH 13/24] [ImgBot] Optimize images (#314) *Total -- 471.04kb -> 377.57kb (19.84%) /src/website/public/NSEWCompass.png -- 6.51kb -> 2.02kb (69.02%) /src/website/public/BoatIconFinal.png -- 14.29kb -> 5.54kb (61.26%) /src/website/public/NSEWCompassBackdrop.png -- 13.31kb -> 5.29kb (60.25%) /src/website/public/SailbotLogo.png -- 9.38kb -> 4.82kb (48.59%) /docs/assets/images/sailbot_workspace/workflow/sailbot_bug.png -- 281.33kb -> 227.03kb (19.3%) /src/custom_interfaces/diagrams/out/external_interfaces.png -- 145.13kb -> 131.80kb (9.19%) /src/website/public/vercel.svg -- 1.08kb -> 1.06kb (1%) Signed-off-by: ImgBotApp Co-authored-by: ImgBotApp Co-authored-by: Patrick Creighton --- .../workflow/sailbot_bug.png | Bin 288079 -> 232483 bytes .../diagrams/out/external_interfaces.png | Bin 148617 -> 134961 bytes src/website/public/BoatIconFinal.png | Bin 14637 -> 5671 bytes src/website/public/NSEWCompass.png | Bin 6671 -> 2067 bytes src/website/public/NSEWCompassBackdrop.png | Bin 13634 -> 5419 bytes src/website/public/SailbotLogo.png | Bin 9606 -> 4938 bytes src/website/public/vercel.svg | 5 +---- 7 files changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/assets/images/sailbot_workspace/workflow/sailbot_bug.png b/docs/assets/images/sailbot_workspace/workflow/sailbot_bug.png index d98be96e260cf29cc618bf9c664a3a18c0f5848f..a84a83a0368230beb46ee63b7e9b1728ca19e0a6 100644 GIT binary patch literal 232483 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 -![External Interface Diagram](https://github.com/UBCSailbot/custom_interfaces/blob/main/diagrams/out/external_interfaces.png?raw=true) +![External Interface Diagram](https://github.com/UBCSailbot/sailbot_workspace/blob/main/src/custom_interfaces/diagrams/out/external_interfaces.png?raw=true) ### Project-wide Internal Interfaces @@ -36,7 +36,7 @@ Update diagram by editing diagrams/src/external_interfaces.puml and the PlantUML ## Boat Simulator Interfaces -ROS messages and services used in our [boat simulator](https://github.com/UBCSailbot/boat_simulator). +ROS messages and services used in our [boat simulator](https://github.com/UBCSailbot/sailbot_workspace/tree/main/src/boat_simulator). ### Boat Simulator External Interfaces diff --git a/src/local_pathfinding/README.md b/src/local_pathfinding/README.md index b92bfcdc3..333b45d71 100644 --- a/src/local_pathfinding/README.md +++ b/src/local_pathfinding/README.md @@ -1,7 +1,5 @@ # Local Pathfinding -[![Tests](https://github.com/UBCSailbot/local_pathfinding/actions/workflows/tests.yml/badge.svg)](https://github.com/UBCSailbot/local_pathfinding/actions/workflows/tests.yml) - UBC Sailbot's local pathfinding ROS package ## Run diff --git a/src/network_systems/README.md b/src/network_systems/README.md index eb0bb74e0..c0d44c3ac 100755 --- a/src/network_systems/README.md +++ b/src/network_systems/README.md @@ -1,7 +1,5 @@ # Network Systems -[![Tests](https://github.com/UBCSailbot/network_systems/actions/workflows/tests.yml/badge.svg)](https://github.com/UBCSailbot/network_systems/actions/workflows/tests.yml) - This repository contains the source code for all of UBC Sailbot's Network Systems programs. It is made to work as part of [Sailbot Workspace](https://github.com/UBCSailbot/sailbot_workspace), and is **_not_** meant to be built as an independent project. From f25552877d2fcfd2762742288195bfe98058e380 Mon Sep 17 00:00:00 2001 From: John Ahn Date: Tue, 19 Mar 2024 09:16:02 -0700 Subject: [PATCH 18/24] Remove legacy-peer-deps header when installing node packages for website (#332) --- .devcontainer/website/website.Dockerfile | 2 +- src/website/package-lock.json | 2 +- src/website/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.devcontainer/website/website.Dockerfile b/.devcontainer/website/website.Dockerfile index 811547bc5..4c8d7f4d1 100644 --- a/.devcontainer/website/website.Dockerfile +++ b/.devcontainer/website/website.Dockerfile @@ -17,4 +17,4 @@ FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT} # Adapted from https://www.digitalocean.com/community/tutorials/how-to-build-a-node-js-application-with-docker WORKDIR /website EXPOSE 3005 -CMD npm install --legacy-peer-deps && npm run dev +CMD npm install && npm run dev diff --git a/src/website/package-lock.json b/src/website/package-lock.json index a33fa035d..6881e5f99 100644 --- a/src/website/package-lock.json +++ b/src/website/package-lock.json @@ -17,7 +17,7 @@ "leaflet-geometryutil": "^0.10.2", "mongodb": "^4.8.1", "mongoose": "^7.5.0", - "next": "*", + "next": "^14.1.3", "react": "^18.2.0", "react-dom": "^18.2.0", "react-leaflet": "^4.2.1", diff --git a/src/website/package.json b/src/website/package.json index d92309816..229d6536a 100644 --- a/src/website/package.json +++ b/src/website/package.json @@ -20,7 +20,7 @@ "leaflet-geometryutil": "^0.10.2", "mongodb": "^4.8.1", "mongoose": "^7.5.0", - "next": "latest", + "next": "^14.1.3", "react": "^18.2.0", "react-dom": "^18.2.0", "react-leaflet": "^4.2.1", From a9134f015898aa59505b6e0dc31c43ca7c3952d9 Mon Sep 17 00:00:00 2001 From: Devon Friend <69879126+DFriend01@users.noreply.github.com> Date: Tue, 19 Mar 2024 14:25:07 -0700 Subject: [PATCH 19/24] Sensors: Add delay model (#311) * Migrate code for issue 308 * Sensors: Separate dataline stdevs * cleanup --------- Co-authored-by: Chris Chang <64388914+chrischang5@users.noreply.github.com> Co-authored-by: Patrick Creighton Co-authored-by: Chris Chang <1chang1chang1@gmail.com> --- .../boat_simulator/common/sensors.py | 177 ++++++++++++++---- .../boat_simulator/common/unit_conversions.py | 11 +- .../tests/unit/common/test_gps_sensor.py | 88 ++------- .../tests/unit/common/test_wind_sensor.py | 70 +++---- 4 files changed, 199 insertions(+), 147 deletions(-) diff --git a/src/boat_simulator/boat_simulator/common/sensors.py b/src/boat_simulator/boat_simulator/common/sensors.py index a03940815..d1bf9d3b1 100644 --- a/src/boat_simulator/boat_simulator/common/sensors.py +++ b/src/boat_simulator/boat_simulator/common/sensors.py @@ -1,24 +1,43 @@ -from dataclasses import dataclass - -from typing import Optional, Any +from typing import Any from numpy.typing import NDArray - +from typing import List +import numpy as np from boat_simulator.common.types import Scalar, ScalarOrArray from boat_simulator.common.generators import ( - ConstantGenerator, MVGaussianGenerator, GaussianGenerator, ) -WindSensorGenerators = Optional[MVGaussianGenerator | ConstantGenerator] -GPSGenerators = Optional[GaussianGenerator | ConstantGenerator] - -@dataclass class Sensor: - """Interface for sensors in the Boat Simulation.""" + """ + + Interface for sensors in the Boat Simulation. + + Data delay and noise models are supported. + + Delay model will delay sensor value updates by one update cycle: + - Sensor has initial data x0 at t = 0 + - Sensor provided new data x_1 at t = 1 + - Sensor provided new data x_2 at t = 2. At t = 2, x1 is registered into the sensor. + - Sensor provided new data x_i at t = i. At t = i, x_{i-1} is registered into the sensor. + + Noise model will add noise to sensor values drawn from a + Gaussian or Multi-variate Gaussian distribution. + """ + + def __init__(self, enable_delay: bool = False, enable_noise: bool = False) -> None: + """ + + Args: + enable_noise (bool): Enables noise for fields. False by default. + enable_delay (bool): Enables delay for fields. False by default. + """ + + self.enable_delay = enable_delay + self.enable_noise = enable_noise def update(self, **kwargs): """ @@ -29,6 +48,7 @@ def update(self, **kwargs): Raises: ValueError: If kwarg is not a defined attribute in Sensor """ + for attr_name, attr_val in kwargs.items(): if attr_name in self.__annotations__: setattr(self, attr_name, attr_val) @@ -60,98 +80,181 @@ def read(self, key: str) -> Any: ) -@dataclass class WindSensor(Sensor): """ Abstraction for wind sensor. - # TODO: Add delay functions. - Properties: wind (ScalarOrArray): Wind x, y components or single value - wind_noisemaker (Optional[MVGaussianGenerator | ConstantGenerator]): - Noise function to emulate sensor noise in wind data reading + enable_noise (bool): Enables noise for fields. False by default. + enable_delay (bool): Enables delay for fields. False by default. """ wind: ScalarOrArray - wind_noisemaker: WindSensorGenerators = None + + def __init__( + self, + wind: ScalarOrArray, + wind_noise_stdev: List[Scalar] = [1.0, 1.0], + enable_noise: bool = False, + enable_delay: bool = False, + ) -> None: + super().__init__(enable_noise=enable_noise, enable_delay=enable_delay) + self._wind = wind + + # TODO: Refactor the initialization of data fields and their respective delay controls. + # Warning: this is not easy! + + self.wind_queue_next: bool = False + self.wind_next_value: ScalarOrArray = wind + self.wind_noisemaker: MVGaussianGenerator = MVGaussianGenerator( + mean=np.array([0, 0]), cov=np.diag(np.power(wind_noise_stdev, 2)) + ) @property # type: ignore def wind(self) -> ScalarOrArray: # TODO: Ensure attribute value and noisemakers are using the same value shape. # - wind scalars should add with noise scalars. # - wind vectors should add with noise vectors. - # Could consider using a __post_init__ function for this + return ( self._wind + self.wind_noisemaker.next() # type: ignore - if self.wind_noisemaker is not None + if self.enable_noise else self._wind ) @wind.setter def wind(self, wind: ScalarOrArray): - self._wind = wind + + if not self.enable_delay: + self._wind = wind + return + + if self.wind_queue_next: + self._wind = self.wind_next_value + else: + self.wind_queue_next = True + + self.wind_next_value = wind -@dataclass class GPS(Sensor): """ Abstraction for GPS. - # TODO: Add delay functions. - Properties: lat_lon (NDArray): Boat latitude and longitude (2x1 array) speed (Scalar): Boat speed heading (Scalar): Boat heading - lat_lon_noisemaker (Optional[GaussianGenerator | ConstantGenerator]): - Noise function to emulate sensor noise in latitude and longitude readings - speed_noisemaker (Optional[GaussianGenerator | ConstantGenerator]): - Noise function to emulate sensor noise in speed readings - heading_noisemaker (Optional[GaussianGenerator | ConstantGenerator]): - Noise function to emulate sensor noise in heading readings + enable_noise (bool): Enables noise for fields. False by default. + enable_delay (bool): Enables delay for fields. False by default. """ lat_lon: NDArray speed: Scalar heading: Scalar - lat_lon_noisemaker: GPSGenerators = None - speed_noisemaker: GPSGenerators = None - heading_noisemaker: GPSGenerators = None + def __init__( + self, + lat_lon: NDArray, + speed: Scalar, + heading: Scalar, + lat_lon_noise_stdev: Scalar = 1, + speed_noise_stdev: Scalar = 1, + heading_noise_stdev: Scalar = 1, + enable_noise: bool = False, + enable_delay: bool = False, + ): + super().__init__(enable_noise=enable_noise, enable_delay=enable_delay) + self._lat_lon = lat_lon + self._speed = speed + self._heading = heading + + # TODO: Refactor the initialization of data fields and their respective delay controls. + # Warning: this is not easy! + + # Delay Controls + self.lat_lon_queue_next: bool = False + self.lat_lon_next_value: NDArray = lat_lon + + self.speed_queue_next: bool = False + self.speed_next_value: Scalar = speed + + self.heading_queue_next: bool = False + self.heading_next_value: Scalar = heading + + self.lat_lon_noisemaker: GaussianGenerator = GaussianGenerator( + mean=0, stdev=lat_lon_noise_stdev + ) + self.speed_noisemaker: GaussianGenerator = GaussianGenerator( + mean=0, stdev=speed_noise_stdev + ) + self.heading_noisemaker: GaussianGenerator = GaussianGenerator( + mean=0, stdev=heading_noise_stdev + ) @property # type: ignore def lat_lon(self) -> NDArray: return ( self._lat_lon + self.lat_lon_noisemaker.next() - if self.lat_lon_noisemaker is not None + if self.enable_noise else self._lat_lon ) @lat_lon.setter def lat_lon(self, lat_lon: NDArray): - self._lat_lon = lat_lon + + if not self.enable_delay: + self._lat_lon = lat_lon + return + + if self.lat_lon_queue_next: + self._lat_lon = self.lat_lon_next_value + else: + self.lat_lon_queue_next = True + + self.lat_lon_next_value = lat_lon @property # type: ignore def speed(self) -> Scalar: return ( self._speed + self.speed_noisemaker.next() # type: ignore - if self.speed_noisemaker is not None + if self.enable_noise else self._speed ) @speed.setter def speed(self, speed: Scalar): - self._speed = speed + + if not self.enable_delay: + self._speed = speed + return + + if self.speed_queue_next: + self._speed = self.speed_next_value + else: + self.speed_queue_next = True + + self.speed_next_value = speed @property # type: ignore def heading(self) -> Scalar: return ( self._heading + self.heading_noisemaker.next() # type: ignore - if self.heading_noisemaker is not None + if self.enable_noise else self._heading ) @heading.setter def heading(self, heading: Scalar): - self._heading = heading + + if not self.enable_delay: + self._heading = heading + return + + if self.heading_queue_next: + self._heading = self.heading_next_value + else: + self.heading_queue_next = True + + self.heading_next_value = heading diff --git a/src/boat_simulator/boat_simulator/common/unit_conversions.py b/src/boat_simulator/boat_simulator/common/unit_conversions.py index 701b443a5..d41706742 100644 --- a/src/boat_simulator/boat_simulator/common/unit_conversions.py +++ b/src/boat_simulator/boat_simulator/common/unit_conversions.py @@ -131,6 +131,9 @@ class ConversionFactors(Enum): km_to_nautical_mi = nautical_mi_to_km.inverse() # Time + sec_to_ms = ConversionFactor(factor=1000) + ms_to_sec = sec_to_ms.inverse() + min_to_sec = ConversionFactor(factor=60) sec_to_min = min_to_sec.inverse() @@ -199,7 +202,9 @@ def __init__(self, **kwargs: EnumAttr): belonging to `ConversionFactors`. """ for attr_name, attr_val in kwargs.items(): - assert isinstance(attr_val, Enum) and isinstance(attr_val.value, ConversionFactor) + assert isinstance(attr_val, Enum) and isinstance( + attr_val.value, ConversionFactor + ) setattr(self, attr_name, attr_val) def convert(self, **kwargs: ScalarOrArray) -> Dict[str, ScalarOrArray]: @@ -223,7 +228,9 @@ def convert(self, **kwargs: ScalarOrArray) -> Dict[str, ScalarOrArray]: for attr_name, attr_val in kwargs.items(): attr = getattr(self, attr_name, None) - assert attr is not None, f"Attribute name {attr} not found in UnitConverter." + assert ( + attr is not None + ), f"Attribute name {attr} not found in UnitConverter." conversion_factor = attr.value converted_values[attr_name] = conversion_factor.forward_convert(attr_val) diff --git a/src/boat_simulator/tests/unit/common/test_gps_sensor.py b/src/boat_simulator/tests/unit/common/test_gps_sensor.py index 60f2568d8..90a3ab982 100644 --- a/src/boat_simulator/tests/unit/common/test_gps_sensor.py +++ b/src/boat_simulator/tests/unit/common/test_gps_sensor.py @@ -1,9 +1,5 @@ from boat_simulator.common.sensors import GPS import numpy as np -from boat_simulator.common.generators import ( - ConstantGenerator, - GaussianGenerator, -) class TestGPS: @@ -11,96 +7,38 @@ def test_gps_init(self): lat_lon = np.array([1, 0]) speed = 100 heading = 1.09 - error_fn = None gps = GPS( lat_lon=lat_lon, speed=speed, heading=heading, - lat_lon_noisemaker=error_fn, - speed_noisemaker=error_fn, - heading_noisemaker=error_fn, ) assert (gps.lat_lon == lat_lon).all() assert gps.speed == speed assert gps.heading == heading - assert gps.lat_lon_noisemaker is error_fn - assert gps.speed_noisemaker is error_fn - assert gps.heading_noisemaker is error_fn - def test_gps_init_implicit_error_fn(self): - lat_lon = np.array([1, 0]) - speed = 100 - heading = 1.09 - - gps = GPS( - lat_lon=lat_lon, - speed=speed, - heading=heading, - ) - - assert (gps.lat_lon == lat_lon).all() - assert gps.speed == speed - assert gps.heading == heading - for noisemaker in [ - gps.lat_lon_noisemaker, - gps.speed_noisemaker, - gps.heading_noisemaker, - ]: - assert noisemaker is None - - def test_gps_read_no_error(self): + def test_gps_read_no_noise(self): lat_lon = np.array([1, 0]) speed = np.random.randint(0, 100) heading = np.random.rand() - gps = GPS( - lat_lon=lat_lon, - speed=speed, - heading=heading, - ) + gps = GPS(lat_lon=lat_lon, speed=speed, heading=heading, enable_noise=False) - assert (gps.read("lat_lon") == lat_lon).all() + assert np.all(gps.read("lat_lon") == lat_lon) assert gps.read("speed") == speed assert gps.read("heading") == heading - def test_gps_read_constant_error(self): - lat_lon = np.array([1, 0]) - speed = np.random.randint(0, 100) - heading = np.random.rand() - constant = 3.01 - error_fn = ConstantGenerator(constant=constant) - - gps = GPS( - lat_lon=lat_lon, - speed=speed, - heading=heading, - lat_lon_noisemaker=error_fn, - speed_noisemaker=error_fn, - heading_noisemaker=error_fn, - ) - - assert (gps.read("lat_lon") == lat_lon + constant).all() - assert gps.read("speed") == speed + constant - assert gps.read("heading") == heading + constant - - def test_gps_gaussian_error(self): + def test_gps_gaussian_noise(self): lat_lon = np.array([1, 0]) speed = np.random.randint(0, 100) heading = np.random.rand() mean = 0 - stdev = 1 - - error_fn = GaussianGenerator(mean=mean, stdev=stdev) gps = GPS( lat_lon=lat_lon, speed=speed, heading=heading, - lat_lon_noisemaker=error_fn, - speed_noisemaker=error_fn, - heading_noisemaker=error_fn, ) NUM_READINGS = 10000 @@ -117,9 +55,9 @@ def test_gps_gaussian_error(self): [speed, heading, lat_lon], ): sample_mean = np.mean(reading, axis=0) - assert np.isclose(sample_mean, mean + init_data, atol=0.1).all() + assert np.allclose(sample_mean, mean + init_data, atol=0.1) - def test_wind_sensor_update(self): + def test_gps_sensor_update(self): lat_lon = np.array([0, 0]) speed = 0 heading = 0 @@ -143,3 +81,17 @@ def test_wind_sensor_update(self): lat_lon_reading = gps.read("lat_lon") assert (lat_lon_reading == np.array([i, i])).all() gps.update(lat_lon=(lat_lon_reading + 1)) + + def test_gps_sensor_update_delay(self): + lat_lon = np.array([0, 0]) + speed0 = 0 + heading = 0 + + # Initialized data is read without delay + gps = GPS(lat_lon=lat_lon, speed=speed0, heading=heading, enable_delay=True) + assert gps.read("speed") == speed0 + + NUM_UPDATES = 3 + for i in range(NUM_UPDATES): + gps.update(speed=(i + 1)) + assert gps.read("speed") == i diff --git a/src/boat_simulator/tests/unit/common/test_wind_sensor.py b/src/boat_simulator/tests/unit/common/test_wind_sensor.py index aed0d1c88..f31512686 100644 --- a/src/boat_simulator/tests/unit/common/test_wind_sensor.py +++ b/src/boat_simulator/tests/unit/common/test_wind_sensor.py @@ -1,59 +1,29 @@ from boat_simulator.common.sensors import WindSensor import numpy as np -from boat_simulator.common.generators import ( - MVGaussianGenerator, - ConstantGenerator, -) class TestWindSensor: def test_wind_sensor_init(self): init_data = np.array([1, 0]) - error_fn = None ws = WindSensor( wind=init_data, - wind_noisemaker=error_fn, ) - assert ws.wind_noisemaker == error_fn assert np.all(ws.wind == init_data) - def test_wind_sensor_init_implicit_error_fn(self): + def test_wind_sensor_read_no_noise(self): init_data = np.array([1, 0]) - ws = WindSensor(wind=init_data) - - assert ws.wind_noisemaker is None - assert np.all(ws.wind == init_data) - - def test_wind_sensor_read_no_error(self): - init_data = np.array([1, 0]) - ws = WindSensor( - wind=init_data, - ) - read_data = ws.read("wind") - assert (init_data == read_data).all() - - def test_wind_sensor_read_constant_error(self): - init_data = np.array([1, 0]) - const_err = 0.1 - error_fn = ConstantGenerator(constant=0.1) ws = WindSensor( wind=init_data, - wind_noisemaker=error_fn, ) - read_data = ws.read("wind") - assert ((init_data + const_err) == read_data).all() + assert np.all(init_data == read_data) - def test_wind_sensor_read_mv_gaussian_error(self): - init_data = np.array([1, 0]) - mean = np.array([1, 1]) + def test_wind_sensor_read_mv_gaussian_noise(self): + init_data = np.array([0, 0]) + mean = np.array([0, 0]) cov = np.eye(2) - error_fn = MVGaussianGenerator(mean=mean, cov=cov) - ws = WindSensor( - wind=init_data, - wind_noisemaker=error_fn, - ) + ws = WindSensor(wind=init_data, enable_noise=True) NUM_READINGS = 10000 reading = np.zeros(shape=(NUM_READINGS, mean.size)) @@ -63,15 +33,35 @@ def test_wind_sensor_read_mv_gaussian_error(self): sample_mean = np.mean(reading, axis=0) sample_cov = np.cov(reading, rowvar=False) + assert np.allclose(sample_mean, mean, atol=0.2) assert np.allclose(sample_cov, cov, atol=0.2) - assert np.isclose(sample_mean, mean + init_data, 0.1).all() - def test_wind_sensor_update(self): - init_data = np.zeros(2) + def test_wind_sensor_update_no_delay(self): + init_data = np.array([0, 0]) ws = WindSensor(wind=init_data) NUM_READINGS = 100 for i in range(NUM_READINGS): wind = ws.read("wind") - assert (wind == np.array([i, i])).all() + assert np.all(wind == np.array([i, i])) ws.update(wind=(wind + 1)) + + def test_wind_sensor_update_with_delay(self): + """ + Attempt to constantly update wind sensor with new data. + Delay causes new data to be read in the next update cycle. + """ + + init_data = np.array([0, 0]) + + ws = WindSensor(wind=init_data, enable_delay=True) + + wind = ws.read("wind") + # Initialized data is read without delay + assert np.all(wind == init_data) + + NUM_UPDATES = 3 + for i in range(NUM_UPDATES): + ws.update(wind=np.array([i + 1, i + 1])) + wind = ws.read("wind") + assert np.all(wind == [i, i]) From e0bdfc7411e68ab163811f166727753929168a84 Mon Sep 17 00:00:00 2001 From: Sean Donaghy <118148642+SPDonaghy@users.noreply.github.com> Date: Tue, 19 Mar 2024 16:41:43 -0700 Subject: [PATCH 20/24] Migrate Package Specific Dependencies from Image to package.xml (#244) * removed local path python deps * plotly and flask should remain in the image as they are not needed by ros nodes * updated base image tag, rebuild when image ready * combined pip3 installs together * changed image tag * update local_path branch to test * Migrate other package-specific dependencies * Move ros log configuration to bashrc script * move rapidyaml to local-base * Update tag * Install python packages last * set local_pathfinding branch back to main in .repos * migrated 3 network deps to package * updated image tag --------- Co-authored-by: Patrick Creighton --- .devcontainer/Dockerfile | 2 +- .devcontainer/base-dev/base-dev.Dockerfile | 49 +++++++--------------- .devcontainer/base-dev/update-bashrc.sh | 2 + setup.sh | 14 +++++++ src/integration_tests/custom-rosdep.yaml | 4 ++ src/integration_tests/package.xml | 4 ++ src/network_systems/package.xml | 12 +++++- 7 files changed, 51 insertions(+), 36 deletions(-) create mode 100644 src/integration_tests/custom-rosdep.yaml diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 28e8a60db..d4d12c078 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM ghcr.io/ubcsailbot/sailbot_workspace/dev:fix-building-pre-base +FROM ghcr.io/ubcsailbot/sailbot_workspace/dev:moved-network-deps # Copy configuration files (e.g., .vimrc) from config/ to the container's home directory ARG USERNAME=ros diff --git a/.devcontainer/base-dev/base-dev.Dockerfile b/.devcontainer/base-dev/base-dev.Dockerfile index 3499485b3..22cb0cc18 100644 --- a/.devcontainer/base-dev/base-dev.Dockerfile +++ b/.devcontainer/base-dev/base-dev.Dockerfile @@ -194,14 +194,6 @@ RUN apt-get update \ && rosdep init || echo "rosdep already initialized" ENV DEBIAN_FRONTEND= -# install base python3 dependencies -RUN pip3 install \ - # from local pathfinding - plotly \ - pyproj \ - flask \ - shapely - # root bash configuration ENV ROS_WORKSPACE=/workspaces/sailbot_workspace COPY update-bashrc.sh /sbin/update-bashrc @@ -213,9 +205,6 @@ RUN chmod +x /sbin/update-bashrc \ # set timezone ENV TZ="America/Vancouver" -# customize ROS log format: https://docs.ros.org/en/humble/Concepts/About-Logging.html#environment-variables -ENV RCUTILS_CONSOLE_OUTPUT_FORMAT="[{severity}] [{time}] [{name}:{line_number}]: {message}" - FROM base as local-base # install virtual iridium dependencies @@ -255,6 +244,17 @@ RUN apt-get update \ && rm -rf /var/lib/apt/lists/{apt,dpkg,cache,log} /tmp/* /var/tmp/* ENV DEBIAN_FRONTEND= +# install rapidyaml for diagnostics +ENV DEBIAN_FRONTEND=noninteractive +RUN wget https://github.com/biojppm/rapidyaml/releases/download/v0.5.0/rapidyaml-0.5.0-src.tgz +RUN tar -xzf rapidyaml-0.5.0-src.tgz && \ + cd rapidyaml-0.5.0-src && \ + cmake -S "." -B ./build/Release/ryml-build "-DCMAKE_INSTALL_PREFIX=/usr" -DCMAKE_BUILD_TYPE=Release && \ + cmake --build ./build/Release/ryml-build --parallel --config Release && \ + cmake --build ./build/Release/ryml-build --config Release --target install && \ + rm -rf *rapidyaml* +ENV DEBIAN_FRONTEND= + FROM local-base as ros-dev # From https://github.com/athackst/dockerfiles/blob/32a872348af0ad25ec4a6e6184cb803357acb6ab/ros2/humble.Dockerfile @@ -354,33 +354,11 @@ RUN apt-get update \ clangd \ clang-tidy \ cmake \ - googletest \ - libboost-all-dev \ - libprotobuf-dev \ - protobuf-compiler \ && apt-get autoremove -y \ && apt-get clean -y \ && rm -rf /var/lib/apt/lists/{apt,dpkg,cache,log} /tmp/* /var/tmp/* ENV DEBIAN_FRONTEND= -# install rapidyaml for diagnostics -ENV DEBIAN_FRONTEND=noninteractive -RUN wget https://github.com/biojppm/rapidyaml/releases/download/v0.5.0/rapidyaml-0.5.0-src.tgz -RUN tar -xzf rapidyaml-0.5.0-src.tgz && \ - cd rapidyaml-0.5.0-src && \ - cmake -S "." -B ./build/Release/ryml-build "-DCMAKE_INSTALL_PREFIX=/usr" -DCMAKE_BUILD_TYPE=Release && \ - cmake --build ./build/Release/ryml-build --parallel --config Release && \ - cmake --build ./build/Release/ryml-build --config Release --target install && \ - rm -rf *rapidyaml* -ENV DEBIAN_FRONTEND= - -# install dev python3 dependencies -RUN pip3 install \ - # to be able to run juypter notebooks - ipykernel \ - # for integration_tests package - types-PyYAML - # install other helpful apt packages ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update \ @@ -392,3 +370,8 @@ RUN apt-get update \ && apt-get clean -y \ && rm -rf /var/lib/apt/lists/{apt,dpkg,cache,log} /tmp/* /var/tmp/* ENV DEBIAN_FRONTEND= + +# install dev python3 dependencies +RUN pip3 install \ + # for juypter notebooks + ipykernel diff --git a/.devcontainer/base-dev/update-bashrc.sh b/.devcontainer/base-dev/update-bashrc.sh index 36efa2dc4..0a08e0118 100644 --- a/.devcontainer/base-dev/update-bashrc.sh +++ b/.devcontainer/base-dev/update-bashrc.sh @@ -11,6 +11,8 @@ echo "" >> $HOME/.bashrc echo "# set up ROS environment" >> $HOME/.bashrc echo "source /usr/share/colcon_cd/function/colcon_cd.sh" >> $HOME/.bashrc echo "export _colcon_cd_root=$ROS_WORKSPACE" >> $HOME/.bashrc +echo "# customize ROS log format: https://docs.ros.org/en/humble/Concepts/About-Logging.html#environment-variables" >> $HOME/.bashrc +echo "export RCUTILS_CONSOLE_OUTPUT_FORMAT='[{severity}] [{time}] [{name}:{line_number}]: {message}'" >> $HOME/.bashrc echo "source /opt/ros/$ROS_DISTRO/setup.bash" >> $HOME/.bashrc echo "if [ -f $ROS_WORKSPACE/install/local_setup.bash ]" >> $HOME/.bashrc echo "then" >> $HOME/.bashrc diff --git a/setup.sh b/setup.sh index ab0425f87..b65ac1040 100755 --- a/setup.sh +++ b/setup.sh @@ -1,6 +1,20 @@ #!/bin/bash set -e +# Create/overwrite the custom rosdep list file +CUSTOM_ROSDEP_LIST="/etc/ros/rosdep/sources.list.d/20-sailbot.list" +CUSTOM_ROSDEP_FILE="custom-rosdep.yaml" +echo "# sailbot" | sudo tee $CUSTOM_ROSDEP_LIST > /dev/null +for DIR in $ROS_WORKSPACE/src/*; do + if [ -d "$DIR" ]; then + FILE="$DIR/$CUSTOM_ROSDEP_FILE" + if [ -f $FILE ]; then + echo "Adding $FILE to $CUSTOM_ROSDEP_LIST" + echo "yaml file://$FILE" | sudo tee --append $CUSTOM_ROSDEP_LIST > /dev/null + fi + fi +done + sudo apt-get update rosdep update --rosdistro $ROS_DISTRO rosdep install --from-paths src --ignore-src -y --rosdistro $ROS_DISTRO diff --git a/src/integration_tests/custom-rosdep.yaml b/src/integration_tests/custom-rosdep.yaml new file mode 100644 index 000000000..0be358f82 --- /dev/null +++ b/src/integration_tests/custom-rosdep.yaml @@ -0,0 +1,4 @@ +python3-pyyaml-types-pip: + ubuntu: + pip: + packages: [types-PyYAML] diff --git a/src/integration_tests/package.xml b/src/integration_tests/package.xml index f15345d2f..fa7ddc0f2 100644 --- a/src/integration_tests/package.xml +++ b/src/integration_tests/package.xml @@ -7,9 +7,13 @@ Henry Huang MIT + rclpy custom_interfaces + + python3-pyyaml-types-pip + ament_python diff --git a/src/network_systems/package.xml b/src/network_systems/package.xml index c0c2c28fd..f57ddba19 100755 --- a/src/network_systems/package.xml +++ b/src/network_systems/package.xml @@ -8,11 +8,19 @@ Henry Huang Apache License 2.0 + ament_cmake - rclcpp - std_msgs custom_interfaces + rclcpp ros2launch + std_msgs + + + boost + + protobuf-dev + + gtest ament_cmake From 0c6d2b2d479bc53a0f4c2a2bfa50fd60715f7cdc Mon Sep 17 00:00:00 2001 From: John Ahn Date: Sat, 23 Mar 2024 11:00:12 -0700 Subject: [PATCH 21/24] Update website base image to node:20-alpine (#339) * Update website base image to node:20-alpine * Remove previous base image --- .devcontainer/website/website.Dockerfile | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.devcontainer/website/website.Dockerfile b/.devcontainer/website/website.Dockerfile index 4c8d7f4d1..0a19cdac8 100644 --- a/.devcontainer/website/website.Dockerfile +++ b/.devcontainer/website/website.Dockerfile @@ -1,7 +1,4 @@ -# Copied from https://github.com/microsoft/vscode-dev-containers/blob/5a084a93b0736ea86395ac99019a5b72a00b6341/containers/javascript-node-mongo/.devcontainer/Dockerfile -# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster -ARG VARIANT=20 -FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT} +FROM node:20-alpine # [Optional] Uncomment this section to install additional OS packages. # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ From e2b3672a86db855c87917d3e51e69154e0ceb0be Mon Sep 17 00:00:00 2001 From: tanmay thakral <46583379+tanmaythakral@users.noreply.github.com> Date: Sat, 23 Mar 2024 14:31:20 -0700 Subject: [PATCH 22/24] Create Fluid Force Computation class (#337) * Migrate code for issue 307 * Made suggested changes to the fluid_forces.py file and added a test case for the same. * Added documentation. * Made sugegsted changes to the fluid_forces.py file and added tests for the same. * minor fixes * Lint fix --------- Co-authored-by: Devon Friend --- .../nodes/physics_engine/fluid_forces.py | 276 +++++++++++++++++- src/boat_simulator/package.xml | 2 + .../nodes/physics_engine/test_fluid_forces.py | 102 +++++++ 3 files changed, 368 insertions(+), 12 deletions(-) create mode 100644 src/boat_simulator/tests/unit/nodes/physics_engine/test_fluid_forces.py diff --git a/src/boat_simulator/boat_simulator/nodes/physics_engine/fluid_forces.py b/src/boat_simulator/boat_simulator/nodes/physics_engine/fluid_forces.py index c479bdb2d..770e8f343 100644 --- a/src/boat_simulator/boat_simulator/nodes/physics_engine/fluid_forces.py +++ b/src/boat_simulator/boat_simulator/nodes/physics_engine/fluid_forces.py @@ -2,9 +2,12 @@ from typing import Tuple +import matplotlib.patches as patches +import matplotlib.pyplot as plt +import numpy as np from numpy.typing import NDArray -from boat_simulator.common.types import Scalar +from boat_simulator.common.utils import Scalar class MediumForceComputation: @@ -18,9 +21,7 @@ class MediumForceComputation: `drag_coefficients` (NDArray): An array of shape (n, 2) where each row contains a pair (x, y) representing an angle of attack, in degrees, and its corresponding drag coefficient. - `areas` (NDArray): An array of shape (n, 2) where each row contains a pair (x, y), - representing an angle of attack, in degrees, and its corresponding area, in square - meters (m^2). + `areas` (Scalar): Corresponding area, in square meters (m^2). `fluid_density` (Scalar): The density of the fluid acting on the medium, expressed in kilograms per cubic meter (kg/m^3). """ @@ -29,7 +30,7 @@ def __init__( self, lift_coefficients: NDArray, drag_coefficients: NDArray, - areas: NDArray, + areas: Scalar, fluid_density: Scalar, ): self.__lift_coefficients = lift_coefficients @@ -37,6 +38,40 @@ def __init__( self.__areas = areas self.__fluid_density = fluid_density + def calculate_attack_angle(self, apparent_velocity: NDArray, orientation: Scalar) -> Scalar: + """Calculates the angle of attack formed between the orientation angle of the medium + and the direction of the apparent velocity, bounded between -180 and 180 degrees. + + Args: + apparent_velocity (NDArray): The apparent (relative) velocity between the fluid and + the medium, expressed in meters per second (m/s). + orientation (Scalar): The orientation angle of the medium in degrees. + + Returns: + Scalar: The angle of attack formed between the orientation angle of the medium and + the direction of the apparent velocity, expressed in degrees + and bounded between -180 and 180 degrees. + """ + # Check if the apparent velocity is [0, 0] + if np.all(apparent_velocity == 0): + # Directly return the normalized orientation as the angle of attack + # Normalize orientation to be within [-180, 180) + return ((orientation + 180) % 360) - 180 + + # Calculate the angle in degrees of the apparent velocity + angle_of_attack_raw = np.rad2deg(np.arctan2(apparent_velocity[1], apparent_velocity[0])) + + # Adjust orientation to be in the range of [-180, 180) + orientation = ((orientation + 180) % 360) - 180 + + # Calculate the raw angle of attack by subtracting the orientation from the velocity angle + angle_of_attack = angle_of_attack_raw - orientation + + # Normalize the angle of attack to [-180, 180) range + angle_of_attack = ((angle_of_attack + 180) % 360) - 180 + + return angle_of_attack + def compute(self, apparent_velocity: NDArray, orientation: Scalar) -> Tuple[NDArray, NDArray]: """Computes the lift and drag forces experienced by a medium immersed in a fluid. @@ -52,11 +87,97 @@ def compute(self, apparent_velocity: NDArray, orientation: Scalar) -> Tuple[NDAr by the medium, both expressed in newtons (N). """ - # TODO: Implement this method. + attack_angle = self.calculate_attack_angle(apparent_velocity, orientation) + lift_coefficient, drag_coefficient = self.interpolate(attack_angle) + velocity_magnitude = np.linalg.norm(apparent_velocity) + + # Calculate the lift and drag forces - raise NotImplementedError() + lift_force_magnitude = self.__calculate_fluid_force_magnitude( + lift_coefficient, velocity_magnitude + ) + drag_force_magnitude = self.__calculate_fluid_force_magnitude( + drag_coefficient, velocity_magnitude + ) - def interpolate(self, attack_angle: Scalar) -> Tuple[Scalar, Scalar, Scalar]: + drag_force_unit_vector = apparent_velocity / velocity_magnitude + drag_force_unit_vector = self.__rotate_vector(drag_force_unit_vector, orientation) + + # Rotate the lift and drag forces by 90 degrees to obtain the lift and drag forces + + # Convention used here is that the positive x-axis is 0 degrees + # and the positive y-axis is 90 degrees + # Positive rotation is counter clockwise + + is_drag_in_first_or_third_quadrant = ( + drag_force_unit_vector[0] > 0 and drag_force_unit_vector[1] > 0 + ) or (drag_force_unit_vector[0] < 0 and drag_force_unit_vector[1] < 0) + + is_drag_in_second_or_fourth_quadrant = ( + drag_force_unit_vector[0] > 0 and drag_force_unit_vector[1] < 0 + ) or (drag_force_unit_vector[0] < 0 and drag_force_unit_vector[1] > 0) + + # Rotate the lift force direction based on the quadrant of the drag force + if is_drag_in_first_or_third_quadrant: + # Rotate counter clockwise to get lift direction + lift_force_direction = np.array( + [-drag_force_unit_vector[1], drag_force_unit_vector[0]] + ) + elif is_drag_in_second_or_fourth_quadrant: + # Rotate clockwise to get lift direction + lift_force_direction = np.array( + [drag_force_unit_vector[1], -drag_force_unit_vector[0]] + ) + else: + # Should not happen if drag force direction is properly normalized + # This could be a fallback for an unexpected case + lift_force_direction = np.array([0, 0]) + + # Rotate the lift and drag forces back to the original orientation + lift_force_direction = self.__rotate_vector( + lift_force_direction, orientation, clockwise=False + ) + drag_force_unit_vector = self.__rotate_vector( + drag_force_unit_vector, orientation, clockwise=False + ) + + lift_force = lift_force_magnitude * lift_force_direction + drag_force = drag_force_magnitude * drag_force_unit_vector + + return lift_force, drag_force + + def __calculate_fluid_force_magnitude( + self, coefficient: Scalar, velocity_magnitude: Scalar + ) -> Scalar: + """Calculates the magnitude of fluid forces based on coefficient and velocity.""" + return 0.5 * self.__fluid_density * coefficient * self.__areas * (velocity_magnitude**2) + + def __rotate_vector(self, v: NDArray, theta_degrees: Scalar, clockwise=True) -> NDArray: + """ + Rotates a vector by a specified angle in degrees. + + Args: + v (np.array): The vector to be rotated. + theta_degrees (float): The rotation angle in degrees. + clockwise (bool, optional): Determines the direction of rotation. If True (default), + rotates the vector clockwise. If False, rotates the vector + counterclockwise. + + Returns: + np.array: The rotated vector. + """ + theta_radians = np.deg2rad(theta_degrees) + sign = 1 if clockwise else -1 + rotation_matrix = np.array( + [ + [np.cos(theta_radians), sign * np.sin(theta_radians)], + [-sign * np.sin(theta_radians), np.cos(theta_radians)], + ] + ) + v_rotated = np.dot(rotation_matrix, v) + return v_rotated + + def interpolate(self, attack_angle: Scalar) -> Tuple[Scalar, Scalar]: """Performs linear interpolation to estimate the lift and drag coefficients, as well as the associated area upon which the fluid acts, based on the provided angle of attack. @@ -65,16 +186,147 @@ def interpolate(self, attack_angle: Scalar) -> Tuple[Scalar, Scalar, Scalar]: the medium and the direction of the apparent velocity, expressed in degrees. Returns: - Tuple[Scalar, Scalar, Scalar]: A tuple representing the computed parameters. The + Tuple[Scalar, Scalar]: A tuple representing the computed parameters. The first scalar denotes the lift coefficient, the second scalar represents the drag coefficient, and the third scalar indicates the surface area upon which the fluid acts. Both lift and drag coefficients are unitless, while the area is expressed in square meters (m^2). """ - # TODO: Implement this method using `np.interp`. + lift_coefficient = np.interp( + attack_angle, self.__lift_coefficients[:, 0], self.__lift_coefficients[:, 1] + ) + drag_coefficient = np.interp( + attack_angle, self.__drag_coefficients[:, 0], self.__drag_coefficients[:, 1] + ) + return lift_coefficient, drag_coefficient + + def _draw_boat(ax, position, orientation): + """Draws a simplified boat shape on the given axes, ensuring it aligns + with the orientation line.""" + boat_length = 1.0 + boat_width = 0.3 + + # Center the shape around (0, 0) + front_extension = 3 * boat_length / 4 + rear_extension = -boat_length / 4 + + # Calculate the offset to center the shape + offset = front_extension + rear_extension + + boat_shape = np.array( + [ + [front_extension - offset, 0], + [boat_length / 2 - offset, boat_width / 2], + [rear_extension - offset, 0], + [boat_length / 2 - offset, -boat_width / 2], + [front_extension - offset, 0], + ] + ) + + # Rotation matrix for anticlockwise rotation + rotation_matrix = np.array( + [ + [np.cos(np.deg2rad(orientation)), np.sin(np.deg2rad(orientation))], + [np.sin(np.deg2rad(orientation)), np.cos(np.deg2rad(orientation))], + ] + ) + + # Apply rotation + rotated_boat = np.dot(boat_shape, rotation_matrix) + + # Translate boat to its position + translated_boat = rotated_boat + np.array(position) + + # Draw the boat + ax.plot(translated_boat[:, 0], translated_boat[:, 1], "k") + + # Ensure the orientation line is drawn correctly + # Calculate a point along the orientation direction + direction = np.array([np.cos(np.deg2rad(orientation)), np.sin(np.deg2rad(orientation))]) + line_start = np.array(position) + line_end = ( + line_start + direction * boat_length + ) # Extend the line out from the boat's position + + # Draw orientation line + ax.plot( + [line_start[0], line_end[0]], [line_start[1], line_end[1]], "black", linestyle="--" + ) + + def visualize_forces( + self, apparent_velocity, lift_force, drag_force, position=[0, 0], orientation=0 + ): + """Visualizes the sailboat, apparent velocity, lift force, and drag force.""" + fig, ax = plt.subplots() + attack_angle = self.calculate_attack_angle(apparent_velocity, orientation) + # Normalize forces for visualization + norm_apparent_velocity = apparent_velocity / np.linalg.norm(apparent_velocity) + norm_lift_force = lift_force / np.linalg.norm(lift_force) + norm_drag_force = drag_force / np.linalg.norm(drag_force) + + # Draw the boat + MediumForceComputation._draw_boat(ax, position, orientation) + # Plot forces and velocity + ax.quiver( + position[0], + position[1], + norm_apparent_velocity[0], + norm_apparent_velocity[1], + color="blue", + scale=5, + label="Apparent Velocity", + pivot="tip", + ) + ax.quiver( + position[0], + position[1], + norm_lift_force[0], + norm_lift_force[1], + color="red", + scale=5, + label="Lift Force", + ) + ax.quiver( + position[0], + position[1], + norm_drag_force[0], + norm_drag_force[1], + color="green", + scale=5, + label="Drag Force", + ) + orientation_rad = np.deg2rad(orientation) # Convert orientation to radians + ax.axline((0, 0), slope=np.tan(orientation_rad), color="black", linestyle="--") + + # Calculate angle for drag force + drag_angle = np.arctan2(norm_drag_force[1], norm_drag_force[0]) + + # Determine start and end angles for the arc + start_angle = np.rad2deg(orientation_rad) + end_angle = np.rad2deg(drag_angle) + + # Draw arc to represent angle between orientation and drag force + radius = 0.05 + arc = patches.Arc( + position, + 2 * radius, + 2 * radius, + angle=0, + theta1=min(start_angle, end_angle), + theta2=max(start_angle, end_angle), + color="purple", + label="Angle Arc", + ) + ax.add_patch(arc) - raise NotImplementedError() + ax.axis("equal") + ax.legend() + plt.title("Forces Acting on Sailboat for Attack Angle: " + str(round(attack_angle))) + plt.xlabel("X-axis") + plt.ylabel("Y-axis") + plt.grid(True) + plt.show() @property def lift_coefficients(self) -> NDArray: @@ -85,7 +337,7 @@ def drag_coefficients(self) -> NDArray: return self.__drag_coefficients @property - def areas(self) -> NDArray: + def areas(self) -> Scalar: return self.__areas @property diff --git a/src/boat_simulator/package.xml b/src/boat_simulator/package.xml index 55401ed73..cec78dd46 100644 --- a/src/boat_simulator/package.xml +++ b/src/boat_simulator/package.xml @@ -11,6 +11,7 @@ ament_flake8 ament_pep257 python3-pytest + python3-matplotlib custom_interfaces @@ -20,6 +21,7 @@ python3-numpy python3-scipy + python3-matplotlib ament_python diff --git a/src/boat_simulator/tests/unit/nodes/physics_engine/test_fluid_forces.py b/src/boat_simulator/tests/unit/nodes/physics_engine/test_fluid_forces.py new file mode 100644 index 000000000..349df2dc6 --- /dev/null +++ b/src/boat_simulator/tests/unit/nodes/physics_engine/test_fluid_forces.py @@ -0,0 +1,102 @@ +import math + +import numpy as np +import pytest + +from boat_simulator.nodes.physics_engine.fluid_forces import MediumForceComputation + + +@pytest.fixture +def medium_force_setup(): + lift_coefficients = np.array([[0, 0], [5, 0.57], [10, 1.10], [15, 1.39], [20, 1.08]]) + drag_coefficients = np.array([[0, 0.013], [5, 0.047], [10, 0.144], [15, 0.279], [20, 0.298]]) + areas = 9.0 + fluid_density = 1.225 + + computation = MediumForceComputation( + lift_coefficients, drag_coefficients, areas, fluid_density + ) + return computation + + +def test_initialization(medium_force_setup): + assert isinstance(medium_force_setup.lift_coefficients, np.ndarray) + assert isinstance(medium_force_setup.drag_coefficients, np.ndarray) + assert isinstance(medium_force_setup.areas, (int, float)) + assert isinstance(medium_force_setup.fluid_density, (int, float)) + + +@pytest.mark.parametrize( + "apparent_velocity, orientation, expected_angle", + [ + # Test zero apparent velocity with various orientations, + # including edge cases and normalization + (np.array([0, 0]), 0, 0), + (np.array([0, 0]), 45, 45), + (np.array([0, 0]), 90, 90), + (np.array([0, 0]), 180, -180), # Normalized to -180 + (np.array([0, 0]), 270, -90), # Normalized to -90 + (np.array([0, 0]), 360, 0), + (np.array([0, 0]), -45, -45), # Test negative orientation + (np.array([0, 0]), 405, 45), # Orientation beyond 360 + (np.array([0, 0]), -405, -45), # Orientation below -360 + # Test non-zero apparent velocity for comprehensive angle of attack calculations + (np.array([1, 0]), 0, 0), + (np.array([0, 1]), 0, 90), + (np.array([-1, 0]), 0, -180), + (np.array([0, -1]), 0, -90), + (np.array([1, 1]), 45, 0), + (np.array([-1, -1]), 135, 90), + # Edge cases where orientation and velocity directions are opposite or identical + (np.array([1, 0]), 180, -180), + (np.array([-1, 0]), 180, 0), + (np.array([0, 1]), 270, -180), + (np.array([0, -1]), 90, -180), + # Additional tests with non-unit vectors + (np.array([2, 0]), 0, 0), # Horizontal vector, twice the unit length + (np.array([0, 2]), 0, 90), # Vertical vector, twice the unit length + (np.array([-2, 0]), 0, -180), # Left horizontal, twice the unit length + (np.array([3, 4]), 0, np.rad2deg(np.arctan2(4, 3))), # 3-4-5 triangle vector + (np.array([5, 5]), 45, 0), # Diagonal upward, aligned with orientation + ], +) +def test_calculate_attack_angle( + apparent_velocity, orientation, expected_angle, medium_force_setup +): + attack_angle = medium_force_setup.calculate_attack_angle(apparent_velocity, orientation) + assert np.isclose( + attack_angle, expected_angle, atol=1e-7 + ), f"Expected {expected_angle}, got {attack_angle}" + + +# Test taken from https://www1.grc.nasa.gov/beginners-guide-to-aeronautics/foilsimstudent/ +@pytest.mark.parametrize( + "orientation, expected_lift, expected_drag, apparent_velocity", + [ + # Tests for attack angle 0 + (0, 0, 140, np.array([44 * math.cos(0), 44 * math.sin(0)])), + # # # Tests for attack angle 5 + (0, 6262, 509, np.array([44 * math.cos(np.deg2rad(5)), 44 * math.sin(np.deg2rad(5))])), + # # Tests for attack angle 10 + (0, 11934, 1568, np.array([44 * math.cos(np.deg2rad(10)), 44 * math.sin(np.deg2rad(10))])), + # # Tests for attack angle 15 + (0, 15162, 3035, np.array([44 * math.cos(np.deg2rad(15)), 44 * math.sin(np.deg2rad(15))])), + # Tests for attack angle 20 + ( + 0, + 11768, + 3249, + np.array([44 * math.cos(np.deg2rad(20)), 44 * math.sin(np.deg2rad(20))]), + ), + ], +) +def test_compute_forces( + medium_force_setup, orientation, expected_lift, expected_drag, apparent_velocity +): + lift_force, drag_force = medium_force_setup.compute(apparent_velocity, orientation) + assert np.isclose( + np.linalg.norm(lift_force), expected_lift, rtol=0.05 + ), f"Expected {expected_lift}, got {np.linalg.norm(lift_force)}" + assert np.isclose( + np.linalg.norm(drag_force), expected_drag, rtol=0.05 + ), f"Expected {expected_drag}, got {np.linalg.norm(drag_force)}" From 092d097a2908c711988bb919bfffadc22ecdbf4c Mon Sep 17 00:00:00 2001 From: Jing <128339540+Jng468@users.noreply.github.com> Date: Sat, 23 Mar 2024 14:34:30 -0700 Subject: [PATCH 23/24] [NET] Local Transceiver: ROS modes and config file (#343) * Added modes and config file for Local Transceiver * Fixed error with spacing * Adjust comments --------- Co-authored-by: hhenry01 --- .../local_transceiver_template.yaml | 6 + src/network_systems/launch/main_launch.py | 32 +++++ .../src/local_transceiver_ros_intf.cpp | 135 +++++++++--------- 3 files changed, 105 insertions(+), 68 deletions(-) create mode 100644 src/network_systems/config/local_transceiver/local_transceiver_template.yaml diff --git a/src/network_systems/config/local_transceiver/local_transceiver_template.yaml b/src/network_systems/config/local_transceiver/local_transceiver_template.yaml new file mode 100644 index 000000000..fd1382e84 --- /dev/null +++ b/src/network_systems/config/local_transceiver/local_transceiver_template.yaml @@ -0,0 +1,6 @@ +# Template for the local_transceiver module +local_transceiver_node: + ros__parameters: + enabled: true + # The following parameters are optional. Defaults are set in local_transceiver_ros_intf.cpp + port: # String: Serial port that the Local Transceiver will use (default: /tmp/local_transceiver_test_port) diff --git a/src/network_systems/launch/main_launch.py b/src/network_systems/launch/main_launch.py index 183a6cbb4..9241dc518 100644 --- a/src/network_systems/launch/main_launch.py +++ b/src/network_systems/launch/main_launch.py @@ -86,6 +86,7 @@ def setup_launch(context: LaunchContext) -> List[Node]: launch_description_entities.append(get_mock_ais_description(context)) launch_description_entities.append(get_can_transceiver_description(context)) launch_description_entities.append(get_remote_transceiver_description(context)) + launch_description_entities.append(get_local_transceiver_description(context)) return launch_description_entities @@ -215,3 +216,34 @@ def get_remote_transceiver_description(context: LaunchContext) -> Node: ) return node + + +def get_local_transceiver_description(context: LaunchContext) -> Node: + """Gets the launch description for the local_transceiver_node. + + Args: + context (LaunchContext): The current launch context. + + Returns: + Node: The node object that launches the local_transceiver_node. + """ + node_name = "local_transceiver_node" + ros_parameters = [ + global_launch_config, + {"mode": LaunchConfiguration("mode")}, + *LaunchConfiguration("config").perform(context).split(","), + ] + ros_arguments: List[SomeSubstitutionsType] = [ + "--log-level", + [f"{node_name}:=", LaunchConfiguration("log_level")], + ] + node = Node( + package=PACKAGE_NAME, + namespace=NAMESPACE, + executable="local_transceiver", + name=node_name, + parameters=ros_parameters, + ros_arguments=ros_arguments, + ) + + return node diff --git a/src/network_systems/projects/local_transceiver/src/local_transceiver_ros_intf.cpp b/src/network_systems/projects/local_transceiver/src/local_transceiver_ros_intf.cpp index 4476c2e32..26844e0e0 100644 --- a/src/network_systems/projects/local_transceiver/src/local_transceiver_ros_intf.cpp +++ b/src/network_systems/projects/local_transceiver/src/local_transceiver_ros_intf.cpp @@ -13,7 +13,7 @@ #include "net_node.h" /** - * Local Transceiver Interface Node + * @brief Connect the Local Transceiver to the ROS network * */ class LocalTransceiverIntf : public NetNode @@ -24,40 +24,67 @@ class LocalTransceiverIntf : public NetNode * * @param lcl_trns Local Transceiver instance */ - explicit LocalTransceiverIntf(std::shared_ptr lcl_trns) - : NetNode(ros_nodes::LOCAL_TRANSCEIVER), lcl_trns_(lcl_trns) + explicit LocalTransceiverIntf() : NetNode("local_transceiver_node") + { - static constexpr int ROS_Q_SIZE = 5; - static constexpr auto TIMER_INTERVAL = std::chrono::milliseconds(500); - timer_ = this->create_wall_timer(TIMER_INTERVAL, std::bind(&LocalTransceiverIntf::pub_cb, this)); - pub_ = this->create_publisher(ros_topics::GLOBAL_PATH, ROS_Q_SIZE); - - // subscriber nodes - sub_wind_sensor = this->create_subscription( - ros_topics::WIND_SENSORS, ROS_Q_SIZE, - std::bind(&LocalTransceiverIntf::sub_wind_sensor_cb, this, std::placeholders::_1)); - sub_batteries = this->create_subscription( - ros_topics::BATTERIES, ROS_Q_SIZE, - std::bind(&LocalTransceiverIntf::sub_batteries_cb, this, std::placeholders::_1)); - sub_data_sensors = this->create_subscription( - ros_topics::DATA_SENSORS, ROS_Q_SIZE, - std::bind(&LocalTransceiverIntf::sub_data_sensors_cb, this, std::placeholders::_1)); - sub_ais_ships = this->create_subscription( - ros_topics::AIS_SHIPS, ROS_Q_SIZE, - std::bind(&LocalTransceiverIntf::sub_ais_ships_cb, this, std::placeholders::_1)); - sub_gps = this->create_subscription( - ros_topics::GPS, ROS_Q_SIZE, std::bind(&LocalTransceiverIntf::sub_gps_cb, this, std::placeholders::_1)); - sub_local_path_data = this->create_subscription( - ros_topics::LOCAL_PATH, ROS_Q_SIZE, - std::bind(&LocalTransceiverIntf::sub_local_path_data_cb, this, std::placeholders::_1)); + this->declare_parameter("enabled", true); + bool enabled_ = this->get_parameter("enabled").as_bool(); + + if (!enabled_) { + RCLCPP_INFO(this->get_logger(), "Local Transceiver is DISABLED"); + } else { + this->declare_parameter("mode", SYSTEM_MODE::DEV); + + rclcpp::Parameter mode_param = this->get_parameter("mode"); + std::string mode = mode_param.as_string(); + std::string default_port; + + if (mode == SYSTEM_MODE::PROD) { + //TODO(Jng468) placeholder + } else if (mode == SYSTEM_MODE::DEV) { + default_port = LOCAL_TRANSCEIVER_TEST_PORT; + } else { + std::string msg = "Error, invalid system mode" + mode; + throw std::runtime_error(msg); + } + + this->declare_parameter("port", default_port); + rclcpp::Parameter default_port_parm = this->get_parameter("port"); + std::string port = default_port_parm.as_string(); + + RCLCPP_INFO( + this->get_logger(), "Running Local Transceiver in mode: %s, with port: %s.", mode.c_str(), port.c_str()); + lcl_trns_ = std::make_unique(port, SATELLITE_BAUD_RATE); + + static constexpr int ROS_Q_SIZE = 5; + static constexpr auto TIMER_INTERVAL = std::chrono::milliseconds(500); + timer_ = this->create_wall_timer(TIMER_INTERVAL, std::bind(&LocalTransceiverIntf::pub_cb, this)); + pub_ = this->create_publisher(ros_topics::GLOBAL_PATH, ROS_Q_SIZE); + + // subscriber nodes + sub_wind_sensor = this->create_subscription( + ros_topics::WIND_SENSORS, ROS_Q_SIZE, + std::bind(&LocalTransceiverIntf::sub_wind_sensor_cb, this, std::placeholders::_1)); + sub_batteries = this->create_subscription( + ros_topics::BATTERIES, ROS_Q_SIZE, + std::bind(&LocalTransceiverIntf::sub_batteries_cb, this, std::placeholders::_1)); + sub_data_sensors = this->create_subscription( + ros_topics::DATA_SENSORS, ROS_Q_SIZE, + std::bind(&LocalTransceiverIntf::sub_data_sensors_cb, this, std::placeholders::_1)); + sub_ais_ships = this->create_subscription( + ros_topics::AIS_SHIPS, ROS_Q_SIZE, + std::bind(&LocalTransceiverIntf::sub_ais_ships_cb, this, std::placeholders::_1)); + sub_gps = this->create_subscription( + ros_topics::GPS, ROS_Q_SIZE, std::bind(&LocalTransceiverIntf::sub_gps_cb, this, std::placeholders::_1)); + sub_local_path_data = this->create_subscription( + ros_topics::LOCAL_PATH, ROS_Q_SIZE, + std::bind(&LocalTransceiverIntf::sub_local_path_data_cb, this, std::placeholders::_1)); + } } private: - // Local Transceiver instance - std::shared_ptr lcl_trns_; - // Publishing timer - rclcpp::TimerBase::SharedPtr timer_; - // String is a placeholder pub and sub msg type - we will definitely define custom message types + std::unique_ptr lcl_trns_; // Local Transceiver instance + rclcpp::TimerBase::SharedPtr timer_; // Publishing timer rclcpp::Publisher::SharedPtr pub_; rclcpp::Subscription::SharedPtr sub_wind_sensor; @@ -71,73 +98,45 @@ class LocalTransceiverIntf : public NetNode * @brief Callback function to publish to onboard ROS network * */ - void pub_cb(/*place*/) + void pub_cb(/*placeholder*/) { - // TODO(Jng468): complete, after receive is done - // std::string recent_data = lcl_trns_->receive(); //receives most recent data from remote server - // auto msg = custom_interfaces::msg::Path(); - // pub_->publish(msg); + // TODO(Jng468): complete this, after receive is done } /** * @brief Callback function to subscribe to the onboard ROS network for wind sensors */ - void sub_wind_sensor_cb(custom_interfaces::msg::WindSensors in_msg) - { - custom_interfaces::msg::WindSensors data = in_msg; - lcl_trns_->updateSensor(data); - } + void sub_wind_sensor_cb(custom_interfaces::msg::WindSensors in_msg) { lcl_trns_->updateSensor(in_msg); } /** * @brief Callback function to subscribe to the onboard ROS network for batteries */ - void sub_batteries_cb(custom_interfaces::msg::Batteries in_msg) - { - custom_interfaces::msg::Batteries data = in_msg; - lcl_trns_->updateSensor(data); - } + void sub_batteries_cb(custom_interfaces::msg::Batteries in_msg) { lcl_trns_->updateSensor(in_msg); } /** * @brief Callback function to subscribe to the onboard ROS network for generic sensors */ - void sub_data_sensors_cb(custom_interfaces::msg::GenericSensors in_msg) - { - custom_interfaces::msg::GenericSensors data = in_msg; - lcl_trns_->updateSensor(data); - } + void sub_data_sensors_cb(custom_interfaces::msg::GenericSensors in_msg) { lcl_trns_->updateSensor(in_msg); } /** * @brief Callback function to subscribe to the onboard ROS network for ais ships */ - void sub_ais_ships_cb(custom_interfaces::msg::AISShips in_msg) - { - custom_interfaces::msg::AISShips data = in_msg; - lcl_trns_->updateSensor(data); - } + void sub_ais_ships_cb(custom_interfaces::msg::AISShips in_msg) { lcl_trns_->updateSensor(in_msg); } /** * @brief Callback function to subscribe to the onboard ROS network for GPS */ - void sub_gps_cb(custom_interfaces::msg::GPS in_msg) - { - custom_interfaces::msg::GPS data = in_msg; - lcl_trns_->updateSensor(data); - } + void sub_gps_cb(custom_interfaces::msg::GPS in_msg) { lcl_trns_->updateSensor(in_msg); } - void sub_local_path_data_cb(custom_interfaces::msg::LPathData in_msg) - { - custom_interfaces::msg::LPathData data = in_msg; - lcl_trns_->updateSensor(data); - } + void sub_local_path_data_cb(custom_interfaces::msg::LPathData in_msg) { lcl_trns_->updateSensor(in_msg); } }; int main(int argc, char * argv[]) { bool err = false; rclcpp::init(argc, argv); - std::shared_ptr lcl_trns = std::make_shared("PLACEHOLDER", SATELLITE_BAUD_RATE); try { - std::shared_ptr node = std::make_shared(lcl_trns); + std::shared_ptr node = std::make_shared(); try { rclcpp::spin(node); } catch (std::exception & e) { From b83005ccc7a67293863e76319a8e541dae81f6fd Mon Sep 17 00:00:00 2001 From: Henry Huang <69825683+hhenry01@users.noreply.github.com> Date: Sat, 23 Mar 2024 16:13:21 -0700 Subject: [PATCH 24/24] Fix Remote Transceiver Test Failures (#342) * This might work * Increase timeout --- .../projects/remote_transceiver/inc/remote_transceiver.h | 3 ++- .../remote_transceiver/test/test_remote_transceiver.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/network_systems/projects/remote_transceiver/inc/remote_transceiver.h b/src/network_systems/projects/remote_transceiver/inc/remote_transceiver.h index da0e05033..ca00bf474 100644 --- a/src/network_systems/projects/remote_transceiver/inc/remote_transceiver.h +++ b/src/network_systems/projects/remote_transceiver/inc/remote_transceiver.h @@ -16,7 +16,8 @@ using tcp = boost::asio::ip::tcp; namespace remote_transceiver { -constexpr int DEFAULT_NUM_IO_THREADS = 2; // Default number of HTTP requests that can be accepted in parallel +// Default number of HTTP requests that can be accepted in parallel +constexpr int DEFAULT_NUM_IO_THREADS = 2; // Production constants are all placheholders static const std::string PROD_DB_NAME = "PLACEHOLDER"; diff --git a/src/network_systems/projects/remote_transceiver/test/test_remote_transceiver.cpp b/src/network_systems/projects/remote_transceiver/test/test_remote_transceiver.cpp index 8b043bf39..415a155bb 100644 --- a/src/network_systems/projects/remote_transceiver/test/test_remote_transceiver.cpp +++ b/src/network_systems/projects/remote_transceiver/test/test_remote_transceiver.cpp @@ -37,7 +37,7 @@ class TestRemoteTransceiver : public ::testing::Test protected: static constexpr int NUM_THREADS = 4; // Need to wait after receiving an HTTP response from the server - static constexpr auto WAIT_AFTER_RES = std::chrono::milliseconds(20); + static constexpr auto WAIT_AFTER_RES = std::chrono::milliseconds(200); // Network objects that are shared amongst all HTTP test suites static bio::io_context io_;