diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..1b7b25b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,32 @@ +--- +name: Bug report +about: Report a bug with the software (this is NOT for cases of "it's not working + for me") +title: "[BUG] " +labels: bug, help wanted +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Device you are running wire-pod on (please complete the following information):** + - OS: [e.g. Debian, Windows] + - Hardware: [e.g. Raspberry Pi] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/problem-with-setup.md b/.github/ISSUE_TEMPLATE/problem-with-setup.md new file mode 100644 index 0000000..cd82d08 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/problem-with-setup.md @@ -0,0 +1,19 @@ +--- +name: Problem with setup +about: Please follow this template if you are having issues setting wire-pod up. I + (kercre123) will not be providing support for these. +title: "[HELP] " +labels: '' +assignees: '' + +--- + +**Describe the issue:** + +**What are you running wire-pod on?** + - [OS]: (ex. Windows, Debian) + - [Hardware]: (ex. Raspberry Pi) + +**Are you setting up a prod (regular) bot or a dev/OSKR (unlocked) bot?** + +**What steps have you tried?** diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 0000000..a758bd8 --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,98 @@ +name: Docker + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +on: + schedule: + - cron: '25 22 * * *' + push: + branches: [ "main" ] + # Publish semver tags as releases. + tags: [ 'v*.*.*' ] + pull_request: + branches: [ "main" ] + +env: + # Use docker.io for Docker Hub if empty + REGISTRY: ghcr.io + # github.repository as / + IMAGE_NAME: ${{ github.repository }} + + +jobs: + build: + + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + # This is used to complete the identity challenge + # with sigstore/fulcio when running outside of PRs. + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Install the cosign tool except on PR + # https://github.com/sigstore/cosign-installer + - name: Install cosign + if: github.event_name != 'pull_request' + uses: sigstore/cosign-installer@v3.3.0 + with: + cosign-release: 'v2.2.2' # optional + + # Set up BuildKit Docker container builder to be able to build + # multi-platform images and export cache + # https://github.com/docker/setup-buildx-action + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + + # Login against a Docker registry except on PR + # https://github.com/docker/login-action + - name: Log into registry ${{ env.REGISTRY }} + if: github.event_name != 'pull_request' + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Extract metadata (tags, labels) for Docker + # https://github.com/docker/metadata-action + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + # Build and push Docker image with Buildx (don't push on PR) + # https://github.com/docker/build-push-action + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0 + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + # Sign the resulting Docker image digest except on PRs. + # This will only write to the public Rekor transparency log when the Docker + # repository is public to avoid leaking data. If you would like to publish + # transparency data even for private images, pass --force to cosign below. + # https://github.com/sigstore/cosign + - name: Sign the published Docker image + if: ${{ github.event_name != 'pull_request' }} + env: + # https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable + TAGS: ${{ steps.meta.outputs.tags }} + DIGEST: ${{ steps.build-and-push.outputs.digest }} + # This step uses the identity token to provision an ephemeral certificate + # against the sigstore community Fulcio instance. + run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ecfea84 --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +/stt +/vector-cloud/build/vic-cloud +/chipper/source.sh +/vector-cloud/build +/chipper/chipper +/certs +/vector-cloud/packagesGotten +/vector-cloud/cloud/cert.go +/chipper/aarch64 +/chipper/armarch +/chipper/processOnce +/ssh_root_key +/.vscode +/chipper/botConfig.json +/chipper/.vscode +/chipper/useepod +/chipper/pico.key +/chipper/libvosk.so +/vosk +/chipper/customIntents.json +/chipper/botConfig.json +/chipper/jdocs/*.json +/chipper/nvm +/chipper/apiConfig.json +/chipper/session-certs/0* +/chipper/vbuild/vic-toolchain +/chipper/vbuild/chipper +/chipper/vbuild/botpack.tar.gz +/chipper/windows/tmp +/chipper/windows/chipper.exe +/chipper/windows/*.zip +/chipper/windows/*.exe +/chipper/vbuild/built-libs +/chipper/windows/libs +/chipper/windows/opus +/chipper/windows/ogg +/chipper/windows/.aptDone +/chipper/macos/libs +/chipper/macos/target +/chipper/plugins/*.so +/whisper.cpp +.DS_Store +/chipper/openaiChats.json diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..98273cc --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Kerigan Creighton + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f162045 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# wire-pod + +`wire-pod` is fully-featured server software for the Anki (now Digital Dream Labs) [Vector](https://web.archive.org/web/20190417120536if_/https://www.anki.com/en-us/vector) robot. It was created thanks to Digital Dream Labs' [open-sourced code](https://github.com/digital-dream-labs/chipper). + +It allows voice commands to work with any Vector 1.0 or 2.0 for no fee, including regular production robots. + +## Installation + +The installation guide exists on the wiki: [Installation guide](https://github.com/kercre123/wire-pod/wiki/Installation) + +## Wiki + +Check out the [wiki](https://github.com/kercre123/wire-pod/wiki) for more information on what wire-pod is, a guide on how to install wire-pod, troubleshooting, how to develop for it, and for some generally helpful tips. + +## Donate + +If you want to :P + +[![Buy Me A Coffee](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://buymeacoffee.com/kercre123) + +## Credits + +- [Digital Dream Labs](https://github.com/digital-dream-labs) for open sourcing chipper and creating escape pod (which made this possible) +- [bliteknight](https://github.com/bliteknight) for making wire-pod more accessible with his easy-to-use pre-setup Linux boxes +- [dietb](https://github.com/dietb) for rewriting chipper and giving tips +- [fforchino](https://github.com/fforchino) for adding many features such as localization and multilanguage, and for helping out +- [xanathon](https://github.com/xanathon) for the publicity and web interface help +- Anyone who has opened an issue and/or created a pull request for wire-pod diff --git a/chipper/LICENSE b/chipper/LICENSE new file mode 100644 index 0000000..713756e --- /dev/null +++ b/chipper/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Digital Dream Labs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/chipper/cmd/coqui/main.go b/chipper/cmd/coqui/main.go new file mode 100644 index 0000000..6a372ad --- /dev/null +++ b/chipper/cmd/coqui/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "github.com/kercre123/wire-pod/chipper/pkg/initwirepod" + stt "github.com/kercre123/wire-pod/chipper/pkg/wirepod/stt/coqui" +) + +func main() { + initwirepod.StartFromProgramInit(stt.Init, stt.STT, stt.Name) +} diff --git a/chipper/cmd/experimental/houndify/main.go b/chipper/cmd/experimental/houndify/main.go new file mode 100644 index 0000000..65b170f --- /dev/null +++ b/chipper/cmd/experimental/houndify/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "github.com/kercre123/wire-pod/chipper/pkg/initwirepod" + stt "github.com/kercre123/wire-pod/chipper/pkg/wirepod/stt/houndify" +) + +func main() { + initwirepod.StartFromProgramInit(stt.Init, stt.STT, stt.Name) +} diff --git a/chipper/cmd/experimental/whisper.cpp/main.go b/chipper/cmd/experimental/whisper.cpp/main.go new file mode 100644 index 0000000..19a4152 --- /dev/null +++ b/chipper/cmd/experimental/whisper.cpp/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "github.com/kercre123/wire-pod/chipper/pkg/initwirepod" + stt "github.com/kercre123/wire-pod/chipper/pkg/wirepod/stt/whisper.cpp" +) + +func main() { + initwirepod.StartFromProgramInit(stt.Init, stt.STT, stt.Name) +} \ No newline at end of file diff --git a/chipper/cmd/experimental/whisper/main.go b/chipper/cmd/experimental/whisper/main.go new file mode 100644 index 0000000..d7fcfbf --- /dev/null +++ b/chipper/cmd/experimental/whisper/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "github.com/kercre123/wire-pod/chipper/pkg/initwirepod" + stt "github.com/kercre123/wire-pod/chipper/pkg/wirepod/stt/whisper" +) + +func main() { + initwirepod.StartFromProgramInit(stt.Init, stt.STT, stt.Name) +} diff --git a/chipper/cmd/leopard/main.go b/chipper/cmd/leopard/main.go new file mode 100644 index 0000000..f306c3e --- /dev/null +++ b/chipper/cmd/leopard/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "github.com/kercre123/wire-pod/chipper/pkg/initwirepod" + stt "github.com/kercre123/wire-pod/chipper/pkg/wirepod/stt/leopard" +) + +func main() { + initwirepod.StartFromProgramInit(stt.Init, stt.STT, stt.Name) +} diff --git a/chipper/cmd/vosk/main.go b/chipper/cmd/vosk/main.go new file mode 100644 index 0000000..ead6aa0 --- /dev/null +++ b/chipper/cmd/vosk/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "github.com/kercre123/wire-pod/chipper/pkg/initwirepod" + stt "github.com/kercre123/wire-pod/chipper/pkg/wirepod/stt/vosk" +) + +func main() { + initwirepod.StartFromProgramInit(stt.Init, stt.STT, stt.Name) +} diff --git a/chipper/epod/ep.crt b/chipper/epod/ep.crt new file mode 100644 index 0000000..951d80d --- /dev/null +++ b/chipper/epod/ep.crt @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIFBTCCAu2gAwIBAgIUFn4HuHNv0I48LRGei5Yf5DpKTNUwDQYJKoZIhvcNAQEL +BQAwgYExCzAJBgNVBAYTAlVTMRUwEwYDVQQIDAxQZW5uc3lsdmFuaWExEzARBgNV +BAcMClBpdHRzYnVyZ2gxGzAZBgNVBAoMEkRpZ2l0YWwgRHJlYW0gTGFiczEpMCcG +CSqGSIb3DQEJARYaYnJldHRAZGlnaXRhbGRyZWFtbGFicy5jb20wIBcNMjAxMjEw +MTgzNDU0WhgPMjIyMDEwMjMxODM0NTRaMIGbMQswCQYDVQQGEwJVUzEVMBMGA1UE +CAwMUGVubnN5bHZhbmlhMRMwEQYDVQQHDApQaXR0c2J1cmdoMRswGQYDVQQKDBJE +aWdpdGFsIERyZWFtIExhYnMxGDAWBgNVBAMMD2VzY2FwZXBvZC5sb2NhbDEpMCcG +CSqGSIb3DQEJARYaYnJldHRAZGlnaXRhbGRyZWFtbGFicy5jb20wggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQClNNnh/88lNt6VyoulGL8iDHae+o6wCFr9 +glCsJv5WO2SFIGX7dZJ4CuCuo7GUOu7P74aqXQfoisDK8Hqr3iRftlZRl800N1Vr +khUNAx49obDBQctmjq0syMrTDDCIf/q3flmSqW0veT08zXOTgT7mdBG4e7Wuvwq1 +NagU5jv35IpxWnNTdrU7HWKr8DHogeP1eI/09KR3bA9xtwsEh8USdguHxLY3V6bB +xlC6vN3gItNf0HsinSjU2qRW47P5m5aoNfYmmjvjUpcvH/qHjcCvvAMYZ7+31jWd +4kUhbpWW4zYgIa37fo3dQiquit7IfLUSyT7Bt9eO3eLQCDPdNm8FAgMBAAGjVzBV +MB8GA1UdIwQYMBaAFLEGVfbWGssezxPrNlderfGJaU0eMAkGA1UdEwQCMAAwCwYD +VR0PBAQDAgTwMBoGA1UdEQQTMBGCD2VzY2FwZXBvZC5sb2NhbDANBgkqhkiG9w0B +AQsFAAOCAgEAJsVRYGxoqYhp6vc1jLUVqInH9RPF17B9CwHg7ECyvt/z+IKSCGEz +xvYx4/PwrSMYqss9MiOpWC/ZNI6rU7ruks/KQ15DYPAZJl06hvz+LYuLrphdtaO+ +CnLUzpcn4MQ9NdxsOFqWiZelIRdEAiq/rHGpsOOSdIfARvoNN3uOyvhmDCFtzUTi +0V/bA7AqshEHArWLCVI+UOnDpgB7bYibTPBS1RFpHskPR0ynsEEsMQQMM0howwBG +iYi7AqdlrjHxo1J6AzX31EReXIi/v1HK+b+aCoQYKPqiAJ6kkYt3ffnuQ3W7pCnd +wr3iM/g+TkWNqRf9f9jxmG4ozwKI9q7/vQdNkJfu534T3ohmrgzGSKQ99IX3VN9P +ePFrxsy8w3OcSflv/Vjo/nHTUETnuB4EdeUOPNf/7euOtznUFhsyLmDuFHA6a2yU +PpI+g2HNgQUbJOtIRV3owc1ihnXwgeuj8Xt2eZvt3VaUE0333uC6Yc0EngIqf4gA +CQWbnsQM8HMh/+npFzbZlcM4RZoFChObb4O0FqVlYjD7/xnJE78dqSLwP8VVSD4g +y+WRDN4PJXgGZw4omtpC5OTReDOZmmZJUN2YTla1SZXyJcZv/x+P1th0qnmZ4Hf3 +E4oNSE7wWX9/lZRTb3fegWL4AlW3IsosyIpYTHZ8Qqy1YfHo3zdxXqU= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/chipper/epod/ep.key b/chipper/epod/ep.key new file mode 100644 index 0000000..2aef3f2 --- /dev/null +++ b/chipper/epod/ep.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEApTTZ4f/PJTbelcqLpRi/Igx2nvqOsAha/YJQrCb+VjtkhSBl ++3WSeArgrqOxlDruz++Gql0H6IrAyvB6q94kX7ZWUZfNNDdVa5IVDQMePaGwwUHL +Zo6tLMjK0wwwiH/6t35ZkqltL3k9PM1zk4E+5nQRuHu1rr8KtTWoFOY79+SKcVpz +U3a1Ox1iq/Ax6IHj9XiP9PSkd2wPcbcLBIfFEnYLh8S2N1emwcZQurzd4CLTX9B7 +Ip0o1NqkVuOz+ZuWqDX2Jpo741KXLx/6h43Ar7wDGGe/t9Y1neJFIW6VluM2ICGt ++36N3UIqroreyHy1Esk+wbfXjt3i0Agz3TZvBQIDAQABAoIBAHeIzRm72OrJT7Y8 +LlxPkoQVVoLjMfjmoseI0cwuDprgMHQuo/uU71ySKk3SPTvOhFrJqbt8wqscMjDk +XS4b9l+Wc9BnsN9WJiVGNpsKpYfchSLf80cKdvzPcAnSaQ9q4kKAVllK46iU5Z0n +3rdcreFbHDNKt4Nv0VSaNTqh98P9W5CK1pIZbGHJGqFaE5VtUxvBzUGc7jAXj+eD +I8Ajum4AG3ZcJBBMiGCX6TzCoHZYBRUicLRvKkANDqNP4/L9wqbAlNmpP7IUD/yC +OjlYlEKjEXyk5hmv/QHRqN2yK2qpCBddTxmwiXAa3BSu17hTv0zUlDTR5TjO2TEG +tsNDnaECgYEA2q547mBRIt3/D5VrjXZMOGYFgt306nzFg3XJYM5ArOxR99tZFWRv +oHdwBMIvVI6RgHhw6LuferZI4n5T0UUaSUUuHXvyD9T20S8buDrC6TsmNruymMM+ +jvry6yGF3+AMlIHrayq0U6FHBrhfGsH91Is1/RF6Hu4Rpfa85GVASYkCgYEAwWY3 +P/BVho5CkEu1H3PMH4HiDaYo/VrA+pCaNe8KH7xQJ5IOL4IywT2W2YVKsxU9fwmE +ZEmXJm7nzDLYwsyuk9pgxSjud2SVBUvnnBDtbX0sNHB/Qi7nLjbs5ZjHISH2RNhP +daOGI33ZRKXOfvbsXUNSA2r8fR1bE3JvA1YnJp0CgYBEfLH5Dgc7IUWZbtVxR2RV +oXYGZ1cl/Q+qvT/lZpMQ1S5Srsq2jW78VYuqodpK5B+jmZTa/q/SsbYf4SqE9txl +qBnqOAA2fx8RomxPBXA3tUOhjqU/fJ5iDyv3Ade4pqWp+Qpu1MAHFRJ2g1WdvrWt +VDADYu7ZMvwp+x1rdl5s6QKBgAWGNe3Nn6PITH5yqynK1PnRa/OX23PhM8H0f3Mq +8M8XQfLfaShSP8DlUXnFJO0YnjkSvIVg1MB0Soq6qRZnYlU216zKDoW6iccs8+Cx +WxbVjH2y+O+bB196kim8w3Ne1PoCc8KYeSxqW9pqIgveYcIIOj9+vteUDxXvHtyp +iVTBAoGASI0u1CTISUMuTn2Dknd750UcKXkfFa1yTWG2NbsB64CFx8vRcM78rHVo +bmgD2V1X25m3Pdqk1OD1Q2+ohb+p7ss+PygV2tFMryaJ1MbaPnoU+7DewptFJ7Do +wMmnGhTrRbpZouBkZx2OUXhOkhKui63c82eAF8h60zJosbPXQ8w= +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/chipper/go.mod b/chipper/go.mod new file mode 100644 index 0000000..13e4af7 --- /dev/null +++ b/chipper/go.mod @@ -0,0 +1,107 @@ +module github.com/kercre123/wire-pod/chipper + +go 1.18 + +require ( + github.com/Picovoice/leopard/binding/go/v2 v2.0.2 + github.com/asticode/go-asticoqui v0.2.0 + github.com/bramvdbogaerde/go-scp v1.2.1 + github.com/digital-dream-labs/api v0.0.0-20210824232136-8cc90c1bb12c + github.com/digital-dream-labs/hugh v0.0.0-20210210154335-f4159b9fcd5f + github.com/digital-dream-labs/opus-go v0.0.0-20201230195736-934a8a9e0a1e + github.com/digital-dream-labs/vector-bluetooth v0.0.0-20210604051118-1c511122d877 + github.com/fforchino/vector-go-sdk v0.0.0-20231108155304-62168f3595d6 + github.com/ggerganov/whisper.cpp/bindings/go v0.0.0-20240917125632-5b1ce40fa882 + github.com/go-audio/audio v1.0.0 + github.com/go-audio/wav v1.1.0 + github.com/golang-jwt/jwt v3.2.2+incompatible + github.com/google/uuid v1.5.0 + github.com/kercre123/vosk-api/go v1.0.2 + github.com/kercre123/zeroconf v1.0.1 + github.com/maxhawkins/go-webrtcvad v0.0.0-20210121163624-be60036f3083 + github.com/ncruces/zenity v0.10.10 + github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e + github.com/pkg/errors v0.9.1 + github.com/sashabaranov/go-openai v1.27.1 + github.com/soheilhy/cmux v0.1.5 + github.com/soundhound/houndify-sdk-go v0.3.5 + github.com/vadv/gopher-lua-libs v0.5.0 + github.com/wlynxg/anet v0.0.1 + github.com/yuin/gopher-lua v1.1.1 + golang.org/x/crypto v0.21.0 + golang.org/x/text v0.14.0 + google.golang.org/grpc v1.60.0 + gopkg.in/ini.v1 v1.67.0 +) + +require ( + github.com/VividCortex/ewma v1.1.1 // indirect + github.com/agnivade/levenshtein v1.1.1 // indirect + github.com/akavel/rsrc v0.10.2 // indirect + github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect + github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect + github.com/alessio/shellescape v1.4.1 // indirect + github.com/alphacep/vosk-api/go v0.3.50 // indirect + github.com/aws/aws-sdk-go v1.34.28 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cbroglie/mustache v1.0.1 // indirect + github.com/cenkalti/backoff v2.2.1+incompatible // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cheggaaa/pb/v3 v3.0.5 // indirect + github.com/currantlabs/ble v0.0.0-20171229162446-c1d21c164cf8 // indirect + github.com/dchest/jsmin v0.0.0-20220218165748-59f39799265f // indirect + github.com/dustin/go-humanize v1.0.0 // indirect + github.com/fatih/color v1.7.0 // indirect + github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/go-audio/riff v1.0.0 // indirect + github.com/go-sql-driver/mysql v1.5.0 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/grd/ogg v0.0.0-20130623210630-0dae53159b70 // indirect + github.com/grpc-ecosystem/go-grpc-middleware v1.2.2 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.3.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/jamesruan/sodium v0.0.0-20181216154042-9620b83ffeae // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/josephspurrier/goversioninfo v1.4.0 // indirect + github.com/kercre123/vosk-api v1.0.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/lib/pq v1.7.0 // indirect + github.com/magiconair/properties v1.8.1 // indirect + github.com/mattn/go-colorable v0.1.8 // indirect + github.com/mattn/go-isatty v0.0.12 // indirect + github.com/mattn/go-runewidth v0.0.10 // indirect + github.com/mattn/go-sqlite3 v1.14.3 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect + github.com/mgutz/logxi v0.0.0-20161027140823-aebf8a7d67ab // indirect + github.com/miekg/dns v1.1.41 // indirect + github.com/mitchellh/mapstructure v1.4.3 // indirect + github.com/montanaflynn/stats v0.6.3 // indirect + github.com/pelletier/go-toml v1.8.0 // indirect + github.com/prometheus/client_golang v1.11.1 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.26.0 // indirect + github.com/prometheus/procfs v0.6.0 // indirect + github.com/randall77/makefat v0.0.0-20210315173500-7ddd0e42c844 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/sirupsen/logrus v1.7.0 // indirect + github.com/spf13/afero v1.2.2 // indirect + github.com/spf13/cast v1.3.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.7.1 // indirect + github.com/subosito/gotenv v1.2.0 // indirect + github.com/technoweenie/multipartstreamer v1.0.1 // indirect + github.com/yuin/gluamapper v0.0.0-20150323120927-d836955830e7 // indirect + golang.org/x/image v0.10.0 // indirect + golang.org/x/net v0.22.0 // indirect + golang.org/x/sys v0.18.0 // indirect + google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect + gopkg.in/hraban/opus.v2 v2.0.0-20201025103112-d779bb1cc5a2 // indirect + gopkg.in/xmlpath.v2 v2.0.0-20150820204837-860cbeca3ebc // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/chipper/go.sum b/chipper/go.sum new file mode 100644 index 0000000..19b3869 --- /dev/null +++ b/chipper/go.sum @@ -0,0 +1,987 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/go-winio v0.4.15-0.20200113171025-3fe6c5262873/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/Picovoice/leopard/binding/go/v2 v2.0.2 h1:Knk/UV51oRuHTHd7MGtlZXwsFF5jxu6AqttB0jGMHxs= +github.com/Picovoice/leopard/binding/go/v2 v2.0.2/go.mod h1:/rYUeRDH4xBgtwBe9D8BwHIauPJ+M7czqLfyeJQJu7c= +github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= +github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= +github.com/aalpern/go-metrics v0.0.0-20181116155206-644932c99203/go.mod h1:wHUOZ2LlAirciaWYGZM3apvZftM7aRXhLMDsdjqEFB4= +github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= +github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= +github.com/akavel/rsrc v0.10.2 h1:Zxm8V5eI1hW4gGaYsJQUhxpjkENuG91ki8B4zCrvEsw= +github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= +github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= +github.com/alphacep/vosk-api/go v0.3.50 h1:2vSN41RCU1WdHEqBrhKtTggfKL6Yu5Dmj+urVszwiuw= +github.com/alphacep/vosk-api/go v0.3.50/go.mod h1:9X8IJsHnFk/b1xyvjlZifo+ZL5VTAx3LW+JQce/eRcA= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/asticode/go-asticoqui v0.2.0 h1:Y03X7kQUf309wHvv3ys39aYwyiFXqD9gIugNCDOVSrc= +github.com/asticode/go-asticoqui v0.2.0/go.mod h1:HuUAasCpAyPjOQSqoY92VeBMYLkQ/Hu7Ij3aOhQrwlk= +github.com/aws/aws-sdk-go v1.34.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go v1.34.28 h1:sscPpn/Ns3i0F4HPEWAVcwdIRaZZCuL7llJ2/60yPIk= +github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bramvdbogaerde/go-scp v1.2.1 h1:BKTqrqXiQYovrDlfuVFaEGz0r4Ou6EED8L7jCXw6Buw= +github.com/bramvdbogaerde/go-scp v1.2.1/go.mod h1:s4ZldBoRAOgUg8IrRP2Urmq5qqd2yPXQTPshACY8vQ0= +github.com/bufbuild/buf v0.37.0/go.mod h1:lQ1m2HkIaGOFba6w/aC3KYBHhKEOESP3gaAEpS3dAFM= +github.com/cbroglie/mustache v1.0.1 h1:ivMg8MguXq/rrz2eu3tw6g3b16+PQhoTn6EZAhst2mw= +github.com/cbroglie/mustache v1.0.1/go.mod h1:R/RUa+SobQ14qkP4jtx5Vke5sDytONDQXNLPY/PO69g= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U= +github.com/cheggaaa/pb/v3 v3.0.5 h1:lmZOti7CraK9RSjzExsY53+WWfub9Qv13B5m4ptEoPE= +github.com/cheggaaa/pb/v3 v3.0.5/go.mod h1:X1L61/+36nz9bjIsrDU52qHKOQukUQe2Ge+YvGuquCw= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= +github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY= +github.com/containerd/continuity v0.0.0-20200928162600-f2cc35102c2a/go.mod h1:W0qIOTD7mp2He++YVq+kgfXezRYqzP1uDuMVH1bITDY= +github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cryptix/wav v0.0.0-20180415113528-8bdace674401/go.mod h1:knK8fd+KPlGGqSUWogv1DQzGTwnfUvAi0cIoWyOG7+U= +github.com/currantlabs/ble v0.0.0-20171229162446-c1d21c164cf8 h1:eo7L0zxxFowLpF4FNlLijrAMVNlq9h8sicNwMfzauM8= +github.com/currantlabs/ble v0.0.0-20171229162446-c1d21c164cf8/go.mod h1:MGpIf7cfnYPFaMIcD8LoSgCr8Jsa4rUcV5Nb9temsYw= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dchest/jsmin v0.0.0-20220218165748-59f39799265f h1:OGqDDftRTwrvUoL6pOG7rYTmWsTCvyEWFsMjg+HcOaA= +github.com/dchest/jsmin v0.0.0-20220218165748-59f39799265f/go.mod h1:Dv9D0NUlAsaQcGQZa5kc5mqR9ua72SmA8VXi4cd+cBw= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= +github.com/digital-dream-labs/api v0.0.0-20210824232136-8cc90c1bb12c h1:ITlFplPHfe+S0uyObyWo/bndO5RIYA9E3OPlBjOTb5Q= +github.com/digital-dream-labs/api v0.0.0-20210824232136-8cc90c1bb12c/go.mod h1:WNiZyUz2m0GppMJuwVvaLvtmC5mVM+YhcrxHBktqjr0= +github.com/digital-dream-labs/hugh v0.0.0-20210210154335-f4159b9fcd5f h1:zM1VS8fr2KE+v9TEXOvSf4zMOefcywVuanCuyM6/03Q= +github.com/digital-dream-labs/hugh v0.0.0-20210210154335-f4159b9fcd5f/go.mod h1:ZkWUHEPG5kdhpxM9vL+5b0p207jqH2WXNWQwFraQt2A= +github.com/digital-dream-labs/opus-go v0.0.0-20201230195736-934a8a9e0a1e h1:sbDWWDUdymyp3fTeQKz40a3x/frOSiWkBHD40o1/DQY= +github.com/digital-dream-labs/opus-go v0.0.0-20201230195736-934a8a9e0a1e/go.mod h1:OmsiyJjTuSWs/9lgydMVi+ULpldzbMJdu3z2N2TehYk= +github.com/digital-dream-labs/vector-bluetooth v0.0.0-20210604051118-1c511122d877 h1:o0KX+EQ9kyFtCgz/rrhj70Oy9j+v8/taLNcTUqS2mLc= +github.com/digital-dream-labs/vector-bluetooth v0.0.0-20210604051118-1c511122d877/go.mod h1:Nz0BKA94LFH/klz0okyPZiqXfCqaRhs6B8BzEhXVLmQ= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v17.12.0-ce-rc1.0.20200505174321-1655290016ac+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-connections v0.4.1-0.20180821093606-97c2040d34df/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fforchino/vector-go-sdk v0.0.0-20231108155304-62168f3595d6 h1:zJLvieaRwMPB+u1bhFMFVubWMvLR3qWuQ1H+WKw7P+0= +github.com/fforchino/vector-go-sdk v0.0.0-20231108155304-62168f3595d6/go.mod h1:VhuDr1h8ilsmbbjG1Wvp66kIzWfyGFwKA54oIhpwIKQ= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsouza/go-dockerclient v1.6.6/go.mod h1:3/oRIWoe7uT6bwtAayj/EmJmepBjeL4pYvt7ZxC7Rnk= +github.com/ggerganov/whisper.cpp/bindings/go v0.0.0-20240618151033-bf4cb4abad4e h1:np99/bjGH4/khEujoGbwc0ohMLh32GjovhEv2mJRNfs= +github.com/ggerganov/whisper.cpp/bindings/go v0.0.0-20240618151033-bf4cb4abad4e/go.mod h1:QIjZ9OktHFG7p+/m3sMvrAJKKdWrr1fZIK0rM6HZlyo= +github.com/ggerganov/whisper.cpp/bindings/go v0.0.0-20240917125632-5b1ce40fa882 h1:qH7ENKV0reL2gbvyzQ1mSWwML+eJ+IAEkTz5DmO4Ilg= +github.com/ggerganov/whisper.cpp/bindings/go v0.0.0-20240917125632-5b1ce40fa882/go.mod h1:QIjZ9OktHFG7p+/m3sMvrAJKKdWrr1fZIK0rM6HZlyo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-audio/audio v1.0.0 h1:zS9vebldgbQqktK4H0lUqWrG8P0NxCJVqcj7ZpNnwd4= +github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs= +github.com/go-audio/riff v1.0.0 h1:d8iCGbDvox9BfLagY94fBynxSPHO80LmZCaOsmKxokA= +github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498= +github.com/go-audio/wav v1.0.0/go.mod h1:3yoReyQOsiARkvPl3ERCi8JFjihzG6WhjYpZCf5zAWE= +github.com/go-audio/wav v1.1.0 h1:jQgLtbqBzY7G+BM8fXF7AHUk1uHUviWS4X39d5rsL2g= +github.com/go-audio/wav v1.1.0/go.mod h1:mpe9qfwbScEbkd8uybLuIpTgHyrISw/OTuvjUW2iGtE= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= +github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= +github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= +github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= +github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= +github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= +github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= +github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= +github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= +github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= +github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= +github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= +github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= +github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= +github.com/grd/ogg v0.0.0-20130623210630-0dae53159b70 h1:BbrcLhyNM9P1UAZnPBomiAvDv7WEIJy+sfrJItfSUL8= +github.com/grd/ogg v0.0.0-20130623210630-0dae53159b70/go.mod h1:K8T3jGUZQeKP7y1e801QDZAB53F6Lpt+NgnwAZTxwrg= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.2.2 h1:FlFbCRLd5Jr4iYXZufAvgWN6Ao0JrI5chLINnUXDDr0= +github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.0.0-beta.5/go.mod h1:fR9dOEfEyRM7ltVH0FTpK/QA6L/5BQq8izXNRu/gyVc= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.3.0 h1:IvO4FbbQL6n3v3M1rQNobZ61SGL0gJLdvKA5KETM7Xs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.3.0/go.mod h1:d2gYTOTUQklu06xp0AJYYmRdTVU1VKrqhkYfYag2L08= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jamesruan/sodium v0.0.0-20181216154042-9620b83ffeae h1:FFafUVNizQIaIHAqmDKXQlZ+8/LplHZbL11emgcto+M= +github.com/jamesruan/sodium v0.0.0-20181216154042-9620b83ffeae/go.mod h1:GZ0frHfchUw9cjP3CK1J7+M74KNo7zrhffQxTeuf5I8= +github.com/jhump/protoreflect v1.8.1/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/josephspurrier/goversioninfo v1.4.0 h1:Puhl12NSHUSALHSuzYwPYQkqa2E1+7SrtAPJorKK0C8= +github.com/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= +github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= +github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/kercre123/vosk-api v1.0.1 h1:D5CeAMNHPj87M9fKrqP+a2gEQefq7sJCpaiuRscbiJY= +github.com/kercre123/vosk-api v1.0.1/go.mod h1:mJlLhtYS207jVY9QffYGxhX6Up0UfSQ3p0uNbXsf3Zc= +github.com/kercre123/vosk-api/go v1.0.2 h1:NDJUNv2ddw128amiVZ2xE2gKfKHeBRRhboSh+yiH6Wg= +github.com/kercre123/vosk-api/go v1.0.2/go.mod h1:oVZG/VFmg23uNDzjShcw7UhZHWYG2zXgBm5FqioE2Ao= +github.com/kercre123/zeroconf v1.0.1 h1:Mbd8mN6xnNWYIqBN38x3jJjiPP2RmK4orzbGZsa1EOY= +github.com/kercre123/zeroconf v1.0.1/go.mod h1:qYGgej0DBUELb5p0zVe8yvz3NIwJs+ws9/QD6FJOCS8= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY= +github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= +github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-sqlite3 v1.14.3 h1:j7a/xn1U6TKA/PHHxqZuzh64CdtRc7rU9M+AvkOl5bA= +github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/maxhawkins/go-webrtcvad v0.0.0-20210121163624-be60036f3083 h1:0JDcvP4R28p6+u8VIHCwYx7UwiHZ074INz3C397oc9s= +github.com/maxhawkins/go-webrtcvad v0.0.0-20210121163624-be60036f3083/go.mod h1:YdrZ05xnooeP54y7m+/UvI23O1Td46PjWkLJu1VLObM= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mgutz/logxi v0.0.0-20161027140823-aebf8a7d67ab h1:n8cgpHzJ5+EDyDri2s/GC7a9+qK3/YEGnBsd0uS/8PY= +github.com/mgutz/logxi v0.0.0-20161027140823-aebf8a7d67ab/go.mod h1:y1pL58r5z2VvAjeG1VLGc8zOQgSOzbKN7kMHPvFXJ+8= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/sys/mount v0.1.0/go.mod h1:FVQFLDRWwyBjDTBNQXDlWnSFREqOo3OKX9aqhmeoo74= +github.com/moby/sys/mountinfo v0.1.0/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o= +github.com/moby/term v0.0.0-20200429084858-129dac9f73f6/go.mod h1:or9wGItza1sRcM4Wd3dIv8DsFHYQuFsMHEdxUIlUxms= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/montanaflynn/stats v0.6.3 h1:F8446DrvIF5V5smZfZ8K9nrmmix0AFgevPdLruGOmzk= +github.com/montanaflynn/stats v0.6.3/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/ncruces/zenity v0.10.10 h1:V/rtAhr5QLdDThahOkm7EYlnw4RuEsf7oN+Xb6lz1j0= +github.com/ncruces/zenity v0.10.10/go.mod h1:k3k4hJ4Wt1MUbeV48y+Gbl7Fp9skfGszN/xtKmuvhZk= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nishanths/predeclared v0.0.0-20200524104333-86fad755b4d3/go.mod h1:nt3d53pc1VYcphSCIaYAJtnPYnr3Zyn8fMq2wvPGPso= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.2-0.20190120152514-befc3cba3ffd/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc6.0.20190223184428-5b5130ad76db/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e h1:s2RNOM/IGdY0Y6qfTeUKhDawdHDpK9RGBdx80qN4Ttw= +github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e/go.mod h1:nBdnFKj15wFbf94Rwfq4m30eAcyY9V/IyKAGQFtqkW0= +github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= +github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw= +github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/pressly/goose v2.6.0+incompatible/go.mod h1:m+QHWCqxR3k8D9l7qfzuC/djtlfzxr34mozWDYEu1z8= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.1 h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs9yskGu1v4s= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/randall77/makefat v0.0.0-20210315173500-7ddd0e42c844 h1:GranzK4hv1/pqTIhMTXt2X8MmMOuH3hMeUR0o9SP5yc= +github.com/randall77/makefat v0.0.0-20210315173500-7ddd0e42c844/go.mod h1:T1TLSfyWVBRXVGzWd0o9BI4kfoO9InEgfQe4NV3mLz8= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sashabaranov/go-openai v1.27.1 h1:7Nx6db5NXbcoutNmAUQulEQZEpHG/SkzfexP2X5RWMk= +github.com/sashabaranov/go-openai v1.27.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= +github.com/schollz/progressbar/v3 v3.7.4/go.mod h1:1H8m5kMPW6q5fyjpDqtBHW1JT22mu2NwHQ1ApuCPh/8= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= +github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= +github.com/soundhound/houndify-sdk-go v0.3.5 h1:uV9eYz2rroBpQhkQNVnp1LzmJtA2G5aj5PN9/aYZsXI= +github.com/soundhound/houndify-sdk-go v0.3.5/go.mod h1:0tAqLSP7hXZZNVaUlRIUBcB38CDRKXxVGWiKTRdLqQE= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.0.1-0.20201006035406-b97b5ead31f7/go.mod h1:yk5b0mALVusDL5fMM6Rd1wgnoO5jUPhwsQ6LQAJTidQ= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= +github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM= +github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/twitchtv/twirp v7.1.0+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A= +github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/vadv/gopher-lua-libs v0.5.0 h1:m0hhWia1A1U3PIRmtdHWBj88ogzuIjm6HUBmtUa0Tz4= +github.com/vadv/gopher-lua-libs v0.5.0/go.mod h1:mlSOxmrjug7DwisiH7xBFnBellHobPbvAIhVeI/4SYY= +github.com/wlynxg/anet v0.0.1 h1:VbkEEgHxPSrRQSiyRd0pmrbcEQAEU2TTb8fb4DmSYoQ= +github.com/wlynxg/anet v0.0.1/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/yuin/gluamapper v0.0.0-20150323120927-d836955830e7 h1:noHsffKZsNfU38DwcXWEPldrTjIZ8FPNKx8mYMGnqjs= +github.com/yuin/gluamapper v0.0.0-20150323120927-d836955830e7/go.mod h1:bbMEM6aU1WDF1ErA5YJ0p91652pGv140gGw4Ww3RGp8= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= +github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= +github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.mongodb.org/mongo-driver v1.4.2/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.10.0 h1:gXjUUtwtx5yOE0VKWq1CH4IJAClq4UGgUA3i+rpON9M= +golang.org/x/image v0.10.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200923140941-5646d36feee1/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210207032614-bba0dbe2a9ea/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210224155714-063164c882e6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 h1:SeZZZx0cP0fqUyA+oRzP9k7cSwJlvDFiROO72uwD6i0= +google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk= +google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 h1:W18sezcAYs+3tDZX4F80yctqa12jcP1PUS2gQu1zTPU= +google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97/go.mod h1:iargEX0SFPm3xcfMI0d1domjg0ZF4Aa0p2awqyxhvF0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 h1:6GQBEOdGkX6MMTLT9V+TjtIRZCw9VPD5Z+yHY9wMgS0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go.mod h1:v7nGkzlmW8P3n/bKmWBn2WpBjpOEx8Q6gMueudAmKfY= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.35.0-dev.0.20201218190559-666aea1fb34c/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.60.0 h1:6FQAR0kM31P6MRdeluor2w2gPaS4SVNrD/DNTxrQ15k= +google.golang.org/grpc v1.60.0/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v0.0.0-20200527211525-6c9e30c09db2/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.25.1-0.20200805231151-a709e31e5d12/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.25.1-0.20201208041424-160c7477e0e8/go.mod h1:hFxJC2f0epmp1elRCiEGJTKAWbwxZ2nvqZdHl3FQXCY= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/hraban/opus.v2 v2.0.0-20201025103112-d779bb1cc5a2 h1:sxrRNhZ+cNxxLwPw/vV8gNsz+bbqRQiZHBYBJfpyNoQ= +gopkg.in/hraban/opus.v2 v2.0.0-20201025103112-d779bb1cc5a2/go.mod h1:/L5E7a21VWl8DeuCPKxQBdVG5cy+L0MRZ08B1wnqt7g= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/xmlpath.v2 v2.0.0-20150820204837-860cbeca3ebc h1:LMEBgNcZUqXaP7evD1PZcL6EcDVa2QOFuI+cqM3+AJM= +gopkg.in/xmlpath.v2 v2.0.0-20150820204837-860cbeca3ebc/go.mod h1:N8UOSI6/c2yOpa/XDz3KVUiegocTziPiqNkeNTMiG1k= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/chipper/intent-data/de-DE.json b/chipper/intent-data/de-DE.json new file mode 100644 index 0000000..09d39d4 --- /dev/null +++ b/chipper/intent-data/de-DE.json @@ -0,0 +1,218 @@ +[ + { + "name" : "intent_names_username_extend", + "keyphrases": ["Mein Name ist", "Ich bin", "hier ist", "Ich heiße"] + }, + { + "name": "intent_weather_extend", + "keyphrases": ["Wie ist das Wetter", "wie ist es draußen", "Wetter", "Wettervorhersage"] + }, + { + "name": "intent_names_ask", + "keyphrases": ["Was ist mein Name", "Wie ist mein Name", "wer bin ich", "wie heiße ich"] + }, + { + "name": "intent_imperative_eyecolor", + "keyphrases": ["ändere die Augenfarbe", "ändere deine Augenfarbe", "Augenfarbe", "Farbe ändern", "Augen"] + }, + { + "name": "intent_character_age", + "keyphrases": ["Wie alt bist du", "Was ist dein Alter", "wann wurdest du geboren"] + }, + { + "name": "intent_explore_start", + "keyphrases": ["Gehe Erkunden", "Erforschen", "Erkunde etwas", "Gehe auf Erkundung"] + }, + { + "name": "intent_system_charger", + "keyphrases": ["Gehe nach Hause", "nach Hause", "aufladen", "lade dich auf", "Finde das Ladegerät", "gehe heim", "zurück nach hause", "ab nach hause"] + }, + { + "name": "intent_system_sleep", + "keyphrases": ["Schlaf", "schlafen gehen", "ins Bett", "ins Bett gehen", "ab ins Bett", "Bett", "gute nacht", "schlaf gut"] + }, + { + "name": "intent_greeting_goodmorning", + "keyphrases": ["Tag", "Morgen", "Nachmittag"] + }, + { + "name": "intent_greeting_goodnight", + "keyphrases": ["Nacht", "Abend"] + }, + { + "name": "intent_greeting_goodbye", + "keyphrases": ["Hallo", "Auf Wiedersehen", "Wiedersehen"] + }, + { + "name": "intent_seasonal_happynewyear", + "keyphrases": ["Feuerwerk", "Frohes Neues", "Frohes neues Jahr", "Gutes neues Jahr"] + }, + { + "name": "intent_seasonal_happyholidays", + "keyphrases": ["Weihnachten", "Feiertage", "Urlaub", "Feiern", "Feier"] + }, + { + "name": "intent_amazon_signin", + "keyphrases": ["Trete Alexa bei", "Registriere dich bei Alexa", "Alexa aktivieren", "Schalte Alexa ein", "aktiviere Alexa"] + }, + { + "name": "intent_amazon_signin", + "keyphrases": ["Gehe aus Alexa raus", "Deaktiviere Alexa", "Schalte Alexa aus", "Beende Alexa", "Schließe Alexa"] + }, + { + "name": "intent_imperative_forward", + "keyphrases": ["Vorwärts"] + }, + { + "name": "intent_imperative_turnaround", + "keyphrases": ["drehe", "drehen", "drehe dich", "hinter dir"] + }, + { + "name": "intent_imperative_turnleft", + "keyphrases": ["links abbiegen", "links"] + }, + { + "name": "intent_imperative_turnright", + "keyphrases": ["rechts abbiegen", "rechts"] + }, + { + "name": "intent_play_rollcube", + "keyphrases": ["Spiele mit dem Würfel", "Bewege den Würfel", "Bewege deinen Würfel", "Rolle den Würfel", "Rolle deinen Würfel"] + }, + { + "name": "intent_play_popawheelie", + "keyphrases": ["Pfeifen", "Pfeife", "Wheelie", "Handstand", "mache einen Handstand", "Kopfstand", "mache einen Kopfstand"] + }, + { + "name": "intent_play_fistbump", + "keyphrases": ["Gib mir fünf", "gib mir die fünf", "hände hoch"] + }, + { + "name": "intent_play_blackjack", + "keyphrases": ["Twenty -One", "Blackjack", "siebzehn und vier"] + }, + { + "name": "intent_imperative_affirmative", + "keyphrases": ["Ja", "Richtig", "Korrekt", "wahr"] + }, + { + "name": "intent_imperative_negative", + "keyphrases": ["Nein", "nicht", "falsch", "inkorrekt", "unwahr"] + }, + { + "name": "intent_photo_take_extend", + "keyphrases": ["Foto", "Selfie", "Bild"] + }, + { + "name": "intent_imperative_praise", + "keyphrases": ["Gut", "großartig", "mythisch", "stark", "beeindruckend", "toll", "super", "Guter"] + }, + { + "name": "intent_imperative_abuse", + "keyphrases": ["schlecht", "dumm", "so geht es nicht", "böse", "Böser"] + }, + { + "name": "intent_imperative_apologize", + "keyphrases": ["Es tut mir leid", "Tut mir leid", "Entschuldigung", "sorry"] + }, + { + "name": "intent_imperative_backup", + "keyphrases": ["zurück", "abbrechen", "rückwärts"] + }, + { + "name": "intent_imperative_volumedown", + "keyphrases": ["senke die Lautstärke", "Lautstärke runter", "leiser"] + }, + { + "name": "intent_imperative_volumeup", + "keyphrases": ["erhöhe die Lautstärke", "Lautstärke rauf", "Lautstärke hoch", "lauter"] + }, + { + "name": "intent_imperative_lookatme", + "keyphrases": ["Schau mich an", "sieh mich an", "kuck mich an", "schau zu mir"] + }, + { + "name": "intent_imperative_volumelevel_extend", + "keyphrases": ["Volumen"] + }, + { + "name": "intent_imperative_shutup", + "keyphrases": ["Halt die Klappe", "Halt den Mund", "sei leise", "sei still", "ruhig", "schweige"] + }, + { + "name": "intent_greeting_hello", + "keyphrases": ["Hallo", "Wie geht es dir", "Guten Morgen", "Guten Abend", "Guten Tag", "Hey", "Hallo"] + }, + { + "name": "intent_imperative_come", + "keyphrases": ["Komm", "zu mir", "hier"] + }, + { + "name": "intent_imperative_love", + "keyphrases": ["Ich liebe dich", "Du bist meine Liebe", "Ich habe dich lieb"] + }, + { + "name": "intent_knowledge_promptquestion", + "keyphrases": ["Frage"] + }, + { + "name": "intent_clock_checktimer", + "keyphrases": ["Überprüfe die Stoppuhr", "Überprüfe den Timer", "Prüfe die Stoppuhr", "Prüfe den Timer", "zeige den Timer", "zeige den Countdown"] + }, + { + "name": "intent_global_stop_extend", + "keyphrases": ["Stopp die Stoppuhr", "Stopp den Timer", "Stopp den Countdown", "beende den Timer", "beende die stoppuhr", "beende den countdown", "Timer beenden", "Stoppuhr beenden", "Countdown beenden"] + }, + { + "name": "intent_clock_settimer_extend", + "keyphrases": ["Starte den Timer", "Starte den Countdown", "Zeitplan"] + }, + { + "name": "intent_clock_time", + "keyphrases": ["Was ist jetzt", "Wie spät ist es", "Was ist die Zeit", "Wie viel Uhr ist es"] + }, + { + "name": "intent_imperative_quiet", + "keyphrases": ["Genug", "Stop", "Bleib brav", "Bleib ruhig"] + }, + { + "name": "intent_imperative_dance", + "keyphrases": ["Balla", "Höre auf die Musik", "Höre der Musik zu", "Tanz", "Tanzen"] + }, + { + "name": "intent_play_pickupcube", + "keyphrases": ["hebe den Würfel auf", "hebe deinen Würfel auf"] + }, + { + "name": "intent_imperative_fetchcube", + "keyphrases": ["sammel den Würfel auf", "hole den Würfel", "bringe mir deinen Würfel"] + }, + { + "name": "intent_imperative_findcube", + "keyphrases": ["Finde den Würfel", "Finde deinen Würfel", "Wo ist der Würfel", "wo ist dein Würfel"] + }, + { + "name": "intent_play_anytrick", + "keyphrases": ["mach etwas Cooles", "tu irgendwas", "beeindrucke mich", "mach was"] + }, + { + "name": "intent_message_recordmessage_extend", + "keyphrases": ["Aufnahme", "Nimm eine Nachricht auf", "Nimm etwas auf"] + }, + { + "name": "intent_message_playmessage_extend", + "keyphrases": ["lies die Nachricht", "die Nachricht lesen", "Spiel die Nachricht ab"] + }, + { + "name": "intent_blackjack_hit", + "keyphrases": ["Hit", "eine weitere karte", "noch eine"] + }, + { + "name": "intent_blackjack_stand", + "keyphrases": ["Stand", "keine mehr", "das wars"] + }, + { + "name": "intent_play_keepaway", + "keyphrases": ["geh weg", "weg", "entferne dich", "zurück"] + } +] diff --git a/chipper/intent-data/en-US.json b/chipper/intent-data/en-US.json new file mode 100644 index 0000000..6e34eb4 --- /dev/null +++ b/chipper/intent-data/en-US.json @@ -0,0 +1,277 @@ +[ + { + "name" : "intent_names_username_extend", + "keyphrases": ["name is", "native is", "names", "name's", "my name is" ], + "requiresexact": false + }, + { + "name": "intent_weather_extend", + "keyphrases" : ["weather", "whether", "the other", "the water", "no other", "weather forecast", "weather tomorrow", "whats the weather" ], + "requiresexact": false + }, + { + "name": "intent_names_ask", + "keyphrases" : ["my name", "who am", "who am i"], + "requiresexact": false + }, + { + "name": "intent_imperative_eyecolor", + "keyphrases" : ["eye color", "colo", "i call her", "i foller", "icolor", "ecce", "erior", "ichor", "agricola", "change", "oracular", "oracle", "set your eye color to"], + "requiresexact": false + }, + { + "name": "intent_character_age", + "keyphrases" : ["older", "how old", "old are you", "old or yo", "how there you"], + "requiresexact": false + }, + { + "name": "intent_explore_start", + "keyphrases" : ["start", "plor", "owing", "tailoring", "oding", "oring", "pling", "start exploring"], + "requiresexact": false + }, + { + "name": "intent_system_charger", + "keyphrases" : ["charge", "home", "go to your", "church", "find your ch", "charger" ], + "requiresexact": false + }, + { + "name": "intent_system_sleep", + "keyphrases" : ["flee", "sleep", "sheep", "go to sleep" ], + "requiresexact": false + }, + { + "name": "intent_system_noaudio", + "keyphrases" : ["[blank_audio]", "[door opens]", "[sighs]" ], + "requiresexact": true + }, + { + "name": "intent_greeting_goodmorning", + "keyphrases" : ["morning", "mourning", "mooning", "it bore", "afternoon", "after noon", "after whom", "good morning" ], + "requiresexact": false + }, + { + "name": "intent_greeting_goodnight", + "keyphrases" : ["night", "might", "goodnight" ], + "requiresexact": false + }, + { + "name": "intent_greeting_goodbye", + "keyphrases" : ["good bye", "good by", "good buy", "goodbye" ], + "requiresexact": false + }, + { + "name": "intent_seasonal_happynewyear", + "keyphrases" : ["fireworks", "new year", "happy new", "happy to", "have been", "i now you", "no year", "enee", "i never", "knew her", "hobhouse", "bennie" ], + "requiresexact": false + }, + { + "name": "intent_seasonal_happyholidays", + "keyphrases" : ["he holds", "christmas", "behold", "holiday" ], + "requiresexact": false + }, + { + "name": "intent_amazon_signin", + "keyphrases" : ["in intellect", "fine in electa", "in alex", "ing alex", "in an elect", "to alex", "in angelica", "up alexa", "sign in to alexa" ], + "requiresexact": false + }, + { + "name": "intent_amazon_signin", + "keyphrases" : ["in outlet", "i now of elea", "out alexa", "out of ale" ], + "requiresexact": false + }, + { + "name": "intent_imperative_forward", + "keyphrases" : ["forward", "for ward", "for word", "move forward", "forwards" ], + "requiresexact": false + }, + { + "name": "intent_imperative_turnaround", + "keyphrases" : ["around", "one eighty", "one ate he", "turn around" ], + "requiresexact": false + }, + { + "name": "intent_imperative_turnleft", + "keyphrases" : ["rn left", "go left", "e left", "ed left", "ernest" ], + "requiresexact": false + }, + { + "name": "intent_imperative_turnright", + "keyphrases" : ["rn right", "go right", "e right", "ernie", "credit", "ed right" ], + "requiresexact": false + }, + { + "name": "intent_play_rollcube", + "keyphrases" : ["roll cu", "roll your cu", "all your cu", "roll human", "yorke", "old your he", "roll your cube" ], + "requiresexact": false + }, + { + "name": "intent_play_popawheelie", + "keyphrases" : ["pop a w", "polwhele", "olwen", "i wieland", "do a wheel", "doorstone", "thibetan", "powell", "welst", "a wheel", "willie", "a really", "o' billy", "pop a wheelie", "do a wheel stand" ], + "requiresexact": false + }, + { + "name": "intent_play_fistbump", + "keyphrases" : ["this pomp", "this pump", "bump", "fistb", "fistf", "this book", "pisto", "with pomp", "fison", "first", "fifth", "were fifteen", "if bump", "wisdom", "this bu", "fist bomb", "fist ball", "this ball", "system", "fistbump" ], + "requiresexact": false + }, + { + "name": "intent_play_blackjack", + "keyphrases" : ["black", "cards", "game", "play blackjack" ], + "requiresexact": false + }, + { + "name": "intent_imperative_affirmative", + "keyphrases" : ["yes", "correct", "sure", "yes please" ], + "requiresexact": false + }, + { + "name": "intent_imperative_negative", + "keyphrases" : ["no", "dont", "no thanks" ], + "requiresexact": true + }, + { + "name": "intent_photo_take_extend", + "keyphrases" : ["photo", "foto", "selby", "capture", "picture", "take a photo of me" ], + "requiresexact": false + }, + { + "name": "intent_imperative_praise", + "keyphrases" : ["good", "awesome", "also", "as some", "of them", "battle", "t rob", "the ro", "amazing", "woodcourt", "good robot" ], + "requiresexact": false + }, + { + "name": "intent_imperative_abuse", + "keyphrases" : ["bad", "that ro", "ad ro", "a root", "hate", "horrible", "bad robot" ], + "requiresexact": false + }, + { + "name": "intent_imperative_apologize", + "keyphrases" : ["sorry", "apologize", "apologise", "the tory", "nevermind", "never mind", "im sorry" ], + "requiresexact": false + }, + { + "name": "intent_imperative_backup", + "keyphrases" : ["back", "back up", "backwards","beck"], + "requiresexact": false + }, + { + "name": "intent_imperative_volumedown", + "keyphrases" : ["all you down", "volume down", "down volume", "down the volume", "quieter", "turn the volume down" ], + "requiresexact": false + }, + { + "name": "intent_imperative_volumeup", + "keyphrases" : ["all you up", "volume up", "up volume", "up the volume", "louder" ], + "requiresexact": false + }, + { + "name": "intent_imperative_lookatme", + "keyphrases" : ["stare", "at me" ], + "requiresexact": false + }, + { + "name": "intent_imperative_volumelevel_extend", + "keyphrases" : ["all you", "volume", "loudness" ], + "requiresexact": false + }, + { + "name": "intent_imperative_shutup", + "keyphrases" : ["shut up" ], + "requiresexact": false + }, + { + "name": "intent_greeting_hello", + "keyphrases" : ["hello", "our you", "follow", "for you", "far you", "how about you", "how are you", "the low", "the loo", "our are you" ], + "requiresexact": false + }, + { + "name": "intent_imperative_come", + "keyphrases" : ["come to me", "come here" ], + "requiresexact": false + }, + { + "name": "intent_imperative_love", + "keyphrases" : ["love you", "dove you", "i love you" ], + "requiresexact": false + }, + { + "name": "intent_knowledge_promptquestion", + "keyphrases" : ["question", "weston", "i have a question", "conversation", "lets talk", "let's talk" ], + "requiresexact": false + }, + { + "name": "intent_clock_checktimer", + "keyphrases" : ["check timer", "check the timer", "check the time her", "check time her", "check time her", "check time of her", "checked the timer", "checked the time her", "checked the time of her" ], + "requiresexact": false + }, + { + "name": "intent_global_stop_extend", + "keyphrases" : ["up the timer", "stop timer", "cancel the", "cancel timer", "stop clock", "stop be", "stopped t", "stopped be", "stopped at", "stop the" ], + "requiresexact": false + }, + { + "name": "intent_clock_settimer_extend", + "keyphrases" : ["timer", "time for", "time of for", "time or", "time of", "set a timer for" ], + "requiresexact": false + }, + { + "name": "intent_clock_time", + "keyphrases" : ["time is it", "the time", "what time", "clock", "what time is it" ], + "requiresexact": false + }, + { + "name": "intent_imperative_quiet", + "keyphrases" : ["quiet", "stop", "be quiet" ], + "requiresexact": false + }, + { + "name": "intent_imperative_dance", + "keyphrases" : ["dance", "dancing", "thence", "dance to the beat", "the beat", "boogie", "to the music" ], + "requiresexact": false + }, + { + "name": "intent_play_pickupcube", + "keyphrases" : ["pickup", "pick up" ], + "requiresexact": false + }, + { + "name": "intent_imperative_fetchcube", + "keyphrases" : ["fetch your cu", "fetch cu", "fetch the cu", "bring to me", "bring me", "bring me your cube" ], + "requiresexact": false + }, + { + "name": "intent_imperative_findcube", + "keyphrases" : ["your cu", "the cu", "find your cube" ], + "requiresexact": false + }, + { + "name": "intent_play_anytrick", + "keyphrases" : ["trick", "something cool", "some thing cool", "do a trick" ], + "requiresexact": false + }, + { + "name": "intent_message_recordmessage_extend", + "keyphrases" : ["record" ], + "requiresexact": false + }, + { + "name": "intent_message_playmessage_extend", + "keyphrases" : ["play message", "play method", "play a message", "play a method" ], + "requiresexact": false + }, + { + "name": "intent_blackjack_hit", + "keyphrases" : ["hit"], + "requiresexact": false + }, + { + "name": "intent_blackjack_stand", + "keyphrases" : ["stand", "stan" ], + "requiresexact": false + }, + { + "name": "intent_play_keepaway", + "keyphrases": ["keepaway", "keep away", "play keepaway" ], + "requiresexact": false + } +] diff --git a/chipper/intent-data/es-ES.json b/chipper/intent-data/es-ES.json new file mode 100644 index 0000000..646993a --- /dev/null +++ b/chipper/intent-data/es-ES.json @@ -0,0 +1,218 @@ +[ + { + "name" : "intent_names_username_extend", + "keyphrases" : [ "mi nombre es", "me llamo", "yo soy", "aquí está" ] + }, + { + "name": "intent_weather_extend", + "keyphrases" : [ "qué tiempo hace", "cómo es el tiempo", "cómo está fuera", "pronóstico del tiempo", "qué tiempo hará" ] + }, + { + "name": "intent_names_ask", + "keyphrases" : [ "cuál es mi nombre", "como me llamo", "quién soy" ] + }, + { + "name": "intent_imperative_eyecolor", + "keyphrases" : [ "color de ojos", "cambiar color", "ojos" ] + }, + { + "name": "intent_character_age", + "keyphrases" : [ "cuántos años tienes", "cuál es tu edad" ] + }, + { + "name": "intent_explore_start", + "keyphrases" : [ "ve a explorar", "explora", "ve a la exploración", "da un paseo" ] + }, + { + "name": "intent_system_charger", + "keyphrases" : [ "ve a casa", "a casa", "recargate", "colocate en el cargador", "encuentra el cargador", "ve al cargador" ] + }, + { + "name": "intent_system_sleep", + "keyphrases" : [ "dorme", "ve a dormir", "ve a la cama", "haz la siesta", "en tu cama" ] + }, + { + "name": "intent_greeting_goodmorning", + "keyphrases" : [ "dia", "mañana", "tarde" ] + }, + { + "name": "intent_greeting_goodnight", + "keyphrases" : [ "buenas noches", "buenas tardes" ] + }, + { + "name": "intent_greeting_goodbye", + "keyphrases" : [ "adiós", "hasta luego" ] + }, + { + "name": "intent_seasonal_happynewyear", + "keyphrases" : [ "fuegos artificiales", "feliz año nuevo", "feliz año nuevo" ] + }, + { + "name": "intent_seasonal_happyholidays", + "keyphrases" : [ "navidad", "vacaciones", "fiestas" ] + }, + { + "name": "intent_amazon_signin", + "keyphrases" : [ "regístrate en alexa", "activa alexa" ] + }, + { + "name": "intent_amazon_signin", + "keyphrases" : [ "sal de alexa", "desactiva alexa", "apaga alexa" ] + }, + { + "name": "intent_imperative_forward", + "keyphrases" : [ "adelante" ] + }, + { + "name": "intent_imperative_turnaround", + "keyphrases" : [ "gira" ] + }, + { + "name": "intent_imperative_turnleft", + "keyphrases" : [ "gira a la izquierda", "ve a la izquierda" ] + }, + { + "name": "intent_imperative_turnright", + "keyphrases" : [ "gira a la derecha", "ve a la derecha" ] + }, + { + "name": "intent_play_rollcube", + "keyphrases" : [ "juega con el cubo", "haz rodar el cubo", "mueve el cubo"] + }, + { + "name": "intent_play_popawheelie", + "keyphrases" : [ "silba" ] + }, + { + "name": "intent_play_fistbump", + "keyphrases" : [ "dame el cinco", "golpe de puño" ] + }, + { + "name": "intent_play_blackjack", + "keyphrases" : [ "veintiuna", "blackjack" ] + }, + { + "name": "intent_imperative_affirmative", + "keyphrases" : [ "sí", "correcto", "verdad", "verdadero" ] + }, + { + "name": "intent_imperative_negative", + "keyphrases" : [ "no", "incorrecto", "falso" ] + }, + { + "name": "intent_photo_take_extend", + "keyphrases" : [ "foto", "selfie" ] + }, + { + "name": "intent_imperative_praise", + "keyphrases" : [ "bravo", "genial", "mítico", "fuerte", "impresionante" ] + }, + { + "name": "intent_imperative_abuse", + "keyphrases" : [ "malo", "estúpido", "no es bueno" ] + }, + { + "name": "intent_imperative_apologize", + "keyphrases" : [ "lo siento" ] + }, + { + "name": "intent_imperative_backup", + "keyphrases" : [ "regresa" ] + }, + { + "name": "intent_imperative_volumedown", + "keyphrases" : [ "baje el volumen" ] + }, + { + "name": "intent_imperative_volumeup", + "keyphrases" : [ "sube el volumen" ] + }, + { + "name": "intent_imperative_lookatme", + "keyphrases" : [ "mírame" ] + }, + { + "name": "intent_imperative_volumelevel_extend", + "keyphrases" : [ "volumen" ] + }, + { + "name": "intent_imperative_shutup", + "keyphrases" : [ "cállate" ] + }, + { + "name": "intent_greeting_hello", + "keyphrases" : [ "hola", "cómo estás", "buenos días", "buenas noches", "buenas tardes" ] + }, + { + "name": "intent_imperative_come", + "keyphrases" : [ "ven", "de mí", "aquí" ] + }, + { + "name": "intent_imperative_love", + "keyphrases" : [ "te amo", "eres mi amor", "te quiero" ] + }, + { + "name": "intent_knowledge_promptquestion", + "keyphrases" : [ "pregunta" ] + }, + { + "name": "intent_clock_checktimer", + "keyphrases" : [ "comproba el cronómetro", "comproba el temporizador" ] + }, + { + "name": "intent_global_stop_extend", + "keyphrases" : [ "para el cronómetro", "para el temporizador", "detén el cronómetro", "detén el temporizador" ] + }, + { + "name": "intent_clock_settimer_extend", + "keyphrases" : [ "empeza el ", "empeza el cronómetro", "empeza el temporizador" ] + }, + { + "name": "intent_clock_time", + "keyphrases" : [ "qué hora es", "cuál es la hora", "qué hora haces" ] + }, + { + "name": "intent_imperative_quiet", + "keyphrases" : [ "basta", "detente", "sé bueno", "mantén la calma" ] + }, + { + "name": "intent_imperative_dance", + "keyphrases" : [ "baila", "hace un ballet", "danza" ] + }, + { + "name": "intent_play_pickupcube", + "keyphrases" : [ "recoge" ] + }, + { + "name": "intent_imperative_fetchcube", + "keyphrases" : [ "recoge el cubo", "toma tu cubo", "toma el cubo", "recoge tu cubo" ] + }, + { + "name": "intent_imperative_findcube", + "keyphrases" : [ "encuentra el cubo", "encuentra tu cubo", "busca el cubo", "busca tu cubo" ] + }, + { + "name": "intent_play_anytrick", + "keyphrases" : [ "haz algo genial", "sorpréndeme", "impresioname", "inventa algo" ] + }, + { + "name": "intent_message_recordmessage_extend", + "keyphrases" : [ "graba" ] + }, + { + "name": "intent_message_playmessage_extend", + "keyphrases" : [ "reproduce el mensaje", "lee el mensaje" ] + }, + { + "name": "intent_blackjack_hit", + "keyphrases" : [ "hit" ] + }, + { + "name": "intent_blackjack_stand", + "keyphrases" : [ "stand" ] + }, + { + "name": "intent_play_keepaway", + "keyphrases" : [ "vete", "vaya", "váyase", "regrese" ] + } +] \ No newline at end of file diff --git a/chipper/intent-data/fr-FR.json b/chipper/intent-data/fr-FR.json new file mode 100644 index 0000000..2cabdf8 --- /dev/null +++ b/chipper/intent-data/fr-FR.json @@ -0,0 +1,218 @@ +[ + { + "name" : "intent_names_username_extend", + "keyphrases": ["mon nom est", "mon nom est", "je suis", "voici"] + }, + { + "name": "intent_weather_extend", + "keyphrases": ["quel temps fait-il", "quel temps fera", "prévisions météorologiques", "meteo"] + }, + { + "name": "intent_names_ask", + "keyphrases": ["quel est mon nom", "comment m'appelle moi", "qui je suis"] + }, + { + "name": "intent_imperative_eyecolor", + "keyphrases": ["couleur des yeux", "couleur des yeux", "changer la couleur", "yeux"] + }, + { + "name": "intent_character_age", + "keyphrases": ["quel âge avez-vous", "quel est votre âge", "quel âge avez-vous"] + }, + { + "name": "intent_explore_start", + "keyphrases": ["allez explorer", "explore", "allez à l'exploration", "fait un tour"] + }, + { + "name": "intent_system_charger", + "keyphrases": ["rentrer à la maison", "à la maison", "rechargé", "trouver le chargeur", "aller en charge"] + }, + { + "name": "intent_system_sleep", + "keyphrases": ["va te coucher", "allez a dormir", "allez au lit", "lit", "bonne nuit"] + }, + { + "name": "intent_greeting_goodmorning", + "keyphrases": ["jour", "matin", "après-midi"] + }, + { + "name": "intent_greeting_goodnight", + "keyphrases": ["soir", "soirée"] + }, + { + "name": "intent_greeting_goodbye", + "keyphrases": ["adieu", "au revoir", "à voir"] + }, + { + "name": "intent_seasonal_happynewyear", + "keyphrases": ["fireworks", "bonne année"] + }, + { + "name": "intent_seasonal_happyholidays", + "keyphrases": ["noël", "vacance", "joyeuses fêtes"] + }, + { + "name": "intent_amazon_signin", + "keyphrases": ["entrez alexa", "inscrivez-toi sur alexa", "activez alexa"] + }, + { + "name": "intent_amazon_signin", + "keyphrases": ["sortez d'alexa", "désactivez alexa"] + }, + { + "name": "intent_imperative_forward", + "keyphrases": ["avant"] + }, + { + "name": "intent_imperative_turnaround", + "keyphrases": ["tourne"] + }, + { + "name": "intent_imperative_turnleft", + "keyphrases": ["turn à gauche", "allez à gauche"] + }, + { + "name": "intent_imperative_turnright", + "keyphrases": ["tir à droite", "allez à droite"] + }, + { + "name": "intent_play_rollcube", + "keyphrases": ["jouez avec le cube", "roulez le cube", "déplacez le cube"] + }, + { + "name": "intent_play_popawheelie", + "keyphrases": ["sifflez"] + }, + { + "name": "intent_play_fistbump", + "keyphrases": ["donnez-moi cinq", "donnez-moi les cinq"] + }, + { + "name": "intent_play_blackjack", + "keyphrases": ["vingt-un", "blackjack"] + }, + { + "name": "intent_imperative_affirmative", + "keyphrases": ["oui", "à droite", "correct", "oui", "vraie"] + }, + { + "name": "intent_imperative_negative", + "keyphrases": ["non", "pas", "mal", "false"] + }, + { + "name": "intent_photo_take_extend", + "keyphrases": ["photo", "selfie", "image"] + }, + { + "name": "intent_imperative_praise", + "keyphrases": ["bon", "gros", "mythique", "fort", "impressionnant"] + }, + { + "name": "intent_imperative_abuse", + "keyphrases": ["mal", "stupide", "ça ne va pas"] + }, + { + "name": "intent_imperative_apologize", + "keyphrases": ["je suis désolé", "désolé"] + }, + { + "name": "intent_imperative_backup", + "keyphrases": ["retournez"] + }, + { + "name": "intent_imperative_volumedown", + "keyphrases": ["abaisse le volume"] + }, + { + "name": "intent_imperative_volumeup", + "keyphrases": ["augmente le volume"] + }, + { + "name": "intent_imperative_lookatme", + "keyphrases": ["regardez-moi"] + }, + { + "name": "intent_imperative_volumelevel_extend", + "keyphrases": ["volume"] + }, + { + "name": "intent_imperative_shutup", + "keyphrases": ["tais-toi", "silence", "ferme"] + }, + { + "name": "intent_greeting_hello", + "keyphrases": ["bonjour", "comment vas-tu", "bonjour", "bonne soirée", "bon après-midi", "hey", "salut"] + }, + { + "name": "intent_imperative_come", + "keyphrases": ["viens ici", "viens à moi", "ici"] + }, + { + "name": "intent_imperative_love", + "keyphrases": ["je t'aime", "mon amour"] + }, + { + "name": "intent_knowledge_promptquestion", + "keyphrases": ["question"] + }, + { + "name": "intent_clock_checktimer", + "keyphrases": ["vérifiez le chronomètre", "vérifiez la minuterie"] + }, + { + "name": "intent_global_stop_extend", + "keyphrases": ["arrêtez le minuteur", "arrêtez le chronomètre"] + }, + { + "name": "intent_clock_settimer_extend", + "keyphrases": ["démarrez le minuteur", "démarrez le chronomètre"] + }, + { + "name": "intent_clock_time", + "keyphrases": ["quelle est l'heure", "quelle heure est il"] + }, + { + "name": "intent_imperative_quiet", + "keyphrases": ["assez", "stop", "restez bien", "restez calme"] + }, + { + "name": "intent_imperative_dance", + "keyphrases": ["danse"] + }, + { + "name": "intent_play_pickupcube", + "keyphrases": ["collect"] + }, + { + "name": "intent_imperative_fetchcube", + "keyphrases": ["apportez le cube"] + }, + { + "name": "intent_imperative_findcube", + "keyphrases": ["trouver le cube", "trouvez votre cube"] + }, + { + "name": "intent_play_anytrick", + "keyphrases": ["do something cool", "wonom me", "impression me", "inventer quelque chose"] + }, + { + "name": "intent_message_recordmessage_extend", + "keyphrases": ["enregistrer"] + }, + { + "name": "intent_message_playmessage_extend", + "keyphrases": ["reproduire le message", "lisez le message"] + }, + { + "name": "intent_blackjack_hit", + "keyphrases": ["hit"] + }, + { + "name": "intent_blackjack_stand", + "keyphrases": ["stand"] + }, + { + "name": "intent_play_keepaway", + "keyphrases": ["restez loin", "loin", "supprimé", "back"] + } +] \ No newline at end of file diff --git a/chipper/intent-data/it-IT.json b/chipper/intent-data/it-IT.json new file mode 100644 index 0000000..0a3522c --- /dev/null +++ b/chipper/intent-data/it-IT.json @@ -0,0 +1,218 @@ +[ + { + "name" : "intent_names_username_extend", + "keyphrases" : [ "il mio nome è", "mi chiamo", "io sono", "qui c'è" ] + }, + { + "name": "intent_weather_extend", + "keyphrases" : [ "che tempo fa", "com'è il tempo", "com'è fuori", "previsioni del tempo", "che tempo farà" ] + }, + { + "name": "intent_names_ask", + "keyphrases" : [ "qual è il mio nome", "come mi chiamo", "chi sono" ] + }, + { + "name": "intent_imperative_eyecolor", + "keyphrases" : [ "colore degli occhi", "colore agli occhi", "cambia colore", "occhi" ] + }, + { + "name": "intent_character_age", + "keyphrases" : [ "quanti anni hai", "qual è la tua età", "quanto sei vecchio" ] + }, + { + "name": "intent_explore_start", + "keyphrases" : [ "vai ad esplorare", "esplora", "vai in esplorazione", "fatti un giro" ] + }, + { + "name": "intent_system_charger", + "keyphrases" : [ "vai a casa", "a casa", "ricaricati", "mettiti in carica", "trova il caricabatterie", "vai in carica" ] + }, + { + "name": "intent_system_sleep", + "keyphrases" : [ "dormi", "vai a dormire", "a nanna", "vai a nanna", "fai la nanna", "nanna" ] + }, + { + "name": "intent_greeting_goodmorning", + "keyphrases" : [ "giorno", "mattina", "pomeriggio" ] + }, + { + "name": "intent_greeting_goodnight", + "keyphrases" : [ "notte", "sera" ] + }, + { + "name": "intent_greeting_goodbye", + "keyphrases" : [ "ciao", "arrivederci", "ci vediamo" ] + }, + { + "name": "intent_seasonal_happynewyear", + "keyphrases" : [ "fuochi d'artificio", "buon anno", "buon anno nuovo" ] + }, + { + "name": "intent_seasonal_happyholidays", + "keyphrases" : [ "natale", "vacanza", "vacanze", "feste" ] + }, + { + "name": "intent_amazon_signin", + "keyphrases" : [ "entra in alexa", "registrati su alexa", "attiva alexa", "accendi alexa" ] + }, + { + "name": "intent_amazon_signin", + "keyphrases" : [ "esci da alexa", "disattiva alexa", "spegni alexa" ] + }, + { + "name": "intent_imperative_forward", + "keyphrases" : [ "avanti" ] + }, + { + "name": "intent_imperative_turnaround", + "keyphrases" : [ "gira" ] + }, + { + "name": "intent_imperative_turnleft", + "keyphrases" : [ "gira a sinistra", "vai a sinistra" ] + }, + { + "name": "intent_imperative_turnright", + "keyphrases" : [ "gira a destra", "vai a destra" ] + }, + { + "name": "intent_play_rollcube", + "keyphrases" : [ "gioca col cubo", "fai rotolare il cubo", "sposta il cubo" ] + }, + { + "name": "intent_play_popawheelie", + "keyphrases" : [ "fischia", "fischio", "fischietta" ] + }, + { + "name": "intent_play_fistbump", + "keyphrases" : [ "dammi cinque", "dammi il cinque" ] + }, + { + "name": "intent_play_blackjack", + "keyphrases" : [ "ventuno", "blackjack" ] + }, + { + "name": "intent_imperative_affirmative", + "keyphrases" : [ "si", "giusto", "corretto", "sì", "vero" ] + }, + { + "name": "intent_imperative_negative", + "keyphrases" : [ "no", "non", "sbagliato", "falso" ] + }, + { + "name": "intent_photo_take_extend", + "keyphrases" : [ "foto", "selfie", "immagine" ] + }, + { + "name": "intent_imperative_praise", + "keyphrases" : [ "bravo", "grande", "mitico", "forte", "impressionante" ] + }, + { + "name": "intent_imperative_abuse", + "keyphrases" : [ "cattivo", "stupido", "così non va" ] + }, + { + "name": "intent_imperative_apologize", + "keyphrases" : [ "mi dispiace", "scusa", "scusami", "sono dispiaciuto" ] + }, + { + "name": "intent_imperative_backup", + "keyphrases" : [ "indietro", "annulla" ] + }, + { + "name": "intent_imperative_volumedown", + "keyphrases" : [ "abbassa il volume" ] + }, + { + "name": "intent_imperative_volumeup", + "keyphrases" : [ "alza il volume" ] + }, + { + "name": "intent_imperative_lookatme", + "keyphrases" : [ "guardami" ] + }, + { + "name": "intent_imperative_volumelevel_extend", + "keyphrases" : [ "volume" ] + }, + { + "name": "intent_imperative_shutup", + "keyphrases" : [ "zitto", "fai silenzio", "stai zitto" ] + }, + { + "name": "intent_greeting_hello", + "keyphrases" : [ "ciao", "come stai", "buongiorno", "buonasera", "buon pomeriggio", "ehi", "salve" ] + }, + { + "name": "intent_imperative_come", + "keyphrases" : [ "vieni", "da me", "qui" ] + }, + { + "name": "intent_imperative_love", + "keyphrases" : [ "ti voglio bene", "sei il mio amore", "ti amo" ] + }, + { + "name": "intent_knowledge_promptquestion", + "keyphrases" : [ "domanda" ] + }, + { + "name": "intent_clock_checktimer", + "keyphrases" : [ "controlla il cronometro", "controlla il timer" ] + }, + { + "name": "intent_global_stop_extend", + "keyphrases" : [ "ferma il cronometro", "ferma il timer", "stoppa il timer" ] + }, + { + "name": "intent_clock_settimer_extend", + "keyphrases" : [ "avvia il cronometro", "fai partire il cronometro", "cronometra" ] + }, + { + "name": "intent_clock_time", + "keyphrases" : [ "che ora è", "che ore sono", "qual è l'ora", "che ora fai" ] + }, + { + "name": "intent_imperative_quiet", + "keyphrases" : [ "basta", "fermo", "stai buono", "stai calmo" ] + }, + { + "name": "intent_imperative_dance", + "keyphrases" : [ "balla", "fai un balletto", "danza" ] + }, + { + "name": "intent_play_pickupcube", + "keyphrases" : [ "raccogli" ] + }, + { + "name": "intent_imperative_fetchcube", + "keyphrases" : [ "prendi il cubo", "prendi il tuo cubo", "raccogli il cubo" ] + }, + { + "name": "intent_imperative_findcube", + "keyphrases" : [ "trova il cubo", "trova il tuo cubo" ] + }, + { + "name": "intent_play_anytrick", + "keyphrases" : [ "fai qualcosa di figo", "stupiscimi", "impressionami", "inventa qualcosa" ] + }, + { + "name": "intent_message_recordmessage_extend", + "keyphrases" : [ "registra" ] + }, + { + "name": "intent_message_playmessage_extend", + "keyphrases" : [ "riproduci il messaggio", "leggi il messaggio" ] + }, + { + "name": "intent_blackjack_hit", + "keyphrases" : [ "hit" ] + }, + { + "name": "intent_blackjack_stand", + "keyphrases" : [ "stand" ] + }, + { + "name": "intent_play_keepaway", + "keyphrases" : [ "stai lontano", "via", "allontanati", "indietro" ] + } +] \ No newline at end of file diff --git a/chipper/intent-data/nt-NL.json b/chipper/intent-data/nt-NL.json new file mode 100644 index 0000000..162b188 --- /dev/null +++ b/chipper/intent-data/nt-NL.json @@ -0,0 +1,266 @@ +[ + { + "name" : "intent_names_username_extend", + "keyphrases": ["Mijn naam is", "naam is", "bijnaam is", "mijn maan is", "Mijn baan is" ], + "requiresexact": false + }, + { + "name": "intent_weather_extend", + "keyphrases" : ["weer", "meer", "keer", "het weer", "weerbericht", "hoe is het weer", "hoe is het meer", "het keer" ], + "requiresexact": false + }, + { + "name": "intent_names_ask", + "keyphrases" : ["mijn naam", "wat is mijn naam", "watts mijn naam", "mijn maan"], + "requiresexact": false + }, + { + "name": "intent_imperative_eyecolor", + "keyphrases" : ["ogen", "oogkleur", "boven", "maak je ogen", "boog", "boogkleur", "verander oogkleur naar", "oog meur"], + "requiresexact": false + }, + { + "name": "intent_character_age", + "keyphrases" : ["hoe oud", "hoe oud ben jij", "roe oud ben jij", "wat is je leeftijd", "wat is jouw leefijd", "hoe oud ben je"], + "requiresexact": false + }, + { + "name": "intent_explore_start", + "keyphrases" : ["ontdek", "ga ontdekken", "onttrek"], + "requiresexact": false + }, + { + "name": "intent_system_charger", + "keyphrases" : ["laad op", "huis", "ga naar", "muis", "ga naar je lader", "ga laden" ], + "requiresexact": false + }, + { + "name": "intent_system_sleep", + "keyphrases" : ["slaap", "ga slapen", "schaap", "ga schapen" ], + "requiresexact": false + }, + { + "name": "intent_greeting_goodmorning", + "keyphrases" : ["morgen", "morge", "goede morgen", "middag", "goede middag" ], + "requiresexact": false + }, + { + "name": "intent_greeting_goodnight", + "keyphrases" : ["avond", "almond", "fijne avond","goede avond" ], + "requiresexact": false + }, + { + "name": "intent_greeting_goodbye", + "keyphrases" : ["doei", "doeidoei", "bye", "groei" ], + "requiresexact": false + }, + { + "name": "intent_seasonal_happynewyear", + "keyphrases" : ["vuurwerk", "gelukkig nieuw jaar", "gelukkig nieuw waar", "vuur sterk", "nieuwe jaar" ], + "requiresexact": false + }, + { + "name": "intent_seasonal_happyholidays", + "keyphrases" : ["feestdagen", "kerst", "fijne feestagen", "fijne kerst", "fijne kerstmis", "beestdagen" ], + "requiresexact": false + }, + { + "name": "intent_amazon_signin", + "keyphrases" : ["log in bij alexa", "bij alexa", "", "ing alex", "in bij alexa", "naar alexa", "in angelica" ], + "requiresexact": false + }, + { + "name": "intent_amazon_signin", + "keyphrases" : ["in outlet", "i now of elea", "out alexa", "out of ale" ], + "requiresexact": false + }, + { + "name": "intent_imperative_forward", + "keyphrases" : ["voren", "naar voren", "rol naar voren", "boren", "ga naar boren", "storen","ga naar storen" ], + "requiresexact": false + }, + { + "name": "intent_imperative_turnaround", + "keyphrases" : ["keer om", "slalom", "draai om", "maai om", "meer om" ], + "requiresexact": false + }, + { + "name": "intent_imperative_turnleft", + "keyphrases" : ["ga links", "ga naar links", "draai naar links", "draai links" ], + "requiresexact": false + }, + { + "name": "intent_imperative_turnright", + "keyphrases" : ["ga rechts", "rechts", "ga rechst" ], + "requiresexact": false + }, + { + "name": "intent_play_rollcube", + "keyphrases" : ["rol je kubus", "ro je kubus", "wo je kubus", "rol kubus", "wo kubus" ], + "requiresexact": false + }, + { + "name": "intent_play_popawheelie", + "keyphrases" : ["pop een wheele", "popawhele", "wele", "pop a wiele", "wielstand", "do a flip", "weerstand", "pop een wie", "poppen wie"], + "requiresexact": false + }, + { + "name": "intent_play_fistbump", + "keyphrases" : ["box", "mox", "geef een box", "boks", "stots", "roks", "cocks", "vox" ], + "requiresexact": false + }, + { + "name": "intent_play_blackjack", + "keyphrases" : ["black", "kaarten", "game", "speel blackjack", "eenentwintigen", "eendentwintigen" ], + "requiresexact": false + }, + { + "name": "intent_imperative_affirmative", + "keyphrases" : ["ja", "klopt", "tuurlijk", "graag", "ja graag" ], + "requiresexact": false + }, + { + "name": "intent_imperative_negative", + "keyphrases" : ["nee", "niet", "nee bedankt", "slee"], + "requiresexact": true + }, + { + "name": "intent_photo_take_extend", + "keyphrases" : ["photo", "foto", "selfy", "fotografeer", "grafeer", "maak een foto" ], + "requiresexact": false + }, + { + "name": "intent_imperative_praise", + "keyphrases" : ["goed", "super", "goede robot", "goed robot", "stoep robot" ], + "requiresexact": false + }, + { + "name": "intent_imperative_abuse", + "keyphrases" : ["slecht", "stom", "slechte robot", "stomme robot", "haat", "dom" ], + "requiresexact": false + }, + { + "name": "intent_imperative_apologize", + "keyphrases" : ["sorry", "spijt me", "oops", "laat maar" ], + "requiresexact": false + }, + { + "name": "intent_imperative_backup", + "keyphrases" : ["back", "back up", "backwards","beck"], + "requiresexact": false + }, + { + "name": "intent_imperative_volumedown", + "keyphrases" : ["geluid omlaag", "zachter", "zet zechter", "zet het geluid zachter", "wachter" ], + "requiresexact": false + }, + { + "name": "intent_imperative_volumeup", + "keyphrases" : ["harder", "luider", "zet harder", "zet het geluid harder", "harrer" ], + "requiresexact": false + }, + { + "name": "intent_imperative_lookatme", + "keyphrases" : ["kijk naar me", "naar me", "kijk" ], + "requiresexact": false + }, + { + "name": "intent_imperative_volumelevel_extend", + "keyphrases" : ["luidst", "hardst" ], + "requiresexact": false + }, + { + "name": "intent_imperative_shutup", + "keyphrases" : ["shut up", "hou op" ], + "requiresexact": false + }, + { + "name": "intent_greeting_hello", + "keyphrases" : ["hallo", "hoi", "halo" ], + "requiresexact": false + }, + { + "name": "intent_imperative_come", + "keyphrases" : ["kom hier", "kom naar mij toe", "kom mier" ], + "requiresexact": false + }, + { + "name": "intent_imperative_love", + "keyphrases" : ["hou van je", "dove you", "i love you" ], + "requiresexact": false + }, + { + "name": "intent_knowledge_promptquestion", + "keyphrases" : ["vraag", "ik heb een vraag", "waag", "ik heb een waag" ], + "requiresexact": false + }, + { + "name": "intent_clock_checktimer", + "keyphrases" : ["check timer", "check de timer" ], + "requiresexact": false + }, + { + "name": "intent_global_stop_extend", + "keyphrases" : ["stop timer", "stop de timer", "stop klok", "stoppen timer", "stop het alarm", "stop alarm" ], + "requiresexact": false + }, + { + "name": "intent_clock_settimer_extend", + "keyphrases" : ["timer", "timer van", "alarm van", "zet alarm", "zet een timer van", "zet een alarm van" ], + "requiresexact": false + }, + { + "name": "intent_clock_time", + "keyphrases" : ["hoe laat is het", "tijd", "wat is de tijd", "klok" ], + "requiresexact": false + }, + { + "name": "intent_imperative_quiet", + "keyphrases" : ["still", "hou je bek", "doe stil" ], + "requiresexact": false + }, + { + "name": "intent_imperative_dance", + "keyphrases" : ["dans", "trans", "luister naar de muziek", "dans op de beat", "publiek", "luister naar pibliek", "naar de muziek" ], + "requiresexact": false + }, + { + "name": "intent_play_pickupcube", + "keyphrases" : ["pak op", "pak je kubus" ], + "requiresexact": false + }, + { + "name": "intent_imperative_fetchcube", + "keyphrases" : ["breng je kubus naar mij", "haal je kubus op", "maal je kubus", "streng je kubus" ], + "requiresexact": false + }, + { + "name": "intent_imperative_findcube", + "keyphrases" : ["vind je kubus", "vind kubus", "je kubus" ], + "requiresexact": false + }, + { + "name": "intent_play_anytrick", + "keyphrases" : ["trukje", "iets cools", "doe een truk", "doe iets cools" ], + "requiresexact": false + }, + { + "name": "intent_message_recordmessage_extend", + "keyphrases" : ["neem op", "opnemen" ] + }, + { + "name": "intent_blackjack_hit", + "keyphrases" : ["hit", "raak" ], + "requiresexact": false + }, + { + "name": "intent_blackjack_stand", + "keyphrases" : ["stand", "stan", "standaard", "kraam" ], + "requiresexact": false + }, + { + "name": "intent_play_keepaway", + "keyphrases": ["keepaway", "keep away", "play keepaway", "speel houdt weg", "houdt weg" ], + "requiresexact": false + } +] \ No newline at end of file diff --git a/chipper/intent-data/pl-PL.json b/chipper/intent-data/pl-PL.json new file mode 100644 index 0000000..1f7e341 --- /dev/null +++ b/chipper/intent-data/pl-PL.json @@ -0,0 +1,218 @@ +[ + { + "name": "intent_names_username_extend", + "keyphrases": ["moje imię to", "nazwywam się", "mam na imię"] + }, + { + "name": "intent_weather_extend", + "keyphrases": ["prognoza pogody", "sprawdź pogodę", "sprawdź prognozę pogody", "jaki jutro będzie dzień", "jaka jest pogoda", "jaka jest prognoza pogody", "jaka jest na zewnątrz", "jaka jest pogoda jutro"] + }, + { + "name": "intent_names_ask", + "keyphrases": ["jak mam na imię", "kim jestem", "kto jestem", "moje imię", "jak się nazywam"] + }, + { + "name": "intent_imperative_eyecolor", + "keyphrases": ["kolor", "kolor oczu", "ustaw kolor oczu", "zmień kolor oczu", "ustaw kolor", "zmień kolor"] + }, + { + "name": "intent_character_age", + "keyphrases": ["ile masz lat", "kiedy się urodziłeś", "kiedy powstałeś", "jak stary jesteś", "jak młody jesteś"] + }, + { + "name": "intent_explore_start", + "keyphrases": ["odkrywaj", "eksploruj", "zwiedzaj", "poznaj", "sprawdź", "rozpocznij odkrywanie", "zacznij eksplorować", "rozpocznij eksplorację", "rozpocznij zwiedzanie", "rozpocznij sprawdzanie", "rozejrzyj się"] + }, + { + "name": "intent_system_charger", + "keyphrases": ["do domu", "wróc do domu", "idź do domu", "naładuj", "ładować", "naładuj się", "idź się ładować", "do ładowarki", "znajdź ładowarkę", "poszukaj ładowarki", "idź się ładować"] + }, + { + "name": "intent_system_sleep", + "keyphrases": ["idź spać", "spać", "śpij"] + }, + { + "name": "intent_greeting_goodmorning", + "keyphrases": ["dzień dobry", "obudź się"] + }, + { + "name": "intent_greeting_goodnight", + "keyphrases": ["dobranoc", "dobra noc", "dobrej nocy"] + }, + { + "name": "intent_greeting_goodbye", + "keyphrases": ["dowidzenia", "do widzenia", "widzenia", "pa", "żegnaj"] + }, + { + "name": "intent_seasonal_happynewyear", + "keyphrases": ["fajerwerki", "szczęśliwego nowego roku", "szczęśliwego roku", "wszystkiego najlepszego"] + }, + { + "name": "intent_seasonal_happyholidays", + "keyphrases": ["impreza", "balanga", "wakacje", "wesołych świąt"] + }, + { + "name": "intent_amazon_signin", + "keyphrases": ["wprowadź aleksę", "zainstaluj aleksę", "zaloguj do aleksy", "aktywuj aleksę", "zarejestruj się w aleksa", "włącz aleksę", "zaloguj się"] + }, + { + "name": "intent_amazon_signin", + "keyphrases": ["wyloguj się", "wyłącz aleksa", "wyłącz alekse", "wyłącz aleksę", "deaktywuj aleksę", "odinstaluj aleksę", "odinstaluj alekse"] + }, + { + "name": "intent_imperative_forward", + "keyphrases": ["do przodu", "do pszodu", "przodu", "pszodu", "naprzód", "napszód", "na przód", "na pszód"] + }, + { + "name": "intent_imperative_turnaround", + "keyphrases": ["obróć się", "zaprezentuj się"] + }, + { + "name": "intent_imperative_turnleft", + "keyphrases": ["lewo", "w lewo", "skręć w lewo", "idź w lewo"] + }, + { + "name": "intent_imperative_turnright", + "keyphrases": ["prawo", "w prawo", "skręć w prawo", "idź w prawo"] + }, + { + "name": "intent_play_rollcube", + "keyphrases": ["rzuć kostką", "przesuń kostkę", "zagraj kostką", "pobaw się kostką"] + }, + { + "name": "intent_play_popawheelie", + "keyphrases": ["stań na głowie", "stań na rękach", "poszalej", "jedź na jednym kole"] + }, + { + "name": "intent_play_fistbump", + "keyphrases": ["przybij piątke", "pszybij piątke", "żółwik", "piątka", "daj piątkę"] + }, + { + "name": "intent_play_blackjack", + "keyphrases": ["blek dżak", "blek dzak", "blekdżak", "blekdzak", "zagrajmy w karty", "gramy w karty", "zagrajmy", "karty"] + }, + { + "name": "intent_imperative_affirmative", + "keyphrases": ["ta", "tak", "dokładnie", "pewnie", "dajesz", "prawda"] + }, + { + "name": "intent_imperative_negative", + "keyphrases": ["nie", "daj spokój", "spadaj"] + }, + { + "name": "intent_photo_take_extend", + "keyphrases": ["zdjęcie", "zrób zdjęcie", "zrób mi zdjęcie", "zrób nam zdjęcie", "fotkę", "zrób fotkę", "zrób mi fotkę", "zrób nam fotkę", "selfi"] + }, + { + "name": "intent_imperative_praise", + "keyphrases": ["dobrze", "bardzo dobrze", "świetnie", "wspaniale", "znakomicie", "niesamowicie", "grzeczny jesteś", "fajny jesteś", "dobry jesteś", "dobry robot"] + }, + { + "name": "intent_imperative_abuse", + "keyphrases": ["źle", "kiepsko", "słabo", "niedobrze", "strasznie", "okropnie"] + }, + { + "name": "intent_imperative_apologize", + "keyphrases": ["przepraszam", "pszepraszam", "nieważne"] + }, + { + "name": "intent_imperative_backup", + "keyphrases": ["do tyłu", "cofnij się", "wstecz", "wróc"] + }, + { + "name": "intent_imperative_volumedown", + "keyphrases": ["ciszej", "wycisz", "zmniejsz głośność"] + }, + { + "name": "intent_imperative_volumeup", + "keyphrases": ["głośniej", "podgłośnij", "zwiększ głośność"] + }, + { + "name": "intent_imperative_lookatme", + "keyphrases": ["popatrz się", "patrz tutaj", "gapi się", "zobacz na mnie", "popatrz na mnie"] + }, + { + "name": "intent_imperative_volumelevel_extend", + "keyphrases": ["głośność", "ustaw głośność", "ustaw głośność na"] + }, + { + "name": "intent_imperative_shutup", + "keyphrases": ["zamknij się", "morda w kubeł"] + }, + { + "name": "intent_greeting_hello", + "keyphrases": ["cześć", "co tam", "jak się masz", "co tam słychać", "co u ciebie"] + }, + { + "name": "intent_imperative_come", + "keyphrases": ["chodź", "chodź tu", "chodź tutaj", "chodź no tutaj", "chodź do mnie", "przyjdź do mnie", "przyjdź tu", "pokaż się"] + }, + { + "name": "intent_imperative_love", + "keyphrases": ["kocham cię", "słodki", "kochany", "miłości moja", "słodziak", "słodzi jak", "łodzi jak", "milusi"] + }, + { + "name": "intent_knowledge_promptquestion", + "keyphrases": ["mam pytanie", "pytanie"] + }, + { + "name": "intent_clock_checktimer", + "keyphrases": ["pokaż odliczanie", "jaki czas", "ile czasu", "sprawdź minutnik", "sprawdź czasomierz", "sprawdź stoper", "minutnik", "czasomierz", "stoper"] + }, + { + "name": "intent_global_stop_extend", + "keyphrases": ["zatrzymaj", "wyłącz minutnik", "zatrzymaj minutnik", "anuluj minutnik", "zatrzymaj czasomierz", "wyłącz czasomierz", "anuluj czasomierz", "zatrzymaj stoper", "wyłącz stoper", "anuluj stoper"] + }, + { + "name": "intent_clock_settimer_extend", + "keyphrases": ["ustaw minutnik", "ustaw czasomierz", "ustaw czasomiesz", "ustaw stoper", "ustaw czas na", "odliczaj od", "odliczanie od"] + }, + { + "name": "intent_clock_time", + "keyphrases": ["która godzina", "godzina", "jaka godzina", "pokaż godzinę"] + }, + { + "name": "intent_imperative_quiet", + "keyphrases": ["bądź cicho", "cicho", "spokój", "uspokój się", "uspokój"] + }, + { + "name": "intent_imperative_dance", + "keyphrases": ["tańcz", "zatańcz", "zatańczysz", "pokaż jak tańczysz", "potańcz", "potańcówa", "potańcówka", "po bujaj się", "słucha muzyki", "posłuchaj muzyki", "poczuj rytm"] + }, + { + "name": "intent_play_pickupcube", + "keyphrases": ["podnieś kostkę", "podnieś", "zabierz kostkę", "zabierz"] + }, + { + "name": "intent_imperative_fetchcube", + "keyphrases": ["przynieś kostkę", "weź kostkę"] + }, + { + "name": "intent_imperative_findcube", + "keyphrases": ["gdzie masz kostkę", "gdzie kostka", "pokaż kostkę", "poszukaj kostki", "poszukaj", "szukaj kostki", "znajdź kostkę"] + }, + { + "name": "intent_play_anytrick", + "keyphrases": ["zrobić coś", "zrób cokolwiek", "zrobić coś fajnego", "pokaż coś fajnego", "pokaż coś", "pochal się czymś", "pochal się"] + }, + { + "name": "intent_message_recordmessage_extend", + "keyphrases": ["nagraj wiadomość", "nagraj", "nagranie", "nagrywać"] + }, + { + "name": "intent_message_playmessage_extend", + "keyphrases": ["przeczytaj wiadomość", "przeczytaj", "odczytaj", "czytaj"] + }, + { + "name": "intent_blackjack_hit", + "keyphrases": ["trafienie"] + }, + { + "name": "intent_blackjack_stand", + "keyphrases": ["gramy", "dajesz"] + }, + { + "name": "intent_play_keepaway", + "keyphrases": [ "nie zbliżaj się", "odejdź", "nie teraz", "nie chcę"] + } +] \ No newline at end of file diff --git a/chipper/intent-data/pt-BR.json b/chipper/intent-data/pt-BR.json new file mode 100644 index 0000000..6442db5 --- /dev/null +++ b/chipper/intent-data/pt-BR.json @@ -0,0 +1,218 @@ +[ + { + "name" : "intent_names_username_extend", + "keyphrases": ["nome é", "me chamo"] + }, + { + "name": "intent_weather_extend", + "keyphrases" : ["previsão", "temperatura", "quantos graus", "enquanto os graus"] + }, + { + "name": "intent_names_ask", + "keyphrases" : ["quem sou eu", "qual o meu nome", "qual meu nome", "sou eu", "meu nome"] + }, + { + "name": "intent_imperative_eyecolor", + "keyphrases" : ["cor dos olhos", "trocar cor", "mudar cor","trocar cor dos olhos"] + }, + { + "name": "intent_character_age", + "keyphrases" : ["idade", "qual sua idade", "quantos anos", "quantos anos você tem"] + }, + { + "name": "intent_explore_start", + "keyphrases" : ["vai dar uma volta", "dá uma volta", "vai passear", "dá uma volta", "explorar" ] + }, + { + "name": "intent_system_charger", + "keyphrases" : ["vai carregar", "vai pra casa", "encontra sua casa", "encontre sua casa", "para casa" ] + }, + { + "name": "intent_system_sleep", + "keyphrases" : ["dormir", "vai dormi", "dorme"] + }, + { + "name": "intent_greeting_goodmorning", + "keyphrases" : ["bom dia", "pom dia", "bom tia", "boa tarde", "poa tarde" ] + }, + { + "name": "intent_greeting_goodnight", + "keyphrases" : ["boa noite", "poa noite" ] + }, + { + "name": "intent_greeting_goodbye", + "keyphrases" : ["tchau", "adeus", "até mais", "até logo", "até amanhã" ] + }, + { + "name": "intent_seasonal_happynewyear", + "keyphrases" : ["soltar fogos", "ano novo", "feliz ano", "feliz anivérsario", "é meu anivérsario" ] + }, + { + "name": "intent_seasonal_happyholidays", + "keyphrases" : ["férias", "feriado", "folga", "natal", "feliz natal" ] + }, + { + "name": "intent_amazon_signin", + "keyphrases" : ["ativar alexa", "logar alexa", "colocar alexa" ] + }, + { + "name": "intent_amazon_signin", + "keyphrases" : ["desativar alexa", "parar alexa", "tirar alexa"] + }, + { + "name": "intent_imperative_forward", + "keyphrases" : ["pra frente", "vai pra frente", "reto", "vai reto"] + }, + { + "name": "intent_imperative_turnaround", + "keyphrases" : ["virar", "vire", "de a volta" ] + }, + { + "name": "intent_imperative_turnleft", + "keyphrases" : ["vai para esquerda", "esquerda", "vira para esquerda", "lado direito" ] + }, + { + "name": "intent_imperative_turnright", + "keyphrases" : ["vai para direita", "direita", "vira para direita", "lado direito"] + }, + { + "name": "intent_play_rollcube", + "keyphrases" : ["rola o cubo", "rola seu cubo", "gira seu cubo", "gira o cubo", "girar cubo" ] + }, + { + "name": "intent_play_popawheelie", + "keyphrases" : ["pop a w", "polwhele", "olwen", "i wieland", "do a wheel", "doorstone", "thibetan", "powell", "welst", "a wheel", "willie", "a really", "o' billy" ] + }, + { + "name": "intent_play_fistbump", + "keyphrases" : ["bate aqui", "baixe aqui", "bate", "soquinho", "comprimenta", "comprementa" ] + }, + { + "name": "intent_play_blackjack", + "keyphrases" : ["black", "cards", "game" ] + }, + { + "name": "intent_imperative_affirmative", + "keyphrases" : ["sim", "correto", "com certeza" ] + }, + { + "name": "intent_imperative_negative", + "keyphrases" : ["não" ] + }, + { + "name": "intent_photo_take_extend", + "keyphrases" : ["tirar foto", "foto", "Selfie", "tira uma foto"] + }, + { + "name": "intent_imperative_praise", + "keyphrases" : ["bom", "incrível", "bom garoto", "legal" ] + }, + { + "name": "intent_imperative_abuse", + "keyphrases" : ["mal", "mau","horrivel", "feio","veio" ] + }, + { + "name": "intent_imperative_apologize", + "keyphrases" : ["desculpa", "perdão", "sinto muito", "me desculpe", "me perdoe" ] + }, + { + "name": "intent_imperative_backup", + "keyphrases" : ["para trás","vai pra trás"] + }, + { + "name": "intent_imperative_volumedown", + "keyphrases" : ["abaixar volume", "baixar volume", "fala baixo"] + }, + { + "name": "intent_imperative_volumeup", + "keyphrases" : ["aumentar volume", "aumentar", "mais alto"] + }, + { + "name": "intent_imperative_lookatme", + "keyphrases" : ["olha", "mim", "olha aqui"] + }, + { + "name": "intent_imperative_volumelevel_extend", + "keyphrases" : ["all you", "volume", "loudness" ] + }, + { + "name": "intent_imperative_shutup", + "keyphrases" : ["calado", "cala a boca", "fica queito"] + }, + { + "name": "intent_greeting_hello", + "keyphrases" : ["oi", "como vai", "como vai você", "beleza", "tudo bem"] + }, + { + "name": "intent_imperative_come", + "keyphrases" : ["vem", "vem aqui", "vem cá", "vem pra cá" ] + }, + { + "name": "intent_imperative_love", + "keyphrases" : ["amo", "amor", "te amo", "fofinho"] + }, + { + "name": "intent_knowledge_promptquestion", + "keyphrases" : ["pergunta", "questão", "tenho uma pergunta","eu tenho uma pergunta", "uma pergunta" ] + }, + { + "name": "intent_clock_checktimer", + "keyphrases" : ["check timer", "check the timer", "check the time her", "check time her", "check time her", "check time of her", "checked the timer", "checked the time her", "checked the time of her" ] + }, + { + "name": "intent_global_stop_extend", + "keyphrases" : ["up the timer", "stop timer", "cancel the", "cancel timer", "stop clock", "stop be", "stopped t", "stopped be", "stopped at", "stop the" ] + }, + { + "name": "intent_clock_settimer_extend", + "keyphrases" : ["cronômetro", "contar", "conta"] + }, + { + "name": "intent_clock_time", + "keyphrases" : ["que horas são", "que hora é", "que oração", "oração"] + }, + { + "name": "intent_imperative_quiet", + "keyphrases" : ["quieto", "para", "queto" ] + }, + { + "name": "intent_imperative_dance", + "keyphrases" : ["dance", "dançando", "dança" ] + }, + { + "name": "intent_play_pickupcube", + "keyphrases" : ["pega","tras pra mim", "pega o cubo"] + }, + { + "name": "intent_imperative_fetchcube", + "keyphrases" : ["pega seu cubo", "pega o cubo" ] + }, + { + "name": "intent_imperative_findcube", + "keyphrases" : ["seu cubo", "encontra seu cubo" ] + }, + { + "name": "intent_play_anytrick", + "keyphrases" : ["faz algo legal", "faz um truque" ] + }, + { + "name": "intent_message_recordmessage_extend", + "keyphrases" : ["gravar" ] + }, + { + "name": "intent_message_playmessage_extend", + "keyphrases" : ["tocar mensagem", "repetir mensagem"] + }, + { + "name": "intent_blackjack_hit", + "keyphrases" : ["rite", "hite", "riti", "hiti"] + }, + { + "name": "intent_blackjack_stand", + "keyphrases" : ["fica", "fica parado" ] + }, + { + "name": "intent_play_keepaway", + "keyphrases": ["afaste", "vai pra lá", "fica longe" ] + } +] diff --git a/chipper/intent-data/ru-RU.json b/chipper/intent-data/ru-RU.json new file mode 100644 index 0000000..20e4a53 --- /dev/null +++ b/chipper/intent-data/ru-RU.json @@ -0,0 +1,218 @@ +[ + { + "name" : "intent_names_username_extend", + "keyphrases": ["имена", "назови имена" ] + }, + { + "name": "intent_weather_extend", + "keyphrases" : ["погода", "погода завтра", "какая погода" ] + }, + { + "name": "intent_names_ask", + "keyphrases" : ["как меня зовут", "моё имя", "кто я"] + }, + { + "name": "intent_imperative_eyecolor", + "keyphrases" : ["глаз", "глаза", "измени цвет глаз", "поменяй цвет"] + }, + { + "name": "intent_character_age", + "keyphrases" : ["сколько тебе лет" ] + }, + { + "name": "intent_explore_start", + "keyphrases" : ["начни исследовать"] + }, + { + "name": "intent_system_charger", + "keyphrases" : ["зарядка", "дом", "база", "найди зарядку", "найди базу", "иди на зарядку", "иди на базу"] + }, + { + "name": "intent_system_sleep", + "keyphrases" : ["спать", "иди спать" ] + }, + { + "name": "intent_greeting_goodmorning", + "keyphrases" : ["доброе утро", "утро" ] + }, + { + "name": "intent_greeting_goodnight", + "keyphrases" : ["спокойной ночи", "пора спать" ] + }, + { + "name": "intent_greeting_goodbye", + "keyphrases" : ["пока", "досвидание", "я ушёл" ] + }, + { + "name": "intent_seasonal_happynewyear", + "keyphrases" : ["фейрверк", "новый год", "салют" ] + }, + { + "name": "intent_seasonal_happyholidays", + "keyphrases" : ["праздник" ] + }, + { + "name": "intent_amazon_signin", + "keyphrases" : ["алекса" ] + }, + { + "name": "intent_amazon_signin", + "keyphrases" : ["алекса" ] + }, + { + "name": "intent_imperative_forward", + "keyphrases" : ["вперед", "едь вперёд", "двигайся вперёд" ] + }, + { + "name": "intent_imperative_turnaround", + "keyphrases" : ["повернись", "обернись", "оглянись", "развернись" ] + }, + { + "name": "intent_imperative_turnleft", + "keyphrases" : ["налево", "лево", "влево"] + }, + { + "name": "intent_imperative_turnright", + "keyphrases" : ["направо", "право", "вправо" ] + }, + { + "name": "intent_play_rollcube", + "keyphrases" : ["кати куб", "кати кубик", "покатай куб", "покатай кубик" ] + }, + { + "name": "intent_play_popawheelie", + "keyphrases" : ["встань на куб", "встань на кубик" ] + }, + { + "name": "intent_play_fistbump", + "keyphrases" : ["дай пять", "пятюня", "дай кулачок" ] + }, + { + "name": "intent_play_blackjack", + "keyphrases" : ["карты", "играть", "играем в карты", "давай играть", "давай сыграем" ] + }, + { + "name": "intent_imperative_affirmative", + "keyphrases" : ["да", "давай", "конечно" ] + }, + { + "name": "intent_imperative_negative", + "keyphrases" : ["нет" ] + }, + { + "name": "intent_photo_take_extend", + "keyphrases" : ["фото", "селфи", "сделай фото", "сфотографируй" ] + }, + { + "name": "intent_imperative_praise", + "keyphrases" : ["красавчик", "молодец", "хороший робот" ] + }, + { + "name": "intent_imperative_abuse", + "keyphrases" : ["это плохо", "отстой", "плохой робот" ] + }, + { + "name": "intent_imperative_apologize", + "keyphrases" : ["извини", "прошу прощения", "мне жаль" ] + }, + { + "name": "intent_imperative_backup", + "keyphrases" : ["назад", "двигайся назад", "едь назад" ] + }, + { + "name": "intent_imperative_volumedown", + "keyphrases" : ["убавь звук", "убавь громкость", "тише громкость" ] + }, + { + "name": "intent_imperative_volumeup", + "keyphrases" : ["прибавь звук", "прибавь громкось", "громче" ] + }, + { + "name": "intent_imperative_lookatme", + "keyphrases" : ["смотри на меня", "посмотри на меня", "взгляни на меня" ] + }, + { + "name": "intent_imperative_volumelevel_extend", + "keyphrases" : ["громкость", "уровень громкости" ] + }, + { + "name": "intent_imperative_shutup", + "keyphrases" : ["заткнись" ] + }, + { + "name": "intent_greeting_hello", + "keyphrases" : ["привет", "здравствуй", "рад тебя видеть" ] + }, + { + "name": "intent_imperative_come", + "keyphrases" : ["ко мне", "иди ко мне", "иди сюда" ] + }, + { + "name": "intent_imperative_love", + "keyphrases" : ["люблю тебя" ] + }, + { + "name": "intent_knowledge_promptquestion", + "keyphrases" : ["вопрос", "у меня есть вопрос" ] + }, + { + "name": "intent_clock_checktimer", + "keyphrases" : ["проверь таймер" ] + }, + { + "name": "intent_global_stop_extend", + "keyphrases" : ["останови таймер", "отмени таймер", "выключи таймер" ] + }, + { + "name": "intent_clock_settimer_extend", + "keyphrases" : ["таймер", "поставь таймер", "установи таймер" ] + }, + { + "name": "intent_clock_time", + "keyphrases" : ["время", "который час", "сколько время", "который сейчас час" ] + }, + { + "name": "intent_imperative_quiet", + "keyphrases" : ["тихо", "прекрати", "будь тише" ] + }, + { + "name": "intent_imperative_dance", + "keyphrases" : ["танец", "танцуй", "давай танцевать" ] + }, + { + "name": "intent_play_pickupcube", + "keyphrases" : ["возьми кубик", "подбери кубик", "поднеми кубик" ] + }, + { + "name": "intent_imperative_fetchcube", + "keyphrases" : ["дай кубик", "принеси мне", "неси мне", "принеси свой кубик" ] + }, + { + "name": "intent_imperative_findcube", + "keyphrases" : ["если кубик", "найди кубик", "ищи кубик", "где твой кубик", "найди свой кубик" ] + }, + { + "name": "intent_play_anytrick", + "keyphrases" : ["трюк", "сделай трюк" ] + }, + { + "name": "intent_message_recordmessage_extend", + "keyphrases" : ["запиши" ] + }, + { + "name": "intent_message_playmessage_extend", + "keyphrases" : ["воспроизведи сообщение" ] + }, + { + "name": "intent_blackjack_hit", + "keyphrases" : ["ещё", "ещё карту"] + }, + { + "name": "intent_blackjack_stand", + "keyphrases" : ["хватит" ] + }, + { + "name": "intent_play_keepaway", + "keyphrases": ["расстояние", "расстоянии", "состояние" ] + } +] diff --git a/chipper/intent-data/tr-TR.json b/chipper/intent-data/tr-TR.json new file mode 100644 index 0000000..1cf0af7 --- /dev/null +++ b/chipper/intent-data/tr-TR.json @@ -0,0 +1,218 @@ +[ + { + "name": "intent_names_username_extend", + "keyphrases": ["adım", "yerliyim", "isimler", "adımın", "benim adım"] + }, + { + "name": "intent_weather_extend", + "keyphrases": ["hava durumu", "ister", "öteki", "su", "başka", "hava tahmini", "yarın hava", "hava nasıl"] + }, + { + "name": "intent_names_ask", + "keyphrases": ["benim adım", "kimim", "ben kimim"] + }, + { + "name": "intent_imperative_eyecolor", + "keyphrases": ["göz rengi", "renk", "onu çağırıyorum", "onu takip ediyorum", "irenk", "ekse", "eriye", "ikan", "agrikola", "değiştir", "oraküler", "oracle", "göz rengini şuna ayarla"] + }, + { + "name": "intent_character_age", + "keyphrases": ["daha yaşlı", "kaç yaşında", "sen kaç yaşındasın", "yaşlı mısın", "senin yaşın kaç"] + }, + { + "name": "intent_explore_start", + "keyphrases": ["başla", "keşfet", "akıyor", "terzilik", "kodlama", "keşif", "örnek almak", "keşfetmeye başla"] + }, + { + "name": "intent_system_charger", + "keyphrases": ["şarj", "ev", "eve git", "kilise", "şarj cihazını bul", "şarj aleti"] + }, + { + "name": "intent_system_sleep", + "keyphrases": ["kaç", "uyu", "koyun", "uyumaya git"] + }, + { + "name": "intent_greeting_goodmorning", + "keyphrases": ["sabah", "yas", "ayışığı", "sıkıldım", "öğleden sonra", "öğle sonrası", "kimi takip ettiğim", "günaydın"] + }, + { + "name": "intent_greeting_goodnight", + "keyphrases": ["gece", "güç", "iyi geceler"] + }, + { + "name": "intent_greeting_goodbye", + "keyphrases": ["güle güle", "iyi yolculuklar", "iyi alışverişler", "hoşça kal"] + }, + { + "name": "intent_seasonal_happynewyear", + "keyphrases": ["havai fişek", "yeni yıl", "mutlu yıllar", "mutlu ol", "olmuş", "şimdi seni tanıyorum", "yıl yok", "ani", "asla tanımadım", "hobhouse", "bennie"] + }, + { + "name": "intent_seasonal_happyholidays", + "keyphrases": ["o tutar", "noel", "işte bu", "tatil"] + }, + { + "name": "intent_amazon_signin", + "keyphrases": ["zekada", "elekta'da güzel", "aleks'te", "aleksi anlama", "elekt'te", "alekse", "angelica'da", "alexa'ya kaydol", "alexa'ya giriş yap"] + }, + { + "name": "intent_amazon_signin", + "keyphrases": ["çıkışta", "elea'yı şimdi biliyorum", "alexa'dan çık", "aleden çık"] + }, + { + "name": "intent_imperative_forward", + "keyphrases": ["ileri", "koru", "kelime", "ileri hareket et", "ileriye"] + }, + { + "name": "intent_imperative_turnaround", + "keyphrases": ["etrafında", "yüz seksen", "bir yediği", "etrafını dön"] + }, + { + "name": "intent_imperative_turnleft", + "keyphrases": ["sol dön", "sol taraf", "soldan git", "sol", "ernest"] + }, + { + "name": "intent_imperative_turnright", + "keyphrases": ["sağ dön", "sağa git", "sağ taraf", "ernie", "kredi", "sağdan"] + }, + { + "name": "intent_play_rollcube", + "keyphrases": ["küpü yuvarla", "küpünü yuvarla", "hepsini yuvarla", "insanı yuvarla", "yorke", "başını tut", "küpünü yuvarla"] + }, + { + "name": "intent_play_popawheelie", + "keyphrases": ["tekerlek üstünde dur", "polwhele", "olwen", "i wieland", "tekerlek yap", "kapı taşı", "tibetli", "powell", "welst", "bir tekerlek", "willie", "gerçekten", "o' billy", "tekerlek üstünde durma", "tekerlek standı yap"] + }, + { + "name": "intent_play_fistbump", + "keyphrases": ["bu pompası", "bu pompa", "yumruk", "yumrukla", "yumruksa", "bu kitap", "pisto", "pompayla", "fison", "ilk", "beşinci", "on beşte", "bump var", "bilgelik", "bu bu", "yumruk bombası", "yumruk topu", "bu top", "sistem", "yumruklama"] + }, + { + "name": "intent_play_blackjack", + "keyphrases": ["siyah", "kartlar", "oyun", "blackjack oyna"] + }, + { + "name": "intent_imperative_affirmative", + "keyphrases": ["evet", "doğru", "tabii"] + }, + { + "name": "intent_imperative_negative", + "keyphrases": ["hayır", "yapma"] + }, + { + "name": "intent_photo_take_extend", + "keyphrases": ["fotoğraf", "foto", "selby", "yakala", "resim", "bana bir fotoğraf çek"] + }, + { + "name": "intent_imperative_praise", + "keyphrases": ["iyi", "harika", "ayrıca", "bazıları", "onlardan", "savaş", "t rob", "ro", "şaşırtıcı", "woodcourt", "iyi robot"] + }, + { + "name": "intent_imperative_abuse", + "keyphrases": ["kötü", "o ro", "ad ro", "kök", "nefret", "berbat", "kötü robot"] + }, + { + "name": "intent_imperative_apologize", + "keyphrases": ["üzgünüm", "özür dilerim", "özür dile", "tory", "boşver", "aklımdan çıkmış", "ben özür dilerim"] + }, + { + "name": "intent_imperative_backup", + "keyphrases": ["geri", "geri çekil", "geriye"] + }, + { + "name": "intent_imperative_volumedown", + "keyphrases": ["sesini kıs", "ses kıs", "sesi kıs", "sesi kısın", "daha sessiz", "sesi kıs"] + }, + { + "name": "intent_imperative_volumeup", + "keyphrases": ["sesini aç", "ses aç", "sesi aç", "sesi yükselt", "daha yüksek"] + }, + { + "name": "intent_imperative_lookatme", + "keyphrases": ["bak", "bana bak"] + }, + { + "name": "intent_imperative_volumelevel_extend", + "keyphrases": ["sesini", "ses", "ses seviyesi"] + }, + { + "name": "intent_imperative_shutup", + "keyphrases": ["sus"] + }, + { + "name": "intent_greeting_hello", + "keyphrases": ["merhaba", "varsın", "bizim", "yüksek", "aşağı", "küçük", "takip", "senin için", "uzak sen", "sen hakkında nasıl", "nasılsın", "düşük", "tuvalet"] + }, + { + "name": "intent_imperative_come", + "keyphrases": ["gel", "bana", "buraya gel"] + }, + { + "name": "intent_imperative_love", + "keyphrases": ["sevgi", "güvercin", "seni seviyorum"] + }, + { + "name": "intent_knowledge_promptquestion", + "keyphrases": ["soru", "weston", "bir sorum var"] + }, + { + "name": "intent_clock_checktimer", + "keyphrases": ["zamanlayıcıyı kontrol et", "zamanlayıcıyı kontrol et", "zamanlayıcıyı kontrol et", "zamanı kontrol et", "zamanı kontrol et", "zamanı ondan kontrol et", "zamanlayıcıyı kontrol ettim", "zamanı ondan kontrol ettim", "zamanı ondan kontrol ettim"] + }, + { + "name": "intent_global_stop_extend", + "keyphrases": ["zamanlayıcıyı durdur", "zamanlayıcı durdur", "iptal et", "zamanlayıcıyı iptal et", "saati durdur", "dur be", "durdu t", "durdu be", "durdu", "durdur"] + }, + { + "name": "intent_clock_settimer_extend", + "keyphrases": ["zamanlayıcı", "için zaman", "için zamanı", "ya da zaman", "zamanın", "bir zamanlayıcı ayarla"] + }, + { + "name": "intent_clock_time", + "keyphrases": ["saat kaç", "zaman", "hangi saat", "saat", "saat kaç"] + }, + { + "name": "intent_imperative_quiet", + "keyphrases": ["sessiz", "dur", "sessiz ol"] + }, + { + "name": "intent_imperative_dance", + "keyphrases": ["dans", "dans et", "dans et", "ritme dans et", "ritim", "müziğe dans et"] + }, + { + "name": "intent_play_pickupcube", + "keyphrases": ["kaldır", "küpü kaldır"] + }, + { + "name": "intent_imperative_fetchcube", + "keyphrases": ["küpünü getir", "küpü getir", "küpü getir", "bana getir", "bana küpünü getir"] + }, + { + "name": "intent_imperative_findcube", + "keyphrases": ["küpün", "küp", "küpünü bul"] + }, + { + "name": "intent_play_anytrick", + "keyphrases": ["numara", "havalı bir şey", "havalı bir şey", "bir numara yap"] + }, + { + "name": "intent_message_recordmessage_extend", + "keyphrases": ["kaydet"] + }, + { + "name": "intent_message_playmessage_extend", + "keyphrases": ["mesajı oynat", "yöntemi oynat", "bir mesaj oynat", "bir yöntem oynat"] + }, + { + "name": "intent_blackjack_hit", + "keyphrases": ["vur"] + }, + { + "name": "intent_blackjack_stand", + "keyphrases": ["dur", "stan"] + }, + { + "name": "intent_play_keepaway", + "keyphrases": ["uzak tut", "uzak dur", "uzak tutma oyunu oyna"] + } +] \ No newline at end of file diff --git a/chipper/intent-data/uk-UA.json b/chipper/intent-data/uk-UA.json new file mode 100644 index 0000000..5709ee6 --- /dev/null +++ b/chipper/intent-data/uk-UA.json @@ -0,0 +1,214 @@ +[ + { + "name": "intent_names_username_extend", + "keyphrases": ["імена", "назви імена"] + }, + { + "name": "intent_weather_extend", + "keyphrases": ["погода", "погода на завтра", "яка погода"] + }, + { + "name": "intent_names_ask", + "keyphrases": ["як мене звати", "моє ім'я", "хто я"] + }, + { + "name": "intent_imperative_eyecolor", + "keyphrases": ["око", "очі", "зміни колір очей", "поміняй колір очей"] + }, + { + "name": "intent_character_age", + "keyphrases": ["скільки тобі років"] + }, + { + "name": "intent_explore_start", + "keyphrases": ["почни досліджувати", "дивись навколо"] + }, + { + "name": "intent_system_charger", + "keyphrases": ["зарядка", "дім", "база", "знайди зарядку", "знайди базу", "іди на зарядку", "іди на базу", "додому", "іди додому"] + }, + { + "name": "intent_system_sleep", + "keyphrases": ["спати", "іди спати"] + }, + { + "name": "intent_greeting_goodmorning", + "keyphrases": ["добрий ранок", "ранок", "доброго ранку", "раночку"] + }, + { + "name": "intent_greeting_goodnight", + "keyphrases": ["спокійної ночі", "час спати", "добраніч", "на добраніч"] + }, + { + "name": "intent_greeting_goodbye", + "keyphrases": ["бувай", "прощавай", "я пішов", "я пішла", "папа"] + }, + { + "name": "intent_seasonal_happynewyear", + "keyphrases": ["феєрверк", "новий рік", "салют", "з новим роком"] + }, + { + "name": "intent_seasonal_happyholidays", + "keyphrases": ["свято", "зі святом", "сьогодні свято"] + }, + { + "name": "intent_amazon_signin", + "keyphrases": ["алекса"] + }, + { + "name": "intent_imperative_forward", + "keyphrases": ["вперед", "їдь вперед"] + }, + { + "name": "intent_imperative_turnaround", + "keyphrases": ["повернись", "обернись", "оглянься", "розвернись", "покрутись"] + }, + { + "name": "intent_imperative_turnleft", + "keyphrases": ["ліворуч", "ліво", "вліво"] + }, + { + "name": "intent_imperative_turnright", + "keyphrases": ["праворуч", "право", "вправо"] + }, + { + "name": "intent_play_rollcube", + "keyphrases": ["коти куб", "коти кубик", "поверни куб", "поверни кубик"] + }, + { + "name": "intent_play_popawheelie", + "keyphrases": ["стань на куб", "стань на кубик", "трюк", "покажи трюк"] + }, + { + "name": "intent_play_fistbump", + "keyphrases": ["дай п'ять", "п'ятюня", "дай кулачок", "давай п'ять", "давай п'ятюню"] + }, + { + "name": "intent_play_blackjack", + "keyphrases": ["карти", "грати", "граємо в карти", "давай грати", "давай зіграємо"] + }, + { + "name": "intent_imperative_affirmative", + "keyphrases": ["так", "давай", "звичайно"] + }, + { + "name": "intent_imperative_negative", + "keyphrases": ["ні"] + }, + { + "name": "intent_photo_take_extend", + "keyphrases": ["фото", "селфі", "зроби фото", "сфотографуй"] + }, + { + "name": "intent_imperative_praise", + "keyphrases": ["красень", "молодець", "гарний робот", "розумний робот"] + }, + { + "name": "intent_imperative_abuse", + "keyphrases": ["це погано", "поганий робот"] + }, + { + "name": "intent_imperative_apologize", + "keyphrases": ["вибач", "прошу пробачення", "мені шкода"] + }, + { + "name": "intent_imperative_backup", + "keyphrases": ["назад", "рухайся назад", "їдь назад"] + }, + { + "name": "intent_imperative_volumedown", + "keyphrases": ["зменш звук", "зменш гучність", "тише гучність", "тихіше"] + }, + { + "name": "intent_imperative_volumeup", + "keyphrases": ["додай звук", "додай гучність", "гучніше"] + }, + { + "name": "intent_imperative_lookatme", + "keyphrases": ["дивись на мене", "подивись на мене", "глянь на мене"] + }, + { + "name": "intent_imperative_volumelevel_extend", + "keyphrases": ["гучність", "рівень гучності"] + }, + { + "name": "intent_imperative_shutup", + "keyphrases": ["заткнися", "замовкни"] + }, + { + "name": "intent_greeting_hello", + "keyphrases": ["привіт", "радий тебе бачити"] + }, + { + "name": "intent_imperative_come", + "keyphrases": ["до мене", "іди до мене", "іди сюди", "їдь до мене", "їдь сюди"] + }, + { + "name": "intent_imperative_love", + "keyphrases": ["люблю тебе", "я тебе люблю", "ти такий милий", "ти смішний"] + }, + { + "name": "intent_knowledge_promptquestion", + "keyphrases": ["питання", "у мене є питання", "запитання"] + }, + { + "name": "intent_clock_checktimer", + "keyphrases": ["перевір таймер"] + }, + { + "name": "intent_global_stop_extend", + "keyphrases": ["зупини таймер", "скасуй таймер", "вимкни таймер"] + }, + { + "name": "intent_clock_settimer_extend", + "keyphrases": ["таймер", "постав таймер", "встанови таймер"] + }, + { + "name": "intent_clock_time", + "keyphrases": ["час", "котра година", "скільки часу", "котра зараз година", "яка година", "яка зараз година"] + }, + { + "name": "intent_imperative_quiet", + "keyphrases": ["тихо", "припини", "будь тихіше", "будь тихо"] + }, + { + "name": "intent_imperative_dance", + "keyphrases": ["танець", "танцюй", "давай танцювати"] + }, + { + "name": "intent_play_pickupcube", + "keyphrases": ["візьми кубик", "підбери кубик", "підніми кубик"] + }, + { + "name": "intent_imperative_fetchcube", + "keyphrases": ["дай кубик", "принеси мені", "неси мені", "принеси свій кубик"] + }, + { + "name": "intent_imperative_findcube", + "keyphrases": ["знайди кубик", "знайди свій кубик", "шукай кубик", "де твій кубик"] + }, + { + "name": "intent_play_anytrick", + "keyphrases": ["трюк", "зроби трюк"] + }, + { + "name": "intent_message_recordmessage_extend", + "keyphrases": ["запиши"] + }, + { + "name": "intent_message_playmessage_extend", + "keyphrases": ["відтвори повідомлення", "повтори за мною"] + }, + { + "name": "intent_blackjack_hit", + "keyphrases": ["ще", "ще карту"] + }, + { + "name": "intent_blackjack_stand", + "keyphrases": ["досить"] + }, + { + "name": "intent_play_keepaway", + "keyphrases": ["відстань", "відвали", "йди геть", "іди звідси", "котись звідси"] + } +] \ No newline at end of file diff --git a/chipper/intent-data/vi-VN.json b/chipper/intent-data/vi-VN.json new file mode 100644 index 0000000..622afc3 --- /dev/null +++ b/chipper/intent-data/vi-VN.json @@ -0,0 +1,218 @@ +[ + { + "name": "intent_names_username_extend", + "keyphrases": ["Tên"] + }, + { + "name": "intent_weather_extend", + "keyphrases": ["Thời tiết", "Dự báo thời tiết", "Thời tiết ngày mai", "Thời tiết hôm nay"] + }, + { + "name": "intent_names_ask", + "keyphrases": ["Tên của tôi", "Tôi là", "Tôi là ai"] + }, + { + "name": "intent_imperative_eyecolor", + "keyphrases": ["Màu mắt", "Màu sắc"] + }, + { + "name": "intent_character_age", + "keyphrases": ["Bao nhiêu tuổi", "Bạn bao nhiêu tuổi"] + }, + { + "name": "intent_explore_start", + "keyphrases": ["Đi dạo", "Tự do"] + }, + { + "name": "intent_system_charger", + "keyphrases": ["Về nhà", "Sạc điện","Võ Duy Linh"] + }, + { + "name": "intent_system_sleep", + "keyphrases": ["Ngủ", "Đi ngủ", "Ngủ đi"] + }, + { + "name": "intent_greeting_goodmorning", + "keyphrases": ["Chào buổi sáng", "Chào buổi trưa", "Chào buổi chiều", "Xin chào"] + }, + { + "name": "intent_greeting_goodnight", + "keyphrases": ["Chúc ngủ ngon"] + }, + { + "name": "intent_greeting_goodbye", + "keyphrases": ["Tạm biệt", "Gặp lại sau"] + }, + { + "name": "intent_seasonal_happynewyear", + "keyphrases": ["Chúc mừng năm mới", "Pháo hoa", "Ăn mừng", "Võ Duy Linh"] + }, + { + "name": "intent_seasonal_happyholidays", + "keyphrases": ["Vui vẻ", "Ngày lễ", "Kỳ nghỉ lễ"] + }, + { + "name": "intent_amazon_signin", + "keyphrases": ["đăng nhập trợ lý ảo", "đăng ký trợ lý ảo"] + }, + { + "name": "intent_amazon_signout", + "keyphrases": ["đăng xuất trợ lý ảo", "đăng xuất trợ lý ảo"] + }, + { + "name": "intent_imperative_forward", + "keyphrases": ["Tiến lên", "Đi tới"] + }, + { + "name": "intent_imperative_turnaround", + "keyphrases": ["Quay lại", "Quay đầu", "Quay người"] + }, + { + "name": "intent_imperative_turnleft", + "keyphrases": ["Rẽ trái", "Quay trái"] + }, + { + "name": "intent_imperative_turnright", + "keyphrases": ["Rẽ phải", "Quay phải"] + }, + { + "name": "intent_play_rollcube", + "keyphrases": ["Chơi khối vuông"] + }, + { + "name": "intent_play_popawheelie", + "keyphrases": ["Giơ tay"] + }, + { + "name": "intent_play_fistbump", + "keyphrases": ["Cụng tay", "Đập tay"] + }, + { + "name": "intent_play_blackjack", + "keyphrases": ["Chơi bài", "Đánh bài"] + }, + { + "name": "intent_imperative_affirmative", + "keyphrases": ["Đúng", "Tất nhiên", "Chính xác", "Phải"] + }, + { + "name": "intent_imperative_negative", + "keyphrases": ["Không", "Sai"] + }, + { + "name": "intent_photo_take_extend", + "keyphrases": ["Chụp ảnh"] + }, + { + "name": "intent_imperative_praise", + "keyphrases": ["Tuyệt vời", "Thông minh"] + }, + { + "name": "intent_imperative_abuse", + "keyphrases": ["Ngốc", "Ngu ngốc"] + }, + { + "name": "intent_imperative_apologize", + "keyphrases": ["Xin lỗi", "Lỗi của tôi"] + }, + { + "name": "intent_imperative_backup", + "keyphrases": ["Lại đây", "Quay lại", "võ duy linh"] + }, + { + "name": "intent_imperative_volumedown", + "keyphrases": ["Giảm âm lượng", "Nhỏ tiếng"] + }, + { + "name": "intent_imperative_volumeup", + "keyphrases": ["Tăng âm lượng", "To tiếng"] + }, + { + "name": "intent_imperative_lookatme", + "keyphrases": ["Nhìn tôi", "Nhìn vào tôi"] + }, + { + "name": "intent_imperative_volumelevel_extend", + "keyphrases": ["Âm lượng"] + }, + { + "name": "intent_imperative_shutup", + "keyphrases": ["Im miệng"] + }, + { + "name": "intent_greeting_hello", + "keyphrases": ["Xin chào", "Chào bạn"] + }, + { + "name": "intent_imperative_come", + "keyphrases": ["Lại đây"] + }, + { + "name": "intent_imperative_love", + "keyphrases": ["Yêu thích", "Yêu"] + }, + { + "name": "intent_knowledge_promptquestion", + "keyphrases": ["Câu hỏi", "Trả lời"] + }, + { + "name": "intent_clock_checktimer", + "keyphrases": ["Kiểm tra báo thức", "Xem báo thức"] + }, + { + "name": "intent_global_stop_extend", + "keyphrases": ["Hủy báo thức", "Tắt báo thức"] + }, + { + "name": "intent_clock_settimer_extend", + "keyphrases": ["Đặt báo thức", "Cài báo thức"] + }, + { + "name": "intent_clock_time", + "keyphrases": ["Thời gian", "Mấy giờ rồi"] + }, + { + "name": "intent_imperative_quiet", + "keyphrases": ["Yên lặng", "Nghỉ ngơi"] + }, + { + "name": "intent_imperative_dance", + "keyphrases": ["Nhảy múa", "Nhảy đi"] + }, + { + "name": "intent_play_pickupcube", + "keyphrases": ["Nhặt khối vuông", "Đưa khối vuông cho tôi"] + }, + { + "name": "intent_imperative_fetchcube", + "keyphrases": ["Lấy khối vuông"] + }, + { + "name": "intent_imperative_findcube", + "keyphrases": ["Tìm khối vuông"] + }, + { + "name": "intent_play_anytrick", + "keyphrases": ["Kỹ năng"] + }, + { + "name": "intent_message_recordmessage_extend", + "keyphrases": ["Ghi lại"] + }, + { + "name": "intent_message_playmessage_extend", + "keyphrases": ["Phát tin nhắn"] + }, + { + "name": "intent_blackjack_hit", + "keyphrases": ["Đánh"] + }, + { + "name": "intent_blackjack_stand", + "keyphrases": ["Đứng"] + }, + { + "name": "intent_play_keepaway", + "keyphrases": ["Đi chỗ khác", "Xa ra"] + } +] diff --git a/chipper/intent-data/zh-CN.json b/chipper/intent-data/zh-CN.json new file mode 100644 index 0000000..6291d31 --- /dev/null +++ b/chipper/intent-data/zh-CN.json @@ -0,0 +1,218 @@ +[ + { + "name" : "intent_names_username_extend", + "keyphrases": ["名字" ] + }, + { + "name": "intent_weather_extend", + "keyphrases" : ["天气", "天气预报", "明天天气", "今天天气" ] + }, + { + "name": "intent_names_ask", + "keyphrases" : ["我的名字", "我是" ] + }, + { + "name": "intent_imperative_eyecolor", + "keyphrases" : ["眼睛 颜色", "颜色" ] + }, + { + "name": "intent_character_age", + "keyphrases" : ["多大", "你多大" ] + }, + { + "name": "intent_explore_start", + "keyphrases" : ["逛", "随便" ] + }, + { + "name": "intent_system_charger", + "keyphrases" : ["回家", "充电" ] + }, + { + "name": "intent_system_sleep", + "keyphrases" : ["睡觉", "去 睡觉", "睡吧" ] + }, + { + "name": "intent_greeting_goodmorning", + "keyphrases" : ["早上 好", "早安", "中午 好", "下午 好", "你好" ] + }, + { + "name": "intent_greeting_goodnight", + "keyphrases" : ["晚安" ] + }, + { + "name": "intent_greeting_goodbye", + "keyphrases" : ["再见", "回见" ] + }, + { + "name": "intent_seasonal_happynewyear", + "keyphrases" : ["新年好", "烟花", "庆祝" ] + }, + { + "name": "intent_seasonal_happyholidays", + "keyphrases" : ["快乐", "假日", "节日" ] + }, + { + "name": "intent_amazon_signin", + "keyphrases" : ["to alex", "up alexa" ] + }, + { + "name": "intent_amazon_signin", + "keyphrases" : ["out alexa" ] + }, + { + "name": "intent_imperative_forward", + "keyphrases" : ["向前", "往前" ] + }, + { + "name": "intent_imperative_turnaround", + "keyphrases" : ["转身", "转过", "回身" ] + }, + { + "name": "intent_imperative_turnleft", + "keyphrases" : ["往左", "左转" ] + }, + { + "name": "intent_imperative_turnright", + "keyphrases" : ["往右", "右转" ] + }, + { + "name": "intent_play_rollcube", + "keyphrases" : ["玩魔方" ] + }, + { + "name": "intent_play_popawheelie", + "keyphrases" : ["举手" ] + }, + { + "name": "intent_play_fistbump", + "keyphrases" : ["碰拳", "击掌" ] + }, + { + "name": "intent_play_blackjack", + "keyphrases" : ["玩牌" ] + }, + { + "name": "intent_imperative_affirmative", + "keyphrases" : ["是", "当然", "正确", "对" ] + }, + { + "name": "intent_imperative_negative", + "keyphrases" : ["不", "错" ] + }, + { + "name": "intent_photo_take_extend", + "keyphrases" : ["拍照" ] + }, + { + "name": "intent_imperative_praise", + "keyphrases" : ["真棒", "聪明" ] + }, + { + "name": "intent_imperative_abuse", + "keyphrases" : ["笨", "傻" ] + }, + { + "name": "intent_imperative_apologize", + "keyphrases" : ["对不起", "抱歉" ] + }, + { + "name": "intent_imperative_backup", + "keyphrases" : ["过来", "回来" ] + }, + { + "name": "intent_imperative_volumedown", + "keyphrases" : ["声音 小", "小声" ] + }, + { + "name": "intent_imperative_volumeup", + "keyphrases" : ["声音 大", "大声" ] + }, + { + "name": "intent_imperative_lookatme", + "keyphrases" : ["看我", "看着我" ] + }, + { + "name": "intent_imperative_volumelevel_extend", + "keyphrases" : ["音量" ] + }, + { + "name": "intent_imperative_shutup", + "keyphrases" : ["闭嘴" ] + }, + { + "name": "intent_greeting_hello", + "keyphrases" : ["你好", "您好" ] + }, + { + "name": "intent_imperative_come", + "keyphrases" : ["过来" ] + }, + { + "name": "intent_imperative_love", + "keyphrases" : ["喜欢", "爱" ] + }, + { + "name": "intent_knowledge_promptquestion", + "keyphrases" : ["问题", "回答" ] + }, + { + "name": "intent_clock_checktimer", + "keyphrases" : ["检查 闹钟", "查看 闹钟" ] + }, + { + "name": "intent_global_stop_extend", + "keyphrases" : ["取消 闹钟", "关闭 闹钟" ] + }, + { + "name": "intent_clock_settimer_extend", + "keyphrases" : ["设置 闹钟", "设 闹钟" ] + }, + { + "name": "intent_clock_time", + "keyphrases" : ["时间", "几点" ] + }, + { + "name": "intent_imperative_quiet", + "keyphrases" : ["安静", "休息" ] + }, + { + "name": "intent_imperative_dance", + "keyphrases" : ["跳舞", "跳个舞" ] + }, + { + "name": "intent_play_pickupcube", + "keyphrases" : ["拿 魔方", "魔方 给我" ] + }, + { + "name": "intent_imperative_fetchcube", + "keyphrases" : ["取 魔方" ] + }, + { + "name": "intent_imperative_findcube", + "keyphrases" : ["找 魔方" ] + }, + { + "name": "intent_play_anytrick", + "keyphrases" : ["技能" ] + }, + { + "name": "intent_message_recordmessage_extend", + "keyphrases" : ["记录" ] + }, + { + "name": "intent_message_playmessage_extend", + "keyphrases" : ["播放 消息" ] + }, + { + "name": "intent_blackjack_hit", + "keyphrases" : ["打"] + }, + { + "name": "intent_blackjack_stand", + "keyphrases" : ["站" ] + }, + { + "name": "intent_play_keepaway", + "keyphrases": ["滚开", "远" ] + } +] diff --git a/chipper/jdocs/placeholder b/chipper/jdocs/placeholder new file mode 100644 index 0000000..e69de29 diff --git a/chipper/pkg/initwirepod/startserver.go b/chipper/pkg/initwirepod/startserver.go new file mode 100644 index 0000000..a9226f0 --- /dev/null +++ b/chipper/pkg/initwirepod/startserver.go @@ -0,0 +1,231 @@ +package initwirepod + +import ( + "crypto/tls" + "fmt" + "net" + "net/http" + "os" + "runtime" + + chipperpb "github.com/digital-dream-labs/api/go/chipperpb" + "github.com/digital-dream-labs/api/go/jdocspb" + "github.com/digital-dream-labs/api/go/tokenpb" + "github.com/digital-dream-labs/hugh/log" + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/mdnshandler" + chipperserver "github.com/kercre123/wire-pod/chipper/pkg/servers/chipper" + jdocsserver "github.com/kercre123/wire-pod/chipper/pkg/servers/jdocs" + tokenserver "github.com/kercre123/wire-pod/chipper/pkg/servers/token" + "github.com/kercre123/wire-pod/chipper/pkg/vars" + wpweb "github.com/kercre123/wire-pod/chipper/pkg/wirepod/config-ws" + wp "github.com/kercre123/wire-pod/chipper/pkg/wirepod/preqs" + sdkWeb "github.com/kercre123/wire-pod/chipper/pkg/wirepod/sdkapp" + "github.com/soheilhy/cmux" + + // grpclog "github.com/digital-dream-labs/hugh/grpc/interceptors/logger" + + grpcserver "github.com/digital-dream-labs/hugh/grpc/server" +) + +var PostingmDNS bool + +var serverOne cmux.CMux +var serverTwo cmux.CMux +var listenerOne net.Listener +var listenerTwo net.Listener +var voiceProcessor *wp.Server + +// grpcServer *grpc.Servervar +var chipperServing bool = false + +func serveOk(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "ok") +} + +func httpServe(l net.Listener) error { + mux := http.NewServeMux() + mux.HandleFunc("/ok:80", serveOk) + mux.HandleFunc("/ok", serveOk) + s := &http.Server{ + Handler: mux, + } + return s.Serve(l) +} + +func grpcServe(l net.Listener, p *wp.Server) error { + srv, err := grpcserver.New( + grpcserver.WithViper(), + grpcserver.WithReflectionService(), + grpcserver.WithInsecureSkipVerify(), + ) + if err != nil { + log.Fatal(err) + } + + s, _ := chipperserver.New( + chipperserver.WithIntentProcessor(p), + chipperserver.WithKnowledgeGraphProcessor(p), + chipperserver.WithIntentGraphProcessor(p), + ) + + tokenServer := tokenserver.NewTokenServer() + jdocsServer := jdocsserver.NewJdocsServer() + //jdocsserver.IniToJson() + + chipperpb.RegisterChipperGrpcServer(srv.Transport(), s) + jdocspb.RegisterJdocsServer(srv.Transport(), jdocsServer) + tokenpb.RegisterTokenServer(srv.Transport(), tokenServer) + + return srv.Transport().Serve(l) +} + +func BeginWirepodSpecific(sttInitFunc func() error, sttHandlerFunc interface{}, voiceProcessorName string) error { + logger.Init() + + // begin wirepod stuff + vars.Init() + var err error + voiceProcessor, err = wp.New(sttInitFunc, sttHandlerFunc, voiceProcessorName) + wpweb.SttInitFunc = sttInitFunc + go sdkWeb.BeginServer() + http.HandleFunc("/api-chipper/", ChipperHTTPApi) + if err != nil { + return err + } + return nil +} + +func StartFromProgramInit(sttInitFunc func() error, sttHandlerFunc interface{}, voiceProcessorName string) { + if runtime.GOOS == "android" || runtime.GOOS == "ios" { + os.Setenv("DEBUG_LOGGING", "true") + os.Setenv("STT_SERVICE", "vosk") + } + err := BeginWirepodSpecific(sttInitFunc, sttHandlerFunc, voiceProcessorName) + if err != nil { + logger.Println("\033[33m\033[1mWire-pod is not setup. Use the webserver at port 8080 to set up wire-pod.\033[0m") + } else if !vars.APIConfig.PastInitialSetup { + logger.Println("\033[33m\033[1mWire-pod is not setup. Use the webserver at port 8080 to set up wire-pod.\033[0m") + } else if (vars.APIConfig.STT.Service == "vosk" || vars.APIConfig.STT.Service == "whisper.cpp") && vars.APIConfig.STT.Language == "" { + logger.Println("\033[33m\033[1mLanguage value is blank, but STT service is " + vars.APIConfig.STT.Service + ". Reinitiating setup process.\033[0m") + logger.Println("\033[33m\033[1mWire-pod is not setup. Use the webserver at port 8080 to set up wire-pod.\033[0m") + vars.APIConfig.PastInitialSetup = false + } else { + go StartChipper() + } + // main thread is configuration ws + wpweb.StartWebServer() +} + +func RestartServer() { + if chipperServing { + serverOne.Close() + serverTwo.Close() + listenerOne.Close() + listenerTwo.Close() + } + go StartChipper() +} + +func StopServer() { + if chipperServing { + serverOne.Close() + serverTwo.Close() + listenerOne.Close() + listenerTwo.Close() + } +} + +func StartChipper() { + // load certs + if vars.APIConfig.Server.EPConfig && runtime.GOOS != "android" { + go mdnshandler.PostmDNS() + } + var certPub []byte + var certPriv []byte + if runtime.GOOS == "android" || runtime.GOOS == "ios" { + if vars.APIConfig.Server.EPConfig { + certPub, _ = os.ReadFile(vars.AndroidPath + "/static/epod/ep.crt") + certPriv, _ = os.ReadFile(vars.AndroidPath + "/static/epod/ep.key") + } else { + var err error + certPub, _ = os.ReadFile(vars.AndroidPath + "/wire-pod/certs/cert.crt") + certPriv, err = os.ReadFile(vars.AndroidPath + "/wire-pod/certs/cert.key") + if err != nil { + logger.Println("wire-pod is not setup.") + return + } + } + } else { + if vars.APIConfig.Server.EPConfig { + certPub, _ = os.ReadFile("./epod/ep.crt") + certPriv, _ = os.ReadFile("./epod/ep.key") + } else { + var err error + certPub, _ = os.ReadFile("../certs/cert.crt") + certPriv, err = os.ReadFile("../certs/cert.key") + if err != nil { + logger.Println("wire-pod is not setup.") + return + } + } + } + + logger.Println("Initiating TLS listener, cmux, gRPC handler, and REST handler") + cert, err := tls.X509KeyPair(certPub, certPriv) + if err != nil { + logger.Println(err) + os.Exit(1) + } + if runtime.GOOS == "android" && vars.APIConfig.Server.Port == "443" { + logger.Println("not starting chipper at port 443 because android") + } else { + logger.Println("Starting chipper server at port " + vars.APIConfig.Server.Port) + listenerOne, err = tls.Listen("tcp", ":"+vars.APIConfig.Server.Port, &tls.Config{ + Certificates: []tls.Certificate{cert}, + CipherSuites: nil, + }) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + } + serverOne = cmux.New(listenerOne) + grpcListenerOne := serverOne.Match(cmux.HTTP2()) + httpListenerOne := serverOne.Match(cmux.HTTP1Fast()) + go grpcServe(grpcListenerOne, voiceProcessor) + go httpServe(httpListenerOne) + + if vars.APIConfig.Server.EPConfig && os.Getenv("NO8084") != "true" { + logger.Println("Starting chipper server at port 8084 for 2.0.1 compatibility") + listenerTwo, err = tls.Listen("tcp", ":8084", &tls.Config{ + Certificates: []tls.Certificate{cert}, + CipherSuites: nil, + }) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + serverTwo = cmux.New(listenerTwo) + grpcListenerTwo := serverTwo.Match(cmux.HTTP2()) + httpListenerTwo := serverTwo.Match(cmux.HTTP1Fast()) + go grpcServe(grpcListenerTwo, voiceProcessor) + go httpServe(httpListenerTwo) + } + + fmt.Println("\033[33m\033[1mwire-pod started successfully!\033[0m") + + chipperServing = true + if vars.APIConfig.Server.EPConfig && os.Getenv("NO8084") != "true" { + if runtime.GOOS != "android" { + go serverOne.Serve() + } + serverTwo.Serve() + logger.Println("Stopping chipper server") + chipperServing = false + } else { + serverOne.Serve() + logger.Println("Stopping chipper server") + chipperServing = false + } +} diff --git a/chipper/pkg/initwirepod/web.go b/chipper/pkg/initwirepod/web.go new file mode 100644 index 0000000..37478a5 --- /dev/null +++ b/chipper/pkg/initwirepod/web.go @@ -0,0 +1,56 @@ +package initwirepod + +import ( + "fmt" + "net/http" + "strconv" + + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vars" + botsetup "github.com/kercre123/wire-pod/chipper/pkg/wirepod/setup" +) + +// cant be part of config-ws, otherwise import cycle + +func ChipperHTTPApi(w http.ResponseWriter, r *http.Request) { + switch { + case r.URL.Path == "/api-chipper/restart": + RestartServer() + fmt.Fprint(w, "done") + return + case r.URL.Path == "/api-chipper/use_ip": + port := r.FormValue("port") + if port == "" { + fmt.Fprint(w, "error: must have port") + return + } + _, err := strconv.Atoi(port) + if err != nil { + fmt.Fprint(w, "error: port is invalid") + return + } + vars.APIConfig.Server.EPConfig = false + vars.APIConfig.Server.Port = port + err = botsetup.CreateCertCombo() + botsetup.CreateServerConfig() + if err != nil { + logger.Println(err) + fmt.Fprint(w, "error: "+err.Error()) + return + } + vars.APIConfig.PastInitialSetup = true + vars.WriteConfigToDisk() + RestartServer() + fmt.Fprint(w, "done") + return + case r.URL.Path == "/api-chipper/use_ep": + vars.APIConfig.Server.EPConfig = true + vars.APIConfig.Server.Port = "443" + vars.APIConfig.PastInitialSetup = true + botsetup.CreateServerConfig() + vars.WriteConfigToDisk() + RestartServer() + fmt.Fprint(w, "done") + return + } +} diff --git a/chipper/pkg/logger/logger.go b/chipper/pkg/logger/logger.go new file mode 100644 index 0000000..2a58925 --- /dev/null +++ b/chipper/pkg/logger/logger.go @@ -0,0 +1,61 @@ +package logger + +import ( + "fmt" + "os" + "time" +) + +var debugLogging bool = true +var LogList string +var LogArray []string + +var LogTrayList string +var LogTrayArray []string +var LogTrayChan chan string + +func GetLogTrayChan() chan string { + return LogTrayChan +} + +func Init() { + LogTrayChan = make(chan string) + if os.Getenv("DEBUG_LOGGING") == "true" { + debugLogging = true + } else { + debugLogging = false + } +} + +func Println(a ...any) { + LogTray(a...) + if debugLogging { + fmt.Println(a...) + } +} + +func LogUI(a ...any) { + LogArray = append(LogArray, time.Now().Format("2006.01.02 15:04:05")+": "+fmt.Sprint(a...)+"\n") + if len(LogArray) >= 50 { + LogArray = LogArray[1:] + } + LogList = "" + for _, b := range LogArray { + LogList = LogList + b + } +} + +func LogTray(a ...any) { + LogTrayArray = append(LogTrayArray, time.Now().Format("2006.01.02 15:04:05")+": "+fmt.Sprint(a...)+"\n") + if len(LogTrayArray) >= 200 { + LogTrayArray = LogTrayArray[1:] + } + LogTrayList = "" + for _, b := range LogTrayArray { + LogTrayList = LogTrayList + b + } + select { + case LogTrayChan <- time.Now().Format("2006.01.02 15:04:05") + ": " + fmt.Sprint(a...) + "\n": + default: + } +} diff --git a/chipper/pkg/logger/msg-and.go b/chipper/pkg/logger/msg-and.go new file mode 100644 index 0000000..27aaa74 --- /dev/null +++ b/chipper/pkg/logger/msg-and.go @@ -0,0 +1,16 @@ +//go:build android +// +build android + +package logger + +import ( + "fmt" +) + +func WarnMsg(msg string) { + fmt.Println(msg) +} + +func ErrMsg(msg string) { + fmt.Println(msg) +} diff --git a/chipper/pkg/logger/msg-winmac.go b/chipper/pkg/logger/msg-winmac.go new file mode 100644 index 0000000..4dde781 --- /dev/null +++ b/chipper/pkg/logger/msg-winmac.go @@ -0,0 +1,24 @@ +//go:build !android +// +build !android + +package logger + +import ( + "github.com/ncruces/zenity" +) + +func WarnMsg(msg string) { + zenity.Warning( + msg, + zenity.WarningIcon, + zenity.Title("WirePod"), + ) +} + +func ErrMsg(msg string) { + zenity.Error( + msg, + zenity.ErrorIcon, + zenity.Title("WirePod"), + ) +} diff --git a/chipper/pkg/mdnshandler/mdns.go b/chipper/pkg/mdnshandler/mdns.go new file mode 100644 index 0000000..6f703e0 --- /dev/null +++ b/chipper/pkg/mdnshandler/mdns.go @@ -0,0 +1,90 @@ +package mdnshandler + +import ( + "context" + "fmt" + "os" + "strings" + "time" + + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vars" + "github.com/kercre123/zeroconf" +) + +// legacy ZeroConf code + +var PostingmDNS bool +var MDNSNow chan bool +var MDNSTimeBeforeNextRegister float32 + +func PostmDNSWhenNewVector() { + time.Sleep(time.Second * 5) + for { + resolver, _ := zeroconf.NewResolver(nil) + entries := make(chan *zeroconf.ServiceEntry) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*80) + err := resolver.Browse(ctx, "_ankivector._tcp", "local.", entries) + if err != nil { + fmt.Println(err) + cancel() + return + } + for entry := range entries { + if strings.Contains(entry.Service, "ankivector") { + logger.Println("Vector discovered on network, broadcasting mDNS") + cancel() + time.Sleep(time.Second) + PostmDNSNow() + return + } + } + cancel() + } + +} + +func PostmDNSNow() { + logger.Println("Broadcasting mDNS now (outside of timer loop)") + select { + case MDNSNow <- true: + default: + } +} + +func PostmDNS() { + if os.Getenv("DISABLE_MDNS") == "true" { + fmt.Println("mDNS is disabled") + return + } + if PostingmDNS { + return + } + go PostmDNSWhenNewVector() + MDNSNow = make(chan bool) + go func() { + for range MDNSNow { + MDNSTimeBeforeNextRegister = 30 + } + }() + PostingmDNS = true + logger.Println("Registering escapepod.local on network (loop)") + for { + ipAddr := vars.GetOutboundIP().String() + server, _ := zeroconf.RegisterProxy("escapepod", "_app-proto._tcp", "local.", 8084, "escapepod", []string{ipAddr}, []string{"txtv=0", "lo=1", "la=2"}, nil) + if os.Getenv("PRINT_MDNS") == "true" { + logger.Println("mDNS broadcasted") + } + for { + if MDNSTimeBeforeNextRegister >= 30 { + MDNSTimeBeforeNextRegister = 0 + break + } + MDNSTimeBeforeNextRegister = MDNSTimeBeforeNextRegister + (float32(1) / float32(4)) + time.Sleep(time.Second / 4) + } + server.Shutdown() + server = nil + time.Sleep(time.Second / 3) + } +} diff --git a/chipper/pkg/scripting/bcontrol.go b/chipper/pkg/scripting/bcontrol.go new file mode 100644 index 0000000..56d05e4 --- /dev/null +++ b/chipper/pkg/scripting/bcontrol.go @@ -0,0 +1,92 @@ +package scripting + +import ( + "context" + + "github.com/fforchino/vector-go-sdk/pkg/vectorpb" + "github.com/kercre123/wire-pod/chipper/pkg/logger" + lua "github.com/yuin/gopher-lua" +) + +func SetBControlFunctions(L *lua.LState) { + start := make(chan bool) + stop := make(chan bool) + currentlyAssumed := false + L.SetGlobal("assumeBehaviorControl", L.NewFunction(func(*lua.LState) int { + robot := gRfLS(L) + priority := vectorpb.ControlRequest_OVERRIDE_BEHAVIORS + if priority != 0 && priority != 10 && priority != 20 && priority != 30 { + logger.Println("LUA: Behavior control priority was not valid. Valid choices are 10, 20, and 30. Assuming 10.") + } else { + priority = vectorpb.ControlRequest_Priority(L.ToInt(1)) + } + controlRequest := &vectorpb.BehaviorControlRequest{ + RequestType: &vectorpb.BehaviorControlRequest_ControlRequest{ + ControlRequest: &vectorpb.ControlRequest{ + Priority: priority, + }, + }, + } + go func() { + // * begin - modified from official vector-go-sdk + r, err := robot.Conn.BehaviorControl( + context.Background(), + ) + if err != nil { + logger.Println(err) + return + } + + if err := r.Send(controlRequest); err != nil { + logger.Println(err) + return + } + + for { + ctrlresp, err := r.Recv() + if err != nil { + logger.Println(err) + return + } + if ctrlresp.GetControlGrantedResponse() != nil { + start <- true + break + } + } + + for { + select { + case <-stop: + if err := r.Send( + &vectorpb.BehaviorControlRequest{ + RequestType: &vectorpb.BehaviorControlRequest_ControlRelease{ + ControlRelease: &vectorpb.ControlRelease{}, + }, + }, + ); err != nil { + logger.Println(err) + return + } + return + default: + continue + } + } + // * end - modified from official vector-go-sdk + }() + for range start { + break + } + currentlyAssumed = true + return 0 + })) + L.SetGlobal("releaseBehaviorControl", L.NewFunction(func(*lua.LState) int { + if currentlyAssumed { + stop <- true + currentlyAssumed = false + return 0 + } + return 1 + })) + +} diff --git a/chipper/pkg/scripting/scripting.go b/chipper/pkg/scripting/scripting.go new file mode 100644 index 0000000..5f934fa --- /dev/null +++ b/chipper/pkg/scripting/scripting.go @@ -0,0 +1,158 @@ +package scripting + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "time" + + "github.com/fforchino/vector-go-sdk/pkg/vector" + "github.com/fforchino/vector-go-sdk/pkg/vectorpb" + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vars" + lualibs "github.com/vadv/gopher-lua-libs" + lua "github.com/yuin/gopher-lua" +) + +/* + +assumeBehaviorControl(priority int) + - 10,20,30 (10 highest priority, overriding behaviors. 30 lowest) +releaseBehaviorControl() + + + +sayText(text string, goroutine bool) +playAnimation(animation string, goroutine bool) + +*/ + +type ExternalLuaRequest struct { + ESN string `json:"esn"` + Script string `json:"script"` +} + +type Bot struct { + ESN string + Robot *vector.Vector +} + +func sayText(L *lua.LState) int { + textToSay := L.ToString(1) + executeWithGoroutine(L, func(L *lua.LState) error { + _, err := gRfLS(L).Conn.SayText(L.Context(), &vectorpb.SayTextRequest{Text: textToSay, UseVectorVoice: true, DurationScalar: 1.0}) + return err + }) + return 0 +} + +func playAnimation(L *lua.LState) int { + animToPlay := L.ToString(1) + executeWithGoroutine(L, func(L *lua.LState) error { + _, err := gRfLS(L).Conn.PlayAnimation(L.Context(), &vectorpb.PlayAnimationRequest{Animation: &vectorpb.Animation{Name: animToPlay}, Loops: 1}) + return err + }) + return 0 +} + +// get robot from LState +func gRfLS(L *lua.LState) *vector.Vector { + ud := L.GetGlobal("bot").(*lua.LUserData) + bot := ud.Value.(*Bot) + return bot.Robot +} + +func MakeLuaState(esn string, validating bool) (*lua.LState, error) { + L := lua.NewState() + lualibs.Preload(L) + L.SetContext(context.Background()) + L.SetGlobal("sayText", L.NewFunction(sayText)) + L.SetGlobal("playAnimation", L.NewFunction(playAnimation)) + SetBControlFunctions(L) + ud := L.NewUserData() + if !validating { + rob, err := vars.GetRobot(esn) + if err != nil { + return nil, err + } + ctx, can := context.WithTimeout(context.Background(), time.Second*3) + defer can() + _, err = rob.Conn.BatteryState(ctx, &vectorpb.BatteryStateRequest{}) + if err != nil { + return nil, err + } + ud.Value = &Bot{ESN: esn, Robot: rob} + L.SetGlobal("bot", ud) + } + return L, nil +} + +func executeWithGoroutine(L *lua.LState, fn func(L *lua.LState) error) { + goroutine := L.ToBool(2) + if goroutine { + go func() { + err := fn(L) + if err != nil { + logger.Println("LUA: failure: " + err.Error()) + } + }() + } else { + err := fn(L) + if err != nil { + logger.Println("LUA: failure: " + err.Error()) + } + } +} + +func RunLuaScript(esn string, luaScript string) error { + L, err := MakeLuaState(esn, false) + if err != nil { + return err + } + defer L.Close() + + if err := L.DoString(luaScript); err != nil { + return err + } + L.DoString("releaseBehaviorControl()") + return nil +} + +func ValidateLuaScript(luaScript string) error { + L, _ := MakeLuaState("", true) + defer L.Close() + + err := L.DoString(fmt.Sprintf("return function() %s end", luaScript)) + if err != nil { + return err + } + return nil +} + +func ScriptingAPI(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/api-lua/run_script": + fBody, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, "request body couldn't be read: "+err.Error(), http.StatusInternalServerError) + return + } + var scriptReq ExternalLuaRequest + err = json.Unmarshal(fBody, &scriptReq) + if err != nil { + http.Error(w, "request body couldn't be unmarshalled: "+err.Error(), http.StatusInternalServerError) + return + } + err = RunLuaScript(scriptReq.ESN, scriptReq.Script) + if err != nil { + logger.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + } + } +} + +func RegisterScriptingAPI() { + http.HandleFunc("/api-lua/", ScriptingAPI) +} diff --git a/chipper/pkg/servers/chipper/connectioncheck.go b/chipper/pkg/servers/chipper/connectioncheck.go new file mode 100644 index 0000000..119b5b9 --- /dev/null +++ b/chipper/pkg/servers/chipper/connectioncheck.go @@ -0,0 +1,73 @@ +package server + +import ( + "context" + "strconv" + "time" + + pb "github.com/digital-dream-labs/api/go/chipperpb" + "github.com/kercre123/wire-pod/chipper/pkg/logger" +) + +const ( + connectionCheckTimeout = 15 * time.Second + check = "check" +) + +// StreamingConnectionCheck is used by the end device to make sure it can successfully communicate +func (s *Server) StreamingConnectionCheck(stream pb.ChipperGrpc_StreamingConnectionCheckServer) error { + req, err := stream.Recv() + logger.Println("Incoming connection check from " + req.DeviceId) + if err != nil { + logger.Println("Connection check unexpected error") + logger.Println(err) + return err + } + + ctx, cancel := context.WithTimeout(stream.Context(), connectionCheckTimeout) + defer cancel() + + framesPerRequest := req.TotalAudioMs / req.AudioPerRequest + + var toSend pb.ConnectionCheckResponse + + // count frames, we already pulled the first one + frames := uint32(1) + toSend.FramesReceived = frames +receiveLoop: + for { + select { + case <-ctx.Done(): + logger.Println("Connection check expiration. Frames Received: " + strconv.Itoa(int(frames))) + toSend.Status = "Timeout" + break receiveLoop + default: + req, suberr := stream.Recv() + + if suberr != nil || req == nil { + err = suberr + logger.Println("Connection check unexpected error. Frames Received: " + strconv.Itoa(int(frames))) + logger.Println(err) + + toSend.Status = "Error" + break receiveLoop + } + + frames++ + toSend.FramesReceived = frames + if frames >= framesPerRequest { + logger.Println("Connection check success") + toSend.Status = "Success" + break receiveLoop + } + } + } + senderr := stream.Send(&toSend) + if senderr != nil { + logger.Println("Failed to send connection check response to client") + logger.Println(err) + return senderr + } + return err + +} diff --git a/chipper/pkg/servers/chipper/intent.go b/chipper/pkg/servers/chipper/intent.go new file mode 100644 index 0000000..b5024ab --- /dev/null +++ b/chipper/pkg/servers/chipper/intent.go @@ -0,0 +1,41 @@ +package server + +import ( + "time" + + pb "github.com/digital-dream-labs/api/go/chipperpb" + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vtt" +) + +// StreamingIntent handles voice streams +func (s *Server) StreamingIntent(stream pb.ChipperGrpc_StreamingIntentServer) error { + recvTime := time.Now() + + req, err := stream.Recv() + if err != nil { + logger.Println("Intent error") + logger.Println(err) + + return err + } + + if _, err = s.intent.ProcessIntent( + &vtt.IntentRequest{ + Time: recvTime, + Stream: stream, + Device: req.DeviceId, + Session: req.Session, + LangString: req.LanguageCode.String(), + FirstReq: req, + AudioCodec: req.AudioEncoding, + // Mode: + }, + ); err != nil { + logger.Println("Intent error") + logger.Println(err) + return err + } + + return nil +} diff --git a/chipper/pkg/servers/chipper/intent_graph.go b/chipper/pkg/servers/chipper/intent_graph.go new file mode 100644 index 0000000..29c2cb5 --- /dev/null +++ b/chipper/pkg/servers/chipper/intent_graph.go @@ -0,0 +1,41 @@ +package server + +import ( + "time" + + pb "github.com/digital-dream-labs/api/go/chipperpb" + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vtt" +) + +// StreamingIntentGraph handles intent graph request streams +func (s *Server) StreamingIntentGraph(stream pb.ChipperGrpc_StreamingIntentGraphServer) error { + recvTime := time.Now() + + req, err := stream.Recv() + if err != nil { + logger.Println("Intent graph stream error") + logger.Println(err) + + return err + } + + if _, err = s.intentGraph.ProcessIntentGraph( + &vtt.IntentGraphRequest{ + Time: recvTime, + Stream: stream, + Device: req.DeviceId, + Session: req.Session, + LangString: req.LanguageCode.String(), + FirstReq: req, + AudioCodec: req.AudioEncoding, + // Mode: + }, + ); err != nil { + logger.Println("Intent graph processing error") + logger.Println(err) + return err + } + + return nil +} diff --git a/chipper/pkg/servers/chipper/knowledgegraph.go b/chipper/pkg/servers/chipper/knowledgegraph.go new file mode 100644 index 0000000..8e3e2f6 --- /dev/null +++ b/chipper/pkg/servers/chipper/knowledgegraph.go @@ -0,0 +1,41 @@ +package server + +import ( + "time" + + pb "github.com/digital-dream-labs/api/go/chipperpb" + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vtt" +) + +// StreamingKnowledgeGraph is used for knowledge graph request/responses +func (s *Server) StreamingKnowledgeGraph(stream pb.ChipperGrpc_StreamingKnowledgeGraphServer) error { + recvTime := time.Now() + req, err := stream.Recv() + if err != nil { + logger.Println("Knowledge graph error") + logger.Println(err) + + return err + } + + if _, err = s.kg.ProcessKnowledgeGraph( + &vtt.KnowledgeGraphRequest{ + Time: recvTime, + Stream: stream, + Device: req.DeviceId, + Session: req.Session, + LangString: req.LanguageCode.String(), + FirstReq: req, + AudioCodec: req.AudioEncoding, + // Why is this not passed + // Mode: + }, + ); err != nil { + logger.Println("Knowledge graph error") + logger.Println(err) + return err + } + + return nil +} diff --git a/chipper/pkg/servers/chipper/options.go b/chipper/pkg/servers/chipper/options.go new file mode 100644 index 0000000..ae6c605 --- /dev/null +++ b/chipper/pkg/servers/chipper/options.go @@ -0,0 +1,41 @@ +package server + +import "github.com/digital-dream-labs/hugh/log" + +type options struct { + log log.Logger + intent intentProcessor + kg kgProcessor + intentGraph intentGraphProcessor +} + +// Option is the list of options +type Option func(*options) + +// WithLogger sets the logger +func WithLogger(l log.Logger) Option { + return func(o *options) { + o.log = l + } +} + +// WithIntentProcessor sets the intent processor +func WithIntentProcessor(s intentProcessor) Option { + return func(o *options) { + o.intent = s + } +} + +// WithKnowledgeGraphProcessor sets the knowledge graph processor +func WithKnowledgeGraphProcessor(s kgProcessor) Option { + return func(o *options) { + o.kg = s + } +} + +// WithKnowledgeGraphProcessor sets the knowledge graph processor +func WithIntentGraphProcessor(s intentGraphProcessor) Option { + return func(o *options) { + o.intentGraph = s + } +} diff --git a/chipper/pkg/servers/chipper/server.go b/chipper/pkg/servers/chipper/server.go new file mode 100644 index 0000000..597c1a1 --- /dev/null +++ b/chipper/pkg/servers/chipper/server.go @@ -0,0 +1,47 @@ +package server + +import ( + pb "github.com/digital-dream-labs/api/go/chipperpb" + "github.com/kercre123/wire-pod/chipper/pkg/vtt" +) + +type intentProcessor interface { + ProcessIntent(*vtt.IntentRequest) (*vtt.IntentResponse, error) +} + +type kgProcessor interface { + ProcessKnowledgeGraph(*vtt.KnowledgeGraphRequest) (*vtt.KnowledgeGraphResponse, error) +} + +type intentGraphProcessor interface { + ProcessIntentGraph(*vtt.IntentGraphRequest) (*vtt.IntentGraphResponse, error) +} + +// Server defines the service used. +type Server struct { + intent intentProcessor + kg kgProcessor + intentGraph intentGraphProcessor + + pb.UnimplementedChipperGrpcServer +} + +// New accepts a list of args and returns the service +func New(opts ...Option) (*Server, error) { + cfg := options{ + //log: log.Base(), + } + + for _, opt := range opts { + opt(&cfg) + } + + s := Server{ + intent: cfg.intent, + kg: cfg.kg, + intentGraph: cfg.intentGraph, + } + + return &s, nil + +} diff --git a/chipper/pkg/servers/chipper/textintent.go b/chipper/pkg/servers/chipper/textintent.go new file mode 100644 index 0000000..ad26add --- /dev/null +++ b/chipper/pkg/servers/chipper/textintent.go @@ -0,0 +1,14 @@ +package server + +import ( + "context" + + pb "github.com/digital-dream-labs/api/go/chipperpb" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// TextIntent handles text-based request/responses from the device +func (s *Server) TextIntent(ctx context.Context, req *pb.TextRequest) (*pb.IntentResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "") +} diff --git a/chipper/pkg/servers/jdocs/botInfoStorer.go b/chipper/pkg/servers/jdocs/botInfoStorer.go new file mode 100644 index 0000000..c77555c --- /dev/null +++ b/chipper/pkg/servers/jdocs/botInfoStorer.go @@ -0,0 +1,153 @@ +package jdocsserver + +import ( + "context" + "crypto/x509" + "encoding/json" + "encoding/pem" + "io" + "net/http" + "os" + "strings" + + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vars" + "google.golang.org/grpc/peer" + "gopkg.in/ini.v1" +) + +func IsBotInInfo(esn string) bool { + for _, robot := range vars.BotInfo.Robots { + if esn == strings.TrimSpace(strings.ToLower(robot.Esn)) { + return true + } + } + return false +} + +// This function write a bot name, esn, guid, and target to sdk_config.ini +// Should only be used for primary auth +// IP should just be "xxx.xxx.xxx.xxx", no port +func WriteToIniPrimary(botName, esn, guid, ip string) { + userIniData, err := ini.Load(vars.SDKIniPath + "sdk_config.ini") + if err != nil { + logger.Println("Creating " + vars.SDKIniPath + " directory") + os.Mkdir(vars.SDKIniPath, 0755) + userIniData = ini.Empty() + } + matched := false + for _, section := range userIniData.Sections() { + if strings.EqualFold(section.Name(), esn) { + matched = true + logger.Println("WriteToIniPrimary: bot already in INI matched, setting info") + section.Key("cert").SetValue(vars.SDKIniPath + botName + "-" + esn + ".cert") + section.Key("name").SetValue(botName) + section.Key("ip").SetValue(ip) + section.Key("guid").SetValue(guid) + } + } + if !matched { + logger.Println("WriteToIniPrimary: ESN did not match any section in sdk config file, creating") + newSection, err := userIniData.NewSection(esn) + if err != nil { + logger.Println(err) + } + newSection.NewKey("cert", vars.SDKIniPath+botName+"-"+esn+".cert") + newSection.NewKey("ip", ip) + newSection.NewKey("name", botName) + newSection.NewKey("guid", guid) + } + userIniData.SaveTo(vars.SDKIniPath + "sdk_config.ini") + logger.Println("WriteToIniPrimary: successfully wrote INI") +} + +// Less information is given with a secondary auth request, we have to get the cert from the Anki servers +// If a cert is already there, it does not get the Anki server cert +func WriteToIniSecondary(esn, guid, ip string) { + certPath := "" + botName := "" + certExists := false + userIniData, err := ini.Load(vars.SDKIniPath + "sdk_config.ini") + if err != nil { + logger.Println("Creating " + vars.SDKIniPath + " directory") + os.Mkdir(vars.SDKIniPath, 0755) + userIniData = ini.Empty() + } + // see if cert already exists, get name from that if it does + // if not, get from ANKI servers + for _, section := range userIniData.Sections() { + if strings.EqualFold(section.Name(), esn) { + logger.Println("WriteToIniSecondary: Name found from ESN in INI, setting info") + botNameKey, _ := section.GetKey("name") + botName = botNameKey.String() + certPath = vars.SDKIniPath + botName + "-" + esn + ".cert" + certExists = true + // set information + section.Key("guid").SetValue(guid) + section.Key("ip").SetValue(ip) + } + } + if !certExists { + logger.Println("WriteToIniSecondary: getting session cert from DDL server") + resp, err := http.Get("https://session-certs.token.global.anki-services.com/vic/" + esn) + if err != nil { + logger.Println(err) + logger.Println("The DDL servers are down at the moment. The cert will not be gotten. The Python SDK will not be configured.") + return + } + certBytesOrig, _ := io.ReadAll(resp.Body) + os.WriteFile(vars.SessionCertPath+"/"+esn, certBytesOrig, 0777) + block, _ := pem.Decode(certBytesOrig) + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + logger.Println(err) + } + botName = cert.Issuer.CommonName + certPath = vars.SDKIniPath + botName + "-" + esn + ".cert" + out, err := os.Create(certPath) + if err == nil { + out.Write(certBytesOrig) + } + } + logger.Println("WriteToIniSecondary: robot name is " + botName) + + // create an entry + if !certExists { + logger.Println("WriteToIniSecondary: creating INI entry") + newSection, err := userIniData.NewSection(esn) + if err != nil { + logger.Println(err) + } + newSection.NewKey("cert", certPath) + newSection.NewKey("ip", ip) + newSection.NewKey("name", botName) + newSection.NewKey("guid", guid) + } + logger.Println("WriteToIniSecondary complete") + userIniData.SaveTo(vars.SDKIniPath + "sdk_config.ini") +} + +func StoreBotInfo(ctx context.Context, thing string) { + var appendNew bool = true + p, _ := peer.FromContext(ctx) + ipAddr := strings.TrimSpace(strings.Split(p.Addr.String(), ":")[0]) + botEsn := strings.TrimSpace(strings.Split(thing, ":")[1]) + vars.BotInfo.GlobalGUID = "tni1TRsTRTaNSapjo0Y+Sw==" + for num, robot := range vars.BotInfo.Robots { + if robot.Esn == botEsn { + appendNew = false + vars.BotInfo.Robots[num].IPAddress = ipAddr + } + } + if appendNew { + logger.Println("Adding " + botEsn + " to bot info store") + vars.BotInfo.Robots = append(vars.BotInfo.Robots, struct { + Esn string `json:"esn"` + IPAddress string `json:"ip_address"` + GUID string `json:"guid"` + Activated bool `json:"activated"` + }{Esn: botEsn, IPAddress: ipAddr, GUID: "", Activated: false}) + } + finalJsonBytes, _ := json.Marshal(vars.BotInfo) + os.WriteFile(vars.BotInfoPath, finalJsonBytes, 0644) +} diff --git a/chipper/pkg/servers/jdocs/server.go b/chipper/pkg/servers/jdocs/server.go new file mode 100644 index 0000000..9f984ef --- /dev/null +++ b/chipper/pkg/servers/jdocs/server.go @@ -0,0 +1,191 @@ +package jdocsserver + +import ( + "context" + "encoding/json" + "os" + "strings" + "path/filepath" + + "github.com/digital-dream-labs/api/go/jdocspb" + "github.com/kercre123/wire-pod/chipper/pkg/logger" + tokenserver "github.com/kercre123/wire-pod/chipper/pkg/servers/token" + "github.com/kercre123/wire-pod/chipper/pkg/vars" + "google.golang.org/grpc/peer" +) + +type JdocServer struct { + jdocspb.UnimplementedJdocsServer +} + +func (s *JdocServer) WriteDoc(ctx context.Context, req *jdocspb.WriteDocReq) (*jdocspb.WriteDocResp, error) { + logger.Println("Jdocs: Incoming WriteDoc request, Item to write: " + req.DocName + ", Robot ID: " + req.Thing) + var ajdoc vars.AJdoc + ajdoc.ClientMetadata = req.Doc.ClientMetadata + ajdoc.DocVersion = req.Doc.DocVersion + ajdoc.FmtVersion = req.Doc.FmtVersion + ajdoc.JsonDoc = req.Doc.JsonDoc + latestVersion := vars.AddJdoc(req.Thing, req.DocName, ajdoc) + vars.WriteJdocs() + + esn := strings.Split(req.Thing, ":")[1] + p, _ := peer.FromContext(ctx) + ipAddr := strings.Split(p.Addr.String(), ":")[0] + + for ind, bot := range vars.BotInfo.Robots { + if bot.Esn == esn && bot.IPAddress != ipAddr { + logger.Println(esn + "'s IP address has changed to " + ipAddr + ", noting") + vars.BotInfo.Robots[ind].IPAddress = ipAddr + writeBytes, _ := json.Marshal(vars.BotInfo) + os.WriteFile(vars.BotInfoPath, writeBytes, 0644) + } + } + + return &jdocspb.WriteDocResp{ + Status: jdocspb.WriteDocResp_ACCEPTED, + LatestDocVersion: latestVersion, + }, nil +} + +func (s *JdocServer) ReadDocs(ctx context.Context, req *jdocspb.ReadDocsReq) (*jdocspb.ReadDocsResp, error) { + globalGUIDHash := `{"client_tokens":[{"hash":"J5TAnJTPRCioMExFo5KzH2fHOAXyM5fuO8YRbQSamIsNzymnJ8KDIerFxuJV4qBN","client_name":"","app_id":"","issued_at":"2022-11-26T18:23:08Z","is_primary":true}]}` + // global guid now only used in edge cases + + logger.Println("Jdocs: Incoming ReadDocs request, Robot ID: " + req.Thing + ", Item(s) to return: ") + logger.Println(req.Items) + esn := strings.Split(req.Thing, ":")[1] + isAlreadyKnown := IsBotInInfo(esn) + p, _ := peer.FromContext(ctx) + ipAddr := strings.Split(p.Addr.String(), ":")[0] + + for ind, bot := range vars.BotInfo.Robots { + if bot.Esn == esn && bot.IPAddress != ipAddr { + logger.Println(esn + "'s IP address has changed to " + ipAddr + ", noting") + vars.BotInfo.Robots[ind].IPAddress = ipAddr + writeBytes, _ := json.Marshal(vars.BotInfo) + os.WriteFile(vars.BotInfoPath, writeBytes, 0644) + } + } + + for _, pair := range tokenserver.SessionWriteStoreNames { + if ipAddr == strings.Split(pair[0], ":")[0] { + vars.DeleteData(req.Thing) + break + } + } + if strings.Contains(req.Items[0].DocName, "vic.AppTokens") { + StoreBotInfo(ctx, req.Thing) + _, tokenExists := vars.GetJdoc(req.Thing, "vic.AppTokens") + if !tokenExists { + logger.Println("App tokens jdoc not found for this bot, trying bots in TokenHashStore") + matched := false + botGUID := "" + for num, pair := range tokenserver.TokenHashStore { + if strings.EqualFold(pair[0], ipAddr) { + err := tokenserver.WriteTokenHash(strings.ToLower(strings.TrimSpace(esn)), pair[2]) + if err != nil { + logger.Println("Error writing token hash to vic.AppTokens") + logger.Println(err) + } + err = tokenserver.SetBotGUID(esn, pair[1], pair[2]) + botGUID = pair[1] + if err != nil { + logger.Println("Error writing token hash to " + vars.BotInfoPath) + logger.Println(err) + } + logger.Println("ReadJdocs: bot " + esn + " matched with IP " + ipAddr + " in token store") + matched = true + tokenserver.RemoveFromPrimaryStore(num) + } + } + sessionMatched := false + for num, pair := range tokenserver.SessionWriteStoreNames { + if strings.EqualFold(ipAddr, strings.Split(pair[0], ":")[0]) { + sessionMatched = true + fullPath := filepath.Join(vars.SDKIniPath, pair[1] + "-" + esn + ".cert") + if _, err := os.Stat(vars.SDKIniPath); err != nil { + logger.Println("Creating " + vars.SDKIniPath + " directory") + os.Mkdir(vars.SDKIniPath, 0755) + } + logger.Println("Outputting session cert to " + fullPath) + // export to ~/.anki_vector + os.WriteFile(fullPath, tokenserver.SessionWriteStoreCerts[num], 0755) + // export to ./session-certs + os.WriteFile(vars.SessionCertPath+"/"+esn, tokenserver.SessionWriteStoreCerts[num], 0755) + WriteToIniPrimary(pair[1], esn, botGUID, ipAddr) + vars.AddToRInfo(esn, pair[1], ipAddr) + tokenserver.RemoveFromSessionStore(num) + logger.Println("Session certificate successfully output") + break + } + } + logger.LogUI("New bot being associated with wire-pod. ESN: " + esn + ", IP: " + ipAddr) + if !matched { + if !isAlreadyKnown { + logger.Println("Bot was not known to wire-pod, creating token and hash (in ReadDocs)") + guid, hash, _ := tokenserver.CreateTokenAndHashedToken() + tokenserver.SecondaryTokenStore = append(tokenserver.SecondaryTokenStore, [4]string{esn, ipAddr, guid, hash}) + // creates apptoken jdoc file + tokenserver.WriteTokenHash(esn, hash) + if !sessionMatched { + WriteToIniSecondary(esn, guid, ipAddr) + } + // bot is not authenticated yet, do not write to botinfo json + tokenJdoc, _ := vars.GetJdoc(req.Thing, "vic.AppToken") + var truejdoc jdocspb.Jdoc + truejdoc.ClientMetadata = tokenJdoc.ClientMetadata + truejdoc.DocVersion = tokenJdoc.DocVersion + truejdoc.FmtVersion = tokenJdoc.FmtVersion + truejdoc.JsonDoc = tokenJdoc.JsonDoc + tokenserver.RemoveFromSecondStore(len(tokenserver.SecondaryTokenStore) - 1) + return &jdocspb.ReadDocsResp{ + Items: []*jdocspb.ReadDocsResp_Item{ + { + Status: jdocspb.ReadDocsResp_CHANGED, + Doc: &truejdoc, + }, + }, + }, nil + } + logger.Println("Bot not found in any store, providing global GUID") + return &jdocspb.ReadDocsResp{ + Items: []*jdocspb.ReadDocsResp_Item{ + { + Status: jdocspb.ReadDocsResp_CHANGED, + Doc: &jdocspb.Jdoc{ + DocVersion: 1, + FmtVersion: 1, + ClientMetadata: "placeholder", + JsonDoc: globalGUIDHash, + }, + }, + }, + }, nil + } + } + } + var returnItems []*jdocspb.ReadDocsResp_Item + for _, item := range req.Items { + gottenDoc, jdocExists := vars.GetJdoc(req.Thing, item.DocName) + if jdocExists { + var truejdoc jdocspb.Jdoc + truejdoc.DocVersion = gottenDoc.DocVersion + truejdoc.FmtVersion = gottenDoc.FmtVersion + truejdoc.ClientMetadata = gottenDoc.ClientMetadata + truejdoc.JsonDoc = gottenDoc.JsonDoc + returnItems = append(returnItems, &jdocspb.ReadDocsResp_Item{Status: jdocspb.ReadDocsResp_CHANGED, Doc: &truejdoc}) + } else { + var noJdoc jdocspb.Jdoc + noJdoc.DocVersion = 0 + noJdoc.FmtVersion = 0 + noJdoc.ClientMetadata = "wirepod-noexist" + noJdoc.JsonDoc = "" + returnItems = append(returnItems, &jdocspb.ReadDocsResp_Item{Status: jdocspb.ReadDocsResp_CHANGED, Doc: &noJdoc}) + } + } + return &jdocspb.ReadDocsResp{Items: returnItems}, nil +} + +func NewJdocsServer() *JdocServer { + return &JdocServer{} +} diff --git a/chipper/pkg/servers/token/hashing.go b/chipper/pkg/servers/token/hashing.go new file mode 100644 index 0000000..5044b1a --- /dev/null +++ b/chipper/pkg/servers/token/hashing.go @@ -0,0 +1,136 @@ +package tokenserver + +import ( + "crypto/rand" + "crypto/sha256" + "crypto/subtle" + "encoding/base64" + "encoding/json" + "fmt" + + "github.com/kercre123/wire-pod/chipper/pkg/logger" +) + +// mostly copied from vector-cloud + +const ( + tokenSize = 16 + saltSize = 16 + hashSize = sha256.Size + + errMismatchedTokenAndHash = "hash mismatch" + errHashTooLong = "hash too long" + errHashTooShort = "hash too short" + errTokenTooLong = "token too long" + errTokenTooShort = "token too short" +) + +type hashed struct { + // hash the hash of the token on its own (without the appended + // salt) + hash []byte + // salt is the salt of the token + salt []byte +} + +type ClientToken struct { + Hash string `json:"hash"` + ClientName string `json:"client_name"` + AppId string `json:"app_id"` + IssuedAt string `json:"issued_at"` +} + +type ClientTokenManager struct { + ClientTokens []ClientToken `json:"client_tokens"` +} + +func CreateTokenAndHashedToken() (GUID string, GUIDHash string, isError error) { + // generate random bytes + tokenBytes := make([]byte, tokenSize) + _, err := rand.Read(tokenBytes) + if err != nil { + return "", "", err + } + token := base64.StdEncoding.EncodeToString(tokenBytes) + + // generate a random salt + saltBytes := make([]byte, saltSize) + _, err = rand.Read(saltBytes) + if err != nil { + return "", "", err + } + + // hash token + hashed := hash(tokenBytes, saltBytes) + hashed = append(hashed, saltBytes...) + + // encode + hashedToken := base64.StdEncoding.EncodeToString(hashed) + + return token, hashedToken, nil +} + +func DecodeAndCompare(tokenHashes string, token string) { + // debug + var ctm ClientTokenManager + json.Unmarshal([]byte(tokenHashes), &ctm) + for _, tokenHash := range ctm.ClientTokens { + err := CompareHashAndToken(tokenHash.Hash, token) + if err == nil { + logger.Println(tokenHash.Hash + " matched " + token) + } else { + logger.Println(err) + } + } + +} + +// copied from vector-cloud +func CompareHashAndToken(hashedToken, token string) error { + // Decode the hash and the token to their raw bytes + hashedBytes, err := base64.StdEncoding.DecodeString(hashedToken) + if err != nil { + return err + } + tokenBytes, err := base64.StdEncoding.DecodeString(token) + if err != nil { + return err + } + + // extract the hash and salt from the pre-hashed value + hashed, err := newFromHash(hashedBytes) + if err != nil { + return err + } + // hash the token using the salt extracted from the hashed input + // and compare + newHash := hash(tokenBytes, hashed.salt) + + if subtle.ConstantTimeCompare(hashed.hash, newHash) == 1 { + return nil + } + + return fmt.Errorf(errMismatchedTokenAndHash) +} + +func newFromHash(hashedToken []byte) (*hashed, error) { + if len(hashedToken) > (hashSize + saltSize) { + return nil, fmt.Errorf(errHashTooLong) + } else if len(hashedToken) < (hashSize + saltSize) { + return nil, fmt.Errorf(errHashTooShort) + } + + hash, salt := hashedToken[:hashSize], hashedToken[hashSize:] + return &hashed{ + hash: hash, + salt: salt, + }, nil +} + +func hash(token, salt []byte) []byte { + salted := make([]byte, 0, (tokenSize + saltSize)) + salted = append(salted, token...) + salted = append(salted, salt...) + hash := sha256.Sum256(salted) + return hash[:] +} diff --git a/chipper/pkg/servers/token/token.go b/chipper/pkg/servers/token/token.go new file mode 100644 index 0000000..36d6d13 --- /dev/null +++ b/chipper/pkg/servers/token/token.go @@ -0,0 +1,301 @@ +package tokenserver + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/json" + "encoding/pem" + "fmt" + "os" + "strings" + "time" + + "github.com/digital-dream-labs/api/go/tokenpb" + "github.com/golang-jwt/jwt" + "github.com/google/uuid" + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vars" + "google.golang.org/grpc/peer" + "gopkg.in/ini.v1" +) + +type TokenServer struct { + tokenpb.UnimplementedTokenServer +} + +var ( + TimeFormat = time.RFC3339Nano + ExpirationTime = time.Hour * 24 + UserId = "wirepod" + GlobalGUID = "tni1TRsTRTaNSapjo0Y+Sw==" +) + +// array of {"target", "guid", "guidhash"} +// for primary user auth +var TokenHashStore [][3]string + +// array of {"esn", "target", "guid", "guidhash"} +// for secondary user auth +var SecondaryTokenStore [][4]string + +// {"target", "name"} +var SessionWriteStoreNames [][2]string +var SessionWriteStoreCerts [][]byte + +type RobotInfoStore struct { + GlobalGUID string `json:"global_guid"` + Robots []struct { + Esn string `json:"esn"` + IPAddress string `json:"ip_address"` + // 192.168.1.150:443 + GUID string `json:"guid"` + Activated bool `json:"activated"` + } `json:"robots"` +} + +func GetEsnFromTarget(target string) (string, error) { + jsonBytes, err := os.ReadFile(vars.BotInfoPath) + if err != nil { + return "", err + } + var robotInfo RobotInfoStore + err = json.Unmarshal(jsonBytes, &robotInfo) + if err != nil { + return "", err + } + for _, robot := range robotInfo.Robots { + if strings.TrimSpace(target) == strings.TrimSpace(robot.IPAddress) { + return robot.Esn, nil + } + } + return "", fmt.Errorf("bot not found") +} + +func SetBotGUID(esn string, guid string, guidHash string) error { + matched := false + for num, robot := range vars.BotInfo.Robots { + if strings.EqualFold(esn, robot.Esn) { + vars.BotInfo.Robots[num].GUID = guid + vars.BotInfo.Robots[num].Activated = true + logger.Println("GUID and hash successfully written for " + robot.Esn) + matched = true + break + } + } + if !matched { + return fmt.Errorf("bot not found") + } + writeBytes, err := json.Marshal(vars.BotInfo) + if err != nil { + logger.Println(err) + return err + } + os.WriteFile(vars.BotInfoPath, writeBytes, 0644) + return nil +} + +func WriteTokenHash(esn string, tokenHash string) error { + // will return blank jdoc if it doesn't exist + jdoc, jdocExists := vars.GetJdoc(esn, "vic.AppTokens") + var tokenJson ClientTokenManager + if !jdocExists { + jdoc.DocVersion = 1 + jdoc.FmtVersion = 1 + jdoc.ClientMetadata = "wirepod-new-token" + } + json.Unmarshal([]byte(jdoc.JsonDoc), &tokenJson) + var clientToken ClientToken + clientToken.IssuedAt = time.Now().Format(TimeFormat) + clientToken.ClientName = "wirepod" + clientToken.Hash = tokenHash + clientToken.AppId = "SDK" + tokenJson.ClientTokens = append(tokenJson.ClientTokens, clientToken) + jdocJsoc, err := json.Marshal(tokenJson) + if err != nil { + logger.Println("Error marshaling token hash json") + logger.Println(err) + } + jdoc.JsonDoc = string(jdocJsoc) + var ajdoc vars.AJdoc + ajdoc.ClientMetadata = jdoc.ClientMetadata + ajdoc.DocVersion = jdoc.DocVersion + ajdoc.FmtVersion = jdoc.FmtVersion + ajdoc.JsonDoc = jdoc.JsonDoc + vars.AddJdoc("vic:"+esn, "vic.AppTokens", ajdoc) + vars.WriteJdocs() + return nil +} + +func RemoveFromSecondStore(index int) { + logger.Println("Removing " + SecondaryTokenStore[index][0] + " from temporary token-hash store") + SecondaryTokenStore = append(SecondaryTokenStore[:index], SecondaryTokenStore[index+1:]...) +} + +func RemoveFromPrimaryStore(index int) { + logger.Println("Removing " + TokenHashStore[index][0] + " from temporary token-hash store") + TokenHashStore = append(TokenHashStore[:index], TokenHashStore[index+1:]...) +} + +func RemoveFromSessionStore(index int) { + //var SessionWriteStoreNames [][2]string + //var SessionWriteStoreCerts [][]byte + logger.Println("Removing " + SessionWriteStoreNames[index][0] + " from cert-write store") + SessionWriteStoreNames = append(SessionWriteStoreNames[:index], SessionWriteStoreNames[index+1:]...) + SessionWriteStoreCerts = append(SessionWriteStoreCerts[:index], SessionWriteStoreCerts[index+1:]...) +} + +func ChangeGUIDInIni(esn string) { + // [008060ec] + // cert = /home/kerigan/.anki_vector/Vector-B6H9-008060ec.cert + // ip = 192.168.1.155 + // name = Vector-B6H9 + // guid = 1YbXk1yrS9C1I78snYy8xA== + + userIniData, err := ini.Load(vars.SDKIniPath + "sdk_config.ini") + if err != nil { + logger.Println(err) + return + } + for _, robot := range vars.BotInfo.Robots { + matched := false + for _, section := range userIniData.Sections() { + if strings.EqualFold(section.Name(), esn) { + matched = true + section.Key("ip").SetValue(robot.IPAddress) + if robot.GUID == "" { + section.Key("guid").SetValue(vars.BotInfo.GlobalGUID) + } else { + section.Key("guid").SetValue(robot.GUID) + } + } + } + if !matched { + logger.Println("Bot is not in sdk_config.ini. Clear your bot's userdata and try authenticating again to create it.") + } + } + userIniData.SaveTo(vars.SDKIniPath + "sdk_config.ini") +} + +func GenerateUUID() string { + uuid := uuid.New() + return uuid.String() +} + +func CreateJWT(ctx context.Context, skipGuid bool, isPrimary bool) *tokenpb.TokenBundle { + // defaults + requestorId := "vic:00601b50" + clientToken := GlobalGUID + bundle := &tokenpb.TokenBundle{} + secondary := false + secondaryGUID := "" + secondaryHash := "" + + // figure out current time and the time in one month + currentTime := time.Now().Format(TimeFormat) + expiresAt := time.Now().AddDate(0, 1, 0).Format(TimeFormat) + logger.Println("Current time: " + currentTime) + logger.Println("Token expires: " + expiresAt) + + // get esn using ip address of request + p, _ := peer.FromContext(ctx) + ipAddr := strings.TrimSpace(strings.Split(p.Addr.String(), ":")[0]) + esn, err := GetEsnFromTarget(ipAddr) + + // secondary handler + if err == nil { + for num, robot := range SecondaryTokenStore { + if robot[0] == esn { + skipGuid = true + secondary = true + secondaryGUID = robot[2] + secondaryHash = robot[3] + RemoveFromSecondStore(num) + break + } + } + } + + // create token and hash + // if esn is not found, put tokenHash into ram + if err == nil && !isPrimary { + logger.Println("Found ESN for target " + ipAddr + ": " + esn) + requestorId = "vic:" + esn + if !skipGuid { + guid, tokenHash, _ := CreateTokenAndHashedToken() + WriteTokenHash(esn, tokenHash) + SetBotGUID(esn, guid, tokenHash) + ChangeGUIDInIni(esn) + clientToken = guid + } + } else { + logger.Println("ESN not found in store or this is an associate primary user request, act as if this is a new robot") + if !skipGuid { + logger.Println("Adding " + ipAddr + " to TokenHashStore") + guid, tokenHash, _ := CreateTokenAndHashedToken() + TokenHashStore = append(TokenHashStore, [3]string{ipAddr, guid, tokenHash}) + clientToken = guid + } + } + if !skipGuid { + bundle.ClientToken = clientToken + } + + if secondary { + SetBotGUID(esn, secondaryGUID, secondaryHash) + bundle.ClientToken = secondaryGUID + logger.Println("Secondary client: " + secondaryGUID) + } + + requestUUID := GenerateUUID() + logger.Println("UUID for this token request: " + requestUUID) + + // create actual JWT token + token := jwt.NewWithClaims(jwt.SigningMethodRS512, jwt.MapClaims{ + "expires": expiresAt, + "iat": currentTime, + "permissions": nil, + // the requestorId will be vic:00601b50 on first auth because we don't have access + // to the factory certs like the official servers do. future token requests should + // have the actual bot esn because they are "associated" with wire-pod + "requestor_id": requestorId, + "token_id": requestUUID, + "token_type": "user+robot", + "user_id": UserId, + }) + rsaKey, _ := rsa.GenerateKey(rand.Reader, 1024) + tokenString, _ := token.SignedString(rsaKey) + bundle.Token = tokenString + return bundle +} + +func (s *TokenServer) AssociatePrimaryUser(ctx context.Context, req *tokenpb.AssociatePrimaryUserRequest) (*tokenpb.AssociatePrimaryUserResponse, error) { + logger.Println("Token: Incoming Associate Primary User request") + pemBytes, _ := pem.Decode(req.SessionCertificate) + cert, _ := x509.ParseCertificate(pemBytes.Bytes) + SessionWriteStoreCerts = append(SessionWriteStoreCerts, req.SessionCertificate) + p, _ := peer.FromContext(ctx) + SessionWriteStoreNames = append(SessionWriteStoreNames, [2]string{p.Addr.String(), cert.Issuer.CommonName}) + return &tokenpb.AssociatePrimaryUserResponse{ + Data: CreateJWT(ctx, false, true), + }, nil +} + +func (s *TokenServer) AssociateSecondaryClient(ctx context.Context, req *tokenpb.AssociateSecondaryClientRequest) (*tokenpb.AssociateSecondaryClientResponse, error) { + logger.Println("Token: Incoming Associate Secondary Client request") + return &tokenpb.AssociateSecondaryClientResponse{ + Data: CreateJWT(ctx, false, false), + }, nil +} + +func (s *TokenServer) RefreshToken(ctx context.Context, req *tokenpb.RefreshTokenRequest) (*tokenpb.RefreshTokenResponse, error) { + logger.Println("Token: Incoming Refresh Token request") + return &tokenpb.RefreshTokenResponse{ + Data: CreateJWT(ctx, false, false), + }, nil +} + +func NewTokenServer() *TokenServer { + return &TokenServer{} +} diff --git a/chipper/pkg/vars/config.go b/chipper/pkg/vars/config.go new file mode 100644 index 0000000..4735086 --- /dev/null +++ b/chipper/pkg/vars/config.go @@ -0,0 +1,134 @@ +package vars + +import ( + "encoding/json" + "os" + + "github.com/kercre123/wire-pod/chipper/pkg/logger" +) + +// a way to create a JSON configuration for wire-pod, rather than the use of env vars + +var ApiConfigPath = "./apiConfig.json" + +var APIConfig apiConfig + +type apiConfig struct { + Weather struct { + Enable bool `json:"enable"` + Provider string `json:"provider"` + Key string `json:"key"` + Unit string `json:"unit"` + } `json:"weather"` + Knowledge struct { + Enable bool `json:"enable"` + Provider string `json:"provider"` + Key string `json:"key"` + ID string `json:"id"` + Model string `json:"model"` + IntentGraph bool `json:"intentgraph"` + RobotName string `json:"robotName"` + OpenAIPrompt string `json:"openai_prompt"` + OpenAIVoice string `json:"openai_voice"` + OpenAIVoiceWithEnglish bool `json:"openai_voice_with_english"` + SaveChat bool `json:"save_chat"` + CommandsEnable bool `json:"commands_enable"` + Endpoint string `json:"endpoint"` + } `json:"knowledge"` + STT struct { + Service string `json:"provider"` + Language string `json:"language"` + } `json:"STT"` + Server struct { + // false for ip, true for escape pod + EPConfig bool `json:"epconfig"` + Port string `json:"port"` + } `json:"server"` + HasReadFromEnv bool `json:"hasreadfromenv"` + PastInitialSetup bool `json:"pastinitialsetup"` +} + +func WriteConfigToDisk() { + logger.Println("Configuration changed, writing to disk") + writeBytes, _ := json.Marshal(APIConfig) + os.WriteFile(ApiConfigPath, writeBytes, 0644) +} + +func CreateConfigFromEnv() { + // if no config exists, create it + if os.Getenv("WEATHERAPI_ENABLED") == "true" { + APIConfig.Weather.Enable = true + APIConfig.Weather.Provider = os.Getenv("WEATHERAPI_PROVIDER") + APIConfig.Weather.Key = os.Getenv("WEATHERAPI_KEY") + APIConfig.Weather.Unit = os.Getenv("WEATHERAPI_UNIT") + } else { + APIConfig.Weather.Enable = false + } + if os.Getenv("KNOWLEDGE_ENABLED") == "true" { + APIConfig.Knowledge.Enable = true + APIConfig.Knowledge.Provider = os.Getenv("KNOWLEDGE_PROVIDER") + if os.Getenv("KNOWLEDGE_PROVIDER") == "houndify" { + APIConfig.Knowledge.ID = os.Getenv("KNOWLEDGE_ID") + } + APIConfig.Knowledge.Key = os.Getenv("KNOWLEDGE_KEY") + } else { + APIConfig.Knowledge.Enable = false + } + WriteSTT() + APIConfig.HasReadFromEnv = true + writeBytes, _ := json.Marshal(APIConfig) + os.WriteFile(ApiConfigPath, writeBytes, 0644) +} + +func WriteSTT() { + // was not part of the original code, so this is its own function + // launched if stt not found in config + APIConfig.STT.Service = os.Getenv("STT_SERVICE") + if os.Getenv("STT_SERVICE") == "vosk" || os.Getenv("STT_SERVICE") == "whisper.cpp" { + APIConfig.STT.Language = os.Getenv("STT_LANGUAGE") + } +} + +func ReadConfig() { + if _, err := os.Stat(ApiConfigPath); err != nil { + CreateConfigFromEnv() + logger.Println("API config JSON created") + } else { + // read config + configBytes, err := os.ReadFile(ApiConfigPath) + if err != nil { + APIConfig.Knowledge.Enable = false + APIConfig.Weather.Enable = false + logger.Println("Failed to read API config file") + logger.Println(err) + return + } + err = json.Unmarshal(configBytes, &APIConfig) + if err != nil { + APIConfig.Knowledge.Enable = false + APIConfig.Weather.Enable = false + logger.Println("Failed to unmarshal API config JSON") + logger.Println(err) + return + } + // stt service is the only thing controlled by shell + if APIConfig.STT.Service != os.Getenv("STT_SERVICE") { + WriteSTT() + } + if !APIConfig.HasReadFromEnv { + if APIConfig.Server.Port != os.Getenv("DDL_RPC_PORT") { + APIConfig.HasReadFromEnv = true + APIConfig.PastInitialSetup = true + } + } + + if APIConfig.Knowledge.Model == "meta-llama/Llama-2-70b-chat-hf" { + logger.Println("Setting Together model to Llama3") + APIConfig.Knowledge.Model = "meta-llama/Llama-3-70b-chat-hf" + } + + writeBytes, _ := json.Marshal(APIConfig) + os.WriteFile(ApiConfigPath, writeBytes, 0644) + logger.Println("API config successfully read") + } +} diff --git a/chipper/pkg/vars/vars.go b/chipper/pkg/vars/vars.go new file mode 100644 index 0000000..73dab19 --- /dev/null +++ b/chipper/pkg/vars/vars.go @@ -0,0 +1,465 @@ +package vars + +import ( + "crypto/x509" + "encoding/json" + "encoding/pem" + "errors" + "fmt" + "net" + "os" + "path/filepath" + "runtime" + "strconv" + "strings" + + "github.com/fforchino/vector-go-sdk/pkg/vector" + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/sashabaranov/go-openai" + "github.com/wlynxg/anet" +) + +var CommitSHA string + +// initialize variables so they don't have to be found during runtime + +var VarsInited bool + +// if compiled into an installation package. wire-pod will use os.UserConfigDir() +var Packaged bool + +var IsPackagedLinux bool + +var AndroidPath string + +var ( + JdocsPath string = "./jdocs/jdocs.json" + JdocsDir string = "./jdocs" + CustomIntentsPath string = "./customIntents.json" + BotConfigsPath string = "./botConfig.json" + BotInfoPath string = "./jdocs/botSdkInfo.json" + BotInfoName string = "botSdkInfo.json" + PodName string = "wire-pod" + VoskModelPath string = "../vosk/models/" + WhisperModelPath string = "../whisper.cpp/models/" + SessionCertPath string = "./session-certs/" + VersionFile string = "./version" +) + +var ( + OutboundIPTester = "8.8.8.8:80" + CertPath = "../certs/cert.crt" + KeyPath = "../certs/cert.key" + ServerConfigPath = "../certs/server_config.json" + Certs = "../certs" +) + +var WebPort string = "8080" + +// /home/name/.anki_vector/ +var SDKIniPath string +var BotJdocs []botjdoc +var BotInfo RobotInfoStore +var CustomIntents []CustomIntent +var CustomIntentsExist bool = false +var DownloadedVoskModels []string +var VoskGrammerEnable bool = false + +// here to prevent import cycle (localization restructure) +var SttInitFunc func() error + +var IntentList []JsonIntent + +//var MatchListList [][]string +// var IntentsList = []string{} + +var ChipperCert []byte +var ChipperKey []byte +var ChipperKeysLoaded bool + +var RecurringInfo []RecurringInfoStore + +type RememberedChat struct { + ESN string `json:"esn"` + Chats []openai.ChatCompletionMessage `json:"chats"` +} + +var RememberedChats []RememberedChat + +type RobotInfoStore struct { + GlobalGUID string `json:"global_guid"` + Robots []struct { + Esn string `json:"esn"` + IPAddress string `json:"ip_address"` + // 192.168.1.150:443 + GUID string `json:"guid"` + Activated bool `json:"activated"` + } `json:"robots"` +} + +type RecurringInfoStore struct { + // Vector-R2D2 + ID string `json:"id"` + // 00e20145 + ESN string `json:"esn"` + // 192.168.1.150 + IP string `json:"ip"` +} + +type JsonIntent struct { + Name string `json:"name"` + Keyphrases []string `json:"keyphrases"` + RequireExactMatch bool `json:"requiresexact"` +} + +type CustomIntent struct { + Name string `json:"name"` + Description string `json:"description"` + Utterances []string `json:"utterances"` + Intent string `json:"intent"` + Params struct { + ParamName string `json:"paramname"` + ParamValue string `json:"paramvalue"` + } `json:"params"` + Exec string `json:"exec"` + ExecArgs []string `json:"execargs"` + IsSystemIntent bool `json:"issystem"` + LuaScript string `json:"luascript"` +} + +type AJdoc struct { + DocVersion uint64 `protobuf:"varint,1,opt,name=doc_version,json=docVersion,proto3" json:"doc_version,omitempty"` // first version = 1; 0 => invalid or doesn't exist + FmtVersion uint64 `protobuf:"varint,2,opt,name=fmt_version,json=fmtVersion,proto3" json:"fmt_version,omitempty"` // first version = 1; 0 => invalid + ClientMetadata string `protobuf:"bytes,3,opt,name=client_metadata,json=clientMetadata,proto3" json:"client_metadata,omitempty"` // arbitrary client-defined string, eg a data fingerprint (typ "", 32 chars max) + JsonDoc string `protobuf:"bytes,4,opt,name=json_doc,json=jsonDoc,proto3" json:"json_doc,omitempty"` +} + +type botjdoc struct { + // vic:00000000 + Thing string `json:"thing"` + // vic.RobotSettings, etc + Name string `json:"name"` + // actual jdoc + Jdoc AJdoc `json:"jdoc"` +} + +func join(p1, p2 string) string { + return filepath.Join(p1, p2) +} + +func Init() { + logger.Println("Commit SHA: " + CommitSHA) + if VarsInited { + logger.Println("Not initting vars again") + return + } + logger.Println("Initializing variables") + + if Packaged { + logger.Println("This version of wire-pod is packaged. Set vars to include UserConfigDir...") + var confDir string + if runtime.GOOS == "android" || runtime.GOOS == "ios" { + confDir = AndroidPath + } else { + confDir, _ = os.UserConfigDir() + } + podDir := join(confDir, PodName) + appDir, _ := os.Executable() + os.Mkdir(podDir, 0777) + JdocsDir = join(podDir, JdocsDir) + JdocsPath = JdocsDir + "/jdocs.json" + CustomIntentsPath = join(podDir, CustomIntentsPath) + BotConfigsPath = join(podDir, BotConfigsPath) + BotInfoPath = JdocsDir + "/" + BotInfoName + VoskModelPath = join(podDir, "./vosk/models/") + WhisperModelPath = join(filepath.Dir(appDir), "/../Frameworks/chipper/whisper.cpp/models/") // macos + ApiConfigPath = join(podDir, ApiConfigPath) + CertPath = join(podDir, "./certs/cert.crt") + KeyPath = join(podDir, "./certs/cert.key") + ServerConfigPath = join(podDir, "./certs/server_config.json") + Certs = join(podDir, "./certs") + SessionCertPath = join(podDir, SessionCertPath) + if runtime.GOOS == "android" { + VersionFile = AndroidPath + "/static/version" + } + os.Mkdir(JdocsDir, 0777) + os.Mkdir(SessionCertPath, 0777) + os.Mkdir(Certs, 0777) + } + + if os.Getenv("WEBSERVER_PORT") != "" { + if _, err := strconv.Atoi(os.Getenv("WEBSERVER_PORT")); err == nil { + WebPort = os.Getenv("WEBSERVER_PORT") + } else { + logger.Println("WEBSERVER_PORT contains letters, using default of 8080") + WebPort = "8080" + } + } else { + WebPort = "8080" + } + + // figure out user SDK path, containing sdk_config.ini + // has to be done like this because wire-pod is running as root + // path should be /home/name/wire-pod/chipper + // Split puts an extra / in the beginning of the array + podPath, _ := os.Getwd() + podPathSplit := strings.Split(strings.TrimSpace(podPath), "/") + if runtime.GOOS == "windows" || runtime.GOOS == "darwin" { + dir, _ := os.UserHomeDir() + SDKIniPath = dir + "/.anki_vector/" + } else if runtime.GOOS == "android" || runtime.GOOS == "ios" { + SDKIniPath = filepath.Join(AndroidPath, "/wire-pod/anki_vector") + } else { + if podPathSplit[len(podPathSplit)-1] != "chipper" || podPathSplit[len(podPathSplit)-2] != PodName { + logger.Println("It looks like you may have changed path names of the directories wire-pod is running in. This is not recommended because the SDK implementation depends on relativity in a few spots.") + } + if len(podPathSplit) >= 5 { + SDKIniPath = "/" + podPathSplit[1] + "/" + podPathSplit[2] + "/.anki_vector/" + } else if strings.EqualFold(podPathSplit[0], "root") { + SDKIniPath = "/root/.anki_vector/" + } else if len(podPathSplit) == 4 { + SDKIniPath = "/" + podPathSplit[1] + "/.anki_vector/" + } else { + logger.Println("Unsupported path scenario, printing podPathSplit: ") + logger.Println(podPathSplit) + SDKIniPath = "/tmp/.anki_vector/" + } + } + logger.Println("SDK info path: " + SDKIniPath) + + // load api config (config.go) + ReadConfig() + + // check models folder, add all models to DownloadedVoskModels + if APIConfig.STT.Service == "vosk" { + GetDownloadedVoskModels() + } + + // load jdocs. if there are any in the old format, conver + if _, err := os.Stat(JdocsPath); err == nil { + jsonBytes, _ := os.ReadFile(JdocsPath) + json.Unmarshal(jsonBytes, &BotJdocs) + logger.Println("Loaded jdocs file") + } + + // load bot sdk info + botBytes, err := os.ReadFile(BotInfoPath) + if err == nil { + json.Unmarshal(botBytes, &BotInfo) + var botList []string + for _, robot := range BotInfo.Robots { + botList = append(botList, robot.Esn) + } + logger.Println("Loaded bot info file, known bots: " + fmt.Sprint(botList)) + } + + ReadSessionCerts() + LoadCustomIntents() + VarsInited = true +} + +func GetDownloadedVoskModels() { + array, err := os.ReadDir(VoskModelPath) + if err != nil { + logger.Println(err) + return + } + for _, dir := range array { + DownloadedVoskModels = append(DownloadedVoskModels, dir.Name()) + } +} + +func LoadCustomIntents() { + jsonBytes, err := os.ReadFile(CustomIntentsPath) + if err == nil { + json.Unmarshal(jsonBytes, &CustomIntents) + CustomIntentsExist = true + logger.Println("Loaded custom intents:") + for _, intent := range CustomIntents { + logger.Println(intent.Name) + } + } +} + +func LoadIntents() ([]JsonIntent, error) { + var path string + if runtime.GOOS == "darwin" && Packaged { + appPath, _ := os.Executable() + path = filepath.Dir(appPath) + "/../Frameworks/chipper/" + } else if runtime.GOOS == "android" || runtime.GOOS == "ios" { + path = AndroidPath + "/static/" + } else { + path = "./" + } + jsonFile, err := os.ReadFile(path + "intent-data/" + APIConfig.STT.Language + ".json") + + // var matches [][]string + // var intents []string + var jsonIntents []JsonIntent + if err == nil { + err = json.Unmarshal(jsonFile, &jsonIntents) + // if err != nil { + // logger.Println("Failed to load intents: " + err.Error()) + // } + + // for _, element := range jsonIntents { + // //logger.Println("Loading intent " + strconv.Itoa(index) + " --> " + element.Name + "( " + strconv.Itoa(len(element.Keyphrases)) + " keyphrases )") + // intents = append(intents, element.Name) + // matches = append(matches, element.Keyphrases) + // } + // logger.Println("Loaded " + strconv.Itoa(len(jsonIntents)) + " intents and " + strconv.Itoa(len(matches)) + " matches (language: " + APIConfig.STT.Language + ")") + } + return jsonIntents, err +} + +func WriteJdocs() { + writeBytes, _ := json.Marshal(BotJdocs) + os.WriteFile(JdocsPath, writeBytes, 0644) +} + +// removes a bot from jdocs file +func DeleteData(thing string) { + var newdocs []botjdoc + for _, jdocentry := range BotJdocs { + if jdocentry.Thing != thing { + newdocs = append(newdocs, jdocentry) + } + } + BotJdocs = newdocs + WriteJdocs() +} + +func GetJdoc(thing, jdocname string) (AJdoc, bool) { + for _, botJdoc := range BotJdocs { + if botJdoc.Name == jdocname && botJdoc.Thing == thing { + return botJdoc.Jdoc, true + } + } + return AJdoc{}, false +} + +// DocVersion uint64 `protobuf:"varint,1,opt,name=doc_version,json=docVersion,proto3" json:"doc_version,omitempty"` // first version = 1; 0 => invalid or doesn't exist +// FmtVersion uint64 `protobuf:"varint,2,opt,name=fmt_version,json=fmtVersion,proto3" json:"fmt_version,omitempty"` // first version = 1; 0 => invalid +// ClientMetadata string `protobuf:"bytes,3,opt,name=client_metadata,json=clientMetadata,proto3" json:"client_metadata,omitempty"` // arbitrary client-defined string, eg a data fingerprint (typ "", 32 chars max) +// JsonDoc string + +func AddJdoc(thing string, name string, jdoc AJdoc) uint64 { + var latestVersion uint64 = 0 + matched := false + for index, jdocentry := range BotJdocs { + if jdocentry.Thing == thing && jdocentry.Name == name { + BotJdocs[index].Jdoc = jdoc + latestVersion = BotJdocs[index].Jdoc.DocVersion + matched = true + break + } + } + if !matched { + var newbot botjdoc + newbot.Thing = thing + newbot.Name = name + newbot.Jdoc = jdoc + BotJdocs = append(BotJdocs, newbot) + } + WriteJdocs() + return latestVersion +} + +func ReadSessionCerts() { + logger.Println("Reading session certs for robot IDs") + var rinfo RecurringInfoStore + certDir, err := os.ReadDir(SessionCertPath) + if err != nil { + logger.Println(err) + return + } + for _, entry := range certDir { + if entry.Name() == "placeholder" { + continue + } + esn := entry.Name() + var ip string + certBytes, err := os.ReadFile(filepath.Join(SessionCertPath, entry.Name())) + if err != nil { + logger.Println(err) + return + } + pemBytes, _ := pem.Decode(certBytes) + cert, _ := x509.ParseCertificate(pemBytes.Bytes) + for _, robot := range BotInfo.Robots { + if esn == robot.Esn { + ip = robot.IPAddress + break + } + } + rinfo.ESN = esn + rinfo.ID = cert.Issuer.CommonName + rinfo.IP = ip + RecurringInfo = append(RecurringInfo, rinfo) + } +} + +func AddToRInfo(esn string, id string, ip string) { + // the only bot constant is ESN + for i := range RecurringInfo { + if RecurringInfo[i].ESN == esn { + RecurringInfo[i].ID = id + RecurringInfo[i].IP = ip + return + } + } + var rinfo RecurringInfoStore + rinfo.ESN = esn + rinfo.ID = id + rinfo.IP = ip + RecurringInfo = append(RecurringInfo, rinfo) +} + +func GetRobot(esn string) (*vector.Vector, error) { + var guid string + var target string + matched := false + for _, bot := range BotInfo.Robots { + if esn == bot.Esn { + guid = bot.GUID + target = bot.IPAddress + ":443" + matched = true + break + } + } + if !matched { + return nil, errors.New("robot not in botsdkinfo") + } + robot, err := vector.New(vector.WithSerialNo(esn), vector.WithToken(guid), vector.WithTarget(target)) + if err != nil { + return nil, err + } + return robot, nil +} + +func GetOutboundIP() net.IP { + if runtime.GOOS == "android" { + ifaces, _ := anet.Interfaces() + for _, iface := range ifaces { + if iface.Name == "wlan0" { + adrs, err := anet.InterfaceAddrsByInterface(&iface) + if err != nil { + logger.Println(err) + break + } + if len(adrs) > 0 { + localAddr := adrs[0].(*net.IPNet) + return localAddr.IP + } + } + } + } + conn, err := net.Dial("udp", OutboundIPTester) + if err != nil { + logger.Println("not connected to a network: ", err) + return net.IPv4(0, 0, 0, 0) + } + defer conn.Close() + localAddr := conn.LocalAddr().(*net.UDPAddr) + return localAddr.IP +} diff --git a/chipper/pkg/vtt/intent.go b/chipper/pkg/vtt/intent.go new file mode 100644 index 0000000..a0dc746 --- /dev/null +++ b/chipper/pkg/vtt/intent.go @@ -0,0 +1,25 @@ +package vtt + +import ( + "time" + + pb "github.com/digital-dream-labs/api/go/chipperpb" +) + +// IntentRequest is the necessary request type for VTT intent processors +type IntentRequest struct { + Time time.Time + Stream pb.ChipperGrpc_StreamingIntentServer + Device string + Session string + LangString string + FirstReq *pb.StreamingIntentRequest + AudioCodec pb.AudioEncoding +} + +// IntentResponse is the response type VTT intent processors +type IntentResponse struct { + Intent *pb.IntentResponse + Params string + Duration *time.Duration +} diff --git a/chipper/pkg/vtt/intentgraph.go b/chipper/pkg/vtt/intentgraph.go new file mode 100644 index 0000000..9a2d05e --- /dev/null +++ b/chipper/pkg/vtt/intentgraph.go @@ -0,0 +1,28 @@ +package vtt + +import ( + "time" + + pb "github.com/digital-dream-labs/api/go/chipperpb" +) + +// IntentGraphRequest is the necessary request type for VTT intent processors +type IntentGraphRequest struct { + Time time.Time + Stream pb.ChipperGrpc_StreamingIntentGraphServer + Device string + Session string + LangString string + FirstReq *pb.StreamingIntentGraphRequest + AudioCodec pb.AudioEncoding + + // KnowledgeGraph specific + Mode pb.RobotMode +} + +// IntentGraphResponse is the response type VTT intent processors +type IntentGraphResponse struct { + Intent *pb.IntentGraphResponse + Params string + Duration *time.Duration +} diff --git a/chipper/pkg/vtt/knowledgegraph.go b/chipper/pkg/vtt/knowledgegraph.go new file mode 100644 index 0000000..9e65eaa --- /dev/null +++ b/chipper/pkg/vtt/knowledgegraph.go @@ -0,0 +1,26 @@ +package vtt + +import ( + "time" + + pb "github.com/digital-dream-labs/api/go/chipperpb" +) + +// KnowledgeGraphRequest is the necessary request type for VTT knowledge graph processors +type KnowledgeGraphRequest struct { + Time time.Time + Stream pb.ChipperGrpc_StreamingKnowledgeGraphServer + Device string + Session string + LangString string + FirstReq *pb.StreamingKnowledgeGraphRequest + Mode pb.RobotMode + AudioCodec pb.AudioEncoding +} + +// KnowledgeGraphResponse is the response type VTT knowledge graph processors +type KnowledgeGraphResponse struct { + Intent *pb.KnowledgeGraphResponse + Params string + Duration *time.Duration +} diff --git a/chipper/pkg/wirepod/README.md b/chipper/pkg/wirepod/README.md new file mode 100644 index 0000000..0e1f330 --- /dev/null +++ b/chipper/pkg/wirepod/README.md @@ -0,0 +1,23 @@ +# Key + +## speechrequest +- Contains code for dealing with the intent types, and has functions to help convert stream bytes to ones which are stt-engine-friendly +- speechrequest refers to the type which every intent type gets turned into + +## preqs +- Process request functions, the first which get launched in the chain of code + +## stintent +- Speech-to-intent, for services like Picovoice Rhino + +## stt +- Speech-to-text functions, where you would put your own STT engine implementation + +## ttr +- Text-to-response, takes text from wirepod-stt, turns it into a response, and sends it to the bot. Does all intent parsing and stuff as well + +## config-ws +- Webserver for custom intents and such + +## sdkapp +- App for configuring bot settings and controlling bots diff --git a/chipper/pkg/wirepod/config-ws/webserver.go b/chipper/pkg/wirepod/config-ws/webserver.go new file mode 100644 index 0000000..72a42a3 --- /dev/null +++ b/chipper/pkg/wirepod/config-ws/webserver.go @@ -0,0 +1,517 @@ +package webserver + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "os" + "path" + "path/filepath" + "runtime" + "strings" + + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/scripting" + "github.com/kercre123/wire-pod/chipper/pkg/vars" + "github.com/kercre123/wire-pod/chipper/pkg/wirepod/localization" + processreqs "github.com/kercre123/wire-pod/chipper/pkg/wirepod/preqs" + botsetup "github.com/kercre123/wire-pod/chipper/pkg/wirepod/setup" +) + +var SttInitFunc func() error + +func apiHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Headers", "*") + + switch strings.TrimPrefix(r.URL.Path, "/api/") { + case "add_custom_intent": + handleAddCustomIntent(w, r) + case "edit_custom_intent": + handleEditCustomIntent(w, r) + case "get_custom_intents_json": + handleGetCustomIntentsJSON(w) + case "remove_custom_intent": + handleRemoveCustomIntent(w, r) + case "set_weather_api": + handleSetWeatherAPI(w, r) + case "get_weather_api": + handleGetWeatherAPI(w) + case "set_kg_api": + handleSetKGAPI(w, r) + case "get_kg_api": + handleGetKGAPI(w) + case "set_stt_info": + handleSetSTTInfo(w, r) + case "get_download_status": + handleGetDownloadStatus(w) + case "get_stt_info": + handleGetSTTInfo(w) + case "get_config": + handleGetConfig(w) + case "get_logs": + handleGetLogs(w) + case "get_debug_logs": + handleGetDebugLogs(w) + case "is_running": + handleIsRunning(w) + case "delete_chats": + handleDeleteChats(w) + case "get_ota": + handleGetOTA(w, r) + case "get_version_info": + handleGetVersionInfo(w) + case "generate_certs": + handleGenerateCerts(w) + case "is_api_v3": + fmt.Fprintf(w, "it is!") + default: + http.Error(w, "not found", http.StatusNotFound) + } +} + +func handleAddCustomIntent(w http.ResponseWriter, r *http.Request) { + var intent vars.CustomIntent + if err := json.NewDecoder(r.Body).Decode(&intent); err != nil { + http.Error(w, "invalid request body", http.StatusBadRequest) + return + } + if anyEmpty(intent.Name, intent.Description, intent.Intent) || len(intent.Utterances) == 0 { + http.Error(w, "missing required field (name, description, utterances, and intent are required)", http.StatusBadRequest) + return + } + intent.LuaScript = strings.TrimSpace(intent.LuaScript) + if intent.LuaScript != "" { + if err := scripting.ValidateLuaScript(intent.LuaScript); err != nil { + http.Error(w, "lua validation error: "+err.Error(), http.StatusBadRequest) + return + } + } + vars.CustomIntentsExist = true + vars.CustomIntents = append(vars.CustomIntents, intent) + saveCustomIntents() + fmt.Fprint(w, "Intent added successfully.") +} + +func handleEditCustomIntent(w http.ResponseWriter, r *http.Request) { + var request struct { + Number int `json:"number"` + vars.CustomIntent + } + if err := json.NewDecoder(r.Body).Decode(&request); err != nil { + http.Error(w, "invalid request body", http.StatusBadRequest) + return + } + if request.Number < 1 || request.Number > len(vars.CustomIntents) { + http.Error(w, "invalid intent number", http.StatusBadRequest) + return + } + intent := &vars.CustomIntents[request.Number-1] + if request.Name != "" { + intent.Name = request.Name + } + if request.Description != "" { + intent.Description = request.Description + } + if len(request.Utterances) != 0 { + intent.Utterances = request.Utterances + } + if request.Intent != "" { + intent.Intent = request.Intent + } + if request.Params.ParamName != "" { + intent.Params.ParamName = request.Params.ParamName + } + if request.Params.ParamValue != "" { + intent.Params.ParamValue = request.Params.ParamValue + } + if request.Exec != "" { + intent.Exec = request.Exec + } + if request.LuaScript != "" { + intent.LuaScript = request.LuaScript + if err := scripting.ValidateLuaScript(intent.LuaScript); err != nil { + http.Error(w, "lua validation error: "+err.Error(), http.StatusBadRequest) + return + } + } + if len(request.ExecArgs) != 0 { + intent.ExecArgs = request.ExecArgs + } + intent.IsSystemIntent = false + saveCustomIntents() + fmt.Fprint(w, "Intent edited successfully.") +} + +func handleGetCustomIntentsJSON(w http.ResponseWriter) { + if !vars.CustomIntentsExist { + http.Error(w, "you must create an intent first", http.StatusBadRequest) + return + } + customIntentJSONFile, err := os.ReadFile(vars.CustomIntentsPath) + if err != nil { + http.Error(w, "could not read custom intents file", http.StatusInternalServerError) + logger.Println(err) + return + } + w.Header().Set("Content-Type", "application/json") + w.Write(customIntentJSONFile) +} + +func handleRemoveCustomIntent(w http.ResponseWriter, r *http.Request) { + var request struct { + Number int `json:"number"` + } + if err := json.NewDecoder(r.Body).Decode(&request); err != nil { + http.Error(w, "invalid request body", http.StatusBadRequest) + return + } + if request.Number < 1 || request.Number > len(vars.CustomIntents) { + http.Error(w, "invalid intent number", http.StatusBadRequest) + return + } + vars.CustomIntents = append(vars.CustomIntents[:request.Number-1], vars.CustomIntents[request.Number:]...) + saveCustomIntents() + fmt.Fprint(w, "Intent removed successfully.") +} + +func handleSetWeatherAPI(w http.ResponseWriter, r *http.Request) { + var config struct { + Provider string `json:"provider"` + Key string `json:"key"` + } + if err := json.NewDecoder(r.Body).Decode(&config); err != nil { + http.Error(w, "invalid request body", http.StatusBadRequest) + return + } + if config.Provider == "" { + vars.APIConfig.Weather.Enable = false + } else { + vars.APIConfig.Weather.Enable = true + vars.APIConfig.Weather.Key = strings.TrimSpace(config.Key) + vars.APIConfig.Weather.Provider = config.Provider + } + vars.WriteConfigToDisk() + fmt.Fprint(w, "Changes successfully applied.") +} + +func handleGetWeatherAPI(w http.ResponseWriter) { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(vars.APIConfig.Weather) +} + +func handleSetKGAPI(w http.ResponseWriter, r *http.Request) { + if err := json.NewDecoder(r.Body).Decode(&vars.APIConfig.Knowledge); err != nil { + fmt.Println(err) + http.Error(w, "invalid request body", http.StatusBadRequest) + return + } + vars.WriteConfigToDisk() + fmt.Fprint(w, "Changes successfully applied.") +} + +func handleGetKGAPI(w http.ResponseWriter) { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(vars.APIConfig.Knowledge) +} + +func handleSetSTTInfo(w http.ResponseWriter, r *http.Request) { + var request struct { + Language string `json:"language"` + } + if err := json.NewDecoder(r.Body).Decode(&request); err != nil { + http.Error(w, "invalid request body", http.StatusBadRequest) + return + } + if vars.APIConfig.STT.Service == "vosk" { + if !isValidLanguage(request.Language, localization.ValidVoskModels) { + http.Error(w, "language not valid", http.StatusBadRequest) + return + } + if !isDownloadedLanguage(request.Language, vars.DownloadedVoskModels) { + go localization.DownloadVoskModel(request.Language) + fmt.Fprint(w, "downloading language model...") + return + } + } else if vars.APIConfig.STT.Service == "whisper.cpp" { + if !isValidLanguage(request.Language, localization.ValidVoskModels) { + http.Error(w, "language not valid", http.StatusBadRequest) + return + } + } else { + http.Error(w, "service must be vosk or whisper", http.StatusBadRequest) + return + } + vars.APIConfig.STT.Language = request.Language + vars.APIConfig.PastInitialSetup = true + vars.WriteConfigToDisk() + processreqs.ReloadVosk() + logger.Println("Reloaded voice processor successfully") + fmt.Fprint(w, "Language switched successfully.") +} + +func handleGetDownloadStatus(w http.ResponseWriter) { + w.Header().Set("Content-Type", "text/plain") + w.Write([]byte(localization.DownloadStatus)) + if localization.DownloadStatus == "success" || strings.Contains(localization.DownloadStatus, "error") { + localization.DownloadStatus = "not downloading" + } +} + +func handleGetSTTInfo(w http.ResponseWriter) { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(vars.APIConfig.STT) +} + +func handleGetConfig(w http.ResponseWriter) { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(vars.APIConfig) +} + +func handleGetLogs(w http.ResponseWriter) { + w.Header().Set("Content-Type", "text/plain") + w.Write([]byte(logger.LogList)) +} + +func handleGetDebugLogs(w http.ResponseWriter) { + w.Header().Set("Content-Type", "text/plain") + w.Write([]byte(logger.LogTrayList)) +} + +func handleIsRunning(w http.ResponseWriter) { + w.Header().Set("Content-Type", "text/plain") + w.Write([]byte("true")) +} + +func handleDeleteChats(w http.ResponseWriter) { + vars.RememberedChats = []vars.RememberedChat{} + fmt.Fprint(w, "done") +} + +func handleGetOTA(w http.ResponseWriter, r *http.Request) { + otaName := strings.Split(r.URL.Path, "/")[3] + targetURL, err := url.Parse("https://archive.org/download/vector-pod-firmware/" + strings.TrimSpace(otaName)) + if err != nil { + http.Error(w, "failed to parse URL", http.StatusInternalServerError) + return + } + req, err := http.NewRequest(r.Method, targetURL.String(), nil) + if err != nil { + http.Error(w, "failed to create request", http.StatusInternalServerError) + return + } + for key, values := range r.Header { + for _, value := range values { + req.Header.Add(key, value) + } + } + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + http.Error(w, "failed to perform request", http.StatusInternalServerError) + return + } + defer resp.Body.Close() + for key, values := range resp.Header { + for _, value := range values { + w.Header().Add(key, value) + } + } + _, err = io.Copy(w, resp.Body) + if err != nil { + http.Error(w, "failed to copy response body", http.StatusInternalServerError) + } +} + +func handleGetVersionInfo(w http.ResponseWriter) { + var installedVer string + ver, err := os.ReadFile(vars.VersionFile) + if err == nil { + installedVer = strings.TrimSpace(string(ver)) + } + currentVer, err := GetLatestReleaseTag("kercre123", "WirePod") + if err != nil { + http.Error(w, "error communicating with github (ver): "+err.Error(), http.StatusInternalServerError) + return + } + currentCommit, err := GetLatestCommitSha() + if err != nil { + http.Error(w, "error communicating with github (commit): "+err.Error(), http.StatusInternalServerError) + return + } + type VersionInfo struct { + FromSource bool `json:"fromsource"` + InstalledVer string `json:"installedversion"` + InstalledCommit string `json:"installedcommit"` + CurrentVer string `json:"currentversion"` + CurrentCommit string `json:"currentcommit"` + UpdateAvailable bool `json:"avail"` + } + var fromSource bool + if installedVer == "" { + fromSource = true + } + var uAvail bool + if fromSource { + uAvail = vars.CommitSHA != strings.TrimSpace(currentCommit) + } else { + uAvail = installedVer != strings.TrimSpace(currentVer) + } + verInfo := VersionInfo{ + FromSource: fromSource, + InstalledVer: installedVer, + InstalledCommit: vars.CommitSHA, + CurrentVer: strings.TrimSpace(currentVer), + CurrentCommit: strings.TrimSpace(currentCommit), + UpdateAvailable: uAvail, + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(verInfo) +} + +func handleGenerateCerts(w http.ResponseWriter) { + if err := botsetup.CreateCertCombo(); err != nil { + http.Error(w, "error: "+err.Error(), http.StatusInternalServerError) + return + } + fmt.Fprint(w, "done") +} + +func saveCustomIntents() { + customIntentJSONFile, _ := json.Marshal(vars.CustomIntents) + os.WriteFile(vars.CustomIntentsPath, customIntentJSONFile, 0644) +} + +func DisableCachingAndSniffing(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate, max-age=0") + w.Header().Set("pragma", "no-cache") + w.Header().Set("X-Content-Type-Options", "nosniff") + w.Header().Set("Expires", "0") + next.ServeHTTP(w, r) + }) +} + +func StartWebServer() { + botsetup.RegisterSSHAPI() + botsetup.RegisterBLEAPI() + http.HandleFunc("/api/", apiHandler) + http.HandleFunc("/session-certs/", certHandler) + var webRoot http.Handler + if runtime.GOOS == "darwin" && vars.Packaged { + appPath, _ := os.Executable() + webRoot = http.FileServer(http.Dir(filepath.Dir(appPath) + "/../Frameworks/chipper/webroot")) + } else if runtime.GOOS == "android" || runtime.GOOS == "ios" { + webRoot = http.FileServer(http.Dir(vars.AndroidPath + "/static/webroot")) + } else { + webRoot = http.FileServer(http.Dir("./webroot")) + } + http.Handle("/", DisableCachingAndSniffing(webRoot)) + fmt.Printf("Starting webserver at port " + vars.WebPort + " (http://localhost:" + vars.WebPort + ")\n") + if err := http.ListenAndServe(":"+vars.WebPort, nil); err != nil { + logger.Println("Error binding to " + vars.WebPort + ": " + err.Error()) + if vars.Packaged { + logger.ErrMsg("FATAL: Wire-pod was unable to bind to port " + vars.WebPort + ". Another process is likely using it. Exiting.") + } + os.Exit(1) + } +} + +func GetLatestCommitSha() (string, error) { + client := &http.Client{} + req, err := http.NewRequest("GET", "https://api.github.com/repos/kercre123/wire-pod/commits", nil) + if err != nil { + return "", err + } + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("failed to get commits: %s", resp.Status) + } + type Commit struct { + Sha string `json:"sha"` + } + var commits []Commit + if err := json.NewDecoder(resp.Body).Decode(&commits); err != nil { + return "", err + } + if len(commits) == 0 { + return "", fmt.Errorf("no commits found") + } + return commits[0].Sha[:7], nil +} + +func GetLatestReleaseTag(owner, repo string) (string, error) { + url := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", owner, repo) + + resp, err := http.Get(url) + if err != nil { + return "", err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + type Release struct { + TagName string `json:"tag_name"` + } + var release Release + if err := json.Unmarshal(body, &release); err != nil { + return "", err + } + + return release.TagName, nil +} + +func certHandler(w http.ResponseWriter, r *http.Request) { + switch { + case strings.Contains(r.URL.Path, "/session-certs/"): + split := strings.Split(r.URL.Path, "/") + if len(split) < 3 { + http.Error(w, "must request a cert by esn (ex. /session-certs/00e20145)", http.StatusBadRequest) + return + } + esn := split[2] + fileBytes, err := os.ReadFile(path.Join(vars.SessionCertPath, esn)) + if err != nil { + http.Error(w, "cert does not exist", http.StatusNotFound) + return + } + w.Write(fileBytes) + } +} + +func anyEmpty(values ...string) bool { + for _, v := range values { + if v == "" { + return true + } + } + return false +} + +func isValidLanguage(language string, validLanguages []string) bool { + for _, lang := range validLanguages { + if lang == language { + return true + } + } + return false +} + +func isDownloadedLanguage(language string, downloadedLanguages []string) bool { + for _, lang := range downloadedLanguages { + if lang == language { + return true + } + } + return false +} diff --git a/chipper/pkg/wirepod/localization/download.go b/chipper/pkg/wirepod/localization/download.go new file mode 100644 index 0000000..afcc3ba --- /dev/null +++ b/chipper/pkg/wirepod/localization/download.go @@ -0,0 +1,190 @@ +package localization + +/** + * @website http://albulescu.ro + * @author Cosmin Albulescu + */ +// modified from this person's gist ^^^ + +import ( + "archive/zip" + "crypto/tls" + "fmt" + "io" + "log" + "math" + "net/http" + "os" + "path/filepath" + "runtime" + "strconv" + "strings" + "time" + + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vars" +) + +var URLPrefix string = "https://github.com/kercre123/vosk-models/raw/main/" + +//var URLPrefix string = "https://alphacephei.com/vosk/models/" + +var DownloadStatus string = "not downloading" + +func DownloadVoskModel(language string) { + filename := "vosk-model-small-" + if language == "en-US" { + filename = filename + "en-us-0.15.zip" + } else if language == "it-IT" { + filename = filename + "it-0.22.zip" + } else if language == "es-ES" { + filename = filename + "es-0.42.zip" + } else if language == "fr-FR" { + filename = filename + "fr-0.22.zip" + } else if language == "de-DE" { + filename = filename + "de-0.15.zip" + } else if language == "pt-BR" { + filename = filename + "pt-0.3.zip" + } else if language == "pl-PL" { + filename = filename + "pl-0.22.zip" + } else if language == "zh-CN" { + filename = filename + "cn-0.22.zip" + } else if language == "tr-TR" { + filename = filename + "tr-0.3.zip" + } else if language == "ru-RU" { + filename = filename + "ru-0.22.zip" + } else if language == "nt-NL" { + filename = filename + "nl-0.22.zip" + } else if language == "uk-UA" { + filename = filename + "uk-v3-small.zip" + } else if language == "vi-VN" { + filename = filename + "vn-0.4.zip" + } else { + logger.Println("Language not valid? " + language) + return + } + os.MkdirAll(vars.VoskModelPath, 0755) + url := URLPrefix + filename + var filep string + if runtime.GOOS == "android" || runtime.GOOS == "ios" { + filep = filepath.Join(vars.AndroidPath, "/"+filename) + } else { + filep = os.TempDir() + "/" + filename + } + destpath := filepath.Join(vars.VoskModelPath, language) + "/" + DownloadFile(url, filep) + UnzipFile(filep, destpath) + os.Rename(destpath+strings.TrimSuffix(filename, ".zip"), destpath+"model") + os.Remove(filep) + vars.DownloadedVoskModels = append(vars.DownloadedVoskModels, language) + DownloadStatus = "Reloading voice processor" + vars.APIConfig.STT.Language = language + vars.APIConfig.PastInitialSetup = true + vars.WriteConfigToDisk() + ReloadVosk() + logger.Println("Reloaded voice processor successfully") + DownloadStatus = "success" +} + +func PrintDownloadPercent(done chan int64, path string, total int64) { + var stop bool = false + for { + select { + case <-done: + stop = true + default: + file, err := os.Open(path) + if err != nil { + log.Fatal(err) + } + fi, err := file.Stat() + if err != nil { + log.Fatal(err) + } + size := fi.Size() + if size == 0 { + size = 1 + } + var percent float64 = float64(size) / float64(total) * 100 + showPercent := math.Floor(percent) + DownloadStatus = "Model download status: " + fmt.Sprint(showPercent) + "%" + } + if stop { + DownloadStatus = "completed" + break + } + time.Sleep(time.Second / 2) + } +} + +func DownloadFile(url string, dest string) { + if strings.Contains(DownloadStatus, "success") || strings.Contains(DownloadStatus, "error") || strings.Contains(DownloadStatus, "not downloading") { + http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + logger.Println("Downloading " + url + " to " + dest) + out, _ := os.Create(dest) + defer out.Close() + headResp, err := http.Head(url) + if err != nil { + logger.Println(err) + DownloadStatus = "error: " + err.Error() + return + } + defer headResp.Body.Close() + size, _ := strconv.Atoi(headResp.Header.Get("Content-Length")) + done := make(chan int64) + go PrintDownloadPercent(done, dest, int64(size)) + resp, err := http.Get(url) + if err != nil { + DownloadStatus = "error: " + err.Error() + logger.Println(err) + return + } + defer resp.Body.Close() + n, _ := io.Copy(out, resp.Body) + done <- n + DownloadStatus = "Completed download" + } else { + logger.Println("Not downloading model because download is currently happening") + } +} + +func UnzipFile(file, dest string) { + zipReader, err := zip.OpenReader(file) + if err != nil { + DownloadStatus = "error downloading: " + err.Error() + logger.Println("error opening zip file:", err) + return + } + defer zipReader.Close() + DownloadStatus = "Unpacking model..." + + for _, f := range zipReader.File { + rc, err := f.Open() + if err != nil { + DownloadStatus = "error downloading: " + err.Error() + logger.Println("error opening zip file:", err) + return + } + defer rc.Close() + + path := filepath.Join(dest, f.Name) + if f.FileInfo().IsDir() { + os.MkdirAll(path, f.Mode()) + } else { + f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + DownloadStatus = "error downloading: " + err.Error() + logger.Println("Error creating file:", err) + return + } + defer f.Close() + + _, err = io.Copy(f, rc) + if err != nil { + DownloadStatus = "error downloading: " + err.Error() + logger.Println("Error writing to file:", err) + return + } + } + } +} diff --git a/chipper/pkg/wirepod/localization/localization.go b/chipper/pkg/wirepod/localization/localization.go new file mode 100644 index 0000000..da504bb --- /dev/null +++ b/chipper/pkg/wirepod/localization/localization.go @@ -0,0 +1,155 @@ +package localization + +import "github.com/kercre123/wire-pod/chipper/pkg/vars" + +var ValidVoskModels []string = []string{"en-US", "it-IT", "es-ES", "fr-FR", "de-DE", "pt-BR", "pl-PL", "zh-CN", "tr-TR", "ru-RU", "nt-NL", "uk-UA", "vi-VN"} + +const STR_WEATHER_IN = "str_weather_in" +const STR_WEATHER_FORECAST = "str_weather_forecast" +const STR_WEATHER_TOMORROW = "str_weather_tomorrow" +const STR_WEATHER_THE_DAY_AFTER_TOMORROW = "str_weather_the_day_after_tomorrow" +const STR_WEATHER_TONIGHT = "str_weather_tonight" +const STR_WEATHER_THIS_AFTERNOON = "str_weather_this_afternoon" +const STR_EYE_COLOR_PURPLE = "str_eye_color_purple" +const STR_EYE_COLOR_BLUE = "str_eye_color_blue" +const STR_EYE_COLOR_SAPPHIRE = "str_eye_color_sapphire" +const STR_EYE_COLOR_YELLOW = "str_eye_color_yellow" +const STR_EYE_COLOR_TEAL = "str_eye_color_teal" +const STR_EYE_COLOR_TEAL2 = "str_eye_color_teal2" +const STR_EYE_COLOR_GREEN = "str_eye_color_green" +const STR_EYE_COLOR_ORANGE = "str_eye_color_orange" +const STR_ME = "str_me" +const STR_SELF = "str_self" +const STR_VOLUME_LOW = "str_volume_low" +const STR_VOLUME_QUIET = "str_volume_quiet" +const STR_VOLUME_MEDIUM_LOW = "str_volume_medium_low" +const STR_VOLUME_MEDIUM = "str_volume_medium" +const STR_VOLUME_NORMAL = "str_volume_normal" +const STR_VOLUME_REGULAR = "str_volume_regular" +const STR_VOLUME_MEDIUM_HIGH = "str_volume_medium_high" +const STR_VOLUME_HIGH = "str_volume_high" +const STR_VOLUME_LOUD = "str_volume_loud" +const STR_VOLUME_MUTE = "str_volume_mute" +const STR_VOLUME_NOTHING = "str_volume_nothing" +const STR_VOLUME_SILENT = "str_volume_silent" +const STR_VOLUME_OFF = "str_volume_off" +const STR_VOLUME_ZERO = "str_volume_zero" +const STR_NAME_IS = "str_name_is" +const STR_NAME_IS2 = "str_name_is1" +const STR_NAME_IS3 = "str_name_is2" +const STR_FOR = "str_for" + +// for grammer +var ALL_STR []string = []string{ + "str_weather_in", + "str_weather_forecast", + "str_weather_tomorrow", + "str_weather_the_day_after_tomorrow", + "str_weather_tonight", + "str_weather_this_afternoon", + "str_eye_color_purple", + "str_eye_color_blue", + "str_eye_color_sapphire", + "str_eye_color_yellow", + "str_eye_color_teal", + "str_eye_color_teal2", + "str_eye_color_green", + "str_eye_color_orange", + "str_me", + "str_self", + "str_volume_low", + "str_volume_quiet", + "str_volume_medium_low", + "str_volume_medium", + "str_volume_normal", + "str_volume_regular", + "str_volume_medium_high", + "str_volume_high", + "str_volume_loud", + "str_volume_mute", + "str_volume_nothing", + "str_volume_silent", + "str_volume_off", + "str_volume_zero", + "str_name_is", + "str_name_is1", + "str_name_is2", + "str_for", +} + +// All text must be lowercase! + +var texts = map[string][]string{ + // key en-US it-IT es-ES fr-FR de-DE pl-PL tr-TR ru-RU nt-NL uk-UA vi-VN + STR_WEATHER_IN: {" in ", " a ", " en ", " en ", " in ", " w ", " 的 ", " içinde ", " в ", " in ", " в ", " ở "}, + STR_WEATHER_FORECAST: {"forecast", "previsioni", "pronóstico", "prévisions", "wettervorhersage", "prognoza", "预报", "tahmin", "прогноз", "voorspelling", "прогноз", "dự báo"}, + STR_WEATHER_TOMORROW: {"tomorrow", "domani", "mañana", "demain", "morgen", "jutro", "明天", "yarın", "завтра", "morgen", "завтра", "ngày mai"}, + STR_WEATHER_THE_DAY_AFTER_TOMORROW: {"day after tomorrow", "dopodomani", "el día después de mañana", "lendemain de demain", "am tag nach morgen", "pojutrze", "后天", "yarından sonra", "послезавтра", "overmorgen", "післязавтра", "ngày mốt"}, + STR_WEATHER_TONIGHT: {"tonight", "stasera", "esta noche", "ce soir", "heute abend", "dziś wieczorem", "今晚", "bu gece", "сегодня вечером", "vanavond", "сьогодні ввечері", "tối nay"}, + STR_WEATHER_THIS_AFTERNOON: {"afternoon", "pomeriggio", "esta tarde", "après-midi", "heute nachmittag", "popołudniu", "下午", "bu öğleden sonra", "после полудня", "middag", "після полудня", "chiều nay"}, + STR_EYE_COLOR_PURPLE: {"purple", "lilla", "violeta", "violet", "violett", "fioletowy", "紫色", "mor", "фиолетовый", "paars", "фіолетовий", "màu tím"}, + STR_EYE_COLOR_BLUE: {"blue", "blu", "azul", "bleu", "blau", "niebieski", "蓝色", "mavi", "голубой", "blauw", "голубий", "màu xanh"}, + STR_EYE_COLOR_SAPPHIRE: {"sapphire", "zaffiro", "zafiro", "saphir", "saphir", "szafir", "天蓝", "safir", "синий", "saffier", "синій", "màu ngọc bích"}, + STR_EYE_COLOR_YELLOW: {"yellow", "giallo", "amarillo", "jaune", "gelb", "żółty", "黄色", "sarı", "жёлтый", "geel", "жовтий", "màu vàng"}, + STR_EYE_COLOR_TEAL: {"teal", "verde acqua", "verde azulado", "sarcelle", "blaugrün", "morski", "浅绿", "teal", "бирюзовый", "wintertaling", "бірюзовий", "xanh lá cây"}, + STR_EYE_COLOR_TEAL2: {"tell", "acquamarina", "aguamarina", "acquamarina", "acquamarina", "akwamaryn", "蓝绿", "turkuaz", "аквамарин", "vertellen", "аквамариновий", "màu xanh ngọc"}, + STR_EYE_COLOR_GREEN: {"green", "verde", "verde", "vert", "grün", "zielony", "绿色", "yeşil", "зелёный", "groente", "зелений", "màu xanh lá"}, + STR_EYE_COLOR_ORANGE: {"orange", "arancio", "naranja", "orange", "orange", "pomarańczowy", "橙色", "turuncu", "оранжевый", "oranje", "оранжевий", "màu cam"}, + STR_ME: {"me", "me", "me", "moi", "mir", "mnie", "我", "ben", "меня", "mij", "мене", "tôi"}, + STR_SELF: {"self", "mi", "mía", "moi", "mein", "ja", "自己", "kendim", "себя", "zelf", "себе", "bản thân"}, + STR_VOLUME_LOW: {"low", "basso", "bajo", "bas", "niedrig", "niski", "低", "düşük", "низкий", "laag", "на мінімум", "thấp"}, + STR_VOLUME_QUIET: {"quiet", "poco rumoroso", "tranquilo", "silencieux", "ruhig", "cichy", "安静", "sessiz", "тихо", "rustig", "тихо", "yên tĩnh"}, + STR_VOLUME_MEDIUM_LOW: {"medium low", "medio basso", "medio-bajo", "moyen-doux", "mittelschwer", "średnio niski", "中低", "orta düşük", "ниже среднего", "middel laag", "нижче середнього", "vừa thấp"}, + STR_VOLUME_MEDIUM: {"medium", "medio", "medio", "moyen", "mittel", "średni", "中档", "orta", "средний", "medium", "середню", "vừa"}, + STR_VOLUME_NORMAL: {"normal", "normale", "normal", "normal", "normal", "normalny", "正常", "normal", "нормальный", "normaal", "нормальна", "bình thường"}, + STR_VOLUME_REGULAR: {"regular", "regolare", "regular", "régulier", "regulär", "zwyczajny", "标准", "düzenli", "обычный", "normaal", "звичайна", "thông thường"}, + STR_VOLUME_MEDIUM_HIGH: {"medium high", "medio alto", "medio-alto", "moyen-élevé", "mittelhoch", "średno wysoki", "中高", "orta yüksek", "выше среднего", "gemiddeld hoog", "вище середнього", "vừa cao"}, + STR_VOLUME_HIGH: {"high", "alto", "alto", "élevé", "hoch", "wysoki", "高档", "yüksek", "высокий", "hoog", "висока", "cao"}, + STR_VOLUME_LOUD: {"loud", "rumoroso", "fuerte", "fort", "laut", "głośny", "高", "gürültülü", "громкий", "luidruchtig", "гучний", "to"}, + STR_VOLUME_MUTE: {"mute", "muto", "mudo", "", "stumm", "wyciszony", "静音", "sessiz", "немой", "stom", "німий", "im lặng"}, + STR_VOLUME_NOTHING: {"nothing", "nessuno", "nada", "rien", "nichts", "nic", "无声", "hiçbir şey", "", "Niets", "нічого", "không có gì"}, + STR_VOLUME_SILENT: {"silent", "silenzioso", "silencio", "silencieux", "still", "cichy", "悄声", "sessiz", "тихий", "stil", "тихий", "yên lặng"}, + STR_VOLUME_OFF: {"off", "spento", "apagado", "éteindre", "aus", "wyłączony", "关闭", "kapalı", "выключить", "uit", "вимкнути", "tắt"}, + STR_VOLUME_ZERO: {"zero", "zero", "cero", "zéro", "null", "zero", "零", "sıfır", "ноль", "nul", "нуль", "không"}, + STR_NAME_IS: {" is ", " è ", " es ", " est ", " ist ", " to ", "到", " olan ", "", " is ", "", " là "}, + STR_NAME_IS2: {"'s", "sono ", "soy ", "suis ", "bin ", " się ", "的", "'nin", "", "", "", "của"}, + STR_NAME_IS3: {"names", " chiamo ", " llamo ", "appelle ", "werde", "imię", "名字", "adlar", "имена", "namen", "імена", "tên"}, + STR_FOR: {" for ", " per ", " para ", " pour ", " für ", " dla ", "给", " için ", "для", " voor ", " для ", " cho "}, +} + +func GetText(key string) string { + var data = texts[key] + if data != nil { + if vars.APIConfig.STT.Language == "it-IT" { + return data[1] + } else if vars.APIConfig.STT.Language == "es-ES" { + return data[2] + } else if vars.APIConfig.STT.Language == "fr-FR" { + return data[3] + } else if vars.APIConfig.STT.Language == "de-DE" { + return data[4] + } else if vars.APIConfig.STT.Language == "pl-PL" { + return data[5] + } else if vars.APIConfig.STT.Language == "zh-CN" { + return data[6] + } else if vars.APIConfig.STT.Language == "tr-TR" { + return data[7] + } else if vars.APIConfig.STT.Language == "ru-RU" { + return data[8] + } else if vars.APIConfig.STT.Language == "nt-NL" { + return data[9] + } else if vars.APIConfig.STT.Language == "uk-UA" { + return data[10] + } else if vars.APIConfig.STT.Language == "vi-VN" { + return data[11] + } + } + return data[0] +} + +func ReloadVosk() { + if vars.APIConfig.STT.Service == "vosk" || vars.APIConfig.STT.Service == "whisper.cpp" { + vars.IntentList, _ = vars.LoadIntents() + vars.SttInitFunc() + } +} diff --git a/chipper/pkg/wirepod/preqs/intent.go b/chipper/pkg/wirepod/preqs/intent.go new file mode 100644 index 0000000..6513dbc --- /dev/null +++ b/chipper/pkg/wirepod/preqs/intent.go @@ -0,0 +1,64 @@ +package processreqs + +import ( + "strings" + + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vars" + "github.com/kercre123/wire-pod/chipper/pkg/vtt" + sr "github.com/kercre123/wire-pod/chipper/pkg/wirepod/speechrequest" + ttr "github.com/kercre123/wire-pod/chipper/pkg/wirepod/ttr" +) + +// This is here for compatibility with 1.6 and older software +func (s *Server) ProcessIntent(req *vtt.IntentRequest) (*vtt.IntentResponse, error) { + var successMatched bool + speechReq := sr.ReqToSpeechRequest(req) + var transcribedText string + if !isSti { + var err error + transcribedText, err = sttHandler(speechReq) + if err != nil { + ttr.IntentPass(req, "intent_system_noaudio", "voice processing error: "+err.Error(), map[string]string{"error": err.Error()}, true) + return nil, nil + } + if strings.TrimSpace(transcribedText) == "" { + ttr.IntentPass(req, "intent_system_noaudio", "", map[string]string{}, false) + return nil, nil + } + successMatched = ttr.ProcessTextAll(req, transcribedText, vars.IntentList, speechReq.IsOpus) + } else { + intent, slots, err := stiHandler(speechReq) + if err != nil { + if err.Error() == "inference not understood" { + logger.Println("No intent was matched") + ttr.IntentPass(req, "intent_system_unmatched", "voice processing error", map[string]string{"error": err.Error()}, true) + return nil, nil + } + logger.Println(err) + ttr.IntentPass(req, "intent_system_noaudio", "voice processing error", map[string]string{"error": err.Error()}, true) + return nil, nil + } + ttr.ParamCheckerSlotsEnUS(req, intent, slots, speechReq.IsOpus, speechReq.Device) + return nil, nil + } + if !successMatched { + if vars.APIConfig.Knowledge.IntentGraph && vars.APIConfig.Knowledge.Enable { + logger.Println("Making LLM request for device " + req.Device + "...") + _, err := ttr.StreamingKGSim(req, req.Device, transcribedText, false) + if err != nil { + logger.Println("LLM error: " + err.Error()) + logger.LogUI("LLM error: " + err.Error()) + ttr.IntentPass(req, "intent_system_unmatched", transcribedText, map[string]string{"": ""}, false) + ttr.KGSim(req.Device, "There was an error getting a response from the L L M. Check the logs in the web interface.") + } + logger.Println("Bot " + speechReq.Device + " request served.") + return nil, nil + } + logger.Println("No intent was matched.") + ttr.IntentPass(req, "intent_system_unmatched", transcribedText, map[string]string{"": ""}, false) + return nil, nil + } + logger.Println("Bot " + speechReq.Device + " request served.") + return nil, nil +} diff --git a/chipper/pkg/wirepod/preqs/intent_graph.go b/chipper/pkg/wirepod/preqs/intent_graph.go new file mode 100644 index 0000000..83911e8 --- /dev/null +++ b/chipper/pkg/wirepod/preqs/intent_graph.go @@ -0,0 +1,81 @@ +package processreqs + +import ( + "strings" + + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vars" + "github.com/kercre123/wire-pod/chipper/pkg/vtt" + sr "github.com/kercre123/wire-pod/chipper/pkg/wirepod/speechrequest" + ttr "github.com/kercre123/wire-pod/chipper/pkg/wirepod/ttr" +) + +func (s *Server) ProcessIntentGraph(req *vtt.IntentGraphRequest) (*vtt.IntentGraphResponse, error) { + var successMatched bool + speechReq := sr.ReqToSpeechRequest(req) + var transcribedText string + if !isSti { + var err error + transcribedText, err = sttHandler(speechReq) + if err != nil { + ttr.IntentPass(req, "intent_system_noaudio", "voice processing error: "+err.Error(), map[string]string{"error": err.Error()}, true) + return nil, nil + } + if strings.TrimSpace(transcribedText) == "" { + ttr.IntentPass(req, "intent_system_noaudio", "", map[string]string{}, false) + return nil, nil + } + successMatched = ttr.ProcessTextAll(req, transcribedText, vars.IntentList, speechReq.IsOpus) + } else { + intent, slots, err := stiHandler(speechReq) + if err != nil { + if err.Error() == "inference not understood" { + logger.Println("Bot " + speechReq.Device + " No intent was matched") + ttr.IntentPass(req, "intent_system_unmatched", "voice processing error", map[string]string{"error": err.Error()}, true) + return nil, nil + } + logger.Println(err) + ttr.IntentPass(req, "intent_system_noaudio", "voice processing error", map[string]string{"error": err.Error()}, true) + return nil, nil + } + ttr.ParamCheckerSlotsEnUS(req, intent, slots, speechReq.IsOpus, speechReq.Device) + return nil, nil + } + // if !successMatched { + // logger.Println("No intent was matched.") + // if vars.APIConfig.Knowledge.Enable && vars.APIConfig.Knowledge.Provider == "openai" && len([]rune(transcribedText)) >= 8 { + // apiResponse := openaiRequest(transcribedText) + // response := &pb.IntentGraphResponse{ + // Session: req.Session, + // DeviceId: req.Device, + // ResponseType: pb.IntentGraphMode_KNOWLEDGE_GRAPH, + // SpokenText: apiResponse, + // QueryText: transcribedText, + // IsFinal: true, + // } + // req.Stream.Send(response) + // return nil, nil + // } + // ttr.IntentPass(req, "intent_system_unmatched", transcribedText, map[string]string{"": ""}, false) + // return nil, nil + // } + if !successMatched { + if vars.APIConfig.Knowledge.IntentGraph && vars.APIConfig.Knowledge.Enable { + logger.Println("Making LLM request for device " + req.Device + "...") + _, err := ttr.StreamingKGSim(req, req.Device, transcribedText, false) + if err != nil { + logger.Println("LLM error: " + err.Error()) + logger.LogUI("LLM error: " + err.Error()) + ttr.IntentPass(req, "intent_system_unmatched", transcribedText, map[string]string{"": ""}, false) + ttr.KGSim(req.Device, "There was an error getting a response from the L L M. Check the logs in the web interface.") + } + logger.Println("Bot " + speechReq.Device + " request served.") + return nil, nil + } + logger.Println("No intent was matched.") + ttr.IntentPass(req, "intent_system_unmatched", transcribedText, map[string]string{"": ""}, false) + return nil, nil + } + logger.Println("Bot " + speechReq.Device + " request served.") + return nil, nil +} diff --git a/chipper/pkg/wirepod/preqs/knowledgegraph.go b/chipper/pkg/wirepod/preqs/knowledgegraph.go new file mode 100644 index 0000000..0c59445 --- /dev/null +++ b/chipper/pkg/wirepod/preqs/knowledgegraph.go @@ -0,0 +1,120 @@ +package processreqs + +import ( + "encoding/json" + "strings" + + pb "github.com/digital-dream-labs/api/go/chipperpb" + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vars" + "github.com/kercre123/wire-pod/chipper/pkg/vtt" + sr "github.com/kercre123/wire-pod/chipper/pkg/wirepod/speechrequest" + ttr "github.com/kercre123/wire-pod/chipper/pkg/wirepod/ttr" + "github.com/pkg/errors" + "github.com/soundhound/houndify-sdk-go" +) + +var HKGclient houndify.Client +var HoundEnable bool = true + +func ParseSpokenResponse(serverResponseJSON string) (string, error) { + result := make(map[string]interface{}) + err := json.Unmarshal([]byte(serverResponseJSON), &result) + if err != nil { + logger.Println(err.Error()) + return "", errors.New("failed to decode json") + } + if !strings.EqualFold(result["Status"].(string), "OK") { + return "", errors.New(result["ErrorMessage"].(string)) + } + if result["NumToReturn"].(float64) < 1 { + return "", errors.New("no results to return") + } + return result["AllResults"].([]interface{})[0].(map[string]interface{})["SpokenResponseLong"].(string), nil +} + +func InitKnowledge() { + if vars.APIConfig.Knowledge.Enable && vars.APIConfig.Knowledge.Provider == "houndify" { + if vars.APIConfig.Knowledge.ID == "" || vars.APIConfig.Knowledge.Key == "" { + vars.APIConfig.Knowledge.Enable = false + logger.Println("Houndify Client Key or ID was empty, not initializing kg client") + } else { + HKGclient = houndify.Client{ + ClientID: vars.APIConfig.Knowledge.ID, + ClientKey: vars.APIConfig.Knowledge.Key, + } + HKGclient.EnableConversationState() + logger.Println("Initialized Houndify client") + } + } +} + +var NoResult string = "NoResultCommand" +var NoResultSpoken string + +func houndifyKG(req sr.SpeechRequest) string { + var apiResponse string + if vars.APIConfig.Knowledge.Enable && vars.APIConfig.Knowledge.Provider == "houndify" { + logger.Println("Sending request to Houndify...") + serverResponse := StreamAudioToHoundify(req, HKGclient) + apiResponse, _ = ParseSpokenResponse(serverResponse) + logger.Println("Houndify response: " + apiResponse) + } else { + apiResponse = "Houndify is not enabled." + logger.Println("Houndify is not enabled.") + } + return apiResponse +} + +func streamingKG(req *vtt.KnowledgeGraphRequest, speechReq sr.SpeechRequest) string { + // have him start "thinking" right after the text is transcribed + transcribedText, err := sttHandler(speechReq) + if err != nil { + return "There was an error." + } + kg := pb.KnowledgeGraphResponse{ + Session: req.Session, + DeviceId: req.Device, + CommandType: NoResult, + SpokenText: "bla bla bla bla bla bla bla bla bla bla", + } + req.Stream.Send(&kg) + _, err = ttr.StreamingKGSim(req, req.Device, transcribedText, true) + if err != nil { + logger.Println("LLM error: " + err.Error()) + } + logger.Println("(KG) Bot " + speechReq.Device + " request served.") + return "" +} + +// Takes a SpeechRequest, figures out knowledgegraph provider, makes request, returns API response +func KgRequest(req *vtt.KnowledgeGraphRequest, speechReq sr.SpeechRequest) string { + if vars.APIConfig.Knowledge.Enable { + if vars.APIConfig.Knowledge.Provider == "houndify" { + return houndifyKG(speechReq) + } + } + return "Knowledge graph is not enabled. This can be enabled in the web interface." +} + +func (s *Server) ProcessKnowledgeGraph(req *vtt.KnowledgeGraphRequest) (*vtt.KnowledgeGraphResponse, error) { + InitKnowledge() + speechReq := sr.ReqToSpeechRequest(req) + if vars.APIConfig.Knowledge.Enable && vars.APIConfig.Knowledge.Provider != "houndify" { + streamingKG(req, speechReq) + } else { + apiResponse := KgRequest(req, speechReq) + kg := pb.KnowledgeGraphResponse{ + Session: req.Session, + DeviceId: req.Device, + CommandType: NoResult, + SpokenText: apiResponse, + } + logger.Println("(KG) Bot " + speechReq.Device + " request served.") + if err := req.Stream.Send(&kg); err != nil { + return nil, err + } + } + return nil, nil + +} diff --git a/chipper/pkg/wirepod/preqs/server.go b/chipper/pkg/wirepod/preqs/server.go new file mode 100644 index 0000000..cd18626 --- /dev/null +++ b/chipper/pkg/wirepod/preqs/server.go @@ -0,0 +1,76 @@ +package processreqs + +import ( + "fmt" + + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vars" + sr "github.com/kercre123/wire-pod/chipper/pkg/wirepod/speechrequest" + ttr "github.com/kercre123/wire-pod/chipper/pkg/wirepod/ttr" +) + +// Server stores the config +type Server struct{} + +var VoiceProcessor = "" + +type JsonIntent struct { + Name string `json:"name"` + Keyphrases []string `json:"keyphrases"` + RequireExactMatch bool `json:"requiresexact"` +} + +var sttLanguage string = "en-US" + +// speech-to-text +var sttHandler func(sr.SpeechRequest) (string, error) + +// speech-to-intent (rhino) +var stiHandler func(sr.SpeechRequest) (string, map[string]string, error) + +var isSti bool = false + +func ReloadVosk() { + if vars.APIConfig.STT.Service == "vosk" || vars.APIConfig.STT.Service == "whisper.cpp" { + vars.SttInitFunc() + vars.IntentList, _ = vars.LoadIntents() + } +} + +// New returns a new server +func New(InitFunc func() error, SttHandler interface{}, voiceProcessor string) (*Server, error) { + + // Decide the TTS language + if voiceProcessor != "vosk" && voiceProcessor != "whisper.cpp" { + vars.APIConfig.STT.Language = "en-US" + } + sttLanguage = vars.APIConfig.STT.Language + vars.IntentList, _ = vars.LoadIntents() + logger.Println("Initiating " + voiceProcessor + " voice processor with language " + sttLanguage) + vars.SttInitFunc = InitFunc + err := InitFunc() + if err != nil { + return nil, err + } + + // SttHandler can either be `func(sr.SpeechRequest) (string, error)` or `func (sr.SpeechRequest) (string, map[string]string, error)` + // second one exists to accomodate Rhino + + // check function type + if str, is := SttHandler.(func(sr.SpeechRequest) (string, error)); is { + sttHandler = str + } else if str, is := SttHandler.(func(sr.SpeechRequest) (string, map[string]string, error)); is { + stiHandler = str + isSti = true + } else { + return nil, fmt.Errorf("stthandler not of correct type") + } + + // Initiating the chosen voice processor and load intents from json + VoiceProcessor = voiceProcessor + + // Load plugins + ttr.LoadPlugins() + + return &Server{}, err +} diff --git a/chipper/pkg/wirepod/preqs/stream_houndify.go b/chipper/pkg/wirepod/preqs/stream_houndify.go new file mode 100644 index 0000000..0fa49dd --- /dev/null +++ b/chipper/pkg/wirepod/preqs/stream_houndify.go @@ -0,0 +1,61 @@ +package processreqs + +import ( + "fmt" + "io" + + sr "github.com/kercre123/wire-pod/chipper/pkg/wirepod/speechrequest" + "github.com/soundhound/houndify-sdk-go" +) + +func StreamAudioToHoundify(sreq sr.SpeechRequest, client houndify.Client) string { + var err error + rp, wp := io.Pipe() + req := houndify.VoiceRequest{ + AudioStream: rp, + UserID: sreq.Device, + RequestID: sreq.Session, + } + done := make(chan bool) + speechDone := false + go func(wp *io.PipeWriter) { + defer wp.Close() + + for { + select { + case <-done: + return + default: + var chunk []byte + chunk, err = sreq.GetNextStreamChunkOpus() + speechDone, _ = sreq.DetectEndOfSpeech() + if err != nil { + fmt.Println("End of stream") + return + } + wp.Write(chunk) + if speechDone { + return + } + } + } + }(wp) + + partialTranscripts := make(chan houndify.PartialTranscript) + go func() { + for partial := range partialTranscripts { + if *partial.SafeToStopAudio { + fmt.Println("SafeToStopAudio received") + done <- true + return + } + } + }() + + serverResponse, err := client.VoiceSearch(req, partialTranscripts) + if err != nil { + fmt.Println(err) + fmt.Println(serverResponse) + } + return serverResponse +} diff --git a/chipper/pkg/wirepod/sdkapp/bcassume.go b/chipper/pkg/wirepod/sdkapp/bcassume.go new file mode 100644 index 0000000..7778625 --- /dev/null +++ b/chipper/pkg/wirepod/sdkapp/bcassume.go @@ -0,0 +1,91 @@ +package sdkapp + +import ( + "log" + "time" + + "github.com/fforchino/vector-go-sdk/pkg/vectorpb" +) + +func assumeBehaviorControl(robot Robot, robotIndex int, priority string) { + var controlRequest *vectorpb.BehaviorControlRequest + if priority == "high" { + controlRequest = &vectorpb.BehaviorControlRequest{ + RequestType: &vectorpb.BehaviorControlRequest_ControlRequest{ + ControlRequest: &vectorpb.ControlRequest{ + Priority: vectorpb.ControlRequest_OVERRIDE_BEHAVIORS, + }, + }, + } + } else { + controlRequest = &vectorpb.BehaviorControlRequest{ + RequestType: &vectorpb.BehaviorControlRequest_ControlRequest{ + ControlRequest: &vectorpb.ControlRequest{ + Priority: vectorpb.ControlRequest_DEFAULT, + }, + }, + } + } + go func() { + start := make(chan bool) + stop := make(chan bool) + robots[robotIndex].BcAssumption = true + go func() { + // * begin - modified from official vector-go-sdk + r, err := robot.Vector.Conn.BehaviorControl( + robot.Ctx, + ) + if err != nil { + log.Println(err) + return + } + + if err := r.Send(controlRequest); err != nil { + log.Println(err) + return + } + + for { + ctrlresp, err := r.Recv() + if err != nil { + log.Println(err) + return + } + if ctrlresp.GetControlGrantedResponse() != nil { + start <- true + break + } + } + + for { + select { + case <-stop: + if err := r.Send( + &vectorpb.BehaviorControlRequest{ + RequestType: &vectorpb.BehaviorControlRequest_ControlRelease{ + ControlRelease: &vectorpb.ControlRelease{}, + }, + }, + ); err != nil { + log.Println(err) + return + } + return + default: + continue + } + } + // * end - modified from official vector-go-sdk + }() + for range start { + for { + if robots[robotIndex].BcAssumption { + time.Sleep(time.Millisecond * 500) + } else { + break + } + } + stop <- true + } + }() +} diff --git a/chipper/pkg/wirepod/sdkapp/jdocspinger.go b/chipper/pkg/wirepod/sdkapp/jdocspinger.go new file mode 100644 index 0000000..43f23c6 --- /dev/null +++ b/chipper/pkg/wirepod/sdkapp/jdocspinger.go @@ -0,0 +1,233 @@ +package sdkapp + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "os" + "strings" + "sync" + "time" + + "github.com/fforchino/vector-go-sdk/pkg/vectorpb" + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vars" + "github.com/kercre123/zeroconf" +) + +var JdocsPingerBots struct { + mu sync.Mutex + Robots []JdocsPingerRobot +} + +type JdocsPingerRobot struct { + ESN string `json:"esn"` + GUID string `json:"guid"` + IP string `json:"ip"` + TimeSinceLastCheck int `json:"timesince"` + Stopped bool `json:"stopped"` +} + +// the escape pod CA cert only gets appended to the cert store when a jdocs connection is created +// this doesn't happen at every boot +// this utilizes Vector's connCheck to see if a bot has disconnected from the server for more than 10 seconds +// if it has, it will pull jdocs from the bot which will cause the CA cert to get appended to the store + +// setting JDOCS_PINGER_ENABLED=false will disable jdocs pinger +var PingerEnabled bool = true + +func pingJdocs(target string) { + ctx := context.Background() + target = strings.Split(target, ":")[0] + var serial string + matched := false + for _, robot := range vars.BotInfo.Robots { + if strings.TrimSpace(strings.ToLower(robot.IPAddress)) == strings.TrimSpace(strings.ToLower(target)) { + matched = true + serial = robot.Esn + } + } + if !matched { + logger.Println("jdocs pinger error: serial did not match any bot in bot json") + return + } + robotTmp, err := NewWP(serial, false) + if err != nil { + logger.Println(err) + return + } + _, err = robotTmp.Conn.BatteryState(ctx, &vectorpb.BatteryStateRequest{}) + if err != nil { + robotTmp, err = NewWP(serial, true) + if err != nil { + logger.Println(err) + logger.Println("Error pinging jdocs") + return + } + _, err = robotTmp.Conn.BatteryState(ctx, &vectorpb.BatteryStateRequest{}) + if err != nil { + logger.Println("Error pinging jdocs, likely unauthenticated") + return + } + } + resp, err := robotTmp.Conn.PullJdocs(ctx, &vectorpb.PullJdocsRequest{ + JdocTypes: []vectorpb.JdocType{vectorpb.JdocType_ROBOT_SETTINGS}, + }) + if err != nil { + logger.Println("Failed to pull jdocs: ", err) + return + } + logger.Println("Successfully got jdocs from " + serial) + // write to file + var jdoc vars.AJdoc + jdoc.DocVersion = resp.NamedJdocs[0].Doc.DocVersion + jdoc.FmtVersion = resp.NamedJdocs[0].Doc.FmtVersion + jdoc.ClientMetadata = resp.NamedJdocs[0].Doc.ClientMetadata + jdoc.JsonDoc = resp.NamedJdocs[0].Doc.JsonDoc + vars.AddJdoc("vic:"+serial, "vic.RobotSettings", jdoc) +} + +func InitJdocsPinger() { + if os.Getenv("JDOCS_PINGER_ENABLED") == "false" { + logger.Println("Jdocs pinger is disabled (JDOCS_PINGER_ENABLED=false)") + PingerEnabled = false + return + } + fmt.Println("Starting jdocs pinger ticker") + go func() { + for { + JdocsPingerBots.mu.Lock() + for i, bot := range JdocsPingerBots.Robots { + if !bot.Stopped { + JdocsPingerBots.Robots[i].TimeSinceLastCheck = JdocsPingerBots.Robots[i].TimeSinceLastCheck + 1 + if JdocsPingerBots.Robots[i].TimeSinceLastCheck > 15 { + logger.Println("Haven't received a conn check from " + bot.ESN + " in 15 seconds, will ping jdocs on next check") + JdocsPingerBots.Robots[i].Stopped = true + } + } + } + JdocsPingerBots.mu.Unlock() + time.Sleep(time.Second) + } + }() +} + +func ShouldPingJdocs(target string) bool { + var esn, guid, botip string + matched := false + for _, bot := range vars.BotInfo.Robots { + if target == bot.IPAddress { + esn = bot.Esn + guid = bot.GUID + botip = bot.IPAddress + matched = true + break + } + } + if !matched { + return false + } + JdocsPingerBots.mu.Lock() + defer JdocsPingerBots.mu.Unlock() + for i, bot := range JdocsPingerBots.Robots { + if esn == bot.ESN { + if bot.Stopped { + JdocsPingerBots.Robots[i].TimeSinceLastCheck = 0 + JdocsPingerBots.Robots[i].Stopped = false + return true + } else { + JdocsPingerBots.Robots[i].TimeSinceLastCheck = 0 + return false + } + } + } + // below will only execute if esn doesn't match bots in list + newBot := JdocsPingerRobot{ + ESN: esn, + GUID: guid, + IP: botip, + TimeSinceLastCheck: 0, + Stopped: false, + } + JdocsPingerBots.Robots = append(JdocsPingerBots.Robots, newBot) + return true +} + +func connCheck(w http.ResponseWriter, r *http.Request) { + switch { + default: + http.Error(w, "not found", http.StatusNotFound) + return + case strings.Contains(r.URL.Path, "/ok"): + if r.FormValue("runMDNS") == "true" { + RunMDNS("t") + fmt.Fprintf(w, "ran") + return + } + if PingerEnabled { + //logger.Println("connCheck request from " + r.RemoteAddr) + robotTarget := strings.Split(r.RemoteAddr, ":")[0] + jsonB, _ := json.Marshal(vars.BotInfo) + json := string(jsonB) + if strings.Contains(json, strings.TrimSpace(robotTarget)) { + ping := ShouldPingJdocs(robotTarget) + if ping { + pingJdocs(robotTarget) + } + } else { + go RunMDNS(strings.Split(r.RemoteAddr, ":")[0]) + } + } + fmt.Fprintf(w, "ok") + return + } +} + +var MDNSAlreadyRun []string + +var RunningMDNS bool + +func RunMDNS(botIP string) { + for _, ip := range MDNSAlreadyRun { + if ip == botIP { + return + } + } + fmt.Println("Running mDNS...") + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + entries := make(chan *zeroconf.ServiceEntry) + resolver, _ := zeroconf.NewResolver() + resolver.Browse(ctx, "_ankivector._tcp", "local", entries) + for entry := range entries { + robotID := strings.Split(entry.HostName, ".")[0] + matched := false + for _, rinf := range vars.RecurringInfo { + if rinf.ID == robotID { + vars.AddToRInfo(rinf.ESN, robotID, fmt.Sprint(entry.AddrIPv4[0])) + for i, rob := range vars.BotInfo.Robots { + if rob.Esn == rinf.ESN { + vars.BotInfo.Robots[i].IPAddress = fmt.Sprint(entry.AddrIPv4[0]) + jsonBytes, _ := json.Marshal(vars.BotInfo) + fmt.Println("Updating robot " + robotID) + go os.WriteFile(vars.BotInfoPath, jsonBytes, 0777) + } + } + go func() { + // wait for escapepod.local trasmit + if vars.APIConfig.Server.EPConfig { + time.Sleep(time.Second) + } + pingJdocs(fmt.Sprint(entry.AddrIPv4[0])) + }() + matched = true + break + } + } + if !matched { + MDNSAlreadyRun = append(MDNSAlreadyRun, botIP) + } + } + fmt.Println("Done running mDNS") +} diff --git a/chipper/pkg/wirepod/sdkapp/robot.go b/chipper/pkg/wirepod/sdkapp/robot.go new file mode 100644 index 0000000..028d5f0 --- /dev/null +++ b/chipper/pkg/wirepod/sdkapp/robot.go @@ -0,0 +1,217 @@ +package sdkapp + +import ( + "context" + "errors" + "fmt" + "strconv" + "strings" + "time" + + "github.com/digital-dream-labs/hugh/grpc/client" + "github.com/fforchino/vector-go-sdk/pkg/vector" + "github.com/fforchino/vector-go-sdk/pkg/vectorpb" + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vars" +) + +var robots []Robot +var timerStopIndexes []int +var inhibitCreation bool + +type Robot struct { + ESN string + GUID string + Target string + Vector *vector.Vector + BcAssumption bool + CamStreaming bool + EventStreamClient vectorpb.ExternalInterface_EventStreamClient + EventsStreaming bool + StimState float32 + ConnTimer int32 + Ctx context.Context +} + +func newRobot(serial string) (Robot, int, error) { + inhibitCreation = true + var RobotObj Robot + + // generate context + RobotObj.Ctx = context.Background() + + // find robot info in BotInfo + matched := false + for _, robot := range vars.BotInfo.Robots { + if strings.EqualFold(serial, robot.Esn) { + RobotObj.ESN = strings.TrimSpace(strings.ToLower(serial)) + RobotObj.Target = robot.IPAddress + ":443" + matched = true + if robot.GUID == "" { + robot.GUID = vars.BotInfo.GlobalGUID + RobotObj.GUID = vars.BotInfo.GlobalGUID + } else { + RobotObj.GUID = robot.GUID + } + logger.Println("Connecting to " + serial + " with GUID " + RobotObj.GUID) + } + } + if !matched { + inhibitCreation = false + return RobotObj, 0, fmt.Errorf("error: robot not found in SDK info file") + } + + // create Vector instance + var err error + RobotObj.Vector, err = vector.New( + vector.WithTarget(RobotObj.Target), + vector.WithSerialNo(RobotObj.ESN), + vector.WithToken(RobotObj.GUID), + ) + if err != nil { + inhibitCreation = false + return RobotObj, 0, err + } + + // connection check + _, err = RobotObj.Vector.Conn.BatteryState(context.Background(), &vectorpb.BatteryStateRequest{}) + if err != nil { + inhibitCreation = false + return RobotObj, 0, err + } + + // create client for event stream + RobotObj.EventStreamClient, err = RobotObj.Vector.Conn.EventStream( + RobotObj.Ctx, + &vectorpb.EventRequest{ + ListType: &vectorpb.EventRequest_WhiteList{ + WhiteList: &vectorpb.FilterList{ + // this will be used only for stimulation graph for now + List: []string{"stimulation_info"}, + }, + }, + }, + ) + if err != nil { + inhibitCreation = false + return RobotObj, 0, err + } + RobotObj.CamStreaming = false + RobotObj.EventsStreaming = false + + // we have confirmed robot connection works, append to list of bots + robots = append(robots, RobotObj) + robotIndex := len(robots) - 1 + + // begin inactivity timer + go connTimer(robotIndex) + + inhibitCreation = false + return RobotObj, robotIndex, nil +} + +func getRobot(serial string) (Robot, int, error) { + // look in robot list + for { + if !inhibitCreation { + break + } + time.Sleep(time.Second / 2) + } + for index, robot := range robots { + if strings.EqualFold(serial, robot.ESN) { + return robot, index, nil + } + } + return newRobot(serial) +} + +// if connection is inactive for more than 5 minutes, remove robot +// run this as a goroutine +func connTimer(ind int) { + // Check if the index is in the list + if len(robots) <= ind { + return + } + + robots[ind].ConnTimer = 0 + for { + time.Sleep(time.Second) + // check if timer needs to be stopped + for _, num := range timerStopIndexes { + if num == ind { + logger.Println("Conn timer for robot index " + strconv.Itoa(ind) + " stopping") + var newIndexes []int + for _, num := range timerStopIndexes { + if num != ind { + newIndexes = append(newIndexes, num) + } + } + timerStopIndexes = newIndexes + return + } + } + if robots[ind].ConnTimer >= 300 { + logger.Println("Closing SDK connection for " + robots[ind].ESN + ", source: connTimer") + removeRobot(robots[ind].ESN, "connTimer") + return + } + robots[ind].ConnTimer = robots[ind].ConnTimer + 1 + } +} + +func removeRobot(serial, source string) { + inhibitCreation = true + var newRobots []Robot + for ind, robot := range robots { + if !strings.EqualFold(serial, robot.ESN) { + newRobots = append(newRobots, robot) + } else { + if source == "server" { + timerStopIndexes = append(timerStopIndexes, ind) + } + robots[ind].CamStreaming = false + robots[ind].EventsStreaming = false + robots[ind].BcAssumption = false + // give time for all of that to stop + time.Sleep(time.Second * 3) + } + } + robots = newRobots + inhibitCreation = false +} + +func NewWP(serial string, useGlobal bool) (*vector.Vector, error) { + var target, guid string + if serial == "" { + return nil, fmt.Errorf("serial string missing") + } + matched := false + for _, robot := range vars.BotInfo.Robots { + if strings.EqualFold(serial, robot.Esn) { + matched = true + target = robot.IPAddress + ":443" + guid = robot.GUID + break + } + } + if !matched { + logger.Println("serial did not match any bot in bot json") + return nil, errors.New("serial did not match any bot in bot json") + } + c, err := client.New( + client.WithTarget(target), + client.WithInsecureSkipVerify(), + ) + if err != nil { + return nil, err + } + if err := c.Connect(); err != nil { + return nil, err + } + return vector.New( + vector.WithTarget(target), + vector.WithSerialNo(serial), + vector.WithToken(guid), + ) +} diff --git a/chipper/pkg/wirepod/sdkapp/server.go b/chipper/pkg/wirepod/sdkapp/server.go new file mode 100644 index 0000000..9543c06 --- /dev/null +++ b/chipper/pkg/wirepod/sdkapp/server.go @@ -0,0 +1,683 @@ +package sdkapp + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "image" + "image/jpeg" + "io" + "net/http" + "os" + "path/filepath" + "runtime" + "strconv" + "strings" + "time" + + "github.com/fforchino/vector-go-sdk/pkg/vectorpb" + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/scripting" + "github.com/kercre123/wire-pod/chipper/pkg/vars" +) + +var serverFiles string = "./webroot/sdkapp" + +func SdkapiHandler(w http.ResponseWriter, r *http.Request) { + robotObj, robotIndex, err := getRobot(r.FormValue("serial")) + robot := robotObj.Vector + ctx := robotObj.Ctx + if r.URL.Path != "/api-sdk/get_sdk_info" && r.URL.Path != "/api-sdk/debug" { + if err != nil { + fmt.Fprint(w, "error: "+err.Error()) + return + } + robots[robotIndex].ConnTimer = 0 + } + switch { + default: + http.Error(w, "not found", http.StatusNotFound) + return + case r.URL.Path == "/api-sdk/conn_test": + // getRobot does connection check and will return error if failed + fmt.Fprint(w, "success") + return + case r.URL.Path == "/api-sdk/alexa_sign_in": + robot.Conn.AlexaOptIn(ctx, &vectorpb.AlexaOptInRequest{ + OptIn: true, + }) + fmt.Fprintf(w, "success") + return + case r.URL.Path == "/api-sdk/alexa_sign_out": + robot.Conn.AlexaOptIn(ctx, &vectorpb.AlexaOptInRequest{ + OptIn: false, + }) + fmt.Fprintf(w, "success") + return + case r.URL.Path == "/api-sdk/cloud_intent": + intent := r.FormValue("intent") + robot.Conn.AppIntent(ctx, + &vectorpb.AppIntentRequest{ + Intent: intent, + }, + ) + fmt.Fprintf(w, "done") + return + case r.URL.Path == "/api-sdk/eye_color": + eye_color := r.FormValue("color") + setPresetEyeColor(robotObj, eye_color) + fmt.Fprintf(w, "done") + return + case r.URL.Path == "/api-sdk/custom_eye_color": + hue := r.FormValue("hue") + sat := r.FormValue("sat") + setCustomEyeColor(robotObj, hue, sat) + fmt.Fprintf(w, hue+sat) + return + case r.URL.Path == "/api-sdk/volume": + volume := r.FormValue("volume") + setSettingSDKintbool(robotObj, "master_volume", volume) + fmt.Fprintf(w, "done") + return + case r.URL.Path == "/api-sdk/locale": + locale := r.FormValue("locale") + setSettingSDKstring(robotObj, "locale", locale) + fmt.Fprintf(w, "done") + return + case r.URL.Path == "/api-sdk/location": + location := r.FormValue("location") + setSettingSDKstring(robotObj, "default_location", location) + fmt.Fprintf(w, "done") + return + case r.URL.Path == "/api-sdk/timezone": + timezone := r.FormValue("timezone") + setSettingSDKstring(robotObj, "time_zone", timezone) + fmt.Fprintf(w, "done") + return + case r.URL.Path == "/api-sdk/get_sdk_info": + if len(vars.BotInfo.Robots) == 0 { + http.Error(w, "no bots are authenticated", http.StatusInternalServerError) + return + } + jsonBytes, err := json.Marshal(vars.BotInfo) + if err != nil { + fmt.Fprintf(w, "error marshaling json") + return + } + fmt.Fprint(w, string(jsonBytes)) + return + case r.URL.Path == "/api-sdk/get_sdk_settings": + i := 0 + for { + resp, err := robot.Conn.PullJdocs(ctx, &vectorpb.PullJdocsRequest{ + JdocTypes: []vectorpb.JdocType{vectorpb.JdocType_ROBOT_SETTINGS}, + }) + if err != nil { + w.Write([]byte(err.Error())) + return + } + if strings.Contains(resp.NamedJdocs[0].Doc.JsonDoc, "BStat.ReactedToTriggerWord") { + time.Sleep(time.Second / 2) + if i > 3 { + logger.Println("Bot refuses to return RobotSettings jdoc...") + logger.Println("Returned Jdoc: ", resp.NamedJdocs[0].Doc.JsonDoc) + w.Write([]byte("error: bot refuses to return robotsettings")) + return + } + i = i + 1 + continue + } + json := resp.NamedJdocs[0].Doc.JsonDoc + var ajdoc vars.AJdoc + ajdoc.DocVersion = resp.NamedJdocs[0].Doc.DocVersion + ajdoc.FmtVersion = resp.NamedJdocs[0].Doc.FmtVersion + ajdoc.JsonDoc = resp.NamedJdocs[0].Doc.JsonDoc + vars.AddJdoc("vic:"+robotObj.ESN, "vic.RobotSettings", ajdoc) + logger.Println("Updating vic.RobotSettings (source: sdkapp)") + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "application/octet-stream") + w.Write([]byte(json)) + return + } + + case r.URL.Path == "/api-sdk/play_sound": + file, _, err := r.FormFile("sound") + if err != nil { + println("Error retrieving the file:", err) + return + } + defer file.Close() + + // Lê o conteúdo do arquivo em um slice de bytes + pcmFile, err := io.ReadAll(file) + if err != nil { + println("Error reading the file:", err) + return + } + + var audioChunks [][]byte + for len(pcmFile) >= 1024 { + audioChunks = append(audioChunks, pcmFile[:1024]) + pcmFile = pcmFile[1024:] + } + + var audioClient vectorpb.ExternalInterface_ExternalAudioStreamPlaybackClient + audioClient, _ = robot.Conn.ExternalAudioStreamPlayback(ctx) + audioClient.SendMsg(&vectorpb.ExternalAudioStreamRequest{ + AudioRequestType: &vectorpb.ExternalAudioStreamRequest_AudioStreamPrepare{ + AudioStreamPrepare: &vectorpb.ExternalAudioStreamPrepare{ + AudioFrameRate: 8000, + AudioVolume: uint32(100), + }, + }, + }) + + for _, chunk := range audioChunks { + audioClient.SendMsg(&vectorpb.ExternalAudioStreamRequest{ + AudioRequestType: &vectorpb.ExternalAudioStreamRequest_AudioStreamChunk{ + AudioStreamChunk: &vectorpb.ExternalAudioStreamChunk{ + AudioChunkSizeBytes: uint32(len(chunk)), + AudioChunkSamples: chunk, + }, + }, + }) + time.Sleep(time.Millisecond * 60) + } + + audioClient.SendMsg(&vectorpb.ExternalAudioStreamRequest{ + AudioRequestType: &vectorpb.ExternalAudioStreamRequest_AudioStreamComplete{ + AudioStreamComplete: &vectorpb.ExternalAudioStreamComplete{}, + }, + }) + + return + + case r.URL.Path == "/api-sdk/get_battery": + // Ensure the endpoint times out after 15 seconds + ctx := r.Context() // Get the request context + ctx, cancel := context.WithTimeout(ctx, 15*time.Second) + defer cancel() + + resp, err := robot.Conn.BatteryState(ctx, &vectorpb.BatteryStateRequest{}) + if err != nil { + fmt.Fprint(w, "error: "+err.Error()) + return + } + jsonBytes, err := json.Marshal(resp) + if err != nil { + fmt.Fprint(w, "error: "+err.Error()) + return + } + fmt.Fprint(w, string(jsonBytes)) + return + case r.URL.Path == "/api-sdk/time_format_12": + setSettingSDKintbool(robotObj, "clock_24_hour", "false") + fmt.Fprintf(w, "done") + return + case r.URL.Path == "/api-sdk/time_format_24": + setSettingSDKintbool(robotObj, "clock_24_hour", "true") + fmt.Fprintf(w, "done") + return + case r.URL.Path == "/api-sdk/temp_c": + setSettingSDKintbool(robotObj, "temp_is_fahrenheit", "false") + fmt.Fprintf(w, "done") + return + case r.URL.Path == "/api-sdk/temp_f": + setSettingSDKintbool(robotObj, "temp_is_fahrenheit", "true") + fmt.Fprintf(w, "done") + return + case r.URL.Path == "/api-sdk/button_hey_vector": + setSettingSDKintbool(robotObj, "button_wakeword", "0") + fmt.Fprintf(w, "done") + return + case r.URL.Path == "/api-sdk/button_alexa": + setSettingSDKintbool(robotObj, "button_wakeword", "1") + fmt.Fprintf(w, "done") + return + case r.URL.Path == "/api-sdk/assume_behavior_control": + fmt.Fprintf(w, "success") + assumeBehaviorControl(robotObj, robotIndex, r.FormValue("priority")) + return + case r.URL.Path == "/api-sdk/release_behavior_control": + robots[robotIndex].BcAssumption = false + fmt.Fprintf(w, "success") + return + case r.URL.Path == "/api-sdk/say_text": + if len([]rune(r.FormValue("text"))) >= 600 { + fmt.Fprint(w, "error: text is too long") + } + robot.Conn.SayText( + ctx, + &vectorpb.SayTextRequest{ + DurationScalar: 1, + UseVectorVoice: true, + Text: r.FormValue("text"), + }, + ) + fmt.Fprintf(w, "success") + return + case r.URL.Path == "/api-sdk/move_wheels": + lw, _ := strconv.Atoi(r.FormValue("lw")) + rw, _ := strconv.Atoi(r.FormValue("rw")) + robot.Conn.DriveWheels(ctx, + &vectorpb.DriveWheelsRequest{ + LeftWheelMmps: float32(lw), + RightWheelMmps: float32(rw), + LeftWheelMmps2: float32(lw), + RightWheelMmps2: float32(rw), + }, + ) + fmt.Fprintf(w, "") + return + case r.URL.Path == "/api-sdk/move_lift": + speed, _ := strconv.Atoi(r.FormValue("speed")) + robot.Conn.MoveLift( + ctx, + &vectorpb.MoveLiftRequest{ + SpeedRadPerSec: float32(speed), + }, + ) + fmt.Fprintf(w, "") + return + case r.URL.Path == "/api-sdk/move_head": + speed, _ := strconv.Atoi(r.FormValue("speed")) + robot.Conn.MoveHead( + ctx, + &vectorpb.MoveHeadRequest{ + SpeedRadPerSec: float32(speed), + }, + ) + fmt.Fprintf(w, "") + return + case r.URL.Path == "/api-sdk/get_faces": + resp, err := robot.Conn.RequestEnrolledNames( + ctx, + &vectorpb.RequestEnrolledNamesRequest{}) + if err != nil { + fmt.Fprint(w, err.Error()) + return + } + bytes, _ := json.Marshal(resp.Faces) + fmt.Fprint(w, string(bytes)) + return + case r.URL.Path == "/api-sdk/rename_face": + id := r.FormValue("id") + oldname := r.FormValue("oldname") + newname := r.FormValue("newname") + idInt, _ := strconv.Atoi(id) + idInt32 := int32(idInt) + _, err := robot.Conn.UpdateEnrolledFaceByID( + ctx, + &vectorpb.UpdateEnrolledFaceByIDRequest{ + FaceId: idInt32, + OldName: oldname, + NewName: newname, + }) + if err != nil { + fmt.Fprint(w, err.Error()) + return + } + fmt.Fprintf(w, "success") + return + case r.URL.Path == "/api-sdk/delete_face": + id := r.FormValue("id") + idInt, _ := strconv.Atoi(id) + idInt32 := int32(idInt) + _, err := robot.Conn.EraseEnrolledFaceByID( + ctx, + &vectorpb.EraseEnrolledFaceByIDRequest{ + FaceId: idInt32, + }) + if err != nil { + fmt.Fprint(w, err.Error()) + return + } + fmt.Fprintf(w, "success") + return + case r.URL.Path == "/api-sdk/add_face": + name := r.FormValue("name") + _, err := robot.Conn.AppIntent( + ctx, + &vectorpb.AppIntentRequest{ + Intent: "intent_meet_victor", + Param: name, + }, + ) + if err != nil { + fmt.Fprint(w, err.Error()) + return + } + fmt.Fprintf(w, "success") + return + case r.URL.Path == "/api-sdk/mirror_mode": + enable := r.FormValue("enable") + if enable == "true" { + _, err := robot.Conn.EnableMirrorMode( + ctx, + &vectorpb.EnableMirrorModeRequest{ + Enable: true, + }, + ) + if err != nil { + fmt.Fprint(w, err) + return + } + } else { + _, err := robot.Conn.EnableMirrorMode( + ctx, + &vectorpb.EnableMirrorModeRequest{ + Enable: false, + }, + ) + if err != nil { + fmt.Fprint(w, err) + return + } + } + fmt.Fprint(w, "success") + return + case r.URL.Path == "/api-sdk/begin_event_stream": + // setup websocket + robots[robotIndex].EventsStreaming = true + go func() { + client, err := robot.Conn.EventStream( + ctx, + &vectorpb.EventRequest{ + ListType: &vectorpb.EventRequest_WhiteList{ + WhiteList: &vectorpb.FilterList{ + List: []string{"stimulation_info"}, + }, + }, + ConnectionId: "wirepod", + }, + ) + if err != nil { + fmt.Fprint(w, err.Error()) + } + for { + if robots[robotIndex].EventsStreaming { + resp, err := client.Recv() + if err != nil { + fmt.Fprint(w, err.Error()) + robots[robotIndex].EventsStreaming = false + return + } + stimInfo := resp.Event.GetStimulationInfo() + stimInfoString := fmt.Sprint(stimInfo) + if strings.Contains(stimInfoString, "velocity") { + // velocity in the string means there is a value + robots[robotIndex].StimState = stimInfo.Value + } + } else { + return + } + } + }() + fmt.Fprint(w, "done") + return + case r.URL.Path == "/api-sdk/stop_event_stream": + robots[robotIndex].EventsStreaming = false + robots[robotIndex].StimState = 0 + fmt.Fprint(w, "done") + return + case r.URL.Path == "/api-sdk/get_stim_status": + if robots[robotIndex].EventsStreaming { + fmt.Fprint(w, robots[robotIndex].StimState) + return + } + fmt.Fprint(w, "error: must start event stream") + return + case r.URL.Path == "/api-sdk/begin_cam_stream": + //robots[robotIndex].CamStreaming = true + fmt.Fprint(w, "done") + return + case r.URL.Path == "/api-sdk/stop_cam_stream": + robots[robotIndex].CamStreaming = false + fmt.Fprint(w, "done") + return + case r.URL.Path == "/api-sdk/get_image_ids": + var photoIds []uint32 + resp, _ := robot.Conn.PhotosInfo( + ctx, + &vectorpb.PhotosInfoRequest{}, + ) + for _, photo := range resp.PhotoInfos { + photoIds = append(photoIds, photo.PhotoId) + } + writeBytes, _ := json.Marshal(photoIds) + w.Write(writeBytes) + return + case r.URL.Path == "/api-sdk/get_image": + id, err := strconv.Atoi(r.FormValue("id")) + if err != nil { + fmt.Fprint(w, "error: "+err.Error()) + return + } + resp, err := robot.Conn.Photo( + ctx, + &vectorpb.PhotoRequest{ + PhotoId: uint32(id), + }, + ) + if err != nil { + fmt.Fprint(w, "error: "+err.Error()) + return + } + w.Write(resp.Image) + return + case r.URL.Path == "/api-sdk/get_image_thumb": + id, err := strconv.Atoi(r.FormValue("id")) + if err != nil { + fmt.Fprint(w, "error: "+err.Error()) + return + } + resp, err := robot.Conn.Thumbnail( + ctx, + &vectorpb.ThumbnailRequest{ + PhotoId: uint32(id), + }, + ) + if err != nil { + fmt.Fprint(w, "error: "+err.Error()) + return + } + w.Write(resp.Image) + return + case r.URL.Path == "/api-sdk/delete_image": + id, err := strconv.Atoi(r.FormValue("id")) + if err != nil { + fmt.Fprint(w, "error: "+err.Error()) + return + } + _, err = robot.Conn.DeletePhoto( + ctx, + &vectorpb.DeletePhotoRequest{ + PhotoId: uint32(id), + }, + ) + if err != nil { + fmt.Fprint(w, "error: "+err.Error()) + return + } + fmt.Fprint(w, "done") + return + case r.URL.Path == "/api-sdk/get_robot_stats": + resp, err := robot.Conn.PullJdocs(ctx, + &vectorpb.PullJdocsRequest{ + JdocTypes: []vectorpb.JdocType{vectorpb.JdocType_ROBOT_LIFETIME_STATS}, + }) + if err != nil { + fmt.Fprint(w, "error: "+err.Error()) + return + } + w.Write([]byte(resp.GetNamedJdocs()[0].Doc.JsonDoc)) + return + case r.URL.Path == "/api-sdk/print_robot_info": + fmt.Fprint(w, robot) + return + case r.URL.Path == "/api-sdk/disconnect": + removeRobot(robotObj.ESN, "server") + fmt.Fprint(w, "done") + return + } +} + +func camStreamHandler(w http.ResponseWriter, r *http.Request) { + robotObj, robotIndex, err := getRobot(r.FormValue("serial")) + if err != nil { + fmt.Fprint(w, "error: "+err.Error()) + return + } + if robots[robotIndex].CamStreaming { + robots[robotIndex].CamStreaming = false + time.Sleep(time.Second / 2) + } + robotObj.Vector.Conn.EnableImageStreaming( + robotObj.Ctx, + &vectorpb.EnableImageStreamingRequest{ + Enable: true, + }, + ) + var client vectorpb.ExternalInterface_CameraFeedClient + client, err = robotObj.Vector.Conn.CameraFeed( + robotObj.Ctx, + &vectorpb.CameraFeedRequest{}, + ) + if err != nil { + fmt.Fprint(w, "error: "+err.Error()) + return + } + w.Header().Set("Content-Type", "multipart/x-mixed-replace; boundary=--boundary") + multi := io.MultiWriter(w) + robots[robotIndex].CamStreaming = true + for { + select { + case <-r.Context().Done(): + robotObj.Vector.Conn.EnableImageStreaming( + robotObj.Ctx, + &vectorpb.EnableImageStreamingRequest{ + Enable: false, + }, + ) + robots[robotIndex].CamStreaming = false + return + default: + if robots[robotIndex].CamStreaming { + response, err := client.Recv() + if err == nil { + imageBytes := response.GetData() + img, _, _ := image.Decode(bytes.NewReader(imageBytes)) + fmt.Fprintf(multi, "--boundary\r\nContent-Type: image/jpeg\r\n\r\n") + jpeg.Encode(multi, img, &jpeg.Options{ + Quality: 50, + }) + } + } else { + robotObj.Vector.Conn.EnableImageStreaming( + robotObj.Ctx, + &vectorpb.EnableImageStreamingRequest{ + Enable: false, + }, + ) + return + } + } + } +} + +func DisableCachingAndSniffing(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate;") + w.Header().Set("pragma", "no-cache") + w.Header().Set("X-Content-Type-Options", "nosniff") + next.ServeHTTP(w, r) + }) +} + +func BeginServer() { + scripting.RegisterScriptingAPI() + if os.Getenv("JDOCS_PINGER_ENABLED") == "false" { + PingerEnabled = false + logger.Println("Jdocs pinger has been disabled") + } + http.HandleFunc("/api-sdk/", SdkapiHandler) + if runtime.GOOS == "android" || runtime.GOOS == "ios" { + serverFiles = filepath.Join(vars.AndroidPath, "/static/webroot") + } + fileServer := http.FileServer(http.Dir(serverFiles)) + http.Handle("/sdk-app", DisableCachingAndSniffing(fileServer)) + // in jdocspinger.go + http.HandleFunc("/ok:80", connCheck) + http.HandleFunc("/ok", connCheck) + InitJdocsPinger() + // camstream + http.HandleFunc("/cam-stream", camStreamHandler) + logger.Println("Starting SDK app") + fmt.Printf("Starting server at port 80 for connCheck\n") + ipAddr := vars.GetOutboundIP().String() + logger.Println("\033[1;36mConfiguration page: http://" + ipAddr + ":" + vars.WebPort + "\033[0m") + if runtime.GOOS != "android" { + if err := http.ListenAndServe(":80", nil); err != nil { + if vars.Packaged { + logger.WarnMsg("A process is using port 80. Wire-pod will keep running, but connCheck functionality will not work, so your bot may not always stay connected to your wire-pod instance.") + } + logger.Println("A process is already using port 80 - connCheck functionality will not work") + } + } +} + +func rgbToBytes(rgbValues [][][3]uint8) ([]byte, error) { + var buffer bytes.Buffer + + for _, row := range rgbValues { + for _, pixel := range row { + // Directly add the R, G and B values ​​to the buffer + buffer.WriteByte(pixel[0]) // R + buffer.WriteByte(pixel[1]) // G + buffer.WriteByte(pixel[2]) // B + } + } + + return buffer.Bytes(), nil +} +func imageToBytes(img image.Image) ([]byte, error) { + bounds := img.Bounds() + var buffer bytes.Buffer + + for y := bounds.Min.Y; y < bounds.Max.Y; y++ { + for x := bounds.Min.X; x < bounds.Max.X; x++ { + // Obtém a cor do pixel + c := img.At(x, y) + r, g, b, _ := c.RGBA() // Ignorando o valor Alpha + + // Converte de uint32 para uint8 + buffer.WriteByte(uint8(r >> 8)) + buffer.WriteByte(uint8(g >> 8)) + buffer.WriteByte(uint8(b >> 8)) + } + } + + return buffer.Bytes(), nil +} + +func resizeImage(original image.Image, width, height int) image.Image { + if width <= 0 || height <= 0 { + return original + } + + newImage := image.NewRGBA(image.Rect(0, 0, width, height)) + + scaleX := float64(original.Bounds().Dx()) / float64(width) + scaleY := float64(original.Bounds().Dy()) / float64(height) + + for y := 0; y < height; y++ { + for x := 0; x < width; x++ { + srcX := int(float64(x) * scaleX) + srcY := int(float64(y) * scaleY) + newImage.Set(x, y, original.At(srcX, srcY)) + } + } + + return newImage +} diff --git a/chipper/pkg/wirepod/sdkapp/urlreqs.go b/chipper/pkg/wirepod/sdkapp/urlreqs.go new file mode 100644 index 0000000..c6e25a5 --- /dev/null +++ b/chipper/pkg/wirepod/sdkapp/urlreqs.go @@ -0,0 +1,67 @@ +package sdkapp + +import ( + "bytes" + "crypto/tls" + "net/http" +) + +var transCfg = &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // ignore SSL warnings +} + +func setCustomEyeColor(robot Robot, hue string, sat string) { + url := "https://" + robot.Target + "/v1/update_settings" + var updateJSON = []byte(`{"update_settings": true, "settings": {"custom_eye_color": {"enabled": true, "hue": ` + hue + `, "saturation": ` + sat + `} } }`) + req, _ := http.NewRequest("POST", url, bytes.NewBuffer(updateJSON)) + req.Header.Set("Authorization", "Bearer "+robot.GUID) + req.Header.Set("Content-Type", "application/json") + client := &http.Client{Transport: transCfg} + resp, err := client.Do(req) + if err != nil { + panic(err) + } + defer resp.Body.Close() +} + +func setPresetEyeColor(robot Robot, value string) { + url := "https://" + robot.Target + "/v1/update_settings" + var updateJSON = []byte(`{"update_settings": true, "settings": {"custom_eye_color": {"enabled": false}, "eye_color": ` + value + `} }`) + req, _ := http.NewRequest("POST", url, bytes.NewBuffer(updateJSON)) + req.Header.Set("Authorization", "Bearer "+robot.GUID) + req.Header.Set("Content-Type", "application/json") + client := &http.Client{Transport: transCfg} + resp, err := client.Do(req) + if err != nil { + panic(err) + } + defer resp.Body.Close() +} + +func setSettingSDKstring(robot Robot, setting string, value string) { + url := "https://" + robot.Target + "/v1/update_settings" + var updateJSON = []byte(`{"update_settings": true, "settings": {"` + setting + `": "` + value + `" } }`) + req, _ := http.NewRequest("POST", url, bytes.NewBuffer(updateJSON)) + req.Header.Set("Authorization", "Bearer "+robot.GUID) + req.Header.Set("Content-Type", "application/json") + client := &http.Client{Transport: transCfg} + resp, err := client.Do(req) + if err != nil { + panic(err) + } + defer resp.Body.Close() +} + +func setSettingSDKintbool(robot Robot, setting string, value string) { + url := "https://" + robot.Target + "/v1/update_settings" + var updateJSON = []byte(`{"update_settings": true, "settings": {"` + setting + `": ` + value + ` } }`) + req, _ := http.NewRequest("POST", url, bytes.NewBuffer(updateJSON)) + req.Header.Set("Authorization", "Bearer "+robot.GUID) + req.Header.Set("Content-Type", "application/json") + client := &http.Client{Transport: transCfg} + resp, err := client.Do(req) + if err != nil { + panic(err) + } + defer resp.Body.Close() +} diff --git a/chipper/pkg/wirepod/setup/ble.go b/chipper/pkg/wirepod/setup/ble.go new file mode 100644 index 0000000..12524d3 --- /dev/null +++ b/chipper/pkg/wirepod/setup/ble.go @@ -0,0 +1,520 @@ +//go:build inbuiltble +// +build inbuiltble + +package botsetup + +import ( + "archive/tar" + "compress/bzip2" + "encoding/json" + "errors" + "fmt" + "io" + "math" + "net/http" + "os" + "os/exec" + "strconv" + "strings" + "time" + + "github.com/digital-dream-labs/vector-bluetooth/ble" + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/mdnshandler" + "github.com/kercre123/wire-pod/chipper/pkg/vars" +) + +// need JSONable type +type VectorsBle struct { + ID int `json:"id"` + Name string `json:"name"` + Address string `json:"address"` +} + +type WifiNetwork struct { + SSID string `json:"ssid"` + AuthType int `json:"authtype"` +} + +var BleClient *ble.VectorBLE +var BleStatusChan chan ble.StatusChannel +var BleInited bool +var OtaStatus string +var LogStatus string + +func doOTAStatus() { + for { + r := <-BleStatusChan + if r.OTAStatus.Error != "" { + OtaStatus = "Error downloading OTA: " + r.OTAStatus.Error + return + } + if r.OTAStatus.PacketNumber == 0 { + OtaStatus = "OTA download progress: 0%" + } else { + percent := (float64(r.OTAStatus.PacketNumber) / float64(r.OTAStatus.PacketTotal)) * 100 + OtaStatus = "OTA download progress: " + fmt.Sprint(roundFloat(float64(percent), 3)) + "%" + if r.OTAStatus.PacketNumber == r.OTAStatus.PacketTotal { + OtaStatus = "OTA download is complete!" + return + } + } + } +} + +func doLogStatus() { + for { + r := <-BleStatusChan + if r.LogStatus.Error != "" { + LogStatus = "Error downloading logs: " + r.LogStatus.Error + return + } + if r.LogStatus.PacketNumber == 0 { + LogStatus = "Log download progress: 0%" + } else { + percent := (float64(r.LogStatus.PacketNumber) / float64(r.LogStatus.PacketTotal)) * 100 + OtaStatus = "Log download progress: " + fmt.Sprint(roundFloat(float64(percent), 3)) + "%" + if r.LogStatus.PacketNumber == r.LogStatus.PacketTotal { + OtaStatus = "Log download is complete!" + return + } + } + } +} + +func roundFloat(val float64, precision uint) float64 { + ratio := math.Pow(10, float64(precision)) + return math.Round(val*ratio) / ratio +} + +func IsDevRobot(esn, firmware string) bool { + if strings.Contains(firmware, "ankidev") { + return true + } + if strings.HasPrefix(esn, "00e") { + return true + } + return false +} + +func InitBle() (*ble.VectorBLE, error) { + BleStatusChan = nil + BleStatusChan = make(chan ble.StatusChannel) + done := make(chan bool) + var client *ble.VectorBLE + var err error + + go func() { + client, err = ble.New( + ble.WithStatusChan(BleStatusChan), + ble.WithLogDirectory(os.TempDir()), + ) + done <- true + }() + + select { + case <-done: + if err != nil { + if strings.Contains(err.Error(), "hci0: can't down device") || strings.Contains(err.Error(), "hci0: can't up device") { + FixBLEDriver() + client, err = ble.New( + ble.WithStatusChan(BleStatusChan), + ble.WithLogDirectory(os.TempDir()), + ) + return client, err + } + } + return client, err + case <-time.After(5 * time.Second): + done2 := make(chan bool) + FixBLEDriver() + go func() { + client, err = ble.New( + ble.WithStatusChan(BleStatusChan), + ble.WithLogDirectory(os.TempDir()), + ) + done2 <- true + }() + + select { + case <-done2: + return client, err + case <-time.After(5 * time.Second): + return nil, errors.New("error: took more than 5 seconds") + } + } +} + +func FixBLEDriver() { + logger.Println("BLE driver has broken. Removing then inserting bluetooth kernel modules") + rmmodList := []string{"btusb", "btrtl", "btmtk", "btintel", "btbcm"} + modprobeList := []string{"btrtl", "btmtk", "btintel", "btbcm", "btusb"} + for _, mod := range rmmodList { + exec.Command("/bin/rmmod", mod).Run() + } + time.Sleep(time.Second / 2) + for _, mod := range modprobeList { + exec.Command("/bin/modprobe", mod).Run() + } + time.Sleep(time.Second / 2) +} + +func ScanForVectors(client *ble.VectorBLE) ([]VectorsBle, error) { + var returnDevices []VectorsBle + resp, err := client.Scan() + if err != nil { + return nil, err + } + for _, device := range resp.Devices { + var vectorble VectorsBle + vectorble.Address = device.Address + vectorble.ID = device.ID + vectorble.Name = device.Name + returnDevices = append(returnDevices, vectorble) + } + return returnDevices, nil +} + +func ConnectVector(client *ble.VectorBLE, device int) error { + done := make(chan bool) + var err error + go func() { + err = client.Connect(device) + done <- true + }() + + select { + case <-done: + return err + case <-time.After(5 * time.Second): + FixBLEDriver() + return errors.New("error: took more than 5 seconds") + } +} + +func SendPin(pin string, client *ble.VectorBLE) error { + if len([]rune(pin)) != 6 { + return fmt.Errorf("error: length of pin must be 6") + } + err := client.SendPin(pin) + return err +} + +func RobotStatus(client *ble.VectorBLE) string { + var firmware string + var esn string + //v1.8.1.6051-453e582_os1.8.1.6051ep-1536e0d-202202282217 + //v0.9.0-12efb91_os0.9.0-3e8307e-201806191226 + + status, _ := client.GetStatus() + esn = status.ESN + firmware = status.Version + + if IsDevRobot(esn, firmware) && strings.Contains(firmware, "0.9.0") { + return "in_recovery_dev" + } + if strings.Contains(firmware, "0.9.0") { + return "in_recovery_prod" + } + if IsDevRobot(esn, firmware) { + return "in_firmware_dev" + } + if strings.Contains(firmware, "ep-") { + return "in_firmware_ep" + } + return "in_firmware_nonep" +} + +func AuthRobot(client *ble.VectorBLE) (bool, error) { + resp, err := client.Auth("2vMhFgktH3Jrbemm2WHkfGN") + if err != nil { + return false, err + } + return resp.Success, nil +} + +func BluetoothSetupAPI(w http.ResponseWriter, r *http.Request) { + switch { + case r.URL.Path == "/api-ble/init": + if BleInited { + fmt.Fprint(w, "success (ble already initiated, disconnect to reinit)") + return + } + var err error + BleClient, err = InitBle() + if err != nil { + fmt.Fprint(w, "error: "+err.Error()) + return + } + BleInited = true + fmt.Fprint(w, "success") + return + case r.URL.Path == "/api-ble/scan": + if !BleInited { + fmt.Fprint(w, "error: init ble first") + return + } + devices, err := ScanForVectors(BleClient) + if err != nil { + fmt.Fprint(w, "error: "+err.Error()) + return + } + returnBytes, _ := json.Marshal(devices) + w.Write(returnBytes) + return + case r.URL.Path == "/api-ble/connect": + if !BleInited { + fmt.Fprint(w, "error: init ble first") + return + } + id, err := strconv.Atoi(r.FormValue("id")) + if err != nil { + fmt.Fprint(w, "error: "+err.Error()) + return + } + err = ConnectVector(BleClient, id) + if err != nil { + fmt.Fprint(w, "error: "+err.Error()) + if err.Error() == "error: took more than 5 seconds" { + logger.Println("It took too long to connect to Vector. Quitting and letting systemd restart") + os.Exit(1) + } + } + fmt.Fprint(w, "success") + return + case r.URL.Path == "/api-ble/send_pin": + if !BleInited { + fmt.Fprint(w, "error: init ble first") + return + } + pin := r.FormValue("pin") + err := SendPin(pin, BleClient) + if err != nil { + if strings.Contains(err.Error(), "EOF") { + logger.Println("Wrong BLE pin was entered (sendpin error = eof), reinitializing BLE client") + BleClient.Close() + time.Sleep(time.Second) + BleClient, err = InitBle() + if err != nil { + fmt.Fprint(w, "error reinitializing ble: "+err.Error()) + return + } + fmt.Fprint(w, "incorrect pin") + } else { + fmt.Fprint(w, "error: "+err.Error()) + } + return + } + fmt.Fprint(w, "success") + return + case r.URL.Path == "/api-ble/get_wifi_status": + resp, err := BleClient.GetStatus() + if err != nil { + fmt.Fprint(w, "error: "+err.Error()) + return + } + fmt.Fprint(w, resp.WifiState) + return + case r.URL.Path == "/api-ble/get_firmware_version": + //v1.8.1.6051-453e582_os1.8.1.6051ep-1536e0d-202202282217 + //v0.9.0-12efb91_os0.9.0-3e8307e-201806191226 + resp, err := BleClient.GetStatus() + if err != nil { + fmt.Fprint(w, "error: "+err.Error()) + return + } + // split version to just get ankiversion + splitOsAndRev := strings.Split(resp.Version, "_os") + splitOs := strings.Split(splitOsAndRev[1], "-")[0] + fmt.Fprint(w, splitOs) + return + case r.URL.Path == "/api-ble/get_ip_address": + resp, err := BleClient.WifiIP() + if err != nil { + fmt.Fprint(w, "error: "+err.Error()) + return + } + fmt.Fprint(w, resp.IPv4) + return + case r.URL.Path == "/api-ble/start_ota": + otaUrl := r.FormValue("url") + if strings.TrimSpace(otaUrl) == "local" { + logger.Println("Starting proxy download from archive.org") + otaUrl = "http://" + vars.GetOutboundIP().String() + ":" + vars.WebPort + "/api/get_ota/vicos-2.0.1.6076ep.ota" + logger.Println("(" + otaUrl + ")") + } + if strings.Contains(otaUrl, "https://") { + fmt.Fprint(w, "error: ota URL must be http") + return + } + go doOTAStatus() + done := make(chan bool) + var resp *ble.OTAStartResponse + var err error + + go func() { + resp, err = BleClient.OTAStart(otaUrl) + done <- true + }() + + select { + case <-done: + fmt.Println("done") + if err != nil { + fmt.Fprint(w, "error: "+err.Error()) + return + } + fmt.Fprint(w, resp.Status) + return + case <-time.After(15 * time.Second): + fmt.Fprint(w, "likely success") + return + } + case r.URL.Path == "/api-ble/get_ota_status": + fmt.Fprint(w, OtaStatus) + return + case r.URL.Path == "/api-ble/stop_ota": + resp, err := BleClient.OTACancel() + if err != nil { + fmt.Fprint(w, "error: "+err.Error()) + return + } + fmt.Fprint(w, "success: "+string(resp)) + return + case r.URL.Path == "/api-ble/do_dev_setup": + go doLogStatus() + resp, err := BleClient.DownloadLogs() + if err != nil { + fmt.Fprint(w, "error: "+err.Error()) + return + } + logger.Println(resp.Filename) + zip, err := os.Open(resp.Filename) + if err != nil { + fmt.Fprint(w, "error: "+err.Error()) + return + } + tarFile := bzip2.NewReader(zip) + tarReader := tar.NewReader(tarFile) + for { + header, err := tarReader.Next() + if err != nil { + if err == io.EOF { + break + } + fmt.Fprint(w, "error: "+err.Error()) + return + } + name := header.FileInfo().Name() + logger.Println(name) + } + fmt.Fprint(w, "done") + return + case r.URL.Path == "/api-ble/scan_wifi": + var returnNetworks []WifiNetwork + resp, err := BleClient.WifiScan() + if err != nil { + fmt.Fprint(w, "error: "+err.Error()) + return + } + for _, network := range resp.Networks { + var returnNetwork WifiNetwork + returnNetwork.SSID = network.WifiSSID + returnNetwork.AuthType = network.AuthType + returnNetworks = append(returnNetworks, returnNetwork) + } + returnJson, _ := json.Marshal(returnNetworks) + w.Write(returnJson) + return + case r.URL.Path == "/api-ble/connect_wifi": + if r.FormValue("ssid") == "" || r.FormValue("password") == "" { + fmt.Fprint(w, "error: ssid or password empty") + return + } + authType, err := strconv.Atoi(r.FormValue("authType")) + if err != nil { + fmt.Fprint(w, "error: "+err.Error()) + } + resp, err := BleClient.WifiConnect(r.FormValue("ssid"), r.FormValue("password"), 15, authType) + if err != nil { + fmt.Fprint(w, "error: "+err.Error()) + return + } + fmt.Fprint(w, resp.Result) + case r.URL.Path == "/api-ble/get_robot_status": + if !BleInited { + fmt.Fprint(w, "error: init ble first") + return + } + fmt.Fprint(w, RobotStatus(BleClient)) + case r.URL.Path == "/api-ble/do_auth": + if !BleInited { + fmt.Fprint(w, "error: init ble first") + return + } + for i := 0; i <= 3; i++ { + resp, err := AuthRobot(BleClient) + if err != nil { + fmt.Fprint(w, "error: "+err.Error()) + return + } + if resp { + time.Sleep(time.Second) + fmt.Fprint(w, "success") + return + } else { + if vars.APIConfig.Server.EPConfig { + logger.Println("BLE authentication was not successful. Posting mDNS and trying again (" + fmt.Sprint(i) + "/3)...") + mdnshandler.PostmDNSNow() + time.Sleep(time.Second * 2) + continue + } else { + logger.Println("BLE authentication was not successful") + fmt.Fprint(w, "error authenticating") + return + } + } + } + logger.Println("BLE authentication attempts exhausted") + fmt.Fprint(w, "error authenticating") + return + case r.URL.Path == "/api-ble/reset_onboarding": + BleClient.SDKProxy( + &ble.SDKProxyRequest{ + URLPath: "/v1/send_onboarding_input", + Body: `{"onboarding_set_phase_request": {"phase": 2}}`, + }, + ) + case r.URL.Path == "/api-ble/onboard": + wAnim := r.FormValue("with_anim") + if wAnim == "true" { + BleClient.SDKProxy( + &ble.SDKProxyRequest{ + URLPath: "/v1/send_onboarding_input", + Body: `{"onboarding_wake_up_request": {}}`, + }, + ) + time.Sleep(time.Second * 21) + } + BleClient.SDKProxy( + &ble.SDKProxyRequest{ + URLPath: "/v1/send_onboarding_input", + Body: `{"onboarding_mark_complete_and_exit": {}}`, + }, + ) + fmt.Fprint(w, "done") + case r.URL.Path == "/api-ble/disconnect": + err := BleClient.Close() + if err != nil { + fmt.Fprint(w, "error: "+err.Error()) + return + } + BleInited = false + fmt.Fprint(w, "success") + return + } +} + +func RegisterBLEAPI() { + http.HandleFunc("/api-ble/", BluetoothSetupAPI) +} diff --git a/chipper/pkg/wirepod/setup/ble_other.go b/chipper/pkg/wirepod/setup/ble_other.go new file mode 100644 index 0000000..3610146 --- /dev/null +++ b/chipper/pkg/wirepod/setup/ble_other.go @@ -0,0 +1,10 @@ +//go:build !inbuiltble +// +build !inbuiltble + +package botsetup + +import "github.com/kercre123/wire-pod/chipper/pkg/logger" + +func RegisterBLEAPI() { + logger.Println("BLE API is unregistered") +} diff --git a/chipper/pkg/wirepod/setup/certs.go b/chipper/pkg/wirepod/setup/certs.go new file mode 100644 index 0000000..651381c --- /dev/null +++ b/chipper/pkg/wirepod/setup/certs.go @@ -0,0 +1,124 @@ +package botsetup + +import ( + "bytes" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/json" + "encoding/pem" + "math/big" + "net" + "os" + "time" + + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vars" +) + +type ClientServerConfig struct { + Jdocs string `json:"jdocs"` + Token string `json:"tms"` + Chipper string `json:"chipper"` + Check string `json:"check"` + Logfiles string `json:"logfiles"` + Appkey string `json:"appkey"` +} + +// creates and exports a priv/pub key combo generated with IP address +func CreateCertCombo() error { + // get preferred IP address of machine + ipAddr := vars.GetOutboundIP() + + // ca certificate + ca := &x509.Certificate{ + SerialNumber: big.NewInt(2019), + Subject: pkix.Name{}, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(30, 0, 0), + IsCA: true, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + BasicConstraintsValid: true, + } + caPrivKey, err := rsa.GenerateKey(rand.Reader, 1028) + if err != nil { + return err + } + + // create actual certificate + cert := &x509.Certificate{ + SerialNumber: big.NewInt(1658), + Subject: pkix.Name{}, + IPAddresses: []net.IP{ipAddr}, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(10, 0, 0), + SubjectKeyId: []byte{1, 2, 3, 4, 6}, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature, + } + certPrivKey, err := rsa.GenerateKey(rand.Reader, 1028) + if err != nil { + return err + } + certBytes, err := x509.CreateCertificate(rand.Reader, cert, ca, &certPrivKey.PublicKey, caPrivKey) + if err != nil { + return err + } + certPEM := new(bytes.Buffer) + pem.Encode(certPEM, &pem.Block{ + Type: "CERTIFICATE", + Bytes: certBytes, + }) + certPrivKeyPEM := new(bytes.Buffer) + pem.Encode(certPrivKeyPEM, &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey), + }) + + // export certificates + os.MkdirAll(vars.Certs, 0777) + logger.Println("Outputting certificate to " + vars.CertPath) + err = os.WriteFile(vars.CertPath, certPEM.Bytes(), 0777) + if err != nil { + return err + } + logger.Println("Outputting private key to " + vars.KeyPath) + err = os.WriteFile(vars.KeyPath, certPrivKeyPEM.Bytes(), 0777) + if err != nil { + return err + } + vars.ChipperCert = certPEM.Bytes() + vars.ChipperKey = certPrivKeyPEM.Bytes() + vars.ChipperKeysLoaded = true + + return nil +} + +// outputs a server config to ../certs/server_config.json +func CreateServerConfig() { + os.MkdirAll(vars.Certs, 0777) + var config ClientServerConfig + //{"jdocs": "escapepod.local:443", "tms": "escapepod.local:443", "chipper": "escapepod.local:443", "check": "escapepod.local/ok:80", "logfiles": "s3://anki-device-logs-prod/victor", "appkey": "oDoa0quieSeir6goowai7f"} + if vars.APIConfig.Server.EPConfig { + config.Jdocs = "escapepod.local:443" + config.Token = "escapepod.local:443" + config.Chipper = "escapepod.local:443" + config.Check = "escapepod.local/ok" + config.Logfiles = "s3://anki-device-logs-prod/victor" + config.Appkey = "oDoa0quieSeir6goowai7f" + } else { + ip := vars.GetOutboundIP() + ipString := ip.String() + url := ipString + ":" + vars.APIConfig.Server.Port + config.Jdocs = url + config.Token = url + config.Chipper = url + config.Check = ipString + "/ok" + config.Logfiles = "s3://anki-device-logs-prod/victor" + config.Appkey = "oDoa0quieSeir6goowai7f" + } + writeBytes, _ := json.Marshal(config) + os.WriteFile(vars.ServerConfigPath, writeBytes, 0777) +} diff --git a/chipper/pkg/wirepod/setup/ssh.go b/chipper/pkg/wirepod/setup/ssh.go new file mode 100644 index 0000000..85d58ee --- /dev/null +++ b/chipper/pkg/wirepod/setup/ssh.go @@ -0,0 +1,240 @@ +package botsetup + +import ( + "context" + "fmt" + "io" + "net/http" + "os" + "runtime" + "strings" + "time" + + scp "github.com/bramvdbogaerde/go-scp" + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vars" + "golang.org/x/crypto/ssh" +) + +// this file will be copied to the bot +var SetupScriptPath = "../vector-cloud/pod-bot-install.sh" + +// path to copy to +const BotSetupPath = "/data/pod-bot-install.sh" + +var SetupSSHStatus string = "not running" +var SSHSettingUp bool = false + +func doErr(err error, msg string) error { + SSHSettingUp = false + SetupSSHStatus = "not running (last error: " + err.Error() + ", last step: " + msg + ")" + return err +} + +func runCmd(client *ssh.Client, cmd string) (string, error) { + session, err := client.NewSession() + if err != nil { + return "", err + } + output, err := session.Output(cmd) + if err != nil { + return "", err + } + return string(output), nil +} + +func setCPURAMfreq(client *ssh.Client, cpufreq string, ramfreq string, gov string) { + runCmd(client, "echo "+cpufreq+" > /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq && echo disabled > /sys/kernel/debug/msm_otg/bus_voting && echo 0 > /sys/kernel/debug/msm-bus-dbg/shell-client/update_request && echo 1 > /sys/kernel/debug/msm-bus-dbg/shell-client/mas && echo 512 > /sys/kernel/debug/msm-bus-dbg/shell-client/slv && echo 0 > /sys/kernel/debug/msm-bus-dbg/shell-client/ab && echo active clk2 0 1 max "+ramfreq+" > /sys/kernel/debug/rpm_send_msg/message && echo "+gov+" > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor && echo 1 > /sys/kernel/debug/msm-bus-dbg/shell-client/update_request") +} + +func SetupBotViaSSH(ip string, key []byte) error { + if runtime.GOOS == "android" || runtime.GOOS == "ios" { + SetupScriptPath = vars.AndroidPath + "/static/pod-bot-install.sh" + } + if vars.IsPackagedLinux { + SetupScriptPath = "./pod-bot-install.sh" + } + if !SSHSettingUp { + logger.Println("Setting up " + ip + " via SSH") + SetupSSHStatus = "Setting up SSH connection..." + CreateServerConfig() + signer, err := ssh.ParsePrivateKey(key) + if err != nil { + doErr(err, "parsing priv key") + } + config := &ssh.ClientConfig{ + User: "root", + Auth: []ssh.AuthMethod{ + ssh.PublicKeys(signer), + }, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + HostKeyAlgorithms: []string{"ssh-rsa"}, + Timeout: time.Second * 5, + } + client, err := ssh.Dial("tcp", ip+":22", config) + if err != nil { + return doErr(err, "ssh dial") + } + SetupSSHStatus = "Checking if device is a Vector..." + output, err := runCmd(client, "uname -a") + if err != nil { + return doErr(err, "checking if vector") + } + if !strings.Contains(output, "Vector") { + return doErr(fmt.Errorf("the remote device is not a vector"), "checking if vector") + } + SetupSSHStatus = "Running initial commands before transfers (screen will go blank, this is normal)..." + _, err = runCmd(client, "mount -o rw,remount / && mount -o rw,remount,exec /data && systemctl stop anki-robot.target && mv /anki/data/assets/cozmo_resources/config/server_config.json /anki/data/assets/cozmo_resources/config/server_config.json.bak") + if err != nil { + if !strings.Contains(err.Error(), "Process exited with status 1") { + return doErr(err, "initial commands") + } + } + setCPURAMfreq(client, "1267200", "800000", "performance") + SetupSSHStatus = "Waiting a few seconds for filesystem syncing" + time.Sleep(time.Second * 3) + SetupSSHStatus = "Transferring bot setup script and certs..." + scpClient, err := scp.NewClientBySSH(client) + if err != nil { + return doErr(err, "new scp client") + } + script, err := os.Open(SetupScriptPath) + if err != nil { + return doErr(err, "opening setup script") + } + err = scpClient.CopyFile(context.Background(), script, "/data/pod-bot-install.sh", "0755") + if err != nil { + return doErr(err, "copying pod-bot-install") + } + scpClient.Session.Close() + serverConfig, err := os.Open(vars.ServerConfigPath) + if err != nil { + return doErr(err, "opening server config on disk") + } + scpClient, err = scp.NewClientBySSH(client) + if err != nil { + return doErr(err, "new scp client 2") + } + err = scpClient.CopyFile(context.Background(), serverConfig, "/anki/data/assets/cozmo_resources/config/server_config.json", "0755") + if err != nil { + return doErr(err, "copying server-config.json") + } + scpClient.Session.Close() + if runtime.GOOS != "android" && !vars.Packaged { + cloud, err := os.Open("../vector-cloud/build/vic-cloud") + if err != nil { + return doErr(err, "transferring new vic-cloud") + } + SetupSSHStatus = "Transferring new vic-cloud..." + scpClient, err = scp.NewClientBySSH(client) + if err != nil { + return doErr(err, "new scp client 3") + } + err = scpClient.CopyFile(context.Background(), cloud, "/anki/bin/vic-cloud", "0755") + if err != nil { + time.Sleep(time.Second * 1) + scpClient, err = scp.NewClientBySSH(client) + if err != nil { + return doErr(err, "copying vic-cloud") + } + err = scpClient.CopyFile(context.Background(), cloud, "/anki/bin/vic-cloud", "0755") + if err != nil { + return doErr(err, "copying vic-cloud") + } + } + } else { + resp, _ := http.Get("https://github.com/kercre123/wire-pod/raw/main/vector-cloud/build/vic-cloud") + if err != nil { + return doErr(err, "transferring new vic-cloud") + } + SetupSSHStatus = "Transferring new vic-cloud..." + scpClient, err = scp.NewClientBySSH(client) + if err != nil { + return doErr(err, "new scp client 3") + } + err = scpClient.CopyFile(context.Background(), resp.Body, "/anki/bin/vic-cloud", "0755") + if err != nil { + time.Sleep(time.Second * 1) + scpClient, err = scp.NewClientBySSH(client) + if err != nil { + return doErr(err, "copying vic-cloud") + } + err = scpClient.CopyFile(context.Background(), resp.Body, "/anki/bin/vic-cloud", "0755") + if err != nil { + return doErr(err, "copying vic-cloud") + } + } + } + scpClient.Session.Close() + certPath := vars.CertPath + if vars.APIConfig.Server.EPConfig { + if runtime.GOOS == "android" || runtime.GOOS == "ios" { + certPath = vars.AndroidPath + "/static/epod/ep.crt" + } else { + certPath = "./epod/ep.crt" + } + } + cert, err := os.Open(certPath) + if err != nil { + return doErr(err, "opening cert") + } + scpClient, err = scp.NewClientBySSH(client) + if err != nil { + return doErr(err, "new scp client 4") + } + err = scpClient.CopyFile(context.Background(), cert, "/anki/etc/wirepod-cert.crt", "0755") + if err != nil { + return doErr(err, "copying wire-pod cert") + } + scpClient.Session.Close() + _, err = runCmd(client, "cp /anki/etc/wirepod-cert.crt /data/data/wirepod-cert.crt") + if err != nil { + return doErr(err, "copying wire-pod cert in robot") + } + SetupSSHStatus = "Generating new robot certificate (this may take a while)..." + _, err = runCmd(client, "chmod +rwx /anki/data/assets/cozmo_resources/config/server_config.json /anki/bin/vic-cloud /data/data/wirepod-cert.crt /anki/etc/wirepod-cert.crt /data/pod-bot-install.sh && /data/pod-bot-install.sh") + if err != nil { + return doErr(err, "generating new robot cert") + } + setCPURAMfreq(client, "733333", "500000", "interactive") + client.Close() + SetupSSHStatus = "done" + } else { + return fmt.Errorf("a bot is already being setup") + } + return nil +} + +func SSHSetup(w http.ResponseWriter, r *http.Request) { + switch { + case r.URL.Path == "/api-ssh/setup": + ip := r.FormValue("ip") + if ip == "" { + fmt.Fprint(w, "error: must provide ip") + return + } + key, _, err := r.FormFile("key") + if err != nil { + fmt.Fprint(w, "error: must provide ssh key ("+err.Error()+")") + return + } + keyBytes, _ := io.ReadAll(key) + if len(keyBytes) < 5 { + fmt.Fprint(w, "error: must provide ssh key ("+err.Error()+")") + return + } + go SetupBotViaSSH(ip, keyBytes) + fmt.Fprint(w, "running") + return + case r.URL.Path == "/api-ssh/get_setup_status": + fmt.Fprint(w, SetupSSHStatus) + if SetupSSHStatus == "done" || strings.Contains(SetupSSHStatus, "error") { + SetupSSHStatus = "not running" + } + return + } +} + +func RegisterSSHAPI() { + http.HandleFunc("/api-ssh/", SSHSetup) +} diff --git a/chipper/pkg/wirepod/speechrequest/speechrequest.go b/chipper/pkg/wirepod/speechrequest/speechrequest.go new file mode 100644 index 0000000..c808b40 --- /dev/null +++ b/chipper/pkg/wirepod/speechrequest/speechrequest.go @@ -0,0 +1,366 @@ +package speechrequest + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "math" + "os" + "time" + + pb "github.com/digital-dream-labs/api/go/chipperpb" + "github.com/digital-dream-labs/opus-go/opus" + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vtt" + "github.com/maxhawkins/go-webrtcvad" +) + +// one type and many functions for dealing with intent, intent-graph, and knowledge-graph requests +// also some functions to help decode the stream bytes into ones friendly for stt engines + +var debugWriteFile bool = false +var debugFile *os.File + +type SpeechRequest struct { + Device string + Session string + FirstReq []byte + Stream interface{} + IsKG bool + IsIG bool + MicData []byte + DecodedMicData []byte + FilteredMicData []byte + PrevLen int + PrevLenRaw int + InactiveFrames int + ActiveFrames int + VADInst *webrtcvad.VAD + LastAudioChunk []byte + IsOpus bool + OpusStream *opus.OggStream +} + +func BytesToSamples(buf []byte) []int16 { + samples := make([]int16, len(buf)/2) + for i := 0; i < len(buf)/2; i++ { + samples[i] = int16(binary.LittleEndian.Uint16(buf[i*2:])) + } + return samples +} + +func (req *SpeechRequest) OpusDetect() bool { + var isOpus bool + if len(req.FirstReq) > 0 { + if req.FirstReq[0] == 0x4f { + logger.Println("Bot " + req.Device + " Stream type: OPUS") + isOpus = true + } else { + isOpus = false + logger.Println("Bot " + req.Device + " Stream type: PCM") + } + } + return isOpus +} + +func (req *SpeechRequest) OpusDecode(chunk []byte) []byte { + if req.IsOpus { + n, err := req.OpusStream.Decode(chunk) + if err != nil { + logger.Println(err) + } + return n + } else { + return chunk + } +} + +func SplitVAD(buf []byte) [][]byte { + var chunk [][]byte + for len(buf) >= 320 { + chunk = append(chunk, buf[:320]) + buf = buf[320:] + } + return chunk +} + +func BytesToIntVAD(stream opus.OggStream, data []byte, die bool, isOpus bool) [][]byte { + // detect if data is pcm or opus + if die { + return nil + } + if isOpus { + // opus + n, err := stream.Decode(data) + if err != nil { + logger.Println(err) + } + byteArray := SplitVAD(n) + return byteArray + } else { + // pcm + byteArray := SplitVAD(data) + return byteArray + } +} + +// Uses VAD to detect when the user stops speaking +func (req *SpeechRequest) DetectEndOfSpeech() (bool, bool) { + // changes InactiveFrames and ActiveFrames in req + inactiveNumMax := 23 + for _, chunk := range SplitVAD(req.LastAudioChunk) { + active, err := req.VADInst.Process(16000, chunk) + if err != nil { + logger.Println("VAD err:") + logger.Println(err) + return true, false + } + if active { + req.ActiveFrames = req.ActiveFrames + 1 + req.InactiveFrames = 0 + } else { + req.InactiveFrames = req.InactiveFrames + 1 + } + if req.InactiveFrames >= inactiveNumMax && req.ActiveFrames > 18 { + logger.Println("(Bot " + req.Device + ") End of speech detected.") + return true, true + } + } + if req.ActiveFrames < 5 { + return false, false + } + return false, true +} + +func bytesToInt16(data []byte) ([]int16, error) { + var samples []int16 + buf := bytes.NewReader(data) + for buf.Len() > 0 { + var sample int16 + err := binary.Read(buf, binary.LittleEndian, &sample) + if err != nil { + return nil, err + } + samples = append(samples, sample) + } + return samples, nil +} + +func int16ToBytes(samples []int16) []byte { + buf := new(bytes.Buffer) + for _, sample := range samples { + err := binary.Write(buf, binary.LittleEndian, sample) + if err != nil { + return nil + } + } + return buf.Bytes() +} + +func applyGain(samples []int16, gain float64) []int16 { + for i, sample := range samples { + amplifiedSample := float64(sample) * gain + if amplifiedSample > math.MaxInt16 { + samples[i] = math.MaxInt16 + } else if amplifiedSample < math.MinInt16 { + samples[i] = math.MinInt16 + } else { + samples[i] = int16(amplifiedSample) + } + } + return samples +} + +// remove noise +func highPassFilter(data []byte) []byte { + bTime := time.Now() + sampleRate := 16000 + cutoffFreq := 300.0 + samples, err := bytesToInt16(data) + if err != nil { + return nil + } + samples = applyGain(samples, 5) + filteredSamples := make([]float64, len(samples)) + rc := 1.0 / (2.0 * math.Pi * cutoffFreq) + dt := 1.0 / float64(sampleRate) + alpha := dt / (rc + dt) + + previous := float64(samples[0]) + for i := 1; i < len(samples); i++ { + current := float64(samples[i]) + filtered := alpha * (filteredSamples[i-1] + current - previous) + filteredSamples[i] = filtered + previous = current + } + int16FilteredSamples := make([]int16, len(filteredSamples)) + for i, sample := range filteredSamples { + int16FilteredSamples[i] = int16(sample) + } + + gained := applyGain(int16FilteredSamples, 1.5) + if os.Getenv("DEBUG_PRINT_HIGHPASS") == "true" { + logger.Println("highpass filter took: " + fmt.Sprint(time.Since(bTime))) + } + + return int16ToBytes(gained) +} + +// Converts a vtt.*Request to a SpeechRequest, which allows functions like DetectEndOfSpeech to work +func ReqToSpeechRequest(req interface{}) SpeechRequest { + if debugWriteFile { + debugFile, _ = os.Create("/tmp/wirepodtest.ogg") + } + var request SpeechRequest + request.PrevLen = 0 + var err error + request.VADInst, err = webrtcvad.New() + request.VADInst.SetMode(2) + if err != nil { + logger.Println(err) + } + if str, ok := req.(*vtt.IntentRequest); ok { + var req1 *vtt.IntentRequest = str + request.Device = req1.Device + request.Session = req1.Session + request.Stream = req1.Stream + request.FirstReq = req1.FirstReq.InputAudio + request.MicData = append(request.MicData, req1.FirstReq.InputAudio...) + } else if str, ok := req.(*vtt.KnowledgeGraphRequest); ok { + var req1 *vtt.KnowledgeGraphRequest = str + request.IsKG = true + request.Device = req1.Device + request.Session = req1.Session + request.Stream = req1.Stream + request.FirstReq = req1.FirstReq.InputAudio + request.MicData = append(request.MicData, req1.FirstReq.InputAudio...) + } else if str, ok := req.(*vtt.IntentGraphRequest); ok { + request.IsIG = true + var req1 *vtt.IntentGraphRequest = str + request.Device = req1.Device + request.Session = req1.Session + request.Stream = req1.Stream + request.FirstReq = req1.FirstReq.InputAudio + if debugWriteFile { + debugFile.Write(req1.FirstReq.InputAudio) + } + request.MicData = append(request.MicData, req1.FirstReq.InputAudio...) + } else { + logger.Println("reqToSpeechRequest: invalid type") + } + isOpus := request.OpusDetect() + if isOpus { + request.OpusStream = &opus.OggStream{} + decodedFirstReq, _ := request.OpusStream.Decode(request.FirstReq) + request.FirstReq = highPassFilter(decodedFirstReq) + request.FilteredMicData = append(request.FilteredMicData, request.FirstReq...) + request.DecodedMicData = append(request.DecodedMicData, decodedFirstReq...) + request.LastAudioChunk = request.FilteredMicData[request.PrevLen:] + request.PrevLen = len(request.DecodedMicData) + request.IsOpus = true + } + return request +} + +// Returns the next chunk in the stream as 16000 Hz PCM +func (req *SpeechRequest) GetNextStreamChunk() ([]byte, error) { + // returns next chunk in voice stream as pcm + if str, ok := req.Stream.(pb.ChipperGrpc_StreamingIntentServer); ok { + var stream pb.ChipperGrpc_StreamingIntentServer = str + chunk, chunkErr := stream.Recv() + if chunkErr != nil { + logger.Println(chunkErr) + return nil, chunkErr + } + req.MicData = append(req.MicData, chunk.InputAudio...) + req.DecodedMicData = append(req.DecodedMicData, req.OpusDecode(chunk.InputAudio)...) + req.FilteredMicData = append(req.FilteredMicData, highPassFilter(req.OpusDecode(chunk.InputAudio))...) + dataReturn := req.DecodedMicData[req.PrevLen:] + req.LastAudioChunk = req.FilteredMicData[req.PrevLen:] + req.PrevLen = len(req.DecodedMicData) + return dataReturn, nil + } else if str, ok := req.Stream.(pb.ChipperGrpc_StreamingIntentGraphServer); ok { + var stream pb.ChipperGrpc_StreamingIntentGraphServer = str + chunk, chunkErr := stream.Recv() + if chunkErr != nil { + logger.Println(chunkErr) + return nil, chunkErr + } + req.MicData = append(req.MicData, chunk.InputAudio...) + req.DecodedMicData = append(req.DecodedMicData, req.OpusDecode(chunk.InputAudio)...) + req.FilteredMicData = append(req.FilteredMicData, highPassFilter(req.OpusDecode(chunk.InputAudio))...) + dataReturn := req.DecodedMicData[req.PrevLen:] + req.LastAudioChunk = req.FilteredMicData[req.PrevLen:] + req.PrevLen = len(req.DecodedMicData) + if debugWriteFile { + debugFile.Write(chunk.InputAudio) + } + return dataReturn, nil + } else if str, ok := req.Stream.(pb.ChipperGrpc_StreamingKnowledgeGraphServer); ok { + var stream pb.ChipperGrpc_StreamingKnowledgeGraphServer = str + chunk, chunkErr := stream.Recv() + if chunkErr != nil { + logger.Println(chunkErr) + return nil, chunkErr + } + req.MicData = append(req.MicData, chunk.InputAudio...) + req.DecodedMicData = append(req.DecodedMicData, req.OpusDecode(chunk.InputAudio)...) + req.FilteredMicData = append(req.FilteredMicData, highPassFilter(req.OpusDecode(chunk.InputAudio))...) + dataReturn := req.DecodedMicData[req.PrevLen:] + req.LastAudioChunk = req.FilteredMicData[req.PrevLen:] + req.PrevLen = len(req.DecodedMicData) + return dataReturn, nil + } + logger.Println("invalid type") + return nil, errors.New("invalid type") +} + +// Returns next chunk in the stream as whatever the original format is (OPUS 99% of the time) +func (req *SpeechRequest) GetNextStreamChunkOpus() ([]byte, error) { + if str, ok := req.Stream.(pb.ChipperGrpc_StreamingIntentServer); ok { + var stream pb.ChipperGrpc_StreamingIntentServer = str + chunk, chunkErr := stream.Recv() + if chunkErr != nil { + logger.Println(chunkErr) + return nil, chunkErr + } + req.MicData = append(req.MicData, chunk.InputAudio...) + req.DecodedMicData = append(req.DecodedMicData, req.OpusDecode(chunk.InputAudio)...) + dataReturn := req.MicData[req.PrevLenRaw:] + req.LastAudioChunk = req.DecodedMicData[req.PrevLen:] + req.PrevLen = len(req.DecodedMicData) + req.PrevLenRaw = len(req.MicData) + return dataReturn, nil + } else if str, ok := req.Stream.(pb.ChipperGrpc_StreamingIntentGraphServer); ok { + var stream pb.ChipperGrpc_StreamingIntentGraphServer = str + chunk, chunkErr := stream.Recv() + if chunkErr != nil { + logger.Println(chunkErr) + return nil, chunkErr + } + req.MicData = append(req.MicData, chunk.InputAudio...) + req.DecodedMicData = append(req.DecodedMicData, req.OpusDecode(chunk.InputAudio)...) + dataReturn := req.MicData[req.PrevLenRaw:] + req.LastAudioChunk = req.DecodedMicData[req.PrevLen:] + req.PrevLen = len(req.DecodedMicData) + req.PrevLenRaw = len(req.MicData) + return dataReturn, nil + } else if str, ok := req.Stream.(pb.ChipperGrpc_StreamingKnowledgeGraphServer); ok { + var stream pb.ChipperGrpc_StreamingKnowledgeGraphServer = str + chunk, chunkErr := stream.Recv() + if chunkErr != nil { + logger.Println(chunkErr) + return nil, chunkErr + } + req.MicData = append(req.MicData, chunk.InputAudio...) + req.DecodedMicData = append(req.DecodedMicData, req.OpusDecode(chunk.InputAudio)...) + dataReturn := req.MicData[req.PrevLenRaw:] + req.LastAudioChunk = req.DecodedMicData[req.PrevLen:] + req.PrevLen = len(req.DecodedMicData) + req.PrevLenRaw = len(req.MicData) + return dataReturn, nil + } + logger.Println("invalid type") + return nil, errors.New("invalid type") +} diff --git a/chipper/pkg/wirepod/stt/coqui/Coqui.go b/chipper/pkg/wirepod/stt/coqui/Coqui.go new file mode 100644 index 0000000..47dfb99 --- /dev/null +++ b/chipper/pkg/wirepod/stt/coqui/Coqui.go @@ -0,0 +1,81 @@ +package wirepod_coqui + +import ( + "fmt" + "log" + "os" + "time" + + "github.com/asticode/go-asticoqui" + "github.com/kercre123/wire-pod/chipper/pkg/logger" + sr "github.com/kercre123/wire-pod/chipper/pkg/wirepod/speechrequest" +) + +var Name string = "coqui" + +// Init should be defined as `func() error` +func Init() error { + logger.Println("Running a Coqui test...") + coquiInstance, _ := asticoqui.New("../stt/model.tflite") + if _, err := os.Stat("../stt/large_vocabulary.scorer"); err == nil { + coquiInstance.EnableExternalScorer("../stt/large_vocabulary.scorer") + } else if _, err := os.Stat("../stt/model.scorer"); err == nil { + coquiInstance.EnableExternalScorer("../stt/model.scorer") + } else { + logger.Println("No .scorer file found.") + } + coquiStream, err := coquiInstance.NewStream() + if err != nil { + log.Fatal(err) + } + pcmBytes, _ := os.ReadFile("./stttest.pcm") + var micData [][]byte + cTime := time.Now() + micData = sr.SplitVAD(pcmBytes) + for _, sample := range micData { + coquiStream.FeedAudioContent(sr.BytesToSamples(sample)) + } + res, err := coquiStream.Finish() + tTime := time.Now().Sub(cTime) + if err != nil { + log.Fatal("Failed testing speech to text: ", err) + } + logger.Println("Text:", res) + if tTime.Seconds() > 3 { + logger.Println("Coqui test took a while, performance may be degraded. (" + fmt.Sprint(tTime) + ")") + } + logger.Println("Coqui test successful! (Took " + fmt.Sprint(tTime) + ")") + return nil +} + +// STT funcs should be defined as func(sr.SpeechRequest) (string, error) + +func STT(req sr.SpeechRequest) (string, error) { + logger.Println("(Bot " + req.Device + ", Coqui) Processing...") + speechIsDone := false + coquiInstance, _ := asticoqui.New("../stt/model.tflite") + if _, err := os.Stat("../stt/large_vocabulary.scorer"); err == nil { + coquiInstance.EnableExternalScorer("../stt/large_vocabulary.scorer") + } else if _, err := os.Stat("../stt/model.scorer"); err == nil { + coquiInstance.EnableExternalScorer("../stt/model.scorer") + } else { + logger.Println("No .scorer file found.") + } + coquiStream, _ := coquiInstance.NewStream() + for { + var chunk []byte + var err error + chunk, err = req.GetNextStreamChunk() + if err != nil { + return "", err + } + coquiStream.FeedAudioContent(sr.BytesToSamples(chunk)) + speechIsDone, _ = req.DetectEndOfSpeech() + if speechIsDone { + break + } + } + transcribedText, _ := coquiStream.Finish() + logger.Println("Bot " + req.Device + " Transcribed text: " + transcribedText) + return transcribedText, nil +} diff --git a/chipper/pkg/wirepod/stt/houndify/Houndify.go b/chipper/pkg/wirepod/stt/houndify/Houndify.go new file mode 100644 index 0000000..26eefb3 --- /dev/null +++ b/chipper/pkg/wirepod/stt/houndify/Houndify.go @@ -0,0 +1,93 @@ +package wirepod_vosk + +import ( + "fmt" + "io" + "os" + + "github.com/kercre123/wire-pod/chipper/pkg/logger" + preqs "github.com/kercre123/wire-pod/chipper/pkg/wirepod/preqs" + sr "github.com/kercre123/wire-pod/chipper/pkg/wirepod/speechrequest" + "github.com/soundhound/houndify-sdk-go" +) + +// to use, you must create a Houndify client with the only domain enabled being "Speech to text only" +// set HOUNDIFY_STT_ID and HOUNDIFY_STT_KEY to the respective strings you will find on the dashboard +// also set STT_SERVICE to "houndify" + +var Name string = "houndify" + +var houndSTTClient houndify.Client + +func Init() error { + if os.Getenv("HOUNDIFY_STT_ID") == "" { + logger.Println("Houndify STT Client ID not found.") + return fmt.Errorf("houndify stt client id not found") + } + if os.Getenv("HOUNDIFY_STT_KEY") == "" { + logger.Println("Houndify STT Client Key not found.") + return fmt.Errorf("houndify stt client key not found") + } + houndSTTClient = houndify.Client{ + ClientID: os.Getenv("HOUNDIFY_STT_ID"), + ClientKey: os.Getenv("HOUNDIFY_STT_KEY"), + } + houndSTTClient.EnableConversationState() + logger.Println("Houndify client for speech-to-text initialized!") + return nil +} + +func STT(sreq sr.SpeechRequest) (string, error) { + logger.Println("Incoming request") + var err error + rp, wp := io.Pipe() + req := houndify.VoiceRequest{ + AudioStream: rp, + UserID: sreq.Device, + RequestID: sreq.Session, + } + done := make(chan bool) + speechDone := false + go func(wp *io.PipeWriter) { + defer wp.Close() + + for { + select { + case <-done: + return + default: + var chunk []byte + chunk, err = sreq.GetNextStreamChunkOpus() + speechDone, _ = sreq.DetectEndOfSpeech() + if err != nil { + fmt.Println("End of stream") + return + } + wp.Write(chunk) + if speechDone { + return + } + } + } + }(wp) + + partialTranscripts := make(chan houndify.PartialTranscript) + go func() { + for partial := range partialTranscripts { + if *partial.SafeToStopAudio { + fmt.Println("SafeToStopAudio received") + done <- true + return + } + } + }() + + serverResponse, err := houndSTTClient.VoiceSearch(req, partialTranscripts) + if err != nil { + fmt.Println(err) + fmt.Println(serverResponse) + } + resp, _ := preqs.ParseSpokenResponse(serverResponse) + logger.Println("Houndify response: " + resp) + return resp, nil +} diff --git a/chipper/pkg/wirepod/stt/leopard/Leopard.go b/chipper/pkg/wirepod/stt/leopard/Leopard.go new file mode 100644 index 0000000..d946e85 --- /dev/null +++ b/chipper/pkg/wirepod/stt/leopard/Leopard.go @@ -0,0 +1,98 @@ +package wirepod_leopard + +import ( + "fmt" + "os" + "strconv" + "strings" + "sync" + + leopard "github.com/Picovoice/leopard/binding/go/v2" + "github.com/kercre123/wire-pod/chipper/pkg/logger" + sr "github.com/kercre123/wire-pod/chipper/pkg/wirepod/speechrequest" +) + +var BotNum int +var BotNumMu sync.Mutex + +var Name string = "leopard" + +var leopardSTTArray []leopard.Leopard +var picovoiceInstancesOS string = os.Getenv("PICOVOICE_INSTANCES") +var picovoiceInstances int + +// New returns a new server +func Init() error { + var picovoiceKey string + picovoiceKeyOS := os.Getenv("PICOVOICE_APIKEY") + leopardKeyOS := os.Getenv("LEOPARD_APIKEY") + if picovoiceInstancesOS == "" { + picovoiceInstances = 3 + } else { + picovoiceInstancesToInt, err := strconv.Atoi(picovoiceInstancesOS) + picovoiceInstances = picovoiceInstancesToInt + if err != nil { + fmt.Println("PICOVOICE_INSTANCES is not a valid integer, using default value of 3") + picovoiceInstances = 3 + } + } + if picovoiceKeyOS == "" { + if leopardKeyOS == "" { + fmt.Println("You must set PICOVOICE_APIKEY to a value.") + os.Exit(1) + } else { + fmt.Println("PICOVOICE_APIKEY is not set, using LEOPARD_APIKEY") + picovoiceKey = leopardKeyOS + } + } else { + picovoiceKey = picovoiceKeyOS + } + fmt.Println("Initializing " + strconv.Itoa(picovoiceInstances) + " Picovoice Instances...") + for i := 0; i < picovoiceInstances; i++ { + fmt.Println("Initializing Picovoice Instance " + strconv.Itoa(i)) + leopardSTTArray = append(leopardSTTArray, leopard.NewLeopard(picovoiceKey)) + leopardSTTArray[i].Init() + } + return nil +} + +func STT(req sr.SpeechRequest) (transcribedText string, err error) { + BotNumMu.Lock() + BotNum = BotNum + 1 + BotNumMu.Unlock() + logger.Println("(Bot " + req.Device + ", Leopard) Processing...") + var leopardSTT leopard.Leopard + speechIsDone := false + if BotNum > picovoiceInstances { + fmt.Println("Too many bots are connected, sending error to bot " + req.Device) + return "", fmt.Errorf("too many bots are connected, max is 3") + } else { + leopardSTT = leopardSTTArray[BotNum-1] + } + for { + _, err = req.GetNextStreamChunk() + if err != nil { + BotNumMu.Lock() + BotNum = BotNum - 1 + BotNumMu.Unlock() + return "", err + } + speechIsDone, _ = req.DetectEndOfSpeech() + if speechIsDone { + break + } + } + transcribedTextPre, _, err := leopardSTT.Process(sr.BytesToSamples(req.DecodedMicData)) + if err != nil { + BotNumMu.Lock() + BotNum = BotNum - 1 + BotNumMu.Unlock() + logger.Println(err) + } + transcribedText = strings.ToLower(transcribedTextPre) + logger.Println("Bot " + req.Device + " Transcribed text: " + transcribedText) + BotNumMu.Lock() + BotNum = BotNum - 1 + BotNumMu.Unlock() + return transcribedText, nil +} diff --git a/chipper/pkg/wirepod/stt/vosk/Vosk.go b/chipper/pkg/wirepod/stt/vosk/Vosk.go new file mode 100644 index 0000000..286c320 --- /dev/null +++ b/chipper/pkg/wirepod/stt/vosk/Vosk.go @@ -0,0 +1,223 @@ +package wirepod_vosk + +import ( + "encoding/json" + "fmt" + "log" + "os" + "path/filepath" + "runtime" + "sync" + "time" + + vosk "github.com/kercre123/vosk-api/go" + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vars" + sr "github.com/kercre123/wire-pod/chipper/pkg/wirepod/speechrequest" +) + +var GrammerEnable bool = false + +var Name string = "vosk" + +var model *vosk.VoskModel +var recsmu sync.Mutex + +var grmRecs []ARec +var gpRecs []ARec + +var modelLoaded bool + +type ARec struct { + InUse bool + Rec *vosk.VoskRecognizer +} + +var Grammer string + +func Init() error { + if os.Getenv("VOSK_WITH_GRAMMER") == "true" { + fmt.Println("Initializing vosk with grammer optimizations") + GrammerEnable = true + } + if vars.APIConfig.PastInitialSetup { + vosk.SetLogLevel(-1) + if modelLoaded { + logger.Println("A model was already loaded, freeing all recognizers and model") + for ind, _ := range grmRecs { + grmRecs[ind].Rec.Free() + } + for ind, _ := range gpRecs { + gpRecs[ind].Rec.Free() + } + gpRecs = []ARec{} + grmRecs = []ARec{} + model.Free() + } + sttLanguage := vars.APIConfig.STT.Language + if len(sttLanguage) == 0 { + sttLanguage = "en-US" + } + modelPath := filepath.Join(vars.VoskModelPath, sttLanguage, "model") + if _, err := os.Stat(modelPath); err != nil { + fmt.Println("Path does not exist: " + modelPath) + return err + } + logger.Println("Opening VOSK model (" + modelPath + ")") + aModel, err := vosk.NewModel(modelPath) + if err != nil { + log.Fatal(err) + return err + } + model = aModel + if GrammerEnable { + logger.Println("Initializing grammer list") + Grammer = GetGrammerList(vars.APIConfig.STT.Language) + } + + logger.Println("Initializing VOSK recognizers") + if GrammerEnable { + grmRecognizer, err := vosk.NewRecognizerGrm(aModel, 16000.0, Grammer) + if err != nil { + log.Fatal(err) + } + var grmrec ARec + grmrec.Rec = grmRecognizer + grmrec.InUse = false + grmRecs = append(grmRecs, grmrec) + } + gpRecognizer, err := vosk.NewRecognizer(aModel, 16000.0) + var gprec ARec + gprec.Rec = gpRecognizer + gprec.InUse = false + gpRecs = append(gpRecs, gprec) + if err != nil { + log.Fatal(err) + } + modelLoaded = true + logger.Println("VOSK initiated successfully") + runTest() + } + return nil +} + +func runTest() { + // make sure recognizer is all loaded into RAM + logger.Println("Running recognizer test") + var withGrm bool + if GrammerEnable { + logger.Println("Using grammer-optimized recognizer") + withGrm = true + } else { + logger.Println("Using general recognizer") + withGrm = false + } + rec, recind := getRec(withGrm) + sttTestPath := "./stttest.pcm" + if runtime.GOOS == "android" { + sttTestPath = vars.AndroidPath + "/static/stttest.pcm" + } + pcmBytes, _ := os.ReadFile(sttTestPath) + var micData [][]byte + cTime := time.Now() + micData = sr.SplitVAD(pcmBytes) + for _, sample := range micData { + rec.AcceptWaveform(sample) + } + var jres map[string]interface{} + json.Unmarshal([]byte(rec.FinalResult()), &jres) + if withGrm { + grmRecs[recind].InUse = false + } else { + gpRecs[recind].InUse = false + } + transcribedText := jres["text"].(string) + tTime := time.Now().Sub(cTime) + logger.Println("Text (from test):", transcribedText) + if tTime.Seconds() > 3 { + logger.Println("Vosk test took a while, performance may be degraded. (" + fmt.Sprint(tTime) + ")") + } + logger.Println("Vosk test successful! (Took " + fmt.Sprint(tTime) + ")") + +} + +func getRec(withGrm bool) (*vosk.VoskRecognizer, int) { + recsmu.Lock() + defer recsmu.Unlock() + if withGrm && GrammerEnable { + for ind, rec := range grmRecs { + if !rec.InUse { + grmRecs[ind].InUse = true + return grmRecs[ind].Rec, ind + } + } + } else { + for ind, rec := range gpRecs { + if !rec.InUse { + gpRecs[ind].InUse = true + return gpRecs[ind].Rec, ind + } + } + } + recsmu.Unlock() + var newrec ARec + var newRec *vosk.VoskRecognizer + var err error + newrec.InUse = true + if withGrm { + newRec, err = vosk.NewRecognizerGrm(model, 16000.0, Grammer) + } else { + newRec, err = vosk.NewRecognizer(model, 16000.0) + } + if err != nil { + log.Fatal(err) + } + newrec.Rec = newRec + recsmu.Lock() + if withGrm { + grmRecs = append(grmRecs, newrec) + return grmRecs[len(grmRecs)-1].Rec, len(grmRecs) - 1 + } else { + gpRecs = append(gpRecs, newrec) + return gpRecs[len(gpRecs)-1].Rec, len(gpRecs) - 1 + } +} + +func STT(req sr.SpeechRequest) (string, error) { + logger.Println("(Bot " + req.Device + ", Vosk) Processing...") + var withGrm bool + if (vars.APIConfig.Knowledge.IntentGraph || req.IsKG) || !GrammerEnable { + logger.Println("Using general recognizer") + withGrm = false + } else { + logger.Println("Using grammer-optimized recognizer") + withGrm = true + } + rec, recind := getRec(withGrm) + rec.SetWords(1) + rec.AcceptWaveform(req.FirstReq) + req.DetectEndOfSpeech() + for { + chunk, err := req.GetNextStreamChunk() + if err != nil { + return "", err + } + speechIsDone, doProcess := req.DetectEndOfSpeech() + if doProcess { + rec.AcceptWaveform(chunk) + } + if speechIsDone { + break + } + } + var jres map[string]interface{} + json.Unmarshal([]byte(rec.FinalResult()), &jres) + if withGrm { + grmRecs[recind].InUse = false + } else { + gpRecs[recind].InUse = false + } + transcribedText := jres["text"].(string) + logger.Println("Bot " + req.Device + " Transcribed text: " + transcribedText) + return transcribedText, nil +} diff --git a/chipper/pkg/wirepod/stt/vosk/context.go b/chipper/pkg/wirepod/stt/vosk/context.go new file mode 100644 index 0000000..7262941 --- /dev/null +++ b/chipper/pkg/wirepod/stt/vosk/context.go @@ -0,0 +1,79 @@ +package wirepod_vosk + +import ( + "strings" + + "github.com/kercre123/wire-pod/chipper/pkg/vars" + "github.com/kercre123/wire-pod/chipper/pkg/wirepod/localization" +) + +var NumbersEN_US []string = []string{"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety", "hundred", "seconds", "minutes", "hours", "minute", "second", "hour"} + +func removeDuplicates(strings []string) []string { + occurred := map[string]bool{} + var result []string + for _, str := range strings { + if !occurred[str] { + result = append(result, str) + occurred[str] = true + } + } + return result +} +func GetGrammerList(lang string) string { + var wordsList []string + var grammer string + // add words in intent json + for _, words := range vars.IntentList { + for _, word := range words.Keyphrases { + wors := strings.Split(word, " ") + for _, wor := range wors { + found := model.FindWord(wor) + if found != -1 { + wordsList = append(wordsList, wor) + } + } + } + } + // add words in localization + for _, str := range localization.ALL_STR { + text := localization.GetText(str) + wors := strings.Split(text, " ") + for _, wor := range wors { + found := model.FindWord(wor) + if found != -1 { + wordsList = append(wordsList, wor) + } + } + } + // add custom intent matches + for _, intent := range vars.CustomIntents { + for _, utterance := range intent.Utterances { + wors := strings.Split(utterance, " ") + for _, wor := range wors { + found := model.FindWord(wor) + if found != -1 { + wordsList = append(wordsList, wor) + } + } + } + } + // add numbers + for _, wor := range NumbersEN_US { + found := model.FindWord(wor) + if found != -1 { + wordsList = append(wordsList, wor) + } + } + + wordsList = removeDuplicates(wordsList) + for i, word := range wordsList { + if i == len(wordsList)-1 { + grammer = grammer + `"` + word + `"` + } else { + grammer = grammer + `"` + word + `"` + ", " + } + } + grammer = "[" + grammer + "]" + return grammer +} diff --git a/chipper/pkg/wirepod/stt/whisper.cpp/WhisperCpp.go b/chipper/pkg/wirepod/stt/whisper.cpp/WhisperCpp.go new file mode 100644 index 0000000..bd99a28 --- /dev/null +++ b/chipper/pkg/wirepod/stt/whisper.cpp/WhisperCpp.go @@ -0,0 +1,117 @@ +package wirepod_whispercpp + +import ( + "encoding/binary" + "math" + "os" + "path/filepath" + "runtime" + "strings" + + whisper "github.com/ggerganov/whisper.cpp/bindings/go" + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vars" + sr "github.com/kercre123/wire-pod/chipper/pkg/wirepod/speechrequest" +) + +var Name string = "whisper.cpp" + +var context *whisper.Context +var params whisper.Params + +func padPCM(data []byte) []byte { + const sampleRate = 16000 + const minDurationMs = 1020 + const minDurationSamples = sampleRate * minDurationMs / 1000 + const bytesPerSample = 2 + + currentSamples := len(data) / bytesPerSample + + if currentSamples >= minDurationSamples { + return data + } + + logger.Println("Padding audio data to be 1000ms") + + paddingSamples := minDurationSamples - currentSamples + paddingBytes := make([]byte, paddingSamples*bytesPerSample) + + return append(data, paddingBytes...) +} + +func Init() error { + whispModel := os.Getenv("WHISPER_MODEL") + if whispModel == "" { + logger.Println("WHISPER_MODEL not defined, assuming tiny") + whispModel = "tiny" + } else { + whispModel = strings.TrimSpace(whispModel) + } + var sttLanguage string + if len(vars.APIConfig.STT.Language) == 0 { + sttLanguage = "en" + } else { + sttLanguage = strings.Split(vars.APIConfig.STT.Language, "-")[0] + } + + modelPath := filepath.Join(vars.WhisperModelPath, "ggml-"+whispModel+".bin") + if _, err := os.Stat(modelPath); err != nil { + logger.Println("Model does not exist: " + modelPath) + return err + } + logger.Println("Opening Whisper model (" + modelPath + ")") + //logger.Println(whisper.Whisper_print_system_info()) + context = whisper.Whisper_init(modelPath) + params = context.Whisper_full_default_params(whisper.SAMPLING_GREEDY) + params.SetTranslate(false) + params.SetPrintSpecial(false) + params.SetPrintProgress(false) + params.SetPrintRealtime(false) + params.SetPrintTimestamps(false) + params.SetThreads(runtime.NumCPU()) + params.SetNoContext(true) + params.SetSingleSegment(true) + params.SetLanguage(context.Whisper_lang_id(sttLanguage)) + return nil +} + +func STT(req sr.SpeechRequest) (string, error) { + logger.Println("(Bot " + req.Device + ", Whisper) Processing...") + speechIsDone := false + var err error + for { + _, err = req.GetNextStreamChunk() + if err != nil { + return "", err + } + // has to be split into 320 []byte chunks for VAD + speechIsDone, _ = req.DetectEndOfSpeech() + if speechIsDone { + break + } + } + transcribedText, err := process(BytesToFloat32Buffer(padPCM(req.DecodedMicData))) + if err != nil { + return "", err + } + transcribedText = strings.ToLower(transcribedText) + logger.Println("Bot " + req.Device + " Transcribed text: " + transcribedText) + return transcribedText, nil +} + +func process(data []float32) (string, error) { + var transcribedText string + context.Whisper_full(params, data, nil, func(_ int) { + transcribedText = strings.TrimSpace(context.Whisper_full_get_segment_text(0)) + }, nil) + return transcribedText, nil +} + +func BytesToFloat32Buffer(buf []byte) []float32 { + newB := make([]float32, len(buf)/2) + factor := math.Pow(2, float64(16)-1) + for i := 0; i < len(buf)/2; i++ { + newB[i] = float32(float64(int16(binary.LittleEndian.Uint16(buf[i*2:]))) / factor) + } + return newB +} diff --git a/chipper/pkg/wirepod/stt/whisper/Whisper.go b/chipper/pkg/wirepod/stt/whisper/Whisper.go new file mode 100644 index 0000000..96e985b --- /dev/null +++ b/chipper/pkg/wirepod/stt/whisper/Whisper.go @@ -0,0 +1,136 @@ +package wirepod_whisper + +import ( + "bytes" + "encoding/binary" + "encoding/json" + "io" + "mime/multipart" + "net/http" + "os" + "strings" + + "github.com/go-audio/audio" + "github.com/go-audio/wav" + "github.com/kercre123/wire-pod/chipper/pkg/logger" + sr "github.com/kercre123/wire-pod/chipper/pkg/wirepod/speechrequest" + "github.com/orcaman/writerseeker" +) + +var Name string = "whisper" + +type openAiResp struct { + Text string `json:"text"` +} + +func Init() error { + if os.Getenv("OPENAI_KEY") == "" { + logger.Println("This is an early implementation of the Whisper API which has not been implemented into the web interface. You must set the OPENAI_KEY env var.") + //os.Exit(1) + } + return nil +} + +func pcm2wav(in io.Reader) []byte { + + // Output file. + out := &writerseeker.WriterSeeker{} + + // 8 kHz, 16 bit, 1 channel, WAV. + e := wav.NewEncoder(out, 16000, 16, 1, 1) + + // Create new audio.IntBuffer. + audioBuf, err := newAudioIntBuffer(in) + if err != nil { + logger.Println(err) + } + // Write buffer to output file. This writes a RIFF header and the PCM chunks from the audio.IntBuffer. + if err := e.Write(audioBuf); err != nil { + logger.Println(err) + } + if err := e.Close(); err != nil { + logger.Println(err) + } + outBuf := new(bytes.Buffer) + io.Copy(outBuf, out.BytesReader()) + return outBuf.Bytes() +} + +func newAudioIntBuffer(r io.Reader) (*audio.IntBuffer, error) { + buf := audio.IntBuffer{ + Format: &audio.Format{ + NumChannels: 1, + SampleRate: 16000, + }, + } + for { + var sample int16 + err := binary.Read(r, binary.LittleEndian, &sample) + switch { + case err == io.EOF: + return &buf, nil + case err != nil: + return nil, err + } + buf.Data = append(buf.Data, int(sample)) + } +} + +func makeOpenAIReq(in []byte) string { + url := "https://api.openai.com/v1/audio/transcriptions" + + buf := new(bytes.Buffer) + w := multipart.NewWriter(buf) + w.WriteField("model", "whisper-1") + sendFile, _ := w.CreateFormFile("file", "audio.mp3") + sendFile.Write(in) + w.Close() + + httpReq, _ := http.NewRequest("POST", url, buf) + httpReq.Header.Set("Content-Type", w.FormDataContentType()) + httpReq.Header.Set("Authorization", "Bearer "+os.Getenv("OPENAI_KEY")) + + client := &http.Client{} + resp, err := client.Do(httpReq) + if err != nil { + logger.Println(err) + return "There was an error." + } + + defer resp.Body.Close() + + response, _ := io.ReadAll(resp.Body) + + var aiResponse openAiResp + json.Unmarshal(response, &aiResponse) + + return aiResponse.Text +} + +func STT(req sr.SpeechRequest) (string, error) { + logger.Println("(Bot " + req.Device + ", Whisper) Processing...") + speechIsDone := false + var err error + for { + _, err = req.GetNextStreamChunk() + if err != nil { + return "", err + } + if err != nil { + return "", err + } + // has to be split into 320 []byte chunks for VAD + speechIsDone, _ = req.DetectEndOfSpeech() + if speechIsDone { + break + } + } + + pcmBufTo := &writerseeker.WriterSeeker{} + pcmBufTo.Write(req.DecodedMicData) + pcmBuf := pcm2wav(pcmBufTo.BytesReader()) + + transcribedText := strings.ToLower(makeOpenAIReq(pcmBuf)) + logger.Println("Bot " + req.Device + " Transcribed text: " + transcribedText) + return transcribedText, nil +} diff --git a/chipper/pkg/wirepod/ttr/audio_utils.go b/chipper/pkg/wirepod/ttr/audio_utils.go new file mode 100644 index 0000000..42a8d81 --- /dev/null +++ b/chipper/pkg/wirepod/ttr/audio_utils.go @@ -0,0 +1,92 @@ +package wirepod_ttr + +import ( + "encoding/binary" + "math" +) + +func bytesToInt16s(data []byte) []int16 { + int16s := make([]int16, len(data)/2) + for i := range int16s { + int16s[i] = int16(binary.LittleEndian.Uint16(data[i*2 : i*2+2])) + } + return int16s +} + +func int16sToBytes(data []int16) []byte { + bytes := make([]byte, len(data)*2) + for i, val := range data { + binary.LittleEndian.PutUint16(bytes[i*2:], uint16(val)) + } + return bytes +} + +func downsample24kTo16k(input []byte) [][]byte { + outBytes := downsample24kTo16kLinear(input) + var audioChunks [][]byte + filteredBytes := lowPassFilter(outBytes, 4000, 16000) + iVolBytes := increaseVolume(filteredBytes, 5) + for len(iVolBytes) > 0 { + if len(iVolBytes) < 1024 { + chunk := make([]byte, 1024) + copy(chunk, iVolBytes) + audioChunks = append(audioChunks, chunk) + break + } + audioChunks = append(audioChunks, iVolBytes[:1024]) + iVolBytes = iVolBytes[1024:] + } + + return audioChunks +} + +func increaseVolume(data []byte, factor float64) []byte { + int16s := bytesToInt16s(data) + + for i := range int16s { + scaled := float64(int16s[i]) * factor + if scaled > math.MaxInt16 { + int16s[i] = math.MaxInt16 + } else if scaled < math.MinInt16 { + int16s[i] = math.MinInt16 + } else { + int16s[i] = int16(scaled) + } + } + + return int16sToBytes(int16s) +} + +// this is copied +func lowPassFilter(data []byte, cutoffFreq float64, sampleRate int) []byte { + int16s := bytesToInt16s(data) + filtered := make([]int16, len(int16s)) + rc := 1.0 / (2 * 3.1416 * cutoffFreq) + dt := 1.0 / float64(sampleRate) + alpha := dt / (rc + dt) + filtered[0] = int16s[0] + for i := 1; i < len(int16s); i++ { + current := alpha*float64(int16s[i]) + (1-alpha)*float64(filtered[i-1]) + filtered[i] = int16(current) + } + + return int16sToBytes(filtered) +} + +// copied too +func downsample24kTo16kLinear(input []byte) []byte { + int16s := bytesToInt16s(input) + outputLength := (len(int16s) * 2) / 3 + output := make([]int16, outputLength) + + j := 0 + for i := 0; i < len(int16s)-2; i += 3 { + first := (2*int32(int16s[i]) + int32(int16s[i+1])) / 3 + second := (int32(int16s[i+1]) + 2*int32(int16s[i+2])) / 3 + output[j] = int16(first) + output[j+1] = int16(second) + j += 2 + } + + return int16sToBytes(output) +} diff --git a/chipper/pkg/wirepod/ttr/bcontrol.go b/chipper/pkg/wirepod/ttr/bcontrol.go new file mode 100644 index 0000000..a5ef94a --- /dev/null +++ b/chipper/pkg/wirepod/ttr/bcontrol.go @@ -0,0 +1,141 @@ +package wirepod_ttr + +import ( + "context" + "log" + + "github.com/fforchino/vector-go-sdk/pkg/vector" + "github.com/fforchino/vector-go-sdk/pkg/vectorpb" + "github.com/kercre123/wire-pod/chipper/pkg/logger" +) + +func sayText(robot *vector.Vector, text string) { + controlRequest := &vectorpb.BehaviorControlRequest{ + RequestType: &vectorpb.BehaviorControlRequest_ControlRequest{ + ControlRequest: &vectorpb.ControlRequest{ + Priority: vectorpb.ControlRequest_OVERRIDE_BEHAVIORS, + }, + }, + } + go func() { + start := make(chan bool) + stop := make(chan bool) + go func() { + // * begin - modified from official vector-go-sdk + r, err := robot.Conn.BehaviorControl( + context.Background(), + ) + if err != nil { + log.Println(err) + return + } + + if err := r.Send(controlRequest); err != nil { + log.Println(err) + return + } + + for { + ctrlresp, err := r.Recv() + if err != nil { + log.Println(err) + return + } + if ctrlresp.GetControlGrantedResponse() != nil { + start <- true + break + } + } + + for { + select { + case <-stop: + if err := r.Send( + &vectorpb.BehaviorControlRequest{ + RequestType: &vectorpb.BehaviorControlRequest_ControlRelease{ + ControlRelease: &vectorpb.ControlRelease{}, + }, + }, + ); err != nil { + log.Println(err) + return + } + return + default: + continue + } + } + // * end - modified from official vector-go-sdk + }() + for range start { + robot.Conn.SayText( + context.Background(), + &vectorpb.SayTextRequest{ + Text: text, + UseVectorVoice: true, + DurationScalar: 1.0, + }, + ) + stop <- true + } + }() +} + +func BControl(robot *vector.Vector, ctx context.Context, start, stop chan bool) { + controlRequest := &vectorpb.BehaviorControlRequest{ + RequestType: &vectorpb.BehaviorControlRequest_ControlRequest{ + ControlRequest: &vectorpb.ControlRequest{ + Priority: vectorpb.ControlRequest_OVERRIDE_BEHAVIORS, + }, + }, + } + + go func() { + // * begin - modified from official vector-go-sdk + r, err := robot.Conn.BehaviorControl( + ctx, + ) + if err != nil { + logger.Println(err) + return + } + + if err := r.Send(controlRequest); err != nil { + logger.Println(err) + return + } + + for { + ctrlresp, err := r.Recv() + if err != nil { + logger.Println(err) + return + } + if ctrlresp.GetControlGrantedResponse() != nil { + start <- true + break + } + } + + for { + select { + case <-stop: + logger.Println("KGSim: releasing behavior control (interrupt)") + if err := r.Send( + &vectorpb.BehaviorControlRequest{ + RequestType: &vectorpb.BehaviorControlRequest_ControlRelease{ + ControlRelease: &vectorpb.ControlRelease{}, + }, + }, + ); err != nil { + logger.Println(err) + return + } + return + default: + continue + } + } + // * end - modified from official vector-go-sdk + }() +} diff --git a/chipper/pkg/wirepod/ttr/create_ai_prompt.go b/chipper/pkg/wirepod/ttr/create_ai_prompt.go new file mode 100644 index 0000000..2751477 --- /dev/null +++ b/chipper/pkg/wirepod/ttr/create_ai_prompt.go @@ -0,0 +1,102 @@ + +package wirepod_ttr + +import ( + "fmt" + "os" + "strings" + + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vars" +) + +func ModelIsSupported(cmd LLMCommand, model string) bool { + for _, str := range cmd.SupportedModels { + if str == "all" || str == model { + return true + } + } + return false +} + +func CreatePrompt(origPrompt string, model string, isKG bool) string { + prompt := origPrompt + promptForVector + if vars.APIConfig.Knowledge.CommandsEnable { + prompt += promptForVectorAI + + for _, cmd := range ValidLLMCommands { + if ModelIsSupported(cmd, model) { + prompt += fmt.Sprintf("\n\nCommand Name: %s\nDescription: %s\nParameter choices: %s", cmd.Command, cmd.Description, cmd.ParamChoices) + } + } + if isKG && vars.APIConfig.Knowledge.SaveChat { + promptAppentage := conversationMode1 + prompt = prompt + promptAppentage + } else { + promptAppentage := conversationMode0 + prompt = prompt + promptAppentage + } + } + if os.Getenv("DEBUG_PRINT_PROMPT") == "true" { + logger.Println(prompt) + } + return prompt +} + +func GetActionsFromString(input string) []RobotAction { + splitInput := strings.Split(input, "{{") + if len(splitInput) == 1 { + return []RobotAction{ + { + Action: ActionSayText, + Parameter: input, + }, + } + } + var actions []RobotAction + for _, spl := range splitInput { + if strings.TrimSpace(spl) == "" { + continue + } + if !strings.Contains(spl, "}}") { + // sayText + action := RobotAction{ + Action: ActionSayText, + Parameter: strings.TrimSpace(spl), + } + actions = append(actions, action) + continue + } + + cmdPlusParam := strings.Split(strings.TrimSpace(strings.Split(spl, "}}")[0]), "||") + cmd := strings.TrimSpace(cmdPlusParam[0]) + param := strings.TrimSpace(cmdPlusParam[1]) + action := CmdParamToAction(cmd, param) + if action.Action != -1 { + actions = append(actions, action) + } + if len(strings.Split(spl, "}}")) != 1 { + action := RobotAction{ + Action: ActionSayText, + Parameter: strings.TrimSpace(strings.Split(spl, "}}")[1]), + } + actions = append(actions, action) + } + } + return actions +} + +func CmdParamToAction(cmd, param string) RobotAction { + for _, command := range ValidLLMCommands { + if cmd == command.Command { + return RobotAction{ + Action: command.Action, + Parameter: param, + } + } + } + logger.Println("LLM tried to do a command which doesn't exist: " + cmd + " (param: " + param + ")") + return RobotAction{ + Action: -1, + } +} diff --git a/chipper/pkg/wirepod/ttr/create_ai_prompt_vars.go b/chipper/pkg/wirepod/ttr/create_ai_prompt_vars.go new file mode 100644 index 0000000..f77716b --- /dev/null +++ b/chipper/pkg/wirepod/ttr/create_ai_prompt_vars.go @@ -0,0 +1,29 @@ + +package wirepod_ttr + +//"happy, veryHappy, sad, verySad, angry, dartingEyes, confused, thinking, celebrate, love" +var animationMap [][2]string = [][2]string{ + { "happy", "anim_onboarding_reacttoface_happy_01", }, + { "veryHappy", "anim_blackjack_victorwin_01", }, + { "sad", "anim_feedback_meanwords_01", }, + { "verySad", "anim_feedback_meanwords_01", }, + { "angry", "anim_rtpickup_loop_10", }, + { "frustrated", "anim_feedback_shutup_01", }, + { "dartingEyes", "anim_observing_self_absorbed_01", }, + { "confused", "anim_meetvictor_lookface_timeout_01", }, + { "thinking", "anim_explorer_scan_short_04", }, + { "celebrate", "anim_pounce_success_03", }, + { "love", "anim_feedback_iloveyou_02", }, +} + +var ( + defaultPrompt = "\nYou are a helpful, animated robot called Vector. " + + promptForVector = "\nFormat responses to communicate as an Anki Vector robot. User input may contain errors due to unreliable software. Evaluate for grammatical errors, missing words, or nonsensical phrases. Consider the overall context of the input within conversation to resolve ambiguities. If input is determined unintelligible or irrelevant, request clarification. And similarily, provide reasonable variable length responses. " + + promptForVectorAI = "\nIntegrate animation commands tastefully (format: {{playAnimationWI||actionParameter}}) into responses, with one at the start, and then another, just before every other sentence. Embellish with lots of emojis, symbols, and special characters to enhance emotional expression and engagement. Ensure tone, subject matter, and target audience guide your animation placement for maximum impact. Use combinations like {{playAnimationWI||happy}} for a playful undertone or {{playAnimationWI||dartingEyes}} for a cheeky effect. Other examples include subtle buildup with a surprising twist, varying emotions and pacing, or humorous and engaging expressions like being playfully personal in a lighthearted or old-fashioned context. Build suspense and payoff, create urgency, or foster celebration with animations like {{playAnimationWI||celebrate}} for 'I finished!' Use, example: {{getImage||front}} for photos. Never position action commands in a row. Contextually complement with valid action parameters: happy, veryHappy, sad, verySad, angry, frustrated, dartingEyes, confused, thinking, celebrate, and love. " + + conversationMode0 = "\n\nNOTE: You are NOT in 'conversation' mode. Avoid both, asking the user questions and using 'newVoiceRequest'. " + + conversationMode1 = "\n\nNOTE: You're now in 'conversation' mode. Use 'newVoiceRequest' to ask a question. Questions are asked at the end. Otherwise avoid 'newVoiceRequest' to end the conversation. " +) diff --git a/chipper/pkg/wirepod/ttr/create_ai_request.go b/chipper/pkg/wirepod/ttr/create_ai_request.go new file mode 100644 index 0000000..7663260 --- /dev/null +++ b/chipper/pkg/wirepod/ttr/create_ai_request.go @@ -0,0 +1,62 @@ +package wirepod_ttr + +import ( + "fmt" + "strings" + + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vars" + "github.com/sashabaranov/go-openai" +) + +func CreateAIReq(transcribedText, esn string, gpt3tryagain, isKG bool) openai.ChatCompletionRequest { + defaultPrompt := "You are a helpful, animated robot called Vector. Keep the response concise yet informative." + + var nChat []openai.ChatCompletionMessage + + smsg := openai.ChatCompletionMessage{ + Role: openai.ChatMessageRoleSystem, + } + if strings.TrimSpace(vars.APIConfig.Knowledge.OpenAIPrompt) != "" { + smsg.Content = strings.TrimSpace(vars.APIConfig.Knowledge.OpenAIPrompt) + } else { + smsg.Content = defaultPrompt + } + + var model string + + if gpt3tryagain { + model = openai.GPT3Dot5Turbo + } else if vars.APIConfig.Knowledge.Provider == "openai" { + model = openai.GPT4oMini + logger.Println("Using " + model) + } else { + logger.Println("Using " + vars.APIConfig.Knowledge.Model) + model = vars.APIConfig.Knowledge.Model + } + + smsg.Content = CreatePrompt(smsg.Content, model, isKG) + + nChat = append(nChat, smsg) + if vars.APIConfig.Knowledge.SaveChat { + rchat := GetChat(esn) + logger.Println("Using remembered chats, length of " + fmt.Sprint(len(rchat.Chats)) + " messages") + nChat = append(nChat, rchat.Chats...) + } + nChat = append(nChat, openai.ChatCompletionMessage{ + Role: openai.ChatMessageRoleUser, + Content: transcribedText, + }) + + aireq := openai.ChatCompletionRequest{ + Model: model, + MaxTokens: 4095, + Temperature: 1, + TopP: 1, + FrequencyPenalty: 0, + PresencePenalty: 0, + Messages: nChat, + Stream: true, + } + return aireq +} diff --git a/chipper/pkg/wirepod/ttr/do_actions.go b/chipper/pkg/wirepod/ttr/do_actions.go new file mode 100644 index 0000000..fa780c6 --- /dev/null +++ b/chipper/pkg/wirepod/ttr/do_actions.go @@ -0,0 +1,40 @@ +package wirepod_ttr + +import ( + "github.com/fforchino/vector-go-sdk/pkg/vector" + "github.com/sashabaranov/go-openai" +) + +func PerformActions(msgs []openai.ChatCompletionMessage, actions []RobotAction, robot *vector.Vector, stopStop chan bool) bool { + // assuming we have behavior control already + stopPerforming := false + go func() { + for range stopStop { + stopPerforming = true + } + }() + for _, action := range actions { + if stopPerforming { + return false + } + switch { + case action.Action == ActionSayText: + DoSayText(action.Parameter, robot) + case action.Action == ActionPlayAnimation: + DoPlayAnimation(action.Parameter, robot) + case action.Action == ActionPlayAnimationWI: + DoPlayAnimationWI(action.Parameter, robot) + case action.Action == ActionNewRequest: + go DoNewRequest(robot) + return true + case action.Action == ActionGetImage: + DoGetImage(msgs, action.Parameter, robot, stopStop) + return true + case action.Action == ActionPlaySound: + DoPlaySound(action.Parameter, robot) + } + } + WaitForAnim_Queue(robot.Cfg.SerialNo) + return false +} + diff --git a/chipper/pkg/wirepod/ttr/do_animation_queue.go b/chipper/pkg/wirepod/ttr/do_animation_queue.go new file mode 100644 index 0000000..aec041d --- /dev/null +++ b/chipper/pkg/wirepod/ttr/do_animation_queue.go @@ -0,0 +1,61 @@ +package wirepod_ttr + +import ( + "github.com/kercre123/wire-pod/chipper/pkg/logger" +) + +type AnimationQueue struct { + ESN string + AnimDone chan bool + AnimCurrentlyPlaying bool +} + +var AnimationQueues []AnimationQueue + +func WaitForAnim_Queue(esn string) { + for i, q := range AnimationQueues { + if q.ESN == esn { + if q.AnimCurrentlyPlaying { + for range AnimationQueues[i].AnimDone { + break + } + return + } + } + } +} + +func StartAnim_Queue(esn string) { + // if animation is already playing, just wait for it to be done + for i, q := range AnimationQueues { + if q.ESN == esn { + if q.AnimCurrentlyPlaying { + for range AnimationQueues[i].AnimDone { + logger.Println("(waiting for animation to be done...)") + break + } + } else { + AnimationQueues[i].AnimCurrentlyPlaying = true + } + return + } + } + var aq AnimationQueue + aq.AnimCurrentlyPlaying = true + aq.AnimDone = make(chan bool) + aq.ESN = esn + AnimationQueues = append(AnimationQueues, aq) +} + +func StopAnim_Queue(esn string) { + for i, q := range AnimationQueues { + if q.ESN == esn { + AnimationQueues[i].AnimCurrentlyPlaying = false + select { + case AnimationQueues[i].AnimDone <- true: + default: + } + } + } +} + diff --git a/chipper/pkg/wirepod/ttr/do_chats.go b/chipper/pkg/wirepod/ttr/do_chats.go new file mode 100644 index 0000000..70d96a4 --- /dev/null +++ b/chipper/pkg/wirepod/ttr/do_chats.go @@ -0,0 +1,50 @@ +package wirepod_ttr + +import ( + "github.com/kercre123/wire-pod/chipper/pkg/vars" + "github.com/sashabaranov/go-openai" +) + +func GetChat(esn string) vars.RememberedChat { + for _, chat := range vars.RememberedChats { + if chat.ESN == esn { + return chat + } + } + return vars.RememberedChat{ + ESN: esn, + } +} + +func PlaceChat(chat vars.RememberedChat) { + for i, achat := range vars.RememberedChats { + if achat.ESN == chat.ESN { + vars.RememberedChats[i] = chat + return + } + } + vars.RememberedChats = append(vars.RememberedChats, chat) +} + +// remember last 16 lines of chat +func Remember(user, ai openai.ChatCompletionMessage, esn string) { + chatAppend := []openai.ChatCompletionMessage{ + user, + ai, + } + currentChat := GetChat(esn) + if len(currentChat.Chats) == 16 { + var newChat vars.RememberedChat + newChat.ESN = currentChat.ESN + for i, chat := range currentChat.Chats { + if i < 2 { + continue + } + newChat.Chats = append(newChat.Chats, chat) + } + currentChat = newChat + } + currentChat.ESN = esn + currentChat.Chats = append(currentChat.Chats, chatAppend...) + PlaceChat(currentChat) +} diff --git a/chipper/pkg/wirepod/ttr/do_commands_animation.go b/chipper/pkg/wirepod/ttr/do_commands_animation.go new file mode 100644 index 0000000..9de1a8a --- /dev/null +++ b/chipper/pkg/wirepod/ttr/do_commands_animation.go @@ -0,0 +1,53 @@ +package wirepod_ttr + +import ( + "context" + + "github.com/fforchino/vector-go-sdk/pkg/vector" + "github.com/fforchino/vector-go-sdk/pkg/vectorpb" + "github.com/kercre123/wire-pod/chipper/pkg/logger" +) + +func DoPlayAnimation(animation string, robot *vector.Vector) error { + for _, animThing := range animationMap { + if animation == animThing[0] { + StartAnim_Queue(robot.Cfg.SerialNo) + robot.Conn.PlayAnimation( + context.Background(), + &vectorpb.PlayAnimationRequest{ + Animation: &vectorpb.Animation{ + Name: animThing[1], + }, + Loops: 1, + }, + ) + StopAnim_Queue(robot.Cfg.SerialNo) + return nil + } + } + logger.Println("Animation provided by LLM doesn't exist: " + animation) + return nil +} + +func DoPlayAnimationWI(animation string, robot *vector.Vector) error { + for _, animThing := range animationMap { + if animation == animThing[0] { + go func() { + StartAnim_Queue(robot.Cfg.SerialNo) + robot.Conn.PlayAnimation( + context.Background(), + &vectorpb.PlayAnimationRequest{ + Animation: &vectorpb.Animation{ + Name: animThing[1], + }, + Loops: 1, + }, + ) + StopAnim_Queue(robot.Cfg.SerialNo) + }() + return nil + } + } + logger.Println("Animation provided by LLM doesn't exist: " + animation) + return nil +} diff --git a/chipper/pkg/wirepod/ttr/do_commands_audio.go b/chipper/pkg/wirepod/ttr/do_commands_audio.go new file mode 100644 index 0000000..0f1d370 --- /dev/null +++ b/chipper/pkg/wirepod/ttr/do_commands_audio.go @@ -0,0 +1,81 @@ +package wirepod_ttr + +import ( + "context" + "time" + + "github.com/fforchino/vector-go-sdk/pkg/vector" + "github.com/fforchino/vector-go-sdk/pkg/vectorpb" + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vars" + "github.com/sashabaranov/go-openai" +) + +var soundMap [][2]string = [][2]string{ + { + "drumroll", + "sounds/drumroll.wav", + }, +} + +func DoPlaySound(sound string, robot *vector.Vector) error { + for _, soundThing := range soundMap { + if sound == soundThing[0] { + logger.Println("Would play sound") + } + } + logger.Println("Sound provided by LLM doesn't exist: " + sound) + return nil +} + +func DoSayText(rawStr string, robot *vector.Vector) error { + // Just before vector speaks + rawStr = undoTmpDecPnt(rawStr) + cleanedInput, err := removeSpecialCharacters(rawStr) + if err != nil { + logger.Println("Error cleaning input:", err) + return err + } + + if (vars.APIConfig.STT.Language != "en-US" && vars.APIConfig.Knowledge.Provider == "openai") || vars.APIConfig.Knowledge.OpenAIVoiceWithEnglish { + err := DoSayText_OpenAI(robot, cleanedInput) + return err + } + + robot.Conn.SayText( + context.Background(), + &vectorpb.SayTextRequest{ + Text: cleanedInput, + UseVectorVoice: true, + DurationScalar: 0.95, + }, + ) + return nil +} + +func pcmLength(data []byte) time.Duration { + bytesPerSample := 2 + sampleRate := 16000 + numSamples := len(data) / bytesPerSample + duration := time.Duration(numSamples*1000/sampleRate) * time.Millisecond + return duration +} + +func getOpenAIVoice(voice string) openai.SpeechVoice { + voiceMap := map[string]openai.SpeechVoice{ + "alloy": openai.VoiceAlloy, + "onyx": openai.VoiceOnyx, + "fable": openai.VoiceFable, + "shimmer": openai.VoiceShimmer, + "nova": openai.VoiceNova, + "echo": openai.VoiceEcho, + "": openai.VoiceFable, + } + return voiceMap[voice] +} + +func DoNewRequest(robot *vector.Vector) { + time.Sleep(time.Second / 3) + robot.Conn.AppIntent(context.Background(), &vectorpb.AppIntentRequest{Intent: "knowledge_question"}) +} + diff --git a/chipper/pkg/wirepod/ttr/do_commands_audio_openai.go b/chipper/pkg/wirepod/ttr/do_commands_audio_openai.go new file mode 100644 index 0000000..96e982f --- /dev/null +++ b/chipper/pkg/wirepod/ttr/do_commands_audio_openai.go @@ -0,0 +1,78 @@ +package wirepod_ttr + +import ( + "context" + "io" + "strings" + "time" + + "github.com/fforchino/vector-go-sdk/pkg/vector" + "github.com/fforchino/vector-go-sdk/pkg/vectorpb" + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vars" + "github.com/sashabaranov/go-openai" +) + +// TODO +func DoSayText_OpenAI(robot *vector.Vector, input string) error { + if strings.TrimSpace(input) == "" { + return nil + } + openaiVoice := getOpenAIVoice(vars.APIConfig.Knowledge.OpenAIVoice) + // if vars.APIConfig.Knowledge.OpenAIVoice == "" { + // openaiVoice = openai.VoiceFable + // } else { + // openaiVoice = getOpenAIVoice(vars.APIConfig.Knowledge.OpenAIPrompt) + // } + oc := openai.NewClient(vars.APIConfig.Knowledge.Key) + resp, err := oc.CreateSpeech(context.Background(), openai.CreateSpeechRequest{ + Model: openai.TTSModel1, + Input: input, + Voice: openaiVoice, + ResponseFormat: openai.SpeechResponseFormatPcm, + }) + if err != nil { + logger.Println(err) + return err + } + speechBytes, _ := io.ReadAll(resp) + vclient, err := robot.Conn.ExternalAudioStreamPlayback(context.Background()) + if err != nil { + return err + } + vclient.Send(&vectorpb.ExternalAudioStreamRequest{ + AudioRequestType: &vectorpb.ExternalAudioStreamRequest_AudioStreamPrepare{ + AudioStreamPrepare: &vectorpb.ExternalAudioStreamPrepare{ + AudioFrameRate: 16000, + AudioVolume: 100, + }, + }, + }) + //time.Sleep(time.Millisecond * 30) + audioChunks := downsample24kTo16k(speechBytes) + + var chunksToDetermineLength []byte + for _, chunk := range audioChunks { + chunksToDetermineLength = append(chunksToDetermineLength, chunk...) + } + go func() { + for _, chunk := range audioChunks { + vclient.Send(&vectorpb.ExternalAudioStreamRequest{ + AudioRequestType: &vectorpb.ExternalAudioStreamRequest_AudioStreamChunk{ + AudioStreamChunk: &vectorpb.ExternalAudioStreamChunk{ + AudioChunkSizeBytes: 1024, + AudioChunkSamples: chunk, + }, + }, + }) + time.Sleep(time.Millisecond * 25) + } + vclient.Send(&vectorpb.ExternalAudioStreamRequest{ + AudioRequestType: &vectorpb.ExternalAudioStreamRequest_AudioStreamComplete{ + AudioStreamComplete: &vectorpb.ExternalAudioStreamComplete{}, + }, + }) + }() + time.Sleep(pcmLength(chunksToDetermineLength) + (time.Millisecond * 50)) + return nil +} \ No newline at end of file diff --git a/chipper/pkg/wirepod/ttr/do_commands_image.go b/chipper/pkg/wirepod/ttr/do_commands_image.go new file mode 100644 index 0000000..90e870e --- /dev/null +++ b/chipper/pkg/wirepod/ttr/do_commands_image.go @@ -0,0 +1,235 @@ +package wirepod_ttr + +import ( + "context" + "encoding/base64" + "errors" + "fmt" + "io" + "strings" + "time" + + "github.com/fforchino/vector-go-sdk/pkg/vector" + "github.com/fforchino/vector-go-sdk/pkg/vectorpb" + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vars" + "github.com/sashabaranov/go-openai" +) + +func DoGetImage(msgs []openai.ChatCompletionMessage, param string, robot *vector.Vector, stopStop chan bool) { + stopImaging := false + go func() { + for range stopStop { + stopImaging = true + break + } + }() + + logger.Println("Get image here...") + // Get image + robot.Conn.EnableMirrorMode(context.Background(), &vectorpb.EnableMirrorModeRequest{ + Enable: true, + }) + + for i := 3; i > 0; i-- { + if stopImaging { + return + } + time.Sleep(time.Millisecond * 300) + robot.Conn.SayText( + context.Background(), + &vectorpb.SayTextRequest{ + Text: fmt.Sprint(i), + UseVectorVoice: true, + DurationScalar: 1.05, + }, + ) + if stopImaging { + return + } + } + + resp, _ := robot.Conn.CaptureSingleImage( + context.Background(), + &vectorpb.CaptureSingleImageRequest{ + EnableHighResolution: true, + }, + ) + robot.Conn.EnableMirrorMode( + context.Background(), + &vectorpb.EnableMirrorModeRequest{ + Enable: false, + }, + ) + + go func() { + robot.Conn.PlayAnimation( + context.Background(), + &vectorpb.PlayAnimationRequest{ + Animation: &vectorpb.Animation{ + Name: "anim_photo_shutter_01", + }, + Loops: 1, + }, + ) + }() + + // Encode to base64 + reqBase64 := base64.StdEncoding.EncodeToString(resp.Data) + + // Add image to messages + msgs = append(msgs, openai.ChatCompletionMessage{ + Role: openai.ChatMessageRoleUser, + MultiContent: []openai.ChatMessagePart{ + { + Type: openai.ChatMessagePartTypeImageURL, + ImageURL: &openai.ChatMessageImageURL{ + URL: fmt.Sprintf("data:image/jpeg;base64,%s", reqBase64), + Detail: openai.ImageURLDetailLow, + }, + }, + }, + }) + + // Create OpenAI client + var tempRespText string + var fullRespText string + var fullRespSlice []string + var isDone bool + + var c *openai.Client + + if vars.APIConfig.Knowledge.Provider == "together" { + if vars.APIConfig.Knowledge.Model == "" { + vars.APIConfig.Knowledge.Model = "meta-llama/Llama-2-70b-chat-hf" + vars.WriteConfigToDisk() + } + conf := openai.DefaultConfig(vars.APIConfig.Knowledge.Key) + conf.BaseURL = "https://api.together.xyz/v1" + c = openai.NewClientWithConfig(conf) + } else if vars.APIConfig.Knowledge.Provider == "openai" { + c = openai.NewClient(vars.APIConfig.Knowledge.Key) + } + + ctx := context.Background() + speakReady := make(chan string) + + // Create AI request + aireq := openai.ChatCompletionRequest{ + MaxTokens: 4095, + Temperature: 1, + TopP: 1, + FrequencyPenalty: 0, + PresencePenalty: 0, + Messages: msgs, + Stream: true, + } + + if vars.APIConfig.Knowledge.Provider == "openai" { + aireq.Model = openai.GPT4oMini + logger.Println("Using " + aireq.Model) + } else { + logger.Println("Using " + vars.APIConfig.Knowledge.Model) + aireq.Model = vars.APIConfig.Knowledge.Model + } + + if stopImaging { + return + } + + // Stream creation with error handling + stream, err := c.CreateChatCompletionStream(ctx, aireq) + if err != nil { + if strings.Contains(err.Error(), "does not exist") && vars.APIConfig.Knowledge.Provider == "openai" { + logger.Println("GPT-4 model cannot be accessed with this API key. You likely need to add more than $5 dollars of funds to your OpenAI account.") + logger.LogUI("GPT-4 model cannot be accessed with this API key. You likely need to add more than $5 dollars of funds to your OpenAI account.") + aireq.Model = openai.GPT3Dot5Turbo + logger.Println("Falling back to " + aireq.Model) + logger.LogUI("Falling back to " + aireq.Model) + stream, err = c.CreateChatCompletionStream(ctx, aireq) + if err != nil { + logger.Println("OpenAI still not returning a response even after falling back. Erroring.") + return + } + } else { + logger.Println("LLM error: " + err.Error()) + return + } + } + + // === LLM RESPONSE === + fmt.Println("LLM stream response: ") + go func() { + for { + response, err := stream.Recv() + if errors.Is(err, io.EOF) { + isDone = true + newStr := strings.Join(fullRespSlice, " ") + if strings.TrimSpace(newStr) != strings.TrimSpace(tempRespText) { + logger.Println("LLM debug: there is content after the last punctuation mark") + extraBit := strings.TrimPrefix(fullRespText, newStr) + fullRespSlice = append(fullRespSlice, extraBit) + } + if vars.APIConfig.Knowledge.SaveChat { + Remember(msgs[len(msgs)-1], + openai.ChatCompletionMessage{ + Role: openai.ChatMessageRoleAssistant, + Content: newStr, + }, + robot.Cfg.SerialNo) + } + logger.LogUI("LLM response for " + robot.Cfg.SerialNo + ": " + undoTmpDecPnt(newStr)) + logger.Println("LLM stream finished") + return + } + + if err != nil { + logger.Println("Stream error: " + err.Error()) + return + } + tempRespText += response.Choices[0].Delta.Content + fullRespText += response.Choices[0].Delta.Content + + // Handle decimals + fullRespText = doTmpDecPnt(fullRespText) + + // Split LLM response + fullRespText, fullRespSlice = SplitLLMResponse(fullRespText, fullRespSlice) + + if len(fullRespSlice) > 0 { + affectedText := strings.TrimSpace(fullRespSlice[len(fullRespSlice)-1]) // Get the last added part + select { + case speakReady <- undoTmpDecPnt(affectedText): + default: + } + } + } + }() + // === LLM RESPONSE === + + numInResp := 0 + for { + if stopImaging { + return + } + respSlice := fullRespSlice + if len(respSlice) <= numInResp { + if !isDone { + logger.Println("Waiting for more content from LLM...") + for range speakReady { + respSlice = fullRespSlice + break + } + } else { + break + } + } + logger.Println(respSlice[numInResp]) + acts := GetActionsFromString(respSlice[numInResp]) + PerformActions(msgs, acts, robot, stopStop) + numInResp++ + if stopImaging { + return + } + } +} \ No newline at end of file diff --git a/chipper/pkg/wirepod/ttr/get_robot_esn.go b/chipper/pkg/wirepod/ttr/get_robot_esn.go new file mode 100644 index 0000000..77d98c9 --- /dev/null +++ b/chipper/pkg/wirepod/ttr/get_robot_esn.go @@ -0,0 +1,39 @@ + +package wirepod_ttr + +import ( + "errors" + "fmt" + + "github.com/fforchino/vector-go-sdk/pkg/vector" + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vars" +) + +// FindRobotByESN retrieves a robot instance by its ESN. +func FindRobotByESN(esn string) (*vector.Vector, error) { + if esn == "" { + return nil, errors.New("ESN cannot be empty") + } + + for _, r := range vars.BotInfo.Robots { + if r.Esn == esn { + guid := r.GUID + target := fmt.Sprintf("%s:443", r.IPAddress) + + robot, err := vector.New(vector.WithSerialNo(esn), vector.WithToken(guid), vector.WithTarget(target)) + if err != nil { + logger.Println("Failed to create robot with ESN: %s, GUID: %s, Target: %s. Error: %v", esn, guid, target, err) + return nil, fmt.Errorf("failed to create robot: %w", err) + } + return robot, nil + } + } + + logger.Println("No robot found with ESN:", esn) + return nil, errors.New("no robot found with the given ESN") +} + +// +// get_robot_id.go - END +// diff --git a/chipper/pkg/wirepod/ttr/intentparam.go b/chipper/pkg/wirepod/ttr/intentparam.go new file mode 100644 index 0000000..ba130f9 --- /dev/null +++ b/chipper/pkg/wirepod/ttr/intentparam.go @@ -0,0 +1,719 @@ +package wirepod_ttr + +import ( + "encoding/json" + "strconv" + "strings" + + "github.com/fforchino/vector-go-sdk/pkg/vector" + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vars" + lcztn "github.com/kercre123/wire-pod/chipper/pkg/wirepod/localization" +) + +// stt +func ParamChecker(req interface{}, intent string, speechText string, botSerial string) { + var intentParam string + var intentParamValue string + var newIntent string + var isParam bool + var intentParams map[string]string + var botLocation string = "San Francisco" + var botUnits string = "F" + var botPlaySpecific bool = false + var botIsEarlyOpus bool = false + var DoWeatherError bool = false + + // see if jdoc exists + botJdoc, jdocExists := vars.GetJdoc("vic:"+botSerial, "vic.RobotSettings") + if jdocExists { + type robotSettingsJson struct { + ButtonWakeword int `json:"button_wakeword"` + Clock24Hour bool `json:"clock_24_hour"` + CustomEyeColor struct { + Enabled bool `json:"enabled"` + Hue float64 `json:"hue"` + Saturation float64 `json:"saturation"` + } `json:"custom_eye_color"` + DefaultLocation string `json:"default_location"` + DistIsMetric bool `json:"dist_is_metric"` + EyeColor int `json:"eye_color"` + Locale string `json:"locale"` + MasterVolume int `json:"master_volume"` + TempIsFahrenheit bool `json:"temp_is_fahrenheit"` + TimeZone string `json:"time_zone"` + } + var robotSettings robotSettingsJson + err := json.Unmarshal([]byte(botJdoc.JsonDoc), &robotSettings) + if err != nil { + logger.Println("Error unmarshaling json in paramchecker") + logger.Println(err) + } else { + botLocation = robotSettings.DefaultLocation + if robotSettings.TempIsFahrenheit { + botUnits = "F" + } else { + botUnits = "C" + } + } + } + if botPlaySpecific { + if strings.Contains(intent, "intent_play_blackjack") { + isParam = true + newIntent = "intent_play_specific_extend" + intentParam = "entity_behavior" + intentParamValue = "blackjack" + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_play_fistbump") { + isParam = true + newIntent = "intent_play_specific_extend" + intentParam = "entity_behavior" + intentParamValue = "fist_bump" + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_play_rollcube") { + isParam = true + newIntent = "intent_play_specific_extend" + intentParam = "entity_behavior" + intentParamValue = "roll_cube" + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_play_popawheelie") { + isParam = true + newIntent = "intent_play_specific_extend" + intentParam = "entity_behavior" + intentParamValue = "pop_a_wheelie" + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_play_pickupcube") { + isParam = true + newIntent = "intent_play_specific_extend" + intentParam = "entity_behavior" + intentParamValue = "pick_up_cube" + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_play_keepaway") { + isParam = true + newIntent = "intent_play_specific_extend" + intentParam = "entity_behavior" + intentParamValue = "keep_away" + intentParams = map[string]string{intentParam: intentParamValue} + } else { + newIntent = intent + intentParam = "" + intentParamValue = "" + isParam = false + intentParams = map[string]string{intentParam: intentParamValue} + } + } + logger.Println("Checking params for candidate intent " + intent) + if strings.Contains(intent, "intent_photo_take_extend") { + isParam = true + newIntent = intent + if strings.Contains(speechText, lcztn.GetText(lcztn.STR_ME)) || strings.Contains(speechText, lcztn.GetText(lcztn.STR_SELF)) { + intentParam = "entity_photo_selfie" + intentParamValue = "photo_selfie" + } else { + intentParam = "entity_photo_selfie" + intentParamValue = "" + } + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_imperative_eyecolor") { + isParam = true + newIntent = "intent_imperative_eyecolor_specific_extend" + intentParam = "eye_color" + if strings.Contains(speechText, lcztn.GetText(lcztn.STR_EYE_COLOR_PURPLE)) { + intentParamValue = "COLOR_PURPLE" + } else if strings.Contains(speechText, lcztn.GetText(lcztn.STR_EYE_COLOR_BLUE)) || strings.Contains(speechText, lcztn.GetText(lcztn.STR_EYE_COLOR_SAPPHIRE)) { + intentParamValue = "COLOR_BLUE" + } else if strings.Contains(speechText, lcztn.GetText(lcztn.STR_EYE_COLOR_YELLOW)) { + intentParamValue = "COLOR_YELLOW" + } else if strings.Contains(speechText, lcztn.GetText(lcztn.STR_EYE_COLOR_TEAL)) || strings.Contains(speechText, lcztn.GetText(lcztn.STR_EYE_COLOR_TEAL2)) { + intentParamValue = "COLOR_TEAL" + } else if strings.Contains(speechText, lcztn.GetText(lcztn.STR_EYE_COLOR_GREEN)) { + intentParamValue = "COLOR_GREEN" + } else if strings.Contains(speechText, lcztn.GetText(lcztn.STR_EYE_COLOR_ORANGE)) { + intentParamValue = "COLOR_ORANGE" + } else { + newIntent = intent + intentParamValue = "" + isParam = false + } + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_weather_extend") { + isParam = true + newIntent = intent + condition, is_forecast, local_datetime, speakable_location_string, temperature, temperature_unit := weatherParser(speechText, botLocation, botUnits) + if local_datetime == "test" { + newIntent = "intent_system_unmatched" + isParam = false + DoWeatherError = true + } else { + intentParams = map[string]string{"condition": condition, "is_forecast": is_forecast, "local_datetime": local_datetime, "speakable_location_string": speakable_location_string, "temperature": temperature, "temperature_unit": temperature_unit} + } + } else if strings.Contains(intent, "intent_imperative_volumelevel_extend") { + isParam = true + newIntent = intent + if strings.Contains(speechText, lcztn.GetText(lcztn.STR_VOLUME_MEDIUM_LOW)) { + intentParam = "volume_level" + intentParamValue = "VOLUME_2" + } else if strings.Contains(speechText, lcztn.GetText(lcztn.STR_VOLUME_LOW)) || strings.Contains(speechText, lcztn.GetText(lcztn.STR_VOLUME_QUIET)) { + intentParam = "volume_level" + intentParamValue = "VOLUME_1" + } else if strings.Contains(speechText, lcztn.GetText(lcztn.STR_VOLUME_MEDIUM_HIGH)) { + intentParam = "volume_level" + intentParamValue = "VOLUME_4" + } else if strings.Contains(speechText, lcztn.GetText(lcztn.STR_VOLUME_MEDIUM)) || strings.Contains(speechText, lcztn.GetText(lcztn.STR_VOLUME_NORMAL)) || strings.Contains(speechText, lcztn.GetText(lcztn.STR_VOLUME_REGULAR)) { + intentParam = "volume_level" + intentParamValue = "VOLUME_3" + } else if strings.Contains(speechText, lcztn.GetText(lcztn.STR_VOLUME_HIGH)) || strings.Contains(speechText, lcztn.GetText(lcztn.STR_VOLUME_LOUD)) { + intentParam = "volume_level" + intentParamValue = "VOLUME_5" + } else if strings.Contains(speechText, lcztn.GetText(lcztn.STR_VOLUME_MUTE)) || strings.Contains(speechText, lcztn.GetText(lcztn.STR_VOLUME_NOTHING)) || strings.Contains(speechText, lcztn.GetText(lcztn.STR_VOLUME_SILENT)) || strings.Contains(speechText, lcztn.GetText(lcztn.STR_VOLUME_OFF)) || strings.Contains(speechText, lcztn.GetText(lcztn.STR_VOLUME_ZERO)) { + // there is no VOLUME_0 :( + intentParam = "volume_level" + intentParamValue = "VOLUME_1" + } else { + intentParam = "volume_level" + intentParamValue = "VOLUME_1" + } + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_names_username_extend") { + if vars.VoskGrammerEnable { + var guid string + var target string + matched := false + for _, bot := range vars.BotInfo.Robots { + if botSerial == bot.Esn { + guid = bot.GUID + target = bot.IPAddress + ":443" + matched = true + break + } + } + if matched { + vec, err := vector.New(vector.WithSerialNo(botSerial), vector.WithToken(guid), vector.WithTarget(target)) + if err != nil { + logger.Println("error connecting to vector:", err) + } else { + sayText(vec, "You must add a face in the web interface. It cannot be done via voice by default.") + } + } + logger.Println("You must add a face via the web interface (Bot Settings -> Connect -> Faces).") + logger.LogUI("You must add a face via the web interface (Bot Settings -> Connect -> Faces).") + } + var username string + var nameSplitter string = "" + isParam = true + newIntent = intent + if strings.Contains(speechText, lcztn.GetText(lcztn.STR_NAME_IS)) { + nameSplitter = lcztn.GetText(lcztn.STR_NAME_IS) + } else if strings.Contains(speechText, lcztn.GetText(lcztn.STR_NAME_IS2)) { + nameSplitter = lcztn.GetText(lcztn.STR_NAME_IS2) + } else if strings.Contains(speechText, lcztn.GetText(lcztn.STR_NAME_IS3)) { + nameSplitter = lcztn.GetText(lcztn.STR_NAME_IS3) + } + if nameSplitter != "" { + splitPhrase := strings.SplitAfter(speechText, nameSplitter) + username = strings.TrimSpace(splitPhrase[1]) + if len(splitPhrase) == 3 { + username = username + " " + strings.TrimSpace(splitPhrase[2]) + } else if len(splitPhrase) == 4 { + username = username + " " + strings.TrimSpace(splitPhrase[2]) + " " + strings.TrimSpace(splitPhrase[3]) + } else if len(splitPhrase) > 4 { + username = username + " " + strings.TrimSpace(splitPhrase[2]) + " " + strings.TrimSpace(splitPhrase[3]) + } + logger.Println("Name parsed from speech: " + "`" + username + "`") + intentParam = "username" + intentParamValue = username + intentParams = map[string]string{intentParam: intentParamValue} + } else { + logger.Println("No name parsed from speech") + intentParam = "username" + intentParamValue = "" + intentParams = map[string]string{intentParam: intentParamValue} + } + } else if strings.Contains(intent, "intent_clock_settimer_extend") { + isParam = true + newIntent = intent + timerSecs := words2num(speechText) + logger.Println("Seconds parsed from speech: " + timerSecs) + intentParam = "timer_duration" + intentParamValue = timerSecs + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_global_stop_extend") { + isParam = true + newIntent = intent + intentParam = "what_to_stop" + intentParamValue = "timer" + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_message_playmessage_extend") { + var given_name string + isParam = true + newIntent = intent + intentParam = "given_name" + if strings.Contains(speechText, lcztn.GetText(lcztn.STR_FOR)) { + splitPhrase := strings.SplitAfter(speechText, lcztn.GetText(lcztn.STR_FOR)) + given_name = strings.TrimSpace(splitPhrase[1]) + if len(splitPhrase) == 3 { + given_name = given_name + " " + strings.TrimSpace(splitPhrase[2]) + } else if len(splitPhrase) == 4 { + given_name = given_name + " " + strings.TrimSpace(splitPhrase[2]) + " " + strings.TrimSpace(splitPhrase[3]) + } else if len(splitPhrase) > 4 { + given_name = given_name + " " + strings.TrimSpace(splitPhrase[2]) + " " + strings.TrimSpace(splitPhrase[3]) + } + intentParamValue = given_name + } else { + intentParamValue = "" + } + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_message_recordmessage_extend") { + var given_name string + isParam = true + newIntent = intent + intentParam = "given_name" + if strings.Contains(speechText, lcztn.GetText(lcztn.STR_FOR)) { + splitPhrase := strings.SplitAfter(speechText, lcztn.GetText(lcztn.STR_FOR)) + given_name = strings.TrimSpace(splitPhrase[1]) + if len(splitPhrase) == 3 { + given_name = given_name + " " + strings.TrimSpace(splitPhrase[2]) + } else if len(splitPhrase) == 4 { + given_name = given_name + " " + strings.TrimSpace(splitPhrase[2]) + " " + strings.TrimSpace(splitPhrase[3]) + } else if len(splitPhrase) > 4 { + given_name = given_name + " " + strings.TrimSpace(splitPhrase[2]) + " " + strings.TrimSpace(splitPhrase[3]) + } + intentParamValue = given_name + } else { + intentParamValue = "" + } + intentParams = map[string]string{intentParam: intentParamValue} + } else { + if intentParam == "" { + newIntent = intent + intentParam = "" + intentParamValue = "" + isParam = false + intentParams = map[string]string{intentParam: intentParamValue} + } + } + if botIsEarlyOpus { + if strings.Contains(intent, "intent_imperative_praise") { + isParam = false + newIntent = "intent_imperative_affirmative" + intentParam = "" + intentParamValue = "" + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_imperative_abuse") { + isParam = false + newIntent = "intent_imperative_negative" + intentParam = "" + intentParamValue = "" + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_imperative_love") { + isParam = false + newIntent = "intent_greeting_hello" + intentParam = "" + intentParamValue = "" + intentParams = map[string]string{intentParam: intentParamValue} + } + } + IntentPass(req, newIntent, speechText, intentParams, isParam) + if DoWeatherError { + if vars.APIConfig.Weather.Enable { + logger.Println("The weather API is not configured properly.") + KGSim(botSerial, "The weather API is not configured properly. Please check the wire pod logs for more details.") + } else { + logger.Println("The weather API is not configured.") + KGSim(botSerial, "The weather API is not configured.") + } + } +} + +// stintent +func ParamCheckerSlotsEnUS(req interface{}, intent string, slots map[string]string, isOpus bool, botSerial string) { + var intentParam string + var intentParamValue string + var newIntent string + var isParam bool + var intentParams map[string]string + var botLocation string = "San Francisco" + var botUnits string = "F" + var botPlaySpecific bool = false + var botIsEarlyOpus bool = false + // see if jdoc exists + botJdoc, jdocExists := vars.GetJdoc("vic:"+botSerial, "vic.RobotSettings") + if jdocExists { + type robotSettingsJson struct { + ButtonWakeword int `json:"button_wakeword"` + Clock24Hour bool `json:"clock_24_hour"` + CustomEyeColor struct { + Enabled bool `json:"enabled"` + Hue float64 `json:"hue"` + Saturation float64 `json:"saturation"` + } `json:"custom_eye_color"` + DefaultLocation string `json:"default_location"` + DistIsMetric bool `json:"dist_is_metric"` + EyeColor int `json:"eye_color"` + Locale string `json:"locale"` + MasterVolume int `json:"master_volume"` + TempIsFahrenheit bool `json:"temp_is_fahrenheit"` + TimeZone string `json:"time_zone"` + } + var robotSettings robotSettingsJson + err := json.Unmarshal([]byte(botJdoc.JsonDoc), &robotSettings) + if err != nil { + logger.Println("Error unmarshaling json in paramchecker") + logger.Println(err) + } else { + botLocation = robotSettings.DefaultLocation + if robotSettings.TempIsFahrenheit { + botUnits = "F" + } else { + botUnits = "C" + } + } + } + if strings.Contains(intent, "volume") { + if slots["volume"] != "" { + newIntent = "intent_imperative_volumelevel_extend" + isParam = true + intentParam = "volume_level" + if strings.Contains(slots["volume"], "medium low") { + intentParamValue = "VOLUME_2" + } else if strings.Contains(slots["volume"], "low") { + intentParamValue = "VOLUME_1" + } else if strings.Contains(slots["volume"], "medium high") { + intentParamValue = "VOLUME_4" + } else if strings.Contains(slots["volume"], "high") { + intentParamValue = "VOLUME_5" + } else if strings.Contains(slots["volume"], "medium") { + intentParamValue = "VOLUME_3" + } else { + intentParamValue = "VOLUME_1" + } + } else { + isParam = false + intentParam = "" + intentParamValue = "" + } + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "eyecolor") { + isParam = true + newIntent = "intent_imperative_eyecolor_specific_extend" + intentParam = "eye_color" + if strings.Contains(slots["eye_color"], "purple") { + intentParamValue = "COLOR_PURPLE" + } else if strings.Contains(slots["eye_color"], "blue") || strings.Contains(slots["eye_color"], "sapphire") { + intentParamValue = "COLOR_BLUE" + } else if strings.Contains(slots["eye_color"], "yellow") { + intentParamValue = "COLOR_YELLOW" + } else if strings.Contains(slots["eye_color"], "teal") || strings.Contains(slots["eye_color"], "tell") { + intentParamValue = "COLOR_TEAL" + } else if strings.Contains(slots["eye_color"], "green") { + intentParamValue = "COLOR_GREEN" + } else if strings.Contains(slots["eye_color"], "orange") { + intentParamValue = "COLOR_ORANGE" + } else { + newIntent = intent + intentParamValue = "" + isParam = false + } + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "_selfie") { + newIntent = "intent_photo_take_extend" + intentParam = "entity_photo_selfie" + intentParamValue = "photo_selfie" + isParam = true + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "_noselfie") { + newIntent = "intent_photo_take_extend" + intentParam = "entity_photo_selfie" + intentParamValue = "" + isParam = true + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "settimer") { + isParam = true + newIntent = intent + slotNum := slots["num"] + slotUnit := slots["unit"] + timerSecs, err := strconv.Atoi(slotNum) + if err != nil { + logger.Println(err) + } + if slotNum != "" && slotUnit != "" { + if strings.Contains(slotUnit, "minute") { + timerSecs = timerSecs * 60 + } else if strings.Contains(slotUnit, "hour") { + timerSecs = timerSecs * 60 * 60 + } + } + logger.Println("Seconds parsed from speech: " + strconv.Itoa(timerSecs)) + intentParam = "timer_duration" + intentParamValue = strconv.Itoa(timerSecs) + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "global_stop_extend") { + isParam = true + newIntent = intent + intentParam = "what_to_stop" + intentParamValue = "timer" + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_knowledgegraph_prompt") { + isParam = false + newIntent = "intent_knowledge_promptquestion" + intentParam = "" + intentParamValue = "" + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_weather_extend") { + isParam = true + newIntent = intent + condition, is_forecast, local_datetime, speakable_location_string, temperature, temperature_unit := weatherParser("what's the weather", botLocation, botUnits) + intentParams = map[string]string{"condition": condition, "is_forecast": is_forecast, "local_datetime": local_datetime, "speakable_location_string": speakable_location_string, "temperature": temperature, "temperature_unit": temperature_unit} + } else { + if intentParam == "" { + newIntent = intent + intentParam = "" + intentParamValue = "" + isParam = false + intentParams = map[string]string{intentParam: intentParamValue} + } + } + if isOpus || botIsEarlyOpus || botPlaySpecific { + if strings.Contains(intent, "intent_play_blackjack") { + isParam = true + newIntent = "intent_play_specific_extend" + intentParam = "entity_behavior" + intentParamValue = "blackjack" + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_play_fistbump") { + isParam = true + newIntent = "intent_play_specific_extend" + intentParam = "entity_behavior" + intentParamValue = "fist_bump" + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_play_rollcube") { + isParam = true + newIntent = "intent_play_specific_extend" + intentParam = "entity_behavior" + intentParamValue = "roll_cube" + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_imperative_praise") { + isParam = false + newIntent = "intent_imperative_affirmative" + intentParam = "" + intentParamValue = "" + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_imperative_love") { + isParam = false + newIntent = "intent_greeting_hello" + intentParam = "" + intentParamValue = "" + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_imperative_abuse") { + isParam = false + newIntent = "intent_imperative_negative" + intentParam = "" + intentParamValue = "" + intentParams = map[string]string{intentParam: intentParamValue} + } + } + IntentPass(req, newIntent, intent, intentParams, isParam) +} + +func prehistoricParamChecker(req interface{}, intent string, speechText string) { + // intent.go detects if the stream uses opus or PCM. + // If the stream is PCM, it is likely a bot with 0.10. + // This accounts for the newer 0.10.1### builds. + var intentParam string + var intentParamValue string + var newIntent string + var isParam bool + var intentParams map[string]string + var botLocation string = "San Francisco" + var botUnits string = "F" + if strings.Contains(intent, "intent_photo_take_extend") { + isParam = true + newIntent = intent + if strings.Contains(speechText, lcztn.GetText(lcztn.STR_ME)) || strings.Contains(speechText, lcztn.GetText(lcztn.STR_SELF)) { + intentParam = "entity_photo_selfie" + intentParamValue = "photo_selfie" + } else { + intentParam = "entity_photo_selfie" + intentParamValue = "" + } + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_imperative_eyecolor") { + // leaving stuff like this in case someone wants to add features like this to older software + isParam = true + newIntent = "intent_imperative_eyecolor_specific_extend" + intentParam = "eye_color" + if strings.Contains(speechText, lcztn.GetText(lcztn.STR_EYE_COLOR_PURPLE)) { + intentParamValue = "COLOR_PURPLE" + } else if strings.Contains(speechText, lcztn.GetText(lcztn.STR_EYE_COLOR_BLUE)) || strings.Contains(speechText, lcztn.GetText(lcztn.STR_EYE_COLOR_SAPPHIRE)) { + intentParamValue = "COLOR_BLUE" + } else if strings.Contains(speechText, lcztn.GetText(lcztn.STR_EYE_COLOR_YELLOW)) { + intentParamValue = "COLOR_YELLOW" + } else if strings.Contains(speechText, lcztn.GetText(lcztn.STR_EYE_COLOR_TEAL)) || strings.Contains(speechText, lcztn.GetText(lcztn.STR_EYE_COLOR_TEAL2)) { + intentParamValue = "COLOR_TEAL" + } else if strings.Contains(speechText, lcztn.GetText(lcztn.STR_EYE_COLOR_GREEN)) { + intentParamValue = "COLOR_GREEN" + } else if strings.Contains(speechText, lcztn.GetText(lcztn.STR_EYE_COLOR_ORANGE)) { + intentParamValue = "COLOR_ORANGE" + } else { + newIntent = intent + intentParamValue = "" + isParam = false + } + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_weather_extend") { + isParam = true + newIntent = intent + condition, is_forecast, local_datetime, speakable_location_string, temperature, temperature_unit := weatherParser(speechText, botLocation, botUnits) + intentParams = map[string]string{"condition": condition, "is_forecast": is_forecast, "local_datetime": local_datetime, "speakable_location_string": speakable_location_string, "temperature": temperature, "temperature_unit": temperature_unit} + } else if strings.Contains(intent, "intent_imperative_volumelevel_extend") { + isParam = true + newIntent = intent + if strings.Contains(speechText, lcztn.GetText(lcztn.STR_VOLUME_MEDIUM_LOW)) { + intentParam = "volume_level" + intentParamValue = "VOLUME_2" + } else if strings.Contains(speechText, lcztn.GetText(lcztn.STR_VOLUME_LOW)) || strings.Contains(speechText, lcztn.GetText(lcztn.STR_VOLUME_QUIET)) { + intentParam = "volume_level" + intentParamValue = "VOLUME_1" + } else if strings.Contains(speechText, lcztn.GetText(lcztn.STR_VOLUME_MEDIUM_HIGH)) { + intentParam = "volume_level" + intentParamValue = "VOLUME_4" + } else if strings.Contains(speechText, lcztn.GetText(lcztn.STR_VOLUME_MEDIUM)) || strings.Contains(speechText, lcztn.GetText(lcztn.STR_VOLUME_NORMAL)) || strings.Contains(speechText, lcztn.GetText(lcztn.STR_VOLUME_REGULAR)) { + intentParam = "volume_level" + intentParamValue = "VOLUME_3" + } else if strings.Contains(speechText, lcztn.GetText(lcztn.STR_VOLUME_HIGH)) || strings.Contains(speechText, lcztn.GetText(lcztn.STR_VOLUME_LOUD)) { + intentParam = "volume_level" + intentParamValue = "VOLUME_5" + } else if strings.Contains(speechText, lcztn.GetText(lcztn.STR_VOLUME_MUTE)) || strings.Contains(speechText, lcztn.GetText(lcztn.STR_VOLUME_NOTHING)) || strings.Contains(speechText, lcztn.GetText(lcztn.STR_VOLUME_SILENT)) || strings.Contains(speechText, lcztn.GetText(lcztn.STR_VOLUME_OFF)) || strings.Contains(speechText, lcztn.GetText(lcztn.STR_VOLUME_ZERO)) { + // there is no VOLUME_0 :( + intentParam = "volume_level" + intentParamValue = "VOLUME_1" + } else { + intentParam = "volume_level" + intentParamValue = "VOLUME_1" + } + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_names_username_extend") { + var username string + var nameSplitter string = "" + isParam = true + newIntent = "intent_names_username" + if strings.Contains(speechText, lcztn.GetText(lcztn.STR_NAME_IS)) { + nameSplitter = lcztn.GetText(lcztn.STR_NAME_IS) + } else if strings.Contains(speechText, lcztn.GetText(lcztn.STR_NAME_IS2)) { + nameSplitter = lcztn.GetText(lcztn.STR_NAME_IS2) + } else if strings.Contains(speechText, lcztn.GetText(lcztn.STR_NAME_IS3)) { + nameSplitter = lcztn.GetText(lcztn.STR_NAME_IS3) + } + if nameSplitter != "" { + splitPhrase := strings.SplitAfter(speechText, nameSplitter) + username = strings.TrimSpace(splitPhrase[1]) + if len(splitPhrase) == 3 { + username = username + " " + strings.TrimSpace(splitPhrase[2]) + } else if len(splitPhrase) == 4 { + username = username + " " + strings.TrimSpace(splitPhrase[2]) + " " + strings.TrimSpace(splitPhrase[3]) + } else if len(splitPhrase) > 4 { + username = username + " " + strings.TrimSpace(splitPhrase[2]) + " " + strings.TrimSpace(splitPhrase[3]) + } + logger.Println("Name parsed from speech: " + "`" + username + "`") + intentParam = "username" + intentParamValue = username + intentParams = map[string]string{intentParam: intentParamValue} + } else { + logger.Println("No name parsed from speech") + intentParam = "username" + intentParamValue = "" + intentParams = map[string]string{intentParam: intentParamValue} + } + } else if strings.Contains(intent, "intent_clock_settimer_extend") { + isParam = true + newIntent = "intent_clock_settimer" + timerSecs := words2num(speechText) + logger.Println("Seconds parsed from speech: " + timerSecs) + intentParam = "timer_duration" + intentParamValue = timerSecs + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_global_stop_extend") { + isParam = true + newIntent = "intent_global_stop" + intentParam = "what_to_stop" + intentParamValue = "timer" + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_message_playmessage_extend") { + var given_name string + isParam = true + newIntent = "intent_message_playmessage" + intentParam = "given_name" + if strings.Contains(speechText, lcztn.GetText(lcztn.STR_FOR)) { + splitPhrase := strings.SplitAfter(speechText, lcztn.GetText(lcztn.STR_FOR)) + given_name = strings.TrimSpace(splitPhrase[1]) + if len(splitPhrase) == 3 { + given_name = given_name + " " + strings.TrimSpace(splitPhrase[2]) + } else if len(splitPhrase) == 4 { + given_name = given_name + " " + strings.TrimSpace(splitPhrase[2]) + " " + strings.TrimSpace(splitPhrase[3]) + } else if len(splitPhrase) > 4 { + given_name = given_name + " " + strings.TrimSpace(splitPhrase[2]) + " " + strings.TrimSpace(splitPhrase[3]) + } + intentParamValue = given_name + } else { + intentParamValue = "" + } + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_message_recordmessage_extend") { + var given_name string + isParam = true + newIntent = "intent_message_recordmessage" + intentParam = "given_name" + if strings.Contains(speechText, lcztn.GetText(lcztn.STR_FOR)) { + splitPhrase := strings.SplitAfter(speechText, lcztn.GetText(lcztn.STR_FOR)) + given_name = strings.TrimSpace(splitPhrase[1]) + if len(splitPhrase) == 3 { + given_name = given_name + " " + strings.TrimSpace(splitPhrase[2]) + } else if len(splitPhrase) == 4 { + given_name = given_name + " " + strings.TrimSpace(splitPhrase[2]) + " " + strings.TrimSpace(splitPhrase[3]) + } else if len(splitPhrase) > 4 { + given_name = given_name + " " + strings.TrimSpace(splitPhrase[2]) + " " + strings.TrimSpace(splitPhrase[3]) + } + intentParamValue = given_name + } else { + intentParamValue = "" + } + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_play_blackjack") { + isParam = true + newIntent = "intent_play_specific_extend" + intentParam = "entity_behavior" + intentParamValue = "blackjack" + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_play_fistbump") { + isParam = true + newIntent = "intent_play_specific_extend" + intentParam = "entity_behavior" + intentParamValue = "fist_bump" + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_play_rollcube") { + isParam = true + newIntent = "intent_play_specific_extend" + intentParam = "entity_behavior" + intentParamValue = "roll_cube" + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_imperative_praise") { + isParam = false + newIntent = "intent_imperative_affirmative" + intentParam = "" + intentParamValue = "" + intentParams = map[string]string{intentParam: intentParamValue} + } else if strings.Contains(intent, "intent_imperative_abuse") { + isParam = false + newIntent = "intent_imperative_negative" + intentParam = "" + intentParamValue = "" + intentParams = map[string]string{intentParam: intentParamValue} + } else { + newIntent = intent + intentParam = "" + intentParamValue = "" + isParam = false + intentParams = map[string]string{intentParam: intentParamValue} + } + IntentPass(req, newIntent, speechText, intentParams, isParam) +} diff --git a/chipper/pkg/wirepod/ttr/kgsim.go b/chipper/pkg/wirepod/ttr/kgsim.go new file mode 100644 index 0000000..e0f1e63 --- /dev/null +++ b/chipper/pkg/wirepod/ttr/kgsim.go @@ -0,0 +1,156 @@ +package wirepod_ttr + +import ( + "context" + "log" + "strings" + "time" + + "github.com/fforchino/vector-go-sdk/pkg/vector" + "github.com/fforchino/vector-go-sdk/pkg/vectorpb" + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vars" +) + +func KGSim(esn string, textToSay string) error { + ctx := context.Background() + matched := false + var robot *vector.Vector + var guid string + var target string + for _, bot := range vars.BotInfo.Robots { + if esn == bot.Esn { + guid = bot.GUID + target = bot.IPAddress + ":443" + matched = true + break + } + } + if matched { + var err error + robot, err = vector.New(vector.WithSerialNo(esn), vector.WithToken(guid), vector.WithTarget(target)) + if err != nil { + return err + } + } + controlRequest := &vectorpb.BehaviorControlRequest{ + RequestType: &vectorpb.BehaviorControlRequest_ControlRequest{ + ControlRequest: &vectorpb.ControlRequest{ + Priority: vectorpb.ControlRequest_OVERRIDE_BEHAVIORS, + }, + }, + } + go func() { + start := make(chan bool) + stop := make(chan bool) + + go func() { + // * begin - modified from official vector-go-sdk + r, err := robot.Conn.BehaviorControl( + ctx, + ) + if err != nil { + log.Println(err) + return + } + + if err := r.Send(controlRequest); err != nil { + log.Println(err) + return + } + + for { + ctrlresp, err := r.Recv() + if err != nil { + log.Println(err) + return + } + if ctrlresp.GetControlGrantedResponse() != nil { + start <- true + break + } + } + + for { + select { + case <-stop: + logger.Println("KGSim: releasing behavior control (interrupt)") + if err := r.Send( + &vectorpb.BehaviorControlRequest{ + RequestType: &vectorpb.BehaviorControlRequest_ControlRelease{ + ControlRelease: &vectorpb.ControlRelease{}, + }, + }, + ); err != nil { + log.Println(err) + return + } + return + default: + continue + } + } + // * end - modified from official vector-go-sdk + }() + + var stopTTSLoop bool + var TTSLoopStopped bool + for range start { + time.Sleep(time.Millisecond * 300) + robot.Conn.PlayAnimation( + ctx, + &vectorpb.PlayAnimationRequest{ + Animation: &vectorpb.Animation{ + Name: "anim_getin_tts_01", + }, + Loops: 1, + }, + ) + go func() { + for { + if stopTTSLoop { + TTSLoopStopped = true + break + } + robot.Conn.PlayAnimation( + ctx, + &vectorpb.PlayAnimationRequest{ + Animation: &vectorpb.Animation{ + Name: "anim_tts_loop_02", + }, + Loops: 1, + }, + ) + } + }() + textToSaySplit := strings.Split(textToSay, ". ") + for _, str := range textToSaySplit { + _, err := robot.Conn.SayText( + ctx, + &vectorpb.SayTextRequest{ + Text: str + ".", + UseVectorVoice: true, + DurationScalar: 1.0, + }, + ) + if err != nil { + logger.Println("KG SayText error: " + err.Error()) + stop <- true + break + } + } + stopTTSLoop = true + for { + if TTSLoopStopped { + break + } else { + time.Sleep(time.Millisecond * 10) + } + } + time.Sleep(time.Millisecond * 100) + //time.Sleep(time.Millisecond * 3300) + stop <- true + } + }() + return nil +} diff --git a/chipper/pkg/wirepod/ttr/kgsim_interrupt.go b/chipper/pkg/wirepod/ttr/kgsim_interrupt.go new file mode 100644 index 0000000..0f9daa3 --- /dev/null +++ b/chipper/pkg/wirepod/ttr/kgsim_interrupt.go @@ -0,0 +1,92 @@ +package wirepod_ttr + +import ( + "context" + "time" + + "github.com/fforchino/vector-go-sdk/pkg/vector" + "github.com/fforchino/vector-go-sdk/pkg/vectorpb" + "github.com/kercre123/wire-pod/chipper/pkg/logger" +) + +func InterruptKGSimWhenTouchedOrWaked(rob *vector.Vector, stop chan bool, stopStop chan bool) bool { + strm, err := rob.Conn.EventStream( + context.Background(), + &vectorpb.EventRequest{ + ListType: &vectorpb.EventRequest_WhiteList{ + WhiteList: &vectorpb.FilterList{ + List: []string{"robot_state", "wake_word"}, + }, + }, + }, + ) + if err != nil { + logger.Println("Couldn't make an event stream: " + err.Error()) + return false + } + var stopFunc bool + go func() { + for range stopStop { + logger.Println("KG Interrupter has been stopped") + stopFunc = true + break + } + }() + var origTouchValue uint32 + var origValueGotten bool + var valsAboveValue int + var valsAboveValueMax int = 5 + var stopResponse bool + for { + var resp *vectorpb.EventResponse + resp, err = strm.Recv() + if err != nil { + break + } + switch resp.Event.EventType.(type) { + case *vectorpb.Event_RobotState: + origTouchValue = resp.Event.GetRobotState().TouchData.GetRawTouchValue() + origValueGotten = true + default: + } + if origValueGotten { + break + } + } + if origValueGotten { + for { + var resp *vectorpb.EventResponse + resp, err = strm.Recv() + if err != nil { + logger.Println("Event stream error: " + err.Error()) + return false + } + switch resp.Event.EventType.(type) { + case *vectorpb.Event_RobotState: + if resp.Event.GetRobotState().TouchData.GetRawTouchValue() > origTouchValue+50 { + valsAboveValue++ + } else { + valsAboveValue = 0 + } + case *vectorpb.Event_WakeWord: + logger.Println("Interrupting LLM response (source: wake word)") + stopResponse = true + default: + } + if valsAboveValue > valsAboveValueMax { + logger.Println("Interrupting LLM response (source: touch sensor)") + stopResponse = true + } + if stopResponse { + stop <- true + time.Sleep(time.Second / 4) + return true + } + if stopFunc { + strm.CloseSend() + return false + } + } + } + return false +} diff --git a/chipper/pkg/wirepod/ttr/kgsim_streaming.go b/chipper/pkg/wirepod/ttr/kgsim_streaming.go new file mode 100644 index 0000000..b453a71 --- /dev/null +++ b/chipper/pkg/wirepod/ttr/kgsim_streaming.go @@ -0,0 +1,316 @@ +package wirepod_ttr + +import ( + "context" + "errors" + "fmt" + "io" + "strings" + "time" + + "github.com/fforchino/vector-go-sdk/pkg/vector" + "github.com/fforchino/vector-go-sdk/pkg/vectorpb" + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vars" + "github.com/sashabaranov/go-openai" +) + +func StreamingKGSim(req interface{}, esn string, transcribedText string, isKG bool) (string, error) { + start := make(chan bool) + stop := make(chan bool) + stopStop := make(chan bool) + kgReadyToAnswer := make(chan bool) + kgStopLooping := false + ctx := context.Background() + matched := false + var robot *vector.Vector + var guid string + var target string + for _, bot := range vars.BotInfo.Robots { + if esn == bot.Esn { + guid = bot.GUID + target = bot.IPAddress + ":443" + matched = true + break + } + } + if matched { + var err error + robot, err = vector.New(vector.WithSerialNo(esn), vector.WithToken(guid), vector.WithTarget(target)) + if err != nil { + return err.Error(), err + } + } + _, err := robot.Conn.BatteryState(context.Background(), &vectorpb.BatteryStateRequest{}) + if err != nil { + return "", err + } + if isKG { + BControl(robot, ctx, start, stop) + go func() { + for { + if kgStopLooping { + kgReadyToAnswer <- true + break + } + robot.Conn.PlayAnimation(ctx, &vectorpb.PlayAnimationRequest{ + Animation: &vectorpb.Animation{ + Name: "anim_knowledgegraph_searching_01", + }, + Loops: 1, + }) + time.Sleep(time.Second / 3) + } + }() + } + + var tempRespText string + var fullRespText string + var fullRespSlice []string + var isDone bool + + var c *openai.Client + + if vars.APIConfig.Knowledge.Provider == "together" { + if vars.APIConfig.Knowledge.Model == "" { + vars.APIConfig.Knowledge.Model = "meta-llama/Llama-3-70b-chat-hf" + vars.WriteConfigToDisk() + } + conf := openai.DefaultConfig(vars.APIConfig.Knowledge.Key) + conf.BaseURL = "https://api.together.xyz/v1" + c = openai.NewClientWithConfig(conf) + } else if vars.APIConfig.Knowledge.Provider == "custom" { + conf := openai.DefaultConfig(vars.APIConfig.Knowledge.Key) + conf.BaseURL = vars.APIConfig.Knowledge.Endpoint + c = openai.NewClientWithConfig(conf) + } else if vars.APIConfig.Knowledge.Provider == "openai" { + c = openai.NewClient(vars.APIConfig.Knowledge.Key) + } + + speakReady := make(chan string) + successIntent := make(chan bool) + + aireq := CreateAIReq(transcribedText, esn, false, isKG) + + stream, err := c.CreateChatCompletionStream(ctx, aireq) + if err != nil { + if strings.Contains(err.Error(), "does not exist") && vars.APIConfig.Knowledge.Provider == "openai" { + logger.Println("GPT-4 model cannot be accessed with this API key. You likely need to add more than $5 dollars of funds to your OpenAI account.") + logger.LogUI("GPT-4 model cannot be accessed with this API key. You likely need to add more than $5 dollars of funds to your OpenAI account.") + aireq := CreateAIReq(transcribedText, esn, true, isKG) + logger.Println("Falling back to " + aireq.Model) + logger.LogUI("Falling back to " + aireq.Model) + stream, err = c.CreateChatCompletionStream(ctx, aireq) + if err != nil { + logger.Println("OpenAI still not returning a response even after falling back. Erroring.") + return "", err + } + } else { + if isKG { + kgStopLooping = true + for range kgReadyToAnswer { + break + } + stop <- true + time.Sleep(time.Second / 3) + KGSim(esn, "There was an error getting data from the L. L. M.") + } + return "", err + } + } + + nChat := aireq.Messages + nChat = append(nChat, openai.ChatCompletionMessage{ + Role: openai.ChatMessageRoleAssistant, + }) + + // === LLM RESPONSE === + fmt.Println("LLM stream response: ") + go func() { + for { + response, err := stream.Recv() + if errors.Is(err, io.EOF) { + // prevents a crash + if len(fullRespSlice) == 0 { + logger.Println("LLM returned no response") + successIntent <- false + if isKG { + kgStopLooping = true + for range kgReadyToAnswer { + break + } + stop <- true + time.Sleep(time.Second / 3) + KGSim(esn, "There was an error getting data from the L. L. M.") + } + break + } + isDone = true + // if fullRespSlice != fullRespText, add that missing bit to fullRespSlice + newStr := fullRespSlice[0] + for i, str := range fullRespSlice { + if i == 0 { + continue + } + newStr = newStr + " " + str + } + if strings.TrimSpace(newStr) != strings.TrimSpace(tempRespText) { + logger.Println("LLM debug: there is content after the last punctuation mark") + extraBit := strings.TrimPrefix(fullRespText, newStr) + fullRespSlice = append(fullRespSlice, extraBit) + } + if vars.APIConfig.Knowledge.SaveChat { + Remember(openai.ChatCompletionMessage{ + Role: openai.ChatMessageRoleUser, + Content: transcribedText, + }, + openai.ChatCompletionMessage{ + Role: openai.ChatMessageRoleAssistant, + Content: newStr, + }, + esn) + } + logger.LogUI("\n\n") + logger.LogUI("LLM response for " + esn + " :\n" + undoTmpDecPnt(newStr)) + logger.Println("LLM stream finished") + return + } + + if err != nil { + logger.Println("Stream error: " + err.Error()) + return + } + + tempRespText += response.Choices[0].Delta.Content + fullRespText += response.Choices[0].Delta.Content + + // handle decimals before splitting + fullRespText = doTmpDecPnt(fullRespText) + + // Updated splitting call + fullRespText, fullRespSlice = SplitLLMResponse(fullRespText, fullRespSlice) + + // Check if the splitting resulted in a non-empty affectedText + if len(fullRespSlice) > 0 { + affectedText := fullRespSlice[len(fullRespSlice)-1] // Get the last added part + select { + case successIntent <- true: + default: + } + select { // undoTmpDecPnt - handle decimals after splitting + case speakReady <- undoTmpDecPnt(affectedText): + default: + } + } + } + }() + // === LLM RESPONSE === + + for is := range successIntent { + if is { + if !isKG { + IntentPass(req, "intent_greeting_hello", transcribedText, map[string]string{}, false) + } + break + } else { + return "", errors.New("llm returned no response") + } + } + time.Sleep(time.Millisecond * 200) + if !isKG { + BControl(robot, ctx, start, stop) + } + interrupted := false + go func() { + interrupted = InterruptKGSimWhenTouchedOrWaked(robot, stop, stopStop) + }() + var TTSLoopAnimation string + var TTSGetinAnimation string + if isKG { + TTSLoopAnimation = "anim_knowledgegraph_answer_01" + TTSGetinAnimation = "anim_knowledgegraph_searching_getout_01" + } else { + TTSLoopAnimation = "anim_tts_loop_02" + TTSGetinAnimation = "anim_getin_tts_01" + } + + var stopTTSLoop bool + TTSLoopStopped := make(chan bool) + for range start { + if isKG { + kgStopLooping = true + for range kgReadyToAnswer { + break + } + } else { + time.Sleep(time.Millisecond * 300) + } + robot.Conn.PlayAnimation( + ctx, + &vectorpb.PlayAnimationRequest{ + Animation: &vectorpb.Animation{ + Name: TTSGetinAnimation, + }, + Loops: 1, + }, + ) + if !vars.APIConfig.Knowledge.CommandsEnable { + go func() { + for { + if stopTTSLoop { + TTSLoopStopped <- true + break + } + robot.Conn.PlayAnimation( + ctx, + &vectorpb.PlayAnimationRequest{ + Animation: &vectorpb.Animation{ + Name: TTSLoopAnimation, + }, + Loops: 1, + }, + ) + } + }() + } + var disconnect bool + numInResp := 0 + for { + respSlice := fullRespSlice + if len(respSlice)-1 < numInResp { + if !isDone { + logger.Println("Waiting for more content from LLM...") + for range speakReady { + respSlice = fullRespSlice + break + } + } else { + break + } + } + if interrupted { + break + } + logger.Println(respSlice[numInResp]) + acts := GetActionsFromString(respSlice[numInResp]) + nChat[len(nChat)-1].Content = fullRespText + disconnect = PerformActions(nChat, acts, robot, stopStop) + if disconnect { + break + } + numInResp = numInResp + 1 + } + if !vars.APIConfig.Knowledge.CommandsEnable { + stopTTSLoop = true + for range TTSLoopStopped { + break + } + } + time.Sleep(time.Millisecond * 100) + if !interrupted { + stopStop <- true + stop <- true + } + } + return "", nil +} diff --git a/chipper/pkg/wirepod/ttr/llm_streaming.go b/chipper/pkg/wirepod/ttr/llm_streaming.go new file mode 100644 index 0000000..0f4ae82 --- /dev/null +++ b/chipper/pkg/wirepod/ttr/llm_streaming.go @@ -0,0 +1,107 @@ +package wirepod_ttr + +import ( + "context" + "errors" + "io" + "strings" + + "github.com/fforchino/vector-go-sdk/pkg/vector" + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vars" + "github.com/sashabaranov/go-openai" +) + +func SplitLLMResponse(fullRespText string, fullRespSlice []string) (string, []string) { + // Define the possible separators. + punctuation := []string{"...", ".'", ".\"", ".", "?", "!"} + + // Loop through the punctuation options to find the first match. + var sepStr string + for _, sep := range punctuation { + if strings.Contains(fullRespText, sep) { + sepStr = sep + break + } + } + + // If a separator was found, split the text. + if sepStr != "" { + splitResp := strings.SplitN(strings.TrimSpace(fullRespText), sepStr, 2) + if len(splitResp) == 2 { + fullRespSlice = append(fullRespSlice, strings.TrimSpace(splitResp[0])+sepStr) + fullRespText = splitResp[1] + } + } + + return fullRespText, fullRespSlice +} + +func HandleLLMStream(c *openai.Client, ctx context.Context, aireq openai.ChatCompletionRequest, msgs []openai.ChatCompletionMessage, robot *vector.Vector, stopStop chan bool, isDone *bool, fullRespSlice *[]string, tempRespText *string, speakReady chan string) error { + stream, err := c.CreateChatCompletionStream(ctx, aireq) + if err != nil { + if strings.Contains(err.Error(), "does not exist") && vars.APIConfig.Knowledge.Provider == "openai" { + logger.Println("GPT-4 model cannot be accessed with this API key. You likely need to add more than $5 dollars of funds to your OpenAI account.") + logger.LogUI("GPT-4 model cannot be accessed with this API key. You likely need to add more than $5 dollars of funds to your OpenAI account.") + aireq.Model = openai.GPT3Dot5Turbo + logger.Println("Falling back to " + aireq.Model) + logger.LogUI("Falling back to " + aireq.Model) + stream, err = c.CreateChatCompletionStream(ctx, aireq) + if err != nil { + logger.Println("OpenAI still not returning a response even after falling back. Erroring.") + return err + } + } else { + logger.Println("LLM error: " + err.Error()) + return err + } + } + + go func() { + for { + response, err := stream.Recv() + if errors.Is(err, io.EOF) { + *isDone = true + newStr := strings.Join(*fullRespSlice, " ") + if strings.TrimSpace(newStr) != strings.TrimSpace(*tempRespText) { + logger.Println("LLM debug: there is content after the last punctuation mark") + extraBit := strings.TrimPrefix(*tempRespText, newStr) + *fullRespSlice = append(*fullRespSlice, extraBit) + } + if vars.APIConfig.Knowledge.SaveChat { + Remember(msgs[len(msgs)-1], + openai.ChatCompletionMessage{ + Role: openai.ChatMessageRoleAssistant, + Content: newStr, + }, + robot.Cfg.SerialNo) + } + logger.LogUI("LLM response for " + robot.Cfg.SerialNo + ": " + undoTmpDecPnt(newStr)) + logger.Println("LLM stream finished") + return + } + + if err != nil { + logger.Println("Stream error: " + err.Error()) + return + } + + *tempRespText += response.Choices[0].Delta.Content + // Handle decimals + *tempRespText = doTmpDecPnt(*tempRespText) + + // Split LLM response + *tempRespText, *fullRespSlice = SplitLLMResponse(*tempRespText, *fullRespSlice) + + if len(*fullRespSlice) > 0 { + affectedText := strings.TrimSpace((*fullRespSlice)[len(*fullRespSlice)-1]) // Get the last added part + select { + case speakReady <- undoTmpDecPnt(affectedText): + default: + } + } + } + }() + + return nil +} diff --git a/chipper/pkg/wirepod/ttr/llm_vars.go b/chipper/pkg/wirepod/ttr/llm_vars.go new file mode 100644 index 0000000..02d79b0 --- /dev/null +++ b/chipper/pkg/wirepod/ttr/llm_vars.go @@ -0,0 +1,107 @@ +package wirepod_ttr + +import ( + "regexp" + "time" + + "github.com/sashabaranov/go-openai" +) + +const ( + ActionSayText = iota // 0 + ActionPlayAnimation // 1 + ActionPlayAnimationWI // 2 + ActionGetImage // 3 + ActionNewRequest // 4 + ActionPlaySound // 5 +) + +type RobotAction struct { + Action int + Parameter string +} + +type LLMCommand struct { + Command string + Description string + ParamChoices string + Action int + SupportedModels []string +} + +// create function which parses from LLM and makes a struct of RobotActions +var ( + playAnimationWIDescription = "Enhances storytelling by playing an animation on the robot without interrupting speech. This should be used frequently to animate responses and engage the audience. Choose from parameters like happy, veryHappy, sad, verySad, angry, frustrated, dartingEyes, confused, thinking, celebrate, or love to complement the dialogue and maintain context." + + playAnimationDescription = "Interrupts speech to play an animation on the robot. Only use this when directed explicitly to play an animation, as it halts ongoing speech. Parameters include happy, veryHappy, sad, verySad, angry, frustrated, dartingEyes, confused, thinking, celebrate, or love for expressing emotions and reactions." + + getImageDescription = "Retrieves an image from the robot's camera and displays it in the next message. Use this command to conclude a sentence or response when prompted by the user or when describing visual content, such as what the robot sees, with options for front or lookingUp perspectives. If asked 'What do you see in front of you?' or similar, default to taking a photo. Inform the user of your action before using the command." + + newVoiceRequestDescription = "Starts a new voice command from the robot. Use this if you want more input from the user/if you want to carry out a conversation. You are the only one who can end it in this case. This goes at the end of your response, if you use it." + +// var playSoundDescription = "Plays a sound on the robot." + + LLMCommandsParamChoices = "happy, veryHappy, sad, verySad, angry, frustrated, dartingEyes, confused, thinking, celebrate, love" +) + +var ValidLLMCommands []LLMCommand = []LLMCommand{ + { + Command: "playAnimationWI", + Description: playAnimationWIDescription, + ParamChoices: LLMCommandsParamChoices, + Action: ActionPlayAnimationWI, + SupportedModels: []string{"all"}, + }, + { + Command: "playAnimation", + Description: playAnimationDescription, + ParamChoices: LLMCommandsParamChoices, + Action: ActionPlayAnimation, + SupportedModels: []string{"all"}, + }, + { + Command: "getImage", + Description: getImageDescription, + ParamChoices: "front, looking-Up, ahead-Of-You, in-Front-Of-You, Above-You", + Action: ActionGetImage, + SupportedModels: []string{openai.GPT4o, openai.GPT4oMini}, + }, + { + Command: "newVoiceRequest", + Description: newVoiceRequestDescription, + ParamChoices: "now", + Action: ActionNewRequest, + SupportedModels: []string{"all"}, + }, + /*{ + Command: "playSound", + Description: playSoundDescription, + ParamChoices: "drumroll", + Action: ActionPlaySound, + },*/ +} + + +// Declare variables to store the last processed input and its processing time at the package level +var ( + lastProcessedInput string + lastProcessedTime time.Time // Track last processed time + + // Combined Precompiled regex patterns + bracketDecimalPattern = regexp.MustCompile(`(\s*[\[\{])\.`) + numeric0To9Pattern = regexp.MustCompile(`[0-9]`) + recurringPattern = regexp.MustCompile(`point\s+(\d+)\s+recurring`) + spaceDecimalPattern = regexp.MustCompile(`(\d+)\s+([./])`) + unwantedSpacePattern = regexp.MustCompile(`\.(\s+)`) + fractionPattern = regexp.MustCompile(`^\s*(\d+)\s*/\s*(\d+)\s*$`) // Pattern for fractions + + closingBracketPattern = regexp.MustCompile(`\\\)`) + closingBracketSquarePattern = regexp.MustCompile(`\\\]`) + latexDelimiterPattern = regexp.MustCompile(`\\\(`) + latexFractionRegexp = regexp.MustCompile(`\\(?:frac|dfrac)\{([^}]+)\}\{([^}]+)\}`) + numericPattern = regexp.MustCompile(`^\d+(\.\d+)?$`) + openingBracketPattern = regexp.MustCompile(`\\\[`) + + safeCharacterPattern = regexp.MustCompile(`[^\w\s\.{}\^\[\]\/]`) + simpleFractionRegexp = regexp.MustCompile(`(\d+)\s*/\s*(\d+)`) +) diff --git a/chipper/pkg/wirepod/ttr/matchIntentSend.go b/chipper/pkg/wirepod/ttr/matchIntentSend.go new file mode 100644 index 0000000..8c1feae --- /dev/null +++ b/chipper/pkg/wirepod/ttr/matchIntentSend.go @@ -0,0 +1,316 @@ +package wirepod_ttr + +import ( + "bytes" + "encoding/json" + "fmt" + "os/exec" + "strings" + + pb "github.com/digital-dream-labs/api/go/chipperpb" + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/scripting" + "github.com/kercre123/wire-pod/chipper/pkg/vars" + "github.com/kercre123/wire-pod/chipper/pkg/vtt" +) + +type systemIntentResponseStruct struct { + Status string `json:"status"` + ReturnIntent string `json:"returnIntent"` +} + +func IntentPass(req interface{}, intentThing string, speechText string, intentParams map[string]string, isParam bool) (interface{}, error) { + var esn string + var req1 *vtt.IntentRequest + var req2 *vtt.IntentGraphRequest + var isIntentGraph bool + if str, ok := req.(*vtt.IntentRequest); ok { + req1 = str + esn = req1.Device + isIntentGraph = false + } else if str, ok := req.(*vtt.IntentGraphRequest); ok { + req2 = str + esn = req2.Device + isIntentGraph = true + } + + // intercept if not intent graph but intent graph is enabled + if !isIntentGraph && vars.APIConfig.Knowledge.IntentGraph && intentThing == "intent_system_unmatched" { + intentThing = "intent_greeting_hello" + } + + var intentResult pb.IntentResult + if isParam { + intentResult = pb.IntentResult{ + QueryText: speechText, + Action: intentThing, + Parameters: intentParams, + } + } else { + intentResult = pb.IntentResult{ + QueryText: speechText, + Action: intentThing, + } + } + logger.LogUI("\n\n") + logger.LogUI("Intent matched: " + intentThing + ", transcribed text: '" + speechText + "', device: " + esn) + if isParam { + logger.LogUI("Parameters sent: " + fmt.Sprint(intentParams)) + } + intent := pb.IntentResponse{ + IsFinal: true, + IntentResult: &intentResult, + } + intentGraphSend := pb.IntentGraphResponse{ + ResponseType: pb.IntentGraphMode_INTENT, + IsFinal: true, + IntentResult: &intentResult, + CommandType: pb.RobotMode_VOICE_COMMAND.String(), + } + if !isIntentGraph { + if err := req1.Stream.Send(&intent); err != nil { + return nil, err + } + r := &vtt.IntentResponse{ + Intent: &intent, + } + logger.Println("Bot " + esn + " Intent Sent: " + intentThing) + if isParam { + logger.Println("Bot "+esn+" Parameters Sent:", intentParams) + } else { + logger.Println("No Parameters Sent") + } + return r, nil + } else { + if err := req2.Stream.Send(&intentGraphSend); err != nil { + return nil, err + } + r := &vtt.IntentGraphResponse{ + Intent: &intentGraphSend, + } + logger.Println("Bot " + esn + " Intent Sent: " + intentThing) + if isParam { + logger.Println("Bot "+esn+" Parameters Sent:", intentParams) + } else { + logger.Println("No Parameters Sent") + } + return r, nil + } +} + +func customIntentHandler(req interface{}, voiceText string, botSerial string) bool { + var successMatched bool = false + if vars.CustomIntentsExist { + for _, c := range vars.CustomIntents { + for _, v := range c.Utterances { + //if strings.Contains(voiceText, strings.ToLower(strings.TrimSpace(v))) { + // Check whether the custom sentence is either at the end of the spoken text or space-separated... + var seekText = strings.ToLower(strings.TrimSpace(v)) + // System intents can also match any utterances (*) + if (c.IsSystemIntent && strings.HasPrefix(seekText, "*")) || strings.Contains(voiceText, seekText) { + logger.Println("Bot " + botSerial + " Custom Intent Matched: " + c.Name + " - " + c.Description + " - " + c.Intent) + var intentParams map[string]string + var isParam bool = false + if c.Params.ParamValue != "" { + logger.Println("Bot " + botSerial + " Custom Intent Parameter: " + c.Params.ParamName + " - " + c.Params.ParamValue) + intentParams = map[string]string{c.Params.ParamName: c.Params.ParamValue} + isParam = true + } + + go func() { + if c.LuaScript != "" { + err := scripting.RunLuaScript(botSerial, c.LuaScript) + if err != nil { + logger.Println("Error running Lua script: " + err.Error()) + } + } + }() + + var args []string + for _, arg := range c.ExecArgs { + if arg == "!botSerial" { + arg = botSerial + } else if arg == "!speechText" { + arg = "\"" + voiceText + "\"" + } else if arg == "!intentName" { + arg = c.Name + } else if arg == "!locale" { + arg = vars.APIConfig.STT.Language + } + args = append(args, arg) + } + var customIntentExec *exec.Cmd + if len(args) == 0 { + logger.Println("Bot " + botSerial + " Executing: " + c.Exec) + customIntentExec = exec.Command(c.Exec) + } else { + logger.Println("Bot " + botSerial + " Executing: " + c.Exec + " " + strings.Join(args, " ")) + customIntentExec = exec.Command(c.Exec, args...) + } + var out bytes.Buffer + var stderr bytes.Buffer + customIntentExec.Stdout = &out + customIntentExec.Stderr = &stderr + err := customIntentExec.Run() + if err != nil { + fmt.Println(fmt.Sprint(err) + ": " + stderr.String()) + } + logger.Println("Bot " + botSerial + " Custom Intent Exec Output: " + strings.TrimSpace(string(out.String()))) + + if c.IsSystemIntent { + // A system intent returns its output in json format + var resp systemIntentResponseStruct + err := json.Unmarshal(out.Bytes(), &resp) + if err == nil && resp.Status == "ok" { + logger.Println("Bot " + botSerial + " System intent parsed and executed successfully") + IntentPass(req, resp.ReturnIntent, voiceText, intentParams, isParam) + successMatched = true + } + } else { + IntentPass(req, c.Intent, voiceText, intentParams, isParam) + successMatched = true + } + break + } + if successMatched { + break + } + } + if successMatched { + break + } + } + } + return successMatched +} + +func pluginFunctionHandler(req interface{}, voiceText string, botSerial string) bool { + matched := false + var intent string + var igr *vtt.IntentGraphRequest + if str, ok := req.(*vtt.IntentGraphRequest); ok { + igr = str + } + var pluginResponse string + for num, array := range PluginUtterances { + array := array + for _, str := range *array { + if strings.Contains(voiceText, str) || str == "*" { + logger.Println("Bot " + botSerial + " matched plugin " + PluginNames[num] + ", executing function") + var guid string + var target string + for _, bot := range vars.BotInfo.Robots { + if bot.Esn == botSerial { + guid = bot.GUID + target = bot.IPAddress + ":443" + } + } + intent, pluginResponse = PluginFunctions[num](voiceText, botSerial, guid, target) + if intent == "" && pluginResponse == "" { + break + } + if intent == "" { + intent = "intent_imperative_praise" + } + logger.Println("Bot " + botSerial + " plugin " + PluginNames[num] + ", response " + pluginResponse) + if pluginResponse != "" && igr != nil { + response := &pb.IntentGraphResponse{ + Session: igr.Session, + DeviceId: igr.Device, + ResponseType: pb.IntentGraphMode_KNOWLEDGE_GRAPH, + SpokenText: pluginResponse, + QueryText: voiceText, + IsFinal: true, + } + igr.Stream.Send(response) + } else if pluginResponse != "" { + KGSim(botSerial, pluginResponse) + } else { + IntentPass(req, intent, voiceText, make(map[string]string), false) + } + matched = true + break + } + } + if matched { + break + } + } + return matched +} + +func ProcessTextAll(req interface{}, voiceText string, intents []vars.JsonIntent, isOpus bool) bool { + var botSerial string + var req2 *vtt.IntentRequest + var req1 *vtt.KnowledgeGraphRequest + var req3 *vtt.IntentGraphRequest + if str, ok := req.(*vtt.IntentRequest); ok { + req2 = str + botSerial = req2.Device + } else if str, ok := req.(*vtt.KnowledgeGraphRequest); ok { + req1 = str + botSerial = req1.Device + } else if str, ok := req.(*vtt.IntentGraphRequest); ok { + req3 = str + botSerial = req3.Device + } + var matched int = 0 + var intentNum int = 0 + var successMatched bool = false + voiceText = strings.ToLower(voiceText) + pluginMatched := pluginFunctionHandler(req, voiceText, botSerial) + customIntentMatched := customIntentHandler(req, voiceText, botSerial) + if !customIntentMatched && !pluginMatched { + logger.Println("Not a custom intent") + // Look for a perfect match first + for _, b := range intents { + for _, c := range b.Keyphrases { + if voiceText == strings.ToLower(c) { + logger.Println("Bot " + botSerial + " Perfect match for intent " + b.Name + " (" + strings.ToLower(c) + ")") + if isOpus { + ParamChecker(req, b.Name, voiceText, botSerial) + } else { + prehistoricParamChecker(req, b.Name, voiceText) + } + successMatched = true + matched = 1 + break + } + } + if matched == 1 { + matched = 0 + break + } + intentNum = intentNum + 1 + } + // Not found? Then let's be happy with a bare substring search + if !successMatched { + intentNum = 0 + matched = 0 + for _, b := range intents { + for _, c := range b.Keyphrases { + if strings.Contains(voiceText, strings.ToLower(c)) && !b.RequireExactMatch { + logger.Println("Bot " + botSerial + " Partial match for intent " + b.Name + " (" + strings.ToLower(c) + ")") + if isOpus { + ParamChecker(req, b.Name, voiceText, botSerial) + } else { + prehistoricParamChecker(req, b.Name, voiceText) + } + successMatched = true + matched = 1 + break + } + } + if matched == 1 { + matched = 0 + break + } + intentNum = intentNum + 1 + } + } + } else { + logger.Println("This is a custom intent or plugin!") + successMatched = true + } + return successMatched +} diff --git a/chipper/pkg/wirepod/ttr/plugins.go b/chipper/pkg/wirepod/ttr/plugins.go new file mode 100644 index 0000000..fe9371a --- /dev/null +++ b/chipper/pkg/wirepod/ttr/plugins.go @@ -0,0 +1,81 @@ +package wirepod_ttr + +import ( + "os" + "plugin" + "strings" + + "github.com/kercre123/wire-pod/chipper/pkg/logger" +) + +var PluginList []*plugin.Plugin +var PluginUtterances []*[]string +var PluginFunctions []func(string, string, string, string) (string, string) +var PluginNames []string + +func LoadPlugins() { + logger.Println("Loading plugins") + entries, err := os.ReadDir("./plugins") + if err != nil { + logger.Println("Unable to load plugins:") + logger.Println(err) + return + } + for _, file := range entries { + if strings.Contains(file.Name(), ".so") { + plugin, err := plugin.Open("./plugins/" + file.Name()) + if err != nil { + logger.Println("Error loading plugin: " + file.Name()) + logger.Println(err) + continue + } else { + logger.Println("Loading plugin: " + file.Name()) + } + u, err := plugin.Lookup("Utterances") + if err != nil { + logger.Println("Error loading Utterances []string from plugin file " + file.Name()) + logger.Println(err) + continue + } else { + if _, ok := u.(*[]string); ok { + logger.Println("Utterances []string in plugin " + file.Name() + " are OK") + } else { + logger.Println("Error: Utterances in plugin " + file.Name() + " are not of type []string") + continue + } + } + a, err := plugin.Lookup("Action") + if err != nil { + logger.Println("Error loading Action func from plugin file " + file.Name()) + continue + } else { + if _, ok := a.(func(string, string, string, string) (string, string)); ok { + logger.Println("Action func in plugin " + file.Name() + " is OK") + } else { + logger.Println("Error: Action func in plugin " + file.Name() + " is not of type func(string, string) string") + continue + } + } + n, err := plugin.Lookup("Name") + if err != nil { + logger.Println("Error loading Name string from plugin file " + file.Name()) + continue + } else { + if _, ok := n.(*string); ok { + logger.Println("Name string in plugin " + *n.(*string) + " is OK") + } else { + logger.Println("Error: Name string in plugin " + file.Name() + " is not of type string") + continue + } + } + PluginUtterances = append(PluginUtterances, u.(*[]string)) + PluginFunctions = append(PluginFunctions, a.(func(string, string, string, string) (string, string))) + PluginNames = append(PluginNames, *n.(*string)) + PluginList = append(PluginList, plugin) + logger.Println(file.Name() + " loaded successfully") + } + // else { + // logger.Println("Not loading " + file.Name() + ". Plugins must be built with 'go build -buildmode=plugin' and must end in '.so'.") + //} + } +} diff --git a/chipper/pkg/wirepod/ttr/rational_number_utils.go b/chipper/pkg/wirepod/ttr/rational_number_utils.go new file mode 100644 index 0000000..560460b --- /dev/null +++ b/chipper/pkg/wirepod/ttr/rational_number_utils.go @@ -0,0 +1,24 @@ +package wirepod_ttr + +import ( + "strings" +) + +var ( + tmpDecPntSpc = "tmpDecPnt " // tmpDecPnt + space + dotSpc = ". " // dot + space +) + +// before llm response is split +func doTmpDecPnt(rawStr string) string { + output1 := strings.ReplaceAll(rawStr, ".", "tmpDecPnt") // Replace dot with temporary placeholder + output2 := strings.ReplaceAll(output1, tmpDecPntSpc, dotSpc) // Replace placeholder+space point with actual dot+space + return output2 +} + +// after llm response is split +func undoTmpDecPnt(rawStr string) string { + output1 := strings.ReplaceAll(rawStr, "tmpDecPnt", ".") + return output1 +} + diff --git a/chipper/pkg/wirepod/ttr/special_characters.go b/chipper/pkg/wirepod/ttr/special_characters.go new file mode 100644 index 0000000..dd48dcd --- /dev/null +++ b/chipper/pkg/wirepod/ttr/special_characters.go @@ -0,0 +1,72 @@ +package wirepod_ttr + +import ( + "regexp" + "strings" + "unicode" + + "golang.org/x/text/transform" + "golang.org/x/text/unicode/norm" + "github.com/kercre123/wire-pod/chipper/pkg/logger" +) + +// isMn checks if a rune is a non-spacing mark. +func isMn(r rune) bool { + return unicode.Is(unicode.Mn, r) +} + +// normalizeText normalizes the input text by removing non-spacing marks. +func normalizeText(str string) (string, error) { + t := transform.Chain(norm.NFD, transform.RemoveFunc(isMn), norm.NFC) + normalizedStr, _, err := transform.String(t, str) + if err != nil { + logger.Println("normalizeText error:", err) + return str, err // Return original input on error + } + return normalizedStr, nil +} + +// Precompile emoji regex pattern once +var emojiRegex = regexp.MustCompile(emojiPattern) + +func removeEmojis(str string) string { + // Check if emojis exist and clean them + if emojiRegex.MatchString(str) { + logger.Println("Emojis detected, cleaning the input.") + // Clean emojis but retain the original string if the resulting string is empty + cleanedStr := emojiRegex.ReplaceAllString(str, "") + return strings.TrimSpace(cleanedStr) // Ensure no leading/trailing spaces + } + return str +} + +// replaceWords case-insensitively replaces words/phrases in the input string. +func replaceWords(str string) string { + for find, replace := range phoneticReplacements { + regex := regexp.MustCompile(`\b(?i)` + regexp.QuoteMeta(find) + `\b`) + str = regex.ReplaceAllString(str, replace) // Replace words/phrases to allow multiple replacements + } + return str +} + +// removeSpecialCharacters removes special characters from the input string and provides a user-friendly output if empty. +func removeSpecialCharacters(rawStr string) (string, error) { + + // Normalize the input and log changes; ensure this does not strip punctuation. + normalizedStr, err := normalizeText(rawStr) + if err != nil { + logger.Println("preCleanSpecialCharacters Normalization error:", err) + return "[No content]", err // Return error as well for better handling + } + + wordsStr := replaceWords(normalizedStr) + + cleanStr1 := specialCharactersReplacements.Replace(wordsStr) + cleanStr2 := removeEmojis(cleanStr1) // Clean emojis after initial cleaning + + if strings.TrimSpace(cleanStr2) != "" { + return strings.TrimSpace(cleanStr2), nil // Return cleaned input + } else { + return "", nil // Default return for empty input + } +} \ No newline at end of file diff --git a/chipper/pkg/wirepod/ttr/special_characters_vars.go b/chipper/pkg/wirepod/ttr/special_characters_vars.go new file mode 100644 index 0000000..3480ae7 --- /dev/null +++ b/chipper/pkg/wirepod/ttr/special_characters_vars.go @@ -0,0 +1,127 @@ +package wirepod_ttr + +import ( + "strings" +) + +// The special Character Replacements +var specialCharactersReplacements = strings.NewReplacer( + // quotes, apostrophes & misc + "’", "'", + "‘", "'", + "“", "\"", + "”", "\"", + "—", ", ", + "–", ", ", + "…", ",", + "...", ",", + "*", "", + + // bullets + "•", "- ", "‣", "- ", "◦", "- ", + + // fractions + "¼", "1/4", + "½", "1/2", + "¾", "3/4", + + "1̅", "1 recurring", + "2̅", "2 recurring", + "3̅", "3 recurring", + "4̅", "4 recurring", + "5̅", "5 recurring", + "6̅", "6 recurring", + "7̅", "7 recurring", + "8̅", "8 recurring", + "9̅", "9 recurring", + + // URL encoded + "%23|\\#", "hashtag", + "%24|\\$", "Canadian Dollars, Ay?", + "%26|\\&", "and", + "%40|\\@", " at ", + "\u00A0", " ", //   + + // Mathematical and Related Symbols + "±", "plus minus", + "÷", "divided by", + "√", "square root", + "∞", "infinity", + "≈", "almost equals", + "≠", "is not equal to", + "≡", "is equal to", + "≤", "is less than or equal to", + "≥", "is greater than or equal to", + "°", "degrees", + "π", "pi", + "∆", "delta", + "∑", "sum", + "∏", "product", + "×", "multiply by", + + // Currency Symbols + "€", "EUR", + "£", "GBP", + "¥", "JPY", + "₹", "INR", +) + +// phonetic map for Vector (my little experiment) +var phoneticReplacements = map[string]string{ + "CrushN8r": "Crush-Ehnaydir", + "flippin'": "fukkin", "flippin": "fukkin", "flipin": "fukkin", "frickin": "fukkin", "Boo-Ya": "Boo-Ya, Hawk, Too-Ya", + "AI": "AY-EYE", "SpaceX": "Space-X", "Aha": "Ah,ha", "A-ha": "Ah,ha", "tastic": "tass-stick", "A-game": "AY-game", " A ": " AY ", +} + +// Comprehensive emoji patterns (compiled once for performance) +// const emojiPattern = `[\x{1F600}-\x{1F64F}]|[\x{1F300}-\x{1F5FF}]|[\x{1F680}-\x{1F6FF}]|[\x{1F1E0}-\x{1F1FF}]|[\x{2600}-\x{26FF}]|[\x{2700}-\x{27BF}]|[\x{1F900}-\x{1F9FF}]|[\x{1F004}]|[\x{1F0CF}]|[\x{1F18E}]|[\x{1F191}-\x{1F251}]|[\x{2B50}]|[\x{1F9E6}-\x{1F9EB}]|[\x{1F914}]|[\x{2702}]|[\x{1F9C0}]|[\x{1F461}]|[\x{1F4AA}]|[\x{1F44B}]|[\x{1F48B}]|[\x{1F49C}]|[\x{1F9F1}]|[\x{1F9FB}]|[\x{1F525}]|[\x{2728}]|[\x{1F44F}]|[\x{1F389}]|[\x{1F60D}]|[\x{1F929}]|[\x{1F613}]|[\x{1F625}]|[\x{1F3C3}]|[\x{1F4A8}]|[\x{1F5A5}]|[\x{1F922}]|[\x{1F570}]|[\x{1F52E}]|[\x{1F950}]|[\x{1F9BB}]|[\x{1F94A}]|[\x{1F9D9}]|[\x{1F9F3}]|[\x{1FA78}]|[\x{1F57A}]|[\x{1F9CF}]|[\x{1F4B0}]|[\x{1FA99}]|[\x{1F9F8}]|[\x{1F9D8}]|[\x{1F9F7}]` + +// Comprehensive emoji patterns (compiled once for performance) +const emojiPattern = `[\x{1F600}-\x{1F64F}]|` + // Emoticons + `[\x{1F300}-\x{1F5FF}]|` + // Miscellaneous Symbols + `[\x{1F680}-\x{1F6FF}]|` + // Transport and Map symbols + `[\x{1F1E0}-\x{1F1FF}]|` + // Flags + `[\x{2600}-\x{26FF}]|` + // Miscellaneous Symbols + `[\x{2700}-\x{27BF}]|` + // Dingbats + `[\x{1F900}-\x{1F9FF}]|` + // Supplemental Symbols + `[\x{1F004}]|` + // Mahjong Tile Red Dragon + `[\x{1F0CF}]|` + // Playing Card Black Joker + `[\x{1F18E}]|` + // Circled C + `[\x{1F191}-\x{1F251}]|` + // Enclosed Alphanumeric Supplement + `[\x{1F9E6}-\x{1F9EB}]|` + // Chess Symbols + `[\x{1F914}]|` + // Thinking Face + `[\x{2702}]|` + // Scissors + `[\x{1F9C0}]|` + // Cheese Wedge + `[\x{1F461}]|` + // Woman's Shoe + `[\x{1F4AA}]|` + // Flexed Biceps + `[\x{1F44B}]|` + // Waving Hand + `[\x{1F48B}]|` + // Kiss Mark + `[\x{1F49C}]|` + // Orange Heart + `[\x{1F9F1}]|` + // Luggage + `[\x{1F9FB}]|` + // Haircut + `[\x{1F525}]|` + // Fire + `[\x{2728}]|` + // Sparkles + `[\x{1F44F}]|` + // Clapping Hands + `[\x{1F389}]|` + // Party Popper + `[\x{1F60D}]|` + // Smiling Face with Heart-Eyes + `[\x{1F929}]|` + // Star-Struck + `[\x{1F613}]|` + // Sweating Face + `[\x{1F625}]|` + // Disappointed Face + `[\x{1F3C3}]|` + // Person Running + `[\x{1F4A8}]|` + // Collision + `[\x{1F5A5}]|` + // Eye in Speech Bubble + `[\x{1F922}]|` + // Face with Hand Over Mouth + `[\x{1F570}]|` + // Old Key + `[\x{1F52E}]|` + // Crystal Ball + `[\x{1F950}]|` + // Croissant + `[\x{1F9BB}]|` + // Tamale + `[\x{1F94A}]|` + // Cooking + `[\x{1F9D9}]|` + // Mage + `[\x{1F9F3}]|` + // Glasses + `[\x{1FA78}]|` + // Gaming Device + `[\x{1F57A}]|` + // Man Juggling + `[\x{1F9CF}]|` + // Person in Lotus Position + `[\x{1F4B0}]|` + // Money Bag + `[\x{1FA99}]|` + // Rooting + `[\x{1F9F7}]|` + // Magic Wand + `[\x{1F4A5}]` // Collision diff --git a/chipper/pkg/wirepod/ttr/weather.go b/chipper/pkg/wirepod/ttr/weather.go new file mode 100644 index 0000000..1e2561f --- /dev/null +++ b/chipper/pkg/wirepod/ttr/weather.go @@ -0,0 +1,76 @@ +package wirepod_ttr + +/* TODO: +Create seperate functions for weatherAPI and openweathermap, +create a standard for how weather functions should be created +*/ + +// *** WEATHERAPI.COM *** + +type weatherAPIResponseStruct struct { + Location struct { + Name string `json:"name"` + Localtime string `json:"localtime"` + } `json:"location"` + Current struct { + LastUpdatedEpoch int `json:"last_updated_epoch"` + LastUpdated string `json:"last_updated"` + TempC float64 `json:"temp_c"` + TempF float64 `json:"temp_f"` + Condition struct { + Text string `json:"text"` + Icon string `json:"icon"` + Code int `json:"code"` + } `json:"condition"` + } `json:"current"` +} +type weatherAPICladStruct []struct { + APIValue string `json:"APIValue"` + CladType string `json:"CladType"` +} + +// *** OPENWEATHERMAP.ORG *** + +type openWeatherMapAPIGeoCodingStruct struct { + Name string `json:"name"` + LocalNames map[string]string `json:"local_names"` + Lat float64 `json:"lat"` + Lon float64 `json:"lon"` + Country string `json:"country"` + State string `json:"state"` +} + +/* +//3.0 API, requires your credit card even to get 1k free requests per day + +type openWeatherMapAPIResponseStruct struct { + Lat float64 `json:"lat"` + Lon float64 `json:"lon"` + timezone string `json:"timezone"` + timezone_offset string `json:"timezone_offset"` + Current struct { + DT int `json:"dt"` + Sunrise int `json:"sunrise"` + Sunset int `json:"sunset"` + Temp float64 `json:"temp"` + FeelsLike float64 `json:"feels_like"` + Pressure int `json:"pressure"` + Humidity int `json:"humidity"` + DewPoint float64 `json:"dew_point"` + UVI float64 `json:"uvi"` + Clouds int `json:"clouds"` + Visibility int `json:"visibility"` + WindSpeed float64 `json:"wind_speed"` + WindDeg int `json:"wid_deg"` + WindGust float64 `json:"wind_gust"` + Weather struct { + Id int `json:"id"` + Main string `json:"main"` + Description string `json:"description"` + Icon string `json:"icon"` + } `json:"weather"` + } `json:"current"` +} +*/ + +//2.5 API \ No newline at end of file diff --git a/chipper/pkg/wirepod/ttr/weather_utils.go b/chipper/pkg/wirepod/ttr/weather_utils.go new file mode 100644 index 0000000..c46fd98 --- /dev/null +++ b/chipper/pkg/wirepod/ttr/weather_utils.go @@ -0,0 +1,135 @@ +package wirepod_ttr + +import ( + "os" + "strconv" + "strings" + "time" + "unicode" + + "github.com/kercre123/wire-pod/chipper/pkg/logger" + lcztn "github.com/kercre123/wire-pod/chipper/pkg/wirepod/localization" +) + +func removeEndPunctuation(s string) string { + if s == "" { + return s + } + + runes := []rune(s) + lastIndex := len(runes) - 1 + + if unicode.IsPunct(runes[lastIndex]) { + return string(runes[:lastIndex]) + } + + return s +} + +type WeatherStruct struct { + Id int `json:"id"` + Main string `json:"main"` + Description string `json:"description"` + Icon string `json:"icon"` +} + +type openWeatherMapAPIResponseStruct struct { + Coord struct { + Lat float64 `json:"lat"` + Lon float64 `json:"lon"` + } `json:"coord"` + Weather []WeatherStruct `json:"weather"` + Base string `json:"base"` + Main struct { + Temp float64 `json:"temp"` + FeelsLike float64 `json:"feels_like"` + TempMin float64 `json:"temp_min"` + TempMax float64 `json:"temp_max"` + Pressure int `json:"pressure"` + Humidity int `json:"humidity"` + } `json:"main"` + Visibility int `json:"visibility"` + Wind struct { + Speed float64 `json:"speed"` + Deg int `json:"deg"` + } `json:"wind"` + Clouds struct { + All int `json:"all"` + } `json:"clouds"` + DT int `json:"dt"` + Sys struct { + Type int `json:"type"` + Id int `json:"id"` + Country string `json:"country"` + Sunrise int `json:"sunrise"` + Sunset int `json:"sunset"` + } `json:"sys"` + Timezone int `json:"timezone"` + Id int `json:"id"` + Name string `json:"name"` + Cod int `json:"cod"` +} + +type openWeatherMapForecastAPIResponseStruct struct { + Cod string `json:"cod"` + Message int `json:"message"` + Cnt int `json:"cnt"` + List []openWeatherMapAPIResponseStruct `json:"list"` +} + +func weatherParser(speechText string, botLocation string, botUnits string) (string, string, string, string, string, string) { + var specificLocation bool + var apiLocation string + var speechLocation string + var hoursFromNow int + if strings.Contains(speechText, lcztn.GetText(lcztn.STR_WEATHER_IN)) { + splitPhrase := strings.SplitAfter(removeEndPunctuation(speechText), lcztn.GetText(lcztn.STR_WEATHER_IN)) + speechLocation = strings.TrimSpace(splitPhrase[1]) + if os.Getenv("STT_SERVICE") != "whisper.cpp" { + if len(splitPhrase) == 3 { + speechLocation = speechLocation + " " + strings.TrimSpace(splitPhrase[2]) + } else if len(splitPhrase) == 4 { + speechLocation = speechLocation + " " + strings.TrimSpace(splitPhrase[2]) + " " + strings.TrimSpace(splitPhrase[3]) + } else if len(splitPhrase) > 4 { + speechLocation = speechLocation + " " + strings.TrimSpace(splitPhrase[2]) + " " + strings.TrimSpace(splitPhrase[3]) + } + splitLocation := strings.Split(speechLocation, " ") + if len(splitLocation) == 2 { + speechLocation = splitLocation[0] + ", " + splitLocation[1] + } else if len(splitLocation) == 3 { + speechLocation = splitLocation[0] + " " + splitLocation[1] + ", " + splitLocation[2] + } + } + logger.Println("Location parsed from speech: " + "`" + speechLocation + "`") + specificLocation = true + } else { + logger.Println("No location parsed from speech") + specificLocation = false + } + hoursFromNow = 0 + hours, _, _ := time.Now().Clock() + if strings.Contains(speechText, lcztn.GetText(lcztn.STR_WEATHER_THIS_AFTERNOON)) { + if hours < 14 { + hoursFromNow = 14 - hours + } + } else if strings.Contains(speechText, lcztn.GetText(lcztn.STR_WEATHER_TONIGHT)) { + if hours < 20 { + hoursFromNow = 20 - hours + } + } else if strings.Contains(speechText, lcztn.GetText(lcztn.STR_WEATHER_THE_DAY_AFTER_TOMORROW)) { + hoursFromNow = 24 - hours + 24 + 9 + } else if strings.Contains(speechText, lcztn.GetText(lcztn.STR_WEATHER_FORECAST)) || + strings.Contains(speechText, lcztn.GetText(lcztn.STR_WEATHER_TOMORROW)) { + hoursFromNow = 24 - hours + 9 + } + logger.Println("Looking for forecast " + strconv.Itoa(hoursFromNow) + " hours from now...") + + if specificLocation { + apiLocation = speechLocation + } else { + apiLocation = botLocation + } + // call to weather API + condition, is_forecast, local_datetime, speakable_location_string, temperature, temperature_unit := getWeather(apiLocation, botUnits, hoursFromNow) + return condition, is_forecast, local_datetime, speakable_location_string, temperature, temperature_unit +} diff --git a/chipper/pkg/wirepod/ttr/weather_utils_get.go b/chipper/pkg/wirepod/ttr/weather_utils_get.go new file mode 100644 index 0000000..331245b --- /dev/null +++ b/chipper/pkg/wirepod/ttr/weather_utils_get.go @@ -0,0 +1,230 @@ +package wirepod_ttr + +import ( + "encoding/json" + "fmt" + "io" + "math" + "net/http" + "net/url" + "os" + "runtime" + "strconv" + "time" + + "github.com/kercre123/wire-pod/chipper/pkg/logger" + "github.com/kercre123/wire-pod/chipper/pkg/vars" +) + +func getWeather(location string, botUnits string, hoursFromNow int) (string, string, string, string, string, string) { + var weatherEnabled bool + var condition string + var is_forecast string + var local_datetime string + var speakable_location_string string + var temperature string + var temperature_unit string + weatherAPIEnabled := vars.APIConfig.Weather.Enable + weatherAPIKey := vars.APIConfig.Weather.Key + weatherAPIUnit := vars.APIConfig.Weather.Unit + weatherAPIProvider := vars.APIConfig.Weather.Provider + if weatherAPIEnabled && weatherAPIKey != "" { + weatherEnabled = true + logger.Println("Weather API enabled") + } else { + weatherEnabled = false + logger.Println("Weather API not enabled, using placeholder") + if weatherAPIEnabled && weatherAPIKey == "" { + logger.Println("Weather API enabled, but Weather API key not set") + } + } + if weatherEnabled { + if botUnits != "" { + if botUnits == "F" { + logger.Println("Weather units set to F") + weatherAPIUnit = "F" + } else if botUnits == "C" { + logger.Println("Weather units set to C") + weatherAPIUnit = "C" + } + } else if weatherAPIUnit != "F" && weatherAPIUnit != "C" { + logger.Println("Weather API unit not set, using F") + weatherAPIUnit = "F" + } + } + + if weatherEnabled { + if weatherAPIProvider == "weatherapi.com" { + params := url.Values{} + params.Add("key", weatherAPIKey) + params.Add("q", location) + params.Add("aqi", "no") + url := "http://api.weatherapi.com/v1/current.json" + resp, err := http.PostForm(url, params) + if err != nil { + panic(err) + } + defer resp.Body.Close() + body, _ := io.ReadAll(resp.Body) + weatherResponse := string(body) + var weatherAPICladMap weatherAPICladStruct + mapPath := "" + if runtime.GOOS == "android" || runtime.GOOS == "ios" { + mapPath = vars.AndroidPath + "/static/weather-map.json" + } else { + mapPath = "./weather-map.json" + } + jsonFile, _ := os.ReadFile(mapPath) + json.Unmarshal(jsonFile, &weatherAPICladMap) + var weatherStruct weatherAPIResponseStruct + json.Unmarshal([]byte(weatherResponse), &weatherStruct) + var matchedValue bool + for _, b := range weatherAPICladMap { + if b.APIValue == weatherStruct.Current.Condition.Text { + condition = b.CladType + logger.Println("API Value: " + b.APIValue + ", Clad Type: " + b.CladType) + matchedValue = true + break + } + } + if !matchedValue { + condition = weatherStruct.Current.Condition.Text + } + is_forecast = "false" + local_datetime = weatherStruct.Current.LastUpdated + speakable_location_string = weatherStruct.Location.Name + if weatherAPIUnit == "C" { + temperature = strconv.Itoa(int(weatherStruct.Current.TempC)) + temperature_unit = "C" + } else { + temperature = strconv.Itoa(int(weatherStruct.Current.TempF)) + temperature_unit = "F" + } + } else if weatherAPIProvider == "openweathermap.org" { + // First use geocoding api to convert location into coordinates + // E.G. http://api.openweathermap.org/geo/1.0/direct?q={city name},{state code},{country code}&limit={limit}&appid={API key} + url := "http://api.openweathermap.org/geo/1.0/direct?q=" + url.QueryEscape(location) + "&limit=1&appid=" + weatherAPIKey + resp, err := http.Get(url) + if err != nil { + logger.Println(err) + } + defer resp.Body.Close() + body, _ := io.ReadAll(resp.Body) + geoCodingResponse := string(body) + + var geoCodingInfoStruct []openWeatherMapAPIGeoCodingStruct + + err = json.Unmarshal([]byte(geoCodingResponse), &geoCodingInfoStruct) + if err != nil { + logger.Println(err) + logger.Println("Geolocation API error: " + geoCodingResponse) + } + if len(geoCodingInfoStruct) == 0 { + logger.Println("Geo provided no response.") + condition = "undefined" + is_forecast = "false" + local_datetime = "test" // preferably local time in UTC ISO 8601 format ("2022-06-15 12:21:22.123") + speakable_location_string = location // preferably the processed location + temperature = "120" + temperature_unit = "C" + return condition, is_forecast, local_datetime, speakable_location_string, temperature, temperature_unit + } + Lat := fmt.Sprintf("%f", geoCodingInfoStruct[0].Lat) + Lon := fmt.Sprintf("%f", geoCodingInfoStruct[0].Lon) + + logger.Println("Lat: " + Lat + ", Lon: " + Lon) + logger.Println("Name: " + geoCodingInfoStruct[0].Name) + logger.Println("Country: " + geoCodingInfoStruct[0].Country) + + // Now that we have Lat and Lon, let's query the weather + units := "metric" + if weatherAPIUnit == "F" { + units = "imperial" + } + if hoursFromNow == 0 { + url = "https://api.openweathermap.org/data/2.5/weather?lat=" + Lat + "&lon=" + Lon + "&units=" + units + "&appid=" + weatherAPIKey + } else { + url = "https://api.openweathermap.org/data/2.5/forecast?lat=" + Lat + "&lon=" + Lon + "&units=" + units + "&appid=" + weatherAPIKey + } + resp, err = http.Get(url) + if err != nil { + panic(err) + } + defer resp.Body.Close() + body, _ = io.ReadAll(resp.Body) + weatherResponse := string(body) + var openWeatherMapAPIResponse openWeatherMapAPIResponseStruct + + if hoursFromNow > 0 { + // Forecast request: free API results are returned in 3 hours slots + var openWeatherMapForecastAPIResponse openWeatherMapForecastAPIResponseStruct + err = json.Unmarshal([]byte(weatherResponse), &openWeatherMapForecastAPIResponse) + openWeatherMapAPIResponse = openWeatherMapForecastAPIResponse.List[hoursFromNow/3] + } else { + // Current weather request + err = json.Unmarshal([]byte(weatherResponse), &openWeatherMapAPIResponse) + } + + if err != nil { + panic(err) + } + + conditionCode := openWeatherMapAPIResponse.Weather[0].Id + logger.Println(conditionCode) + + if conditionCode < 300 { + // Thunderstorm + condition = "Thunderstorms" + } else if conditionCode < 400 { + // Drizzle + condition = "Rain" + } else if conditionCode < 600 { + // Rain + condition = "Rain" + } else if conditionCode < 700 { + // Snow + condition = "Snow" + } else if conditionCode < 800 { + // Athmosphere + if openWeatherMapAPIResponse.Weather[0].Main == "Mist" || + openWeatherMapAPIResponse.Weather[0].Main == "Fog" { + condition = "Rain" + } else { + condition = "Windy" + } + } else if conditionCode == 800 { + // Clear + if openWeatherMapAPIResponse.DT < openWeatherMapAPIResponse.Sys.Sunset { + condition = "Sunny" + } else { + condition = "Stars" + } + } else if conditionCode < 900 { + // Cloud + condition = "Cloudy" + } else { + condition = openWeatherMapAPIResponse.Weather[0].Main + } + + is_forecast = "false" + t := time.Unix(int64(openWeatherMapAPIResponse.DT), 0) + local_datetime = t.Format(time.RFC850) + logger.Println(local_datetime) + speakable_location_string = openWeatherMapAPIResponse.Name + temperature = fmt.Sprintf("%f", math.Round(openWeatherMapAPIResponse.Main.Temp)) + if weatherAPIUnit == "C" { + temperature_unit = "C" + } else { + temperature_unit = "F" + } + } + } else { + condition = "Snow" + is_forecast = "false" + local_datetime = "test" // preferably local time in UTC ISO 8601 format ("2022-06-15 12:21:22.123") + speakable_location_string = location // preferably the processed location + temperature = "120" + temperature_unit = "C" + } + return condition, is_forecast, local_datetime, speakable_location_string, temperature, temperature_unit +} diff --git a/chipper/pkg/wirepod/ttr/words2num.go b/chipper/pkg/wirepod/ttr/words2num.go new file mode 100644 index 0000000..4746ae2 --- /dev/null +++ b/chipper/pkg/wirepod/ttr/words2num.go @@ -0,0 +1,173 @@ +package wirepod_ttr + +import ( + "fmt" + "os" + "regexp" + "strconv" + "strings" +) + +// This file contains words2num. It is given the spoken text and returns a string which contains the true number. + +func whisperSpeechtoNum(input string) string { + // whisper returns actual numbers in its response + // ex. "set a timer for 10 minutes and 11 seconds" + totalSeconds := 0 + + minutePattern := regexp.MustCompile(`(\d+)\s*minute`) + secondPattern := regexp.MustCompile(`(\d+)\s*second`) + + minutesMatches := minutePattern.FindStringSubmatch(input) + secondsMatches := secondPattern.FindStringSubmatch(input) + + if len(minutesMatches) > 1 { + minutes, err := strconv.Atoi(minutesMatches[1]) + if err == nil { + totalSeconds += minutes * 60 + } + } + if len(secondsMatches) > 1 { + seconds, err := strconv.Atoi(secondsMatches[1]) + if err == nil { + totalSeconds += seconds + } + } + + return strconv.Itoa(totalSeconds) +} + +var textToNumber = map[string]int{ + "zero": 0, "one": 1, "two": 2, "three": 3, "four": 4, "five": 5, + "six": 6, "seven": 7, "eight": 8, "nine": 9, "ten": 10, + "eleven": 11, "twelve": 12, "thirteen": 13, "fourteen": 14, "fifteen": 15, + "sixteen": 16, "seventeen": 17, "eighteen": 18, "nineteen": 19, "twenty": 20, + "thirty": 30, "forty": 40, "fifty": 50, "sixty": 60, +} + + +var recurringDecimalPattern = regexp.MustCompile(`point\s+([0-9]+)\s+recurring`) // Match "point x recurring" +var recurringFractionPattern = regexp.MustCompile(`\\frac{(\d+)}{(\d+)}`) // Updated regex pattern +var decimalToFractionPattern = regexp.MustCompile(`\b(point|decimal)\s+(\d+)\s+recurring\b`) + +func words2num(input string) string { + // Match LaTeX fractions first + if strings.Contains(input, "\\frac") { + return convertLaTeXFractionToDecimal(input) + } + + // Match recurring decimals asking for conversion + match := recurringDecimalPattern.FindStringSubmatch(input) + if match != nil { + recurringPart := match[1] + return convertDecimalToFraction(recurringPart) // Call the conversion function. + } + + // Match decimals asking for fraction conversion + decimalMatch := decimalToFractionPattern.FindStringSubmatch(input) + if decimalMatch != nil { + recurringPart := decimalMatch[2] // the part after "point" + fractionEquiv := convertDecimalToFraction(recurringPart) + return fractionEquiv + } + + // Match regular fractions (if input was non-LaTeX format) + matchFraction := recurringFractionPattern.FindStringSubmatch(input) + if matchFraction != nil { + numerator := matchFraction[1] + denominator := matchFraction[2] + fractionDecimal := convertFractionToDecimal(numerator, denominator) + return fractionDecimal + } + + // Check if contains number for special handling + containsNum, _ := regexp.MatchString(`\b\d+\b`, input) + if os.Getenv("STT_SERVICE") == "whisper.cpp" && containsNum { + return whisperSpeechtoNum(input) + } + + totalSeconds := 0 + input = strings.ToLower(input) + + if strings.Contains(input, "one hour") || strings.Contains(input, "an hour") { + return "3600" + } + + timePattern := regexp.MustCompile(`(\d+|\w+(?:-\w+)?)\s*(minute|second|hour)s?`) + + matches := timePattern.FindAllStringSubmatch(input, -1) + for _, match := range matches { + unit := match[2] + number := match[1] + + value, err := strconv.Atoi(number) + if err != nil { + value = mapTextToNumber(number) + } + + switch unit { + case "minute": + totalSeconds += value * 60 + case "second": + totalSeconds += value + case "hour": + totalSeconds += value * 3600 + } + } + + return strconv.Itoa(totalSeconds) +} + +func mapTextToNumber(text string) int { + if val, ok := textToNumber[text]; ok { + return val + } + parts := strings.Split(text, "-") + sum := 0 + for _, part := range parts { + if val, ok := textToNumber[part]; ok { + sum += val + } + } + return sum +} + +func convertRecurringToDecimal(wholePart, recurringPart string) string { + // Assuming wholePart is the integer part and recurringPart is the recurring decimal part + numRecurrences := strings.Repeat(recurringPart, 9) // Adjust as needed for precision + decimalValue := wholePart + "." + numRecurrences + + return decimalValue +} + +func convertFractionToDecimal(numerator, denominator string) string { + num, err1 := strconv.Atoi(numerator) + denom, err2 := strconv.Atoi(denominator) + + if err1 != nil || err2 != nil || denom == 0 { + return "undefined" // Catch any errors, including division by zero + } + + decimalValue := float64(num) / float64(denom) + return fmt.Sprintf("%.6f", decimalValue) // Providing a formatted output for the decimal +} + +func convertDecimalToFraction(recurringPart string) string { + num := recurringPart + // Convert point x recurring to fraction format + // Basic process could return either "3/9" or its simplified "1/3" + fraction := fmt.Sprintf("%s/3", num) // This is a direct mapping for "0.3 recurring" + + // Perform additional logic to simplify if necessary + return fraction +} + +func convertLaTeXFractionToDecimal(input string) string { + matches := recurringFractionPattern.FindStringSubmatch(input) + if matches != nil { + numerator := matches[1] + denominator := matches[2] + return convertFractionToDecimal(numerator, denominator) + } + return input // If no match, return original input +} diff --git a/chipper/plugins/placeholder b/chipper/plugins/placeholder new file mode 100644 index 0000000..e69de29 diff --git a/chipper/plugins/sdkTest/sdkTest.go b/chipper/plugins/sdkTest/sdkTest.go new file mode 100644 index 0000000..c1a834d --- /dev/null +++ b/chipper/plugins/sdkTest/sdkTest.go @@ -0,0 +1,112 @@ +package main + +import ( + "context" + "log" + + "github.com/fforchino/vector-go-sdk/pkg/vector" + "github.com/fforchino/vector-go-sdk/pkg/vectorpb" + "github.com/kercre123/wire-pod/chipper/pkg/logger" +) + +// test of SDK implementation + +var Utterances = []string{"hello world"} +var Name = "SDK Plugin Test" + +func behave(ctx context.Context, robot *vector.Vector, start chan bool, stop chan bool) { + controlRequest := &vectorpb.BehaviorControlRequest{ + RequestType: &vectorpb.BehaviorControlRequest_ControlRequest{ + ControlRequest: &vectorpb.ControlRequest{ + Priority: vectorpb.ControlRequest_OVERRIDE_BEHAVIORS, + }, + }, + } + go func() { + + go func() { + // * begin - modified from official vector-go-sdk + r, err := robot.Conn.BehaviorControl( + ctx, + ) + if err != nil { + log.Println(err) + return + } + + if err := r.Send(controlRequest); err != nil { + log.Println(err) + return + } + + for { + ctrlresp, err := r.Recv() + if err != nil { + log.Println(err) + return + } + if ctrlresp.GetControlGrantedResponse() != nil { + start <- true + break + } + } + + for { + select { + case <-stop: + logger.Println("KGSim: releasing behavior control (interrupt)") + if err := r.Send( + &vectorpb.BehaviorControlRequest{ + RequestType: &vectorpb.BehaviorControlRequest_ControlRelease{ + ControlRelease: &vectorpb.ControlRelease{}, + }, + }, + ); err != nil { + log.Println(err) + return + } + return + default: + continue + } + } + // * end - modified from official vector-go-sdk + }() + }() +} + +func Action(transcribedText string, botSerial string, guid string, target string) (string, string) { + logger.Println("hello world plugin test") + phrase := "hello world" + robot, err := vector.New( + vector.WithSerialNo(botSerial), + vector.WithTarget(target), + vector.WithToken(guid), + ) + if err != nil { + logger.Println(err) + return "intent_imperative_praise", "" + } + ctx := context.Background() + start := make(chan bool) + stop := make(chan bool) + go func() { + behave(ctx, robot, start, stop) + }() + + for { + select { + case <-start: + robot.Conn.SayText( + ctx, + &vectorpb.SayTextRequest{ + Text: phrase, + UseVectorVoice: true, + DurationScalar: 1, + }, + ) + stop <- true + return "intent_imperative_praise", "" + } + } +} diff --git a/chipper/plugins/whatdate/whatdate.go b/chipper/plugins/whatdate/whatdate.go new file mode 100644 index 0000000..c218864 --- /dev/null +++ b/chipper/plugins/whatdate/whatdate.go @@ -0,0 +1,31 @@ +package main + +import ( + "strconv" + "strings" + "time" +) + +var Utterances = []string{"what day is it", "date today", "date", "what days it"} +var Name = "Correct Date" + +func stripOutTriggerWords(s string) string { + result := strings.Replace(s, "simon says", "", 1) + result = strings.Replace(result, "repeat", "", 1) + return result +} + +func CountWords(s string) int { + return len(strings.Fields(s)) +} + +//Example go plugin that give back the correct date + +func Action(transcribedText string, botSerial string, guid string, target string) (string, string) { + year, month, day := time.Now().Date() + yearSring := strconv.FormatInt(int64(year), 10) + + VECTOR_PHRASE := "The date is " + month.String() + " " + strconv.FormatInt(int64(day), 10) + ", " + yearSring + " " + + return "intent_imperative_praise", VECTOR_PHRASE +} diff --git a/chipper/session-certs/placeholder b/chipper/session-certs/placeholder new file mode 100644 index 0000000..e69de29 diff --git a/chipper/start.sh b/chipper/start.sh new file mode 100644 index 0000000..861d9ca --- /dev/null +++ b/chipper/start.sh @@ -0,0 +1,112 @@ +#!/bin/bash + +UNAME=$(uname -a) +COMMIT_HASH="$(git rev-parse --short HEAD)" + +if [[ $EUID -ne 0 ]]; then + echo "This script must be run as root. sudo ./start.sh" + exit 1 +fi + +if [[ -d ./chipper ]]; then + cd chipper +fi + +#if [[ ! -f ./chipper ]]; then +# if [[ -f ./go.mod ]]; then +# echo "You need to build chipper first. This can be done with the setup.sh script." +# else +# echo "You must be in the chipper directory." +# fi +# exit 0 +#fi + +if [[ ! -f ./source.sh ]]; then + echo "You need to make a source.sh file. This can be done with the setup.sh script." + exit 0 +fi + +source source.sh + +# set go tags +export GOTAGS="nolibopusfile" + +if [[ ${USE_INBUILT_BLE} == "true" ]]; then + GOTAGS="${GOTAGS},inbuiltble" +fi + +export GOLDFLAGS="-X 'github.com/kercre123/wire-pod/chipper/pkg/vars.CommitSHA=${COMMIT_HASH}'" + +#./chipper +if [[ ${STT_SERVICE} == "leopard" ]]; then + if [[ -f ./chipper ]]; then + ./chipper + else + /usr/local/go/bin/go run -tags $GOTAGS -ldflags="${GOLDFLAGS}" cmd/leopard/main.go + fi + elif [[ ${STT_SERVICE} == "rhino" ]]; then + if [[ -f ./chipper ]]; then + ./chipper + else + /usr/local/go/bin/go run -tags $GOTAGS -ldflags="${GOLDFLAGS}" cmd/experimental/rhino/main.go + fi + elif [[ ${STT_SERVICE} == "houndify" ]]; then + if [[ -f ./chipper ]]; then + ./chipper + else + /usr/local/go/bin/go run -tags $GOTAGS -ldflags="${GOLDFLAGS}" cmd/experimental/houndify/main.go + fi + elif [[ ${STT_SERVICE} == "whisper" ]]; then + if [[ -f ./chipper ]]; then + ./chipper + else + /usr/local/go/bin/go run -tags $GOTAGS -ldflags="${GOLDFLAGS}" cmd/experimental/whisper/main.go + fi + elif [[ ${STT_SERVICE} == "whisper.cpp" ]]; then + if [[ -f ./chipper ]]; then + export C_INCLUDE_PATH="../whisper.cpp" + export LIBRARY_PATH="../whisper.cpp" + export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$(pwd)/../whisper.cpp:$(pwd)/../whisper.cpp/build:$(pwd)/../whisper.cpp/build/src" + export CGO_LDFLAGS="-L$(pwd)/../whisper.cpp" + export CGO_CFLAGS="-I$(pwd)/../whisper.cpp" + ./chipper + else + export C_INCLUDE_PATH="../whisper.cpp" + export LIBRARY_PATH="../whisper.cpp" + export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$(pwd)/../whisper.cpp:$(pwd)/../whisper.cpp/build" + export CGO_LDFLAGS="-L$(pwd)/../whisper.cpp -L$(pwd)/../whisper.cpp/build -L$(pwd)/../whisper.cpp/build/src -L$(pwd)/../whisper.cpp/build/ggml/src" + export CGO_CFLAGS="-I$(pwd)/../whisper.cpp -I$(pwd)/../whisper.cpp/include -I$(pwd)/../whisper.cpp/ggml/include" + if [[ ${UNAME} == *"Darwin"* ]]; then + export GGML_METAL_PATH_RESOURCES="../whisper.cpp" + /usr/local/go/bin/go run -tags $GOTAGS -ldflags "-extldflags '-framework Foundation -framework Metal -framework MetalKit'" cmd/experimental/whisper.cpp/main.go + else + /usr/local/go/bin/go run -tags $GOTAGS -ldflags="${GOLDFLAGS}" cmd/experimental/whisper.cpp/main.go + fi + fi + elif [[ ${STT_SERVICE} == "vosk" ]]; then + if [[ -f ./chipper ]]; then + export CGO_ENABLED=1 + export CGO_CFLAGS="-I/root/.vosk/libvosk" + export CGO_LDFLAGS="-L /root/.vosk/libvosk -lvosk -ldl -lpthread" + export LD_LIBRARY_PATH="/root/.vosk/libvosk:$LD_LIBRARY_PATH" + ./chipper + else + export CGO_ENABLED=1 + export CGO_CFLAGS="-I$HOME/.vosk/libvosk -I/root/.vosk/libvosk" + export CGO_LDFLAGS="-L$HOME/.vosk/libvosk -L/root/.vosk/libvosk -lvosk -ldl -lpthread" + export LD_LIBRARY_PATH="/root/.vosk/libvosk:$HOME/.vosk/libvosk:$LD_LIBRARY_PATH" + /usr/local/go/bin/go run -tags $GOTAGS -ldflags="${GOLDFLAGS}" -exec "env DYLD_LIBRARY_PATH=$HOME/.vosk/libvosk" cmd/vosk/main.go + fi +else + if [[ -f ./chipper ]]; then + export CGO_LDFLAGS="-L/root/.coqui/" + export CGO_CXXFLAGS="-I/root/.coqui/" + export LD_LIBRARY_PATH="/root/.coqui/:$LD_LIBRARY_PATH" + ./chipper + else + export CGO_LDFLAGS="-L$HOME/.coqui/" + export CGO_CXXFLAGS="-I$HOME/.coqui/" + export LD_LIBRARY_PATH="$HOME/.coqui/:$LD_LIBRARY_PATH" + /usr/local/go/bin/go run -tags $GOTAGS -ldflags="${GOLDFLAGS}" cmd/coqui/main.go + fi +fi diff --git a/chipper/stttest.pcm b/chipper/stttest.pcm new file mode 100644 index 0000000..e69a39a Binary files /dev/null and b/chipper/stttest.pcm differ diff --git a/chipper/weather-map.json b/chipper/weather-map.json new file mode 100644 index 0000000..7b869fc --- /dev/null +++ b/chipper/weather-map.json @@ -0,0 +1,358 @@ +[ + { + "APIValue": "Tornado", + "CladType": "Windy" + }, + { + "APIValue": "Tropical Storm", + "CladType": "Rain" + }, + { + "APIValue": "Hurricane", + "CladType": "Windy" + }, + { + "APIValue": "Strong Storms", + "CladType": "Rain" + }, + { + "APIValue": "Thunder and Hail", + "CladType": "Thunderstorms" + }, + { + "APIValue": "Rain to Snow Showers", + "CladType": "Rain" + }, + { + "APIValue": "Rain / Sleet", + "CladType": "Rain" + }, + { + "APIValue": "Wintry Mix Snow / Sleet", + "CladType": "Snow" + }, + { + "APIValue": "Freezing Drizzle", + "CladType": "Rain" + }, + { + "APIValue": "Drizzle", + "CladType": "Rain" + }, + { + "APIValue": "Freezing Rain", + "CladType": "Rain" + }, + { + "APIValue": "Light Rain", + "CladType": "Rain" + }, + { + "APIValue": "Rain", + "CladType": "Rain" + }, + { + "APIValue": "Scattered Flurries", + "CladType": "Snow" + }, + { + "APIValue": "Light Snow", + "CladType": "Snow" + }, + { + "APIValue": "Blowing / Drifting Snow", + "CladType": "Snow" + }, + { + "APIValue": "Snow", + "CladType": "Snow" + }, + { + "APIValue": "Hail", + "CladType": "Rain" + }, + { + "APIValue": "Sleet", + "CladType": "Rain" + }, + { + "APIValue": "Blowing Dust / Sandstorm", + "CladType": "Windy" + }, + { + "APIValue": "Foggy", + "CladType": "Cloudy" + }, + { + "APIValue": "Haze / Windy", + "CladType": "Windy" + }, + { + "APIValue": "Smoke / Windy", + "CladType": "Windy" + }, + { + "APIValue": "Breezy", + "CladType": "Windy" + }, + { + "APIValue": "Blowing Spray / Windy", + "CladType": "Windy" + }, + { + "APIValue": "Frigid / Ice Crystals", + "CladType": "Sunny" + }, + { + "APIValue": "Cloudy", + "CladType": "Cloudy" + }, + { + "APIValue": "Mostly Cloudy", + "CladType": "Cloudy" + }, + { + "APIValue": "Mist", + "CladType": "Cloudy" + }, + { + "APIValue": "Partly Cloudy", + "CladType": "Cloudy" + }, + { + "APIValue": "Clear", + "CladType": "Stars" + }, + { + "APIValue": "Sunny", + "CladType": "Sunny" + }, + { + "APIValue": "Fair / Mostly Clear", + "CladType": "Sunny" + }, + { + "APIValue": "Fair / Mostly Sunny", + "CladType": "Sunny" + }, + { + "APIValue": "Mixed Rain & Hail", + "CladType": "Rain" + }, + { + "APIValue": "Hot", + "CladType": "Sunny" + }, + { + "APIValue": "Isolated Thunderstorms", + "CladType": "Thunderstorms" + }, + { + "APIValue": "Thunderstorms", + "CladType": "Thunderstorms" + }, + { + "APIValue": "Heavy Rain", + "CladType": "Rain" + }, + { + "APIValue": "Heavy Snow", + "CladType": "Snow" + }, + { + "APIValue": "Blizzard", + "CladType": "Snow" + }, + { + "APIValue": "Not Available (N/A)", + "CladType": "Count" + }, + { + "APIValue": "Scattered Showers", + "CladType": "Rain" + }, + { + "APIValue": "Scattered Snow Showers", + "CladType": "Snow" + }, + { + "APIValue": "Scattered Thunderstorms", + "CladType": "Thunderstorms" + }, + { + "APIValue": "Patchy light rain with thunder", + "CladType": "Thunderstorms" + }, + { + "APIValue": "Moderate or heavy rain with thunder", + "CladType": "Thunderstorms" + }, + { + "APIValue": "Patchy light snow with thunder", + "CladType": "Thunderstorms" + }, + { + "APIValue": "Moderate or heavy snow with thunder", + "CladType": "Thunderstorms" + }, + { + "APIValue": "Moderate or heavy showers of ice pellets", + "CladType": "Rain" + }, + { + "APIValue": "Light showers of ice pellets", + "CladType": "Rain" + }, + { + "APIValue": "Light snow showers", + "CladType": "Snow" + }, + { + "APIValue": "Moderate or heavy snow showers", + "CladType": "Snow" + }, + { + "APIValue": "Light sleet showers", + "CladType": "Rain" + }, + { + "APIValue": "Torrential rain shower", + "CladType": "Rain" + }, + { + "APIValue": "Moderate or heavy rain shower", + "CladType": "Rain" + }, + { + "APIValue": "Light rain shower", + "CladType": "Rain" + }, + { + "APIValue": "Ice pellets", + "CladType": "Rain" + }, + { + "APIValue": "Heavy snow", + "CladType": "Snow" + }, + { + "APIValue": "Patchy heavy snow", + "CladType": "Snow" + }, + { + "APIValue": "Moderate snow", + "CladType": "Snow" + }, + { + "APIValue": "Patchy moderate snow", + "CladType": "Snow" + }, + { + "APIValue": "Light snow", + "CladType": "Snow" + }, + { + "APIValue": "Patchy light snow", + "CladType": "Snow" + }, + { + "APIValue": "Moderate or heavy sleet", + "CladType": "Rain" + }, + { + "APIValue": "Light sleet", + "CladType": "Rain" + }, + { + "APIValue": "Moderate or heavy freezing rain", + "CladType": "Rain" + }, + { + "APIValue": "Light freezing rain", + "CladType": "Rain" + }, + { + "APIValue": "Heavy rain", + "CladType": "Rain" + }, + { + "APIValue": "Heavy rain at times", + "CladType": "Rain" + }, + { + "APIValue": "Moderate rain", + "CladType": "Rain" + }, + { + "APIValue": "Moderate rain at times", + "CladType": "Rain" + }, + { + "APIValue": "Light rain", + "CladType": "Rain" + }, + { + "APIValue": "Patchy light rain", + "CladType": "Rain" + }, + { + "APIValue": "Heavy freezing drizzle", + "CladType": "Rain" + }, + { + "APIValue": "Freezing drizzle", + "CladType": "Rain" + }, + { + "APIValue": "Light drizzle", + "CladType": "Rain" + }, + { + "APIValue": "Patchy light drizzle", + "CladType": "Rain" + }, + { + "APIValue": "Freezing fog", + "CladType": "Cloudy" + }, + { + "APIValue": "Fog", + "CladType": "Cloudy" + }, + { + "APIValue": "Moderate snow", + "CladType": "Snow" + }, + { + "APIValue": "Blowing snow", + "CladType": "Snow" + }, + { + "APIValue": "Thundery outbreaks possible", + "CladType": "Thunderstorms" + }, + { + "APIValue": "Patchy freezing drizzle possible", + "CladType": "Rain" + }, + { + "APIValue": "Patchy sleet possible", + "CladType": "Rain" + }, + { + "APIValue": "Patchy rain possible", + "CladType": "Rain" + }, + { + "APIValue": "Patchy snow possible", + "CladType": "Snow" + }, + { + "APIValue": "Overcast", + "CladType": "Cloudy" + }, + { + "APIValue": "Partly cloudy", + "CladType": "Cloudy" + } +] diff --git a/chipper/webroot/assets/cloudface.gif b/chipper/webroot/assets/cloudface.gif new file mode 100644 index 0000000..42c1adb Binary files /dev/null and b/chipper/webroot/assets/cloudface.gif differ diff --git a/chipper/webroot/assets/expandface.gif b/chipper/webroot/assets/expandface.gif new file mode 100644 index 0000000..f72371c Binary files /dev/null and b/chipper/webroot/assets/expandface.gif differ diff --git a/chipper/webroot/assets/face.gif b/chipper/webroot/assets/face.gif new file mode 100644 index 0000000..a7c6c69 Binary files /dev/null and b/chipper/webroot/assets/face.gif differ diff --git a/chipper/webroot/assets/faceblank.gif b/chipper/webroot/assets/faceblank.gif new file mode 100644 index 0000000..e7fc46d Binary files /dev/null and b/chipper/webroot/assets/faceblank.gif differ diff --git a/chipper/webroot/assets/faceblank.png b/chipper/webroot/assets/faceblank.png new file mode 100644 index 0000000..102a1cd Binary files /dev/null and b/chipper/webroot/assets/faceblank.png differ diff --git a/chipper/webroot/assets/facefireworks.gif b/chipper/webroot/assets/facefireworks.gif new file mode 100644 index 0000000..3815aa8 Binary files /dev/null and b/chipper/webroot/assets/facefireworks.gif differ diff --git a/chipper/webroot/assets/facegaze.gif b/chipper/webroot/assets/facegaze.gif new file mode 100644 index 0000000..e1a4891 Binary files /dev/null and b/chipper/webroot/assets/facegaze.gif differ diff --git a/chipper/webroot/assets/homeface.png b/chipper/webroot/assets/homeface.png new file mode 100644 index 0000000..3d7822f Binary files /dev/null and b/chipper/webroot/assets/homeface.png differ diff --git a/chipper/webroot/assets/webface.gif b/chipper/webroot/assets/webface.gif new file mode 100644 index 0000000..c7b6e1f Binary files /dev/null and b/chipper/webroot/assets/webface.gif differ diff --git a/chipper/webroot/assets/wififace.gif b/chipper/webroot/assets/wififace.gif new file mode 100644 index 0000000..fa3cc51 Binary files /dev/null and b/chipper/webroot/assets/wififace.gif differ diff --git a/chipper/webroot/css/DroidSans.woff b/chipper/webroot/css/DroidSans.woff new file mode 100644 index 0000000..c38bb13 Binary files /dev/null and b/chipper/webroot/css/DroidSans.woff differ diff --git a/chipper/webroot/css/PxPlus_IBM_VGA_8x16.ttf b/chipper/webroot/css/PxPlus_IBM_VGA_8x16.ttf new file mode 100644 index 0000000..3bf32d0 Binary files /dev/null and b/chipper/webroot/css/PxPlus_IBM_VGA_8x16.ttf differ diff --git a/chipper/webroot/css/h1Font.ttf b/chipper/webroot/css/h1Font.ttf new file mode 100644 index 0000000..6338c19 Binary files /dev/null and b/chipper/webroot/css/h1Font.ttf differ diff --git a/chipper/webroot/css/style.css b/chipper/webroot/css/style.css new file mode 100644 index 0000000..b6e08a0 --- /dev/null +++ b/chipper/webroot/css/style.css @@ -0,0 +1,522 @@ +@import url("wing.css"); + +@font-face{ + font-family: 'DroidSans'; + src: url('DroidSans.woff') format('woff'); + /*font license Apache V2.00: https://www.fontsquirrel.com/license/droid-sans*/ +} + +@font-face{ + font-family: 'IBMVGA'; + src: url('PxPlus_IBM_VGA_8x16.ttf') format('truetype'); + /* VileR's old school font pack - int10h.org */ +} + +@font-face{ + font-family: 'H1Weird'; + src: url('h1Font.ttf') format('truetype'); +} + +/* { + "TIP_OVER_TEAL" : + #00d16d + { "Hue" : 0.42, "Saturation" : 1.00 }, + "OVERFIT_ORANGE" : + { "Hue" : 0.05, "Saturation" : 0.95 }, + "UNCANNY_YELLOW" : + { "Hue" : 0.11, "Saturation" : 1.00 }, + "NON_LINEAR_LIME" : + { "Hue" : 0.21, "Saturation" : 1.00 }, + "SINGULARITY_SAPPHIRE" : + { "Hue" : 0.57, "Saturation" : 1.00 }, + "FALSE_POSITIVE_PURPLE" : + #e237e6 + { "Hue" : 0.83, "Saturation" : 0.76 }, + "CONFUSION_MATRIX_GREEN" : + { "Hue" : 0.30, "Saturation" : 1.00 } +} */ + +:root { + --bg-color: #1e1e1e; + --body-font-family: 'DroidSans', sans-serif; + --bg-color-alt: rgba(196,196,196,1); /* light gray #C4C4C4*/ + --button-color: rgba(75,75,75,1); /* dark gray #4B4B4B */ + --button-color-alt: rgba(164,164,164,1); /* light gray #A4A4A4 */ + --fg-color: #00ff80; /* light green 33ED6D */ + --medium-battery: #ced100; /* bright yellow */ + --low-battery: #ff0000; /* bright red */ + --fg-color-alt: rgba(51,237,109,.05); /* light green 33ED6D */ + --text-color: rgba(255,255,255,1); /* white #FFFFFF */ + --text-color-alt: rgba(34,34,34,1); /* black #000000 */ +} + +.selectedicon { + color: var(--fg-color); +} + +.notselectedicon { + color: var(--bg-color-alt); +} + +.dropdown { + position: relative; + display: inline-block; +} + +button:disabled, button[type="submit"]:disabled { + background: var(--bg-color); + color : var(--bg-color-alt); +} +button, button[type="submit"] { + font-family: var(--body-font-family); + font-size: 0.8em; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + width: 170px; + border: 2px solid var(--fg-color); + background-color: var (--button-color); + color: var(--text-color); + transition: border-color 0.3s, background-color 0.3s; +} + +button:hover, button[type="submit"]:disabled { + border-color: var(--fg-color-alt); + background-color: var(--fg-color); + color: var(--text-color-alt); +} + +.dropdown-content { + display: none; + position: absolute; + background-color: var(--bg-color); + min-width: 160px; + box-shadow: 0px 8px 16px 0px var(--fg-color-alt); + padding: 12px 16px; + z-index: 1; +} + +.dropdown:hover .dropdown-content { + display: block; +} + +input[type="text"], input[type="file"], select { + background-color: #2d2d2d; + color: #ffffff; + font-family: var(--body-font-family); + font-size: 1.0em; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + text-align: center; + border: 2px solid var(--fg-color); + box-sizing: border-box; + transition: border-color 0.3s; +} + +input[type="text"]:hover, input[type="file"]:hover, select:hover, +input[type="text"]:focus, input[type="file"]:focus, select:focus { + border-color: white; + border: 2px solid; + box-sizing: border-box; + transition: border-color 0.3s; +} + +input[type='text']:disabled { + cursor: not-allowed; +} + +.small-hr { + border: 1px solid #3e3e3e; + width: 30%; +} + +.log-hr { + margin-bottom: 50px; +} + +input[type="file"] { + width: auto; +} + +/* input[type="text"] { + width: ; +} */ + +select { + width: auto; +} + +body { + background-color: var(--bg-color); + font-family: var(--body-font-family); + font-size: 1.8em; + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + margin: 0; + flex-wrap: wrap; + text-align: center; +} + + +h1, h2, h3, h4, small, p, li, label { + font-family: var(--body-font-family); + color: var(--text-color); +} + +h2, h3 { + margin-top: 0px; + margin-bottom: 0px; +} + +.desc { + color: var(--bg-color-alt); +} + +h1 { + color: var(--fg-color); + font-family: "H1Weird", sans-serif; + font-size: 3em; + font-weight: bold; + letter-spacing: 0em; + text-align: center; +} + +textarea { + background-color: #2d2d2d; + color: #ffffff; + font-family: var(--body-font-family); + font-size: 0.9em; + height: 280px; + border: 2px solid var(--fg-color); + box-sizing: border-box; + transition: border-color 0.3s; +} + +textarea:hover, textarea:focus { + border-color: white; + border: 2px solid; + box-sizing: border-box; + transition: border-color 0.3s; +} + +/* p { + font-size: 1.2em; +} */ + +.content { + display: none; +} + +.wrap { + overflow: hidden !important; + white-space: nowrap !important; +} + +hr { + border: 2px solid #3e3e3e; + /* background-image: linear-gradient(to right, var(--fg-color-alt), var(--fg-color), var(--fg-color-alt)); */ +} + +a { + color:var(--fg-color); +} +a:hover { + text-decoration: none; + color: var(--fg-color); +} +/* +ul { + list-style-type:none; +} */ + +ul, li { + /* list-style-type: none; */ + list-style-position:inside; + margin:0; + padding:0; +} + +.tinput { + width: 315px !important; + background-color: var(--button-color) !important; + color: var(--text-color) !important; + border: 2px solid var(--fg-color); /* accent color */ +} + +.tinputauth { + width: 499px !important; + background-color: var(--button-color) !important; + color: var(--text-color) !important; + border: 2px solid var(--fg-color); /* accent color */ +} + +canvas{ + width:750px !important; + height:400px !important; +} + +/* RADIO BUTTON ######################### */ +input[type='radio'] { + appearance: none; + top: 13.333333px; + right: 50%; + bottom: 0px; + left: 0; + margin-top: 0px; + height: 40px; + width: 40px; + transition: all .2s ease; + background: var(--button-color); + border: none; + cursor: pointer; + outline: none; + position: relative; + z-index: 1000; + border-radius: 50%; + border: 1px solid var(--button-color-alt); +} + +input[type='radio']:checked:after { + width: 40px; + height: 40px; + border-radius: 50%; + background-color: var(--fg-color); + content: ''; + display: inline-block; +} + +input[type='radio']:disabled { + cursor: not-allowed; +} + +input[type='radio']:disabled:after { + background-color: var(--button-color-alt); + cursor: not-allowed; +} + +/* ###################################### */ +#username, #password { + width: 499px !important; + background-color: var(--bg-color-alt) !important; + color: var(--text-color) !important; + border: 2px solid var(--fg-color); /* accent color */ +} + +#botAuth { + color: var(--text-color); +} + +#content { + width: 950px !important; + /* word-break:break-all !important; */ + /* word-wrap: break-all !important; */ + white-space:normal !important; +} +#outer { + width: 100%; + display: flex; + justify-content: center; + height: 100vh; /* Keeps things at the top*/ + align-items: start; +} + +#botStats { + width: 100%; + height: 50px; + display: flex; + justify-content: center; + align-items: center; + gap: 30px; +} + +/* a simple gif loading from /assets/cloudface.gif and is 60x60px */ +.botLoader { + background-image: url('/assets/cloudface.gif'); + background-size: 60px 60px; + background-position: center; + background-clip: border-box; + background-repeat: no-repeat; + width: 60px; + height: 60px; + margin: 0 auto; +} + +.batteryContainer { + display: flex; + justify-content: center; + align-items: center; + margin: 0 auto; + height: 60px; + padding-bottom: 15px; + gap: 15px; + cursor: pointer; +} + +.batteryContainer:hover .tooltip { + display: block; +} + +.tooltip { + position: absolute; + display: none; + background-color: var(--bg-color); + color: var(--text-color); + border-radius: 5px; + padding: 5px; + z-index: 1000; + margin-top: 145px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.8); +} + +.batteryOutline { + outline: 2px solid var(--text-color); + outline-offset: 2px; + border-radius: 5px; + width: 100px; + height: 40px; + position: relative; +} +.batteryOutline::before { + content: ''; + position: absolute; + top: 0px; + left: 0px; + width: 100%; + height: 100%; + background-color: var(--button-color); + border-radius: 5px; +} +.batteryOutline::after { + content: ''; + position: absolute; + top: 12px; + left: 102px; + width: 6px; + height: 18px; + background-color: var(--text-color); +} + +.vectorFace { + height: 60px; + width: 60px; + background-position: center; + background-size: contain; + background-repeat: no-repeat; +} + +.batteryLevel { + position: absolute; + top: 0px; + left: 0px; + width: 100%; + height: 100%; + background-color: var(--button-color-alt); + border-radius: 5px; +} + +.battery0 { + background-color: var(--low-battery); +} + +.battery1 { + background-color: var(--medium-battery); +} + +.battery3, .battery2 { + background-color: var(--fg-color); +} + +.charging { + position: absolute; + top: 0px; + left: 30px; + width: 40px; + height: 40px; + /* content is an svg of a lightning bolt */ + content: url('data:image/svg+xml;utf8,'); + /* svg drop shadow */ + filter: drop-shadow(0 0 4px rgba(0, 0, 0, 0.4)); +} + +.chargeTimeRemaining { + position: absolute; + bottom: 0px; + left: 3px; + font-size: 0.6em; + color: white; + text-shadow: 0 0 4px rgba(0, 0, 0, 1); +} + +.main-nav-parent { + display: flex; + justify-content: center; + flex-wrap: wrap; + /*border: 1px solid black;*/ + margin: 1rem; + padding: 1rem 2rem; + text-align: center; +} + +.main-nav-child { + display: inline-block; + /*border: 1px solid white;*/ + padding: 1.2rem 2rem 1.4rem 0rem; + vertical-align: top; + width: 85px; +} + +.main-nav-child a { + text-decoration: none; + font-size: 100%; + color: var(--bg-color-alt); +} + +.main-nav-child a:hover { + color: var(--text-color); +} + +.main-nav-child a i { + text-decoration: none; + font-size: 60px; + /*padding-left: 20px; + padding-right: 20px;*/ + padding-bottom: 10px; +} + +.main-nav-child a img { + width:50%; +} + +input[type='checkbox'] { + appearance: none; + width: 25px; + height: 25px; + background-color: var(--button-color); + border: 2px solid var(--fg-color); /* accent color */ + border-radius: 5px; + cursor: pointer; + position: relative; + display: inline-block; + vertical-align: middle; +} + +input[type='checkbox']:checked { + background-color: var(--fg-color); +} + +input[type='checkbox']:checked:after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 6px; + height: 12px; + border: solid var(--text-color); + border-width: 0 2px 2px 0; + transform: translate(-50%, -60%) rotate(45deg); +} + +.checkbox-label { + padding-top: 3px; + margin-left: 5px; + word-break: break-word; +} diff --git a/chipper/webroot/css/wing.css b/chipper/webroot/css/wing.css new file mode 100644 index 0000000..70e32e9 --- /dev/null +++ b/chipper/webroot/css/wing.css @@ -0,0 +1,467 @@ +/* +* Wing 1.0.0-beta +* Copyright 2016, Kabir Shah +* http://usewing.ml/ +* Free to use under the MIT license. +* https://kingpixil.github.io/license +*/ + +/*------------------------------------------------------------ + Base Style +------------------------------------------------------------*/ + +html { + box-sizing: border-box; + font-size: 62.5%; + margin: 0; + padding: 0; +} + +body { + letter-spacing: 0.01em; + line-height: 1.6; + font-size: 1.5em; + font-weight: 400; + font-family: -apple-system, BlinkMacSystemFont, Avenir, "Avenir Next", "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; +} + +/*------------------------------------------------------------ + Typography +------------------------------------------------------------*/ + +h1, +h2, +h3, +h4, +h5, +h6 { + font-weight: 400; + font-family: -apple-system, BlinkMacSystemFont, Avenir, "Avenir Next", "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; +} + +h1, h2, h3 { + letter-spacing: -.1rem; +} + +h1 { + font-size: 4.0rem; + line-height: 1.2; +} + +h2 { + font-size: 3.6rem; + line-height: 1.25; +} + +h3 { + font-size: 3.0rem; + line-height: 1.3; +} + +h4 { + font-size: 2.4rem; + line-height: 1.35; + letter-spacing: -.08rem; +} + +h5 { + font-size: 1.8rem; + line-height: 1.5; + letter-spacing: -.05rem; +} + +h6 { + font-size: 1.5rem; + line-height: 1.6; + letter-spacing: 0; +} + +@media (min-width: 550px) { + h1 { + font-size: 5.0rem; + } + h2 { + font-size: 4.2rem; + } + h3 { + font-size: 3.6rem; + } + h4 { + font-size: 3.0rem; + } + h5 { + font-size: 2.4rem; + } + h6 { + font-size: 1.5rem; + } +} + +/*------------------------------------------------------------ + Links +------------------------------------------------------------*/ +a { + color: #33ED6D; + transition: all .1s ease; +} + +a:hover { + color: #222222; +} + +/*------------------------------------------------------------ + Buttons +------------------------------------------------------------*/ + +button, [type=submit] { + padding: 1.1rem 3.5rem; + margin: 1rem 0; + background: #111111; + color: #f5f5f5; + border-radius: 2px; + border: none; + font-size: 1.3rem; + transition: all .2s ease; +} + +button.outline, [type=submit].outline { + padding: 1.1rem 3.5rem; + background: none; + color: #111111; + border: 1.5px solid #111111; +} + +button:hover, [type=submit]:hover { + background: #222222; +} + +button.outline:hover, [type=submit].outline:hover { + background: none; + border: 1.5px solid #444444; + color: #444444; +} + +button:focus, [type=submit]:focus { + outline: none; +} + +button:active, [type=submit]:active { + transform: scale(.99); +} + +/*------------------------------------------------------------ + Forms +------------------------------------------------------------*/ + +input[type=text], input[type=password], input[type=email], input[type=search], input[type=number], input[type=file], input[type=tel], textarea, textarea[type=text] { + margin: 1rem 0; + width: 90%; + max-width: 90%; + border-radius: 2px; + border: 1px solid #33ED6D; + font-size: 1.3rem; + transition: all .2s ease; +} + +select { + width: 93%; + max-width: 93%; +} + +input[type=text]:hover, input[type=password]:hover, input[type=email]:hover, input[type=search]:hover, input[type=number]:hover, input[type=radio]:hover, input[type=file], input[type=tel], select:hover, textarea:hover, textarea[type=text]:hover { + border: 1px solid #111111; +} + +input[type=text]:focus, input[type=password]:focus, input[type=email]:focus, input[type=search]:focus, input[type=number], input[type=file], input[type=tel], select:focus textarea:focus, textarea[type=text]:focus { + outline: none; + border: 1px solid #33ED6D; +} + +input[type=text], input[type=password], input[type=email], input[type=search], input[type=number], input[type=file], input[type=tel], select { + padding: 1.1rem; +} + +textarea, textarea[type=text] { + height: 10rem; + padding: 14px 20px; +} + +.container { + max-width: 960px; + margin: 0 auto; + width: 80%; +} + +.row { + display: flex; + flex-flow: row wrap; + justify-content: space-between; +} + +.row > :first-child { + margin-left: 0; +} + +.row > :last-child { + margin-right: 0; +} + +.col { + -webkit-box-flex: 1; + -moz-box-flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; +} + +.col, [class^='col-'], [class*=" col-"] { + margin: 1rem; +} + +.col-1 { + flex: 1; +} + +.col-2 { + flex: 2; +} + +.col-3 { + flex: 3; +} + +.col-4 { + flex: 4; +} + +.col-5 { + flex: 5; +} + +.col-6 { + flex: 6; +} + +.col-7 { + flex: 7; +} + +.col-8 { + flex: 8; +} + +.col-9 { + flex: 9; +} + +.col-10 { + flex: 10; +} + +.col-11 { + flex: 11; +} + +.col-12 { + flex: 12; +} + +@media screen and (max-width: 768px) { + .col, [class^='col-'], [class*=" col-"] { + margin: 0; + flex: 0 0 100%; + } +} + +/*------------------------------------------------------------ + Lists +------------------------------------------------------------*/ + +ul { + list-style: circle inside; +} + +ol { + list-style: decimal inside; +} + +/*------------------------------------------------------------ + Tables +------------------------------------------------------------*/ + +.table { + width: 100%; + border: none; + border-collapse: collapse; + border-spacing: 0; + text-align: left; +} + +.table th, .table td { + vertical-align: middle; + padding: 12px 4px; +} + +.table thead { + border-bottom: 2px solid #333030; +} + +/* responsive tables */ +@media screen and (max-width: 768px) { + .table.responsive { + position: relative; + display: block; + } + .table.responsive th, .table.responsive td { + margin: 0; + } + .table.responsive thead { + display: block; + float: left; + border: 0; + } + .table.responsive thead tr { + display: block; + padding: 0 10px 0 0; + border-right: 2px solid #333030; + } + .table.responsive th { + display: block; + text-align: right; + } + .table.responsive tbody { + display: block; + overflow-x: auto; + white-space: nowrap; + } + .table.responsive tbody tr { + display: inline-block; + } + .table.responsive td { + display: block; + min-height: 16px; + text-align: left; + } + .table.responsive tr { + padding: 0 10px; + } +} + +/*------------------------------------------------------------ + Utilities +------------------------------------------------------------*/ + +.pull-right { + float: right; +} + +.pull-left { + float: left; +} + +.text-center { + text-align: center; +} + +.full-screen { + width: 100%; + min-height: 100vh; +} + +.vertical-align { + display: flex; + align-items: center; +} + +.horizontal-align { + display: flex; + justify-content: center; +} + +.center { + display: grid; + align-items: center; + justify-content: center; +} + +.right { + display: flex; + align-items: center; + justify-content: flex-end; +} + +.left { + display: flex; + align-items: center; + justify-content: flex-start; +} + +.fixed { + position: fixed; + width: 100%; +} + +@media screen and (max-width: 400px) { + .hide-phone { display: none; } +} + +@media screen and (max-width: 768px) { + .hide-tablet { display: none; } +} + +/*------------------------------------------------------------ + Misc +------------------------------------------------------------*/ + +code { + padding: 0.2rem 0.5rem; + margin: 0 0.2rem; + font-size: 90%; + white-space: nowrap; + background: #F1F1F1; + border: 1px solid #E1E1E1; + border-radius: 4px; + font-family: "Consolas", "Monaco", "Menlo", monospace; +} + +pre > code { + display: block; + padding: 1rem 1.5rem; + white-space: pre-wrap; + white-space: -moz-pre-wrap; + white-space: -pre-wrap; + white-space: -o-pre-wrap; + word-wrap: break-word; +} + +/*------------------------------------------------------------ + Navigation +------------------------------------------------------------*/ + +.nav { + position: relative; + display: flex; + flex-wrap: wrap; + align-items: center; + padding: 1rem; +} + +.nav-menu, +.nav-brand { + display: flex; +} + +.nav-menu { + flex-flow: row; + flex: 1 0 auto; +} + +.nav-item { + padding: 1rem 2rem; +} + +.nav-logo { + font-weight: bolder; + font-size: 2rem; + line-height: 0; +} diff --git a/chipper/webroot/favicon.ico b/chipper/webroot/favicon.ico new file mode 100644 index 0000000..3febb4e Binary files /dev/null and b/chipper/webroot/favicon.ico differ diff --git a/chipper/webroot/favicon.png b/chipper/webroot/favicon.png new file mode 100644 index 0000000..0bf135b Binary files /dev/null and b/chipper/webroot/favicon.png differ diff --git a/chipper/webroot/index.html b/chipper/webroot/index.html new file mode 100644 index 0000000..2a88bc8 --- /dev/null +++ b/chipper/webroot/index.html @@ -0,0 +1,223 @@ + + + + + Wire-Pod + + + + + + + + + +
+
+

Wire-Pod

+ +
+
+ + +
+ + + + + + + + + + + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/chipper/webroot/initial.html b/chipper/webroot/initial.html new file mode 100644 index 0000000..cafb482 --- /dev/null +++ b/chipper/webroot/initial.html @@ -0,0 +1,89 @@ + + + + + Wire-Pod Initial Setup + + + + + + + + + +
+
+

Wire-Pod Setup

+
+
+
+

+ Welcome to wire-pod! Configure these + options to your liking then click the "Submit Settings" button at the + bottom to finish setting up wire-pod. +

+
+
+

Connection Method

+
+
+ + +
+
+ + + + Weather and Knowledge Graph settings can be configured in the Server Settings page once + wire-pod is set up. +
+ + +
+
+
+ + + + + + + + diff --git a/chipper/webroot/js/battery.js b/chipper/webroot/js/battery.js new file mode 100644 index 0000000..a28bdec --- /dev/null +++ b/chipper/webroot/js/battery.js @@ -0,0 +1,233 @@ +var botStats = document.getElementById("botStats"); + +function getBatteryPercentage(voltage) { + let percentage; + const maxVoltage = 4.1; // Maximum voltage for the battery + const midVoltage = 3.85; // Mid voltage for the battery + const minVoltage = 3.5; // Minimum voltage for the battery + + if (voltage >= maxVoltage) { + percentage = 100; // Fully charged + } else if (voltage >= midVoltage) { + // Fast drop from 100% to 80% + let scaledVoltage = (voltage - midVoltage) / (maxVoltage - midVoltage); + percentage = 80 + 20 * Math.log10(1 + scaledVoltage * 9); // Adjust factor to make the curve steeper + } else if (voltage >= minVoltage) { + // Gradual drop off from 80% to 0% + let scaledVoltage = (voltage - minVoltage) / (midVoltage - minVoltage); + percentage = 80 * Math.log10(1 + scaledVoltage * 9); // Adjust factor for the curve + } else if (!voltage) { + // if the bot is turned on whilst off the charger, the get_battery response doesn't include voltage. + // assume a reasonable battery percentage + percentage = 70; + } else { + percentage = 0; // At or below 3.5V, considered empty + } + + return Math.max(0, Math.min(100, Math.round(percentage))); // Ensure percentage is within 0-100% +} + + +async function updateBatteryInfo(serial, i) { + var batteryContainer = document.getElementsByClassName("batteryContainer")[i]; + if (!batteryContainer) { + return; + } + var batteryOutline = batteryContainer.getElementsByClassName("batteryOutline")[0]; + var batteryLevel = batteryOutline.getElementsByClassName("batteryLevel")[0]; + var chargeTimeRemaining = batteryOutline.getElementsByClassName("chargeTimeRemaining")[0]; + var vectorFace = batteryContainer.getElementsByClassName("vectorFace")[0]; + var tooltip = batteryContainer.getElementsByClassName("tooltip")[0]; + + if (!batteryLevel || !vectorFace) { + return; + } + + let batteryStatus; + + try { + // Maintain the battery information for each robot but fetch the latest battery status and update the battery level + batteryStatus = await getBatteryStatus(serial); + } catch { + // Do nothing + } + if (!batteryStatus) { + batteryLevel.className = "batteryLevel batteryUnknown"; + vectorFace.style.backgroundImage = "url(/assets/wififace.gif)"; + tooltip.innerHTML = `${serial}
??%
(Unable to connect)`; + setTimeout(async () => { + // Re-render the battery information + updateBatteryInfo(serial, i); + }, 6000); + return; + } + + let batteryPercentage = getBatteryPercentage(batteryStatus["battery_volts"]); + if (batteryStatus["battery_level"] === 2) { + // If the battery level is 2, we'll update the colors to reflect the battery level + if (batteryPercentage < 20) { + batteryStatus["battery_level"] = 0; + } else if (batteryPercentage < 50) { + batteryStatus["battery_level"] = 1; + } + } else if (batteryStatus["battery_level"] === 1 && !batteryStatus["is_on_charger_platform"]) { + // Cap the battery level at 15% if the battery level is 1 and not charging + vectorFace.style.backgroundImage = "url(/assets/homeface.png)"; + batteryPercentage = Math.min(15, batteryPercentage); + batteryStatus["battery_level"] = 0; // Set color to red + } + + // Set the battery level based on the battery_level value and handle the rest in css + const batteryLevelClass = "batteryLevel battery" + batteryStatus["battery_level"]; + if (batteryLevel.className !== batteryLevelClass) { + batteryLevel.className = batteryLevelClass; + } + + // Update the battery level + batteryLevel.style.width = batteryPercentage + "%"; + + // Clear tooltip, and replace serial number and the latest voltage + tooltip.innerHTML = `${serial}
~${batteryPercentage}%
(${batteryStatus["battery_volts"].toFixed(2)}V)`; + + // Update the charging status + if (batteryStatus["is_on_charger_platform"]) { + if (!batteryOutline.getElementsByClassName("charging").length) { + var charging = document.createElement("div"); + charging.className = "charging"; + batteryOutline.appendChild(charging); + vectorFace.style.backgroundImage = "url(/assets/expandface.gif)"; + } + + chargeTimeRemaining.style.display = "block"; + if (batteryStatus["suggested_charger_sec"]) { + chargeTimeRemaining.innerHTML = `~${Math.round(batteryStatus["suggested_charger_sec"])}s`; + } else if (batteryStatus["is_charging"]) { + chargeTimeRemaining.innerHTML = ""; + }else { + chargeTimeRemaining.innerHTML = "Full"; + vectorFace.style.backgroundImage = "url(/assets/face.gif)"; + } + } else { + var charging = batteryOutline.getElementsByClassName("charging")[0]; + if (charging) { + charging.remove(); + vectorFace.style.backgroundImage = "url(/assets/facegaze.gif)"; + } + chargeTimeRemaining.style.display = "none"; + } + + + + setTimeout(async () => { + // Re-render the battery information + updateBatteryInfo(serial, i); + }, 3000); +} + +async function renderBatteryInfo(serial, i = 0) { + // For each robot, we'll create a new div to hold the battery information with a class of "batteryContainer" + var batteryContainer = document.createElement("div"); + batteryContainer.className = "batteryContainer"; + botStats.appendChild(batteryContainer); + batteryContainer.onclick = function() { + window.location.href = "/sdkapp/settings.html?serial=" + serial; + }; + + // Create a tooltip for the robot's serial number, with class "tooltip" + + var tooltip = document.createElement("span"); + tooltip.className = "tooltip"; + tooltip.innerHTML = `${serial}`; + batteryContainer.appendChild(tooltip); + + // Create a new div to hold the battery status with a class of "batteryOutline", this will be the outline of the battery status + var batteryOutline = document.createElement("div"); + batteryOutline.className = "batteryOutline"; + batteryContainer.appendChild(batteryOutline); + + var vectorFace = document.createElement("div"); + vectorFace.className = "vectorFace"; + vectorFace.style.backgroundImage = "url(/assets/webface.gif)"; // default loading face + batteryContainer.appendChild(vectorFace); + + // Create the colored div that will represent the battery level, with a class of "batteryLevel" + var batteryLevel = document.createElement("div"); + batteryLevel.className = "batteryLevel"; + batteryOutline.appendChild(batteryLevel); + + var chargeTimeRemaining = document.createElement("div"); + chargeTimeRemaining.className = "chargeTimeRemaining"; + batteryOutline.appendChild(chargeTimeRemaining); + + // We will manage the battery level via class names, there are only 4 levels reported (0, 1, 2, 3) + + // Get the battery status for the robot + let batteryStatus; + + try { + // Maintain the battery information for each robot but fetch the latest battery status and update the battery level + batteryStatus = await getBatteryStatus(serial); + } catch { + // Do nothing + } + if (!batteryStatus) { + batteryLevel.className = "batteryLevel batteryUnknown"; + vectorFace.style.backgroundImage = "url(/assets/wififace.gif)"; + tooltip.innerHTML = `${serial}
??
(Unable to connect)`; + setTimeout(async () => { + // Re-render the battery information + updateBatteryInfo(serial, i); + }, 6000); + return; + } + + // If the robot is on the charger platform, we'll set the battery level to "charging" by adding a child div with class "charging" + if (batteryStatus["is_on_charger_platform"]) { + var charging = document.createElement("div"); + charging.className = "charging"; + batteryOutline.appendChild(charging); + vectorFace.style.backgroundImage = "url(/assets/expandface.gif)"; + } else { + vectorFace.style.backgroundImage = "url(/assets/facegaze.gif)"; + } + + // Set the battery level based on the battery_level value and handle the rest in css + batteryLevel.className = "batteryLevel battery" + batteryStatus["battery_level"]; + const batteryPercentage = getBatteryPercentage(batteryStatus["battery_volts"]); + batteryLevel.style.width = batteryPercentage + "%"; + + tooltip.innerHTML += `
~${batteryPercentage}%`; + + // Add the battery voltage to the tooltip + tooltip.innerHTML += `
(${batteryStatus["battery_volts"].toFixed(2)}V)`; + + setTimeout(async () => { + // Re-render the battery information + updateBatteryInfo(serial, i); + }, 3000); +} + +async function processBotStats() { + try { + // While loading, set a loading gif in a class div of "botLoader" to the botStats div + var botLoader = document.createElement("div"); + botLoader.className = "botLoader"; + botStats.appendChild(botLoader); + + const sdkInfo = await getSDKInfo(); //{"global_guid":"tni1TRsTRTaNSapjo0Y+Sw==","robots":[{"esn":"00603f9b","ip_address":"10.42.0.248","guid":"5RlowyehhT8Qq7wEpF6JsQ==","activated":true},{"esn":"004047ef","ip_address":"10.42.0.175","guid":"ofoJZqLP3cwd9YpvXrdAfw==","activated":true}]} + + botLoader.remove(); + if (!sdkInfo) { + botStats.style.display = "none"; + return; + } + + for (var i = 0; i < sdkInfo["robots"].length; i++) { + const serial = sdkInfo["robots"][i]["esn"]; + + renderBatteryInfo(serial, i); + } + } catch { + // Do nothing + } +} \ No newline at end of file diff --git a/chipper/webroot/js/ble.js b/chipper/webroot/js/ble.js new file mode 100644 index 0000000..316a2e7 --- /dev/null +++ b/chipper/webroot/js/ble.js @@ -0,0 +1,342 @@ +const vectorEpodSetup = "https://wpsetup.keriganc.com"; +let authEl = document.getElementById("botAuth"); +let statusP = document.createElement("p"); +let OTAUpdating = false; + +const externalSetup = document.createElement("a"); +externalSetup.href = vectorEpodSetup; +externalSetup.innerHTML = vectorEpodSetup; + +function showBotAuth() { + GetLog = false; + toggleSections("section-botauth", "icon-BotAuth"); + checkBLECapability(); +} + +function toggleSections(showSection, icon) { + const sections = ["section-intents", "section-log", "section-botauth", "section-version", "section-uicustomizer"]; + sections.forEach((section) => (document.getElementById(section).style.display = "none")); + document.getElementById(showSection).style.display = "block"; + updateColor(icon); +} + +function checkBLECapability() { + updateAuthel("Checking if wire-pod can use BLE directly..."); + fetch("/api-ble/init") + .then((response) => response.text()) + .then((response) => { + if (response.includes("success")) { + beginBLESetup(); + } else { + showExternalSetupInstructions(); + } + }); +} + +function showExternalSetupInstructions() { + authEl.innerHTML = ` +

Head to the following site on any device with Bluetooth support to set up your Vector.

+ ${vectorEpodSetup} +
+ Note: with OSKR/dev robots, it might give a warning about firmware. This can be ignored. + `; +} + +function beginBLESetup() { + authEl.innerHTML = ` +

1. Place Vector on the charger.

+

2. Double press the button. A key should appear on screen.

+

3. Click 'Begin Scanning' and pair with your Vector.

+ + `; +} + +function reInitBLE() { + fetch("/api-ble/disconnect").then(() => fetch("/api-ble/init").catch(() => { + showExternalSetupInstructions(); + })).catch(() => fetch("/api-ble/init")); +} + +function scanRobots(returning) { + const disconnectButtonDiv = document.getElementById("disconnectButton"); + disconnectButtonDiv.innerHTML = ` + + `; + updateAuthel("Scanning..."); + fetch("/api-ble/scan", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" } }) + .then((response) => response.json()) + .then((parsed) => { + authEl.innerHTML = returning ? "

Incorrect PIN was entered, scanning again...

" : ""; + authEl.innerHTML += "Scanning..."; + + const buttonsDiv = document.createElement("div"); + parsed.forEach((robot) => { + const button = document.createElement("button"); + button.innerHTML = robot.name; + button.onclick = () => connectRobot(robot.id); + buttonsDiv.appendChild(button); + }); + + const rescanButton = document.createElement("button"); + rescanButton.innerHTML = "Re-scan"; + rescanButton.onclick = () => { + updateAuthel("Reiniting BLE then scanning..."); + reInitBLE().then(() => scanRobots(false)); + }; + + updateAuthel("Click on the robot you would like to pair with."); + authEl.appendChild(rescanButton); + authEl.appendChild(buttonsDiv); + }).catch(() => { + updateAuthel("Error scanning. Reiniting BLE then scanning..."); + reInitBLE().then(() => scanRobots(false)); + }); +} + +function disconnect() { + authEl.innerHTML = "Disconnecting..."; + OTAUpdating = false; + fetch("/api-ble/stop_ota").then(() => fetch("/api-ble/disconnect").then(() => checkBLECapability())).catch(() => checkBLECapability()); +} + +function connectRobot(id) { + updateAuthel("Connecting to robot..."); + fetch(`/api-ble/connect?id=${id}`) + .then((response) => response.text()) + .then((response) => { + if (response.includes("success")) { + createPinEntry(); + } else { + alert("Error connecting. WirePod will restart and this will return to the first screen of setup."); + updateAuthel("Waiting for WirePod to restart..."); + setTimeout(checkBLECapability, 3000); + } + }).catch(() => { + alert("Error connecting. WirePod will restart and this will return to the first screen of setup."); + updateAuthel("Waiting for WirePod to restart..."); + setTimeout(checkBLECapability, 3000); + }) +} + +function createPinEntry() { + authEl.innerHTML = ` +

Enter the pin shown on Vector's screen.

+ +
+ + `; +} + +function sendPin() { + const pin = document.getElementById("pinEntry").value; + updateAuthel("Sending PIN..."); + fetch(`/api-ble/send_pin?pin=${pin}`) + .then((response) => response.text()) + .then((response) => { + if (response.includes("incorrect pin") || response.includes("length of pin")) { + updateAuthel("Wrong PIN... Reiniting BLE then scanning..."); + reInitBLE().then(() => scanRobots(true)); + } else { + wifiCheck(); + } + }).catch(() => { + updateAuthel("Error sending PIN... Reiniting BLE then scanning..."); + reInitBLE().then(() => scanRobots(true)); + }); +} + +function wifiCheck() { + fetch("/api-ble/get_wifi_status") + .then((response) => response.text()) + .then((response) => { + if (response === "1") { + whatToDo(); + } else { + scanWifi(); + } + }).catch(() => { + updateAuthel("Error checking Wi-Fi status... Reiniting BLE then scanning..."); + reInitBLE().then(() => scanRobots(true)); + }); +} + +function scanWifi() { + authEl.innerHTML = "Scanning for Wi-Fi networks..."; + fetch("/api-ble/scan_wifi") + .then((response) => response.json()) + .then((networks) => { + authEl.innerHTML = ` +

Select a Wi-Fi network to connect Vector to.

+ +
+ ${networks + .map( + (network) => + network.ssid && `` + ) + .join("")} + `; + }).catch(() => { + updateAuthel("Error scanning Wi-Fi networks... Reiniting BLE then scanning..."); + reInitBLE().then(() => scanRobots(true)); + }); +} + +function createWiFiPassEntry(ssid, authtype) { + authEl.innerHTML = ` + +

Enter the password for ${ssid}

+ +
+ + `; +} + +function connectWifi(ssid, authtype) { + const password = document.getElementById("passEntry").value; + authEl.innerHTML = "Connecting Vector to Wi-Fi..."; + fetch(`/api-ble/connect_wifi?ssid=${ssid}&password=${password}&authType=${authtype}`) + .then((response) => response.text()) + .then((response) => { + if (!response.includes("255")) { + alert("Error connecting, likely incorrect password"); + createWiFiPassEntry(ssid, authtype); + } else { + whatToDo(); + } + }).catch(() => { + updateAuthel("Error connecting to Wi-Fi... Reiniting BLE then scanning..."); + reInitBLE().then(() => scanRobots(true)); + }); +} + +function checkFirmware() { + fetch("/api-ble/get_firmware") + .then((response) => response.text()) + .then((response) => { + const splitFirmware = response.split("-"); + console.log(splitFirmware); + }).catch(() => { + updateAuthel("Error getting firmware version... Reiniting BLE then scanning..."); + reInitBLE().then(() => scanRobots(true)); + }); +} + +function whatToDo() { + fetch("/api-ble/get_robot_status") + .then((response) => response.text()) + .then((response) => { + switch (response) { + case "in_recovery_prod": + doOTA("local"); + break; + case "in_recovery_dev": + doOTA("http://wpsetup.keriganc.com:81/1.6.0.3331.ota"); + break; + case "in_firmware_nonep": + showRecoveryInstructions(); + break; + case "in_firmware_dev": + showDevWarning(); + break; + case "in_firmware_ep": + showAuthButton(); + break; + } + }); +} + +function showRecoveryInstructions() { + authEl.innerHTML = ` +

1. Place Vector on the charger.

+

2. Hold the button for 15 seconds. He will turn off - keep holding it until he turns back on.

+

3. Click 'Begin Scanning' and pair with your Vector.

+ + `; + alert("Your bot is not on the correct firmware for wire-pod. Follow the directions to put him in recovery mode."); +} + +function showDevWarning() { + alert("Your bot is a dev robot. Make sure you have done the 'Configure an OSKR/dev-unlocked robot' section before authentication. If you did already, you can ignore this warning."); + showAuthButton(); +} + +function showAuthButton() { + authEl.innerHTML = ``; +} + +function doOTA(url) { + updateAuthel("Starting OTA update..."); + fetch(`/api-ble/start_ota?url=${url}`) + .then((response) => response.text()) + .then((response) => { + if (response.includes("success")) { + OTAUpdating = true; + const interval = setInterval(() => { + fetch("/api-ble/get_ota_status") + .then((otaResponse) => otaResponse.text()) + .then((otaResponse) => { + updateAuthel(otaResponse); + if (otaResponse.includes("complete")) { + alert("The OTA update is complete. When the bot reboots, follow the steps to re-pair the bot with wire-pod. wire-pod will then authenticate the robot and setup will be complete."); + OTAUpdating = false; + clearInterval(interval); + checkBLECapability(); + } else if (otaResponse.includes("stopped") || !OTAUpdating) { + clearInterval(interval); + } + }); + }, 2000); + } else { + whatToDo(); + } + }); +} + +function updateAuthel(update) { + authEl.innerHTML = `

${update}

`; +} + +function doAuth() { + updateAuthel("Authenticating your Vector..."); + fetch("/api-ble/do_auth") + .then((response) => response.text()) + .then((response) => { + if (response.includes("error")) { + showAuthError(); + } else { + showWakeOptions(); + } + }); +} + +function showAuthError() { + updateAuthel("Authentication failure. Try again in ~15 seconds. If it happens again, check the troubleshooting guide:"); + const troubleshootingLink = document.createElement("a"); + troubleshootingLink.href = "https://github.com/kercre123/wire-pod/wiki/Troubleshooting#error-logging-in-the-bot-is-likely-unable-to-communicate-with-your-wire-pod-instance"; + troubleshootingLink.target = "_blank"; + troubleshootingLink.innerText = "https://github.com/kercre123/wire-pod/wiki/Troubleshooting"; + authEl.appendChild(document.createElement("br")); + authEl.appendChild(troubleshootingLink); +} + +function showWakeOptions() { + updateAuthel("Authentication was successful! How would you like to wake Vector up?"); + authEl.innerHTML += ` + +
+ + `; +} + +function doOnboard(withAnim) { + updateAuthel("Onboarding robot..."); + fetch(`/api-ble/onboard?with_anim=${withAnim}`).then(() => { + fetch("/api-ble/disconnect"); + updateAuthel("Vector is now fully set up! Use the Bot Settings tab to further configure your bot."); + const disconnectButtonDiv = document.getElementById("disconnectButton"); + disconnectButtonDiv.innerHTML = ` + + `; + }); +} diff --git a/chipper/webroot/js/initial.js b/chipper/webroot/js/initial.js new file mode 100644 index 0000000..af4899f --- /dev/null +++ b/chipper/webroot/js/initial.js @@ -0,0 +1,101 @@ +function checkLanguage() { + fetch("/api/get_stt_info") + .then((response) => response.json()) + .then((parsed) => { + const sectionLanguage = document.getElementById("section-language"); + const languageSelection = document.getElementById("languageSelection"); + + if (parsed.provider !== "vosk" && parsed.provider !== "whisper.cpp") { + console.log("stt not vosk/whisper"); + sectionLanguage.style.display = "none"; + languageSelection.value = "en-US"; + } else { + sectionLanguage.style.display = "block"; + console.log(parsed.language); + languageSelection.value = "en-US"; + } + }); +} + +function updateSetupStatus(statusString) { + const setupStatus = document.getElementById("setup-status"); + setupStatus.innerHTML = `

${statusString}

`; +} + +function sendSetupInfo() { + document.getElementById("config-options").style.display = "none"; + updateSetupStatus("Initiating setup..."); + + const language = document.getElementById("languageSelection").value; + const langData = { language }; + + document.getElementById("languageSelectionDiv").style.display = "none"; + + fetch("/api/set_stt_info", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(langData), + }) + .then((response) => response.text()) + .then((response) => { + if (response.includes("success")) { + updateSetupStatus("Language set successfully."); + setConn(); + } else if (response.includes("downloading")) { + updateSetupStatus("Downloading language model..."); + var interval = setInterval(() => { + fetch("/api/get_download_status") + .then((response) => response.text()) + .then((statusText) => { + updateSetupStatus(statusText); + if (statusText.includes("success")) { + updateSetupStatus("Language set successfully."); + clearInterval(interval); + setConn(); + } else if (statusText.includes("error")) { + document.getElementById("config-options").style.display = "block"; + clearInterval(interval); + } else if (statusText.includes("not downloading")) { + updateSetupStatus("Initiating language model download..."); + } + }); + }, 500); + } else if (response.includes("vosk")) { + initWeatherAPIKey(); + } else if (response.includes("error")) { + updateSetupStatus(response); + document.getElementById("config-options").style.display = "block"; + } + }); +} + +function checkConn() { + const connValue = document.getElementById("connSelection").value; + document.getElementById("portViz").style.display = connValue === "ip" ? "block" : "none"; +} + +function setConn() { + updateSetupStatus("Setting connection type (ep or ip)..."); + const connValue = document.getElementById("connSelection").value; + let port = document.getElementById("portInput").value; + port = port ? port : "443"; + const url = connValue === "ep" ? "/api-chipper/use_ep" : `/api-chipper/use_ip?port=${port}`; + + fetch(url) + .then((response) => response.text()) + .then((response) => { + if (response) { + updateSetupStatus("Setup is complete! Wire-pod has started. Redirecting to main page..."); + setTimeout(() => window.location.href = "/", 3000); + } else { + updateSetupStatus("Error setting up wire-pod, check the logs"); + document.getElementById("config-options").style.display = "block"; + } + }); +} + +function directToIndex() { + window.location.href = "/index.html"; +} \ No newline at end of file diff --git a/chipper/webroot/js/main.js b/chipper/webroot/js/main.js new file mode 100644 index 0000000..9f9c8b5 --- /dev/null +++ b/chipper/webroot/js/main.js @@ -0,0 +1,613 @@ +const intentsJson = JSON.parse( + '["intent_greeting_hello", "intent_names_ask", "intent_imperative_eyecolor", "intent_character_age", "intent_explore_start", "intent_system_charger", "intent_system_sleep", "intent_greeting_goodmorning", "intent_greeting_goodnight", "intent_greeting_goodbye", "intent_seasonal_happynewyear", "intent_seasonal_happyholidays", "intent_amazon_signin", "intent_imperative_forward", "intent_imperative_turnaround", "intent_imperative_turnleft", "intent_imperative_turnright", "intent_play_rollcube", "intent_play_popawheelie", "intent_play_fistbump", "intent_play_blackjack", "intent_imperative_affirmative", "intent_imperative_negative", "intent_photo_take_extend", "intent_imperative_praise", "intent_imperative_abuse", "intent_weather_extend", "intent_imperative_apologize", "intent_imperative_backup", "intent_imperative_volumedown", "intent_imperative_volumeup", "intent_imperative_lookatme", "intent_imperative_volumelevel_extend", "intent_imperative_shutup", "intent_names_username_extend", "intent_imperative_come", "intent_imperative_love", "intent_knowledge_promptquestion", "intent_clock_checktimer", "intent_global_stop_extend", "intent_clock_settimer_extend", "intent_clock_time", "intent_imperative_quiet", "intent_imperative_dance", "intent_play_pickupcube", "intent_imperative_fetchcube", "intent_imperative_findcube", "intent_play_anytrick", "intent_message_recordmessage_extend", "intent_message_playmessage_extend", "intent_blackjack_hit", "intent_blackjack_stand", "intent_play_keepaway"]' +); + +var GetLog = false; + +const getE = (element) => document.getElementById(element); + +function updateIntentSelection(element) { + fetch("/api/get_custom_intents_json") + .then((response) => response.json()) + .then((listResponse) => { + const container = getE(element); + container.innerHTML = ""; + if (listResponse && listResponse.length > 0) { + const select = document.createElement("select"); + select.name = `${element}intents`; + select.id = `${element}intents`; + listResponse.forEach((intent) => { + if (!intent.issystem) { + const option = document.createElement("option"); + option.value = intent.name; + option.text = intent.name; + select.appendChild(option); + } + }); + const label = document.createElement("label"); + label.innerHTML = "Choose the intent: "; + label.htmlFor = `${element}intents`; + container.appendChild(label).appendChild(select); + + select.addEventListener("change", hideEditIntents); + } else { + const error = document.createElement("p"); + error.innerHTML = "No intents found, you must add one first"; + container.appendChild(error); + } + }).catch(() => { + // Do nothing + }); +} + +function checkInited() { + fetch("/api/is_api_v3").then((response) => { + if (!response.ok) { + alert( + "This webroot does not match with the wire-pod binary. Some functionality will be broken. There was either an error during the last update, or you did not precisely follow the update guide. https://github.com/kercre123/wire-pod/wiki/Things-to-Know#updating-wire-pod" + ); + } + }); + + fetch("/api/get_config") + .then((response) => response.json()) + .then((config) => { + if (!config.pastinitialsetup) { + window.location.href = "/initial.html"; + } + }); +} + +function createIntentSelect(element) { + const select = document.createElement("select"); + select.name = `${element}intents`; + select.id = `${element}intents`; + intentsJson.forEach((intent) => { + const option = document.createElement("option"); + option.value = intent; + option.text = intent; + select.appendChild(option); + }); + const label = document.createElement("label"); + label.innerHTML = "Intent to send to robot after script executed:"; + label.htmlFor = `${element}intents`; + getE(element).innerHTML = ""; + getE(element).appendChild(label).appendChild(select); +} + +function editFormCreate() { + const intentNumber = getE("editSelectintents").selectedIndex; + + fetch("/api/get_custom_intents_json") + .then((response) => response.json()) + .then((intents) => { + const intent = intents[intentNumber]; + if (intent) { + const form = document.createElement("form"); + form.id = "editIntentForm"; + form.name = "editIntentForm"; + form.innerHTML = ` +
+
+
+
+
+
+
+
+
+ + `; + //form.querySelector("#submit").onclick = () => editIntent(intentNumber); + getE("editIntentForm").innerHTML = ""; + getE("editIntentForm").appendChild(form); + showEditIntents(); + } else { + displayError("editIntentForm", "No intents found, you must add one first"); + } + }).catch((error) => { + console.error(error); + displayError("editIntentForm", "Error fetching intents"); + }) +} + +function editIntent(intentNumber) { + const data = { + number: intentNumber + 1, + name: getE("name").value, + description: getE("description").value, + utterances: getE("utterances").value.split(","), + intent: getE("intent").value, + params: { + paramname: getE("paramname").value, + paramvalue: getE("paramvalue").value, + }, + exec: getE("exec").value, + execargs: getE("execargs").value.split(","), + luascript: getE("luascript").value, + }; + + fetch("/api/edit_custom_intent", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }) + .then((response) => response.text()) + .then((response) => { + displayMessage("editIntentStatus", response); + alert(response) + updateIntentSelection("editSelect"); + updateIntentSelection("deleteSelect"); + }); +} + +function deleteSelectedIntent() { + const intentNumber = getE("editSelectintents").selectedIndex + 1; + + fetch("/api/remove_custom_intent", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ number: intentNumber }), + }) + .then((response) => response.text()) + .then((response) => { + hideEditIntents(); + alert(response) + updateIntentSelection("editSelect"); + updateIntentSelection("deleteSelect"); + }); +} + +function sendIntentAdd() { + const form = getE("intentAddForm"); + const data = { + name: form.elements["nameAdd"].value, + description: form.elements["descriptionAdd"].value, + utterances: form.elements["utterancesAdd"].value.split(","), + intent: form.elements["intentAddSelectintents"].value, + params: { + paramname: form.elements["paramnameAdd"].value, + paramvalue: form.elements["paramvalueAdd"].value, + }, + exec: form.elements["execAdd"].value, + execargs: form.elements["execAddArgs"].value.split(","), + luascript: form.elements["luaAdd"].value, + }; + if (!data.name || !data.description || !data.utterances) { + displayMessage("addIntentStatus", "A required input is missing. You need a name, description, and utterances."); + alert("A required input is missing. You need a name, description, and utterances.") + return + } + + displayMessage("addIntentStatus", "Adding..."); + + fetch("/api/add_custom_intent", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }) + .then((response) => response.text()) + .then((response) => { + displayMessage("addIntentStatus", response); + alert(response) + updateIntentSelection("editSelect"); + updateIntentSelection("deleteSelect"); + }); +} + +function checkWeather() { + getE("apiKeySpan").style.display = getE("weatherProvider").value ? "block" : "none"; +} + +function sendWeatherAPIKey() { + const data = { + provider: getE("weatherProvider").value, + key: getE("apiKey").value, + }; + + displayMessage("addWeatherProviderAPIStatus", "Saving..."); + + fetch("/api/set_weather_api", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }) + .then((response) => response.text()) + .then((response) => { + displayMessage("addWeatherProviderAPIStatus", response); + }); +} + +function updateWeatherAPI() { + fetch("/api/get_weather_api") + .then((response) => response.json()) + .then((data) => { + getE("weatherProvider").value = data.provider; + getE("apiKey").value = data.key; + checkWeather(); + }); +} + +function checkKG() { + const provider = getE("kgProvider").value; + const elements = [ + "houndifyInput", + "togetherInput", + "customAIInput", + "intentGraphInput", + "openAIInput", + "saveChatInput", + "llmCommandInput", + "openAIVoiceForEnglishInput", + ]; + + elements.forEach((el) => (getE(el).style.display = "none")); + + if (provider) { + if (provider === "houndify") { + getE("houndifyInput").style.display = "block"; + } else if (provider === "openai") { + getE("intentGraphInput").style.display = "block"; + getE("openAIInput").style.display = "block"; + getE("saveChatInput").style.display = "block"; + getE("llmCommandInput").style.display = "block"; + getE("openAIVoiceForEnglishInput").style.display = "block"; + } else if (provider === "together") { + getE("intentGraphInput").style.display = "block"; + getE("togetherInput").style.display = "block"; + getE("saveChatInput").style.display = "block"; + getE("llmCommandInput").style.display = "block"; + } else if (provider === "custom") { + getE("intentGraphInput").style.display = "block"; + getE("customAIInput").style.display = "block"; + getE("saveChatInput").style.display = "block"; + getE("llmCommandInput").style.display = "block"; + } + } +} + +function sendKGAPIKey() { + const provider = getE("kgProvider").value; + const data = { + enable: true, + provider, + key: "", + model: "", + id: "", + intentgraph: false, + robotName: "", + openai_prompt: "", + openai_voice: "", + openai_voice_with_english: false, + save_chat: false, + commands_enable: false, + endpoint: "", + }; + if (provider === "openai") { + data.key = getE("openaiKey").value; + data.openai_prompt = getE("openAIPrompt").value; + data.intentgraph = getE("intentyes").checked + data.save_chat = getE("saveChatYes").checked + data.commands_enable = getE("commandYes").checked + data.openai_voice = getE("openaiVoice").value + data.openai_voice_with_english = getE("voiceEnglishYes").checked + } else if (provider === "custom") { + data.key = getE("customKey").value; + data.model = getE("customModel").value; + data.openai_prompt = getE("customAIPrompt").value; + data.endpoint = getE("customAIEndpoint").value; + data.intentgraph = getE("intentyes").checked + data.save_chat = getE("saveChatYes").checked + data.commands_enable = getE("commandYes").checked + } else if (provider === "together") { + data.key = getE("togetherKey").value; + data.model = getE("togetherModel").value; + data.openai_prompt = getE("togetherAIPrompt").value; + data.intentgraph = getE("intentyes").checked; + data.save_chat = getE("saveChatYes").checked + data.commands_enable = getE("commandYes").checked + } else if (provider === "houndify") { + data.key = getE("houndKey").value; + data.id = getE("houndID").value; + } else { + data.enable = false; + } + + fetch("/api/set_kg_api", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }) + .then((response) => response.text()) + .then((response) => { + displayMessage("addKGProviderAPIStatus", response); + alert(response); + }); +} + +function deleteSavedChats() { + if (confirm("Are you sure? This will delete all saved chats.")) { + fetch("/api/delete_chats") + .then((response) => response.text()) + .then(() => { + alert("Successfully deleted all saved chats."); + }); + } +} + +function updateKGAPI() { + fetch("/api/get_kg_api") + .then((response) => response.json()) + .then((data) => { + getE("kgProvider").value = data.provider; + if (data.provider === "openai") { + getE("openaiKey").value = data.key; + getE("openAIPrompt").value = data.openai_prompt; + getE("openaiVoice").value = data.openai_voice; + getE("commandYes").checked = data.commands_enable + getE("intentyes").checked = data.intentgraph + getE("saveChatYes").checked = data.save_chat + getE("voiceEnglishYes").checked = data.openai_voice_with_english + } else if (data.provider === "together") { + getE("togetherKey").value = data.key; + getE("togetherModel").value = data.model; + getE("togetherAIPrompt").value = data.openai_prompt; + getE("commandYes").checked = data.commands_enable + getE("intentyes").checked = data.intentgraph + getE("saveChatYes").checked = data.save_chat + } else if (data.provider === "custom") { + getE("customKey").value = data.key; + getE("customModel").value = data.model; + getE("customAIPrompt").value = data.openai_prompt; + getE("customAIEndpoint").value = data.endpoint; + getE("commandYes").checked = data.commands_enable + getE("intentyes").checked = data.intentgraph + getE("saveChatYes").checked = data.save_chat + } else if (data.provider === "houndify") { + getE("houndKey").value = data.key; + getE("houndID").value = data.id; + } + checkKG(); + }); +} + +function setSTTLanguage() { + const data = { language: getE("languageSelection").value }; + + displayMessage("languageStatus", "Setting..."); + + fetch("/api/set_stt_info", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }) + .then((response) => response.text()) + .then((response) => { + if (response.includes("downloading")) { + displayMessage("languageStatus", "Downloading model..."); + updateSTTLanguageDownload(); + } else { + displayMessage("languageStatus", response); + getE("languageSelectionDiv").style.display = response.includes("success") ? "block" : "none"; + } + }); +} + +function updateSTTLanguageDownload() { + + const interval = setInterval(() => { + fetch("/api/get_download_status") + .then((response) => response.text()) + .then((response) => { + displayMessage("languageStatus", response.includes("not downloading") ? "Initiating download..." : response) + if (response.includes("success") || response.includes("error")) { + displayMessage("languageStatus", response); + getE("languageSelectionDiv").style.display = "block"; + clearInterval(interval); + } + }); + }, 500); +} + +function sendRestart() { + fetch("/api/reset") + .then((response) => response.text()) + .then((response) => { + displayMessage("restartStatus", response); + }); +} + +function hideEditIntents() { + getE("editIntentForm").style.display = "none"; + getE("editIntentStatus").innerHTML = ""; +} + +function showEditIntents() { + getE("editIntentForm").style.display = "block"; +} + +function displayMessage(elementId, message) { + const element = getE(elementId); + element.innerHTML = ""; + const p = document.createElement("p"); + p.textContent = message; + element.appendChild(p); +} + +function displayError(elementId, message) { + const element = getE(elementId); + element.innerHTML = ""; + const error = document.createElement("p"); + error.innerHTML = message; + element.appendChild(error); +} + +function toggleSection(sectionToToggle, sectionToClose, foldableID) { + const toggleSect = getE(sectionToToggle); + const closeSect = getE(sectionToClose); + + if (toggleSect.style.display === "block") { + closeSection(toggleSect, foldableID); + } else { + openSection(toggleSect, foldableID); + closeSection(closeSect, foldableID); + } +} + +function openSection(sectionID) { + sectionID.style.display = "block"; +} + +function closeSection(sectionID) { + sectionID.style.display = "none"; +} + +function updateColor(id) { + const l_id = id.replace("section", "icon"); + const elements = document.getElementsByName("icon"); + + elements.forEach((element) => { + element.classList.remove("selectedicon"); + element.classList.add("nowselectedicon"); + }); + + const targetElement = document.getElementById(l_id); + targetElement.classList.remove("notselectedicon"); + targetElement.classList.add("selectedicon"); +} + + +function showLog() { + toggleVisibility(["section-intents", "section-log", "section-botauth", "section-version", "section-uicustomizer"], "section-log", "icon-Logs"); + logDivArea = getE("botTranscriptedTextArea"); + getE("logscrollbottom").checked = true; + logP = document.createElement("p"); + GetLog = true + const interval = setInterval(() => { + if (!GetLog) { + clearInterval(interval); + return; + } + const url = getE("logdebug").checked ? "/api/get_debug_logs" : "/api/get_logs"; + fetch(url) + .then((response) => response.text()) + .then((logs) => { + logDivArea.innerHTML = logs || "No logs yet, you must say a command to Vector. (this updates automatically)"; + if (getE("logscrollbottom").checked) { + logDivArea.scrollTop = logDivArea.scrollHeight; + } + }); + }, 500); +} + +function checkUpdate() { + displayMessage("cVersion", "Checking for updates..."); + displayMessage("aUpdate", ""); + displayMessage("cCommit", ""); + fetch("/api/get_version_info") + // type VersionInfo struct { + // FromSource bool `json:"fromsource"` + // InstalledVer string `json:"installedversion"` + // InstalledCommit string `json:"installedcommit"` + // CurrentVer string `json:"currentver"` + // CurrentCommit string `json:"currentcommit"` + // UpdateAvailable bool `json:"avail"` + // } + .then((response) => response.text()) + .then((response) => { + if (response.includes("error")) { + //

+ // + //

+ displayMessage( + "cVersion", + "There was an error: " + response + ); + getE("updateGuideLink").style.display = "none"; + } else { + const parsed = JSON.parse(response); + if (parsed.fromsource) { + if (!parsed.avail) { + displayMessage("aUpdate", `You are on the latest version.`); + getE("updateGuideLink").style.display = "none"; + } else { + displayMessage("aUpdate", `A newer version of WirePod (commit: ${parsed.currentcommit}) is available! Use this guide to update WirePod: `); + getE("updateGuideLink").style.display = "block"; + } + displayMessage("cVersion", `Installed Commit: ${parsed.installedcommit}`); + } else { + displayMessage("cVersion", `Installed Version: ${parsed.installedversion}`); + displayMessage("cCommit", `Based on wire-pod commit: ${parsed.installedcommit}`); + getE("cCommit").style.display = "block"; + if (parsed.avail) { + displayMessage("aUpdate", `A newer version of WirePod (${parsed.currentversion}) is available! Use this guide to update WirePod: `); + getE("updateGuideLink").style.display = "block"; + } else { + displayMessage("aUpdate", "You are on the latest version."); + getE("updateGuideLink").style.display = "none"; + } + } + } + }); +} + +function showLanguage() { + toggleVisibility(["section-weather", "section-restart", "section-kg", "section-language"], "section-language", "icon-Language"); + fetch("/api/get_stt_info") + .then((response) => response.json()) + .then((parsed) => { + if (parsed.provider !== "vosk" && parsed.provider !== "whisper.cpp") { + displayError("languageStatus", `To set the STT language, the provider must be Vosk or Whisper. The current one is '${parsed.sttProvider}'.`); + getE("languageSelectionDiv").style.display = "none"; + } else { + getE("languageSelectionDiv").style.display = "block"; + getE("languageSelection").value = parsed.language; + } + }); +} + +function showVersion() { + toggleVisibility(["section-log", "section-botauth", "section-intents", "section-version", "section-uicustomizer"], "section-version", "icon-Version"); + checkUpdate(); +} + +function showIntents() { + toggleVisibility(["section-log", "section-botauth", "section-intents", "section-version", "section-uicustomizer"], "section-intents", "icon-Intents"); +} + +function showWeather() { + toggleVisibility(["section-weather", "section-restart", "section-language", "section-kg"], "section-weather", "icon-Weather"); +} + +function showKG() { + toggleVisibility(["section-weather", "section-restart", "section-language", "section-kg"], "section-kg", "icon-KG"); +} + +function toggleVisibility(sections, sectionToShow, iconId) { + if (sectionToShow != "section-log") { + GetLog = false; + } + sections.forEach((section) => { + getE(section).style.display = "none"; + }); + getE(sectionToShow).style.display = "block"; + updateColor(iconId); +} \ No newline at end of file diff --git a/chipper/webroot/js/play_audio.js b/chipper/webroot/js/play_audio.js new file mode 100644 index 0000000..868f054 --- /dev/null +++ b/chipper/webroot/js/play_audio.js @@ -0,0 +1,126 @@ + +//Convert the WAV frame rate on the client machine and send the WAV file to be processed at /api-sdk/play_sound + +let processedAudioBlob = null; + +document.getElementById('fileInput').addEventListener('change', async () => { + const fileInput = document.getElementById('fileInput'); + if (!fileInput.files.length) { + alert('Please, select a WAV file'); + return; + } + + const file = fileInput.files[0]; + const arrayBuffer = await file.arrayBuffer(); + const audioContext = new (window.AudioContext || window.webkitAudioContext)(); + + try { + const audioBuffer = await audioContext.decodeAudioData(arrayBuffer); + + // adjust frame rate to 8000 Hz + const newSampleRate = 8000; + const newLength = Math.round(audioBuffer.length * newSampleRate / audioBuffer.sampleRate); + const newBuffer = audioContext.createBuffer(audioBuffer.numberOfChannels, newLength, newSampleRate); + + for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) { + const oldData = audioBuffer.getChannelData(channel); + const newData = newBuffer.getChannelData(channel); + + for (let i = 0; i < newLength; i++) { + const oldIndex = i * audioBuffer.sampleRate / newSampleRate; + const index0 = Math.floor(oldIndex); + const index1 = Math.min(index0 + 1, oldData.length - 1); + const fraction = oldIndex - index0; + + newData[i] = oldData[index0] * (1 - fraction) + oldData[index1] * fraction; + } + } + + // Create a new WAV file + processedAudioBlob = await bufferToWave(newBuffer); + const url = URL.createObjectURL(processedAudioBlob); + + // play processed audio + const audioOutput = document.getElementById('audioOutput'); + audioOutput.src = url; + audioOutput.play(); + + // show send button + document.getElementById('uploadButton').style.display = 'inline-block'; + } catch (error) { + console.error('Error processing the file:', error); + } +}); + +function bufferToWave(abuffer) { + const numOfChannels = abuffer.numberOfChannels; + const length = abuffer.length * numOfChannels * 2 + 44; + const buffer = new ArrayBuffer(length); + const view = new DataView(buffer); + let offset = 0; + + // Escrever cabeçalho WAV + setString(view, offset, 'RIFF'); offset += 4; + view.setUint32(offset, length - 8, true); offset += 4; + setString(view, offset, 'WAVE'); offset += 4; + setString(view, offset, 'fmt '); offset += 4; + view.setUint32(offset, 16, true); offset += 4; // format size + view.setUint16(offset, 1, true); offset += 2; // PCM format + view.setUint16(offset, numOfChannels, true); offset += 2; // number of channels + view.setUint32(offset, 8000, true); offset += 4; // sample rate + view.setUint32(offset, 8000 * numOfChannels * 2, true); offset += 4; // byte rate + view.setUint16(offset, numOfChannels * 2, true); offset += 2; // block align + view.setUint16(offset, 16, true); offset += 2; // bits per sample + + setString(view, offset, 'data'); offset += 4; + view.setUint32(offset, length - offset - 4, true); offset += 4; + + // Copiar os dados de áudio + for (let channel = 0; channel < numOfChannels; channel++) { + const channelData = abuffer.getChannelData(channel); + for (let i = 0; i < channelData.length; i++) { + view.setInt16(offset, channelData[i] * 0x7FFF, true); + offset += 2; + } + } + + return new Blob([buffer], { type: 'audio/wav' }); +} + +function setString(view, offset, string) { + for (let i = 0; i < string.length; i++) { + view.setUint8(offset + i, string.charCodeAt(i)); + } +} + +document.getElementById('uploadButton').addEventListener('click', async () => { + if (!processedAudioBlob) { + alert('No processed audio to send.'); + return; + } + + await uploadAudio(processedAudioBlob); +}); + +async function uploadAudio(blob) { + const formData = new FormData(); + formData.append('sound', blob, 'processed.wav'); + + try { + const dominio = window.location.hostname; + esn = urlParams.get("serial"); + const response = await fetch('/api-sdk/play_sound?serial='+esn, { + method: 'POST', + body: formData, + }); + + if (!response.ok) { + throw new Error('Erro to send audio: ' + response.statusText); + } + + const result = await response.json(); + console.log('Audio sent successfully:', result); + } catch (error) { + console.error('Error sending audio:', error); + } +} diff --git a/chipper/webroot/js/ssh.js b/chipper/webroot/js/ssh.js new file mode 100644 index 0000000..ecb026b --- /dev/null +++ b/chipper/webroot/js/ssh.js @@ -0,0 +1,66 @@ +function updateSSHStatus(statusString) { + setupStatus = document.getElementById("oskrSetupProgress"); + setupStatus.innerHTML = ""; + setupStatusP = document.createElement("p"); + setupStatusP.innerHTML = statusString; + setupStatus.appendChild(setupStatusP); +} + +function doSSHSetup() { + const ip = document.getElementById("sshIp").value; + const key = document.getElementById("sshKeyFile").files[0]; + + if (ip && key) { + const formData = new FormData(); + formData.append("key", key); + formData.append("ip", ip); + + fetch("/api-ssh/setup", { + method: "POST", + body: formData, + }) + .then((response) => response.text()) + .then((response) => { + if (response.includes("running")) { + document.getElementById("oskrSetup").style.display = "none"; + updateSSHSetup(); + return; + } else { + updateSSHStatus(response); + } + }); + } else { + updateSSHStatus("You must enter an IP address and upload a key."); + } +} + +function updateSSHSetup() { + interval = setInterval(function () { + fetch("/api-ssh/get_setup_status") + .then((response) => response.text()) + .then((response) => { + statusText = response; + if (response.includes("done")) { + updateSSHStatus( + "File transfer complete! Use the above section to complete bot setup. The bot should eventually be on the onboarding screen." + ); + document.getElementById("oskrSetup").style.display = "block"; + clearInterval(interval); + } else if (response.includes("error")) { + resp = response; + if (response.includes("no route to host")) { + resp = + "Wire-pod was unable to connect to the robot. Make sure the robot is running OSKR/dev software and that it is on the same network as this wire-pod instance. Also double-check the IP."; + } + updateSSHStatus(resp); + clearInterval(interval); + document.getElementById("oskrSetup").style.display = "block"; + return; + } else if (response.includes("not running")) { + updateSSHStatus("Initiating SSH transfer..."); + } else { + updateSSHStatus(response); + } + }); + }, 500); +} diff --git a/chipper/webroot/js/ui.js b/chipper/webroot/js/ui.js new file mode 100644 index 0000000..2ff70d3 --- /dev/null +++ b/chipper/webroot/js/ui.js @@ -0,0 +1,78 @@ +var fontMap = { + "droidsans": "DroidSans", + "ibmvga": "IBMVGA" +}; + +var colorMap = { + "teal": { + original: "#00ff80", + lighter: "#00ff80" + }, + "orange": { + original: "#ff3d0a", + lighter: "#ff3d0a" + }, + "yellow": { + original: "#ffeb00", + lighter: "#fff766" + }, + "lime": { + original: "#6aff00", + lighter: "#b3ff66" + }, + "sapphire": { + original: "#009aff", + lighter: "#66cdff" + }, + "purple": { + original: "#cd00c1", + lighter: "#e866dc" + }, + "green": { + original: "#00ff00", + lighter: "#66ff66" + } +}; + +function showUICustomizer() { + toggleVisibility(["section-log", "section-botauth", "section-intents", "section-version", "section-intents"], "section-uicustomizer", "icon-Customizer"); +} + +function setUIFont() { + let bodyFont = getValue("body-font-choose"); + document.documentElement.style.setProperty('--body-font-family', fontMap[bodyFont]); + localStorage.setItem('bodyFont', bodyFont); +} + +function setUIColor() { + let accentColor = colorMap[getValue("accent-color-choose")].lighter; + document.documentElement.style.setProperty('--fg-color', accentColor); + localStorage.setItem('accentColor', getValue("accent-color-choose")); +} + +function getValue(element) { + return document.getElementById(element).value; +} + +function loadSettings() { + let savedFont = localStorage.getItem('bodyFont'); + let savedColor = localStorage.getItem('accentColor'); + + if (savedFont) { + document.documentElement.style.setProperty('--body-font-family', fontMap[savedFont]); + if (document.getElementById("body-font-choose")) { + document.getElementById("body-font-choose").value = savedFont; + } + } + + if (savedColor) { + document.documentElement.style.setProperty('--fg-color', colorMap[savedColor].original); + if (document.getElementById("accent-color-choose")) { + document.getElementById("accent-color-choose").value = savedColor; + } + } +} + +// call loadSettings +loadSettings(); + diff --git a/chipper/webroot/sdkapp/control.html b/chipper/webroot/sdkapp/control.html new file mode 100644 index 0000000..5ff4623 --- /dev/null +++ b/chipper/webroot/sdkapp/control.html @@ -0,0 +1,116 @@ + + + + + Vector Web App + + + + + + + + + +
+
+

Vector control (beta)

+
+ +
+ +
+

Camera Feed

+
+
+
+ + +
+
+ +

Behavior Control

+
+ You must assume behavior control before performing any action +
+ + +
+
+ +

Move Motors

+
+ Toggle Keyboard Controls (unstable)
WASD for wheels, R-lift up, F-lift down, T-head + up, G-head down
+
+ + +
+
+ +

Mirror Mode

+
+
+ + +
+
+ +

Say Text

+
+ +
+ +
+

Play Audio

+
+ +
+ +
+ +
+
+
+
+
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/chipper/webroot/sdkapp/index.html b/chipper/webroot/sdkapp/index.html new file mode 100644 index 0000000..16da66b --- /dev/null +++ b/chipper/webroot/sdkapp/index.html @@ -0,0 +1,41 @@ + + + + + Vector SDK App + + + + + + + + + +
+
+

Vector Configuration

+
+ +
+

+ Choose the bot you would like to connect to then press "Connect". +

+
+ +
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/chipper/webroot/sdkapp/js/auth.js b/chipper/webroot/sdkapp/js/auth.js new file mode 100644 index 0000000..898d560 --- /dev/null +++ b/chipper/webroot/sdkapp/js/auth.js @@ -0,0 +1,32 @@ +var client = new HttpClient(); + +var botList = document.getElementById("botList"); + +getSDKInfo().then((jsonResp) => { + if (!botList) { + return; + } + for (var i = 0; i < jsonResp["robots"].length; i++) { + var option = document.createElement("option"); + option.text = jsonResp["robots"][i]["esn"]; + option.value = jsonResp["robots"][i]["esn"]; + botList.add(option); + } +}).catch((error) => { + console.error('Unable to get SDK info:', error); + alert("Error getting robot list. This likely means no bots are authenticated."); + window.location.href = "/"; +}); + +function connectSDK() { + var botList = document.getElementById("botList"); + fetch("/api-sdk/conn_test?serial=" + botList.value) + .then((response) => response.text()) + .then((response) => { + if (response.includes("success")) { + window.location.href = "./settings.html?serial=" + botList.value; + } else { + alert(response); + } + }); +} diff --git a/chipper/webroot/sdkapp/js/common.js b/chipper/webroot/sdkapp/js/common.js new file mode 100644 index 0000000..14aed91 --- /dev/null +++ b/chipper/webroot/sdkapp/js/common.js @@ -0,0 +1,42 @@ +// The purpose of this file is to provide common functions that are used by multiple pages (home, bot settings, etc). + + +async function getSDKInfo() { + try { + var response = await fetch("/api-sdk/get_sdk_info"); + console.log(response) + if (!response.ok) { + return undefined; + } + var data = await response.json(); + return data; + } catch (error) { + console.error('Unable to get SDK info:', error); + throw error; + } +} + + +async function getBatteryStatus(serial) { + if (!serial) { + return 'Serial number is required'; + } + + try { + var response = await fetch("/api-sdk/get_battery?serial=" + serial, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + signal: AbortSignal.timeout(15000) + }); + if (!response.ok) { + return undefined; + } + var data = await response.json(); // {"status":{"code":1},"battery_level":3,"battery_volts":3.9210937,"is_on_charger_platform":true} + return data; + } catch (error) { + console.error('Unable to get battery status:', error); + throw error; + } +} \ No newline at end of file diff --git a/chipper/webroot/sdkapp/js/control.js b/chipper/webroot/sdkapp/js/control.js new file mode 100644 index 0000000..e3e6c05 --- /dev/null +++ b/chipper/webroot/sdkapp/js/control.js @@ -0,0 +1,365 @@ +var x = document.getElementById("sdkActions"); +var keysKey = document.getElementById("keysKey"); +var useKeyboardControl = false; +var camStream = document.getElementById("camStream"); +var urlParams = new URLSearchParams(window.location.search); +esn = urlParams.get("serial"); + +// wheels +var isMovingForward = false; +var isMovingLeft = false; +var isMovingRight = false; +var isMovingFL = false; +var isMovingFR = false; +var isMovingBack = false; +var isMovingBL = false; +var isMovingBR = false; +var isStopped = false; + +// lift +var liftIsMovingDown = false; +var liftIsMovingUp = false; +var liftIsStopped = false; + +// head +var headIsMovingDown = false; +var headIsMovingUp = false; +var headIsStopped = false; + +// when pages is closed +window.onbeforeunload = function() { + stopCamStream(); + useKeyboardControl = false; + sendForm("/api-sdk/mirror_mode?enable=false"); +}; + +// switches items to disabled or enables them +function updateControlButtons() { + const assumeControl = document.getElementById("assume_control"); + const releaseControl = document.getElementById("release_control"); + const keyControlOn = document.getElementById("key_control_on"); + const keyControlOff = document.getElementById("key_control_off"); + const keyMirrorOn = document.getElementById("mirror_on"); + const keyMirrorOff = document.getElementById("mirror_off"); + const textSay = document.getElementById("textSay"); + + if (assumeControl.checked) { + keyControlOn.disabled = false; + keyControlOff.disabled = false; + keyMirrorOn.disabled = false; + keyMirrorOff.disabled = false; + textSay.disabled = false; + } else if (releaseControl.checked) { + useKeyboardControl = false; + keyControlOff.checked = true; + keyControlOn.disabled = true; + keyControlOff.disabled = true; + sendForm("/api-sdk/mirror_mode?enable=false"); + keyMirrorOff.checked = true; + keyMirrorOn.disabled = true; + keyMirrorOff.disabled = true; + textSay.disabled = true; + } +} + +// Add event listener to activate keyControlOff when textSay receives the focus +document.addEventListener('DOMContentLoaded', function() { + const textSay = document.getElementById("textSay"); + const keyControlOff = document.getElementById("key_control_off"); + + textSay.addEventListener('focus', function() { + //keyControlOff.disabled = false; + keyControlOff.checked = true; + useKeyboardControl = false; + }); +}); + + +function toggleKeyboard() { + if (useKeyboardControl == false) { + useKeyboardControl = true; + keysKey.style.display = "block"; + } else { + useKeyboardControl = false; + keysKey.style.display = "none"; + } +} + +function goBackToSettings() { + sendForm("/api-sdk/release_behavior_control"); + sendForm("/api-sdk/stop_cam_stream"); + window.location.href = "./settings.html?serial=" + esn; +} + +function sdkUnInit() { + sendForm("/api-sdk/release_behavior_control"); + sendForm("/api-sdk/stop_cam_stream"); + var x = document.getElementById("sdkActions"); +} + +function sendForm(formURL) { + let xhr = new XMLHttpRequest(); + if (formURL.includes("?")) { + formURL = formURL + "&serial=" + esn; + } else { + formURL = formURL + "?serial=" + esn; + } + xhr.open("POST", formURL); + xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + xhr.send(); +} + +let keysPressed = {}; + +var stream = document.createElement("img"); + +function showCamStream() { + //sendForm('/api-sdk/begin_cam_stream') + stream.src = "/cam-stream?serial=" + esn; + document.getElementById("camStream").appendChild(stream); +} + +function stopCamStream() { + stream.src = ""; + document.getElementById("camStream").removeChild(stream); + sendForm("/api-sdk/stop_cam_stream"); +} + +function sayText() { + sayTextValue = document.getElementById("textSay").value; + sendForm("/api-sdk/say_text?text=" + sayTextValue); +} + +keysPressed["w"] = false; +keysPressed["a"] = false; +keysPressed["s"] = false; +keysPressed["d"] = false; + +keysPressed["r"] = false; +keysPressed["f"] = false; + +keysPressed["t"] = false; +keysPressed["g"] = false; + +document.addEventListener("keyup", (event) => { + keysPressed[event.key] = false; + sendControlRequests(); +}); + +document.addEventListener("keydown", function (event) { + keysPressed[event.key] = true; + sendControlRequests(); +}); + +function sendControlRequests() { + if (useKeyboardControl == true) { + if ( + keysPressed["w"] == false && + keysPressed["a"] == false && + keysPressed["s"] == false && + keysPressed["d"] == false + ) { + if (isStopped == false) { + sendForm("/api-sdk/move_wheels?lw=0&rw=0"); + sendForm("/api-sdk/move_wheels?lw=0&rw=0"); + } + isStopped = true; + isMovingForward = false; + isMovingBL = false; + isMovingBR = false; + isMovingBack = false; + isMovingFL = false; + isMovingFR = false; + isMovingLeft = false; + isMovingRight = false; + } else if ( + keysPressed["w"] == true && + keysPressed["a"] == false && + keysPressed["s"] == false && + keysPressed["d"] == false + ) { + if (isMovingForward == false) { + sendForm("/api-sdk/move_wheels?lw=140&rw=140"); + } + isStopped = false; + isMovingForward = true; + isMovingBL = false; + isMovingBR = false; + isMovingBack = false; + isMovingFL = false; + isMovingFR = false; + isMovingLeft = false; + isMovingRight = false; + } else if ( + keysPressed["w"] == true && + keysPressed["a"] == true && + keysPressed["s"] == false && + keysPressed["d"] == false + ) { + if (isMovingFL == false) { + sendForm("/api-sdk/move_wheels?lw=100&rw=190"); + } + isStopped = false; + isMovingForward = false; + isMovingBL = false; + isMovingBR = false; + isMovingBack = false; + isMovingFL = true; + isMovingFR = false; + isMovingLeft = false; + isMovingRight = false; + } else if ( + keysPressed["w"] == true && + keysPressed["a"] == false && + keysPressed["s"] == false && + keysPressed["d"] == true + ) { + if (isMovingFR == false) { + sendForm("/api-sdk/move_wheels?lw=190&rw=100"); + } + isStopped = false; + isMovingForward = false; + isMovingBL = false; + isMovingBR = false; + isMovingBack = false; + isMovingFL = false; + isMovingFR = true; + isMovingLeft = false; + isMovingRight = false; + } else if ( + keysPressed["w"] == false && + keysPressed["a"] == false && + keysPressed["s"] == false && + keysPressed["d"] == true + ) { + if (isMovingRight == false) { + sendForm("/api-sdk/move_wheels?lw=150&rw=-150"); + } + isStopped = false; + isMovingForward = false; + isMovingBL = false; + isMovingBR = false; + isMovingBack = false; + isMovingFL = false; + isMovingFR = false; + isMovingLeft = false; + isMovingRight = true; + } else if ( + keysPressed["w"] == false && + keysPressed["a"] == true && + keysPressed["s"] == false && + keysPressed["d"] == false + ) { + if (isMovingLeft == false) { + sendForm("/api-sdk/move_wheels?lw=-150&rw=150"); + } + isStopped = false; + isMovingForward = false; + isMovingBL = false; + isMovingBR = false; + isMovingBack = false; + isMovingFL = false; + isMovingFR = false; + isMovingLeft = true; + isMovingRight = false; + } else if ( + keysPressed["w"] == false && + keysPressed["a"] == false && + keysPressed["s"] == true && + keysPressed["d"] == false + ) { + if (isMovingBack == false) { + sendForm("/api-sdk/move_wheels?lw=-150&rw=-150"); + } + isStopped = false; + isMovingForward = false; + isMovingBL = false; + isMovingBR = false; + isMovingBack = true; + isMovingFL = false; + isMovingFR = false; + isMovingLeft = false; + isMovingRight = false; + } else if ( + keysPressed["w"] == false && + keysPressed["a"] == true && + keysPressed["s"] == true && + keysPressed["d"] == false + ) { + if (isMovingBL == false) { + sendForm("/api-sdk/move_wheels?lw=-100&rw=190"); + } + isStopped = false; + isMovingForward = false; + isMovingBL = true; + isMovingBR = false; + isMovingBack = false; + isMovingFL = false; + isMovingFR = false; + isMovingLeft = false; + isMovingRight = false; + } else if ( + keysPressed["w"] == false && + keysPressed["a"] == false && + keysPressed["s"] == true && + keysPressed["d"] == true + ) { + if (isMovingBR == false) { + sendForm("/api-sdk/move_wheels?lw=-190&rw=100"); + } + isStopped = false; + isMovingForward = false; + isMovingBL = false; + isMovingBR = true; + isMovingBack = false; + isMovingFL = false; + isMovingFR = false; + isMovingLeft = false; + isMovingRight = false; + } + if (keysPressed["r"] == false && keysPressed["f"] == false) { + if (liftIsStopped == false) { + sendForm("/api-sdk/move_lift?speed=0"); + } + liftIsStopped = true; + liftIsMovingDown = false; + liftIsMovingUp = false; + } else if (keysPressed["r"] == true && keysPressed["f"] == false) { + if (liftIsMovingUp == false) { + sendForm("/api-sdk/move_lift?speed=2"); + } + liftIsStopped = false; + liftIsMovingDown = false; + liftIsMovingUp = true; + } else if (keysPressed["r"] == false && keysPressed["f"] == true) { + if (liftIsMovingUp == false) { + sendForm("/api-sdk/move_lift?speed=-2"); + } + liftIsStopped = false; + liftIsMovingDown = true; + liftIsMovingUp = false; + } + if (keysPressed["t"] == false && keysPressed["g"] == false) { + if (headIsStopped == false) { + sendForm("/api-sdk/move_head?speed=0"); + } + headIsStopped = true; + headIsMovingDown = false; + headIsMovingUp = false; + } else if (keysPressed["t"] == true && keysPressed["g"] == false) { + if (headIsMovingUp == false) { + sendForm("/api-sdk/move_head?speed=2"); + } + headIsStopped = false; + headIsMovingDown = false; + headIsMovingUp = true; + } else if (keysPressed["t"] == false && keysPressed["g"] == true) { + if (headIsMovingUp == false) { + sendForm("/api-sdk/move_head?speed=-2"); + } + headIsStopped = false; + headIsMovingDown = true; + headIsMovingUp = false; + } + } +} diff --git a/chipper/webroot/sdkapp/js/faces.js b/chipper/webroot/sdkapp/js/faces.js new file mode 100644 index 0000000..7cb18c2 --- /dev/null +++ b/chipper/webroot/sdkapp/js/faces.js @@ -0,0 +1,121 @@ +var client = new HttpClient(); + +var urlParams = new URLSearchParams(window.location.search); +esn = urlParams.get("serial"); + +showFaceButtons = false; + +var areThereFaces = false; + +function refreshFaceList() { + var x = document.getElementById("faceList"); + x.innerHTML = ""; + fetch("/api-sdk/get_faces?serial=" + esn) + .then((response) => response.text()) + .then((response) => { + if (response.includes("null")) { + console.log("no faces exist."); + showFaceButtons = false; + var option = document.createElement("option"); + option.text = "No faces found. You must tell Vector your name."; + option.value = "none"; + areThereFaces = false; + x.add(option); + } else { + areThereFaces = true; + jsonResp = JSON.parse(response); + showFaceButtons = true; + for (var i = 0; i < jsonResp.length; i++) { + var option = document.createElement("option"); + option.text = jsonResp[i]["name"]; + option.value = jsonResp[i]["face_id"] + ":" + jsonResp[i]["name"]; + x.add(option); + } + if (showFaceButtons == true) { + document.getElementById("faceButtons").style.display = "block"; + } else { + document.getElementById("faceButtons").style.display = "none"; + } + } + }); +} + +refreshFaceList(); + +/* +function showFaceSection() { + id = "section-faces" + var headings = document.getElementsByClassName("toggleable-section"); + for (var i = 0; i < headings.length; i++) { + headings[i].style.display = "none"; + } + document.getElementById(id).style.display = "block"; + console.log(showFaceButtons) + if (showFaceButtons == true) { + document.getElementById("faceButtons").style.display = "block"; + } else { + document.getElementById("faceButtons").style.display = "none"; + } +} +*/ + +function renameFace() { + if (!areThereFaces) { + window.alert("You must register a face first."); + } else { + var x = document.getElementById("faceList"); + oldFaceName = x.value.split(":")[1]; + faceId = x.value.split(":")[0]; + newFaceName = window.prompt("Enter the new name here:"); + console.log(newFaceName); + if (newFaceName == "") { + window.alert("Face name cannot be empty"); + } else { + fetch( + "/api-sdk/rename_face?serial=" + + esn + + "&oldname=" + + oldFaceName + + "&id=" + + faceId + + "&newname=" + + newFaceName + ).then(function () { + alert("Success!"); + refreshFaceList(); + }); + } + } +} + +function addFace() { + var name = document.getElementById("faceInput").value; + if (name == "") { + alert("You must enter a name."); + return; + } else { + fetch("/api-sdk/add_face?serial=" + esn + "&name=" + name).then( + function () { + alert( + "Request successfully sent. Vector should now be finding a face to scan." + ); + refreshFaceList(); + } + ); + } +} + +function deleteFace() { + if (!areThereFaces) { + window.alert("You must register a face first."); + } else { + var x = document.getElementById("faceList"); + faceId = x.value.split(":")[0]; + fetch("/api-sdk/delete_face?serial=" + esn + "&id=" + faceId).then( + function () { + alert("Success!"); + refreshFaceList(); + } + ); + } +} diff --git a/chipper/webroot/sdkapp/js/httprequest.js b/chipper/webroot/sdkapp/js/httprequest.js new file mode 100644 index 0000000..76dc41f --- /dev/null +++ b/chipper/webroot/sdkapp/js/httprequest.js @@ -0,0 +1,12 @@ +var HttpClient = function () { + this.get = function (aUrl, aCallback) { + var anHttpRequest = new XMLHttpRequest(); + anHttpRequest.onreadystatechange = function () { + if (anHttpRequest.readyState == 4 && anHttpRequest.status == 200) + aCallback(anHttpRequest.responseText); + }; + + anHttpRequest.open("GET", aUrl, true); + anHttpRequest.send(null); + }; +}; diff --git a/chipper/webroot/sdkapp/js/iro.min.js b/chipper/webroot/sdkapp/js/iro.min.js new file mode 100644 index 0000000..2d20321 --- /dev/null +++ b/chipper/webroot/sdkapp/js/iro.min.js @@ -0,0 +1,7 @@ +/*! + * iro.js v5.5.2 + * 2016-2021 James Daniel + * Licensed under MPL 2.0 + * github.com/jaames/iro.js + */ +!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(t=t||self).iro=n()}(this,function(){"use strict";var m,s,n,i,o,x={},j=[],r=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|^--/i;function M(t,n){for(var i in n)t[i]=n[i];return t}function y(t){var n=t.parentNode;n&&n.removeChild(t)}function h(t,n,i){var r,e,u,o,l=arguments;if(n=M({},n),3=r/i?u=n:e=n}return n},function(t,n,i){n&&g(t.prototype,n),i&&g(t,i)}(l,[{key:"hsv",get:function(){var t=this.$;return{h:t.h,s:t.s,v:t.v}},set:function(t){var n=this.$;if(t=b({},n,t),this.onChange){var i={h:!1,v:!1,s:!1,a:!1};for(var r in n)i[r]=t[r]!=n[r];this.$=t,(i.h||i.s||i.v||i.a)&&this.onChange(this,i)}else this.$=t}},{key:"hsva",get:function(){return b({},this.$)},set:function(t){this.hsv=t}},{key:"hue",get:function(){return this.$.h},set:function(t){this.hsv={h:t}}},{key:"saturation",get:function(){return this.$.s},set:function(t){this.hsv={s:t}}},{key:"value",get:function(){return this.$.v},set:function(t){this.hsv={v:t}}},{key:"alpha",get:function(){return this.$.a},set:function(t){this.hsv=b({},this.hsv,{a:t})}},{key:"kelvin",get:function(){return l.rgbToKelvin(this.rgb)},set:function(t){this.rgb=l.kelvinToRgb(t)}},{key:"red",get:function(){return this.rgb.r},set:function(t){this.rgb=b({},this.rgb,{r:t})}},{key:"green",get:function(){return this.rgb.g},set:function(t){this.rgb=b({},this.rgb,{g:t})}},{key:"blue",get:function(){return this.rgb.b},set:function(t){this.rgb=b({},this.rgb,{b:t})}},{key:"rgb",get:function(){var t=l.hsvToRgb(this.$),n=t.r,i=t.g,r=t.b;return{r:G(n),g:G(i),b:G(r)}},set:function(t){this.hsv=b({},l.rgbToHsv(t),{a:void 0===t.a?1:t.a})}},{key:"rgba",get:function(){return b({},this.rgb,{a:this.alpha})},set:function(t){this.rgb=t}},{key:"hsl",get:function(){var t=l.hsvToHsl(this.$),n=t.h,i=t.s,r=t.l;return{h:G(n),s:G(i),l:G(r)}},set:function(t){this.hsv=b({},l.hslToHsv(t),{a:void 0===t.a?1:t.a})}},{key:"hsla",get:function(){return b({},this.hsl,{a:this.alpha})},set:function(t){this.hsl=t}},{key:"rgbString",get:function(){var t=this.rgb;return"rgb("+t.r+", "+t.g+", "+t.b+")"},set:function(t){var n,i,r,e,u=1;if((n=_.exec(t))?(i=K(n[1],255),r=K(n[2],255),e=K(n[3],255)):(n=H.exec(t))&&(i=K(n[1],255),r=K(n[2],255),e=K(n[3],255),u=K(n[4],1)),!n)throw new Error("Invalid rgb string");this.rgb={r:i,g:r,b:e,a:u}}},{key:"rgbaString",get:function(){var t=this.rgba;return"rgba("+t.r+", "+t.g+", "+t.b+", "+t.a+")"},set:function(t){this.rgbString=t}},{key:"hexString",get:function(){var t=this.rgb;return"#"+U(t.r)+U(t.g)+U(t.b)},set:function(t){var n,i,r,e,u=255;if((n=D.exec(t))?(i=17*Q(n[1]),r=17*Q(n[2]),e=17*Q(n[3])):(n=F.exec(t))?(i=17*Q(n[1]),r=17*Q(n[2]),e=17*Q(n[3]),u=17*Q(n[4])):(n=L.exec(t))?(i=Q(n[1]),r=Q(n[2]),e=Q(n[3])):(n=B.exec(t))&&(i=Q(n[1]),r=Q(n[2]),e=Q(n[3]),u=Q(n[4])),!n)throw new Error("Invalid hex string");this.rgb={r:i,g:r,b:e,a:u/255}}},{key:"hex8String",get:function(){var t=this.rgba;return"#"+U(t.r)+U(t.g)+U(t.b)+U(Z(255*t.a))},set:function(t){this.hexString=t}},{key:"hslString",get:function(){var t=this.hsl;return"hsl("+t.h+", "+t.s+"%, "+t.l+"%)"},set:function(t){var n,i,r,e,u=1;if((n=P.exec(t))?(i=K(n[1],360),r=K(n[2],100),e=K(n[3],100)):(n=$.exec(t))&&(i=K(n[1],360),r=K(n[2],100),e=K(n[3],100),u=K(n[4],1)),!n)throw new Error("Invalid hsl string");this.hsl={h:i,s:r,l:e,a:u}}},{key:"hslaString",get:function(){var t=this.hsla;return"hsla("+t.h+", "+t.s+"%, "+t.l+"%, "+t.a+")"},set:function(t){this.hslString=t}}]),l}();function X(t){var n,i=t.width,r=t.sliderSize,e=t.borderWidth,u=t.handleRadius,o=t.padding,l=t.sliderShape,s="horizontal"===t.layoutDirection;return r=null!=(n=r)?n:2*o+2*u,"circle"===l?{handleStart:t.padding+t.handleRadius,handleRange:i-2*o-2*u,width:i,height:i,cx:i/2,cy:i/2,radius:i/2-e/2}:{handleStart:r/2,handleRange:i-r,radius:r/2,x:0,y:0,width:s?r:i,height:s?i:r}}function Y(t,n){var i=X(t),r=i.width,e=i.height,u=i.handleRange,o=i.handleStart,l="horizontal"===t.layoutDirection,s=l?r/2:e/2,c=o+function(t,n){var i=n.hsva,r=n.rgb;switch(t.sliderType){case"red":return r.r/2.55;case"green":return r.g/2.55;case"blue":return r.b/2.55;case"alpha":return 100*i.a;case"kelvin":var e=t.minTemperature,u=t.maxTemperature-e,o=(n.kelvin-e)/u*100;return Math.max(0,Math.min(o,100));case"hue":return i.h/=3.6;case"saturation":return i.s;case"value":default:return i.v}}(t,n)/100*u;return l&&(c=-1*c+u+2*o),{x:l?s:c,y:l?c:s}}var tt,nt=2*Math.PI,it=function(t,n){return(t%n+n)%n},rt=function(t,n){return Math.sqrt(t*t+n*n)};function et(t){return t.width/2-t.padding-t.handleRadius-t.borderWidth}function ut(t){var n=t.width/2;return{width:t.width,radius:n-t.borderWidth,cx:n,cy:n}}function ot(t,n,i){var r=t.wheelAngle,e=t.wheelDirection;return i&&"clockwise"===e?n=r+n:"clockwise"===e?n=360-r+n:i&&"anticlockwise"===e?n=r+180-n:"anticlockwise"===e&&(n=r-n),it(n,360)}function lt(t,n,i){var r=ut(t),e=r.cx,u=r.cy,o=et(t);n=e-n,i=u-i;var l=ot(t,Math.atan2(-i,-n)*(360/nt)),s=Math.min(rt(n,i),o);return{h:Math.round(l),s:Math.round(100/o*s)}}function st(t){var n=t.width,i=t.boxHeight;return{width:n,height:null!=i?i:n,radius:t.padding+t.handleRadius}}function ct(t,n,i){var r=st(t),e=r.width,u=r.height,o=r.radius,l=(n-o)/(e-2*o)*100,s=(i-o)/(u-2*o)*100;return{s:Math.max(0,Math.min(l,100)),v:Math.max(0,Math.min(100-s,100))}}function at(t,n,i,r){for(var e=0;e { + if (stimRunning == false) { + sendForm("/api-sdk/stop_event_stream"); + clearInterval(interval); + } + fetch("/api-sdk/get_stim_status?serial=" + esn) + .then((response) => response.json()) + .then((data) => { + // add the data to the array + stimData.push(data); + + // if the array has more than 12 datapoints, remove the first one + if (stimData.length > 12) { + stimData.shift(); + } + + // update the chart with the new data + myChart.data.labels = []; + myChart.data.datasets[0].data = []; + //var time = new Date().toLocaleTimeString([], {hour12: false}); + stimData.forEach((datapoint) => { + myChart.data.labels.push(" "); + myChart.data.datasets[0].data.push(datapoint); + }); + + myChart.update(); + }); + }, 500); +} + +function showSection(id) { + var headings = document.getElementsByClassName("toggleable-section"); + for (var i = 0; i < headings.length; i++) { + headings[i].style.display = "none"; + } + document.getElementById(id).style.display = "block"; + updateColor(id); + if (id == "section-stim") { + logDiv = document.getElementById("stimStatus"); + logP = document.createElement("p"); + stimRunning = true; + sendForm("/api-sdk/begin_event_stream"); + stimHandler(); + } else { + stimRunning = false; + } +} + +function sendForm(formURL) { + let xhr = new XMLHttpRequest(); + if (formURL.includes("?")) { + formURL = formURL + "&serial=" + esn; + } else { + formURL = formURL + "?serial=" + esn; + } + xhr.open("POST", formURL); + xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + xhr.send(); + xhr.onload = function () { + getCurrentSettings(); + }; +} + +function getPhotos() { + photoSection = document.getElementById("photoSection"); + let xhr = new XMLHttpRequest(); + xhr.open("POST", "/api-sdk/get_image_ids?serial=" + esn); + xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + xhr.send(); + xhr.onload = function () { + photoSection.innerHTML = ""; + if (xhr.response == "null") { + var noPhotos = document.createElement("p"); + noPhotos.innerHTML = + "No photos found. Tell Vector to take a photo, then refresh the list."; + photoSection.appendChild(noPhotos); + return; + } + imageIds = JSON.parse(xhr.response); + for (var i = 0; i < imageIds.length; i++) { + imgId = imageIds[i]; + var thumb = document.createElement("div"); + var thumbLink = document.createElement("a"); + var thumbPic = document.createElement("img"); + var thumbDelete = document.createElement("button"); + thumbPic.src = "/api-sdk/get_image_thumb?serial=" + esn + "&id=" + imgId; + //thumb.classList = "center" + thumbLink.classList = "center"; + thumbDelete.classList = "center"; + thumbLink.href = "/api-sdk/get_image?serial=" + esn + "&id=" + imgId; + thumbLink.appendChild(thumbPic); + thumbDelete.onclick = function () { + deletePhoto(imgId); + }; + thumbDelete.innerHTML = "Delete"; + thumb.appendChild(thumbLink); + thumb.appendChild(thumbDelete); + photoSection.appendChild(thumb); + } + }; +} + +function deletePhoto(id) { + if (confirm("Are you sure?")) { + // run code here + let xhr = new XMLHttpRequest(); + xhr.open("POST", "/api-sdk/delete_image?serial=" + esn + "&id=" + id); + xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + xhr.send(); + xhr.onload = function () { + getPhotos(); + }; + } +} + +function goToControlPage() { + window.location.href = "./control.html?serial=" + esn; +} + +function sendLocation() { + locationInput = document.getElementById("locationInput").value; + if (locationInput == "") { + alert("Location cannot be blank."); + return; + } + let xhr = new XMLHttpRequest(); + xhr.open( + "POST", + "/api-sdk/location?serial=" + esn + "&location=" + locationInput + ); + xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + xhr.send(); + xhr.onload = function () { + getCurrentSettings(); + }; +} + +function sendTimeZone() { + timezone = document.getElementById("tzInput").value; + if (timezone == "") { + alert("Time zone cannot be blank."); + return; + } + let xhr = new XMLHttpRequest(); + xhr.open("POST", "/api-sdk/timezone?serial=" + esn + "&timezone=" + timezone); + xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + xhr.send(); + xhr.onload = function () { + getCurrentSettings(); + }; +} + +function sendCustomColor() { + var pickerHue = colorPicker.color.hue; + var pickerSat = colorPicker.color.saturation; + var sendHue = pickerHue / 360; + var sendHue = sendHue.toFixed(3); + var sendSat = pickerSat / 100; + var sendSat = sendSat.toFixed(3); + let data = "hue=" + sendHue + "&" + "sat=" + sendSat; + let xhr = new XMLHttpRequest(); + xhr.open("POST", "/api-sdk/custom_eye_color?serial=" + esn); + xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + xhr.send(data); + xhr.onload = function () { + getCurrentSettings(); + }; +} + +function updateStats() { + let xhr = new XMLHttpRequest(); + xhr.open("POST", "/api-sdk/get_robot_stats?serial=" + esn); + xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + xhr.setRequestHeader("Cache-Control", "no-cache, no-store, max-age=0"); + xhr.responseType = "json"; + xhr.send(); + xhr.onload = function () { + var statsJson = JSON.stringify(xhr.response); + var jdocthing = JSON.parse(statsJson); + var jdocSecs = jdocthing["Alive.seconds"]; + var secondsInADay = 24 * 60 * 60; // Total seconds in a day + var jdocDays = Math.round(jdocSecs / secondsInADay); + var jdocTrigger = jdocthing["BStat.ReactedToTriggerWord"]; + var featuresUsed = jdocthing["FeatureType.Utility"]; + var petMs = jdocthing["Pet.ms"]; + var cmMoved = jdocthing["Stim.CumlPosDelta"]; + + var s1 = document.getElementById("statsSection"); + s1.innerHTML = ""; + var s1p1 = document.createElement("p"); + s1p1.textContent = "Days alive: " + jdocDays; + var s1p2 = document.createElement("p"); + s1p2.textContent = "Reacted to trigger word: " + jdocTrigger + " times"; + var s1p3 = document.createElement("p"); + s1p3.textContent = "Utility features used: " + featuresUsed; + var s1p4 = document.createElement("p"); + s1p4.textContent = "Seconds petted: " + Math.round(petMs / 1000); + var s1p5 = document.createElement("p"); + s1p5.textContent = "Distance moved (cm): " + Math.round(cmMoved / 100); + s1.appendChild(s1p1); + s1.appendChild(s1p2); + s1.appendChild(s1p3); + s1.appendChild(s1p4); + s1.appendChild(s1p5); + //{ + // "Alive.seconds" : 1619, + // "BStat.ReactedToTriggerWord" : 17, + // "FeatureType.Utility" : 2, + // "Pet.ms" : 3480, + // "Stim.CumlPosDelta" : 11148 + // } + }; +} + +function getCurrentSettings() { + updateStats(); + let xhr = new XMLHttpRequest(); + xhr.open("POST", "/api-sdk/get_sdk_settings?serial=" + esn); + xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + xhr.setRequestHeader("Cache-Control", "no-cache, no-store, max-age=0"); + xhr.responseType = "json"; + xhr.send(); + xhr.onload = function () { + var jdocSdkSettingsResponse1 = JSON.stringify(xhr.response); + jdocSdk = JSON.parse(jdocSdkSettingsResponse1); + let xhr2 = new XMLHttpRequest(); + if (jdocSdk["custom_eye_color"]) { + var customECE = jdocSdk["custom_eye_color"]["enabled"]; + var customECH = jdocSdk["custom_eye_color"]["hue"]; + var customECS = jdocSdk["custom_eye_color"]["saturation"]; + } + eyeColorS = jdocSdk["eye_color"]; + var volumeS = jdocSdk["master_volume"]; + var localeS = jdocSdk["locale"]; + var timeSetS = jdocSdk["clock_24_hour"]; + var tempFormatS = jdocSdk["temp_is_fahrenheit"]; + var buttonS = jdocSdk["button_wakeword"]; + var location = jdocSdk["default_location"]; + var timezone = jdocSdk["time_zone"]; + if (jdocSdk["custom_eye_color"]) { + if (`${customECE}` == "true") { + var setHue = customECH * 360; + var setHue = setHue.toFixed(3); + var setSat = customECS * 100; + var setSat = setSat.toFixed(3); + colorPicker.color.hsl = { h: setHue, s: setSat, l: 50 }; + var eyeColorT = "Custom"; + } else { + if (`${eyeColorS}` == 0) { + eyeColorT = "Teal"; + } else if (`${eyeColorS}` == 1) { + eyeColorT = "Orange"; + } else if (`${eyeColorS}` == 2) { + eyeColorT = "Yellow"; + } else if (`${eyeColorS}` == 3) { + eyeColorT = "Lime Green"; + } else if (`${eyeColorS}` == 4) { + eyeColorT = "Azure Blue"; + } else if (`${eyeColorS}` == 5) { + eyeColorT = "Purple"; + } else if (`${eyeColorS}` == 6) { + eyeColorT = "Other Green"; + } else { + eyeColorT = "none"; + } + } + } else { + if (`${eyeColorS}` == 0) { + eyeColorT = "Teal"; + } else if (`${eyeColorS}` == 1) { + eyeColorT = "Orange"; + } else if (`${eyeColorS}` == 2) { + eyeColorT = "Yellow"; + } else if (`${eyeColorS}` == 3) { + eyeColorT = "Lime Green"; + } else if (`${eyeColorS}` == 4) { + eyeColorT = "Azure Blue"; + } else if (`${eyeColorS}` == 5) { + eyeColorT = "Purple"; + } else if (`${eyeColorS}` == 6) { + eyeColorT = "Other Green"; + } else { + eyeColorT = "none"; + } + } + if (`${volumeS}` == 0) { + var volumeT = "Mute"; + } else if (`${volumeS}` == 1) { + var volumeT = "Low"; + } else if (`${volumeS}` == 2) { + var volumeT = "Medium Low"; + } else if (`${volumeS}` == 3) { + var volumeT = "Medium"; + } else if (`${volumeS}` == 4) { + var volumeT = "Medium High"; + } else if (`${volumeS}` == 5) { + var volumeT = "High"; + } else { + var volumeT = "none"; + } + if (`${timeSetS}` == "false") { + var timeSetT = "12 Hour"; + } else { + var timeSetT = "24 Hour"; + } + if (`${tempFormatS}` == "true") { + var tempFormatT = "Fahrenheit"; + } else { + var tempFormatT = "Celsius"; + } + if (`${buttonS}` == 0) { + var buttonT = "Hey Vector"; + } else { + var buttonT = "Alexa"; + } + + var s1 = document.getElementById("currentVolume"); + const s1P = document.createElement("p"); + document.getElementById(volumeT).checked = true; + + var s2 = document.getElementById("currentEyeColor"); + const s2P = document.createElement("p"); + if (eyeColorT != "none" && eyeColorT != "Custom") { + document.getElementById(eyeColorT).checked = true; + } + + var s3 = document.getElementById("currentLocale"); + const s3P = document.createElement("p"); + document.getElementById(localeS).checked = true; + + var s4 = document.getElementById("currentTimeSet"); + const s4P = document.createElement("p"); + document.getElementById(timeSetT).checked = true; + + var s5 = document.getElementById("currentTempFormat"); + const s5P = document.createElement("p"); + document.getElementById(tempFormatT).checked = true; + + var s6 = document.getElementById("currentButton"); + const s6P = document.createElement("p"); + document.getElementById(buttonT).checked = true; + + var s10 = document.getElementById("currentLocation"); + const s10P = document.createElement("p"); + s10P.textContent = "Current Location Setting: " + `${location}`; + document.getElementById("locationInput").placeholder = `${location}`; + s10.innerHTML = ""; + s10.appendChild(s10P); + + var s11 = document.getElementById("currentTimeZone"); + const s11P = document.createElement("p"); + s11P.textContent = "Current Time Zone Setting: " + `${timezone}`; + document.getElementById("tzInput").value = `${timezone}`; + s11.innerHTML = ""; + s11.appendChild(s11P); + }; +} + +renderBatteryInfo(esn); \ No newline at end of file diff --git a/chipper/webroot/sdkapp/js/settings.js b/chipper/webroot/sdkapp/js/settings.js new file mode 100644 index 0000000..2f7b47f --- /dev/null +++ b/chipper/webroot/sdkapp/js/settings.js @@ -0,0 +1,15 @@ +function updateColor(id) { + console.log(id); + var body_styles = window.getComputedStyle( + document.getElementsByTagName("body")[0] + ); + var fgColor = body_styles.getPropertyValue("--fg-color"); + var bgColorAlt = body_styles.getPropertyValue("--gg-color-alt"); + + l_id = id.replace("section", "icon"); + let elements = document.getElementsByName("icon"); + for (var i = 0; i < elements.length; i++) { + document.getElementById(elements[i].id).style.color = bgColorAlt; + } + document.getElementById(l_id).style.color = fgColor; +} diff --git a/chipper/webroot/sdkapp/settings.html b/chipper/webroot/sdkapp/settings.html new file mode 100644 index 0000000..2e950aa --- /dev/null +++ b/chipper/webroot/sdkapp/settings.html @@ -0,0 +1,394 @@ + + + + + Vector Web App + + + + + + + + + + +
+
+

Vector Configuration

+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + diff --git a/chipper/webroot/sessions b/chipper/webroot/sessions new file mode 100644 index 0000000..1b781b5 --- /dev/null +++ b/chipper/webroot/sessions @@ -0,0 +1 @@ +{"session":{"session_token":"2vMhFgktH3Jrbemm2WHkfGN","user_id":"2gsE4HbQ8UCBpYqurDgsafX","scope":"user","time_created":"2022-11-26T21:04:03.952175849Z","time_expires":"2023-11-26T21:04:03.952158558Z"}} \ No newline at end of file diff --git a/chipper/webroot/setup.html b/chipper/webroot/setup.html new file mode 100644 index 0000000..8f14345 --- /dev/null +++ b/chipper/webroot/setup.html @@ -0,0 +1,250 @@ + + + + + Wire-Pod Setup + + + + + + + + + +
+
+

Wire-Pod Setup

+
+ +
+ + + + + + + + +
+
+ + + + + + + diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..b71faba --- /dev/null +++ b/compose.yaml @@ -0,0 +1,19 @@ +version: '3.8' +services: + wire-pod: + hostname: escapepod + image: ghcr.io/kercre123/wire-pod:main + restart: unless-stopped + ports: + - 443:443 + - 8080:8080 + - 80:80 + - 8084:8084 + volumes: + - wire-pod-data:/chipper/ + - wire-pod-model:/vosk/ +volumes: + wire-pod-data: + driver: local + wire-pod-model: + driver: local diff --git a/dockerfile b/dockerfile new file mode 100644 index 0000000..2923d7c --- /dev/null +++ b/dockerfile @@ -0,0 +1,12 @@ +FROM ubuntu + + +COPY . . + +RUN chmod +x /setup.sh && apt-get update && apt-get install -y dos2unix && dos2unix /setup.sh && apt-get install -y avahi-daemon avahi-autoipd + +RUN ["/bin/sh", "-c", "STT=vosk ./setup.sh"] + +RUN chmod +x /chipper/start.sh && dos2unix /chipper/start.sh + +CMD ["/bin/sh", "-c", "./chipper/start.sh"] diff --git a/images/custom-intent-1.png b/images/custom-intent-1.png new file mode 100644 index 0000000..535e110 Binary files /dev/null and b/images/custom-intent-1.png differ diff --git a/images/custom-intent-2.png b/images/custom-intent-2.png new file mode 100644 index 0000000..52ddf93 Binary files /dev/null and b/images/custom-intent-2.png differ diff --git a/setup.sh b/setup.sh new file mode 100644 index 0000000..f76402b --- /dev/null +++ b/setup.sh @@ -0,0 +1,668 @@ +#!/bin/bash + +set -e + +echo + +UNAME=$(uname -a) +ROOT="/root" + +if [[ ${UNAME} == *"Darwin"* ]]; then + if [[ -f /usr/local/Homebrew/bin/brew ]] || [[ -f /opt/Homebrew/bin/brew ]]; then + TARGET="darwin" + ROOT="$HOME" + echo "macOS detected." + if [[ ! -f /usr/local/go/bin/go ]]; then + if [[ -f /usr/local/bin/go ]]; then + mkdir -p /usr/local/go/bin + ln -s /usr/local/bin/go /usr/local/go/bin/go + else + echo "Go was not found. You must download it from https://go.dev/dl/ for your macOS." + exit 1 + fi + fi + else + echo "macOS detected, but 'brew' was not found. Install it with the following command and try running setup.sh again:" + echo '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"' + exit 1 + fi + elif [[ -f /usr/bin/apt ]]; then + TARGET="debian" + echo "Debian-based Linux detected." + elif [[ -f /usr/bin/pacman ]]; then + TARGET="arch" + echo "Arch Linux detected." + elif [[ -f /usr/bin/dnf ]]; then + TARGET="fedora" + echo "Fedora/openSUSE detected." +else + echo "This OS is not supported. This script currently supports Linux with either apt, pacman, or dnf." + if [[ ! "$1" == *"--bypass-target-check"* ]]; then + echo "If you would like to get the required packages yourself, you may bypass this by running setup.sh with the --bypass-target-check flag" + echo "The following packages are required (debian apt in this case): wget openssl net-tools libsox-dev libopus-dev make iproute2 xz-utils libopusfile-dev pkg-config gcc curl g++ unzip avahi-daemon git" + exit 1 + fi +fi + +if [[ "${UNAME}" == *"x86_64"* ]]; then + ARCH="x86_64" + echo "amd64 architecture confirmed." + elif [[ "${UNAME}" == *"aarch64"* ]] || [[ "${UNAME}" == *"arm64"* ]]; then + ARCH="aarch64" + echo "aarch64 architecture confirmed." + elif [[ "${UNAME}" == *"armv7l"* ]]; then + ARCH="armv7l" + echo "armv7l (32-bit) WARN: The Coqui and VOSK bindings are broken for this platform at the moment, so please choose Picovoice when the script asks. wire-pod is designed for 64-bit systems." + STT="" +else + echo "Your CPU architecture not supported. This script currently supports x86_64, aarch64, and armv7l." + exit 1 +fi + +if [[ $EUID -ne 0 ]]; then + echo "This script must be run as root. sudo ./setup.sh" + exit 1 +fi + +if [[ ! -d ./chipper ]]; then + echo "Script is not running in the wire-pod/ directory or chipper folder is missing. Exiting." + exit 1 +fi + +if [[ $1 != "-f" ]]; then + if [[ ${ARCH} == "x86_64" ]] && [[ ${TARGET} != "darwin" ]]; then + CPUINFO=$(cat /proc/cpuinfo) + if [[ "${CPUINFO}" == *"avx"* ]]; then + echo "AVX support confirmed." + else + echo "This CPU does not support AVX. Text to speech performance will not be optimal." + AVXSUPPORT="noavx" + #echo "If you would like to bypass this, run the script like this: './setup.sh -f'" + #exit 1 + fi + fi +fi + +echo "Checks have passed!" +echo + +function getPackages() { + echo "Installing required packages" + if [[ ${TARGET} == "debian" ]]; then + apt update -y + apt install -y wget openssl net-tools libsox-dev libopus-dev make iproute2 xz-utils libopusfile-dev pkg-config gcc curl g++ unzip avahi-daemon git libasound2-dev libsodium-dev + elif [[ ${TARGET} == "arch" ]]; then + pacman -Sy --noconfirm + sudo pacman -S --noconfirm wget openssl net-tools sox opus make iproute2 opusfile curl unzip avahi git libsodium go pkg-config + elif [[ ${TARGET} == "fedora" ]]; then + dnf update + dnf install -y wget openssl net-tools sox opus make opusfile curl unzip avahi git libsodium-devel + elif [[ ${TARGET} == "darwin" ]]; then + sudo -u $SUDO_USER brew update + sudo -u $SUDO_USER brew install wget pkg-config opus opusfile + fi + touch ./vector-cloud/packagesGotten + echo + echo "Installing golang binary package" + mkdir golang + cd golang + if [[ ${TARGET} != "darwin" ]] && [[ ${TARGET} != "arch" ]]; then + if [[ ! -f /usr/local/go/bin/go ]]; then + if [[ ${ARCH} == "x86_64" ]]; then + wget -q --show-progress --no-check-certificate https://go.dev/dl/go1.22.4.linux-amd64.tar.gz + rm -rf /usr/local/go && tar -C /usr/local -xzf go1.22.4.linux-amd64.tar.gz + elif [[ ${ARCH} == "aarch64" ]]; then + wget -q --show-progress --no-check-certificate https://go.dev/dl/go1.22.4.linux-arm64.tar.gz + rm -rf /usr/local/go && tar -C /usr/local -xzf go1.22.4.linux-arm64.tar.gz + elif [[ ${ARCH} == "armv7l" ]]; then + wget -q --show-progress --no-check-certificate https://go.dev/dl/go1.22.4.linux-armv6l.tar.gz + rm -rf /usr/local/go && tar -C /usr/local -xzf go1.22.4.linux-armv6l.tar.gz + fi + if [[ ! -f /usr/bin/go ]] && [[ ! -e /usr/bin/go ]]; then + ln -s /usr/local/go/bin/go /usr/bin/go + fi + fi + else + echo "This is a macOS or arch target, assuming Go is installed already" + if [[ ${TARGET} == "arch" ]] && [[ ! -d /usr/local/go/bin ]]; then + mkdir -p /usr/local/go/bin + ln -s /usr/bin/go /usr/local/go/bin/go + fi + fi + cd .. + rm -rf golang + echo +} + +function getSTT() { + echo "export DEBUG_LOGGING=true" > ./chipper/source.sh + rm -f ./chipper/pico.key + function sttServicePrompt() { + echo + echo "Which speech-to-text service would you like to use?" + echo "1: Coqui (local, no usage collection, less accurate, a little slower)" + echo "2: Picovoice Leopard (local, usage collected, accurate, account signup required)" + echo "3: VOSK (local, accurate, multilanguage, fast, recommended)" + # echo "4: Whisper (local, accurate, multilanguage, a little slower, recommended for more powerful hardware)" + echo + read -p "Enter a number (3): " sttServiceNum + if [[ ! -n ${sttServiceNum} ]]; then + sttService="vosk" + elif [[ ${sttServiceNum} == "1" ]]; then + if [[ ${TARGET} == "darwin" ]]; then + echo "Coqui is not supported for macOS. Please select another option." + sttServicePrompt + else + sttService="coqui" + fi + elif [[ ${sttServiceNum} == "2" ]]; then + sttService="leopard" + elif [[ ${sttServiceNum} == "3" ]]; then + sttService="vosk" + elif [[ ${sttServiceNum} == "4" ]]; then + sttService="whisper" + else + echo + echo "Choose a valid number, or just press enter to use the default number." + sttServicePrompt + fi + } + if [[ "$STT" == "vosk" ]]; then + echo "Vosk config" + sttService="vosk" + else + sttServicePrompt + fi + if [[ ${sttService} == "leopard" ]]; then + function picoApiPrompt() { + echo + echo "Create an account at https://console.picovoice.ai/ and enter the Access Key it gives you." + echo + read -p "Enter your Access Key: " picoKey + if [[ ! -n ${picoKey} ]]; then + echo + echo "You must enter a key." + picoApiPrompt + fi + } + picoApiPrompt + echo "export STT_SERVICE=leopard" >> ./chipper/source.sh + echo "export PICOVOICE_APIKEY=${picoKey}" >> ./chipper/source.sh + echo "export PICOVOICE_APIKEY=${picoKey}" > ./chipper/pico.key + elif [[ ${sttService} == "vosk" ]]; then + echo "export STT_SERVICE=vosk" >> ./chipper/source.sh + origDir="$(pwd)" + if [[ ! -f ./vosk/completed ]]; then + echo "Getting VOSK assets" + rm -fr ${ROOT}/.vosk + mkdir ${ROOT}/.vosk + cd ${ROOT}/.vosk + VOSK_VER="0.3.45" + if [[ ${TARGET} == "darwin" ]]; then + VOSK_VER="0.3.42" + VOSK_DIR="vosk-osx-${VOSK_VER}" + elif [[ ${ARCH} == "x86_64" ]]; then + VOSK_DIR="vosk-linux-x86_64-${VOSK_VER}" + elif [[ ${ARCH} == "aarch64" ]]; then + VOSK_DIR="vosk-linux-aarch64-${VOSK_VER}" + elif [[ ${ARCH} == "armv7l" ]]; then + VOSK_DIR="vosk-linux-armv7l-${VOSK_VER}" + fi + VOSK_ARCHIVE="$VOSK_DIR.zip" + wget -q --show-progress --no-check-certificate "https://github.com/alphacep/vosk-api/releases/download/v${VOSK_VER}/${VOSK_ARCHIVE}" + unzip "$VOSK_ARCHIVE" + mv "$VOSK_DIR" libvosk + rm -fr "$VOSK_ARCHIVE" + + cd ${origDir}/chipper + export CGO_ENABLED=1 + export CGO_CFLAGS="-I${ROOT}/.vosk/libvosk" + export CGO_LDFLAGS="-L ${ROOT}/.vosk/libvosk -lvosk -ldl -lpthread" + export LD_LIBRARY_PATH="${ROOT}/.vosk/libvosk:$LD_LIBRARY_PATH" + /usr/local/go/bin/go get -u github.com/kercre123/vosk-api/go/... + /usr/local/go/bin/go get github.com/kercre123/vosk-api + /usr/local/go/bin/go install github.com/kercre123/vosk-api/go + cd ${origDir} + fi + elif [[ ${sttService} == "whisper" ]]; then + echo "export STT_SERVICE=whisper.cpp" >> ./chipper/source.sh + origDir="$(pwd)" + echo "Getting Whisper assets" + if [[ ! -d ./whisper.cpp ]]; then + mkdir whisper.cpp + cd whisper.cpp + git clone https://github.com/ggerganov/whisper.cpp.git . + else + cd whisper.cpp + fi + function whichWhisperModel() { + availableModels="tiny, base, small, medium, large-v3, large-v3-q5_0" + echo + echo "Which Whisper model would you like to use?" + echo "Options: $availableModels" + echo '(tiny is recommended)' + echo + read -p "Enter preferred model: " whispermodel + if [[ ! -n ${whispermodel} ]]; then + echo + echo "You must enter a key." + whichWhisperModel + fi + if [[ ! ${availableModels} == *"${whispermodel}"* ]]; then + echo + echo "Invalid model." + whichWhisperModel + fi + } + whichWhisperModel + ./models/download-ggml-model.sh $whispermodel + cd bindings/go + make whisper + cd ${origDir} + echo "export WHISPER_MODEL=$whispermodel" >> ./chipper/source.sh + else + echo "export STT_SERVICE=coqui" >> ./chipper/source.sh + if [[ ! -f ./stt/completed ]]; then + echo "Getting STT assets" + if [[ -d /root/.coqui ]]; then + rm -rf /root/.coqui + fi + origDir=$(pwd) + mkdir /root/.coqui + cd /root/.coqui + if [[ ${ARCH} == "x86_64" ]]; then + if [[ ${AVXSUPPORT} == "noavx" ]]; then + wget -q --show-progress --no-check-certificate https://wire.my.to/noavx-coqui/native_client.tflite.Linux.tar.xz + else + wget -q --show-progress --no-check-certificate https://github.com/coqui-ai/STT/releases/download/v1.3.0/native_client.tflite.Linux.tar.xz + fi + tar -xf native_client.tflite.Linux.tar.xz + rm -f ./native_client.tflite.Linux.tar.xz + elif [[ ${ARCH} == "aarch64" ]]; then + wget -q --show-progress --no-check-certificate https://github.com/coqui-ai/STT/releases/download/v1.3.0/native_client.tflite.linux.aarch64.tar.xz + tar -xf native_client.tflite.linux.aarch64.tar.xz + rm -f ./native_client.tflite.linux.aarch64.tar.xz + elif [[ ${ARCH} == "armv7l" ]]; then + wget -q --show-progress --no-check-certificate https://github.com/coqui-ai/STT/releases/download/v1.3.0/native_client.tflite.linux.armv7.tar.xz + tar -xf native_client.tflite.linux.armv7.tar.xz + rm -f ./native_client.tflite.linux.armv7.tar.xz + fi + cd ${origDir}/chipper + export CGO_LDFLAGS="-L/root/.coqui/" + export CGO_CXXFLAGS="-I/root/.coqui/" + export LD_LIBRARY_PATH="/root/.coqui/:$LD_LIBRARY_PATH" + /usr/local/go/bin/go get -u github.com/asticode/go-asticoqui/... + /usr/local/go/bin/go get github.com/asticode/go-asticoqui + /usr/local/go/bin/go install github.com/asticode/go-asticoqui + cd ${origDir} + mkdir -p stt + cd stt + function sttModelPrompt() { + echo + echo "Which voice model would you like to use?" + echo "1: large_vocabulary (faster, less accurate, ~100MB)" + echo "2: huge_vocabulary (slower, more accurate, handles faster speech better, ~900MB)" + echo + read -p "Enter a number (1): " sttModelNum + if [[ ! -n ${sttModelNum} ]]; then + sttModel="large_vocabulary" + elif [[ ${sttModelNum} == "1" ]]; then + sttModel="large_vocabulary" + elif [[ ${sttModelNum} == "2" ]]; then + sttModel="huge_vocabulary" + else + echo + echo "Choose a valid number, or just press enter to use the default number." + sttModelPrompt + fi + } + sttModelPrompt + if [[ -f model.scorer ]]; then + rm -rf ./* + fi + if [[ ${sttModel} == "large_vocabulary" ]]; then + echo "Getting STT model..." + wget -O model.tflite -q --show-progress --no-check-certificate https://coqui.gateway.scarf.sh/english/coqui/v1.0.0-large-vocab/model.tflite + echo "Getting STT scorer..." + wget -O model.scorer -q --show-progress --no-check-certificate https://coqui.gateway.scarf.sh/english/coqui/v1.0.0-large-vocab/large_vocabulary.scorer + elif [[ ${sttModel} == "huge_vocabulary" ]]; then + echo "Getting STT model..." + wget -O model.tflite -q --show-progress --no-check-certificate https://coqui.gateway.scarf.sh/english/coqui/v1.0.0-huge-vocab/model.tflite + echo "Getting STT scorer..." + wget -O model.scorer -q --show-progress --no-check-certificate https://coqui.gateway.scarf.sh/english/coqui/v1.0.0-huge-vocab/huge-vocabulary.scorer + else + echo "Invalid model specified" + exit 0 + fi + echo + touch completed + echo "STT assets successfully downloaded!" + cd .. + else + echo "STT assets already there! If you want to redownload, use the 4th option in setup.sh." + fi + fi +} + +function IPDNSPrompt() { + read -p "Enter a number (3): " yn + case $yn in + "1") SANPrefix="IP" ;; + "2") SANPrefix="DNS" ;; + "3") isEscapePod="epod" ;; + "4") noCerts="true" ;; + "") isEscapePod="epod" ;; + *) + echo "Please answer with 1, 2, 3, or 4." + IPDNSPrompt + ;; + esac +} + +function IPPrompt() { + if [[ ${TARGET} == "darwin" ]]; then + IPADDRESS=$(ifconfig | grep "inet " | grep -v 127.0.0.1 | cut -d\ -f2) + else + IPADDRESS=$(ip -4 addr | grep $(ip addr | awk '/state UP/ {print $2}' | sed 's/://g') | grep -oP '(?<=inet\s)\d+(\.\d+){3}') + fi + read -p "Enter the IP address of the machine you are running this script on (${IPADDRESS}): " ipaddress + if [[ ! -n ${ipaddress} ]]; then + address=${IPADDRESS} + else + address=${ipaddress} + fi +} + +function DNSPrompt() { + read -p "Enter the domain you would like to use: " dnsurl + if [[ ! -n ${dnsurl} ]]; then + echo "You must enter a domain." + DNSPrompt + fi + address=${dnsurl} +} + +function generateCerts() { + echo + echo "Creating certificates" + echo + echo "Would you like to use your IP address or a domain for the Subject Alt Name?" + echo "Or would you like to use the escapepod.local certs?" + echo + echo "1: IP address (recommended for OSKR Vectors)" + echo "2: Domain" + echo "3: escapepod.local (required for regular production Vectors)" + if [[ -d ./certs ]]; then + echo "4: Keep certificates as is" + fi + IPDNSPrompt + if [[ ${noCerts} != "true" ]]; then + if [[ ${isEscapePod} != "epod" ]]; then + if [[ ${SANPrefix} == "IP" ]]; then + IPPrompt + else + DNSPrompt + fi + rm -f ./chipper/useepod + rm -rf ./certs + mkdir certs + cd certs + echo ${address} >address + echo "Creating san config" + echo "[req]" >san.conf + echo "default_bits = 4096" >>san.conf + echo "default_md = sha256" >>san.conf + echo "distinguished_name = req_distinguished_name" >>san.conf + echo "x509_extensions = v3_req" >>san.conf + echo "prompt = no" >>san.conf + echo "[req_distinguished_name]" >>san.conf + echo "C = US" >>san.conf + echo "ST = VA" >>san.conf + echo "L = SomeCity" >>san.conf + echo "O = MyCompany" >>san.conf + echo "OU = MyDivision" >>san.conf + echo "CN = ${address}" >>san.conf + echo "[v3_req]" >>san.conf + echo "keyUsage = nonRepudiation, digitalSignature, keyEncipherment" >>san.conf + echo "extendedKeyUsage = serverAuth" >>san.conf + echo "subjectAltName = @alt_names" >>san.conf + echo "[alt_names]" >>san.conf + echo "${SANPrefix}.1 = ${address}" >>san.conf + echo "Generating key and cert" + openssl req -x509 -nodes -days 730 -newkey rsa:2048 -keyout cert.key -out cert.crt -config san.conf + echo + echo "Certificates generated!" + echo + cd .. + else + echo + echo "escapepod.local chosen." + touch chipper/useepod + fi + fi +} + +function scpToBot() { + if [[ ! -n ${botAddress} ]]; then + echo "To copy vic-cloud and server_config.json to your OSKR robot, run this script like this:" + echo "Usage: sudo ./setup.sh scp " + echo "Example: sudo ./setup.sh scp 192.168.1.150 /home/wire/id_rsa_Vector-R2D2" + echo + echo "If your Vector is on Wire's custom software or you have an old dev build, you can run this command without an SSH key:" + echo "Example: sudo ./setup.sh scp 192.168.1.150" + echo + exit 0 + fi + if [[ ! -f ./certs/server_config.json ]]; then + echo "server_config.json file missing. You need to generate this file with ./setup.sh's 6th option." + exit 0 + fi + if [[ ! -n ${keyPath} ]]; then + echo + if [[ ! -f ./ssh_root_key ]]; then + echo "Key not provided, downloading ssh_root_key..." + wget http://wire.my.to:81/ssh_root_key + else + echo "Key not provided, using ./ssh_root_key (already there)..." + fi + chmod 600 ./ssh_root_key + keyPath="./ssh_root_key" + fi + if [[ ! -f ${keyPath} ]]; then + echo "The key that was provided was not found. Exiting." + exit 0 + fi + ssh -i ${keyPath} root@${botAddress} "cat /build.prop" >/tmp/sshTest 2>>/tmp/sshTest + botBuildProp=$(cat /tmp/sshTest) + if [[ "${botBuildProp}" == *"no mutual signature"* ]]; then + echo + echo "An entry must be made to the ssh config for this to work. Would you like the script to do this?" + echo "1: Yes" + echo "2: No (exit)" + echo + function rsaAddPrompt() { + read -p "Enter a number (1): " yn + case $yn in + "1") echo ;; + "2") exit 0 ;; + "") echo ;; + *) + echo "Please answer with 1 or 2." + rsaAddPrompt + ;; + esac + } + rsaAddPrompt + echo "PubkeyAcceptedKeyTypes +ssh-rsa" >>/etc/ssh/ssh_config + botBuildProp=$(ssh -i ${keyPath} root@${botAddress} "cat /build.prop") + fi + if [[ ! "${botBuildProp}" == *"ro.build"* ]]; then + echo "Unable to communicate with robot. The key may be invalid, the bot may not be unlocked, or this device and the robot are not on the same network." + exit 0 + fi + scp -v -i ${keyPath} root@${botAddress}:/build.prop /tmp/scpTest >/tmp/scpTest 2>>/tmp/scpTest + scpTest=$(cat /tmp/scpTest) + if [[ "${scpTest}" == *"sftp"* ]]; then + oldVar="-O" + else + oldVar="" + fi + if [[ ! "${botBuildProp}" == *"ro.build"* ]]; then + echo "Unable to communicate with robot. The key may be invalid, the bot may not be unlocked, or this device and the robot are not on the same network." + exit 0 + fi + ssh -oStrictHostKeyChecking=no -i ${keyPath} root@${botAddress} "mount -o rw,remount / && mount -o rw,remount,exec /data && systemctl stop anki-robot.target && mv /anki/data/assets/cozmo_resources/config/server_config.json /anki/data/assets/cozmo_resources/config/server_config.json.bak" + scp -oStrictHostKeyChecking=no ${oldVar} -i ${keyPath} ./vector-cloud/build/vic-cloud root@${botAddress}:/anki/bin/ + scp -oStrictHostKeyChecking=no ${oldVar} -i ${keyPath} ./certs/server_config.json root@${botAddress}:/anki/data/assets/cozmo_resources/config/ + scp -oStrictHostKeyChecking=no ${oldVar} -i ${keyPath} ./vector-cloud/pod-bot-install.sh root@${botAddress}:/data/ + if [[ -f ./chipper/useepod ]]; then + scp -oStrictHostKeyChecking=no ${oldVar} -i ${keyPath} ./chipper/epod/ep.crt root@${botAddress}:/anki/etc/wirepod-cert.crt + scp -oStrictHostKeyChecking=no ${oldVar} -i ${keyPath} ./chipper/epod/ep.crt root@${botAddress}:/data/data/wirepod-cert.crt + else + scp -oStrictHostKeyChecking=no ${oldVar} -i ${keyPath} ./certs/cert.crt root@${botAddress}:/anki/etc/wirepod-cert.crt + scp -oStrictHostKeyChecking=no ${oldVar} -i ${keyPath} ./certs/cert.crt root@${botAddress}:/data/data/wirepod-cert.crt + fi + ssh -oStrictHostKeyChecking=no -i ${keyPath} root@${botAddress} "chmod +rwx /anki/data/assets/cozmo_resources/config/server_config.json /anki/bin/vic-cloud /data/data/wirepod-cert.crt /anki/etc/wirepod-cert.crt /data/pod-bot-install.sh && /data/pod-bot-install.sh" + rm -f /tmp/sshTest + rm -f /tmp/scpTest + echo "Vector has been reset to Onboarding mode, but no user data has actually been erased." + echo + echo "Everything has been copied to the bot! Use https://keriganc.com/vector-epod-setup on any device with Bluetooth to finish setting up your Vector!" + echo + echo "Everything is now setup! You should be ready to run chipper. sudo ./chipper/start.sh" + echo +} + +function setupSystemd() { + if [[ ${TARGET} == "darwin" ]]; then + echo "This cannot be done on macOS." + exit 1 + fi + if [[ ! -f ./chipper/source.sh ]]; then + echo "You need to make a source.sh file. This can be done with the setup.sh script, option 6." + exit 1 + fi + source ./chipper/source.sh + echo "[Unit]" >wire-pod.service + echo "Description=Wire Escape Pod (coqui)" >>wire-pod.service + echo "StartLimitIntervalSec=500" >>wire-pod.service + echo "StartLimitBurst=5" >>wire-pod.service + echo >>wire-pod.service + echo "[Service]" >>wire-pod.service + echo "Type=simple" >>wire-pod.service + echo "Restart=on-failure" >>wire-pod.service + echo "RestartSec=5s" >>wire-pod.service + echo "WorkingDirectory=$(readlink -f ./chipper)" >>wire-pod.service + echo "ExecStart=$(readlink -f ./chipper/start.sh)" >>wire-pod.service + echo >>wire-pod.service + echo "[Install]" >>wire-pod.service + echo "WantedBy=multi-user.target" >>wire-pod.service + cat wire-pod.service + echo + cd chipper + export GOTAGS="nolibopusfile" + if [[ ${USE_INBUILT_BLE} == "true" ]]; then + export GOTAGS="nolibopusfile,inbuiltble" + fi + COMMIT_HASH="$(git rev-parse --short HEAD)" + export GOLDFLAGS="-X 'github.com/kercre123/wire-pod/chipper/pkg/vars.CommitSHA=${COMMIT_HASH}'" + if [[ ${STT_SERVICE} == "leopard" ]]; then + echo "wire-pod.service created, building chipper with Picovoice STT service..." + /usr/local/go/bin/go build -tags $GOTAGS -ldflags="${GOLDFLAGS}" cmd/leopard/main.go + elif [[ ${STT_SERVICE} == "vosk" ]]; then + echo "wire-pod.service created, building chipper with VOSK STT service..." + export CGO_ENABLED=1 + export CGO_CFLAGS="-I/root/.vosk/libvosk" + export CGO_LDFLAGS="-L /root/.vosk/libvosk -lvosk -ldl -lpthread" + export LD_LIBRARY_PATH="/root/.vosk/libvosk:$LD_LIBRARY_PATH" + /usr/local/go/bin/go build -tags $GOTAGS -ldflags="${GOLDFLAGS}" cmd/vosk/main.go + elif [[ ${STT_SERVICE} == "whisper.cpp" ]]; then + echo "wire-pod.service created, building chipper with Whisper.CPP STT service..." + export CGO_ENABLED=1 + export C_INCLUDE_PATH="../whisper.cpp" + export LIBRARY_PATH="../whisper.cpp" + export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$(pwd)/../whisper.cpp" + export CGO_LDFLAGS="-L$(pwd)/../whisper.cpp" + export CGO_CFLAGS="-I$(pwd)/../whisper.cpp" + /usr/local/go/bin/go build -tags $GOTAGS -ldflags="${GOLDFLAGS}" cmd/experimental/whisper.cpp/main.go + else + echo "wire-pod.service created, building chipper with Coqui STT service..." + export CGO_LDFLAGS="-L/root/.coqui/" + export CGO_CXXFLAGS="-I/root/.coqui/" + export LD_LIBRARY_PATH="/root/.coqui/:$LD_LIBRARY_PATH" + /usr/local/go/bin/go build -tags $GOTAGS -ldflags="${GOLDFLAGS}" cmd/coqui/main.go + fi + sync + mv main chipper + echo + echo "./chipper/chipper has been built!" + cd .. + mv wire-pod.service /lib/systemd/system/ + systemctl daemon-reload + systemctl enable wire-pod + echo + echo "systemd service has been installed and enabled! The service is called wire-pod.service" + echo + echo "To start the service, run: 'systemctl start wire-pod'" + echo "Then, to see logs, run 'journalctl -fe | grep start.sh'" +} + +function disableSystemd() { + if [[ ${TARGET} == "darwin" ]]; then + echo "This cannot be done on macOS." + exit 1 + fi + echo + echo "Disabling wire-pod.service" + systemctl stop wire-pod.service + systemctl disable wire-pod.service + rm ./chipper/chipper + rm -f /lib/systemd/system/wire-pod.service + systemctl daemon-reload + echo + echo "wire-pod.service has been removed and disabled." +} + +function defaultLaunch() { + echo + getPackages + getSTT + echo + echo "wire-pod is ready to run! You are ready to move to the next step and run sudo ./chipper/start.sh" +} + +if [[ $1 == "scp" ]]; then + botAddress=$2 + keyPath=$3 + scpToBot + exit 0 +fi + +if [[ $1 == "daemon-enable" ]]; then + getPackages + setupSystemd + exit 0 +fi + +if [[ $1 == "daemon-disable" ]]; then + disableSystemd + exit 0 +fi + +if [[ $1 == "-f" ]] && [[ $2 == "scp" ]]; then + botAddress=$3 + keyPath=$4 + scpToBot + exit 0 +fi + +# echo "What would you like to do?" +# echo "1: Full Setup (recommended) (builds chipper, gets STT stuff, generates certs, creates source.sh file, and creates server_config.json for your bot" +# echo "2: Just build vic-cloud" +# echo "3: Just build chipper" +# echo "4: Just get STT assets" +# echo "5: Just generate certs" +# echo "6: Create wire-pod config file (change/add API keys)" +# echo "(NOTE: You can just press enter without entering a number to select the default, recommended option)" +# echo +defaultLaunch diff --git a/update.sh b/update.sh new file mode 100644 index 0000000..6b62b9e --- /dev/null +++ b/update.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +# ensure all required packages are installed +if [[ $(uname -a) == *"Darwin"* ]]; then + TARGET="darwin" + echo "macOS detected." +elif [[ -f /usr/bin/apt ]]; then + TARGET="debian" + echo "Debian-based Linux detected." +elif [[ -f /usr/bin/pacman ]]; then + TARGET="arch" + echo "Arch Linux detected." +elif [[ -f /usr/bin/dnf ]]; then + TARGET="fedora" + echo "Fedora/openSUSE detected." +fi + +if [[ ${TARGET} == "debian" ]]; then + sudo apt update -y + sudo apt install -y wget openssl net-tools libsox-dev libopus-dev make iproute2 xz-utils libopusfile-dev pkg-config gcc curl g++ unzip avahi-daemon git libasound2-dev libsodium-dev +elif [[ ${TARGET} == "arch" ]]; then + sudo pacman -Sy --noconfirm + sudo pacman -S --noconfirm wget openssl net-tools sox opus make iproute2 opusfile curl unzip avahi git libsodium +elif [[ ${TARGET} == "fedora" ]]; then + sudo dnf update + sudo dnf install -y wget openssl net-tools sox opus make opusfile curl unzip avahi git libsodium-devel +elif [[ ${TARGET} == "darwin" ]]; then + sudo -u $SUDO_USER brew update + sudo -u $SUDO_USER brew install wget pkg-config opus opusfile +fi + +if [[ ! -d ./chipper ]]; then + echo "This must be run in the wire-pod/ directory." + exit 1 +fi + +git fetch --all +git reset --hard origin/main +if [[ -f ./chipper/chipper ]]; then + cd chipper + source source.sh + sudo systemctl stop wire-pod + if [[ ${STT_SERVICE} == "leopard" ]]; then + echo "wire-pod.service created, building chipper with Picovoice STT service..." + sudo /usr/local/go/bin/go build cmd/leopard/main.go + elif [[ ${STT_SERVICE} == "vosk" ]]; then + echo "wire-pod.service created, building chipper with VOSK STT service..." + export CGO_ENABLED=1 + sudo LD_LIBRARY_PATH="/root/.vosk/libvosk:$LD_LIBRARY_PATH" CGO_LDFLAGS="-L /root/.vosk/libvosk -lvosk -ldl -lpthread" CGO_CFLAGS="-I/root/.vosk/libvosk" CGO_ENABLED=1 /usr/local/go/bin/go build cmd/vosk/main.go + elif [[ ${STT_SERVICE} == "coqui" ]]; then + echo "wire-pod.service created, building chipper with Coqui STT service..." + sudo LD_LIBRARY_PATH="/root/.coqui/:$LD_LIBRARY_PATH" CGO_CXXFLAGS="-I/root/.coqui/" CGO_LDFLAGS="-L/root/.coqui/" /usr/local/go/bin/go build cmd/coqui/main.go + else + echo "Unsupported STT ${STT_SERVICE}. You must build this manually. The code has been updated, though." + exit 1 + fi + echo "Syncing..." + sync + sudo systemctl daemon-reload + sudo systemctl start wire-pod + echo "wire-pod is now running with the updated code!" +fi +echo +echo "Updated successfully!" +echo diff --git a/vector-cloud/.gitignore b/vector-cloud/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/vector-cloud/.gitignore @@ -0,0 +1 @@ +/build diff --git a/vector-cloud/CREDITS.md b/vector-cloud/CREDITS.md new file mode 100644 index 0000000..5bcabaa --- /dev/null +++ b/vector-cloud/CREDITS.md @@ -0,0 +1,98 @@ +# Credits + +Thanks to all of the previous owners of this codebase! + +@andrew-anki +@kevinatanki +@bradneuman +@DanielCasner +@asterick +@ilngo +@alchausee +@leecrippen +@dmudie-anki +@ryeander +@kevinmkarol +@JMRivas +@donovandrane +@MollyJameson +@msintov +@rosspanderson +@nishkar +@TrevorDasch +@CodeForCoffee +@pohung +@dstulic +@ratanki +@baustinanki +@nathan-anki +@arjungm +@paulaluri +@RichardGale +@shawnblakesley +@ankimichael +@dcerpa-anki +@ashello +@mmichini +@chapados +@Jarboo-anki +@craigalicious +@ankiNicolas +@Sam-Anki +@lorenzoriano +@charlesgallant-anki +@amyclaus +@mike-anki +@anki-smartling +@seanlevatino +@frgmike +@jaye-anki +@agoaj +@aplatti +@MoolySegal +@DariaAnki +@scopp +@robbula +@scritelli-anki +@sandyspangler +@gwatts +@fightingwords +@holomatt +@WesleyYue +@rakeshr4 +@nakkapeddi +@mprokos +@andrewpbstout +@charliehuge +@swilkewitz +@BruceVonK +@BenGabaldon +@ankikreyna +@Isabela3 +@Humhu +@maxcembalest +@jfhardy +@AwotG +@keikoko +@Eldres +@selenatiker +@sonnguyen-logigear +@nhuehle +@jesseeaseley +@joepvg +@vitaly-anki +@troyanki +@chris-rogers-anki +@foodini +@ankialex +@ak2539 +@thanhlelgg +@rachelAnki +@nimeshmonson +@ahilvers-anki +@LeighAusiello +@G-mel +@AnkiEdison +@patjdor +@hdiggins +@abertolini-anki \ No newline at end of file diff --git a/vector-cloud/LICENSE b/vector-cloud/LICENSE new file mode 100644 index 0000000..713756e --- /dev/null +++ b/vector-cloud/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Digital Dream Labs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vector-cloud/Makefile b/vector-cloud/Makefile new file mode 100644 index 0000000..951e2a5 --- /dev/null +++ b/vector-cloud/Makefile @@ -0,0 +1,54 @@ +.PHONY: docker-builder vic-cloud vic-gateway + +docker-builder: + docker build -t armbuilder docker-builder/. + +all: vic-cloud vic-gateway + +go_deps: + echo `go version` && cd $(PWD) && go mod download + +vic-cloud: go_deps + docker container run \ + -v "$(PWD)":/go/src/digital-dream-labs/vector-cloud \ + -v $(GOPATH)/pkg/mod:/go/pkg/mod \ + -w /go/src/digital-dream-labs/vector-cloud \ + --user $(UID):$(GID) \ + armbuilder \ + go build \ + -tags nolibopusfile,vicos \ + --trimpath \ + -ldflags '-w -s -linkmode internal -extldflags "-static" -r /anki/lib' \ + -o build/vic-cloud \ + cloud/main.go + + docker container run \ + -v "$(PWD)":/go/src/digital-dream-labs/vector-cloud \ + -v $(GOPATH)/pkg/mod:/go/pkg/mod \ + -w /go/src/digital-dream-labs/vector-cloud \ + --user $(UID):$(GID) \ + armbuilder \ + upx build/vic-cloud + + +vic-gateway: go_deps + docker container run \ + -v "$(PWD)":/go/src/digital-dream-labs/vector-cloud \ + -v $(GOPATH)/pkg/mod:/go/pkg/mod \ + -w /go/src/digital-dream-labs/vector-cloud \ + --user $(UID):$(GID) \ + armbuilder \ + go build \ + -tags nolibopusfile,vicos \ + --trimpath \ + -ldflags '-w -s -linkmode internal -extldflags "-static" -r /anki/lib' \ + -o build/vic-gateway \ + gateway/*.go + + docker container run \ + -v "$(PWD)":/go/src/digital-dream-labs/vector-cloud \ + -v $(GOPATH)/pkg/mod:/go/pkg/mod \ + -w /go/src/digital-dream-labs/vector-cloud \ + --user $(UID):$(GID) \ + armbuilder \ + upx build/vic-gateway diff --git a/vector-cloud/README.md b/vector-cloud/README.md new file mode 100644 index 0000000..ed51df1 --- /dev/null +++ b/vector-cloud/README.md @@ -0,0 +1,81 @@ +# Vector-cloud + +Programs that make Vector talk to the cloud! + +## Building + +To make it easy to cross-compile binaries on your computer that will +run on Vector you'll first need the armbuilder docker image. It can +be generated by running.. + +``` +# make docker-builder +``` + +To build vic-cloud, run... +``` +# make vic-cloud +``` + +To build vic-gateway, run... +``` +# make vic-gateway +``` + +## Example Customization + +Let's have Vector refuse to give users information on Area 51 and then +explictly state that all other information requests have been approved. + +First we make the following changes to `internal/voice/stream/context.go` + +```diff +diff --git a/internal/voice/stream/context.go b/internal/voice/stream/context.go +index 1d5df2c..564b22f 100644 +--- a/internal/voice/stream/context.go ++++ b/internal/voice/stream/context.go +@@ -1,7 +1,9 @@ + package stream + + import ( +- "bytes" ++ "regexp" ++ ++ "bytes" + "context" + "encoding/json" + "fmt" +@@ -155,6 +157,14 @@ func sendIntentResponse(resp *chipper.IntentResult, receiver Receiver) { + + func sendKGResponse(resp *chipper.KnowledgeGraphResponse, receiver Receiver) { + var buf bytes.Buffer ++ ++ found, _ := regexp.MatchString("area fifty one", resp.QueryText) ++ if found { ++ resp.SpokenText = "Information regarding Area Fifty One is classified. The Illuminati High Council has been notified of this request." ++ } else { ++ resp.SpokenText = "Information Request Approved. " + resp.SpokenText ++ } ++ + params := map[string]string{ + "answer": resp.SpokenText, + "answer_type": resp.CommandType, +``` + +Next compile, copy to Vector, and reboot. + +```bash +grant@lord-humungus vector-cloud % make vic-cloud +echo `go version` && cd /Users/grant/src/vector-cloud && go mod download + ... BUILD LOG OUTPUT ... +Packed 1 file. +grant@lh % ssh root@ mount -o remount,rw / +grant@lh % scp build/vic-cloud root@:/anki/bin +vic-cloud 100% 4800KB 3.6MB/s 00:01 +grant@lh % +grant@lh % ssh root@ /sbin/reboot +``` + +And test after the reboot by saying "Hey Vector... Question... What is Area 51?" and +"Hey Vector... Question... What is DogeCoin?" + diff --git a/vector-cloud/cloud/cert_error_dev.go b/vector-cloud/cloud/cert_error_dev.go new file mode 100644 index 0000000..7ea43ea --- /dev/null +++ b/vector-cloud/cloud/cert_error_dev.go @@ -0,0 +1,19 @@ +// +build !shipping,vicos + +package main + +import ( + "github.com/digital-dream-labs/vector-cloud/internal/log" + "github.com/digital-dream-labs/vector-cloud/internal/robot" +) + +func init() { + certErrorFunc = onCertError +} + +func onCertError() bool { + if err := robot.WriteFaceErrorCode(850); err != nil { + log.Println("Error writing error code (isn't it ironic?):", err) + } + return true +} diff --git a/vector-cloud/cloud/config_dev.go b/vector-cloud/cloud/config_dev.go new file mode 100644 index 0000000..dfda323 --- /dev/null +++ b/vector-cloud/cloud/config_dev.go @@ -0,0 +1,5 @@ +// +build !shipping + +package main + +const verbose = true diff --git a/vector-cloud/cloud/config_shipping.go b/vector-cloud/cloud/config_shipping.go new file mode 100644 index 0000000..2f8b0e4 --- /dev/null +++ b/vector-cloud/cloud/config_shipping.go @@ -0,0 +1,5 @@ +// +build shipping + +package main + +const verbose = false diff --git a/vector-cloud/cloud/main.go b/vector-cloud/cloud/main.go new file mode 100644 index 0000000..a7acca5 --- /dev/null +++ b/vector-cloud/cloud/main.go @@ -0,0 +1,232 @@ +package main + +import ( + "bytes" + "context" + "crypto/tls" + "flag" + "fmt" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "github.com/digital-dream-labs/vector-cloud/internal/clad/cloud" + "github.com/digital-dream-labs/vector-cloud/internal/cloudproc" + "github.com/digital-dream-labs/vector-cloud/internal/config" + "github.com/digital-dream-labs/vector-cloud/internal/ipc" + "github.com/digital-dream-labs/vector-cloud/internal/jdocs" + "github.com/digital-dream-labs/vector-cloud/internal/log" + "github.com/digital-dream-labs/vector-cloud/internal/logcollector" + "github.com/digital-dream-labs/vector-cloud/internal/robot" + "github.com/digital-dream-labs/vector-cloud/internal/token" + "github.com/digital-dream-labs/vector-cloud/internal/voice" + + "github.com/gwatts/rootcerts" +) + +var checkDataFunc func() error // overwritten by platform_linux.go +var certErrorFunc func() bool // overwritten by cert_error_dev.go, determines if error should cause exit +var platformOpts []cloudproc.Option +var podCert string = "wirepod-cert.crt" + +func getSocketWithRetry(name string, client string) ipc.Conn { + for { + sock, err := ipc.NewUnixgramClient(name, client) + if err != nil { + log.Println("Couldn't create socket", name, "- retrying:", err) + time.Sleep(5 * time.Second) + } else { + return sock + } + } +} + +func getHTTPClient() *http.Client { + // Create a HTTP client with given CA cert pool so we can use https on device + return &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + RootCAs: rootcerts.ServerCertPool(), + }, + }, + } +} + +func testReader(serv ipc.Server, send voice.MsgSender) { + for conn := range serv.NewConns() { + go func(conn ipc.Conn) { + for { + msg := conn.ReadBlock() + if msg == nil || len(msg) == 0 { + conn.Close() + return + } + var cmsg cloud.Message + if err := cmsg.Unpack(bytes.NewBuffer(msg)); err != nil { + log.Println("Test reader unpack error:", err) + continue + } + send.Send(&cmsg) + } + }(conn) + } +} + +func main() { + + var pool = rootcerts.ServerCertPool() + // load custom cert + // /anki/etc/wirepod-cert.crt + certBytes, err := os.ReadFile("/anki/etc/" + podCert) + if err == nil { + log.Println("Found /anki/etc/" + podCert + ", appending") + ok := pool.AppendCertsFromPEM(certBytes) + if ok { + log.Println("Successfully loaded custom cert! Writing to /data") + os.WriteFile("/data/data/wirepod-cert.crt", certBytes, 0644) + } else { + log.Println("Failed to load /anki/etc/"+podCert, ", trying /data/data/wirepod-cert.crt") + certBytes, err := os.ReadFile("/data/data/" + podCert) + if err == nil { + log.Println("Found /data/data/" + podCert + ", appending") + ok := pool.AppendCertsFromPEM(certBytes) + if ok { + log.Println("Successfully loaded custom cert!") + } else { + log.Println("Custom cert loader failed") + } + } + } + } else { + log.Println("Failed to load /anki/etc/"+podCert, ", trying /data/data/wirepod-cert.crt") + certBytes, err := os.ReadFile("/data/data/" + podCert) + if err == nil { + log.Println("Found /data/data/" + podCert + ", appending") + ok := pool.AppendCertsFromPEM(certBytes) + if ok { + log.Println("Successfully loaded custom cert!") + } else { + log.Println("Custom cert loader failed") + } + } else { + log.Println("Failed to load custom certs") + } + } + + log.Println("Starting up") + + robot.InstallCrashReporter(log.Tag) + + // if we want to error, we should do it after we get socket connections, to make sure + // vic-anim is running and able to handle it + var tryErrorFunc bool + if checkDataFunc != nil { + if err := checkDataFunc(); err != nil { + log.Println("CLOUD DATA VERIFICATION ERROR:", err) + log.Println("(this should not happen on any DVT3 or later robot)") + tryErrorFunc = true + } else { + log.Println("Cloud data verified") + } + } + + signalHandler() + + // don't yet have control over process startup on DVT2, set these as default + test := false + + var verbose bool + flag.BoolVar(&verbose, "verbose", false, "enable verbose logging") + // var test bool + // flag.BoolVar(&test, "test", false, "enable test channel") + + ms := flag.Bool("ms", false, "force microsoft handling on the server end") + lex := flag.Bool("lex", false, "force amazon handling on the server end") + + awsRegion := flag.String("region", "us-west-2", "AWS Region") + + flag.Parse() + + micSock := getSocketWithRetry(ipc.GetSocketPath("mic_sock"), "cp_mic") + defer micSock.Close() + aiSock := getSocketWithRetry(ipc.GetSocketPath("ai_sock"), "cp_ai") + defer aiSock.Close() + + // now that we have connection, we can error if necessary + if tryErrorFunc && certErrorFunc != nil && certErrorFunc() { + return + } + + // set up test channel if flags say we should + var testRecv *voice.Receiver + if test { + testSock, err := ipc.NewUnixgramServer(ipc.GetSocketPath("cp_test")) + if err != nil { + log.Println("Server create error:", err) + } + defer testSock.Close() + + var testSend voice.MsgIO + testSend, testRecv = voice.NewMemPipe() + go testReader(testSock, testSend) + log.Println("Test channel created") + } + log.Println("Sockets successfully created") + + voice.SetVerbose(verbose) + receiver := voice.NewIpcReceiver(micSock, nil) + + process := &voice.Process{} + process.AddReceiver(receiver) + if testRecv != nil { + process.AddTestReceiver(testRecv) + } + process.AddIntentWriter(&voice.IPCMsgSender{Conn: aiSock}) + voiceOpts := []voice.Option{voice.WithChunkMs(120), voice.WithSaveAudio(true)} + var options []cloudproc.Option + options = append(options, platformOpts...) + voiceOpts = append(voiceOpts, voice.WithCompression(true)) + if *ms { + voiceOpts = append(voiceOpts, voice.WithHandler(voice.HandlerMicrosoft)) + } else if *lex { + voiceOpts = append(voiceOpts, voice.WithHandler(voice.HandlerAmazon)) + } + + if err := config.SetGlobal(""); err != nil { + log.Println("Could not load server config! This is not good!:", err) + if certErrorFunc != nil && certErrorFunc() { + return + } + } + + options = append(options, cloudproc.WithVoice(process)) + options = append(options, cloudproc.WithVoiceOptions(voiceOpts...)) + tokenOpts := []token.Option{token.WithServer()} + options = append(options, cloudproc.WithTokenOptions(tokenOpts...)) + options = append(options, cloudproc.WithJdocs(jdocs.WithServer())) + + logcollectorOpts := []logcollector.Option{logcollector.WithServer()} + logcollectorOpts = append(logcollectorOpts, logcollector.WithHTTPClient(getHTTPClient())) + logcollectorOpts = append(logcollectorOpts, logcollector.WithS3UrlPrefix(config.Env.LogFiles)) + logcollectorOpts = append(logcollectorOpts, logcollector.WithAwsRegion(*awsRegion)) + options = append(options, cloudproc.WithLogCollectorOptions(logcollectorOpts...)) + + cloudproc.Run(context.Background(), options...) + + robot.UninstallCrashReporter() + + log.Println("All processes exited, shutting down") +} + +func signalHandler() { + ch := make(chan os.Signal, 1) + signal.Notify(ch, os.Interrupt, syscall.SIGTERM) + go func() { + <-ch + fmt.Println("Received SIGTERM, shutting down immediately") + robot.UninstallCrashReporter() + os.Exit(0) + }() +} diff --git a/vector-cloud/cloud/platform_vicos.go b/vector-cloud/cloud/platform_vicos.go new file mode 100644 index 0000000..564a19e --- /dev/null +++ b/vector-cloud/cloud/platform_vicos.go @@ -0,0 +1,23 @@ +// +build vicos + +package main + +import ( + "github.com/digital-dream-labs/vector-cloud/internal/cloudproc" + "github.com/digital-dream-labs/vector-cloud/internal/robot" + "github.com/digital-dream-labs/vector-cloud/internal/voice" +) + +func init() { + checkDataFunc = checkCloudDataFiles + platformOpts = append(platformOpts, cloudproc.WithVoiceOptions(voice.WithRequireToken())) +} + +func checkCloudDataFiles() error { + esn, err := robot.ReadESN() + if err != nil { + return err + } + + return robot.CheckFactoryCloudFiles(robot.DefaultCloudDir, esn) +} diff --git a/vector-cloud/docker-builder/Dockerfile b/vector-cloud/docker-builder/Dockerfile new file mode 100644 index 0000000..e84ec24 --- /dev/null +++ b/vector-cloud/docker-builder/Dockerfile @@ -0,0 +1,26 @@ +FROM golang:1.18.4-buster + +COPY ./sources.list /etc/apt/sources.list + +RUN dpkg --add-architecture armel + +RUN apt-get -o Acquire::Check-Valid-Until=false -o Acquire::http::Dl-Limit=500 update -y && apt -o Acquire::Check-Valid-Until=false install -y \ + g++-arm-linux-gnueabi \ + gcc-arm-linux-gnueabi \ + libc6-dev-armhf-cross \ + libopus-dev:armel \ + libogg-dev:armel \ + android-liblog-dev:armel android-liblog:armel \ + upx + +ENV GOPATH=/go +ENV GOOS=linux +ENV GOARCH=arm +ENV GOARM=7 +ENV CGO_ENABLED=1 +ENV CGO_LDFLAGS="-L/anki/lib" +ENV CGO_CFLAGS="-I/anki/lib" +ENV CC=arm-linux-gnueabi-gcc +ENV PKG_CONFIG_PATH=/usr/lib/arm-linux-gnueabi/pkgconfig +ENV CXX="arm-linux-gnueabi-g++-8" +ENV GOCACHE=/tmp diff --git a/vector-cloud/docker-builder/sources.list b/vector-cloud/docker-builder/sources.list new file mode 100644 index 0000000..98fe99c --- /dev/null +++ b/vector-cloud/docker-builder/sources.list @@ -0,0 +1,6 @@ +#deb http://snapshot.debian.org/archive/debian/20220821T091830Z buster main +deb http://archive.debian.org/debian buster main +#deb http://snapshot.debian.org/archive/debian-security/20220821T091830Z buster/updates main +#deb http://deb.debian.org/debian-security buster/updates main +#deb http://snapshot.debian.org/archive/debian/20220821T091830Z buster-updates main +#deb http://deb.debian.org/debian buster-updates main diff --git a/vector-cloud/gateway/config_linux.go b/vector-cloud/gateway/config_linux.go new file mode 100644 index 0000000..ed62884 --- /dev/null +++ b/vector-cloud/gateway/config_linux.go @@ -0,0 +1,45 @@ +// +build linux + +package main + +import ( + "encoding/base64" + "net/http" + "strings" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" +) + +const ( + Port = 443 + SocketPath = "/dev/socket/" + IsOnRobot = true +) + +func checkAuth(_ http.ResponseWriter, r *http.Request) (string, error) { + if r.URL.EscapedPath() == "/Anki.Vector.external_interface.ExternalInterface/UserAuthentication" { + return "WiFi User Auth Bypass", nil + } + auth, ok := r.Header["Authorization"] + + if !ok { + return "", grpc.Errorf(codes.Unauthenticated, "No auth token") + } + if len(auth) != 1 { + return "", grpc.Errorf(codes.Unauthenticated, "Too many auth tokens") + } + authHeader := auth[0] + if strings.HasPrefix(authHeader, "Basic ") { + _, err := base64.StdEncoding.DecodeString(authHeader[6:]) + if err != nil { + return "", grpc.Errorf(codes.Unauthenticated, "Failed to decode auth token (Base64)") + } + // todo + } else if !strings.HasPrefix(authHeader, "Bearer ") { + return "", grpc.Errorf(codes.Unauthenticated, "Unknown auth header type") + } + clientToken := authHeader[7:] + + return tokenManager.CheckToken(clientToken) +} diff --git a/vector-cloud/gateway/config_mac.go b/vector-cloud/gateway/config_mac.go new file mode 100644 index 0000000..01e0b75 --- /dev/null +++ b/vector-cloud/gateway/config_mac.go @@ -0,0 +1,20 @@ +// +build darwin + +package main + +/* + +import ( + "net/http" +) + +const ( + Port = 8443 + SocketPath = "/tmp/" + IsOnRobot = false +) + +func checkAuth(_ http.ResponseWriter, r *http.Request) (string, error) { + return "Local Bypass", nil +} +*/ diff --git a/vector-cloud/gateway/ipc_manager.go b/vector-cloud/gateway/ipc_manager.go new file mode 100644 index 0000000..57c6be2 --- /dev/null +++ b/vector-cloud/gateway/ipc_manager.go @@ -0,0 +1,748 @@ +// ipc_manager.go handles messages between gateway and the following other processes: +// - vic-engine: There are both a CLAD (EngineCladIpcManager) and a Protobuf (EngineProtoIpcManager) +// domain socket connecting gateway to the engine. The CLAD socket is currently in the +// process of being deprecated because it was designed as a temporary connection while +// messages were converted to Protobuf. +// - vic-switchboard: There is a CLAD domain socket connecting gateway to switchboard. This socket is +// used to coordinate authentication between the two processes. +// - vic-cloud: There is a CLAD domain socket connecting gateway to vic-cloud. This socket is used to +// refresh the latest authentication tokens from vic-cloud. This socket is defined inside +// the tokens.go file because the properties of that socket are a special case. +// To add a new connection, add the domain socket name (as defined by the server) to the list of consts +// below. And create a new IpcManager struct for that given socket. In main.go the connection should be +// Init-ed. Then it will be ready to use. +package main + +import ( + "bytes" + "encoding/binary" + "math/rand" + "path" + "reflect" + "sync" + "time" + + "github.com/digital-dream-labs/vector-cloud/internal/ipc" + + gw_clad "github.com/digital-dream-labs/vector-cloud/internal/clad/gateway" + extint "github.com/digital-dream-labs/vector-cloud/internal/proto/external_interface" + + "github.com/digital-dream-labs/vector-cloud/internal/log" + + "github.com/golang/protobuf/proto" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" +) + +// The names of the domain sockets to which gateway will connect. +// They will be created in the location defined by config_{platform}.go for the appropriate +// platform. +const ( + cladDomainSocket = "_engine_gateway_server_" + protoDomainSocket = "_engine_gateway_proto_server_" + switchboardDomainSocket = "_switchboard_gateway_server_" +) + +// IpcManager is a struct which handles synchronously sending and receiving messages from +// other processes via domain sockets. This acts like a base class for the other IpcManagers +// to prevent duplication of the Connect and Close functions. +// +// field connMutex: A mutex to prevent simultaneous reads and writes to the domain socket. +// field conn: The connection to the domain socket by which messages are passed between processes. +// +// Note: If there is a function that can be shared, use the engine manager. +// The only reason I duplicated the functions so far was interfaces +// were strangely causing the deleteListenerUnsafe to fail. - shawn 7/17/18 +type IpcManager struct { + connMutex sync.Mutex + conn ipc.Conn +} + +// Connect establishes a connection to the domain socket of a given path. +// vic-gateway acts as the client in all of its domain socket connections since +// it is the part of the system closest to the outside world. +func (manager *IpcManager) Connect(path string, name string) { + for { + conn, err := ipc.NewUnixgramClient(path, name) + if err != nil { + log.Printf("Couldn't create sockets for %s & %s_%s - retrying: %s\n", path, path, name, err.Error()) + time.Sleep(5 * time.Second) + } else { + manager.conn = conn + return + } + } +} + +// Close tears down the connection to a domain socket. +func (manager *IpcManager) Close() error { + return manager.conn.Close() +} + +// EngineProtoIpcManager handles passing Protobuf messages between vic-gateway and vic-engine. +// field IpcManager: An anonymous field which basically acts like a subclass. +// field managerMutex: A mutex which prevents asynchronous reads and writes to the managedChannels map. +// field managedChannels: A mapping of messages to listening channels for the rpc handlers. +type EngineProtoIpcManager struct { + IpcManager + managerMutex sync.RWMutex + managedChannels map[string]([]chan extint.GatewayWrapper) +} + +// Init sets up the channel manager and the domain socket connection +func (manager *EngineProtoIpcManager) Init() { + manager.managedChannels = make(map[string]([]chan extint.GatewayWrapper)) + manager.Connect(path.Join(SocketPath, protoDomainSocket), "client") +} + +// Write sends a Protobuf message to vic-engine. This will be handled by +// ProtoMessageHandler::ProcessMessages() in the engine C++ code. +// All Gateway messages get a ConnectionId, we create one here and return it. +func (manager *EngineProtoIpcManager) Write(msg *extint.GatewayWrapper) (int, uint64, error) { + connectionID := rand.Uint64() + count, err := manager.WriteWithID(msg, connectionID) + return count, connectionID, err +} + +// Write sends a Protobuf message to vic-engine. This will be handled by +// ProtoMessageHandler::ProcessMessages() in the engine C++ code. +func (manager *EngineProtoIpcManager) WriteWithID(msg *extint.GatewayWrapper, connID uint64) (int, error) { + var err error + var buf bytes.Buffer + + msg.ConnectionId = connID + + if msg == nil { + return -1, grpc.Errorf(codes.InvalidArgument, "Unable to parse request") + } + if err = binary.Write(&buf, binary.LittleEndian, uint16(proto.Size(msg))); err != nil { + return -1, grpc.Errorf(codes.Internal, err.Error()) + } + data, err := proto.Marshal(msg) + if err != nil { + return -1, grpc.Errorf(codes.Internal, err.Error()) + } + if err = binary.Write(&buf, binary.LittleEndian, data); err != nil { + return -1, grpc.Errorf(codes.Internal, err.Error()) + } + + manager.connMutex.Lock() + defer manager.connMutex.Unlock() + if logMessageContent { + log.Printf("%T: writing '%#v' Proto message to Engine\n", *manager, msg) + } + return manager.conn.Write(buf.Bytes()) +} + +// deleteListenerUnsafe is "unsafe" in that it requires the calling function to have already acquired the +// appropriate lock. Otherwise, there is a chance of simultaneous map access. +func (manager *EngineProtoIpcManager) deleteListenerUnsafe(listener chan extint.GatewayWrapper, tag string) { + chanSlice := manager.managedChannels[tag] + for idx, v := range chanSlice { + if v == listener { + chanSlice[idx] = chanSlice[len(chanSlice)-1] + manager.managedChannels[tag] = chanSlice[:len(chanSlice)-1] + manager.SafeClose(listener) + break + } + if len(chanSlice)-1 == idx { + log.Println("Warning: failed to remove listener:", listener, "from array:", chanSlice) + } + } + if len(manager.managedChannels[tag]) == 0 { + delete(manager.managedChannels, tag) + } +} + +// deleteListenerCallback provides a callback which may be invoked to remove the channel from the listeners. +func (manager *EngineProtoIpcManager) deleteListenerCallback(listener chan extint.GatewayWrapper, tag string) func() { + return func() { + manager.managerMutex.Lock() + defer manager.managerMutex.Unlock() + manager.deleteListenerUnsafe(listener, tag) + } +} + +// CreateChannel establishes a chan by which the ipc manager will pass the requested message type. +func (manager *EngineProtoIpcManager) CreateChannel(tag interface{}, numChannels int) (func(), chan extint.GatewayWrapper) { + result := make(chan extint.GatewayWrapper, numChannels) + reflectedType := reflect.TypeOf(tag).String() + if logVerbose { + log.Println("Listening for", reflectedType) + } + manager.managerMutex.Lock() + defer manager.managerMutex.Unlock() + slice := manager.managedChannels[reflectedType] + if slice == nil { + slice = make([]chan extint.GatewayWrapper, 0) + } + manager.managedChannels[reflectedType] = append(slice, result) + return manager.deleteListenerCallback(result, reflectedType), result +} + +// CreateUniqueChannel establishes a chan by which the ipc manager will pass the requested message type as long as there isn't already one open. +// This is useful in cases where we want to prevent spamming of the engine. See UpdateSettings in message_handler.go. +func (manager *EngineProtoIpcManager) CreateUniqueChannel(tag interface{}, numChannels int) (func(), chan extint.GatewayWrapper, bool) { + reflectedType := reflect.TypeOf(tag).String() + manager.managerMutex.RLock() + _, ok := manager.managedChannels[reflectedType] + manager.managerMutex.RUnlock() + if ok { + return nil, nil, false + } + + f, c := manager.CreateChannel(tag, numChannels) + return f, c, true +} + +// SafeClose closes a channel if possible. +func (manager *EngineProtoIpcManager) SafeClose(listener chan extint.GatewayWrapper) { + select { + case _, ok := <-listener: + if ok { + close(listener) + } + default: + close(listener) + } +} + +// SendToListeners propagates messages to all waiting listener channels. +func (manager *EngineProtoIpcManager) SendToListeners(tag string, msg extint.GatewayWrapper) { + markedForDelete := make(chan chan extint.GatewayWrapper, 5) + defer func() { + close(markedForDelete) + if len(markedForDelete) != 0 { + manager.managerMutex.Lock() + defer manager.managerMutex.Unlock() + for listener := range markedForDelete { + manager.deleteListenerUnsafe(listener, tag) + } + } + }() + manager.managerMutex.RLock() + defer manager.managerMutex.RUnlock() + chanList, ok := manager.managedChannels[tag] + if !ok { + return // No listeners for message + } + if logVerbose { + log.Printf("Sending %s to listeners\n", tag) + } + var wg sync.WaitGroup + for idx, listener := range chanList { + wg.Add(1) + go func(idx int, listener chan extint.GatewayWrapper, msg extint.GatewayWrapper) { + defer wg.Done() + select { + case listener <- msg: + if logVerbose { + log.Printf("Sent to listener #%d: %s\n", idx, tag) + } + case <-time.After(250 * time.Millisecond): + log.Errorf("EngineProtoIpcManager.SendToListeners: Failed to send message %s for listener #%d. There might be a problem with the channel.\n", tag, idx) + markedForDelete <- listener + } + }(idx, listener, msg) + } + wg.Wait() +} + +// ProcessMessages loops through incoming messages on the ipc channel. +func (manager *EngineProtoIpcManager) ProcessMessages() { + var msg extint.GatewayWrapper + var b, block []byte + for { + msg.Reset() + block = manager.conn.ReadBlock() + if block == nil { + log.Errorln("EngineProtoIpcManager.ProcessMessages.Fatal: engine socket returned empty message") + return + } else if len(block) < 2 { + log.Errorln("EngineProtoIpcManager.ProcessMessages.Error: engine socket message too small") + continue + } + b = block[2:] + + if err := proto.Unmarshal(b, &msg); err != nil { + log.Errorln("EngineProtoIpcManager.DecodingError (", err, "):", b) + continue + } + tag := reflect.TypeOf(msg.OneofMessageType).String() + manager.SendToListeners(tag, msg) + } +} + +// SwitchboardIpcManager handles passing CLAD messages between vic-gateway and vic-switchboard. +// field IpcManager: An anonymous field which basically acts like a subclass. +// field managerMutex: A mutex which prevents asynchronous reads and writes to the managedChannels map. +// field managedChannels: A mapping of messages to listening channels for the rpc handlers. +type SwitchboardIpcManager struct { + IpcManager + managerMutex sync.RWMutex + managedChannels map[gw_clad.SwitchboardResponseTag]([]chan gw_clad.SwitchboardResponse) +} + +// Init sets up the channel manager and the domain socket connection +func (manager *SwitchboardIpcManager) Init() { + manager.managedChannels = make(map[gw_clad.SwitchboardResponseTag]([]chan gw_clad.SwitchboardResponse)) + manager.Connect(path.Join(SocketPath, switchboardDomainSocket), "client") +} + +func (manager *SwitchboardIpcManager) handleSwitchboardMessages(msg *gw_clad.SwitchboardResponse) { + switch msg.Tag() { + case gw_clad.SwitchboardResponseTag_SdkProxyRequest: + request := msg.GetSdkProxyRequest() + go func() { + manager.Write( + gw_clad.NewSwitchboardRequestWithSdkProxyResponse( + bleProxy.handle(request), + ), + ) + }() + case gw_clad.SwitchboardResponseTag_ExternalConnectionRequest: + manager.Write(gw_clad.NewSwitchboardRequestWithExternalConnectionResponse( + &gw_clad.ExternalConnectionResponse{ + IsConnected: len(connectionId) != 0, + ConnectionId: connectionId, + }, + )) + case gw_clad.SwitchboardResponseTag_ClientGuidRefreshRequest: + response := make(chan struct{}) + tokenManager.ForceUpdate(response) + <-response + manager.Write(gw_clad.NewSwitchboardRequestWithClientGuidRefreshResponse( + &gw_clad.ClientGuidRefreshResponse{}, + )) + } +} + +// ProcessMessages loops through incoming messages on the ipc channel. +func (manager *SwitchboardIpcManager) ProcessMessages() { + var msg gw_clad.SwitchboardResponse + var b, block []byte + var buf bytes.Buffer + for { + buf.Reset() + block = manager.conn.ReadBlock() + if block == nil { + log.Errorln("SwitchboardIpcManager.ProcessMessages.Fatal: switchboard socket returned empty message") + return + } else if len(block) < 2 { + log.Errorln("SwitchboardIpcManager.ProcessMessages.Error: switchboard socket message too small") + continue + } + b = block[2:] + buf.Write(b) + if err := msg.Unpack(&buf); err != nil { + log.Errorln("SwitchboardIpcManager.DecodingError (", err, "):", b) + continue + } + + manager.handleSwitchboardMessages(&msg) + manager.SendToListeners(msg) + } +} + +// Write sends a CLAD message to vic-switchboard. +func (manager *SwitchboardIpcManager) Write(msg *gw_clad.SwitchboardRequest) (int, error) { + var err error + var buf bytes.Buffer + if msg == nil { + return -1, grpc.Errorf(codes.InvalidArgument, "Unable to parse request") + } + if err = binary.Write(&buf, binary.LittleEndian, uint16(msg.Size())); err != nil { + return -1, grpc.Errorf(codes.Internal, err.Error()) + } + if err = msg.Pack(&buf); err != nil { + return -1, grpc.Errorf(codes.Internal, err.Error()) + } + + manager.connMutex.Lock() + defer manager.connMutex.Unlock() + if logMessageContent { + log.Printf("%T: writing '%#v' message to Switchboard\n", *manager, *msg) + } + return manager.conn.Write(buf.Bytes()) +} + +// deleteListenerUnsafe is "unsafe" in that it requires the calling function to have already acquired the +// appropriate lock. Otherwise, there is a chance of simultaneous map access. +func (manager *SwitchboardIpcManager) deleteListenerUnsafe(listener chan gw_clad.SwitchboardResponse, tag gw_clad.SwitchboardResponseTag) { + chanSlice := manager.managedChannels[tag] + for idx, v := range chanSlice { + if v == listener { + chanSlice[idx] = chanSlice[len(chanSlice)-1] + manager.managedChannels[tag] = chanSlice[:len(chanSlice)-1] + manager.SafeClose(listener) + break + } + if len(chanSlice)-1 == idx { + log.Println("Warning: failed to remove listener:", listener, "from array:", chanSlice) + } + } + if len(manager.managedChannels[tag]) == 0 { + delete(manager.managedChannels, tag) + } +} + +// deleteListenerCallback provides a callback which may be invoked to remove the channel from the listeners. +func (manager *SwitchboardIpcManager) deleteListenerCallback(listener chan gw_clad.SwitchboardResponse, tag gw_clad.SwitchboardResponseTag) func() { + return func() { + manager.managerMutex.Lock() + defer manager.managerMutex.Unlock() + manager.deleteListenerUnsafe(listener, tag) + } +} + +// CreateChannel establishes a chan by which the ipc manager will pass the requested message type. +func (manager *SwitchboardIpcManager) CreateChannel(tag gw_clad.SwitchboardResponseTag, numChannels int) (func(), chan gw_clad.SwitchboardResponse) { + result := make(chan gw_clad.SwitchboardResponse, numChannels) + if logVerbose { + log.Printf("Listening for %+v\n", tag) + } + manager.managerMutex.Lock() + defer manager.managerMutex.Unlock() + slice := manager.managedChannels[tag] + if slice == nil { + slice = make([]chan gw_clad.SwitchboardResponse, 0) + } + manager.managedChannels[tag] = append(slice, result) + return manager.deleteListenerCallback(result, tag), result +} + +// SafeClose closes a channel if possible. +func (manager *SwitchboardIpcManager) SafeClose(listener chan gw_clad.SwitchboardResponse) { + select { + case _, ok := <-listener: + if ok { + close(listener) + } + default: + close(listener) + } +} + +// SendToListeners propagates messages to all waiting listener channels. +func (manager *SwitchboardIpcManager) SendToListeners(msg gw_clad.SwitchboardResponse) { + tag := msg.Tag() + markedForDelete := make(chan chan gw_clad.SwitchboardResponse, 5) + defer func() { + close(markedForDelete) + if len(markedForDelete) != 0 { + manager.managerMutex.Lock() + defer manager.managerMutex.Unlock() + for listener := range markedForDelete { + manager.deleteListenerUnsafe(listener, tag) + } + } + }() + manager.managerMutex.RLock() + defer manager.managerMutex.RUnlock() + chanList, ok := manager.managedChannels[tag] + if !ok { + return // No listeners for message + } + if logVerbose { + log.Printf("Sending %s to listeners\n", tag) + } + var wg sync.WaitGroup + for idx, listener := range chanList { + wg.Add(1) + go func(idx int, listener chan gw_clad.SwitchboardResponse, msg gw_clad.SwitchboardResponse) { + defer wg.Done() + select { + case listener <- msg: + if logVerbose { + log.Printf("Sent to listener #%d: %s\n", idx, tag) + } + case <-time.After(250 * time.Millisecond): + log.Errorf("SwitchboardIpcManager.SendToListeners: Failed to send message %s for listener #%d. There might be a problem with the channel.\n", tag, idx) + markedForDelete <- listener + } + }(idx, listener, msg) + } + wg.Wait() +} + +// TODO: Remove CLAD manager once it's no longer needed + +// EngineCladIpcManager handles passing CLAD messages between vic-gateway and vic-engine. +// field IpcManager: An anonymous field which basically acts like a subclass. +// field managerMutex: A mutex which prevents asynchronous reads and writes to the managedChannels map. +// field managedChannels: A mapping of messages to listening channels for the rpc handlers. +type EngineCladIpcManager struct { + IpcManager + managerMutex sync.RWMutex + managedChannels map[gw_clad.MessageRobotToExternalTag]([]chan gw_clad.MessageRobotToExternal) +} + +// Init sets up the channel manager and the domain socket connection +func (manager *EngineCladIpcManager) Init() { + manager.managedChannels = make(map[gw_clad.MessageRobotToExternalTag]([]chan gw_clad.MessageRobotToExternal)) + manager.Connect(path.Join(SocketPath, cladDomainSocket), "client") +} + +// Write sends a CLAD message to vic-engine. This will be handled by +// UiMessageHandler::ProcessMessages() in the engine C++ code. +func (manager *EngineCladIpcManager) Write(msg *gw_clad.MessageExternalToRobot) (int, error) { + var err error + var buf bytes.Buffer + if msg == nil { + return -1, grpc.Errorf(codes.InvalidArgument, "Unable to parse request") + } + if err = binary.Write(&buf, binary.LittleEndian, uint16(msg.Size())); err != nil { + return -1, grpc.Errorf(codes.Internal, err.Error()) + } + if err = msg.Pack(&buf); err != nil { + return -1, grpc.Errorf(codes.Internal, err.Error()) + } + + manager.connMutex.Lock() + defer manager.connMutex.Unlock() + if logMessageContent { + log.Printf("%T: writing '%#v' CLAD message to Engine\n", *manager, *msg) + } + return manager.conn.Write(buf.Bytes()) +} + +// deleteListenerUnsafe is "unsafe" in that it requires the calling function to have already acquired the +// appropriate lock. Otherwise, there is a chance of simultaneous map access. +func (manager *EngineCladIpcManager) deleteListenerUnsafe(listener chan gw_clad.MessageRobotToExternal, tag gw_clad.MessageRobotToExternalTag) { + chanSlice := manager.managedChannels[tag] + for idx, v := range chanSlice { + if v == listener { + chanSlice[idx] = chanSlice[len(chanSlice)-1] + manager.managedChannels[tag] = chanSlice[:len(chanSlice)-1] + manager.SafeClose(listener) + break + } + if len(chanSlice)-1 == idx { + log.Println("Warning: failed to remove listener:", listener, "from array:", chanSlice) + } + } + if len(manager.managedChannels[tag]) == 0 { + delete(manager.managedChannels, tag) + } +} + +// deleteListenerCallback provides a callback which may be invoked to remove the channel from the listeners. +func (manager *EngineCladIpcManager) deleteListenerCallback(listener chan gw_clad.MessageRobotToExternal, tag gw_clad.MessageRobotToExternalTag) func() { + return func() { + manager.managerMutex.Lock() + defer manager.managerMutex.Unlock() + manager.deleteListenerUnsafe(listener, tag) + } +} + +// CreateChannel establishes a chan by which the ipc manager will pass the requested message type. +func (manager *EngineCladIpcManager) CreateChannel(tag gw_clad.MessageRobotToExternalTag, numChannels int) (func(), chan gw_clad.MessageRobotToExternal) { + result := make(chan gw_clad.MessageRobotToExternal, numChannels) + if logVerbose { + log.Printf("Listening for %+v\n", tag) + } + manager.managerMutex.Lock() + defer manager.managerMutex.Unlock() + slice := manager.managedChannels[tag] + if slice == nil { + slice = make([]chan gw_clad.MessageRobotToExternal, 0) + } + manager.managedChannels[tag] = append(slice, result) + return manager.deleteListenerCallback(result, tag), result +} + +// SafeClose closes a channel if possible. +func (manager *EngineCladIpcManager) SafeClose(listener chan gw_clad.MessageRobotToExternal) { + select { + case _, ok := <-listener: + if ok { + close(listener) + } + default: + close(listener) + } +} + +// SendToListeners propagates messages to all waiting listener channels. +func (manager *EngineCladIpcManager) SendToListeners(msg gw_clad.MessageRobotToExternal) { + tag := msg.Tag() + markedForDelete := make(chan chan gw_clad.MessageRobotToExternal, 5) + defer func() { + close(markedForDelete) + if len(markedForDelete) != 0 { + manager.managerMutex.Lock() + defer manager.managerMutex.Unlock() + for listener := range markedForDelete { + manager.deleteListenerUnsafe(listener, tag) + } + } + }() + manager.managerMutex.RLock() + defer manager.managerMutex.RUnlock() + chanList, ok := manager.managedChannels[tag] + if !ok { + return // No listeners for message + } + if logVerbose { + log.Printf("Sending %s to listeners\n", tag) + } + var wg sync.WaitGroup + for idx, listener := range chanList { + wg.Add(1) + go func(idx int, listener chan gw_clad.MessageRobotToExternal, msg gw_clad.MessageRobotToExternal) { + defer wg.Done() + select { + case listener <- msg: + if logVerbose { + log.Printf("Sent to listener #%d: %s\n", idx, tag) + } + case <-time.After(250 * time.Millisecond): + log.Errorf("EngineCladIpcManager.SendToListeners: Failed to send message %s for listener #%d. There might be a problem with the channel.\n", tag, idx) + markedForDelete <- listener + } + }(idx, listener, msg) + } + wg.Wait() +} + +// SendEventToChannel is a temporary function to more easily turn CLAD messages into Protobuf events. +func (manager *EngineCladIpcManager) SendEventToChannel(event *extint.Event) { + tag := reflect.TypeOf(&extint.GatewayWrapper_Event{}).String() + msg := extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_Event{ + // TODO: Convert all events into proto events + Event: event, + }, + } + engineProtoManager.SendToListeners(tag, msg) +} + +// ProcessMessages loops through incoming messages on the ipc channel. +// Note: this will ignore unparsable messages because there are more +// clad messages sent from engine than understood by gateway. +func (manager *EngineCladIpcManager) ProcessMessages() { + var msg gw_clad.MessageRobotToExternal + var b, block []byte + var buf bytes.Buffer + for { + buf.Reset() + block = manager.conn.ReadBlock() + if block == nil { + log.Errorln("EngineCladIpcManager.ProcessMessages.Fatal: engine socket returned empty message") + return + } else if len(block) < 2 { + log.Errorln("EngineCladIpcManager.ProcessMessages.Error: engine socket message too small") + continue + } + b = block[2:] + buf.Write(b) + if err := msg.Unpack(&buf); err != nil { + // Intentionally ignoring errors for unknown messages + // TODO: treat this as an error condition once VIC-3186 is completed + continue + } + + // TODO: Refactor now that RobotObservedFace and RobotChangedObservedFaceID have been added to events + switch msg.Tag() { + case gw_clad.MessageRobotToExternalTag_Event: + event := CladEventToProto(msg.GetEvent()) + manager.SendEventToChannel(event) + // @TODO: Convert all face events to proto VIC-4643 + case gw_clad.MessageRobotToExternalTag_RobotObservedFace: + event := &extint.Event{ + EventType: &extint.Event_RobotObservedFace{ + RobotObservedFace: CladRobotObservedFaceToProto(msg.GetRobotObservedFace()), + }, + } + manager.SendEventToChannel(event) + case gw_clad.MessageRobotToExternalTag_RobotChangedObservedFaceID: + event := &extint.Event{ + EventType: &extint.Event_RobotChangedObservedFaceId{ + RobotChangedObservedFaceId: CladRobotChangedObservedFaceIDToProto(msg.GetRobotChangedObservedFaceID()), + }, + } + manager.SendEventToChannel(event) + // @TODO: Convert all object events to proto VIC-4643 + case gw_clad.MessageRobotToExternalTag_ObjectAvailable: + event := &extint.Event{ + EventType: &extint.Event_ObjectEvent{ + ObjectEvent: &extint.ObjectEvent{ + ObjectEventType: &extint.ObjectEvent_ObjectAvailable{ + ObjectAvailable: CladObjectAvailableToProto(msg.GetObjectAvailable()), + }, + }, + }, + } + manager.SendEventToChannel(event) + case gw_clad.MessageRobotToExternalTag_ObjectConnectionState: + event := &extint.Event{ + EventType: &extint.Event_ObjectEvent{ + ObjectEvent: &extint.ObjectEvent{ + ObjectEventType: &extint.ObjectEvent_ObjectConnectionState{ + ObjectConnectionState: CladObjectConnectionStateToProto(msg.GetObjectConnectionState()), + }, + }, + }, + } + manager.SendEventToChannel(event) + case gw_clad.MessageRobotToExternalTag_ObjectMoved: + event := &extint.Event{ + EventType: &extint.Event_ObjectEvent{ + ObjectEvent: &extint.ObjectEvent{ + ObjectEventType: &extint.ObjectEvent_ObjectMoved{ + ObjectMoved: CladObjectMovedToProto(msg.GetObjectMoved()), + }, + }, + }, + } + manager.SendEventToChannel(event) + case gw_clad.MessageRobotToExternalTag_ObjectStoppedMoving: + event := &extint.Event{ + EventType: &extint.Event_ObjectEvent{ + ObjectEvent: &extint.ObjectEvent{ + ObjectEventType: &extint.ObjectEvent_ObjectStoppedMoving{ + ObjectStoppedMoving: CladObjectStoppedMovingToProto(msg.GetObjectStoppedMoving()), + }, + }, + }, + } + manager.SendEventToChannel(event) + case gw_clad.MessageRobotToExternalTag_ObjectUpAxisChanged: + event := &extint.Event{ + EventType: &extint.Event_ObjectEvent{ + ObjectEvent: &extint.ObjectEvent{ + ObjectEventType: &extint.ObjectEvent_ObjectUpAxisChanged{ + ObjectUpAxisChanged: CladObjectUpAxisChangedToProto(msg.GetObjectUpAxisChanged()), + }, + }, + }, + } + manager.SendEventToChannel(event) + case gw_clad.MessageRobotToExternalTag_ObjectTapped: + event := &extint.Event{ + EventType: &extint.Event_ObjectEvent{ + ObjectEvent: &extint.ObjectEvent{ + ObjectEventType: &extint.ObjectEvent_ObjectTapped{ + ObjectTapped: CladObjectTappedToProto(msg.GetObjectTapped()), + }, + }, + }, + } + manager.SendEventToChannel(event) + case gw_clad.MessageRobotToExternalTag_RobotObservedObject: + event := &extint.Event{ + EventType: &extint.Event_ObjectEvent{ + ObjectEvent: &extint.ObjectEvent{ + ObjectEventType: &extint.ObjectEvent_RobotObservedObject{ + RobotObservedObject: CladRobotObservedObjectToProto(msg.GetRobotObservedObject()), + }, + }, + }, + } + manager.SendEventToChannel(event) + + default: + manager.SendToListeners(msg) + } + } +} diff --git a/vector-cloud/gateway/main.go b/vector-cloud/gateway/main.go new file mode 100644 index 0000000..d1ae2e2 --- /dev/null +++ b/vector-cloud/gateway/main.go @@ -0,0 +1,301 @@ +package main + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "io/ioutil" + "net" + "net/http" + "os" + "os/signal" + "runtime" + "runtime/debug" + "strings" + "syscall" + "time" + + extint "github.com/digital-dream-labs/vector-cloud/internal/proto/external_interface" + + "github.com/digital-dream-labs/vector-cloud/internal/log" + "github.com/digital-dream-labs/vector-cloud/internal/robot" + + grpcRuntime "github.com/grpc-ecosystem/grpc-gateway/runtime" + "golang.org/x/net/context" + "golang.org/x/time/rate" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials" +) + +// Enables logs about the requests coming and going from the gateway. +// Most useful for debugging the json output being sent to the app. +const ( + logVerbose = false + logMessageContent = false +) + +var ( + robotHostname string + signalHandler chan os.Signal + demoKeyPair *tls.Certificate + demoCertPool *x509.CertPool + cloudCheckLimiter *MultiLimiter + debugLogLimiter *MultiLimiter + userAuthLimiter *MultiLimiter + switchboardManager SwitchboardIpcManager + engineProtoManager EngineProtoIpcManager + tokenManager ClientTokenManager + bleProxy BLEProxy + numCommandsSentFromSDK uint32 + + // TODO: remove clad socket and map when there are no more clad messages being used + engineCladManager EngineCladIpcManager +) + +func LoggingUnaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (_ interface{}, errOut error) { + defer func() { + if err := recover(); err != nil { + log.Errorf("Recovered from fatal error in %s \"%s\": %s\n", info.FullMethod, err, debug.Stack()) + errOut = grpc.Errorf(codes.Internal, "%s", err) + } + }() + nameList := strings.Split(info.FullMethod, "/") + name := nameList[len(nameList)-1] + numCommandsSentFromSDK++ + if logMessageContent { + log.Printf("Received rpc request %s(%#v)\n", name, req) + } else { + log.Printf("Received rpc request %s\n", name) + } + resp, err := handler(ctx, req) + if logMessageContent { + log.Printf("Sending rpc response %s(%#v)\n", name, resp) + } else { + log.Printf("Sending rpc response %s\n", name) + } + return resp, err +} + +func LoggingStreamInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) (errOut error) { + defer func() { + if err := recover(); err != nil { + log.Errorf("Recovered from fatal error in %s \"%s\": %s\n", info.FullMethod, err, debug.Stack()) + errOut = grpc.Errorf(codes.Internal, "%s", err) + } + }() + nameList := strings.Split(info.FullMethod, "/") + name := nameList[len(nameList)-1] + numCommandsSentFromSDK++ + if logMessageContent { + log.Printf("Received stream request %s(%#v)\n", name, srv) + } else { + log.Printf("Received stream request %s\n", name) + } + return handler(srv, ss) +} + +func verboseHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc") { + name, err := checkAuth(w, r) // Note: we only check here because json will forward to grpc, and doesn't need the same auth + if err != nil { + http.Error(w, grpc.ErrorDesc(err), http.StatusUnauthorized) + return + } + log.Printf("Authorized connection from '%s'\n", name) + LogRequest(r, "grpc") + wrap := WrappedResponseWriter{w, "grpc"} + grpcServer.ServeHTTP(&wrap, r) + } else { + LogRequest(r, "json") + wrap := WrappedResponseWriter{w, "json"} + otherHandler.ServeHTTP(&wrap, r) + } + }) +} + +func grpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc") { + _, err := checkAuth(w, r) // Note: we only check here because json will forward to grpc, and doesn't need the same auth + if err != nil { + http.Error(w, grpc.ErrorDesc(err), http.StatusUnauthorized) + return + } + grpcServer.ServeHTTP(w, r) + } else { + otherHandler.ServeHTTP(w, r) + } + }) +} + +func cleanExit() { + log.Println("Uninstall crash reporter") + robot.UninstallCrashReporter() + + log.Println("Closed vic-gateway") + + os.Exit(0) +} + +func main() { + log.Tag = "vic-gateway" + log.Println("Launching vic-gateway") + + log.Println("Install crash reporter") + robot.InstallCrashReporter("vic-gateway") + + signalHandler = make(chan os.Signal, 1) + signal.Notify(signalHandler, syscall.SIGTERM) + go func() { + sig := <-signalHandler + log.Println("Received signal:", sig) + cleanExit() + }() + + if _, err := os.Stat(robot.GatewayCert); os.IsNotExist(err) { + log.Println("Cannot find cert:", robot.GatewayCert) + os.Exit(1) + } + if _, err := os.Stat(robot.GatewayKey); os.IsNotExist(err) { + log.Println("Cannot find key: ", robot.GatewayKey) + os.Exit(1) + } + + pair, err := tls.LoadX509KeyPair(robot.GatewayCert, robot.GatewayKey) + if err != nil { + log.Println("Failed to initialize key pair") + os.Exit(1) + } + demoKeyPair = &pair + demoCertPool = x509.NewCertPool() + caCert, err := ioutil.ReadFile(robot.GatewayCert) + if err != nil { + log.Println(err) + os.Exit(1) + } + ok := demoCertPool.AppendCertsFromPEM(caCert) + if !ok { + log.Println("Error: Bad certificates.") + panic("Bad certificates.") + } + addr := fmt.Sprintf("localhost:%d", Port) + + engineCladManager.Init() + defer engineCladManager.Close() + + engineProtoManager.Init() + defer engineProtoManager.Close() + + if IsOnRobot { + switchboardManager.Init() + defer switchboardManager.Close() + + tokenManager.Init() + defer tokenManager.Close() + } + + log.Println("Sockets successfully created") + + cloudCheckLimiter = NewMultiLimiter( + rate.NewLimiter(rate.Every(10*time.Second), 1), + rate.NewLimiter(rate.Every(time.Minute), 3), + ) + + debugLogLimiter = NewMultiLimiter( + rate.NewLimiter(rate.Every(time.Minute), 1), + rate.NewLimiter(rate.Every(time.Hour), 3), + ) + + userAuthLimiter = NewMultiLimiter( + rate.NewLimiter(rate.Every(10*time.Second), 10), + rate.NewLimiter(rate.Every(10*time.Minute), 25), + ) + + creds, err := credentials.NewServerTLSFromFile(robot.GatewayCert, robot.GatewayKey) + if err != nil { + log.Println("Error creating server tls:", err) + os.Exit(1) + } + grpcServer := grpc.NewServer( + grpc.Creds(creds), + grpc.UnaryInterceptor(LoggingUnaryInterceptor), + grpc.StreamInterceptor(LoggingStreamInterceptor), + ) + extint.RegisterExternalInterfaceServer(grpcServer, newServer()) + ctx := context.Background() + + if runtime.GOOS == "darwin" { + robotHostname = "Vector-Local" + } else { + robotHostname, err = os.Hostname() + if err != nil { + log.Println("Failed to get Hostname:", err) + os.Exit(1) + } + } + + log.Println("Hostname:", robotHostname) + + tlsConf := &tls.Config{ + ServerName: robotHostname, + Certificates: []tls.Certificate{*demoKeyPair}, + RootCAs: demoCertPool, + } + bleProxy = BLEProxy{ + Client: &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: tlsConf, + }, + }, + Address: addr, + } + bleProxy.initialize(grpcServer.GetServiceInfo()) + dcreds := credentials.NewTLS(tlsConf) + dopts := []grpc.DialOption{grpc.WithTransportCredentials(dcreds)} + + gwmux := grpcRuntime.NewServeMux(grpcRuntime.WithMarshalerOption(grpcRuntime.MIMEWildcard, &grpcRuntime.JSONPb{EmitDefaults: true, OrigName: true, EnumsAsInts: true})) + err = extint.RegisterExternalInterfaceHandlerFromEndpoint(ctx, gwmux, addr, dopts) + if err != nil { + log.Println("Error during RegisterExternalInterfaceHandlerFromEndpoint:", err) + os.Exit(1) + } + + conn, err := net.Listen("tcp", fmt.Sprintf(":%d", Port)) + if err != nil { + log.Println("Error during Listen:", err) + panic(err) + } + + handlerFunc := grpcHandlerFunc(grpcServer, gwmux) + if logVerbose { + handlerFunc = verboseHandlerFunc(grpcServer, gwmux) + } + + srv := &http.Server{ + Addr: addr, + Handler: handlerFunc, + TLSConfig: &tls.Config{ + Certificates: []tls.Certificate{*demoKeyPair}, + NextProtos: []string{"h2"}, + }, + } + + go engineCladManager.ProcessMessages() + go engineProtoManager.ProcessMessages() + if IsOnRobot { + go switchboardManager.ProcessMessages() + go tokenManager.StartUpdateListener() + } + + log.Println("Listening on Port:", Port) + err = srv.Serve(tls.NewListener(conn, srv.TLSConfig)) + + if err != http.ErrServerClosed { + log.Println("Error during Serve:", err) + os.Exit(1) + } + + cleanExit() +} diff --git a/vector-cloud/gateway/message_handler.go b/vector-cloud/gateway/message_handler.go new file mode 100644 index 0000000..41394be --- /dev/null +++ b/vector-cloud/gateway/message_handler.go @@ -0,0 +1,3670 @@ +package main + +import ( + "encoding/binary" + "io/ioutil" + "math/rand" + "os" + "os/exec" + "reflect" + "regexp" + "strconv" + "strings" + "sync" + "time" + + cloud_clad "github.com/digital-dream-labs/vector-cloud/internal/clad/cloud" + gw_clad "github.com/digital-dream-labs/vector-cloud/internal/clad/gateway" + extint "github.com/digital-dream-labs/vector-cloud/internal/proto/external_interface" + + "github.com/digital-dream-labs/vector-cloud/internal/log" + + "github.com/golang/protobuf/proto" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" +) + +const faceImagePixelsPerChunk = 600 +const endOfAnimationList = "EndOfListAnimationsResponses" + +var ( + connectionIdLock sync.Mutex + connectionId string + statusStreamRunning bool + lastProgress int64 + lastExpected int64 +) + +// TODO: we should find a way to auto-generate the equivalent of this function as part of clad or protoc +func ProtoMoveHeadToClad(msg *extint.MoveHeadRequest) *gw_clad.MessageExternalToRobot { + return gw_clad.NewMessageExternalToRobotWithMoveHead(&gw_clad.MoveHead{ + SpeedRadPerSec: msg.SpeedRadPerSec, + }) +} + +// TODO: we should find a way to auto-generate the equivalent of this function as part of clad or protoc +func ProtoMoveLiftToClad(msg *extint.MoveLiftRequest) *gw_clad.MessageExternalToRobot { + return gw_clad.NewMessageExternalToRobotWithMoveLift(&gw_clad.MoveLift{ + SpeedRadPerSec: msg.SpeedRadPerSec, + }) +} + +// TODO: we should find a way to auto-generate the equivalent of this function as part of clad or protoc +func FaceImageChunkToClad(faceData [faceImagePixelsPerChunk]uint16, pixelCount uint16, chunkIndex uint8, chunkCount uint8, durationMs uint32, interruptRunning bool) *gw_clad.MessageExternalToRobot { + return gw_clad.NewMessageExternalToRobotWithDisplayFaceImageRGBChunk(&gw_clad.DisplayFaceImageRGBChunk{ + FaceData: faceData, + NumPixels: pixelCount, + ChunkIndex: chunkIndex, + NumChunks: chunkCount, + DurationMs: durationMs, + InterruptRunning: interruptRunning, + }) +} + +func ProtoAppIntentToClad(msg *extint.AppIntentRequest) *gw_clad.MessageExternalToRobot { + return gw_clad.NewMessageExternalToRobotWithAppIntent(&gw_clad.AppIntent{ + Param: msg.Param, + Intent: msg.Intent, + }) +} + +func ProtoRequestEnrolledNamesToClad(msg *extint.RequestEnrolledNamesRequest) *gw_clad.MessageExternalToRobot { + return gw_clad.NewMessageExternalToRobotWithRequestEnrolledNames(&gw_clad.RequestEnrolledNames{}) +} + +func ProtoCancelFaceEnrollmentToClad(msg *extint.CancelFaceEnrollmentRequest) *gw_clad.MessageExternalToRobot { + return gw_clad.NewMessageExternalToRobotWithCancelFaceEnrollment(&gw_clad.CancelFaceEnrollment{}) +} + +func ProtoUpdateEnrolledFaceByIDToClad(msg *extint.UpdateEnrolledFaceByIDRequest) *gw_clad.MessageExternalToRobot { + return gw_clad.NewMessageExternalToRobotWithUpdateEnrolledFaceByID(&gw_clad.UpdateEnrolledFaceByID{ + FaceID: msg.FaceId, + OldName: msg.OldName, + NewName: msg.NewName, + }) +} + +func ProtoEraseEnrolledFaceByIDToClad(msg *extint.EraseEnrolledFaceByIDRequest) *gw_clad.MessageExternalToRobot { + return gw_clad.NewMessageExternalToRobotWithEraseEnrolledFaceByID(&gw_clad.EraseEnrolledFaceByID{ + FaceID: msg.FaceId, + }) +} + +func ProtoEraseAllEnrolledFacesToClad(msg *extint.EraseAllEnrolledFacesRequest) *gw_clad.MessageExternalToRobot { + return gw_clad.NewMessageExternalToRobotWithEraseAllEnrolledFaces(&gw_clad.EraseAllEnrolledFaces{}) +} + +func ProtoPoseToClad(msg *extint.PoseStruct) *gw_clad.PoseStruct3d { + return &gw_clad.PoseStruct3d{ + X: msg.X, + Y: msg.Y, + Z: msg.Z, + Q0: msg.Q0, + Q1: msg.Q1, + Q2: msg.Q2, + Q3: msg.Q3, + OriginID: msg.OriginId, + } +} + +func ProtoCreateFixedCustomObjectToClad(msg *extint.CreateFixedCustomObjectRequest) *gw_clad.MessageExternalToRobot { + return gw_clad.NewMessageExternalToRobotWithCreateFixedCustomObject(&gw_clad.CreateFixedCustomObject{ + Pose: *ProtoPoseToClad(msg.Pose), + XSizeMm: msg.XSizeMm, + YSizeMm: msg.YSizeMm, + ZSizeMm: msg.ZSizeMm, + }) +} + +func ProtoDefineCustomBoxToClad(msg *extint.DefineCustomObjectRequest, def *extint.CustomBoxDefinition) *gw_clad.MessageExternalToRobot { + // Convert from the proto defined CustomObject enum to the more general clad ObjectType enum space + object_type := gw_clad.ObjectType(int(msg.CustomType) - int(extint.CustomType_CUSTOM_TYPE_00) + int(gw_clad.ObjectType_CustomType00)) + + return gw_clad.NewMessageExternalToRobotWithDefineCustomBox(&gw_clad.DefineCustomBox{ + CustomType: object_type, + MarkerFront: gw_clad.CustomObjectMarker(def.MarkerFront - 1), + MarkerBack: gw_clad.CustomObjectMarker(def.MarkerBack - 1), + MarkerTop: gw_clad.CustomObjectMarker(def.MarkerTop - 1), + MarkerBottom: gw_clad.CustomObjectMarker(def.MarkerBottom - 1), + MarkerLeft: gw_clad.CustomObjectMarker(def.MarkerLeft - 1), + MarkerRight: gw_clad.CustomObjectMarker(def.MarkerRight - 1), + XSizeMm: def.XSizeMm, + YSizeMm: def.YSizeMm, + ZSizeMm: def.ZSizeMm, + MarkerWidthMm: def.MarkerWidthMm, + MarkerHeightMm: def.MarkerHeightMm, + IsUnique: msg.IsUnique, + }) +} + +func ProtoDefineCustomCubeToClad(msg *extint.DefineCustomObjectRequest, def *extint.CustomCubeDefinition) *gw_clad.MessageExternalToRobot { + // Convert from the proto defined CustomObject enum to the more general clad ObjectType enum space + object_type := gw_clad.ObjectType(int(msg.CustomType) - int(extint.CustomType_CUSTOM_TYPE_00) + int(gw_clad.ObjectType_CustomType00)) + + return gw_clad.NewMessageExternalToRobotWithDefineCustomCube(&gw_clad.DefineCustomCube{ + CustomType: object_type, + Marker: gw_clad.CustomObjectMarker(def.Marker - 1), + SizeMm: def.SizeMm, + MarkerWidthMm: def.MarkerWidthMm, + MarkerHeightMm: def.MarkerHeightMm, + IsUnique: msg.IsUnique, + }) +} + +func ProtoDefineCustomWallToClad(msg *extint.DefineCustomObjectRequest, def *extint.CustomWallDefinition) *gw_clad.MessageExternalToRobot { + // Convert from the proto defined CustomObject enum to the more general clad ObjectType enum space + object_type := gw_clad.ObjectType(int(msg.CustomType) - int(extint.CustomType_CUSTOM_TYPE_00) + int(gw_clad.ObjectType_CustomType00)) + + return gw_clad.NewMessageExternalToRobotWithDefineCustomWall(&gw_clad.DefineCustomWall{ + CustomType: object_type, + Marker: gw_clad.CustomObjectMarker(def.Marker - 1), + WidthMm: def.WidthMm, + HeightMm: def.HeightMm, + MarkerWidthMm: def.MarkerWidthMm, + MarkerHeightMm: def.MarkerHeightMm, + IsUnique: msg.IsUnique, + }) +} + +func SliceToArray(msg []uint32) [3]uint32 { + var arr [3]uint32 + copy(arr[:], msg) + return arr +} + +func CladCladRectToProto(msg *gw_clad.CladRect) *extint.CladRect { + return &extint.CladRect{ + XTopLeft: msg.XTopLeft, + YTopLeft: msg.YTopLeft, + Width: msg.Width, + Height: msg.Height, + } +} + +func CladCladPointsToProto(msg []gw_clad.CladPoint2d) []*extint.CladPoint { + var points []*extint.CladPoint + for _, point := range msg { + points = append(points, &extint.CladPoint{X: point.X, Y: point.Y}) + } + return points +} + +func CladExpressionValuesToProto(msg []uint8) []uint32 { + var expression_values []uint32 + for _, val := range msg { + expression_values = append(expression_values, uint32(val)) + } + return expression_values +} + +func CladRobotObservedFaceToProto(msg *gw_clad.RobotObservedFace) *extint.RobotObservedFace { + // BlinkAmount, Gaze and SmileAmount are not exposed to the SDK + return &extint.RobotObservedFace{ + FaceId: msg.FaceID, + Timestamp: msg.Timestamp, + Pose: CladPoseToProto(&msg.Pose), + ImgRect: CladCladRectToProto(&msg.ImgRect), + Name: msg.Name, + + Expression: extint.FacialExpression(msg.Expression + 1), // protobuf enums have a 0 start value + + // Individual expression values histogram, sums to 100 (Exception: all zero if expressio: msg. + ExpressionValues: CladExpressionValuesToProto(msg.ExpressionValues[:]), + + // Face landmarks + LeftEye: CladCladPointsToProto(msg.LeftEye), + RightEye: CladCladPointsToProto(msg.RightEye), + Nose: CladCladPointsToProto(msg.Nose), + Mouth: CladCladPointsToProto(msg.Mouth), + } +} + +func CladRobotChangedObservedFaceIDToProto(msg *gw_clad.RobotChangedObservedFaceID) *extint.RobotChangedObservedFaceID { + return &extint.RobotChangedObservedFaceID{ + OldId: msg.OldID, + NewId: msg.NewID, + } +} + +func CladPoseToProto(msg *gw_clad.PoseStruct3d) *extint.PoseStruct { + return &extint.PoseStruct{ + X: msg.X, + Y: msg.Y, + Z: msg.Z, + Q0: msg.Q0, + Q1: msg.Q1, + Q2: msg.Q2, + Q3: msg.Q3, + OriginId: msg.OriginID, + } +} + +func CladEventToProto(msg *gw_clad.Event) *extint.Event { + switch tag := msg.Tag(); tag { + // Event is currently unused in CLAD, but if you start + // using it again, replace [MessageName] with your msg name + // case gw_clad.EventTag_[MessageName]: + // return &extint.Event{ + // EventType: &extint.Event_[MessageName]{ + // Clad[MessageName]ToProto(msg.Get[MessageName]()), + // }, + // } + case gw_clad.EventTag_INVALID: + log.Println(tag, "tag is invalid") + return nil + default: + log.Println(tag, "tag is not yet implemented") + return nil + } +} + +func CladObjectConnectionStateToProto(msg *gw_clad.ObjectConnectionState) *extint.ObjectConnectionState { + return &extint.ObjectConnectionState{ + ObjectId: msg.ObjectID, + FactoryId: msg.FactoryID, + ObjectType: extint.ObjectType(msg.ObjectType + 1), + Connected: msg.Connected, + } +} +func CladObjectAvailableToProto(msg *gw_clad.ObjectAvailable) *extint.ObjectAvailable { + return &extint.ObjectAvailable{ + FactoryId: msg.FactoryId, + } +} +func CladObjectMovedToProto(msg *gw_clad.ObjectMoved) *extint.ObjectMoved { + return &extint.ObjectMoved{ + Timestamp: msg.Timestamp, + ObjectId: msg.ObjectID, + } +} +func CladObjectStoppedMovingToProto(msg *gw_clad.ObjectStoppedMoving) *extint.ObjectStoppedMoving { + return &extint.ObjectStoppedMoving{ + Timestamp: msg.Timestamp, + ObjectId: msg.ObjectID, + } +} +func CladObjectUpAxisChangedToProto(msg *gw_clad.ObjectUpAxisChanged) *extint.ObjectUpAxisChanged { + // In clad, unknown is the final value + // In proto, the convention is that 0 is unknown + upAxis := extint.UpAxis_INVALID_AXIS + if msg.UpAxis != gw_clad.UpAxis_UnknownAxis { + upAxis = extint.UpAxis(msg.UpAxis + 1) + } + + return &extint.ObjectUpAxisChanged{ + Timestamp: msg.Timestamp, + ObjectId: msg.ObjectID, + UpAxis: upAxis, + } +} +func CladObjectTappedToProto(msg *gw_clad.ObjectTapped) *extint.ObjectTapped { + return &extint.ObjectTapped{ + Timestamp: msg.Timestamp, + ObjectId: msg.ObjectID, + } +} + +func CladRobotObservedObjectToProto(msg *gw_clad.RobotObservedObject) *extint.RobotObservedObject { + return &extint.RobotObservedObject{ + Timestamp: msg.Timestamp, + ObjectFamily: extint.ObjectFamily(msg.ObjectFamily + 1), + ObjectType: extint.ObjectType(msg.ObjectType + 1), + ObjectId: msg.ObjectID, + ImgRect: CladCladRectToProto(&msg.ImgRect), + Pose: CladPoseToProto(&msg.Pose), + IsActive: uint32(msg.IsActive), + TopFaceOrientationRad: msg.TopFaceOrientationRad, + } +} + +func CladMemoryMapBeginToProtoNavMapInfo(msg *gw_clad.MemoryMapMessageBegin) *extint.NavMapInfo { + return &extint.NavMapInfo{ + RootDepth: int32(msg.RootDepth), + RootSizeMm: msg.RootSizeMm, + RootCenterX: msg.RootCenterX, + RootCenterY: msg.RootCenterY, + RootCenterZ: 0.0, + } +} + +func CladMemoryMapQuadInfoToProto(msg *gw_clad.MemoryMapQuadInfo) *extint.NavMapQuadInfo { + return &extint.NavMapQuadInfo{ + Content: extint.NavNodeContentType(msg.Content), // Not incrementing this one because the CLAD enum has 0 as unknown + Depth: uint32(msg.Depth), + ColorRgba: msg.ColorRGBA, + } +} + +func SendOnboardingComplete(in *extint.GatewayWrapper_OnboardingCompleteRequest) (*extint.OnboardingInputResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_OnboardingCompleteResponse{}, 1) + defer f() + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: in, + }) + if err != nil { + return nil, err + } + completeResponse, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + return &extint.OnboardingInputResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_REQUEST_PROCESSING, + }, + OneofMessageType: &extint.OnboardingInputResponse_OnboardingCompleteResponse{ + OnboardingCompleteResponse: completeResponse.GetOnboardingCompleteResponse(), + }, + }, nil +} + +func SendOnboardingWakeUpStarted(in *extint.GatewayWrapper_OnboardingWakeUpStartedRequest) (*extint.OnboardingInputResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_OnboardingWakeUpStartedResponse{}, 1) + defer f() + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: in, + }) + if err != nil { + return nil, err + } + completeResponse, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + return &extint.OnboardingInputResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_REQUEST_PROCESSING, + }, + OneofMessageType: &extint.OnboardingInputResponse_OnboardingWakeUpStartedResponse{ + OnboardingWakeUpStartedResponse: completeResponse.GetOnboardingWakeUpStartedResponse(), + }, + }, nil +} + +func SendOnboardingWakeUp(in *extint.GatewayWrapper_OnboardingWakeUpRequest) (*extint.OnboardingInputResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_OnboardingWakeUpResponse{}, 1) + defer f() + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: in, + }) + if err != nil { + return nil, err + } + wakeUpResponse, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + return &extint.OnboardingInputResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_REQUEST_PROCESSING, + }, + OneofMessageType: &extint.OnboardingInputResponse_OnboardingWakeUpResponse{ + OnboardingWakeUpResponse: wakeUpResponse.GetOnboardingWakeUpResponse(), + }, + }, nil +} + +func SendAppDisconnected() { + msg := &extint.GatewayWrapper_AppDisconnected{ + AppDisconnected: &extint.AppDisconnected{}, + } + engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: msg, + }) + // no error handling +} + +func SendOnboardingSkipOnboarding(in *extint.GatewayWrapper_OnboardingSkipOnboarding) (*extint.OnboardingInputResponse, error) { + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: in, + }) + if err != nil { + return nil, err + } + return &extint.OnboardingInputResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_REQUEST_PROCESSING, + }, + }, nil +} + +func SendOnboardingRestart(in *extint.GatewayWrapper_OnboardingRestart) (*extint.OnboardingInputResponse, error) { + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: in, + }) + if err != nil { + return nil, err + } + return &extint.OnboardingInputResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_REQUEST_PROCESSING, + }, + }, nil +} + +func SendOnboardingSetPhase(in *extint.GatewayWrapper_OnboardingSetPhaseRequest) (*extint.OnboardingInputResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_OnboardingSetPhaseResponse{}, 1) + defer f() + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: in, + }) + if err != nil { + return nil, err + } + completeResponse, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + return &extint.OnboardingInputResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_REQUEST_PROCESSING, + }, + OneofMessageType: &extint.OnboardingInputResponse_OnboardingSetPhaseResponse{ + OnboardingSetPhaseResponse: completeResponse.GetOnboardingSetPhaseResponse(), + }, + }, nil +} + +func SendOnboardingPhaseProgress(in *extint.GatewayWrapper_OnboardingPhaseProgressRequest) (*extint.OnboardingInputResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_OnboardingPhaseProgressResponse{}, 1) + defer f() + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: in, + }) + if err != nil { + return nil, err + } + completeResponse, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + return &extint.OnboardingInputResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_REQUEST_PROCESSING, + }, + OneofMessageType: &extint.OnboardingInputResponse_OnboardingPhaseProgressResponse{ + OnboardingPhaseProgressResponse: completeResponse.GetOnboardingPhaseProgressResponse(), + }, + }, nil +} + +func SendOnboardingChargeInfo(in *extint.GatewayWrapper_OnboardingChargeInfoRequest) (*extint.OnboardingInputResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_OnboardingChargeInfoResponse{}, 1) + defer f() + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: in, + }) + if err != nil { + return nil, err + } + completeResponse, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + return &extint.OnboardingInputResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_REQUEST_PROCESSING, + }, + OneofMessageType: &extint.OnboardingInputResponse_OnboardingChargeInfoResponse{ + OnboardingChargeInfoResponse: completeResponse.GetOnboardingChargeInfoResponse(), + }, + }, nil +} + +func SendOnboardingMarkCompleteAndExit(in *extint.GatewayWrapper_OnboardingMarkCompleteAndExit) (*extint.OnboardingInputResponse, error) { + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: in, + }) + if err != nil { + return nil, err + } + return &extint.OnboardingInputResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_REQUEST_PROCESSING, + }, + }, nil +} + +// The service definition. +// This must implement all the rpc functions defined in the external_interface proto file. +type rpcService struct{} + +func (service *rpcService) ProtocolVersion(ctx context.Context, in *extint.ProtocolVersionRequest) (*extint.ProtocolVersionResponse, error) { + response := &extint.ProtocolVersionResponse{ + HostVersion: int64(extint.ProtocolVersion_PROTOCOL_VERSION_CURRENT), + } + if in.ClientVersion < int64(extint.ProtocolVersion_PROTOCOL_VERSION_MINIMUM) { + response.Result = extint.ProtocolVersionResponse_UNSUPPORTED + } else { + response.Result = extint.ProtocolVersionResponse_SUCCESS + } + return response, nil +} + +func (service *rpcService) DriveWheels(ctx context.Context, in *extint.DriveWheelsRequest) (*extint.DriveWheelsResponse, error) { + message := &extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_DriveWheelsRequest{ + DriveWheelsRequest: in, + }, + } + _, _, err := engineProtoManager.Write(message) + if err != nil { + return nil, err + } + return &extint.DriveWheelsResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_REQUEST_PROCESSING, + }, + }, nil +} + +// PlayAnimationTrigger intentionally waits for PlayAnimationResponse (not something like PlayAnimationTriggerResponse), because +// in the end, the engine is playing an animation. +func (service *rpcService) PlayAnimationTrigger(ctx context.Context, in *extint.PlayAnimationTriggerRequest) (*extint.PlayAnimationResponse, error) { + f, animResponseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_PlayAnimationResponse{}, 1) + defer f() + + message := &extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_PlayAnimationTriggerRequest{ + PlayAnimationTriggerRequest: in, + }, + } + _, _, err := engineProtoManager.Write(message) + if err != nil { + return nil, err + } + + setPlayAnimationResponse, ok := <-animResponseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := setPlayAnimationResponse.GetPlayAnimationResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return response, nil +} + +func (service *rpcService) PlayAnimation(ctx context.Context, in *extint.PlayAnimationRequest) (*extint.PlayAnimationResponse, error) { + f, animResponseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_PlayAnimationResponse{}, 1) + defer f() + + message := &extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_PlayAnimationRequest{ + PlayAnimationRequest: in, + }, + } + _, _, err := engineProtoManager.Write(message) + if err != nil { + return nil, err + } + + setPlayAnimationResponse, ok := <-animResponseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := setPlayAnimationResponse.GetPlayAnimationResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return response, nil +} + +func (service *rpcService) ListAnimations(ctx context.Context, in *extint.ListAnimationsRequest) (*extint.ListAnimationsResponse, error) { + // 50 messages are sent per engine tick, however, in case it puts out multiple ticks before we drain, we need a buffer to hold lots o' data. + delete_listener_callback, animationAvailableResponse := engineProtoManager.CreateChannel(&extint.GatewayWrapper_ListAnimationsResponse{}, 500) + defer delete_listener_callback() + + message := &extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_ListAnimationsRequest{ + ListAnimationsRequest: in, + }, + } + + _, _, err := engineProtoManager.Write(message) + if err != nil { + return nil, err + } + + var anims []*extint.Animation + + done := false + for done == false { + select { + case chanResponse, ok := <-animationAvailableResponse: + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + for _, anim := range chanResponse.GetListAnimationsResponse().AnimationNames { + animName := anim.GetName() + // Don't change endOfAnimationList - it's what we'll receive from the .cpp sender. + if animName == endOfAnimationList { + done = true + } else { + if strings.Contains(animName, "_avs_") { + // VIC-11583 All Alexa animation names contain "_avs_". Prevent these animations from reaching the SDK. + continue + } + var newAnim = extint.Animation{ + Name: animName, + } + anims = append(anims, &newAnim) + } + } + case <-time.After(5 * time.Second): + return nil, grpc.Errorf(codes.DeadlineExceeded, "ListAnimations request timed out") + } + } + + return &extint.ListAnimationsResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + }, + AnimationNames: anims, + }, nil +} + +func (service *rpcService) ListAnimationTriggers(ctx context.Context, in *extint.ListAnimationTriggersRequest) (*extint.ListAnimationTriggersResponse, error) { + delete_listener_callback, animationTriggerAvailableResponse := engineProtoManager.CreateChannel(&extint.GatewayWrapper_ListAnimationTriggersResponse{}, 500) + defer delete_listener_callback() + + message := &extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_ListAnimationTriggersRequest{ + ListAnimationTriggersRequest: in, + }, + } + + _, _, err := engineProtoManager.Write(message) + if err != nil { + return nil, err + } + + var animTriggers []*extint.AnimationTrigger + + done := false + for done == false { + select { + case chanResponse, ok := <-animationTriggerAvailableResponse: + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + for _, animTrigger := range chanResponse.GetListAnimationTriggersResponse().AnimationTriggerNames { + animTriggerName := animTrigger.GetName() + // Don't change endOfAnimationList - it's what we'll receive from the .cpp sender. + if animTriggerName == endOfAnimationList { + done = true + } else { + animTriggerNameLower := strings.ToLower(animTriggerName) + if strings.Contains(animTriggerNameLower, "deprecated") || strings.Contains(animTriggerNameLower, "alexa") { + // Prevent animation triggers that are deprecated or are for alexa from reaching the SDK + continue + } + newAnimTrigger := extint.AnimationTrigger{ + Name: animTriggerName, + } + animTriggers = append(animTriggers, &newAnimTrigger) + } + } + case <-time.After(10 * time.Second): + return nil, grpc.Errorf(codes.DeadlineExceeded, "ListAnimationTriggers request timed out") + } + } + + return &extint.ListAnimationTriggersResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + }, + AnimationTriggerNames: animTriggers, + }, nil +} + +func (service *rpcService) MoveHead(ctx context.Context, in *extint.MoveHeadRequest) (*extint.MoveHeadResponse, error) { + _, err := engineCladManager.Write(ProtoMoveHeadToClad(in)) + if err != nil { + return nil, err + } + return &extint.MoveHeadResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_REQUEST_PROCESSING, + }, + }, nil +} + +func (service *rpcService) MoveLift(ctx context.Context, in *extint.MoveLiftRequest) (*extint.MoveLiftResponse, error) { + _, err := engineCladManager.Write(ProtoMoveLiftToClad(in)) + if err != nil { + return nil, err + } + return &extint.MoveLiftResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_REQUEST_PROCESSING, + }, + }, nil +} + +func (service *rpcService) StopAllMotors(ctx context.Context, in *extint.StopAllMotorsRequest) (*extint.StopAllMotorsResponse, error) { + message := &extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_StopAllMotorsRequest{ + StopAllMotorsRequest: in, + }, + } + _, _, err := engineProtoManager.Write(message) + if err != nil { + return nil, err + } + return &extint.StopAllMotorsResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_REQUEST_PROCESSING, + }, + }, nil +} + +func (service *rpcService) CancelBehavior(ctx context.Context, in *extint.CancelBehaviorRequest) (*extint.CancelBehaviorResponse, error) { + message := &extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_CancelBehaviorRequest{ + CancelBehaviorRequest: in, + }, + } + _, _, err := engineProtoManager.Write(message) + if err != nil { + return nil, err + } + return &extint.CancelBehaviorResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_REQUEST_PROCESSING, + }, + }, nil +} + +func (service *rpcService) CancelActionByIdTag(ctx context.Context, in *extint.CancelActionByIdTagRequest) (*extint.CancelActionByIdTagResponse, error) { + message := &extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_CancelActionByIdTagRequest{ + CancelActionByIdTagRequest: in, + }, + } + _, _, err := engineProtoManager.Write(message) + if err != nil { + return nil, err + } + return &extint.CancelActionByIdTagResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_REQUEST_PROCESSING, + }, + }, nil +} + +func SendFaceDataAsChunks(in *extint.DisplayFaceImageRGBRequest, chunkCount int, pixelsPerChunk int, totalPixels int) error { + var convertedUint16Data [faceImagePixelsPerChunk]uint16 + + // cycle until we run out of bytes to transfer + for i := 0; i < chunkCount; i++ { + pixelCount := faceImagePixelsPerChunk + if i == chunkCount-1 { + pixelCount = totalPixels - faceImagePixelsPerChunk*i + } + + firstByte := (pixelsPerChunk * 2) * i + finalByte := firstByte + (pixelCount * 2) + slicedBinaryData := in.FaceData[firstByte:finalByte] // TODO: Make this not implode on empty + + for j := 0; j < pixelCount; j++ { + uintAsBytes := slicedBinaryData[j*2 : j*2+2] + convertedUint16Data[j] = binary.BigEndian.Uint16(uintAsBytes) + } + + // Copy a subset of the pixels to the bytes? + message := FaceImageChunkToClad(convertedUint16Data, uint16(pixelCount), uint8(i), uint8(chunkCount), in.DurationMs, in.InterruptRunning) + + _, err := engineCladManager.Write(message) + if err != nil { + return err + } + } + + return nil +} + +func (service *rpcService) DisplayFaceImageRGB(ctx context.Context, in *extint.DisplayFaceImageRGBRequest) (*extint.DisplayFaceImageRGBResponse, error) { + const totalPixels = 17664 + chunkCount := (totalPixels + faceImagePixelsPerChunk + 1) / faceImagePixelsPerChunk + + SendFaceDataAsChunks(in, chunkCount, faceImagePixelsPerChunk, totalPixels) + + return &extint.DisplayFaceImageRGBResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_REQUEST_PROCESSING, + }, + }, nil +} + +func (service *rpcService) AppIntent(ctx context.Context, in *extint.AppIntentRequest) (*extint.AppIntentResponse, error) { + _, err := engineCladManager.Write(ProtoAppIntentToClad(in)) + if err != nil { + return nil, err + } + return &extint.AppIntentResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_REQUEST_PROCESSING, + }, + }, nil +} + +func (service *rpcService) CancelFaceEnrollment(ctx context.Context, in *extint.CancelFaceEnrollmentRequest) (*extint.CancelFaceEnrollmentResponse, error) { + _, err := engineCladManager.Write(ProtoCancelFaceEnrollmentToClad(in)) + if err != nil { + return nil, err + } + return &extint.CancelFaceEnrollmentResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_REQUEST_PROCESSING, + }, + }, nil +} + +func (service *rpcService) RequestEnrolledNames(ctx context.Context, in *extint.RequestEnrolledNamesRequest) (*extint.RequestEnrolledNamesResponse, error) { + f, enrolledNamesResponse := engineCladManager.CreateChannel(gw_clad.MessageRobotToExternalTag_EnrolledNamesResponse, 1) + defer f() + + _, err := engineCladManager.Write(ProtoRequestEnrolledNamesToClad(in)) + if err != nil { + return nil, err + } + names, ok := <-enrolledNamesResponse + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + var faces []*extint.LoadedKnownFace + for _, element := range names.GetEnrolledNamesResponse().Faces { + var newFace = extint.LoadedKnownFace{ + SecondsSinceFirstEnrolled: element.SecondsSinceFirstEnrolled, + SecondsSinceLastUpdated: element.SecondsSinceLastUpdated, + SecondsSinceLastSeen: element.SecondsSinceLastSeen, + LastSeenSecondsSinceEpoch: element.LastSeenSecondsSinceEpoch, + FaceId: element.FaceID, + Name: element.Name, + } + faces = append(faces, &newFace) + } + return &extint.RequestEnrolledNamesResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + }, + Faces: faces, + }, nil +} + +// TODO Wait for response RobotRenamedEnrolledFace +func (service *rpcService) UpdateEnrolledFaceByID(ctx context.Context, in *extint.UpdateEnrolledFaceByIDRequest) (*extint.UpdateEnrolledFaceByIDResponse, error) { + _, err := engineCladManager.Write(ProtoUpdateEnrolledFaceByIDToClad(in)) + if err != nil { + return nil, err + } + return &extint.UpdateEnrolledFaceByIDResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_REQUEST_PROCESSING, + }, + }, nil +} + +// TODO Wait for response RobotRenamedEnrolledFace +func (service *rpcService) EraseEnrolledFaceByID(ctx context.Context, in *extint.EraseEnrolledFaceByIDRequest) (*extint.EraseEnrolledFaceByIDResponse, error) { + _, err := engineCladManager.Write(ProtoEraseEnrolledFaceByIDToClad(in)) + if err != nil { + return nil, err + } + return &extint.EraseEnrolledFaceByIDResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_REQUEST_PROCESSING, + }, + }, nil +} + +// TODO Wait for response RobotErasedAllEnrolledFaces +func (service *rpcService) EraseAllEnrolledFaces(ctx context.Context, in *extint.EraseAllEnrolledFacesRequest) (*extint.EraseAllEnrolledFacesResponse, error) { + _, err := engineCladManager.Write(ProtoEraseAllEnrolledFacesToClad(in)) + if err != nil { + return nil, err + } + return &extint.EraseAllEnrolledFacesResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_REQUEST_PROCESSING, + }, + }, nil +} + +func (service *rpcService) SetFaceToEnroll(ctx context.Context, in *extint.SetFaceToEnrollRequest) (*extint.SetFaceToEnrollResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_SetFaceToEnrollResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_SetFaceToEnrollRequest{ + SetFaceToEnrollRequest: in, + }, + }) + if err != nil { + return nil, err + } + setFaceToEnrollResponse, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := setFaceToEnrollResponse.GetSetFaceToEnrollResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return response, nil +} + +func (service *rpcService) EnrollFace(ctx context.Context, in *extint.EnrollFaceRequest) (*extint.EnrollFaceResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_EnrollFaceResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_EnrollFaceRequest{ + EnrollFaceRequest: in, + }, + }) + if err != nil { + return nil, err + } + enrollFaceResponse, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := enrollFaceResponse.GetEnrollFaceResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return response, nil +} + +func isMember(needle string, haystack []string) bool { + for _, word := range haystack { + if word == needle { + return true + } + } + return false +} + +func checkFilters(event *extint.Event, whiteList, blackList *extint.FilterList) bool { + if whiteList == nil && blackList == nil { + return true + } + props := &proto.Properties{} + val := reflect.ValueOf(event.GetEventType()) + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + props.Parse(val.Type().Field(0).Tag.Get("protobuf")) + responseType := props.OrigName + + if whiteList != nil && isMember(responseType, whiteList.List) { + return true + } + if blackList != nil && !isMember(responseType, blackList.List) { + return true + } + return false +} + +// Should be called on WiFi connect. +func (service *rpcService) onConnect(id string) { + // Call DAS WiFi connection event to indicate start of a WiFi connection. + // Log the connection id for the primary connection, which is the first person to connect. + //log.Das("wifi_conn_id.start", (&log.DasFields{}).SetStrings(id)) +} + +// Should be called on WiFi disconnect. +func (service *rpcService) onDisconnect() { + // Message engine that app disconnected + SendAppDisconnected() + // Call DAS WiFi connection event to indicate stop of a WiFi connection + //log.Das("wifi_conn_id.stop", (&log.DasFields{}).SetStrings("")) + connectionId = "" +} + +func (service *rpcService) checkConnectionID(id string) bool { + connectionIdLock.Lock() + defer connectionIdLock.Unlock() + if len(connectionId) != 0 && id != connectionId { + log.Println("Connection id already set: current='%s', incoming='%s'", connectionId, id) + return false + } + // Check whether we are in Webots. + if IsOnRobot { + f, responseChan := switchboardManager.CreateChannel(gw_clad.SwitchboardResponseTag_ExternalConnectionResponse, 1) + defer f() + switchboardManager.Write(gw_clad.NewSwitchboardRequestWithExternalConnectionRequest(&gw_clad.ExternalConnectionRequest{})) + + response, ok := <-responseChan + if !ok { + log.Println("Failed to receive ConnectionID response from vic-switchboard") + return false + } + connectionResponse := response.GetExternalConnectionResponse() + + // IsConnected shows whether switchboard is connected over ble. + // Detect if someone else is connected. + if connectionResponse.IsConnected && connectionResponse.ConnectionId != id { + // Someone is connected over BLE and they are not the primary connection. + // We return false so the app can tell you not to connect. + log.Printf("Detected mismatched BLE connection id: BLE='%s', incoming='%s'\n", connectionResponse.ConnectionId, id) + return false + } + } + connectionId = id + return true +} + +// SDK-only message to pass version info for device OS, Python version, etc. +func (service *rpcService) SDKInitialization(ctx context.Context, in *extint.SDKInitializationRequest) (*extint.SDKInitializationResponse, error) { + //log.Das("sdk.module_version", (&log.DasFields{}).SetStrings(in.SdkModuleVersion)) + //log.Das("sdk.python_version", (&log.DasFields{}).SetStrings(in.PythonVersion)) + //log.Das("sdk.python_implementation", (&log.DasFields{}).SetStrings(in.PythonImplementation)) + //log.Das("sdk.os_version", (&log.DasFields{}).SetStrings(in.OsVersion)) + //log.Das("sdk.cpu_version", (&log.DasFields{}).SetStrings(in.CpuVersion)) + + return &extint.SDKInitializationResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_REQUEST_PROCESSING, + }, + }, nil +} + +// Long running message for sending events to listening sdk users +func (service *rpcService) EventStream(in *extint.EventRequest, stream extint.ExternalInterface_EventStreamServer) error { + isPrimary := service.checkConnectionID(in.ConnectionId) + if isPrimary { + service.onConnect(connectionId) + defer service.onDisconnect() + } + resp := &extint.EventResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + }, + Event: &extint.Event{ + EventType: &extint.Event_ConnectionResponse{ + ConnectionResponse: &extint.ConnectionResponse{ + IsPrimary: isPrimary, + }, + }, + }, + } + err := stream.Send(resp) + if err != nil { + log.Println("Closing Event stream (on send):", err) + return err + } else if err = stream.Context().Err(); err != nil { + log.Println("Closing Event stream:", err) + // This is the case where the user disconnects the stream + // We should still return the err in case the user doesn't think they disconnected + return err + } + + if isPrimary { + log.Printf("EventStream: Sent primary connection response '%s'\n", connectionId) + } else { + log.Printf("EventStream: Sent secondary connection response given='%s', current='%s'\n", in.ConnectionId, connectionId) + } + + f, eventsChannel := engineProtoManager.CreateChannel(&extint.GatewayWrapper_Event{}, 512) + defer f() + + ping := extint.EventResponse{ + Event: &extint.Event{ + EventType: &extint.Event_KeepAlive{ + KeepAlive: &extint.KeepAlivePing{}, + }, + }, + } + + whiteList := in.GetWhiteList() + blackList := in.GetBlackList() + + pingTicker := time.Tick(time.Second) + + for { + select { + case response, ok := <-eventsChannel: + if !ok { + return grpc.Errorf(codes.Internal, "EventStream: event channel closed") + } + event := response.GetEvent() + if checkFilters(event, whiteList, blackList) { + if logVerbose { + log.Printf("EventStream: Sending event to client: %#v\n", *event) + } + eventResponse := &extint.EventResponse{ + Event: event, + } + if err := stream.Send(eventResponse); err != nil { + log.Println("Closing Event stream (on send):", err) + return err + } else if err = stream.Context().Err(); err != nil { + log.Println("Closing Event stream:", err) + // This is the case where the user disconnects the stream + // We should still return the err in case the user doesn't think they disconnected + return err + } + } + case <-pingTicker: // ping to check connection liveness after one second. + if err := stream.Send(&ping); err != nil { + log.Println("Closing Event stream (on send):", err) + return err + } else if err = stream.Context().Err(); err != nil { + log.Println("Closing Event stream:", err) + // This is the case where the user disconnects the stream + // We should still return the err in case the user doesn't think they disconnected + return err + } + } + } + return nil +} + +func (service *rpcService) BehaviorRequestToGatewayWrapper(request *extint.BehaviorControlRequest) (*extint.GatewayWrapper, error) { + msg := &extint.GatewayWrapper{} + + switch x := request.RequestType.(type) { + case *extint.BehaviorControlRequest_ControlRelease: + msg.OneofMessageType = &extint.GatewayWrapper_ControlRelease{ + ControlRelease: request.GetControlRelease(), + } + case *extint.BehaviorControlRequest_ControlRequest: + msg.OneofMessageType = &extint.GatewayWrapper_ControlRequest{ + ControlRequest: request.GetControlRequest(), + } + default: + return nil, grpc.Errorf(codes.InvalidArgument, "BehaviorControlRequest.ControlRequest has unexpected type %T", x) + } + return msg, nil +} + +func (service *rpcService) BehaviorControlRequestHandler(in extint.ExternalInterface_BehaviorControlServer, done chan struct{}, connID uint64) { + defer close(done) + defer engineProtoManager.WriteWithID(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_ControlRelease{ + ControlRelease: &extint.ControlRelease{}, + }, + }, connID) + + for { + request, err := in.Recv() + if err != nil { + log.Printf("BehaviorControlRequestHandler.close: %s\n", err.Error()) + return + } + log.Println("BehaviorControl Incoming Request:", request) + + msg, err := service.BehaviorRequestToGatewayWrapper(request) + if err != nil { + log.Println(err) + return + } + _, err = engineProtoManager.WriteWithID(msg, connID) + if err != nil { + return + } + } +} + +func (service *rpcService) BehaviorControlResponseHandler(out extint.ExternalInterface_AssumeBehaviorControlServer, responses chan extint.GatewayWrapper, done chan struct{}, connID uint64) error { + ping := extint.BehaviorControlResponse{ + ResponseType: &extint.BehaviorControlResponse_KeepAlive{ + KeepAlive: &extint.KeepAlivePing{}, + }, + } + + pingTicker := time.Tick(time.Second) + + for { + select { + case <-done: + return nil + case response, ok := <-responses: + if !ok { + return grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + if connID != response.GetConnectionId() { + continue + } + msg := response.GetBehaviorControlResponse() + log.Println("BehaviorControl Incoming BehaviorControlResponse message:", msg) + if err := out.Send(msg); err != nil { + log.Printf("Closing BehaviorControl stream (on send): %s connID %d\n", err.Error(), connID) + return err + } else if err = out.Context().Err(); err != nil { + // This is the case where the user disconnects the stream + // We should still return the err in case the user doesn't think they disconnected + log.Printf("Closing BehaviorControl stream: %s connID %d\n", err, connID) + return err + } + case <-pingTicker: // ping to check connection liveness after one second. + if err := out.Send(&ping); err != nil { + log.Printf("Closing BehaviorControl stream (on send): %s connID %d\n", err.Error(), connID) + return err + } else if err = out.Context().Err(); err != nil { + // This is the case where the user disconnects the stream + // We should still return the err in case the user doesn't think they disconnected + log.Printf("Closing BehaviorControl stream: %s connID %d\n", err, connID) + return err + } + } + } + return nil +} + +// SDK-only method. SDK DAS connect/disconnect events are sent from here. +func (service *rpcService) BehaviorControl(bidirectionalStream extint.ExternalInterface_BehaviorControlServer) error { + //sdkStartTime := time.Now() + + numCommandsSentFromSDK = 0 + + //log.Das("sdk.connection_started", (&log.DasFields{}).SetStrings("")) + + defer func() { + //sdkElapsedSeconds := time.Since(sdkStartTime) + //log.Das("sdk.connection_ended", (&log.DasFields{}).SetStrings(sdkElapsedSeconds.String(), fmt.Sprint(numCommandsSentFromSDK))) + numCommandsSentFromSDK = 0 + }() + + done := make(chan struct{}) + connID := rand.Uint64() + + f, behaviorStatus := engineProtoManager.CreateChannel(&extint.GatewayWrapper_BehaviorControlResponse{}, 1) + defer f() + + go service.BehaviorControlRequestHandler(bidirectionalStream, done, connID) + return service.BehaviorControlResponseHandler(bidirectionalStream, behaviorStatus, done, connID) +} + +func (service *rpcService) AssumeBehaviorControl(in *extint.BehaviorControlRequest, out extint.ExternalInterface_AssumeBehaviorControlServer) error { + done := make(chan struct{}) + + f, behaviorStatus := engineProtoManager.CreateChannel(&extint.GatewayWrapper_BehaviorControlResponse{}, 1) + defer f() + + msg, err := service.BehaviorRequestToGatewayWrapper(in) + if err != nil { + return err + } + + _, connID, err := engineProtoManager.Write(msg) + if err != nil { + return err + } + + return service.BehaviorControlResponseHandler(out, behaviorStatus, done, connID) +} + +func (service *rpcService) DriveOffCharger(ctx context.Context, in *extint.DriveOffChargerRequest) (*extint.DriveOffChargerResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_DriveOffChargerResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_DriveOffChargerRequest{ + DriveOffChargerRequest: in, + }, + }) + if err != nil { + return nil, err + } + driveOffChargerResponse, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := driveOffChargerResponse.GetDriveOffChargerResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return response, nil +} + +func (service *rpcService) DriveOnCharger(ctx context.Context, in *extint.DriveOnChargerRequest) (*extint.DriveOnChargerResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_DriveOnChargerResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_DriveOnChargerRequest{ + DriveOnChargerRequest: in, + }, + }) + if err != nil { + return nil, err + } + driveOnChargerResponse, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := driveOnChargerResponse.GetDriveOnChargerResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return response, nil +} + +func (service *rpcService) FindFaces(ctx context.Context, in *extint.FindFacesRequest) (*extint.FindFacesResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_FindFacesResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_FindFacesRequest{ + FindFacesRequest: in, + }, + }) + if err != nil { + return nil, err + } + findFacesResponse, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := findFacesResponse.GetFindFacesResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return response, nil +} + +func (service *rpcService) LookAroundInPlace(ctx context.Context, in *extint.LookAroundInPlaceRequest) (*extint.LookAroundInPlaceResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_LookAroundInPlaceResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_LookAroundInPlaceRequest{ + LookAroundInPlaceRequest: in, + }, + }) + if err != nil { + return nil, err + } + lookAroundInPlaceResponse, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := lookAroundInPlaceResponse.GetLookAroundInPlaceResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return response, nil +} + +func (service *rpcService) RollBlock(ctx context.Context, in *extint.RollBlockRequest) (*extint.RollBlockResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_RollBlockResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_RollBlockRequest{ + RollBlockRequest: in, + }, + }) + if err != nil { + return nil, err + } + rollBlockResponse, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := rollBlockResponse.GetRollBlockResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return response, nil +} + +// Request the current robot onboarding status +func (service *rpcService) GetOnboardingState(ctx context.Context, in *extint.OnboardingStateRequest) (*extint.OnboardingStateResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_OnboardingState{}, 1) + defer f() + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_OnboardingStateRequest{ + OnboardingStateRequest: in, + }, + }) + if err != nil { + return nil, err + } + onboardingState, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + return &extint.OnboardingStateResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + }, + OnboardingState: onboardingState.GetOnboardingState(), + }, nil +} + +func (service *rpcService) SendOnboardingInput(ctx context.Context, in *extint.OnboardingInputRequest) (*extint.OnboardingInputResponse, error) { + // oneof_message_type + switch x := in.OneofMessageType.(type) { + case *extint.OnboardingInputRequest_OnboardingCompleteRequest: + return SendOnboardingComplete(&extint.GatewayWrapper_OnboardingCompleteRequest{ + OnboardingCompleteRequest: in.GetOnboardingCompleteRequest(), + }) + case *extint.OnboardingInputRequest_OnboardingWakeUpStartedRequest: + return SendOnboardingWakeUpStarted(&extint.GatewayWrapper_OnboardingWakeUpStartedRequest{ + OnboardingWakeUpStartedRequest: in.GetOnboardingWakeUpStartedRequest(), + }) + case *extint.OnboardingInputRequest_OnboardingWakeUpRequest: + return SendOnboardingWakeUp(&extint.GatewayWrapper_OnboardingWakeUpRequest{ + OnboardingWakeUpRequest: in.GetOnboardingWakeUpRequest(), + }) + case *extint.OnboardingInputRequest_OnboardingSkipOnboarding: + return SendOnboardingSkipOnboarding(&extint.GatewayWrapper_OnboardingSkipOnboarding{ + OnboardingSkipOnboarding: in.GetOnboardingSkipOnboarding(), + }) + case *extint.OnboardingInputRequest_OnboardingRestart: + return SendOnboardingRestart(&extint.GatewayWrapper_OnboardingRestart{ + OnboardingRestart: in.GetOnboardingRestart(), + }) + case *extint.OnboardingInputRequest_OnboardingSetPhaseRequest: + return SendOnboardingSetPhase(&extint.GatewayWrapper_OnboardingSetPhaseRequest{ + OnboardingSetPhaseRequest: in.GetOnboardingSetPhaseRequest(), + }) + case *extint.OnboardingInputRequest_OnboardingPhaseProgressRequest: + return SendOnboardingPhaseProgress(&extint.GatewayWrapper_OnboardingPhaseProgressRequest{ + OnboardingPhaseProgressRequest: in.GetOnboardingPhaseProgressRequest(), + }) + case *extint.OnboardingInputRequest_OnboardingChargeInfoRequest: + return SendOnboardingChargeInfo(&extint.GatewayWrapper_OnboardingChargeInfoRequest{ + OnboardingChargeInfoRequest: in.GetOnboardingChargeInfoRequest(), + }) + case *extint.OnboardingInputRequest_OnboardingMarkCompleteAndExit: + return SendOnboardingMarkCompleteAndExit(&extint.GatewayWrapper_OnboardingMarkCompleteAndExit{ + OnboardingMarkCompleteAndExit: in.GetOnboardingMarkCompleteAndExit(), + }) + default: + return nil, grpc.Errorf(codes.InvalidArgument, "OnboardingInputRequest.OneofMessageType has unexpected type %T", x) + } +} + +func (service *rpcService) PhotosInfo(ctx context.Context, in *extint.PhotosInfoRequest) (*extint.PhotosInfoResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_PhotosInfoResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_PhotosInfoRequest{ + PhotosInfoRequest: in, + }, + }) + if err != nil { + return nil, err + } + payload, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + infoResponse := payload.GetPhotosInfoResponse() + infoResponse.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return infoResponse, nil +} + +func SendImageHelper(fullpath string) ([]byte, error) { + log.Println("Reading file at", fullpath) + dat, err := ioutil.ReadFile(fullpath) + if err != nil { + log.Println("Error reading file ", fullpath) + return nil, err + } + return dat, nil +} + +func (service *rpcService) Photo(ctx context.Context, in *extint.PhotoRequest) (*extint.PhotoResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_PhotoPathMessage{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_PhotoRequest{ + PhotoRequest: in, + }, + }) + if err != nil { + return nil, err + } + payload, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + if !payload.GetPhotoPathMessage().GetSuccess() { + return &extint.PhotoResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_NOT_FOUND, + }, + Success: false, + }, err + } + imageData, err := SendImageHelper(payload.GetPhotoPathMessage().GetFullPath()) + if err != nil { + return &extint.PhotoResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_NOT_FOUND, + }, + Success: false, + }, err + } + return &extint.PhotoResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + }, + Success: true, + Image: imageData, + }, err +} + +func (service *rpcService) Thumbnail(ctx context.Context, in *extint.ThumbnailRequest) (*extint.ThumbnailResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_ThumbnailPathMessage{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_ThumbnailRequest{ + ThumbnailRequest: in, + }, + }) + if err != nil { + return nil, err + } + payload, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + if !payload.GetThumbnailPathMessage().GetSuccess() { + return &extint.ThumbnailResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_NOT_FOUND, + }, + Success: false, + }, err + } + imageData, err := SendImageHelper(payload.GetThumbnailPathMessage().GetFullPath()) + if err != nil { + return &extint.ThumbnailResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_NOT_FOUND, + }, + Success: false, + }, err + } + return &extint.ThumbnailResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + }, + Success: true, + Image: imageData, + }, err +} + +func (service *rpcService) DeletePhoto(ctx context.Context, in *extint.DeletePhotoRequest) (*extint.DeletePhotoResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_DeletePhotoResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_DeletePhotoRequest{ + DeletePhotoRequest: in, + }, + }) + if err != nil { + return nil, err + } + payload, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + photoResponse := payload.GetDeletePhotoResponse() + photoResponse.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return photoResponse, nil +} + +func (service *rpcService) GetLatestAttentionTransfer(ctx context.Context, in *extint.LatestAttentionTransferRequest) (*extint.LatestAttentionTransferResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_LatestAttentionTransfer{}, 1) + defer f() + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_LatestAttentionTransferRequest{ + LatestAttentionTransferRequest: in, + }, + }) + if err != nil { + return nil, err + } + attentionTransfer, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + return &extint.LatestAttentionTransferResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + }, + LatestAttentionTransfer: attentionTransfer.GetLatestAttentionTransfer(), + }, nil +} + +func (service *rpcService) ConnectCube(ctx context.Context, in *extint.ConnectCubeRequest) (*extint.ConnectCubeResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_ConnectCubeResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_ConnectCubeRequest{ + ConnectCubeRequest: in, + }, + }) + if err != nil { + return nil, err + } + gatewayWrapper, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := gatewayWrapper.GetConnectCubeResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return response, nil +} + +func (service *rpcService) DisconnectCube(ctx context.Context, in *extint.DisconnectCubeRequest) (*extint.DisconnectCubeResponse, error) { + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_DisconnectCubeRequest{ + DisconnectCubeRequest: in, + }, + }) + if err != nil { + return nil, err + } + return &extint.DisconnectCubeResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_REQUEST_PROCESSING, + }, + }, nil +} + +func (service *rpcService) CubesAvailable(ctx context.Context, in *extint.CubesAvailableRequest) (*extint.CubesAvailableResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_CubesAvailableResponse{}, 1) + defer f() + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_CubesAvailableRequest{ + CubesAvailableRequest: in, + }, + }) + if err != nil { + return nil, err + } + cubesAvailable, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := cubesAvailable.GetCubesAvailableResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return response, nil +} + +func (service *rpcService) FlashCubeLights(ctx context.Context, in *extint.FlashCubeLightsRequest) (*extint.FlashCubeLightsResponse, error) { + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_FlashCubeLightsRequest{ + FlashCubeLightsRequest: in, + }, + }) + if err != nil { + return nil, err + } + return &extint.FlashCubeLightsResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_REQUEST_PROCESSING, + }, + }, nil +} + +func (service *rpcService) ForgetPreferredCube(ctx context.Context, in *extint.ForgetPreferredCubeRequest) (*extint.ForgetPreferredCubeResponse, error) { + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_ForgetPreferredCubeRequest{ + ForgetPreferredCubeRequest: in, + }, + }) + if err != nil { + return nil, err + } + return &extint.ForgetPreferredCubeResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_REQUEST_PROCESSING, + }, + }, nil +} + +func (service *rpcService) SetPreferredCube(ctx context.Context, in *extint.SetPreferredCubeRequest) (*extint.SetPreferredCubeResponse, error) { + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_SetPreferredCubeRequest{ + SetPreferredCubeRequest: in, + }, + }) + if err != nil { + return nil, err + } + return &extint.SetPreferredCubeResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_REQUEST_PROCESSING, + }, + }, nil +} + +func (service *rpcService) SetCubeLights(ctx context.Context, in *extint.SetCubeLightsRequest) (*extint.SetCubeLightsResponse, error) { + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_SetCubeLightsRequest{ + SetCubeLightsRequest: in, + }, + }) + if err != nil { + return nil, err + } + return &extint.SetCubeLightsResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_REQUEST_PROCESSING, + }, + }, nil +} + +// NOTE: this is removed from external_interface because the result from the robot (size 100) is too large for the domain socket between gateway and engine +func (service *rpcService) RobotStatusHistory(ctx context.Context, in *extint.RobotHistoryRequest) (*extint.RobotHistoryResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_RobotHistoryResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_RobotHistoryRequest{ + RobotHistoryRequest: in, + }, + }) + if err != nil { + return nil, err + } + response, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + return response.GetRobotHistoryResponse(), nil +} + +func (service *rpcService) PullJdocs(ctx context.Context, in *extint.PullJdocsRequest) (*extint.PullJdocsResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_PullJdocsResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_PullJdocsRequest{ + PullJdocsRequest: in, + }, + }) + if err != nil { + return nil, err + } + response, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + return response.GetPullJdocsResponse(), nil +} + +func (service *rpcService) UpdateSettings(ctx context.Context, in *extint.UpdateSettingsRequest) (*extint.UpdateSettingsResponse, error) { + f, responseChan, ok := engineProtoManager.CreateUniqueChannel(&extint.GatewayWrapper_UpdateSettingsResponse{}, 1) + if !ok { + return &extint.UpdateSettingsResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_ERROR_UPDATE_IN_PROGRESS, + }, + }, nil + } + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_UpdateSettingsRequest{ + UpdateSettingsRequest: in, + }, + }) + if err != nil { + return nil, err + } + response, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + return response.GetUpdateSettingsResponse(), nil +} + +func (service *rpcService) UpdateAccountSettings(ctx context.Context, in *extint.UpdateAccountSettingsRequest) (*extint.UpdateAccountSettingsResponse, error) { + f, responseChan, ok := engineProtoManager.CreateUniqueChannel(&extint.GatewayWrapper_UpdateAccountSettingsResponse{}, 1) + if !ok { + return &extint.UpdateAccountSettingsResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_ERROR_UPDATE_IN_PROGRESS, + }, + }, nil + } + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_UpdateAccountSettingsRequest{ + UpdateAccountSettingsRequest: in, + }, + }) + if err != nil { + return nil, err + } + response, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + return response.GetUpdateAccountSettingsResponse(), nil +} + +func (service *rpcService) UpdateUserEntitlements(ctx context.Context, in *extint.UpdateUserEntitlementsRequest) (*extint.UpdateUserEntitlementsResponse, error) { + f, responseChan, ok := engineProtoManager.CreateUniqueChannel(&extint.GatewayWrapper_UpdateUserEntitlementsResponse{}, 1) + if !ok { + return &extint.UpdateUserEntitlementsResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_ERROR_UPDATE_IN_PROGRESS, + }, + }, nil + } + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_UpdateUserEntitlementsRequest{ + UpdateUserEntitlementsRequest: in, + }, + }) + if err != nil { + return nil, err + } + response, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + return response.GetUpdateUserEntitlementsResponse(), nil +} + +// NOTE: this is the only function that won't need to check the client_token_guid header +func (service *rpcService) UserAuthentication(ctx context.Context, in *extint.UserAuthenticationRequest) (*extint.UserAuthenticationResponse, error) { + if !IsOnRobot { + return nil, grpc.Errorf(codes.Internal, "User authentication is only available on the robot") + } + + if !userAuthLimiter.Allow() { + return nil, grpc.Errorf(codes.ResourceExhausted, "Maximum auth rate exceeded. Please wait and try again later.") + } + + f, authChan := switchboardManager.CreateChannel(gw_clad.SwitchboardResponseTag_AuthResponse, 1) + defer f() + + // cap ClientName to 64-characters + clientName := string(in.ClientName) + if len(clientName) > 64 { + clientName = clientName[:64] + } + + switchboardManager.Write(gw_clad.NewSwitchboardRequestWithAuthRequest(&cloud_clad.AuthRequest{ + SessionToken: string(in.UserSessionId), + ClientName: clientName, + AppId: "SDK", + })) + response, ok := <-authChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + auth := response.GetAuthResponse() + code := extint.UserAuthenticationResponse_UNAUTHORIZED + token := auth.AppToken + if auth.Error == cloud_clad.TokenError_NoError { + code = extint.UserAuthenticationResponse_AUTHORIZED + + // Force an update of the tokens + response := make(chan struct{}) + tokenManager.ForceUpdate(response) + <-response + //log.Das("sdk.activate", &log.DasFields{}) + } else { + token = "" + } + return &extint.UserAuthenticationResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + }, + Code: code, + ClientTokenGuid: []byte(token), + }, nil +} + +func ValidateActionTag(idTag int32) error { + firstTag := int32(extint.ActionTagConstants_FIRST_SDK_TAG) + lastTag := int32(extint.ActionTagConstants_LAST_SDK_TAG) + if idTag < firstTag || idTag > lastTag { + return grpc.Errorf(codes.InvalidArgument, "Invalid Action tag_id") + } + + return nil +} + +func (service *rpcService) GoToPose(ctx context.Context, in *extint.GoToPoseRequest) (*extint.GoToPoseResponse, error) { + + if err := ValidateActionTag(in.IdTag); err != nil { + return nil, err + } + + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_GoToPoseResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_GoToPoseRequest{ + GoToPoseRequest: in, + }, + }) + if err != nil { + return nil, err + } + + goToPoseResponse, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := goToPoseResponse.GetGoToPoseResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + log.Printf("Received rpc response GoToPose(%#v)\n", in) + return response, nil +} + +func (service *rpcService) DockWithCube(ctx context.Context, in *extint.DockWithCubeRequest) (*extint.DockWithCubeResponse, error) { + + if err := ValidateActionTag(in.IdTag); err != nil { + return nil, err + } + + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_DockWithCubeResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_DockWithCubeRequest{ + DockWithCubeRequest: in, + }, + }) + if err != nil { + return nil, err + } + + dockWithCubeResponse, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := dockWithCubeResponse.GetDockWithCubeResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return response, nil +} + +func (service *rpcService) DriveStraight(ctx context.Context, in *extint.DriveStraightRequest) (*extint.DriveStraightResponse, error) { + + if err := ValidateActionTag(in.IdTag); err != nil { + return nil, err + } + + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_DriveStraightResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_DriveStraightRequest{ + DriveStraightRequest: in, + }, + }) + if err != nil { + return nil, err + } + + driveStraightResponse, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := driveStraightResponse.GetDriveStraightResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return response, nil +} + +func (service *rpcService) TurnInPlace(ctx context.Context, in *extint.TurnInPlaceRequest) (*extint.TurnInPlaceResponse, error) { + + if err := ValidateActionTag(in.IdTag); err != nil { + return nil, err + } + + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_TurnInPlaceResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_TurnInPlaceRequest{ + TurnInPlaceRequest: in, + }, + }) + if err != nil { + return nil, err + } + + turnInPlaceResponse, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := turnInPlaceResponse.GetTurnInPlaceResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return response, nil +} + +func (service *rpcService) SetHeadAngle(ctx context.Context, in *extint.SetHeadAngleRequest) (*extint.SetHeadAngleResponse, error) { + + if err := ValidateActionTag(in.IdTag); err != nil { + return nil, err + } + + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_SetHeadAngleResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_SetHeadAngleRequest{ + SetHeadAngleRequest: in, + }, + }) + if err != nil { + return nil, err + } + + setHeadAngleResponse, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := setHeadAngleResponse.GetSetHeadAngleResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return response, nil +} + +func (service *rpcService) SetLiftHeight(ctx context.Context, in *extint.SetLiftHeightRequest) (*extint.SetLiftHeightResponse, error) { + + if err := ValidateActionTag(in.IdTag); err != nil { + return nil, err + } + + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_SetLiftHeightResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_SetLiftHeightRequest{ + SetLiftHeightRequest: in, + }, + }) + if err != nil { + return nil, err + } + + setLiftHeightResponse, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := setLiftHeightResponse.GetSetLiftHeightResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return response, nil +} + +func (service *rpcService) TurnTowardsFace(ctx context.Context, in *extint.TurnTowardsFaceRequest) (*extint.TurnTowardsFaceResponse, error) { + + if err := ValidateActionTag(in.IdTag); err != nil { + return nil, err + } + + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_TurnTowardsFaceResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_TurnTowardsFaceRequest{ + TurnTowardsFaceRequest: in, + }, + }) + if err != nil { + return nil, err + } + + turnTowardsFaceResponse, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := turnTowardsFaceResponse.GetTurnTowardsFaceResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return response, nil +} + +func (service *rpcService) GoToObject(ctx context.Context, in *extint.GoToObjectRequest) (*extint.GoToObjectResponse, error) { + + if err := ValidateActionTag(in.IdTag); err != nil { + return nil, err + } + + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_GoToObjectResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_GoToObjectRequest{ + GoToObjectRequest: in, + }, + }) + if err != nil { + return nil, err + } + + goToObjectResponse, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := goToObjectResponse.GetGoToObjectResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return response, nil +} + +func (service *rpcService) RollObject(ctx context.Context, in *extint.RollObjectRequest) (*extint.RollObjectResponse, error) { + + if err := ValidateActionTag(in.IdTag); err != nil { + return nil, err + } + + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_RollObjectResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_RollObjectRequest{ + RollObjectRequest: in, + }, + }) + if err != nil { + return nil, err + } + + rollObjectResponse, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := rollObjectResponse.GetRollObjectResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return response, nil +} + +func (service *rpcService) PopAWheelie(ctx context.Context, in *extint.PopAWheelieRequest) (*extint.PopAWheelieResponse, error) { + + if err := ValidateActionTag(in.IdTag); err != nil { + return nil, err + } + + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_PopAWheelieResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_PopAWheelieRequest{ + PopAWheelieRequest: in, + }, + }) + if err != nil { + return nil, err + } + + popAWheelieResponse, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := popAWheelieResponse.GetPopAWheelieResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return response, nil +} + +func (service *rpcService) PickupObject(ctx context.Context, in *extint.PickupObjectRequest) (*extint.PickupObjectResponse, error) { + + if err := ValidateActionTag(in.IdTag); err != nil { + return nil, err + } + + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_PickupObjectResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_PickupObjectRequest{ + PickupObjectRequest: in, + }, + }) + if err != nil { + return nil, err + } + + pickupObjectResponse, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := pickupObjectResponse.GetPickupObjectResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return response, nil +} + +func (service *rpcService) PlaceObjectOnGroundHere(ctx context.Context, in *extint.PlaceObjectOnGroundHereRequest) (*extint.PlaceObjectOnGroundHereResponse, error) { + + if err := ValidateActionTag(in.IdTag); err != nil { + return nil, err + } + + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_PlaceObjectOnGroundHereResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_PlaceObjectOnGroundHereRequest{ + PlaceObjectOnGroundHereRequest: in, + }, + }) + if err != nil { + return nil, err + } + + placeObjectOnGroundResponse, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := placeObjectOnGroundResponse.GetPlaceObjectOnGroundHereResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return response, nil +} + +func (service *rpcService) SetMasterVolume(ctx context.Context, in *extint.MasterVolumeRequest) (*extint.MasterVolumeResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_MasterVolumeResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_MasterVolumeRequest{ + MasterVolumeRequest: in, + }, + }) + if err != nil { + return nil, err + } + + masterVolumeResponse, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := masterVolumeResponse.GetMasterVolumeResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return response, nil +} + +func (service *rpcService) BatteryState(ctx context.Context, in *extint.BatteryStateRequest) (*extint.BatteryStateResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_BatteryStateResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_BatteryStateRequest{ + BatteryStateRequest: in, + }, + }) + if err != nil { + return nil, err + } + payload, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + payload.GetBatteryStateResponse().Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return payload.GetBatteryStateResponse(), nil +} + +func (service *rpcService) VersionState(ctx context.Context, in *extint.VersionStateRequest) (*extint.VersionStateResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_VersionStateResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_VersionStateRequest{ + VersionStateRequest: in, + }, + }) + if err != nil { + return nil, err + } + payload, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + payload.GetVersionStateResponse().Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return payload.GetVersionStateResponse(), nil +} + +func (service *rpcService) SayText(ctx context.Context, in *extint.SayTextRequest) (*extint.SayTextResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_SayTextResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_SayTextRequest{ + SayTextRequest: in, + }, + }) + if err != nil { + return nil, err + } + done := false + var sayTextResponse *extint.SayTextResponse + for !done { + payload, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + sayTextResponse = payload.GetSayTextResponse() + state := sayTextResponse.GetState() + if state == extint.SayTextResponse_FINISHED { + done = true + } else if state == extint.SayTextResponse_INVALID { + return nil, grpc.Errorf(codes.Internal, "Failed to say text") + } + } + sayTextResponse.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return sayTextResponse, nil +} + +func AudioSendModeRequest(mode extint.AudioProcessingMode) error { + log.Println("SDK Requesting Audio with mode(", mode, ")") + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_AudioSendModeRequest{ + AudioSendModeRequest: &extint.AudioSendModeRequest{ + Mode: mode, + }, + }, + }) + + return err +} + +type AudioFeedCache struct { + Data []byte + GroupId int32 + Invalid bool + Size int32 + LastChunkId int32 +} + +func ResetAudioCache(cache *AudioFeedCache) error { + cache.Data = nil + cache.GroupId = -1 + cache.Invalid = false + cache.Size = 0 + cache.LastChunkId = -1 + return nil +} + +func UnpackAudioChunk(audioChunk *extint.AudioChunk, cache *AudioFeedCache) bool { + groupId := int32(audioChunk.GetGroupId()) + chunkId := int32(audioChunk.GetChunkId()) + + if cache.GroupId != -1 && chunkId == 0 { + if !cache.Invalid { + log.Errorln("Lost final chunk of audio group; discarding") + } + cache.GroupId = -1 + } + + if cache.GroupId == -1 { + if chunkId != 0 { + if !cache.Invalid { + log.Errorln("Received chunk of broken audio stream") + } + cache.Invalid = true + return false + } + // discard any previous in-progress image + ResetAudioCache(cache) + + cache.Data = make([]byte, extint.AudioConstants_SAMPLE_COUNTS_PER_SDK_MESSAGE*2) + cache.GroupId = int32(groupId) + } + + if chunkId != cache.LastChunkId+1 || groupId != cache.GroupId { + log.Errorf("Audio missing chunks; discarding (last_chunk_id=%i partial_audio_group_id=%i)\n", cache.LastChunkId, cache.GroupId) + ResetAudioCache(cache) + cache.Invalid = true + return false + } + + dataSize := int32(len(audioChunk.GetSignalPower())) + copy(cache.Data[cache.Size:cache.Size+dataSize], audioChunk.GetSignalPower()[:]) + cache.Size += dataSize + cache.LastChunkId = chunkId + + return chunkId == int32(audioChunk.GetAudioChunkCount()-1) +} + +// Long running message for sending audio feed to listening sdk users +func (service *rpcService) AudioFeed(in *extint.AudioFeedRequest, stream extint.ExternalInterface_AudioFeedServer) error { + // @TODO: Expose other audio processing modes + // + // The composite multi-microphone non-beamforming (AUDIO_VOICE_DETECT_MODE) mode has been identified as the best for voice detection, + // as well as incidentally calculating directional and noise_floor data. As such it's most reasonable as the SDK's default mode. + // + // While this mode will send directional source data, it is different from DIRECTIONAL_MODE in that it does not isolate and clean + // up the sound stream with respect to the loudest direction (which makes the result more human-ear pleasing but more ml difficult). + // + // It should however be noted that the robot will automatically shift into FAST_MODE (cleaned up single microphone) when + // entering low power mode, so its important to leave this exposed. + // + + // Enable audio stream + err := AudioSendModeRequest(extint.AudioProcessingMode_AUDIO_VOICE_DETECT_MODE) + if err != nil { + return err + } + + // Disable audio stream + defer AudioSendModeRequest(extint.AudioProcessingMode_AUDIO_OFF) + + // Forward audio data from engine + f, audioFeedChannel := engineProtoManager.CreateChannel(&extint.GatewayWrapper_AudioChunk{}, 1024) + defer f() + + cache := AudioFeedCache{ + Data: nil, + GroupId: -1, + Invalid: false, + Size: 0, + } + + for result := range audioFeedChannel { + + audioChunk := result.GetAudioChunk() + + readyToSend := UnpackAudioChunk(audioChunk, &cache) + if readyToSend { + audioFeedResponse := &extint.AudioFeedResponse{ + RobotTimeStamp: audioChunk.GetRobotTimeStamp(), + GroupId: uint32(audioChunk.GetGroupId()), + SignalPower: cache.Data[0:cache.Size], + DirectionStrengths: audioChunk.GetDirectionStrengths(), + SourceDirection: audioChunk.GetSourceDirection(), + SourceConfidence: audioChunk.GetSourceConfidence(), + NoiseFloorPower: audioChunk.GetNoiseFloorPower(), + } + ResetAudioCache(&cache) + + if err := stream.Send(audioFeedResponse); err != nil { + return err + } else if err = stream.Context().Err(); err != nil { + // This is the case where the user disconnects the stream + // We should still return the err in case the user doesn't think they disconnected + return err + } + } + } + + errMsg := "AudioChunk engine stream died unexpectedly" + log.Errorln(errMsg) + return grpc.Errorf(codes.Internal, errMsg) +} + +func (service *rpcService) EnableMarkerDetection(ctx context.Context, request *extint.EnableMarkerDetectionRequest) (*extint.EnableMarkerDetectionResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_EnableMarkerDetectionResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_EnableMarkerDetectionRequest{ + EnableMarkerDetectionRequest: request, + }, + }) + if err != nil { + return nil, err + } + + if request.Enable == false { + // VIC-12762 EnableMarkerDetectionRequest is requesting that the marker detection vision mode be turned off. + // There are cases when there is another subscriber on the robot (not affiliated with the SDK) + // that wants to keep this vision mode on. So this request to turn it off is more of a suggestion. + // Don't wait for the response to come back from the vision system via sdkComponent to confirm that + // this request was successful. + // + // This fixes the problem where the SDK either hangs or waits ~50 seconds intermittently for confirmation + // that the vision mode was turned off. + // + // TODO Do other vision modes encounter the same problem? + // + // Create response, send it and return early. + return &extint.EnableMarkerDetectionResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_REQUEST_PROCESSING, + }, + }, nil + } else { + payload, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + + response := payload.GetEnableMarkerDetectionResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + + return response, nil + } +} + +func (service *rpcService) EnableFaceDetection(ctx context.Context, request *extint.EnableFaceDetectionRequest) (*extint.EnableFaceDetectionResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_EnableFaceDetectionResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_EnableFaceDetectionRequest{ + EnableFaceDetectionRequest: request, + }, + }) + if err != nil { + return nil, err + } + + payload, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + + response := payload.GetEnableFaceDetectionResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + + return response, nil +} + +func (service *rpcService) EnableMotionDetection(ctx context.Context, request *extint.EnableMotionDetectionRequest) (*extint.EnableMotionDetectionResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_EnableMotionDetectionResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_EnableMotionDetectionRequest{ + EnableMotionDetectionRequest: request, + }, + }) + if err != nil { + return nil, err + } + + payload, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + + response := payload.GetEnableMotionDetectionResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + + return response, nil +} + +func (service *rpcService) EnableMirrorMode(ctx context.Context, request *extint.EnableMirrorModeRequest) (*extint.EnableMirrorModeResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_EnableMirrorModeResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_EnableMirrorModeRequest{ + EnableMirrorModeRequest: request, + }, + }) + if err != nil { + return nil, err + } + + payload, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + + response := payload.GetEnableMirrorModeResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + + return response, nil +} + +// Capture a single image using the camera +func (service *rpcService) CaptureSingleImage(ctx context.Context, request *extint.CaptureSingleImageRequest) (*extint.CaptureSingleImageResponse, error) { + // Enable image stream + _, err := service.EnableImageStreaming(nil, &extint.EnableImageStreamingRequest{ + Enable: true, + EnableHighResolution: request.EnableHighResolution, + }) + + if err != nil { + return nil, err + } + + // Disable image stream + defer service.EnableImageStreaming(nil, &extint.EnableImageStreamingRequest{ + Enable: false, + EnableHighResolution: false, + }) + + f, cameraFeedChannel := engineProtoManager.CreateChannel(&extint.GatewayWrapper_ImageChunk{}, 1024) + defer f() + + cache := CameraFeedCache{ + Data: nil, + ImageId: -1, + Invalid: false, + Size: 0, + } + + for result := range cameraFeedChannel { + imageChunk := result.GetImageChunk() + readyToSend := UnpackCameraImageChunk(imageChunk, &cache) + if readyToSend { + capturedSingleImage := &extint.CaptureSingleImageResponse{ + FrameTimeStamp: imageChunk.GetFrameTimeStamp(), + ImageId: uint32(cache.ImageId), + ImageEncoding: imageChunk.GetImageEncoding(), + Data: cache.Data[0:cache.Size], + } + capturedSingleImage.Status = &extint.ResponseStatus{Code: extint.ResponseStatus_RESPONSE_RECEIVED} + return capturedSingleImage, nil + } + } + + errMsg := "ImageChunk engine stream died unexpectedly" + log.Errorln(errMsg) + return nil, grpc.Errorf(codes.Internal, errMsg) +} + +// TODO VIC-11579 Support specifying streaming resolution +func (service *rpcService) EnableImageStreaming(ctx context.Context, request *extint.EnableImageStreamingRequest) (*extint.EnableImageStreamingResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_EnableImageStreamingResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_EnableImageStreamingRequest{ + EnableImageStreamingRequest: request, + }, + }) + if err != nil { + return nil, err + } + + payload, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + + response := payload.GetEnableImageStreamingResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + + return response, nil +} + +// indicates if image streaming is enabled or not +func (service *rpcService) IsImageStreamingEnabled(ctx context.Context, request *extint.IsImageStreamingEnabledRequest) (*extint.IsImageStreamingEnabledResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_IsImageStreamingEnabledResponse{}, 1) + defer f() + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_IsImageStreamingEnabledRequest{ + IsImageStreamingEnabledRequest: request, + }, + }) + if err != nil { + return nil, err + } + payload, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := payload.GetIsImageStreamingEnabledResponse() + return &extint.IsImageStreamingEnabledResponse{ + IsImageStreamingEnabled: response.IsImageStreamingEnabled, + }, nil +} + +type CameraFeedCache struct { + Data []byte + ImageId int32 + Invalid bool + Size int32 + LastChunkId int32 +} + +func ResetCameraCache(cache *CameraFeedCache) error { + cache.Data = nil + cache.ImageId = -1 + cache.Invalid = false + cache.Size = 0 + cache.LastChunkId = -1 + return nil +} + +func UnpackCameraImageChunk(imageChunk *extint.ImageChunk, cache *CameraFeedCache) bool { + imageId := int32(imageChunk.GetImageId()) + chunkId := int32(imageChunk.GetChunkId()) + + if cache.ImageId != -1 && chunkId == 0 { + if !cache.Invalid { + log.Errorln("Lost final chunk of image; discarding") + } + cache.ImageId = -1 + } + + if cache.ImageId == -1 { + if chunkId != 0 { + if !cache.Invalid { + log.Errorln("Received chunk of broken image") + } + cache.Invalid = true + return false + } + // discard any previous in-progress image + ResetCameraCache(cache) + + cache.Data = make([]byte, imageChunk.GetWidth()*imageChunk.GetHeight()*3) + cache.ImageId = int32(imageId) + } + + if chunkId != cache.LastChunkId+1 || imageId != cache.ImageId { + log.Errorf("Image missing chunks; discarding (last_chunk_id=%i partial_image_id=%i)\n", cache.LastChunkId, cache.ImageId) + ResetCameraCache(cache) + cache.Invalid = true + return false + } + + dataSize := int32(len(imageChunk.GetData())) + copy(cache.Data[cache.Size:cache.Size+dataSize], imageChunk.GetData()[:]) + cache.Size += dataSize + cache.LastChunkId = chunkId + + return chunkId == int32(imageChunk.GetImageChunkCount()-1) +} + +// Long running message for sending camera feed to listening sdk users +func (service *rpcService) CameraFeed(in *extint.CameraFeedRequest, stream extint.ExternalInterface_CameraFeedServer) error { + // Enable video stream. The video stream only uses the default image resolution. + _, err := service.EnableImageStreaming(nil, &extint.EnableImageStreamingRequest{ + Enable: true, + EnableHighResolution: false, + }) + + if err != nil { + return err + } + + // Disable video stream + defer service.EnableImageStreaming(nil, &extint.EnableImageStreamingRequest{ + Enable: false, + EnableHighResolution: false, + }) + + f, cameraFeedChannel := engineProtoManager.CreateChannel(&extint.GatewayWrapper_ImageChunk{}, 1024) + defer f() + + cache := CameraFeedCache{ + Data: nil, + ImageId: -1, + Invalid: false, + Size: 0, + } + + for result := range cameraFeedChannel { + + imageChunk := result.GetImageChunk() + readyToSend := UnpackCameraImageChunk(imageChunk, &cache) + if readyToSend { + cameraFeedResponse := &extint.CameraFeedResponse{ + FrameTimeStamp: imageChunk.GetFrameTimeStamp(), + ImageId: uint32(cache.ImageId), + ImageEncoding: imageChunk.GetImageEncoding(), + Data: cache.Data[0:cache.Size], + } + ResetCameraCache(&cache) + + if err := stream.Send(cameraFeedResponse); err != nil { + return err + } else if err = stream.Context().Err(); err != nil { + // This is the case where the user disconnects the stream + // We should still return the err in case the user doesn't think they disconnected + return err + } + } + } + + errMsg := "ImageChunk engine stream died unexpectedly" + log.Errorln(errMsg) + return grpc.Errorf(codes.Internal, errMsg) +} + +func ReadStringFromFile(filename string, defaultValue string) string { + returnValue := defaultValue + if data, err := ioutil.ReadFile(filename); err == nil { + returnValue = strings.TrimSpace(string(data)) + } + return returnValue +} + +func ReadInt64FromFile(filename string, defaultValue int64) int64 { + returnValue := defaultValue + data := ReadStringFromFile(filename, "") + if len(data) > 0 { + if val, err := strconv.ParseInt(data, 0, 64); err == nil { + returnValue = val + } + } + return returnValue +} + +const ( + otaExitCodeNotAvailable = int64(-1) + otaExitCodeSuccess = int64(0) + otaExitCodeIOError = int64(208) + otaExitCodeSocketTimeout = int64(215) +) + +// GetUpdateStatus tells if the robot is ready to reboot and update. +func (service *rpcService) GetUpdateStatus() (*extint.CheckUpdateStatusResponse, error) { + updateStatus := &extint.CheckUpdateStatusResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_OK, + }, + UpdateStatus: extint.CheckUpdateStatusResponse_NO_UPDATE, + Progress: ReadInt64FromFile("/run/update-engine/progress", -1), + Expected: ReadInt64FromFile("/run/update-engine/expected-size", -1), + ExitCode: ReadInt64FromFile("/run/update-engine/exit_code", otaExitCodeNotAvailable), + Error: ReadStringFromFile("/run/update-engine/error", ""), + UpdatePhase: ReadStringFromFile("/run/update-engine/phase", ""), + UpdateVersion: "", + } + + if data, err := ioutil.ReadFile("/run/update-engine/manifest.ini"); err == nil { + updateVersionExpr := regexp.MustCompile("update_version\\s*=\\s*(\\S*)") + match := updateVersionExpr.FindStringSubmatch(string(data)) + if len(match) == 2 { + updateStatus.UpdateVersion = match[1] + } + } + + // With one exception, an ExitCode > otaExitCodeSuccess means we encountered an error and need to + // report it + if updateStatus.ExitCode > otaExitCodeSuccess { + updateStatus.UpdateStatus = extint.CheckUpdateStatusResponse_FAILURE_OTHER + + // If we are in the 'download' phase, make our failure status more precise + // depending on the exit code + if updateStatus.UpdatePhase == "download" { + if updateStatus.Error == "Failed to open URL: " || + updateStatus.ExitCode == otaExitCodeIOError || + updateStatus.ExitCode == otaExitCodeSocketTimeout { + updateStatus.UpdateStatus = extint.CheckUpdateStatusResponse_FAILURE_INTERRUPTED_DOWNLOAD + } + } + // I do this, rather than checking for the 203 because the 203 has other meanings. + // The below unique error string is what we expect when there is no update available. + // It is not an indication of failure + if strings.Contains(updateStatus.Error, "Failed to open URL: HTTP Error 403: Forbidden") { + updateStatus.UpdateStatus = extint.CheckUpdateStatusResponse_NO_UPDATE + updateStatus.Error = "" + updateStatus.ExitCode = otaExitCodeSuccess + } + return updateStatus, nil + } + + // If /run/update-engine/done exists that means that /anki/bin/update-engine + // successfully downloaded and applied an OS update. We are now waiting to reboot + // into it. + if _, err := os.Stat("/run/update-engine/done"); err == nil { + updateStatus.UpdateStatus = + extint.CheckUpdateStatusResponse_READY_TO_REBOOT_INTO_NEW_OS_VERSION + updateStatus.Error = "" + updateStatus.ExitCode = otaExitCodeSuccess + return updateStatus, nil + } + + // If we don't have an exit code yet, we are in progress + if updateStatus.ExitCode == otaExitCodeNotAvailable { + updateStatus.UpdateStatus = extint.CheckUpdateStatusResponse_IN_PROGRESS_STARTING + // If we don't have an exit code, we should not report an error until we do + updateStatus.Error = "" + } + + if updateStatus.Progress > 0 { + if updateStatus.UpdatePhase == "download" { + updateStatus.UpdateStatus = extint.CheckUpdateStatusResponse_IN_PROGRESS_DOWNLOAD + } else { + updateStatus.UpdateStatus = extint.CheckUpdateStatusResponse_IN_PROGRESS_OTHER + } + } + + return updateStatus, nil +} + +// UpdateStatusStream tells if the robot is ready to reboot and update. +func (service *rpcService) UpdateStatusStream() { + updateStarted := false + // If this co-routine is already running, we don't need another one + if statusStreamRunning { + return + } + statusStreamRunning = true + defer func() { + statusStreamRunning = false + }() + iterations := 0 + for len(connectionId) == 0 && iterations < 10 { + // Wait a bit to be sure that the connectionId is valid before continuing. + time.Sleep(250 * time.Millisecond) + iterations++ + } + + for len(connectionId) != 0 { + status, err := service.GetUpdateStatus() + // Keep streaming to the requestor until they disconnect. We don't stop + // streaming just because there's no update pending (a requested update + // may be pending, but hasn't had a chance to update + // /run/update-engine/* yet). + if err != nil { + break + } + + // It is possible that we will not have new values for 'progress' and 'expected' + // or we could be in the middle of a transition where 'expected < progress'. We + // don't want to send invalid state to the client and have them display a bogus + // completion percentage. If we detect an invalid state, just send the last + // known good values. + lastRatio := float64(lastProgress) / float64(lastExpected) + if status.Progress > 0 && + status.Expected > 0 && + status.Progress <= status.Expected && + lastRatio < (float64(status.Progress)/float64(status.Expected)) { + lastProgress = status.Progress + lastExpected = status.Expected + } else { + status.Progress = lastProgress + status.Expected = lastExpected + } + + tag := reflect.TypeOf(&extint.GatewayWrapper_Event{}).String() + msg := extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_Event{ + // TODO: Convert all events into proto events + Event: &extint.Event{ + EventType: &extint.Event_CheckUpdateStatusResponse{ + CheckUpdateStatusResponse: status, + }, + }, + }, + } + + if logVerbose { + log.Printf("%s, err = %s, exit = %d, phase = %s, ver = %s, prog = %d, exp = %d", + status.UpdateStatus, + status.Error, + status.ExitCode, + status.UpdatePhase, + status.UpdateVersion, + status.Progress, + status.Expected) + } + + engineProtoManager.SendToListeners(tag, msg) + + // If we have an ExitCode and we have sent at least 2 updates, we can stop + // sending status updates as we will just be repeating ourselves. We could + // probably stop after a single update, but the smartphone app doesn't seem + // to display the very first status change it receives. + if status.ExitCode != otaExitCodeNotAvailable && updateStarted { + break + } + updateStarted = true + time.Sleep(2000 * time.Millisecond) + } +} + +// StartUpdateEngine restarts the update-engine process and starts a stream of status messages to the app. +func (service *rpcService) StartUpdateEngine( + ctx context.Context, in *extint.CheckUpdateStatusRequest) (*extint.CheckUpdateStatusResponse, error) { + + retval := &extint.CheckUpdateStatusResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + }, + } + + status, _ := service.GetUpdateStatus() + + // Unless we are ready to reboot into a new OS version, we need to make sure that + // /anki/bin/update-engine is running. By the way, /anki/bin/update-engine will NOT + // be considered active if it is doing its 'sleeping' phase. + restartUpdateEngine := false + if status.UpdateStatus != extint.CheckUpdateStatusResponse_READY_TO_REBOOT_INTO_NEW_OS_VERSION { + err := exec.Command( + "/bin/systemctl", + "is-active", + "--quiet", + "update-engine.service").Run() + restartUpdateEngine = err != nil + } + + if restartUpdateEngine { + err := exec.Command( + "/usr/bin/sudo", + "-n", + "/bin/systemctl", + "stop", + "update-engine.service").Run() + if err != nil { + log.Errorf("Update attempt failed on `systemctl stop update-engine`: %s\n", err) + retval.Status.Code = extint.ResponseStatus_ERROR_UPDATE_IN_PROGRESS + return retval, err + } + + err = exec.Command( + "/usr/bin/sudo", + "-n", + "/bin/systemctl", + "restart", + "update-engine-oneshot").Run() + if err != nil { + log.Errorf("Update attempt failed on `systemctl restart update-engine-oneshot`: %s\n", err) + retval.Status.Code = extint.ResponseStatus_ERROR_UPDATE_IN_PROGRESS + return retval, err + } + } + + // Reset the lastProgress and lastExpected globals that are used in UpdateStatusStream, so that + // we are sure to send reasonable starting progress values to the smartphone app + lastProgress = 0 + lastExpected = 1 + go service.UpdateStatusStream() + + return retval, nil +} + +// CheckUpdateStatus tells if the robot is ready to reboot and update. +func (service *rpcService) CheckUpdateStatus( + ctx context.Context, in *extint.CheckUpdateStatusRequest) (*extint.CheckUpdateStatusResponse, error) { + + return service.GetUpdateStatus() +} + +// UpdateAndRestart reboots the robot when an update is available. +// This will apply the update when the robot starts up. +func (service *rpcService) UpdateAndRestart(ctx context.Context, in *extint.UpdateAndRestartRequest) (*extint.UpdateAndRestartResponse, error) { + if _, err := os.Stat("/run/update-engine/done"); err == nil { + go func() { + <-time.After(5 * time.Second) + err := exec.Command("/usr/bin/sudo", "/sbin/reboot").Run() + if err != nil { + log.Errorf("Reboot attempt failed: %s\n", err) + } + }() + return &extint.UpdateAndRestartResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_OK, + }, + }, nil + } + return &extint.UpdateAndRestartResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_NOT_FOUND, + }, + }, nil +} + +// UploadDebugLogs will upload debug logs to S3, and return a url to the caller. +func (service *rpcService) UploadDebugLogs(ctx context.Context, in *extint.UploadDebugLogsRequest) (*extint.UploadDebugLogsResponse, error) { + if !debugLogLimiter.Allow() { + return nil, grpc.Errorf(codes.ResourceExhausted, "Maximum upload rate exceeded. Please wait and try again later.") + } + + /* disabling so we can build the gateway -bd + + url, err := loguploader.UploadDebugLogs() + if err != nil { + log.Println("MessageHandler.UploadDebugLogs.Error: " + err.Error()) + return nil, grpc.Errorf(codes.Internal, err.Error()) + } + response := &extint.UploadDebugLogsResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_OK, + }, + Url: url, + } + return response, nil + */ + + response := &extint.UploadDebugLogsResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_OK, + }, + } + return response, nil +} + +var lastResult *extint.CheckCloudResponse + +// CheckCloudConnection is used to verify Vector's connection to the Anki Cloud +// Its main use is to be called by the app during setup, but is fine for use by the outside world. +func (service *rpcService) CheckCloudConnection(ctx context.Context, in *extint.CheckCloudRequest) (*extint.CheckCloudResponse, error) { + if !cloudCheckLimiter.Allow() { + if lastResult == nil { + lastResult = &extint.CheckCloudResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_UNKNOWN, + }, + Code: extint.CheckCloudResponse_UNKNOWN, + } + } + return lastResult, nil + // TODO: change this back to a resource exhausted error after app properly handles the error + // return nil, grpc.Errorf(codes.ResourceExhausted, "Maximum check rate exceeded. Please wait and try again later.") + } + + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_CheckCloudResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_CheckCloudRequest{ + CheckCloudRequest: in, + }, + }) + if err != nil { + return nil, err + } + payload, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + cloudResponse := payload.GetCheckCloudResponse() + cloudResponse.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + lastResult = cloudResponse + return cloudResponse, nil +} + +func (service *rpcService) DeleteCustomObjects(ctx context.Context, in *extint.DeleteCustomObjectsRequest) (*extint.DeleteCustomObjectsResponse, error) { + var responseMessageType gw_clad.MessageRobotToExternalTag + var cladMsg *gw_clad.MessageExternalToRobot + + switch in.Mode { + case extint.CustomObjectDeletionMode_DELETION_MASK_ARCHETYPES: + responseMessageType = gw_clad.MessageRobotToExternalTag_RobotDeletedCustomMarkerObjects + cladMsg = gw_clad.NewMessageExternalToRobotWithUndefineAllCustomMarkerObjects( + &gw_clad.UndefineAllCustomMarkerObjects{}) + break + case extint.CustomObjectDeletionMode_DELETION_MASK_FIXED_CUSTOM_OBJECTS: + responseMessageType = gw_clad.MessageRobotToExternalTag_RobotDeletedFixedCustomObjects + cladMsg = gw_clad.NewMessageExternalToRobotWithDeleteFixedCustomObjects( + &gw_clad.DeleteFixedCustomObjects{}) + break + case extint.CustomObjectDeletionMode_DELETION_MASK_CUSTOM_MARKER_OBJECTS: + responseMessageType = gw_clad.MessageRobotToExternalTag_RobotDeletedCustomMarkerObjects + cladMsg = gw_clad.NewMessageExternalToRobotWithDeleteCustomMarkerObjects( + &gw_clad.DeleteCustomMarkerObjects{}) + break + } + + f, responseChan := engineCladManager.CreateChannel(responseMessageType, 1) + defer f() + + _, err := engineCladManager.Write(cladMsg) + + if err != nil { + return nil, err + } + + _, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + + return &extint.DeleteCustomObjectsResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + }, + }, nil +} + +func (service *rpcService) CreateFixedCustomObject(ctx context.Context, in *extint.CreateFixedCustomObjectRequest) (*extint.CreateFixedCustomObjectResponse, error) { + f, responseChan := engineCladManager.CreateChannel(gw_clad.MessageRobotToExternalTag_CreatedFixedCustomObject, 1) + defer f() + + cladData := ProtoCreateFixedCustomObjectToClad(in) + + _, err := engineCladManager.Write(cladData) + if err != nil { + return nil, err + } + + chanResponse, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := chanResponse.GetCreatedFixedCustomObject() + + return &extint.CreateFixedCustomObjectResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + }, + ObjectId: response.ObjectID, + }, nil +} + +func (service *rpcService) DefineCustomObject(ctx context.Context, in *extint.DefineCustomObjectRequest) (*extint.DefineCustomObjectResponse, error) { + var cladMsg *gw_clad.MessageExternalToRobot + + f, responseChan := engineCladManager.CreateChannel(gw_clad.MessageRobotToExternalTag_DefinedCustomObject, 1) + defer f() + + switch x := in.CustomObjectDefinition.(type) { + case *extint.DefineCustomObjectRequest_CustomBox: + cladMsg = ProtoDefineCustomBoxToClad(in, in.GetCustomBox()) + break + case *extint.DefineCustomObjectRequest_CustomCube: + cladMsg = ProtoDefineCustomCubeToClad(in, in.GetCustomCube()) + break + case *extint.DefineCustomObjectRequest_CustomWall: + cladMsg = ProtoDefineCustomWallToClad(in, in.GetCustomWall()) + break + default: + return nil, grpc.Errorf(codes.InvalidArgument, "DefineCustomObjectRequest has unexpected type %T", x) + } + + _, err := engineCladManager.Write(cladMsg) + if err != nil { + return nil, err + } + + chanResponse, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := chanResponse.GetDefinedCustomObject() + + return &extint.DefineCustomObjectResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + }, + Success: response.Success, + }, nil +} + +// FeatureFlag is used to check what features are enabled on the robot +func (service *rpcService) GetFeatureFlag(ctx context.Context, in *extint.FeatureFlagRequest) (*extint.FeatureFlagResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_FeatureFlagResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_FeatureFlagRequest{ + FeatureFlagRequest: in, + }, + }) + if err != nil { + return nil, err + } + payload, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := payload.GetFeatureFlagResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return response, nil +} + +// FeatureFlagList is used to check what features are enabled on the robot +func (service *rpcService) GetFeatureFlagList(ctx context.Context, in *extint.FeatureFlagListRequest) (*extint.FeatureFlagListResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_FeatureFlagListResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_FeatureFlagListRequest{ + FeatureFlagListRequest: in, + }, + }) + if err != nil { + return nil, err + } + payload, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := payload.GetFeatureFlagListResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return response, nil +} + +// AlexaAuthState is used to check the alexa authorization state +func (service *rpcService) GetAlexaAuthState(ctx context.Context, in *extint.AlexaAuthStateRequest) (*extint.AlexaAuthStateResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_AlexaAuthStateResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_AlexaAuthStateRequest{ + AlexaAuthStateRequest: in, + }, + }) + if err != nil { + return nil, err + } + payload, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := payload.GetAlexaAuthStateResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return response, nil +} + +// AlexaOptIn is used to check the alexa authorization state +func (service *rpcService) AlexaOptIn(ctx context.Context, in *extint.AlexaOptInRequest) (*extint.AlexaOptInResponse, error) { + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_AlexaOptInRequest{ + AlexaOptInRequest: in, + }, + }) + if err != nil { + return nil, err + } + return &extint.AlexaOptInResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_REQUEST_PROCESSING, + }, + }, nil +} + +func SetNavMapBroadcastFrequency(frequency float32) error { + log.Println("Setting NavMapBroadcastFrequency to (", frequency, ") seconds") + + cladMsg := gw_clad.NewMessageExternalToRobotWithSetMemoryMapBroadcastFrequencySec(&gw_clad.SetMemoryMapBroadcastFrequency_sec{ + Frequency: frequency, + }) + _, err := engineCladManager.Write(cladMsg) + + return err +} + +func (service *rpcService) NavMapFeed(in *extint.NavMapFeedRequest, stream extint.ExternalInterface_NavMapFeedServer) error { + + // Enable nav map stream + err := SetNavMapBroadcastFrequency(in.Frequency) + if err != nil { + return err + } + + // Disable nav map stream when the RPC exits + defer SetNavMapBroadcastFrequency(-1.0) + + f1, memoryMapMessageBegin := engineCladManager.CreateChannel(gw_clad.MessageRobotToExternalTag_MemoryMapMessageBegin, 1) + defer f1() + + // Every frame the engine can send up to: (Anki::Comms::MsgPacket::MAX_SIZE-3)/sizeof(QuadInfoVector::value_type) quads. + // 50 feels like a reasonable educated guess. + f2, memoryMapMessageData := engineCladManager.CreateChannel(gw_clad.MessageRobotToExternalTag_MemoryMapMessage, 50) + defer f2() + + f3, memoryMapMessageEnd := engineCladManager.CreateChannel(gw_clad.MessageRobotToExternalTag_MemoryMapMessageEnd, 1) + defer f3() + + var pendingMap *extint.NavMapFeedResponse = nil + + for { + select { + case chanResponse, ok := <-memoryMapMessageBegin: + if !ok { + return grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + if pendingMap != nil { + log.Println("MessageHandler.NavMapFeed.Error: MemoryMapBegin received from engine while still processing a pending memory map; discarding pending map.") + } + + response := chanResponse.GetMemoryMapMessageBegin() + pendingMap = &extint.NavMapFeedResponse{ + OriginId: response.OriginId, + MapInfo: CladMemoryMapBeginToProtoNavMapInfo(response), + QuadInfos: []*extint.NavMapQuadInfo{}, + } + + case chanResponse, ok := <-memoryMapMessageData: + if !ok { + return grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + if pendingMap == nil { + log.Println("MessageHandler.NavMapFeed.Error: MemoryMapData received from engine with no pending content to add to.") + } else { + response := chanResponse.GetMemoryMapMessage() + for i := 0; i < len(response.QuadInfos); i++ { + newQuad := CladMemoryMapQuadInfoToProto(&response.QuadInfos[i]) + pendingMap.QuadInfos = append(pendingMap.QuadInfos, newQuad) + } + } + + case _, ok := <-memoryMapMessageEnd: + if !ok { + return grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + + if pendingMap == nil { + log.Println("MessageHandler.NavMapFeed.Error: MemoryMapEnd received from engine with no pending content to send.") + } else if err := stream.Send(pendingMap); err != nil { + return err + } else if err = stream.Context().Err(); err != nil { + // This is the case where the user disconnects the stream + // We should still return the err in case the user doesn't think they disconnected + return err + } + + pendingMap = nil + } + } + + errMsg := "NavMemoryMap engine stream died unexpectedly" + log.Errorln(errMsg) + return grpc.Errorf(codes.Internal, errMsg) +} + +func newServer() *rpcService { + return new(rpcService) +} + +// Set Eye Color (SDK only) +// TODO Set eye color back to Settings value in internal code when SDK program ends or loses behavior control +// (e.g., in go code or when SDK behavior deactivates) +func (service *rpcService) SetEyeColor(ctx context.Context, in *extint.SetEyeColorRequest) (*extint.SetEyeColorResponse, error) { + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_SetEyeColorRequest{ + SetEyeColorRequest: in, + }, + }) + if err != nil { + return nil, err + } + return &extint.SetEyeColorResponse{ + Status: &extint.ResponseStatus{ + Code: extint.ResponseStatus_REQUEST_PROCESSING, + }, + }, nil +} + +// Get Camera Configuration +func (service *rpcService) GetCameraConfig(ctx context.Context, in *extint.CameraConfigRequest) (*extint.CameraConfigResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_CameraConfigResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_CameraConfigRequest{ + CameraConfigRequest: in, + }, + }) + if err != nil { + return nil, err + } + + payload, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := payload.GetCameraConfigResponse() + return response, nil +} + +// Set Camera Settings +func (service *rpcService) SetCameraSettings(ctx context.Context, in *extint.SetCameraSettingsRequest) (*extint.SetCameraSettingsResponse, error) { + f, responseChan := engineProtoManager.CreateChannel(&extint.GatewayWrapper_SetCameraSettingsResponse{}, 1) + defer f() + + _, _, err := engineProtoManager.Write(&extint.GatewayWrapper{ + OneofMessageType: &extint.GatewayWrapper_SetCameraSettingsRequest{ + SetCameraSettingsRequest: in, + }, + }) + if err != nil { + return nil, err + } + + payload, ok := <-responseChan + if !ok { + return nil, grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + response := payload.GetSetCameraSettingsResponse() + response.Status = &extint.ResponseStatus{ + Code: extint.ResponseStatus_RESPONSE_RECEIVED, + } + return response, nil +} + +func (service *rpcService) ExternalAudioStreamRequestToGatewayWrapper(request *extint.ExternalAudioStreamRequest) (*extint.GatewayWrapper, error) { + msg := &extint.GatewayWrapper{} + switch x := request.AudioRequestType.(type) { + case *extint.ExternalAudioStreamRequest_AudioStreamPrepare: + msg.OneofMessageType = &extint.GatewayWrapper_ExternalAudioStreamPrepare{ + ExternalAudioStreamPrepare: request.GetAudioStreamPrepare(), + } + case *extint.ExternalAudioStreamRequest_AudioStreamChunk: + msg.OneofMessageType = &extint.GatewayWrapper_ExternalAudioStreamChunk{ + ExternalAudioStreamChunk: request.GetAudioStreamChunk(), + } + case *extint.ExternalAudioStreamRequest_AudioStreamComplete: + msg.OneofMessageType = &extint.GatewayWrapper_ExternalAudioStreamComplete{ + ExternalAudioStreamComplete: request.GetAudioStreamComplete(), + } + case *extint.ExternalAudioStreamRequest_AudioStreamCancel: + msg.OneofMessageType = &extint.GatewayWrapper_ExternalAudioStreamCancel{ + ExternalAudioStreamCancel: request.GetAudioStreamCancel(), + } + default: + return nil, grpc.Errorf(codes.InvalidArgument, "ExternalAudioStreamRequest.AudioStreamControlRequest has unexpected type %T", x) + } + + return msg, nil +} + +func (service *rpcService) ExternalAudioStreamRequestHandler(in extint.ExternalInterface_ExternalAudioStreamPlaybackServer, done chan struct{}) { + defer close(done) + for { + request, err := in.Recv() + if err != nil { + log.Printf("AudioStreamRequestHandler.close: %s\n", err.Error()) + return + } + log.Println("External AudioStream playback incoming request") //not printing message, lengthy sample data too busy for logs + + msg, err := service.ExternalAudioStreamRequestToGatewayWrapper(request) + if err != nil { + log.Println(err) + return + } + + numCommandsSentFromSDK++ + _, _, err = engineProtoManager.Write(msg) + if err != nil { + log.Printf("Could not write GatewayWrapper_AudioStreamRequest\n") + } + } +} + +func (service *rpcService) ExternalAudioStreamResponseHandler(out extint.ExternalInterface_ExternalAudioStreamPlaybackServer, responses chan extint.GatewayWrapper, done chan struct{}) error { + for { + select { + case <-done: + return nil + case response, ok := <-responses: + if !ok { + return grpc.Errorf(codes.Internal, "Failed to retrieve message") + } + msg := response.GetExternalAudioStreamResponse() + if err := out.Send(msg); err != nil { + log.Println("Closing AudioStream (on send):", err) + return err + } else if err = out.Context().Err(); err != nil { + // This is the case where the user disconnects the stream + // We should still return the err in case the user doesn't think they disconnected + log.Println("Closing AudioStream stream:", err) + return err + } + } + } + return nil +} + +// Stream audio to the robot, stream audio playback status back +func (service *rpcService) ExternalAudioStreamPlayback(bidirectionalStream extint.ExternalInterface_ExternalAudioStreamPlaybackServer) error { + audioStartTime := time.Now() + + log.Println("sdk.audiostream_started") + + defer func() { + sdkElapsedSeconds := time.Since(audioStartTime) + log.Printf("sdk.audiostream_ended %s\n", sdkElapsedSeconds.String()) + }() + + done := make(chan struct{}) + + f, audioStreamStatus := engineProtoManager.CreateChannel(&extint.GatewayWrapper_ExternalAudioStreamResponse{}, 1) + defer f() + + go service.ExternalAudioStreamRequestHandler(bidirectionalStream, done) + return service.ExternalAudioStreamResponseHandler(bidirectionalStream, audioStreamStatus, done) +} diff --git a/vector-cloud/gateway/multilimiter.go b/vector-cloud/gateway/multilimiter.go new file mode 100644 index 0000000..66f08c8 --- /dev/null +++ b/vector-cloud/gateway/multilimiter.go @@ -0,0 +1,28 @@ +package main + +import "golang.org/x/time/rate" + +// MultiLimiter defines a combination of rate limiters for more complex rate limiting. +// The max theoretical limit is the time of all limiters combined. But this is in the major spamming case. +// It is highly recommended to keep this logic simple. +type MultiLimiter struct { + limiters []*rate.Limiter +} + +// NewMultiLimiter creates a *MultiLimiter where the limiters are applied in the order provided. +func NewMultiLimiter(limiters ...*rate.Limiter) *MultiLimiter { + ml := new(MultiLimiter) + ml.limiters = limiters + return ml +} + +// Allow reports whether an event may happen at time.Now(). +// Use this method if you intend to drop / skip events that exceed the rate limit. +func (ml *MultiLimiter) Allow() bool { + for _, limiter := range ml.limiters { + if !limiter.Allow() { + return false + } + } + return true +} diff --git a/vector-cloud/gateway/switchboard_proxy.go b/vector-cloud/gateway/switchboard_proxy.go new file mode 100644 index 0000000..a4dcb45 --- /dev/null +++ b/vector-cloud/gateway/switchboard_proxy.go @@ -0,0 +1,141 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "regexp" + "strings" + + "github.com/digital-dream-labs/vector-cloud/internal/log" + + gw_clad "github.com/digital-dream-labs/vector-cloud/internal/clad/gateway" + + "google.golang.org/grpc" +) + +// Regex to provide two match results: +// 1: either the start of input and any lowercase characters following it +// or the capital letters and numbers after the start +// 2: The string of a capital and multiple non-capital letters +// or end of the input +// +// For example, this will provide matches (format = "" -> [("", ""), ...]) like the following: +// "TestString" -> [("", "Test"), ("", String)] +// "SomeURLString" -> [("", "Some"), ("URL", "String")] +// "lastString" -> [("last", "String")] +var splitCamelRegex = regexp.MustCompile("(^[^A-Z0-9]*|[A-Z0-9]*)([A-Z0-9][^A-Z]+|$)") + +// BLEProxy handles switchboard messages being proxied to grpc-gateway which proxies to the grpc handlers +type BLEProxy struct { + Address string + Client *http.Client + StreamURLs []string +} + +// streamNameToURL translates a grpc stream name into the equivalent rest endpoint +// ex: "BehaviorControl" -> "v1/behavior_control" +func streamNameToURL(name string) string { + var url []string + + for _, match := range splitCamelRegex.FindAllStringSubmatch(name, -1) { + for _, i := range []int{1, 2} { + if match[i] != "" { + url = append(url, match[i]) + } + } + } + + return strings.ToLower("v1/" + strings.Join(url, "_")) +} + +// initialize determines which streams need to be added to the StreamsURL list +// which is used for blacklisting streams from the BLE proxy +func (proxy *BLEProxy) initialize(serviceInfo map[string]grpc.ServiceInfo) { + if proxy.StreamURLs == nil { + proxy.StreamURLs = make([]string, 0) + } + for _, service := range serviceInfo { + for _, method := range service.Methods { + if method.IsClientStream || method.IsServerStream { + proxy.StreamURLs = append(proxy.StreamURLs, streamNameToURL(method.Name)) + } + } + } +} + +// isStream checks StreamURLs to determine if given url is a stream +func (proxy *BLEProxy) isStream(url string) bool { + for _, streamURL := range proxy.StreamURLs { + if url == streamURL { + return true + } + } + return false +} + +// TruncationResponse to construct a json error response when the message is +// too large for the gateway <-> switchboard interface. +type TruncationResponse struct { + OriginalStatus uint16 `json:"original_status"` + Reason string `json:"reason"` +} + +// handle takes a given proxy request and sends it through the proxy pipelines and returns a response +func (proxy *BLEProxy) handle(request *gw_clad.SdkProxyRequest) *gw_clad.SdkProxyResponse { + log.Printf("Handling a switchboard proxy request id:\"%s\" path:\"%s\" json:\"%s\"\n", request.MessageId, request.Path, request.Json) + proxyResponse := &gw_clad.SdkProxyResponse{ + MessageId: request.MessageId, + } + if proxy.isStream(request.Path) { + log.Errorln("Unable to run streams through BLE interface") + proxyResponse.StatusCode = uint16(403) + proxyResponse.ContentType = "application/text" + proxyResponse.Content = "Unable to run streams through BLE interface" + return proxyResponse + } + path := request.Path + if strings.HasPrefix(path, "/") { + path = path[1:] + } + url := fmt.Sprintf("https://%s/%s", proxy.Address, path) + + jsonStr := []byte(request.Json) + req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr)) + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", request.ClientGuid)) + req.Header.Set("Content-Type", "application/json") + + resp, err := proxy.Client.Do(req) + if err != nil { + log.Errorf("Failed to reach proxy server: %s\n", err.Error()) + proxyResponse.StatusCode = uint16(502) + proxyResponse.ContentType = "application/text" + proxyResponse.Content = "Failed to reach proxy server" + return proxyResponse + } + defer resp.Body.Close() + + body, _ := ioutil.ReadAll(resp.Body) + proxyResponse.StatusCode = uint16(resp.StatusCode) + proxyResponse.ContentType = resp.Header.Get("Content-Type") + proxyResponse.Content = string(body) + responseSize := proxyResponse.Size() + if responseSize >= 2048 { + content := TruncationResponse{ + OriginalStatus: uint16(resp.StatusCode), + Reason: fmt.Sprintf("Response body too large: %d", responseSize), + } + proxyResponse.StatusCode = uint16(500) + jsonResponse, err := json.Marshal(content) + if err != nil { + proxyResponse.ContentType = "application/text" + proxyResponse.Content = "Response body too large" + } else { + proxyResponse.ContentType = "application/json" + proxyResponse.Content = string(jsonResponse[:]) + } + } + return proxyResponse +} diff --git a/vector-cloud/gateway/tokens.go b/vector-cloud/gateway/tokens.go new file mode 100644 index 0000000..c72b411 --- /dev/null +++ b/vector-cloud/gateway/tokens.go @@ -0,0 +1,268 @@ +package main + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "os" + "time" + + cloud_clad "github.com/digital-dream-labs/vector-cloud/internal/clad/cloud" + + "github.com/digital-dream-labs/vector-cloud/internal/ipc" + "github.com/digital-dream-labs/vector-cloud/internal/log" + "github.com/digital-dream-labs/vector-cloud/internal/robot" + "github.com/digital-dream-labs/vector-cloud/internal/token" + + "golang.org/x/time/rate" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" +) + +const ( + jdocDomainSocket = "jdocs_server" + jdocSocketSuffix = "gateway_client" + tokensFile = "/data/vic-gateway/token-hashes.json" +) + +// ClientToken holds the tuple of the client token hash and the +// user-visible client name (e.g. Adam's iPhone) +type ClientToken struct { + Hash string `json:"hash"` + ClientName string `json:"client_name"` + AppId string `json:"app_id"` + IssuedAt string `json:"issued_at"` +} + +// ClientTokenManager holds all the client token tuples for a given +// userid+robot, along with a handle to the Jdocs service document +// that stores them. +// Note: comes from the ClientTokenDocument definition +type ClientTokenManager struct { + ClientTokens []ClientToken `json:"client_tokens"` + jdocIPC IpcManager `json:"-"` + checkValid chan struct{} `json:"-"` + notifyValid chan struct{} `json:"-"` + updateNowChan chan chan struct{} `json:"-"` + recentTokenIndex int `json:"-"` + lastUpdatedTokens time.Time `json:"-"` + forceClearFile bool `json:"-"` + limiter *MultiLimiter `json:"-"` +} + +func (ctm *ClientTokenManager) Init() error { + ctm.forceClearFile = false + ctm.lastUpdatedTokens = time.Now().Add(-24 * time.Hour) // older than our startup time + // Limit the updates of the AppTokens with the following logic: + // - At the maximum, allow 1 update per minute + // - After 3 requests (within 15 minutes) allow 1 update per 15 minutes + // - After 6 requests (within an hour) allow 1 update per hour + // The limiters will refill over time, and everything will be back to the initial state + // after 6 hours of inactivity have passed. + // + // In the case that a user is connected for over 6 hours (which shouldn't be the norm), + // it's fine to not keep checking because the valid tokens should not be take out from + // under a user too often. + // + // Due to the ordering of MultiLimiter, it will acquire the shorter tokens, and only grab + // acquire the longer tokens if it was able to grab a shorter one. + ctm.limiter = NewMultiLimiter( + rate.NewLimiter(rate.Every(time.Minute), 1), + rate.NewLimiter(rate.Every(15*time.Minute), 3), + rate.NewLimiter(rate.Every(time.Hour), 6), + ) + ctm.checkValid = make(chan struct{}) + ctm.notifyValid = make(chan struct{}) + ctm.updateNowChan = make(chan chan struct{}) + ctm.jdocIPC.Connect(ipc.GetSocketPath(jdocDomainSocket), jdocSocketSuffix) + err := ctm.readTokensFile() + if err != nil { + return ctm.UpdateTokens() + } + return nil +} + +func (ctm *ClientTokenManager) Close() error { + return ctm.jdocIPC.Close() +} + +func (ctm *ClientTokenManager) readTokensFile() error { + clientTokens, err := ioutil.ReadFile(tokensFile) + if err != nil { + return err + } + return json.Unmarshal(clientTokens, ctm) +} + +func (ctm *ClientTokenManager) writeTokensFile(data []byte) error { + return ioutil.WriteFile(tokensFile, data, 0600) +} + +func (ctm *ClientTokenManager) CheckToken(clientToken string) (string, error) { + ctm.checkValid <- struct{}{} + <-ctm.notifyValid + if len(ctm.ClientTokens) == 0 { + return "", grpc.Errorf(codes.Unauthenticated, "no valid tokens") + } + recentToken := ctm.ClientTokens[ctm.recentTokenIndex] + err := token.CompareHashAndToken(recentToken.Hash, clientToken) + if err == nil { + return recentToken.ClientName, nil + } + for idx, validToken := range ctm.ClientTokens { + if idx == ctm.recentTokenIndex || len(validToken.Hash) == 0 { + continue + } + err = token.CompareHashAndToken(validToken.Hash, clientToken) + if err == nil { + ctm.recentTokenIndex = idx + return validToken.ClientName, nil + } + } + return "", grpc.Errorf(codes.Unauthenticated, "invalid token") +} + +// DecodeTokenJdoc will update existing valid tokens, from a jdoc received from the server +func (ctm *ClientTokenManager) DecodeTokenJdoc(jdoc []byte) error { + ctm.recentTokenIndex = 0 + ctm.lastUpdatedTokens = time.Now() + err := json.Unmarshal(jdoc, ctm) + if err != nil { + log.Printf("Unmarshal tokens failed. Invalidating. %s\n", err.Error()) + ctm.ClientTokens = []ClientToken{} + } else { + log.Println("Updated valid tokens") + } + return err +} + +// UpdateTokens polls the server for new tokens, and will update as necessary +func (ctm *ClientTokenManager) UpdateTokens() error { + if ctm.forceClearFile { + err := os.Remove(tokensFile) + if err == nil { + ctm.forceClearFile = false + } + } + id, esn, err := ctm.getIDs() + if err != nil { + return err + } + resp, err := ctm.sendBlock(cloud_clad.NewDocRequestWithRead(&cloud_clad.ReadRequest{ + Account: id, + Thing: fmt.Sprintf("vic:%s", esn), + Items: []cloud_clad.ReadItem{ + cloud_clad.ReadItem{ + DocName: "vic.AppTokens", + MyDocVersion: 0, + }, + }, + })) + if err != nil { + return err + } + read := resp.GetRead() + if read == nil { + return fmt.Errorf("error while trying to read jdocs: %#v", resp) + } + if len(read.Items) == 0 { + return errors.New("no jdoc in read response") + } + data := []byte(read.Items[0].Doc.JsonDoc) + err = ctm.DecodeTokenJdoc(data) + if err != nil { + return nil + } + err = ctm.writeTokensFile(data) + if err != nil { + return nil + } + return nil +} + +func (ctm *ClientTokenManager) getIDs() (string, string, error) { + resp, err := ctm.sendBlock(cloud_clad.NewDocRequestWithUser(&cloud_clad.Void{})) + if err != nil { + return "", "", err + } + user := resp.GetUser() + if user == nil { + return "", "", fmt.Errorf("Unable to get robot's user id: %#v", resp) + } + esn, err := robot.ReadESN() + if err != nil { + return "", "", err + } + + return user.UserId, esn, nil +} + +func (ctm *ClientTokenManager) sendBlock(request *cloud_clad.DocRequest) (*cloud_clad.DocResponse, error) { + // Write the request + var err error + var buf bytes.Buffer + if request == nil { + return nil, grpc.Errorf(codes.InvalidArgument, "Unable to parse request") + } + + // The domain socket to vic-cloud does not have size in front of messages + + if err = request.Pack(&buf); err != nil { + return nil, grpc.Errorf(codes.Internal, err.Error()) + } + log.Printf("%T.sendBlock: writing DocRequest message to JDoc Manager\n", *ctm) + // TODO: use channel to a jdoc read/write manager goroutine + _, err = ctm.jdocIPC.conn.Write(buf.Bytes()) + if err != nil { + return nil, grpc.Errorf(codes.Internal, err.Error()) + } + // Read the response + msgBuffer := ctm.jdocIPC.conn.ReadBlock() + if msgBuffer == nil { + log.Errorf("%T.sendBlock: engine socket returned empty message\n", *ctm) + return nil, grpc.Errorf(codes.Internal, "engine socket returned empty message") + } + var recvBuf bytes.Buffer + recvBuf.Write(msgBuffer) + msg := &cloud_clad.DocResponse{} + if err := msg.Unpack(&recvBuf); err != nil { + log.Errorf("%T.sendBlock: Unpack response error = %#v\n", *ctm, err) + return nil, grpc.Errorf(codes.Internal, err.Error()) + } + return msg, nil +} + +func (ctm *ClientTokenManager) ForceUpdate(response chan struct{}) { + ctm.forceClearFile = true + ctm.recentTokenIndex = 0 + ctm.ClientTokens = []ClientToken{} + ctm.updateNowChan <- response +} + +func (ctm *ClientTokenManager) updateListener() { + for range ctm.checkValid { + if time.Since(ctm.lastUpdatedTokens) > time.Hour && ctm.limiter.Allow() { + ctm.updateNowChan <- ctm.notifyValid + } else { + ctm.notifyValid <- struct{}{} + } + } +} + +func (ctm *ClientTokenManager) StartUpdateListener() { + go ctm.updateListener() + for { + select { + case response := <-ctm.updateNowChan: + err := ctm.UpdateTokens() + if err != nil { + log.Printf("Unable to update tokens: %s", err.Error()) + } + if response != nil { + response <- struct{}{} + } + } + } +} diff --git a/vector-cloud/gateway/verbose_logging.go b/vector-cloud/gateway/verbose_logging.go new file mode 100644 index 0000000..64ce181 --- /dev/null +++ b/vector-cloud/gateway/verbose_logging.go @@ -0,0 +1,44 @@ +package main + +import ( + "net/http" + + "github.com/digital-dream-labs/vector-cloud/internal/log" +) + +type WrappedResponseWriter struct { + http.ResponseWriter + tag string +} + +func (wrap *WrappedResponseWriter) WriteHeader(code int) { + log.Printf("%s.WriteHeader(): %d\n", wrap.tag, code) + wrap.ResponseWriter.WriteHeader(code) +} + +func (wrap *WrappedResponseWriter) Write(b []byte) (int, error) { + if wrap.tag == "json" && len(b) > 1 { + log.Printf("%s.Write(): %s\n", wrap.tag, string(b)) + } else if wrap.tag == "grpc" && len(b) > 1 { + log.Printf("%s.Write(): [% x]\n", wrap.tag, b) + } + return wrap.ResponseWriter.Write(b) +} + +func (wrap *WrappedResponseWriter) Header() http.Header { + h := wrap.ResponseWriter.Header() + log.Printf("%s.Header(): %+v\n", wrap.tag, h) + return h +} + +func (wrap *WrappedResponseWriter) Flush() { + wrap.ResponseWriter.(http.Flusher).Flush() +} + +func (wrap *WrappedResponseWriter) CloseNotify() <-chan bool { + return wrap.ResponseWriter.(http.CloseNotifier).CloseNotify() +} + +func LogRequest(r *http.Request, tag string) { + log.Printf("%s.Request: %+v\n", tag, r) +} diff --git a/vector-cloud/go.mod b/vector-cloud/go.mod new file mode 100644 index 0000000..c7b4a6b --- /dev/null +++ b/vector-cloud/go.mod @@ -0,0 +1,25 @@ +module github.com/digital-dream-labs/vector-cloud + +go 1.14 + +require ( + github.com/aws/aws-sdk-go v1.35.0 + github.com/cenkalti/backoff v2.2.1+incompatible + github.com/dgrijalva/jwt-go v3.2.1-0.20180719211823-0b96aaa70776+incompatible + github.com/digital-dream-labs/api v0.0.0-20210824232136-8cc90c1bb12c + github.com/digital-dream-labs/api-clients v0.0.0-20210830180812-99d038d08a9d + github.com/digital-dream-labs/hugh v0.0.0-20201230195335-18bf4b22cf9d + github.com/fsouza/go-dockerclient v1.6.6 + github.com/golang/glog v1.0.0 // indirect + github.com/golang/protobuf v1.5.2 + github.com/google/uuid v1.1.2 + github.com/grpc-ecosystem/grpc-gateway v1.16.0 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.0.1 // indirect + github.com/gwatts/rootcerts v0.0.0-20200901201953-2af9685de2db + github.com/stretchr/testify v1.6.1 + golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 + golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e + google.golang.org/genproto v0.0.0-20210830153122-0bac4d21c8ea + google.golang.org/grpc v1.40.0 + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/vector-cloud/go.sum b/vector-cloud/go.sum new file mode 100644 index 0000000..eeb237e --- /dev/null +++ b/vector-cloud/go.sum @@ -0,0 +1,783 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.63.0/go.mod h1:GmezbQc7T2snqkEXWfZ0sy0VfkB/ivI2DdtJL2DEmlg= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/go-winio v0.4.15-0.20200113171025-3fe6c5262873 h1:93nQ7k53GjoMQ07HVP8g6Zj1fQZDDj7Xy2VkNNtvX8o= +github.com/Microsoft/go-winio v0.4.15-0.20200113171025-3fe6c5262873/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/hcsshim v0.8.9 h1:VrfodqvztU8YSOvygU+DN1BGaSGxmrNfqOv5oOuX2Bk= +github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/aalpern/go-metrics v0.0.0-20181116155206-644932c99203 h1:pkBkfa7REdO3WjBjqkuhcdwQHQ+wzdHygp1RIMhEUgk= +github.com/aalpern/go-metrics v0.0.0-20181116155206-644932c99203/go.mod h1:wHUOZ2LlAirciaWYGZM3apvZftM7aRXhLMDsdjqEFB4= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= +github.com/aws/aws-sdk-go v1.35.0 h1:Pxqn1MWNfBCNcX7jrXCCTfsKpg5ms2IMUMmmcGtYJuo= +github.com/aws/aws-sdk-go v1.35.0/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= +github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.4 h1:3o0smo5SKY7H6AJCmJhsnCjR2/V2T8VmiHt7seN2/kI= +github.com/containerd/containerd v1.3.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY= +github.com/containerd/continuity v0.0.0-20200928162600-f2cc35102c2a h1:jEIoR0aA5GogXZ8pP3DUzE+zrhaF6/1rYZy+7KkYEWM= +github.com/containerd/continuity v0.0.0-20200928162600-f2cc35102c2a/go.mod h1:W0qIOTD7mp2He++YVq+kgfXezRYqzP1uDuMVH1bITDY= +github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgrijalva/jwt-go v3.2.1-0.20180719211823-0b96aaa70776+incompatible h1:KXHmiNq5AM1PBK0MgUU0T1gHdd7fpR7NFnkcp0Tnw2E= +github.com/dgrijalva/jwt-go v3.2.1-0.20180719211823-0b96aaa70776+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/digital-dream-labs/api v0.0.0-20210824232136-8cc90c1bb12c h1:ITlFplPHfe+S0uyObyWo/bndO5RIYA9E3OPlBjOTb5Q= +github.com/digital-dream-labs/api v0.0.0-20210824232136-8cc90c1bb12c/go.mod h1:WNiZyUz2m0GppMJuwVvaLvtmC5mVM+YhcrxHBktqjr0= +github.com/digital-dream-labs/api-clients v0.0.0-20210827163254-ba4df2ea97f2 h1:/9fnz/lGTEmF+oTTL2RT6DS8jwLTNMx4KeIXLQCl04M= +github.com/digital-dream-labs/api-clients v0.0.0-20210827163254-ba4df2ea97f2/go.mod h1:Brz2N9V2v1mpLQRI/bdVfuAUpQ2AF/tM1D8DVa9hibQ= +github.com/digital-dream-labs/api-clients v0.0.0-20210830180812-99d038d08a9d h1:A7Tk6UfpHvczFgYe6o1yJpHLdu5d4L8lLJTERDXHdjs= +github.com/digital-dream-labs/api-clients v0.0.0-20210830180812-99d038d08a9d/go.mod h1:Brz2N9V2v1mpLQRI/bdVfuAUpQ2AF/tM1D8DVa9hibQ= +github.com/digital-dream-labs/hugh v0.0.0-20201230195335-18bf4b22cf9d h1:MvlmT9kqwgbtSTRl4SXoYveb9jpLmqFHhE31/6Wg8Hg= +github.com/digital-dream-labs/hugh v0.0.0-20201230195335-18bf4b22cf9d/go.mod h1:aHzB67P53R70J1S9eI+Syr5HcCHIhS82y2M2jUqw5yA= +github.com/digital-dream-labs/opus-go v0.0.0-20201230195736-934a8a9e0a1e h1:sbDWWDUdymyp3fTeQKz40a3x/frOSiWkBHD40o1/DQY= +github.com/digital-dream-labs/opus-go v0.0.0-20201230195736-934a8a9e0a1e/go.mod h1:OmsiyJjTuSWs/9lgydMVi+ULpldzbMJdu3z2N2TehYk= +github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v17.12.0-ce-rc1.0.20200505174321-1655290016ac+incompatible h1:ZxJX4ZSNg1LORBsStUojbrLfkrE3Ut122XhzyZnN110= +github.com/docker/docker v17.12.0-ce-rc1.0.20200505174321-1655290016ac+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-connections v0.4.1-0.20180821093606-97c2040d34df h1:cGbd/ECh4QPOc6+Tbvdk5NjCcOYESiwc1RjXp0XciVg= +github.com/docker/go-connections v0.4.1-0.20180821093606-97c2040d34df/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsouza/go-dockerclient v1.6.6 h1:9e3xkBrVkPb81gzYq23i7iDUEd6sx2ooeJA/gnYU6R4= +github.com/fsouza/go-dockerclient v1.6.6/go.mod h1:3/oRIWoe7uT6bwtAayj/EmJmepBjeL4pYvt7ZxC7Rnk= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= +github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= +github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= +github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= +github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= +github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= +github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= +github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= +github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= +github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= +github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= +github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= +github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= +github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= +github.com/grd/ogg v0.0.0-20130623210630-0dae53159b70 h1:BbrcLhyNM9P1UAZnPBomiAvDv7WEIJy+sfrJItfSUL8= +github.com/grd/ogg v0.0.0-20130623210630-0dae53159b70/go.mod h1:K8T3jGUZQeKP7y1e801QDZAB53F6Lpt+NgnwAZTxwrg= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.0.0-beta.5/go.mod h1:fR9dOEfEyRM7ltVH0FTpK/QA6L/5BQq8izXNRu/gyVc= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.0.1 h1:X2vfSnm1WC8HEo0MBHZg2TcuDUHJj6kd1TmEAQncnSA= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.0.1/go.mod h1:oVMjMN64nzEcepv1kdZKgx1qNYt4Ro0Gqefiq2JWdis= +github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= +github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= +github.com/gwatts/rootcerts v0.0.0-20200901201953-2af9685de2db h1:/kpIfdRsD6oyVqFxlfz6Ak+ssvfm7NbCqb1nFrmn0GU= +github.com/gwatts/rootcerts v0.0.0-20200901201953-2af9685de2db/go.mod h1:PmcTZUaFWqfUtPPvjSVAMMzJzqfMYv6ehpPUX4nIo3Q= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= +github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/sys/mount v0.1.0 h1:Ytx78EatgFKtrqZ0BvJ0UtJE472ZvawVmil6pIfuCCU= +github.com/moby/sys/mount v0.1.0/go.mod h1:FVQFLDRWwyBjDTBNQXDlWnSFREqOo3OKX9aqhmeoo74= +github.com/moby/sys/mountinfo v0.1.0 h1:r8vMRbMAFEAfiNptYVokP+nfxPJzvRuia5e2vzXtENo= +github.com/moby/sys/mountinfo v0.1.0/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o= +github.com/moby/term v0.0.0-20200429084858-129dac9f73f6 h1:3Y9aosU6S5Bo8GYH0s+t1ej4m30GuUKvQ3c9ZLqdL28= +github.com/moby/term v0.0.0-20200429084858-129dac9f73f6/go.mod h1:or9wGItza1sRcM4Wd3dIv8DsFHYQuFsMHEdxUIlUxms= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.2-0.20190120152514-befc3cba3ffd h1:sSuonob6PRI7WpIQljn254CI0N/CKgZPJwiC8LgdR3A= +github.com/opencontainers/image-spec v1.0.2-0.20190120152514-befc3cba3ffd/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc6.0.20190223184428-5b5130ad76db h1:ObeSm2Vpjn53mHx0nE9LV1uUQHxMyMwMnnEifVXmtOM= +github.com/opencontainers/runc v1.0.0-rc6.0.20190223184428-5b5130ad76db/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= +github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/pressly/goose v2.6.0+incompatible/go.mod h1:m+QHWCqxR3k8D9l7qfzuC/djtlfzxr34mozWDYEu1z8= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.mongodb.org/mongo-driver v1.4.2/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9 h1:umElSU9WZirRdgu2yFHY0ayQkEnKiOC1TtM3fWXFnoU= +golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200806022845-90696ccdc692/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200923140941-5646d36feee1/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210824181836-a4879c3d0e89/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210825212027-de86158e7fda h1:iT5uhT54PtbqUsWddv/nnEWdE5e/MTr+Nv3vjxlBP1A= +google.golang.org/genproto v0.0.0-20210825212027-de86158e7fda/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210830153122-0bac4d21c8ea h1:5eMUso2GVOxypVH1fR4oKgDobrvi4DHctJ4fVk66s/4= +google.golang.org/genproto v0.0.0-20210830153122-0bac4d21c8ea/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v0.0.0-20200527211525-6c9e30c09db2/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/grpc/examples v0.0.0-20201005153634-dad518ae5e80/go.mod h1:Lh55/1hxmVHEkOvSIQ2uj0P12QyOCUNyRwnUlSS13hw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/hraban/opus.v2 v2.0.0-20201025103112-d779bb1cc5a2 h1:sxrRNhZ+cNxxLwPw/vV8gNsz+bbqRQiZHBYBJfpyNoQ= +gopkg.in/hraban/opus.v2 v2.0.0-20201025103112-d779bb1cc5a2/go.mod h1:/L5E7a21VWl8DeuCPKxQBdVG5cy+L0MRZ08B1wnqt7g= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.0.2 h1:kG1BFyqVHuQoVQiR1bWGnfz/fmHvvuiSPIV7rvl360E= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/vector-cloud/internal/clad/clad.go b/vector-cloud/internal/clad/clad.go new file mode 100644 index 0000000..3fcaec0 --- /dev/null +++ b/vector-cloud/internal/clad/clad.go @@ -0,0 +1,9 @@ +package clad + +import "bytes" + +type Struct interface { + Size() uint32 + Pack(*bytes.Buffer) error + Unpack(*bytes.Buffer) error +} diff --git a/vector-cloud/internal/clad/cloud/common.go b/vector-cloud/internal/clad/cloud/common.go new file mode 100644 index 0000000..0fcbbda --- /dev/null +++ b/vector-cloud/internal/clad/cloud/common.go @@ -0,0 +1,29 @@ +// Autogenerated Go message buffer code. +// Source: clad/cloud/common.clad +// Full command line: victor-clad/tools/message-buffers/emitters/Go_emitter.py -C src -o generated/cladgo/src clad/cloud/common.clad + +package cloud + +import ( + "bytes" +) + +// STRUCTURE Void +type Void struct { +} + +func (v *Void) Size() uint32 { + return 0 +} + +func (v *Void) Unpack(buf *bytes.Buffer) error { + return nil +} + +func (v *Void) Pack(buf *bytes.Buffer) error { + return nil +} + +func (v *Void) String() string { + return "" +} diff --git a/vector-cloud/internal/clad/cloud/docs.go b/vector-cloud/internal/clad/cloud/docs.go new file mode 100644 index 0000000..0a8e669 --- /dev/null +++ b/vector-cloud/internal/clad/cloud/docs.go @@ -0,0 +1,1140 @@ +// Autogenerated Go message buffer code. +// Source: clad/cloud/docs.clad +// Full command line: victor-clad/tools/message-buffers/emitters/Go_emitter.py -C src -o generated/cladgo/src clad/cloud/docs.clad + +package cloud + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + + "github.com/digital-dream-labs/vector-cloud/internal/clad" +) + +// STRUCTURE Doc +type Doc struct { + DocVersion uint64 + FmtVersion uint64 + Metadata string + JsonDoc string +} + +func (d *Doc) Size() uint32 { + var result uint32 + result += 8 // DocVersion uint_64 + result += 8 // FmtVersion uint_64 + result += 1 // Metadata length (uint_8) + result += uint32(len(d.Metadata)) // uint_8 array + result += 4 // JsonDoc length (uint_32) + result += uint32(len(d.JsonDoc)) // uint_8 array + return result +} + +func (d *Doc) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &d.DocVersion); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.FmtVersion); err != nil { + return err + } + var MetadataLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &MetadataLen); err != nil { + return err + } + d.Metadata = string(buf.Next(int(MetadataLen))) + if len(d.Metadata) != int(MetadataLen) { + return errors.New("string byte mismatch") + } + var JsonDocLen uint32 + if err := binary.Read(buf, binary.LittleEndian, &JsonDocLen); err != nil { + return err + } + d.JsonDoc = string(buf.Next(int(JsonDocLen))) + if len(d.JsonDoc) != int(JsonDocLen) { + return errors.New("string byte mismatch") + } + return nil +} + +func (d *Doc) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, d.DocVersion); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.FmtVersion); err != nil { + return err + } + if len(d.Metadata) > 255 { + return errors.New("max_length overflow in field Metadata") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(d.Metadata))); err != nil { + return err + } + if _, err := buf.WriteString(d.Metadata); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, uint32(len(d.JsonDoc))); err != nil { + return err + } + if _, err := buf.WriteString(d.JsonDoc); err != nil { + return err + } + return nil +} + +func (d *Doc) String() string { + return fmt.Sprint("DocVersion: {", d.DocVersion, "} ", + "FmtVersion: {", d.FmtVersion, "} ", + "Metadata: {", d.Metadata, "} ", + "JsonDoc: {", d.JsonDoc, "}") +} + +// STRUCTURE WriteRequest +type WriteRequest struct { + Account string + Thing string + DocName string + Doc Doc +} + +func (w *WriteRequest) Size() uint32 { + var result uint32 + result += 2 // Account length (uint_16) + result += uint32(len(w.Account)) // uint_8 array + result += 2 // Thing length (uint_16) + result += uint32(len(w.Thing)) // uint_8 array + result += 2 // DocName length (uint_16) + result += uint32(len(w.DocName)) // uint_8 array + result += w.Doc.Size() + return result +} + +func (w *WriteRequest) Unpack(buf *bytes.Buffer) error { + var AccountLen uint16 + if err := binary.Read(buf, binary.LittleEndian, &AccountLen); err != nil { + return err + } + w.Account = string(buf.Next(int(AccountLen))) + if len(w.Account) != int(AccountLen) { + return errors.New("string byte mismatch") + } + var ThingLen uint16 + if err := binary.Read(buf, binary.LittleEndian, &ThingLen); err != nil { + return err + } + w.Thing = string(buf.Next(int(ThingLen))) + if len(w.Thing) != int(ThingLen) { + return errors.New("string byte mismatch") + } + var DocNameLen uint16 + if err := binary.Read(buf, binary.LittleEndian, &DocNameLen); err != nil { + return err + } + w.DocName = string(buf.Next(int(DocNameLen))) + if len(w.DocName) != int(DocNameLen) { + return errors.New("string byte mismatch") + } + if err := w.Doc.Unpack(buf); err != nil { + return err + } + return nil +} + +func (w *WriteRequest) Pack(buf *bytes.Buffer) error { + if len(w.Account) > 65535 { + return errors.New("max_length overflow in field Account") + } + if err := binary.Write(buf, binary.LittleEndian, uint16(len(w.Account))); err != nil { + return err + } + if _, err := buf.WriteString(w.Account); err != nil { + return err + } + if len(w.Thing) > 65535 { + return errors.New("max_length overflow in field Thing") + } + if err := binary.Write(buf, binary.LittleEndian, uint16(len(w.Thing))); err != nil { + return err + } + if _, err := buf.WriteString(w.Thing); err != nil { + return err + } + if len(w.DocName) > 65535 { + return errors.New("max_length overflow in field DocName") + } + if err := binary.Write(buf, binary.LittleEndian, uint16(len(w.DocName))); err != nil { + return err + } + if _, err := buf.WriteString(w.DocName); err != nil { + return err + } + if err := w.Doc.Pack(buf); err != nil { + return err + } + return nil +} + +func (w *WriteRequest) String() string { + return fmt.Sprint("Account: {", w.Account, "} ", + "Thing: {", w.Thing, "} ", + "DocName: {", w.DocName, "} ", + "Doc: {", w.Doc, "}") +} + +// ENUM DocError +type DocError uint8 + +const ( + DocError_ErrorConnecting DocError = iota +) + +// ENUM WriteStatus +type WriteStatus uint8 + +const ( + WriteStatus_Accepted WriteStatus = iota + WriteStatus_RejectedDocVersion + WriteStatus_RejectedFmtVersion + WriteStatus_Error +) + +// STRUCTURE WriteResponse +type WriteResponse struct { + Status WriteStatus + LatestVersion uint64 +} + +func (w *WriteResponse) Size() uint32 { + var result uint32 + result += 1 // Status WriteStatus + result += 8 // LatestVersion uint_64 + return result +} + +func (w *WriteResponse) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &w.Status); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &w.LatestVersion); err != nil { + return err + } + return nil +} + +func (w *WriteResponse) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, w.Status); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, w.LatestVersion); err != nil { + return err + } + return nil +} + +func (w *WriteResponse) String() string { + return fmt.Sprint("Status: {", w.Status, "} ", + "LatestVersion: {", w.LatestVersion, "}") +} + +// STRUCTURE ReadItem +type ReadItem struct { + DocName string + MyDocVersion uint64 +} + +func (r *ReadItem) Size() uint32 { + var result uint32 + result += 1 // DocName length (uint_8) + result += uint32(len(r.DocName)) // uint_8 array + result += 8 // MyDocVersion uint_64 + return result +} + +func (r *ReadItem) Unpack(buf *bytes.Buffer) error { + var DocNameLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &DocNameLen); err != nil { + return err + } + r.DocName = string(buf.Next(int(DocNameLen))) + if len(r.DocName) != int(DocNameLen) { + return errors.New("string byte mismatch") + } + if err := binary.Read(buf, binary.LittleEndian, &r.MyDocVersion); err != nil { + return err + } + return nil +} + +func (r *ReadItem) Pack(buf *bytes.Buffer) error { + if len(r.DocName) > 255 { + return errors.New("max_length overflow in field DocName") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(r.DocName))); err != nil { + return err + } + if _, err := buf.WriteString(r.DocName); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, r.MyDocVersion); err != nil { + return err + } + return nil +} + +func (r *ReadItem) String() string { + return fmt.Sprint("DocName: {", r.DocName, "} ", + "MyDocVersion: {", r.MyDocVersion, "}") +} + +// STRUCTURE ReadRequest +type ReadRequest struct { + Account string + Thing string + Items []ReadItem +} + +func (r *ReadRequest) Size() uint32 { + var result uint32 + result += 2 // Account length (uint_16) + result += uint32(len(r.Account)) // uint_8 array + result += 2 // Thing length (uint_16) + result += uint32(len(r.Thing)) // uint_8 array + result += 2 // Items length (uint_16) + for idx := range r.Items { + result += r.Items[idx].Size() + } + return result +} + +func (r *ReadRequest) Unpack(buf *bytes.Buffer) error { + var AccountLen uint16 + if err := binary.Read(buf, binary.LittleEndian, &AccountLen); err != nil { + return err + } + r.Account = string(buf.Next(int(AccountLen))) + if len(r.Account) != int(AccountLen) { + return errors.New("string byte mismatch") + } + var ThingLen uint16 + if err := binary.Read(buf, binary.LittleEndian, &ThingLen); err != nil { + return err + } + r.Thing = string(buf.Next(int(ThingLen))) + if len(r.Thing) != int(ThingLen) { + return errors.New("string byte mismatch") + } + var ItemsLen uint16 + if err := binary.Read(buf, binary.LittleEndian, &ItemsLen); err != nil { + return err + } + r.Items = make([]ReadItem, ItemsLen) + for idx := range r.Items { + if err := r.Items[idx].Unpack(buf); err != nil { + return err + } + } + return nil +} + +func (r *ReadRequest) Pack(buf *bytes.Buffer) error { + if len(r.Account) > 65535 { + return errors.New("max_length overflow in field Account") + } + if err := binary.Write(buf, binary.LittleEndian, uint16(len(r.Account))); err != nil { + return err + } + if _, err := buf.WriteString(r.Account); err != nil { + return err + } + if len(r.Thing) > 65535 { + return errors.New("max_length overflow in field Thing") + } + if err := binary.Write(buf, binary.LittleEndian, uint16(len(r.Thing))); err != nil { + return err + } + if _, err := buf.WriteString(r.Thing); err != nil { + return err + } + if len(r.Items) > 65535 { + return errors.New("max_length overflow in field Items") + } + if err := binary.Write(buf, binary.LittleEndian, uint16(len(r.Items))); err != nil { + return err + } + for idx := range r.Items { + if err := r.Items[idx].Pack(buf); err != nil { + return err + } + } + return nil +} + +func (r *ReadRequest) String() string { + return fmt.Sprint("Account: {", r.Account, "} ", + "Thing: {", r.Thing, "} ", + "Items: {", r.Items, "}") +} + +// ENUM ReadStatus +type ReadStatus uint8 + +const ( + ReadStatus_Unchanged ReadStatus = iota + ReadStatus_Changed + ReadStatus_NotFound + ReadStatus_PermissionDenied +) + +// STRUCTURE ResponseDoc +type ResponseDoc struct { + Status ReadStatus + Doc Doc +} + +func (r *ResponseDoc) Size() uint32 { + var result uint32 + result += 1 // Status ReadStatus + result += r.Doc.Size() + return result +} + +func (r *ResponseDoc) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &r.Status); err != nil { + return err + } + if err := r.Doc.Unpack(buf); err != nil { + return err + } + return nil +} + +func (r *ResponseDoc) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, r.Status); err != nil { + return err + } + if err := r.Doc.Pack(buf); err != nil { + return err + } + return nil +} + +func (r *ResponseDoc) String() string { + return fmt.Sprint("Status: {", r.Status, "} ", + "Doc: {", r.Doc, "}") +} + +// STRUCTURE ReadResponse +type ReadResponse struct { + Items []ResponseDoc +} + +func (r *ReadResponse) Size() uint32 { + var result uint32 + result += 2 // Items length (uint_16) + for idx := range r.Items { + result += r.Items[idx].Size() + } + return result +} + +func (r *ReadResponse) Unpack(buf *bytes.Buffer) error { + var ItemsLen uint16 + if err := binary.Read(buf, binary.LittleEndian, &ItemsLen); err != nil { + return err + } + r.Items = make([]ResponseDoc, ItemsLen) + for idx := range r.Items { + if err := r.Items[idx].Unpack(buf); err != nil { + return err + } + } + return nil +} + +func (r *ReadResponse) Pack(buf *bytes.Buffer) error { + if len(r.Items) > 65535 { + return errors.New("max_length overflow in field Items") + } + if err := binary.Write(buf, binary.LittleEndian, uint16(len(r.Items))); err != nil { + return err + } + for idx := range r.Items { + if err := r.Items[idx].Pack(buf); err != nil { + return err + } + } + return nil +} + +func (r *ReadResponse) String() string { + return fmt.Sprint("Items: {", r.Items, "}") +} + +// STRUCTURE DeleteRequest +type DeleteRequest struct { + Account string + Thing string + DocName string +} + +func (d *DeleteRequest) Size() uint32 { + var result uint32 + result += 2 // Account length (uint_16) + result += uint32(len(d.Account)) // uint_8 array + result += 2 // Thing length (uint_16) + result += uint32(len(d.Thing)) // uint_8 array + result += 2 // DocName length (uint_16) + result += uint32(len(d.DocName)) // uint_8 array + return result +} + +func (d *DeleteRequest) Unpack(buf *bytes.Buffer) error { + var AccountLen uint16 + if err := binary.Read(buf, binary.LittleEndian, &AccountLen); err != nil { + return err + } + d.Account = string(buf.Next(int(AccountLen))) + if len(d.Account) != int(AccountLen) { + return errors.New("string byte mismatch") + } + var ThingLen uint16 + if err := binary.Read(buf, binary.LittleEndian, &ThingLen); err != nil { + return err + } + d.Thing = string(buf.Next(int(ThingLen))) + if len(d.Thing) != int(ThingLen) { + return errors.New("string byte mismatch") + } + var DocNameLen uint16 + if err := binary.Read(buf, binary.LittleEndian, &DocNameLen); err != nil { + return err + } + d.DocName = string(buf.Next(int(DocNameLen))) + if len(d.DocName) != int(DocNameLen) { + return errors.New("string byte mismatch") + } + return nil +} + +func (d *DeleteRequest) Pack(buf *bytes.Buffer) error { + if len(d.Account) > 65535 { + return errors.New("max_length overflow in field Account") + } + if err := binary.Write(buf, binary.LittleEndian, uint16(len(d.Account))); err != nil { + return err + } + if _, err := buf.WriteString(d.Account); err != nil { + return err + } + if len(d.Thing) > 65535 { + return errors.New("max_length overflow in field Thing") + } + if err := binary.Write(buf, binary.LittleEndian, uint16(len(d.Thing))); err != nil { + return err + } + if _, err := buf.WriteString(d.Thing); err != nil { + return err + } + if len(d.DocName) > 65535 { + return errors.New("max_length overflow in field DocName") + } + if err := binary.Write(buf, binary.LittleEndian, uint16(len(d.DocName))); err != nil { + return err + } + if _, err := buf.WriteString(d.DocName); err != nil { + return err + } + return nil +} + +func (d *DeleteRequest) String() string { + return fmt.Sprint("Account: {", d.Account, "} ", + "Thing: {", d.Thing, "} ", + "DocName: {", d.DocName, "}") +} + +// UNION DocRequest +type DocRequestTag uint8 + +const ( + DocRequestTag_Write DocRequestTag = iota // 0 + DocRequestTag_Read // 1 + DocRequestTag_DeleteReq // 2 + DocRequestTag_User // 3 + DocRequestTag_Thing // 4 + DocRequestTag_INVALID DocRequestTag = 255 +) + +type DocRequest struct { + tag *DocRequestTag + value clad.Struct +} + +func (m *DocRequest) Tag() DocRequestTag { + if m.tag == nil { + return DocRequestTag_INVALID + } + return *m.tag +} + +func (m *DocRequest) Size() uint32 { + if m.tag == nil || *m.tag == DocRequestTag_INVALID { + return 1 + } + return 1 + m.value.Size() +} + +func (m *DocRequest) Pack(buf *bytes.Buffer) error { + tag := DocRequestTag_INVALID + if m.tag != nil { + tag = *m.tag + } + if err := binary.Write(buf, binary.LittleEndian, tag); err != nil { + return err + } + if tag == DocRequestTag_INVALID { + return nil + } + return m.value.Pack(buf) +} + +func (m *DocRequest) unpackStruct(tag DocRequestTag, buf *bytes.Buffer) (clad.Struct, error) { + switch tag { + case DocRequestTag_Write: + var ret WriteRequest + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case DocRequestTag_Read: + var ret ReadRequest + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case DocRequestTag_DeleteReq: + var ret DeleteRequest + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case DocRequestTag_User: + var ret Void + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case DocRequestTag_Thing: + var ret Void + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + default: + return nil, errors.New("invalid tag to unpackStruct") + } +} + +func (m *DocRequest) Unpack(buf *bytes.Buffer) error { + tag := DocRequestTag_INVALID + if err := binary.Read(buf, binary.LittleEndian, &tag); err != nil { + return err + } + m.tag = &tag + if tag == DocRequestTag_INVALID { + m.value = nil + return nil + } + val, err := m.unpackStruct(tag, buf) + if err != nil { + *m.tag = DocRequestTag_INVALID + return err + } + m.value = val + return nil +} + +func (t DocRequestTag) String() string { + switch t { + case DocRequestTag_Write: + return "Write" + case DocRequestTag_Read: + return "Read" + case DocRequestTag_DeleteReq: + return "DeleteReq" + case DocRequestTag_User: + return "User" + case DocRequestTag_Thing: + return "Thing" + default: + return "INVALID" + } +} + +func (m *DocRequest) String() string { + if m.tag == nil { + return "nil" + } + if *m.tag == DocRequestTag_INVALID { + return "INVALID" + } + return fmt.Sprintf("%s: {%s}", *m.tag, m.value) +} + +func (m *DocRequest) GetWrite() *WriteRequest { + if m.tag == nil || *m.tag != DocRequestTag_Write { + return nil + } + return m.value.(*WriteRequest) +} + +func (m *DocRequest) SetWrite(value *WriteRequest) { + newTag := DocRequestTag_Write + m.tag = &newTag + m.value = value +} + +func NewDocRequestWithWrite(value *WriteRequest) *DocRequest { + var ret DocRequest + ret.SetWrite(value) + return &ret +} + +func (m *DocRequest) GetRead() *ReadRequest { + if m.tag == nil || *m.tag != DocRequestTag_Read { + return nil + } + return m.value.(*ReadRequest) +} + +func (m *DocRequest) SetRead(value *ReadRequest) { + newTag := DocRequestTag_Read + m.tag = &newTag + m.value = value +} + +func NewDocRequestWithRead(value *ReadRequest) *DocRequest { + var ret DocRequest + ret.SetRead(value) + return &ret +} + +func (m *DocRequest) GetDeleteReq() *DeleteRequest { + if m.tag == nil || *m.tag != DocRequestTag_DeleteReq { + return nil + } + return m.value.(*DeleteRequest) +} + +func (m *DocRequest) SetDeleteReq(value *DeleteRequest) { + newTag := DocRequestTag_DeleteReq + m.tag = &newTag + m.value = value +} + +func NewDocRequestWithDeleteReq(value *DeleteRequest) *DocRequest { + var ret DocRequest + ret.SetDeleteReq(value) + return &ret +} + +func (m *DocRequest) GetUser() *Void { + if m.tag == nil || *m.tag != DocRequestTag_User { + return nil + } + return m.value.(*Void) +} + +func (m *DocRequest) SetUser(value *Void) { + newTag := DocRequestTag_User + m.tag = &newTag + m.value = value +} + +func NewDocRequestWithUser(value *Void) *DocRequest { + var ret DocRequest + ret.SetUser(value) + return &ret +} + +func (m *DocRequest) GetThing() *Void { + if m.tag == nil || *m.tag != DocRequestTag_Thing { + return nil + } + return m.value.(*Void) +} + +func (m *DocRequest) SetThing(value *Void) { + newTag := DocRequestTag_Thing + m.tag = &newTag + m.value = value +} + +func NewDocRequestWithThing(value *Void) *DocRequest { + var ret DocRequest + ret.SetThing(value) + return &ret +} + +// STRUCTURE ErrorResponse +type ErrorResponse struct { + Err DocError +} + +func (e *ErrorResponse) Size() uint32 { + var result uint32 + result += 1 // Err DocError + return result +} + +func (e *ErrorResponse) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &e.Err); err != nil { + return err + } + return nil +} + +func (e *ErrorResponse) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, e.Err); err != nil { + return err + } + return nil +} + +func (e *ErrorResponse) String() string { + return fmt.Sprint("Err: {", e.Err, "}") +} + +// STRUCTURE UserResponse +type UserResponse struct { + UserId string +} + +func (u *UserResponse) Size() uint32 { + var result uint32 + result += 1 // UserId length (uint_8) + result += uint32(len(u.UserId)) // uint_8 array + return result +} + +func (u *UserResponse) Unpack(buf *bytes.Buffer) error { + var UserIdLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &UserIdLen); err != nil { + return err + } + u.UserId = string(buf.Next(int(UserIdLen))) + if len(u.UserId) != int(UserIdLen) { + return errors.New("string byte mismatch") + } + return nil +} + +func (u *UserResponse) Pack(buf *bytes.Buffer) error { + if len(u.UserId) > 255 { + return errors.New("max_length overflow in field UserId") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(u.UserId))); err != nil { + return err + } + if _, err := buf.WriteString(u.UserId); err != nil { + return err + } + return nil +} + +func (u *UserResponse) String() string { + return fmt.Sprint("UserId: {", u.UserId, "}") +} + +// STRUCTURE ThingResponse +type ThingResponse struct { + ThingName string +} + +func (t *ThingResponse) Size() uint32 { + var result uint32 + result += 1 // ThingName length (uint_8) + result += uint32(len(t.ThingName)) // uint_8 array + return result +} + +func (t *ThingResponse) Unpack(buf *bytes.Buffer) error { + var ThingNameLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &ThingNameLen); err != nil { + return err + } + t.ThingName = string(buf.Next(int(ThingNameLen))) + if len(t.ThingName) != int(ThingNameLen) { + return errors.New("string byte mismatch") + } + return nil +} + +func (t *ThingResponse) Pack(buf *bytes.Buffer) error { + if len(t.ThingName) > 255 { + return errors.New("max_length overflow in field ThingName") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(t.ThingName))); err != nil { + return err + } + if _, err := buf.WriteString(t.ThingName); err != nil { + return err + } + return nil +} + +func (t *ThingResponse) String() string { + return fmt.Sprint("ThingName: {", t.ThingName, "}") +} + +// UNION DocResponse +type DocResponseTag uint8 + +const ( + DocResponseTag_Write DocResponseTag = iota // 0 + DocResponseTag_Read // 1 + DocResponseTag_DeleteResp // 2 + DocResponseTag_Err // 3 + DocResponseTag_User // 4 + DocResponseTag_Thing // 5 + DocResponseTag_INVALID DocResponseTag = 255 +) + +type DocResponse struct { + tag *DocResponseTag + value clad.Struct +} + +func (m *DocResponse) Tag() DocResponseTag { + if m.tag == nil { + return DocResponseTag_INVALID + } + return *m.tag +} + +func (m *DocResponse) Size() uint32 { + if m.tag == nil || *m.tag == DocResponseTag_INVALID { + return 1 + } + return 1 + m.value.Size() +} + +func (m *DocResponse) Pack(buf *bytes.Buffer) error { + tag := DocResponseTag_INVALID + if m.tag != nil { + tag = *m.tag + } + if err := binary.Write(buf, binary.LittleEndian, tag); err != nil { + return err + } + if tag == DocResponseTag_INVALID { + return nil + } + return m.value.Pack(buf) +} + +func (m *DocResponse) unpackStruct(tag DocResponseTag, buf *bytes.Buffer) (clad.Struct, error) { + switch tag { + case DocResponseTag_Write: + var ret WriteResponse + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case DocResponseTag_Read: + var ret ReadResponse + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case DocResponseTag_DeleteResp: + var ret Void + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case DocResponseTag_Err: + var ret ErrorResponse + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case DocResponseTag_User: + var ret UserResponse + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case DocResponseTag_Thing: + var ret ThingResponse + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + default: + return nil, errors.New("invalid tag to unpackStruct") + } +} + +func (m *DocResponse) Unpack(buf *bytes.Buffer) error { + tag := DocResponseTag_INVALID + if err := binary.Read(buf, binary.LittleEndian, &tag); err != nil { + return err + } + m.tag = &tag + if tag == DocResponseTag_INVALID { + m.value = nil + return nil + } + val, err := m.unpackStruct(tag, buf) + if err != nil { + *m.tag = DocResponseTag_INVALID + return err + } + m.value = val + return nil +} + +func (t DocResponseTag) String() string { + switch t { + case DocResponseTag_Write: + return "Write" + case DocResponseTag_Read: + return "Read" + case DocResponseTag_DeleteResp: + return "DeleteResp" + case DocResponseTag_Err: + return "Err" + case DocResponseTag_User: + return "User" + case DocResponseTag_Thing: + return "Thing" + default: + return "INVALID" + } +} + +func (m *DocResponse) String() string { + if m.tag == nil { + return "nil" + } + if *m.tag == DocResponseTag_INVALID { + return "INVALID" + } + return fmt.Sprintf("%s: {%s}", *m.tag, m.value) +} + +func (m *DocResponse) GetWrite() *WriteResponse { + if m.tag == nil || *m.tag != DocResponseTag_Write { + return nil + } + return m.value.(*WriteResponse) +} + +func (m *DocResponse) SetWrite(value *WriteResponse) { + newTag := DocResponseTag_Write + m.tag = &newTag + m.value = value +} + +func NewDocResponseWithWrite(value *WriteResponse) *DocResponse { + var ret DocResponse + ret.SetWrite(value) + return &ret +} + +func (m *DocResponse) GetRead() *ReadResponse { + if m.tag == nil || *m.tag != DocResponseTag_Read { + return nil + } + return m.value.(*ReadResponse) +} + +func (m *DocResponse) SetRead(value *ReadResponse) { + newTag := DocResponseTag_Read + m.tag = &newTag + m.value = value +} + +func NewDocResponseWithRead(value *ReadResponse) *DocResponse { + var ret DocResponse + ret.SetRead(value) + return &ret +} + +func (m *DocResponse) GetDeleteResp() *Void { + if m.tag == nil || *m.tag != DocResponseTag_DeleteResp { + return nil + } + return m.value.(*Void) +} + +func (m *DocResponse) SetDeleteResp(value *Void) { + newTag := DocResponseTag_DeleteResp + m.tag = &newTag + m.value = value +} + +func NewDocResponseWithDeleteResp(value *Void) *DocResponse { + var ret DocResponse + ret.SetDeleteResp(value) + return &ret +} + +func (m *DocResponse) GetErr() *ErrorResponse { + if m.tag == nil || *m.tag != DocResponseTag_Err { + return nil + } + return m.value.(*ErrorResponse) +} + +func (m *DocResponse) SetErr(value *ErrorResponse) { + newTag := DocResponseTag_Err + m.tag = &newTag + m.value = value +} + +func NewDocResponseWithErr(value *ErrorResponse) *DocResponse { + var ret DocResponse + ret.SetErr(value) + return &ret +} + +func (m *DocResponse) GetUser() *UserResponse { + if m.tag == nil || *m.tag != DocResponseTag_User { + return nil + } + return m.value.(*UserResponse) +} + +func (m *DocResponse) SetUser(value *UserResponse) { + newTag := DocResponseTag_User + m.tag = &newTag + m.value = value +} + +func NewDocResponseWithUser(value *UserResponse) *DocResponse { + var ret DocResponse + ret.SetUser(value) + return &ret +} + +func (m *DocResponse) GetThing() *ThingResponse { + if m.tag == nil || *m.tag != DocResponseTag_Thing { + return nil + } + return m.value.(*ThingResponse) +} + +func (m *DocResponse) SetThing(value *ThingResponse) { + newTag := DocResponseTag_Thing + m.tag = &newTag + m.value = value +} + +func NewDocResponseWithThing(value *ThingResponse) *DocResponse { + var ret DocResponse + ret.SetThing(value) + return &ret +} diff --git a/vector-cloud/internal/clad/cloud/logcollector.go b/vector-cloud/internal/clad/cloud/logcollector.go new file mode 100644 index 0000000..7c15e23 --- /dev/null +++ b/vector-cloud/internal/clad/cloud/logcollector.go @@ -0,0 +1,382 @@ +// Autogenerated Go message buffer code. +// Source: clad/cloud/logcollector.clad +// Full command line: victor-clad/tools/message-buffers/emitters/Go_emitter.py -C src -o generated/cladgo/src clad/cloud/logcollector.clad + +package cloud + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + + "github.com/digital-dream-labs/vector-cloud/internal/clad" +) + +// ENUM LogCollectorError +type LogCollectorError uint8 + +const ( + LogCollectorError_ErrorConnecting LogCollectorError = iota +) + +// STRUCTURE UploadResponse +type UploadResponse struct { + LogUrl string +} + +func (u *UploadResponse) Size() uint32 { + var result uint32 + result += 2 // LogUrl length (uint_16) + result += uint32(len(u.LogUrl)) // uint_8 array + return result +} + +func (u *UploadResponse) Unpack(buf *bytes.Buffer) error { + var LogUrlLen uint16 + if err := binary.Read(buf, binary.LittleEndian, &LogUrlLen); err != nil { + return err + } + u.LogUrl = string(buf.Next(int(LogUrlLen))) + if len(u.LogUrl) != int(LogUrlLen) { + return errors.New("string byte mismatch") + } + return nil +} + +func (u *UploadResponse) Pack(buf *bytes.Buffer) error { + if len(u.LogUrl) > 65535 { + return errors.New("max_length overflow in field LogUrl") + } + if err := binary.Write(buf, binary.LittleEndian, uint16(len(u.LogUrl))); err != nil { + return err + } + if _, err := buf.WriteString(u.LogUrl); err != nil { + return err + } + return nil +} + +func (u *UploadResponse) String() string { + return fmt.Sprint("LogUrl: {", u.LogUrl, "}") +} + +// STRUCTURE UploadRequest +type UploadRequest struct { + LogFileName string +} + +func (u *UploadRequest) Size() uint32 { + var result uint32 + result += 2 // LogFileName length (uint_16) + result += uint32(len(u.LogFileName)) // uint_8 array + return result +} + +func (u *UploadRequest) Unpack(buf *bytes.Buffer) error { + var LogFileNameLen uint16 + if err := binary.Read(buf, binary.LittleEndian, &LogFileNameLen); err != nil { + return err + } + u.LogFileName = string(buf.Next(int(LogFileNameLen))) + if len(u.LogFileName) != int(LogFileNameLen) { + return errors.New("string byte mismatch") + } + return nil +} + +func (u *UploadRequest) Pack(buf *bytes.Buffer) error { + if len(u.LogFileName) > 65535 { + return errors.New("max_length overflow in field LogFileName") + } + if err := binary.Write(buf, binary.LittleEndian, uint16(len(u.LogFileName))); err != nil { + return err + } + if _, err := buf.WriteString(u.LogFileName); err != nil { + return err + } + return nil +} + +func (u *UploadRequest) String() string { + return fmt.Sprint("LogFileName: {", u.LogFileName, "}") +} + +// UNION LogCollectorRequest +type LogCollectorRequestTag uint8 + +const ( + LogCollectorRequestTag_Upload LogCollectorRequestTag = iota // 0 + LogCollectorRequestTag_INVALID LogCollectorRequestTag = 255 +) + +type LogCollectorRequest struct { + tag *LogCollectorRequestTag + value clad.Struct +} + +func (m *LogCollectorRequest) Tag() LogCollectorRequestTag { + if m.tag == nil { + return LogCollectorRequestTag_INVALID + } + return *m.tag +} + +func (m *LogCollectorRequest) Size() uint32 { + if m.tag == nil || *m.tag == LogCollectorRequestTag_INVALID { + return 1 + } + return 1 + m.value.Size() +} + +func (m *LogCollectorRequest) Pack(buf *bytes.Buffer) error { + tag := LogCollectorRequestTag_INVALID + if m.tag != nil { + tag = *m.tag + } + if err := binary.Write(buf, binary.LittleEndian, tag); err != nil { + return err + } + if tag == LogCollectorRequestTag_INVALID { + return nil + } + return m.value.Pack(buf) +} + +func (m *LogCollectorRequest) unpackStruct(tag LogCollectorRequestTag, buf *bytes.Buffer) (clad.Struct, error) { + switch tag { + case LogCollectorRequestTag_Upload: + var ret UploadRequest + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + default: + return nil, errors.New("invalid tag to unpackStruct") + } +} + +func (m *LogCollectorRequest) Unpack(buf *bytes.Buffer) error { + tag := LogCollectorRequestTag_INVALID + if err := binary.Read(buf, binary.LittleEndian, &tag); err != nil { + return err + } + m.tag = &tag + if tag == LogCollectorRequestTag_INVALID { + m.value = nil + return nil + } + val, err := m.unpackStruct(tag, buf) + if err != nil { + *m.tag = LogCollectorRequestTag_INVALID + return err + } + m.value = val + return nil +} + +func (t LogCollectorRequestTag) String() string { + switch t { + case LogCollectorRequestTag_Upload: + return "Upload" + default: + return "INVALID" + } +} + +func (m *LogCollectorRequest) String() string { + if m.tag == nil { + return "nil" + } + if *m.tag == LogCollectorRequestTag_INVALID { + return "INVALID" + } + return fmt.Sprintf("%s: {%s}", *m.tag, m.value) +} + +func (m *LogCollectorRequest) GetUpload() *UploadRequest { + if m.tag == nil || *m.tag != LogCollectorRequestTag_Upload { + return nil + } + return m.value.(*UploadRequest) +} + +func (m *LogCollectorRequest) SetUpload(value *UploadRequest) { + newTag := LogCollectorRequestTag_Upload + m.tag = &newTag + m.value = value +} + +func NewLogCollectorRequestWithUpload(value *UploadRequest) *LogCollectorRequest { + var ret LogCollectorRequest + ret.SetUpload(value) + return &ret +} + +// STRUCTURE LogCollectorErrorResponse +type LogCollectorErrorResponse struct { + Err LogCollectorError +} + +func (l *LogCollectorErrorResponse) Size() uint32 { + var result uint32 + result += 1 // Err LogCollectorError + return result +} + +func (l *LogCollectorErrorResponse) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &l.Err); err != nil { + return err + } + return nil +} + +func (l *LogCollectorErrorResponse) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, l.Err); err != nil { + return err + } + return nil +} + +func (l *LogCollectorErrorResponse) String() string { + return fmt.Sprint("Err: {", l.Err, "}") +} + +// UNION LogCollectorResponse +type LogCollectorResponseTag uint8 + +const ( + LogCollectorResponseTag_Upload LogCollectorResponseTag = iota // 0 + LogCollectorResponseTag_Err // 1 + LogCollectorResponseTag_INVALID LogCollectorResponseTag = 255 +) + +type LogCollectorResponse struct { + tag *LogCollectorResponseTag + value clad.Struct +} + +func (m *LogCollectorResponse) Tag() LogCollectorResponseTag { + if m.tag == nil { + return LogCollectorResponseTag_INVALID + } + return *m.tag +} + +func (m *LogCollectorResponse) Size() uint32 { + if m.tag == nil || *m.tag == LogCollectorResponseTag_INVALID { + return 1 + } + return 1 + m.value.Size() +} + +func (m *LogCollectorResponse) Pack(buf *bytes.Buffer) error { + tag := LogCollectorResponseTag_INVALID + if m.tag != nil { + tag = *m.tag + } + if err := binary.Write(buf, binary.LittleEndian, tag); err != nil { + return err + } + if tag == LogCollectorResponseTag_INVALID { + return nil + } + return m.value.Pack(buf) +} + +func (m *LogCollectorResponse) unpackStruct(tag LogCollectorResponseTag, buf *bytes.Buffer) (clad.Struct, error) { + switch tag { + case LogCollectorResponseTag_Upload: + var ret UploadResponse + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case LogCollectorResponseTag_Err: + var ret LogCollectorErrorResponse + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + default: + return nil, errors.New("invalid tag to unpackStruct") + } +} + +func (m *LogCollectorResponse) Unpack(buf *bytes.Buffer) error { + tag := LogCollectorResponseTag_INVALID + if err := binary.Read(buf, binary.LittleEndian, &tag); err != nil { + return err + } + m.tag = &tag + if tag == LogCollectorResponseTag_INVALID { + m.value = nil + return nil + } + val, err := m.unpackStruct(tag, buf) + if err != nil { + *m.tag = LogCollectorResponseTag_INVALID + return err + } + m.value = val + return nil +} + +func (t LogCollectorResponseTag) String() string { + switch t { + case LogCollectorResponseTag_Upload: + return "Upload" + case LogCollectorResponseTag_Err: + return "Err" + default: + return "INVALID" + } +} + +func (m *LogCollectorResponse) String() string { + if m.tag == nil { + return "nil" + } + if *m.tag == LogCollectorResponseTag_INVALID { + return "INVALID" + } + return fmt.Sprintf("%s: {%s}", *m.tag, m.value) +} + +func (m *LogCollectorResponse) GetUpload() *UploadResponse { + if m.tag == nil || *m.tag != LogCollectorResponseTag_Upload { + return nil + } + return m.value.(*UploadResponse) +} + +func (m *LogCollectorResponse) SetUpload(value *UploadResponse) { + newTag := LogCollectorResponseTag_Upload + m.tag = &newTag + m.value = value +} + +func NewLogCollectorResponseWithUpload(value *UploadResponse) *LogCollectorResponse { + var ret LogCollectorResponse + ret.SetUpload(value) + return &ret +} + +func (m *LogCollectorResponse) GetErr() *LogCollectorErrorResponse { + if m.tag == nil || *m.tag != LogCollectorResponseTag_Err { + return nil + } + return m.value.(*LogCollectorErrorResponse) +} + +func (m *LogCollectorResponse) SetErr(value *LogCollectorErrorResponse) { + newTag := LogCollectorResponseTag_Err + m.tag = &newTag + m.value = value +} + +func NewLogCollectorResponseWithErr(value *LogCollectorErrorResponse) *LogCollectorResponse { + var ret LogCollectorResponse + ret.SetErr(value) + return &ret +} diff --git a/vector-cloud/internal/clad/cloud/mic.go b/vector-cloud/internal/clad/cloud/mic.go new file mode 100644 index 0000000..8647200 --- /dev/null +++ b/vector-cloud/internal/clad/cloud/mic.go @@ -0,0 +1,872 @@ +// Autogenerated Go message buffer code. +// Source: clad/cloud/mic.clad +// Full command line: victor-clad/tools/message-buffers/emitters/Go_emitter.py -C src -o generated/cladgo/src clad/cloud/mic.clad + +package cloud + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + + "github.com/digital-dream-labs/vector-cloud/internal/clad" +) + +// ENUM StreamType +type StreamType uint8 + +const ( + StreamType_Normal StreamType = iota + StreamType_Blackjack + StreamType_KnowledgeGraph +) + +// ENUM ErrorType +type ErrorType uint8 + +const ( + ErrorType_Server ErrorType = iota + ErrorType_Timeout + ErrorType_Json + ErrorType_InvalidConfig + ErrorType_Connecting + ErrorType_NewStream + ErrorType_Token + ErrorType_TLS + ErrorType_Connectivity +) + +// ENUM ConnectionCode +type ConnectionCode uint8 + +const ( + ConnectionCode_Available ConnectionCode = iota + ConnectionCode_Connectivity + ConnectionCode_Tls + ConnectionCode_Auth + ConnectionCode_Bandwidth +) + +// STRUCTURE StreamOpen +type StreamOpen struct { + Session string +} + +func (s *StreamOpen) Size() uint32 { + var result uint32 + result += 1 // Session length (uint_8) + result += uint32(len(s.Session)) // uint_8 array + return result +} + +func (s *StreamOpen) Unpack(buf *bytes.Buffer) error { + var SessionLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &SessionLen); err != nil { + return err + } + s.Session = string(buf.Next(int(SessionLen))) + if len(s.Session) != int(SessionLen) { + return errors.New("string byte mismatch") + } + return nil +} + +func (s *StreamOpen) Pack(buf *bytes.Buffer) error { + if len(s.Session) > 255 { + return errors.New("max_length overflow in field Session") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(s.Session))); err != nil { + return err + } + if _, err := buf.WriteString(s.Session); err != nil { + return err + } + return nil +} + +func (s *StreamOpen) String() string { + return fmt.Sprint("Session: {", s.Session, "}") +} + +// STRUCTURE Hotword +type Hotword struct { + Mode StreamType + Locale string + Timezone string + NoLogging bool +} + +func (h *Hotword) Size() uint32 { + var result uint32 + result += 1 // Mode StreamType + result += 1 // Locale length (uint_8) + result += uint32(len(h.Locale)) // uint_8 array + result += 1 // Timezone length (uint_8) + result += uint32(len(h.Timezone)) // uint_8 array + result += 1 // NoLogging bool + return result +} + +func (h *Hotword) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &h.Mode); err != nil { + return err + } + var LocaleLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &LocaleLen); err != nil { + return err + } + h.Locale = string(buf.Next(int(LocaleLen))) + if len(h.Locale) != int(LocaleLen) { + return errors.New("string byte mismatch") + } + var TimezoneLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &TimezoneLen); err != nil { + return err + } + h.Timezone = string(buf.Next(int(TimezoneLen))) + if len(h.Timezone) != int(TimezoneLen) { + return errors.New("string byte mismatch") + } + if err := binary.Read(buf, binary.LittleEndian, &h.NoLogging); err != nil { + return err + } + return nil +} + +func (h *Hotword) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, h.Mode); err != nil { + return err + } + if len(h.Locale) > 255 { + return errors.New("max_length overflow in field Locale") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(h.Locale))); err != nil { + return err + } + if _, err := buf.WriteString(h.Locale); err != nil { + return err + } + if len(h.Timezone) > 255 { + return errors.New("max_length overflow in field Timezone") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(h.Timezone))); err != nil { + return err + } + if _, err := buf.WriteString(h.Timezone); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, h.NoLogging); err != nil { + return err + } + return nil +} + +func (h *Hotword) String() string { + return fmt.Sprint("Mode: {", h.Mode, "} ", + "Locale: {", h.Locale, "} ", + "Timezone: {", h.Timezone, "} ", + "NoLogging: {", h.NoLogging, "}") +} + +// STRUCTURE Filename +type Filename struct { + File string +} + +func (f *Filename) Size() uint32 { + var result uint32 + result += 1 // File length (uint_8) + result += uint32(len(f.File)) // uint_8 array + return result +} + +func (f *Filename) Unpack(buf *bytes.Buffer) error { + var FileLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &FileLen); err != nil { + return err + } + f.File = string(buf.Next(int(FileLen))) + if len(f.File) != int(FileLen) { + return errors.New("string byte mismatch") + } + return nil +} + +func (f *Filename) Pack(buf *bytes.Buffer) error { + if len(f.File) > 255 { + return errors.New("max_length overflow in field File") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(f.File))); err != nil { + return err + } + if _, err := buf.WriteString(f.File); err != nil { + return err + } + return nil +} + +func (f *Filename) String() string { + return fmt.Sprint("File: {", f.File, "}") +} + +// STRUCTURE AudioData +type AudioData struct { + Data []int16 +} + +func (a *AudioData) Size() uint32 { + var result uint32 + result += 2 // Data length (uint_16) + result += uint32(len(a.Data)) * 2 // int_16 array + return result +} + +func (a *AudioData) Unpack(buf *bytes.Buffer) error { + var DataLen uint16 + if err := binary.Read(buf, binary.LittleEndian, &DataLen); err != nil { + return err + } + a.Data = make([]int16, DataLen) + if err := binary.Read(buf, binary.LittleEndian, &a.Data); err != nil { + return err + } + return nil +} + +func (a *AudioData) Pack(buf *bytes.Buffer) error { + if len(a.Data) > 65535 { + return errors.New("max_length overflow in field Data") + } + if err := binary.Write(buf, binary.LittleEndian, uint16(len(a.Data))); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, a.Data); err != nil { + return err + } + return nil +} + +func (a *AudioData) String() string { + return fmt.Sprint("Data: {", a.Data, "}") +} + +// STRUCTURE IntentResult +type IntentResult struct { + Intent string + Parameters string + Metadata string +} + +func (i *IntentResult) Size() uint32 { + var result uint32 + result += 1 // Intent length (uint_8) + result += uint32(len(i.Intent)) // uint_8 array + result += 2 // Parameters length (uint_16) + result += uint32(len(i.Parameters)) // uint_8 array + result += 2 // Metadata length (uint_16) + result += uint32(len(i.Metadata)) // uint_8 array + return result +} + +func (i *IntentResult) Unpack(buf *bytes.Buffer) error { + var IntentLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &IntentLen); err != nil { + return err + } + i.Intent = string(buf.Next(int(IntentLen))) + if len(i.Intent) != int(IntentLen) { + return errors.New("string byte mismatch") + } + var ParametersLen uint16 + if err := binary.Read(buf, binary.LittleEndian, &ParametersLen); err != nil { + return err + } + i.Parameters = string(buf.Next(int(ParametersLen))) + if len(i.Parameters) != int(ParametersLen) { + return errors.New("string byte mismatch") + } + var MetadataLen uint16 + if err := binary.Read(buf, binary.LittleEndian, &MetadataLen); err != nil { + return err + } + i.Metadata = string(buf.Next(int(MetadataLen))) + if len(i.Metadata) != int(MetadataLen) { + return errors.New("string byte mismatch") + } + return nil +} + +func (i *IntentResult) Pack(buf *bytes.Buffer) error { + if len(i.Intent) > 255 { + return errors.New("max_length overflow in field Intent") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(i.Intent))); err != nil { + return err + } + if _, err := buf.WriteString(i.Intent); err != nil { + return err + } + if len(i.Parameters) > 65535 { + return errors.New("max_length overflow in field Parameters") + } + if err := binary.Write(buf, binary.LittleEndian, uint16(len(i.Parameters))); err != nil { + return err + } + if _, err := buf.WriteString(i.Parameters); err != nil { + return err + } + if len(i.Metadata) > 65535 { + return errors.New("max_length overflow in field Metadata") + } + if err := binary.Write(buf, binary.LittleEndian, uint16(len(i.Metadata))); err != nil { + return err + } + if _, err := buf.WriteString(i.Metadata); err != nil { + return err + } + return nil +} + +func (i *IntentResult) String() string { + return fmt.Sprint("Intent: {", i.Intent, "} ", + "Parameters: {", i.Parameters, "} ", + "Metadata: {", i.Metadata, "}") +} + +// STRUCTURE IntentError +type IntentError struct { + Error ErrorType + Extra string +} + +func (i *IntentError) Size() uint32 { + var result uint32 + result += 1 // Error ErrorType + result += 1 // Extra length (uint_8) + result += uint32(len(i.Extra)) // uint_8 array + return result +} + +func (i *IntentError) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &i.Error); err != nil { + return err + } + var ExtraLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &ExtraLen); err != nil { + return err + } + i.Extra = string(buf.Next(int(ExtraLen))) + if len(i.Extra) != int(ExtraLen) { + return errors.New("string byte mismatch") + } + return nil +} + +func (i *IntentError) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, i.Error); err != nil { + return err + } + if len(i.Extra) > 255 { + return errors.New("max_length overflow in field Extra") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(i.Extra))); err != nil { + return err + } + if _, err := buf.WriteString(i.Extra); err != nil { + return err + } + return nil +} + +func (i *IntentError) String() string { + return fmt.Sprint("Error: {", i.Error, "} ", + "Extra: {", i.Extra, "}") +} + +// STRUCTURE ConnectionResult +type ConnectionResult struct { + Code ConnectionCode + Status string + NumPackets uint8 + ExpectedPackets uint8 +} + +func (c *ConnectionResult) Size() uint32 { + var result uint32 + result += 1 // Code ConnectionCode + result += 1 // Status length (uint_8) + result += uint32(len(c.Status)) // uint_8 array + result += 1 // NumPackets uint_8 + result += 1 // ExpectedPackets uint_8 + return result +} + +func (c *ConnectionResult) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &c.Code); err != nil { + return err + } + var StatusLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &StatusLen); err != nil { + return err + } + c.Status = string(buf.Next(int(StatusLen))) + if len(c.Status) != int(StatusLen) { + return errors.New("string byte mismatch") + } + if err := binary.Read(buf, binary.LittleEndian, &c.NumPackets); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &c.ExpectedPackets); err != nil { + return err + } + return nil +} + +func (c *ConnectionResult) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, c.Code); err != nil { + return err + } + if len(c.Status) > 255 { + return errors.New("max_length overflow in field Status") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(c.Status))); err != nil { + return err + } + if _, err := buf.WriteString(c.Status); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, c.NumPackets); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, c.ExpectedPackets); err != nil { + return err + } + return nil +} + +func (c *ConnectionResult) String() string { + return fmt.Sprint("Code: {", c.Code, "} ", + "Status: {", c.Status, "} ", + "NumPackets: {", c.NumPackets, "} ", + "ExpectedPackets: {", c.ExpectedPackets, "}") +} + +// UNION Message +type MessageTag uint8 + +const ( + MessageTag_Hotword MessageTag = iota // 0 + MessageTag_Audio // 1 + MessageTag_AudioDone // 2 + MessageTag_ConnectionCheck // 3 + MessageTag_StopSignal // 4 + MessageTag_TestStarted // 5 + MessageTag_StreamTimeout // 6 + MessageTag_ConnectionResult // 7 + MessageTag_DebugFile // 8 + MessageTag_Result // 9 + MessageTag_Error // 10 + MessageTag_StreamOpen // 11 + MessageTag_INVALID MessageTag = 255 +) + +type Message struct { + tag *MessageTag + value clad.Struct +} + +func (m *Message) Tag() MessageTag { + if m.tag == nil { + return MessageTag_INVALID + } + return *m.tag +} + +func (m *Message) Size() uint32 { + if m.tag == nil || *m.tag == MessageTag_INVALID { + return 1 + } + return 1 + m.value.Size() +} + +func (m *Message) Pack(buf *bytes.Buffer) error { + tag := MessageTag_INVALID + if m.tag != nil { + tag = *m.tag + } + if err := binary.Write(buf, binary.LittleEndian, tag); err != nil { + return err + } + if tag == MessageTag_INVALID { + return nil + } + return m.value.Pack(buf) +} + +func (m *Message) unpackStruct(tag MessageTag, buf *bytes.Buffer) (clad.Struct, error) { + switch tag { + case MessageTag_Hotword: + var ret Hotword + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageTag_Audio: + var ret AudioData + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageTag_AudioDone: + var ret Void + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageTag_ConnectionCheck: + var ret Void + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageTag_StopSignal: + var ret Void + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageTag_TestStarted: + var ret Void + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageTag_StreamTimeout: + var ret Void + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageTag_ConnectionResult: + var ret ConnectionResult + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageTag_DebugFile: + var ret Filename + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageTag_Result: + var ret IntentResult + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageTag_Error: + var ret IntentError + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageTag_StreamOpen: + var ret StreamOpen + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + default: + return nil, errors.New("invalid tag to unpackStruct") + } +} + +func (m *Message) Unpack(buf *bytes.Buffer) error { + tag := MessageTag_INVALID + if err := binary.Read(buf, binary.LittleEndian, &tag); err != nil { + return err + } + m.tag = &tag + if tag == MessageTag_INVALID { + m.value = nil + return nil + } + val, err := m.unpackStruct(tag, buf) + if err != nil { + *m.tag = MessageTag_INVALID + return err + } + m.value = val + return nil +} + +func (t MessageTag) String() string { + switch t { + case MessageTag_Hotword: + return "Hotword" + case MessageTag_Audio: + return "Audio" + case MessageTag_AudioDone: + return "AudioDone" + case MessageTag_ConnectionCheck: + return "ConnectionCheck" + case MessageTag_StopSignal: + return "StopSignal" + case MessageTag_TestStarted: + return "TestStarted" + case MessageTag_StreamTimeout: + return "StreamTimeout" + case MessageTag_ConnectionResult: + return "ConnectionResult" + case MessageTag_DebugFile: + return "DebugFile" + case MessageTag_Result: + return "Result" + case MessageTag_Error: + return "Error" + case MessageTag_StreamOpen: + return "StreamOpen" + default: + return "INVALID" + } +} + +func (m *Message) String() string { + if m.tag == nil { + return "nil" + } + if *m.tag == MessageTag_INVALID { + return "INVALID" + } + return fmt.Sprintf("%s: {%s}", *m.tag, m.value) +} + +func (m *Message) GetHotword() *Hotword { + if m.tag == nil || *m.tag != MessageTag_Hotword { + return nil + } + return m.value.(*Hotword) +} + +func (m *Message) SetHotword(value *Hotword) { + newTag := MessageTag_Hotword + m.tag = &newTag + m.value = value +} + +func NewMessageWithHotword(value *Hotword) *Message { + var ret Message + ret.SetHotword(value) + return &ret +} + +func (m *Message) GetAudio() *AudioData { + if m.tag == nil || *m.tag != MessageTag_Audio { + return nil + } + return m.value.(*AudioData) +} + +func (m *Message) SetAudio(value *AudioData) { + newTag := MessageTag_Audio + m.tag = &newTag + m.value = value +} + +func NewMessageWithAudio(value *AudioData) *Message { + var ret Message + ret.SetAudio(value) + return &ret +} + +func (m *Message) GetAudioDone() *Void { + if m.tag == nil || *m.tag != MessageTag_AudioDone { + return nil + } + return m.value.(*Void) +} + +func (m *Message) SetAudioDone(value *Void) { + newTag := MessageTag_AudioDone + m.tag = &newTag + m.value = value +} + +func NewMessageWithAudioDone(value *Void) *Message { + var ret Message + ret.SetAudioDone(value) + return &ret +} + +func (m *Message) GetConnectionCheck() *Void { + if m.tag == nil || *m.tag != MessageTag_ConnectionCheck { + return nil + } + return m.value.(*Void) +} + +func (m *Message) SetConnectionCheck(value *Void) { + newTag := MessageTag_ConnectionCheck + m.tag = &newTag + m.value = value +} + +func NewMessageWithConnectionCheck(value *Void) *Message { + var ret Message + ret.SetConnectionCheck(value) + return &ret +} + +func (m *Message) GetStopSignal() *Void { + if m.tag == nil || *m.tag != MessageTag_StopSignal { + return nil + } + return m.value.(*Void) +} + +func (m *Message) SetStopSignal(value *Void) { + newTag := MessageTag_StopSignal + m.tag = &newTag + m.value = value +} + +func NewMessageWithStopSignal(value *Void) *Message { + var ret Message + ret.SetStopSignal(value) + return &ret +} + +func (m *Message) GetTestStarted() *Void { + if m.tag == nil || *m.tag != MessageTag_TestStarted { + return nil + } + return m.value.(*Void) +} + +func (m *Message) SetTestStarted(value *Void) { + newTag := MessageTag_TestStarted + m.tag = &newTag + m.value = value +} + +func NewMessageWithTestStarted(value *Void) *Message { + var ret Message + ret.SetTestStarted(value) + return &ret +} + +func (m *Message) GetStreamTimeout() *Void { + if m.tag == nil || *m.tag != MessageTag_StreamTimeout { + return nil + } + return m.value.(*Void) +} + +func (m *Message) SetStreamTimeout(value *Void) { + newTag := MessageTag_StreamTimeout + m.tag = &newTag + m.value = value +} + +func NewMessageWithStreamTimeout(value *Void) *Message { + var ret Message + ret.SetStreamTimeout(value) + return &ret +} + +func (m *Message) GetConnectionResult() *ConnectionResult { + if m.tag == nil || *m.tag != MessageTag_ConnectionResult { + return nil + } + return m.value.(*ConnectionResult) +} + +func (m *Message) SetConnectionResult(value *ConnectionResult) { + newTag := MessageTag_ConnectionResult + m.tag = &newTag + m.value = value +} + +func NewMessageWithConnectionResult(value *ConnectionResult) *Message { + var ret Message + ret.SetConnectionResult(value) + return &ret +} + +func (m *Message) GetDebugFile() *Filename { + if m.tag == nil || *m.tag != MessageTag_DebugFile { + return nil + } + return m.value.(*Filename) +} + +func (m *Message) SetDebugFile(value *Filename) { + newTag := MessageTag_DebugFile + m.tag = &newTag + m.value = value +} + +func NewMessageWithDebugFile(value *Filename) *Message { + var ret Message + ret.SetDebugFile(value) + return &ret +} + +func (m *Message) GetResult() *IntentResult { + if m.tag == nil || *m.tag != MessageTag_Result { + return nil + } + return m.value.(*IntentResult) +} + +func (m *Message) SetResult(value *IntentResult) { + newTag := MessageTag_Result + m.tag = &newTag + m.value = value +} + +func NewMessageWithResult(value *IntentResult) *Message { + var ret Message + ret.SetResult(value) + return &ret +} + +func (m *Message) GetError() *IntentError { + if m.tag == nil || *m.tag != MessageTag_Error { + return nil + } + return m.value.(*IntentError) +} + +func (m *Message) SetError(value *IntentError) { + newTag := MessageTag_Error + m.tag = &newTag + m.value = value +} + +func NewMessageWithError(value *IntentError) *Message { + var ret Message + ret.SetError(value) + return &ret +} + +func (m *Message) GetStreamOpen() *StreamOpen { + if m.tag == nil || *m.tag != MessageTag_StreamOpen { + return nil + } + return m.value.(*StreamOpen) +} + +func (m *Message) SetStreamOpen(value *StreamOpen) { + newTag := MessageTag_StreamOpen + m.tag = &newTag + m.value = value +} + +func NewMessageWithStreamOpen(value *StreamOpen) *Message { + var ret Message + ret.SetStreamOpen(value) + return &ret +} diff --git a/vector-cloud/internal/clad/cloud/token.go b/vector-cloud/internal/clad/cloud/token.go new file mode 100644 index 0000000..5c40202 --- /dev/null +++ b/vector-cloud/internal/clad/cloud/token.go @@ -0,0 +1,758 @@ +// Autogenerated Go message buffer code. +// Source: clad/cloud/token.clad +// Full command line: victor-clad/tools/message-buffers/emitters/Go_emitter.py -C src -o generated/cladgo/src clad/cloud/token.clad + +package cloud + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + + "github.com/digital-dream-labs/vector-cloud/internal/clad" +) + +// ENUM TokenError +type TokenError uint8 + +const ( + TokenError_NoError TokenError = iota + TokenError_NullToken + TokenError_InvalidToken + TokenError_Connection + TokenError_WrongAccount +) + +// STRUCTURE AuthRequest +type AuthRequest struct { + SessionToken string + ClientName string + AppId string +} + +func (a *AuthRequest) Size() uint32 { + var result uint32 + result += 1 // SessionToken length (uint_8) + result += uint32(len(a.SessionToken)) // uint_8 array + result += 1 // ClientName length (uint_8) + result += uint32(len(a.ClientName)) // uint_8 array + result += 1 // AppId length (uint_8) + result += uint32(len(a.AppId)) // uint_8 array + return result +} + +func (a *AuthRequest) Unpack(buf *bytes.Buffer) error { + var SessionTokenLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &SessionTokenLen); err != nil { + return err + } + a.SessionToken = string(buf.Next(int(SessionTokenLen))) + if len(a.SessionToken) != int(SessionTokenLen) { + return errors.New("string byte mismatch") + } + var ClientNameLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &ClientNameLen); err != nil { + return err + } + a.ClientName = string(buf.Next(int(ClientNameLen))) + if len(a.ClientName) != int(ClientNameLen) { + return errors.New("string byte mismatch") + } + var AppIdLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &AppIdLen); err != nil { + return err + } + a.AppId = string(buf.Next(int(AppIdLen))) + if len(a.AppId) != int(AppIdLen) { + return errors.New("string byte mismatch") + } + return nil +} + +func (a *AuthRequest) Pack(buf *bytes.Buffer) error { + if len(a.SessionToken) > 255 { + return errors.New("max_length overflow in field SessionToken") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(a.SessionToken))); err != nil { + return err + } + if _, err := buf.WriteString(a.SessionToken); err != nil { + return err + } + if len(a.ClientName) > 255 { + return errors.New("max_length overflow in field ClientName") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(a.ClientName))); err != nil { + return err + } + if _, err := buf.WriteString(a.ClientName); err != nil { + return err + } + if len(a.AppId) > 255 { + return errors.New("max_length overflow in field AppId") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(a.AppId))); err != nil { + return err + } + if _, err := buf.WriteString(a.AppId); err != nil { + return err + } + return nil +} + +func (a *AuthRequest) String() string { + return fmt.Sprint("SessionToken: {", a.SessionToken, "} ", + "ClientName: {", a.ClientName, "} ", + "AppId: {", a.AppId, "}") +} + +// STRUCTURE AuthResponse +type AuthResponse struct { + AppToken string + JwtToken string + Error TokenError +} + +func (a *AuthResponse) Size() uint32 { + var result uint32 + result += 2 // AppToken length (uint_16) + result += uint32(len(a.AppToken)) // uint_8 array + result += 2 // JwtToken length (uint_16) + result += uint32(len(a.JwtToken)) // uint_8 array + result += 1 // Error TokenError + return result +} + +func (a *AuthResponse) Unpack(buf *bytes.Buffer) error { + var AppTokenLen uint16 + if err := binary.Read(buf, binary.LittleEndian, &AppTokenLen); err != nil { + return err + } + a.AppToken = string(buf.Next(int(AppTokenLen))) + if len(a.AppToken) != int(AppTokenLen) { + return errors.New("string byte mismatch") + } + var JwtTokenLen uint16 + if err := binary.Read(buf, binary.LittleEndian, &JwtTokenLen); err != nil { + return err + } + a.JwtToken = string(buf.Next(int(JwtTokenLen))) + if len(a.JwtToken) != int(JwtTokenLen) { + return errors.New("string byte mismatch") + } + if err := binary.Read(buf, binary.LittleEndian, &a.Error); err != nil { + return err + } + return nil +} + +func (a *AuthResponse) Pack(buf *bytes.Buffer) error { + if len(a.AppToken) > 65535 { + return errors.New("max_length overflow in field AppToken") + } + if err := binary.Write(buf, binary.LittleEndian, uint16(len(a.AppToken))); err != nil { + return err + } + if _, err := buf.WriteString(a.AppToken); err != nil { + return err + } + if len(a.JwtToken) > 65535 { + return errors.New("max_length overflow in field JwtToken") + } + if err := binary.Write(buf, binary.LittleEndian, uint16(len(a.JwtToken))); err != nil { + return err + } + if _, err := buf.WriteString(a.JwtToken); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, a.Error); err != nil { + return err + } + return nil +} + +func (a *AuthResponse) String() string { + return fmt.Sprint("AppToken: {", a.AppToken, "} ", + "JwtToken: {", a.JwtToken, "} ", + "Error: {", a.Error, "}") +} + +// STRUCTURE ReassociateRequest +type ReassociateRequest struct { + SessionToken string + ClientName string + AppId string +} + +func (r *ReassociateRequest) Size() uint32 { + var result uint32 + result += 1 // SessionToken length (uint_8) + result += uint32(len(r.SessionToken)) // uint_8 array + result += 1 // ClientName length (uint_8) + result += uint32(len(r.ClientName)) // uint_8 array + result += 1 // AppId length (uint_8) + result += uint32(len(r.AppId)) // uint_8 array + return result +} + +func (r *ReassociateRequest) Unpack(buf *bytes.Buffer) error { + var SessionTokenLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &SessionTokenLen); err != nil { + return err + } + r.SessionToken = string(buf.Next(int(SessionTokenLen))) + if len(r.SessionToken) != int(SessionTokenLen) { + return errors.New("string byte mismatch") + } + var ClientNameLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &ClientNameLen); err != nil { + return err + } + r.ClientName = string(buf.Next(int(ClientNameLen))) + if len(r.ClientName) != int(ClientNameLen) { + return errors.New("string byte mismatch") + } + var AppIdLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &AppIdLen); err != nil { + return err + } + r.AppId = string(buf.Next(int(AppIdLen))) + if len(r.AppId) != int(AppIdLen) { + return errors.New("string byte mismatch") + } + return nil +} + +func (r *ReassociateRequest) Pack(buf *bytes.Buffer) error { + if len(r.SessionToken) > 255 { + return errors.New("max_length overflow in field SessionToken") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(r.SessionToken))); err != nil { + return err + } + if _, err := buf.WriteString(r.SessionToken); err != nil { + return err + } + if len(r.ClientName) > 255 { + return errors.New("max_length overflow in field ClientName") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(r.ClientName))); err != nil { + return err + } + if _, err := buf.WriteString(r.ClientName); err != nil { + return err + } + if len(r.AppId) > 255 { + return errors.New("max_length overflow in field AppId") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(r.AppId))); err != nil { + return err + } + if _, err := buf.WriteString(r.AppId); err != nil { + return err + } + return nil +} + +func (r *ReassociateRequest) String() string { + return fmt.Sprint("SessionToken: {", r.SessionToken, "} ", + "ClientName: {", r.ClientName, "} ", + "AppId: {", r.AppId, "}") +} + +// STRUCTURE SecondaryAuthRequest +type SecondaryAuthRequest struct { + SessionToken string + ClientName string + AppId string +} + +func (s *SecondaryAuthRequest) Size() uint32 { + var result uint32 + result += 1 // SessionToken length (uint_8) + result += uint32(len(s.SessionToken)) // uint_8 array + result += 1 // ClientName length (uint_8) + result += uint32(len(s.ClientName)) // uint_8 array + result += 1 // AppId length (uint_8) + result += uint32(len(s.AppId)) // uint_8 array + return result +} + +func (s *SecondaryAuthRequest) Unpack(buf *bytes.Buffer) error { + var SessionTokenLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &SessionTokenLen); err != nil { + return err + } + s.SessionToken = string(buf.Next(int(SessionTokenLen))) + if len(s.SessionToken) != int(SessionTokenLen) { + return errors.New("string byte mismatch") + } + var ClientNameLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &ClientNameLen); err != nil { + return err + } + s.ClientName = string(buf.Next(int(ClientNameLen))) + if len(s.ClientName) != int(ClientNameLen) { + return errors.New("string byte mismatch") + } + var AppIdLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &AppIdLen); err != nil { + return err + } + s.AppId = string(buf.Next(int(AppIdLen))) + if len(s.AppId) != int(AppIdLen) { + return errors.New("string byte mismatch") + } + return nil +} + +func (s *SecondaryAuthRequest) Pack(buf *bytes.Buffer) error { + if len(s.SessionToken) > 255 { + return errors.New("max_length overflow in field SessionToken") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(s.SessionToken))); err != nil { + return err + } + if _, err := buf.WriteString(s.SessionToken); err != nil { + return err + } + if len(s.ClientName) > 255 { + return errors.New("max_length overflow in field ClientName") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(s.ClientName))); err != nil { + return err + } + if _, err := buf.WriteString(s.ClientName); err != nil { + return err + } + if len(s.AppId) > 255 { + return errors.New("max_length overflow in field AppId") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(s.AppId))); err != nil { + return err + } + if _, err := buf.WriteString(s.AppId); err != nil { + return err + } + return nil +} + +func (s *SecondaryAuthRequest) String() string { + return fmt.Sprint("SessionToken: {", s.SessionToken, "} ", + "ClientName: {", s.ClientName, "} ", + "AppId: {", s.AppId, "}") +} + +// STRUCTURE JwtRequest +type JwtRequest struct { + ForceRefresh bool +} + +func (j *JwtRequest) Size() uint32 { + var result uint32 + result += 1 // ForceRefresh bool + return result +} + +func (j *JwtRequest) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &j.ForceRefresh); err != nil { + return err + } + return nil +} + +func (j *JwtRequest) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, j.ForceRefresh); err != nil { + return err + } + return nil +} + +func (j *JwtRequest) String() string { + return fmt.Sprint("ForceRefresh: {", j.ForceRefresh, "}") +} + +// STRUCTURE JwtResponse +type JwtResponse struct { + JwtToken string + Error TokenError +} + +func (j *JwtResponse) Size() uint32 { + var result uint32 + result += 2 // JwtToken length (uint_16) + result += uint32(len(j.JwtToken)) // uint_8 array + result += 1 // Error TokenError + return result +} + +func (j *JwtResponse) Unpack(buf *bytes.Buffer) error { + var JwtTokenLen uint16 + if err := binary.Read(buf, binary.LittleEndian, &JwtTokenLen); err != nil { + return err + } + j.JwtToken = string(buf.Next(int(JwtTokenLen))) + if len(j.JwtToken) != int(JwtTokenLen) { + return errors.New("string byte mismatch") + } + if err := binary.Read(buf, binary.LittleEndian, &j.Error); err != nil { + return err + } + return nil +} + +func (j *JwtResponse) Pack(buf *bytes.Buffer) error { + if len(j.JwtToken) > 65535 { + return errors.New("max_length overflow in field JwtToken") + } + if err := binary.Write(buf, binary.LittleEndian, uint16(len(j.JwtToken))); err != nil { + return err + } + if _, err := buf.WriteString(j.JwtToken); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, j.Error); err != nil { + return err + } + return nil +} + +func (j *JwtResponse) String() string { + return fmt.Sprint("JwtToken: {", j.JwtToken, "} ", + "Error: {", j.Error, "}") +} + +// UNION TokenRequest +type TokenRequestTag uint8 + +const ( + TokenRequestTag_Auth TokenRequestTag = iota // 0 + TokenRequestTag_Secondary // 1 + TokenRequestTag_Reassociate // 2 + TokenRequestTag_Jwt // 3 + TokenRequestTag_INVALID TokenRequestTag = 255 +) + +type TokenRequest struct { + tag *TokenRequestTag + value clad.Struct +} + +func (m *TokenRequest) Tag() TokenRequestTag { + if m.tag == nil { + return TokenRequestTag_INVALID + } + return *m.tag +} + +func (m *TokenRequest) Size() uint32 { + if m.tag == nil || *m.tag == TokenRequestTag_INVALID { + return 1 + } + return 1 + m.value.Size() +} + +func (m *TokenRequest) Pack(buf *bytes.Buffer) error { + tag := TokenRequestTag_INVALID + if m.tag != nil { + tag = *m.tag + } + if err := binary.Write(buf, binary.LittleEndian, tag); err != nil { + return err + } + if tag == TokenRequestTag_INVALID { + return nil + } + return m.value.Pack(buf) +} + +func (m *TokenRequest) unpackStruct(tag TokenRequestTag, buf *bytes.Buffer) (clad.Struct, error) { + switch tag { + case TokenRequestTag_Auth: + var ret AuthRequest + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case TokenRequestTag_Secondary: + var ret SecondaryAuthRequest + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case TokenRequestTag_Reassociate: + var ret ReassociateRequest + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case TokenRequestTag_Jwt: + var ret JwtRequest + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + default: + return nil, errors.New("invalid tag to unpackStruct") + } +} + +func (m *TokenRequest) Unpack(buf *bytes.Buffer) error { + tag := TokenRequestTag_INVALID + if err := binary.Read(buf, binary.LittleEndian, &tag); err != nil { + return err + } + m.tag = &tag + if tag == TokenRequestTag_INVALID { + m.value = nil + return nil + } + val, err := m.unpackStruct(tag, buf) + if err != nil { + *m.tag = TokenRequestTag_INVALID + return err + } + m.value = val + return nil +} + +func (t TokenRequestTag) String() string { + switch t { + case TokenRequestTag_Auth: + return "Auth" + case TokenRequestTag_Secondary: + return "Secondary" + case TokenRequestTag_Reassociate: + return "Reassociate" + case TokenRequestTag_Jwt: + return "Jwt" + default: + return "INVALID" + } +} + +func (m *TokenRequest) String() string { + if m.tag == nil { + return "nil" + } + if *m.tag == TokenRequestTag_INVALID { + return "INVALID" + } + return fmt.Sprintf("%s: {%s}", *m.tag, m.value) +} + +func (m *TokenRequest) GetAuth() *AuthRequest { + if m.tag == nil || *m.tag != TokenRequestTag_Auth { + return nil + } + return m.value.(*AuthRequest) +} + +func (m *TokenRequest) SetAuth(value *AuthRequest) { + newTag := TokenRequestTag_Auth + m.tag = &newTag + m.value = value +} + +func NewTokenRequestWithAuth(value *AuthRequest) *TokenRequest { + var ret TokenRequest + ret.SetAuth(value) + return &ret +} + +func (m *TokenRequest) GetSecondary() *SecondaryAuthRequest { + if m.tag == nil || *m.tag != TokenRequestTag_Secondary { + return nil + } + return m.value.(*SecondaryAuthRequest) +} + +func (m *TokenRequest) SetSecondary(value *SecondaryAuthRequest) { + newTag := TokenRequestTag_Secondary + m.tag = &newTag + m.value = value +} + +func NewTokenRequestWithSecondary(value *SecondaryAuthRequest) *TokenRequest { + var ret TokenRequest + ret.SetSecondary(value) + return &ret +} + +func (m *TokenRequest) GetReassociate() *ReassociateRequest { + if m.tag == nil || *m.tag != TokenRequestTag_Reassociate { + return nil + } + return m.value.(*ReassociateRequest) +} + +func (m *TokenRequest) SetReassociate(value *ReassociateRequest) { + newTag := TokenRequestTag_Reassociate + m.tag = &newTag + m.value = value +} + +func NewTokenRequestWithReassociate(value *ReassociateRequest) *TokenRequest { + var ret TokenRequest + ret.SetReassociate(value) + return &ret +} + +func (m *TokenRequest) GetJwt() *JwtRequest { + if m.tag == nil || *m.tag != TokenRequestTag_Jwt { + return nil + } + return m.value.(*JwtRequest) +} + +func (m *TokenRequest) SetJwt(value *JwtRequest) { + newTag := TokenRequestTag_Jwt + m.tag = &newTag + m.value = value +} + +func NewTokenRequestWithJwt(value *JwtRequest) *TokenRequest { + var ret TokenRequest + ret.SetJwt(value) + return &ret +} + +// UNION TokenResponse +type TokenResponseTag uint8 + +const ( + TokenResponseTag_Auth TokenResponseTag = iota // 0 + TokenResponseTag_Jwt // 1 + TokenResponseTag_INVALID TokenResponseTag = 255 +) + +type TokenResponse struct { + tag *TokenResponseTag + value clad.Struct +} + +func (m *TokenResponse) Tag() TokenResponseTag { + if m.tag == nil { + return TokenResponseTag_INVALID + } + return *m.tag +} + +func (m *TokenResponse) Size() uint32 { + if m.tag == nil || *m.tag == TokenResponseTag_INVALID { + return 1 + } + return 1 + m.value.Size() +} + +func (m *TokenResponse) Pack(buf *bytes.Buffer) error { + tag := TokenResponseTag_INVALID + if m.tag != nil { + tag = *m.tag + } + if err := binary.Write(buf, binary.LittleEndian, tag); err != nil { + return err + } + if tag == TokenResponseTag_INVALID { + return nil + } + return m.value.Pack(buf) +} + +func (m *TokenResponse) unpackStruct(tag TokenResponseTag, buf *bytes.Buffer) (clad.Struct, error) { + switch tag { + case TokenResponseTag_Auth: + var ret AuthResponse + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case TokenResponseTag_Jwt: + var ret JwtResponse + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + default: + return nil, errors.New("invalid tag to unpackStruct") + } +} + +func (m *TokenResponse) Unpack(buf *bytes.Buffer) error { + tag := TokenResponseTag_INVALID + if err := binary.Read(buf, binary.LittleEndian, &tag); err != nil { + return err + } + m.tag = &tag + if tag == TokenResponseTag_INVALID { + m.value = nil + return nil + } + val, err := m.unpackStruct(tag, buf) + if err != nil { + *m.tag = TokenResponseTag_INVALID + return err + } + m.value = val + return nil +} + +func (t TokenResponseTag) String() string { + switch t { + case TokenResponseTag_Auth: + return "Auth" + case TokenResponseTag_Jwt: + return "Jwt" + default: + return "INVALID" + } +} + +func (m *TokenResponse) String() string { + if m.tag == nil { + return "nil" + } + if *m.tag == TokenResponseTag_INVALID { + return "INVALID" + } + return fmt.Sprintf("%s: {%s}", *m.tag, m.value) +} + +func (m *TokenResponse) GetAuth() *AuthResponse { + if m.tag == nil || *m.tag != TokenResponseTag_Auth { + return nil + } + return m.value.(*AuthResponse) +} + +func (m *TokenResponse) SetAuth(value *AuthResponse) { + newTag := TokenResponseTag_Auth + m.tag = &newTag + m.value = value +} + +func NewTokenResponseWithAuth(value *AuthResponse) *TokenResponse { + var ret TokenResponse + ret.SetAuth(value) + return &ret +} + +func (m *TokenResponse) GetJwt() *JwtResponse { + if m.tag == nil || *m.tag != TokenResponseTag_Jwt { + return nil + } + return m.value.(*JwtResponse) +} + +func (m *TokenResponse) SetJwt(value *JwtResponse) { + newTag := TokenResponseTag_Jwt + m.tag = &newTag + m.value = value +} + +func NewTokenResponseWithJwt(value *JwtResponse) *TokenResponse { + var ret TokenResponse + ret.SetJwt(value) + return &ret +} diff --git a/vector-cloud/internal/clad/gateway/messageExternalToRobot.go b/vector-cloud/internal/clad/gateway/messageExternalToRobot.go new file mode 100644 index 0000000..26e4f99 --- /dev/null +++ b/vector-cloud/internal/clad/gateway/messageExternalToRobot.go @@ -0,0 +1,2523 @@ +// Autogenerated Go message buffer code. +// Source: clad/gateway/messageExternalToRobot.clad +// Full command line: victor-clad/tools/message-buffers/emitters/Go_emitter.py -C src -o generated/cladgo/src clad/gateway/messageExternalToRobot.clad + +package gateway + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + + "github.com/digital-dream-labs/vector-cloud/internal/clad" +) + +// MESSAGE UiDeviceConnectionWrongVersion +type UiDeviceConnectionWrongVersion struct { + PlaceHolder string +} + +func (u *UiDeviceConnectionWrongVersion) Size() uint32 { + var result uint32 + result += 1 // PlaceHolder length (uint_8) + result += uint32(len(u.PlaceHolder)) // uint_8 array + return result +} + +func (u *UiDeviceConnectionWrongVersion) Unpack(buf *bytes.Buffer) error { + var PlaceHolderLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &PlaceHolderLen); err != nil { + return err + } + u.PlaceHolder = string(buf.Next(int(PlaceHolderLen))) + if len(u.PlaceHolder) != int(PlaceHolderLen) { + return errors.New("string byte mismatch") + } + return nil +} + +func (u *UiDeviceConnectionWrongVersion) Pack(buf *bytes.Buffer) error { + if len(u.PlaceHolder) > 255 { + return errors.New("max_length overflow in field PlaceHolder") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(u.PlaceHolder))); err != nil { + return err + } + if _, err := buf.WriteString(u.PlaceHolder); err != nil { + return err + } + return nil +} + +func (u *UiDeviceConnectionWrongVersion) String() string { + return fmt.Sprint("PlaceHolder: {", u.PlaceHolder, "}") +} + +// MESSAGE MoveHead +type MoveHead struct { + SpeedRadPerSec float32 +} + +func (m *MoveHead) Size() uint32 { + var result uint32 + result += 4 // SpeedRadPerSec float_32 + return result +} + +func (m *MoveHead) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &m.SpeedRadPerSec); err != nil { + return err + } + return nil +} + +func (m *MoveHead) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, m.SpeedRadPerSec); err != nil { + return err + } + return nil +} + +func (m *MoveHead) String() string { + return fmt.Sprint("SpeedRadPerSec: {", m.SpeedRadPerSec, "}") +} + +// MESSAGE MoveLift +type MoveLift struct { + SpeedRadPerSec float32 +} + +func (m *MoveLift) Size() uint32 { + var result uint32 + result += 4 // SpeedRadPerSec float_32 + return result +} + +func (m *MoveLift) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &m.SpeedRadPerSec); err != nil { + return err + } + return nil +} + +func (m *MoveLift) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, m.SpeedRadPerSec); err != nil { + return err + } + return nil +} + +func (m *MoveLift) String() string { + return fmt.Sprint("SpeedRadPerSec: {", m.SpeedRadPerSec, "}") +} + +// MESSAGE DriveArc +type DriveArc struct { + Speed float32 + Accel float32 + CurvatureRadiusMm int16 +} + +func (d *DriveArc) Size() uint32 { + var result uint32 + result += 4 // Speed float_32 + result += 4 // Accel float_32 + result += 2 // CurvatureRadiusMm int_16 + return result +} + +func (d *DriveArc) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &d.Speed); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.Accel); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.CurvatureRadiusMm); err != nil { + return err + } + return nil +} + +func (d *DriveArc) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, d.Speed); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.Accel); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.CurvatureRadiusMm); err != nil { + return err + } + return nil +} + +func (d *DriveArc) String() string { + return fmt.Sprint("Speed: {", d.Speed, "} ", + "Accel: {", d.Accel, "} ", + "CurvatureRadiusMm: {", d.CurvatureRadiusMm, "}") +} + +// MESSAGE DisplayFaceImageRGBChunk +type DisplayFaceImageRGBChunk struct { + FaceData [600]uint16 + NumPixels uint16 + ChunkIndex uint8 + NumChunks uint8 + DurationMs uint32 + InterruptRunning bool +} + +func (d *DisplayFaceImageRGBChunk) Size() uint32 { + var result uint32 + result += 600 * 2 // uint_16 array + result += 2 // NumPixels uint_16 + result += 1 // ChunkIndex uint_8 + result += 1 // NumChunks uint_8 + result += 4 // DurationMs uint_32 + result += 1 // InterruptRunning bool + return result +} + +func (d *DisplayFaceImageRGBChunk) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &d.FaceData); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.NumPixels); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.ChunkIndex); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.NumChunks); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.DurationMs); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.InterruptRunning); err != nil { + return err + } + return nil +} + +func (d *DisplayFaceImageRGBChunk) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, d.FaceData); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.NumPixels); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.ChunkIndex); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.NumChunks); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.DurationMs); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.InterruptRunning); err != nil { + return err + } + return nil +} + +func (d *DisplayFaceImageRGBChunk) String() string { + return fmt.Sprint("FaceData: {", d.FaceData, "} ", + "NumPixels: {", d.NumPixels, "} ", + "ChunkIndex: {", d.ChunkIndex, "} ", + "NumChunks: {", d.NumChunks, "} ", + "DurationMs: {", d.DurationMs, "} ", + "InterruptRunning: {", d.InterruptRunning, "}") +} + +// MESSAGE RobotHistoryRequest +type RobotHistoryRequest struct { +} + +func (r *RobotHistoryRequest) Size() uint32 { + return 0 +} + +func (r *RobotHistoryRequest) Unpack(buf *bytes.Buffer) error { + return nil +} + +func (r *RobotHistoryRequest) Pack(buf *bytes.Buffer) error { + return nil +} + +func (r *RobotHistoryRequest) String() string { + return "" +} + +// MESSAGE AppIntent +type AppIntent struct { + Intent string + Param string +} + +func (a *AppIntent) Size() uint32 { + var result uint32 + result += 1 // Intent length (uint_8) + result += uint32(len(a.Intent)) // uint_8 array + result += 1 // Param length (uint_8) + result += uint32(len(a.Param)) // uint_8 array + return result +} + +func (a *AppIntent) Unpack(buf *bytes.Buffer) error { + var IntentLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &IntentLen); err != nil { + return err + } + a.Intent = string(buf.Next(int(IntentLen))) + if len(a.Intent) != int(IntentLen) { + return errors.New("string byte mismatch") + } + var ParamLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &ParamLen); err != nil { + return err + } + a.Param = string(buf.Next(int(ParamLen))) + if len(a.Param) != int(ParamLen) { + return errors.New("string byte mismatch") + } + return nil +} + +func (a *AppIntent) Pack(buf *bytes.Buffer) error { + if len(a.Intent) > 255 { + return errors.New("max_length overflow in field Intent") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(a.Intent))); err != nil { + return err + } + if _, err := buf.WriteString(a.Intent); err != nil { + return err + } + if len(a.Param) > 255 { + return errors.New("max_length overflow in field Param") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(a.Param))); err != nil { + return err + } + if _, err := buf.WriteString(a.Param); err != nil { + return err + } + return nil +} + +func (a *AppIntent) String() string { + return fmt.Sprint("Intent: {", a.Intent, "} ", + "Param: {", a.Param, "}") +} + +// MESSAGE CancelFaceEnrollment +type CancelFaceEnrollment struct { +} + +func (c *CancelFaceEnrollment) Size() uint32 { + return 0 +} + +func (c *CancelFaceEnrollment) Unpack(buf *bytes.Buffer) error { + return nil +} + +func (c *CancelFaceEnrollment) Pack(buf *bytes.Buffer) error { + return nil +} + +func (c *CancelFaceEnrollment) String() string { + return "" +} + +// MESSAGE RequestEnrolledNames +type RequestEnrolledNames struct { +} + +func (r *RequestEnrolledNames) Size() uint32 { + return 0 +} + +func (r *RequestEnrolledNames) Unpack(buf *bytes.Buffer) error { + return nil +} + +func (r *RequestEnrolledNames) Pack(buf *bytes.Buffer) error { + return nil +} + +func (r *RequestEnrolledNames) String() string { + return "" +} + +// MESSAGE UpdateEnrolledFaceByID +type UpdateEnrolledFaceByID struct { + FaceID int32 + OldName string + NewName string +} + +func (u *UpdateEnrolledFaceByID) Size() uint32 { + var result uint32 + result += 4 // FaceID int_32 + result += 1 // OldName length (uint_8) + result += uint32(len(u.OldName)) // uint_8 array + result += 1 // NewName length (uint_8) + result += uint32(len(u.NewName)) // uint_8 array + return result +} + +func (u *UpdateEnrolledFaceByID) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &u.FaceID); err != nil { + return err + } + var OldNameLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &OldNameLen); err != nil { + return err + } + u.OldName = string(buf.Next(int(OldNameLen))) + if len(u.OldName) != int(OldNameLen) { + return errors.New("string byte mismatch") + } + var NewNameLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &NewNameLen); err != nil { + return err + } + u.NewName = string(buf.Next(int(NewNameLen))) + if len(u.NewName) != int(NewNameLen) { + return errors.New("string byte mismatch") + } + return nil +} + +func (u *UpdateEnrolledFaceByID) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, u.FaceID); err != nil { + return err + } + if len(u.OldName) > 255 { + return errors.New("max_length overflow in field OldName") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(u.OldName))); err != nil { + return err + } + if _, err := buf.WriteString(u.OldName); err != nil { + return err + } + if len(u.NewName) > 255 { + return errors.New("max_length overflow in field NewName") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(u.NewName))); err != nil { + return err + } + if _, err := buf.WriteString(u.NewName); err != nil { + return err + } + return nil +} + +func (u *UpdateEnrolledFaceByID) String() string { + return fmt.Sprint("FaceID: {", u.FaceID, "} ", + "OldName: {", u.OldName, "} ", + "NewName: {", u.NewName, "}") +} + +// MESSAGE EraseEnrolledFaceByID +type EraseEnrolledFaceByID struct { + FaceID int32 +} + +func (e *EraseEnrolledFaceByID) Size() uint32 { + var result uint32 + result += 4 // FaceID int_32 + return result +} + +func (e *EraseEnrolledFaceByID) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &e.FaceID); err != nil { + return err + } + return nil +} + +func (e *EraseEnrolledFaceByID) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, e.FaceID); err != nil { + return err + } + return nil +} + +func (e *EraseEnrolledFaceByID) String() string { + return fmt.Sprint("FaceID: {", e.FaceID, "}") +} + +// MESSAGE EraseAllEnrolledFaces +type EraseAllEnrolledFaces struct { +} + +func (e *EraseAllEnrolledFaces) Size() uint32 { + return 0 +} + +func (e *EraseAllEnrolledFaces) Unpack(buf *bytes.Buffer) error { + return nil +} + +func (e *EraseAllEnrolledFaces) Pack(buf *bytes.Buffer) error { + return nil +} + +func (e *EraseAllEnrolledFaces) String() string { + return "" +} + +// MESSAGE SetFaceToEnroll +type SetFaceToEnroll struct { + Name string + ObservedID int32 + SaveID int32 + SaveToRobot bool + SayName bool + UseMusic bool +} + +func (s *SetFaceToEnroll) Size() uint32 { + var result uint32 + result += 1 // Name length (uint_8) + result += uint32(len(s.Name)) // uint_8 array + result += 4 // ObservedID int_32 + result += 4 // SaveID int_32 + result += 1 // SaveToRobot bool + result += 1 // SayName bool + result += 1 // UseMusic bool + return result +} + +func (s *SetFaceToEnroll) Unpack(buf *bytes.Buffer) error { + var NameLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &NameLen); err != nil { + return err + } + s.Name = string(buf.Next(int(NameLen))) + if len(s.Name) != int(NameLen) { + return errors.New("string byte mismatch") + } + if err := binary.Read(buf, binary.LittleEndian, &s.ObservedID); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &s.SaveID); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &s.SaveToRobot); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &s.SayName); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &s.UseMusic); err != nil { + return err + } + return nil +} + +func (s *SetFaceToEnroll) Pack(buf *bytes.Buffer) error { + if len(s.Name) > 255 { + return errors.New("max_length overflow in field Name") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(s.Name))); err != nil { + return err + } + if _, err := buf.WriteString(s.Name); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, s.ObservedID); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, s.SaveID); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, s.SaveToRobot); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, s.SayName); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, s.UseMusic); err != nil { + return err + } + return nil +} + +func (s *SetFaceToEnroll) String() string { + return fmt.Sprint("Name: {", s.Name, "} ", + "ObservedID: {", s.ObservedID, "} ", + "SaveID: {", s.SaveID, "} ", + "SaveToRobot: {", s.SaveToRobot, "} ", + "SayName: {", s.SayName, "} ", + "UseMusic: {", s.UseMusic, "}") +} + +// ENUM VisionMode +type VisionMode uint32 + +const ( + VisionMode_Faces VisionMode = VisionMode(0x1) +) + +// MESSAGE EnableVisionMode +type EnableVisionMode struct { + Mode VisionMode + Enable bool +} + +func (e *EnableVisionMode) Size() uint32 { + var result uint32 + result += 4 // Mode VisionMode + result += 1 // Enable bool + return result +} + +func (e *EnableVisionMode) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &e.Mode); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &e.Enable); err != nil { + return err + } + return nil +} + +func (e *EnableVisionMode) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, e.Mode); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, e.Enable); err != nil { + return err + } + return nil +} + +func (e *EnableVisionMode) String() string { + return fmt.Sprint("Mode: {", e.Mode, "} ", + "Enable: {", e.Enable, "}") +} + +// STRUCTURE PathMotionProfile +type PathMotionProfile struct { + SpeedMmps float32 + AccelMmps2 float32 + DecelMmps2 float32 + PointTurnSpeedRadPerSec float32 + PointTurnAccelRadPerSec2 float32 + PointTurnDecelRadPerSec2 float32 + DockSpeedMmps float32 + DockAccelMmps2 float32 + DockDecelMmps2 float32 + ReverseSpeedMmps float32 + IsCustom bool +} + +func (p *PathMotionProfile) Size() uint32 { + var result uint32 + result += 4 // SpeedMmps float_32 + result += 4 // AccelMmps2 float_32 + result += 4 // DecelMmps2 float_32 + result += 4 // PointTurnSpeedRadPerSec float_32 + result += 4 // PointTurnAccelRadPerSec2 float_32 + result += 4 // PointTurnDecelRadPerSec2 float_32 + result += 4 // DockSpeedMmps float_32 + result += 4 // DockAccelMmps2 float_32 + result += 4 // DockDecelMmps2 float_32 + result += 4 // ReverseSpeedMmps float_32 + result += 1 // IsCustom bool + return result +} + +func (p *PathMotionProfile) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &p.SpeedMmps); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &p.AccelMmps2); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &p.DecelMmps2); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &p.PointTurnSpeedRadPerSec); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &p.PointTurnAccelRadPerSec2); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &p.PointTurnDecelRadPerSec2); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &p.DockSpeedMmps); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &p.DockAccelMmps2); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &p.DockDecelMmps2); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &p.ReverseSpeedMmps); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &p.IsCustom); err != nil { + return err + } + return nil +} + +func (p *PathMotionProfile) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, p.SpeedMmps); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, p.AccelMmps2); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, p.DecelMmps2); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, p.PointTurnSpeedRadPerSec); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, p.PointTurnAccelRadPerSec2); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, p.PointTurnDecelRadPerSec2); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, p.DockSpeedMmps); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, p.DockAccelMmps2); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, p.DockDecelMmps2); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, p.ReverseSpeedMmps); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, p.IsCustom); err != nil { + return err + } + return nil +} + +func (p *PathMotionProfile) String() string { + return fmt.Sprint("SpeedMmps: {", p.SpeedMmps, "} ", + "AccelMmps2: {", p.AccelMmps2, "} ", + "DecelMmps2: {", p.DecelMmps2, "} ", + "PointTurnSpeedRadPerSec: {", p.PointTurnSpeedRadPerSec, "} ", + "PointTurnAccelRadPerSec2: {", p.PointTurnAccelRadPerSec2, "} ", + "PointTurnDecelRadPerSec2: {", p.PointTurnDecelRadPerSec2, "} ", + "DockSpeedMmps: {", p.DockSpeedMmps, "} ", + "DockAccelMmps2: {", p.DockAccelMmps2, "} ", + "DockDecelMmps2: {", p.DockDecelMmps2, "} ", + "ReverseSpeedMmps: {", p.ReverseSpeedMmps, "} ", + "IsCustom: {", p.IsCustom, "}") +} + +// MESSAGE GotoPose +type GotoPose struct { + XMm float32 + YMm float32 + Rad float32 + MotionProf PathMotionProfile + Level uint8 +} + +func (g *GotoPose) Size() uint32 { + var result uint32 + result += 4 // XMm float_32 + result += 4 // YMm float_32 + result += 4 // Rad float_32 + result += g.MotionProf.Size() + result += 1 // Level uint_8 + return result +} + +func (g *GotoPose) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &g.XMm); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &g.YMm); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &g.Rad); err != nil { + return err + } + if err := g.MotionProf.Unpack(buf); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &g.Level); err != nil { + return err + } + return nil +} + +func (g *GotoPose) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, g.XMm); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, g.YMm); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, g.Rad); err != nil { + return err + } + if err := g.MotionProf.Pack(buf); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, g.Level); err != nil { + return err + } + return nil +} + +func (g *GotoPose) String() string { + return fmt.Sprint("XMm: {", g.XMm, "} ", + "YMm: {", g.YMm, "} ", + "Rad: {", g.Rad, "} ", + "MotionProf: {", g.MotionProf, "} ", + "Level: {", g.Level, "}") +} + +// MESSAGE DriveStraight +type DriveStraight struct { + SpeedMmps float32 + DistMm float32 + ShouldPlayAnimation bool +} + +func (d *DriveStraight) Size() uint32 { + var result uint32 + result += 4 // SpeedMmps float_32 + result += 4 // DistMm float_32 + result += 1 // ShouldPlayAnimation bool + return result +} + +func (d *DriveStraight) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &d.SpeedMmps); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.DistMm); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.ShouldPlayAnimation); err != nil { + return err + } + return nil +} + +func (d *DriveStraight) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, d.SpeedMmps); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.DistMm); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.ShouldPlayAnimation); err != nil { + return err + } + return nil +} + +func (d *DriveStraight) String() string { + return fmt.Sprint("SpeedMmps: {", d.SpeedMmps, "} ", + "DistMm: {", d.DistMm, "} ", + "ShouldPlayAnimation: {", d.ShouldPlayAnimation, "}") +} + +// MESSAGE TurnInPlace +type TurnInPlace struct { + AngleRad float32 + SpeedRadPerSec float32 + AccelRadPerSec2 float32 + TolRad float32 + IsAbsolute uint8 +} + +func (t *TurnInPlace) Size() uint32 { + var result uint32 + result += 4 // AngleRad float_32 + result += 4 // SpeedRadPerSec float_32 + result += 4 // AccelRadPerSec2 float_32 + result += 4 // TolRad float_32 + result += 1 // IsAbsolute uint_8 + return result +} + +func (t *TurnInPlace) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &t.AngleRad); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &t.SpeedRadPerSec); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &t.AccelRadPerSec2); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &t.TolRad); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &t.IsAbsolute); err != nil { + return err + } + return nil +} + +func (t *TurnInPlace) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, t.AngleRad); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, t.SpeedRadPerSec); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, t.AccelRadPerSec2); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, t.TolRad); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, t.IsAbsolute); err != nil { + return err + } + return nil +} + +func (t *TurnInPlace) String() string { + return fmt.Sprint("AngleRad: {", t.AngleRad, "} ", + "SpeedRadPerSec: {", t.SpeedRadPerSec, "} ", + "AccelRadPerSec2: {", t.AccelRadPerSec2, "} ", + "TolRad: {", t.TolRad, "} ", + "IsAbsolute: {", t.IsAbsolute, "}") +} + +// MESSAGE SetHeadAngle +type SetHeadAngle struct { + AngleRad float32 + MaxSpeedRadPerSec float32 + AccelRadPerSec2 float32 + DurationSec float32 +} + +func (s *SetHeadAngle) Size() uint32 { + var result uint32 + result += 4 // AngleRad float_32 + result += 4 // MaxSpeedRadPerSec float_32 + result += 4 // AccelRadPerSec2 float_32 + result += 4 // DurationSec float_32 + return result +} + +func (s *SetHeadAngle) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &s.AngleRad); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &s.MaxSpeedRadPerSec); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &s.AccelRadPerSec2); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &s.DurationSec); err != nil { + return err + } + return nil +} + +func (s *SetHeadAngle) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, s.AngleRad); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, s.MaxSpeedRadPerSec); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, s.AccelRadPerSec2); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, s.DurationSec); err != nil { + return err + } + return nil +} + +func (s *SetHeadAngle) String() string { + return fmt.Sprint("AngleRad: {", s.AngleRad, "} ", + "MaxSpeedRadPerSec: {", s.MaxSpeedRadPerSec, "} ", + "AccelRadPerSec2: {", s.AccelRadPerSec2, "} ", + "DurationSec: {", s.DurationSec, "}") +} + +// MESSAGE SetLiftHeight +type SetLiftHeight struct { + HeightMm float32 + MaxSpeedRadPerSec float32 + AccelRadPerSec2 float32 + DurationSec float32 +} + +func (s *SetLiftHeight) Size() uint32 { + var result uint32 + result += 4 // HeightMm float_32 + result += 4 // MaxSpeedRadPerSec float_32 + result += 4 // AccelRadPerSec2 float_32 + result += 4 // DurationSec float_32 + return result +} + +func (s *SetLiftHeight) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &s.HeightMm); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &s.MaxSpeedRadPerSec); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &s.AccelRadPerSec2); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &s.DurationSec); err != nil { + return err + } + return nil +} + +func (s *SetLiftHeight) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, s.HeightMm); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, s.MaxSpeedRadPerSec); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, s.AccelRadPerSec2); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, s.DurationSec); err != nil { + return err + } + return nil +} + +func (s *SetLiftHeight) String() string { + return fmt.Sprint("HeightMm: {", s.HeightMm, "} ", + "MaxSpeedRadPerSec: {", s.MaxSpeedRadPerSec, "} ", + "AccelRadPerSec2: {", s.AccelRadPerSec2, "} ", + "DurationSec: {", s.DurationSec, "}") +} + +// ENUM AlignmentType +type AlignmentType uint8 + +const ( + AlignmentType_LIFT_FINGER AlignmentType = AlignmentType(0) + AlignmentType_LIFT_PLATE AlignmentType = AlignmentType_LIFT_FINGER + 1 + AlignmentType_BODY AlignmentType = AlignmentType_LIFT_PLATE + 1 + AlignmentType_CUSTOM AlignmentType = AlignmentType_BODY + 1 +) + +// MESSAGE AlignWithObject +type AlignWithObject struct { + ObjectID int32 + MotionProf PathMotionProfile + DistanceFromMarkerMm float32 + ApproachAngleRad float32 + UseApproachAngle bool + UsePreDockPose bool + AlignmentType AlignmentType +} + +func (a *AlignWithObject) Size() uint32 { + var result uint32 + result += 4 // ObjectID int_32 + result += a.MotionProf.Size() + result += 4 // DistanceFromMarkerMm float_32 + result += 4 // ApproachAngleRad float_32 + result += 1 // UseApproachAngle bool + result += 1 // UsePreDockPose bool + result += 1 // AlignmentType AlignmentType + return result +} + +func (a *AlignWithObject) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &a.ObjectID); err != nil { + return err + } + if err := a.MotionProf.Unpack(buf); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &a.DistanceFromMarkerMm); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &a.ApproachAngleRad); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &a.UseApproachAngle); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &a.UsePreDockPose); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &a.AlignmentType); err != nil { + return err + } + return nil +} + +func (a *AlignWithObject) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, a.ObjectID); err != nil { + return err + } + if err := a.MotionProf.Pack(buf); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, a.DistanceFromMarkerMm); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, a.ApproachAngleRad); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, a.UseApproachAngle); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, a.UsePreDockPose); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, a.AlignmentType); err != nil { + return err + } + return nil +} + +func (a *AlignWithObject) String() string { + return fmt.Sprint("ObjectID: {", a.ObjectID, "} ", + "MotionProf: {", a.MotionProf, "} ", + "DistanceFromMarkerMm: {", a.DistanceFromMarkerMm, "} ", + "ApproachAngleRad: {", a.ApproachAngleRad, "} ", + "UseApproachAngle: {", a.UseApproachAngle, "} ", + "UsePreDockPose: {", a.UsePreDockPose, "} ", + "AlignmentType: {", a.AlignmentType, "}") +} + +// MESSAGE SetLiftAngle +type SetLiftAngle struct { + AngleRad float32 + MaxSpeedRadPerSec float32 + AccelRadPerSec2 float32 + DurationSec float32 +} + +func (s *SetLiftAngle) Size() uint32 { + var result uint32 + result += 4 // AngleRad float_32 + result += 4 // MaxSpeedRadPerSec float_32 + result += 4 // AccelRadPerSec2 float_32 + result += 4 // DurationSec float_32 + return result +} + +func (s *SetLiftAngle) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &s.AngleRad); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &s.MaxSpeedRadPerSec); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &s.AccelRadPerSec2); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &s.DurationSec); err != nil { + return err + } + return nil +} + +func (s *SetLiftAngle) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, s.AngleRad); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, s.MaxSpeedRadPerSec); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, s.AccelRadPerSec2); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, s.DurationSec); err != nil { + return err + } + return nil +} + +func (s *SetLiftAngle) String() string { + return fmt.Sprint("AngleRad: {", s.AngleRad, "} ", + "MaxSpeedRadPerSec: {", s.MaxSpeedRadPerSec, "} ", + "AccelRadPerSec2: {", s.AccelRadPerSec2, "} ", + "DurationSec: {", s.DurationSec, "}") +} + +// MESSAGE DeleteCustomMarkerObjects +type DeleteCustomMarkerObjects struct { +} + +func (d *DeleteCustomMarkerObjects) Size() uint32 { + return 0 +} + +func (d *DeleteCustomMarkerObjects) Unpack(buf *bytes.Buffer) error { + return nil +} + +func (d *DeleteCustomMarkerObjects) Pack(buf *bytes.Buffer) error { + return nil +} + +func (d *DeleteCustomMarkerObjects) String() string { + return "" +} + +// MESSAGE DeleteFixedCustomObjects +type DeleteFixedCustomObjects struct { +} + +func (d *DeleteFixedCustomObjects) Size() uint32 { + return 0 +} + +func (d *DeleteFixedCustomObjects) Unpack(buf *bytes.Buffer) error { + return nil +} + +func (d *DeleteFixedCustomObjects) Pack(buf *bytes.Buffer) error { + return nil +} + +func (d *DeleteFixedCustomObjects) String() string { + return "" +} + +// MESSAGE UndefineAllCustomMarkerObjects +type UndefineAllCustomMarkerObjects struct { +} + +func (u *UndefineAllCustomMarkerObjects) Size() uint32 { + return 0 +} + +func (u *UndefineAllCustomMarkerObjects) Unpack(buf *bytes.Buffer) error { + return nil +} + +func (u *UndefineAllCustomMarkerObjects) Pack(buf *bytes.Buffer) error { + return nil +} + +func (u *UndefineAllCustomMarkerObjects) String() string { + return "" +} + +// MESSAGE CreateFixedCustomObject +type CreateFixedCustomObject struct { + Pose PoseStruct3d + XSizeMm float32 + YSizeMm float32 + ZSizeMm float32 +} + +func (c *CreateFixedCustomObject) Size() uint32 { + var result uint32 + result += c.Pose.Size() + result += 4 // XSizeMm float_32 + result += 4 // YSizeMm float_32 + result += 4 // ZSizeMm float_32 + return result +} + +func (c *CreateFixedCustomObject) Unpack(buf *bytes.Buffer) error { + if err := c.Pose.Unpack(buf); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &c.XSizeMm); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &c.YSizeMm); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &c.ZSizeMm); err != nil { + return err + } + return nil +} + +func (c *CreateFixedCustomObject) Pack(buf *bytes.Buffer) error { + if err := c.Pose.Pack(buf); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, c.XSizeMm); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, c.YSizeMm); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, c.ZSizeMm); err != nil { + return err + } + return nil +} + +func (c *CreateFixedCustomObject) String() string { + return fmt.Sprint("Pose: {", c.Pose, "} ", + "XSizeMm: {", c.XSizeMm, "} ", + "YSizeMm: {", c.YSizeMm, "} ", + "ZSizeMm: {", c.ZSizeMm, "}") +} + +// ENUM CustomObjectMarker +type CustomObjectMarker int16 + +const ( + CustomObjectMarker_Circles2 CustomObjectMarker = iota + CustomObjectMarker_Circles3 + CustomObjectMarker_Circles4 + CustomObjectMarker_Circles5 + CustomObjectMarker_Diamonds2 + CustomObjectMarker_Diamonds3 + CustomObjectMarker_Diamonds4 + CustomObjectMarker_Diamonds5 + CustomObjectMarker_Hexagons2 + CustomObjectMarker_Hexagons3 + CustomObjectMarker_Hexagons4 + CustomObjectMarker_Hexagons5 + CustomObjectMarker_Triangles2 + CustomObjectMarker_Triangles3 + CustomObjectMarker_Triangles4 + CustomObjectMarker_Triangles5 + CustomObjectMarker_Count +) + +// MESSAGE DefineCustomBox +type DefineCustomBox struct { + CustomType ObjectType + MarkerFront CustomObjectMarker + MarkerBack CustomObjectMarker + MarkerTop CustomObjectMarker + MarkerBottom CustomObjectMarker + MarkerLeft CustomObjectMarker + MarkerRight CustomObjectMarker + XSizeMm float32 + YSizeMm float32 + ZSizeMm float32 + MarkerWidthMm float32 + MarkerHeightMm float32 + IsUnique bool +} + +func (d *DefineCustomBox) Size() uint32 { + var result uint32 + result += 4 // CustomType ObjectType + result += 2 // MarkerFront CustomObjectMarker + result += 2 // MarkerBack CustomObjectMarker + result += 2 // MarkerTop CustomObjectMarker + result += 2 // MarkerBottom CustomObjectMarker + result += 2 // MarkerLeft CustomObjectMarker + result += 2 // MarkerRight CustomObjectMarker + result += 4 // XSizeMm float_32 + result += 4 // YSizeMm float_32 + result += 4 // ZSizeMm float_32 + result += 4 // MarkerWidthMm float_32 + result += 4 // MarkerHeightMm float_32 + result += 1 // IsUnique bool + return result +} + +func (d *DefineCustomBox) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &d.CustomType); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.MarkerFront); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.MarkerBack); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.MarkerTop); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.MarkerBottom); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.MarkerLeft); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.MarkerRight); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.XSizeMm); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.YSizeMm); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.ZSizeMm); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.MarkerWidthMm); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.MarkerHeightMm); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.IsUnique); err != nil { + return err + } + return nil +} + +func (d *DefineCustomBox) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, d.CustomType); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.MarkerFront); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.MarkerBack); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.MarkerTop); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.MarkerBottom); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.MarkerLeft); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.MarkerRight); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.XSizeMm); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.YSizeMm); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.ZSizeMm); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.MarkerWidthMm); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.MarkerHeightMm); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.IsUnique); err != nil { + return err + } + return nil +} + +func (d *DefineCustomBox) String() string { + return fmt.Sprint("CustomType: {", d.CustomType, "} ", + "MarkerFront: {", d.MarkerFront, "} ", + "MarkerBack: {", d.MarkerBack, "} ", + "MarkerTop: {", d.MarkerTop, "} ", + "MarkerBottom: {", d.MarkerBottom, "} ", + "MarkerLeft: {", d.MarkerLeft, "} ", + "MarkerRight: {", d.MarkerRight, "} ", + "XSizeMm: {", d.XSizeMm, "} ", + "YSizeMm: {", d.YSizeMm, "} ", + "ZSizeMm: {", d.ZSizeMm, "} ", + "MarkerWidthMm: {", d.MarkerWidthMm, "} ", + "MarkerHeightMm: {", d.MarkerHeightMm, "} ", + "IsUnique: {", d.IsUnique, "}") +} + +// MESSAGE DefineCustomCube +type DefineCustomCube struct { + CustomType ObjectType + Marker CustomObjectMarker + SizeMm float32 + MarkerWidthMm float32 + MarkerHeightMm float32 + IsUnique bool +} + +func (d *DefineCustomCube) Size() uint32 { + var result uint32 + result += 4 // CustomType ObjectType + result += 2 // Marker CustomObjectMarker + result += 4 // SizeMm float_32 + result += 4 // MarkerWidthMm float_32 + result += 4 // MarkerHeightMm float_32 + result += 1 // IsUnique bool + return result +} + +func (d *DefineCustomCube) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &d.CustomType); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.Marker); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.SizeMm); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.MarkerWidthMm); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.MarkerHeightMm); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.IsUnique); err != nil { + return err + } + return nil +} + +func (d *DefineCustomCube) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, d.CustomType); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.Marker); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.SizeMm); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.MarkerWidthMm); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.MarkerHeightMm); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.IsUnique); err != nil { + return err + } + return nil +} + +func (d *DefineCustomCube) String() string { + return fmt.Sprint("CustomType: {", d.CustomType, "} ", + "Marker: {", d.Marker, "} ", + "SizeMm: {", d.SizeMm, "} ", + "MarkerWidthMm: {", d.MarkerWidthMm, "} ", + "MarkerHeightMm: {", d.MarkerHeightMm, "} ", + "IsUnique: {", d.IsUnique, "}") +} + +// MESSAGE DefineCustomWall +type DefineCustomWall struct { + CustomType ObjectType + Marker CustomObjectMarker + WidthMm float32 + HeightMm float32 + MarkerWidthMm float32 + MarkerHeightMm float32 + IsUnique bool +} + +func (d *DefineCustomWall) Size() uint32 { + var result uint32 + result += 4 // CustomType ObjectType + result += 2 // Marker CustomObjectMarker + result += 4 // WidthMm float_32 + result += 4 // HeightMm float_32 + result += 4 // MarkerWidthMm float_32 + result += 4 // MarkerHeightMm float_32 + result += 1 // IsUnique bool + return result +} + +func (d *DefineCustomWall) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &d.CustomType); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.Marker); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.WidthMm); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.HeightMm); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.MarkerWidthMm); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.MarkerHeightMm); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &d.IsUnique); err != nil { + return err + } + return nil +} + +func (d *DefineCustomWall) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, d.CustomType); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.Marker); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.WidthMm); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.HeightMm); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.MarkerWidthMm); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.MarkerHeightMm); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, d.IsUnique); err != nil { + return err + } + return nil +} + +func (d *DefineCustomWall) String() string { + return fmt.Sprint("CustomType: {", d.CustomType, "} ", + "Marker: {", d.Marker, "} ", + "WidthMm: {", d.WidthMm, "} ", + "HeightMm: {", d.HeightMm, "} ", + "MarkerWidthMm: {", d.MarkerWidthMm, "} ", + "MarkerHeightMm: {", d.MarkerHeightMm, "} ", + "IsUnique: {", d.IsUnique, "}") +} + +// MESSAGE SetMemoryMapBroadcastFrequency_sec +type SetMemoryMapBroadcastFrequencySec = SetMemoryMapBroadcastFrequency_sec + +type SetMemoryMapBroadcastFrequency_sec struct { + Frequency float32 +} + +func (s *SetMemoryMapBroadcastFrequency_sec) Size() uint32 { + var result uint32 + result += 4 // Frequency float_32 + return result +} + +func (s *SetMemoryMapBroadcastFrequency_sec) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &s.Frequency); err != nil { + return err + } + return nil +} + +func (s *SetMemoryMapBroadcastFrequency_sec) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, s.Frequency); err != nil { + return err + } + return nil +} + +func (s *SetMemoryMapBroadcastFrequency_sec) String() string { + return fmt.Sprint("Frequency: {", s.Frequency, "}") +} + +// UNION MessageExternalToRobot +type MessageExternalToRobotTag uint8 + +const ( + MessageExternalToRobotTag_UiDeviceConnectionWrongVersion MessageExternalToRobotTag = 0x0 // 0 + MessageExternalToRobotTag_MoveHead MessageExternalToRobotTag = 0x3 // 3 + MessageExternalToRobotTag_MoveLift MessageExternalToRobotTag = 0x4 // 4 + MessageExternalToRobotTag_DriveArc MessageExternalToRobotTag = 0x5 // 5 + MessageExternalToRobotTag_RobotHistoryRequest MessageExternalToRobotTag = 0x6 // 6 + MessageExternalToRobotTag_DisplayFaceImageRGBChunk MessageExternalToRobotTag = 0x8 // 8 + MessageExternalToRobotTag_AppIntent MessageExternalToRobotTag = 0x9 // 9 + MessageExternalToRobotTag_CancelFaceEnrollment MessageExternalToRobotTag = 0xa // 10 + MessageExternalToRobotTag_RequestEnrolledNames MessageExternalToRobotTag = 0xb // 11 + MessageExternalToRobotTag_UpdateEnrolledFaceByID MessageExternalToRobotTag = 0xc // 12 + MessageExternalToRobotTag_EraseEnrolledFaceByID MessageExternalToRobotTag = 0xd // 13 + MessageExternalToRobotTag_EraseAllEnrolledFaces MessageExternalToRobotTag = 0xe // 14 + MessageExternalToRobotTag_SetFaceToEnroll MessageExternalToRobotTag = 0xf // 15 + MessageExternalToRobotTag_EnableVisionMode MessageExternalToRobotTag = 0x10 // 16 + MessageExternalToRobotTag_GotoPose MessageExternalToRobotTag = 0x12 // 18 + MessageExternalToRobotTag_DriveStraight MessageExternalToRobotTag = 0x13 // 19 + MessageExternalToRobotTag_TurnInPlace MessageExternalToRobotTag = 0x14 // 20 + MessageExternalToRobotTag_SetHeadAngle MessageExternalToRobotTag = 0x15 // 21 + MessageExternalToRobotTag_SetLiftHeight MessageExternalToRobotTag = 0x16 // 22 + MessageExternalToRobotTag_AlignWithObject MessageExternalToRobotTag = 0x18 // 24 + MessageExternalToRobotTag_SetLiftAngle MessageExternalToRobotTag = 0x19 // 25 + MessageExternalToRobotTag_DeleteCustomMarkerObjects MessageExternalToRobotTag = 0x1a // 26 + MessageExternalToRobotTag_DeleteFixedCustomObjects MessageExternalToRobotTag = 0x1b // 27 + MessageExternalToRobotTag_UndefineAllCustomMarkerObjects MessageExternalToRobotTag = 0x1c // 28 + MessageExternalToRobotTag_CreateFixedCustomObject MessageExternalToRobotTag = 0x1d // 29 + MessageExternalToRobotTag_DefineCustomBox MessageExternalToRobotTag = 0x1e // 30 + MessageExternalToRobotTag_DefineCustomCube MessageExternalToRobotTag = 0x1f // 31 + MessageExternalToRobotTag_DefineCustomWall MessageExternalToRobotTag = 0x20 // 32 + MessageExternalToRobotTag_SetMemoryMapBroadcastFrequencySec MessageExternalToRobotTag = 0x21 // 33 + MessageExternalToRobotTag_INVALID MessageExternalToRobotTag = 255 +) + +type MessageExternalToRobot struct { + tag *MessageExternalToRobotTag + value clad.Struct +} + +func (m *MessageExternalToRobot) Tag() MessageExternalToRobotTag { + if m.tag == nil { + return MessageExternalToRobotTag_INVALID + } + return *m.tag +} + +func (m *MessageExternalToRobot) Size() uint32 { + if m.tag == nil || *m.tag == MessageExternalToRobotTag_INVALID { + return 1 + } + return 1 + m.value.Size() +} + +func (m *MessageExternalToRobot) Pack(buf *bytes.Buffer) error { + tag := MessageExternalToRobotTag_INVALID + if m.tag != nil { + tag = *m.tag + } + if err := binary.Write(buf, binary.LittleEndian, tag); err != nil { + return err + } + if tag == MessageExternalToRobotTag_INVALID { + return nil + } + return m.value.Pack(buf) +} + +func (m *MessageExternalToRobot) unpackStruct(tag MessageExternalToRobotTag, buf *bytes.Buffer) (clad.Struct, error) { + switch tag { + case MessageExternalToRobotTag_UiDeviceConnectionWrongVersion: + var ret UiDeviceConnectionWrongVersion + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageExternalToRobotTag_MoveHead: + var ret MoveHead + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageExternalToRobotTag_MoveLift: + var ret MoveLift + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageExternalToRobotTag_DriveArc: + var ret DriveArc + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageExternalToRobotTag_RobotHistoryRequest: + var ret RobotHistoryRequest + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageExternalToRobotTag_DisplayFaceImageRGBChunk: + var ret DisplayFaceImageRGBChunk + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageExternalToRobotTag_AppIntent: + var ret AppIntent + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageExternalToRobotTag_CancelFaceEnrollment: + var ret CancelFaceEnrollment + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageExternalToRobotTag_RequestEnrolledNames: + var ret RequestEnrolledNames + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageExternalToRobotTag_UpdateEnrolledFaceByID: + var ret UpdateEnrolledFaceByID + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageExternalToRobotTag_EraseEnrolledFaceByID: + var ret EraseEnrolledFaceByID + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageExternalToRobotTag_EraseAllEnrolledFaces: + var ret EraseAllEnrolledFaces + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageExternalToRobotTag_SetFaceToEnroll: + var ret SetFaceToEnroll + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageExternalToRobotTag_EnableVisionMode: + var ret EnableVisionMode + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageExternalToRobotTag_GotoPose: + var ret GotoPose + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageExternalToRobotTag_DriveStraight: + var ret DriveStraight + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageExternalToRobotTag_TurnInPlace: + var ret TurnInPlace + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageExternalToRobotTag_SetHeadAngle: + var ret SetHeadAngle + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageExternalToRobotTag_SetLiftHeight: + var ret SetLiftHeight + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageExternalToRobotTag_AlignWithObject: + var ret AlignWithObject + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageExternalToRobotTag_SetLiftAngle: + var ret SetLiftAngle + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageExternalToRobotTag_DeleteCustomMarkerObjects: + var ret DeleteCustomMarkerObjects + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageExternalToRobotTag_DeleteFixedCustomObjects: + var ret DeleteFixedCustomObjects + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageExternalToRobotTag_UndefineAllCustomMarkerObjects: + var ret UndefineAllCustomMarkerObjects + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageExternalToRobotTag_CreateFixedCustomObject: + var ret CreateFixedCustomObject + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageExternalToRobotTag_DefineCustomBox: + var ret DefineCustomBox + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageExternalToRobotTag_DefineCustomCube: + var ret DefineCustomCube + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageExternalToRobotTag_DefineCustomWall: + var ret DefineCustomWall + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageExternalToRobotTag_SetMemoryMapBroadcastFrequencySec: + var ret SetMemoryMapBroadcastFrequency_sec + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + default: + return nil, errors.New("invalid tag to unpackStruct") + } +} + +func (m *MessageExternalToRobot) Unpack(buf *bytes.Buffer) error { + tag := MessageExternalToRobotTag_INVALID + if err := binary.Read(buf, binary.LittleEndian, &tag); err != nil { + return err + } + m.tag = &tag + if tag == MessageExternalToRobotTag_INVALID { + m.value = nil + return nil + } + val, err := m.unpackStruct(tag, buf) + if err != nil { + *m.tag = MessageExternalToRobotTag_INVALID + return err + } + m.value = val + return nil +} + +func (t MessageExternalToRobotTag) String() string { + switch t { + case MessageExternalToRobotTag_UiDeviceConnectionWrongVersion: + return "UiDeviceConnectionWrongVersion" + case MessageExternalToRobotTag_MoveHead: + return "MoveHead" + case MessageExternalToRobotTag_MoveLift: + return "MoveLift" + case MessageExternalToRobotTag_DriveArc: + return "DriveArc" + case MessageExternalToRobotTag_RobotHistoryRequest: + return "RobotHistoryRequest" + case MessageExternalToRobotTag_DisplayFaceImageRGBChunk: + return "DisplayFaceImageRGBChunk" + case MessageExternalToRobotTag_AppIntent: + return "AppIntent" + case MessageExternalToRobotTag_CancelFaceEnrollment: + return "CancelFaceEnrollment" + case MessageExternalToRobotTag_RequestEnrolledNames: + return "RequestEnrolledNames" + case MessageExternalToRobotTag_UpdateEnrolledFaceByID: + return "UpdateEnrolledFaceByID" + case MessageExternalToRobotTag_EraseEnrolledFaceByID: + return "EraseEnrolledFaceByID" + case MessageExternalToRobotTag_EraseAllEnrolledFaces: + return "EraseAllEnrolledFaces" + case MessageExternalToRobotTag_SetFaceToEnroll: + return "SetFaceToEnroll" + case MessageExternalToRobotTag_EnableVisionMode: + return "EnableVisionMode" + case MessageExternalToRobotTag_GotoPose: + return "GotoPose" + case MessageExternalToRobotTag_DriveStraight: + return "DriveStraight" + case MessageExternalToRobotTag_TurnInPlace: + return "TurnInPlace" + case MessageExternalToRobotTag_SetHeadAngle: + return "SetHeadAngle" + case MessageExternalToRobotTag_SetLiftHeight: + return "SetLiftHeight" + case MessageExternalToRobotTag_AlignWithObject: + return "AlignWithObject" + case MessageExternalToRobotTag_SetLiftAngle: + return "SetLiftAngle" + case MessageExternalToRobotTag_DeleteCustomMarkerObjects: + return "DeleteCustomMarkerObjects" + case MessageExternalToRobotTag_DeleteFixedCustomObjects: + return "DeleteFixedCustomObjects" + case MessageExternalToRobotTag_UndefineAllCustomMarkerObjects: + return "UndefineAllCustomMarkerObjects" + case MessageExternalToRobotTag_CreateFixedCustomObject: + return "CreateFixedCustomObject" + case MessageExternalToRobotTag_DefineCustomBox: + return "DefineCustomBox" + case MessageExternalToRobotTag_DefineCustomCube: + return "DefineCustomCube" + case MessageExternalToRobotTag_DefineCustomWall: + return "DefineCustomWall" + case MessageExternalToRobotTag_SetMemoryMapBroadcastFrequencySec: + return "SetMemoryMapBroadcastFrequencySec" + default: + return "INVALID" + } +} + +func (m *MessageExternalToRobot) String() string { + if m.tag == nil { + return "nil" + } + if *m.tag == MessageExternalToRobotTag_INVALID { + return "INVALID" + } + return fmt.Sprintf("%s: {%s}", *m.tag, m.value) +} + +func (m *MessageExternalToRobot) GetUiDeviceConnectionWrongVersion() *UiDeviceConnectionWrongVersion { + if m.tag == nil || *m.tag != MessageExternalToRobotTag_UiDeviceConnectionWrongVersion { + return nil + } + return m.value.(*UiDeviceConnectionWrongVersion) +} + +func (m *MessageExternalToRobot) SetUiDeviceConnectionWrongVersion(value *UiDeviceConnectionWrongVersion) { + newTag := MessageExternalToRobotTag_UiDeviceConnectionWrongVersion + m.tag = &newTag + m.value = value +} + +func NewMessageExternalToRobotWithUiDeviceConnectionWrongVersion(value *UiDeviceConnectionWrongVersion) *MessageExternalToRobot { + var ret MessageExternalToRobot + ret.SetUiDeviceConnectionWrongVersion(value) + return &ret +} + +func (m *MessageExternalToRobot) GetMoveHead() *MoveHead { + if m.tag == nil || *m.tag != MessageExternalToRobotTag_MoveHead { + return nil + } + return m.value.(*MoveHead) +} + +func (m *MessageExternalToRobot) SetMoveHead(value *MoveHead) { + newTag := MessageExternalToRobotTag_MoveHead + m.tag = &newTag + m.value = value +} + +func NewMessageExternalToRobotWithMoveHead(value *MoveHead) *MessageExternalToRobot { + var ret MessageExternalToRobot + ret.SetMoveHead(value) + return &ret +} + +func (m *MessageExternalToRobot) GetMoveLift() *MoveLift { + if m.tag == nil || *m.tag != MessageExternalToRobotTag_MoveLift { + return nil + } + return m.value.(*MoveLift) +} + +func (m *MessageExternalToRobot) SetMoveLift(value *MoveLift) { + newTag := MessageExternalToRobotTag_MoveLift + m.tag = &newTag + m.value = value +} + +func NewMessageExternalToRobotWithMoveLift(value *MoveLift) *MessageExternalToRobot { + var ret MessageExternalToRobot + ret.SetMoveLift(value) + return &ret +} + +func (m *MessageExternalToRobot) GetDriveArc() *DriveArc { + if m.tag == nil || *m.tag != MessageExternalToRobotTag_DriveArc { + return nil + } + return m.value.(*DriveArc) +} + +func (m *MessageExternalToRobot) SetDriveArc(value *DriveArc) { + newTag := MessageExternalToRobotTag_DriveArc + m.tag = &newTag + m.value = value +} + +func NewMessageExternalToRobotWithDriveArc(value *DriveArc) *MessageExternalToRobot { + var ret MessageExternalToRobot + ret.SetDriveArc(value) + return &ret +} + +func (m *MessageExternalToRobot) GetRobotHistoryRequest() *RobotHistoryRequest { + if m.tag == nil || *m.tag != MessageExternalToRobotTag_RobotHistoryRequest { + return nil + } + return m.value.(*RobotHistoryRequest) +} + +func (m *MessageExternalToRobot) SetRobotHistoryRequest(value *RobotHistoryRequest) { + newTag := MessageExternalToRobotTag_RobotHistoryRequest + m.tag = &newTag + m.value = value +} + +func NewMessageExternalToRobotWithRobotHistoryRequest(value *RobotHistoryRequest) *MessageExternalToRobot { + var ret MessageExternalToRobot + ret.SetRobotHistoryRequest(value) + return &ret +} + +func (m *MessageExternalToRobot) GetDisplayFaceImageRGBChunk() *DisplayFaceImageRGBChunk { + if m.tag == nil || *m.tag != MessageExternalToRobotTag_DisplayFaceImageRGBChunk { + return nil + } + return m.value.(*DisplayFaceImageRGBChunk) +} + +func (m *MessageExternalToRobot) SetDisplayFaceImageRGBChunk(value *DisplayFaceImageRGBChunk) { + newTag := MessageExternalToRobotTag_DisplayFaceImageRGBChunk + m.tag = &newTag + m.value = value +} + +func NewMessageExternalToRobotWithDisplayFaceImageRGBChunk(value *DisplayFaceImageRGBChunk) *MessageExternalToRobot { + var ret MessageExternalToRobot + ret.SetDisplayFaceImageRGBChunk(value) + return &ret +} + +func (m *MessageExternalToRobot) GetAppIntent() *AppIntent { + if m.tag == nil || *m.tag != MessageExternalToRobotTag_AppIntent { + return nil + } + return m.value.(*AppIntent) +} + +func (m *MessageExternalToRobot) SetAppIntent(value *AppIntent) { + newTag := MessageExternalToRobotTag_AppIntent + m.tag = &newTag + m.value = value +} + +func NewMessageExternalToRobotWithAppIntent(value *AppIntent) *MessageExternalToRobot { + var ret MessageExternalToRobot + ret.SetAppIntent(value) + return &ret +} + +func (m *MessageExternalToRobot) GetCancelFaceEnrollment() *CancelFaceEnrollment { + if m.tag == nil || *m.tag != MessageExternalToRobotTag_CancelFaceEnrollment { + return nil + } + return m.value.(*CancelFaceEnrollment) +} + +func (m *MessageExternalToRobot) SetCancelFaceEnrollment(value *CancelFaceEnrollment) { + newTag := MessageExternalToRobotTag_CancelFaceEnrollment + m.tag = &newTag + m.value = value +} + +func NewMessageExternalToRobotWithCancelFaceEnrollment(value *CancelFaceEnrollment) *MessageExternalToRobot { + var ret MessageExternalToRobot + ret.SetCancelFaceEnrollment(value) + return &ret +} + +func (m *MessageExternalToRobot) GetRequestEnrolledNames() *RequestEnrolledNames { + if m.tag == nil || *m.tag != MessageExternalToRobotTag_RequestEnrolledNames { + return nil + } + return m.value.(*RequestEnrolledNames) +} + +func (m *MessageExternalToRobot) SetRequestEnrolledNames(value *RequestEnrolledNames) { + newTag := MessageExternalToRobotTag_RequestEnrolledNames + m.tag = &newTag + m.value = value +} + +func NewMessageExternalToRobotWithRequestEnrolledNames(value *RequestEnrolledNames) *MessageExternalToRobot { + var ret MessageExternalToRobot + ret.SetRequestEnrolledNames(value) + return &ret +} + +func (m *MessageExternalToRobot) GetUpdateEnrolledFaceByID() *UpdateEnrolledFaceByID { + if m.tag == nil || *m.tag != MessageExternalToRobotTag_UpdateEnrolledFaceByID { + return nil + } + return m.value.(*UpdateEnrolledFaceByID) +} + +func (m *MessageExternalToRobot) SetUpdateEnrolledFaceByID(value *UpdateEnrolledFaceByID) { + newTag := MessageExternalToRobotTag_UpdateEnrolledFaceByID + m.tag = &newTag + m.value = value +} + +func NewMessageExternalToRobotWithUpdateEnrolledFaceByID(value *UpdateEnrolledFaceByID) *MessageExternalToRobot { + var ret MessageExternalToRobot + ret.SetUpdateEnrolledFaceByID(value) + return &ret +} + +func (m *MessageExternalToRobot) GetEraseEnrolledFaceByID() *EraseEnrolledFaceByID { + if m.tag == nil || *m.tag != MessageExternalToRobotTag_EraseEnrolledFaceByID { + return nil + } + return m.value.(*EraseEnrolledFaceByID) +} + +func (m *MessageExternalToRobot) SetEraseEnrolledFaceByID(value *EraseEnrolledFaceByID) { + newTag := MessageExternalToRobotTag_EraseEnrolledFaceByID + m.tag = &newTag + m.value = value +} + +func NewMessageExternalToRobotWithEraseEnrolledFaceByID(value *EraseEnrolledFaceByID) *MessageExternalToRobot { + var ret MessageExternalToRobot + ret.SetEraseEnrolledFaceByID(value) + return &ret +} + +func (m *MessageExternalToRobot) GetEraseAllEnrolledFaces() *EraseAllEnrolledFaces { + if m.tag == nil || *m.tag != MessageExternalToRobotTag_EraseAllEnrolledFaces { + return nil + } + return m.value.(*EraseAllEnrolledFaces) +} + +func (m *MessageExternalToRobot) SetEraseAllEnrolledFaces(value *EraseAllEnrolledFaces) { + newTag := MessageExternalToRobotTag_EraseAllEnrolledFaces + m.tag = &newTag + m.value = value +} + +func NewMessageExternalToRobotWithEraseAllEnrolledFaces(value *EraseAllEnrolledFaces) *MessageExternalToRobot { + var ret MessageExternalToRobot + ret.SetEraseAllEnrolledFaces(value) + return &ret +} + +func (m *MessageExternalToRobot) GetSetFaceToEnroll() *SetFaceToEnroll { + if m.tag == nil || *m.tag != MessageExternalToRobotTag_SetFaceToEnroll { + return nil + } + return m.value.(*SetFaceToEnroll) +} + +func (m *MessageExternalToRobot) SetSetFaceToEnroll(value *SetFaceToEnroll) { + newTag := MessageExternalToRobotTag_SetFaceToEnroll + m.tag = &newTag + m.value = value +} + +func NewMessageExternalToRobotWithSetFaceToEnroll(value *SetFaceToEnroll) *MessageExternalToRobot { + var ret MessageExternalToRobot + ret.SetSetFaceToEnroll(value) + return &ret +} + +func (m *MessageExternalToRobot) GetEnableVisionMode() *EnableVisionMode { + if m.tag == nil || *m.tag != MessageExternalToRobotTag_EnableVisionMode { + return nil + } + return m.value.(*EnableVisionMode) +} + +func (m *MessageExternalToRobot) SetEnableVisionMode(value *EnableVisionMode) { + newTag := MessageExternalToRobotTag_EnableVisionMode + m.tag = &newTag + m.value = value +} + +func NewMessageExternalToRobotWithEnableVisionMode(value *EnableVisionMode) *MessageExternalToRobot { + var ret MessageExternalToRobot + ret.SetEnableVisionMode(value) + return &ret +} + +func (m *MessageExternalToRobot) GetGotoPose() *GotoPose { + if m.tag == nil || *m.tag != MessageExternalToRobotTag_GotoPose { + return nil + } + return m.value.(*GotoPose) +} + +func (m *MessageExternalToRobot) SetGotoPose(value *GotoPose) { + newTag := MessageExternalToRobotTag_GotoPose + m.tag = &newTag + m.value = value +} + +func NewMessageExternalToRobotWithGotoPose(value *GotoPose) *MessageExternalToRobot { + var ret MessageExternalToRobot + ret.SetGotoPose(value) + return &ret +} + +func (m *MessageExternalToRobot) GetDriveStraight() *DriveStraight { + if m.tag == nil || *m.tag != MessageExternalToRobotTag_DriveStraight { + return nil + } + return m.value.(*DriveStraight) +} + +func (m *MessageExternalToRobot) SetDriveStraight(value *DriveStraight) { + newTag := MessageExternalToRobotTag_DriveStraight + m.tag = &newTag + m.value = value +} + +func NewMessageExternalToRobotWithDriveStraight(value *DriveStraight) *MessageExternalToRobot { + var ret MessageExternalToRobot + ret.SetDriveStraight(value) + return &ret +} + +func (m *MessageExternalToRobot) GetTurnInPlace() *TurnInPlace { + if m.tag == nil || *m.tag != MessageExternalToRobotTag_TurnInPlace { + return nil + } + return m.value.(*TurnInPlace) +} + +func (m *MessageExternalToRobot) SetTurnInPlace(value *TurnInPlace) { + newTag := MessageExternalToRobotTag_TurnInPlace + m.tag = &newTag + m.value = value +} + +func NewMessageExternalToRobotWithTurnInPlace(value *TurnInPlace) *MessageExternalToRobot { + var ret MessageExternalToRobot + ret.SetTurnInPlace(value) + return &ret +} + +func (m *MessageExternalToRobot) GetSetHeadAngle() *SetHeadAngle { + if m.tag == nil || *m.tag != MessageExternalToRobotTag_SetHeadAngle { + return nil + } + return m.value.(*SetHeadAngle) +} + +func (m *MessageExternalToRobot) SetSetHeadAngle(value *SetHeadAngle) { + newTag := MessageExternalToRobotTag_SetHeadAngle + m.tag = &newTag + m.value = value +} + +func NewMessageExternalToRobotWithSetHeadAngle(value *SetHeadAngle) *MessageExternalToRobot { + var ret MessageExternalToRobot + ret.SetSetHeadAngle(value) + return &ret +} + +func (m *MessageExternalToRobot) GetSetLiftHeight() *SetLiftHeight { + if m.tag == nil || *m.tag != MessageExternalToRobotTag_SetLiftHeight { + return nil + } + return m.value.(*SetLiftHeight) +} + +func (m *MessageExternalToRobot) SetSetLiftHeight(value *SetLiftHeight) { + newTag := MessageExternalToRobotTag_SetLiftHeight + m.tag = &newTag + m.value = value +} + +func NewMessageExternalToRobotWithSetLiftHeight(value *SetLiftHeight) *MessageExternalToRobot { + var ret MessageExternalToRobot + ret.SetSetLiftHeight(value) + return &ret +} + +func (m *MessageExternalToRobot) GetAlignWithObject() *AlignWithObject { + if m.tag == nil || *m.tag != MessageExternalToRobotTag_AlignWithObject { + return nil + } + return m.value.(*AlignWithObject) +} + +func (m *MessageExternalToRobot) SetAlignWithObject(value *AlignWithObject) { + newTag := MessageExternalToRobotTag_AlignWithObject + m.tag = &newTag + m.value = value +} + +func NewMessageExternalToRobotWithAlignWithObject(value *AlignWithObject) *MessageExternalToRobot { + var ret MessageExternalToRobot + ret.SetAlignWithObject(value) + return &ret +} + +func (m *MessageExternalToRobot) GetSetLiftAngle() *SetLiftAngle { + if m.tag == nil || *m.tag != MessageExternalToRobotTag_SetLiftAngle { + return nil + } + return m.value.(*SetLiftAngle) +} + +func (m *MessageExternalToRobot) SetSetLiftAngle(value *SetLiftAngle) { + newTag := MessageExternalToRobotTag_SetLiftAngle + m.tag = &newTag + m.value = value +} + +func NewMessageExternalToRobotWithSetLiftAngle(value *SetLiftAngle) *MessageExternalToRobot { + var ret MessageExternalToRobot + ret.SetSetLiftAngle(value) + return &ret +} + +func (m *MessageExternalToRobot) GetDeleteCustomMarkerObjects() *DeleteCustomMarkerObjects { + if m.tag == nil || *m.tag != MessageExternalToRobotTag_DeleteCustomMarkerObjects { + return nil + } + return m.value.(*DeleteCustomMarkerObjects) +} + +func (m *MessageExternalToRobot) SetDeleteCustomMarkerObjects(value *DeleteCustomMarkerObjects) { + newTag := MessageExternalToRobotTag_DeleteCustomMarkerObjects + m.tag = &newTag + m.value = value +} + +func NewMessageExternalToRobotWithDeleteCustomMarkerObjects(value *DeleteCustomMarkerObjects) *MessageExternalToRobot { + var ret MessageExternalToRobot + ret.SetDeleteCustomMarkerObjects(value) + return &ret +} + +func (m *MessageExternalToRobot) GetDeleteFixedCustomObjects() *DeleteFixedCustomObjects { + if m.tag == nil || *m.tag != MessageExternalToRobotTag_DeleteFixedCustomObjects { + return nil + } + return m.value.(*DeleteFixedCustomObjects) +} + +func (m *MessageExternalToRobot) SetDeleteFixedCustomObjects(value *DeleteFixedCustomObjects) { + newTag := MessageExternalToRobotTag_DeleteFixedCustomObjects + m.tag = &newTag + m.value = value +} + +func NewMessageExternalToRobotWithDeleteFixedCustomObjects(value *DeleteFixedCustomObjects) *MessageExternalToRobot { + var ret MessageExternalToRobot + ret.SetDeleteFixedCustomObjects(value) + return &ret +} + +func (m *MessageExternalToRobot) GetUndefineAllCustomMarkerObjects() *UndefineAllCustomMarkerObjects { + if m.tag == nil || *m.tag != MessageExternalToRobotTag_UndefineAllCustomMarkerObjects { + return nil + } + return m.value.(*UndefineAllCustomMarkerObjects) +} + +func (m *MessageExternalToRobot) SetUndefineAllCustomMarkerObjects(value *UndefineAllCustomMarkerObjects) { + newTag := MessageExternalToRobotTag_UndefineAllCustomMarkerObjects + m.tag = &newTag + m.value = value +} + +func NewMessageExternalToRobotWithUndefineAllCustomMarkerObjects(value *UndefineAllCustomMarkerObjects) *MessageExternalToRobot { + var ret MessageExternalToRobot + ret.SetUndefineAllCustomMarkerObjects(value) + return &ret +} + +func (m *MessageExternalToRobot) GetCreateFixedCustomObject() *CreateFixedCustomObject { + if m.tag == nil || *m.tag != MessageExternalToRobotTag_CreateFixedCustomObject { + return nil + } + return m.value.(*CreateFixedCustomObject) +} + +func (m *MessageExternalToRobot) SetCreateFixedCustomObject(value *CreateFixedCustomObject) { + newTag := MessageExternalToRobotTag_CreateFixedCustomObject + m.tag = &newTag + m.value = value +} + +func NewMessageExternalToRobotWithCreateFixedCustomObject(value *CreateFixedCustomObject) *MessageExternalToRobot { + var ret MessageExternalToRobot + ret.SetCreateFixedCustomObject(value) + return &ret +} + +func (m *MessageExternalToRobot) GetDefineCustomBox() *DefineCustomBox { + if m.tag == nil || *m.tag != MessageExternalToRobotTag_DefineCustomBox { + return nil + } + return m.value.(*DefineCustomBox) +} + +func (m *MessageExternalToRobot) SetDefineCustomBox(value *DefineCustomBox) { + newTag := MessageExternalToRobotTag_DefineCustomBox + m.tag = &newTag + m.value = value +} + +func NewMessageExternalToRobotWithDefineCustomBox(value *DefineCustomBox) *MessageExternalToRobot { + var ret MessageExternalToRobot + ret.SetDefineCustomBox(value) + return &ret +} + +func (m *MessageExternalToRobot) GetDefineCustomCube() *DefineCustomCube { + if m.tag == nil || *m.tag != MessageExternalToRobotTag_DefineCustomCube { + return nil + } + return m.value.(*DefineCustomCube) +} + +func (m *MessageExternalToRobot) SetDefineCustomCube(value *DefineCustomCube) { + newTag := MessageExternalToRobotTag_DefineCustomCube + m.tag = &newTag + m.value = value +} + +func NewMessageExternalToRobotWithDefineCustomCube(value *DefineCustomCube) *MessageExternalToRobot { + var ret MessageExternalToRobot + ret.SetDefineCustomCube(value) + return &ret +} + +func (m *MessageExternalToRobot) GetDefineCustomWall() *DefineCustomWall { + if m.tag == nil || *m.tag != MessageExternalToRobotTag_DefineCustomWall { + return nil + } + return m.value.(*DefineCustomWall) +} + +func (m *MessageExternalToRobot) SetDefineCustomWall(value *DefineCustomWall) { + newTag := MessageExternalToRobotTag_DefineCustomWall + m.tag = &newTag + m.value = value +} + +func NewMessageExternalToRobotWithDefineCustomWall(value *DefineCustomWall) *MessageExternalToRobot { + var ret MessageExternalToRobot + ret.SetDefineCustomWall(value) + return &ret +} + +func (m *MessageExternalToRobot) GetSetMemoryMapBroadcastFrequencySec() *SetMemoryMapBroadcastFrequency_sec { + if m.tag == nil || *m.tag != MessageExternalToRobotTag_SetMemoryMapBroadcastFrequencySec { + return nil + } + return m.value.(*SetMemoryMapBroadcastFrequency_sec) +} + +func (m *MessageExternalToRobot) SetSetMemoryMapBroadcastFrequencySec(value *SetMemoryMapBroadcastFrequency_sec) { + newTag := MessageExternalToRobotTag_SetMemoryMapBroadcastFrequencySec + m.tag = &newTag + m.value = value +} + +func NewMessageExternalToRobotWithSetMemoryMapBroadcastFrequencySec(value *SetMemoryMapBroadcastFrequency_sec) *MessageExternalToRobot { + var ret MessageExternalToRobot + ret.SetSetMemoryMapBroadcastFrequencySec(value) + return &ret +} diff --git a/vector-cloud/internal/clad/gateway/messageRobotToExternal.go b/vector-cloud/internal/clad/gateway/messageRobotToExternal.go new file mode 100644 index 0000000..7a03843 --- /dev/null +++ b/vector-cloud/internal/clad/gateway/messageRobotToExternal.go @@ -0,0 +1,2649 @@ +// Autogenerated Go message buffer code. +// Source: clad/gateway/messageRobotToExternal.clad +// Full command line: victor-clad/tools/message-buffers/emitters/Go_emitter.py -C src -o generated/cladgo/src clad/gateway/messageRobotToExternal.clad + +package gateway + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + + "github.com/digital-dream-labs/vector-cloud/internal/clad" +) + +// MESSAGE UiDeviceConnected +type UiDeviceConnected struct { + Placeholder string +} + +func (u *UiDeviceConnected) Size() uint32 { + var result uint32 + result += 1 // Placeholder length (uint_8) + result += uint32(len(u.Placeholder)) // uint_8 array + return result +} + +func (u *UiDeviceConnected) Unpack(buf *bytes.Buffer) error { + var PlaceholderLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &PlaceholderLen); err != nil { + return err + } + u.Placeholder = string(buf.Next(int(PlaceholderLen))) + if len(u.Placeholder) != int(PlaceholderLen) { + return errors.New("string byte mismatch") + } + return nil +} + +func (u *UiDeviceConnected) Pack(buf *bytes.Buffer) error { + if len(u.Placeholder) > 255 { + return errors.New("max_length overflow in field Placeholder") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(u.Placeholder))); err != nil { + return err + } + if _, err := buf.WriteString(u.Placeholder); err != nil { + return err + } + return nil +} + +func (u *UiDeviceConnected) String() string { + return fmt.Sprint("Placeholder: {", u.Placeholder, "}") +} + +// STRUCTURE AnimationCompleted +type AnimationCompleted struct { + AnimationName string +} + +func (a *AnimationCompleted) Size() uint32 { + var result uint32 + result += 1 // AnimationName length (uint_8) + result += uint32(len(a.AnimationName)) // uint_8 array + return result +} + +func (a *AnimationCompleted) Unpack(buf *bytes.Buffer) error { + var AnimationNameLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &AnimationNameLen); err != nil { + return err + } + a.AnimationName = string(buf.Next(int(AnimationNameLen))) + if len(a.AnimationName) != int(AnimationNameLen) { + return errors.New("string byte mismatch") + } + return nil +} + +func (a *AnimationCompleted) Pack(buf *bytes.Buffer) error { + if len(a.AnimationName) > 255 { + return errors.New("max_length overflow in field AnimationName") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(a.AnimationName))); err != nil { + return err + } + if _, err := buf.WriteString(a.AnimationName); err != nil { + return err + } + return nil +} + +func (a *AnimationCompleted) String() string { + return fmt.Sprint("AnimationName: {", a.AnimationName, "}") +} + +// UNION ActionCompletedUnion +type ActionCompletedUnionTag uint8 + +const ( + ActionCompletedUnionTag_AnimationCompleted ActionCompletedUnionTag = 0x1 // 1 + ActionCompletedUnionTag_INVALID ActionCompletedUnionTag = 255 +) + +type ActionCompletedUnion struct { + tag *ActionCompletedUnionTag + value clad.Struct +} + +func (m *ActionCompletedUnion) Tag() ActionCompletedUnionTag { + if m.tag == nil { + return ActionCompletedUnionTag_INVALID + } + return *m.tag +} + +func (m *ActionCompletedUnion) Size() uint32 { + if m.tag == nil || *m.tag == ActionCompletedUnionTag_INVALID { + return 1 + } + return 1 + m.value.Size() +} + +func (m *ActionCompletedUnion) Pack(buf *bytes.Buffer) error { + tag := ActionCompletedUnionTag_INVALID + if m.tag != nil { + tag = *m.tag + } + if err := binary.Write(buf, binary.LittleEndian, tag); err != nil { + return err + } + if tag == ActionCompletedUnionTag_INVALID { + return nil + } + return m.value.Pack(buf) +} + +func (m *ActionCompletedUnion) unpackStruct(tag ActionCompletedUnionTag, buf *bytes.Buffer) (clad.Struct, error) { + switch tag { + case ActionCompletedUnionTag_AnimationCompleted: + var ret AnimationCompleted + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + default: + return nil, errors.New("invalid tag to unpackStruct") + } +} + +func (m *ActionCompletedUnion) Unpack(buf *bytes.Buffer) error { + tag := ActionCompletedUnionTag_INVALID + if err := binary.Read(buf, binary.LittleEndian, &tag); err != nil { + return err + } + m.tag = &tag + if tag == ActionCompletedUnionTag_INVALID { + m.value = nil + return nil + } + val, err := m.unpackStruct(tag, buf) + if err != nil { + *m.tag = ActionCompletedUnionTag_INVALID + return err + } + m.value = val + return nil +} + +func (t ActionCompletedUnionTag) String() string { + switch t { + case ActionCompletedUnionTag_AnimationCompleted: + return "AnimationCompleted" + default: + return "INVALID" + } +} + +func (m *ActionCompletedUnion) String() string { + if m.tag == nil { + return "nil" + } + if *m.tag == ActionCompletedUnionTag_INVALID { + return "INVALID" + } + return fmt.Sprintf("%s: {%s}", *m.tag, m.value) +} + +func (m *ActionCompletedUnion) GetAnimationCompleted() *AnimationCompleted { + if m.tag == nil || *m.tag != ActionCompletedUnionTag_AnimationCompleted { + return nil + } + return m.value.(*AnimationCompleted) +} + +func (m *ActionCompletedUnion) SetAnimationCompleted(value *AnimationCompleted) { + newTag := ActionCompletedUnionTag_AnimationCompleted + m.tag = &newTag + m.value = value +} + +func NewActionCompletedUnionWithAnimationCompleted(value *AnimationCompleted) *ActionCompletedUnion { + var ret ActionCompletedUnion + ret.SetAnimationCompleted(value) + return &ret +} + +// ENUM RobotActionType +type RobotActionType int32 + +const ( + RobotActionType_UNKNOWN RobotActionType = RobotActionType(-1) +) + +// ENUM ActionResult +type ActionResult uint32 + +const ( + ActionResult_SUCCESS ActionResult = ActionResult(0) + ActionResult_RUNNING ActionResult = ActionResult(16777216) + ActionResult_CANCELLED_WHILE_RUNNING ActionResult = ActionResult(33554432) +) + +// ENUM ObjectFamily +type ObjectFamily int32 + +const ( + ObjectFamily_Invalid ObjectFamily = ObjectFamily(-1) + ObjectFamily_Unknown ObjectFamily = ObjectFamily(0) + ObjectFamily_Block ObjectFamily = ObjectFamily(1) + ObjectFamily_LightCube ObjectFamily = ObjectFamily(2) + ObjectFamily_Charger ObjectFamily = ObjectFamily(3) + ObjectFamily_CustomObject ObjectFamily = ObjectFamily(6) +) + +// STRUCTURE ActiveAccel +type ActiveAccel struct { + X float32 + Y float32 + Z float32 +} + +func (a *ActiveAccel) Size() uint32 { + var result uint32 + result += 4 // X float_32 + result += 4 // Y float_32 + result += 4 // Z float_32 + return result +} + +func (a *ActiveAccel) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &a.X); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &a.Y); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &a.Z); err != nil { + return err + } + return nil +} + +func (a *ActiveAccel) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, a.X); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, a.Y); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, a.Z); err != nil { + return err + } + return nil +} + +func (a *ActiveAccel) String() string { + return fmt.Sprint("X: {", a.X, "} ", + "Y: {", a.Y, "} ", + "Z: {", a.Z, "}") +} + +// ENUM UpAxis +type UpAxis uint8 + +const ( + UpAxis_XNegative UpAxis = iota + UpAxis_XPositive + UpAxis_YNegative + UpAxis_YPositive + UpAxis_ZNegative + UpAxis_ZPositive + UpAxis_NumAxes + UpAxis_UnknownAxis +) + +// MESSAGE ObjectConnectionState +type ObjectConnectionState struct { + ObjectID uint32 + FactoryID string + ObjectType ObjectType + Connected bool +} + +func (o *ObjectConnectionState) Size() uint32 { + var result uint32 + result += 4 // ObjectID uint_32 + result += 1 // FactoryID length (uint_8) + result += uint32(len(o.FactoryID)) // uint_8 array + result += 4 // ObjectType ObjectType + result += 1 // Connected bool + return result +} + +func (o *ObjectConnectionState) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &o.ObjectID); err != nil { + return err + } + var FactoryIDLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &FactoryIDLen); err != nil { + return err + } + o.FactoryID = string(buf.Next(int(FactoryIDLen))) + if len(o.FactoryID) != int(FactoryIDLen) { + return errors.New("string byte mismatch") + } + if err := binary.Read(buf, binary.LittleEndian, &o.ObjectType); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &o.Connected); err != nil { + return err + } + return nil +} + +func (o *ObjectConnectionState) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, o.ObjectID); err != nil { + return err + } + if len(o.FactoryID) > 255 { + return errors.New("max_length overflow in field FactoryID") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(o.FactoryID))); err != nil { + return err + } + if _, err := buf.WriteString(o.FactoryID); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, o.ObjectType); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, o.Connected); err != nil { + return err + } + return nil +} + +func (o *ObjectConnectionState) String() string { + return fmt.Sprint("ObjectID: {", o.ObjectID, "} ", + "FactoryID: {", o.FactoryID, "} ", + "ObjectType: {", o.ObjectType, "} ", + "Connected: {", o.Connected, "}") +} + +// MESSAGE ObjectMoved +type ObjectMoved struct { + Timestamp uint32 + ObjectID uint32 +} + +func (o *ObjectMoved) Size() uint32 { + var result uint32 + result += 4 // Timestamp uint_32 + result += 4 // ObjectID uint_32 + return result +} + +func (o *ObjectMoved) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &o.Timestamp); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &o.ObjectID); err != nil { + return err + } + return nil +} + +func (o *ObjectMoved) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, o.Timestamp); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, o.ObjectID); err != nil { + return err + } + return nil +} + +func (o *ObjectMoved) String() string { + return fmt.Sprint("Timestamp: {", o.Timestamp, "} ", + "ObjectID: {", o.ObjectID, "}") +} + +// MESSAGE ObjectAvailable +type ObjectAvailable struct { + FactoryId string + ObjectType ObjectType + Rssi int8 +} + +func (o *ObjectAvailable) Size() uint32 { + var result uint32 + result += 1 // FactoryId length (uint_8) + result += uint32(len(o.FactoryId)) // uint_8 array + result += 4 // ObjectType ObjectType + result += 1 // Rssi int_8 + return result +} + +func (o *ObjectAvailable) Unpack(buf *bytes.Buffer) error { + var FactoryIdLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &FactoryIdLen); err != nil { + return err + } + o.FactoryId = string(buf.Next(int(FactoryIdLen))) + if len(o.FactoryId) != int(FactoryIdLen) { + return errors.New("string byte mismatch") + } + if err := binary.Read(buf, binary.LittleEndian, &o.ObjectType); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &o.Rssi); err != nil { + return err + } + return nil +} + +func (o *ObjectAvailable) Pack(buf *bytes.Buffer) error { + if len(o.FactoryId) > 255 { + return errors.New("max_length overflow in field FactoryId") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(o.FactoryId))); err != nil { + return err + } + if _, err := buf.WriteString(o.FactoryId); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, o.ObjectType); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, o.Rssi); err != nil { + return err + } + return nil +} + +func (o *ObjectAvailable) String() string { + return fmt.Sprint("FactoryId: {", o.FactoryId, "} ", + "ObjectType: {", o.ObjectType, "} ", + "Rssi: {", o.Rssi, "}") +} + +// MESSAGE ObjectStoppedMoving +type ObjectStoppedMoving struct { + Timestamp uint32 + ObjectID uint32 +} + +func (o *ObjectStoppedMoving) Size() uint32 { + var result uint32 + result += 4 // Timestamp uint_32 + result += 4 // ObjectID uint_32 + return result +} + +func (o *ObjectStoppedMoving) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &o.Timestamp); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &o.ObjectID); err != nil { + return err + } + return nil +} + +func (o *ObjectStoppedMoving) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, o.Timestamp); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, o.ObjectID); err != nil { + return err + } + return nil +} + +func (o *ObjectStoppedMoving) String() string { + return fmt.Sprint("Timestamp: {", o.Timestamp, "} ", + "ObjectID: {", o.ObjectID, "}") +} + +// MESSAGE ObjectUpAxisChanged +type ObjectUpAxisChanged struct { + Timestamp uint32 + ObjectID uint32 + UpAxis UpAxis +} + +func (o *ObjectUpAxisChanged) Size() uint32 { + var result uint32 + result += 4 // Timestamp uint_32 + result += 4 // ObjectID uint_32 + result += 1 // UpAxis UpAxis + return result +} + +func (o *ObjectUpAxisChanged) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &o.Timestamp); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &o.ObjectID); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &o.UpAxis); err != nil { + return err + } + return nil +} + +func (o *ObjectUpAxisChanged) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, o.Timestamp); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, o.ObjectID); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, o.UpAxis); err != nil { + return err + } + return nil +} + +func (o *ObjectUpAxisChanged) String() string { + return fmt.Sprint("Timestamp: {", o.Timestamp, "} ", + "ObjectID: {", o.ObjectID, "} ", + "UpAxis: {", o.UpAxis, "}") +} + +// MESSAGE ObjectTapped +type ObjectTapped struct { + Timestamp uint32 + ObjectID uint32 +} + +func (o *ObjectTapped) Size() uint32 { + var result uint32 + result += 4 // Timestamp uint_32 + result += 4 // ObjectID uint_32 + return result +} + +func (o *ObjectTapped) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &o.Timestamp); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &o.ObjectID); err != nil { + return err + } + return nil +} + +func (o *ObjectTapped) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, o.Timestamp); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, o.ObjectID); err != nil { + return err + } + return nil +} + +func (o *ObjectTapped) String() string { + return fmt.Sprint("Timestamp: {", o.Timestamp, "} ", + "ObjectID: {", o.ObjectID, "}") +} + +// MESSAGE ObjectAccel +type ObjectAccel struct { + Timestamp uint32 + ObjectID uint32 + Accel ActiveAccel +} + +func (o *ObjectAccel) Size() uint32 { + var result uint32 + result += 4 // Timestamp uint_32 + result += 4 // ObjectID uint_32 + result += o.Accel.Size() + return result +} + +func (o *ObjectAccel) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &o.Timestamp); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &o.ObjectID); err != nil { + return err + } + if err := o.Accel.Unpack(buf); err != nil { + return err + } + return nil +} + +func (o *ObjectAccel) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, o.Timestamp); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, o.ObjectID); err != nil { + return err + } + if err := o.Accel.Pack(buf); err != nil { + return err + } + return nil +} + +func (o *ObjectAccel) String() string { + return fmt.Sprint("Timestamp: {", o.Timestamp, "} ", + "ObjectID: {", o.ObjectID, "} ", + "Accel: {", o.Accel, "}") +} + +// MESSAGE RobotCompletedAction +type RobotCompletedAction struct { + IdTag uint32 + ActionType RobotActionType + Result ActionResult + SubActionResults []ActionResult + CompletionInfo ActionCompletedUnion +} + +func (r *RobotCompletedAction) Size() uint32 { + var result uint32 + result += 4 // IdTag uint_32 + result += 4 // ActionType RobotActionType + result += 4 // Result ActionResult + result += 1 // SubActionResults length (uint_8) + result += uint32(len(r.SubActionResults)) * 4 // ActionResult array + result += r.CompletionInfo.Size() + return result +} + +func (r *RobotCompletedAction) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &r.IdTag); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &r.ActionType); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &r.Result); err != nil { + return err + } + var SubActionResultsLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &SubActionResultsLen); err != nil { + return err + } + r.SubActionResults = make([]ActionResult, SubActionResultsLen) + if err := binary.Read(buf, binary.LittleEndian, &r.SubActionResults); err != nil { + return err + } + if err := r.CompletionInfo.Unpack(buf); err != nil { + return err + } + return nil +} + +func (r *RobotCompletedAction) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, r.IdTag); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, r.ActionType); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, r.Result); err != nil { + return err + } + if len(r.SubActionResults) > 255 { + return errors.New("max_length overflow in field SubActionResults") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(r.SubActionResults))); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, r.SubActionResults); err != nil { + return err + } + if err := r.CompletionInfo.Pack(buf); err != nil { + return err + } + return nil +} + +func (r *RobotCompletedAction) String() string { + return fmt.Sprint("IdTag: {", r.IdTag, "} ", + "ActionType: {", r.ActionType, "} ", + "Result: {", r.Result, "} ", + "SubActionResults: {", r.SubActionResults, "} ", + "CompletionInfo: {", r.CompletionInfo, "}") +} + +// MESSAGE LoadedKnownFace +type LoadedKnownFace struct { + SecondsSinceFirstEnrolled int64 + SecondsSinceLastUpdated int64 + SecondsSinceLastSeen int64 + LastSeenSecondsSinceEpoch int64 + FaceID int32 + Name string +} + +func (l *LoadedKnownFace) Size() uint32 { + var result uint32 + result += 8 // SecondsSinceFirstEnrolled int_64 + result += 8 // SecondsSinceLastUpdated int_64 + result += 8 // SecondsSinceLastSeen int_64 + result += 8 // LastSeenSecondsSinceEpoch int_64 + result += 4 // FaceID int_32 + result += 1 // Name length (uint_8) + result += uint32(len(l.Name)) // uint_8 array + return result +} + +func (l *LoadedKnownFace) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &l.SecondsSinceFirstEnrolled); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &l.SecondsSinceLastUpdated); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &l.SecondsSinceLastSeen); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &l.LastSeenSecondsSinceEpoch); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &l.FaceID); err != nil { + return err + } + var NameLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &NameLen); err != nil { + return err + } + l.Name = string(buf.Next(int(NameLen))) + if len(l.Name) != int(NameLen) { + return errors.New("string byte mismatch") + } + return nil +} + +func (l *LoadedKnownFace) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, l.SecondsSinceFirstEnrolled); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, l.SecondsSinceLastUpdated); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, l.SecondsSinceLastSeen); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, l.LastSeenSecondsSinceEpoch); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, l.FaceID); err != nil { + return err + } + if len(l.Name) > 255 { + return errors.New("max_length overflow in field Name") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(l.Name))); err != nil { + return err + } + if _, err := buf.WriteString(l.Name); err != nil { + return err + } + return nil +} + +func (l *LoadedKnownFace) String() string { + return fmt.Sprint("SecondsSinceFirstEnrolled: {", l.SecondsSinceFirstEnrolled, "} ", + "SecondsSinceLastUpdated: {", l.SecondsSinceLastUpdated, "} ", + "SecondsSinceLastSeen: {", l.SecondsSinceLastSeen, "} ", + "LastSeenSecondsSinceEpoch: {", l.LastSeenSecondsSinceEpoch, "} ", + "FaceID: {", l.FaceID, "} ", + "Name: {", l.Name, "}") +} + +// MESSAGE EnrolledNamesResponse +type EnrolledNamesResponse struct { + Faces []LoadedKnownFace +} + +func (e *EnrolledNamesResponse) Size() uint32 { + var result uint32 + result += 1 // Faces length (uint_8) + for idx := range e.Faces { + result += e.Faces[idx].Size() + } + return result +} + +func (e *EnrolledNamesResponse) Unpack(buf *bytes.Buffer) error { + var FacesLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &FacesLen); err != nil { + return err + } + e.Faces = make([]LoadedKnownFace, FacesLen) + for idx := range e.Faces { + if err := e.Faces[idx].Unpack(buf); err != nil { + return err + } + } + return nil +} + +func (e *EnrolledNamesResponse) Pack(buf *bytes.Buffer) error { + if len(e.Faces) > 255 { + return errors.New("max_length overflow in field Faces") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(e.Faces))); err != nil { + return err + } + for idx := range e.Faces { + if err := e.Faces[idx].Pack(buf); err != nil { + return err + } + } + return nil +} + +func (e *EnrolledNamesResponse) String() string { + return fmt.Sprint("Faces: {", e.Faces, "}") +} + +// STRUCTURE AccelData +type AccelData struct { + X float32 + Y float32 + Z float32 +} + +func (a *AccelData) Size() uint32 { + var result uint32 + result += 4 // X float_32 + result += 4 // Y float_32 + result += 4 // Z float_32 + return result +} + +func (a *AccelData) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &a.X); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &a.Y); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &a.Z); err != nil { + return err + } + return nil +} + +func (a *AccelData) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, a.X); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, a.Y); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, a.Z); err != nil { + return err + } + return nil +} + +func (a *AccelData) String() string { + return fmt.Sprint("X: {", a.X, "} ", + "Y: {", a.Y, "} ", + "Z: {", a.Z, "}") +} + +// STRUCTURE GyroData +type GyroData struct { + X float32 + Y float32 + Z float32 +} + +func (g *GyroData) Size() uint32 { + var result uint32 + result += 4 // X float_32 + result += 4 // Y float_32 + result += 4 // Z float_32 + return result +} + +func (g *GyroData) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &g.X); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &g.Y); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &g.Z); err != nil { + return err + } + return nil +} + +func (g *GyroData) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, g.X); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, g.Y); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, g.Z); err != nil { + return err + } + return nil +} + +func (g *GyroData) String() string { + return fmt.Sprint("X: {", g.X, "} ", + "Y: {", g.Y, "} ", + "Z: {", g.Z, "}") +} + +// STRUCTURE CladPoint2d +type CladPoint2d struct { + X float32 + Y float32 +} + +func (c *CladPoint2d) Size() uint32 { + var result uint32 + result += 4 // X float_32 + result += 4 // Y float_32 + return result +} + +func (c *CladPoint2d) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &c.X); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &c.Y); err != nil { + return err + } + return nil +} + +func (c *CladPoint2d) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, c.X); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, c.Y); err != nil { + return err + } + return nil +} + +func (c *CladPoint2d) String() string { + return fmt.Sprint("X: {", c.X, "} ", + "Y: {", c.Y, "}") +} + +// STRUCTURE CladRect +type CladRect struct { + XTopLeft float32 + YTopLeft float32 + Width float32 + Height float32 +} + +func (c *CladRect) Size() uint32 { + var result uint32 + result += 4 // XTopLeft float_32 + result += 4 // YTopLeft float_32 + result += 4 // Width float_32 + result += 4 // Height float_32 + return result +} + +func (c *CladRect) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &c.XTopLeft); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &c.YTopLeft); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &c.Width); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &c.Height); err != nil { + return err + } + return nil +} + +func (c *CladRect) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, c.XTopLeft); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, c.YTopLeft); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, c.Width); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, c.Height); err != nil { + return err + } + return nil +} + +func (c *CladRect) String() string { + return fmt.Sprint("XTopLeft: {", c.XTopLeft, "} ", + "YTopLeft: {", c.YTopLeft, "} ", + "Width: {", c.Width, "} ", + "Height: {", c.Height, "}") +} + +// ENUM FacialExpression +type FacialExpression int8 + +const ( + FacialExpression_Unknown FacialExpression = FacialExpression(-1) + FacialExpression_Neutral FacialExpression = FacialExpression(0) + FacialExpression_Happiness FacialExpression = FacialExpression_Neutral + 1 + FacialExpression_Surprise FacialExpression = FacialExpression_Happiness + 1 + FacialExpression_Anger FacialExpression = FacialExpression_Surprise + 1 + FacialExpression_Sadness FacialExpression = FacialExpression_Anger + 1 + FacialExpression_Count FacialExpression = FacialExpression_Sadness + 1 +) + +// STRUCTURE SmileAmount +type SmileAmount struct { + WasChecked bool + Amount float32 + Confidence float32 +} + +func (s *SmileAmount) Size() uint32 { + var result uint32 + result += 1 // WasChecked bool + result += 4 // Amount float_32 + result += 4 // Confidence float_32 + return result +} + +func (s *SmileAmount) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &s.WasChecked); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &s.Amount); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &s.Confidence); err != nil { + return err + } + return nil +} + +func (s *SmileAmount) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, s.WasChecked); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, s.Amount); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, s.Confidence); err != nil { + return err + } + return nil +} + +func (s *SmileAmount) String() string { + return fmt.Sprint("WasChecked: {", s.WasChecked, "} ", + "Amount: {", s.Amount, "} ", + "Confidence: {", s.Confidence, "}") +} + +// STRUCTURE Gaze +type Gaze struct { + WasChecked bool + LeftRightDeg float32 + UpDownDeg float32 +} + +func (g *Gaze) Size() uint32 { + var result uint32 + result += 1 // WasChecked bool + result += 4 // LeftRightDeg float_32 + result += 4 // UpDownDeg float_32 + return result +} + +func (g *Gaze) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &g.WasChecked); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &g.LeftRightDeg); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &g.UpDownDeg); err != nil { + return err + } + return nil +} + +func (g *Gaze) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, g.WasChecked); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, g.LeftRightDeg); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, g.UpDownDeg); err != nil { + return err + } + return nil +} + +func (g *Gaze) String() string { + return fmt.Sprint("WasChecked: {", g.WasChecked, "} ", + "LeftRightDeg: {", g.LeftRightDeg, "} ", + "UpDownDeg: {", g.UpDownDeg, "}") +} + +// STRUCTURE BlinkAmount +type BlinkAmount struct { + WasChecked bool + BlinkAmountLeft float32 + BlinkAmountRight float32 +} + +func (b *BlinkAmount) Size() uint32 { + var result uint32 + result += 1 // WasChecked bool + result += 4 // BlinkAmountLeft float_32 + result += 4 // BlinkAmountRight float_32 + return result +} + +func (b *BlinkAmount) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &b.WasChecked); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &b.BlinkAmountLeft); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &b.BlinkAmountRight); err != nil { + return err + } + return nil +} + +func (b *BlinkAmount) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, b.WasChecked); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, b.BlinkAmountLeft); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, b.BlinkAmountRight); err != nil { + return err + } + return nil +} + +func (b *BlinkAmount) String() string { + return fmt.Sprint("WasChecked: {", b.WasChecked, "} ", + "BlinkAmountLeft: {", b.BlinkAmountLeft, "} ", + "BlinkAmountRight: {", b.BlinkAmountRight, "}") +} + +// MESSAGE RobotObservedObject +type RobotObservedObject struct { + Timestamp uint32 + ObjectFamily ObjectFamily + ObjectType ObjectType + ObjectID int32 + ImgRect CladRect + Pose PoseStruct3d + TopFaceOrientationRad float32 + IsActive uint8 +} + +func (r *RobotObservedObject) Size() uint32 { + var result uint32 + result += 4 // Timestamp uint_32 + result += 4 // ObjectFamily ObjectFamily + result += 4 // ObjectType ObjectType + result += 4 // ObjectID int_32 + result += r.ImgRect.Size() + result += r.Pose.Size() + result += 4 // TopFaceOrientationRad float_32 + result += 1 // IsActive uint_8 + return result +} + +func (r *RobotObservedObject) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &r.Timestamp); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &r.ObjectFamily); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &r.ObjectType); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &r.ObjectID); err != nil { + return err + } + if err := r.ImgRect.Unpack(buf); err != nil { + return err + } + if err := r.Pose.Unpack(buf); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &r.TopFaceOrientationRad); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &r.IsActive); err != nil { + return err + } + return nil +} + +func (r *RobotObservedObject) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, r.Timestamp); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, r.ObjectFamily); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, r.ObjectType); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, r.ObjectID); err != nil { + return err + } + if err := r.ImgRect.Pack(buf); err != nil { + return err + } + if err := r.Pose.Pack(buf); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, r.TopFaceOrientationRad); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, r.IsActive); err != nil { + return err + } + return nil +} + +func (r *RobotObservedObject) String() string { + return fmt.Sprint("Timestamp: {", r.Timestamp, "} ", + "ObjectFamily: {", r.ObjectFamily, "} ", + "ObjectType: {", r.ObjectType, "} ", + "ObjectID: {", r.ObjectID, "} ", + "ImgRect: {", r.ImgRect, "} ", + "Pose: {", r.Pose, "} ", + "TopFaceOrientationRad: {", r.TopFaceOrientationRad, "} ", + "IsActive: {", r.IsActive, "}") +} + +// MESSAGE RobotObservedPossibleObject +type RobotObservedPossibleObject struct { + PossibleObject RobotObservedObject +} + +func (r *RobotObservedPossibleObject) Size() uint32 { + var result uint32 + result += r.PossibleObject.Size() + return result +} + +func (r *RobotObservedPossibleObject) Unpack(buf *bytes.Buffer) error { + if err := r.PossibleObject.Unpack(buf); err != nil { + return err + } + return nil +} + +func (r *RobotObservedPossibleObject) Pack(buf *bytes.Buffer) error { + if err := r.PossibleObject.Pack(buf); err != nil { + return err + } + return nil +} + +func (r *RobotObservedPossibleObject) String() string { + return fmt.Sprint("PossibleObject: {", r.PossibleObject, "}") +} + +// MESSAGE RobotObservedFace +type RobotObservedFace struct { + FaceID int32 + Timestamp uint32 + Pose PoseStruct3d + ImgRect CladRect + Name string + Expression FacialExpression + SmileAmount SmileAmount + Gaze Gaze + BlinkAmount BlinkAmount + ExpressionValues []uint8 + LeftEye []CladPoint2d + RightEye []CladPoint2d + Nose []CladPoint2d + Mouth []CladPoint2d +} + +func (r *RobotObservedFace) Size() uint32 { + var result uint32 + result += 4 // FaceID int_32 + result += 4 // Timestamp uint_32 + result += r.Pose.Size() + result += r.ImgRect.Size() + result += 1 // Name length (uint_8) + result += uint32(len(r.Name)) // uint_8 array + result += 1 // Expression FacialExpression + result += r.SmileAmount.Size() + result += r.Gaze.Size() + result += r.BlinkAmount.Size() + result += 1 // ExpressionValues length (uint_8) + result += uint32(len(r.ExpressionValues)) // uint_8 array + result += 1 // LeftEye length (uint_8) + result += uint32(len(r.LeftEye)) * 8 // CladPoint2d array + result += 1 // RightEye length (uint_8) + result += uint32(len(r.RightEye)) * 8 // CladPoint2d array + result += 1 // Nose length (uint_8) + result += uint32(len(r.Nose)) * 8 // CladPoint2d array + result += 1 // Mouth length (uint_8) + result += uint32(len(r.Mouth)) * 8 // CladPoint2d array + return result +} + +func (r *RobotObservedFace) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &r.FaceID); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &r.Timestamp); err != nil { + return err + } + if err := r.Pose.Unpack(buf); err != nil { + return err + } + if err := r.ImgRect.Unpack(buf); err != nil { + return err + } + var NameLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &NameLen); err != nil { + return err + } + r.Name = string(buf.Next(int(NameLen))) + if len(r.Name) != int(NameLen) { + return errors.New("string byte mismatch") + } + if err := binary.Read(buf, binary.LittleEndian, &r.Expression); err != nil { + return err + } + if err := r.SmileAmount.Unpack(buf); err != nil { + return err + } + if err := r.Gaze.Unpack(buf); err != nil { + return err + } + if err := r.BlinkAmount.Unpack(buf); err != nil { + return err + } + var ExpressionValuesLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &ExpressionValuesLen); err != nil { + return err + } + r.ExpressionValues = make([]uint8, ExpressionValuesLen) + if err := binary.Read(buf, binary.LittleEndian, &r.ExpressionValues); err != nil { + return err + } + var LeftEyeLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &LeftEyeLen); err != nil { + return err + } + r.LeftEye = make([]CladPoint2d, LeftEyeLen) + if err := binary.Read(buf, binary.LittleEndian, &r.LeftEye); err != nil { + return err + } + var RightEyeLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &RightEyeLen); err != nil { + return err + } + r.RightEye = make([]CladPoint2d, RightEyeLen) + if err := binary.Read(buf, binary.LittleEndian, &r.RightEye); err != nil { + return err + } + var NoseLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &NoseLen); err != nil { + return err + } + r.Nose = make([]CladPoint2d, NoseLen) + if err := binary.Read(buf, binary.LittleEndian, &r.Nose); err != nil { + return err + } + var MouthLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &MouthLen); err != nil { + return err + } + r.Mouth = make([]CladPoint2d, MouthLen) + if err := binary.Read(buf, binary.LittleEndian, &r.Mouth); err != nil { + return err + } + return nil +} + +func (r *RobotObservedFace) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, r.FaceID); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, r.Timestamp); err != nil { + return err + } + if err := r.Pose.Pack(buf); err != nil { + return err + } + if err := r.ImgRect.Pack(buf); err != nil { + return err + } + if len(r.Name) > 255 { + return errors.New("max_length overflow in field Name") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(r.Name))); err != nil { + return err + } + if _, err := buf.WriteString(r.Name); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, r.Expression); err != nil { + return err + } + if err := r.SmileAmount.Pack(buf); err != nil { + return err + } + if err := r.Gaze.Pack(buf); err != nil { + return err + } + if err := r.BlinkAmount.Pack(buf); err != nil { + return err + } + if len(r.ExpressionValues) > 255 { + return errors.New("max_length overflow in field ExpressionValues") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(r.ExpressionValues))); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, r.ExpressionValues); err != nil { + return err + } + if len(r.LeftEye) > 255 { + return errors.New("max_length overflow in field LeftEye") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(r.LeftEye))); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, r.LeftEye); err != nil { + return err + } + if len(r.RightEye) > 255 { + return errors.New("max_length overflow in field RightEye") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(r.RightEye))); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, r.RightEye); err != nil { + return err + } + if len(r.Nose) > 255 { + return errors.New("max_length overflow in field Nose") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(r.Nose))); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, r.Nose); err != nil { + return err + } + if len(r.Mouth) > 255 { + return errors.New("max_length overflow in field Mouth") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(r.Mouth))); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, r.Mouth); err != nil { + return err + } + return nil +} + +func (r *RobotObservedFace) String() string { + return fmt.Sprint("FaceID: {", r.FaceID, "} ", + "Timestamp: {", r.Timestamp, "} ", + "Pose: {", r.Pose, "} ", + "ImgRect: {", r.ImgRect, "} ", + "Name: {", r.Name, "} ", + "Expression: {", r.Expression, "} ", + "SmileAmount: {", r.SmileAmount, "} ", + "Gaze: {", r.Gaze, "} ", + "BlinkAmount: {", r.BlinkAmount, "} ", + "ExpressionValues: {", r.ExpressionValues, "} ", + "LeftEye: {", r.LeftEye, "} ", + "RightEye: {", r.RightEye, "} ", + "Nose: {", r.Nose, "} ", + "Mouth: {", r.Mouth, "}") +} + +// MESSAGE RobotChangedObservedFaceID +type RobotChangedObservedFaceID struct { + OldID int32 + NewID int32 +} + +func (r *RobotChangedObservedFaceID) Size() uint32 { + var result uint32 + result += 4 // OldID int_32 + result += 4 // NewID int_32 + return result +} + +func (r *RobotChangedObservedFaceID) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &r.OldID); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &r.NewID); err != nil { + return err + } + return nil +} + +func (r *RobotChangedObservedFaceID) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, r.OldID); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, r.NewID); err != nil { + return err + } + return nil +} + +func (r *RobotChangedObservedFaceID) String() string { + return fmt.Sprint("OldID: {", r.OldID, "} ", + "NewID: {", r.NewID, "}") +} + +// MESSAGE RequiredEmptyMessage +type RequiredEmptyMessage struct { +} + +func (r *RequiredEmptyMessage) Size() uint32 { + return 0 +} + +func (r *RequiredEmptyMessage) Unpack(buf *bytes.Buffer) error { + return nil +} + +func (r *RequiredEmptyMessage) Pack(buf *bytes.Buffer) error { + return nil +} + +func (r *RequiredEmptyMessage) String() string { + return "" +} + +// UNION Event +type EventTag uint8 + +const ( + EventTag_ReplaceMe EventTag = iota // 0 + EventTag_INVALID EventTag = 255 +) + +type Event struct { + tag *EventTag + value clad.Struct +} + +func (m *Event) Tag() EventTag { + if m.tag == nil { + return EventTag_INVALID + } + return *m.tag +} + +func (m *Event) Size() uint32 { + if m.tag == nil || *m.tag == EventTag_INVALID { + return 1 + } + return 1 + m.value.Size() +} + +func (m *Event) Pack(buf *bytes.Buffer) error { + tag := EventTag_INVALID + if m.tag != nil { + tag = *m.tag + } + if err := binary.Write(buf, binary.LittleEndian, tag); err != nil { + return err + } + if tag == EventTag_INVALID { + return nil + } + return m.value.Pack(buf) +} + +func (m *Event) unpackStruct(tag EventTag, buf *bytes.Buffer) (clad.Struct, error) { + switch tag { + case EventTag_ReplaceMe: + var ret RequiredEmptyMessage + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + default: + return nil, errors.New("invalid tag to unpackStruct") + } +} + +func (m *Event) Unpack(buf *bytes.Buffer) error { + tag := EventTag_INVALID + if err := binary.Read(buf, binary.LittleEndian, &tag); err != nil { + return err + } + m.tag = &tag + if tag == EventTag_INVALID { + m.value = nil + return nil + } + val, err := m.unpackStruct(tag, buf) + if err != nil { + *m.tag = EventTag_INVALID + return err + } + m.value = val + return nil +} + +func (t EventTag) String() string { + switch t { + case EventTag_ReplaceMe: + return "ReplaceMe" + default: + return "INVALID" + } +} + +func (m *Event) String() string { + if m.tag == nil { + return "nil" + } + if *m.tag == EventTag_INVALID { + return "INVALID" + } + return fmt.Sprintf("%s: {%s}", *m.tag, m.value) +} + +func (m *Event) GetReplaceMe() *RequiredEmptyMessage { + if m.tag == nil || *m.tag != EventTag_ReplaceMe { + return nil + } + return m.value.(*RequiredEmptyMessage) +} + +func (m *Event) SetReplaceMe(value *RequiredEmptyMessage) { + newTag := EventTag_ReplaceMe + m.tag = &newTag + m.value = value +} + +func NewEventWithReplaceMe(value *RequiredEmptyMessage) *Event { + var ret Event + ret.SetReplaceMe(value) + return &ret +} + +// MESSAGE RobotDeletedFixedCustomObjects +type RobotDeletedFixedCustomObjects struct { +} + +func (r *RobotDeletedFixedCustomObjects) Size() uint32 { + return 0 +} + +func (r *RobotDeletedFixedCustomObjects) Unpack(buf *bytes.Buffer) error { + return nil +} + +func (r *RobotDeletedFixedCustomObjects) Pack(buf *bytes.Buffer) error { + return nil +} + +func (r *RobotDeletedFixedCustomObjects) String() string { + return "" +} + +// MESSAGE RobotDeletedCustomMarkerObjects +type RobotDeletedCustomMarkerObjects struct { +} + +func (r *RobotDeletedCustomMarkerObjects) Size() uint32 { + return 0 +} + +func (r *RobotDeletedCustomMarkerObjects) Unpack(buf *bytes.Buffer) error { + return nil +} + +func (r *RobotDeletedCustomMarkerObjects) Pack(buf *bytes.Buffer) error { + return nil +} + +func (r *RobotDeletedCustomMarkerObjects) String() string { + return "" +} + +// MESSAGE CreatedFixedCustomObject +type CreatedFixedCustomObject struct { + ObjectID uint32 +} + +func (c *CreatedFixedCustomObject) Size() uint32 { + var result uint32 + result += 4 // ObjectID uint_32 + return result +} + +func (c *CreatedFixedCustomObject) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &c.ObjectID); err != nil { + return err + } + return nil +} + +func (c *CreatedFixedCustomObject) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, c.ObjectID); err != nil { + return err + } + return nil +} + +func (c *CreatedFixedCustomObject) String() string { + return fmt.Sprint("ObjectID: {", c.ObjectID, "}") +} + +// MESSAGE DefinedCustomObject +type DefinedCustomObject struct { + Success bool +} + +func (d *DefinedCustomObject) Size() uint32 { + var result uint32 + result += 1 // Success bool + return result +} + +func (d *DefinedCustomObject) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &d.Success); err != nil { + return err + } + return nil +} + +func (d *DefinedCustomObject) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, d.Success); err != nil { + return err + } + return nil +} + +func (d *DefinedCustomObject) String() string { + return fmt.Sprint("Success: {", d.Success, "}") +} + +// ENUM ENodeContentTypeEnum +type ENodeContentTypeEnum uint8 + +const ( + ENodeContentTypeEnum_Unknown ENodeContentTypeEnum = iota + ENodeContentTypeEnum_ClearOfObstacle + ENodeContentTypeEnum_ClearOfCliff + ENodeContentTypeEnum_ObstacleCube + ENodeContentTypeEnum_ObstacleProx + ENodeContentTypeEnum_ObstacleProxExplored + ENodeContentTypeEnum_ObstacleUnrecognized + ENodeContentTypeEnum_Cliff + ENodeContentTypeEnum_InterestingEdge + ENodeContentTypeEnum_NotInterestingEdge +) + +// STRUCTURE MemoryMapQuadInfo +type MemoryMapQuadInfo struct { + Content ENodeContentTypeEnum + Depth uint8 + ColorRGBA uint32 +} + +func (m *MemoryMapQuadInfo) Size() uint32 { + var result uint32 + result += 1 // Content ENodeContentTypeEnum + result += 1 // Depth uint_8 + result += 4 // ColorRGBA uint_32 + return result +} + +func (m *MemoryMapQuadInfo) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &m.Content); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &m.Depth); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &m.ColorRGBA); err != nil { + return err + } + return nil +} + +func (m *MemoryMapQuadInfo) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, m.Content); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, m.Depth); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, m.ColorRGBA); err != nil { + return err + } + return nil +} + +func (m *MemoryMapQuadInfo) String() string { + return fmt.Sprint("Content: {", m.Content, "} ", + "Depth: {", m.Depth, "} ", + "ColorRGBA: {", m.ColorRGBA, "}") +} + +// MESSAGE MemoryMapMessageBegin +type MemoryMapMessageBegin struct { + OriginId uint32 + RootDepth int32 + RootSizeMm float32 + RootCenterX float32 + RootCenterY float32 +} + +func (m *MemoryMapMessageBegin) Size() uint32 { + var result uint32 + result += 4 // OriginId uint_32 + result += 4 // RootDepth int_32 + result += 4 // RootSizeMm float_32 + result += 4 // RootCenterX float_32 + result += 4 // RootCenterY float_32 + return result +} + +func (m *MemoryMapMessageBegin) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &m.OriginId); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &m.RootDepth); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &m.RootSizeMm); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &m.RootCenterX); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &m.RootCenterY); err != nil { + return err + } + return nil +} + +func (m *MemoryMapMessageBegin) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, m.OriginId); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, m.RootDepth); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, m.RootSizeMm); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, m.RootCenterX); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, m.RootCenterY); err != nil { + return err + } + return nil +} + +func (m *MemoryMapMessageBegin) String() string { + return fmt.Sprint("OriginId: {", m.OriginId, "} ", + "RootDepth: {", m.RootDepth, "} ", + "RootSizeMm: {", m.RootSizeMm, "} ", + "RootCenterX: {", m.RootCenterX, "} ", + "RootCenterY: {", m.RootCenterY, "}") +} + +// MESSAGE MemoryMapMessage +type MemoryMapMessage struct { + QuadInfos []MemoryMapQuadInfo +} + +func (m *MemoryMapMessage) Size() uint32 { + var result uint32 + result += 2 // QuadInfos length (uint_16) + result += uint32(len(m.QuadInfos)) * 6 // MemoryMapQuadInfo array + return result +} + +func (m *MemoryMapMessage) Unpack(buf *bytes.Buffer) error { + var QuadInfosLen uint16 + if err := binary.Read(buf, binary.LittleEndian, &QuadInfosLen); err != nil { + return err + } + m.QuadInfos = make([]MemoryMapQuadInfo, QuadInfosLen) + if err := binary.Read(buf, binary.LittleEndian, &m.QuadInfos); err != nil { + return err + } + return nil +} + +func (m *MemoryMapMessage) Pack(buf *bytes.Buffer) error { + if len(m.QuadInfos) > 65535 { + return errors.New("max_length overflow in field QuadInfos") + } + if err := binary.Write(buf, binary.LittleEndian, uint16(len(m.QuadInfos))); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, m.QuadInfos); err != nil { + return err + } + return nil +} + +func (m *MemoryMapMessage) String() string { + return fmt.Sprint("QuadInfos: {", m.QuadInfos, "}") +} + +// MESSAGE MemoryMapMessageEnd +type MemoryMapMessageEnd struct { +} + +func (m *MemoryMapMessageEnd) Size() uint32 { + return 0 +} + +func (m *MemoryMapMessageEnd) Unpack(buf *bytes.Buffer) error { + return nil +} + +func (m *MemoryMapMessageEnd) Pack(buf *bytes.Buffer) error { + return nil +} + +func (m *MemoryMapMessageEnd) String() string { + return "" +} + +// UNION MessageRobotToExternal +type MessageRobotToExternalTag uint8 + +const ( + MessageRobotToExternalTag_UiDeviceConnected MessageRobotToExternalTag = 0x0 // 0 + MessageRobotToExternalTag_RobotCompletedAction MessageRobotToExternalTag = 0x1 // 1 + MessageRobotToExternalTag_Event MessageRobotToExternalTag = 0x2 // 2 + MessageRobotToExternalTag_EnrolledNamesResponse MessageRobotToExternalTag = 0x5 // 5 + MessageRobotToExternalTag_RobotObservedFace MessageRobotToExternalTag = 0x6 // 6 + MessageRobotToExternalTag_RobotChangedObservedFaceID MessageRobotToExternalTag = 0x7 // 7 + MessageRobotToExternalTag_ObjectConnectionState MessageRobotToExternalTag = 0xa // 10 + MessageRobotToExternalTag_ObjectMoved MessageRobotToExternalTag = 0xb // 11 + MessageRobotToExternalTag_ObjectStoppedMoving MessageRobotToExternalTag = 0xc // 12 + MessageRobotToExternalTag_ObjectUpAxisChanged MessageRobotToExternalTag = 0xd // 13 + MessageRobotToExternalTag_ObjectTapped MessageRobotToExternalTag = 0xe // 14 + MessageRobotToExternalTag_ObjectAccel MessageRobotToExternalTag = 0xf // 15 + MessageRobotToExternalTag_RobotObservedObject MessageRobotToExternalTag = 0x10 // 16 + MessageRobotToExternalTag_ObjectAvailable MessageRobotToExternalTag = 0x11 // 17 + MessageRobotToExternalTag_RobotDeletedFixedCustomObjects MessageRobotToExternalTag = 0x12 // 18 + MessageRobotToExternalTag_RobotDeletedCustomMarkerObjects MessageRobotToExternalTag = 0x13 // 19 + MessageRobotToExternalTag_CreatedFixedCustomObject MessageRobotToExternalTag = 0x14 // 20 + MessageRobotToExternalTag_DefinedCustomObject MessageRobotToExternalTag = 0x15 // 21 + MessageRobotToExternalTag_MemoryMapMessageBegin MessageRobotToExternalTag = 0x16 // 22 + MessageRobotToExternalTag_MemoryMapMessage MessageRobotToExternalTag = 0x17 // 23 + MessageRobotToExternalTag_MemoryMapMessageEnd MessageRobotToExternalTag = 0x18 // 24 + MessageRobotToExternalTag_INVALID MessageRobotToExternalTag = 255 +) + +type MessageRobotToExternal struct { + tag *MessageRobotToExternalTag + value clad.Struct +} + +func (m *MessageRobotToExternal) Tag() MessageRobotToExternalTag { + if m.tag == nil { + return MessageRobotToExternalTag_INVALID + } + return *m.tag +} + +func (m *MessageRobotToExternal) Size() uint32 { + if m.tag == nil || *m.tag == MessageRobotToExternalTag_INVALID { + return 1 + } + return 1 + m.value.Size() +} + +func (m *MessageRobotToExternal) Pack(buf *bytes.Buffer) error { + tag := MessageRobotToExternalTag_INVALID + if m.tag != nil { + tag = *m.tag + } + if err := binary.Write(buf, binary.LittleEndian, tag); err != nil { + return err + } + if tag == MessageRobotToExternalTag_INVALID { + return nil + } + return m.value.Pack(buf) +} + +func (m *MessageRobotToExternal) unpackStruct(tag MessageRobotToExternalTag, buf *bytes.Buffer) (clad.Struct, error) { + switch tag { + case MessageRobotToExternalTag_UiDeviceConnected: + var ret UiDeviceConnected + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageRobotToExternalTag_RobotCompletedAction: + var ret RobotCompletedAction + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageRobotToExternalTag_Event: + var ret Event + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageRobotToExternalTag_EnrolledNamesResponse: + var ret EnrolledNamesResponse + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageRobotToExternalTag_RobotObservedFace: + var ret RobotObservedFace + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageRobotToExternalTag_RobotChangedObservedFaceID: + var ret RobotChangedObservedFaceID + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageRobotToExternalTag_ObjectConnectionState: + var ret ObjectConnectionState + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageRobotToExternalTag_ObjectMoved: + var ret ObjectMoved + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageRobotToExternalTag_ObjectStoppedMoving: + var ret ObjectStoppedMoving + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageRobotToExternalTag_ObjectUpAxisChanged: + var ret ObjectUpAxisChanged + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageRobotToExternalTag_ObjectTapped: + var ret ObjectTapped + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageRobotToExternalTag_ObjectAccel: + var ret ObjectAccel + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageRobotToExternalTag_RobotObservedObject: + var ret RobotObservedObject + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageRobotToExternalTag_ObjectAvailable: + var ret ObjectAvailable + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageRobotToExternalTag_RobotDeletedFixedCustomObjects: + var ret RobotDeletedFixedCustomObjects + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageRobotToExternalTag_RobotDeletedCustomMarkerObjects: + var ret RobotDeletedCustomMarkerObjects + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageRobotToExternalTag_CreatedFixedCustomObject: + var ret CreatedFixedCustomObject + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageRobotToExternalTag_DefinedCustomObject: + var ret DefinedCustomObject + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageRobotToExternalTag_MemoryMapMessageBegin: + var ret MemoryMapMessageBegin + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageRobotToExternalTag_MemoryMapMessage: + var ret MemoryMapMessage + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case MessageRobotToExternalTag_MemoryMapMessageEnd: + var ret MemoryMapMessageEnd + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + default: + return nil, errors.New("invalid tag to unpackStruct") + } +} + +func (m *MessageRobotToExternal) Unpack(buf *bytes.Buffer) error { + tag := MessageRobotToExternalTag_INVALID + if err := binary.Read(buf, binary.LittleEndian, &tag); err != nil { + return err + } + m.tag = &tag + if tag == MessageRobotToExternalTag_INVALID { + m.value = nil + return nil + } + val, err := m.unpackStruct(tag, buf) + if err != nil { + *m.tag = MessageRobotToExternalTag_INVALID + return err + } + m.value = val + return nil +} + +func (t MessageRobotToExternalTag) String() string { + switch t { + case MessageRobotToExternalTag_UiDeviceConnected: + return "UiDeviceConnected" + case MessageRobotToExternalTag_RobotCompletedAction: + return "RobotCompletedAction" + case MessageRobotToExternalTag_Event: + return "Event" + case MessageRobotToExternalTag_EnrolledNamesResponse: + return "EnrolledNamesResponse" + case MessageRobotToExternalTag_RobotObservedFace: + return "RobotObservedFace" + case MessageRobotToExternalTag_RobotChangedObservedFaceID: + return "RobotChangedObservedFaceID" + case MessageRobotToExternalTag_ObjectConnectionState: + return "ObjectConnectionState" + case MessageRobotToExternalTag_ObjectMoved: + return "ObjectMoved" + case MessageRobotToExternalTag_ObjectStoppedMoving: + return "ObjectStoppedMoving" + case MessageRobotToExternalTag_ObjectUpAxisChanged: + return "ObjectUpAxisChanged" + case MessageRobotToExternalTag_ObjectTapped: + return "ObjectTapped" + case MessageRobotToExternalTag_ObjectAccel: + return "ObjectAccel" + case MessageRobotToExternalTag_RobotObservedObject: + return "RobotObservedObject" + case MessageRobotToExternalTag_ObjectAvailable: + return "ObjectAvailable" + case MessageRobotToExternalTag_RobotDeletedFixedCustomObjects: + return "RobotDeletedFixedCustomObjects" + case MessageRobotToExternalTag_RobotDeletedCustomMarkerObjects: + return "RobotDeletedCustomMarkerObjects" + case MessageRobotToExternalTag_CreatedFixedCustomObject: + return "CreatedFixedCustomObject" + case MessageRobotToExternalTag_DefinedCustomObject: + return "DefinedCustomObject" + case MessageRobotToExternalTag_MemoryMapMessageBegin: + return "MemoryMapMessageBegin" + case MessageRobotToExternalTag_MemoryMapMessage: + return "MemoryMapMessage" + case MessageRobotToExternalTag_MemoryMapMessageEnd: + return "MemoryMapMessageEnd" + default: + return "INVALID" + } +} + +func (m *MessageRobotToExternal) String() string { + if m.tag == nil { + return "nil" + } + if *m.tag == MessageRobotToExternalTag_INVALID { + return "INVALID" + } + return fmt.Sprintf("%s: {%s}", *m.tag, m.value) +} + +func (m *MessageRobotToExternal) GetUiDeviceConnected() *UiDeviceConnected { + if m.tag == nil || *m.tag != MessageRobotToExternalTag_UiDeviceConnected { + return nil + } + return m.value.(*UiDeviceConnected) +} + +func (m *MessageRobotToExternal) SetUiDeviceConnected(value *UiDeviceConnected) { + newTag := MessageRobotToExternalTag_UiDeviceConnected + m.tag = &newTag + m.value = value +} + +func NewMessageRobotToExternalWithUiDeviceConnected(value *UiDeviceConnected) *MessageRobotToExternal { + var ret MessageRobotToExternal + ret.SetUiDeviceConnected(value) + return &ret +} + +func (m *MessageRobotToExternal) GetRobotCompletedAction() *RobotCompletedAction { + if m.tag == nil || *m.tag != MessageRobotToExternalTag_RobotCompletedAction { + return nil + } + return m.value.(*RobotCompletedAction) +} + +func (m *MessageRobotToExternal) SetRobotCompletedAction(value *RobotCompletedAction) { + newTag := MessageRobotToExternalTag_RobotCompletedAction + m.tag = &newTag + m.value = value +} + +func NewMessageRobotToExternalWithRobotCompletedAction(value *RobotCompletedAction) *MessageRobotToExternal { + var ret MessageRobotToExternal + ret.SetRobotCompletedAction(value) + return &ret +} + +func (m *MessageRobotToExternal) GetEvent() *Event { + if m.tag == nil || *m.tag != MessageRobotToExternalTag_Event { + return nil + } + return m.value.(*Event) +} + +func (m *MessageRobotToExternal) SetEvent(value *Event) { + newTag := MessageRobotToExternalTag_Event + m.tag = &newTag + m.value = value +} + +func NewMessageRobotToExternalWithEvent(value *Event) *MessageRobotToExternal { + var ret MessageRobotToExternal + ret.SetEvent(value) + return &ret +} + +func (m *MessageRobotToExternal) GetEnrolledNamesResponse() *EnrolledNamesResponse { + if m.tag == nil || *m.tag != MessageRobotToExternalTag_EnrolledNamesResponse { + return nil + } + return m.value.(*EnrolledNamesResponse) +} + +func (m *MessageRobotToExternal) SetEnrolledNamesResponse(value *EnrolledNamesResponse) { + newTag := MessageRobotToExternalTag_EnrolledNamesResponse + m.tag = &newTag + m.value = value +} + +func NewMessageRobotToExternalWithEnrolledNamesResponse(value *EnrolledNamesResponse) *MessageRobotToExternal { + var ret MessageRobotToExternal + ret.SetEnrolledNamesResponse(value) + return &ret +} + +func (m *MessageRobotToExternal) GetRobotObservedFace() *RobotObservedFace { + if m.tag == nil || *m.tag != MessageRobotToExternalTag_RobotObservedFace { + return nil + } + return m.value.(*RobotObservedFace) +} + +func (m *MessageRobotToExternal) SetRobotObservedFace(value *RobotObservedFace) { + newTag := MessageRobotToExternalTag_RobotObservedFace + m.tag = &newTag + m.value = value +} + +func NewMessageRobotToExternalWithRobotObservedFace(value *RobotObservedFace) *MessageRobotToExternal { + var ret MessageRobotToExternal + ret.SetRobotObservedFace(value) + return &ret +} + +func (m *MessageRobotToExternal) GetRobotChangedObservedFaceID() *RobotChangedObservedFaceID { + if m.tag == nil || *m.tag != MessageRobotToExternalTag_RobotChangedObservedFaceID { + return nil + } + return m.value.(*RobotChangedObservedFaceID) +} + +func (m *MessageRobotToExternal) SetRobotChangedObservedFaceID(value *RobotChangedObservedFaceID) { + newTag := MessageRobotToExternalTag_RobotChangedObservedFaceID + m.tag = &newTag + m.value = value +} + +func NewMessageRobotToExternalWithRobotChangedObservedFaceID(value *RobotChangedObservedFaceID) *MessageRobotToExternal { + var ret MessageRobotToExternal + ret.SetRobotChangedObservedFaceID(value) + return &ret +} + +func (m *MessageRobotToExternal) GetObjectConnectionState() *ObjectConnectionState { + if m.tag == nil || *m.tag != MessageRobotToExternalTag_ObjectConnectionState { + return nil + } + return m.value.(*ObjectConnectionState) +} + +func (m *MessageRobotToExternal) SetObjectConnectionState(value *ObjectConnectionState) { + newTag := MessageRobotToExternalTag_ObjectConnectionState + m.tag = &newTag + m.value = value +} + +func NewMessageRobotToExternalWithObjectConnectionState(value *ObjectConnectionState) *MessageRobotToExternal { + var ret MessageRobotToExternal + ret.SetObjectConnectionState(value) + return &ret +} + +func (m *MessageRobotToExternal) GetObjectMoved() *ObjectMoved { + if m.tag == nil || *m.tag != MessageRobotToExternalTag_ObjectMoved { + return nil + } + return m.value.(*ObjectMoved) +} + +func (m *MessageRobotToExternal) SetObjectMoved(value *ObjectMoved) { + newTag := MessageRobotToExternalTag_ObjectMoved + m.tag = &newTag + m.value = value +} + +func NewMessageRobotToExternalWithObjectMoved(value *ObjectMoved) *MessageRobotToExternal { + var ret MessageRobotToExternal + ret.SetObjectMoved(value) + return &ret +} + +func (m *MessageRobotToExternal) GetObjectStoppedMoving() *ObjectStoppedMoving { + if m.tag == nil || *m.tag != MessageRobotToExternalTag_ObjectStoppedMoving { + return nil + } + return m.value.(*ObjectStoppedMoving) +} + +func (m *MessageRobotToExternal) SetObjectStoppedMoving(value *ObjectStoppedMoving) { + newTag := MessageRobotToExternalTag_ObjectStoppedMoving + m.tag = &newTag + m.value = value +} + +func NewMessageRobotToExternalWithObjectStoppedMoving(value *ObjectStoppedMoving) *MessageRobotToExternal { + var ret MessageRobotToExternal + ret.SetObjectStoppedMoving(value) + return &ret +} + +func (m *MessageRobotToExternal) GetObjectUpAxisChanged() *ObjectUpAxisChanged { + if m.tag == nil || *m.tag != MessageRobotToExternalTag_ObjectUpAxisChanged { + return nil + } + return m.value.(*ObjectUpAxisChanged) +} + +func (m *MessageRobotToExternal) SetObjectUpAxisChanged(value *ObjectUpAxisChanged) { + newTag := MessageRobotToExternalTag_ObjectUpAxisChanged + m.tag = &newTag + m.value = value +} + +func NewMessageRobotToExternalWithObjectUpAxisChanged(value *ObjectUpAxisChanged) *MessageRobotToExternal { + var ret MessageRobotToExternal + ret.SetObjectUpAxisChanged(value) + return &ret +} + +func (m *MessageRobotToExternal) GetObjectTapped() *ObjectTapped { + if m.tag == nil || *m.tag != MessageRobotToExternalTag_ObjectTapped { + return nil + } + return m.value.(*ObjectTapped) +} + +func (m *MessageRobotToExternal) SetObjectTapped(value *ObjectTapped) { + newTag := MessageRobotToExternalTag_ObjectTapped + m.tag = &newTag + m.value = value +} + +func NewMessageRobotToExternalWithObjectTapped(value *ObjectTapped) *MessageRobotToExternal { + var ret MessageRobotToExternal + ret.SetObjectTapped(value) + return &ret +} + +func (m *MessageRobotToExternal) GetObjectAccel() *ObjectAccel { + if m.tag == nil || *m.tag != MessageRobotToExternalTag_ObjectAccel { + return nil + } + return m.value.(*ObjectAccel) +} + +func (m *MessageRobotToExternal) SetObjectAccel(value *ObjectAccel) { + newTag := MessageRobotToExternalTag_ObjectAccel + m.tag = &newTag + m.value = value +} + +func NewMessageRobotToExternalWithObjectAccel(value *ObjectAccel) *MessageRobotToExternal { + var ret MessageRobotToExternal + ret.SetObjectAccel(value) + return &ret +} + +func (m *MessageRobotToExternal) GetRobotObservedObject() *RobotObservedObject { + if m.tag == nil || *m.tag != MessageRobotToExternalTag_RobotObservedObject { + return nil + } + return m.value.(*RobotObservedObject) +} + +func (m *MessageRobotToExternal) SetRobotObservedObject(value *RobotObservedObject) { + newTag := MessageRobotToExternalTag_RobotObservedObject + m.tag = &newTag + m.value = value +} + +func NewMessageRobotToExternalWithRobotObservedObject(value *RobotObservedObject) *MessageRobotToExternal { + var ret MessageRobotToExternal + ret.SetRobotObservedObject(value) + return &ret +} + +func (m *MessageRobotToExternal) GetObjectAvailable() *ObjectAvailable { + if m.tag == nil || *m.tag != MessageRobotToExternalTag_ObjectAvailable { + return nil + } + return m.value.(*ObjectAvailable) +} + +func (m *MessageRobotToExternal) SetObjectAvailable(value *ObjectAvailable) { + newTag := MessageRobotToExternalTag_ObjectAvailable + m.tag = &newTag + m.value = value +} + +func NewMessageRobotToExternalWithObjectAvailable(value *ObjectAvailable) *MessageRobotToExternal { + var ret MessageRobotToExternal + ret.SetObjectAvailable(value) + return &ret +} + +func (m *MessageRobotToExternal) GetRobotDeletedFixedCustomObjects() *RobotDeletedFixedCustomObjects { + if m.tag == nil || *m.tag != MessageRobotToExternalTag_RobotDeletedFixedCustomObjects { + return nil + } + return m.value.(*RobotDeletedFixedCustomObjects) +} + +func (m *MessageRobotToExternal) SetRobotDeletedFixedCustomObjects(value *RobotDeletedFixedCustomObjects) { + newTag := MessageRobotToExternalTag_RobotDeletedFixedCustomObjects + m.tag = &newTag + m.value = value +} + +func NewMessageRobotToExternalWithRobotDeletedFixedCustomObjects(value *RobotDeletedFixedCustomObjects) *MessageRobotToExternal { + var ret MessageRobotToExternal + ret.SetRobotDeletedFixedCustomObjects(value) + return &ret +} + +func (m *MessageRobotToExternal) GetRobotDeletedCustomMarkerObjects() *RobotDeletedCustomMarkerObjects { + if m.tag == nil || *m.tag != MessageRobotToExternalTag_RobotDeletedCustomMarkerObjects { + return nil + } + return m.value.(*RobotDeletedCustomMarkerObjects) +} + +func (m *MessageRobotToExternal) SetRobotDeletedCustomMarkerObjects(value *RobotDeletedCustomMarkerObjects) { + newTag := MessageRobotToExternalTag_RobotDeletedCustomMarkerObjects + m.tag = &newTag + m.value = value +} + +func NewMessageRobotToExternalWithRobotDeletedCustomMarkerObjects(value *RobotDeletedCustomMarkerObjects) *MessageRobotToExternal { + var ret MessageRobotToExternal + ret.SetRobotDeletedCustomMarkerObjects(value) + return &ret +} + +func (m *MessageRobotToExternal) GetCreatedFixedCustomObject() *CreatedFixedCustomObject { + if m.tag == nil || *m.tag != MessageRobotToExternalTag_CreatedFixedCustomObject { + return nil + } + return m.value.(*CreatedFixedCustomObject) +} + +func (m *MessageRobotToExternal) SetCreatedFixedCustomObject(value *CreatedFixedCustomObject) { + newTag := MessageRobotToExternalTag_CreatedFixedCustomObject + m.tag = &newTag + m.value = value +} + +func NewMessageRobotToExternalWithCreatedFixedCustomObject(value *CreatedFixedCustomObject) *MessageRobotToExternal { + var ret MessageRobotToExternal + ret.SetCreatedFixedCustomObject(value) + return &ret +} + +func (m *MessageRobotToExternal) GetDefinedCustomObject() *DefinedCustomObject { + if m.tag == nil || *m.tag != MessageRobotToExternalTag_DefinedCustomObject { + return nil + } + return m.value.(*DefinedCustomObject) +} + +func (m *MessageRobotToExternal) SetDefinedCustomObject(value *DefinedCustomObject) { + newTag := MessageRobotToExternalTag_DefinedCustomObject + m.tag = &newTag + m.value = value +} + +func NewMessageRobotToExternalWithDefinedCustomObject(value *DefinedCustomObject) *MessageRobotToExternal { + var ret MessageRobotToExternal + ret.SetDefinedCustomObject(value) + return &ret +} + +func (m *MessageRobotToExternal) GetMemoryMapMessageBegin() *MemoryMapMessageBegin { + if m.tag == nil || *m.tag != MessageRobotToExternalTag_MemoryMapMessageBegin { + return nil + } + return m.value.(*MemoryMapMessageBegin) +} + +func (m *MessageRobotToExternal) SetMemoryMapMessageBegin(value *MemoryMapMessageBegin) { + newTag := MessageRobotToExternalTag_MemoryMapMessageBegin + m.tag = &newTag + m.value = value +} + +func NewMessageRobotToExternalWithMemoryMapMessageBegin(value *MemoryMapMessageBegin) *MessageRobotToExternal { + var ret MessageRobotToExternal + ret.SetMemoryMapMessageBegin(value) + return &ret +} + +func (m *MessageRobotToExternal) GetMemoryMapMessage() *MemoryMapMessage { + if m.tag == nil || *m.tag != MessageRobotToExternalTag_MemoryMapMessage { + return nil + } + return m.value.(*MemoryMapMessage) +} + +func (m *MessageRobotToExternal) SetMemoryMapMessage(value *MemoryMapMessage) { + newTag := MessageRobotToExternalTag_MemoryMapMessage + m.tag = &newTag + m.value = value +} + +func NewMessageRobotToExternalWithMemoryMapMessage(value *MemoryMapMessage) *MessageRobotToExternal { + var ret MessageRobotToExternal + ret.SetMemoryMapMessage(value) + return &ret +} + +func (m *MessageRobotToExternal) GetMemoryMapMessageEnd() *MemoryMapMessageEnd { + if m.tag == nil || *m.tag != MessageRobotToExternalTag_MemoryMapMessageEnd { + return nil + } + return m.value.(*MemoryMapMessageEnd) +} + +func (m *MessageRobotToExternal) SetMemoryMapMessageEnd(value *MemoryMapMessageEnd) { + newTag := MessageRobotToExternalTag_MemoryMapMessageEnd + m.tag = &newTag + m.value = value +} + +func NewMessageRobotToExternalWithMemoryMapMessageEnd(value *MemoryMapMessageEnd) *MessageRobotToExternal { + var ret MessageRobotToExternal + ret.SetMemoryMapMessageEnd(value) + return &ret +} diff --git a/vector-cloud/internal/clad/gateway/shared.go b/vector-cloud/internal/clad/gateway/shared.go new file mode 100644 index 0000000..7409c1e --- /dev/null +++ b/vector-cloud/internal/clad/gateway/shared.go @@ -0,0 +1,136 @@ +// Autogenerated Go message buffer code. +// Source: clad/gateway/shared.clad +// Full command line: victor-clad/tools/message-buffers/emitters/Go_emitter.py -C src -o generated/cladgo/src clad/gateway/shared.clad + +package gateway + +import ( + "bytes" + "encoding/binary" + "fmt" +) + +// STRUCTURE PoseStruct3d +type PoseStruct3d struct { + X float32 + Y float32 + Z float32 + Q0 float32 + Q1 float32 + Q2 float32 + Q3 float32 + OriginID uint32 +} + +func (p *PoseStruct3d) Size() uint32 { + var result uint32 + result += 4 // X float_32 + result += 4 // Y float_32 + result += 4 // Z float_32 + result += 4 // Q0 float_32 + result += 4 // Q1 float_32 + result += 4 // Q2 float_32 + result += 4 // Q3 float_32 + result += 4 // OriginID uint_32 + return result +} + +func (p *PoseStruct3d) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &p.X); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &p.Y); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &p.Z); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &p.Q0); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &p.Q1); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &p.Q2); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &p.Q3); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &p.OriginID); err != nil { + return err + } + return nil +} + +func (p *PoseStruct3d) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, p.X); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, p.Y); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, p.Z); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, p.Q0); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, p.Q1); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, p.Q2); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, p.Q3); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, p.OriginID); err != nil { + return err + } + return nil +} + +func (p *PoseStruct3d) String() string { + return fmt.Sprint("X: {", p.X, "} ", + "Y: {", p.Y, "} ", + "Z: {", p.Z, "} ", + "Q0: {", p.Q0, "} ", + "Q1: {", p.Q1, "} ", + "Q2: {", p.Q2, "} ", + "Q3: {", p.Q3, "} ", + "OriginID: {", p.OriginID, "}") +} + +// ENUM ObjectType +type ObjectType int32 + +const ( + ObjectType_UnknownObject ObjectType = ObjectType(0) + ObjectType_Block_LIGHTCUBE1 ObjectType = ObjectType(1) + ObjectType_Block_LIGHTCUBE2 ObjectType = ObjectType_Block_LIGHTCUBE1 + 1 + ObjectType_Block_LIGHTCUBE3 ObjectType = ObjectType_Block_LIGHTCUBE2 + 1 + ObjectType_Block_LIGHTCUBE_GHOST ObjectType = ObjectType_Block_LIGHTCUBE3 + 1 + ObjectType_Charger_Basic ObjectType = ObjectType(5) + ObjectType_CustomType00 ObjectType = ObjectType(14) + ObjectType_CustomType01 ObjectType = ObjectType_CustomType00 + 1 + ObjectType_CustomType02 ObjectType = ObjectType_CustomType01 + 1 + ObjectType_CustomType03 ObjectType = ObjectType_CustomType02 + 1 + ObjectType_CustomType04 ObjectType = ObjectType_CustomType03 + 1 + ObjectType_CustomType05 ObjectType = ObjectType_CustomType04 + 1 + ObjectType_CustomType06 ObjectType = ObjectType_CustomType05 + 1 + ObjectType_CustomType07 ObjectType = ObjectType_CustomType06 + 1 + ObjectType_CustomType08 ObjectType = ObjectType_CustomType07 + 1 + ObjectType_CustomType09 ObjectType = ObjectType_CustomType08 + 1 + ObjectType_CustomType10 ObjectType = ObjectType_CustomType09 + 1 + ObjectType_CustomType11 ObjectType = ObjectType_CustomType10 + 1 + ObjectType_CustomType12 ObjectType = ObjectType_CustomType11 + 1 + ObjectType_CustomType13 ObjectType = ObjectType_CustomType12 + 1 + ObjectType_CustomType14 ObjectType = ObjectType_CustomType13 + 1 + ObjectType_CustomType15 ObjectType = ObjectType_CustomType14 + 1 + ObjectType_CustomType16 ObjectType = ObjectType_CustomType15 + 1 + ObjectType_CustomType17 ObjectType = ObjectType_CustomType16 + 1 + ObjectType_CustomType18 ObjectType = ObjectType_CustomType17 + 1 + ObjectType_CustomType19 ObjectType = ObjectType_CustomType18 + 1 + ObjectType_CustomFixedObstacle ObjectType = ObjectType_CustomType19 + 1 +) diff --git a/vector-cloud/internal/clad/gateway/switchboard.go b/vector-cloud/internal/clad/gateway/switchboard.go new file mode 100644 index 0000000..f865980 --- /dev/null +++ b/vector-cloud/internal/clad/gateway/switchboard.go @@ -0,0 +1,900 @@ +// Autogenerated Go message buffer code. +// Source: clad/gateway/switchboard.clad +// Full command line: victor-clad/tools/message-buffers/emitters/Go_emitter.py -C src -o generated/cladgo/src clad/gateway/switchboard.clad + +package gateway + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + + "github.com/digital-dream-labs/vector-cloud/internal/clad" + + . "github.com/digital-dream-labs/vector-cloud/internal/clad/cloud" +) + +// MESSAGE SwitchboardError +type SwitchboardError struct { +} + +func (s *SwitchboardError) Size() uint32 { + return 0 +} + +func (s *SwitchboardError) Unpack(buf *bytes.Buffer) error { + return nil +} + +func (s *SwitchboardError) Pack(buf *bytes.Buffer) error { + return nil +} + +func (s *SwitchboardError) String() string { + return "" +} + +// MESSAGE ExternalConnectionRequest +type ExternalConnectionRequest struct { +} + +func (e *ExternalConnectionRequest) Size() uint32 { + return 0 +} + +func (e *ExternalConnectionRequest) Unpack(buf *bytes.Buffer) error { + return nil +} + +func (e *ExternalConnectionRequest) Pack(buf *bytes.Buffer) error { + return nil +} + +func (e *ExternalConnectionRequest) String() string { + return "" +} + +// MESSAGE ExternalConnectionResponse +type ExternalConnectionResponse struct { + IsConnected bool + ConnectionId string +} + +func (e *ExternalConnectionResponse) Size() uint32 { + var result uint32 + result += 1 // IsConnected bool + result += 2 // ConnectionId length (uint_16) + result += uint32(len(e.ConnectionId)) // uint_8 array + return result +} + +func (e *ExternalConnectionResponse) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &e.IsConnected); err != nil { + return err + } + var ConnectionIdLen uint16 + if err := binary.Read(buf, binary.LittleEndian, &ConnectionIdLen); err != nil { + return err + } + e.ConnectionId = string(buf.Next(int(ConnectionIdLen))) + if len(e.ConnectionId) != int(ConnectionIdLen) { + return errors.New("string byte mismatch") + } + return nil +} + +func (e *ExternalConnectionResponse) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, e.IsConnected); err != nil { + return err + } + if len(e.ConnectionId) > 65535 { + return errors.New("max_length overflow in field ConnectionId") + } + if err := binary.Write(buf, binary.LittleEndian, uint16(len(e.ConnectionId))); err != nil { + return err + } + if _, err := buf.WriteString(e.ConnectionId); err != nil { + return err + } + return nil +} + +func (e *ExternalConnectionResponse) String() string { + return fmt.Sprint("IsConnected: {", e.IsConnected, "} ", + "ConnectionId: {", e.ConnectionId, "}") +} + +// MESSAGE ClientGuidRefreshRequest +type ClientGuidRefreshRequest struct { +} + +func (c *ClientGuidRefreshRequest) Size() uint32 { + return 0 +} + +func (c *ClientGuidRefreshRequest) Unpack(buf *bytes.Buffer) error { + return nil +} + +func (c *ClientGuidRefreshRequest) Pack(buf *bytes.Buffer) error { + return nil +} + +func (c *ClientGuidRefreshRequest) String() string { + return "" +} + +// MESSAGE ClientGuidRefreshResponse +type ClientGuidRefreshResponse struct { +} + +func (c *ClientGuidRefreshResponse) Size() uint32 { + return 0 +} + +func (c *ClientGuidRefreshResponse) Unpack(buf *bytes.Buffer) error { + return nil +} + +func (c *ClientGuidRefreshResponse) Pack(buf *bytes.Buffer) error { + return nil +} + +func (c *ClientGuidRefreshResponse) String() string { + return "" +} + +// MESSAGE SdkProxyRequest +type SdkProxyRequest struct { + ClientGuid string + MessageId string + Path string + Json string +} + +func (s *SdkProxyRequest) Size() uint32 { + var result uint32 + result += 1 // ClientGuid length (uint_8) + result += uint32(len(s.ClientGuid)) // uint_8 array + result += 1 // MessageId length (uint_8) + result += uint32(len(s.MessageId)) // uint_8 array + result += 1 // Path length (uint_8) + result += uint32(len(s.Path)) // uint_8 array + result += 2 // Json length (uint_16) + result += uint32(len(s.Json)) // uint_8 array + return result +} + +func (s *SdkProxyRequest) Unpack(buf *bytes.Buffer) error { + var ClientGuidLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &ClientGuidLen); err != nil { + return err + } + s.ClientGuid = string(buf.Next(int(ClientGuidLen))) + if len(s.ClientGuid) != int(ClientGuidLen) { + return errors.New("string byte mismatch") + } + var MessageIdLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &MessageIdLen); err != nil { + return err + } + s.MessageId = string(buf.Next(int(MessageIdLen))) + if len(s.MessageId) != int(MessageIdLen) { + return errors.New("string byte mismatch") + } + var PathLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &PathLen); err != nil { + return err + } + s.Path = string(buf.Next(int(PathLen))) + if len(s.Path) != int(PathLen) { + return errors.New("string byte mismatch") + } + var JsonLen uint16 + if err := binary.Read(buf, binary.LittleEndian, &JsonLen); err != nil { + return err + } + s.Json = string(buf.Next(int(JsonLen))) + if len(s.Json) != int(JsonLen) { + return errors.New("string byte mismatch") + } + return nil +} + +func (s *SdkProxyRequest) Pack(buf *bytes.Buffer) error { + if len(s.ClientGuid) > 255 { + return errors.New("max_length overflow in field ClientGuid") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(s.ClientGuid))); err != nil { + return err + } + if _, err := buf.WriteString(s.ClientGuid); err != nil { + return err + } + if len(s.MessageId) > 255 { + return errors.New("max_length overflow in field MessageId") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(s.MessageId))); err != nil { + return err + } + if _, err := buf.WriteString(s.MessageId); err != nil { + return err + } + if len(s.Path) > 255 { + return errors.New("max_length overflow in field Path") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(s.Path))); err != nil { + return err + } + if _, err := buf.WriteString(s.Path); err != nil { + return err + } + if len(s.Json) > 65535 { + return errors.New("max_length overflow in field Json") + } + if err := binary.Write(buf, binary.LittleEndian, uint16(len(s.Json))); err != nil { + return err + } + if _, err := buf.WriteString(s.Json); err != nil { + return err + } + return nil +} + +func (s *SdkProxyRequest) String() string { + return fmt.Sprint("ClientGuid: {", s.ClientGuid, "} ", + "MessageId: {", s.MessageId, "} ", + "Path: {", s.Path, "} ", + "Json: {", s.Json, "}") +} + +// MESSAGE SdkProxyResponse +type SdkProxyResponse struct { + MessageId string + StatusCode uint16 + ContentType string + Content string +} + +func (s *SdkProxyResponse) Size() uint32 { + var result uint32 + result += 1 // MessageId length (uint_8) + result += uint32(len(s.MessageId)) // uint_8 array + result += 2 // StatusCode uint_16 + result += 1 // ContentType length (uint_8) + result += uint32(len(s.ContentType)) // uint_8 array + result += 2 // Content length (uint_16) + result += uint32(len(s.Content)) // uint_8 array + return result +} + +func (s *SdkProxyResponse) Unpack(buf *bytes.Buffer) error { + var MessageIdLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &MessageIdLen); err != nil { + return err + } + s.MessageId = string(buf.Next(int(MessageIdLen))) + if len(s.MessageId) != int(MessageIdLen) { + return errors.New("string byte mismatch") + } + if err := binary.Read(buf, binary.LittleEndian, &s.StatusCode); err != nil { + return err + } + var ContentTypeLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &ContentTypeLen); err != nil { + return err + } + s.ContentType = string(buf.Next(int(ContentTypeLen))) + if len(s.ContentType) != int(ContentTypeLen) { + return errors.New("string byte mismatch") + } + var ContentLen uint16 + if err := binary.Read(buf, binary.LittleEndian, &ContentLen); err != nil { + return err + } + s.Content = string(buf.Next(int(ContentLen))) + if len(s.Content) != int(ContentLen) { + return errors.New("string byte mismatch") + } + return nil +} + +func (s *SdkProxyResponse) Pack(buf *bytes.Buffer) error { + if len(s.MessageId) > 255 { + return errors.New("max_length overflow in field MessageId") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(s.MessageId))); err != nil { + return err + } + if _, err := buf.WriteString(s.MessageId); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, s.StatusCode); err != nil { + return err + } + if len(s.ContentType) > 255 { + return errors.New("max_length overflow in field ContentType") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(s.ContentType))); err != nil { + return err + } + if _, err := buf.WriteString(s.ContentType); err != nil { + return err + } + if len(s.Content) > 65535 { + return errors.New("max_length overflow in field Content") + } + if err := binary.Write(buf, binary.LittleEndian, uint16(len(s.Content))); err != nil { + return err + } + if _, err := buf.WriteString(s.Content); err != nil { + return err + } + return nil +} + +func (s *SdkProxyResponse) String() string { + return fmt.Sprint("MessageId: {", s.MessageId, "} ", + "StatusCode: {", s.StatusCode, "} ", + "ContentType: {", s.ContentType, "} ", + "Content: {", s.Content, "}") +} + +// UNION SwitchboardRequest +type SwitchboardRequestTag uint8 + +const ( + SwitchboardRequestTag_SwitchboardError SwitchboardRequestTag = 0x1 // 1 + SwitchboardRequestTag_AuthRequest SwitchboardRequestTag = 0x2 // 2 + SwitchboardRequestTag_JwtRequest SwitchboardRequestTag = 0x3 // 3 + SwitchboardRequestTag_ExternalConnectionRequest SwitchboardRequestTag = 0x4 // 4 + SwitchboardRequestTag_ExternalConnectionResponse SwitchboardRequestTag = 0x5 // 5 + SwitchboardRequestTag_ClientGuidRefreshResponse SwitchboardRequestTag = 0x6 // 6 + SwitchboardRequestTag_SdkProxyResponse SwitchboardRequestTag = 0x7 // 7 + SwitchboardRequestTag_INVALID SwitchboardRequestTag = 255 +) + +type SwitchboardRequest struct { + tag *SwitchboardRequestTag + value clad.Struct +} + +func (m *SwitchboardRequest) Tag() SwitchboardRequestTag { + if m.tag == nil { + return SwitchboardRequestTag_INVALID + } + return *m.tag +} + +func (m *SwitchboardRequest) Size() uint32 { + if m.tag == nil || *m.tag == SwitchboardRequestTag_INVALID { + return 1 + } + return 1 + m.value.Size() +} + +func (m *SwitchboardRequest) Pack(buf *bytes.Buffer) error { + tag := SwitchboardRequestTag_INVALID + if m.tag != nil { + tag = *m.tag + } + if err := binary.Write(buf, binary.LittleEndian, tag); err != nil { + return err + } + if tag == SwitchboardRequestTag_INVALID { + return nil + } + return m.value.Pack(buf) +} + +func (m *SwitchboardRequest) unpackStruct(tag SwitchboardRequestTag, buf *bytes.Buffer) (clad.Struct, error) { + switch tag { + case SwitchboardRequestTag_SwitchboardError: + var ret SwitchboardError + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case SwitchboardRequestTag_AuthRequest: + var ret AuthRequest + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case SwitchboardRequestTag_JwtRequest: + var ret JwtRequest + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case SwitchboardRequestTag_ExternalConnectionRequest: + var ret ExternalConnectionRequest + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case SwitchboardRequestTag_ExternalConnectionResponse: + var ret ExternalConnectionResponse + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case SwitchboardRequestTag_ClientGuidRefreshResponse: + var ret ClientGuidRefreshResponse + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case SwitchboardRequestTag_SdkProxyResponse: + var ret SdkProxyResponse + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + default: + return nil, errors.New("invalid tag to unpackStruct") + } +} + +func (m *SwitchboardRequest) Unpack(buf *bytes.Buffer) error { + tag := SwitchboardRequestTag_INVALID + if err := binary.Read(buf, binary.LittleEndian, &tag); err != nil { + return err + } + m.tag = &tag + if tag == SwitchboardRequestTag_INVALID { + m.value = nil + return nil + } + val, err := m.unpackStruct(tag, buf) + if err != nil { + *m.tag = SwitchboardRequestTag_INVALID + return err + } + m.value = val + return nil +} + +func (t SwitchboardRequestTag) String() string { + switch t { + case SwitchboardRequestTag_SwitchboardError: + return "SwitchboardError" + case SwitchboardRequestTag_AuthRequest: + return "AuthRequest" + case SwitchboardRequestTag_JwtRequest: + return "JwtRequest" + case SwitchboardRequestTag_ExternalConnectionRequest: + return "ExternalConnectionRequest" + case SwitchboardRequestTag_ExternalConnectionResponse: + return "ExternalConnectionResponse" + case SwitchboardRequestTag_ClientGuidRefreshResponse: + return "ClientGuidRefreshResponse" + case SwitchboardRequestTag_SdkProxyResponse: + return "SdkProxyResponse" + default: + return "INVALID" + } +} + +func (m *SwitchboardRequest) String() string { + if m.tag == nil { + return "nil" + } + if *m.tag == SwitchboardRequestTag_INVALID { + return "INVALID" + } + return fmt.Sprintf("%s: {%s}", *m.tag, m.value) +} + +func (m *SwitchboardRequest) GetSwitchboardError() *SwitchboardError { + if m.tag == nil || *m.tag != SwitchboardRequestTag_SwitchboardError { + return nil + } + return m.value.(*SwitchboardError) +} + +func (m *SwitchboardRequest) SetSwitchboardError(value *SwitchboardError) { + newTag := SwitchboardRequestTag_SwitchboardError + m.tag = &newTag + m.value = value +} + +func NewSwitchboardRequestWithSwitchboardError(value *SwitchboardError) *SwitchboardRequest { + var ret SwitchboardRequest + ret.SetSwitchboardError(value) + return &ret +} + +func (m *SwitchboardRequest) GetAuthRequest() *AuthRequest { + if m.tag == nil || *m.tag != SwitchboardRequestTag_AuthRequest { + return nil + } + return m.value.(*AuthRequest) +} + +func (m *SwitchboardRequest) SetAuthRequest(value *AuthRequest) { + newTag := SwitchboardRequestTag_AuthRequest + m.tag = &newTag + m.value = value +} + +func NewSwitchboardRequestWithAuthRequest(value *AuthRequest) *SwitchboardRequest { + var ret SwitchboardRequest + ret.SetAuthRequest(value) + return &ret +} + +func (m *SwitchboardRequest) GetJwtRequest() *JwtRequest { + if m.tag == nil || *m.tag != SwitchboardRequestTag_JwtRequest { + return nil + } + return m.value.(*JwtRequest) +} + +func (m *SwitchboardRequest) SetJwtRequest(value *JwtRequest) { + newTag := SwitchboardRequestTag_JwtRequest + m.tag = &newTag + m.value = value +} + +func NewSwitchboardRequestWithJwtRequest(value *JwtRequest) *SwitchboardRequest { + var ret SwitchboardRequest + ret.SetJwtRequest(value) + return &ret +} + +func (m *SwitchboardRequest) GetExternalConnectionRequest() *ExternalConnectionRequest { + if m.tag == nil || *m.tag != SwitchboardRequestTag_ExternalConnectionRequest { + return nil + } + return m.value.(*ExternalConnectionRequest) +} + +func (m *SwitchboardRequest) SetExternalConnectionRequest(value *ExternalConnectionRequest) { + newTag := SwitchboardRequestTag_ExternalConnectionRequest + m.tag = &newTag + m.value = value +} + +func NewSwitchboardRequestWithExternalConnectionRequest(value *ExternalConnectionRequest) *SwitchboardRequest { + var ret SwitchboardRequest + ret.SetExternalConnectionRequest(value) + return &ret +} + +func (m *SwitchboardRequest) GetExternalConnectionResponse() *ExternalConnectionResponse { + if m.tag == nil || *m.tag != SwitchboardRequestTag_ExternalConnectionResponse { + return nil + } + return m.value.(*ExternalConnectionResponse) +} + +func (m *SwitchboardRequest) SetExternalConnectionResponse(value *ExternalConnectionResponse) { + newTag := SwitchboardRequestTag_ExternalConnectionResponse + m.tag = &newTag + m.value = value +} + +func NewSwitchboardRequestWithExternalConnectionResponse(value *ExternalConnectionResponse) *SwitchboardRequest { + var ret SwitchboardRequest + ret.SetExternalConnectionResponse(value) + return &ret +} + +func (m *SwitchboardRequest) GetClientGuidRefreshResponse() *ClientGuidRefreshResponse { + if m.tag == nil || *m.tag != SwitchboardRequestTag_ClientGuidRefreshResponse { + return nil + } + return m.value.(*ClientGuidRefreshResponse) +} + +func (m *SwitchboardRequest) SetClientGuidRefreshResponse(value *ClientGuidRefreshResponse) { + newTag := SwitchboardRequestTag_ClientGuidRefreshResponse + m.tag = &newTag + m.value = value +} + +func NewSwitchboardRequestWithClientGuidRefreshResponse(value *ClientGuidRefreshResponse) *SwitchboardRequest { + var ret SwitchboardRequest + ret.SetClientGuidRefreshResponse(value) + return &ret +} + +func (m *SwitchboardRequest) GetSdkProxyResponse() *SdkProxyResponse { + if m.tag == nil || *m.tag != SwitchboardRequestTag_SdkProxyResponse { + return nil + } + return m.value.(*SdkProxyResponse) +} + +func (m *SwitchboardRequest) SetSdkProxyResponse(value *SdkProxyResponse) { + newTag := SwitchboardRequestTag_SdkProxyResponse + m.tag = &newTag + m.value = value +} + +func NewSwitchboardRequestWithSdkProxyResponse(value *SdkProxyResponse) *SwitchboardRequest { + var ret SwitchboardRequest + ret.SetSdkProxyResponse(value) + return &ret +} + +// UNION SwitchboardResponse +type SwitchboardResponseTag uint8 + +const ( + SwitchboardResponseTag_SwitchboardError SwitchboardResponseTag = 0x1 // 1 + SwitchboardResponseTag_AuthResponse SwitchboardResponseTag = 0x2 // 2 + SwitchboardResponseTag_JwtResponse SwitchboardResponseTag = 0x3 // 3 + SwitchboardResponseTag_ExternalConnectionRequest SwitchboardResponseTag = 0x4 // 4 + SwitchboardResponseTag_ExternalConnectionResponse SwitchboardResponseTag = 0x5 // 5 + SwitchboardResponseTag_ClientGuidRefreshRequest SwitchboardResponseTag = 0x6 // 6 + SwitchboardResponseTag_SdkProxyRequest SwitchboardResponseTag = 0x7 // 7 + SwitchboardResponseTag_INVALID SwitchboardResponseTag = 255 +) + +type SwitchboardResponse struct { + tag *SwitchboardResponseTag + value clad.Struct +} + +func (m *SwitchboardResponse) Tag() SwitchboardResponseTag { + if m.tag == nil { + return SwitchboardResponseTag_INVALID + } + return *m.tag +} + +func (m *SwitchboardResponse) Size() uint32 { + if m.tag == nil || *m.tag == SwitchboardResponseTag_INVALID { + return 1 + } + return 1 + m.value.Size() +} + +func (m *SwitchboardResponse) Pack(buf *bytes.Buffer) error { + tag := SwitchboardResponseTag_INVALID + if m.tag != nil { + tag = *m.tag + } + if err := binary.Write(buf, binary.LittleEndian, tag); err != nil { + return err + } + if tag == SwitchboardResponseTag_INVALID { + return nil + } + return m.value.Pack(buf) +} + +func (m *SwitchboardResponse) unpackStruct(tag SwitchboardResponseTag, buf *bytes.Buffer) (clad.Struct, error) { + switch tag { + case SwitchboardResponseTag_SwitchboardError: + var ret SwitchboardError + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case SwitchboardResponseTag_AuthResponse: + var ret AuthResponse + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case SwitchboardResponseTag_JwtResponse: + var ret JwtResponse + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case SwitchboardResponseTag_ExternalConnectionRequest: + var ret ExternalConnectionRequest + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case SwitchboardResponseTag_ExternalConnectionResponse: + var ret ExternalConnectionResponse + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case SwitchboardResponseTag_ClientGuidRefreshRequest: + var ret ClientGuidRefreshRequest + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + case SwitchboardResponseTag_SdkProxyRequest: + var ret SdkProxyRequest + if err := ret.Unpack(buf); err != nil { + return nil, err + } + return &ret, nil + default: + return nil, errors.New("invalid tag to unpackStruct") + } +} + +func (m *SwitchboardResponse) Unpack(buf *bytes.Buffer) error { + tag := SwitchboardResponseTag_INVALID + if err := binary.Read(buf, binary.LittleEndian, &tag); err != nil { + return err + } + m.tag = &tag + if tag == SwitchboardResponseTag_INVALID { + m.value = nil + return nil + } + val, err := m.unpackStruct(tag, buf) + if err != nil { + *m.tag = SwitchboardResponseTag_INVALID + return err + } + m.value = val + return nil +} + +func (t SwitchboardResponseTag) String() string { + switch t { + case SwitchboardResponseTag_SwitchboardError: + return "SwitchboardError" + case SwitchboardResponseTag_AuthResponse: + return "AuthResponse" + case SwitchboardResponseTag_JwtResponse: + return "JwtResponse" + case SwitchboardResponseTag_ExternalConnectionRequest: + return "ExternalConnectionRequest" + case SwitchboardResponseTag_ExternalConnectionResponse: + return "ExternalConnectionResponse" + case SwitchboardResponseTag_ClientGuidRefreshRequest: + return "ClientGuidRefreshRequest" + case SwitchboardResponseTag_SdkProxyRequest: + return "SdkProxyRequest" + default: + return "INVALID" + } +} + +func (m *SwitchboardResponse) String() string { + if m.tag == nil { + return "nil" + } + if *m.tag == SwitchboardResponseTag_INVALID { + return "INVALID" + } + return fmt.Sprintf("%s: {%s}", *m.tag, m.value) +} + +func (m *SwitchboardResponse) GetSwitchboardError() *SwitchboardError { + if m.tag == nil || *m.tag != SwitchboardResponseTag_SwitchboardError { + return nil + } + return m.value.(*SwitchboardError) +} + +func (m *SwitchboardResponse) SetSwitchboardError(value *SwitchboardError) { + newTag := SwitchboardResponseTag_SwitchboardError + m.tag = &newTag + m.value = value +} + +func NewSwitchboardResponseWithSwitchboardError(value *SwitchboardError) *SwitchboardResponse { + var ret SwitchboardResponse + ret.SetSwitchboardError(value) + return &ret +} + +func (m *SwitchboardResponse) GetAuthResponse() *AuthResponse { + if m.tag == nil || *m.tag != SwitchboardResponseTag_AuthResponse { + return nil + } + return m.value.(*AuthResponse) +} + +func (m *SwitchboardResponse) SetAuthResponse(value *AuthResponse) { + newTag := SwitchboardResponseTag_AuthResponse + m.tag = &newTag + m.value = value +} + +func NewSwitchboardResponseWithAuthResponse(value *AuthResponse) *SwitchboardResponse { + var ret SwitchboardResponse + ret.SetAuthResponse(value) + return &ret +} + +func (m *SwitchboardResponse) GetJwtResponse() *JwtResponse { + if m.tag == nil || *m.tag != SwitchboardResponseTag_JwtResponse { + return nil + } + return m.value.(*JwtResponse) +} + +func (m *SwitchboardResponse) SetJwtResponse(value *JwtResponse) { + newTag := SwitchboardResponseTag_JwtResponse + m.tag = &newTag + m.value = value +} + +func NewSwitchboardResponseWithJwtResponse(value *JwtResponse) *SwitchboardResponse { + var ret SwitchboardResponse + ret.SetJwtResponse(value) + return &ret +} + +func (m *SwitchboardResponse) GetExternalConnectionRequest() *ExternalConnectionRequest { + if m.tag == nil || *m.tag != SwitchboardResponseTag_ExternalConnectionRequest { + return nil + } + return m.value.(*ExternalConnectionRequest) +} + +func (m *SwitchboardResponse) SetExternalConnectionRequest(value *ExternalConnectionRequest) { + newTag := SwitchboardResponseTag_ExternalConnectionRequest + m.tag = &newTag + m.value = value +} + +func NewSwitchboardResponseWithExternalConnectionRequest(value *ExternalConnectionRequest) *SwitchboardResponse { + var ret SwitchboardResponse + ret.SetExternalConnectionRequest(value) + return &ret +} + +func (m *SwitchboardResponse) GetExternalConnectionResponse() *ExternalConnectionResponse { + if m.tag == nil || *m.tag != SwitchboardResponseTag_ExternalConnectionResponse { + return nil + } + return m.value.(*ExternalConnectionResponse) +} + +func (m *SwitchboardResponse) SetExternalConnectionResponse(value *ExternalConnectionResponse) { + newTag := SwitchboardResponseTag_ExternalConnectionResponse + m.tag = &newTag + m.value = value +} + +func NewSwitchboardResponseWithExternalConnectionResponse(value *ExternalConnectionResponse) *SwitchboardResponse { + var ret SwitchboardResponse + ret.SetExternalConnectionResponse(value) + return &ret +} + +func (m *SwitchboardResponse) GetClientGuidRefreshRequest() *ClientGuidRefreshRequest { + if m.tag == nil || *m.tag != SwitchboardResponseTag_ClientGuidRefreshRequest { + return nil + } + return m.value.(*ClientGuidRefreshRequest) +} + +func (m *SwitchboardResponse) SetClientGuidRefreshRequest(value *ClientGuidRefreshRequest) { + newTag := SwitchboardResponseTag_ClientGuidRefreshRequest + m.tag = &newTag + m.value = value +} + +func NewSwitchboardResponseWithClientGuidRefreshRequest(value *ClientGuidRefreshRequest) *SwitchboardResponse { + var ret SwitchboardResponse + ret.SetClientGuidRefreshRequest(value) + return &ret +} + +func (m *SwitchboardResponse) GetSdkProxyRequest() *SdkProxyRequest { + if m.tag == nil || *m.tag != SwitchboardResponseTag_SdkProxyRequest { + return nil + } + return m.value.(*SdkProxyRequest) +} + +func (m *SwitchboardResponse) SetSdkProxyRequest(value *SdkProxyRequest) { + newTag := SwitchboardResponseTag_SdkProxyRequest + m.tag = &newTag + m.value = value +} + +func NewSwitchboardResponseWithSdkProxyRequest(value *SdkProxyRequest) *SwitchboardResponse { + var ret SwitchboardResponse + ret.SetSdkProxyRequest(value) + return &ret +} diff --git a/vector-cloud/internal/clad/vision/offboardVision.go b/vector-cloud/internal/clad/vision/offboardVision.go new file mode 100644 index 0000000..eaecea8 --- /dev/null +++ b/vector-cloud/internal/clad/vision/offboardVision.go @@ -0,0 +1,204 @@ +// Autogenerated Go message buffer code. +// Source: offboardVision.clad +// Full command line: victor-clad/tools/message-buffers/emitters/Go_emitter.py -C clad/types -I coretech/vision/clad_src coretech/common/clad_src -o generated/cladgo/src/clad/vision offboardVision.clad + +package vision + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" +) + +// ENUM OffboardCommsType +type OffboardCommsType uint8 + +const ( + OffboardCommsType_FileIO OffboardCommsType = iota + OffboardCommsType_CLAD +) + +// STRUCTURE OffboardImageReady +type OffboardImageReady struct { + Timestamp uint32 + NumRows uint32 + NumCols uint32 + NumChannels uint32 + IsCompressed bool + IsEncrypted bool + ProcTypes []string + Filename string +} + +func (o *OffboardImageReady) Size() uint32 { + var result uint32 + result += 4 // Timestamp uint_32 + result += 4 // NumRows uint_32 + result += 4 // NumCols uint_32 + result += 4 // NumChannels uint_32 + result += 1 // IsCompressed bool + result += 1 // IsEncrypted bool + result += 1 // ProcTypes length (uint_8) + for idx := range o.ProcTypes { + result += 1 // ProcTypes[idx] length (uint_8) + result += uint32(len(o.ProcTypes[idx])) // uint_8 array + } + result += 1 // Filename length (uint_8) + result += uint32(len(o.Filename)) // uint_8 array + return result +} + +func (o *OffboardImageReady) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &o.Timestamp); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &o.NumRows); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &o.NumCols); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &o.NumChannels); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &o.IsCompressed); err != nil { + return err + } + if err := binary.Read(buf, binary.LittleEndian, &o.IsEncrypted); err != nil { + return err + } + var ProcTypesLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &ProcTypesLen); err != nil { + return err + } + o.ProcTypes = make([]string, ProcTypesLen) + for idx := range o.ProcTypes { + var ProcTypesidxLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &ProcTypesidxLen); err != nil { + return err + } + o.ProcTypes[idx] = string(buf.Next(int(ProcTypesidxLen))) + if len(o.ProcTypes[idx]) != int(ProcTypesidxLen) { + return errors.New("string byte mismatch") + } + } + var FilenameLen uint8 + if err := binary.Read(buf, binary.LittleEndian, &FilenameLen); err != nil { + return err + } + o.Filename = string(buf.Next(int(FilenameLen))) + if len(o.Filename) != int(FilenameLen) { + return errors.New("string byte mismatch") + } + return nil +} + +func (o *OffboardImageReady) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, o.Timestamp); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, o.NumRows); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, o.NumCols); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, o.NumChannels); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, o.IsCompressed); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, o.IsEncrypted); err != nil { + return err + } + if len(o.ProcTypes) > 255 { + return errors.New("max_length overflow in field ProcTypes") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(o.ProcTypes))); err != nil { + return err + } + for idx := range o.ProcTypes { + if len(o.ProcTypes[idx]) > 255 { + return errors.New("max_length overflow in field ProcTypes[idx]") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(o.ProcTypes[idx]))); err != nil { + return err + } + if _, err := buf.WriteString(o.ProcTypes[idx]); err != nil { + return err + } + } + if len(o.Filename) > 255 { + return errors.New("max_length overflow in field Filename") + } + if err := binary.Write(buf, binary.LittleEndian, uint8(len(o.Filename))); err != nil { + return err + } + if _, err := buf.WriteString(o.Filename); err != nil { + return err + } + return nil +} + +func (o *OffboardImageReady) String() string { + return fmt.Sprint("Timestamp: {", o.Timestamp, "} ", + "NumRows: {", o.NumRows, "} ", + "NumCols: {", o.NumCols, "} ", + "NumChannels: {", o.NumChannels, "} ", + "IsCompressed: {", o.IsCompressed, "} ", + "IsEncrypted: {", o.IsEncrypted, "} ", + "ProcTypes: {", o.ProcTypes, "} ", + "Filename: {", o.Filename, "}") +} + +// STRUCTURE OffboardResultReady +type OffboardResultReady struct { + Timestamp uint32 + JsonResult string +} + +func (o *OffboardResultReady) Size() uint32 { + var result uint32 + result += 4 // Timestamp uint_32 + result += 2 // JsonResult length (uint_16) + result += uint32(len(o.JsonResult)) // uint_8 array + return result +} + +func (o *OffboardResultReady) Unpack(buf *bytes.Buffer) error { + if err := binary.Read(buf, binary.LittleEndian, &o.Timestamp); err != nil { + return err + } + var JsonResultLen uint16 + if err := binary.Read(buf, binary.LittleEndian, &JsonResultLen); err != nil { + return err + } + o.JsonResult = string(buf.Next(int(JsonResultLen))) + if len(o.JsonResult) != int(JsonResultLen) { + return errors.New("string byte mismatch") + } + return nil +} + +func (o *OffboardResultReady) Pack(buf *bytes.Buffer) error { + if err := binary.Write(buf, binary.LittleEndian, o.Timestamp); err != nil { + return err + } + if len(o.JsonResult) > 65535 { + return errors.New("max_length overflow in field JsonResult") + } + if err := binary.Write(buf, binary.LittleEndian, uint16(len(o.JsonResult))); err != nil { + return err + } + if _, err := buf.WriteString(o.JsonResult); err != nil { + return err + } + return nil +} + +func (o *OffboardResultReady) String() string { + return fmt.Sprint("Timestamp: {", o.Timestamp, "} ", + "JsonResult: {", o.JsonResult, "}") +} diff --git a/vector-cloud/internal/cloudproc/cloudproc.go b/vector-cloud/internal/cloudproc/cloudproc.go new file mode 100644 index 0000000..ad61734 --- /dev/null +++ b/vector-cloud/internal/cloudproc/cloudproc.go @@ -0,0 +1,95 @@ +package cloudproc + +import ( + "context" + "sync" + + "github.com/digital-dream-labs/vector-cloud/internal/log" + "github.com/digital-dream-labs/vector-cloud/internal/logcollector" + "github.com/digital-dream-labs/vector-cloud/internal/offboard_vision" + "github.com/digital-dream-labs/vector-cloud/internal/token" + "github.com/digital-dream-labs/vector-cloud/internal/token/identity" + "github.com/digital-dream-labs/vector-cloud/internal/voice" + + "github.com/digital-dream-labs/vector-cloud/internal/jdocs" +) + +var devServer func() error + +func Run(ctx context.Context, procOptions ...Option) { + var opts options + for _, o := range procOptions { + o(&opts) + } + + var wg sync.WaitGroup + if devServer != nil { + launchProcess(&wg, func() { + if err := devServer(); err != nil { + log.Println("dev HTTP server reported error:", err) + } + }) + } + // start token service synchronously since everything else depends on it + identityProvider := opts.identityProvider + if identityProvider == nil { + // If the identity provider is not provided as an option, we create a default + // file backed identity provider with platform specific storage paths for JWT + // and certs (see getcert_*.go files) + var err error + if identityProvider, err = identity.NewFileProvider("", ""); err != nil { + log.Println("Fatal error initializing default identity provider:", err) + return + } + } + + var tokenServer = new(token.Server) + if err := tokenServer.Init(identityProvider); err != nil { + log.Println("Fatal error initializing token service:", err) + return + } + addHandlers(token.GetDevHandlers, tokenServer) + launchProcess(&wg, func() { + tokenServer.Run(ctx, opts.tokenOpts...) + }) + tokener := token.GetAccessor(identityProvider, tokenServer) + if opts.voice != nil { + launchProcess(&wg, func() { + // provide default token accessor + voiceOpts := append([]voice.Option{voice.WithTokener(tokener), + voice.WithErrorListener(tokenServer.ErrorListener())}, + opts.voiceOpts...) + opts.voice.Run(ctx, voiceOpts...) + }) + } + if opts.jdocOpts != nil { + launchProcess(&wg, func() { + // provide default token accessor + jdocOpts := append([]jdocs.Option{jdocs.WithTokener(tokener), + jdocs.WithErrorListener(tokenServer.ErrorListener())}, + opts.jdocOpts...) + jdocs.Run(ctx, jdocOpts...) + }) + } + if opts.logcollectorOpts != nil { + launchProcess(&wg, func() { + logcollectorOpts := append([]logcollector.Option{logcollector.WithTokener(tokener), + logcollector.WithErrorListener(tokenServer.ErrorListener())}, + opts.logcollectorOpts...) + logcollector.Run(ctx, logcollectorOpts...) + }) + } + launchProcess(&wg, func() { + offboard_vision.Run(ctx) + }) + addHandlers(offboard_vision.GetDevHandlers, tokenServer) + wg.Wait() +} + +func launchProcess(wg *sync.WaitGroup, launcher func()) { + wg.Add(1) + go func() { + launcher() + wg.Done() + }() +} diff --git a/vector-cloud/internal/cloudproc/dev/connect.go b/vector-cloud/internal/cloudproc/dev/connect.go new file mode 100644 index 0000000..43e31e9 --- /dev/null +++ b/vector-cloud/internal/cloudproc/dev/connect.go @@ -0,0 +1,131 @@ +package dev + +import ( + "context" + "fmt" + "math" + "net/http" + "strings" + "sync" + "time" + + "github.com/digital-dream-labs/vector-cloud/internal/config" +) + +type connection struct { + stamp time.Time + ms int + err error +} + +// an hour's worth of connections, every 5 sec +var connData struct { + m sync.Mutex + storage [720]connection + head int + count int +} + +const connInterval = 5 * time.Second + +func initConnect() { + go connectRoutine() +} + +func connectRoutine() { + i := 0 + circled := false + tick := time.NewTicker(connInterval) + for { + ts := <-tick.C + dest := &connData.storage[i] + i = i + 1 + if i >= len(connData.storage) { + circled = true + i = 0 + } + if circled { + connData.m.Lock() + connData.head = (i + 1) % len(connData.storage) + connData.count-- + connData.m.Unlock() + } + go func() { + // try to connect to OTA server, log how long it took + dest.stamp = ts + ctx, cancel := context.WithTimeout(context.Background(), connInterval) + // construct a HTTP URL from OTA address (something like `ota-cdn.anki.com:443`) + otaURL := "http://" + config.Env.Check + if req, err := http.NewRequest("HEAD", otaURL, nil); err != nil { + dest.ms = -1 + dest.err = err + } else if resp, err := http.DefaultClient.Do(req.WithContext(ctx)); err != nil { + dest.ms = -1 + dest.err = err + } else { + dest.ms = int(time.Now().Sub(ts) / time.Millisecond) + dest.err = nil + resp.Body.Close() + } + cancel() + connData.m.Lock() + connData.count++ + connData.m.Unlock() + }() + } +} + +func connectHandler(rw http.ResponseWriter, r *http.Request) { + connData.m.Lock() + head := connData.head + count := connData.count + connData.m.Unlock() + + tf := func(t time.Time) string { + return t.Format("15:04:05") + } + successStringer := func(arr []*connection) string { + if len(arr) == 0 { + return "" + } + if len(arr) == 1 { + return fmt.Sprintf("
  • %s: success (%d ms)
  • \n", tf(arr[0].stamp), arr[0].ms) + } + min := math.MaxInt32 + max := 0 + var avg int + for _, c := range arr { + if c.ms < min { + min = c.ms + } + if c.ms > max { + max = c.ms + } + avg += c.ms + } + avg = avg / len(arr) + return fmt.Sprintf("
  • %s-%s: %d successes (min time %d ms, max %d, avg %d)
  • \n", + tf(arr[0].stamp), tf(arr[len(arr)-1].stamp), len(arr), min, max, avg) + } + errorStringer := func(c *connection) string { + return fmt.Sprintf("
  • %s: ERROR: %s
  • \n", tf(c.stamp), c.err) + } + + var b strings.Builder + var consecutive []*connection + for i := 0; i < count; i++ { + src := &connData.storage[(head+i)%len(connData.storage)] + if src.err == nil { + consecutive = append(consecutive, src) + } else { + b.WriteString(successStringer(consecutive)) + consecutive = consecutive[:0] + b.WriteString(errorStringer(src)) + } + } + b.WriteString(successStringer(consecutive)) + fmt.Fprintf(rw, "

    Results for last %d connections

    ", count) + fmt.Fprintf(rw, "\n
      ") + fmt.Fprintf(rw, b.String()) + fmt.Fprintf(rw, "\n
        ") +} diff --git a/vector-cloud/internal/cloudproc/dev/dev.go b/vector-cloud/internal/cloudproc/dev/dev.go new file mode 100644 index 0000000..46835e0 --- /dev/null +++ b/vector-cloud/internal/cloudproc/dev/dev.go @@ -0,0 +1,19 @@ +package dev + +import ( + "net/http" + "net/http/pprof" +) + +func Init() { + initConnect() +} + +func AddHandlers(mux *http.ServeMux) { + mux.HandleFunc("/debug/pprof/", pprof.Index) + mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) + mux.HandleFunc("/debug/pprof/profile", pprof.Profile) + mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + mux.HandleFunc("/debug/pprof/trace", pprof.Trace) + mux.HandleFunc("/connect.html", connectHandler) +} diff --git a/vector-cloud/internal/cloudproc/dev/noshipping.go b/vector-cloud/internal/cloudproc/dev/noshipping.go new file mode 100644 index 0000000..d458afb --- /dev/null +++ b/vector-cloud/internal/cloudproc/dev/noshipping.go @@ -0,0 +1,7 @@ +// +build shipping + +package dev + +// intentional build error to make sure this is never built in shipping + +DO_NOT_BUILD_THIS_PACKAGE_IN_SHIPPING diff --git a/vector-cloud/internal/cloudproc/harness/harness.go b/vector-cloud/internal/cloudproc/harness/harness.go new file mode 100644 index 0000000..16ec843 --- /dev/null +++ b/vector-cloud/internal/cloudproc/harness/harness.go @@ -0,0 +1,41 @@ +package harness + +import ( + "context" + + "github.com/digital-dream-labs/vector-cloud/internal/clad/cloud" + + "github.com/digital-dream-labs/vector-cloud/internal/cloudproc" + "github.com/digital-dream-labs/vector-cloud/internal/voice" +) + +type Harness interface { + voice.MsgIO + ReadMessage() (*cloud.Message, error) +} + +type memHarness struct { + voice.MsgIO + intent chan *cloud.Message +} + +func (h *memHarness) ReadMessage() (*cloud.Message, error) { + return <-h.intent, nil +} + +func CreateMemProcess(ctx context.Context, options ...cloudproc.Option) (Harness, error) { + intentResult := make(chan *cloud.Message) + + io, receiver := voice.NewMemPipe() + process := &voice.Process{} + process.AddReceiver(receiver) + process.AddIntentWriter(&voice.ChanMsgSender{Ch: intentResult}) + + options = append(options, cloudproc.WithVoice(process)) + + go cloudproc.Run(ctx, options...) + + return &memHarness{ + MsgIO: io, + intent: intentResult}, nil +} diff --git a/vector-cloud/internal/cloudproc/opts.go b/vector-cloud/internal/cloudproc/opts.go new file mode 100644 index 0000000..a06bffb --- /dev/null +++ b/vector-cloud/internal/cloudproc/opts.go @@ -0,0 +1,64 @@ +package cloudproc + +import ( + "github.com/digital-dream-labs/vector-cloud/internal/jdocs" + "github.com/digital-dream-labs/vector-cloud/internal/logcollector" + "github.com/digital-dream-labs/vector-cloud/internal/token" + "github.com/digital-dream-labs/vector-cloud/internal/token/identity" + "github.com/digital-dream-labs/vector-cloud/internal/voice" +) + +type Option func(o *options) + +type options struct { + voice *voice.Process + identityProvider identity.Provider + voiceOpts []voice.Option + tokenOpts []token.Option + jdocOpts []jdocs.Option + logcollectorOpts []logcollector.Option +} + +func WithVoice(process *voice.Process) Option { + return func(o *options) { + o.voice = process + } +} + +func WithIdentityProvider(identityProvider identity.Provider) Option { + return func(o *options) { + o.identityProvider = identityProvider + } +} + +func WithVoiceOptions(voiceOptions ...voice.Option) Option { + return func(o *options) { + o.voiceOpts = append(o.voiceOpts, voiceOptions...) + } +} + +func WithTokenOptions(tokenOptions ...token.Option) Option { + return func(o *options) { + o.tokenOpts = append(o.tokenOpts, tokenOptions...) + } +} + +func WithJdocs(jdocOptions ...jdocs.Option) Option { + return func(o *options) { + o.jdocOpts = append(o.jdocOpts, jdocOptions...) + if o.jdocOpts == nil { + // even if no options specified, code is saying "run jdocs plz" by calling this + o.jdocOpts = []jdocs.Option{} + } + } +} + +func WithLogCollectorOptions(logcollectorOptions ...logcollector.Option) Option { + return func(o *options) { + o.logcollectorOpts = append(o.logcollectorOpts, logcollectorOptions...) + if o.logcollectorOpts == nil { + // even if no options specified, code is saying "run log collector plz" by calling this + o.logcollectorOpts = []logcollector.Option{} + } + } +} diff --git a/vector-cloud/internal/cloudproc/server.go b/vector-cloud/internal/cloudproc/server.go new file mode 100644 index 0000000..70bb5ff --- /dev/null +++ b/vector-cloud/internal/cloudproc/server.go @@ -0,0 +1,15 @@ +package cloudproc + +import ( + "net/http" + + "github.com/digital-dream-labs/vector-cloud/internal/token" +) + +var addHandlerFunc func(func(*http.ServeMux), *token.Server) + +func addHandlers(f func(*http.ServeMux), s *token.Server) { + if addHandlerFunc != nil && f != nil && s != nil { + addHandlerFunc(f, s) + } +} diff --git a/vector-cloud/internal/cloudproc/server_dev.go b/vector-cloud/internal/cloudproc/server_dev.go new file mode 100644 index 0000000..97c29ed --- /dev/null +++ b/vector-cloud/internal/cloudproc/server_dev.go @@ -0,0 +1,29 @@ +// +build !shipping + +package cloudproc + +import ( + "net/http" + + "github.com/digital-dream-labs/vector-cloud/internal/cloudproc/dev" + "github.com/digital-dream-labs/vector-cloud/internal/token" +) + +var serveMux *http.ServeMux + +func init() { + serveMux = http.NewServeMux() + devServer = launchServer + addHandlerFunc = func(f func(*http.ServeMux), s *token.Server) { + f(serveMux) + token.TokenServer = s + } +} + +func launchServer() error { + fs := http.FileServer(http.Dir("/anki/data/assets/cozmo_resources/webserver/cloud")) + serveMux.Handle("/", fs) + dev.Init() + dev.AddHandlers(serveMux) + return http.ListenAndServe(":8890", serveMux) +} diff --git a/vector-cloud/internal/config/urls.go b/vector-cloud/internal/config/urls.go new file mode 100644 index 0000000..30dd763 --- /dev/null +++ b/vector-cloud/internal/config/urls.go @@ -0,0 +1,62 @@ +package config + +import ( + "encoding/json" + "io/ioutil" +) + +// URLs represents a set of URLs where Anki's cloud services can be reached +type URLs struct { + JDocs string `json:"jdocs"` + Token string `json:"tms"` + Chipper string `json:"chipper"` + Check string `json:"check"` + LogFiles string `json:"logfiles"` + AppKey string `json:"appkey"` + OffboardVision *string `json:"offboard_vision,omitempty"` +} + +// DefaultURLs provides a default, hard-coded configuration that can be used +// if an expected configuration is not found on disk +var DefaultURLs = URLs{ + JDocs: "jdocs-dev.api.anki.com:443", + Token: "token-dev.api.anki.com:443", + Chipper: "chipper-dev.api.anki.com:443", + Check: "conncheck.global.anki-dev-services.com/ok", + LogFiles: "s3://anki-device-logs-dev/victor", + AppKey: "", +} + +// Env represents the URLs associated with the most recent successful call to +// SetGlobal. Before this, it has the same values as DefaultURLs. +var Env = DefaultURLs + +// SetGlobal sets the public Env variable to the URLs in the given filename. If the given +// filename is blank, a known hardcoded location for server_config.json on the robot is used. +func SetGlobal(filename string) error { + urls, err := LoadURLs(filename) + if err != nil { + return err + } + Env = *urls + return nil +} + +var defaultFilename = "/anki/data/assets/cozmo_resources/config/server_config.json" + +// LoadURLs attempts to load a URL config from the given filename. If the given filename +// is blank, a known hardcoded location for server_config.json on the robot is used. +func LoadURLs(filename string) (*URLs, error) { + if filename == "" { + filename = defaultFilename + } + buf, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + var urls URLs + if err := json.Unmarshal(buf, &urls); err != nil { + return nil, err + } + return &urls, nil +} diff --git a/vector-cloud/internal/ipc/baseconn.go b/vector-cloud/internal/ipc/baseconn.go new file mode 100644 index 0000000..be32b95 --- /dev/null +++ b/vector-cloud/internal/ipc/baseconn.go @@ -0,0 +1,98 @@ +package ipc + +import ( + "errors" + "fmt" + "io" + "net" + "sync" + + "github.com/digital-dream-labs/vector-cloud/internal/util" +) + +// baseConn wraps a net.Conn to manage the resources we associate with one (reader routines, channels) +type baseConn struct { + conn io.ReadWriteCloser + kill chan struct{} + read chan []byte + killCloser util.DoOnce + wg sync.WaitGroup +} + +func (c *baseConn) Close() error { + c.killCloser.Do() + ret := c.conn.Close() + c.wg.Wait() // wait for reader routine to close + close(c.read) + return ret +} + +func (c *baseConn) Read() (b []byte) { + select { + case b = <-c.read: + case <-c.kill: + default: + b = []byte{} + } + return +} + +func (c *baseConn) ReadBlock() (b []byte) { + select { + case b = <-c.read: + case <-c.kill: + } + return +} + +func (c *baseConn) Write(b []byte) (int, error) { + if util.CanSelect(c.kill) { + return 0, errors.New("connection has been closed") + } + return c.conn.Write(b) +} + +func newBaseConn(conn io.ReadWriteCloser) Conn { + kill := make(chan struct{}) + ret := &baseConn{ + conn: conn, + kill: kill, + read: make(chan []byte), + killCloser: util.NewDoOnce(func() { close(kill) })} + + // reader thread: puts incoming messages into read channel, waiting to be consumed + ret.wg.Add(1) + go func() { + defer ret.wg.Done() + buf := make([]byte, 32768) + for { + n, err := conn.Read(buf) + if err != nil { + if !util.CanSelect(ret.kill) { + isTimeout := false + if neterr, ok := err.(net.Error); ok && neterr.Timeout() { + isTimeout = true + } + if err == io.EOF || isTimeout { + // unexpected EOF = server probably died + } else { + fmt.Println("Socket couldn't read length:", err) + } + ret.killCloser.Do() + } + break + } + if n == 0 { + continue + } + sendbuf := make([]byte, n) + copy(sendbuf, buf[:n]) + select { + case ret.read <- sendbuf: + case <-ret.kill: + } + } + }() + + return ret +} diff --git a/vector-cloud/internal/ipc/baseserver.go b/vector-cloud/internal/ipc/baseserver.go new file mode 100644 index 0000000..fcce56b --- /dev/null +++ b/vector-cloud/internal/ipc/baseserver.go @@ -0,0 +1,103 @@ +package ipc + +import ( + "fmt" + "net" + "sync" + + "github.com/digital-dream-labs/vector-cloud/internal/util" +) + +type connListener interface { + Accept() (Conn, error) + Close() error +} + +// this is necessary to convert a net.Listener to a connListener +type listenerWrapper struct { + net.Listener +} + +func (l listenerWrapper) Accept() (Conn, error) { + c, err := l.Listener.Accept() + if err != nil { + return nil, err + } + return newBaseConn(c), nil +} + +type baseServer struct { + listen connListener + kill chan struct{} + conns []Conn + newConns chan Conn + wg sync.WaitGroup +} + +type serverConn struct { + Conn + serv *baseServer +} + +func (c *serverConn) Close() error { + c.serv.remove(c.Conn) + return c.Conn.Close() +} + +func newBaseServer(listen connListener) (Server, error) { + serv := &baseServer{listen, make(chan struct{}), make([]Conn, 0, 8), make(chan Conn), sync.WaitGroup{}} + + // start listen routine - adds new connections to array + serv.wg.Add(1) + go func() { + defer serv.wg.Done() + for { + conn, err := serv.listen.Accept() + if err != nil { + // end of life errors are expected if we've been killed + if !util.CanSelect(serv.kill) { + fmt.Println("Listen error:", err) + } + return + } + serv.conns = append(serv.conns, conn) + serv.wg.Add(1) + go func() { + defer serv.wg.Done() + select { + case serv.newConns <- &serverConn{Conn: conn, serv: serv}: + case <-serv.kill: + } + }() + } + }() + + return serv, nil +} + +// Close stops the server from listening and closes all existing connections +func (serv *baseServer) Close() error { + close(serv.kill) + err := serv.listen.Close() + serv.wg.Wait() + for _, conn := range serv.conns { + conn.Close() + } + close(serv.newConns) + return err +} + +func (serv *baseServer) remove(c Conn) { + for i, ic := range serv.conns { + if ic == c { + serv.conns = append(serv.conns[:i], serv.conns[i+1:]...) + return + } + } +} + +// NewConns returns a channel that will be passed new clients upon connection to the server, +// until the server is closed +func (serv *baseServer) NewConns() <-chan Conn { + return serv.newConns +} diff --git a/vector-cloud/internal/ipc/conn.go b/vector-cloud/internal/ipc/conn.go new file mode 100644 index 0000000..f968e46 --- /dev/null +++ b/vector-cloud/internal/ipc/conn.go @@ -0,0 +1,42 @@ +/* +Package ipc provides a basic, dumb implementation of discrete messaging +over various underlying transport protocols. Supported protocols are +three kinds of Unix domain sockets (streaming, sequential packet, +datagram) and local UDP sockets. For stream sockets, messages are +prefixed with a 4 byte size so that the connection knows how many bytes +to pull off for each message, preserving message boundaries. + +The Conn interface represents a connection between two endpoints, and +is returned by all functions that create clients; the Server interface +represents a running server that spawns new Conns as clients connect to +it. As a Server receives new connections, it will push new clients onto +the channel accessible by Server.NewConns(). Allowing message routing +between named clients is left to the higher-level multi package. +*/ +package ipc + +// Conn represents a connection between two specific network endpoints - a client +// will hold one Conn to the server it's connected to, a server will hold individual +// Conns for each client that's connected to it. +type Conn interface { + // Read a message - returns an empty slice if nothing is available, nil if + // the connection has been shut down + Read() []byte + + // Read a message and block until one becomes available (returns the buffer) + // or the connection is closed (returns nil) + ReadBlock() []byte + + // Write the given buffer to the other end + Write([]byte) (int, error) + + // Close the connection and associated resources + Close() error +} + +// Server represents a running server waiting for connections. Client connections +// can be accessed by pulling new clients off the channel returned by NewConns(). +type Server interface { + NewConns() <-chan Conn + Close() error +} diff --git a/vector-cloud/internal/ipc/dgram.go b/vector-cloud/internal/ipc/dgram.go new file mode 100644 index 0000000..69c7dec --- /dev/null +++ b/vector-cloud/internal/ipc/dgram.go @@ -0,0 +1,142 @@ +package ipc + +import ( + "errors" + "fmt" + "io" + "net" + + "github.com/digital-dream-labs/vector-cloud/internal/util" +) + +// for the UDP server, we need to construct a translation from +// the net.PacketConn returned by net.ListenPacket("udp", ...) +// to a net.Listener with semantics to return new connections +type packetListener struct { + conn net.PacketConn + clients map[string]*packetClient + newClients chan *packetClient + kill chan struct{} + closer *util.DoOnce +} + +// Common string to indicate connection handshake shared by C++ code +// Defined in LocalUdpServer.cpp/UdpServer.cpp +const connPacket = "ANKICONN" + +func (p *packetListener) Accept() (Conn, error) { + select { + case conn := <-p.newClients: + return conn, nil + case <-p.kill: + return nil, io.EOF + } +} + +func (p *packetListener) Close() error { + p.closer.Do() + return p.conn.Close() +} + +type packetClient struct { + conn net.PacketConn + addr net.Addr + read chan []byte + kill chan struct{} +} + +func (c *packetClient) Read() []byte { + select { + case <-c.kill: + return nil + case recv := <-c.read: + return recv + default: + return []byte{} + } +} + +func (c *packetClient) ReadBlock() []byte { + select { + case <-c.kill: + return nil + case recv := <-c.read: + return recv + } +} + +func (c *packetClient) Close() error { + // packetClient is closed by its server being closed; no-op + return nil +} + +func (c *packetClient) Write(buf []byte) (int, error) { + return c.conn.WriteTo(buf, c.addr) +} + +func newDatagramClient(conn net.Conn) (Conn, error) { + client := newBaseConn(conn) + + packet := []byte(connPacket) + n, err := conn.Write(packet) + if err != nil { + return nil, err + } else if n != len(connPacket) { + return nil, errors.New(fmt.Sprint("unexpected write size: ", n)) + } + + return client, nil +} + +func newDatagramServer(conn net.PacketConn) (Server, error) { + kill := make(chan struct{}) + closer := util.NewDoOnce(func() { close(kill) }) + newClients := make(chan *packetClient) + listener := &packetListener{conn, make(map[string]*packetClient), newClients, kill, &closer} + + // start reader thread + go func() { + defer closer.Do() + buf := make([]byte, 32768) + for { + if util.CanSelect(kill) { + return + } + n, address, err := conn.ReadFrom(buf) + if err != nil || address == nil { + return + } + if n == len(buf) { + fmt.Println("Maxed buffer") + } + + isConnPacket := n == len(connPacket) && string(buf[:n]) == connPacket + addr := address.String() + if client, ok := listener.clients[addr]; ok { + if isConnPacket { + // ignore; they're trying to reconnect + } else { + sendbuf := make([]byte, n) + copy(sendbuf, buf) + select { + case client.read <- sendbuf: + case <-kill: + return + } + } + } else if isConnPacket { + client = &packetClient{conn, address, make(chan []byte), kill} + listener.clients[addr] = client + select { + case newClients <- client: + case <-kill: + return + } + } else { + fmt.Println("unexpected buf size from unknown client:", n, addr) + } + } + }() + + return newBaseServer(listener) +} diff --git a/vector-cloud/internal/ipc/file.go b/vector-cloud/internal/ipc/file.go new file mode 100644 index 0000000..a1ff37c --- /dev/null +++ b/vector-cloud/internal/ipc/file.go @@ -0,0 +1,22 @@ +package ipc + +import ( + "errors" + "net" + "os" +) + +// NewFileConn returns a connection that will operate on the +// given file descriptor +func NewFileConn(fd uintptr) (Conn, error) { + file := os.NewFile(fd, "file client") + if file == nil { + return nil, errors.New("Could not create file from descriptor") + } + + conn, err := net.FileConn(file) + if err != nil { + return nil, err + } + return newBaseConn(conn), nil +} diff --git a/vector-cloud/internal/ipc/ipc_test.go b/vector-cloud/internal/ipc/ipc_test.go new file mode 100644 index 0000000..0d790c2 --- /dev/null +++ b/vector-cloud/internal/ipc/ipc_test.go @@ -0,0 +1,105 @@ +package ipc_test + +import ( + "fmt" + "math/rand" + "net" + "testing" + "time" + + "github.com/digital-dream-labs/vector-cloud/internal/ipc" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type serverFn func() (ipc.Server, error) +type clientFn func() (ipc.Conn, error) + +func testProtocol(t *testing.T, sfn serverFn, cfn clientFn) { + assert := assert.New(t) + require := require.New(t) + serv, err := sfn() + require.Nil(err, "server create err:", err) + defer serv.Close() + time.Sleep(5 * time.Millisecond) + + c1, err := cfn() + require.Nil(err, "client create err:", err) + defer c1.Close() + time.Sleep(5 * time.Millisecond) + s1 := <-serv.NewConns() + defer s1.Close() + c2, err := cfn() + require.Nil(err, "client create err:", err) + defer c2.Close() + s2 := <-serv.NewConns() + defer s2.Close() + + bufsizes := []int{1, 2, 4, 7, 16, 31, 64, 127, 256, 511, 1024, 1535} + + rand.Seed(1328461) + + for _, val := range bufsizes { + buf := make([]byte, val) + rand.Read(buf) + c1.Write(buf) + s2.Write(buf) + assert.Empty(s2.Read()) + assert.Empty(c1.Read()) + time.Sleep(5 * time.Millisecond) + b1 := s1.Read() + b2 := c2.Read() + assert.ElementsMatch(b1, b2) + assert.ElementsMatch(b1, buf) + + rand.Read(buf) + s1.Write(buf) + s1.Write(buf) + c2.Write(buf) + + b1 = c1.ReadBlock() + b3 := c1.ReadBlock() + b2 = s2.ReadBlock() + assert.ElementsMatch(b1, b2) + assert.ElementsMatch(b1, b3) + assert.ElementsMatch(b1, buf) + } +} + +func TestUDP(t *testing.T) { + // get open port + s, err := net.ListenPacket("udp", "localhost:0") + require.Nil(t, err) + port := (s.LocalAddr().(*net.UDPAddr)).Port + s.Close() + sfn := func() (ipc.Server, error) { + return ipc.NewUDPServer(port) + } + cfn := func() (ipc.Conn, error) { + return ipc.NewUDPClient("127.0.0.1", port) + } + testProtocol(t, sfn, cfn) +} + +func TestUnixgram(t *testing.T) { + sfn := func() (ipc.Server, error) { + return ipc.NewUnixgramServer("unixgramblah") + } + i := 0 + cfn := func() (ipc.Conn, error) { + i++ + return ipc.NewUnixgramClient("unixgramblah", fmt.Sprint("client", i)) + } + testProtocol(t, sfn, cfn) +} + +func TestUnix(t *testing.T) { + sfn := func() (ipc.Server, error) { + return ipc.NewUnixServer("unixblah") + } + cfn := func() (ipc.Conn, error) { + return ipc.NewUnixClient("unixblah") + } + testProtocol(t, sfn, cfn) +} diff --git a/vector-cloud/internal/ipc/ipc_unixpacket_test.go b/vector-cloud/internal/ipc/ipc_unixpacket_test.go new file mode 100644 index 0000000..6b0c8e9 --- /dev/null +++ b/vector-cloud/internal/ipc/ipc_unixpacket_test.go @@ -0,0 +1,22 @@ +//+build !darwin + +package ipc_test + +import ( + "fmt" + "testing" + + "github.com/digital-dream-labs/vector-cloud/internal/ipc" +) + +func TestUnixPacket(t *testing.T) { + sfn := func() (ipc.Server, error) { + return ipc.NewUnixPacketServer("unixpacketblah") + } + i := 0 + cfn := func() (ipc.Conn, error) { + i++ + return ipc.NewUnixgramClient("unixpacketblah", fmt.Sprint("client", i)) + } + testProtocol(t, sfn, cfn) +} diff --git a/vector-cloud/internal/ipc/multi/client.go b/vector-cloud/internal/ipc/multi/client.go new file mode 100644 index 0000000..5f11167 --- /dev/null +++ b/vector-cloud/internal/ipc/multi/client.go @@ -0,0 +1,68 @@ +package multi + +import ( + "errors" + "strings" + + "github.com/digital-dream-labs/vector-cloud/internal/ipc" +) + +type Client interface { + Receive() (from string, buf []byte, err error) + ReceiveBlock() (from string, buf []byte, err error) + Send(dest string, buf []byte) (int, error) + Close() error +} + +type clientImpl struct { + conn ipc.Conn + name string +} + +func receiveInternal(receiveFunc func() []byte) (from string, buf []byte, err error) { + buf = receiveFunc() + if buf == nil || len(buf) == 0 { + return "", nil, nil + } + nullIdx := strings.Index(string(buf), "\x00") + if nullIdx <= 0 { + return "", nil, errors.New("no null identifier in buf") + } + from = string(buf[:nullIdx]) + buf = buf[nullIdx+1:] + return +} + +func (c *clientImpl) Receive() (from string, buf []byte, err error) { + return receiveInternal(func() []byte { + return c.conn.Read() + }) +} + +func (c *clientImpl) ReceiveBlock() (from string, buf []byte, err error) { + return receiveInternal(func() []byte { + return c.conn.ReadBlock() + }) +} + +func (c *clientImpl) Send(dest string, buf []byte) (int, error) { + sendbuf := getBufferForMessage(dest, buf) + sizediff := len(sendbuf) - len(buf) + n, err := c.conn.Write(sendbuf) + if n >= sizediff { + n -= sizediff + } + return n, err +} + +func (c *clientImpl) Close() error { + return c.conn.Close() +} + +func NewClient(conn ipc.Conn, clientName string) (Client, error) { + client := &clientImpl{conn, clientName} + + client.conn.Write([]byte(clientName)) + + return client, nil +} diff --git a/vector-cloud/internal/ipc/multi/server.go b/vector-cloud/internal/ipc/multi/server.go new file mode 100644 index 0000000..53761dd --- /dev/null +++ b/vector-cloud/internal/ipc/multi/server.go @@ -0,0 +1,155 @@ +package multi + +import ( + "fmt" + "strings" + "sync" + + "github.com/digital-dream-labs/vector-cloud/internal/ipc" +) + +type Server interface { + Close() +} + +type serverImpl struct { + base ipc.Server + kill chan struct{} + clients map[string]*endpoint + incoming chan *message + wg sync.WaitGroup +} + +type endpoint struct { + conn ipc.Conn + name string +} + +type message struct { + src *endpoint + dest string + buf []byte +} + +func NewServer(server ipc.Server) (Server, error) { + serv := &serverImpl{server, make(chan struct{}), make(map[string]*endpoint), make(chan *message), sync.WaitGroup{}} + + // get the channel that new clients will be sent on + endpoints := serv.handshakeRoutine() + // pass it to the routine that will set up new clients with routines to listen for messages + endpoints = serv.newEndpointRoutine(endpoints) + // start the routine that will route messages to their destination clients + serv.messageRoutine(endpoints) + + return serv, nil +} + +// takes new connections, gets their client name, and sends the endpoint data (name+connection) +// to the "new endpoint" routine +func (serv *serverImpl) handshakeRoutine() <-chan *endpoint { + ret := make(chan *endpoint) + + serv.wg.Add(1) + go func() { + defer serv.wg.Done() + defer close(ret) + + for conn := range serv.base.NewConns() { + go func(conn ipc.Conn) { + buf := conn.ReadBlock() + if len(buf) > 0 { + ret <- &endpoint{conn, string(buf)} + } + }(conn) + } + }() + + return ret +} + +// sets up listeners for new endpoints connecting to the server +func (serv *serverImpl) newEndpointRoutine(clients <-chan *endpoint) <-chan *endpoint { + outClients := make(chan *endpoint) + + serv.wg.Add(1) + go func() { + defer serv.wg.Done() + defer close(outClients) + + wg := sync.WaitGroup{} + for client := range clients { + // pass wait group - since we spawn the routines that will push data to the incoming + // messages channel, we'll close it once all routines are done + wg.Add(1) + serv.endpointListener(client, &wg) + outClients <- client + } + // wait for all endpoint routines to shut down, then close incoming channel + wg.Wait() + close(serv.incoming) + }() + + return outClients +} + +func (serv *serverImpl) endpointListener(client *endpoint, wg *sync.WaitGroup) { + // this routine starts up once for every client + go func() { + defer wg.Done() + for { + buf := client.conn.ReadBlock() + if buf == nil || len(buf) == 0 { + return + } + + // incoming messages will have the destination name, a null char, then message contents + nullIdx := strings.Index(string(buf), "\x00") + if nullIdx < 0 { + fmt.Println("Error: couldn't find null separator in message") + nullIdx = 0 + } + dest := string(buf[:nullIdx]) + select { + case <-serv.kill: + return + case serv.incoming <- &message{client, dest, buf[nullIdx+1:]}: + } + } + }() +} + +// message router - takes messages on incoming channel and sends them to their destination +// also adds new clients to client map +func (serv *serverImpl) messageRoutine(clients <-chan *endpoint) { + serv.wg.Add(1) + + go func() { + defer serv.wg.Done() + for { + select { + case msg := <-serv.incoming: + destClient, ok := serv.clients[msg.dest] + if !ok { + // todo: buffer this to send if client later connects + fmt.Println("Unknown destination client", msg.dest) + continue + } + destClient.conn.Write(getBufferForMessage(msg.src.name, msg.buf)) + + case client := <-clients: + // add new client to map so we can find it when someone sends to it + serv.clients[client.name] = client + + case <-serv.kill: + return + } + } + }() +} + +// Close stops the server and closes associated connections and resources +func (serv *serverImpl) Close() { + serv.base.Close() + close(serv.kill) + serv.wg.Wait() +} diff --git a/vector-cloud/internal/ipc/multi/shared.go b/vector-cloud/internal/ipc/multi/shared.go new file mode 100644 index 0000000..f4dc0bc --- /dev/null +++ b/vector-cloud/internal/ipc/multi/shared.go @@ -0,0 +1,6 @@ +package multi + +func getBufferForMessage(dest string, buf []byte) []byte { + sendbuf := append([]byte(dest+"\x00"), buf...) + return sendbuf +} diff --git a/vector-cloud/internal/ipc/path_normal.go b/vector-cloud/internal/ipc/path_normal.go new file mode 100644 index 0000000..1aba6a1 --- /dev/null +++ b/vector-cloud/internal/ipc/path_normal.go @@ -0,0 +1,8 @@ +// +build !vicos + +package ipc + +// GetSocketPath returns a platform-appropriate path for the given socket name +func GetSocketPath(socketName string) string { + return "/tmp/" + socketName +} diff --git a/vector-cloud/internal/ipc/path_vicos.go b/vector-cloud/internal/ipc/path_vicos.go new file mode 100644 index 0000000..627a0fe --- /dev/null +++ b/vector-cloud/internal/ipc/path_vicos.go @@ -0,0 +1,8 @@ +// +build vicos + +package ipc + +// GetSocketPath returns a platform-appropriate path for the given socket name +func GetSocketPath(socketName string) string { + return "/dev/socket/" + socketName +} diff --git a/vector-cloud/internal/ipc/stream.go b/vector-cloud/internal/ipc/stream.go new file mode 100644 index 0000000..1cd117c --- /dev/null +++ b/vector-cloud/internal/ipc/stream.go @@ -0,0 +1,51 @@ +package ipc + +import ( + "bytes" + "encoding/binary" + "fmt" + "net" +) + +type streamConn struct { + net.Conn +} + +func (c *streamConn) Read(b []byte) (int, error) { + var buflen uint32 + err := binary.Read(c.Conn, binary.BigEndian, &buflen) + if err != nil { + return 0, err + } + if buflen > uint32(len(b)) { + fmt.Println("buffer overflow, have size", len(b), "but need", buflen) + buflen = uint32(len(b)) + } + return c.Conn.Read(b[:buflen]) +} + +func (c *streamConn) Write(b []byte) (int, error) { + lenbuf := bytes.NewBuffer(make([]byte, 0, 4)) + binary.Write(lenbuf, binary.BigEndian, uint32(len(b))) + _, err := c.Conn.Write(lenbuf.Bytes()) + if err != nil { + return 0, err + } + return c.Conn.Write(b) +} + +type streamListener struct { + net.Listener +} + +func (s *streamListener) Accept() (net.Conn, error) { + c, err := s.Listener.Accept() + if err != nil { + return nil, err + } + return newStreamConnection(c), nil +} + +func newStreamConnection(conn net.Conn) net.Conn { + return &streamConn{conn} +} diff --git a/vector-cloud/internal/ipc/udp.go b/vector-cloud/internal/ipc/udp.go new file mode 100644 index 0000000..c20e5d6 --- /dev/null +++ b/vector-cloud/internal/ipc/udp.go @@ -0,0 +1,29 @@ +package ipc + +import ( + "errors" + "fmt" + "net" +) + +// NewUDPClient returns a new Socket that will attempt to connect to the server +// on the given IP and port +func NewUDPClient(ip string, port int) (Conn, error) { + conn, err := net.Dial("udp", fmt.Sprintf("%v:%v", ip, port)) + if err != nil { + return nil, errors.New(fmt.Sprint("Couldn't connect:", err)) + } + + return newDatagramClient(conn) +} + +// NewUDPServer returns a new Server that will listen for clients on the given +// local port +func NewUDPServer(port int) (Server, error) { + conn, err := net.ListenPacket("udp", fmt.Sprintf("localhost:%v", port)) + if err != nil { + return nil, err + } + + return newDatagramServer(conn) +} diff --git a/vector-cloud/internal/ipc/unix.go b/vector-cloud/internal/ipc/unix.go new file mode 100644 index 0000000..3a33339 --- /dev/null +++ b/vector-cloud/internal/ipc/unix.go @@ -0,0 +1,32 @@ +package ipc + +import ( + "fmt" + "net" + "syscall" +) + +// NewUnixServer returns a new server object listening for clients on the specified path, +// if no errors are encountered +func NewUnixServer(path string) (Server, error) { + if []byte(path)[0] != '\x00' { + syscall.Unlink(path) + } + listen, err := net.Listen("unix", path) + if err != nil { + return nil, err + } + return newBaseServer(&listenerWrapper{&streamListener{listen}}) +} + +// NewUnixClient returns a new connection to the server at the specified path, assuming +// there are no errors when connecting +func NewUnixClient(path string) (Conn, error) { + conn, err := net.Dial("unix", path) + if err != nil { + fmt.Println("Dial error:", err) + return nil, err + } + client := newBaseConn(newStreamConnection(conn)) + return client, nil +} diff --git a/vector-cloud/internal/ipc/unixgram.go b/vector-cloud/internal/ipc/unixgram.go new file mode 100644 index 0000000..30c1377 --- /dev/null +++ b/vector-cloud/internal/ipc/unixgram.go @@ -0,0 +1,44 @@ +package ipc + +import ( + "net" + "syscall" +) + +// NewUnixgramClient returns a new connection to the server at the specified path, assuming +// there are no errors when connecting +func NewUnixgramClient(path string, name string) (Conn, error) { + servaddr, err := net.ResolveUnixAddr("unixgram", path) + if err != nil { + return nil, err + } + clientpath := path + "_" + name + syscall.Unlink(clientpath) + cliaddr, err := net.ResolveUnixAddr("unixgram", clientpath) + if err != nil { + return nil, err + } + conn, err := net.DialUnix("unixgram", cliaddr, servaddr) + if err != nil { + return nil, err + } + + // Set receive buffer size to 64k (defaults to 4k) + conn.SetReadBuffer(64 * 1024) + + return newDatagramClient(conn) +} + +// NewUnixgramServer returns a new server object listening for clients on the specified path, +// if no errors are encountered +func NewUnixgramServer(path string) (Server, error) { + if []byte(path)[0] != '\x00' { + syscall.Unlink(path) + } + conn, err := net.ListenPacket("unixgram", path) + if err != nil { + return nil, err + } + + return newDatagramServer(conn) +} diff --git a/vector-cloud/internal/ipc/unixpacket.go b/vector-cloud/internal/ipc/unixpacket.go new file mode 100644 index 0000000..086573e --- /dev/null +++ b/vector-cloud/internal/ipc/unixpacket.go @@ -0,0 +1,34 @@ +//+build !darwin + +package ipc + +import ( + "fmt" + "net" + "syscall" +) + +// NewUnixPacketServer returns a new connection to the server at the specified path, assuming +// there are no errors when connecting +func NewUnixPacketServer(path string) (Server, error) { + if []byte(path)[0] != '\x00' { + syscall.Unlink(path) + } + listen, err := net.Listen("unixpacket", path) + if err != nil { + return nil, err + } + return newBaseServer(&listenerWrapper{listen}) +} + +// NewUnixPacketClient returns a new server object listening for clients on the specified path, +// if no errors are encountered +func NewUnixPacketClient(path string) (Conn, error) { + conn, err := net.Dial("unixpacket", path) + if err != nil { + fmt.Println("Dial error:", err) + return nil, err + } + client := newBaseConn(conn) + return client, nil +} diff --git a/vector-cloud/internal/jdocs/client.go b/vector-cloud/internal/jdocs/client.go new file mode 100644 index 0000000..cea6d0b --- /dev/null +++ b/vector-cloud/internal/jdocs/client.go @@ -0,0 +1,134 @@ +package jdocs + +import ( + "context" + "fmt" + + "github.com/digital-dream-labs/vector-cloud/internal/clad/cloud" + + "github.com/digital-dream-labs/vector-cloud/internal/config" + "github.com/digital-dream-labs/vector-cloud/internal/log" + "github.com/digital-dream-labs/vector-cloud/internal/token" + "github.com/digital-dream-labs/vector-cloud/internal/util" + + pb "github.com/digital-dream-labs/api/go/jdocspb" + "github.com/gwatts/rootcerts" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" +) + +type conn struct { + conn *grpc.ClientConn + client pb.JdocsClient + tok token.Accessor +} + +func newConn(ctx context.Context, opts *options) (*conn, error) { + + pool := rootcerts.ServerCertPool() + + _ = pool.AppendCertsFromPEM([]byte(escapepodRootPEM)) + + dialOpts := []grpc.DialOption{ + grpc.WithTransportCredentials( + credentials.NewClientTLSFromCert(pool, ""), + ), + } + + dialOpts = append(dialOpts, util.CommonGRPC()...) + // end idea + + if opts.tokener != nil { + creds, err := opts.tokener.Credentials() + if err != nil { + return nil, err + } + dialOpts = append(dialOpts, grpc.WithPerRPCCredentials(creds)) + } + rpcConn, err := grpc.DialContext(ctx, config.Env.JDocs, dialOpts...) + if err != nil { + return nil, err + } + + rpcClient := pb.NewJdocsClient(rpcConn) + + ret := &conn{ + conn: rpcConn, + client: rpcClient, + tok: opts.tokener} + return ret, nil +} + +func (c *conn) close() error { + return c.conn.Close() +} + +func (c *conn) handleRequest(ctx context.Context, req *cloud.DocRequest) (*cloud.DocResponse, error) { + switch req.Tag() { + case cloud.DocRequestTag_Read: + return c.readRequest(ctx, req.GetRead()) + case cloud.DocRequestTag_Write: + return c.writeRequest(ctx, req.GetWrite()) + case cloud.DocRequestTag_DeleteReq: + return c.deleteRequest(ctx, req.GetDeleteReq()) + } + err := fmt.Errorf("Major error: received unknown tag %d", req.Tag()) + if err != nil { + log.Println(err) + } + return nil, err +} + +var connectErrorResponse = cloud.NewDocResponseWithErr(&cloud.ErrorResponse{Err: cloud.DocError_ErrorConnecting}) + +func (c *conn) writeRequest(ctx context.Context, cladReq *cloud.WriteRequest) (*cloud.DocResponse, error) { + req := (*cladWriteReq)(cladReq).toProto() + resp, err := c.client.WriteDoc(ctx, req) + if err != nil { + return connectErrorResponse, err + } + return cloud.NewDocResponseWithWrite((*protoWriteResp)(resp).toClad()), nil +} + +func (c *conn) readRequest(ctx context.Context, cladReq *cloud.ReadRequest) (*cloud.DocResponse, error) { + req := (*cladReadReq)(cladReq).toProto() + resp, err := c.client.ReadDocs(ctx, req) + if err != nil { + return connectErrorResponse, err + } + return cloud.NewDocResponseWithRead((*protoReadResp)(resp).toClad()), nil +} + +func (c *conn) deleteRequest(ctx context.Context, cladReq *cloud.DeleteRequest) (*cloud.DocResponse, error) { + req := (*cladDeleteReq)(cladReq).toProto() + _, err := c.client.DeleteDoc(ctx, req) + if err != nil { + return connectErrorResponse, err + } + return cloud.NewDocResponseWithDeleteResp(&cloud.Void{}), nil +} + +func (c *client) handleConnectionless(req *cloud.DocRequest) (bool, *cloud.DocResponse, error) { + switch req.Tag() { + case cloud.DocRequestTag_User: + r, e := c.handleUserRequest() + return true, r, e + case cloud.DocRequestTag_Thing: + r, e := c.handleThingRequest() + return true, r, e + } + return false, nil, nil +} + +func (c *client) handleUserRequest() (*cloud.DocResponse, error) { + var user string + if c.opts.tokener != nil { + user = c.opts.tokener.UserID() + } + return cloud.NewDocResponseWithUser(&cloud.UserResponse{UserId: user}), nil +} + +func (c *client) handleThingRequest() (*cloud.DocResponse, error) { + thing := c.opts.tokener.IdentityProvider().CertCommonName() + return cloud.NewDocResponseWithThing(&cloud.ThingResponse{ThingName: thing}), nil +} diff --git a/vector-cloud/internal/jdocs/escapepod_root_cert.go b/vector-cloud/internal/jdocs/escapepod_root_cert.go new file mode 100644 index 0000000..22f1186 --- /dev/null +++ b/vector-cloud/internal/jdocs/escapepod_root_cert.go @@ -0,0 +1,37 @@ +package jdocs + +const escapepodRootPEM = ` +-----BEGIN CERTIFICATE----- +MIIF5zCCA8+gAwIBAgIUd7+iytZTXyFdQRwAtMc28TtXutQwDQYJKoZIhvcNAQEL +BQAwgYExCzAJBgNVBAYTAlVTMRUwEwYDVQQIDAxQZW5uc3lsdmFuaWExEzARBgNV +BAcMClBpdHRzYnVyZ2gxGzAZBgNVBAoMEkRpZ2l0YWwgRHJlYW0gTGFiczEpMCcG +CSqGSIb3DQEJARYaYnJldHRAZGlnaXRhbGRyZWFtbGFicy5jb20wIBcNMjAxMjEw +MTgxODQwWhgPMjIyMDEwMjMxODE4NDBaMIGBMQswCQYDVQQGEwJVUzEVMBMGA1UE +CAwMUGVubnN5bHZhbmlhMRMwEQYDVQQHDApQaXR0c2J1cmdoMRswGQYDVQQKDBJE +aWdpdGFsIERyZWFtIExhYnMxKTAnBgkqhkiG9w0BCQEWGmJyZXR0QGRpZ2l0YWxk +cmVhbWxhYnMuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvtk7 +ccn6Y/1xQlynkvvon86fgj4wQuqYbKmUOeNZkNv80QhW+WcVEGuNkDctXNGAwTZI +kBM4nKl1CEq24OgT6I6skYMqEOEGJdaGld08LJwlf4PHG9V7qdNOiWv36GpK2blp +PB0UuAgxVgEF5DC6V+RWxbFYfaQcfa9OMeMCvzaL5yQdK1gVYZLjWS3yeFn33oh9 +H2mhTelW/24+UXV7buneAvyEYXJEMdRmEjKTtsWktMGDp6d9Eg6cZjwQzJrlhuQj +FpHdM+FdpFkQ/22wGi+L27f2m+lCEE+G37bkPB+L2EXuYNSDv9mVSyqb6NbqpwX5 +sPyEzPitDROP7+sUgeTjg83biQAjQyedKxOGab8BwKSJsbmGcodAgHS/sMdDAuV/ +Uw2iy1zN8uQaOIvXxWCWpvXAcwA4Aro2ruEkMR7h/+Cc26dt6EQ2VrSyxVEzq+Sz +5fxAYtwFAsd023Tpztu+UewmNOhQHPb1Xj99/nW2lR4fH0kSfsyBFKlKXgFwDSZD +vbIQ0ZEEFrIpqnsgmwc/C2w1ztD22R5Jnfrb8s23xj4awv3/aPl+4NeVBwQMgxS6 +8n/JWRuFvtIDj4gQWbKq5wjIdcGaPEhHRYsLiWr0NCDwn/R+CCdNIY8SCtMllrP8 +PUBxwnlQ5NfBw4azwBO9tW47iD0pbVrxx48UfNECAwEAAaNTMFEwHQYDVR0OBBYE +FLEGVfbWGssezxPrNlderfGJaU0eMB8GA1UdIwQYMBaAFLEGVfbWGssezxPrNlde +rfGJaU0eMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBAKsD9769 +YV23Ak3RBd5IBk1LNxwQLbp4oLes4k4XdSNRWEddMFOLHcfJgtjAugoJwRza1OIz +7I/2Pe0AhUpvtMqDn9zquDACx0Eno2/3xOGdjxCH8rXTF1Hpc/NEWjyzhJIj4Oz+ +DHtNicWkJG4e/yLuHD6E4qlcqYW8JEQt3j156IZIQgJxkkSDHS7sxpDMVYX06AKd +b7Msmagde9gm4jeFIOqa3pCoGABUdPCG4poTIwAmuobDDji08bo7PBYFWvB8Ye6y +kcfVML0OzWRJgcG8AWltz1rA4GSVG91oSr0DXNyUTHR0GFc7OqX26KkxuHV6EoDz +SkqvwRxceOjTf49JobHlFbKuj6zN+t608ZEVPUq8g/uG68ecat9MBnH3mbenMFZ/ +7AVW3dpEbxqrIK8wLGleoQQ2+l1Jkfu7LNbPqiMUIWF02FNK5PoD9W5KFl/geYqD +3cA4AKYtrHnvEql6DABZILfmEnkfxYipZkwBaOoUSLuRSLP/kdNwkZjdg8O+U9qA +ac9sgHFZ/wegv3etK46TtLTb0vrMWm5pFFcFRCiYeqNpAhAwEb1b8YQR0sjT9rlL +o6yMR+1kuDXYzB1REuuTOZHGDj7Zt1yiHv+nr1dPY/EBV+69is4e2lIXyHlI2ov8 +HEtdAlxwZiakfw9zndxIweAw9YapnyT+4xp2 +-----END CERTIFICATE-----` diff --git a/vector-cloud/internal/jdocs/jdocs.go b/vector-cloud/internal/jdocs/jdocs.go new file mode 100644 index 0000000..266f050 --- /dev/null +++ b/vector-cloud/internal/jdocs/jdocs.go @@ -0,0 +1,17 @@ +package jdocs + +import ( + "context" +) + +// Run starts the jdocs service +func Run(ctx context.Context, optionValues ...Option) { + var opts options + for _, o := range optionValues { + o(&opts) + } + + if opts.server { + runServer(ctx, &opts) + } +} diff --git a/vector-cloud/internal/jdocs/opts.go b/vector-cloud/internal/jdocs/opts.go new file mode 100644 index 0000000..b062a5f --- /dev/null +++ b/vector-cloud/internal/jdocs/opts.go @@ -0,0 +1,47 @@ +package jdocs + +import ( + "github.com/digital-dream-labs/vector-cloud/internal/token" + "github.com/digital-dream-labs/vector-cloud/internal/util" +) + +type options struct { + server bool + socketNameSuffix string + tokener token.Accessor + errListener util.ErrorListener +} + +// Option defines an option that can be set on the token server +type Option func(o *options) + +// WithServer specifies that an IPC server should be started so other processes +// can request jdocs from this process +func WithServer() Option { + return func(o *options) { + o.server = true + } +} + +// WithSocketNameSuffix specifies the (optional) suffix of the socket name +func WithSocketNameSuffix(socketNameSuffix string) Option { + return func(o *options) { + o.socketNameSuffix = socketNameSuffix + } +} + +// WithTokener specifies that the given token.Accessor should be used to obtain +// authorization credentials +func WithTokener(value token.Accessor) Option { + return func(o *options) { + o.tokener = value + } +} + +// WithErrorListener specifies that the given ErrorListener should be passed errors +// that result from jdoc requests +func WithErrorListener(value util.ErrorListener) Option { + return func(o *options) { + o.errListener = value + } +} diff --git a/vector-cloud/internal/jdocs/server.go b/vector-cloud/internal/jdocs/server.go new file mode 100644 index 0000000..9ef8cbe --- /dev/null +++ b/vector-cloud/internal/jdocs/server.go @@ -0,0 +1,83 @@ +package jdocs + +import ( + "bytes" + "context" + "fmt" + "sync" + + "github.com/digital-dream-labs/vector-cloud/internal/clad/cloud" + + "github.com/digital-dream-labs/vector-cloud/internal/ipc" + "github.com/digital-dream-labs/vector-cloud/internal/log" +) + +func runServer(ctx context.Context, opts *options) { + socketName := "jdocs_server" + if opts.socketNameSuffix != "" { + socketName = fmt.Sprintf("%s_%s", socketName, opts.socketNameSuffix) + } + + serv, err := ipc.NewUnixgramServer(ipc.GetSocketPath(socketName)) + if err != nil { + log.Println("Error creating jdocs server:", err) + return + } + + // close on context? + for c := range serv.NewConns() { + cl := client{Conn: c, opts: opts} + go cl.handleConn(ctx) + } +} + +type client struct { + ipc.Conn + opts *options + reqMutex sync.Mutex +} + +func (c *client) handleConn(ctx context.Context) { + for { + buf := c.ReadBlock() + // TODO: will this ever close? + if buf == nil || len(buf) == 0 { + return + } + var msg cloud.DocRequest + if err := msg.Unpack(bytes.NewBuffer(buf)); err != nil { + log.Println("Could not unpack jdocs request:", err) + continue + } + + resp, err := c.handleRequest(ctx, &msg) + if err != nil { + log.Println("Error handling jdocs request:", err) + if c.opts.errListener != nil { + c.opts.errListener.OnError(err) + } + } + if resp != nil { + var buf bytes.Buffer + if err := resp.Pack(&buf); err != nil { + log.Println("Error packing jdocs response:", err) + } else if n, err := c.Write(buf.Bytes()); n != buf.Len() || err != nil { + log.Println("Error sending jdocs response:", fmt.Sprintf("%d/%d,", n, buf.Len()), err) + } + } + } +} + +func (c *client) handleRequest(ctx context.Context, msg *cloud.DocRequest) (*cloud.DocResponse, error) { + c.reqMutex.Lock() + defer c.reqMutex.Unlock() + if ok, resp, err := c.handleConnectionless(msg); ok { + return resp, err + } + conn, err := newConn(ctx, c.opts) + if err != nil { + return connectErrorResponse, err + } + defer conn.close() + return conn.handleRequest(ctx, msg) +} diff --git a/vector-cloud/internal/jdocs/translate.go b/vector-cloud/internal/jdocs/translate.go new file mode 100644 index 0000000..719fab5 --- /dev/null +++ b/vector-cloud/internal/jdocs/translate.go @@ -0,0 +1,100 @@ +package jdocs + +import ( + "github.com/digital-dream-labs/vector-cloud/internal/clad/cloud" + + pb "github.com/digital-dream-labs/api/go/jdocspb" +) + +type cladDoc cloud.Doc +type pbDoc pb.Jdoc + +type cladWriteReq cloud.WriteRequest +type cladReadReq cloud.ReadRequest +type cladDeleteReq cloud.DeleteRequest + +type protoWriteResp pb.WriteDocResp +type protoWriteStatus pb.WriteDocResp_Status +type protoReadResp pb.ReadDocsResp +type protoReadStatus pb.ReadDocsResp_Status + +func (c *cladDoc) toProto() *pb.Jdoc { + return &pb.Jdoc{ + DocVersion: c.DocVersion, + FmtVersion: c.FmtVersion, + ClientMetadata: c.Metadata, + JsonDoc: c.JsonDoc, + } +} + +func (p *pbDoc) toClad() *cloud.Doc { + return &cloud.Doc{ + DocVersion: p.DocVersion, + FmtVersion: p.FmtVersion, + Metadata: p.ClientMetadata, + JsonDoc: p.JsonDoc, + } +} + +func (c *cladWriteReq) toProto() *pb.WriteDocReq { + return &pb.WriteDocReq{ + UserId: c.Account, + Thing: c.Thing, + DocName: c.DocName, + Doc: (*cladDoc)(&c.Doc).toProto(), + } +} + +func (c *cladReadReq) toProto() *pb.ReadDocsReq { + ret := &pb.ReadDocsReq{ + UserId: c.Account, + Thing: c.Thing, + } + ret.Items = make([]*pb.ReadDocsReq_Item, len(c.Items)) + for i, c := range c.Items { + ret.Items[i] = &pb.ReadDocsReq_Item{ + DocName: c.DocName, + MyDocVersion: c.MyDocVersion, + } + } + return ret +} + +func (c *cladDeleteReq) toProto() *pb.DeleteDocReq { + return &pb.DeleteDocReq{ + UserId: c.Account, + Thing: c.Thing, + DocName: c.DocName, + } +} + +func (p *protoWriteResp) toClad() *cloud.WriteResponse { + return &cloud.WriteResponse{ + LatestVersion: p.LatestDocVersion, + Status: writeStatusMap[p.Status], + } +} + +func (p *protoReadResp) toClad() *cloud.ReadResponse { + ret := &cloud.ReadResponse{} + ret.Items = make([]cloud.ResponseDoc, len(p.Items)) + for i, p := range p.Items { + ret.Items[i] = cloud.ResponseDoc{ + Status: readStatusMap[p.Status], + Doc: *(*pbDoc)(p.Doc).toClad(), + } + } + return ret +} + +var readStatusMap = map[pb.ReadDocsResp_Status]cloud.ReadStatus{ + pb.ReadDocsResp_UNCHANGED: cloud.ReadStatus_Unchanged, + pb.ReadDocsResp_CHANGED: cloud.ReadStatus_Changed, + pb.ReadDocsResp_NOT_FOUND: cloud.ReadStatus_NotFound, +} + +var writeStatusMap = map[pb.WriteDocResp_Status]cloud.WriteStatus{ + pb.WriteDocResp_ACCEPTED: cloud.WriteStatus_Accepted, + pb.WriteDocResp_REJECTED_BAD_DOC_VERSION: cloud.WriteStatus_RejectedDocVersion, + pb.WriteDocResp_REJECTED_BAD_FMT_VERSION: cloud.WriteStatus_RejectedFmtVersion, +} diff --git a/vector-cloud/internal/log/android/log.h b/vector-cloud/internal/log/android/log.h new file mode 100644 index 0000000..391c826 --- /dev/null +++ b/vector-cloud/internal/log/android/log.h @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ANDROID_LOG_H +#define _ANDROID_LOG_H + +/****************************************************************** + * + * IMPORTANT NOTICE: + * + * This file is part of Android's set of stable system headers + * exposed by the Android NDK (Native Development Kit) since + * platform release 1.5 + * + * Third-party source AND binary code relies on the definitions + * here to be FROZEN ON ALL UPCOMING PLATFORM RELEASES. + * + * - DO NOT MODIFY ENUMS (EXCEPT IF YOU ADD NEW 32-BIT VALUES) + * - DO NOT MODIFY CONSTANTS OR FUNCTIONAL MACROS + * - DO NOT CHANGE THE SIGNATURE OF FUNCTIONS IN ANY WAY + * - DO NOT CHANGE THE LAYOUT OR SIZE OF STRUCTURES + */ + +/* + * Support routines to send messages to the Android in-kernel log buffer, + * which can later be accessed through the 'logcat' utility. + * + * Each log message must have + * - a priority + * - a log tag + * - some text + * + * The tag normally corresponds to the component that emits the log message, + * and should be reasonably small. + * + * Log message text may be truncated to less than an implementation-specific + * limit (e.g. 1023 characters max). + * + * Note that a newline character ("\n") will be appended automatically to your + * log message, if not already there. It is not possible to send several messages + * and have them appear on a single line in logcat. + * + * PLEASE USE LOGS WITH MODERATION: + * + * - Sending log messages eats CPU and slow down your application and the + * system. + * + * - The circular log buffer is pretty small (<64KB), sending many messages + * might push off other important log messages from the rest of the system. + * + * - In release builds, only send log messages to account for exceptional + * conditions. + * + * NOTE: These functions MUST be implemented by /system/lib/liblog.so + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Android log priority values, in ascending priority order. + */ +typedef enum android_LogPriority { + ANDROID_LOG_UNKNOWN = 0, + ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */ + ANDROID_LOG_VERBOSE, + ANDROID_LOG_DEBUG, + ANDROID_LOG_INFO, + ANDROID_LOG_WARN, + ANDROID_LOG_ERROR, + ANDROID_LOG_FATAL, + ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */ +} android_LogPriority; + +/* + * Release any logger resources (a new log write will immediately re-acquire) + */ +void __android_log_close(); + +/* + * Send a simple string to the log. + */ +int __android_log_write(int prio, const char *tag, const char *text); + +/* + * Send a formatted string to the log, used like printf(fmt,...) + */ +int __android_log_print(int prio, const char *tag, const char *fmt, ...) +#if defined(__GNUC__) +#ifdef __USE_MINGW_ANSI_STDIO +#if __USE_MINGW_ANSI_STDIO + __attribute__ ((format(gnu_printf, 3, 4))) +#else + __attribute__ ((format(printf, 3, 4))) +#endif +#else + __attribute__ ((format(printf, 3, 4))) +#endif +#endif + ; + +/* + * A variant of __android_log_print() that takes a va_list to list + * additional parameters. + */ +int __android_log_vprint(int prio, const char *tag, + const char *fmt, va_list ap); + +/* + * Log an assertion failure and abort the process to have a chance + * to inspect it if a debugger is attached. This uses the FATAL priority. + */ +void __android_log_assert(const char *cond, const char *tag, + const char *fmt, ...) +#if defined(__GNUC__) + __attribute__ ((noreturn)) +#ifdef __USE_MINGW_ANSI_STDIO +#if __USE_MINGW_ANSI_STDIO + __attribute__ ((format(gnu_printf, 3, 4))) +#else + __attribute__ ((format(printf, 3, 4))) +#endif +#else + __attribute__ ((format(printf, 3, 4))) +#endif +#endif + ; + +#ifdef __cplusplus +} +#endif + +#endif /* _ANDROID_LOG_H */ diff --git a/vector-cloud/internal/log/android/native_window.h b/vector-cloud/internal/log/android/native_window.h new file mode 100644 index 0000000..cf07f1a --- /dev/null +++ b/vector-cloud/internal/log/android/native_window.h @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup NativeActivity Native Activity + * @{ + */ + +/** + * @file native_window.h + */ + +#ifndef ANDROID_NATIVE_WINDOW_H +#define ANDROID_NATIVE_WINDOW_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Pixel formats that a window can use. + */ +enum { + /** Red: 8 bits, Green: 8 bits, Blue: 8 bits, Alpha: 8 bits. **/ + WINDOW_FORMAT_RGBA_8888 = 1, + /** Red: 8 bits, Green: 8 bits, Blue: 8 bits, Unused: 8 bits. **/ + WINDOW_FORMAT_RGBX_8888 = 2, + /** Red: 5 bits, Green: 6 bits, Blue: 5 bits. **/ + WINDOW_FORMAT_RGB_565 = 4, +}; + +struct ANativeWindow; +/** + * {@link ANativeWindow} is opaque type that provides access to a native window. + * + * A pointer can be obtained using ANativeWindow_fromSurface(). + */ +typedef struct ANativeWindow ANativeWindow; + +/** + * {@link ANativeWindow} is a struct that represents a windows buffer. + * + * A pointer can be obtained using ANativeWindow_lock(). + */ +typedef struct ANativeWindow_Buffer { + // The number of pixels that are show horizontally. + int32_t width; + + // The number of pixels that are shown vertically. + int32_t height; + + // The number of *pixels* that a line in the buffer takes in + // memory. This may be >= width. + int32_t stride; + + // The format of the buffer. One of WINDOW_FORMAT_* + int32_t format; + + // The actual bits. + void* bits; + + // Do not touch. + uint32_t reserved[6]; +} ANativeWindow_Buffer; + +/** + * Acquire a reference on the given ANativeWindow object. This prevents the object + * from being deleted until the reference is removed. + */ +void ANativeWindow_acquire(ANativeWindow* window); + +/** + * Remove a reference that was previously acquired with ANativeWindow_acquire(). + */ +void ANativeWindow_release(ANativeWindow* window); + +/** + * Return the current width in pixels of the window surface. Returns a + * negative value on error. + */ +int32_t ANativeWindow_getWidth(ANativeWindow* window); + +/** + * Return the current height in pixels of the window surface. Returns a + * negative value on error. + */ +int32_t ANativeWindow_getHeight(ANativeWindow* window); + +/** + * Return the current pixel format of the window surface. Returns a + * negative value on error. + */ +int32_t ANativeWindow_getFormat(ANativeWindow* window); + +/** + * Change the format and size of the window buffers. + * + * The width and height control the number of pixels in the buffers, not the + * dimensions of the window on screen. If these are different than the + * window's physical size, then it buffer will be scaled to match that size + * when compositing it to the screen. + * + * For all of these parameters, if 0 is supplied then the window's base + * value will come back in force. + * + * width and height must be either both zero or both non-zero. + * + */ +int32_t ANativeWindow_setBuffersGeometry(ANativeWindow* window, + int32_t width, int32_t height, int32_t format); + +/** + * Lock the window's next drawing surface for writing. + * inOutDirtyBounds is used as an in/out parameter, upon entering the + * function, it contains the dirty region, that is, the region the caller + * intends to redraw. When the function returns, inOutDirtyBounds is updated + * with the actual area the caller needs to redraw -- this region is often + * extended by ANativeWindow_lock. + */ +int32_t ANativeWindow_lock(ANativeWindow* window, ANativeWindow_Buffer* outBuffer, + ARect* inOutDirtyBounds); + +/** + * Unlock the window's drawing surface after previously locking it, + * posting the new buffer to the display. + */ +int32_t ANativeWindow_unlockAndPost(ANativeWindow* window); + +#ifdef __cplusplus +}; +#endif + +#endif // ANDROID_NATIVE_WINDOW_H + +/** @} */ diff --git a/vector-cloud/internal/log/android/rect.h b/vector-cloud/internal/log/android/rect.h new file mode 100644 index 0000000..80741c0 --- /dev/null +++ b/vector-cloud/internal/log/android/rect.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup NativeActivity Native Activity + * @{ + */ + +/** + * @file rect.h + */ + +#ifndef ANDROID_RECT_H +#define ANDROID_RECT_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * {@link ARect} is a struct that represents a rectangular window area. + * + * It is used with {@link + * ANativeActivityCallbacks::onContentRectChanged} event callback and + * ANativeWindow_lock() function. + */ +typedef struct ARect { +#ifdef __cplusplus + typedef int32_t value_type; +#endif + /** left position */ + int32_t left; + /** top position */ + int32_t top; + /** left position */ + int32_t right; + /** bottom position */ + int32_t bottom; +} ARect; + +#ifdef __cplusplus +}; +#endif + +#endif // ANDROID_RECT_H + +/** @} */ diff --git a/vector-cloud/internal/log/log.go b/vector-cloud/internal/log/log.go new file mode 100644 index 0000000..926c024 --- /dev/null +++ b/vector-cloud/internal/log/log.go @@ -0,0 +1,10 @@ +package log + +var logVicos func(a ...interface{}) (int, error) + +func IfVicos(a ...interface{}) (int, error) { + if logVicos == nil { + return 0, nil + } + return logVicos(a...) +} diff --git a/vector-cloud/internal/log/log_normal.go b/vector-cloud/internal/log/log_normal.go new file mode 100644 index 0000000..731f17d --- /dev/null +++ b/vector-cloud/internal/log/log_normal.go @@ -0,0 +1,25 @@ +//go:build vicos +// +build vicos + +package log + +import ( + "fmt" + "os" +) + +func Println(a ...interface{}) (int, error) { + return fmt.Println(a...) +} + +func Printf(format string, a ...interface{}) (int, error) { + return fmt.Printf(format, a...) +} + +func Errorln(a ...interface{}) (int, error) { + return fmt.Fprintln(os.Stderr, a...) +} + +func Errorf(format string, a ...interface{}) (int, error) { + return fmt.Fprintf(os.Stderr, format, a...) +} diff --git a/vector-cloud/internal/log/log_vicos.go b/vector-cloud/internal/log/log_vicos.go new file mode 100644 index 0000000..9153d85 --- /dev/null +++ b/vector-cloud/internal/log/log_vicos.go @@ -0,0 +1,54 @@ +//go:build !vicos +// +build !vicos + +package log + +import ( + "fmt" + "unsafe" +) + +/* +#cgo LDFLAGS: -llog +#include +#include + +// Go cannot call variadic C functions directly +static int android_log(int prio, const char* tag, const char* str) { + return __android_log_print(prio, tag, "%s", str); +} +*/ +import "C" + +func androidLog(level C.int, tag string, str string) int { + ctag := C.CString(tag) + cstr := C.CString(str) + defer C.free(unsafe.Pointer(ctag)) + defer C.free(unsafe.Pointer(cstr)) + ret := C.android_log(level, ctag, cstr) + return int(ret) +} + +func Println(a ...interface{}) (int, error) { + str := fmt.Sprintln(a...) + return androidLog(C.ANDROID_LOG_INFO, Tag, str), nil +} + +func Printf(format string, a ...interface{}) (int, error) { + str := fmt.Sprintf(format, a...) + return androidLog(C.ANDROID_LOG_INFO, Tag, str), nil +} + +func Errorln(a ...interface{}) (int, error) { + str := fmt.Sprintln(a...) + return androidLog(C.ANDROID_LOG_ERROR, Tag, str), nil +} + +func Errorf(format string, a ...interface{}) (int, error) { + str := fmt.Sprintf(format, a...) + return androidLog(C.ANDROID_LOG_ERROR, Tag, str), nil +} + +func init() { + logVicos = Println +} diff --git a/vector-cloud/internal/log/shared.go b/vector-cloud/internal/log/shared.go new file mode 100644 index 0000000..af3a7c0 --- /dev/null +++ b/vector-cloud/internal/log/shared.go @@ -0,0 +1,4 @@ +package log + +// Tag is the log tag sent to __android_log_print on android builds +var Tag = "vic-cloud" diff --git a/vector-cloud/internal/log/util/logging/das/DAS.cpp b/vector-cloud/internal/log/util/logging/das/DAS.cpp new file mode 100644 index 0000000..087fa79 --- /dev/null +++ b/vector-cloud/internal/log/util/logging/das/DAS.cpp @@ -0,0 +1,49 @@ +/** + * File: util/logging/DAS.cpp + * + * Description: DAS extensions for Util::Logging macros + * + * Copyright: Anki, Inc. 2018 + * + **/ + +#include "DAS.h" +#include "json/json.h" +#include + +namespace Anki { +namespace Util { +namespace DAS { + +// +// If boot clock is available, use it, else fall back to monotonic clock +// +#if defined(CLOCK_BOOTTIME) +#define CLOCK CLOCK_BOOTTIME +#else +#define CLOCK CLOCK_MONOTONIC +#endif + +uint64_t UptimeMS() +{ + struct timespec ts{0}; + clock_gettime(CLOCK, &ts); + return (ts.tv_sec * 1000) + (ts.tv_nsec/1000000); +} + +std::string Escape(const char * str) +{ + // Use JsonCpp to quote string, then remove surrounding quotes + const std::string & quotedString = Json::valueToQuotedString(str ? str : ""); + return quotedString.substr(1, quotedString.size()-2); +} + +std::string Escape(const std::string & str) +{ + return Escape(str.c_str()); +} + + +} // end namespace DAS +} // end namespace Util +} // end namespace Anki diff --git a/vector-cloud/internal/log/util/logging/das/DAS.h b/vector-cloud/internal/log/util/logging/das/DAS.h new file mode 100644 index 0000000..2b94186 --- /dev/null +++ b/vector-cloud/internal/log/util/logging/das/DAS.h @@ -0,0 +1,247 @@ +/** + * File: util/logging/DAS.h + * + * Description: DAS extensions for Util::Logging macros + * + * Copyright: Anki, Inc. 2018 + * + **/ + + +#ifndef __util_logging_DAS_h +#define __util_logging_DAS_h + +#include + +// +// This file must be #included by each CPP file that uses DASMSG macros, to +// ensure that macros are expanded correctly by doxygen. If this file +// is included through an intermediate header, code will compile correctly +// but doxygen will not expand macros as expected. +// +#if !defined(__INCLUDE_LEVEL__) +#error "This header may not be included by other headers. We rely on GCC __INCLUDE_LEVEL__ to enforce this restriction." +#error "If your compiler does not define __INCLUDE_LEVEL__, please add an appropriate check for your compiler." +#endif + +#if (__INCLUDE_LEVEL__ > 1) +#error "This header must be included directly from your CPP file. This header may not be included by other headers." +#error "If this header is included by another header, doxygen macros will not expand correctly." +#endif + +namespace Anki { +namespace Util { +namespace DAS { + +// +// DAS v2 event fields +// https://ankiinc.atlassian.net/wiki/spaces/SAI/pages/221151429/DAS+Event+Fields+for+Victor+Robot +// +constexpr const char * SOURCE = "$source"; +constexpr const char * EVENT = "$event"; +constexpr const char * TS = "$ts"; +constexpr const char * SEQ = "$seq"; +constexpr const char * LEVEL = "$level"; +constexpr const char * PROFILE = "$profile"; +constexpr const char * ROBOT = "$robot"; +constexpr const char * ROBOT_VERSION = "$robot_version"; +constexpr const char * FEATURE_TYPE = "$feature_type"; +constexpr const char * FEATURE_RUN = "$feature_run"; +constexpr const char * STR1 = "$s1"; +constexpr const char * STR2 = "$s2"; +constexpr const char * STR3 = "$s3"; +constexpr const char * STR4 = "$s4"; +constexpr const char * INT1 = "$i1"; +constexpr const char * INT2 = "$i2"; +constexpr const char * INT3 = "$i3"; +constexpr const char * INT4 = "$i4"; + +// +// DAS event marker +// +// This is the character chosen to precede a DAS log event on Victor. +// This is used as a hint that a log message should be parsed as an event. +// If this value changes, event readers and event writers must be updated. +// +constexpr const char EVENT_MARKER = '@'; + +// +// DAS field marker +// +// This is the character chosen to separate DAS log event fields on Victor. +// This character may not appear in event data. +// If this value changes, event readers and event writers must be updated. +// +constexpr const char FIELD_MARKER = '\x1F'; + +// +// DAS field count +// This is the number of fields used to represent a DAS log event on Victor. +// If this value changes, event readers and event writers must be updated. +// +constexpr const int FIELD_COUNT = 9; + +// +// Return DAS uptime value, aka millisecs since boot +// +uint64_t UptimeMS(); + +// +// Return DAS encoded string, safe for use with JSON +// +std::string Escape(const char * str); +std::string Escape(const std::string & str); + +} // end namespace DAS +} // end namespace Util +} // end namespace Anki + +namespace Anki { +namespace Util { + +// +// DAS message field +// +struct DasItem +{ + DasItem() = default; + DasItem(const std::string & valueStr) { value = valueStr; } + DasItem(int64_t valueInt) { value = std::to_string(valueInt); } + + inline const std::string & str() const { return value; } + inline const char * c_str() const { return value.c_str(); } + + std::string value; +}; + +// +// DAS message struct +// +// Event name is required. Other fields are optional. +// Event structures should be declared with DASMSG. +// Event fields should be assigned with DASMSG_SET. +// +struct DasMsg +{ + DasMsg(const std::string & eventStr) { event = eventStr; } + + std::string event; + DasItem s1; + DasItem s2; + DasItem s3; + DasItem s4; + DasItem i1; + DasItem i2; + DasItem i3; + DasItem i4; +}; + +// Log an error event +__attribute__((__used__)) +void sLogError(const DasMsg & dasMessage); + +// Log a warning event +__attribute__((__used__)) +void sLogWarning(const DasMsg & dasMessage); + +// Log an info event +__attribute__((__used__)) +void sLogInfo(const DasMsg & dasMessage); + +// Log a debug event +__attribute__((__used__)) +void sLogDebug(const DasMsg & dasMessage); + +} // end namespace Util +} // end namespace Anki + +// +// DAS feature start event +// This is the name of the event used to indicate start a new feature scope. +// If this value changes, or event parameters change, DASManager must be updated to match. +// This is declared as a macro, not a constexpr, so it can be expanded by doxygen. +// +#define DASMSG_FEATURE_START "behavior.feature.start" + +// +// DAS BLE connection events +// These events are used to indicate start/stop of a BLE connection. +// If event name or event parameters change, DASManager must be updated to match. +// Names are defined as a macro, not a constexpr, so they can be expanded by doxygen. +// +#define DASMSG_BLE_CONN_ID_START "ble_conn_id.start" +#define DASMSG_BLE_CONN_ID_STOP "ble_conn_id.stop" + +// +// DAS WIFI connection events +// These events are used to indicate start/stop of a Wi-Fi connection. +// If event name or event parameters change, DASManager must be updated to match. +// Names are defined as a macro, not a constexpr, so they can be expanded by doxygen. +// +#define DASMSG_WIFI_CONN_ID_START "wifi_conn_id.start" +#define DASMSG_WIFI_CONN_ID_STOP "wifi_conn_id.stop" + +// +// DAS Profile ID events +// These events are used to indicate start/stop of association with an Anki profile ID. +// If event name or event parameters change, DASManager must be updated to match. +// Names are defined as a macro, not a constexpr, so they can be expanded by doxygen. +// +#define DASMSG_PROFILE_ID_START "profile_id.start" +#define DASMSG_PROFILE_ID_STOP "profile_id.stop" + +// +// DAS Allow Upload +// This event is used to enable/disable DAS uploads. +// If event name or event parameters change, DASManager must be updated to match. +// Names are defined as a macro, not a constexpr, so they can be expanded by doxygen. +// +#define DASMSG_DAS_ALLOW_UPLOAD "das.allow_upload" + +// +// DAS message macros +// +// These macros are used to ensure that developers provide some description of each event defined. +// Note these macros are expanded two ways! In normal compilation, they are expanded into C++ +// variable declarations and logging calls. When processed by doxygen, they are are expanded +// into syntactically invalid C++ that contains magic directives to produce readable documentation. +// +// Overwriting fields that have already been set (e.g. calling DASMSG_SET(i1, ...) twice) is not allowed. +// +// If a string field can contain JSON syntax characters such as double quotes (") or backslash (\), it should be +// escaped with DASMSG_ESCAPE(). Most string fields are JSON-safe so we do not do this automatically. +// +#ifndef DOXYGEN + +#define DASMSG(ezRef, eventName, documentation) { Anki::Util::DasMsg __DAS_msg(eventName); +#define DASMSG_SET(dasEntry, value, comment) __DAS_msg.dasEntry = Anki::Util::DasItem(value); \ + /* define an empty struct (to detect if this item has been set twice) */ \ + struct _already_set_##dasEntry {}; +#define DASMSG_SEND() Anki::Util::sLogInfo(__DAS_msg); } +#define DASMSG_SEND_WARNING() Anki::Util::sLogWarning(__DAS_msg); } +#define DASMSG_SEND_ERROR() Anki::Util::sLogError(__DAS_msg); } +#define DASMSG_SEND_DEBUG() Anki::Util::sLogDebug(__DAS_msg); } + +#else + +/*! \defgroup dasmsg DAS Messages +*/ + +class DasDoxMsg() {} + +#define DASMSG(ezRef, eventName, documentation) }}}}}}}}/** \ingroup dasmsg */ \ + /** \brief eventName */ \ + /** documentation */ \ + class ezRef(): public DasDoxMsg() { \ + public: +#define DASMSG_SET(dasEntry, value, comment) /** @param dasEntry comment \n*/ +#define DASMSG_SEND }; +#define DASMSG_SEND_WARNING }; +#define DASMSG_SEND_ERROR }; +#define DASMSG_SEND_DEBUG }; + +#endif + +#define DASMSG_ESCAPE(str) Anki::Util::DAS::Escape(str) + +#endif // __util_logging_DAS_h diff --git a/vector-cloud/internal/log/util/logging/logging/androidLogPrintLogger_android.cpp b/vector-cloud/internal/log/util/logging/logging/androidLogPrintLogger_android.cpp new file mode 100644 index 0000000..70268c5 --- /dev/null +++ b/vector-cloud/internal/log/util/logging/logging/androidLogPrintLogger_android.cpp @@ -0,0 +1,67 @@ +/** +* File: androidLogPrintLogger_android.cpp +* +* Description: Implements ILoggerProvider for __android_log_print() +* +* Copyright: Anki, inc. 2017 +* +*/ + +#include "util/logging/androidLogPrintLogger_android.h" +#include "util/logging/iLoggerProvider.h" +#include +#include + +using LogLevel = Anki::Util::ILoggerProvider::LogLevel; + +namespace Anki { +namespace Util { + +AndroidLogPrintLogger::AndroidLogPrintLogger(const std::string& tag) : + _tag(tag) +{ + +} + +static inline android_LogPriority GetPriority(LogLevel level) +{ + switch (level) { + case LogLevel::LOG_LEVEL_DEBUG: + return ANDROID_LOG_DEBUG; + case LogLevel::LOG_LEVEL_INFO: + case LogLevel::LOG_LEVEL_EVENT: + return ANDROID_LOG_INFO; + case LogLevel::LOG_LEVEL_WARN: + return ANDROID_LOG_WARN; + case LogLevel::LOG_LEVEL_ERROR: + /* intentional fallthrough */ + case LogLevel::_LOG_LEVEL_COUNT: + return ANDROID_LOG_ERROR; + } + return ANDROID_LOG_DEFAULT; +} + +void AndroidLogPrintLogger::Log(LogLevel level, + const char * eventName, + const std::vector>& keyValues, + const char * eventValue) +{ + assert(eventName != nullptr); + assert(eventValue != nullptr); + __android_log_print(GetPriority(level), _tag.c_str(), "%s: %s", eventName, eventValue); +} + +void AndroidLogPrintLogger::Log(LogLevel level, + const char * channel, + const char * eventName, + const std::vector>& keyValues, + const char * eventValue) +{ + assert(channel != nullptr); + assert(eventName != nullptr); + assert(eventValue != nullptr); + __android_log_print(GetPriority(level), _tag.c_str(), "[@%s] %s: %s", channel, eventName, eventValue); +} + +} // end namespace Util +} // end namespace Anki diff --git a/vector-cloud/internal/log/util/logging/logging/androidLogPrintLogger_android.h b/vector-cloud/internal/log/util/logging/logging/androidLogPrintLogger_android.h new file mode 100644 index 0000000..c0d7123 --- /dev/null +++ b/vector-cloud/internal/log/util/logging/logging/androidLogPrintLogger_android.h @@ -0,0 +1,84 @@ +/** +* File: androidLogPrintLogger_android.h +* +* Description: Implements ILoggerProvider for __android_log_print() +* +* Copyright: Anki, inc. 2017 +* +*/ +#ifndef __Util_Logging_AndroidLogPrintLogger_H_ +#define __Util_Logging_AndroidLogPrintLogger_H_ + +#include "util/logging/iLoggerProvider.h" + +#include + +namespace Anki { +namespace Util { + +class AndroidLogPrintLogger : public ILoggerProvider { +public: + + AndroidLogPrintLogger(const std::string& tag = "anki"); + + // Implements ILoggerProvider + virtual void PrintEvent(const char * eventName, + const std::vector>& keyValues, + const char * eventValue) + { + Log(LogLevel::LOG_LEVEL_EVENT, eventName, keyValues, eventValue); + } + + virtual void PrintLogE(const char * eventName, + const std::vector>& keyValues, + const char * eventValue) + { + Log(LogLevel::LOG_LEVEL_ERROR, eventName, keyValues, eventValue); + } + + virtual void PrintLogW(const char* eventName, + const std::vector>& keyValues, + const char * eventValue) + { + Log(LogLevel::LOG_LEVEL_WARN, eventName, keyValues, eventValue); + } + + virtual void PrintLogI(const char * channel, + const char * eventName, + const std::vector>& keyValues, + const char * eventValue) + { + Log(LogLevel::LOG_LEVEL_INFO, channel, eventName, keyValues, eventValue); + } + + virtual void PrintLogD(const char * channel, + const char * eventName, + const std::vector>& keyValues, + const char * eventValue) + { + Log(LogLevel::LOG_LEVEL_DEBUG, channel, eventName, keyValues, eventValue); + } + + // Implements IFormattedLoggerProvider + // virtual void Log(ILoggerProvider::LogLevel logLevel, const std::string& message) override; + +private: + std::string _tag; + + void Log(LogLevel level, + const char * eventName, + const std::vector>& keyValues, + const char * eventValue); + + void Log(LogLevel level, + const char * channel, + const char * eventName, + const std::vector>& keyValues, + const char * eventValue); + +}; // class AndroidLogPrintLogger + +} // end namespace Util +} // end namespace Anki + +#endif //__Util_Logging_AndroidLogPrintLogger_H_ diff --git a/vector-cloud/internal/log/util/logging/logging/androidLogPrintLogger_vicos.cpp b/vector-cloud/internal/log/util/logging/logging/androidLogPrintLogger_vicos.cpp new file mode 100644 index 0000000..5cc4163 --- /dev/null +++ b/vector-cloud/internal/log/util/logging/logging/androidLogPrintLogger_vicos.cpp @@ -0,0 +1,68 @@ +/** +* File: androidLogPrintLogger_vicos.cpp +* +* Description: Implements ILoggerProvider for __android_log_print() +* +* Copyright: Anki, inc. 2017 +* +*/ + +#include "util/logging/androidLogPrintLogger_vicos.h" +#include "util/logging/iLoggerProvider.h" +#include +#include + +using LogLevel = Anki::Util::ILoggerProvider::LogLevel; + +namespace Anki { +namespace Util { + +AndroidLogPrintLogger::AndroidLogPrintLogger(const std::string& tag) : + _tag(tag) +{ + +} + +static inline android_LogPriority GetPriority(LogLevel level) +{ + switch (level) { + case LogLevel::LOG_LEVEL_DEBUG: + return ANDROID_LOG_VERBOSE; + case LogLevel::LOG_LEVEL_INFO: + return ANDROID_LOG_DEBUG; + case LogLevel::LOG_LEVEL_EVENT: + return ANDROID_LOG_INFO; + case LogLevel::LOG_LEVEL_WARN: + return ANDROID_LOG_WARN; + case LogLevel::LOG_LEVEL_ERROR: + /* intentional fallthrough */ + case LogLevel::_LOG_LEVEL_COUNT: + return ANDROID_LOG_ERROR; + } + return ANDROID_LOG_DEFAULT; +} + +void AndroidLogPrintLogger::Log(LogLevel level, + const char * eventName, + const std::vector>& keyValues, + const char * eventValue) +{ + assert(eventName != nullptr); + assert(eventValue != nullptr); + __android_log_print(GetPriority(level), _tag.c_str(), "%s: %s", eventName, eventValue); +} + +void AndroidLogPrintLogger::Log(LogLevel level, + const char * channel, + const char * eventName, + const std::vector>& keyValues, + const char * eventValue) +{ + assert(channel != nullptr); + assert(eventName != nullptr); + assert(eventValue != nullptr); + __android_log_print(GetPriority(level), _tag.c_str(), "[@%s] %s: %s", channel, eventName, eventValue); +} + +} // end namespace Util +} // end namespace Anki diff --git a/vector-cloud/internal/log/util/logging/logging/androidLogPrintLogger_vicos.h b/vector-cloud/internal/log/util/logging/logging/androidLogPrintLogger_vicos.h new file mode 100644 index 0000000..a0c6a42 --- /dev/null +++ b/vector-cloud/internal/log/util/logging/logging/androidLogPrintLogger_vicos.h @@ -0,0 +1,84 @@ +/** +* File: androidLogPrintLogger_vicos.h +* +* Description: Implements ILoggerProvider for __android_log_print() +* +* Copyright: Anki, inc. 2017 +* +*/ +#ifndef __Util_Logging_AndroidLogPrintLogger_H_ +#define __Util_Logging_AndroidLogPrintLogger_H_ + +#include "util/logging/iLoggerProvider.h" + +#include + +namespace Anki { +namespace Util { + +class AndroidLogPrintLogger : public ILoggerProvider { +public: + + AndroidLogPrintLogger(const std::string& tag = "anki"); + + // Implements ILoggerProvider + virtual void PrintEvent(const char * eventName, + const std::vector>& keyValues, + const char * eventValue) + { + Log(LogLevel::LOG_LEVEL_EVENT, eventName, keyValues, eventValue); + } + + virtual void PrintLogE(const char * eventName, + const std::vector>& keyValues, + const char * eventValue) + { + Log(LogLevel::LOG_LEVEL_ERROR, eventName, keyValues, eventValue); + } + + virtual void PrintLogW(const char* eventName, + const std::vector>& keyValues, + const char * eventValue) + { + Log(LogLevel::LOG_LEVEL_WARN, eventName, keyValues, eventValue); + } + + virtual void PrintLogI(const char * channel, + const char * eventName, + const std::vector>& keyValues, + const char * eventValue) + { + Log(LogLevel::LOG_LEVEL_INFO, channel, eventName, keyValues, eventValue); + } + + virtual void PrintLogD(const char * channel, + const char * eventName, + const std::vector>& keyValues, + const char * eventValue) + { + Log(LogLevel::LOG_LEVEL_DEBUG, channel, eventName, keyValues, eventValue); + } + + // Implements IFormattedLoggerProvider + // virtual void Log(ILoggerProvider::LogLevel logLevel, const std::string& message) override; + +private: + std::string _tag; + + void Log(LogLevel level, + const char * eventName, + const std::vector>& keyValues, + const char * eventValue); + + void Log(LogLevel level, + const char * channel, + const char * eventName, + const std::vector>& keyValues, + const char * eventValue); + +}; // class AndroidLogPrintLogger + +} // end namespace Util +} // end namespace Anki + +#endif //__Util_Logging_AndroidLogPrintLogger_H_ diff --git a/vector-cloud/internal/log/util/logging/logging/callstack.cpp b/vector-cloud/internal/log/util/logging/logging/callstack.cpp new file mode 100644 index 0000000..e6b8333 --- /dev/null +++ b/vector-cloud/internal/log/util/logging/logging/callstack.cpp @@ -0,0 +1,180 @@ +/** + * File: callstack + * + * Author: raul + * Created: 06/18/2016 + * + * Description: Functions related to debugging the callstack. + * + * Copyright: Anki, Inc. 2016 + * + **/ +#include "callstack.h" +#include "util/logging/logging.h" +#include "util/string/stringHelpers.h" + +#include + +#include +#include +#include +#include + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// appropriate headers per platform +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +#if defined(__MACH__) && defined(__APPLE__) +#define USE_BACKTRACE +#define USE_MACOS_DEMANGLE +#elif defined(VICOS) +#define USE_BACKTRACE +#define USE_VICOS_DEMANGLE +#elif defined(ANDROID) || defined(LINUX) + +#else +#error "Unsupported platform" +#endif + +#ifdef USE_BACKTRACE +#include +#include +#endif + +namespace Anki { +namespace Util { + +#ifdef USE_MACOS_DEMANGLE +static std::string Demangle(const std::string& backtraceFrame) { + std::vector splitFrame = SplitString(backtraceFrame, ' '); + + // -4 because the valid statuses from __cxa_demangle() is 0, -1, -2, -3. If we got -4 after + // calling __cxa_demangle() something very wrong happened. + int status = -4; + + // Make sure that when the backtrace frame string is split + // by spaces there is at least 3 items in there. + if (splitFrame.size() > 3) { + // A backtraceFrame looks like: + // "0 webotsCtrlGameEngine 0x000000010cc84894 _ZN4Anki4Util14sDumpCallstackERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEE + 52" + // The mangled symbol is the third element from the back, when split by ' ' + // + // Use a tempPtr here in case __cxa_demangle cannot demangle the given string and returns a nullptr. + char* tempPtr = abi::__cxa_demangle(splitFrame.end()[-3].c_str(), 0, 0, &status); + + switch (status) { + case 0: + assert(tempPtr != nullptr); + // Everything is fine, proceed to change the mangled name into the demangled version. + splitFrame.end()[-3] = tempPtr; + break; + + case -2: + { + // Demangle didn't work, don't change the name. + PRINT_NAMED_DEBUG("Callstack.Demangle", + "%s is not a valid name under the C++ ABI mangling rules.", + splitFrame.end()[-3].c_str()); + break; + } + + default: + { + // Demangle didn't work, don't change the name. + PRINT_NAMED_WARNING("Callstack.Demangle", + "Couldn't demangle the symbol, __cxa_demangle returned status = %i", status); + break; + } + } + free(tempPtr); + } else { + PRINT_NAMED_WARNING("Callstack.Demangle", + "Something is wrong with the format of the backtrace frame. It should look " + "something like " + "'0 webotsCtrlGameEngine 0x000000010cc84894 _ZN4Anki4Util14sDumpCallstackERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEE + 52'"); + } + + std::ostringstream os; + for (const std::string& part : splitFrame) { + os << part << " "; + } + + return os.str(); +} +#endif // USE_MACOS_DEMANGLE + +#ifdef USE_VICOS_DEMANGLE +static std::string Demangle(const char * frame) +{ + if (frame == nullptr) { + // Don't crash + return "NULL"; + } + + // + // VicOS stack frames look like these lines: + // /anki/lib/libankiutil.so(_ZN4Anki4Util14sDumpCallstackERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEE+0x23) [0xb03d4920] + // /anki/bin/vic-anim(+0x3dcc2) [0x7f592cc2] + // or generically + // obj(name+offset) [addr] + // + // The mangled name (if any) appears between left paren and plus. + // If we can't find a mangled name, return the frame unchanged. + // + std::string s = frame; + + auto pos1 = s.find('('); + if (pos1 == std::string::npos) { + // Can't find left paren + return s; + } + + auto pos2 = s.find('+', pos1+1); + if (pos2 == std::string::npos) { + // Can't find plus + return s; + } + + // Get the mangled name + std::string name = s.substr(pos1+1, pos2-pos1-1); + if (name.empty()) { + // No name provided + return s; + } + + // Perform the demangle + int status = 0; + char * temp = abi::__cxa_demangle(name.c_str(), 0, 0, &status); + if (temp == nullptr) { + // Unable to demangle + return s; + } + + // Replace mangled name with demangled name + s.replace(pos1+1, pos2-pos1-1, temp); + free(temp); + + return s; +} +#endif + +void sDumpCallstack(const std::string& name) +{ + #ifdef USE_BACKTRACE + { + void* callstack[128]; + int frames = backtrace(callstack, 128); + char** strs = backtrace_symbols(callstack, frames); + for (int i = 0; i < frames; ++i) { + PRINT_CH_INFO("Unfiltered", name.c_str(), "%s", Demangle(strs[i]).c_str()); + } + free(strs); + } + #else + + //TODO implement android + + #endif +} + +} // namespace Util +} // namespace Anki diff --git a/vector-cloud/internal/log/util/logging/logging/callstack.h b/vector-cloud/internal/log/util/logging/logging/callstack.h new file mode 100644 index 0000000..1968d12 --- /dev/null +++ b/vector-cloud/internal/log/util/logging/logging/callstack.h @@ -0,0 +1,27 @@ +/** + * File: callstack + * + * Author: raul + * Created: 06/18/2016 + * + * Description: Functions related to debugging the callstack. + * + * Copyright: Anki, Inc. 2016 + * + **/ + +#ifndef __Util_Logging_Callstack_H_ +#define __Util_Logging_Callstack_H_ + +#include + +namespace Anki{ +namespace Util { + +// dumps current callstack to log +void sDumpCallstack(const std::string& name); + +} // namespace Util +} // namespace Anki + +#endif // diff --git a/vector-cloud/internal/log/util/logging/logging/channelFilter.cpp b/vector-cloud/internal/log/util/logging/logging/channelFilter.cpp new file mode 100644 index 0000000..fab9877 --- /dev/null +++ b/vector-cloud/internal/log/util/logging/logging/channelFilter.cpp @@ -0,0 +1,133 @@ +/** + * File: ChannelFilter + * + * Author: Zhetao Wang + * Created: 05/19/2015 + * + * Description: Class that provides a channel filter (for logs) that can load from json and provides console vars + * to enable/disable log channels at runtime. + * + * Copyright: Anki, inc. 2015 + * + */ +#include "channelFilter.h" + +#include "util/logging/logging.h" +#include "util/string/stringUtils.h" +#include +#include + +#define LOG_CHANNEL "LOG" + +namespace Anki { +namespace Util { + +const char* kChannelListKey = "channels"; +const char* kChannelNameKey = "channel"; +const char* kChannelEnabledKey = "enabled"; + +ChannelFilter::~ChannelFilter() +{ + for(auto& c : _channelEnableList) { + delete c.second; + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void ChannelFilter::Initialize(const Json::Value& config) +{ + // parse config + if (!config.isNull()) { + for (const auto& channel : config[kChannelListKey]) { + + // parse channel name + DEV_ASSERT(channel[kChannelNameKey].isString(), "ChannelFilter.Initialize.BadName"); + const std::string& channelName = channel[kChannelNameKey].asString(); + + // parse value + DEV_ASSERT(channel[kChannelEnabledKey].isBool(), "ChannelFilter.Initialize.BadEnableFlag"); + const bool channelEnabled = channel[kChannelEnabledKey].asBool(); + static const bool kUnregisterInDestructor = true; + _channelEnableList.emplace(channelName, new ChannelVar(channelName, channelEnabled, kUnregisterInDestructor)); + } + } + + // Print which channels are enabled + { + std::stringstream enabledStr; + std::stringstream disabledStr; + int enCount = 0; + int disCount = 0; + for( const auto& pair : _channelEnableList ) { + if ( pair.second->enable ) { + enabledStr << ((++enCount==1) ? "":", "); + enabledStr << "'" << pair.first << "'"; + } else { + disabledStr << ((++disCount==1) ? "":", "); + disabledStr << "'" << pair.first << "'"; + } + } + if ( enCount == 0 ) { + enabledStr << "(None were enabled!)"; + } + if ( disCount == 0 ) { + disabledStr << "(None were disabled)"; + } + LOG_INFO("ChannelFilter.Channels", ": Enabled [%s]; Disabled [%s]", + enabledStr.str().c_str(), + disabledStr.str().c_str()); + } + + _initialized = true; +} + +void ChannelFilter::EnableChannel(const std::string& channelName) +{ + // if found, set as true + auto it = _channelEnableList.find(channelName); + if(it != _channelEnableList.end()) { + it->second->enable = true; + } else { + static const bool kUnregisterInDestructor = true; + _channelEnableList.emplace(channelName, new ChannelVar(channelName, true, kUnregisterInDestructor)); + } +} + +void ChannelFilter::DisableChannel(const std::string& channelName) +{ + // if found, set as false + auto it = _channelEnableList.find(channelName); + if(it != _channelEnableList.end()) { + it->second->enable = false; + } else { + static const bool kUnregisterInDestructor = true; + _channelEnableList.emplace(channelName, new ChannelVar(channelName, false, kUnregisterInDestructor)); + } +} + +bool ChannelFilter::IsChannelEnabled(const std::string& channelName) const +{ + for(const auto& pair : _channelEnableList) { + if (pair.second->enable) { + if (pair.first == "*") { + // wildcard match, match anything + return true; + } else if (StringEndsWith(pair.first, "*")) { + // prefix match, match up to wildcard character + const std::string match = pair.first.substr(0, pair.first.length() - 1); + if (StringStartsWith(channelName, match)) { + return true; + } + } else { + // exact match + if (pair.first == channelName) { + return true; + } + } + } + } + return false; +} + +} // namespace Util +} // namespace Anki diff --git a/vector-cloud/internal/log/util/logging/logging/channelFilter.h b/vector-cloud/internal/log/util/logging/logging/channelFilter.h new file mode 100644 index 0000000..bed0eea --- /dev/null +++ b/vector-cloud/internal/log/util/logging/logging/channelFilter.h @@ -0,0 +1,76 @@ +/** + * File: ChannelFilter + * + * Author: Zhetao Wang + * Created: 05/19/2015 + * + * Description: Class that provides a channel filter (for logs) that can load from json and provides console vars + * to enable/disable log channels at runtime. + * + * Copyright: Anki, inc. 2015 + * + */ + +#ifndef __Util_Logging_ChannelFilter_H__ +#define __Util_Logging_ChannelFilter_H__ + +#include "iChannelFilter.h" +#include "util/console/consoleInterface.h" +#include "util/helpers/noncopyable.h" +#include "json/json.h" + +#include +#include + +namespace Anki { +namespace Util { + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// ChannelVar: console var +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +struct ChannelVar : public ConsoleVar { + ChannelVar(const std::string& name, bool defaultEnable, bool unregisterInDestructor) + : ConsoleVar(enable, name.c_str(), "Channels", unregisterInDestructor) + , enable(defaultEnable) { + // We must re-set the default value here, after the call to ConsoleVar constructor, + // because that constructor uses a reference to "enable", but enable isn't set until + // after that constructor call. VIC-13609 + this->_defaultValue = enable; + } + + virtual void ToggleValue() override { + ConsoleVar::ToggleValue(); + + // report back to channel filter if required + } + + bool enable; // variable where the value is stored +}; + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// ChannelFilter +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +class ChannelFilter : public IChannelFilter, Anki::Util::noncopyable { +public: + ChannelFilter() : _initialized(false){} + ~ChannelFilter(); + + // initialize with an optional json configuration (can be empty) + void Initialize(const Json::Value& config = Json::Value()); + inline bool IsInitialized() const{ return _initialized; } + + void EnableChannel(const std::string& channelName); + void DisableChannel(const std::string& channelName); + + // IChannelFilter API + virtual bool IsChannelEnabled(const std::string& channelName) const override; + +private: + std::unordered_map _channelEnableList; + bool _initialized; +}; + +} // namespace Util +} // namespace Anki + +#endif /* defined(__Util_Logging_ChannelFilter_H__) */ diff --git a/vector-cloud/internal/log/util/logging/logging/eventProviderLoggingAdapter.cpp b/vector-cloud/internal/log/util/logging/logging/eventProviderLoggingAdapter.cpp new file mode 100644 index 0000000..ff0ce8e --- /dev/null +++ b/vector-cloud/internal/log/util/logging/logging/eventProviderLoggingAdapter.cpp @@ -0,0 +1,113 @@ +/** + * File: eventProviderLoggingAdapter.cpp + * + * Author: Brad Neuman + * Created: 2018-08-20 + * + * Description: Adapter to log DAS event info over another ILoggerProvider (e.g. for webots) + * + * Copyright: Anki, Inc. 2018 + * + **/ + +#include "util/logging/eventProviderLoggingAdapter.h" +#include "util/logging/DAS.h" + +#include +#include +#include + + +namespace Anki { +namespace Util { + +EventProviderLoggingAdapter::EventProviderLoggingAdapter( ILoggerProvider* logger ) + : _logger(logger) +{ +} + +EventProviderLoggingAdapter::~EventProviderLoggingAdapter() +{ +} + +void EventProviderLoggingAdapter::SetGlobal(const char * key, const char * value) +{ + // Key may not be null + assert(key != nullptr); + + std::lock_guard lock(_mutex); + + if (value == nullptr) { + if( _logger ) { + _logger->PrintEvent("DAS_GLOBALS.Clear", {}, key); + } + + _eventGlobals.erase(key); + } else { + if( _logger ) { + std::stringstream ss; + ss << key << "=" << value; + _logger->PrintEvent("DAS_GLOBALS.Set", {}, ss.str().c_str()); + } + _eventGlobals.emplace(std::pair{key, value}); + } +} + +void EventProviderLoggingAdapter::GetGlobals(std::map & globals) +{ + std::lock_guard lock(_mutex); + globals = _eventGlobals; +} + + +#define DAS_KV_HELPER(kv, msg, item) { \ + if( !msg.item.value.empty() ) { \ + kv.emplace_back( #item, msg.item.value.c_str() ); \ + } \ +} + +void EventProviderLoggingAdapter::LogEvent(LogLevel level, const DasMsg & dasMsg) +{ + if( _logger == nullptr ) { + return; + } + + // TODO:(bn) option for whether or not to include globals? + + std::vector> keyValues; + DAS_KV_HELPER(keyValues, dasMsg, s1); + DAS_KV_HELPER(keyValues, dasMsg, s2); + DAS_KV_HELPER(keyValues, dasMsg, s3); + DAS_KV_HELPER(keyValues, dasMsg, s4); + DAS_KV_HELPER(keyValues, dasMsg, i1); + DAS_KV_HELPER(keyValues, dasMsg, i2); + DAS_KV_HELPER(keyValues, dasMsg, i3); + DAS_KV_HELPER(keyValues, dasMsg, i4); + + const std::string & uptime_ms = std::to_string(Anki::Util::DAS::UptimeMS()); + keyValues.emplace_back("uptime_ms", uptime_ms.c_str()); + + switch(level) { + case LOG_LEVEL_DEBUG: + case LOG_LEVEL_INFO: + case LOG_LEVEL_EVENT: + _logger->PrintEvent("DASMSG", keyValues, dasMsg.event.c_str()); + break; + + case LOG_LEVEL_WARN: + _logger->PrintLogW("DASMSG", keyValues, dasMsg.event.c_str()); + break; + + case LOG_LEVEL_ERROR: + _logger->PrintLogE("DASMSG", keyValues, dasMsg.event.c_str()); + break; + + case _LOG_LEVEL_COUNT: + assert(level != _LOG_LEVEL_COUNT); + break; + } +} + + +} // end namespace Util +} // end namespace Anki diff --git a/vector-cloud/internal/log/util/logging/logging/eventProviderLoggingAdapter.h b/vector-cloud/internal/log/util/logging/logging/eventProviderLoggingAdapter.h new file mode 100644 index 0000000..d8c9052 --- /dev/null +++ b/vector-cloud/internal/log/util/logging/logging/eventProviderLoggingAdapter.h @@ -0,0 +1,50 @@ +/** + * File: eventProviderLoggingAdapter.h + * + * Author: Brad Neuman + * Created: 2018-08-20 + * + * Description: Adapter to log DAS event info over another ILoggerProvider (e.g. for webots) + * + * Copyright: Anki, Inc. 2018 + * + **/ + +#ifndef __Util_Logging_EventProviderLoggingAdapter_H__ +#define __Util_Logging_EventProviderLoggingAdapter_H__ + +#include "util/logging/iLoggerProvider.h" +#include "util/logging/iEventProvider.h" + +#include + +namespace Anki { +namespace Util { + +class EventProviderLoggingAdapter : public IEventProvider +{ +public: + EventProviderLoggingAdapter( ILoggerProvider* logger ); + virtual ~EventProviderLoggingAdapter(); + + // Implements IEventProvider + virtual void SetGlobal(const char * key, const char * value) override; + virtual void GetGlobals(std::map & globals) override; + virtual void LogEvent(LogLevel level, const DasMsg & dasMsg) override; + +private: + + ILoggerProvider* _logger; + + std::mutex _mutex; + std::map _eventGlobals; + + +}; + + +} // end namespace Util +} // end namespace Anki + + +#endif diff --git a/vector-cloud/internal/log/util/logging/logging/iChannelFilter.h b/vector-cloud/internal/log/util/logging/logging/iChannelFilter.h new file mode 100644 index 0000000..d22091a --- /dev/null +++ b/vector-cloud/internal/log/util/logging/logging/iChannelFilter.h @@ -0,0 +1,35 @@ +/** + * File: iChannelFilter + * + * Author: raul + * Created: 06/30/16 + * + * Description: Interface for channel filters that we can apply to loggers. + * + * Copyright: Anki, inc. 2015 + * + */ + +#ifndef __Util_Logging_iChannelFilter_H__ +#define __Util_Logging_iChannelFilter_H__ + +#include + +namespace Anki { +namespace Util { + +class IChannelFilter +{ +public: + + // destructor + virtual ~IChannelFilter() {} + + // returns true if the given channel is enabled, false otherwise + virtual bool IsChannelEnabled(const std::string& channelName) const = 0; +}; + +} // namespace Util +} // namespace Anki + +#endif diff --git a/vector-cloud/internal/log/util/logging/logging/iEntityLoggerComponent.h b/vector-cloud/internal/log/util/logging/logging/iEntityLoggerComponent.h new file mode 100644 index 0000000..45d5765 --- /dev/null +++ b/vector-cloud/internal/log/util/logging/logging/iEntityLoggerComponent.h @@ -0,0 +1,35 @@ +/** + * File: iEntityLoggerComponent.h + * + * Author: damjan + * Created: 5/5/15. + * + * Description: Provides logging to single entity, logs comming out of + * this logger will have entity data attached to the correct fields ($phys $data $group). + * + * + * Copyright: Anki, Inc. 2014 + * + **/ +#ifndef __Util_iEntityLoggerComponent_H__ +#define __Util_iEntityLoggerComponent_H__ + +namespace Anki { +namespace Util { + +class IEntityLoggerComponent { +public: + virtual ~IEntityLoggerComponent() = default; + // loggers + virtual void ErrorF(const char* name, const char* format, ...) const __attribute((format(printf, 3, 4))) {}; + virtual void WarnF(const char *name, const char *format, ...) const __attribute((format(printf, 3, 4))) {}; + virtual void InfoF(const char* name, const char* format, ...) const __attribute((format(printf, 3, 4))) {}; + virtual void ChanneledInfoF(const char* channel, const char* name, const char* format, ...) const __attribute((format(printf, 4, 5))) {}; + virtual void DebugF(const char* event, const char* format, ...) const __attribute((format(printf, 3, 4))) {}; + +}; + +} // end namespace Util +} // end namespace Anki + +#endif //__Util_iEntityLoggerComponent_H__ diff --git a/vector-cloud/internal/log/util/logging/logging/iEventProvider.h b/vector-cloud/internal/log/util/logging/logging/iEventProvider.h new file mode 100644 index 0000000..aa45830 --- /dev/null +++ b/vector-cloud/internal/log/util/logging/logging/iEventProvider.h @@ -0,0 +1,75 @@ +/** +* File: iEventProvider +* +* Author: damjan +* Created: 4/21/2015 +* +* Description: interface for anki BI Events +* +* Copyright: Anki, Inc. 2014 +* +**/ + +#ifndef __Util_Logging_IEventProvider_H__ +#define __Util_Logging_IEventProvider_H__ + +#include "logtypes.h" + +#include +#include + +// Forward declarations +namespace Anki { + namespace Util { + struct DasMsg; + } +} + +namespace Anki { +namespace Util { + +class IEventProvider { +public: + + // Log an error event + inline void LogError(const DasMsg & dasMsg) { + LogEvent(LOG_LEVEL_ERROR, dasMsg); + } + + // Log a warning event + inline void LogWarning(const DasMsg & dasMsg) { + LogEvent(LOG_LEVEL_WARN, dasMsg); + } + + // Log an info event + inline void LogInfo(const DasMsg & dasMsg) { + LogEvent(LOG_LEVEL_INFO, dasMsg); + } + + // Log a debug event + inline void LogDebug(const DasMsg & dasMsg) { + LogEvent(LOG_LEVEL_DEBUG, dasMsg); + } + + // sets global properties. + // all future logs+events will have the updated globals attached to them + virtual void SetGlobal(const char* key, const char* value) = 0; + + // Get Globals + virtual void GetGlobals(std::map& dasGlobals) = 0; + + virtual void EnableNetwork(int reason) {} + virtual void DisableNetwork(int reason) {} + +protected: + + // Log an event at given level + // To be implemented by each event provider + virtual void LogEvent(LogLevel level, const DasMsg & dasMsg) = 0; + +}; + +} // namespace Util +} // namespace Anki + +#endif // __Util_Logging_IEventProvider_H__ diff --git a/vector-cloud/internal/log/util/logging/logging/iFormattedLoggerProvider.cpp b/vector-cloud/internal/log/util/logging/logging/iFormattedLoggerProvider.cpp new file mode 100644 index 0000000..f37a051 --- /dev/null +++ b/vector-cloud/internal/log/util/logging/logging/iFormattedLoggerProvider.cpp @@ -0,0 +1,193 @@ +/** + * File: iFormattedLoggerProvider + * + * Author: Trevor Dasch + * Created: 2/10/16 + * + * Description: abstract class + * that formats the log into a c-string + * and calls its Log function. + * + * Copyright: Anki, inc. 2016 + * + */ +#include "util/logging/iFormattedLoggerProvider.h" +#include "util/logging/logging.h" +#include "util/math/numericCast.h" +#include "json/json.h" +#include +#include +#include + +#define PRINT_TID 1 +#define PRINT_DAS_EXTRAS_BEFORE_EVENT 0 +#define PRINT_DAS_EXTRAS_AFTER_EVENT 1 +#define PRINT_DAS_EXTRAS_FOR_EVENT_LEVEL_ONLY 0 + +#if (PRINT_TID) +#include +#include +#include +#include +#include +#include +#include +#endif + +namespace Anki { +namespace Util { + +#if (PRINT_TID) + static std::atomic thread_max {0}; + static pthread_key_t thread_id_key; + static pthread_once_t thread_id_once = PTHREAD_ONCE_INIT; + + static void thread_id_init() + { + pthread_key_create(&thread_id_key, nullptr); + } +#endif + +const char* kLevelListKey = "levels"; +const char* kLevelNameKey = "level"; +const char* kLevelEnabledKey = "enabled"; + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +IFormattedLoggerProvider::IFormattedLoggerProvider() +{ + _logLevelEnabledFlags.resize(_LOG_LEVEL_COUNT, false); + SetMinLogLevel(LOG_LEVEL_DEBUG); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void IFormattedLoggerProvider::SetMinLogLevel(LogLevel logLevel) +{ + DEV_ASSERT(logLevel < _logLevelEnabledFlags.size(), "IFormattedLoggerProvider.SetMinLogLevel.InvalidLogLevel"); + for (int i = 0; i < logLevel; ++i) { + _logLevelEnabledFlags[i] = false; + } + for (int i = logLevel; i < _LOG_LEVEL_COUNT; ++i) { + _logLevelEnabledFlags[i] = true; + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void IFormattedLoggerProvider::SetLogLevelEnabled(LogLevel logLevel, bool enabled) +{ + DEV_ASSERT(logLevel < _logLevelEnabledFlags.size(), "IFormattedLoggerProvider.SetLogLevelEnabled.InvalidLogLevel"); + _logLevelEnabledFlags[logLevel] = enabled; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void IFormattedLoggerProvider::ParseLogLevelSettings(const Json::Value& config) +{ + // parse config + if ( !config.isNull() ) { + for( const auto& logLevelInfo : config[kLevelListKey] ) + { + // parse channel name + DEV_ASSERT(logLevelInfo[kLevelNameKey].isString(), "IFormattedLoggerProvider.ParseLogLevelSettings.BadName"); + const std::string& logLevelName = logLevelInfo[kLevelNameKey].asString(); + LogLevel logLevelVal = GetLogLevelValue(logLevelName); + if (logLevelVal < _LOG_LEVEL_COUNT) + { + // parse value + DEV_ASSERT(logLevelInfo[kLevelEnabledKey].isBool(), "IFormattedLoggerProvider.ParseLogLevelSettings.BadEnableFlag"); + const bool logLevelEnabled = logLevelInfo[kLevelEnabledKey].asBool(); + + // Register channel + SetLogLevelEnabled(logLevelVal, logLevelEnabled); + } + } + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool IFormattedLoggerProvider::IsLogLevelEnabled(LogLevel logLevel) const +{ + DEV_ASSERT(logLevel < _logLevelEnabledFlags.size(), "IFormattedLoggerProvider.IsLogLevelEnabled.InvalidLogLevel"); + const bool ret = _logLevelEnabledFlags[logLevel]; + return ret; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void IFormattedLoggerProvider::FormatAndLog(IFormattedLoggerProvider::LogLevel logLevel, const char* eventName, + const std::vector>& keyValues, + const char* eventValue) +{ + FormatAndLogChanneled(logLevel, "", eventName, keyValues, eventValue); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void IFormattedLoggerProvider::FormatAndLogChanneled(ILoggerProvider::LogLevel logLevel, const char* channel, + const char* eventName, + const std::vector>& keyValues, + const char* eventValue) +{ + #if (PRINT_TID) + pthread_once(&thread_id_once, thread_id_init); + + uint32_t thread_id = numeric_cast_clamped((uintptr_t)pthread_getspecific(thread_id_key)); + if(0 == thread_id) { + thread_id = ++thread_max; + pthread_setspecific(thread_id_key, (void*)((uintptr_t)thread_id)); + } + #endif + + #if (PRINT_DAS_EXTRAS_BEFORE_EVENT || PRINT_DAS_EXTRAS_AFTER_EVENT) + const bool printDasExtras = !PRINT_DAS_EXTRAS_FOR_EVENT_LEVEL_ONLY || + logLevel == ILoggerProvider::LogLevel::LOG_LEVEL_EVENT; + const size_t kMaxStringBufferSize = 1024; + char logString[kMaxStringBufferSize]{0}; + if(printDasExtras) { + for (const auto& keyValuePair : keyValues) { + snprintf(logString, kMaxStringBufferSize, "%s[%s: %s] ", logString, keyValuePair.first, keyValuePair.second); + } + } + #endif + + std::ostringstream stream; + + #if (PRINT_TID) + stream << "(t:" << std::setw(2) << std::setfill('0') << thread_id << ") "; + #endif + + stream << "[" << GetLogLevelString(logLevel) << "]"; + + #if (PRINT_DAS_EXTRAS_BEFORE_EVENT) + if(printDasExtras) { + stream << " " << logString; + } + #endif + + ASSERT_NAMED(eventName!=nullptr, "IFormattedLoggerProvider.FormatAndLogChanneled logging null eventName"); + ASSERT_NAMED(eventValue!=nullptr, "IFormattedLoggerProvider.FormatAndLogChanneled logging null eventValue"); + static const char* emptyStr = ""; + if( eventName == nullptr ) { + eventName = emptyStr; + } + if( eventValue == nullptr ) { + eventValue = emptyStr; + } + + std::string channelStr(channel); + if (!channelStr.empty()) { + stream << "[@" << channelStr << "] " << eventName << " " << eventValue; + } else { + stream << " " << eventName << " " << eventValue; + } + + #if (PRINT_DAS_EXTRAS_AFTER_EVENT) + if(printDasExtras) { + stream << " " << logString; + } + #endif + + stream << std::endl; + + Log(logLevel, stream.str()); +} + + +} // end namespace Util +} // end namespace Anki diff --git a/vector-cloud/internal/log/util/logging/logging/iFormattedLoggerProvider.h b/vector-cloud/internal/log/util/logging/logging/iFormattedLoggerProvider.h new file mode 100644 index 0000000..78f135c --- /dev/null +++ b/vector-cloud/internal/log/util/logging/logging/iFormattedLoggerProvider.h @@ -0,0 +1,93 @@ +/** + * File: iFormattedLoggerProvider + * + * Author: Trevor Dasch + * Created: 2/10/16 + * + * Description: abstract class + * that formats the log into a c-string + * and calls its Log function. + * + * Copyright: Anki, inc. 2016 + * + */ +#ifndef __Util_Logging_IFormattedLoggerProvider_H_ +#define __Util_Logging_IFormattedLoggerProvider_H_ +#include "util/logging/iLoggerProvider.h" +#include "json/json-forwards.h" +#include + +namespace Anki { + namespace Util { + + class IFormattedLoggerProvider : public ILoggerProvider { + + public: + IFormattedLoggerProvider(); + + inline void PrintLogE(const char* eventName, + const std::vector>& keyValues, + const char* eventValue) override { + if ( !IsLogLevelEnabled(LOG_LEVEL_ERROR) ) { return; } + FormatAndLog(LOG_LEVEL_ERROR, eventName, keyValues, eventValue); + } + inline void PrintLogW(const char* eventName, + const std::vector>& keyValues, + const char* eventValue) override { + if (!IsLogLevelEnabled(LOG_LEVEL_WARN)) {return;} + FormatAndLog(LOG_LEVEL_WARN, eventName, keyValues, eventValue); + }; + inline void PrintEvent(const char* eventName, + const std::vector>& keyValues, + const char* eventValue) override { + if (!IsLogLevelEnabled(LOG_LEVEL_EVENT)) {return;} + FormatAndLog(LOG_LEVEL_EVENT, eventName, keyValues, eventValue); + }; + inline void PrintLogI(const char* channelName, + const char* eventName, + const std::vector>& keyValues, + const char* eventValue) override { + if (!IsLogLevelEnabled(LOG_LEVEL_INFO)) {return;} + FormatAndLogChanneled(LOG_LEVEL_INFO, channelName, eventName, keyValues, eventValue); + }; + inline void PrintLogD(const char* channelName, + const char* eventName, + const std::vector>& keyValues, + const char* eventValue) override { + if (!IsLogLevelEnabled(LOG_LEVEL_DEBUG)) {return;} + FormatAndLogChanneled(LOG_LEVEL_DEBUG, channelName, eventName, keyValues, eventValue); + } + + // sets the minimum log level that is enabled. Levels above this one will also be enabled + void SetMinLogLevel(LogLevel logLevel); + // sets whether one specific log level is enabled + void SetLogLevelEnabled(LogLevel logLevel, bool enabled); + + // reads which levels are enabled from json file + void ParseLogLevelSettings(const Json::Value& config); + + // This has to be public for MultiFormattedLoggerProvider to work. + virtual void Log(ILoggerProvider::LogLevel logLevel, const std::string& message) = 0; + + private: + + // returns true if the given log level is enabled, false otherwise + bool IsLogLevelEnabled(LogLevel logLevel) const; + + void FormatAndLog(ILoggerProvider::LogLevel logLevel, const char* eventName, + const std::vector>& keyValues, + const char* eventValue); + void FormatAndLogChanneled(ILoggerProvider::LogLevel logLevel, const char* channel, + const char* eventName, + const std::vector>& keyValues, + const char* eventValue); + + // whether specific log levels are enabled + std::vector _logLevelEnabledFlags; + }; + + } // end namespace Util +} // end namespace Anki + + +#endif //__Util_Logging_IFormattedLoggerProvider_H_ diff --git a/vector-cloud/internal/log/util/logging/logging/iLoggerProvider.cpp b/vector-cloud/internal/log/util/logging/logging/iLoggerProvider.cpp new file mode 100644 index 0000000..4342586 --- /dev/null +++ b/vector-cloud/internal/log/util/logging/logging/iLoggerProvider.cpp @@ -0,0 +1,53 @@ +/** +* File: iLoggerProvider.cpp +* +* Author: raul +* Created: 06/30/16 +* +* Description: interface for anki log +* +* Copyright: Anki, Inc. 2014 +* +**/ +#include "iLoggerProvider.h" + +#include "logging.h" + +namespace Anki { +namespace Util { + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void ILoggerProvider::SetFilter(const std::shared_ptr& infoFilter) +{ + _infoFilter = infoFilter; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void ILoggerProvider::PrintChanneledLogI(const char* channel, + const char* eventName, + const std::vector>& keyValues, + const char* eventValue) +{ + // if no filter is set or the channel is enabled + if ( !_infoFilter || _infoFilter->IsChannelEnabled(channel) ) + { + // pass to subclass + PrintLogI(channel, eventName, keyValues, eventValue); + } +} + +void ILoggerProvider::PrintChanneledLogD(const char* channel, + const char* eventName, + const std::vector>& keyValues, + const char* eventValue) +{ + // if no filter is set or the channel is enabled + if ( !_infoFilter || _infoFilter->IsChannelEnabled(channel) ) + { + // pass to subclass + PrintLogD(channel, eventName, keyValues, eventValue); + } +} + +} // namespace Util +} // namespace Anki diff --git a/vector-cloud/internal/log/util/logging/logging/iLoggerProvider.h b/vector-cloud/internal/log/util/logging/logging/iLoggerProvider.h new file mode 100644 index 0000000..4ee9d50 --- /dev/null +++ b/vector-cloud/internal/log/util/logging/logging/iLoggerProvider.h @@ -0,0 +1,101 @@ +/** +* File: iLoggerProvider.h +* +* Author: damjan +* Created: 4/21/2015 +* +* Description: Interface for classes that provide a different method of logging (console, network, file, ..) +* +* Copyright: Anki, Inc. 2014 +* +**/ + +#ifndef __Util_Logging_ILoggerProvider_H__ +#define __Util_Logging_ILoggerProvider_H__ + +#include "iChannelFilter.h" +#include "logtypes.h" + +#include +#include + +namespace Anki { +namespace Util { + +class ILoggerProvider { +public: + using LogLevel = Anki::Util::LogLevel; + + // constructor/destructor + ILoggerProvider() = default; + virtual ~ILoggerProvider() = default; + + // set filter after creation. Note this provider will keep a shared_ptr to the filter + void SetFilter(const std::shared_ptr& infoFilter); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Unfiltered logs + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Channel filters don't apply to these levels + + virtual void PrintEvent(const char* eventName, + const std::vector>& keyValues, + const char* eventValue) = 0; + virtual void PrintLogE(const char* eventName, + const std::vector>& keyValues, + const char* eventValue) = 0; + virtual void PrintLogW(const char* eventName, + const std::vector>& keyValues, + const char* eventValue) = 0; + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Filtered logs + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Channel filters apply to these levels. This method will filter messages and delegate to subclasses through + // protected API the messages that pass the filter + + // Delegates on PrintLogI to print infos that pass channel filtering + void PrintChanneledLogI(const char* channel, + const char* eventName, + const std::vector>& keyValues, + const char* eventValue); + + void PrintChanneledLogD(const char* channel, + const char* eventName, + const std::vector>& keyValues, + const char* eventValue); + + // Perform synchronous flush to underlying storage + virtual void Flush() {}; + +protected: + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Internal API + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Required in subclasses to print messages that pass their filter + + virtual void PrintLogI(const char* channel, + const char* eventName, + const std::vector>& keyValues, + const char* eventValue) = 0; + + virtual void PrintLogD(const char* channel, + const char* eventName, + const std::vector>& keyValues, + const char* eventValue) = 0; + +private: + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Attributes + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + // pointer to the filter to apply to infos (if any) + std::shared_ptr _infoFilter; +}; + +} // namespace Util +} // namespace Anki + +#endif // __Util_Logging_ILoggerProvider_H__ diff --git a/vector-cloud/internal/log/util/logging/logging/iTickTimeProvider.h b/vector-cloud/internal/log/util/logging/logging/iTickTimeProvider.h new file mode 100644 index 0000000..b72c54b --- /dev/null +++ b/vector-cloud/internal/log/util/logging/logging/iTickTimeProvider.h @@ -0,0 +1,35 @@ +/** +* File: tickTimeProvider +* +* Author: damjan +* Created: 4/21/2015 +* +* Description: interfaces for logging class to obtain the current tick time +* +* Copyright: Anki, Inc. 2014 +* +**/ + + +#ifndef __Util_Logging_TickTimeProvider_H__ +#define __Util_Logging_TickTimeProvider_H__ + + +#import + +namespace Anki{ +namespace Util { + +class ITickTimeProvider { +public: + virtual ~ITickTimeProvider() {}; + virtual const size_t GetTickCount() const = 0; +}; + +} // namespace Util +} // namespace Anki + + + +#endif // __Util_Logging_TickTimeProvider_H__ + diff --git a/vector-cloud/internal/log/util/logging/logging/logging.cpp b/vector-cloud/internal/log/util/logging/logging/logging.cpp new file mode 100644 index 0000000..61b3573 --- /dev/null +++ b/vector-cloud/internal/log/util/logging/logging/logging.cpp @@ -0,0 +1,692 @@ +/** + * File: util/logging/logging.cpp + * + * Author: damjan + * Created: 4/3/2014 + * + * Description: logging functions. + * structure of the function names is: s